
springboot3+vue3实战(大事件工程)
为了巩固一些基础知识,所以现在我重新开一个小项目,重点还是深入了解一下原理和补全一些之前没有关注的知识点。
1、业务范围
2、基础架构构建
3、用户模块开发
4、分类模块开发
1、业务范围
主要关注的是登陆注册的原理
简单的分页查询
前端知识补齐
2、基础架构构建
2.1、创建maven项目
直接新建一个maven项目,选择的脚手架是quickstart
注意的是我这边idea一直默认识别自己的maven,需要在创建之前先配置好自己的maven路径。
2.2、引入基础依赖
创建好的项目非常干净,我们把pom里面默认引入的junit也删了,增加自己的配置
添加了夫工程配置
web的依赖、mybatis的依赖、mysql驱动、lombok
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.0</version>
</parent>
<groupId>com.hm</groupId>
<artifactId>big-event</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>big-event</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- web依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
2.3、基础架构搭建和简单配置
创建resources目录和下面的application.yml,做好数据源的配置。
server:
port: 8888
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://100.66.1.1:33306/big_event?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: bigevent
password: Sz9227328
再创建好mvc架构的目录。
2.4、启动测试
改造启动类:修改名字,修改为spring boot启动:src/main/java/com/hm/BigEventApplication.java
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Hello world!
*
*/
@SpringBootApplication
public class BigEventApplication
{
public static void main( String[] args ){
SpringApplication.run(BigEventApplication.class, args);
}
}
启动后发现报错:java: 没有为模块 'big-event' 指定 JDK
处理办法:
在模块配置里给模块配置好jdk,
在启动就正常。
3、用户模块开发
一个6个接口开发:
3.1、注册
请求路径:/user/register 请求方式:POST 接口描述:该接口用于注册新用户
请求数据样例:username=zhangsan&password=123456
响应数据样例:
{ "code": 0, "message": "操作成功", "data": null }
controller:
@Autowired
private UserService userService;
/***
* 注册
* @return
*/
@PostMapping("/register")
public Result register(String username, String password) {
// 先判断是有重复的用户名
User user = userService.findUserByUsername(username);
if (user == null){// 没有重复
userService.insertUser(username, password);
return Result.success("注册成功");
} else { // 有重复
return Result.error("用户名重复");
}
}
src/main/java/com/hm/service/UserService.java
/**
* 根据用户名查询用户
* @param username
* @return
*/
User findUserByUsername(String username);
/**
* 注册用户
* @param username
* @param password
*/
void insertUser(String username, String
src/main/java/com/hm/service/impl/UserServiceImpl.java
@Autowired
private UserMapper userMapper;
@Override
public User findUserByUsername(String username) {
return userMapper.findUserByUsername(username);
}
@Override
public void insertUser(String username, String password) {
// 把密码加密一下
password = Md5Util.getMD5String(password);
userMapper.insertUser(username, password);
}
src/main/java/com/hm/mapper/UserMapper.java
@Select("select * from user where username = #{username}")
User findUserByUsername(String username);
@Select("insert into user(username, password, create_time, update_time)" +
" values(#{username}, #{password}, now(), now())")
void insertUser(String username, String password);
用postman测试的时候出现问题,数据可以成果入库,但是返回406
{
"timestamp": "2025-06-14T04:55:08.079+00:00",
"status": 406,
"error": "Not Acceptable",
"path": "/user/register"
}
查看日志:
Resolved [org.springframework.web.HttpMediaTypeNotAcceptableException: No acceptable representation]
原因是返回的Result没有添加get和set导致
添加后问题解决。
package com.hm.pojo;
//统一响应结果
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result<T> {
private Integer code;//业务状态码 0-成功 1-失败
private String message;//提示信息
private T data;//响应数据
//快速返回操作成功响应结果(带响应数据)
public static <E> Result<E> success(E data) {
return new Result<>(0, "操作成功", data);
}
//快速返回操作成功响应结果
public static Result success() {
return new Result(0, "操作成功", null);
}
public static Result error(String message) {
return new Result(1, message, null);
}
}
postman返回正常。
3.1.1、参数校验
3.1.1.1、springValication和全局异常处理器
参数校验要求输入的用户名密码长度是5到16位的非空字符串,用if判断不优雅,可以用spring validation实现。
先引入依赖:
<!-- spring validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
在入参加上@Pattern注解,在controller加上@Validated注解。
@RestController
@RequestMapping("/user")
@Validated
public class UserController {
@Autowired
private UserService userService;
/***
* 注册
* @return
*/
@PostMapping("/register")
public Result register(@Pattern(regexp = "^\\S{5,16}$", message = "用户名格式错误") String username,
@Pattern(regexp = "^\\S{5,16}$", message = "密码格式错误") String password) {
// 先判断是有重复的用户名
User user = userService.findUserByUsername(username);
if (user == null){// 没有重复
userService.insertUser(username, password);
return Result.success("注册成功");
} else { // 有重复
return Result.error("用户名重复");
}
}
}
但是这样有问题,spring validation在判断入参格式有问题后,会直接抛出异常,前端收到的是一个500的报错,没法获取到具体的报错信息。
后台报错
jakarta.validation.ConstraintViolationException: register.username: 用户名格式错误
at org.springframework.validation.beanvalidation.MethodValidationInterceptor.invoke(MethodValidationInterceptor.java:170) ~[spring-context-6.2.7.jar:6.2.7]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.2.7.jar:6.2.7]
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:728) ~[spring-aop-6.2.7.jar:6.2.7]
at com.hm.controller.UserController$$SpringCGLIB$$0.register(<generated>) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:258) ~[spring-web-6.2.7.jar:6.2.7]
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:191) ~[spring-web-6.2.7.jar:6.2.7]
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118) ~[spring-webmvc-6.2.7.jar:6.2.7]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:986) ~[spring-webmvc-6.2.7.jar:6.2.7]
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:891) ~[spring-webmvc-6.2.7.jar:6.2.7]
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-6.2.7.jar:6.2.7]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089) ~[spring-webmvc-6.2.7.jar:6.2.7]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979) ~[spring-webmvc-6.2.7.jar:6.2.7]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014) ~[spring-webmvc-6.2.7.jar:6.2.7]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:914) ~[spring-webmvc-6.2.7.jar:6.2.7]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:590) ~[tomcat-embed-core-10.1.41.jar:6.0]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885) ~[spring-webmvc-6.2.7.jar:6.2.7]
at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658) ~[tomcat-embed-core-10.1.41.jar:6.0]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51) ~[tomcat-embed-websocket-10.1.41.jar:10.1.41]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-6.2.7.jar:6.2.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.7.jar:6.2.7]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-6.2.7.jar:6.2.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.7.jar:6.2.7]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-6.2.7.jar:6.2.7]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:116) ~[spring-web-6.2.7.jar:6.2.7]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:483) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:116) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:398) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:903) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1740) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1189) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:658) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63) ~[tomcat-embed-core-10.1.41.jar:10.1.41]
at java.base/java.lang.Thread.run(Thread.java:842) ~[na:na]
postman返回数据:
{
"timestamp": "2025-06-14T05:18:58.503+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/user/register"
}
这显然不是我们要的。所以要创建一个全局异常处理器,把报错的信息封装到Result对象里面去。src/main/java/com/hm/exception/GlobalExceptionHandler.java
package com.hm.exception;
import com.hm.pojo.Result;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
// 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public Result handleException(Exception e) {
e.printStackTrace();
return Result.error(StringUtils.hasLength(e.getMessage())? e.getMessage() : "服务更新中,请稍后再试");
}
}
还遇到一个问题,@Validated注解在处理实体类的非空校验的时候,会出现不同接口对实体类的非空要求不一样的情况。这时候需要用到分组校验。直接去看4.1.
3.2、登录
请求路径:/user/login 请求方式:POST 接口描述:该接口用于登录
请求数据样例:username=zhangsan&password=123456
响应数据样例:{ "code": 0, "message": "操作成功", "data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjUsInVzZXJuYW1lIjoid2FuZ2JhI n0sImV4cCI6MTY5MzcxNTk3OH0.pE_RATcoF7Nm9KEp9eC3CzcBbKWAFOL0IsuMNjnZ95M" }
src/main/java/com/hm/controller/UserController.java
/***
* 登录
* @return
*/
@PostMapping(value = "/login")
public Result login(@Pattern(regexp = "^\\S{5,16}$", message = "用户名格式错误") String username,
@Pattern(regexp = "^\\S{5,16}$", message = "密码格式错误") String password) {
User user = userService.findUserByUsername(username);
if (user == null){
return Result.error("该用户不存在");
} else {
if (Md5Util.checkPassword(password, user.getPassword())){
return Result.success("登录成功");
} else {
return Result.error("密码错误");
}
}
}
这里涉及到一个jwt的概念,登录成果以后会生成一个token,后续的前端接口都需要携带token请求。
用户登录成功后,系统会自动下发JWT令牌,然后在后续的每次请求中,浏览器都需要在请求头header中携带 到服务端,请求头的名称为 Authorization,值为 登录时下发的JWT令牌。 如果检测到用户未登录,则http响应状态码为401
3.2.1、jwt令牌
一个 JWT 由三部分组成,用 .
分隔:
1、Header:包含 Token 类型("typ": "JWT"
)和签名算法(如 HS256
、RS256
):
{
"alg": "HS256", // 签名算法(如 HS256、RS256)
"typ": "JWT" // Token 类型
}
2、Payload(负载):存放实际数据(如用户ID、角色、过期时间),分为三类:
Registered Claims(标准字段)
如iss
(签发者)、exp
(过期时间)、sub
(主题)。Public Claims(公共字段)
可自定义,但需避免冲突(建议用 IANA 注册的字段)。Private Claims(私有字段)
用于业务数据(如userId
、role
)。
示例:
{
"sub": "1234567890", // 用户ID
"name": "John Doe", // 用户名
"admin": true, // 角色
"exp": 1710000000 // 过期时间(Unix 时间戳)
}
3、Signature(签名):对 Header
和 Payload
的签名,防止篡改:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secretKey // 密钥(HS256)或私钥(RS256)
)
pom直接使用现成的就完事了,引入java-jwt,顺便用个单元测试
<!-- java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<!-- springboot的单元测试-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
引入工具类src/main/java/com/hm/utils/JwtUtil.java
package com.hm.utils;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import java.util.Map;
public class JwtUtil {
private static final String KEY = "itheima";
//接收业务数据,生成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(KEY));
}
//接收token,验证token,并返回业务数据
public static Map<String, Object> parseToken(String token) {
return JWT.require(Algorithm.HMAC256(KEY))
.build()
.verify(token)
.getClaim("claims")
.asMap();
}
}
修改一些登录的逻辑就行。src/main/java/com/hm/controller/UserController.java
/***
* 登录
* @return
*/
@PostMapping(value = "/login")
public Result login(@Pattern(regexp = "^\\S{5,16}$", message = "用户名格式错误") String username,
@Pattern(regexp = "^\\S{5,16}$", message = "密码格式错误") String password) {
User user = userService.findUserByUsername(username);
if (user == null){ // 没有该用户
return Result.error("该用户不存在");
} else { // 有该用户
if (Md5Util.checkPassword(password, user.getPassword())){ // 密码正确
HashMap<String, Object> map = new HashMap<String, Object>();
map.put("username", user.getUsername());
map.put("id", user.getId());
String token = JwtUtil.genToken(map);
return Result.success(token);
} else { // 密码错误
return Result.error("密码错误");
}
}
}
查看token生成是否成功。
{
"code": 0,
"message": "操作成功",
"data": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJjbGFpbXMiOnsiaWQiOjEsInVzZXJuYW1lIjoiemhhbmdzYW4ifSwiZXhwIjoxNzQ5OTMxNDg0fQ.fcL0Vlj4auc103q7JJP-dTtMoJzrPz6R5LMnr2U84Wg"
}
3.2.2、拦截器实现鉴权校验
首先创建一个拦截器处理登录信息的鉴权,并把登录注册接口排除。src/main/java/com/hm/interceptor/LoginInterceptor.java
package com.hm.interceptor;
import com.hm.utils.JwtUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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) throws Exception {
String authorization = request.getHeader("Authorization");
if (authorization == null) {
response.setStatus(401);
return false;
}
try {
Map<String, Object> claims = JwtUtil.parseToken(authorization);
return true;
} catch (Exception e) {
response.setStatus(401);
return false;
}
}
}
还需要把自定义的拦截器注册到spring:src/main/java/com/hm/config/WebConfig.java
package com.hm.config;
import com.hm.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor interceptor;
/**
* 配置拦截器
*
* 该方法用于向Spring MVC的InterceptorRegistry中添加拦截器,以配置哪些请求需要被拦截处理
* 主要目的是设置拦截器interceptor,同时排除用户登录和注册页面,避免这些请求被拦截
*
* @param registry InterceptorRegistry实例,用于注册拦截器
*/
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器,同时指定不应用拦截器的路径
registry.addInterceptor(interceptor).excludePathPatterns("/user/login", "/user/register");
}
}
这样就可以实现拦截所有请求,校验鉴权信息后再决定是否放行。
3.3、获取用户详细信息
请求路径:/user/userInfo 请求方式:GET 接口描述:该接口用于获取当前已登录用户的详细信息
请求参数:无
响应数据样例:
{
"code": 0,
"message": "操作成功",
"data": {
"id": 5,
"username": "wangba",
"password": "e10adc3949ba59abbe56e057f20f883e",
"nickname": "",
"email": "",
"userPic": "",
"createTime": "2023-09-02 22:21:31",
"updateTime": "2023-09-02 22:21:31"
}
}
思路:从header里面获取到token,解析出用户名再去查询出数据。
src/main/java/com/hm/controller/UserController.java
/***
* 获取用户信息
* @return
*/
@GetMapping(value = "/userInfo")
public Result getUserInfo(@RequestHeader("Authorization") String authorization) {
Map<String, Object> map = JwtUtil.parseToken(authorization);
User user = userService.findUserByUsername((String) map.get("username"));
return Result.success(user);
}
逻辑很简单,但是有两个知识点。一个是如何在实体类转换为JSON的时候忽略某个字段?二是如何开启mybatis的驼峰命名?
实体类转换为JSON的时候忽略某个字段:在实体类的字段上加上@JsonIgnore
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private Integer id;//主键ID
private String username;//用户名
@JsonIgnore
private String password;//密码
private String nickname;//昵称
private String email;//邮箱
private String userPic;//用户头像地址
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
如何开启mybatis的驼峰命名:配置文件上配置:
mybatis:
configuration:
map-underscore-to-camel-case: true
另外还有一个知识点,就是接口里是用JwtUtil解析出用户名,这一步在登陆拦截器里已经做过了,这里重复了,不太优雅。
办法就是利用ThreadLocal存储不同登陆用户的信息。
3.3.1、ThreadLocal核心特性
线程隔离
每个线程拥有独立的变量副本,互不干扰。
示例:线程 A 修改自己的副本,不会影响线程 B 的副本。
避免同步
由于变量不共享,无需使用
synchronized
或锁,天然线程安全。
内存结构
每个线程的
Thread
类内部维护一个ThreadLocalMap
(类似哈希表)。ThreadLocal
作为Key
,线程的变量副本作为Value
存储。
新增工具类来实现:src/main/java/com/hm/utils/ThreadLocalUtil.java
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,最后还需要把里面的数据清理,以防止内存泄露。
import com.hm.utils.JwtUtil;
import com.hm.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;
@Component
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
// 创建登录拦截器
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("--------开始执行--------preHandle----------------");
String authorization = request.getHeader("Authorization");
if (authorization == null) {
response.setStatus(401);
return false;
}
try {
// 解析token是否成功
Map<String, Object> claims = JwtUtil.parseToken(authorization);
// 把数据存储到ThreadLocal
ThreadLocalUtil.set(claims);
return true;
} catch (Exception e) {
response.setStatus(401);
return false;
} finally {
log.info("--------结束执行--------preHandle----------------");
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("--------开始执行--------afterCompletion----------------");
ThreadLocalUtil.remove();
log.info("--------结束执行--------afterCompletion----------------");
}
}
重新修改获取用户信息的controller
/***
* 获取用户信息
* @return
*/
@GetMapping(value = "/userInfo")
public Result getUserInfo(/** @RequestHeader("Authorization") String authorization **/) {
// Map<String, Object> map = JwtUtil.parseToken(authorization);
// 改为从ThreadLocal中获取
Map<String, Object> map = ThreadLocalUtil.get();
User user = userService.findUserByUsername((String) map.get("username"));
return Result.success(user);
}
3.4、更新用户基本信息
请求路径:/user/update
请求方式:PUT
接口描述:该接口用于更新已登录用户的基本信息(除头像和密码)
请求数据样例:
响应数据样例:
{
"code": 0,
"message": "操作成功",
"data": null
}
controller:src/main/java/com/hm/controller/UserController.java
/**
* 更新用户信息
*
* 该方法通过HTTP PUT请求接收一个用户对象,用于更新用户信息
* 使用@PutMapping注解指定请求路径和方法,@RequestBody注解用于将HTTP请求正文转换为User对象,
* @Validated注解用于验证传入的User对象属性是否符合规范
*
* @param user 待更新的用户对象,需要通过JSON格式传入请求体中
* @return 返回一个Result对象,表示更新操作的结果
*/
@PutMapping("/update")
public Result update(@RequestBody @Validated User user){
userService.update(user);
return Result.success();
}
service:src/main/java/com/hm/service/UserService.java
/**
* 更新用户
* @param user
*/
void update(User user);
src/main/java/com/hm/service/impl/UserServiceImpl.java
/**
* 更新用户信息
* @param user
*/
@Override
public void update(User user) {
userMapper.update(user);
}
mapper:src/main/java/com/hm/mapper/UserMapper.java
@Select("update user set email = #{email},nickname = #{nickname}, update_time = now() where id = #{id}")
void update(User user);
最后看看后端是如何参数校验的:首先是在实体类加上规则,再去入参上加上注解@Validated
package com.hm.pojo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@NotNull
private Integer id;//主键ID
private String username;//用户名
@JsonIgnore
private String password;//密码
@NotEmpty
@Pattern(regexp = "^\\S{5,16}$", message = "昵称格式错误")
private String nickname;//昵称
@Email
private String email;//邮箱
private String userPic;//用户头像地址
private LocalDateTime createTime;//创建时间
private LocalDateTime updateTime;//更新时间
}
测试不规范传参的后果:
3.5、更新用户头像
请求路径:/user/updateAvata
请求方式:PATCH
接口描述:该接口用于更新已登录用户的头像
请求数据样例:
avatarUrl=
https://big-event-gwd.oss-cn-beijing.aliyuncs.com/9bf1cf5b-1420-4c1b-91ade0f4631cbed4.png
响应数据样例:
{
"code": 0,
"message": "操作成功",
"data": null
}
controller:src/main/java/com/hm/controller/UserController.java
@PatchMapping("/updateAvatar")
public Result updateAvatar(@RequestParam String avatar){
userService.updateAvata(avatar);
return Result.success();
}
service:src/main/java/com/hm/service/UserService.java
/**
* 更新用户头像
* @param avatar
*/
void updateAvata(String avatar);
serviceimpl:src/main/java/com/hm/service/impl/UserServiceImpl.java
@Override
public void updateAvata(String avatar) {
Map<String,Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
userMapper.updateAvata(avatar,id);
}
mapper:src/main/java/com/hm/mapper/UserMapper.java
@Select("update user set user_pic = #{avatar}, update_time = now() where id = #{id}")
void updateAvata(String avatar,Integer id);
测试:这次的参数是拼接在url里面的,参数在params里面,所以controller要用@RequestParam接收。
3.6、更新用户密码
请求路径:/user/updatePwd
请求方式:PATCH
接口描述:该接口用于更新已登录用户的密码
请求数据样例:
{
"old_pwd":"123456",
"new_pwd":"234567",
"re_pwd":"234567"
}
响应数据样例:
{
"code": 0,
"message": "操作成功",
"data": null
}
4、分类模块开发
还遇到一个问题,@Validated注解在处理实体类的非空校验的时候,会出现不同接口对实体类的非空要求不一样的情况。这时候需要用到分组校验。
4.1、Validated分组校验
实体类:
package com.hm.pojo;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Category {
@NotNull(message = "ID不能为空", groups = {Category.Update.class})
private Integer id;//主键ID
@NotEmpty(message = "分类名称不能为空", groups = {Category.Add.class, Category.Update.class})
private String categoryName;//分类名称
@NotEmpty(message = "分类别名不能为空", groups = {Category.Add.class, Category.Update.class})
private String categoryAlias;//分类别名
private Integer createUser;//创建人ID
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime createTime;//创建时间
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
private LocalDateTime updateTime;//更新时间
public interface Add {
}
public interface Update {
}
}
接口处理:
package com.hm.controller;
@RestController
@RequestMapping(value = "/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
@PostMapping
public Result add(@RequestBody @Validated(Category.Add.class) Category category){
Category categoryadd = categoryService.add(category);
return Result.success(categoryadd);
}
@PutMapping
public Result update(@RequestBody @Validated(Category.Update.class) Category category){
return Result.success(categoryService.update(category));
}
}