首先简单建张用户表。
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (`id` bigint NOT NULL AUTO_INCREMENT,`name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,`username` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
用户注册
业务逻辑:向用户表插入一条用户数据,处理用户已存在的异常
1.确定请求路径,确定dto(请求参数)和vo(响应参数)
请求方式肯定为post,设计为/admin/register
dto设计如下。
加入了非空和正则校验,pom先引入依赖。
<!-- 参数校验 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>
public class UserDTO {@NotEmpty(message = "【名称】不能为空")private String username;@NotEmpty(message = "【密码】不能为空")@Pattern(regexp = "^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,32}$", message = "【密码】规则不正确")private String password;public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}
}
vo不需要返回数据,定义一个通用的返回类
public class Result<T> implements Serializable {private Integer code; //编码:1成功,0和其它数字为失败private String msg; //错误信息private T data; //数据public Integer getCode() {return code;}public void setCode(Integer code) {this.code = code;}public String getMsg() {return msg;}public void setMsg(String msg) {this.msg = msg;}public T getData() {return data;}public void setData(T data) {this.data = data;}public static <T> Result<T> success() {Result<T> result = new Result<T>();result.code = 1;return result;}public static <T> Result<T> success(T object) {Result<T> result = new Result<T>();result.data = object;result.code = 1;return result;}public static <T> Result<T> error(String msg) {Result result = new Result();result.msg = msg;result.code = 0;return result;}}
2.编写Controller
@RestController
@RequestMapping("/admin")public class UserController {@Autowiredprivate UserService userService;@Autowiredprivate JwtProperties jwtProperties;/*** 注册*/@PostMapping("/register")public Result Register(@RequestBody UserRegisterDTO userRegisterDTO) {userService.Register(userRegisterDTO);return Result.success();}
}
3.编写Service接口和实现类
public interface UserService {/*** 注册* @param userRegisterDTO*/void Register(UserRegisterDTO userRegisterDTO);
}
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;/*** 用户注册* @param userRegisterDTO*/@Overridepublic void Register(UserRegisterDTO userRegisterDTO) {//判断当前用户是否存在User user = userMapper.getByUsername(userRegisterDTO.getUsername());if(user!=null){throw new AccountFoundException(MessageConstant.ALREADY_EXISTS);}User user1 = new User();//类的属性拷贝BeanUtils.copyProperties(userRegisterDTO,user1);String password = userRegisterDTO.getPassword();//进行md5加密password= DigestUtils.md5DigestAsHex(password.getBytes());user1.setPassword(password);userMapper.insert(user1);}
}
其中我们编写了几个对应的异常类和一个异常信息常量类,并对异常类进行了统一处理
建一个Exception包,放以下几个异常类
基本业务异常类
/*** 业务异常*/
public class BaseException extends RuntimeException {public BaseException() {}public BaseException(String msg) {super(msg);}}
账号已存在异常类
/*** 账号已存在异常*/
public class AccountFoundException extends BaseException{public AccountFoundException() {}public AccountFoundException(String msg) {super(msg);}
}
账号不存在异常类
/*** 账号不存在异常*/
public class AccountNotFoundException extends BaseException {public AccountNotFoundException() {}public AccountNotFoundException(String msg) {super(msg);}}
密码错误异常类
/*** 密码错误异常*/
public class PasswordErrorException extends BaseException {public PasswordErrorException() {}public PasswordErrorException(String msg) {super(msg);}}
异常信息常量类
/*** 信息提示常量类*/
public class MessageConstant {public static final String PASSWORD_ERROR = "密码错误";public static final String ACCOUNT_NOT_FOUND = "账号不存在";public static final String ALREADY_EXISTS = "账号已存在";}
异常统一处理类(建个handler包)
/*** 全局异常处理器,处理项目中抛出的业务异常*/
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler {/*** 捕获业务异常* @param ex* @return*/@ExceptionHandlerpublic Result exceptionHandler(BaseException ex){log.error("异常信息:{}", ex.getMessage());return Result.error(ex.getMessage());}}
4.编写mapper
@Mapper
public interface UserMapper {/*** 根据用户名查询用户* @param username* @return*/@Select("select * from user where username = #{username}")User getByUsername(String username);/*** 插入一条用户* @param user*/@Insert("insert into User(name,username,password)" +"values "+"(#{name},#{username},#{password})")void insert(User user);
}
postman测试,查看数据库
二次注册
用户登录
业务逻辑:根据用户名查询数据库中的数据,处理用户名不存在,密码不正确的异常,如果用户登录成功,则生成jwt令牌(token),以便其他页面访问时进行jwt校验(拦截器)
1.确定请求路径,确定dto(请求参数)和vo(响应参数)
请求方式为post,路径为请求参数为username和password就不特意封装dto类了,vo设计如下。data注解自动装配get、set函数,另外增加注解builder构造器,无参构造方法,有参构造方法
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserVO implements Serializable {private Long id;private String userName;private String name;private String token;}
2.引入jwt
(1)引入pom依赖
<dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-jackson</artifactId><version>0.11.5</version></dependency><dependency><groupId>javax.activation</groupId><artifactId>activation</artifactId><version>1.1.1</version></dependency>
(2)引入jwt生成类(生成token)
public class JwtUtil {/*** 生成jwt* 使用Hs256算法, 私匙使用固定秘钥** @param secretKey jwt秘钥* @param ttlMillis jwt过期时间(毫秒)* @param claims 设置的信息* @return*/// 使用Keys类生成安全的密钥private static final SecretKey SECRET_KEY = Keys.secretKeyFor(SignatureAlgorithm.HS256);public static String createJWT(String secretKey, long ttlMillis, Map<String, Object> claims) {// 指定签名的时候使用的签名算法,也就是header那部分SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;// 生成JWT的时间long expMillis = System.currentTimeMillis() + ttlMillis;Date exp = new Date(expMillis);String jws = Jwts.builder().setClaims(claims).setExpiration(exp).signWith(SECRET_KEY).compact();return jws;}/*** Token解密** @param secretKey jwt秘钥 此秘钥一定要保留好在服务端, 不能暴露出去, 否则sign就可以被伪造, 如果对接多个客户端建议改造成多个* @param token 加密后的token* @return*/public static Jws<Claims> parseJWT(String secretKey, String token) {Jws<Claims> claims = Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token);return claims;}}
(3)引入jwt所需属性类,并在配置文件(yml/properties)中配置
@Component
@ConfigurationProperties(prefix = "user.jwt")
@Data
public class JwtProperties {/*** 用户生成jwt令牌相关配置*/private String adminSecretKey;private long adminTtl;private String adminTokenName;}
user.jwt.admin-secret-key=itcast
user.jwt.admin-ttl=7200000
user.jwt.admin-token-name=token
(4)设置threadlocal类(用于获取,设置,移除当前用户id)
public class BaseContext {public static ThreadLocal<Long> threadLocal = new ThreadLocal<>();public static void setCurrentId(Long id) {threadLocal.set(id);}public static Long getCurrentId() {return threadLocal.get();}public static void removeCurrentId() {threadLocal.remove();}}
(5)引入jwt拦截器,用于校验用户token
@Component
@Slf4j
public class JwtTokenAdminInterceptor implements HandlerInterceptor {@Autowiredprivate JwtProperties jwtProperties;/*** 校验jwt** @param request* @param response* @param handler* @return* @throws Exception*/public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {System.out.println("当前线程的id:"+Thread.currentThread().getId());//判断当前拦截到的是Controller的方法还是其他资源if (!(handler instanceof HandlerMethod)) {//当前拦截到的不是动态方法,直接放行return true;}//1、从请求头中获取令牌String token = request.getHeader(jwtProperties.getAdminTokenName());//2、校验令牌try {log.info("jwt校验:{}", token);Claims claims = JwtUtil.parseJWT(jwtProperties.getAdminSecretKey(), token).getBody();Long id = Long.valueOf(claims.get(JwtClaimsConstant.USER_ID).toString());log.info("当前使用者id:", id);BaseContext.setCurrentId(id);//3、通过,放行return true;} catch (Exception ex) {System.out.println(ex.getStackTrace());//4、不通过,响应401状态码response.setStatus(401);return false;}}
}
(5)开启统一拦截器,添加jwt拦截器,excludePathPatterns中为放行类(浏览器直接能够访问)
/*** 配置类,注册web层相关组件*/
@Configuration
@Slf4j
public class WebMvcConfiguration extends WebMvcConfigurationSupport {@Autowiredprivate JwtTokenAdminInterceptor jwtTokenAdminInterceptor;/*** 注册自定义拦截器** @param registry*/protected void addInterceptors(InterceptorRegistry registry) {log.info("开始注册自定义拦截器...");registry.addInterceptor(jwtTokenAdminInterceptor).addPathPatterns("/**").excludePathPatterns("/admin/register").excludePathPatterns("/admin/login");}}
以上关于jwt的配置完成,我们来编写业务代码。
2.编写Controller层
/*** 登录** @param userDTO* @return*/@PostMapping("/login")public Result<UserVO> login(@RequestBody UserDTO userDTO) {User user = userService.login(userDTO);//登录成功后,生成jwt令牌Map<String, Object> claims = new HashMap<>();claims.put(JwtClaimsConstant.USER_ID, user.getId());String token = JwtUtil.createJWT(jwtProperties.getAdminSecretKey(),jwtProperties.getAdminTtl(),claims);//构造返回类UserVO userLoginVO = UserVO.builder().id(user.getId()).userName(user.getUsername()).name(user.getName()).token(token).build();return Result.success(userLoginVO);}
User实体类如下
@Data
public class User {private Long id;private String username;private String name;private String password;}
3.编写Service层
/*** 登录* @param userDTO* @return*/User login(UserDTO userDTO);
/*** 用户登录** @param* @return*/public User login(UserDTO userDTO) {String username = userDTO.getUsername();String password = userDTO.getPassword();//1、根据用户名查询数据库中的数据User user = userMapper.getByUsername(username);//2、处理各种异常情况(用户名不存在、密码不对、账号被锁定)if (user == null) {//账号不存在throw new IllegalArgumentException(MessageConstant.ACCOUNT_NOT_FOUND);}//密码比对//进行md5加密password= DigestUtils.md5DigestAsHex(password.getBytes());if (!password.equals(user.getPassword())) {//密码错误throw new PasswordErrorException(MessageConstant.PASSWORD_ERROR);}//3、返回实体对象return user;}
postman测试,完结撒花。