数据库构建
创建数据库

用户表创建
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
`password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码(加密存储)',
`email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '邮箱',
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '手机号',
`user_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色code',
`name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '姓名',
`avatar` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '头像',
`status` tinyint NULL DEFAULT 1 COMMENT '状态(0:禁用,1:正常)',
`created_at` datetime NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` datetime NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`sex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '性别',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_username`(`username` ASC) USING BTREE,
UNIQUE INDEX `uk_email`(`email` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户信息表' ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'test', '$2a$10$iul6jocLsH.A4gN1QUpgDexDq6KO89syHjUkRD3NbA1L6CTVrNRMO', '1796145118@qq.com', '15345678111', 'USER', 'ainio', '/files/bussiness/user_avatar/1763198720591.jpg', 1, '2025-05-14 10:03:12', '2025-11-15 17:25:26', '男');
INSERT INTO `user` VALUES (2, 'admin', '$2a$10$JXCy/159QjA5hJBzy6DYmeOhQSb00nmjeMPJdrfUIUc1HUPYZ98ea', '123456789@qq.com', '13345678910', 'ADMIN', 'admin', '/files/bussiness/user_avatar/1763198163777.jpg', 1, '2025-05-14 11:05:08', '2025-11-15 17:16:04', '男');
INSERT INTO `user` VALUES (4, 'test1', '$2a$10$WM6WQHi9AwRkORfy9TE8PerZ/vRnkK81WUv1d.3KvKvdEzUqqGTiy', '1111@qq.com', '13123456789', 'USER', NULL, '/files/bussiness/user_avatar/1759645666149.jpg', 1, '2025-10-05 14:27:30', '2025-10-05 14:32:46', '男');
SET FOREIGN_KEY_CHECKS = 1;用户登录接口实现
用户实体
package top.yxqz.springboot.entity;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 用户实体类
* @author 余小小
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
@Schema(description = "用户实体类")
public class User {
@TableId(type = IdType.AUTO)
@Schema(description = "用户ID")
private Long id;
@Schema(description = "用户名")
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 50, message = "用户名长度必须在3到50个字符之间")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")
private String username;
@Schema(description = "密码")
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 100, message = "密码长度必须在6到100个字符之间")
private String password;
@Schema(description = "邮箱")
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
@Size(max = 100, message = "邮箱长度不能超过100个字符")
private String email;
@Schema(description = "手机号")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
private String phone;
@Schema(description = "用户类型")
@TableField("user_type")
private String userType;
@Schema(description = "姓名")
@Size(max = 50, message = "姓名长度不能超过50个字符")
private String name;
@Schema(description = "头像")
@Size(max = 200, message = "头像路径长度不能超过200个字符")
private String avatar;
@Schema(description = "状态(0:禁用,1:正常)")
private Integer status;
@Schema(description = "创建时间")
@TableField(value = "created_at", fill = FieldFill.INSERT)
private LocalDateTime createdAt;
@Schema(description = "更新时间")
@TableField(value = "updated_at", fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedAt;
@Schema(description = "性别")
private String sex;
/**
* 是否为管理员
*/
public boolean isAdmin() {
return "ADMIN".equals(this.userType);
}
/**
* 是否为普通用户
*/
public boolean isUser() {
return "USER".equals(this.userType);
}
/**
* 是否为正常状态
*/
public boolean isActive() {
return this.status != null && this.status == 1;
}
/**
* 是否被禁用
*/
public boolean isDisabled() {
return this.status != null && this.status == 0;
}
/**
* 获取用户类型显示名称
*/
public String getUserTypeDisplayName() {
if (userType == null) {
return "未知";
}
switch (userType) {
case "ADMIN":
return "管理员";
case "USER":
return "普通用户";
default:
return "未知";
}
}
/**
* 获取用户状态显示名称
*/
public String getStatusDisplayName() {
if (status == null) {
return "未知";
}
switch (status) {
case 1:
return "正常";
case 0:
return "禁用";
default:
return "未知";
}
}
/**
* 获取显示名称(优先使用姓名,其次用户名)
*/
public String getDisplayName() {
if (name != null && !name.trim().isEmpty()) {
return name;
}
return username;
}
}用户类型枚举
package top.yxqz.springboot.enums;
import lombok.Getter;
/**
* 用户类型枚举
* @author 余小小
*/
@Getter
public enum UserType {
USER("USER", "普通用户"),
ADMIN("ADMIN", "管理员");
private final String code;
private final String description;
UserType(String code, String description) {
this.code = code;
this.description = description;
}
/**
* 根据代码获取枚举
*/
public static UserType fromCode(String code) {
if (code == null) {
return null;
}
for (UserType type : UserType.values()) {
if (type.getCode().equals(code)) {
return type;
}
}
throw new IllegalArgumentException("未知的用户类型代码: " + code);
}
/**
* 验证用户类型代码是否有效
*/
public static boolean isValidCode(String code) {
if (code == null) {
return false;
}
for (UserType type : UserType.values()) {
if (type.getCode().equals(code)) {
return true;
}
}
return false;
}
}用户状态枚举
package top.yxqz.springboot.enums;
import lombok.Getter;
/**
* 用户状态枚举
* @author 余小小
*/
@Getter
public enum UserStatus {
DISABLED(0, "禁用"),
NORMAL(1, "正常");
private final Integer code;
private final String description;
UserStatus(Integer code, String description) {
this.code = code;
this.description = description;
}
/**
* 根据代码获取枚举
*/
public static UserStatus fromCode(Integer code) {
if (code == null) {
return null;
}
for (UserStatus status : UserStatus.values()) {
if (status.getCode().equals(code)) {
return status;
}
}
throw new IllegalArgumentException("未知的用户状态代码: " + code);
}
/**
* 验证用户状态代码是否有效
*/
public static boolean isValidCode(Integer code) {
if (code == null) {
return false;
}
for (UserStatus status : UserStatus.values()) {
if (status.getCode().equals(code)) {
return true;
}
}
return false;
}
}用户Mapper接口
package top.yxqz.kindergraten.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import top.yxqz.kindergraten.entity.User;
/**
* 用户数据访问层
* @author 余小小
*/
@Mapper
public interface UserMapper extends BaseMapper<User> {
}用户登录服务
- 用户查询
使用 LambdaQueryWrapper 构建查询条件
支持通过用户名或邮箱进行登录(OR 条件)
调用 userMapper.selectOne() 查询用户 - 验证逻辑
用户存在性检查:如果查询结果为 null,抛出"用户不存在"异常
密码验证:使用 BCryptPasswordEncoder 的 matches 方法验证密码
用户状态检查:验证用户是否处于激活状态(isActive()) - Token 生成与响应构建
使用 JwtTokenUtils.generateToken() 生成 JWT 令牌
令牌包含用户 ID、用户名和用户类型信息
使用 UserConvert.entityToDetailResponse() 转换用户实体为详情响应 DTO
使用 UserConvert.buildLoginResponse() 构建登录响应 - 异常处理
使用 try-catch 结构捕获不同类型的异常
业务异常(BusinessException)直接抛出
其他异常记录日志并抛出 ServiceException - 安全特性
使用 BCrypt 算法对密码进行哈希处理和验证
支持多因素登录(用户名/邮箱)
用户状态验证防止已禁用账户登录
package top.yxqz.kindergraten.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import top.yxqz.kindergraten.dto.command.UserLoginCommandDTO;
import top.yxqz.kindergraten.dto.response.UserDetailResponseDTO;
import top.yxqz.kindergraten.dto.response.UserLoginResponseDTO;
import top.yxqz.kindergraten.entity.User;
import top.yxqz.kindergraten.exception.BusinessException;
import top.yxqz.kindergraten.exception.ServiceException;
import top.yxqz.kindergraten.mapper.UserMapper;
import top.yxqz.kindergraten.service.convert.UserConvert;
import top.yxqz.kindergraten.util.JwtTokenUtils;
import java.time.LocalDateTime;
import java.util.List;
/**
* 用户业务逻辑层
* @author ftfx
*/
@Slf4j
@Service
public class UserService {
@Resource
private UserMapper userMapper;
private final BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
/**
* 用户登录
* @param loginDTO 登录命令
* @return 登录响应
*/
public UserLoginResponseDTO login(UserLoginCommandDTO loginDTO) {
try {
// 根据用户名或邮箱查找用户
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getUsername, loginDTO.getUsername())
.or()
.eq(User::getEmail, loginDTO.getUsername());
User user = userMapper.selectOne(queryWrapper);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 验证密码
if (!passwordEncoder.matches(loginDTO.getPassword(), user.getPassword())) {
throw new BusinessException("用户名或密码错误");
}
// 检查用户状态
if (!user.isActive()) {
throw new BusinessException("账号已被禁用,请联系管理员");
}
// 生成JWT token
String token = JwtTokenUtils.generateToken(user.getId(), user.getUsername(), user.getUserType());
// 构建响应
UserDetailResponseDTO userInfo = UserConvert.entityToDetailResponse(user);
return UserConvert.buildLoginResponse(token, userInfo);
} catch (BusinessException e) {
throw e;
} catch (Exception e) {
log.error("用户登录失败", e);
throw new ServiceException("登录失败,请稍后重试");
}
}
}用户登录DTO
package top.yxqz.kindergraten.dto.command;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
/**
* 用户登录命令DTO
* @author 余小小
*/
@Data
@Schema(description = "用户登录命令")
public class UserLoginCommandDTO {
@Schema(description = "用户名或邮箱", example = "admin")
@NotBlank(message = "用户名或邮箱不能为空")
@Size(max = 100, message = "用户名或邮箱长度不能超过100个字符")
private String username;
@Schema(description = "密码", example = "123456")
@NotBlank(message = "密码不能为空")
@Size(min = 6, max = 50, message = "密码长度必须在6到50个字符之间")
private String password;
}用户登录响应
package top.yxqz.kindergraten.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 用户登录响应DTO
* @author 余小小
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "用户登录响应")
public class UserLoginResponseDTO {
@Schema(description = "用户信息")
private UserDetailResponseDTO userInfo;
@Schema(description = "访问令牌")
private String token;
@Schema(description = "角色代码")
private String roleType;
}用户详情相应
package top.yxqz.kindergraten.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
/**
* 用户详情响应DTO
* @author 余小小
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "用户详情响应")
public class UserDetailResponseDTO {
@Schema(description = "用户ID", example = "1")
private Long id;
@Schema(description = "用户名", example = "admin")
private String username;
@Schema(description = "邮箱", example = "admin@drone.com")
private String email;
@Schema(description = "姓名", example = "系统管理员")
private String name;
@Schema(description = "头像", example = "/avatars/admin.jpg")
private String avatar;
@Schema(description = "手机号", example = "13800138000")
private String phone;
@Schema(description = "性别", example = "男")
private String sex;
@Schema(description = "用户类型", example = "ADMIN")
private String userType;
@Schema(description = "用户类型显示名称", example = "管理员")
private String userTypeDisplayName;
@Schema(description = "用户状态", example = "1")
private Integer status;
@Schema(description = "用户状态显示名称", example = "正常")
private String statusDisplayName;
@Schema(description = "显示名称", example = "系统管理员")
private String displayName;
@Schema(description = "创建时间")
private LocalDateTime createdAt;
@Schema(description = "更新时间")
private LocalDateTime updatedAt;
}用户接口控制层
package top.yxqz.kindergraten.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import top.yxqz.kindergraten.common.Result;
import top.yxqz.kindergraten.dto.command.UserLoginCommandDTO;
import top.yxqz.kindergraten.dto.response.UserDetailResponseDTO;
import top.yxqz.kindergraten.dto.response.UserLoginResponseDTO;
import top.yxqz.kindergraten.service.UserService;
import top.yxqz.kindergraten.util.JwtTokenUtils;
/**
* 用户管理控制器
* @author 余小小
*/
@Tag(name = "用户管理")
@RestController
@Slf4j
@RequestMapping("/user")
public class UserController {
@Resource
private UserService userService;
/**
* 用户登录
*/
@Operation(summary = "用户登录")
@PostMapping("/login")
public Result<UserLoginResponseDTO> login(@Valid @RequestBody UserLoginCommandDTO loginDTO) {
log.info("用户登录请求: {}", loginDTO.getUsername());
UserLoginResponseDTO response = userService.login(loginDTO);
return Result.success("登录成功", response);
}
}JWT解析工具
package top.yxqz.kindergraten.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.yxqz.kindergraten.dto.response.UserDetailResponseDTO;
import java.util.Date;
/**
* JWT工具类 - 用于JWT token的生成、验证和用户信息获取
*
* 主要功能:
* 1. 生成JWT token(包含userId、username、roleType)
* 2. 验证JWT token有效性和过期检查
* 3. 从请求属性中获取当前用户信息(由JwtAuthenticationFilter设置)
*
* 使用说明:
* - Token的解析和提取由JwtAuthenticationFilter负责
* - Controller中使用getCurrentUser()和getCurrentUserId()获取当前用户信息
* - 不要直接操作token,所有token相关逻辑在Filter中处理
*
* Token传输规范:
* - 只支持标准的 Authorization: Bearer <token> 方式
*
* 安全特性:
* - 使用HMAC256算法签名
* - Token有效期7天
* - 完善的异常处理和日志记录
*/
@Slf4j
public class JwtTokenUtils {
/**
* JWT密钥
*/
private static final String SECRET = "drone_management_system_jwt_secret_key_2024";
/**
* Token过期时间(7天)
*/
private static final long EXPIRE_TIME = 7 * 24 * 60 * 60 * 1000L;
/**
* Token发行者
*/
private static final String ISSUER = "drone-management-system";
/**
* 生成JWT token
* @param userId 用户ID
* @param username 用户名
* @param roleType 角色代码
* @return JWT token
*/
public static String generateToken(Long userId, String username, String roleType) {
try {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
Date expireDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
return JWT.create()
.withClaim("userId", userId)
.withClaim("username", username)
.withClaim("roleType", roleType)
.withExpiresAt(expireDate)
.withIssuedAt(new Date())
.withIssuer(ISSUER)
.sign(algorithm);
} catch (Exception e) {
log.error("生成JWT token失败", e);
throw new RuntimeException("生成JWT token失败", e);
}
}
/**
* 验证JWT token有效性
* @param token JWT token
* @return 解码后的JWT
* @throws JWTVerificationException token验证失败
*/
public static DecodedJWT verifyToken(String token) throws JWTVerificationException {
Algorithm algorithm = Algorithm.HMAC256(SECRET);
JWTVerifier verifier = JWT.require(algorithm)
.withIssuer(ISSUER)
.build();
return verifier.verify(token);
}
/**
* 检查token是否过期
* @param token JWT token
* @return 是否过期
*/
public static boolean isTokenExpired(String token) {
try {
DecodedJWT jwt = verifyToken(token);
return jwt.getExpiresAt().before(new Date());
} catch (Exception e) {
return true;
}
}
/**
* 获取当前请求的用户ID(从RequestContextHolder获取)
* @return 当前用户ID,获取失败返回null
*/
public static Long getCurrentUserId() {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Object userId = request.getAttribute("currentUserId");
if (userId instanceof Long) {
return (Long) userId;
}
}
} catch (Exception e) {
log.error("获取当前用户ID失败", e);
}
return null;
}
/**
* 获取当前请求的用户信息(从请求属性中获取)
* 注意:此方法依赖于JwtAuthenticationFilter设置的请求属性
* @return 当前用户对象,获取失败返回null
*/
public static UserDetailResponseDTO getCurrentUser() {
try {
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
return (UserDetailResponseDTO)request.getAttribute("currentUser");
}
} catch (Exception e) {
log.error("获取当前用户对象失败", e);
}
return null;
}
/**
* 获取当前用户ID的字符串形式
* @return 当前用户ID字符串,获取失败返回null
*/
public static String getCurrentUserIdAsString() {
Long userId = getCurrentUserId();
return userId != null ? String.valueOf(userId) : null;
}
/**
* 获取当前用户类型/角色
* @return 当前用户类型,获取失败返回null
*/
public static String getCurrentUserType() {
UserDetailResponseDTO currentUser = getCurrentUser();
if(currentUser==null)return null;
return currentUser.getUserType();
}
/**
* 判断当前用户是否为管理员
* @return 是否为管理员
*/
public static boolean isAdmin() {
return "ADMIN".equals(getCurrentUserType());
}
}业务异常类
package top.yxqz.kindergraten.exception;
import lombok.Getter;
/**
* 业务异常类
* @author 余小小
*/
@Getter
public class BusinessException extends RuntimeException {
private final String code;
public BusinessException(String message) {
super(message);
this.code = "BUSINESS_ERROR";
}
public BusinessException(String code, String message) {
super(message);
this.code = code;
}
public BusinessException(String message, Throwable cause) {
super(message, cause);
this.code = "BUSINESS_ERROR";
}
public BusinessException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
}
}服务异常类
package top.yxqz.kindergraten.exception;
import lombok.Getter;
/**
* @author ftfx
*/
@Getter
public class ServiceException extends RuntimeException {
private final String code;
private final String msg;
public ServiceException(String code, String msg) {
super(msg);
this.code = code;
this.msg = msg;
}
public ServiceException(String msg) {
super(msg);
this.code = "-1";
this.msg = msg;
}
} 用户转换类
package top.yxqz.kindergraten.service.convert;
import top.yxqz.kindergraten.dto.response.UserDetailResponseDTO;
import top.yxqz.kindergraten.dto.response.UserLoginResponseDTO;
import top.yxqz.kindergraten.entity.User;
import java.time.LocalDateTime;
/**
* 用户转换类
* @author 余小小
*/
public class UserConvert {
/**
* User实体转换为详情响应DTO
* @param user User实体
* @return 用户详情响应DTO
*/
public static UserDetailResponseDTO entityToDetailResponse(User user) {
return UserDetailResponseDTO.builder()
.id(user.getId())
.username(user.getUsername())
.email(user.getEmail())
.name(user.getName())
.avatar(user.getAvatar())
.phone(user.getPhone())
.sex(user.getSex())
.userType(user.getUserType())
.userTypeDisplayName(user.getUserTypeDisplayName())
.status(user.getStatus())
.statusDisplayName(user.getStatusDisplayName())
.displayName(user.getDisplayName())
.createdAt(user.getCreatedAt())
.updatedAt(user.getUpdatedAt())
.build();
}
/**
* 构建登录响应DTO
* @param token JWT令牌
* @param userInfo 用户信息
* @return 登录响应DTO
*/
public static UserLoginResponseDTO buildLoginResponse(String token, UserDetailResponseDTO userInfo) {
return UserLoginResponseDTO.builder()
.userInfo(userInfo)
.token(token)
.roleType(userInfo.getUserType())
.build();
}
}接口测试
使用之前,记得修改配置(文档扫描的包路径)

Knife4J
http://localhost:8889/doc.html
账号:admin
密码:admin