『博客开发日记-后台』之更新用户信息接口的实现

本文最后更新于 2026年5月20日 晚上

编辑用户信息接口的实现


编辑用户信息接口的需求

其实用户信息的需求和新增的差不多

要注意的是

超级管理员只能由自己修改自己的信息 别人不能改管理员信息

如果是修改用户状态(正常或禁用)

就给对应用户发送邮箱(如果用户有邮箱的话)


代码实现

创建 UpdateUserDto

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* 更新用户请求DTO
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@ApiModel(description = "更新用户请求对象")
public class UpdateUserDto
{
@ApiModelProperty(value = "用户头像", example = "https://avatars.githubusercontent.com/u/155459505?v=4")
private String avatar;

@NotBlank(message = "部门id不能为空")
@ApiModelProperty(value = "部门id", required = true, example = "3")
private String deptId;

@ApiModelProperty(value = "邮箱", example = "2962933152@qq.com")
private String email;

@ApiModelProperty(value = "用户性别,(0 男,1 女,2 隐藏)", example = "0")
private String sex;

@ApiModelProperty(value = "手机号", example = "1234567890")
private String mobile;

@NotBlank(message = "昵称不能为空")
@ApiModelProperty(value = "昵称", required = true, example = "云梦泽")
private String nickname;

@NotEmpty(message = "角色不能为空")
@ApiModelProperty(value = "角色id列表", required = true, example = "[1,2,3]")
private List<Long> roleIds;

@NotBlank(message = "状态不能为空")
@Pattern(regexp = "^[01]$", message = "状态只能为0或1")
@ApiModelProperty(value = "状态(0-正常,1-禁用)", required = true, example = "0")
private String status;

@NotBlank(message = "用户名不能为空")
@ApiModelProperty(value = "用户名", required = true, example = "云梦泽")
private String username;

}

在 LoginUser 中添加对用户状态的返回

1
2
3
4
@Override
public boolean isEnabled() {
return sysUser == null || SystemConstants.USER_STATUS_NORMAL.equals(sysUser.getStatus());
}

添加常量

1
2
3
4
5
6
7
8
/**
* 用户状态:正常
*/
public static final String USER_STATUS_NORMAL = "0";
/**
* 用户状态:已封禁
*/
public static final String USER_STATUS_BANNED = "1";

在 AppHttpCodeEnum 中添加枚举

1
ACCOUNT_DISABLED(1024, "账号已被封禁"),

发送在邮件的服务类 EmailServiceImpl 中添加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//发送用户禁用通知邮件
@Async("asyncExecutor")
public void sendUserDisabledNotification(String toEmail, String nickname, String username)
{
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

helper.setFrom(from);
helper.setTo(toEmail);
helper.setSubject("【云梦泽的个人博客】您的账号已被禁用");

String htmlContent = buildUserDisabledNotificationHtml(nickname, username);
helper.setText(htmlContent, true);

mailSender.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}

//发送用户解封通知邮件
@Async("asyncExecutor")
public void sendUserEnabledNotification(String toEmail, String nickname, String username)
{
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");

helper.setFrom(from);
helper.setTo(toEmail);
helper.setSubject("【云梦泽的个人博客】您的账号已恢复正常使用");

String htmlContent = buildUserEnabledNotificationHtml(nickname, username);
helper.setText(htmlContent, true);

mailSender.send(message);
} catch (MessagingException e) {
e.printStackTrace();
}
}

