【SpringBoot开发】之商城项目案例(实现登陆版)

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

🏅我是君易--鑨,一个在CSDN分享笔记的博主。📚📚

🌟推荐给大家我的博客专栏《SpringBoot开发之商城项目系列》。🎯🎯

🎁如果感觉还不错的话请给我关注加三连吧!🎁🎁


前言

        在上一期的博客分享中我们初步的将项目搭建起来了,简单的实现了首页的数据数据库绑定查询显示,今天给大家带来的是登陆的功能实现,以及对其登陆功能的完善。

一、登陆功能实现代码编写

1. 导入数据回显的响应封装类

        我们导入的是前面几期的博客中分享到的响应封装类,用户数据回显以及数据查询的状态。

 JsonResponseBody.java

package com.yx.yxshop.resp;import lombok.Data;@Data
public class JsonResponseBody<T> {private Integer code;private String msg;private T data;private Long total;private JsonResponseBody(JsonResponseStatus jsonResponseStatus, T data) {this.code = jsonResponseStatus.getCode();this.msg = jsonResponseStatus.getMsg();this.data = data;}private JsonResponseBody(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>(JsonResponseStatus.OK, null);}public static <T> JsonResponseBody<T> success(T data) {return new JsonResponseBody<T>(JsonResponseStatus.OK, data);}public static <T> JsonResponseBody<T> success(T data, Long total) {return new JsonResponseBody<T>(JsonResponseStatus.OK, data, total);}public static <T> JsonResponseBody<T> unknown() {return new JsonResponseBody<T>(JsonResponseStatus.UN_KNOWN, null);}public static <T> JsonResponseBody<T> other(JsonResponseStatus jsonResponseStatus) {return new JsonResponseBody<T>(jsonResponseStatus, null);}}

 JsonResponseStatus.java

package com.yx.yxshop.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();}}

2. 控制层编写请求

        在编写控制层的请求方法之前首先新建一个文件夹存放我们的vo视图类,vo实体类用于操作数据库的传参设置,避免改动对应表的实体对象类

UserVo.java
package com.yx.yxshop.vo;import lombok.Data;/*** com.yx.yxshop.vo** @author 君易--鑨* @site www.yangxin.com* @company 木易* @create 2023/12/28* 确保实体类的正确性,用于请求操作*/
@Data  //提供set/get方法
public class UserVo {//    定义所需的属性private String phone;//手机号码private String password;//用户密码}

        借助我们的JsonResponseBody响应封装了进行编写登陆请求以及登陆的方法、接口以及接口实现类

 IUserService.java
package com.yx.yxshop.service;import com.yx.yxshop.pojo.User;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yx.yxshop.resp.JsonResponseBody;
import com.yx.yxshop.vo.UserVo;/*** <p>* 用户信息表 服务类* </p>** @author yangxin* @since 2023-12-27*/
public interface IUserService extends IService<User> {JsonResponseBody<?> login(UserVo vo);}
UserServiceImpl.java 
package com.yx.yxshop.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yx.yxshop.pojo.User;
import com.yx.yxshop.mapper.UserMapper;
import com.yx.yxshop.resp.JsonResponseBody;
import com.yx.yxshop.resp.JsonResponseStatus;
import com.yx.yxshop.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yx.yxshop.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;/*** <p>* 用户信息表 服务实现类* </p>** @author yangxin* @since 2023-12-27*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic JsonResponseBody<?> login(UserVo vo) {//        对其带来的参数对象进行判断
//        电话号码if (vo.getPhone()==null){
//                  返回没有手机号码的异常状态return JsonResponseBody.other(JsonResponseStatus.LOGIN_MOBILE_INFO);}
//        密码if (vo.getPassword()==null){
//            返回没有密码的异常状态return JsonResponseBody.other(JsonResponseStatus.LOGIN_PASSWORD_INFO);}
//     从数据库中查询用户信息User one = getOne(new QueryWrapper<User>().lambda()                       //false表示有多条数据不报错.eq(User::getId, vo.getPhone()).eq(User::getPassword, vo.getPassword()), false);if (one==null){//为查询到指定数据return JsonResponseBody.other(JsonResponseStatus.LOGIN_NO_EQUALS);//返回登录信息不一致的}
//        登录成功   (查询到了指定的数据)return JsonResponseBody.success(one);}
}
 UserController.java
package com.yx.yxshop.controller;import com.yx.yxshop.pojo.User;
import com.yx.yxshop.resp.JsonResponseBody;
import com.yx.yxshop.service.IUserService;
import com.yx.yxshop.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** <p>* 用户信息表 前端控制器* </p>** @author yangxin* @since 2023-12-27*/
@Controller
@RequestMapping("/user")
public class UserController {// 引入用户接口类@Autowiredprivate IUserService userService;//    编写一个用户登陆的请求public JsonResponseBody<?> userLogin(UserVo vo){return userService.login(vo);}}

