✅ 执行状态(2026-06-18 体检确认):本计划已执行完成——
operation_log表 +aivfo-oplog微服务(端口 10060)+ 消费入库 + 保留期清理全部落地;端到端发测试消息→入库验证通过(V-122 自测达成,真机操作触发待复验)。详见进度/交接卡.md、进度/进度状态.yaml。后续 P2(Java 接入)/P3(C# 端)/Pjava 亦已完成。For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 建好"日志入库"地基——log 库新增 operation_log 表 + 新建 aivfo-oplog 微服务消费 Kafka topic tl-oplog 入库 + 保留期清理;做完后发一条测试 JSON 消息能落库即验证通过。
Architecture: 新建单模块 Spring Boot 微服务 aivfo-oplog(结构照搬 aivfo-service),用现有 aivfo-kafka-spring-boot(KafkaFactory/ReceiveMessage/ConsumerManualCommit)消费 tl-oplog,JSON 反序列化为 OperationLog 实体,经 mybatis-plus 写 log 库 operation_log 表。保留期用 @Scheduled 定时删过期。本计划是 P1;P2(Java 接入)/P3(C# 端) 另出。
Tech Stack: Java 11 / Spring Boot / aivfo-framework(parent aivfo-business-parent:1.2.0-SNAPSHOT) / aivfo-kafka-spring-boot / mybatis-plus / Nacos / MySQL log 库 / Jackson(JSON)。
前置环境(已就绪):JDK11+Maven@C:\TLData\tools、Nexus 凭证已配、mvn install aivfo-framework 先行;中间件 7 容器在跑(含 Kafka localhost:9092、log 库 localhost:3306 root/root);详见 项目文档/开发环境/环境与账号清单.md、记忆 java-microservice-runtime。
时差项目源代码/
├─ sql/
│ └─ migrations/2026-06-18-operation-log.sql [新增] operation_log 建表
└─ aivfo-oplog/ [新增微服务,结构仿 aivfo-service]
├─ pom.xml 父=aivfo-business-parent;依赖 kafka/mybatis-plus/web/log/nacos starter
└─ src/main/
├─ java/com/aivfo/oplog/
│ ├─ OplogApplication.java 启动类
│ ├─ entity/OperationLog.java mybatis-plus 实体(对应 operation_log)
│ ├─ entity/OperationLogMessage.java Kafka JSON 消息 DTO(C#/Java 共用 schema)
│ ├─ mapper/OperationLogMapper.java BaseMapper
│ ├─ service/OperationLogService.java +impl/OperationLogServiceImpl.java IService
│ ├─ kafka/OplogReceiveMessage.java ReceiveMessage 实现:JSON→实体→入库
│ ├─ kafka/OplogConsumerRunner.java 启动时拉起 tl-oplog 消费者
│ └─ schedule/RetentionCleanTask.java 保留期清理(@Scheduled)
└─ resources/
├─ application.properties spring.application.name=aivfo-oplog + active profile
└─ application-local.properties nacos/datasource(log库)/kafka 地址(localhost)
说明:
operation_log与现有system_log同在log库,但system_log由 logbackLoggerDBAppender写、operation_log由本微服务写,两者经trace_id关联(见 14 §6)。
Files:
Create: 时差项目源代码/sql/migrations/2026-06-18-operation-log.sql
[ ] Step 1: 写建表脚本(字段对齐 14 §5;引擎/字符集同 system_log:InnoDB/utf8mb4)
-- 全量操作日志表(操作审计)。库:log。与 system_log 并列,共享 trace_id 可 join。见 需求文档/14 §5。
-- 幂等:DROP IF EXISTS + CREATE。
DROP TABLE IF EXISTS `operation_log`;
CREATE TABLE `operation_log` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '自增id',
`trace_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '全链路串联ID',
`parent_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '父子调用层级ID',
`op_time` datetime(3) NULL DEFAULT NULL COMMENT '操作时间(毫秒)',
`project` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '端/服务:operate/front/tl-control...',
`module` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '功能模块(可读):对焦/串口/患者...',
`operation` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '操作(可读):一键标定/打开端口...',
`operator` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '谁:登录用户/工程师/系统/设备SN',
`input` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '输入(JSON;大对象只存文件名/关键字段)',
`output` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '输出(JSON;同上)',
`result` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '结果:成功/失败',
`error` text CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT '报错:消息+堆栈摘要',
`elapsed_ms` bigint NULL DEFAULT NULL COMMENT '耗时(ms)',
`level` varchar(16) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '级别(操作级默认)',
`house_sn` int NULL DEFAULT NULL COMMENT '仓室号(便于按舱过滤)',
`well_sn` int NULL DEFAULT NULL COMMENT 'well号',
`tl_sn` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备SN',
`host` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '来源主机/IP',
`create_time` datetime(3) NULL DEFAULT NULL COMMENT '入库时间',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_trace`(`trace_id`) USING BTREE COMMENT '按trace_id拉链路/join system_log',
INDEX `idx_query`(`project`, `module`, `op_time`) USING BTREE COMMENT '按项目>模块>时间查询',
INDEX `idx_device`(`tl_sn`, `house_sn`, `op_time`) USING BTREE COMMENT '按设备/舱查询',
INDEX `idx_optime`(`op_time`) USING BTREE COMMENT '保留期清理用'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '全量操作日志(操作审计);与system_log经trace_id关联' ROW_FORMAT = Dynamic;
[ ] Step 2: 应用到 log 库并校验
Run:
export PATH="$PATH:/c/Program Files/Docker/Docker/resources/bin"
docker exec -i tl-mysql mysql -uroot -proot log < "/c/TLData/trae_projects/Project_TL/时差项目源代码/sql/migrations/2026-06-18-operation-log.sql"
docker exec tl-mysql mysql -uroot -proot -N -e "SELECT COUNT(*) FROM information_schema.columns WHERE table_schema='log' AND table_name='operation_log';"
Expected: 列数 = 19(输出 19)。
[ ] Step 3: Commit
cd 时差项目源代码 && git add sql/migrations/2026-06-18-operation-log.sql
git commit -m "M8-01: operation_log 建表脚本(log库,操作审计表)"
Files:
时差项目源代码/aivfo-oplog/pom.xml时差项目源代码/aivfo-oplog/src/main/java/com/aivfo/oplog/OplogApplication.java时差项目源代码/aivfo-oplog/src/main/resources/application.propertiesCreate: 时差项目源代码/aivfo-oplog/src/main/resources/application-local.properties
[ ] Step 1: 写 pom.xml(parent 同 aivfo-service;依赖在 aivfo-service 基础上加 kafka + nacos starter)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>aivfo-business-parent</artifactId>
<groupId>com.aivfo</groupId>
<version>1.2.0-SNAPSHOT</version>
</parent>
<artifactId>aivfo-oplog</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<description>全量操作日志微服务:消费 tl-oplog 入 operation_log</description>
<dependencies>
<dependency><groupId>com.aivfo</groupId><artifactId>aivfo-web-servlet-spring-boot-starter</artifactId></dependency>
<dependency><groupId>com.aivfo</groupId><artifactId>aivfo-mybatis-plus-spring-boot-starter</artifactId></dependency>
<dependency><groupId>com.aivfo</groupId><artifactId>aivfo-log-spring-boot-starter</artifactId></dependency>
<dependency><groupId>com.aivfo</groupId><artifactId>aivfo-kafka-spring-boot-starter</artifactId></dependency>
<dependency><groupId>com.aivfo</groupId><artifactId>aivfo-nacos-spring-boot-starter</artifactId></dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration><mainClass>com.aivfo.oplog.OplogApplication</mainClass></configuration>
<executions><execution><goals><goal>repackage</goal></goals></execution></executions>
</plugin>
</plugins>
</build>
</project>
⚠ 执行时核对:starter 的确切 artifactId/版本以
aivfo-data-transmission的 pom 为准(它同时用了 kafka+mybatis-plus+nacos)。若aivfo-kafka-spring-boot-starter/aivfo-nacos-spring-boot-starter名称不符,照 data-transmission pom 改正。
[ ] Step 2: 写启动类 OplogApplication.java
package com.aivfo.oplog;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication(scanBasePackages = "com.aivfo")
@EnableScheduling
@Slf4j
public class OplogApplication {
public static void main(String[] args) {
SpringApplication.run(OplogApplication.class, args);
log.info("全量操作日志服务 aivfo-oplog 启动成功!");
}
}
[ ] Step 3: 写 application.properties + application-local.properties
application.properties:
spring.application.name=aivfo-oplog
spring.profiles.active=local
server.port=10060
application-local.properties(datasource 指 log 库;nacos/kafka 用 localhost;参照 data-transmission application-local 的 key 名):
server.ip=localhost
# 数据源:log 库
aivfo.mybatis-plus.datasource.jdbcUrl=jdbc:mysql://${server.ip}:3306/log?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true
aivfo.mybatis-plus.datasource.username=root
aivfo.mybatis-plus.datasource.password=root
# nacos 注册
spring.cloud.nacos.discovery.server-addr=${server.ip}:8848
# kafka
aivfo.kafka.properties.ips=${server.ip}:9092
aivfo.kafka.properties.consumer.autoOffsetReset=earliest
aivfo.kafka.properties.consumer.group=aivfo-oplog
# 操作日志 topic + 保留天数
aivfo.oplog.topic=tl-oplog
aivfo.oplog.topic.partition.number=3
aivfo.oplog.retention.days=30
⚠
aivfo.kafka.properties.*的确切 key 以 data-transmission application-local.properties 为准(execute 时 grepaivfo.kafka对照)。
Run:
cd 时差项目源代码/aivfo-oplog
mvn -q -DskipTests compile 2>&1 | tail -5; echo "EXIT=${PIPESTATUS[0]}"
Expected: EXIT=0(此时无 Kafka/实体代码,仅骨架,能编译)。
[ ] Step 5: Commit
cd 时差项目源代码 && git add aivfo-oplog
git commit -m "M8-01: aivfo-oplog 微服务骨架(pom/启动类/配置)"
Files:
aivfo-oplog/src/main/java/com/aivfo/oplog/entity/OperationLog.javaaivfo-oplog/src/main/java/com/aivfo/oplog/mapper/OperationLogMapper.javaaivfo-oplog/src/main/java/com/aivfo/oplog/service/OperationLogService.javaCreate: aivfo-oplog/src/main/java/com/aivfo/oplog/service/impl/OperationLogServiceImpl.java
[ ] Step 1: 写实体 OperationLog.java(mybatis-plus;字段对应 operation_log 列)
package com.aivfo.oplog.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.time.LocalDateTime;
@Data
@TableName("operation_log")
public class OperationLog {
@TableId(type = IdType.AUTO)
private Long id;
private String traceId;
private String parentId;
private LocalDateTime opTime;
private String project;
private String module;
private String operation;
private String operator;
private String input;
private String output;
private String result;
private String error;
private Long elapsedMs;
private String level;
private Integer houseSn;
private Integer wellSn;
private String tlSn;
private String host;
private LocalDateTime createTime;
}
注:mybatis-plus 默认驼峰转下划线(traceId→trace_id),与表列一致;若项目关了该开关需加
@TableField。执行时对照 data-transmission 的某 DAO 是否用@TableField。
OperationLogMapper.java:
package com.aivfo.oplog.mapper;
import com.aivfo.oplog.entity.OperationLog;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface OperationLogMapper extends BaseMapper<OperationLog> {}
OperationLogService.java:
package com.aivfo.oplog.service;
import com.aivfo.oplog.entity.OperationLog;
import com.baomidou.mybatisplus.extension.service.IService;
public interface OperationLogService extends IService<OperationLog> {}
OperationLogServiceImpl.java:
package com.aivfo.oplog.service.impl;
import com.aivfo.oplog.entity.OperationLog;
import com.aivfo.oplog.mapper.OperationLogMapper;
import com.aivfo.oplog.service.OperationLogService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
@Service
public class OperationLogServiceImpl extends ServiceImpl<OperationLogMapper, OperationLog> implements OperationLogService {}
[ ] Step 3: 在启动类加 @MapperScan(OplogApplication.java 类注解上方加)
@org.mybatis.spring.annotation.MapperScan("com.aivfo.oplog.mapper")
(加在 @SpringBootApplication 同级注解处)
Run: cd 时差项目源代码/aivfo-oplog && mvn -q -DskipTests compile 2>&1 | tail -5; echo EXIT=${PIPESTATUS[0]}
Expected: EXIT=0
[ ] Step 5: Commit
cd 时差项目源代码 && git add aivfo-oplog && git commit -m "M8-01: OperationLog 实体/mapper/service(mybatis-plus)"
Files:
aivfo-oplog/src/main/java/com/aivfo/oplog/entity/OperationLogMessage.javaaivfo-oplog/src/main/java/com/aivfo/oplog/kafka/OplogReceiveMessage.javaCreate: aivfo-oplog/src/main/java/com/aivfo/oplog/kafka/OplogConsumerRunner.java
[ ] Step 1: 写 Kafka JSON 消息 DTO OperationLogMessage.java(C#/Java 共用 schema,字段名与 14 §5 一致,JSON 驼峰)
package com.aivfo.oplog.entity;
import lombok.Data;
/** Kafka tl-oplog 消息体(JSON)。C# 与 Java 生产端按此字段名序列化。见 需求文档/14 §5。 */
@Data
public class OperationLogMessage {
private String traceId;
private String parentId;
private Long time; // epoch millis
private String project;
private String module;
private String operation;
private String operator;
private String input;
private String output;
private String result;
private String error;
private Long elapsedMs;
private String level;
private Integer houseSn;
private Integer wellSn;
private String tlSn;
private String host;
}
[ ] Step 2: 写消费回调 OplogReceiveMessage.java(实现 ReceiveMessage<String, byte[]>;JSON→实体→入库;全程 try 兜底,解析失败丢弃不阻塞)
package com.aivfo.oplog.kafka;
import com.aivfo.oplog.entity.OperationLog;
import com.aivfo.oplog.entity.OperationLogMessage;
import com.aivfo.oplog.service.OperationLogService;
import com.aivfo.kafka.callback.ReceiveMessage;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
@Slf4j
@Component("oplogReceive")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OplogReceiveMessage implements ReceiveMessage<String, byte[]> {
final OperationLogService operationLogService;
static final ObjectMapper MAPPER = new ObjectMapper();
@Override
public boolean receive(ConsumerRecord<String, byte[]> record) {
try {
OperationLogMessage msg = MAPPER.readValue(new String(record.value(), StandardCharsets.UTF_8), OperationLogMessage.class);
OperationLog entity = new OperationLog();
entity.setTraceId(msg.getTraceId());
entity.setParentId(msg.getParentId());
entity.setOpTime(msg.getTime() == null ? null
: LocalDateTime.ofInstant(Instant.ofEpochMilli(msg.getTime()), ZoneId.systemDefault()));
entity.setProject(msg.getProject());
entity.setModule(msg.getModule());
entity.setOperation(msg.getOperation());
entity.setOperator(msg.getOperator());
entity.setInput(msg.getInput());
entity.setOutput(msg.getOutput());
entity.setResult(msg.getResult());
entity.setError(msg.getError());
entity.setElapsedMs(msg.getElapsedMs());
entity.setLevel(msg.getLevel());
entity.setHouseSn(msg.getHouseSn());
entity.setWellSn(msg.getWellSn());
entity.setTlSn(msg.getTlSn());
entity.setHost(msg.getHost());
entity.setCreateTime(LocalDateTime.now());
operationLogService.save(entity);
return true;
} catch (Exception e) {
// 解析/入库失败:记错、丢弃该条,不阻塞后续(操作日志不应反向影响系统)
log.error("操作日志入库失败,topic={},offset={}", record.topic(), record.offset(), e);
return true; // 返回 true 提交位移,避免坏消息无限重投
}
}
}
[ ] Step 3: 写消费者启动 OplogConsumerRunner.java(应用启动后拉起 tl-oplog 消费者,仿 KafkaManage.startPictureReceiveTopic 用 KafkaFactory/TopicManage)
package com.aivfo.oplog.kafka;
import com.aivfo.kafka.KafkaFactory;
import com.aivfo.kafka.callback.ReceiveMessage;
import com.aivfo.kafka.consumer.ConsumerManualCommit;
import com.aivfo.kafka.topic.TopicManage;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.Arrays;
import java.util.Map;
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class OplogConsumerRunner implements ApplicationRunner {
final KafkaFactory kafkaFactory;
final TopicManage topicManage;
final Map<String, ReceiveMessage> receiveMessageInfo; // spring 收集所有 ReceiveMessage bean,key=bean名
// ReceiveErrorInfo:照 data-transmission 注入其 receiveErrorInfo;若该 bean 在 starter 提供则 @Autowired,否则按 data-transmission 的实现复制一个最简版到本包
@Value("${aivfo.oplog.topic:tl-oplog}")
String topic;
@Value("${aivfo.oplog.topic.partition.number:3}")
int partitions;
@Override
public void run(org.springframework.boot.ApplicationArguments args) {
topicManage.createTopic(topic, partitions);
for (int i = 0; i < partitions; i++) {
ConsumerManualCommit consumer = kafkaFactory.buildConsumerManualCommit(
receiveMessageInfo.get("oplogReceive"), topic, null, Arrays.asList(i), Duration.ofSeconds(5));
consumer.start();
}
log.info("aivfo-oplog 开始监听 topic[{}],分区数={}", topic, partitions);
}
}
⚠
buildConsumerManualCommit第三参在 data-transmission 传的是receiveErrorInfo(ReceiveErrorInfo)。执行时:先看com.aivfo.kafka.KafkaFactory#buildConsumerManualCommit签名 + data-transmission 的ReceiveErrorInfo来源。若 starter 不强制,可传 null;否则照 data-transmission 复制一个最简ReceiveErrorInfo实现到com.aivfo.oplog.kafka。
Run: cd 时差项目源代码/aivfo-oplog && mvn -q -DskipTests compile 2>&1 | tail -8; echo EXIT=${PIPESTATUS[0]}
Expected: EXIT=0(若报 ReceiveErrorInfo/buildConsumerManualCommit 签名不符,按上方 ⚠ 对照 data-transmission 修正)
[ ] Step 5: Commit
cd 时差项目源代码 && git add aivfo-oplog && git commit -m "M8-01: tl-oplog 消费者(JSON→operation_log 入库,失败兜底)"
Files:
Create: aivfo-oplog/src/main/java/com/aivfo/oplog/schedule/RetentionCleanTask.java
[ ] Step 1: 写清理任务(每天凌晨删 op_time 早于保留天数的;用 mybatis-plus LambdaQuery delete)
package com.aivfo.oplog.schedule;
import com.aivfo.oplog.entity.OperationLog;
import com.aivfo.oplog.service.OperationLogService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
@Slf4j
@Component
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RetentionCleanTask {
final OperationLogService operationLogService;
@Value("${aivfo.oplog.retention.days:30}")
int retentionDays;
/** 每天 03:17 清理过期操作日志(避开整点) */
@Scheduled(cron = "0 17 3 * * ?")
public void clean() {
LocalDateTime cutoff = LocalDateTime.now().minusDays(retentionDays);
LambdaQueryWrapper<OperationLog> w = new LambdaQueryWrapper<>();
w.lt(OperationLog::getOpTime, cutoff);
long before = operationLogService.count();
boolean ok = operationLogService.remove(w);
log.info("操作日志保留期清理:保留{}天,cutoff={},删除前总数={},结果={}", retentionDays, cutoff, before, ok);
}
}
[ ] Step 2: 编译 + Commit
cd 时差项目源代码/aivfo-oplog && mvn -q -DskipTests compile 2>&1 | tail -3; echo EXIT=${PIPESTATUS[0]}
cd 时差项目源代码 && git add aivfo-oplog && git commit -m "M8-01: operation_log 保留期清理定时任务"
Files: 无(验证步骤)
[ ] Step 1: install + 打包 + 启动 aivfo-oplog
cd 时差项目源代码/aivfo-oplog && mvn -q -DskipTests install 2>&1 | tail -3
java -jar target/aivfo-oplog-1.0.0-SNAPSHOT.jar > /c/TLData/_setup/oplog.log 2>&1 &
sleep 40
grep -aE "Started OplogApplication|开始监听 topic|register finished" /c/TLData/_setup/oplog.log | tail -5
Expected: 见 Started OplogApplication、开始监听 topic[tl-oplog]、Nacos register finished。
[ ] Step 2: 用 Kafka 控制台生产者发一条测试 JSON 消息
export PATH="$PATH:/c/Program Files/Docker/Docker/resources/bin"
MSG='{"traceId":"test-trace-001","time":1718700000000,"project":"test","module":"串口","operation":"打开端口","operator":"工程师","input":"{\"port\":\"COM3\"}","result":"失败","error":"超时无响应","elapsedMs":1200,"level":"INFO","houseSn":3}'
echo "$MSG" | docker exec -i tl-kafka /opt/kafka/bin/kafka-console-producer.sh --bootstrap-server localhost:9092 --topic tl-oplog
sleep 5
⚠ kafka 镜像内脚本路径以
apache/kafka:3.7.0为准(常见/opt/kafka/bin/);若不符,docker exec tl-kafka sh -c 'ls /opt/kafka/bin | grep console-producer'找正确路径。
[ ] Step 3: 查 operation_log 验证入库
docker exec tl-mysql mysql -uroot -proot -N -e "SELECT trace_id,project,module,operation,operator,result,error,house_sn FROM log.operation_log WHERE trace_id='test-trace-001';"
Expected: 查到一行:test-trace-001 test 串口 打开端口 工程师 失败 超时无响应 3
[ ] Step 4: 停掉测试服务
# 找到 oplog 进程并停止(或用 TaskStop 若由后台任务起)
netstat -ano | grep ":10060.*LISTENING" # 拿 PID
# taskkill //PID <pid> //F (Windows) 或停止后台任务
[ ] Step 5: 回写进度 + Commit
按回写纪律更新 进度状态.yaml/交接卡.md/进度数据.js:M8-01(P1 基础设施)完成、operation_log 入库链路验证通过(V-122 部分达成)。
cd 时差项目源代码 && git add 项目文档/进度
git commit -m "M8-01 完成: P1 日志基础设施入库链路验证通过(发测试消息→operation_log)"
Spec coverage(对照 14 号 §6 基础设施部分):
Placeholder scan: 无 TODO/TBD;3 处 ⚠ 是"执行时照 data-transmission 对照确认"的明确校验指令(starter artifactId / buildConsumerManualCommit 签名+ReceiveErrorInfo / kafka 脚本路径),非占位——给了确切对照对象和动作。
Type consistency: OperationLog(实体,下划线列驼峰属性) / OperationLogMessage(JSON DTO,time 为 epoch millis) / operation_log(表) 三者字段一一对应;消费端 OplogReceiveMessage 把 Message.time(Long)→entity.opTime(LocalDateTime) 转换明确;oplogReceive bean 名在 Task 4 Step 2(@Component) 与 Step 3(receiveMessageInfo.get) 一致。
风险/依赖: 本计划假设 aivfo-kafka-spring-boot 的 KafkaFactory.buildConsumerManualCommit/TopicManage.createTopic/ReceiveMessage API 与 data-transmission 用法一致(已读其源码确认)。3 处 ⚠ 是执行时唯一需对照现有代码的点。
@OperateLog 切面采集 → 发 Kafka tl-oplog(JSON,字段同 OperationLogMessage);网关生成/透传 traceId;各微服务关键方法铺注解。Aivfo.OperationLog 组件(异步队列→Kafka + traceId 生成透传 + 配置开关 + 调试级本地文件);operate/front 全埋。