一、项目整体信息
技术栈
- SpringBoot 3
- SpringMVC
- MyBatis
- MySQL
- JWT(登录令牌)
- Validation(参数校验)
- ThreadLocal(线程内存用户)
- MD5(密码加密)
统一返回结果 Result
java
运行
@Data
public class Result {
private Integer code; // 0成功 1失败
private String msg;
private Object data;
public static Result success(){...}
public static Result success(Object data){...}
public static Result error(String msg){...}
}
二、核心技术详解(带介绍 + 注解 + 用法)
1)Validation 参数校验
作用
自动校验前端传参是否合法,不用手写 if 判断
核心注解
@Validated- 写在Controller 类上:开启普通参数校验
- 写在实体参数前:开启对象内部字段校验
@Pattern(regexp="^\\S{5,16}$"):正则匹配@NotNull:不能为 null@NotEmpty:字符串不能 null 且不能为空@Email:邮箱格式@URL:必须是合法网址
生效规则(必背)
- 普通参数(String/Integer)→ Controller 类上加 @Validated
- 实体对象(User)→ 参数前加 @Validated
- 两个都加,实体类校验才生效
2)JWT 登录认证
作用
无状态登录,前端带令牌访问,后端识别身份
结构
Header.Payload.Signature
JWT 工具类代码
java
运行
//生成token
public static String genToken(Map<String, Object> claims) {
return JWT.create()
.withClaim("claims", claims)
.withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12))
.sign(Algorithm.HMAC256("itheima"));
}
//解析token
public static Map<String, Object> parseToken(String token) {
return JWT.require(Algorithm.HMAC256("itheima"))
.build()
.verify(token)
.getClaim("claims")
.asMap();
}
3)登录拦截器 LoginInterceptor
作用
请求进入 Controller 前,统一校验是否登录
执行顺序
请求 → 拦截器 → Controller → Service → Mapper
核心逻辑
- 拿请求头
Authorization的 token - 解析 token 是否合法
- 存用户到 ThreadLocal
- 放行 / 拦截
拦截器代码
java
运行
@Component
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("Authorization");
try {
Map<String, Object> map = JwtUtil.parseToken(token);
ThreadLocalUtil.set(map);
return true; //放行
} catch (Exception e) {
response.setStatus(401);
return false; //拦截
}
}
@Override
public void afterCompletion(...) {
ThreadLocalUtil.remove(); //必须清除
}
}
关键知识点
return true:给 SpringBoot,放行去 Controllerreturn false:给 SpringBoot,拦截,不走 Controller- 拦截器不能 return Result,只能设置 401 状态码
- 拦截器异常自己 try-catch,全局异常捕获不到
4)ThreadLocal 线程本地变量
作用
同一个请求线程内安全存储用户信息,随处可拿
ThreadLocalUtil 代码
java
运行
public class ThreadLocalUtil {
private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL = new ThreadLocal<>();
public static void set(Map<String, Object> map) {
THREAD_LOCAL.set(map);
}
public static Map<String, Object> get() {
return THREAD_LOCAL.get();
}
public static void remove() {
THREAD_LOCAL.remove();
}
}
关键点
- 拦截器
set - Controller/Service
get - 请求结束
remove,防内存泄漏
5)全局异常处理器
作用
统一捕获 Controller/Service/Mapper 异常,返回友好提示
代码
java
运行
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
e.printStackTrace();
return Result.error(e.getMessage());
}
}
捕获范围
- Controller 异常 ✅
- Service 异常 ✅
- Mapper 异常 ✅
- 拦截器异常 ❌ 管不到
三、用户模块所有接口(带代码 + 注解)
1)注册 POST /user/register
java
运行
@PostMapping("/register")
public Result register(
@Pattern(regexp = "^\\S{5,16}$") String username,
@Pattern(regexp = "^\\S{5,16}$") String password
) {
User u = userService.findByUserName(username);
if (u == null) {
userService.register(username, password);
return Result.success();
} else {
return Result.error("用户名已存在");
}
}
2)登录 POST /user/login
java
运行
@PostMapping("/login")
public Result<String> login(
@Pattern(regexp = "^\\S{5,16}$") String username,
@Pattern(regexp = "^\\S{5,16}$") String password
) {
User loginUser = userService.findByUserName(username);
if (loginUser == null) return Result.error("用户名不存在");
if (Md5Util.getMD5String(password).equals(loginUser.getPassword())) {
Map<String, Object> map = new HashMap<>();
map.put("id", loginUser.getId());
map.put("username", loginUser.getUsername());
String token = JwtUtil.genToken(map);
return Result.success(token);
}
return Result.error("密码错误");
}
3)获取用户信息 GET /user/userInfo
java
运行
@GetMapping("/userInfo")
public Result<User> userInfo() {
Map<String, Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User user = userService.findByUserName(username);
return Result.success(user);
}
4)更新用户信息 PUT /user/update
java
运行
@PutMapping("/update")
public Result update(@RequestBody @Validated User user) {
userService.update(user);
return Result.success();
}
5)更新头像 PATCH /user/updateAvatar
java
运行
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam @URL String avatar) {
userService.updateAvatar(avatar);
return Result.success();
}
6)修改密码 PATCH /user/updatePwd
java
运行
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody Map<String, String> params) {
String oldPwd = params.get("oldPwd");
String newPwd = params.get("newPwd");
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 user = userService.findByUserName(username);
if (!user.getPassword().equals(Md5Util.getMD5String(oldPwd))) {
return Result.error("旧密码错误");
}
if (!newPwd.equals(rePwd)) {
return Result.error("两次密码不一致");
}
userService.updatePwd(newPwd);
return Result.success();
}
四、请求方法规范(背会)
- 查询 →
@GetMapping - 新增 →
@PostMapping - 全量更新 →
@PutMapping - 局部更新 →
@PatchMapping - 删除 →
@DeleteMapping
五、整套流程逻辑(最清晰总结)
- 前端登录 → 后端验证密码 → 生成 JWT 令牌返回
- 前端每次请求在请求头带:
Authorization: Bearer token - 拦截器 统一校验 token
- 合法 → 存 ThreadLocal → 放行到 Controller
- 不合法 → 返回 401 → 拦截
- Controller/Service 从 ThreadLocal 拿当前用户
- 异常统一由全局异常处理器返回 Result
- 请求结束 → 清除 ThreadLocal