        代码编写到这里我们的登陆的基本功能已经基本的实现了,但是我们接下来会对其进行完善。

二、完善登陆之全局异常处理

        我们直接对其抛出异常,但是我们的系统也会对其抛出异常。因此我们新建一个包,用于管理我们的全局异常。

 
BusinessException.java
package com.yx.yxshop.exp;import com.yx.yxshop.resp.JsonResponseStatus;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;/*** 错误异常处理类*/
@EqualsAndHashCode(callSuper = true)
@AllArgsConstructor
@NoArgsConstructor
@Data
public class BusinessException extends RuntimeException {private JsonResponseStatus jsonResponseStatus;}

        全局异常处理类,用于处理全局的异常捕捉。

 GlobalExceptionHandler.java
package com.yx.yxshop.exp;import com.yx.yxshop.resp.JsonResponseBody;
import com.yx.yxshop.resp.JsonResponseStatus;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvice//增强类返回json数据
//@ControllerAdvice//增强类返回页面
@Slf4j
public class GlobalExceptionHandler {@ExceptionHandler(BusinessException.class)public JsonResponseBody<?> BusinessExceptionHandler(BusinessException e){
//        获取异常状态JsonResponseStatus status = e.getJsonResponseStatus();
//        返回json数据return JsonResponseBody.other(status);}//    捕捉所有的异常@ExceptionHandler(Throwable.class)public JsonResponseBody<?> BusinessExceptionHandler(Throwable e){
//        返回json数据return JsonResponseBody.unknown();}
}

        在接口实现类进行稍微的调整

         我们这里直接运行项目,然后直接访问登陆的请求

三、完善登陆之jsr303验证

        集成jsr303验证首先要导入对应的pom文件依赖

<!--   导入jsr303依赖  --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>

         集成了之后我们在实体类进行对应的编写修改,在属性上打上注释进行标记

         在控制类进行标记使用

         在全局异常类中添加一个方法进行捕捉异常

         我们在网页上直接进行发送登陆请求

四、完善登陆之密码加密

1. 前端进行加密

        首先在我们的登陆界面编写登陆的点击事件

<script>
<#--			设置登录的点击事件-->$("#login").click(()=>{// 获取到密码与手机号码let phone = $("#mobile").val();let password = $("#password").val();// 发送登陆的post请求$.post('${springMacroRequestContext.contextPath}/user/login',{// 传递参数phone,password},resq=>{//接受对象类型},"json")})</script>

        接下来我们进行加密,首先我们先导入我们的js,选择的函数MD5加密

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

         进行数据对其加密

 

         然后我们进入登陆,在控制天查看可以查看加密后的密码

         我们导入MD5的工具类,进行后台加密

 MD5Utils.java
package com.yx.yxshop.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);}}

        然后对其前台传来的密码进行二次加密,在service层进行加密

