diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/dal/dataobject/log/OpsBusinessEventLogDO.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/dal/dataobject/log/OpsBusinessEventLogDO.java new file mode 100644 index 0000000..5b9028f --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/dal/dataobject/log/OpsBusinessEventLogDO.java @@ -0,0 +1,138 @@ +package com.viewsh.module.ops.dal.dataobject.log; + +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.KeySequence; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import com.viewsh.framework.mybatis.core.dataobject.BaseDO; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 通用业务事件日志 DO + *

+ * 用于记录所有模块的业务事件日志,支持多模块(clean/repair/patrol等) + * 采用方案A:在记录时��步填充设备/人员名称冗余字段,便于展示时直接查询 + * + * @author lzh + */ +@TableName(value = "ops_business_event_log", autoResultMap = true) +@KeySequence("ops_business_event_log_seq") +@Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class OpsBusinessEventLogDO extends BaseDO { + + // ==================== 主键 ==================== + + /** + * 主键ID + */ + @TableId + private Long id; + + // ==================== 时间与级别 ==================== + + /** + * 事件时间 + */ + private LocalDateTime eventTime; + + /** + * 日志级别(INFO=信息/WARN=警告/ERROR=错误) + */ + private String eventLevel; + + // ==================== 业务分类 ==================== + + /** + * 模块标识(clean=保洁/repair=维修/patrol=巡检) + */ + private String module; + + /** + * 事件域(DISPATCH=调度/BEACON=信标/TRAFFIC=客流/DEVICE=设备/SYSTEM=系统/AUDIT=审计) + */ + private String eventDomain; + + /** + * 事件类型(如:ARRIVE_CONFIRMED/TTS_SENT/AUTO_DISPATCH) + */ + private String eventType; + + // ==================== 设备关联 ==================== + + /** + * 设备ID(关联 iot_device.id) + */ + private Long deviceId; + + /** + * 设备名称(冗余字段,异步填充) + */ + private String deviceName; + + /** + * 设备编码(冗余字段,如设备序列号) + */ + private String deviceCode; + + /** + * 设备类型(冗余字段,如:BADGE/TRAFFIC_COUNTER/BEACON) + */ + private String deviceType; + + // ==================== 人员关联 ==================== + + /** + * 人员ID(保洁员/巡检员等) + */ + private Long personId; + + /** + * 人员姓名(冗余字段,异步填充) + */ + private String personName; + + /** + * 人员类型(冗余字段,如:CLEANER/INSPECTOR) + */ + private String personType; + + // ==================== 业务实体关联 ==================== + + /** + * 业务实体ID(如工单ID、区域ID) + */ + private Long targetId; + + /** + * 业务实体类型(order=工单/area=区域/task=任务) + */ + private String targetType; + + // ==================== 日志内容 ==================== + + /** + * 事件描述(完整描述) + */ + private String eventMessage; + + /** + * 简要摘要(用于列表展示,可选) + */ + private String eventSummary; + + /** + * 扩展数据(JSON格式,存储额外结构化信息) + */ + @TableField(typeHandler = JacksonTypeHandler.class) + private Map eventPayload; + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/dal/mysql/log/OpsBusinessEventLogMapper.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/dal/mysql/log/OpsBusinessEventLogMapper.java new file mode 100644 index 0000000..1bd81cf --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/dal/mysql/log/OpsBusinessEventLogMapper.java @@ -0,0 +1,125 @@ +package com.viewsh.module.ops.dal.mysql.log; + +import com.viewsh.framework.mybatis.core.mapper.BaseMapperX; +import com.viewsh.framework.mybatis.core.query.LambdaQueryWrapperX; +import com.viewsh.module.ops.dal.dataobject.log.OpsBusinessEventLogDO; +import org.apache.ibatis.annotations.Mapper; + +import java.util.List; + +/** + * 通用业务事件日志 Mapper + * + * @author lzh + */ +@Mapper +public interface OpsBusinessEventLogMapper extends BaseMapperX { + + /** + * 根据模块查询日志 + * + * @param module 模块标识(clean/repair/patrol) + * @return 日志列表 + */ + default List selectListByModule(String module) { + return selectList(new LambdaQueryWrapperX() + .eq(OpsBusinessEventLogDO::getModule, module) + .orderByDesc(OpsBusinessEventLogDO::getEventTime)); + } + + /** + * 根据模块和事件域查询日志 + * + * @param module 模块标识 + * @param eventDomain 事件域 + * @return 日志列表 + */ + default List selectListByModuleAndDomain(String module, String eventDomain) { + return selectList(new LambdaQueryWrapperX() + .eq(OpsBusinessEventLogDO::getModule, module) + .eq(OpsBusinessEventLogDO::getEventDomain, eventDomain) + .orderByDesc(OpsBusinessEventLogDO::getEventTime)); + } + + /** + * 根据设备ID查询日志 + * + * @param deviceId 设备ID + * @return 日志列表 + */ + default List selectListByDeviceId(Long deviceId) { + return selectList(new LambdaQueryWrapperX() + .eq(OpsBusinessEventLogDO::getDeviceId, deviceId) + .orderByDesc(OpsBusinessEventLogDO::getEventTime)); + } + + /** + * 根据人员ID查询日志 + * + * @param personId 人员ID + * @return 日志列表 + */ + default List selectListByPersonId(Long personId) { + return selectList(new LambdaQueryWrapperX() + .eq(OpsBusinessEventLogDO::getPersonId, personId) + .orderByDesc(OpsBusinessEventLogDO::getEventTime)); + } + + /** + * 根据目标实体查询日志 + * + * @param targetType 目标类型(order/area/task) + * @param targetId 目标ID + * @return 日志列表 + */ + default List selectListByTarget(String targetType, Long targetId) { + return selectList(new LambdaQueryWrapperX() + .eq(OpsBusinessEventLogDO::getTargetType, targetType) + .eq(OpsBusinessEventLogDO::getTargetId, targetId) + .orderByDesc(OpsBusinessEventLogDO::getEventTime)); + } + + /** + * 根据工单ID查询日志 + * + * @param orderId 工单ID + * @return 日志列表 + */ + default List selectListByOrderId(Long orderId) { + return selectList(new LambdaQueryWrapperX() + .eq(OpsBusinessEventLogDO::getTargetType, "order") + .eq(OpsBusinessEventLogDO::getTargetId, orderId) + .orderByDesc(OpsBusinessEventLogDO::getEventTime)); + } + + /** + * 根据事件域和类型查询日志 + * + * @param eventDomain 事件域 + * @param eventType 事件类型 + * @return 日志列表 + */ + default List selectListByDomainAndType(String eventDomain, String eventType) { + return selectList(new LambdaQueryWrapperX() + .eq(OpsBusinessEventLogDO::getEventDomain, eventDomain) + .eq(OpsBusinessEventLogDO::getEventType, eventType) + .orderByDesc(OpsBusinessEventLogDO::getEventTime)); + } + + /** + * 根据模块、事件域和类型查询日志 + * + * @param module 模块标识 + * @param eventDomain 事件域 + * @param eventType 事件类型 + * @return 日志列表 + */ + default List selectListByModuleDomainAndType(String module, String eventDomain, String eventType) { + return selectList(new LambdaQueryWrapperX() + .eq(OpsBusinessEventLogDO::getModule, module) + .eq(OpsBusinessEventLogDO::getEventDomain, eventDomain) + .eq(OpsBusinessEventLogDO::getEventType, eventType) + .orderByDesc(OpsBusinessEventLogDO::getEventTime)); + } + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/enumeration/EventDomain.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/enumeration/EventDomain.java new file mode 100644 index 0000000..109bc45 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/enumeration/EventDomain.java @@ -0,0 +1,57 @@ +package com.viewsh.module.ops.infrastructure.log.enumeration; + +/** + * 事件域枚举 + *

+ * 用于标识业务事件发生的领域/模块 + * + * @author lzh + */ +public enum EventDomain { + + /** + * 调度域 - 工单派发、分配等 + */ + DISPATCH("dispatch", "调度"), + + /** + * 信标域 - 信标检测、到岗确认等 + */ + BEACON("beacon", "信标"), + + /** + * 客流域 - 客流统计、阈值触发等 + */ + TRAFFIC("traffic", "客流"), + + /** + * 设备域 - 设备控制、TTS��震动等 + */ + DEVICE("device", "设备"), + + /** + * 系统域 - 系统级事件 + */ + SYSTEM("system", "系统"), + + /** + * 审计域 - 审计日志 + */ + AUDIT("audit", "审计"); + + private final String code; + private final String description; + + EventDomain(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/enumeration/EventLevel.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/enumeration/EventLevel.java new file mode 100644 index 0000000..d6dd87a --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/enumeration/EventLevel.java @@ -0,0 +1,40 @@ +package com.viewsh.module.ops.infrastructure.log.enumeration; + +/** + * 事件日志级别枚举 + * + * @author lzh + */ +public enum EventLevel { + + /** + * 信息级别 - 正常业务流程 + */ + INFO("INFO", "信息"), + + /** + * 警告级别 - 需要关注但不影响流程 + */ + WARN("WARN", "警告"), + + /** + * 错误级别 - 业务异常或失败 + */ + ERROR("ERROR", "错误"); + + private final String code; + private final String description; + + EventLevel(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogPersister.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogPersister.java new file mode 100644 index 0000000..ba78a58 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogPersister.java @@ -0,0 +1,29 @@ +package com.viewsh.module.ops.infrastructure.log.recorder; + +import com.viewsh.module.ops.dal.dataobject.log.OpsBusinessEventLogDO; + +/** + * 事件日志持久化接口 + *

+ * 负责将日志记录持久化到数据库 + * + * @author lzh + */ +public interface EventLogPersister { + + /** + * 持久化日志记录(同步) + * + * @param recordDO 日志DO + * @return 是否成功 + */ + boolean persist(OpsBusinessEventLogDO recordDO); + + /** + * 持久化日志记录(异步) + * + * @param recordDO 日志DO + */ + void persistAsync(OpsBusinessEventLogDO recordDO); + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogPersisterImpl.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogPersisterImpl.java new file mode 100644 index 0000000..324c25d --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogPersisterImpl.java @@ -0,0 +1,40 @@ +package com.viewsh.module.ops.infrastructure.log.recorder; + +import com.viewsh.module.ops.dal.dataobject.log.OpsBusinessEventLogDO; +import com.viewsh.module.ops.dal.mysql.log.OpsBusinessEventLogMapper; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * 事件日志持久化实现 + * + * @author lzh + */ +@Slf4j +@Component +public class EventLogPersisterImpl implements EventLogPersister { + + @Resource + private OpsBusinessEventLogMapper eventLogMapper; + + @Override + public boolean persist(OpsBusinessEventLogDO recordDO) { + try { + int rows = eventLogMapper.insert(recordDO); + return rows > 0; + } catch (Exception e) { + log.error("[EventLogPersister] 持久化失败: module={}, domain={}, type={}", + recordDO.getModule(), recordDO.getEventDomain(), recordDO.getEventType(), e); + return false; + } + } + + @Override + @Async("ops-task-executor") + public void persistAsync(OpsBusinessEventLogDO recordDO) { + persist(recordDO); + } + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogRecord.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogRecord.java new file mode 100644 index 0000000..dd3ef7d --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogRecord.java @@ -0,0 +1,223 @@ +package com.viewsh.module.ops.infrastructure.log.recorder; + +import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.Map; + +/** + * 事件日志记录实体 + *

+ * 用于记录业务事件日志,包含设备、人员、业务实体的关联信息 + * 采用方案A:在记录时异步填充名称冗余字段,便于展示时直接查询 + * + * @author lzh + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class EventLogRecord { + + // ==================== 基础分类字段 ==================== + + /** + * 模块标识(如:clean, repair, patrol) + */ + private String module; + + /** + * 事件域 + */ + private EventDomain domain; + + /** + * 事件类型(如:ARRIVE_CONFIRMED, TTS_SENT, AUTO_DISPATCH) + */ + private String eventType; + + /** + * 日志级别 + */ + @Builder.Default + private EventLevel level = EventLevel.INFO; + + // ==================== 内容字段 ==================== + + /** + * 事件描述(完整描述) + */ + private String message; + + /** + * 简要摘要(用于列表展示,可选) + */ + private String summary; + + // ==================== 设备关联字段 ==================== + + /** + * 设备ID + */ + private Long deviceId; + + /** + * 设备名称(冗余字段,异步填充) + */ + private String deviceName; + + /** + * 设备编码(冗余字段,如设备序列号) + */ + private String deviceCode; + + /** + * 设备类型(冗余字段,如:BADGE, TRAFFIC_COUNTER) + */ + private String deviceType; + + // ==================== 人员关联字段 ==================== + + /** + * 人员ID(保洁员、巡检员等) + */ + private Long personId; + + /** + * 人员姓名(冗余字段,异步填充) + */ + private String personName; + + /** + * 人员类型(冗余字段,如:CLEANER, INSPECTOR) + */ + private String personType; + + // ==================== 业务实体关联字段 ==================== + + /** + * 业务实体ID(如工单ID、区域ID) + */ + private Long targetId; + + /** + * 业务实体类型(如:order, area, task) + */ + private String targetType; + + // ==================== 扩展字段 ==================== + + /** + * 扩展数据(JSON格式) + */ + @Builder.Default + private Map payload = new HashMap<>(); + + /** + * 事件时间 + */ + @Builder.Default + private LocalDateTime eventTime = LocalDateTime.now(); + + // ==================== 便捷方法 ==================== + + /** + * 添加扩展数据 + */ + public void putPayload(String key, Object value) { + if (this.payload == null) { + this.payload = new HashMap<>(); + } + this.payload.put(key, value); + } + + /** + * 获取扩展数据 + */ + public Object getPayload(String key) { + return payload != null ? payload.get(key) : null; + } + + /** + * 获取扩展数据(String类型) + */ + public String getPayloadString(String key) { + Object value = getPayload(key); + return value != null ? String.valueOf(value) : null; + } + + /** + * 获取扩展数据(Long类型) + */ + public Long getPayloadLong(String key) { + Object value = getPayload(key); + if (value instanceof Number) { + return ((Number) value).longValue(); + } + return null; + } + + /** + * 获取扩展数据(Integer类型) + */ + public Integer getPayloadInt(String key) { + Object value = getPayload(key); + if (value instanceof Number) { + return ((Number) value).intValue(); + } + return null; + } + + // ==================== 静态构建方法 ==================== + + /** + * 创建设备事件日志 + */ + public static EventLogRecord forDevice(String module, EventDomain domain, String eventType, + String message, Long deviceId) { + return EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(message) + .deviceId(deviceId) + .build(); + } + + /** + * 创建工单事件日志 + */ + public static EventLogRecord forOrder(String module, EventDomain domain, String eventType, + String message, Long orderId, Long deviceId, Long personId) { + return EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(message) + .targetId(orderId) + .targetType("order") + .deviceId(deviceId) + .personId(personId) + .build(); + } + + /** + * 创建系统事件日志 + */ + public static EventLogRecord forSystem(String module, EventDomain domain, String eventType, + String message) { + return EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(message) + .build(); + } +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogRecorder.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogRecorder.java new file mode 100644 index 0000000..7440065 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogRecorder.java @@ -0,0 +1,110 @@ +package com.viewsh.module.ops.infrastructure.log.recorder; + +import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; + +/** + * 事件日志记录器接口 + *

+ * 提供业务事件日志记录的统一入口 + * + * @author lzh + */ +public interface EventLogRecorder { + + /** + * 记录事件日志(同步) + * + * @param record 日志记录 + */ + void record(EventLogRecord record); + + /** + * 记录事件日志(异步) + * + * @param record 日志记录 + */ + void recordAsync(EventLogRecord record); + + // ==================== 便捷方法:按级别记录 ==================== + + /** + * 记录信息级别日志 + * + * @param module 模块标识 + * @param domain 事件域 + * @param eventType 事件类型 + * @param message 事件消息 + */ + void info(String module, EventDomain domain, String eventType, String message); + + /** + * 记录信息级别日志(带设备关联) + * + * @param module 模块标识 + * @param domain 事件域 + * @param eventType 事件类型 + * @param message 事件消息 + * @param deviceId 设备ID + */ + void info(String module, EventDomain domain, String eventType, String message, Long deviceId); + + /** + * 记录信息级别日志(带工单关联) + * + * @param module 模块标识 + * @param domain 事件域 + * @param eventType 事件类型 + * @param message 事件消息 + * @param orderId 工单ID + * @param deviceId 设备ID + * @param personId 人员ID + */ + void info(String module, EventDomain domain, String eventType, String message, + Long orderId, Long deviceId, Long personId); + + /** + * 记录警告级别日志 + * + * @param module 模块标识 + * @param domain 事件域 + * @param eventType 事件类型 + * @param message 事件消息 + */ + void warn(String module, EventDomain domain, String eventType, String message); + + /** + * 记录警告级别日志(带设备关联) + * + * @param module 模块标识 + * @param domain 事件域 + * @param eventType 事件类型 + * @param message 事件消息 + * @param deviceId 设备ID + */ + void warn(String module, EventDomain domain, String eventType, String message, Long deviceId); + + /** + * 记录错误级别日志 + * + * @param module 模块标识 + * @param domain 事件域 + * @param eventType 事件类型 + * @param message 事件消息 + * @param throwable 异常信息 + */ + void error(String module, EventDomain domain, String eventType, String message, Throwable throwable); + + /** + * 记录错误级别日志(带设备关联) + * + * @param module 模块标识 + * @param domain 事件域 + * @param eventType 事件类型 + * @param message 事件消息 + * @param deviceId 设备ID + * @param throwable 异常信息 + */ + void error(String module, EventDomain domain, String eventType, String message, + Long deviceId, Throwable throwable); + +} diff --git a/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogRecorderImpl.java b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogRecorderImpl.java new file mode 100644 index 0000000..25c3b10 --- /dev/null +++ b/viewsh-module-ops/viewsh-module-ops-biz/src/main/java/com/viewsh/module/ops/infrastructure/log/recorder/EventLogRecorderImpl.java @@ -0,0 +1,250 @@ +package com.viewsh.module.ops.infrastructure.log.recorder; + +import com.viewsh.module.ops.dal.dataobject.log.OpsBusinessEventLogDO; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventDomain; +import com.viewsh.module.ops.infrastructure.log.enumeration.EventLevel; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +/** + * 事件日志记录器实现 + *

+ * 负责记录业务事件日志,包含: + * 1. 将 EventLogRecord 转换为 OpsBusinessEventLogDO + * 2. 异步填充设备/人员名称(方案A:冗余存储) + * 3. 持久化到数据库 + * 4. 输出到控制台(用于调试) + *

+ * 设计说明: + * - record() 方法默认异步执行,不阻塞主业务流程 + * - recordSync() 方法同步执行,用于需要确认日志写入成功的场景 + * - recordAsync() 方法保留,语义更明确 + * + * @author lzh + */ +@Slf4j +@Component +public class EventLogRecorderImpl implements EventLogRecorder { + + @Resource + private EventLogPersister persister; + + /** + * 异步记录日志(默认方式) + *

+ * 使用 @Async 确保日志记录不阻塞主业务流程 + */ + @Override + @Async("ops-task-executor") + public void record(EventLogRecord record) { + doRecord(record); + } + + /** + * 异步记录日志(语义明确的方法名) + */ + @Override + @Async("ops-task-executor") + public void recordAsync(EventLogRecord record) { + doRecord(record); + } + + /** + * 同步记录日志 + *

+ * 用于需要确认日志写入成功的场景(如测试、关键业务) + */ + public void recordSync(EventLogRecord record) { + doRecord(record); + } + + /** + * 实际执行记录逻辑 + */ + private void doRecord(EventLogRecord record) { + try { + // 转换为 DO + OpsBusinessEventLogDO recordDO = convertToDO(record); + + // TODO: 方案A - 异步填充设备/人员名称 + // 这里预留接口,后续通过 Feign 调用 IoT/Ops 模块获取名称 + enrichRecord(recordDO); + + // 持久化(同步写入,但在异步线程中执行) + persister.persist(recordDO); + + // 控制台输出(便于调试) + logConsole(record); + + } catch (Exception e) { + log.error("[EventLogRecorder] 记录失败: module={}, domain={}, type={}", + record.getModule(), record.getDomain(), record.getEventType(), e); + } + } + + @Override + public void info(String module, EventDomain domain, String eventType, String message) { + record(EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(message) + .level(EventLevel.INFO) + .build()); + } + + @Override + public void info(String module, EventDomain domain, String eventType, String message, Long deviceId) { + record(EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(message) + .deviceId(deviceId) + .level(EventLevel.INFO) + .build()); + } + + @Override + public void info(String module, EventDomain domain, String eventType, String message, + Long orderId, Long deviceId, Long personId) { + record(EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(message) + .targetId(orderId) + .targetType("order") + .deviceId(deviceId) + .personId(personId) + .level(EventLevel.INFO) + .build()); + } + + @Override + public void warn(String module, EventDomain domain, String eventType, String message) { + record(EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(message) + .level(EventLevel.WARN) + .build()); + } + + @Override + public void warn(String module, EventDomain domain, String eventType, String message, Long deviceId) { + record(EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(message) + .deviceId(deviceId) + .level(EventLevel.WARN) + .build()); + } + + @Override + public void error(String module, EventDomain domain, String eventType, String message, Throwable throwable) { + EventLogRecord record = EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(message) + .level(EventLevel.ERROR) + .build(); + if (throwable != null) { + record.putPayload("errorMessage", throwable.getMessage()); + record.putPayload("errorClass", throwable.getClass().getSimpleName()); + } + record(record); + } + + @Override + public void error(String module, EventDomain domain, String eventType, String message, + Long deviceId, Throwable throwable) { + EventLogRecord record = EventLogRecord.builder() + .module(module) + .domain(domain) + .eventType(eventType) + .message(message) + .deviceId(deviceId) + .level(EventLevel.ERROR) + .build(); + if (throwable != null) { + record.putPayload("errorMessage", throwable.getMessage()); + record.putPayload("errorClass", throwable.getClass().getSimpleName()); + } + record(record); + } + + /** + * 转换为 DO + */ + private OpsBusinessEventLogDO convertToDO(EventLogRecord record) { + return OpsBusinessEventLogDO.builder() + .eventTime(record.getEventTime()) + .eventLevel(record.getLevel() != null ? record.getLevel().getCode() : EventLevel.INFO.getCode()) + .module(record.getModule()) + .eventDomain(record.getDomain() != null ? record.getDomain().getCode() : null) + .eventType(record.getEventType()) + .deviceId(record.getDeviceId()) + .deviceName(record.getDeviceName()) + .deviceCode(record.getDeviceCode()) + .deviceType(record.getDeviceType()) + .personId(record.getPersonId()) + .personName(record.getPersonName()) + .personType(record.getPersonType()) + .targetId(record.getTargetId()) + .targetType(record.getTargetType()) + .eventMessage(record.getMessage()) + .eventSummary(record.getSummary()) + .eventPayload(record.getPayload()) + .build(); + } + + /** + * 异步填充设备/人员名称(方案A) + *

+ * TODO: 后续通过 Feign 调用以下接口异步填充: + * - IotDeviceApi.getBasicInfo(deviceId) -> 获取设备名称、编码、类型 + * - CleanerService.getBasicInfo(personId) -> 获取人员姓名、类型 + *

+ * 当前先留空,待 Feign 接口创建后再补充 + */ + private void enrichRecord(OpsBusinessEventLogDO recordDO) { + // 暂不实现异步填充,待后续完成 Feign 接口后再补充 + // 设计时考虑: + // 1. 如果 recordDO.getDeviceId() 不为空,调用 IoT 模块获取设备信息 + // 2. 如果 recordDO.getPersonId() 不为空,调用 Ops 模块获取人员信息 + // 3. 填充冗余字段:deviceName, deviceCode, deviceType, personName, personType + } + + /** + * 控制台输出(便于调试) + */ + private void logConsole(EventLogRecord record) { + String deviceInfo = record.getDeviceId() != null + ? " [设备:" + record.getDeviceId() + (record.getDeviceName() != null ? "(" + record.getDeviceName() + ")" : "") + "]" + : ""; + String personInfo = record.getPersonId() != null + ? " [人员:" + record.getPersonId() + (record.getPersonName() != null ? "(" + record.getPersonName() + ")" : "") + "]" + : ""; + String targetInfo = record.getTargetId() != null + ? " [" + record.getTargetType() + ":" + record.getTargetId() + "]" + : ""; + + log.info("[EventLog] [{}] {} {} {} - {}{}{}", + record.getLevel() != null ? record.getLevel().getCode() : "INFO", + record.getModule(), + record.getDomain() != null ? record.getDomain().getCode() : "UNKNOWN", + record.getEventType(), + record.getMessage(), + deviceInfo, + personInfo, + targetInfo); + } + +}