From a49994cc574da181aaaf1cf14bf98852f8f335f0 Mon Sep 17 00:00:00 2001 From: lzh Date: Wed, 21 Jan 2026 18:43:39 +0800 Subject: [PATCH] =?UTF-8?q?refactor(ops):=20=E9=87=8D=E6=9E=84=E4=B8=9A?= =?UTF-8?q?=E5=8A=A1=E6=97=A5=E5=BF=97=E6=B3=A8=E8=A7=A3=E5=92=8C=E5=88=87?= =?UTF-8?q?=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 更新 BusinessLog 注解以支持新的事件日志模型 - 更新 BusinessLogAspect 切面使用 EventLogRecorder Co-Authored-By: Claude Opus 4.5 --- .../log/annotation/BusinessLog.java | 95 +++++- .../log/aspect/BusinessLogAspect.java | 318 ++++++++++++------ 2 files changed, 295 insertions(+), 118 deletions(-) diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/annotation/BusinessLog.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/annotation/BusinessLog.java index 04512a4..e2790cc 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/annotation/BusinessLog.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/annotation/BusinessLog.java @@ -1,5 +1,7 @@ package com.viewsh.module.ops.infrastructure.log.annotation; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventLevel; import com.viewsh.module.ops.infrastructure.log.enumeration.LogScope; import com.viewsh.module.ops.infrastructure.log.enumeration.LogType; @@ -13,11 +15,20 @@ import java.lang.annotation.*; * 使用示例: *
  * {@code
+ * // 基础用法(使用 LogType/LogScope)
  * @BusinessLog(type = LogType.DISPATCH, scope = LogScope.ORDER,
- *             description = "自动派单", includeParams = true, includeResult = true)
+ *             description = "自动派单", includeParams = true)
  * public DispatchResult dispatch(OrderDispatchContext context) {
  *     // ...
  * }
+ *
+ * // 新用法(直接指定 EventDomain 和 Module)
+ * @BusinessLog(module = "clean", domain = EventDomain.DEVICE,
+ *             eventType = "TTS_SENT", description = "语音播报",
+ *             deviceId = "#deviceId", personId = "#context.cleanerId")
+ * public void broadcast(String text, Long deviceId) {
+ *     // ...
+ * }
  * }
  * 