package com.yx.yxshop.service.impl;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.yx.yxshop.exp.BusinessException;
import com.yx.yxshop.pojo.User;
import com.yx.yxshop.mapper.UserMapper;
import com.yx.yxshop.resp.JsonResponseBody;
import com.yx.yxshop.resp.JsonResponseStatus;
import com.yx.yxshop.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yx.yxshop.vo.UserVo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;import java.nio.charset.StandardCharsets;import static com.yx.yxshop.resp.JsonResponseStatus.LOGIN_NO_EQUALS;/*** <p>* 用户信息表 服务实现类* </p>** @author yangxin* @since 2023-12-27*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic JsonResponseBody<?> login(UserVo vo) {//     从数据库中查询用户信息User one = getOne(new QueryWrapper<User>().lambda()                       //false表示有多条数据不报错.eq(User::getId, vo.getPhone()), false);//不带密码查询if (one==null){//为查询到指定数据throw new BusinessException(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(LOGIN_NO_EQUALS);//抛出异常}
//        登录成功   (查询到了指定的数据)return JsonResponseBody.success();}
}

         然后我们运行进行登陆测试

        我们最后将登陆的请求方法完善在前端的JS中 

五、完善登陆之Redis集成使用

        当我们登陆完成之后我们在后续的很多地方需要获取到用户信息进行操作,例如:添加购物车、下单、主页显示登陆用户,因此我们使用的是Redis来存储我们的登陆用户信息。

1. 导入Redis的pom依赖

<!--       导入Redis依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

         当然我们集成了redis之后还要将我们的Redis服务打开才能正常使用

2. 配置yml连接

        在yml文件中进行我们redis相关的文件配置

    redis:host: 127.0.0.1port: 6379password: 123456database: 0

3. 集成使用 

         接下来就是对其集成Redis到我们的项目之中进行使用,在UserSeriviceImpl中进行使用

         考虑到后续存放我们的用于信息时我们的键重复时,因此我们借用使用雪花ID进行生成键。对其的导入其pom文件的依赖。

<dependency><groupId>com.github.yitter</groupId><artifactId>yitter-idgenerator</artifactId><version>1.0.6</version></dependency>

         接下来就是雪花ID与Redis的集成使用

         我们现在的项目并不是前后端分离的项目,所以前端要获取到用户的信息需要我们使用到Cookie存储,因此需要借助一些工具类

CookieUtils.java
package com.yx.yxshop.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;}}

        导入完其帮助类时我们在UserServiceImpl进行运用使用,但是需要请求响应,在控制层进行带入。 

        后面就是其运用了。 

        在进行测试之前必须在实体类中要进行一个修改 

         导入一个我们Redis的配置类利于我们存储数据及数据查看

 RedisConfig.java
package com.yx.yxshop.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;}}

        然后进行运行测试项目

 

         我们运行之后在网页进行访问页面,进行登陆的操作在网页的应用程序生成了一个Cookie和在Redis缓存中生成对应的用户信息。但是以后在完善代码的过程中要多次获取Cookie中的用户信息,我们把所涉及到的代码给他封装成一个类。

 IRedisService.java
package com.yx.yxshop.service;import com.yx.yxshop.pojo.User;/*** com.yx.yxshop.service** @author 君易--鑨* @site www.yangxin.com* @company 木易* @create 2023/12/30* reids封装类,用于封装redis操作*/
public interface IRedisService {/*** 保存用户*/void saveUser(String key, User user);/*** 读取用户*/User loadUser(String key);
}
 IRedisServiceImpl.java
package com.yx.yxshop.service.impl;import com.yx.yxshop.pojo.User;
import com.yx.yxshop.service.IRedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;/*** com.yx.yxshop.service.impl** @author 君易--鑨* @site www.yangxin.com* @company 木易* @create 2023/12/30* 接口实现类*/
@Service
public class IRedisServiceImpl implements IRedisService {@Autowiredprivate RedisTemplate redisTemplate;@Overridepublic void saveUser(String token, User user) {//        将个人信息存放到我们的缓存中redisTemplate.opsForValue().set("user:"+token,user);}@Overridepublic User loadUser(String token) {return (User) redisTemplate.opsForValue().get("user:"+token);}
}

        控制层引用封装类

 

           在代码当中尽量不要出现"user:",尽量使用常量,我们新建一个类进行定义我们常用的常量。

Constans.java
package com.yx.yxshop.core;/*** com.yx.yxshop.core** @author 君易--鑨* @site www.yangxin.com* @company 木易* @create 2023/12/30* 常量类*/
public class Constans {public static final String REDIS_USER_PREFIX = "user:";//缓存前缀类public static final String REDIS_CAR_PREFIX = "car:";//购物车常量public static final String REDIS_ORDER_PREFIX = "order:";//订单常量}

         在方法中进行引用

        为了登陆成功在首页显示登陆用户信息的姓名是,我们在后台进行一个Cookie存值。

         运行测试

 ​​​​​​​

六、自定义验证注解

        写一个自定义注解类

 IsTrue.java
package com.yx.yxshop.core;import javax.validation.Payload;
import java.lang.annotation.*;/*** com.yx.yxshop.core** @author 君易--鑨* @site www.yangxin.com* @company 木易* @create 2023/12/30* 自定义注解类*/@Retention(RetentionPolicy.RUNTIME)//该注解表示项目运行时有效
@Documented//生成文档注解
@Target(ElementType.FIELD)//代表该注解可以用于属性字段
public @interface IsTrue {/**** 这个字段是否必须*/boolean required() default false;/**** 字段的正则*/String expr() default "";//    错误提交信息String message() default "{javax.validation.constraints.NotBlank.message}";Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}

