首先简单建张用户表。
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 {
@Autowired
private UserService userService;
@Autowired
private 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 {
@Autowired
private UserMapper userMapper;
/**
* 用户注册
* @param userRegisterDTO
*/
@Override
public 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
*/
@ExceptionHandler
public 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 {
@Autowired
private 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 {
@Autowired
private 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测试,完结撒花。