From fb99b2d632d69c596a6619e01e980e7649d4fdb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B8=B8=E7=86=9F=E5=90=B4=E5=BD=A6=E7=A5=96?= Date: Mon, 26 Jan 2026 11:19:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20=E6=B7=BB=E5=8A=A0=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E9=94=99=E8=AF=AF=E6=97=A5=E5=BF=97=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 创建ErrorLogUtils工具类,提供静态方法记录业务错误、接口错误和异常 - 新增SysErrorLog实体类,定义错误日志数据表结构和字段映射 - 实现SysErrorLogController控制器,提供分页查询和详情查看接口 - 创建SysErrorLogData查询条件类,支持按站点、模块、时间等多维度筛选 - 实现SysErrorLogMapper数据访问层,包含分页查询和总数统计功能 - 配置SysErrorLogMapper.xml映射文件,定义动态SQL查询条件 - 构建SysErrorLogService服务层接口及其实现类,封装业务逻辑 - 支持工厂编码自动获取、调用方法名自动识别和异常堆栈自动提取功能 --- .../gaotao/common/utils/ErrorLogUtils.java | 259 ++++++++++++++++++ .../api/controller/SysErrorLogController.java | 48 ++++ .../modules/api/dao/SysErrorLogMapper.java | 33 +++ .../modules/api/entity/SysErrorLog.java | 137 +++++++++ .../modules/api/entity/SysErrorLogData.java | 56 ++++ .../api/service/SysErrorLogService.java | 22 ++ .../service/impl/SysErrorLogServiceImpl.java | 40 +++ .../mapper/api/SysErrorLogMapper.xml | 66 +++++ 8 files changed, 661 insertions(+) create mode 100644 src/main/java/com/gaotao/common/utils/ErrorLogUtils.java create mode 100644 src/main/java/com/gaotao/modules/api/controller/SysErrorLogController.java create mode 100644 src/main/java/com/gaotao/modules/api/dao/SysErrorLogMapper.java create mode 100644 src/main/java/com/gaotao/modules/api/entity/SysErrorLog.java create mode 100644 src/main/java/com/gaotao/modules/api/entity/SysErrorLogData.java create mode 100644 src/main/java/com/gaotao/modules/api/service/SysErrorLogService.java create mode 100644 src/main/java/com/gaotao/modules/api/service/impl/SysErrorLogServiceImpl.java create mode 100644 src/main/resources/mapper/api/SysErrorLogMapper.xml diff --git a/src/main/java/com/gaotao/common/utils/ErrorLogUtils.java b/src/main/java/com/gaotao/common/utils/ErrorLogUtils.java new file mode 100644 index 0000000..8b999c7 --- /dev/null +++ b/src/main/java/com/gaotao/common/utils/ErrorLogUtils.java @@ -0,0 +1,259 @@ +package com.gaotao.common.utils; + +import com.gaotao.modules.api.dao.SysErrorLogMapper; +import com.gaotao.modules.api.entity.SysErrorLog; +import com.gaotao.modules.sys.entity.SysUserEntity; +import jakarta.annotation.PostConstruct; +import org.apache.shiro.SecurityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.Date; + +/** + * 错误日志工具类 + * 提供简单易用的静态方法记录错误日志 + * + * 使用示例: + * 1. 业务错误:ErrorLogUtils.log("55", "立库自动化", "直接组盘", "W00250", "栈板不存在"); + * 2. 接口错误:ErrorLogUtils.logInterface("55", "立库自动化", "推送WCS", "WCS", "PushPalletDetailNew", "W00250", requestJson, "WCS返回失败"); + * 3. 异常记录:ErrorLogUtils.logException("55", "立库自动化", "直接组盘", palletId, e); + * + * 注意:site参数可以传null,传null时会自动从当前登录用户获取 + * + * @author rqrq + * @date 2026/01/26 + */ +@Component +public class ErrorLogUtils { + + private static final Logger logger = LoggerFactory.getLogger(ErrorLogUtils.class); + + private static SysErrorLogMapper errorLogMapper; + + @Autowired + private SysErrorLogMapper mapper; + + @PostConstruct + public void init() { + ErrorLogUtils.errorLogMapper = this.mapper; + } + + // ==================== 业务错误记录 ==================== + + /** + * 记录业务错误 + * @param site 工厂编码(可为null,null时自动获取) + * @param moduleName 模块名称(可为null) + * @param functionName 功能名称(可为null) + * @param businessKey 业务主键(如:W00250) + * @param errorMessage 错误信息 + */ + public static void log(String site, String moduleName, String functionName, + String businessKey, String errorMessage) { + saveLog(site, moduleName, functionName, businessKey, "N", null, null, null, + errorMessage, null); + } + + /** + * 记录业务错误(带详情) + * @param site 工厂编码(可为null,null时自动获取) + * @param moduleName 模块名称(可为null) + * @param functionName 功能名称(可为null) + * @param businessKey 业务主键 + * @param errorMessage 错误信息 + * @param errorDetail 错误详情 + */ + public static void log(String site, String moduleName, String functionName, + String businessKey, String errorMessage, String errorDetail) { + saveLog(site, moduleName, functionName, businessKey, "N", null, null, null, + errorMessage, errorDetail); + } + + // ==================== 接口错误记录 ==================== + + /** + * 记录接口错误 + * @param site 工厂编码(可为null,null时自动获取) + * @param moduleName 模块名称(可为null) + * @param functionName 功能名称(可为null) + * @param interfaceType 接口类型(WCS/IFS/TUSK) + * @param interfaceName 接口名称(如:PushPalletDetailNew) + * @param businessKey 业务主键 + * @param requestData 接口入参(JSON字符串) + * @param errorMessage 错误信息 + */ + public static void logInterface(String site, String moduleName, String functionName, + String interfaceType, String interfaceName, + String businessKey, String requestData, + String errorMessage) { + saveLog(site, moduleName, functionName, businessKey, "Y", interfaceType, interfaceName, + requestData, errorMessage, null); + } + + /** + * 记录接口错误(带详情) + * @param site 工厂编码(可为null,null时自动获取) + * @param moduleName 模块名称(可为null) + * @param functionName 功能名称(可为null) + * @param interfaceType 接口类型(WCS/IFS/TUSK) + * @param interfaceName 接口名称 + * @param businessKey 业务主键 + * @param requestData 接口入参 + * @param errorMessage 错误信息 + * @param errorDetail 错误详情(如响应数据) + */ + public static void logInterface(String site, String moduleName, String functionName, + String interfaceType, String interfaceName, + String businessKey, String requestData, + String errorMessage, String errorDetail) { + saveLog(site, moduleName, functionName, businessKey, "Y", interfaceType, interfaceName, + requestData, errorMessage, errorDetail); + } + + // ==================== 异常记录 ==================== + + /** + * 记录异常(自动提取堆栈) + * @param site 工厂编码(可为null,null时自动获取) + * @param moduleName 模块名称(可为null) + * @param functionName 功能名称(可为null) + * @param businessKey 业务主键 + * @param e 异常对象 + */ + public static void logException(String site, String moduleName, String functionName, + String businessKey, Exception e) { + String errorDetail = getStackTrace(e); + saveLog(site, moduleName, functionName, businessKey, "N", null, null, null, + e.getMessage(), errorDetail); + } + + /** + * 记录接口异常 + * @param site 工厂编码(可为null,null时自动获取) + * @param moduleName 模块名称(可为null) + * @param functionName 功能名称(可为null) + * @param interfaceType 接口类型(WCS/IFS/TUSK) + * @param interfaceName 接口名称 + * @param businessKey 业务主键 + * @param requestData 接口入参 + * @param e 异常对象 + */ + public static void logInterfaceException(String site, String moduleName, String functionName, + String interfaceType, String interfaceName, + String businessKey, String requestData, Exception e) { + String errorDetail = getStackTrace(e); + saveLog(site, moduleName, functionName, businessKey, "Y", interfaceType, interfaceName, + requestData, e.getMessage(), errorDetail); + } + + // ==================== 私有方法 ==================== + + /** + * 保存错误日志 + */ + private static void saveLog(String site, String moduleName, String functionName, String businessKey, + String isInterface, String interfaceType, String interfaceName, + String requestData, String errorMessage, String errorDetail) { + try { + if (errorLogMapper == null) { + logger.warn("ErrorLogUtils未初始化,无法记录错误日志"); + return; + } + + SysErrorLog log = new SysErrorLog(); + // site为null时自动获取 - rqrq + log.setSite(site != null ? site : getSite()); + log.setModuleName(moduleName); + log.setFunctionName(functionName); + log.setBusinessKey(businessKey); + log.setIsInterface(isInterface); + log.setInterfaceType(interfaceType); + log.setInterfaceName(interfaceName); + log.setRequestData(requestData); + log.setMethodName(getCallerMethod()); // 自动获取调用者方法名 + log.setErrorMessage(truncate(errorMessage, 500)); + log.setErrorDetail(errorDetail); + log.setUsername(getUsername()); + log.setCreatedTime(new Date()); + + errorLogMapper.insert(log); + } catch (Exception ex) { + // 记录日志失败不影响主流程,仅打印警告 + logger.warn("记录错误日志失败: {}", ex.getMessage()); + } + } + + /** + * 获取当前用户名 + */ + private static String getUsername() { + try { + return ((SysUserEntity) SecurityUtils.getSubject().getPrincipal()).getUsername(); + } catch (Exception e) { + return "后台定时任务"; + } + } + + /** + * 获取当前站点 + */ + private static String getSite() { + try { + return ((SysUserEntity) SecurityUtils.getSubject().getPrincipal()).getSite(); + } catch (Exception e) { + return "55"; // 默认站点 + } + } + + /** + * 自动获取调用者的方法名 + * 格式:类名.方法名 + */ + private static String getCallerMethod() { + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + // 0: getStackTrace + // 1: getCallerMethod + // 2: saveLog + // 3: log/logInterface/logException等方法 + // 4: 实际调用者 + if (stackTrace.length > 4) { + StackTraceElement caller = stackTrace[4]; + String className = caller.getClassName(); + // 只取类名,不要包名 + int lastDot = className.lastIndexOf('.'); + if (lastDot > 0) { + className = className.substring(lastDot + 1); + } + return className + "." + caller.getMethodName(); + } + return "Unknown"; + } + + /** + * 截断字符串 + */ + private static String truncate(String str, int maxLen) { + if (str == null) { + return null; + } + return str.length() > maxLen ? str.substring(0, maxLen) : str; + } + + /** + * 获取异常堆栈信息 + */ + private static String getStackTrace(Exception e) { + if (e == null) { + return null; + } + StringWriter sw = new StringWriter(); + e.printStackTrace(new PrintWriter(sw)); + return sw.toString(); + } +} diff --git a/src/main/java/com/gaotao/modules/api/controller/SysErrorLogController.java b/src/main/java/com/gaotao/modules/api/controller/SysErrorLogController.java new file mode 100644 index 0000000..2c92d80 --- /dev/null +++ b/src/main/java/com/gaotao/modules/api/controller/SysErrorLogController.java @@ -0,0 +1,48 @@ +package com.gaotao.modules.api.controller; + +import com.gaotao.common.utils.PageUtils; +import com.gaotao.common.utils.R; +import com.gaotao.modules.api.entity.SysErrorLog; +import com.gaotao.modules.api.entity.SysErrorLogData; +import com.gaotao.modules.api.service.SysErrorLogService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +/** + * 系统错误日志Controller + * 提供错误日志的查询接口 + * + * @author rqrq + * @date 2026/01/26 + */ +@RestController +@RequestMapping("/api/sysErrorLog") +public class SysErrorLogController { + + @Autowired + private SysErrorLogService sysErrorLogService; + + /** + * 分页查询错误日志 + * @param data 查询条件 + * @return 分页结果 + * @author rqrq + */ + @PostMapping("/list") + public R list(@RequestBody SysErrorLogData data) { + PageUtils page = sysErrorLogService.queryPage(data); + return R.ok().put("page", page); + } + + /** + * 查询错误日志详情 + * @param id 日志ID + * @return 日志详情 + * @author rqrq + */ + @PostMapping("/detail") + public R detail(@RequestBody SysErrorLogData data) { + SysErrorLog log = sysErrorLogService.getById(data.getId()); + return R.ok().put("row", log); + } +} diff --git a/src/main/java/com/gaotao/modules/api/dao/SysErrorLogMapper.java b/src/main/java/com/gaotao/modules/api/dao/SysErrorLogMapper.java new file mode 100644 index 0000000..95db2c4 --- /dev/null +++ b/src/main/java/com/gaotao/modules/api/dao/SysErrorLogMapper.java @@ -0,0 +1,33 @@ +package com.gaotao.modules.api.dao; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.gaotao.modules.api.entity.SysErrorLog; +import com.gaotao.modules.api.entity.SysErrorLogData; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 系统错误日志Mapper + * + * @author rqrq + * @date 2026/01/26 + */ +@Mapper +public interface SysErrorLogMapper extends BaseMapper { + + /** + * 分页查询错误日志 + * @param data 查询条件 + * @return 错误日志列表 + */ + List queryList(@Param("data") SysErrorLogData data); + + /** + * 查询总数 + * @param data 查询条件 + * @return 总数 + */ + int queryTotal(@Param("data") SysErrorLogData data); +} diff --git a/src/main/java/com/gaotao/modules/api/entity/SysErrorLog.java b/src/main/java/com/gaotao/modules/api/entity/SysErrorLog.java new file mode 100644 index 0000000..974b931 --- /dev/null +++ b/src/main/java/com/gaotao/modules/api/entity/SysErrorLog.java @@ -0,0 +1,137 @@ +package com.gaotao.modules.api.entity; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import org.apache.ibatis.type.Alias; + +import java.io.Serializable; +import java.util.Date; + +/** + * 系统错误日志实体类 + * 表名: sys_error_log + * 用途: 记录系统所有模块的错误信息,供问题排查和统计分析 + * + * 索引信息: + * - PRIMARY KEY (id) + * - INDEX idx_error_site_time (site, created_time) + * - INDEX idx_error_module (module_name) + * - INDEX idx_error_business (business_key) + * + * @author rqrq + * @date 2026/01/26 + */ +@Data +@TableName("sys_error_log") +@Alias("SysErrorLog") +public class SysErrorLog implements Serializable { + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @TableId(type = IdType.AUTO) + private Long id; + + /** + * 工厂编码 + * 如:55 + */ + @TableField("site") + private String site; + + // ========== 模块和功能信息 ========== + + /** + * 模块名称 + * 如:立库自动化、仓库管理 + * 可为空 + */ + @TableField("module_name") + private String moduleName; + + /** + * 功能名称 + * 如:直接组盘、IFS重试 + * 可为空 + */ + @TableField("function_name") + private String functionName; + + // ========== 业务信息 ========== + + /** + * 业务主键 + * 如:栈板号W00250、申请单号REQ202601、标签号A552026011600000892 + */ + @TableField("business_key") + private String businessKey; + + // ========== 接口信息 ========== + + /** + * 是否接口调用 + * Y-是 N-否 + */ + @TableField("is_interface") + private String isInterface; + + /** + * 接口类型 + * WCS/IFS/TUSK + */ + @TableField("interface_type") + private String interfaceType; + + /** + * 接口名称 + * 如:PushPalletDetailNew、MoveInventoryPart + */ + @TableField("interface_name") + private String interfaceName; + + /** + * 接口入参 + * JSON格式 + */ + @TableField("request_data") + private String requestData; + + // ========== 方法和错误信息 ========== + + /** + * 后台方法名 + * 自动获取,格式:类名.方法名 + * 如:WcsIntegrationServiceImpl.completePalletAssembly + */ + @TableField("method_name") + private String methodName; + + /** + * 错误信息 + * 简短错误描述,最长500字符 + */ + @TableField("error_message") + private String errorMessage; + + /** + * 错误详情 + * 完整堆栈或响应数据等 + */ + @TableField("error_detail") + private String errorDetail; + + // ========== 其他信息 ========== + + /** + * 操作用户名 + */ + @TableField("username") + private String username; + + /** + * 创建时间 + */ + @TableField(value = "created_time", fill = FieldFill.INSERT) + private Date createdTime; +} diff --git a/src/main/java/com/gaotao/modules/api/entity/SysErrorLogData.java b/src/main/java/com/gaotao/modules/api/entity/SysErrorLogData.java new file mode 100644 index 0000000..6771370 --- /dev/null +++ b/src/main/java/com/gaotao/modules/api/entity/SysErrorLogData.java @@ -0,0 +1,56 @@ +package com.gaotao.modules.api.entity; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.apache.ibatis.type.Alias; + +/** + * 系统错误日志查询条件类 + * 继承SysErrorLog,增加分页和查询参数 + * + * @author rqrq + * @date 2026/01/26 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Alias("SysErrorLogData") +public class SysErrorLogData extends SysErrorLog { + private static final long serialVersionUID = 1L; + + // ========== 分页参数 ========== + + /** + * 页码 + */ + private Integer page; + + /** + * 每页条数 + */ + private Integer limit; + + /** + * 分页起始位置 + * SQL: OFFSET offset ROWS FETCH NEXT limit ROWS ONLY + */ + private Integer offset; + + // ========== 查询参数 ========== + + /** + * 开始时间 + * 格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss + */ + private String startTime; + + /** + * 结束时间 + * 格式:yyyy-MM-dd 或 yyyy-MM-dd HH:mm:ss + */ + private String endTime; + + /** + * 错误信息模糊查询 + */ + private String searchErrorMessage; +} diff --git a/src/main/java/com/gaotao/modules/api/service/SysErrorLogService.java b/src/main/java/com/gaotao/modules/api/service/SysErrorLogService.java new file mode 100644 index 0000000..b3b22d4 --- /dev/null +++ b/src/main/java/com/gaotao/modules/api/service/SysErrorLogService.java @@ -0,0 +1,22 @@ +package com.gaotao.modules.api.service; + +import com.baomidou.mybatisplus.extension.service.IService; +import com.gaotao.common.utils.PageUtils; +import com.gaotao.modules.api.entity.SysErrorLog; +import com.gaotao.modules.api.entity.SysErrorLogData; + +/** + * 系统错误日志Service接口 + * + * @author rqrq + * @date 2026/01/26 + */ +public interface SysErrorLogService extends IService { + + /** + * 分页查询错误日志 + * @param data 查询条件 + * @return 分页结果 + */ + PageUtils queryPage(SysErrorLogData data); +} diff --git a/src/main/java/com/gaotao/modules/api/service/impl/SysErrorLogServiceImpl.java b/src/main/java/com/gaotao/modules/api/service/impl/SysErrorLogServiceImpl.java new file mode 100644 index 0000000..b6e3623 --- /dev/null +++ b/src/main/java/com/gaotao/modules/api/service/impl/SysErrorLogServiceImpl.java @@ -0,0 +1,40 @@ +package com.gaotao.modules.api.service.impl; + +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.gaotao.common.utils.PageUtils; +import com.gaotao.modules.api.dao.SysErrorLogMapper; +import com.gaotao.modules.api.entity.SysErrorLog; +import com.gaotao.modules.api.entity.SysErrorLogData; +import com.gaotao.modules.api.service.SysErrorLogService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * 系统错误日志Service实现类 + * + * @author rqrq + * @date 2026/01/26 + */ +@Service("sysErrorLogService") +public class SysErrorLogServiceImpl extends ServiceImpl + implements SysErrorLogService { + + @Override + public PageUtils queryPage(SysErrorLogData data) { + // 计算分页偏移量 - rqrq + if (data.getPage() != null && data.getLimit() != null) { + data.setOffset((data.getPage() - 1) * data.getLimit()); + } else { + data.setPage(1); + data.setLimit(10); + data.setOffset(0); + } + + // 查询数据 - rqrq + List list = baseMapper.queryList(data); + int total = baseMapper.queryTotal(data); + + return new PageUtils(list, total, data.getLimit(), data.getPage()); + } +} diff --git a/src/main/resources/mapper/api/SysErrorLogMapper.xml b/src/main/resources/mapper/api/SysErrorLogMapper.xml new file mode 100644 index 0000000..3c996f0 --- /dev/null +++ b/src/main/resources/mapper/api/SysErrorLogMapper.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + AND site = #{data.site} + + + AND module_name = #{data.moduleName} + + + AND function_name = #{data.functionName} + + + AND business_key LIKE '%' + #{data.businessKey} + '%' + + + AND is_interface = #{data.isInterface} + + + AND interface_type = #{data.interfaceType} + + + AND interface_name LIKE '%' + #{data.interfaceName} + '%' + + + AND username LIKE '%' + #{data.username} + '%' + + + AND error_message LIKE '%' + #{data.searchErrorMessage} + '%' + + + AND created_time >= #{data.startTime} + + + AND created_time <= #{data.endTime} + + + + + + + + + + +