SpringBoot学习项目-博客系统-part6
注册
- 根据文档可知请求地址、请求方式、请求参数
RegisterController
- 为什么要用LoginService?
因为注册了之后就形同于登陆,LoginService这个接口中定义个关于登陆、注册、退出、验证等。
@RestController
@RequestMapping("register")
public class RegisterController {
@Autowired
private LoginService loginService;
@PostMapping
public Result register(@RequestBody LoginParam loginParam){
return loginService.register(loginParam);
}
}
- 请求参数中有用户账户、用户密码、用户昵称
- 因为前面已经有一个LoginParam类,这个类中有account、password属性,所以现在只需要根据现在的参数新增一个nickname属性
@Data
public class LoginParam {
private String account;
private String password;
private String nickname;
}
LoginService
//注册
Result register(LoginParam loginParam);
LoginServiceImpl
- 分析注册逻辑:
- 根据传入的参数得到账户、密码、昵称,如果这三个有一个为空,那么就返回一个传参错误,根据Errcode枚举类定义的。
- 都不为空,那么去UserService去根据账户找,因为账户名不能一样,如果找到了,说明被注册过,就返回一个账户已经存在。
- 没有对应的账户,那么就new一个用户,设置对应的属性,并且要保存到数据库中。
- 接下来就是和登陆逻辑一样了,创建token,保存在redis中,最后返回token(因为返回数据形式也是token)
@Override
public Result register(LoginParam loginParam) {
String account = loginParam.getAccount();
String password = loginParam.getPassword();
String nickname = loginParam.getNickname();
if (StringUtils.isBlank(account)
|| StringUtils.isBlank(password)
|| StringUtils.isBlank(nickname)
){
return Result.fail(ErrorCode.PARAMS_ERROR.getCode(),ErrorCode.PARAMS_ERROR.getMsg());
}
SysUser sysUser = this.sysUserService.findUserByAccount(account);
if (sysUser != null){
return Result.fail(ErrorCode.ACCOUNT_EXIST.getCode(),ErrorCode.ACCOUNT_EXIST.getMsg());
}
sysUser = new SysUser();
sysUser.setNickname(nickname);
sysUser.setAccount(account);
sysUser.setPassword(DigestUtils.md5Hex(password+slat));
sysUser.setCreateDate(System.currentTimeMillis());
sysUser.setLastLogin(System.currentTimeMillis());
sysUser.setAvatar("/static/img/logo.b3a48c0.png");
sysUser.setAdmin(1); //1 为true
sysUser.setDeleted(0); // 0 为false
sysUser.setSalt("");
sysUser.setStatus("");
sysUser.setEmail("");
this.sysUserService.save(sysUser);
//token
String token = JWTUtils.createToken(sysUser.getId());
redisTemplate.opsForValue().set("TOKEN_"+token, JSON.toJSONString(sysUser),1, TimeUnit.DAYS);
return Result.success(token);
}
SysUserService
SysUser findUserByAccount(String account);
void save(SysUser sysUser);
SysUserServiceImpl
@Override
public SysUser findUserByAccount(String account) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getAccount,account);
queryWrapper.last("limit 1");
return sysUserMapper.selectOne(queryWrapper);
}
@Override
public void save(SysUser sysUser) {
//注意 默认生成的id 是分布式id 采用了雪花算法
this.sysUserMapper.insert(sysUser);
}
事务
- 本项目中没有涉及到钱什么的,为什么需要用到事务呢?
- 假设如果服务端redis关闭了,那么意味着注册用户的时候不应该添加到数据库中,应该执行回滚操作。
- 加事务
- 通常加以一般加在接口上
@Transactional
public interface LoginService {
//登陆
Result login(LoginParam loginParam);
//验证
SysUser checkToken(String token);
//退出
Result logout(String token);
//注册
Result register(LoginParam loginParam);
}
登陆拦截
- 思考:为什幺登陆拦截?
- 设想如果所有的接口访问都需要登陆,那么一旦登陆逻辑发生改变,变化的地方就过多,此时就需要登陆拦截,比如我只访问某些资源的时候才需要进行一个登陆。
- 使用拦截器进行登陆拦截,如果遇到需要登陆才能访问的接口,必须让其登陆,如果没有登陆,要跳转到登陆页面
- 怎样去配置这个拦截器?
- 写一个LoginInterCeptor去实现HandlerInterceptor接口,去实现preHandle方法,每次之前都要进行一个拦截
- 拦截逻辑分析
- 判断请求接口路径是否为判断请求的接口路径是否为HandlerMethod,访问的是controller的方法。handler可能是 RequestResourceHandler springboot程序访问静态资源默认去classpath下的static目录去查询
- 接下来因为请求头的Authorization携带的token信息,去判断token是否为空,
- 为空就没有登陆
- 不为空,进行一个登陆验证,因为前面在登陆的时候已经写过该方法,所以要注入LoginService
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
@Autowired
private LoginService loginService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)){
return true;
}
String token = request.getHeader("Authorization");
log.info("=================request start===========================");
String requestURI = request.getRequestURI();
log.info("request uri:{}",requestURI);
log.info("request method:{}",request.getMethod());
log.info("token:{}", token);
log.info("=================request end===========================");
if (token == null){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
// 浏览器识别返回的是json
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
SysUser sysUser = loginService.checkToken(token);
if (sysUser == null){
Result result = Result.fail(ErrorCode.NO_LOGIN.getCode(), "未登录");
response.setContentType("application/json;charset=utf-8");
response.getWriter().print(JSON.toJSONString(result));
return false;
}
//是登录状态,放行
return true;
}
}
ThreadLocal保存用户信息
- 思考:如何在登陆的时候获取到用户的信息呢?比如后续的写文章的操作。
- 考虑ThreadLocal(这个之后会专门写一个ThreadLocal的总结),现在只需要记住,ThreadLocal中的mapkey是ThreadLocal,value是要注入的值
自定义一个ThreadLocal
public class UserThreadLocal {
private UserThreadLocal(){}
//线程变量隔离
private static final ThreadLocal<SysUser> LOCAL = new ThreadLocal<>();
// 放
public static void put(SysUser sysUser){
LOCAL.set(sysUser);
}
// 取
public static SysUser get(){
return LOCAL.get();
}
// 清除
public static void remove(){
LOCAL.remove();
}
}
同时在每次拦截放行之后,说明是有用户存在,并且访问的是要登陆才能访问的资源,此时将查询出的用户放到UserThreadLocal中
UserThreadLocal.put(sysUser);
同时,在所有处理完成之后,要进行回收,不然会产生内存泄露风险
// 如果不删除ThreadLocal中用完的信息,会有内存泄露的风险
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
UserThreadLocal.remove();
}

浙公网安备 33010602011771号