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

2023-12-30 02:20

本文主要是介绍【电商项目实战】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自定义注解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/551413

相关文章

网页解析 lxml 库--实战

lxml库使用流程 lxml 是 Python 的第三方解析库,完全使用 Python 语言编写,它对 XPath表达式提供了良好的支 持,因此能够了高效地解析 HTML/XML 文档。本节讲解如何通过 lxml 库解析 HTML 文档。 pip install lxml lxm| 库提供了一个 etree 模块,该模块专门用来解析 HTML/XML 文档,下面来介绍一下 lxml 库

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

这15个Vue指令,让你的项目开发爽到爆

1. V-Hotkey 仓库地址: github.com/Dafrok/v-ho… Demo: 戳这里 https://dafrok.github.io/v-hotkey 安装: npm install --save v-hotkey 这个指令可以给组件绑定一个或多个快捷键。你想要通过按下 Escape 键后隐藏某个组件,按住 Control 和回车键再显示它吗?小菜一碟: <template

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

如何用Docker运行Django项目

本章教程,介绍如何用Docker创建一个Django,并运行能够访问。 一、拉取镜像 这里我们使用python3.11版本的docker镜像 docker pull python:3.11 二、运行容器 这里我们将容器内部的8080端口,映射到宿主机的80端口上。 docker run -itd --name python311 -p

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

C#实战|大乐透选号器[6]:实现实时显示已选择的红蓝球数量

哈喽,你好啊,我是雷工。 关于大乐透选号器在前面已经记录了5篇笔记,这是第6篇; 接下来实现实时显示当前选中红球数量,蓝球数量; 以下为练习笔记。 01 效果演示 当选择和取消选择红球或蓝球时,在对应的位置显示实时已选择的红球、蓝球的数量; 02 标签名称 分别设置Label标签名称为:lblRedCount、lblBlueCount

【测试】输入正确用户名和密码,点击登录没有响应的可能性原因

目录 一、前端问题 1. 界面交互问题 2. 输入数据校验问题 二、网络问题 1. 网络连接中断 2. 代理设置问题 三、后端问题 1. 服务器故障 2. 数据库问题 3. 权限问题: 四、其他问题 1. 缓存问题 2. 第三方服务问题 3. 配置问题 一、前端问题 1. 界面交互问题 登录按钮的点击事件未正确绑定,导致点击后无法触发登录操作。 页面可能存在

在cscode中通过maven创建java项目

在cscode中创建java项目 可以通过博客完成maven的导入 建立maven项目 使用快捷键 Ctrl + Shift + P 建立一个 Maven 项目 1 Ctrl + Shift + P 打开输入框2 输入 "> java create"3 选择 maven4 选择 No Archetype5 输入 域名6 输入项目名称7 建立一个文件目录存放项目,文件名一般为项目名8 确定

滚雪球学Java(87):Java事务处理:JDBC的ACID属性与实战技巧!真有两下子!

咦咦咦,各位小可爱,我是你们的好伙伴——bug菌,今天又来给大家普及Java SE啦,别躲起来啊,听我讲干货还不快点赞,赞多了我就有动力讲得更嗨啦!所以呀,养成先点赞后阅读的好习惯,别被干货淹没了哦~ 🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,助你一臂之力,带你早日登顶🚀,欢迎大家关注&&收藏!持续更新中,up!up!up!! 环境说明:Windows 10