* @@ -28,15 +39,19 @@ import java.lang.annotation.*; @Documented public @interface BusinessLog { - /** - * 日志类型 - */ - LogType type(); + // ==================== 旧版字段(保持兼容) ==================== /** - * 日志作用域 + * 日志类型(旧版,保持兼容) + *

+ * 当指定了 domain 和 eventType 时,此字段可忽略 */ - LogScope scope(); + LogType type() default LogType.SYSTEM; + + /** + * 日志作用域(旧版,保持兼容) + */ + LogScope scope() default LogScope.SYSTEM; /** * 操作描述 @@ -84,4 +99,70 @@ public @interface BusinessLog { * 自定义标签 */ String[] tags() default {}; + + // ==================== 新版字段(支持 EventLogRecorder) ==================== + + /** + * 模块标识(新版) + *

+ * 如:clean, repair, patrol + *

+ * 当指定此字段时,将使用 EventLogRecorder 记录日志 + */ + String module() default ""; + + /** + * 事件域(新版) + *

+ * 当指定此字段时,将使用 EventLogRecorder 记录日志 + */ + EventDomain domain() default EventDomain.SYSTEM; + + /** + * 事件类型(新版) + *

+ * 当指定此字段时,将使用 EventLogRecorder 记录日志 + */ + String eventType() default ""; + + /** + * 日志级别(新版) + */ + EventLevel level() default EventLevel.INFO; + + /** + * 设备ID提取表达式(SpEL) + *

+ * 示例:"#deviceId", "#context.deviceId", "#request.deviceId" + */ + String deviceId() default ""; + + /** + * 人员ID提取表达式(SpEL) + *

+ * 示例:"#cleanerId", "#context.userId", "#request.assigneeId" + */ + String personId() default ""; + + /** + * 业务实体ID提取表达式(SpEL) + *

+ * 示例:"#orderId", "#context.orderId" + */ + String targetId() default ""; + + /** + * 业务实体类型 + *

+ * 如:order, area, task + */ + String targetType() default ""; + + /** + * 简要摘要模板(用于列表展示) + *

+ * 示例:"播报: {text}", "到岗: {areaName}" + */ + String summary() default ""; + } diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/aspect/BusinessLogAspect.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/aspect/BusinessLogAspect.java index 2d2381a..dac2e44 100644 --- a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/aspect/BusinessLogAspect.java +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/aspect/BusinessLogAspect.java @@ -1,10 +1,12 @@ package com.viewsh.module.ops.infrastructure.log.aspect; import com.viewsh.module.ops.infrastructure.log.annotation.BusinessLog; -import com.viewsh.module.ops.infrastructure.log.context.BusinessLogContext; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventLevel; import com.viewsh.module.ops.infrastructure.log.enumeration.LogScope; import com.viewsh.module.ops.infrastructure.log.enumeration.LogType; -import com.viewsh.module.ops.infrastructure.log.publisher.BusinessLogPublisher; +import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecord; +import com.viewsh.module.ops.infrastructure.log.recorder.EventLogRecorder; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; @@ -19,8 +21,6 @@ import org.springframework.expression.spel.support.StandardEvaluationContext; import org.springframework.stereotype.Component; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.List; /** * 业务日志切面 @@ -28,12 +28,12 @@ import java.util.List; * 职责: * 1. 拦截 @BusinessLog 注解的方法 * 2. 提取方法参数和结果 - * 3. 发布业务日志 + * 3. 统一通过 EventLogRecorder 记录日志 *

* 设计说明: * - 使用环绕通知 * - 支持 SpEL 表达式提取参数和结果 - * - 支持异步日志发布 + * - 所有日志异步记录,不阻塞主业务流程 * * @author lzh */ @@ -43,69 +43,67 @@ import java.util.List; public class BusinessLogAspect { @Resource - private BusinessLogPublisher businessLogPublisher; + private EventLogRecorder eventLogRecorder; private final ExpressionParser parser = new SpelExpressionParser(); @Around("@annotation(businessLog)") public Object around(ProceedingJoinPoint joinPoint, BusinessLog businessLog) throws Throwable { - long startTime = System.currentTimeMillis(); - - // 构建日志上下文 - BusinessLogContext context = buildContext(joinPoint, businessLog); - Object result = null; - Throwable throwable = null; try { // 执行目标方法 result = joinPoint.proceed(); - // 提取结果信息 - if (businessLog.includeResult() || !businessLog.result().isEmpty()) { - extractResultInfo(context, result, businessLog); - } - - // 发布成功日志 - if (businessLog.async()) { - businessLogPublisher.publishSuccess(context); - } else { - businessLogPublisher.publishSuccess(context); - } + // 发布日志 + publishLog(joinPoint, businessLog, result, null); return result; } catch (Exception e) { - throwable = e; - // 发布失败日志 - if (businessLog.async()) { - businessLogPublisher.publishError(context, e); - } else { - businessLogPublisher.publishError(context, e); - } - + publishLog(joinPoint, businessLog, null, e); throw e; - - } finally { - // 记录执行时长 - long duration = System.currentTimeMillis() - startTime; - context.putExtra("duration", duration); - - // 记录参数信息 - if (businessLog.includeParams()) { - extractParamsInfo(context, joinPoint, businessLog); - } } } /** - * 构建日志上下文 + * 发布日志 */ - private BusinessLogContext buildContext(ProceedingJoinPoint joinPoint, BusinessLog businessLog) { - LogType type = businessLog.type(); - LogScope scope = businessLog.scope(); + private void publishLog(ProceedingJoinPoint joinPoint, BusinessLog businessLog, Object result, Throwable throwable) { + try { + EventLogRecord record = buildEventLogRecord(joinPoint, businessLog); + // 设置日志级别 + if (throwable != null) { + record.setLevel(EventLevel.ERROR); + } else { + record.setLevel(businessLog.level()); + } + + // 提取关联ID和扩展信息 + EvaluationContext evalContext = createEvaluationContext(joinPoint, result); + extractIds(record, businessLog, evalContext); + extractExtraInfo(record, businessLog, evalContext, throwable); + + // 处理摘要模板 + if (!businessLog.summary().isEmpty()) { + String summary = processSummaryTemplate(businessLog.summary(), evalContext); + record.setSummary(summary); + } + + // 异步记录(record 方法已默认异步) + eventLogRecorder.record(record); + + } catch (Exception e) { + log.warn("[BusinessLogAspect] 记录日志失败", e); + } + } + + /** + * 构建 EventLogRecord + */ + private EventLogRecord buildEventLogRecord(ProceedingJoinPoint joinPoint, BusinessLog businessLog) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); String className = method.getDeclaringClass().getSimpleName(); @@ -116,98 +114,170 @@ public class BusinessLogAspect { description = className + "." + methodName; } - BusinessLogContext context = BusinessLogContext.builder() - .type(type) - .scope(scope) - .description(description) + // 判断使用新版还是旧版注解 + String module = !businessLog.module().isEmpty() ? businessLog.module() : "ops"; + EventDomain domain = businessLog.domain() != EventDomain.SYSTEM ? businessLog.domain() : mapLogTypeToEventDomain(businessLog.type()); + String eventType = !businessLog.eventType().isEmpty() ? businessLog.eventType() : + (businessLog.type() != null ? businessLog.type().getCode() : "UNKNOWN"); + + EventLogRecord record = EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(description) .build(); - // 添加标签 - for (String tag : businessLog.tags()) { - context.putExtra("tag:" + tag, true); + // 添加扩展信息 + if (businessLog.tags().length > 0) { + for (String tag : businessLog.tags()) { + record.putPayload("tag:" + tag, true); + } } + record.putPayload("className", className); + record.putPayload("methodName", methodName); + record.putPayload("scope", businessLog.scope().getCode()); - // 添加方法信息 - context.putExtra("className", className); - context.putExtra("methodName", methodName); - - return context; + return record; } /** - * 提取参数信息 + * 提取设备、人员、业务实体ID */ - private void extractParamsInfo(BusinessLogContext context, ProceedingJoinPoint joinPoint, - BusinessLog businessLog) { - MethodSignature signature = (MethodSignature) joinPoint.getSignature(); - String[] parameterNames = signature.getParameterNames(); - Object[] args = joinPoint.getArgs(); - - if (parameterNames == null || parameterNames.length == 0) { - return; + private void extractIds(EventLogRecord record, BusinessLog businessLog, EvaluationContext evalContext) { + // 提取设备ID + if (!businessLog.deviceId().isEmpty()) { + extractId(record, businessLog.deviceId(), evalContext, "deviceId", true); } - // 提取指定参数 + // 提取人员ID + if (!businessLog.personId().isEmpty()) { + extractId(record, businessLog.personId(), evalContext, "personId", true); + } + + // 提取业务实体ID + if (!businessLog.targetId().isEmpty()) { + extractId(record, businessLog.targetId(), evalContext, "targetId", true); + } + + // 设置业务实体类型 + if (!businessLog.targetType().isEmpty()) { + record.setTargetType(businessLog.targetType()); + } else if (businessLog.scope() == LogScope.ORDER) { + record.setTargetType("order"); + } + } + + /** + * 提取扩展信息 + */ + private void extractExtraInfo(EventLogRecord record, BusinessLog businessLog, + EvaluationContext evalContext, Throwable throwable) { + // 提取结果信息 + if (businessLog.includeResult() && !businessLog.result().isEmpty()) { + try { + Expression expression = parser.parseExpression(businessLog.result()); + Object value = expression.getValue(evalContext); + if (value != null) { + String key = extractKey(businessLog.result()); + record.putPayload(key, value); + } + } catch (Exception e) { + log.debug("Failed to extract result: {}", businessLog.result()); + } + } + + // 提取自定义参数 if (businessLog.params().length > 0) { - EvaluationContext evalContext = createEvaluationContext(joinPoint); for (String param : businessLog.params()) { try { Expression expression = parser.parseExpression(param); Object value = expression.getValue(evalContext); - String key = extractKey(param); - context.putExtra(key, value); + if (value != null) { + String key = extractKey(param); + record.putPayload(key, value); + } } catch (Exception e) { - log.warn("Failed to extract param: {}", param, e); + log.debug("Failed to extract param: {}", param); } } - } else { - // 提取所有参数 - for (int i = 0; i < Math.min(parameterNames.length, args.length); i++) { - context.putExtra("param:" + parameterNames[i], args[i]); - } + } + + // 添加错误信息 + if (throwable != null) { + record.putPayload("errorMessage", throwable.getMessage()); + record.putPayload("errorClass", throwable.getClass().getSimpleName()); } } /** - * 提取结果信息 + * 提取ID值 */ - private void extractResultInfo(BusinessLogContext context, Object result, BusinessLog businessLog) { - if (!businessLog.result().isEmpty()) { - // 使用 SpEL 表达式提取 - EvaluationContext evalContext = new StandardEvaluationContext(result); - try { - Expression expression = parser.parseExpression(businessLog.result()); - Object value = expression.getValue(evalContext); - String key = extractKey(businessLog.result()); - context.putExtra(key, value); - } catch (Exception e) { - log.warn("Failed to extract result: {}", businessLog.result(), e); - } - } else { - // 存储完整结果 - context.putExtra("result", result); - } - - // 尝试提取 success 字段 - if (result != null) { - try { - EvaluationContext evalContext = new StandardEvaluationContext(result); - Expression expression = parser.parseExpression("success"); - Object success = expression.getValue(evalContext); - if (success instanceof Boolean) { - context.setSuccess((Boolean) success); + private void extractId(EventLogRecord record, String expression, EvaluationContext evalContext, + String fieldName, boolean setField) { + try { + Expression expr = parser.parseExpression(expression); + Object value = expr.getValue(evalContext); + if (value instanceof Long) { + if (setField && "deviceId".equals(fieldName)) { + record.setDeviceId((Long) value); + } else if (setField && "personId".equals(fieldName)) { + record.setPersonId((Long) value); + } else if (setField && "targetId".equals(fieldName)) { + record.setTargetId((Long) value); } - } catch (Exception ignored) { - // 没有 success 字段,默认为成功 - context.setSuccess(true); + } else if (value instanceof Integer) { + long longValue = ((Integer) value).longValue(); + if (setField && "deviceId".equals(fieldName)) { + record.setDeviceId(longValue); + } else if (setField && "personId".equals(fieldName)) { + record.setPersonId(longValue); + } else if (setField && "targetId".equals(fieldName)) { + record.setTargetId(longValue); + } + } else if (value != null) { + record.putPayload(fieldName, value); } + } catch (Exception e) { + log.debug("Failed to extract {}: {}", fieldName, expression); + } + } + + /** + * 处理摘要模板 + *

+ * 支持 {paramName} 占位符替换 + */ + private String processSummaryTemplate(String template, EvaluationContext evalContext) { + try { + String result = template; + int start = template.indexOf('{'); + while (start >= 0) { + int end = template.indexOf('}', start); + if (end < 0) break; + + String paramName = template.substring(start + 1, end); + try { + Expression expression = parser.parseExpression("#" + paramName); + Object value = expression.getValue(evalContext); + if (value != null) { + result = result.replace("{" + paramName + "}", String.valueOf(value)); + } + } catch (Exception e) { + // 保持原样 + } + + start = template.indexOf('{', end + 1); + } + return result; + } catch (Exception e) { + return template; } } /** * 创建 SpEL 评估上下文 */ - private EvaluationContext createEvaluationContext(ProceedingJoinPoint joinPoint) { + private EvaluationContext createEvaluationContext(ProceedingJoinPoint joinPoint, Object result) { StandardEvaluationContext context = new StandardEvaluationContext(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); @@ -220,15 +290,41 @@ public class BusinessLogAspect { } } + // 添加 result 变量(用于结果提取) + if (result != null) { + context.setVariable("result", result); + } + return context; } + /** + * 将 LogType 映射到 EventDomain + */ + private EventDomain mapLogTypeToEventDomain(LogType logType) { + if (logType == null) { + return EventDomain.SYSTEM; + } + + switch (logType) { + case DISPATCH: + return EventDomain.DISPATCH; + case DEVICE: + return EventDomain.DEVICE; + case NOTIFICATION: + return EventDomain.DEVICE; + case CLEANER: + case SYSTEM: + case QUEUE: + case LIFECYCLE: + case TRANSITION: + default: + return EventDomain.SYSTEM; + } + } + /** * 从表达式中提取键名 - *

- * 示例: - * - "#context.orderId" -> "orderId" - * - "#result.assigneeId" -> "assigneeId" */ private String extractKey(String expression) { if (expression.contains(".")) {