1.获取用户详细信息
跟着黑马程序员继续学习SpringBoot3+Vue3
用户登录成功之后跳转到首页,需要获取用户的详细信息
打开接口文档
使用Token令牌解析得到用户名
我们需要根据用户名查询用户,获取详细信息
但是请求参数是无,由于都需要携带Token令牌, 可以解析Token令牌得到用户名。
打开UserController.java
在下面声明方法userInfo,返回类型Result<User>,添加注解,映射路径。想得到用户名就要声明一个String类型的Token,并且从请求头获取的。
发送请求
401?为什么?因为没有携带请求头,打开postman,携带请求头,再次发送就成功了
很多接口都需要添加请求头,为了测试方便,在postman中,可以给集合里所有的请求头统一添加请求头
回到响应数据里发现把password也给响应回来了
在user类里面,passward上面添加@JsonIgnore
作用:让springmvc把当前对象转换成Json字符串的时候,忽略password,最终json字符串中就没有password这个属性了
But! 我遇到了问题(未解决)
我加上了后,password还是在这里????!!!!为什么?!
先不理!!
我们发现后面两个时间为null?为什么?因为数据库里的命名和实体类里的不一样
配置yml文件中添加://开启驼峰命名和下划线命令的自动转换 mybatis: configuration: map-underscore-to-camel-case: true
发现已经有数据了
2.优化获取用户详细信息的代码
因为我们在拦截器里已经解析了Token,所以在用户获取详细信息时,不用再次解析Token了。
我们需要复用拦截器中的代码,那如何能做到呢
使用ThreadLocal对象优化
作用:提供线程的局部变量
ThreadLocal
- 使用set()/get()存取数据
- 使用TreadLocal存储的数据,线程安全(与局部变量一样,每个线程自己玩自己的)
- 用完记得清除数据(自定义remove方法)
在Test里创建一个测试类ThreadLocalTest .java
package org.exampletest;
import org.junit.jupiter.api.Test;
public class ThreadLocalTest {
@Test
public void testThreadLocalSetAndGet() {
//提供一个ThreadLocal对象(线程局部对象)
ThreadLocal tl = new ThreadLocal();
//开启两个线程
new Thread(()->{
tl.set("笑言");
System.out.println(Thread.currentThread().getName()+":"+tl.get());
System.out.println(Thread.currentThread().getName()+":"+tl.get());
System.out.println(Thread.currentThread().getName()+":"+tl.get());
},"蓝色").start();
new Thread(()->{
tl.set("药尘");
System.out.println(Thread.currentThread().getName()+":"+tl.get());
System.out.println(Thread.currentThread().getName()+":"+tl.get());
System.out.println(Thread.currentThread().getName()+":"+tl.get());
},"绿色").start();
}
}
与当前的需求有什么关系呢?
假如程序中有这几个
都有add的方法且都需要userId信息,我们就需要在每个里面都声明一个userId
使用ThreadLocal优化,可以维护一个全局的ThreadLocal tl对象存储用户名这类数据,有了这个对象,请求到达拦截器之后,就可以调用ThreadLocal对象的set()方法存储用户id.
所以当请求到达Controller,Service,Dao之后就可以使用tl.get()方法获取到这个用户id进行使用。
每个Controller,Controller,Service,Dao在容器当中都是单例的,我们怎么知道用户id是哪个呢?
借助ThreadLocal完成两个事情:
- 减少参数的传递
- 在同一个线程间进行共享数据
UserController.java类
使用ThreadLocal对象获取用户id(为了使用方便,封装到工具类里),不用再次解析Token
@GetMapping("/userInfo")
public Result<User> userInfo(/*@RequestHeader(name="Authorization") String token*/){
//根据用户名查询用户
// Map<String,Object>map=JwtUtil.parseToken(token);
Map<String,Object>map = ThreadLocalUtil.get();
String username=(String)map.get("username");//map.get("username")是Object类型,我们知道存username是String,强转
User user=userService.findByUserName(username);
return Result.success(user);//把user响应给浏览器
}
工具类
ThreadLocalUtil.java
package org.exampletest.utils;
import java.util.HashMap;
import java.util.Map;
/**
* ThreadLocal 工具类
*/
@SuppressWarnings("all")
public class ThreadLocalUtil {
//提供ThreadLocal对象,
private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();
//根据键获取值
public static <T> 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();
}
}
在拦截器里把业务数据存储到ThreadLocal中,并且需要在请求完成之后清除数据,防止内存泄漏
//把业务数据存储到ThreadLocal中
ThreadLocalUtil.set(claims);
完整代码:
package org.exampletest.interceptors;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.exampletest.pojo.Result;
import org.exampletest.utils.JwtUtil;
import org.exampletest.utils.ThreadLocalUtil;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler){
//令牌验证
String token=request.getHeader("Authorization");
//验证token
try{
Map<String, Object> claims= JwtUtil.parseToken(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();
}
}
3.更新用户基本信息
查看接口文档
得到请求路径,请求方式,请求参数,响应数据等
实现思路
在UserController中添加一个方法,方法上添加注解@PutMapping("/update"),因为这个方法是put请求方式。
浏览器将来会给我们提供id,nickname,email这样的数据并且是在请求体中以json格式给我们携带过来的,所以就把这些数据封装到一个实体类对象User里面进行接收。为了让我们的框架能够自动的把请求体里面的json数据转换成一个实体类对象,需要在参数前面添加一个注解@RequestBoby
@PutMapping("/update")
public Result update(@RequestBody User user){
userService.update(user);
return Result.success();
}
接下来在方法体内,只需要调用service层的方法完成更新就可以了,所以service层也要提供对应的更新的相关方法,在mapper层也要执行对应的sql(username,用户不能进行修改;updatetime更新时间也要进行修改)
service层:
void update(User user);
实现接口:UserServiceImpl.java
@Override
public void update(User user) {
user.setUpdateTime(now());
userMapper.update(user);
}
mapper层
@Update("update user set nickname=#{nickname},email=#{email},update_time=#{updateTimme} where id=#{id}")
void update(User user);
在Body里面添加
遇到的问题?(为解决版)
数据库中user表:
参数校验
我们把请求的参数封装到一个实体类对象user里面
对实体参数完成校验
1.在实体类成员变量上添加 Validation提供的注解,对指定的属性值完成校验
2.添加完指定注解之后,在实体类参数前添加注解@Validated(使得实体类中属性上的注解生效)
@PutMapping("/update")
public Result update(@RequestBody @Validated User user){
userService.update(user);
return Result.success();
}
此时校验已经生效了(虽然我遇到了问题,但是请求是成功的)
4.更新用户头像
接口文档
实现
需要从RequestParam里面获取到这个数据
@URL校验是不是一个url地址
参数里没有id,而sql语句中需要根据用户名进行查询,使用拦截器解析得的,ThreadLocal中的数据
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam @URL String avatarUrl){
Map<String,Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
userService.updateAvatar(avatarUrl,id);
return Result.success();
}
service层
//更新头像
void updateAvatar(String avatarUrl,Integer id);
实现接口.Impl
@Override
public void updateAvatar(String avatarUrl, Integer 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) ;
5.更新用户密码
接口文档
实现
在UserController里添加一个方法,方法上添加@PatchMapping("/updatePwd")指定访问的路径
方法上需要声明一个Map类型的参数params,用来接收前端提交的json参数。
在前面更新用户信息时,也接收json参数,当时是声明一个user实体对象来接收。当时传递的json数据中的键名和实体类属性名一样。
现在请求参数中键名是old_pwd,new_pwd,re_pwd等,没有和实体类属性名一致,所以需要声明一个map集合来接收参数,到时候MVC框架会自动帮我们把json数据转换成map集合对象
添加@RequestBodyMVC框架才会自动读取请求体里面的数据,然后转换成map集合对象
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody Map<String,String> params){
//1.校验参数,没有提供相应的注解能满足,需要手动校验参数
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<String,Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User loginUser = userService.findByUserName(username);
if(! loginUser.getPassword().equals(Md5Util.getMD5String(oldPwd))){
return Result.error("原密码不正确");
}
//校验newPwd与rePwd是否一致
if(!newPwd.equals(rePwd)){
return Result.error("两次输入的新密码不一致");
}
//2.调用service完成密码更新
Integer id = (Integer) map.get("id");
userService.updatePwd(newPwd,id);
return Result.success();
}
//更新密码
void updatePwd(String newPwd,Integer id);
@Override
public void updatePwd(String newPwd, Integer id) {
usermapper.updatePwd(Md5Util.getMD5String(newPwd),id);
}
@Update("update user set password=#{md5String},update_time=now() where id=#{id}")
void updatePwd(String md5String, Integer id);