//用户账号封禁通知邮件
private String buildUserDisabledNotificationHtml(String nickname, String username)
{
return "<!DOCTYPE html>" +
"<html>" +
"<head>" +
" <meta charset=\"UTF-8\">" +
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" +
"</head>" +
"<body style=\"margin: 0; padding: 0; background: linear-gradient(135deg, #a5dff9 0%, #008c9e 100%); font-family: 'Segoe UI', Arial, sans-serif;\">" +
" <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"padding: 40px 20px;\">" +
" <tr>" +
" <td align=\"center\">" +
" <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color: #f5fffa; border-radius: 12px; box-shadow: 0 12px 15px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19); overflow: hidden;\">" +
" <tr>" +
" <td style=\"background: rgb(125, 182, 191); padding: 40px 30px; text-align: center;\">" +
" <h1 style=\"color: #2c3e50; margin: 0; font-size: 28px; font-weight: 600; letter-spacing: 1px;\">云梦泽的个人博客</h1>" +
" <p style=\"color: #546e7a; margin: 10px 0 0 0; font-size: 14px;\">账号状态变更通知</p>" +
" </td>" +
" </tr>" +
" <tr>" +
" <td style=\"padding: 50px 40px;\">" +
" <h2 style=\"color: #3c4858; margin: 0 0 20px 0; font-size: 22px; font-weight: 600;\">您好," + (StringUtils.hasText(nickname) ? nickname : username) + "!</h2>" +
" <p style=\"color: #718096; line-height: 1.8; margin: 0 0 30px 0; font-size: 15px;\">" +
" 由于您在平台中的不正当行为,我们已将您的账号进行封禁处理,您此后不允许使用该账号在我们平台上登录。 " +
" </p>" +
" <div style=\"background: linear-gradient(135deg, #f8f9fa 0%, #e8f4f8 100%); border-left: 4px solid #f44336; border-radius: 8px; padding: 25px; margin: 30px 0; box-shadow: 0 2px 8px rgba(244,67,54,0.1);\">" +
" <p style=\"color: #3c4858; line-height: 1.8; margin: 0 0 15px 0; font-size: 15px; font-weight: 600;\">" +
" (╯-_-)╯╧╧ 账号被封禁" +
" </p>" +
" <p style=\"color: #718096; line-height: 1.8; margin: 0; font-size: 14px;\">" +
" 您的账号(" + username + ")当前状态:<strong style=\"color: #f44336;\">已被禁用</strong>" +
" </p>" +
" </div>" +
" </td>" +
" </tr>" +
" <tr>" +
" <td style=\"background: linear-gradient(135deg, #f8f9fa 0%, #eaecef 100%); padding: 30px; text-align: center; border-top: 1px solid #eaecef;\">" +
" <p style=\"color: #718096; margin: 0 0 10px 0; font-size: 13px;\">此邮件由系统自动发送,请勿直接回复</p>" +
" <p style=\"color: #a7a9ad; margin: 0; font-size: 12px;\">© " + LocalDate.now().getYear() + " 云梦泽的个人博客 · All rights reserved</p>" +
" </td>" +
" </tr>" +
" </table>" +
" </td>" +
" </tr>" +
" </table>" +
"</body>" +
"</html>";
}