        我们这个自定义注解类的使用范围比较广,只需要填写两个参数,一个数是否启用,一个是正则。 我们还需要配置一个解析器

 IsTrueConstraintValidator.java
package com.yx.yxshop.core;import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import lombok.Data;import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;@Data
public class IsTrueConstraintValidator implements ConstraintValidator<IsTrue, String> {private boolean require;private String expr;@Overridepublic void initialize(IsTrue isTrue) {expr = isTrue.expr();require = isTrue.require();}@Overridepublic boolean isValid(String value, ConstraintValidatorContext context) {if (!require) return true;if (StringUtils.isEmpty(value)) return false;return value.matches(expr);}}

         有了解析器之后我们还需要在自定义注解类中指定

         后面对其进行使用

 ​​​​​​​

        最后运行项目进行登陆测试 


 

 🎉🎉本期的博客分享到此结束🎉🎉

📚📚各位老铁慢慢消化📚📚

🎯🎯下期博客博主会带来新货🎯🎯

🎁三连加关注,阅读不迷路 !🎁

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/228473.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Android Gradle 插件】Android Plugin DSL Reference 离线文档下载 ( GitHub 下载文档 | 查看文档 )

一、Android Plugin DSL Reference 文档下载 二、Android Plugin DSL Reference 文档查看 一、Android Plugin DSL Reference 文档下载 在之前的博客 【Android Gradle 插件】Android Plugin DSL Reference 文档介绍 ( 1.2 ~ 3.4 版本文档地址 | 4.1 ~ 7.1 版本文档地址 ) 中…

ffmpeg两种windows版本区别说明

版本一 必须拷贝exe和dll文件才能使用&#xff0c;如果缺少dll则exe不正正常执行 如果缺少dll &#xff0c;执行 exe会报错如下 版本2 直接拷贝exe就能使用&#xff0c;没有依赖的环境

uniapp实现前端银行卡隐藏中间的数字,及隐藏姓名后两位

Vue 实现前端银行卡隐藏中间的数字 主要应用了 filters过滤器 来实现效果 实现效果&#xff0c;如图&#xff1a; <template><div><div style"background-color: #f4f4f4;margin:50px 0 0 460px;width:900px;height:300px;"><p>原来&#…

Android 13 动态启用或禁用IPV6

介绍 客户想要通过APK来控制IPV6的启用和禁用&#xff0c;这里我们通过广播的方式来让客户控制IPV6。 效果展示 adb shell ifconfig 这里我们用debug软件&#xff0c;将下面节点置为1 如图ipv6已被禁用了 echo 1 > /proc/sys/net/ipv6/conf/all/disable_ipv6 修改 接下来…

老虎目标检测数据集VOC格式900张

老虎是地球上最为壮丽而令人敬畏的野生动物之一&#xff0c;是大型猫科动物中的一员。老虎通常具有强壮的体格和敏捷的身体机能&#xff0c;是世界上最顶级的掠食者之一。 老虎的外貌特征鲜明&#xff0c;身体长约2至3米&#xff0c;体重可达200至300公斤。它们的体型庞大&…

Kubernetes 学习总结(41)—— 云原生容器网络详解

背景 随着网络技术的发展&#xff0c;网络的虚拟化程度越来越高&#xff0c;特别是云原生网络&#xff0c;叠加了物理网络、虚机网络和容器网络&#xff0c;数据包在网络 OSI 七层网络模型、TCP/IP 五层网络模型的不同网络层进行封包、转发和解包。网络数据包跨主机网络、容器…

2023下半年的总结

我从八月下旬开始写的&#xff0c;到现在差不多有半年了&#xff0c;总结一下吧&#xff01; 1.计算机视觉 在计算机视觉方面&#xff0c;想必两个有名的深度学习框架&#xff08;TensorFlow和PyTorch&#xff09;大家都很清楚吧&#xff0c;以及OpenCV库。对于人脸识别&…

2024年【黑龙江省安全员C证】考试及黑龙江省安全员C证找解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年黑龙江省安全员C证考试为正在备考黑龙江省安全员C证操作证的学员准备的理论考试专题&#xff0c;每个月更新的黑龙江省安全员C证找解析祝您顺利通过黑龙江省安全员C证考试。 1、【多选题】下列属于编制安全检查…

【力扣题解】P530-二叉搜索树的最小绝对差-Java题解

&#x1f468;‍&#x1f4bb;博客主页&#xff1a;花无缺 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! 本文由 花无缺 原创 收录于专栏 【力扣题解】 文章目录 【力扣题解】P530-二叉搜索树的最小绝对差-Java题解&#x1f30f;题目描述&#x1f4a1;题解&…

解密C++中的forward<int>(a)和forward<int >(a):你真的了解它们之间的区别吗?

一文看尽C中的forward完美转发 一、前言二、深入理解forward和完美转发三、对forward<int>(a)的解析四、对forward<int &&>(a)的解析五、forward<int>(a)和forward<int &&>(a)的区别总结 一、前言 完美转发在C中具有重要性&#xff0…

数据结构期末复习(2)链表

链表 链表&#xff08;Linked List&#xff09;是一种常见的数据结构&#xff0c;用于存储一系列具有相同类型的元素。链表由节点&#xff08;Node&#xff09;组成&#xff0c;每个节点包含两部分&#xff1a;数据域&#xff08;存储元素值&#xff09;和指针域&#xff08;指…

Python学习笔记之(一)搭建Python 环境

搭建Python 环境 1. 使用工具准备1.1 Python 安装1.1.1 下载Python 安装包1.1.2 安装Python 1.2 VScode 安装1.2.1 下载VScode安装包1.2.2 给VScode安装Python 扩展 2. 第一次编写Python 程序 本篇文章以Windows 系统为例。 1. 使用工具准备 1.1 Python 安装 1.1.1 下载Pytho…

双向循环链表实现C语言关键字中英翻译机 ฅ( ̳• · • ̳ฅ)

目录 1.双向循环链表的声明与定义&#xff1a; 2. 创建链表并对节点中的数据赋初值 3. 插入节点并链接 4.中英翻译 5. 小游戏的实现 6.菜单的实现 7. 释放内存 8.在主函数中用刚才定义的函数实现各种代码 输入样例&#xff1a; 实现方法&#xff1a;双向循环链表来实…

华为ensp网络设计期末测试题-复盘

网络拓扑图 地址分配表 vlan端口分配表 需求 The device is running!<Huawei>sys Enter system view, return user view with CtrlZ. [Huawei]un in en Info: Information center is disabled. [Huawei]sys S1 [S1]vlan 99 [S1-vlan99]vlan 100 [S1-vlan100]des IT [S1-…

万字长文谈自动驾驶occupancy感知

文章目录 prologuepaper listVision-based occupancy :1. [MonoScene: Monocular 3D Semantic Scene Completion [CVPR 2022]](https://arxiv.org/pdf/2112.00726.pdf)2. [Tri-Perspective View for Vision-Based 3D Semantic Occupancy Prediction [CVPR 2023]](https://arxiv…

跳跃表原理及实现

一、跳表数据结构 跳表是有序表的一种&#xff0c;其底层是通过链表实现的。链表的特点是插入删除效率高&#xff0c;但是查找节点效率很低&#xff0c;最坏的时间复杂度是O(N)&#xff0c;那么跳表就是解决这一痛点而生的。 为了提高查询效率&#xff0c;我们可以给链表加上索…

新手小白:一文带你用vite从零搭建企业级开发环境

在这工作的半年时间里&#xff0c;开始接触了前端开发&#xff0c;技术栈主要用的是 vue2&#xff0c;但是自己利用时间也学习了 vue3&#xff0c;组合式 api 和 vue3 的各种生态比 vue2 好用太多了&#xff0c;特别是状态管理库 pinia 比 vuex 简介很多&#xff0c;构建工具也…

rancher 手册

官方 https://www.rancher.com/https://github.com/rancher/rancherhttps://docs.rke2.io/ rancher kubernetesl yaml deploy rancher serverHelm Deploy Online Rancher DemoHelm & Kubernetes Offline Deploy Rancher v2.7.5 Demohelm upgrade rancher server from v2…

[Linux开发工具]——vim使用

Linux编辑器——vim的使用 一、什么是集成开发环境&#xff1f;二、什么是vim&#xff1f;三、vim的概念四、vim的基本操作五、vim命令模式命令集5.1 移动光标5.2 删除文字5.3 复制粘贴5.4 其他操作 六、vim底行模式命令集6.1 首先在命令模式下shift&#xff1b;进入末行模式。…

uni-app/vue封装etc车牌照输入,获取键盘按键键值

先看下效果如下&#xff1a; 动态图如下 uniapp的keyup获取不到keyCode和compositionstart&#xff0c;compositionend&#xff0c;所以需要监听input节点的keyup事件&#xff0c; 思路以及代码如下&#xff1a; 1.将每一个字符用文本框输入&#xff0c;代码如下 <view …