From 7ecbc16b694c97a57242fd57afdde79a1a13e1e4 Mon Sep 17 00:00:00 2001 From: "Ray.Hao" <1490493387@qq.com> Date: Sat, 22 Aug 2026 12:00:25 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20RuntimeException=20=E7=BB=9F?= =?UTF-8?q?=E4=B8=80=E4=B8=BA=20BusinessException=20=E5=B9=B6=E8=A7=84?= =?UTF-8?q?=E8=8C=83=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 文件上传、代码生成、用户/菜单/角色校验等场景的 RuntimeException 统一替换为 BusinessException - 阿里云 OSS 上传失败补记日志并保留原始异常 cause - 修复 prod 微信小程序配置 app-secret 无法绑定 WxMaProperties.secret 字段的问题 - UserServiceImpl 展开通配符 import 并规范化 javadoc --- .../codegen/controller/CodegenController.java | 3 +- .../service/impl/CodegenServiceImpl.java | 2 +- .../boot/framework/web/log/OperationLog.java | 23 ++- .../framework/web/log/OperationLogAspect.java | 3 +- .../framework/web/log/OperationLogPort.java | 6 +- .../service/impl/AliyunFileServiceImpl.java | 6 +- .../service/impl/LocalFileServiceImpl.java | 3 +- .../adapter/log/OperationLogAdapter.java | 9 +- .../system/controller/UserController.java | 5 +- .../boot/system/converter/LogConverter.java | 10 +- .../system/service/impl/MenuServiceImpl.java | 3 +- .../system/service/impl/RoleServiceImpl.java | 2 +- .../system/service/impl/UserServiceImpl.java | 169 +++++++++++------- src/main/resources/application-prod.yml | 4 +- 14 files changed, 159 insertions(+), 89 deletions(-) diff --git a/src/main/java/com/youlai/boot/codegen/controller/CodegenController.java b/src/main/java/com/youlai/boot/codegen/controller/CodegenController.java index 7e589f1a..ccbd9b70 100644 --- a/src/main/java/com/youlai/boot/codegen/controller/CodegenController.java +++ b/src/main/java/com/youlai/boot/codegen/controller/CodegenController.java @@ -1,6 +1,7 @@ package com.youlai.boot.codegen.controller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.common.result.PageResult; import com.youlai.boot.common.result.Result; import com.youlai.boot.codegen.config.CodegenProperties; @@ -106,7 +107,7 @@ public class CodegenController { outputStream.flush(); } catch (IOException e) { log.error("Error while writing the zip file1 to response", e); - throw new RuntimeException("Failed to write the zip file1 to response", e); + throw new BusinessException("代码生成压缩包响应写入失败", e); } } } diff --git a/src/main/java/com/youlai/boot/codegen/service/impl/CodegenServiceImpl.java b/src/main/java/com/youlai/boot/codegen/service/impl/CodegenServiceImpl.java index cd9a9e1a..259cbf06 100644 --- a/src/main/java/com/youlai/boot/codegen/service/impl/CodegenServiceImpl.java +++ b/src/main/java/com/youlai/boot/codegen/service/impl/CodegenServiceImpl.java @@ -389,7 +389,7 @@ public class CodegenServiceImpl implements CodegenService { } catch (IOException e) { log.error("Error while generating zip for code download", e); - throw new RuntimeException("Failed to generate code zip file1", e); + throw new BusinessException("代码生成压缩包失败", e); } } diff --git a/src/main/java/com/youlai/boot/framework/web/log/OperationLog.java b/src/main/java/com/youlai/boot/framework/web/log/OperationLog.java index b30715bd..db9ee85d 100644 --- a/src/main/java/com/youlai/boot/framework/web/log/OperationLog.java +++ b/src/main/java/com/youlai/boot/framework/web/log/OperationLog.java @@ -6,30 +6,47 @@ import com.youlai.boot.common.enums.LogModuleEnum; import java.time.LocalDateTime; /** - * 操作日志通用载荷 - *
由操作日志切面采集,经 {@link OperationLogPort} 交给各业务模块落库
+ * 操作日志载荷 * * @author Ray.Hao - * @since 0.0.1 + * @since 4.7.0 */ public record OperationLog( + /** 模块 */ LogModuleEnum module, + /** 操作类型 */ ActionTypeEnum actionType, + /** 操作标题 */ String title, + /** 自定义日志内容 */ String content, + /** 操作人 ID */ Long operatorId, + /** 操作人名称 */ String operatorName, + /** 请求路径 */ String requestUri, + /** 请求方式 */ String requestMethod, + /** IP 地址 */ String ip, + /** 省份 */ String province, + /** 城市 */ String city, + /** 设备 */ String device, + /** 操作系统 */ String os, + /** 浏览器 */ String browser, + /** 状态:0 失败 1 成功 */ Integer status, + /** 错误信息 */ String errorMsg, + /** 执行时间(毫秒) */ Integer executionTime, + /** 操作时间 */ LocalDateTime createTime ) { } diff --git a/src/main/java/com/youlai/boot/framework/web/log/OperationLogAspect.java b/src/main/java/com/youlai/boot/framework/web/log/OperationLogAspect.java index f193a6d0..ec9e294d 100644 --- a/src/main/java/com/youlai/boot/framework/web/log/OperationLogAspect.java +++ b/src/main/java/com/youlai/boot/framework/web/log/OperationLogAspect.java @@ -25,10 +25,9 @@ import java.util.concurrent.Executor; /** * 操作日志切面 - *@Log 标注的方法执行后异步采集操作日志,通过 {@link OperationLogPort} 落库
* * @author Ray.Hao - * @since 0.0.1 + * @since 4.7.0 */ @Aspect @Component diff --git a/src/main/java/com/youlai/boot/framework/web/log/OperationLogPort.java b/src/main/java/com/youlai/boot/framework/web/log/OperationLogPort.java index 780d6ae7..3e8ef74c 100644 --- a/src/main/java/com/youlai/boot/framework/web/log/OperationLogPort.java +++ b/src/main/java/com/youlai/boot/framework/web/log/OperationLogPort.java @@ -2,12 +2,10 @@ package com.youlai.boot.framework.web.log; /** * 操作日志落库端口 - *由各业务模块实现,framework 层通过此接口写入操作日志,不依赖具体落库实现。 - * 如 system 模块的 {@code OperationLogAdapter} 落库 sys_log 表。
+ *由 {@link com.youlai.boot.system.adapter.log.OperationLogAdapter} 实现
* - * @see com.youlai.boot.system.adapter.log.OperationLogAdapter * @author Ray.Hao - * @since 0.0.1 + * @since 4.7.0 */ public interface OperationLogPort { diff --git a/src/main/java/com/youlai/boot/support/file/service/impl/AliyunFileServiceImpl.java b/src/main/java/com/youlai/boot/support/file/service/impl/AliyunFileServiceImpl.java index bc3b2a31..b50a32ec 100644 --- a/src/main/java/com/youlai/boot/support/file/service/impl/AliyunFileServiceImpl.java +++ b/src/main/java/com/youlai/boot/support/file/service/impl/AliyunFileServiceImpl.java @@ -8,12 +8,14 @@ import com.aliyun.oss.OSS; import com.aliyun.oss.OSSClientBuilder; import com.aliyun.oss.model.ObjectMetadata; import com.aliyun.oss.model.PutObjectRequest; +import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.support.file.model.FileInfo; import com.youlai.boot.support.file.service.FileService; import jakarta.annotation.PostConstruct; import lombok.Data; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @@ -29,6 +31,7 @@ import java.time.LocalDateTime; * 上传文件按日期分目录存储,URL 格式:{@code https://bucket.endpoint/yyyyMMdd/uuid.suffix}。 */ @Component +@Slf4j @ConditionalOnProperty(value = "file-storage.type", havingValue = "aliyun") @ConfigurationProperties(prefix = "file-storage.aliyun") @RequiredArgsConstructor @@ -67,7 +70,8 @@ public class AliyunFileServiceImpl implements FileService { PutObjectRequest putObjectRequest = new PutObjectRequest(bucket, fileName, inputStream, metadata); aliyunOssClient.putObject(putObjectRequest); } catch (Exception e) { - throw new RuntimeException("文件上传失败"); + log.error("文件上传失败", e); + throw new BusinessException("文件上传失败", e); } String fileUrl = "https://" + bucket + "." + endpoint + "/" + fileName; FileInfo fileInfo = new FileInfo(); diff --git a/src/main/java/com/youlai/boot/support/file/service/impl/LocalFileServiceImpl.java b/src/main/java/com/youlai/boot/support/file/service/impl/LocalFileServiceImpl.java index 657bb6f3..890b1188 100644 --- a/src/main/java/com/youlai/boot/support/file/service/impl/LocalFileServiceImpl.java +++ b/src/main/java/com/youlai/boot/support/file/service/impl/LocalFileServiceImpl.java @@ -4,6 +4,7 @@ import cn.hutool.core.date.DatePattern; import cn.hutool.core.date.DateUtil; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.IdUtil; +import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.support.file.model.FileInfo; import com.youlai.boot.support.file.service.FileService; import lombok.Data; @@ -52,7 +53,7 @@ public class LocalFileServiceImpl implements FileService { FileUtil.writeFromStream(inputStream, filePrefix + folder + File.separator + fileName); } catch (Exception e) { log.error("文件上传失败", e); - throw new RuntimeException("文件上传失败"); + throw new BusinessException("文件上传失败"); } String fileUrl = File.separator + folder + File.separator + fileName; FileInfo fileInfo = new FileInfo(); diff --git a/src/main/java/com/youlai/boot/system/adapter/log/OperationLogAdapter.java b/src/main/java/com/youlai/boot/system/adapter/log/OperationLogAdapter.java index 6794000e..c1f6b90f 100644 --- a/src/main/java/com/youlai/boot/system/adapter/log/OperationLogAdapter.java +++ b/src/main/java/com/youlai/boot/system/adapter/log/OperationLogAdapter.java @@ -3,18 +3,16 @@ package com.youlai.boot.system.adapter.log; import com.youlai.boot.framework.web.log.OperationLog; import com.youlai.boot.framework.web.log.OperationLogPort; import com.youlai.boot.system.converter.LogConverter; -import com.youlai.boot.system.model.entity.SysLog; import com.youlai.boot.system.service.LogService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; /** * 操作日志适配器 - *实现 framework 层的 {@link OperationLogPort},将切面采集的操作日志落库 sys_log 表
+ *实现 {@link OperationLogPort},落库 sys_log
* * @author Ray.Hao - * @since 0.0.1 - * @see OperationLogPort + * @since 4.7.0 */ @Component @RequiredArgsConstructor @@ -25,9 +23,6 @@ public class OperationLogAdapter implements OperationLogPort { /** * 记录操作日志 - *通过 {@link LogConverter} 将操作日志载荷转换为 {@link SysLog} 后落库
- * - * @param operationLog 操作日志载荷 */ @Override public void record(OperationLog operationLog) { diff --git a/src/main/java/com/youlai/boot/system/controller/UserController.java b/src/main/java/com/youlai/boot/system/controller/UserController.java index b3abe52e..26ab457b 100644 --- a/src/main/java/com/youlai/boot/system/controller/UserController.java +++ b/src/main/java/com/youlai/boot/system/controller/UserController.java @@ -7,6 +7,7 @@ import com.youlai.boot.common.annotation.RepeatSubmit; import com.youlai.boot.common.enums.ActionTypeEnum; import com.youlai.boot.common.enums.LogModuleEnum; import com.youlai.boot.common.enums.StatusEnum; +import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.framework.security.util.SecurityUtils; import com.youlai.boot.common.model.Option; import com.youlai.boot.common.result.ExcelResult; @@ -57,7 +58,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; /** - * 用户控制层 + * 用户管理控制器. * * @author Ray.Hao * @since 4.3.1 @@ -175,7 +176,7 @@ public class UserController { ExcelWriter excelWriter = EasyExcel.write(outputStream).withTemplate(inputStream).build()) { excelWriter.finish(); } catch (IOException e) { - throw new RuntimeException("用户导入模板下载失败", e); + throw new BusinessException("用户导入模板下载失败", e); } } diff --git a/src/main/java/com/youlai/boot/system/converter/LogConverter.java b/src/main/java/com/youlai/boot/system/converter/LogConverter.java index c7bec1fa..199f6bc6 100644 --- a/src/main/java/com/youlai/boot/system/converter/LogConverter.java +++ b/src/main/java/com/youlai/boot/system/converter/LogConverter.java @@ -5,13 +5,19 @@ import com.youlai.boot.system.model.entity.SysLog; import org.mapstruct.Mapper; /** - * 系统日志对象转换器 + * 操作日志转换器 * * @author Ray.Hao - * @since 0.0.1 + * @since 4.7.0 */ @Mapper(componentModel = "spring") public interface LogConverter { + /** + * 操作日志载荷转为日志实体 + * + * @param operationLog 操作日志载荷 + * @return 日志实体 + */ SysLog toEntity(OperationLog operationLog); } diff --git a/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java b/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java index 28c581dd..d3026401 100644 --- a/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java +++ b/src/main/java/com/youlai/boot/system/service/impl/MenuServiceImpl.java @@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.youlai.boot.codegen.model.entity.GenTable; +import com.youlai.boot.common.exception.BusinessException; import com.youlai.boot.framework.security.util.SecurityUtils; import com.youlai.boot.system.converter.MenuConverter; import com.youlai.boot.system.mapper.MenuMapper; @@ -303,7 +304,7 @@ public class MenuServiceImpl extends ServiceImpl校验用户名唯一性,设置默认加密密码,保存用户角色关联(事务一致)。
*
- * @param userForm 用户表单对象
- * @return true|false
+ * @param userForm 用户表单(username 必填且唯一,roleIds 为角色 ID 列表)
+ * @return 新增成功返回 true
+ * @throws BusinessException 如果用户名已存在
*/
@Override
@Transactional(rollbackFor = Exception.class)
@@ -144,11 +157,13 @@ public class UserServiceImpl extends ServiceImpl 校验用户名唯一性(排除自身),同步更新角色关联(事务一致)。
*
- * @param userId 用户ID
- * @param userForm 用户表单对象
- * @return true|false
+ * @param userId 用户 ID
+ * @param userForm 用户表单(username 必填且唯一,roleIds 为角色 ID 列表)
+ * @return 更新成功返回 true
+ * @throws BusinessException 如果用户不存在或用户名已被占用
*/
@Override
@Transactional(rollbackFor = Exception.class)
@@ -181,10 +196,11 @@ public class UserServiceImpl extends ServiceImpl 含角色集合和数据权限范围。
*
- * @param username 用户名
- * @return 用户认证凭证信息 {@link SecurityUser}
+ * @param username 用户名(唯一)
+ * @return 认证凭证,用户不存在时返回 null
*/
@Override
public SecurityUser getAuthInfoByUsername(String username) {
@@ -217,7 +234,11 @@ public class UserServiceImpl extends ServiceImpl 含角色集合和数据权限范围。
+ *
+ * @param mobile 手机号
+ * @return 认证凭证,手机号为空或用户不存在时返回 null
*/
@Override
public SecurityUser getAuthInfoByMobile(String mobile) {
@@ -234,10 +255,11 @@ public class UserServiceImpl extends ServiceImpl 含性别字典翻译,用于 Excel 导出。
*
- * @param queryParams 查询参数
- * @return {@link List 含基础信息、部门名称、角色集合、权限集合(缓存未命中时触发全量刷新)。
*
- * @return {@link CurrentUserVO} 用户信息
+ * @return 当前用户信息(含角色、权限)
*/
@Override
public CurrentUserVO getCurrentUserInfo() {
@@ -338,10 +361,10 @@ public class UserServiceImpl extends ServiceImpl 仅支持修改昵称、头像、性别,至少修改一个字段。
*
- * @param formData 表单数据
- * @return true|false
+ * @param formData 个人中心表单(nickname/avatar/gender 至少一个非空)
+ * @return 修改成功返回 true
+ * @throws BusinessException 如果所有字段均为空
*/
@Override
public boolean updateUserProfile(UserProfileForm formData) {
@@ -371,11 +396,13 @@ public class UserServiceImpl extends ServiceImpl 校验原密码,确认新旧不同,密码变更后使所有会话失效。
*
- * @param userId 用户ID
- * @param data 密码修改表单数据
- * @return true|false
+ * @param userId 用户 ID
+ * @param data 密码表单(含原密码、新密码、确认密码)
+ * @return 修改成功返回 true
+ * @throws BusinessException 如果用户不存在、原密码错误、新旧密码相同或确认密码不一致
*/
@Override
public boolean changeUserPassword(Long userId, PasswordUpdateForm data) {
@@ -415,11 +442,12 @@ public class UserServiceImpl extends ServiceImpl 管理员操作,重置后使该用户所有会话失效。
*
- * @param userId 用户ID
- * @param password 密码重置表单数据
- * @return true|false
+ * @param userId 用户 ID
+ * @param password 新密码(明文,内部加密存储)
+ * @return 重置成功返回 true
*/
@Override
public boolean resetUserPassword(Long userId, String password) {
@@ -435,10 +463,12 @@ public class UserServiceImpl extends ServiceImpl 校验手机号未被其他账号绑定,验证码缓存 5 分钟。
*
* @param mobile 手机号
- * @return true|false
+ * @return 发送成功返回 true,失败返回 false 并记录日志
+ * @throws BusinessException 如果手机号已被其他账号绑定
*/
@Override
public boolean sendMobileCode(String mobile) {
@@ -474,10 +504,12 @@ public class UserServiceImpl extends ServiceImpl 校验密码、验证码、手机号唯一性,成功后删除验证码缓存。
*
- * @param form 表单数据
- * @return true|false
+ * @param form 手机号绑定表单(含密码、手机号、验证码)
+ * @return 绑定成功返回 true
+ * @throws BusinessException 如果用户不存在、密码错误、验证码过期/错误或手机号已被绑定
*/
@Override
public boolean bindOrChangeMobile(MobileUpdateForm form) {
@@ -527,9 +559,11 @@ public class UserServiceImpl extends ServiceImpl 校验邮箱未被其他账号绑定,验证码缓存 5 分钟。
*
- * @param email 邮箱
+ * @param email 邮箱地址
+ * @throws BusinessException 如果邮箱已被其他账号绑定
*/
@Override
public void sendEmailCode(String email) {
@@ -554,10 +588,12 @@ public class UserServiceImpl extends ServiceImpl 校验密码、验证码、邮箱唯一性,成功后删除验证码缓存。
*
- * @param form 表单数据
- * @return true|false
+ * @param form 邮箱绑定表单(含密码、邮箱、验证码)
+ * @return 绑定成功返回 true
+ * @throws BusinessException 如果用户不存在、密码错误、验证码过期/错误或邮箱已被绑定
*/
@Override
public boolean bindOrChangeEmail(EmailUpdateForm form) {
@@ -608,10 +644,12 @@ public class UserServiceImpl extends ServiceImpl 校验密码后将手机号置空。
*
- * @param form 表单数据
- * @return true|false
+ * @param form 密码验证表单(含密码)
+ * @return 解绑成功返回 true
+ * @throws BusinessException 如果用户不存在、未绑定手机号或密码错误
*/
@Override
public boolean unbindMobile(PasswordVerifyForm form) {
@@ -638,10 +676,12 @@ public class UserServiceImpl extends ServiceImpl 校验密码后将邮箱置空。
*
- * @param form 表单数据
- * @return true|false
+ * @param form 密码验证表单(含密码)
+ * @return 解绑成功返回 true
+ * @throws BusinessException 如果用户不存在、未绑定邮箱或密码错误
*/
@Override
public boolean unbindEmail(PasswordVerifyForm form) {
@@ -667,6 +707,13 @@ public class UserServiceImpl extends ServiceImpl