当前位置: 首页 > news >正文

【电商项目实战】MD5登录加密及JSR303自定义注解

🎉🎉欢迎来到我的CSDN主页!🎉🎉

🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚

🌟推荐给大家我的专栏《电商项目实战》。🎯🎯

👉点击这里,就可以查看我的主页啦!👇👇

Java方文山的个人主页

🎁如果感觉还不错的话请给我点赞吧!🎁🎁

💖期待你的加入,一起学习,一起进步!💖💖

请添加图片描述

目录

一、登录功能实现

1.数据接收类

2.数据响应类

3.功能实现

二、全局异常抓捕

1.自定义异常

2.全局异常抓捕

 三、JSR303验证

 1.JSR303验证

 2.自定义JSR303注解

四、MD5加密登录

​ 1.前端加密

 2.后端加密

五、用户数据存储

 1.用户信息存储Redis

 2.Redis存储代码优化

 3.前端顶部信息显示


一、登录功能实现

1.数据接收类

我们在做登录功能的时候肯定需要接收前端传递到后端的值,但是我们直接用已有的实体会污染这个类(如果还需要做验证之类的话)所以我们先编写一个Vo类进行参数接收。

UserVo

@Data
public class UserVo {private String phone;//用户电话private String password;//用户密码}

 2.数据响应类

 我们后端向前端响应数据最好统一格式,所以这里就有两个类响应类和响应枚举类JsonResponseBody

package com.csdn.shop.resp;import lombok.Data;@Data
public class JsonResponseBody<T> {private Integer code;private String msg;private T data;private Long total;private JsonResponseBody(com.star.easyshop.resp.JsonResponseStatus jsonResponseStatus, T data) {this.code = jsonResponseStatus.getCode();this.msg = jsonResponseStatus.getMsg();this.data = data;}private JsonResponseBody(com.star.easyshop.resp.JsonResponseStatus jsonResponseStatus, T data, Long total) {this.code = jsonResponseStatus.getCode();this.msg = jsonResponseStatus.getMsg();this.data = data;this.total = total;}public static <T> JsonResponseBody<T> success() {return new JsonResponseBody<T>(com.star.easyshop.resp.JsonResponseStatus.OK, null);}public static <T> JsonResponseBody<T> success(T data) {return new JsonResponseBody<T>(com.star.easyshop.resp.JsonResponseStatus.OK, data);}public static <T> JsonResponseBody<T> success(T data, Long total) {return new JsonResponseBody<T>(com.star.easyshop.resp.JsonResponseStatus.OK, data, total);}public static <T> JsonResponseBody<T> unknown() {return new JsonResponseBody<T>(com.star.easyshop.resp.JsonResponseStatus.UN_KNOWN, null);}public static <T> JsonResponseBody<T> other(com.star.easyshop.resp.JsonResponseStatus jsonResponseStatus) {return new JsonResponseBody<T>(jsonResponseStatus, null);}}

JsonResponseStatus

package com.csdn.shop.resp;import lombok.Getter;@Getter
public enum JsonResponseStatus {OK(200, "OK"),UN_KNOWN(500, "未知错误"),LOGIN_MOBILE_INFO(5001, "未携带手机号或手机号格式有误"),LOGIN_PASSWORD_INFO(5002, "未携带密码或不满足格式"),LOGIN_NO_EQUALS(5003, "登录信息不一致"),LOGIN_MOBILE_NOT_FOUND(5004, "登录手机号未找到"),;private final Integer code;private final String msg;JsonResponseStatus(Integer code, String msg) {this.code = code;this.msg = msg;}public String getName(){return this.name();}}

3.功能实现

这里就直接展示controller和servicelmpl的代码,接口的定义我相信大家也都会写。