//用户账号解封通知邮件
private String buildUserEnabledNotificationHtml(String nickname, String username)
{
return "<!DOCTYPE html>" +
"<html>" +
"<head>" +
" <meta charset=\"UTF-8\">" +
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">" +
"</head>" +
"<body style=\"margin: 0; padding: 0; background: linear-gradient(135deg, #a5dff9 0%, #008c9e 100%); font-family: 'Segoe UI', Arial, sans-serif;\">" +
" <table width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"padding: 40px 20px;\">" +
" <tr>" +
" <td align=\"center\">" +
" <table width=\"600\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color: #f5fffa; border-radius: 12px; box-shadow: 0 12px 15px 0 rgba(0,0,0,0.24), 0 17px 50px 0 rgba(0,0,0,0.19); overflow: hidden;\">" +
" <tr>" +
" <td style=\"background: rgb(125, 182, 191); padding: 40px 30px; text-align: center;\">" +
" <h1 style=\"color: #2c3e50; margin: 0; font-size: 28px; font-weight: 600; letter-spacing: 1px;\">云梦泽的个人博客</h1>" +
" <p style=\"color: #546e7a; margin: 10px 0 0 0; font-size: 14px;\">账号解封通知</p>" +
" </td>" +
" </tr>" +
" <tr>" +
" <td style=\"padding: 50px 40px;\">" +
" <h2 style=\"color: #3c4858; margin: 0 0 20px 0; font-size: 22px; font-weight: 600;\">您好," + (StringUtils.hasText(nickname) ? nickname : username) + "!</h2>" +
" <p style=\"color: #718096; line-height: 1.8; margin: 0 0 30px 0; font-size: 15px;\">" +
" 您的账号已解封,您可以继续在平台中使用该账号。" +
" </p>" +
" <div style=\"background: linear-gradient(135deg, #f8f9fa 0%, #e8f4f8 100%); border-left: 4px solid #52c41a; border-radius: 8px; padding: 25px; margin: 30px 0; box-shadow: 0 2px 8px rgba(82,196,26,0.1);\">" +
" <p style=\"color: #3c4858; line-height: 1.8; margin: 0 0 15px 0; font-size: 15px; font-weight: 600;\">" +
" - ̗̀ ෆ( ˶’ᵕ’˶)ෆ ̖́- 账号解封" +
" </p>" +
" <p style=\"color: #718096; line-height: 1.8; margin: 0; font-size: 14px;\">" +
" 您的账号(" + username + ")当前状态:<strong style=\"color: #52c41a;\">已恢复正常使用</strong>" +
" </p>" +
" </div>" +
" </td>" +
" </tr>" +
" <tr>" +
" <td style=\"background: linear-gradient(135deg, #f8f9fa 0%, #eaecef 100%); padding: 30px; text-align: center; border-top: 1px solid #eaecef;\">" +
" <p style=\"color: #718096; margin: 0 0 10px 0; font-size: 13px;\">此邮件由系统自动发送,请勿直接回复</p>" +
" <p style=\"color: #a7a9ad; margin: 0; font-size: 12px;\">© " + LocalDate.now().getYear() + " 云梦泽的个人博客 · All rights reserved</p>" +
" </td>" +
" </tr>" +
" </table>" +
" </td>" +
" </tr>" +
" </table>" +
"</body>" +
"</html>";
}

在 AdminUserServiceImpl 中实现方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
 //更新用户
@Override
@Transactional(rollbackFor = Exception.class)
public ResponseResult updateUser(Long id, UpdateUserDto updateUserDto)
{
//当前登录用户
LoginUser loginUser = SecurityUtils.getLoginUser();
Long currentUserId = null;
if (loginUser != null) {
currentUserId = loginUser.getSysUser().getId();
}

//检查用户是否存在
SysUser oldUser = adminUserService.getById(id);
if (oldUser == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "该用户不存在!");
}

//超级管理员只能由自己修改自己的信息
if (Objects.equals(oldUser.getId(), 1L) && !Objects.equals(currentUserId, 1L)) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "只有超级管理员本人可以修改超级管理员信息!");
}

//检查用户名是否已经存在(除了自己)
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getUsername, updateUserDto.getUsername())
.ne(SysUser::getId, id);
long count = adminUserService.count(queryWrapper);
if (count > 0) {
return ResponseResult.errorResult(AppHttpCodeEnum.USERNAME_EXIST, "用户名重复,请使用其他用户名!");
}

//检查手机号是否被其他用户使用
if (StringUtils.hasText(updateUserDto.getMobile())) {
LambdaQueryWrapper<SysUser> phoneWrapper = new LambdaQueryWrapper<>();
phoneWrapper.eq(SysUser::getPhone, updateUserDto.getMobile())
.ne(SysUser::getId, id);
SysUser existSysUser = adminUserService.getOne(phoneWrapper);
if (existSysUser != null) {
return ResponseResult.errorResult(AppHttpCodeEnum.PHONENUMBER_EXIST, "该手机号已被占用!");
}
}

