diff --git a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java index 4064126..7be0862 100644 --- a/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java +++ b/rdms-system/rdms-system-api/src/main/java/com/njcn/rdms/module/system/enums/ErrorCodeConstants.java @@ -60,6 +60,10 @@ public interface ErrorCodeConstants { ErrorCode USER_MANAGEMENT_RELATION_NOT_FOUND = new ErrorCode(1_002_003_100, "用户管理链路不存在"); ErrorCode USER_MANAGEMENT_RELATION_MANAGER_EXISTS = new ErrorCode(1_002_003_101, "该用户已有直属上级,不能重复添加"); ErrorCode USER_MANAGEMENT_RELATION_EXISTS = new ErrorCode(1_002_003_102, "该用户在管理链路中还在使用,不可删除!"); + ErrorCode USER_AVATAR_SUFFIX_ERROR = new ErrorCode(1_002_003_103, "头像文件格式错误"); + ErrorCode USER_AVATAR_SIZE_ERROR = new ErrorCode(1_002_003_104, "头像文件大小不能超过 5M"); + ErrorCode USER_AVATAR_UPLOAD_FAILED = new ErrorCode(1_002_003_105, "头像上传失败"); + // ========== 部门模块 1-002-004-000 ========== ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, "已经存在该名字的部门"); diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.java index 445443e..c353414 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/controller/admin/user/UserProfileController.java @@ -1,7 +1,7 @@ package com.njcn.rdms.module.system.controller.admin.user; -import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.common.enums.CommonStatusEnum; +import com.njcn.rdms.framework.common.pojo.CommonResult; import com.njcn.rdms.framework.encrypt.core.annotation.ApiEncrypt; import com.njcn.rdms.module.system.controller.admin.user.vo.profile.UserProfileRespVO; import com.njcn.rdms.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; @@ -9,6 +9,7 @@ import com.njcn.rdms.module.system.controller.admin.user.vo.profile.UserProfileU import com.njcn.rdms.module.system.convert.user.UserConvert; import com.njcn.rdms.module.system.dal.dataobject.dept.DeptDO; import com.njcn.rdms.module.system.dal.dataobject.dept.PostDO; +import com.njcn.rdms.module.system.dal.dataobject.file.FileDO; import com.njcn.rdms.module.system.dal.dataobject.permission.RoleDO; import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO; import com.njcn.rdms.module.system.service.dept.DeptService; @@ -22,11 +23,8 @@ import jakarta.annotation.Resource; import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import java.util.List; @@ -84,4 +82,12 @@ public class UserProfileController { return success(true); } + @PutMapping("/update-avatar") + @Operation(summary = "修改用户个人头像") + public CommonResult updateUserAvatar(@RequestParam("file") MultipartFile file) { + // 获得用户基本信息 + AdminUserDO user = userService.getUser(getLoginUserId()); + FileDO fileDO = userService.uploadAvatar(file, user); + return success(fileDO); + } } diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/AdminUserService.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/AdminUserService.java index b69ec22..adee632 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/AdminUserService.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/AdminUserService.java @@ -10,14 +10,12 @@ import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserImportExcel import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserImportRespVO; import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserPageReqVO; import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserSaveReqVO; +import com.njcn.rdms.module.system.dal.dataobject.file.FileDO; import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO; import jakarta.validation.Valid; +import org.springframework.web.multipart.MultipartFile; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * 后台用户 Service 接口 @@ -52,7 +50,7 @@ public interface AdminUserService { /** * 更新用户的最后登录信息 * - * @param id 用户编号 + * @param id 用户编号 * @param loginIp 登录 IP */ void updateUserLogin(Long id, String loginIp); @@ -60,7 +58,7 @@ public interface AdminUserService { /** * 修改用户个人信息 * - * @param id 用户编号 + * @param id 用户编号 * @param reqVO 用户个人信息 */ void updateUserProfile(Long id, @Valid UserProfileUpdateReqVO reqVO); @@ -68,7 +66,7 @@ public interface AdminUserService { /** * 修改用户个人密码 * - * @param id 用户编号 + * @param id 用户编号 * @param reqVO 更新用户个人密码 */ void updateUserPassword(Long id, @Valid UserProfileUpdatePasswordReqVO reqVO); @@ -76,7 +74,7 @@ public interface AdminUserService { /** * 修改密码 * - * @param id 用户编号 + * @param id 用户编号 * @param password 密码 */ void updateUserPassword(Long id, String password); @@ -84,7 +82,7 @@ public interface AdminUserService { /** * 修改状态 * - * @param id 用户编号 + * @param id 用户编号 * @param status 状态 */ void updateUserStatus(Long id, Integer status); @@ -193,7 +191,7 @@ public interface AdminUserService { /** * 批量导入用户 * - * @param importUsers 导入用户列表 + * @param importUsers 导入用户列表 * @param isUpdateSupport 是否支持更新 * @return 导入结果 */ @@ -210,7 +208,7 @@ public interface AdminUserService { /** * 判断密码是否匹配 * - * @param rawPassword 未加密的密码 + * @param rawPassword 未加密的密码 * @param encodedPassword 加密后的密码 * @return 是否匹配 */ @@ -226,7 +224,7 @@ public interface AdminUserService { /** * 判断用户当前是否可用 - * + *

* 口径: * 1. `status` 必须为启用 * 2. `resignedAt` 为空,或者晚于当前时间 @@ -252,4 +250,13 @@ public interface AdminUserService { * @return 可用用户 ID 集合 */ Set listEnabledUserIdsByDeptIds(Collection deptIds); + + /** + * 上传头像 + * + * @param file + * @param user + * @return + */ + FileDO uploadAvatar(MultipartFile file, AdminUserDO user); } diff --git a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/AdminUserServiceImpl.java b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/AdminUserServiceImpl.java index c408df5..d8ece3f 100644 --- a/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/AdminUserServiceImpl.java +++ b/rdms-system/rdms-system-boot/src/main/java/com/njcn/rdms/module/system/service/user/AdminUserServiceImpl.java @@ -1,8 +1,12 @@ package com.njcn.rdms.module.system.service.user; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.digest.DigestUtil; import com.google.common.annotations.VisibleForTesting; import com.mzt.logapi.context.LogRecordContext; import com.mzt.logapi.service.impl.DiffParseFunction; @@ -23,11 +27,16 @@ import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserPageReqVO; import com.njcn.rdms.module.system.controller.admin.user.vo.user.UserSaveReqVO; import com.njcn.rdms.module.system.dal.dataobject.config.ConfigDO; import com.njcn.rdms.module.system.dal.dataobject.dept.DeptDO; +import com.njcn.rdms.module.system.dal.dataobject.file.FileDO; import com.njcn.rdms.module.system.dal.dataobject.user.AdminUserDO; +import com.njcn.rdms.module.system.dal.mysql.file.FileMapper; import com.njcn.rdms.module.system.dal.mysql.user.AdminUserMapper; +import com.njcn.rdms.module.system.framework.file.core.client.FileClient; +import com.njcn.rdms.module.system.framework.file.core.utils.FileTypeUtils; import com.njcn.rdms.module.system.service.config.ConfigService; import com.njcn.rdms.module.system.service.dept.DeptService; import com.njcn.rdms.module.system.service.dept.PostService; +import com.njcn.rdms.module.system.service.file.FileConfigService; import com.njcn.rdms.module.system.service.oauth2.OAuth2TokenService; import com.njcn.rdms.module.system.service.permission.PermissionService; import jakarta.annotation.Resource; @@ -37,6 +46,7 @@ import org.springframework.context.annotation.Lazy; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import java.time.LocalDateTime; import java.util.*; @@ -77,6 +87,10 @@ public class AdminUserServiceImpl implements AdminUserService { private ConfigService configService; @Resource private UserManagementRelationService userManagementRelationService; + @Resource + private FileConfigService fileConfigService; + @Resource + private FileMapper fileMapper; @Override @Transactional(rollbackFor = Exception.class) @@ -216,7 +230,7 @@ public class AdminUserServiceImpl implements AdminUserService { @Transactional(rollbackFor = Exception.class) public void deleteUserList(List ids) { //批量删除前查看是否管理链路表还在使用该用户 - for (Long id : ids){ + for (Long id : ids) { Boolean res = userManagementRelationService.hasRelation(id); if (res) { throw exception(USER_MANAGEMENT_RELATION_EXISTS); @@ -404,7 +418,7 @@ public class AdminUserServiceImpl implements AdminUserService { /** * 校验旧密码 * - * @param id 用户 ID + * @param id 用户 ID * @param oldPassword 旧密码 */ @VisibleForTesting @@ -513,6 +527,79 @@ public class AdminUserServiceImpl implements AdminUserService { return getUserListByDeptIds(deptCondition); } + @Override + public FileDO uploadAvatar(MultipartFile file, AdminUserDO user) { + String type = file.getContentType(); + String name = file.getOriginalFilename(); + FileDO fileDO = null; + try { + byte[] content = IoUtil.readBytes(file.getInputStream()); + + // 1.1 处理 type 为空的情况 + if (StrUtil.isEmpty(type)) { + type = FileTypeUtils.getMineType(content, name); + } + // 验证文件类型 + validateImageFile(file); + + // 1.2 处理 name 为空的情况 + if (StrUtil.isEmpty(name)) { + name = DigestUtil.sha256Hex(content); + } + if (StrUtil.isEmpty(FileUtil.extName(name))) { + // 如果 name 没有后缀 type,则补充后缀 + String extension = FileTypeUtils.getExtension(type); + if (StrUtil.isNotEmpty(extension)) { + name = name + extension; + } + } + + // 2.1 生成上传的 path,需要保证唯一 + String path = FileUtil.FILE_SEPARATOR + "rdms\\avatar" + FileUtil.FILE_SEPARATOR + generateUniqueFileName(name); + // 2.2 上传到文件存储器 + FileClient client = fileConfigService.getMasterFileClient(); + Assert.notNull(client, "客户端(master) 不能为空"); + String url = null; + + url = client.upload(content, path, type); + + //删除旧的 + client.delete(user.getAvatar()); + + // 3. 保存到数据库 + fileDO = new FileDO().setConfigId(client.getId()) + .setName(name).setPath(path).setUrl(url) + .setType(type).setSize((long) content.length); + fileMapper.insert(fileDO); + user.setAvatar(fileDO.getUrl()); + this.userMapper.updateById(user); + } catch (Exception e) { + throw exception(USER_AVATAR_UPLOAD_FAILED); + } + return fileDO; + } + + private void validateImageFile(MultipartFile file) { + // 检查文件类型 + String contentType = file.getContentType(); + if (contentType == null || !contentType.startsWith("image/")) { + throw exception(USER_AVATAR_SUFFIX_ERROR); + } + + // 检查文件大小(例如限制5MB) + if (file.getSize() > 5 * 1024 * 1024) { + throw exception(USER_AVATAR_SIZE_ERROR); + } + } + + private String generateUniqueFileName(String originalFilename) { + String extension = ""; + if (originalFilename != null && originalFilename.contains(".")) { + extension = originalFilename.substring(originalFilename.lastIndexOf(".")); + } + return UUID.randomUUID().toString() + extension; + } + /** * 对密码进行加密 *