BigEvent项目后端学习笔记(一)用户管理模块 | 注册登录与用户信息全流程解析(含优化)
📖 模块概述
- 用户管理模块是系统的核心基础模块,包含 注册、登录、用户信息维护 等功能。本模块涉及 JWT Token认证、密码加密存储、文件上传 等关键技术点,是理解前后端分离架构中安全与数据交互的典型实践。
- 本篇对于原项目进行了代码优化,将原先写在
Controller
层的业务逻辑代码迁移至了Service
层,并且额外定义了全局异常处理类中的参数校验异常
处理方法。
🛠️ 技术实现要点
- 前端:Vue3
- 后端:SpringBoot3 + MyBatis + JWT
- 安全:Token身份验证
- 存储:用户头像上传至OSS(或本地目录)
⚙️ 各层职责与代码规范
🔗 Controller 层
⭐️ 核心职责:
- 接收HTTP请求参数(@RequestBody/@RequestParam)
- 调用Service层方法
- 返回统一响应格式(如Result对象)
@RestController
@RequestMapping("/user")
@Validated
@RequiredArgsConstructor
public class UserController {
...
}
💡 注意事项:
- 禁止在Controller中编写业务逻辑
- 使用DTO对象接收参数,而非直接传递Entity
- 统一异常处理(通过@ControllerAdvice)
🔗 Service 层
⭐️ 核心职责:
- 实现具体业务逻辑(如数据校验、事务管理)
- 组合多个Mapper操作
- 处理异常并抛出自定义业务异常
public interface UserService {
...
}
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
...
}
💡 最佳实践:
- 接口与实现分离(UserService + UserServiceImpl)
- 方法需明确事务边界(@Transactional注解)
- 避免“上帝Service”(按业务拆分多个Service)
🔗 Mapper 层
⭐️ 核心职责:
- 定义数据库操作方法(SQL映射)
- 基于MyBatis/MyBatis-Plus实现CRUD
@Mapper
public interface UserMapper {
...
}
<!-- 动态 SQL -->
<select id="getArticleList" resultType="com.itheima.pojo.Article">
select * from article
<where>
<if test="categoryId != null">
category_id = #{categoryId}
</if>
<if test="state != null">
and state = #{state}
</if>
and create_user = #{userId}
</where>
</select>
💡 规范建议:
- 优先使用MyBatis原生方法
- 定义数据库操作方法(SQL映射)
- 基于MyBatis/MyBatis-Plus实现CRUD
🔗 层间交互与依赖注入
推荐使用构造函数
注入:
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserMapper userMapper;
// Lombok自动生成构造函数
}
@RestController
@RequestMapping("/user")
@Validated
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
// Lombok自动生成构造函数
}
层级 | 对象类型 | 说明 |
---|---|---|
Controller | DTO(请求/响应对象) | 屏蔽Entity细节,保证接口安全 |
Service | Entity、DTO、VO | 内部处理使用Entity,返回VO |
Mapper | Entity、Query对象 | 直接与数据库表结构对应 |
🔍 功能实现详解
🎯 注册
🧩 Controller 层
请求路径:/user/register
请求方式:POST
接口描述:该接口用于注册新用户
@PostMapping("/register")
public Result register(@Validated RegisterOrLoginDTO dto) {
return userService.register(dto.getUsername(), dto.getPassword());
}
🧩 Service 层
- 接口
// 根据用户名查询用户
User findByUsername(String username);
// 注册
Result register(String username, String password);
- 实现
/**
* 根据用户名查询用户
*
* @param username 需要查询的用户名
* @return 查询到的用户对象
*/
@Override
public User findByUsername(String username) {
return userMapper.findByUsername(username);
}
/**
* 注册
*
* @param username 需要注册的用户名
* @param password 需要注册的密码
* @return 注册结果
*/
@Override
public Result register(String username, String password) {
User user = findByUsername(username);
if (user != null) {
return Result.error("用户名已被占用");
}
userMapper.add(username, Md5Util.getMD5String(password));
return Result.success();
}
🧩 Mapper 层
// 根据用户名查询用户
@Select("select * from user where username = #{username}")
User findByUsername(String username);
🎯 登录
🧩 Controller 层
请求路径:/user/login
请求方式:POST
接口描述:该接口用于登录
@PostMapping("/login")
public Result login(@Validated RegisterOrLoginDTO dto) {
return userService.authenticateUser(dto.getUsername(), dto.getPassword());
}
🧩 Service 层
- 接口
// 登录验证
Result authenticateUser(String username, String password);
- 实现
/**
* 登录
*
* @param username 用户名
* @param password 密码
* @return 登录结果
*/
@Override
public Result authenticateUser(String username, String password) {
User user = findByUsername(username);
if (user == null) {
return Result.error("用户不存在");
}
if (Md5Util.getMD5String(password).equals(user.getPassword())) {
// 登录成功
Map<String, Object> claims = new HashMap<>();
claims.put("id", user.getId());
claims.put("username", user.getUsername());
String token = JwtUtil.genToken(claims);
return Result.success(token);
}
return Result.error("密码错误!");
}
🧩 Mapper 层
// 无
🎯 获取用户详细信息
🧩 Controller 层
请求路径:/user/userInfo
请求方式:GET
接口描述:该接口用于获取当前已登录用户的详细信息
@GetMapping("/userInfo")
public Result<User> getUserInfo() {
return userService.getUserInfo();
}
🧩 Service 层
- 接口
// 获取用户信息
Result<User> getUserInfo();
- 实现
/**
* 获取用户信息
*
* @return 用户信息
*/
@Override
public Result<User> getUserInfo() {
Map<String, Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User user = findByUsername(username);
return Result.success(user);
}
🧩 Mapper 层
// 无
🎯 更新用户基本信息
🧩 Controller 层
请求路径:/user/update
请求方式:PUT
接口描述:该接口用于更新已登录用户的基本信息(除头像和密码)
@PutMapping("/update")
public Result update(@RequestBody @Validated User user) {
return userService.update(user);
}
🧩 Service 层
- 接口
// 更新
Result update(User user);
- 实现
/**
* 更新用户昵称和邮箱
*
* @param user 用户对象
*/
@Override
public Result update(User user) {
user.setUpdateTime(LocalDateTime.now());
userMapper.update(user);
return Result.success();
}
🧩 Mapper 层
// 修改昵称和邮箱
@Update("update user set nickname = #{nickname}, email = #{email}, update_time = #{updateTime} where id = #{id}")
void update(User user);
🎯 更新用户头像
🧩 Controller 层
请求路径:/user/updateAvatar
请求方式:PATCH
接口描述:该接口用于更新已登录用户的头像
@PatchMapping("updateAvatar")
public Result updateAvatar(@RequestParam @URL String avatarUrl) {
return userService.updateAvatar(avatarUrl);
}
🧩 Service 层
- 接口
// 更新头像
Result updateAvatar(String avatarUrl);
- 实现
/**
* 更新用户头像
*
* @param avatarUrl 用户头像地址
*/
@Override
public Result updateAvatar(String avatarUrl) {
Map<String, Object> map = ThreadLocalUtil.get();
Integer id = (Integer) map.get("id");
userMapper.updateAvatar(avatarUrl, id);
return Result.success();
}
🧩 Mapper 层
// 修改头像
@Update("update user set user_pic = #{avatarUrl}, update_time = now() where id = #{id}")
void updateAvatar(String avatarUrl, Integer id);
🎯 更新用户密码
🧩 Controller 层
请求路径:/user/updatePwd
请求方式:PATCH
接口描述:该接口用于更新已登录用户的密码
@PatchMapping("/updatePwd")
public Result updatePwd(@RequestBody @Validated PwdUpdateDTO dto) {
return userService.updatePwd(dto);
}
🧩 Service 层
- 接口
// 更新密码
Result updatePwd(PwdUpdateDTO dto);
- 实现
/**
* 更新用户密码
*
* @param dto 密码传输对象
* @return 更新密码结果
*/
@Override
public Result updatePwd(PwdUpdateDTO dto) {
// 1.校验参数
if (!StringUtils.hasLength(dto.getOldPwd())
|| !StringUtils.hasLength(dto.getNewPwd())
|| !StringUtils.hasLength(dto.getRePwd())) {
return Result.error("缺少必要的参数");
}
// 2.校验旧密码
Map<String, Object> map = ThreadLocalUtil.get();
String username = (String) map.get("username");
User loginUser = findByUsername(username);
if (loginUser == null) {
return Result.error("用户不存在");
}
if (!loginUser.getPassword().equals(Md5Util.getMD5String(dto.getOldPwd()))) {
return Result.error("旧密码填写错误");
}
if (!dto.getNewPwd().equals(dto.getRePwd())) {
return Result.error("两次密码不一致");
}
if (loginUser.getPassword().equals(Md5Util.getMD5String(dto.getNewPwd()))) {
return Result.error("新密码不能与旧密码相同");
}
// 3.更新密码
userMapper.updatePwd(Md5Util.getMD5String(dto.getNewPwd()), loginUser.getId());
return Result.success();
}
🧩 Mapper 层
// 修改密码
@Update("update user set password = #{md5String}, update_time = now() where id = #{id}")
void updatePwd(String md5String, Integer id);