 controller

@Controller
@RequestMapping("/user")
public class UserController {@Autowiredprivate IUserService userService;@ResponseBody@RequestMapping("/login")public JsonResponseBody<?> login(UserVo vo){return userService.login(vo);}}

UserServiceImpl


@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate UserMapper userMapper;@Overridepublic JsonResponseBody<?> login(UserVo vo) {//手机号为空if (vo.getPhone()==null || vo.getPhone().trim().length()==0){return  JsonResponseBody.other(JsonResponseStatus.LOGIN_MOBILE_INFO);}//密码为空if (vo.getPassword()==null || vo.getPassword().trim().length()==0){return  JsonResponseBody.other(JsonResponseStatus.LOGIN_PASSWORD_INFO);}//根据手机号和密码查询User one = getOne(new QueryWrapper<User>().lambda().eq(User::getId, vo.getPhone()).eq(User::getPassword, vo.getPassword()), false);//用户未找到if (one==null){return  JsonResponseBody.other(JsonResponseStatus.LOGIN_NO_EQUALS);}return  JsonResponseBody.other(JsonResponseStatus.OK);}
}

二、全局异常抓捕

1.自定义异常

全局异常捕获可以用于收集应用程序中发生的异常信息,并进行监控和分析。通过记录异常日志、统计异常发生的频率和类型等,可以帮助开发人员及时发现潜在的问题,并进行修复和优化。

如果直接在刚刚我们所写的代码中添加日志记录,会比较麻烦,我们将他变成一个异常然后被异常抓捕后进行记录就大大减少了我们的代码量。

BusinessException

@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class BusinessException extends RuntimeException {private JsonResponseStatus jsonResponseStatus;}

自定义异常类并提供了一些常用的方法和构造函数,方便在业务逻辑中抛出和处理异常。异常类中的 JsonResponseStatus 用于封装业务异常的相关状态信息,方便异常处理器进行处理和返回给前端。

2.全局异常抓捕

首先修改UserServiceImpl的代码,全部改为抛异常的方式

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate UserMapper userMapper;@Overridepublic JsonResponseBody<?> login(UserVo vo) {//手机号为空if (vo.getPhone()==null || vo.getPhone().trim().length()==0){throw new BusinessException(JsonResponseStatus.LOGIN_MOBILE_INFO);}//密码为空if (vo.getPassword()==null || vo.getPassword().trim().length()==0){throw new BusinessException(JsonResponseStatus.LOGIN_PASSWORD_INFO);}//根据手机号和密码查询User one = getOne(new QueryWrapper<User>().lambda().eq(User::getId,     vo.getPhone()).eq(User::getPassword, vo.getPassword()), false);//用户未找到if (one==null){throw new BusinessException(JsonResponseStatus.LOGIN_NO_EQUALS);}return  JsonResponseBody.other(JsonResponseStatus.OK);}
}

编写一个全局异常抓捕类,进行异常抓捕,该类是一个增强controller,会将原本的异常拦截在此

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {//业务异常抓捕@ExceptionHandler(BusinessException.class)public JsonResponseBody<?> exceptionBusinessException(BusinessException e) {JsonResponseStatus status = e.getJsonResponseStatus();//日志记录log.info(status.getMsg());return JsonResponseBody.other(status);}//未知异常抓捕@ExceptionHandler(Throwable.class)public JsonResponseBody<?> exceptionThrowable(Throwable e) {//日志记录log.info(e.getMessage());return JsonResponseBody.other(JsonResponseStatus.UN_KNOWN);}}

测试一下,查看效果

 三、JSR303验证

1.JSR303验证

在刚刚的那种方式中太过于简单,而且还没有进行手机号正则判断、密码正则判断等,如果我们需要判断的属性非常多if标签就需要写很多,非常的不方便,所以需要用到JSR303进行验证

JSR303是依赖所以首先需要在pom文件中添加依赖

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>

 JSR303基本校验规则

注解作用类型解释
@NotNull任何类型属性不能为null
@NotEmpty集合集合不能为null,且size大于0
@NotBlanck字符串、字符字符类不能为null,且去掉空格之后长度大于0
@AssertTrueBoolean、boolean布尔属性必须是true
@Min数字类型(原子和包装)限定数字的最小值(整型)
@Max同@Min限定数字的最大值(整型)
@DecimalMin同@Min限定数字的最小值(字符串,可以是小数)
@DecimalMax同@Min限定数字的最大值(字符串,可以是小数)
@Range数字类型(原子和包装)限定数字范围(长整型)
@Length字符串限定字符串长度
@Size集合限定集合大小
@Past时间、日期必须是一个过去的时间或日期
@Future时期、时间必须是一个未来的时间或日期
@Email字符串必须是一个邮箱格式
@Pattern字符串、字符正则匹配字符串

在我们的Vo类加上注解

@Data
public class UserVo {@NotBlankprivate String phone;//用户电话@NotBlankprivate String password;//用户密码}

在controller的方法上开启检查加上注解@Valid 或者 @Validated 进行参数校验的时候,如果不加BindingResult那么会抛出异常

既然这里会抛出异常,那么我们继续往全局异常抓捕类里面加一个异常抓捕方法

     //绑定异常抓捕@ExceptionHandler(BindException.class)public JsonResponseBody<?> exceptionThrowable(BindException e) {//日志记录log.info(e.getMessage());return JsonResponseBody.other(JsonResponseStatus.LOGIN_NO_EQUALS);}

2.自定义JSR303注解

刚刚我们的JSR303虽然拦截非空字段但是没有判断手机号是否正常等,所以我们现在写一个自定义JSR303注解类进行正则判断

package com.csdn.shop.core;import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;/*** @author Java方文山* @compay csdn_Java方文山* @create 2023-12-29-19:03*/
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {MatchExprConstraintValidator.class}
)
public @interface BooleanExpr {String message() default "{javax.validation.constraints.NotBlank.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};boolean require () default  false;//判断这个字段是否是必填String expr () default "" ;//正则规则}

 这里的expr也就是正则判断是通过用户定义的,大大提升了复用性。

@Data
public class UserVo {@BooleanExpr(require = true,expr = "(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}")private String phone;//用户电话@BooleanExpr(require = true ,expr ="[a-zA-Z0-9]{32}")private String password;//用户密码}

我们还需要 自定义的验证器,用于验证字符串是否满足指定的正则表达式规则。

package com.csdn.shop.core;import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.Data;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;@Data
class MatchExprConstraintValidator implements ConstraintValidator<BooleanExpr, String> {private boolean require;private String expr;@Overridepublic void initialize(BooleanExpr matchExpr) {expr = matchExpr.expr();require = matchExpr.require();}@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {if (!require) return true;if (StringUtils.isEmpty(value)) return false;return value.matches(expr);}}

在这个类中, ConstraintValidator<BooleanExpr, String> 表示该验证器的泛型参数,其中 BooleanExpr 是一个自定义注解,String 则是要验证的目标类型。

该类实现了 ConstraintValidator 接口,并重写了 initialize isValid 方法。initialize 方法用于初始化验证器,从注解中获取正则表达式和是否必须的信息。isValid 方法用于实际的验证逻辑,根据是否必须和目标字符串是否为空来判断是否满足正则表达式规则。

现在我们登录错误的电话验证一下

四、MD5加密登录

首先看一下登录页面

<!DOCTYPE html>
<html><head><#include "common/head.html"><link rel="stylesheet" type="text/css" href="css/public.css"/><link rel="stylesheet" type="text/css" href="css/login.css"/><script type="text/javascript" src="js/jquery-1.12.4.min.js"></script></head><body><!-------------------login--------------------------><div class="login"><form action="${ctx}/user/login" method="post"><h1><a href="${ctx}/"><img src="img/temp/logo.png"></a></h1><p></p><div class="msg-warn hide"><b></b>公共场所不建议自动登录,以防账号丢失</div><p><input style="font-size:14px;" type="text" id="phone" value="" placeholder="昵称/邮箱/手机号"></p><p><input style="font-size:14px;" type="password" id="password" value="" placeholder="密码"></p><p><input type="button" id="login" value="登  录"></p><p class="txt"><a class="" href="${ctx}/page/reg.html">免费注册</a><a href="${ctx}/page/forget.html">忘记密码?</a></p></form></div></body>
</html><script>//登陆点击事件$("#login").click(()=>{//获取手机号和密码的值let phone=$("#phone").val()let password=$("#password").val()$.post('${springMacroRequestContext.contextPath}/user/login',{phone,password},resp=>{},"json")})
</script>

这么发送登录请求,密码暴露的风险太大了

 1.前端加密

首先引入md5的依赖

<script src="http://www.gongjuji.net/Content/files/jquery.md5.js" type="text/javascript"></script>

对password进行加密

<script>//登陆点击事件$("#login").click(()=>{//获取手机号和密码的值let phone=$("#phone").val()let password=$("#password").val()//加密的同时加saltpassword=$.md5("csdn"+password+"xw"+password)$.post('${springMacroRequestContext.contextPath}/user/login',{phone,password},resp=>{},"json")})
</script>

这时候同样的密码123显示的就不一样了

 2.后端加密

在后端也进行加密双重保证,这里提供一个MD5工具类方便大家使用

package com.csdn.shop.utils;import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;import java.nio.charset.StandardCharsets;
import java.util.UUID;@Component
public class MD5Utils {//加密盐,与前端一致private static final String salt = "f1g2h3j4";public static String md5(String src) {return DigestUtils.md5DigestAsHex(src.getBytes(StandardCharsets.UTF_8));}public static String createSalt() {return UUID.randomUUID().toString().replace("-", "");}/*** 将前端的明文密码通过MD5加密方式加密成后端服务所需密码,混淆固定盐salt,安全性更可靠*/public static String inputPassToFormPass(String inputPass) {String str = salt.charAt(1) + String.valueOf(salt.charAt(5)) + inputPass + salt.charAt(0) + salt.charAt(3);return md5(str);}/*** 将后端密文密码+随机salt生成数据库的密码,混淆固定盐salt,安全性更可靠*/public static String formPassToDbPass(String formPass, String salt) {String str = salt.charAt(7) + String.valueOf(salt.charAt(9)) + formPass + salt.charAt(1) + salt.charAt(5);return md5(str);}public static void main(String[] args) {String formPass = inputPassToFormPass("123456");System.out.println("前端加密密码:" + formPass);String salt = createSalt();System.out.println("后端加密随机盐:" + salt);String dbPass = formPassToDbPass(formPass, salt);System.out.println("后端加密密码:" + dbPass);}}

 首先需要将我们数据库的加密密码和salt修改成我们目前的规则

  @Overridepublic JsonResponseBody<?> login(UserVo vo) {//将前端的密码进行加密String pwd=vo.getPassword();long time = System.currentTimeMillis();//进行加秘密(salt就是时间戳)DigestUtils.md5DigestAsHex((pwd+time).getBytes(StandardCharsets.UTF_8));//根据手机号和密码查询User one = getOne(new QueryWrapper<User>().lambda().eq(User::getId, vo.getPhone()).eq(User::getPassword, vo.getPassword()), false);//用户未找到if (one==null){throw new BusinessException(JsonResponseStatus.LOGIN_NO_EQUALS);}return  JsonResponseBody.other(JsonResponseStatus.OK);}

登录测试一下

 将我们的salt和加密后的密码赋值给数据库的用户(注意这个操作就类似于注册,如果不将数据库的密码和salt进行替换待会登录的时候,我们的这种加密方式是获取不到真正的密码的)

修改登录的方法

    @Overridepublic JsonResponseBody<?> login(UserVo vo) {//根据手机号和密码查询User one = getOne(new QueryWrapper<User>().lambda().eq(User::getId, vo.getPhone()), false);//用户未找到if (one==null){throw new BusinessException(JsonResponseStatus.LOGIN_NO_EQUALS);}//前端的密码 + 数据库的盐String str=vo.getPassword()+one.getSalt();//进行加密str= DigestUtils.md5DigestAsHex(str.getBytes(StandardCharsets.UTF_8));//将加密后的密码和数据库的原密码进行比较if(!str.equals(one.getPassword())){throw new BusinessException(JsonResponseStatus.LOGIN_PASSWORD_NOT_FOUND);}return  JsonResponseBody.other(JsonResponseStatus.OK);}
}

 现在就是先根据电话号查询用户,将用户的salt取出来后和前端的密码进行加密,如果加密后的密码和数据库的密码一致就说明登录成功,如果salt不一样,或者前端密码不一样都不会和数据库的密码一致。

随后在前端做好返回值的判断即可

五、用户数据存储

我们用户进行登录后,肯定需要将用户信息进行存储的但是存在session会加大服务器的资源,所以我们要将登录成功的用户信息存储在redis缓存中去。

1.用户信息存储Redis

pom依赖引入

<!-- redis:缓存数据库 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><version>2.5.6</version>
</dependency>
<!-- commons-pool2:实现对象池化的框架,没有的话启动时redis报错 -->
<dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId><version>2.11.1</version>
</dependency>

yml配置文件

#redis
redis:# redis数据库索引(默认为0)database: 0# redis服务器地址(默认为localhost)host: 182.92.153.8# redis端口(默认为6379)port: 6379# redis访问密码(默认为空)password:# redis连接超时时间(单位毫秒):不能设置为0,否则报无法连接timeout: 6000ms# redis高级客户端lettuce:pool:# 最大可用连接数(默认为8,负数表示无限)max-active: 8# 最大空闲连接数(默认为8,负数表示无限)max-idle: 8# 最小空闲连接数(默认为0,该值只有为正数才有用)min-idle: 0# 从连接池中获取连接最大等待时间(默认为-1,单位为毫秒,负数表示无限)max-wait: -1

将用户信息存储于redis,并且将redis的键保存到前端cookie,我们首先需要在controller接收 HttpServletRequest , HttpServletResponse两个对象

   @ResponseBody@RequestMapping("/login")public JsonResponseBody<?> login(@Validated UserVo vo, HttpServletRequest request, HttpServletResponse response){return userService.login(vo,request,response);}

 serviceimpl代码

这里我们的键最好是唯一的,什么是唯一的?雪花ID,所以将雪花ID的依赖加入进来生成我们的token即可,在redis中的键就是"user:+token"

         <dependency><groupId>com.github.yitter</groupId><artifactId>yitter-idgenerator</artifactId><version>1.0.6</version></dependency><!-- 雪花ID -->

我们的前端也需要保存用户的token方便前端向后端发送请求,这里用到cookie的一个工具类

package com.csdn.shop.utils;import lombok.extern.slf4j.Slf4j;import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;@Slf4j
public class CookieUtils {/*** @Description: 得到Cookie的值, 不编码*/public static String getCookieValue(HttpServletRequest request, String cookieName) {return getCookieValue(request, cookieName, false);}/*** @Description: 得到Cookie的值*/public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {if (isDecoder) {retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8");} else {retValue = cookieList[i].getValue();}break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/*** @Description: 得到Cookie的值*/public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) {Cookie[] cookieList = request.getCookies();if (cookieList == null || cookieName == null) {return null;}String retValue = null;try {for (int i = 0; i < cookieList.length; i++) {if (cookieList[i].getName().equals(cookieName)) {retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString);break;}}} catch (UnsupportedEncodingException e) {e.printStackTrace();}return retValue;}/*** @Description: 设置Cookie的值 不设置生效时间默认浏览器关闭即失效,也不编码*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue) {setCookie(request, response, cookieName, cookieValue, -1);}/*** @param request* @param response* @param cookieName* @param cookieValue* @param cookieMaxage* @Description: 设置Cookie的值 在指定时间内生效,但不编码*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage) {setCookie(request, response, cookieName, cookieValue, cookieMaxage, false);}/*** @Description: 设置Cookie的值 不设置生效时间,但编码* 在服务器被创建,返回给客户端,并且保存客户端* 如果设置了SETMAXAGE(int seconds),会把cookie保存在客户端的硬盘中* 如果没有设置,会默认把cookie保存在浏览器的内存中* 一旦设置setPath():只能通过设置的路径才能获取到当前的cookie信息*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, boolean isEncode) {setCookie(request, response, cookieName, cookieValue, -1, isEncode);}/*** @Description: 设置Cookie的值 在指定时间内生效, 编码参数*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage, boolean isEncode) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode);}/*** @Description: 设置Cookie的值 在指定时间内生效, 编码参数(指定编码)*/public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName,String cookieValue, int cookieMaxage, String encodeString) {doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString);}/*** @Description: 删除Cookie带cookie域名*/public static void deleteCookie(HttpServletRequest request, HttpServletResponse response,String cookieName) {doSetCookie(request, response, cookieName, null, -1, false);}/*** @Description: 设置Cookie的值,并使其在指定时间内生效*/private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response,String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) {try {if (cookieValue == null) {cookieValue = "";} else if (isEncode) {cookieValue = URLEncoder.encode(cookieValue, "utf-8");}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0)cookie.setMaxAge(cookieMaxage);if (null != request) {// 设置域名的cookieString domainName = getDomainName(request);log.info("========== domainName: {} ==========", domainName);if (!"localhost".equals(domainName)) {cookie.setDomain(domainName);}}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}/*** @Description: 设置Cookie的值,并使其在指定时间内生效*/private static void doSetCookie(HttpServletRequest request, HttpServletResponse response,String cookieName, String cookieValue, int cookieMaxage, String encodeString) {try {if (cookieValue == null) {cookieValue = "";} else {cookieValue = URLEncoder.encode(cookieValue, encodeString);}Cookie cookie = new Cookie(cookieName, cookieValue);if (cookieMaxage > 0)cookie.setMaxAge(cookieMaxage);if (null != request) {// 设置域名的cookieString domainName = getDomainName(request);log.info("========== domainName: {} ==========", domainName);if (!"localhost".equals(domainName)) {cookie.setDomain(domainName);}}cookie.setPath("/");response.addCookie(cookie);} catch (Exception e) {e.printStackTrace();}}/*** @Description: 得到cookie的域名*/private static String getDomainName(HttpServletRequest request) {String domainName = null;String serverName = request.getRequestURL().toString();if (serverName == null || serverName.equals("")) {domainName = "";} else {serverName = serverName.toLowerCase();serverName = serverName.substring(7);final int end = serverName.indexOf("/");serverName = serverName.substring(0, end);if (serverName.indexOf(":") > 0) {String[] ary = serverName.split("\\:");serverName = ary[0];}final String[] domains = serverName.split("\\.");int len = domains.length;if (len > 3 && !isIp(serverName)) {// www.xxx.com.cndomainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1];} else if (len <= 3 && len > 1) {// xxx.com or xxx.cndomainName = "." + domains[len - 2] + "." + domains[len - 1];} else {domainName = serverName;}}return domainName;}public static String trimSpaces(String IP) {//去掉IP字符串前后所有的空格while (IP.startsWith(" ")) {IP = IP.substring(1, IP.length()).trim();}while (IP.endsWith(" ")) {IP = IP.substring(0, IP.length() - 1).trim();}return IP;}public static boolean isIp(String IP) {//判断是否是一个IPboolean b = false;IP = trimSpaces(IP);if (IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")) {String s[] = IP.split("\\.");if (Integer.parseInt(s[0]) < 255)if (Integer.parseInt(s[1]) < 255)if (Integer.parseInt(s[2]) < 255)if (Integer.parseInt(s[3]) < 255)b = true;}return b;}}

 修改service中的代码

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic JsonResponseBody<?> login(UserVo vo, HttpServletRequest request, HttpServletResponse response) {//将前端的密码进行加密String pwd=vo.getPassword();//根据手机号和密码查询User one = getOne(new QueryWrapper<User>().lambda().eq(User::getId, vo.getPhone()), false);//用户未找到if (one==null){throw new BusinessException(JsonResponseStatus.LOGIN_NO_EQUALS);}//前端的密码 + 数据库的盐String str=vo.getPassword()+one.getSalt();//进行加密str= DigestUtils.md5DigestAsHex(str.getBytes(StandardCharsets.UTF_8));//将加密后的密码和数据库的原密码进行比较if(!str.equals(one.getPassword())){throw new BusinessException(JsonResponseStatus.LOGIN_PASSWORD_NOT_FOUND);}//因为每个用户都是唯一的所以用到唯一表示——雪花IDString token = YitIdHelper.nextId()+"";//将用户信息保存到RedisredisTemplate.opsForValue().set("user:"+token,one);//将token保存在cookie方便前端向后端取值CookieUtils.setCookie(request,response,"userToken",token,7200);return  JsonResponseBody.other(JsonResponseStatus.OK);}
}

这里还需要注意两个地方,我们的实体中的时间类型是LocalDateTime我们需要改成Date

 第二个就是我们的数据存储在redis的时候格式不美观所以借助一个工具类进行格式转换避免乱码

package com.csdn.shop.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;@Configuration
public class RedisConfig {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();redisTemplate.setKeySerializer(new StringRedisSerializer());redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setHashKeySerializer(new StringRedisSerializer());redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());redisTemplate.setConnectionFactory(connectionFactory);redisTemplate.afterPropertiesSet();return redisTemplate;}}

这时候进行登录测试一下

可以看到我们的token存储在了浏览器,用户信息也存储在了redis,下次前端想要获取用户信息就可以带着这个token去到redis进行查找。 

2.Redis存储代码优化

在我们刚刚的代码中用到的键都是手写的"user:"+token,但是这样的代码不好维护,所以我们将这个自变量变为常量(我们也可将上面的正则设置为常量)

public class Constants {public static final  String REDIS_USER_PREFIX="user:";public static final String EXPR_MOBILE = "(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}";public static final String EXPR_PASSWORD = "[a-zA-Z0-9]{32}";}

其次,我们操作Redis的代码可能在别的地方也需要用到,所以我们将其进行封装

Service

public interface IRedisService {// 添加用户数据void saveUser(String token,User user);// 根据token查询用户信息User loadUser(String token);}

ServiceImpl

@Service
public class IRedisServiceImpl implements IRedisService {@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic void saveUser(String token,User user) {//将用户信息保存到RedisredisTemplate.opsForValue().set(Constants.REDIS_USER_PREFIX +token,user);}@Overridepublic User loadUser(String token) {return (User) redisTemplate.opsForValue().get(Constants.REDIS_USER_PREFIX +token);}
}

 随后修改我们UserServiceImpl的代码即可,调用IRedisServiceImpl的方法

 3.前端顶部信息显示

  $(function(){let nickname=getCookie("nickname");if(null!=nickname&&''!=nickname&&undefined!=nickname) {//设置昵称$('#nickname').text("您好,"+nickname);//隐藏登录注册按钮$('p.fl>span:eq(1)').css("display","none");//显示昵称和退出按钮$('p.fl>span:eq(0)').css("display","block");}else{//隐藏昵称$('#nickname').text("");//显示登录注册按钮$('p.fl>span:eq(1)').css("display","block");//隐藏昵称和退出按钮$('p.fl>span:eq(0)').css("display","none");}

在前端做好有无用户登录的显示,并在后端做好相应数据的回显

登录前:

登录后:

请添加图片描述

到这里我的分享就结束了,欢迎到评论区探讨交流!!

💖如果觉得有用的话还请点个赞吧 💖

相关文章:

【电商项目实战】MD5登录加密及JSR303自定义注解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《电商项目实战》。&#x1f3af;&#x1f3af; &am…...

2014,TEVC,A competitive swarm optimizer for large scale optimization(CSO)

PSO 分析&#xff08;从而引入 CSO&#xff09; CSO (competitive swarm optimizer) 算法是在PSO (particle swarm optimization) 算法的基础上改进而来的。PSO算法是一种功能强大、应用广泛的群体智能算法&#xff0c;主要用来解决优化问题。PSO算法包含一个粒子群&#xff0…...

【机器学习】【线性回归】梯度下降

文章目录 [toc]数据集实际值估计值估计误差代价函数学习率参数更新Python实现导包数据预处理迭代过程数据可视化完整代码 线性拟合结果代价结果 个人主页&#xff1a;丷从心 系列专栏&#xff1a;机器学习 数据集 ( x ( i ) , y ( i ) ) , i 1 , 2 , ⋯ , m \left(x^{(i)} , …...

JMeter逻辑控制器之While控制器

JMeter逻辑控制器之While控制器 1. 背景2.目的3. 介绍4.While示例4.1 添加While控制器4.2 While控制器面板4.3 While控制器添加请求4.3 While控制器应用场景 1. 背景 存在一些使用场景&#xff0c;比如&#xff1a;某个请求必须等待上一个请求正确响应后才能开始执行。或者&…...

记录 Docker 外部访问的基本操作

目录 1. 启动 docker 时挂载本地目录2. 外部访问 docker 容器 (-p/-P)3. 无法连接 docker 内 SSH 解决方案 1. 启动 docker 时挂载本地目录 # 将本地 D:/SDK 目录 挂载到 容器里的 /mnt/host 目录中 # 注意&#xff1a;-v /d/SDK:/mnt/host/ 必须放到 IMAGE_ID 前面才行 # …...

【Android 13】使用Android Studio调试系统应用之Settings移植(六):BannerMessagePreference

文章目录 一、篇头二、系列文章2.1 Android 13 系列文章2.2 Android 9 系列文章2.3 Android 11 系列文章三、BannerMessagePreference的移植3.1 新的问题:找不到 R.dimen.settingslib_preferred_minimum_touch_target3.2 问题分析(一)3.2.1 资源定义的位置3.2.2 检查依赖3.2…...

Python 变量

打印输出内容 print(‘rumenle’) print(‘haode’) 缩进需要tab 注释将需要注释的部分开头用# 多行注释 1、用你也可以左键选中我们需要注释的代码&#xff0c;松开&#xff0c;按&#xff1a;Ctrl/&#xff0c;就完成相同效果注释 2、把要注释的内容放到三个引号对里面 …...

ComfyUI如何中文汉化

comfyui中文地址如下&#xff1a; https://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translationhttps://github.com/AIGODLIKE/AIGODLIKE-ComfyUI-Translation如何安装&#xff1f; 1. git安装 进入项目目录下的custom_nodes目录下&#xff0c;然后进入控制台&#xff0c;运…...

Glary Utilities Pro - 电脑系统优化全面指南:详尽使用教程

软件简介&#xff1a; Glary Utilities Pro 是一款全面的电脑优化工具&#xff0c;它旨在帮助用户提升计算机的性能和稳定性。这款软件提供了多种功能&#xff0c;包括系统清理、优化、修复以及保护。通过一键扫描&#xff0c;它可以识别并清除无用文件、临时数据、注册表错误等…...

1.4分页和排序

排序&#xff1a; -- 分页(limit)和排序(order by) -- 排序&#xff1a;升序ASC,降序DESC -- ORDER BY 通过字段排序&#xff0c;怎么排 -- 查询的结果根据成绩降序&#xff0c;升序 SELECT s.studentno,studentname,sub.subjectname,studentresult FROM student s RIGHT JO…...

Modbus转Profinet,不会编程也能用!轻松快上手!

Modbus转Profinet是一种用于工业自动化领域的通信协议转换器&#xff0c;可以将Modbus协议转换为Profinet协议&#xff0c;实现设备之间的数据交换与通信。这个工具的使用非常简单&#xff0c;即使没有编程经验的人也可以轻松上手。即使不会编程的人也可以轻松快速上手使用Modb…...

鸿蒙原生应用/元服务开发-Stage模型能力接口(十)下

ohos.app.form.FormExtensionAbility (FormExtensionAbility) 系统能力&#xff1a;SystemCapability.Ability.Form 示例 import FormExtensionAbility from ohos.app.form.FormExtensionAbility; import formBindingData from ohos.app.form.formBindingData; import formP…...

QT QPluginloader 加载失败,出现Unknown error 0x000000c1的问题

最近在学习Qt的插件开发&#xff0c;在加载插件时&#xff0c;一直失败&#xff0c;用如下代码加载并打印错误信息。 QDir dir("./testplugin.dll"); QPluginLoader pluginLoader(dir.absolutePath());//需要绝对路径 pluginLoader.load(); qDebug()<< "…...

众和策略:今年首次!A股罕见一幕

岁末&#xff0c;A股走出了不常见的行情。 这儿指的不单单是指数上涨。今天上午&#xff0c;A股逾3900只个股上涨&#xff0c;昨日逾4400只个股上涨&#xff0c;前天逾3700只个股上涨。据通达信数据显现&#xff0c;这种连续的普涨行情在本年还是头一次。 本年10月底&#xf…...

EasyExcel实现动态表头(注解实现)

要实现上述动态头&#xff0c;按每日统计&#xff0c;每月统计&#xff0c;每年统计。而时间是一直变化&#xff0c;所以我们需要表头也一直动态生成。 首先&#xff0c;我们需要定义所需要实体类 public class CountDayData {ExcelProperty(value "业务员姓名")p…...

什么是工厂方法模式,工厂方法模式解决了什么问题?

工厂方法模式是一种创建型设计模式&#xff0c;它定义了一个用于创建对象的接口&#xff0c;但将实际的实例化过程延迟到子类中。这样&#xff0c;客户端代码在不同的子类中实例化具体对象&#xff0c;而不是直接实例化具体类。工厂方法模式允许一个类的实例化延迟到其子类&…...

Flink 输出至 Elasticsearch

【1】引入pom.xml依赖 <dependency><groupId>org.apache.flink</groupId><artifactId>flink-connector-elasticsearch6_2.12</artifactId><version>1.10.0</version> </dependency>【2】ES6 Scala代码&#xff0c;自动导入的…...

web三层架构

目录 1.什么是三层架构 2.运用三层架构的目的 2.1规范代码 2.2解耦 2.3代码的复用和劳动成本的减少 3.各个层次的任务 3.1web层&#xff08;表现层) 3.2service 层(业务逻辑层) 3.3dao 持久层(数据访问层) 4.结合mybatis简单实例演示 1.什么是三层架构 三层架构就是把…...

智能优化算法应用:基于厨师算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于厨师算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于厨师算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.厨师算法4.实验参数设定5.算法结果6.参考文献7.MA…...

写在2023年末,软件测试面试题总结

大家好&#xff0c;最近有不少小伙伴在后台留言&#xff0c;得准备年后面试了&#xff0c;又不知道从何下手&#xff01;为了帮大家节约时间&#xff0c;特意准备了一份面试相关的资料&#xff0c;内容非常的全面&#xff0c;真的可以好好补一补&#xff0c;希望大家在都能拿到…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄

文&#xff5c;魏琳华 编&#xff5c;王一粟 一场大会&#xff0c;聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中&#xff0c;汇集了学界、创业公司和大厂等三方的热门选手&#xff0c;关于多模态的集中讨论达到了前所未有的热度。其中&#xff0c;…...

突破不可导策略的训练难题:零阶优化与强化学习的深度嵌合

强化学习&#xff08;Reinforcement Learning, RL&#xff09;是工业领域智能控制的重要方法。它的基本原理是将最优控制问题建模为马尔可夫决策过程&#xff0c;然后使用强化学习的Actor-Critic机制&#xff08;中文译作“知行互动”机制&#xff09;&#xff0c;逐步迭代求解…...

iPhone密码忘记了办?iPhoneUnlocker,iPhone解锁工具Aiseesoft iPhone Unlocker 高级注册版​分享

平时用 iPhone 的时候&#xff0c;难免会碰到解锁的麻烦事。比如密码忘了、人脸识别 / 指纹识别突然不灵&#xff0c;或者买了二手 iPhone 却被原来的 iCloud 账号锁住&#xff0c;这时候就需要靠谱的解锁工具来帮忙了。Aiseesoft iPhone Unlocker 就是专门解决这些问题的软件&…...

【磁盘】每天掌握一个Linux命令 - iostat

目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat&#xff08;I/O Statistics&#xff09;是Linux系统下用于监视系统输入输出设备和CPU使…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

华为OD机考-机房布局

import java.util.*;public class DemoTest5 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseSystem.out.println(solve(in.nextLine()));}}priv…...