目录
用户信息部分
1、获取用户详细信息
前言
代码分析
代码实现
测试
2、更新用户信息
前言
代码实现
测试
3、更新用户头像
前言
代码实现
测试
4、更新用户密码
前言
代码实现
测试
承接上一篇博客登录注册功能实现
由于我们的数据库字段是下划线命名的creat_time,而实体类中对应的是creatTime,字段不一致无法直接获取。
因此需要在yml配置文件中开启驼峰命名
mybatis:
configuration:
mapUnderscoreToCamelCase: true
既然要获取用户的详细信息,所以会自然的想到在controller中写一个/info接口,然后在service中调用mapper,mapper再调数据库,这样也没有错。但是我们再学习token的时候就说过了,token本身就是由用户的各种信息组成的,所以其实我们直接从token获取用户信息就行了,这样效率更高代码更少。
首先由于token中包含了用户的各种信息,所以默认也会包含密码在内,但是我们不希望在响应的时候把密码也解析出来,这是很不安全的,因此我们需要使用一个注解@JsonIgnore
@JsonIgnore:让springmvc把当前对象转换为json字符串的时候,忽略掉该注解作用的字段,最终的json中就没有这个字段了
其次我们在创建拦截器的时候就对令牌进行了一次解析响应,虽然我们在使用的时候也可以重新写一遍解析jwt,但是既然已经被解析过了,我们还是直接去拦截器中取比较好,这样显得比较专业和逼格。为此我们需要使用到ThreadLocal
ThreadLocal:提供线程变量
- 用来存储数据:get()和set()
- 使用ThreadLocal存储的数据,线程安全
线程安全就是说假如我们直接使用一个全局静态变量才存储变量,那么两个不同的线程同时会对这一个变量进行操作,数据就会错乱。但是使用ThreadLocal就可以保证不同的线程中只使用到自己的变量,不会互相串数据
ThreadLocalUtil工具类,这个类是为了让我们更方便的使用ThreadLocal。其中我们声明了一个全局的静态变量THREAD_LOCAL,可以对其进行set值和get值,还有一个remove方法是因为它的生命周期很长,为了防止内存泄漏要在一次线程完成时把这个变量的值删掉
/**
* ThreadLocal 工具类
*/
@SuppressWarnings("all")
public class ThreadLocalUtil {
//提供ThreadLocal对象,
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();
//根据键获取值
public static T get(){
return (T) THREAD_LOCAL.get();
}
//存储键值对
public static void set(Object value){
THREAD_LOCAL.set(value);
}
//清除ThreadLocal 防止内存泄漏
public static void remove(){
THREAD_LOCAL.remove();
}
}
优化拦截器 ,相比上次写的多了一个afterCompletion方法,这个方法也是顾名思义在目标方法执行后它再执行
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//获取令牌
String token = request.getHeader("Authorization");
//验证token
try {
Map claims = JwtUtil.parseToken(token);
//将解析的token信息放入ThreadLocal
ThreadLocalUtil.set(claims);
//没有异常就放行
return true;
} catch (Exception e) {
//未登录,不放行
response.setStatus(401);
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清空ThreadLocal的数据
ThreadLocalUtil.remove();
}
}
Controller
可以直接这里面获取ThreadLocal的值,之前往里存放的是什么类型的值就用什么类型接收就行了。
@RestController
@RequestMapping("/article")
public class ArticleController {
@Autowired
private UserService userService;
@GetMapping("/list")
public Result list(){
Map map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User user = userService.findByUserName(username);
return Result.success(user);
}
}
发起请求
使用者在前端修改用户的个人信息,发送到后端。我们接收过来的就是一个User对象,但是spring无法直接识别一个对象类,所以我们需要使用注解@RequestBody。
其次就是参数校验,实际上这个功能是要在前端完成的,不过我们在后端也写一下吧。我们之前在注册模块对密码进行过检验,让他限定在5~16位。使用Spring Validation,在需要被校验的参数前加上@Pattern(regexp = "^正则表达式$"),在需要生效的地方加上@Validated就完成了。
user实体类,直接在类上加上注解即可参数校验
数据校验
因为头像只是更新用户信息中的一部分,所以使用PATCH请求方式
avaterUrl是头像的地址,一般都存放在阿里云中
controller
@URL是数据校验是否是一个url格式的字符串,防止胡乱上传
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam @URL String avatarUrl){
userService.updateAvatar(avatarUrl);
return Result.success(avatarUrl);
}
service
直接从mapper中获取当前登录人token中的id
@Override
public void updateAvatar(String avatarUrl) {
Map map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
userMapper.updateAvatar(avatarUrl,id);
}
mapper
@Update("update user set user_pic=#{avatarUrl},update_time=now() " +
"where id = #{id}")
void updateAvatar(String avatarUrl,Integer id);
同样地,更新密码也是用户信息整体的一部分,所以用PATCH请求方式
在之前我们更新用户基本信息的时候,传递的参数是一个user实体类参数,且json字段名和user属性名一摸一样。但是在更新密码中,有一个new_pwd和old_pwd来表示新密码和旧密码,user类中就没有这个属性了,添加进去在逻辑上也不太合适。所以我们可以通过传递一个map集合来表示这个参数。
由于我们的密码在数据库中已经经过了MD5加密,所以直接拿传递过来的参数与数据库中的密码比对是不行的,可以先对参数进行MD5加密,然后在与数据库数据进行比对检验是否正确。
Controller
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody Map params){
//校验参数
String oldPwd = params.get("old_pwd");
String newPwd = params.get("new_pwd");
String rePwd = params.get("re_pwd");
if (!StringUtils.hasLength(oldPwd) || !StringUtils.hasLength(newPwd)
|| !StringUtils.hasLength(rePwd)){
return Result.error("缺少必要的参数");
}
//比较原密码是否正确
Map map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User loginUser = userService.findByUserName(username);
if (!loginUser.getPassword().equals(Md5Util.getMD5String(oldPwd))){
return Result.error("原密码错误");
}
//比较新密码和重复新密码是否正确
if (!rePwd.equals(newPwd)){
return Result.error("两次填写的新密码不一样");
}
//调用service完成密码更新
userService.updatePwd(newPwd);
return Result.success();
}
Service
@Override
public void updatePwd(String newPwd) {
Map map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
userMapper.updatePwd(Md5Util.getMD5String(newPwd),id);
}
Mapper
@Update("update user set password=#{newPwd},update_time=now() where id = #{id} ")
void updatePwd(String newPwd,Integer id);