超详细讲解Java秒杀项目登陆模块的实现
目录
- 一、项目前准备
- 1、新建项目
- 2、导入依赖
- 3、执行sql脚本
- 4、配置yml文件
- 5、在启动类加入注解
- 6、自动生成器
- 二、前端构建
- 1、导入layui
- 2、将界面放到template
- 3、在js目录下新建目录project
- 4、新建controller类
- 三、MD5加密
- 1、导入帮助包与exception包
- 2、新建vo类
- 3、登录方法:
- 4、密码加密
- 四、 全局异常抓获
- 1、给实体类userVo加入注解
- 2、导入帮助包validate,异常抓获
- 3、在UserController类方法中加入注解
- 4、实现类抛出异常
一、项目前准备
1、新建项目
2、导入依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- mybatis plus依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.1</version> </dependency> <!--mybatis-plus生成器--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.2</version> </dependency> <!-- MD5依赖 --> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency> <!-- valid验证依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <!--redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--hariki 连接池--> <dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!-- https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui --> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> </dependencies>
3、执行sql脚本
得到表:
首先查看登录的表数据:
4、配置yml文件
spring:
application:
name: seckill
datasource:
url: jdbc:mysql://localhost:3306/seckill?useSSL=false&useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF8
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123456
hikari:
# 最小空闲连接数量
minimum-idle: 5
# 空闲连接存活最大时间,默认600000(10分钟)
idle-timeout: 180000
# 连接池最大连接数,默认是10
maximum-pool-size: 10
# 此属性控制从池返回的连接的默认自动提交行为,默认值:true
auto-commit: true
# 连接池名称
pool-name: MyHikariCP
# 此属性控制池中连接的最长生命周期,值0表示无限生命周期,默认1800000即30分钟
max-lifetime: 1800000
# 数据库连接超时时间,默认30秒,即30000
connection-timeout: 30000
freemarker:
#设置编码格式
charset: UTF-8
#后缀
suffix: .ftl
#文档类型
content-type: text/html
#模板前端
template-loader-path: classpath:/templates/
#启用模板
enabled: true
# 开启静态资源
mvc:
static-path-pattern: /static/**
mybatis-plus:
mapper-locations: classpath*:/mapper/*Mapper.xml
type-aliases-package: com.example.seckill.pojo
configuration:
map-underscore-to-camel-case: true
logging:
level:
com.example.seckill.mapper: debug
由于2.4.1版本是没有templates目录,而我配置的模板的位置在此处,所以在resource内新建templates目录
5、在启动类加入注解
SeckillApplication类:
//开启切面 @EnableAspectJAutoProxy //开启事务 @EnableTransactionManagement //扫描mapper层 @MapperScan("com.example.seckill.mapper")
6、自动生成器
package com.example.seckill.generator; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.generator.FastAutoGenerator; import com.baomidou.mybatisplus.generator.config.DataSourceConfig; import com.baomidou.mybatisplus.generator.config.OutputFile; import com.baomidou.mybatisplus.generator.config.rules.DateType; import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine; import com.baomidou.mybatisplus.generator.fill.Column; import lombok.Data; import lombok.extern.slf4j.Slf4j; import java.util.Arrays; import java.util.Collections; import java.util.List; @SuppressWarnings("all") @Slf4j @Data public class MybatisPlusGenerator { protected static String URL = "jdbc:mysql://localhost:3306/seckill?useEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false"; protected static String USERNAME = "root"; protected static String PASSWORD = "123456"; protected static DataSourceConfig.Builder DATA_SOURCE_CONFIG = new DataSourceConfig.Builder(URL, USERNAME, PASSWORD); public static void main(String[] args) { FastAutoGenerator.create(DATA_SOURCE_CONFIG) .globalConfig( (scanner/*lamdba*/, builder/*变量*/) -> builder.author(scanner.apply("请输入作者名称?")) .enableSwagger() .fileOverride() .outputDir(System.getProperty("user.dir") + "\\src\\main\\java") .commentDate("yyyy-MM-dd") .dateType(DateType.TIME_PACK) ) .packageConfig((builder) -> builder.parent("com.example.seckill") .entity("pojo") .service("service") .serviceImpl("service.impl") .mapper("mapper") .xml("mapper.xml") .pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "\\src\\main\\resources\\mapper")) ) .injectionConfig((builder) -> builder.beforeOutputFile( (a, b) -> log.warn("tableInfo: " + a.getEntityName()) ) ) .strategyConfig((scanner, builder) -> builder.addInclude(getTables(scanner.apply("请输入表名,多个英文逗号分隔?所有输入 all"))) .addTablePrefix("tb_", "t_") .entityBuilder() .enableChainModel() .enableLombok() .enableTableFieldAnnotation() .addTableFills( new Column("create_time", FieldFill.INSERT) ) .controllerBuilder() .enableRestStyle() .enableHyphenStyle() .build()) .templateEngine(new FreemarkerTemplateEngine()) .execute(); } protected static List<String> getTables(String tables) { return "all".equals(tables) ? Collections.emptyList() : Arrays.asList(tables.split(",")); } }
相关文件生成:
自动生成时,mapper文件未加入注解,无法加载到spring容器中
@Repository
二、前端构建
1、导入layui
2、将界面放到template
head.ftl文件:
<meta charset="UTF-8"> <title>秒杀项目</title> <script src="/static/asset/js/layui/layui.js" type="text/javascript"></script> <link href="/static/asset/js/layui/css/layui.css" rel="stylesheet" type="text/css"/> <meta http-equiv="Expires" content="0"> <meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-control" content="no-cache"> <meta http-equiv="Cache" content="no-cache"> <#assign ctx> ${springMacroRequestContext.getContextPath()} </#assign>
goodsList.ftl文件:
<!DOCTYPE html> <html lang="en"> <head> <#include "../common/head.ftl"> </head> <body> <h1>这是商品展示界面</h1> </body> </html>
login.ftl文件:
<!DOCTYPE html> <html lang="zh"> <head> <#include "common/head.ftl"/> <style> .layui-panel { position: absolute; width: 400px; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 15px 15px 0px 15px; border-radius: 20px; } .layui-form-label { padding: 9px 0px; } h3 { text-align: center; line-height: 45px; font-size: 40px; color: white; padding-bottom: 15px; } </style> </head> <body> <div> <div class="layui-panel layui-bg-cyan"> <h3>用户登录</h3> <div class="layui-form-item"> <label class="layui-form-label">用户账号</label> <div class="layui-input-block"> <input type="text" id="mobile" autocomplete="on" class="layui-input" value="18684671234"> </div> </div> <div class="layui-form-item"> <label class="layui-form-label">用户密码</label> <div class="layui-input-block"> <input type="password" id="password" autocomplete="on" class="layui-input" value="123456"> </div> </div> <div class="layui-form-item" style="text-align:center;"> <button class="layui-btn" id="login" style="width:46%">登录</button> <button class="layui-btn layui-btn-normal" id="register" style="width:46%">注册</button> </div> </div> </div> <script src="${ctx}/static/asset/js/md5.js"></script> <script src="${ctx}/static/asset/js/project/login.js"></script> </body> </html>
3、在js目录下新建目录project
新建JavaScript文件login:
layui.define(()=>{ // 得到layui中封装的jquery let $=layui.jquery // 给登录按钮设置事件 $(login).click(()=>{ // 取到表单的值 let mobile = $("#mobile").val(); let password=$("#password").val(); // 将数据给后台(前后端分离axios,普通开发ajax) $.ajax({ url:"",//后台登录接口 data:{ // 需要携带的数据 mobile, password }, datatype: "json",//后端给你的数据类型 success(e){ // 成功的回调函数 }, error(e){ // 报错的回调函数 } }) }) })
4、新建controller类
专门用于跳转路径:PathController类
package com.example.seckill.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class PathController { // 登录跳首页 @RequestMapping("/") public String toPath(){ return "login"; } // 跳所有二级页面 @RequestMapping("/{dir}/{path}") public String toPath(@PathVariable("dir") String dir,@PathVariable("path") String path){ return dir+"/"+path; } }
得到界面:
三、MD5加密
1、导入帮助包与exception包
①、exception
package com.example.seckill.exception; import com.example.seckill.util.response.ResponseResultCode; import lombok.Data; @SuppressWarnings("all") @Data public class BusinessException extends RuntimeException { private ResponseResultCode responseResultCode; public BusinessException(ResponseResultCode responseResultCode) { this.responseResultCode = responseResultCode; } }
②、response包,用于后面的全局异常
JsonResponseParse :
package com.example.seckill.util.response; import lombok.extern.slf4j.Slf4j; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; @SuppressWarnings("all") @RestControllerAdvice @Slf4j public class JsonResponseParse implements ResponseBodyAdvice { @Override public boolean supports(MethodParameter methodParameter, Class aClass) { //返回值决定他是否需要进入beforeBodyWrite return methodParameter.getMethod().isAnnotationPresent(JsonResponseResult.class); } @Override public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { //更改返回值 if (o == null) { return ResponseResult.success(); } if (o instanceof Integer) { return ResponseResult.failure(ResponseResultCode.queryCode((Integer) o)); } if (o instanceof ResponseResultCode) { return ResponseResult.failure((ResponseResultCode) o); } if (o instanceof ResponseResult) { return o; } return ResponseResult.success(o); } }
JsonResponseResult :
package com.example.seckill.util.response; import java.lang.annotation.*; @SuppressWarnings("all") @Retention(value = RetentionPolicy.RUNTIME) @Documented @Target({ElementType.METHOD}) public @interface JsonResponseResult { }
ResponseResult:
package com.example.seckill.util.response; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; @SuppressWarnings("all") @Data @NoArgsConstructor @AllArgsConstructor public class ResponseResult<T> implements Serializable { private int code; private String message; private T data; private Long total; /** * 私有构造, 只允许通过static调用构造 * * @param resultCode 结果枚举 * @param data 响应数据 */ private ResponseResult(ResponseResultCode resultCode, T data) { this.code = resultCode.getCode(); this.message = resultCode.getMessage(); this.data = data; } /** * 私有构造, 只允许通过static调用构造 * * @param resultCode 结果枚举 * @param data 响应数据 */ private ResponseResult(ResponseResultCode resultCode, Long total, T data) { this.code = resultCode.getCode(); this.message = resultCode.getMessage(); this.data = data; this.total = total; } /** * 成功调用返回的结果(无数据携带) */ public static ResponseResult success() { return success(null); } /** * 成功调用返回的结果(数据携带) * * @param data 携带的数据 */ public static <T> ResponseResult success(T data) { return new ResponseResult(ResponseResultCode.SUCCESS, data); } /** * 成功调用返回的结果(分页使用) * * @param data 携带的数据 * @param total 数据总条数 */ public static <T> ResponseResult success(T data, Long total) { return new ResponseResult(ResponseResultCode.SUCCESS, total, data); } /** * 失败调用返回的结果(数据携带) * * @param resultCode 状态枚举 * @param data 携带的数据 */ public static <T> ResponseResult failure(ResponseResultCode resultCode, T data) { return new ResponseResult(resultCode, data); } /** * 失败调用返回的结果(无数据携带) * * @param resultCode 状态枚举 */ public static ResponseResult failure(ResponseResultCode resultCode) { return failure(resultCode, null); } }
ResponseResultCode :
package com.example.seckill.util.response; import java.io.Serializable; @SuppressWarnings("all") public enum ResponseResultCode implements Serializable { /* 正常状态 */ SUCCESS(200, "成功"), FAILURE(300, "失败"), UNKNOWN(400, "未知错误"), /** * 用户code范围: 1000; */ USER_ACCOUNT_NOT_FIND(1001, "用户名不存在"), USER_ACCOUNT_DISABLED(1002, "该用户已被禁用"), USER_PASSWORD_NOT_MATCH(1003, "该用户密码不一致"), USER_PERMISSION_ERROR(1004, "该用户不具备访问权限"), USER_STATE_OFF_LINE(1005, "该用户未登录"), USER_CREDENTIAL_NOT_BE_EMPTY(1006, "用户的登录信息不能为空值"), USER_ACCOUNT_NOT_MOBLIE(1007, "该用户登录信息格式不符合"), USER_LOGIN_ERROR(1008, "登录失败"), /** * 其它异常: 4000; */ TICKET_ERROR(4001, "TICKET失效,请重新登录"), /** * 商品异常: 6000; */ GOODS_ADD_ERROR(6001, "商品添加失败"), GOODS_EDIT_ERROR(6002, "商品修改失败"), GOODS_REMOVE_ERROR(6003, "商品删除失败"), /** * 秒杀商品异常: 8000 */ SECKILL_GOODS_ADD_ERROR(8001, "秒杀商品增加失败"), /** * 秒杀订单异常: 10000 */ SECKILL_ORDER_ERROR(10001, "秒杀订单增加失败"), SECKILL_ORDER_QUANTITY_ERROR(10002, "秒杀商品数量不足"), SECKILL_ORDER_EXISTS_ERROR(10002, "秒杀订单已经存在"), ; private final Integer code; private final String message; ResponseResultCode(Integer code, String message) { this.code = code; this.message = message; } public static ResponseResultCode queryCode(Integer code) { for (ResponseResultCode value : values()) { if (code.equals(value.code)) { return value; } } return UNKNOWN; } public Integer getCode() { return code; } public String getMessage() { return message; } }
RestThrowableAdvice :
package com.example.seckill.util.response; import com.example.seckill.exception.BusinessException; import lombok.extern.slf4j.Slf4j; import org.springframework.ui.Model; import org.springframework.validation.BindException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.Arrays; @SuppressWarnings("all") @RestControllerAdvice @Slf4j public class RestThrowableAdvice { @JsonResponseResult @ExceptionHandler(value = {BusinessException.class}) public Object globalBusinessException(Model m, Exception e) { log.error(e.toString()); return ((BusinessException) e).getResponseResultCode(); } @JsonResponseResult @ExceptionHandler(value = {BindException.class}) public Object globalBindException(Model m, Exception e) { log.error(e.toString()); BindException error = (BindException) e; return Arrays .stream(error.getFieldError().getArguments()) .filter(arg -> arg instanceof ResponseResultCode) .findAny() .orElse(ResponseResultCode.UNKNOWN); } @JsonResponseResult @ExceptionHandler(value = {Throwable.class}) public Object globalException(Model m, Exception e) { log.error(e.toString()); return ResponseResultCode.UNKNOWN; } }
③、MD5Utils
package com.example.seckill.util; import org.apache.commons.codec.digest.DigestUtils; import org.springframework.stereotype.Component; import java.util.UUID; /** * MD5加密 * 用户端:password=MD5(明文+固定Salt) * 服务端:password=MD5(用户输入+随机Salt) * 用户端MD5加密是为了防止用户密码在网络中明文传输,服务端MD5加密是为了提高密码安全性,双重保险。 */ @Component @SuppressWarnings("all") public class MD5Utils { //加密盐,与前端一致 private static String salt = "f1g2h3j4"; /** * md5加密 * * @param src * @return */ public static String md5(String src) { return DigestUtils.md5Hex(src); } /** * 获取加密的盐 * * @return */ public static String createSalt() { return UUID.randomUUID().toString().replace("-", ""); } /** * 将前端的明文密码通过MD5加密方式加密成后端服务所需密码 * 注意:该步骤实际是在前端完成!!! * * @param inputPass 明文密码 * @return */ public static String inputPassToFormpass(String inputPass) { //混淆固定盐salt,安全性更可靠 String str = salt.charAt(1) + "" + salt.charAt(5) + inputPass + salt.charAt(0) + "" + salt.charAt(3); return md5(str); } /** * 将后端密文密码+随机salt生成数据库的密码 * * @param formPass * @param salt * @return */ public static String formPassToDbPass(String formPass, String salt) { //混淆固定盐salt,安全性更可靠 String str = salt.charAt(7) + "" + salt.charAt(9) + formPass + salt.charAt(1) + "" + salt.charAt(5); return md5(str); } /** * 将用户输入的密码转换成数据库的密码 * * @param inputPass 明文密码 * @param salt 盐 * @return */ public static String inputPassToDbPass(String inputPass, String salt) { String formPass = inputPassToFormpass(inputPass); String dbPass = formPassToDbPass(formPass, salt); return dbPass; } 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); String dbPass1 = inputPassToDbPass("123456", salt); System.out.println("最终加密密码:" + dbPass1); } }
ValidatorUtils :
package com.example.seckill.util; import org.apache.commons.lang3.StringUtils; import java.util.regex.Matcher; import java.util.regex.Pattern; @SuppressWarnings("all") public class ValidatorUtils { private static final Pattern mobile_pattern = Pattern.compile("[1]([0-9])[0-9]{9}$"); public static boolean isMobile(String mobile) { if (StringUtils.isEmpty(mobile)) { return false; } Matcher matcher = mobile_pattern.matcher(mobile); return matcher.matches(); } }
2、新建vo类
用于前后端传值:
package com.example.seckill.vo; import lombok.Data; @Data public class UserVo { // 手机号 private String mobile; // 密码 private String password; }
3、登录方法:
IUserService层:
package com.example.seckill.service; import com.example.seckill.pojo.User; import com.baomidou.mybatisplus.extension.service.IService; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.vo.UserVo; /** * <p> * 用户信息表 服务类 * </p> * * @author lv * @since 2022-03-15 */ public interface IUserService extends IService<User> { ResponseResult<?> findByAccount(UserVo userVo); }
UserServiceImpl类:
package com.example.seckill.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.example.seckill.pojo.User; import com.example.seckill.mapper.UserMapper; import com.example.seckill.service.IUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.seckill.util.ValidatorUtils; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.util.response.ResponseResultCode; import com.example.seckill.vo.UserVo; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; /** * <p> * 用户信息表 服务实现类 * </p> * * @author lv * @since 2022-03-15 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override public ResponseResult<?> findByAccount(UserVo userVo) { // 先判断信息是否符合(账号是否是手机号码,密码是不是空) if(!ValidatorUtils.isMobile(userVo.getMobile())){ return ResponseResult.failure(ResponseResultCode.USER_ACCOUNT_NOT_MOBLIE); } if(!StringUtils.isBlank(userVo.getPassword())){ return ResponseResult.failure(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } // 再去数据库查出对应的用户(mobile) User user=this.getOne(new QueryWrapper<User>().eq("id",userVo.getMobile())); if(user==null){ return ResponseResult.failure(ResponseResultCode.USER_ACCOUNT_NOT_FIND); } // 比较密码 if(userVo.getPassword().equals(user.getPassword())){ return ResponseResult.failure(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } return ResponseResult.success(); } }
UserController类:
package com.example.seckill.controller; import com.example.seckill.service.IUserService; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.vo.UserVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * <p> * 用户信息表 前端控制器 * </p> * * @author lv * @since 2022-03-15 */ @RestController @RequestMapping("/user") public class UserController { @Autowired private IUserService userService; // 用户登录 @RequestMapping("/login") public ResponseResult<?> login(UserVo userVo){ // 调用service的登录验证 return userService.findByAccount(userVo); } }
4、密码加密
①、将md5.js放到js文件中
②、前端密码进行加密
login.js文件:
layui.use(["jquery","layer"],()=>{ // 得到layui中封装的jquery let $=layui.jquery let layer=layui.layer // 给登录按钮设置事件 $(login).click(()=>{ // 取到表单的值 let mobile = $("#mobile").val(); let password=$("#password").val(); // 前端加密的盐 let salt= "f1g2h3j4"; if(password){ // 将密码和盐混在一起 password=salt.charAt(1) + "" + salt.charAt(5) + password + salt.charAt(0) + "" + salt.charAt(3); // 进行MD5加密 password=md5(password) } console.log(password) // 将数据给后台(前后端分离axios,普通开发ajax) $.ajax({ url:"/user/login",//后台登录接口 data:{ // 需要携带的数据 mobile, password }, datatype: "json",//后端给你的数据类型 success(e){ // 成功的回调函数 layer.msg(e.message,{icon: 6}); }, error(e){ // 报错的回调函数 } }) }) })
UserServiceImpl文件:
package com.example.seckill.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.example.seckill.pojo.User; import com.example.seckill.mapper.UserMapper; import com.example.seckill.service.IUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.seckill.util.MD5Utils; import com.example.seckill.util.ValidatorUtils; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.util.response.ResponseResultCode; import com.example.seckill.vo.UserVo; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; /** * <p> * 用户信息表 服务实现类 * </p> * * @author lv * @since 2022-03-15 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override public ResponseResult<?> findByAccount(UserVo userVo) { // 先判断信息是否符合(账号是否是手机号码,密码是不是空) if(!ValidatorUtils.isMobile(userVo.getMobile())){ return ResponseResult.failure(ResponseResultCode.USER_ACCOUNT_NOT_MOBLIE); } if(StringUtils.isBlank(userVo.getPassword())){ return ResponseResult.failure(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } // 再去数据库查出对应的用户(mobile) User user=this.getOne(new QueryWrapper<User>().eq("id",userVo.getMobile())); if(user==null){ return ResponseResult.failure(ResponseResultCode.USER_ACCOUNT_NOT_FIND); } // 比较密码 // 二重加密(前端->后端,后端->数据库) String salt=user.getSalt(); // 将前台的加密密码和后端的盐再次进行加密 String newPassword=MD5Utils.formPassToDbPass(userVo.getPassword(),salt); if(!newPassword.equals(user.getPassword())){ return ResponseResult.failure(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } return ResponseResult.success(); } }
得到密钥,登录成功:
四、 全局异常抓获
1、给实体类userVo加入注解
package com.example.seckill.vo; import com.example.seckill.util.response.ResponseResultCode; import com.example.seckill.util.validate.IsMobile; import com.example.seckill.util.validate.IsRequired; import lombok.Data; import javax.validation.constraints.NotEmpty; @Data public class UserVo { // 手机号 @IsMobile(code = ResponseResultCode.USER_ACCOUNT_NOT_FIND) private String mobile; // 密码 @IsRequired(code = ResponseResultCode.USER_CREDENTIAL_NOT_BE_EMPTY) private String password; }
2、导入帮助包validate,异常抓获
IsMobile:
package com.example.seckill.util.validate; import com.example.seckill.util.response.ResponseResultCode; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @SuppressWarnings("all") @Documented @Constraint( // 告知使用哪个解析器 validatedBy = {IsMobileValidator.class} ) @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface IsMobile { ResponseResultCode code() default ResponseResultCode.UNKNOWN; String message() default ""; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
IsMobileValidator:
package com.example.seckill.util.validate; import com.example.seckill.util.ValidatorUtils; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; //IsMobile的解析类 public class IsMobileValidator implements ConstraintValidator<IsMobile, String> { @Override public boolean isValid(String mobile, ConstraintValidatorContext context) { // 调用帮助类判断格式是否正确 return ValidatorUtils.isMobile(mobile); } }
IsRequired:
package com.example.seckill.util.validate; import com.example.seckill.util.response.ResponseResultCode; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @SuppressWarnings("all") @Documented @Constraint( // 告知使用哪个解析器 validatedBy = {IsRequiredValidator.class} ) @Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface IsRequired { ResponseResultCode code() default ResponseResultCode.UNKNOWN; String message() default ""; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
IsRequiredValidator:
package com.example.seckill.util.validate; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; @Slf4j public class IsRequiredValidator implements ConstraintValidator<IsRequired, String> { @Override public boolean isValid(String str, ConstraintValidatorContext context) { return StringUtils.isNotBlank(str); } }
ThrowableAdvice:
package com.example.seckill.util.response; import com.example.seckill.exception.BusinessException; import lombok.extern.slf4j.Slf4j; import org.springframework.ui.Model; import org.springframework.validation.BindException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * 全局异常 */ //RestController的增强类 @RestControllerAdvice @Slf4j //抓取异常 public class ThrowableAdvice { // 第一种方法 // 将你的结果直接封装为ResponseResult @JsonResponseResult // 抓捕一个异常 @ExceptionHandler(value = {BusinessException.class}) public ResponseResultCode globalBusinessException(Model m, Exception e) { log.error(e.toString()); e.printStackTrace(); return ((BusinessException) e).getResponseResultCode(); } // 第二种方法 //@JsonResponseResult //@ExceptionHandler(value = {BusinessException.class}) //public Object globalBusinessException(Model m, Exception e) { // Object[] arguments=((BindException)e).getFieldError().getArguments(); // 找到该注解上的响应码并且返回 // return Arrays.stream(arguments) // .filter(t->t instanceof ResponseResultCode) // .findAny() // .orElse(ResponseResultCode.UNKNOWN); //} @JsonResponseResult @ExceptionHandler(value = {BindException.class}) public ResponseResultCode globalBindException(Model m, Exception e) { log.error(e.toString()); BindException error = (BindException) e; e.printStackTrace(); return (ResponseResultCode) error.getFieldError().getArguments()[1]; } @JsonResponseResult @ExceptionHandler(value = {Throwable.class}) public ResponseResultCode globalException(Model m, Exception e) { log.error(e.toString()); e.printStackTrace(); return ResponseResultCode.UNKNOWN; } }
3、在UserController类方法中加入注解
开启jsr303验证
@Valid
4、实现类抛出异常
package com.example.seckill.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.example.seckill.exception.BusinessException; import com.example.seckill.pojo.User; import com.example.seckill.mapper.UserMapper; import com.example.seckill.service.IUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.seckill.util.MD5Utils; import com.example.seckill.util.ValidatorUtils; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.util.response.ResponseResultCode; import com.example.seckill.vo.UserVo; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; import java.util.Date; /** * <p> * 用户信息表 服务实现类 * </p> * * @author lv * @since 2022-03-15 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Override public ResponseResult<?> findByAccount(UserVo userVo) { // 先判断信息是否符合(账号是否是手机号码,密码是不是空) // 由于UserVo加入了注解,就无需抛出异常 // if(!ValidatorUtils.isMobile(userVo.getMobile())){ // throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_MOBLIE); // } // if(StringUtils.isBlank(userVo.getPassword())){ // throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH); // } // 再去数据库查出对应的用户(mobile) User user=this.getOne(new QueryWrapper<User>().eq("id",userVo.getMobile())); if(user==null){ throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_FIND); } // 比较密码 // 二重加密(前端->后端,后端->数据库) String salt=user.getSalt(); // 将前台的加密密码和后端的盐再次进行加密 String newPassword=MD5Utils.formPassToDbPass(userVo.getPassword(),salt); if(!newPassword.equals(user.getPassword())){ throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } // 修改最后的登录时间 this.update(new UpdateWrapper<User>().eq("id",userVo.getMobile()).set("last_login_date",new Date()).setSql("login_count=login_count+1")); return ResponseResult.success(); } }
密码错误时不会报错:
本期内容结束~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
到此这篇关于超详细讲解Java秒杀项目登陆模块的实现的文章就介绍到这了,更多相关Java 登陆模块内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!