//检查邮箱是否被其他用户使用
if (StringUtils.hasText(updateUserDto.getEmail())) {
LambdaQueryWrapper<SysUser> emailWrapper = new LambdaQueryWrapper<>();
emailWrapper.eq(SysUser::getEmail, updateUserDto.getEmail())
.ne(SysUser::getId, id);
SysUser existSysUser = adminUserService.getOne(emailWrapper);
if (existSysUser != null) {
return ResponseResult.errorResult(AppHttpCodeEnum.EMAIL_EXIST, "该邮箱已被占用!");
}
}

//检查部门是否存在
SysDept dept = null;
if (StringUtils.hasText(updateUserDto.getDeptId())) {
try {
dept = sysDeptService.getById(Long.valueOf(updateUserDto.getDeptId()));
} catch (NumberFormatException e) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "部门ID格式不正确");
}
}
if (dept == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "部门不存在!");
}

//检查角色是否存在,且不能包含系统超级管理员角色
for (Long roleId : updateUserDto.getRoleIds())
{
SysRole role = sysRoleService.getById(roleId);
if (role == null) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "角色不存在!");
}
if (Objects.equals(role.getId(), 1L)) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "管理员是神圣不可侵犯的!");
}
}

//更新用户信息
SysUser user = BeanCopyUtils.copyBean(updateUserDto, SysUser.class);
user.setId(id);
boolean update = adminUserService.updateById(user);
if (!update) {
return ResponseResult.errorResult(AppHttpCodeEnum.SYSTEM_ERROR, "更新用户失败!");
}

//如果用户态发生变化,在用户有邮箱的前提下,会发送通知邮件(账号封禁或解封)
boolean statusChanged = !Objects.equals(oldUser.getStatus(), user.getStatus());
boolean statusSwitchToNormalOrDisabled = Objects.equals(oldUser.getStatus(), SystemConstants.ARTICLE_STATUS_NORMAL) || Objects.equals(user.getStatus(), SystemConstants.ARTICLE_STATUS_NORMAL);
if (statusChanged && statusSwitchToNormalOrDisabled && StringUtils.hasText(oldUser.getEmail())) {
if (Objects.equals(user.getStatus(), SystemConstants.ARTICLE_STATUS_NORMAL)) {
emailService.sendUserEnabledNotification(oldUser.getEmail(), oldUser.getNickname(), oldUser.getUsername());
} else {
emailService.sendUserDisabledNotification(oldUser.getEmail(), oldUser.getNickname(), oldUser.getUsername());
}
}

//删除原有角色关联
LambdaQueryWrapper<SysUserRole> deleteWrapper = new LambdaQueryWrapper<>();
deleteWrapper.eq(SysUserRole::getUserId, id);
sysUserRoleMapper.delete(deleteWrapper);

//新增角色关联
List<SysUserRole> userRoles = updateUserDto.getRoleIds().stream()
.map(roleId -> {
SysUserRole userRole = new SysUserRole();
userRole.setUserId(id);
userRole.setRoleId(roleId);
userRole.setCreateTime(new Date());
return userRole;
})
.collect(Collectors.toList());

for (SysUserRole userRole : userRoles) {
sysUserRoleMapper.insert(userRole);
}

return ResponseResult.okResult();
}


还要注意

被封禁的账号是不允许登录前后台的

后台 AdminAuthServiceImpl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
@Override
public ResponseResult login(AdminLoginDto dto)
{
// 预防编程,用户名是否为空
if (!StringUtils.hasText(dto.getUsername())) {
throw new SystemException(AppHttpCodeEnum.REQUIRE_USERNAME);
}

// 验证码校验(如果提供了验证码)
if (StringUtils.hasText(dto.getCaptchaId()) && StringUtils.hasText(dto.getCaptchaCode()))
{
String cacheKey = "captcha:" + dto.getCaptchaId();
String cachedCode = redisCache.getCacheObject(cacheKey);

if (cachedCode == null) {
throw new SystemException(AppHttpCodeEnum.CAPTCHA_EXPIRED);
}

if (!cachedCode.equalsIgnoreCase(dto.getCaptchaCode())) {
throw new SystemException(AppHttpCodeEnum.CAPTCHA_ERROR);
}

// 验证成功后删除验证码
redisCache.deleteObject(cacheKey);
}

// 将DTO转换为User实体
SysUser sysUser = BeanCopyUtils.copyBean(dto, SysUser.class);

UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
Authentication authenticate;
try {
authenticate = authenticationManager.authenticate(authenticationToken);
} catch (DisabledException e) {
//如果账号为被封禁,则提示
throw new SystemException(AppHttpCodeEnum.ACCOUNT_DISABLED);
}

//判断是否认证通过
if (Objects.isNull(authenticate)) {
throw new RuntimeException("用户名或密码错误!");
}

//获取userid生成token
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
SysUser sysUserInfo = loginUser.getSysUser();
String userId = sysUserInfo.getId().toString();

// 生成访问令牌(1小时)
String token = JwtUtil.createJWT(userId);

// 生成刷新令牌(7天)
String refreshToken = JwtUtil.createJWT(userId, JwtUtil.JWT_REFRESH_TTL);

//把用户信息存入Redis (格式为 adminLogin:id,用于后台管理系统),设置过期时间与刷新Token一致
redisCache.setCacheObject("adminLogin:" + userId, loginUser, JwtUtil.JWT_REFRESH_TTL.intValue(), TimeUnit.MILLISECONDS);

// 构建响应对象
AdminAuthVo vo = new AdminAuthVo(token, refreshToken, JwtUtil.JWT_TTL / 1000);
return ResponseResult.okResult(vo);
}

前台 BlogLoginServiceImpl 中的 generateLoginResponse 方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//生成登录响应(token和用户信息)
private ResponseResult generateLoginResponse(SysUser sysUser)
{
// 创建LoginUser对象
LoginUser loginUser = new LoginUser(sysUser);

// 生成JWT token
String userId = sysUser.getId().toString();
String jwt = JwtUtil.createJWT(userId);

// 把用户信息存入Redis,设置过期时间与JWT一致
redisCache.setCacheObject("blogLogin:" + userId, loginUser, JwtUtil.JWT_TTL.intValue(), TimeUnit.MILLISECONDS);

// 封装返回数据
SysUserInfoVo sysUserInfoVo = BeanCopyUtils.copyBean(sysUser, SysUserInfoVo.class);

// 如果昵称为空,使用用户名代替
if (!StringUtils.hasText(sysUserInfoVo.getNickname())) {
sysUserInfoVo.setNickname(sysUser.getUsername());
}
// 如果头像为空,根据邮箱生成头像
if (!StringUtils.hasText(sysUserInfoVo.getAvatar())) {
sysUserInfoVo.setAvatar(GravatarUtils.getGravatarUrl(sysUser.getEmail()));
}

// 禁用账号不允许登录
if (!SystemConstants.USER_STATUS_NORMAL.equals(sysUser.getStatus())) {
return ResponseResult.errorResult(AppHttpCodeEnum.ACCOUNT_DISABLED);
}

// 查询用户角色
List<String> roles = sysRoleMapper.selectRoleCodeByUserId(sysUser.getId());
if (roles == null || roles.isEmpty()) {
// 如果没有角色,设置为空列表
roles = Collections.emptyList();
}
sysUserInfoVo.setRoles(roles);

BlogUserLoginVo vo = new BlogUserLoginVo(jwt, sysUserInfoVo);
return ResponseResult.okResult(vo);
}




PS:该系列只做为作者学习开发项目做的笔记用

不一定符合读者来学习,仅供参考


预告

后续会记录博客的开发过程

每次学习会做一份笔记来进行发表

“一花一世界,一叶一菩提”


版权所有 © 2026 云梦泽
欢迎访问我的个人网站:https://hgt12.github.io/


『博客开发日记-后台』之更新用户信息接口的实现
http://example.com/2026/05/18/『博客开发日记-后台』之更新用户信息接口的实现/
作者
云梦泽
发布于
2026年5月18日
更新于
2026年5月20日
许可协议