diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index fa154571..ed2c0423 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -72,7 +72,7 @@ services:
logging:
driver: "json-file"
options:
- max-size: "10m"
+ max-size: "50m"
max-file: "2"
ports:
- 8080:8080
diff --git a/docs/knowledge-base/07-TraceId 链路追踪实现.md b/docs/knowledge-base/07-TraceId 链路追踪实现.md
new file mode 100644
index 00000000..56095386
--- /dev/null
+++ b/docs/knowledge-base/07-TraceId 链路追踪实现.md
@@ -0,0 +1,1213 @@
+# 任务:TraceId 链路追踪与上下文传播能力
+
+你现在需要为我的 Java 单体项目实现一套“统一 TraceId 链路追踪基础设施”。
+
+## 一、项目背景
+
+当前项目是 Java 单体应用,技术栈:
+
+- JDK 8
+- Spring Boot 2.5.x
+- Spring MVC
+- Logback / SLF4J
+- MySQL
+- Redis
+- Quartz
+- MQ
+- Netty
+- MQTT
+- 后续会接入 Python AI 服务、LLM、微服务、OpenTelemetry
+
+项目未来会逐步发展为:
+
+```text
+单体应用
+ ↓
+IoT + SaaS + AI
+ ↓
+微服务
+ ↓
+OpenTelemetry / 分布式链路追踪
+```
+
+因此这次实现不能只是简单地增加一个 MDC 字段,而要设计成可长期演进的基础框架。
+
+---
+
+# 二、核心目标
+
+实现统一的:
+
+```text
+TraceContext
+```
+
+核心字段至少包括:
+
+```text
+traceId
+tenantId
+userId
+```
+
+其中当前阶段最重要的是:
+
+```text
+traceId
+```
+
+核心原则:
+
+> 一次完整业务执行链使用同一个 traceId。
+
+例如:
+
+```text
+前端请求
+ ↓
+Controller
+ ↓
+Service
+ ↓
+线程池
+ ↓
+MQ
+ ↓
+MQ Consumer
+ ↓
+AI 服务
+ ↓
+数据库
+ ↓
+通知
+```
+
+如果它们属于同一次业务执行,则全部使用:
+
+```text
+traceId = A
+```
+
+---
+
+# 三、TraceId 生命周期规则
+
+必须严格遵循以下规则。
+
+## 1. 有上游 TraceId
+
+优先继承。
+
+例如 HTTP:
+
+```http
+X-Trace-Id: 01KABC...
+```
+
+后端收到后:
+
+```text
+TraceId = 01KABC...
+```
+
+不要重新生成。
+
+---
+
+## 2. 没有上游 TraceId
+
+自动创建新的 TraceId。
+
+例如:
+
+```text
+浏览器
+ ↓
+HTTP
+ ↓
+Java
+```
+
+如果 HTTP 没有:
+
+```http
+X-Trace-Id
+```
+
+则 Java 自动生成。
+
+---
+
+## 3. 定时任务没有上游 TraceId
+
+Quartz / @Scheduled 等任务启动时自动生成:
+
+```text
+traceId
+```
+
+例如:
+
+```text
+traceId=T202609150001
+```
+
+整个任务执行过程保持相同 TraceId。
+
+---
+
+# 四、TraceId 生成器
+
+设计独立:
+
+```java
+TraceIdGenerator
+```
+
+不要把 TraceId 生成逻辑写死在 Filter 中。
+
+要求:
+
+```text
+TraceContext
+TraceIdGenerator
+```
+
+职责分离。
+
+优先考虑 ULID 或项目中已有的高质量 ID 生成方案。
+
+要求:
+
+- 唯一
+- 高并发安全
+- 尽量时间有序
+- 适合日志检索
+- 不依赖数据库
+- 后续可以替换实现
+
+---
+
+# 五、TraceContext 设计
+
+创建统一:
+
+```java
+TraceContext
+```
+
+例如:
+
+```text
+TraceContext
+├── getTraceId()
+├── setTraceId()
+├── getOrCreateTraceId()
+├── set()
+├── get()
+├── clear()
+└── snapshot()
+```
+
+不要让业务代码直接到处:
+
+```java
+MDC.put("traceId", ...)
+```
+
+业务代码应该依赖:
+
+```java
+TraceContext
+```
+
+MDC 只是 TraceContext 的一种底层日志传播实现。
+
+核心原则:
+
+```text
+TraceContext ≠ MDC
+```
+
+而应该是:
+
+```text
+TraceContext
+ ↓
+MDC
+HTTP Header
+MQ Header
+ThreadPool
+Future
+```
+
+---
+
+# 六、Web 请求
+
+实现 Spring MVC Filter。
+
+建议:
+
+```java
+OncePerRequestFilter
+```
+
+功能:
+
+1. 从 HTTP Header 获取:
+
+```http
+X-Trace-Id
+```
+
+2. 如果存在且合法:
+
+```text
+继续使用
+```
+
+3. 如果不存在:
+
+```text
+生成 TraceId
+```
+
+4. 放入:
+
+```text
+TraceContext
+MDC
+```
+
+5. 将 TraceId 写入 HTTP Response Header:
+
+```http
+X-Trace-Id: 01KABC...
+```
+
+6. 请求结束后必须清理:
+
+```text
+MDC
+TraceContext
+```
+
+防止 Tomcat 线程池复用造成 TraceId 污染。
+
+---
+
+# 七、日志输出
+
+修改 Logback 日志格式,使所有日志自动包含:
+
+```text
+traceId
+```
+
+例如:
+
+```text
+2026-09-15 11:20:31.123 INFO
+[http-nio-8080-exec-5]
+traceId=01KABC123
+DeviceService
+-
+查询设备成功 deviceId=DEV001
+```
+
+要求:
+
+- 控制台日志包含 traceId
+- 普通文件日志包含 traceId
+- error 日志包含 traceId
+- 不要求业务代码修改每一条 log.info()
+- 使用 MDC 自动完成
+
+同时考虑 JSON 日志扩展能力。
+
+---
+
+# 八、线程池上下文传播
+
+这是本任务的重点。
+
+普通:
+
+```java
+ThreadPoolExecutor
+```
+
+或者:
+
+```java
+CompletableFuture
+```
+
+不能保证 MDC 自动传播。
+
+必须实现统一的上下文传播机制。
+
+例如:
+
+```text
+TraceContext
+ ↓
+snapshot
+ ↓
+Runnable / Callable
+ ↓
+restore
+ ↓
+执行
+ ↓
+clear
+```
+
+要求实现类似:
+
+```text
+TraceContextRunnable
+TraceContextCallable
+TraceableExecutor
+```
+
+或者使用 Spring 提供的:
+
+```text
+TaskDecorator
+```
+
+如果项目使用 Spring ThreadPoolTaskExecutor,优先通过:
+
+```text
+TaskDecorator
+```
+
+实现。
+
+目标:
+
+```text
+Web线程
+traceId=A
+ ↓
+线程池
+traceId=A
+```
+
+例如:
+
+```java
+executor.execute(() -> {
+ log.info("异步处理");
+});
+```
+
+日志必须仍然是:
+
+```text
+traceId=A
+```
+
+---
+
+# 九、CompletableFuture
+
+重点处理:
+
+```java
+CompletableFuture.runAsync(...)
+CompletableFuture.supplyAsync(...)
+thenApply(...)
+thenAccept(...)
+thenRun(...)
+```
+
+分析项目中现有异步代码。
+
+不要简单粗暴地修改所有业务代码。
+
+优先通过统一线程池:
+
+```text
+TraceableExecutor
+```
+
+解决。
+
+如果无法完全覆盖 CompletableFuture 的默认线程池,需要明确指出风险,并提供统一解决方案。
+
+禁止让业务代码到处手动:
+
+```java
+MDC.put(...)
+MDC.clear(...)
+```
+
+---
+
+# 十、MQ TraceId 传播
+
+这是第二个重点。
+
+当 Java 发送 MQ 消息时:
+
+必须把当前:
+
+```text
+traceId
+```
+
+写入 MQ Message Header / Properties。
+
+例如:
+
+```text
+traceId=A
+```
+
+发送:
+
+```text
+MQ Message
+├── Header
+│ └── traceId=A
+└── Body
+```
+
+Consumer 收到消息:
+
+```text
+MQ Header
+ ↓
+读取 traceId
+ ↓
+TraceContext.setTraceId()
+ ↓
+MDC
+ ↓
+业务处理
+```
+
+这样:
+
+```text
+Web
+traceId=A
+ ↓
+MQ Producer
+traceId=A
+ ↓
+MQ
+traceId=A
+ ↓
+Consumer
+traceId=A
+```
+
+如果 MQ 消息没有 TraceId:
+
+```text
+Consumer
+ ↓
+自动生成新的 TraceId
+```
+
+但正常情况下必须继承 Producer 的 TraceId。
+
+---
+
+# 十一、MQ 不同消息中间件
+
+先检查当前项目实际使用的 MQ。
+
+不要凭空假设。
+
+如果项目目前只有一种 MQ:
+
+优先实现当前实际使用的 MQ。
+
+同时将设计抽象成:
+
+```text
+TraceMessageInterceptor
+```
+
+未来可以支持:
+
+```text
+RabbitMQ
+Kafka
+RocketMQ
+Redis Stream
+其他 MQ
+```
+
+不要为了扩展性一次性实现大量没有使用的 MQ。
+
+---
+
+# 十二、HTTP 调用链
+
+如果 Java 单体项目会调用其他服务:
+
+例如:
+
+```text
+Java
+ ↓ HTTP
+Python AI
+```
+
+必须自动传递:
+
+```http
+X-Trace-Id: A
+```
+
+如果项目使用:
+
+```text
+RestTemplate
+WebClient
+OpenFeign
+HttpClient
+OkHttp
+```
+
+检查实际使用情况。
+
+优先通过统一 Interceptor 实现。
+
+例如:
+
+```text
+Java
+traceId=A
+ ↓
+HTTP Header
+X-Trace-Id=A
+ ↓
+Python AI
+traceId=A
+```
+
+这样 Java 和 Python 日志可以使用同一个 TraceId 检索。
+
+---
+
+# 十三、Netty / MQTT / TCP / UDP
+
+这是 IoT 项目的重要场景。
+
+检查项目现有:
+
+```text
+Netty
+MQTT
+TCP
+UDP
+CoAP
+```
+
+入口。
+
+对于每一个独立的消息 / 请求:
+
+如果存在上游 TraceId:
+
+```text
+继承
+```
+
+否则:
+
+```text
+生成
+```
+
+然后进入:
+
+```text
+TraceContext
+```
+
+例如:
+
+```text
+设备 DEV001
+ ↓
+MQTT
+ ↓
+traceId=M001
+ ↓
+数据解析
+ ↓
+物模型
+ ↓
+数据库
+ ↓
+告警
+```
+
+整条处理链使用:
+
+```text
+traceId=M001
+```
+
+注意:
+
+> 设备每次独立上报消息属于独立业务事件,因此通常应该生成新的 TraceId,而不是让一个设备永久使用同一个 TraceId。
+
+---
+
+# 十五、SaaS 上下文
+
+项目未来是 SaaS,因此 TraceContext 预留:
+
+```text
+traceId
+tenantId
+userId
+```
+
+如果当前项目已经有:
+
+```text
+tenantId
+userId
+```
+
+则在请求认证成功后自动放入上下文。
+
+例如:
+
+```text
+traceId=A
+tenantId=10001
+userId=9527
+```
+
+但不要因为本次任务而大范围修改现有权限系统。
+
+只需要提供统一上下文承载能力。
+
+---
+
+# 十六、线程安全
+
+这是重点。
+
+必须考虑:
+
+```text
+Tomcat线程池复用
+Quartz线程池
+业务线程池
+CompletableFuture
+MQ Consumer线程
+Netty EventLoop
+```
+
+任何任务结束后都必须:
+
+```text
+clear
+```
+
+禁止出现:
+
+```text
+请求A
+traceId=A
+
+请求结束
+
+请求B
+traceId=A
+```
+
+这种严重的数据串链问题。
+
+---
+
+# 十七、异常处理
+
+异常情况下 TraceId 也必须保留。
+
+例如:
+
+```text
+traceId=A
+ERROR
+设备数据处理失败
+```
+
+完整异常堆栈必须能够通过:
+
+```text
+traceId=A
+```
+
+检索出来。
+
+Web 异常响应最好也返回:
+
+```http
+X-Trace-Id: A
+```
+
+这样前端报错:
+
+```text
+TraceId: A
+```
+
+我就可以直接去 Kibana / 日志文件搜索:
+
+```text
+traceId=A
+```
+
+定位问题。
+
+---
+
+# 十八、日志检索目标
+
+最终必须支持:
+
+```bash
+grep "traceId=01KABC" app.log
+```
+
+或者 Kibana:
+
+```text
+traceId:"01KABC"
+```
+
+一次找到:
+
+```text
+HTTP请求
+Service
+数据库
+线程池
+MQ Producer
+MQ Consumer
+AI
+异常
+```
+
+---
+
+# 十九、代码结构要求
+
+根据当前项目实际结构,合理放置。
+
+如果存在类似:
+
+```text
+common
+framework
+core
+starter
+```
+
+优先放到公共基础模块。
+
+推荐结构:
+
+```text
+xxx-core
+└── trace
+ ├── TraceContext
+ ├── TraceIdGenerator
+ ├── TraceContextSnapshot
+ ├── TraceContextRunnable
+ └── TraceContextCallable
+
+xxx-web-starter
+└── trace
+ └── TraceIdFilter
+
+xxx-log-starter
+└── trace
+ └── MDC / Logback
+
+xxx-schedule-starter
+└── trace
+ └── Quartz Trace
+
+xxx-mq-starter
+└── trace
+ ├── Producer Trace
+ └── Consumer Trace
+```
+
+具体目录必须根据当前项目实际结构调整,不要机械套用。
+
+---
+
+# 二十、不要过度设计
+
+当前项目是单体项目。
+
+第一阶段:
+
+必须实现:
+
+```text
+TraceId
+MDC
+Web
+ThreadPool
+Quartz
+MQ
+HTTP
+```
+
+暂时不要自行实现完整:
+
+```text
+Span
+Trace Graph
+Jaeger
+Zipkin
+SkyWalking
+OpenTelemetry Collector
+```
+
+但是代码设计要为未来 OpenTelemetry 留出扩展点。
+
+也就是说:
+
+```text
+现在:
+TraceId + MDC
+
+未来:
+TraceId + SpanId + ParentSpanId + OpenTelemetry
+```
+
+不要把当前 TraceContext 设计成未来无法迁移的死结构。
+
+---
+
+# 二十一、需要先做代码分析
+
+在开始修改代码之前:
+
+1. 扫描整个项目结构。
+2. 找到 Spring Boot 启动模块。
+3. 找到 Web 请求入口。
+4. 找到现有 Filter / Interceptor。
+5. 找到现有线程池。
+6. 找到 CompletableFuture。
+7. 找到 Quartz / @Scheduled。
+8. 找到 MQ Producer。
+9. 找到 MQ Consumer。
+10. 找到 RestTemplate / Feign / HTTP Client。
+11. 找到 Netty / MQTT / TCP / UDP 入口。
+12. 找到 Logback 配置。
+13. 找到现有 MDC / ThreadLocal / RequestContext 实现。
+14. 检查项目是否已经存在类似 TraceId / RequestId 机制。
+
+如果已经存在类似能力:
+
+> 优先重构和统一,不要重复造轮子。
+
+---
+
+# 二十二、实现原则
+
+遵守:
+
+```text
+低侵入
+高复用
+自动传播
+自动清理
+统一入口
+统一出口
+```
+
+业务代码尽可能不需要修改。
+
+理想效果:
+
+原来的代码:
+
+```java
+log.info("开始处理设备");
+```
+
+完全不用修改。
+
+自动变成:
+
+```text
+traceId=A 开始处理设备
+```
+
+---
+
+# 二十三、测试要求
+
+实现完成后必须增加测试。
+
+至少测试:
+
+### Test 1:HTTP
+
+```text
+请求进入
+↓
+生成 TraceId
+↓
+Controller
+↓
+Service
+↓
+日志
+↓
+Response Header
+```
+
+确认 TraceId 一致。
+
+### Test 2:HTTP携带 TraceId
+
+```text
+X-Trace-Id=A
+```
+
+确认后端仍然使用:
+
+```text
+A
+```
+
+### Test 3:线程池
+
+```text
+Web traceId=A
+↓
+ThreadPool
+```
+
+确认:
+
+```text
+traceId=A
+```
+
+### Test 4:MQ
+
+```text
+Producer traceId=A
+↓
+MQ Header
+↓
+Consumer
+```
+
+确认 Consumer:
+
+```text
+traceId=A
+```
+
+### Test 5:MQ无 TraceId
+
+确认 Consumer 自动生成。
+
+### Test 6:定时任务
+
+确认:
+
+```text
+TASK traceId=A
+```
+
+整个任务一致。
+
+### Test 7:线程池污染
+
+执行:
+
+```text
+请求A
+请求B
+请求C
+```
+
+确认:
+
+```text
+A ≠ B ≠ C
+```
+
+并且不会出现线程复用导致 TraceId 串线。
+
+### Test 8:异常
+
+异常发生后:
+
+```text
+traceId
+```
+
+仍然存在于 ERROR 日志。
+
+### Test 9:并发
+
+同时执行大量请求:
+
+```text
+100+
+1000+
+```
+
+确认 TraceId 不串线。
+
+---
+
+# 二十四、最终输出要求
+
+完成后请给我:
+
+## 1. 修改文件列表
+
+例如:
+
+```text
+新增:
+xxx/TraceContext.java
+
+修改:
+xxx/Logback.xml
+xxx/xxxFilter.java
+```
+
+## 2. 架构说明
+
+说明:
+
+```text
+Web
+ ↓
+TraceContext
+ ↓
+MDC
+
+ThreadPool
+ ↓
+Context Propagation
+
+MQ
+ ↓
+Message Header
+
+HTTP
+ ↓
+X-Trace-Id
+```
+
+## 3. 使用示例
+
+告诉我业务代码以后应该怎么写。
+
+目标是:
+
+```java
+log.info("开始处理");
+```
+
+而不是让业务代码手动管理 TraceId。
+
+## 4. 测试结果
+
+说明所有测试是否通过。
+
+## 5. 风险
+
+明确指出:
+
+- 哪些场景已经支持
+- 哪些场景暂未支持
+- 是否存在 MDC 丢失风险
+- 是否存在线程池上下文污染风险
+- 是否存在 MQ TraceId 丢失风险
+
+## 6. 后续演进建议
+
+最后说明未来如何从:
+
+```text
+TraceId + MDC
+```
+
+演进到:
+
+```text
+TraceId
++
+SpanId
++
+ParentSpanId
++
+OpenTelemetry
+```
+
+但本次不要提前实现完整分布式链路追踪系统。
+
+---
+
+# 最终验收标准
+
+只有同时满足以下条件才算完成:
+
+```text
+ traceId=A
+ │
+ ┌───────┴───────┐
+ │ │
+ Web Task
+ │
+ ▼
+ Service
+ │
+ ┌───────┴───────┐
+ ▼ ▼
+ ThreadPool MQ
+ │ │
+ │ ▼
+ │ Consumer
+ │ │
+ └───────┬───────┘
+ ▼
+ AI/HTTP
+ │
+ ▼
+ DB
+```
+
+同一业务链可以通过:
+
+```text
+traceId=A
+```
+
+在日志文件、IDEA Console、Kibana 中完整检索。
+
+尤其必须保证:
+
+> **跨线程、跨线程池、跨 MQ、跨 HTTP 的 TraceId 不丢失;任务执行完成后上下文必须清理,不允许线程复用造成 TraceId 污染。**
+
+请先分析当前代码,再实施修改。
+
+不要在没有检查项目现有实现的情况下直接创建重复的 TraceId / MDC / ThreadLocal 体系。
\ No newline at end of file
diff --git a/fastbee-admin/src/main/java/com/fastbee/web/task/LogCleanTask.java b/fastbee-admin/src/main/java/com/fastbee/web/task/LogCleanTask.java
new file mode 100644
index 00000000..a742b693
--- /dev/null
+++ b/fastbee-admin/src/main/java/com/fastbee/web/task/LogCleanTask.java
@@ -0,0 +1,88 @@
+package com.fastbee.web.task;
+
+import com.fastbee.iot.service.IAlertLogService;
+import com.fastbee.iot.service.IEventLogService;
+import com.fastbee.notify.service.INotifyLogService;
+import com.fastbee.quartz.service.ISysJobLogService;
+import com.fastbee.system.service.ISysLogininforService;
+import com.fastbee.system.service.ISysOperLogService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+/**
+ * 日志清理定时任务
+ *
+ * @author fastbee
+ */
+@Component("logCleanTask")
+public class LogCleanTask {
+ private static final Logger log = LoggerFactory.getLogger(LogCleanTask.class);
+
+ @Autowired
+ private IEventLogService eventLogService;
+
+ @Autowired
+ private ISysOperLogService operLogService;
+
+ @Autowired
+ private ISysLogininforService logininforService;
+
+ @Autowired
+ private IAlertLogService alertLogService;
+
+ @Autowired
+ private ISysJobLogService jobLogService;
+
+ @Autowired
+ private INotifyLogService notifyLogService;
+
+ // 之后还有清理的表往下写
+
+ /**
+ * 清理日志(默认清理15天前)
+ */
+ public void cleanLogs() {
+ cleanLogs(15);
+ }
+
+ /**
+ * 清理日志
+ *
+ * @param days 保留天数
+ */
+ public void cleanLogs(int days) {
+ log.info("========== 开始清理日志数据,保留最近 {} 天的记录 ==========", days);
+ try {
+ // 清理 IoT 事件日志
+ eventLogService.cleanEventLog(days);
+ //log.info("已清理 iot_event_log 表 {} 天前的数据", days);
+
+ // 清理系统操作日志
+ operLogService.cleanOperLog(days);
+ //log.info("已清理 sys_oper_log 表 {} 天前的数据", days);
+
+ // 清理登录日志
+ logininforService.cleanLogininfor(days);
+ //log.info("已清理 sys_logininfor 表 {} 天前的数据", days);
+
+ // 清理设备告警日志
+ alertLogService.cleanAlertLog(days);
+ //log.info("已清理 iot_alert_log 表 {} 天前的数据", days);
+
+ // 清理定时任务日志
+ jobLogService.cleanJobLog(days);
+ //log.info("已清理 sys_job_log 表 {} 天前的数据", days);
+
+ // 清理通知日志
+ notifyLogService.cleanNotifyLog(days);
+ //log.info("已清理 notify_log 表 {} 天前的数据", days);
+
+ log.info("========== 日志清理任务执行完成 ==========");
+ } catch (Exception e) {
+ log.error("日志清理任务执行失败", e);
+ throw e;
+ }
+ }
+}
\ No newline at end of file
diff --git a/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java b/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java
index f2d1afb2..67f2d431 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/config/DeviceTask.java
@@ -1,6 +1,7 @@
package com.fastbee.common.config;
import com.fastbee.common.constant.FastBeeConstant;
+import com.fastbee.common.trace.MdcTaskDecorator;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
@@ -60,7 +61,11 @@ public class DeviceTask {
return builder(FastBeeConstant.TASK.FUNCTION_INVOKE_TASK);
}
- /*内部消费线程*/
+ /**
+ * 内部消费线程
+ * 监听循环从消息恢复 TraceId 并逐条清理;
+ * TaskDecorator 用于让 @Async 消费方法继承监听线程的 TraceId
+ */
@Bean(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK)
public Executor messageConsumeTaskExecutor() {
return builder(FastBeeConstant.TASK.MESSAGE_CONSUME_TASK);
@@ -104,7 +109,7 @@ public class DeviceTask {
}
- /*组装线程池*/
+ /*组装线程池(带 TaskDecorator,用于跨线程传播 TraceId)*/
private ThreadPoolTaskExecutor builder(String threadNamePrefix) {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(coreSize);
@@ -115,6 +120,10 @@ public class DeviceTask {
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
//线程池名的前缀
executor.setThreadNamePrefix(threadNamePrefix);
+
+ // 配置 TaskDecorator,自动传播 TraceId
+ executor.setTaskDecorator(new MdcTaskDecorator());
+
executor.initialize();
return executor;
}
diff --git a/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java b/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java
index 89be5860..d8eebe75 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/constant/FastBeeConstant.java
@@ -15,6 +15,8 @@ public interface FastBeeConstant {
String MQTT = "mqtt";
String PORT = "port";
String ADAPTER = "adapter";
+ /** 链路追踪处理器名 */
+ String TRACE = "trace";
String FRAMEDECODER = "frameDecoder";
String DISPATCHER = "dispatcher";
String DECODER = "decoder";
diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReplyBo.java b/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReplyBo.java
index e45f1ea8..c849844a 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReplyBo.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReplyBo.java
@@ -8,6 +8,9 @@ import lombok.Data;
@Data
public class DeviceReplyBo {
+ /** 链路追踪ID */
+ private String traceId;
+
/*设备下发消息id*/
private String messageId;
/*标识符*/
diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReportBo.java b/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReportBo.java
index d4506ccd..edbce754 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReportBo.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceReportBo.java
@@ -24,6 +24,9 @@ import java.util.List;
@AllArgsConstructor
public class DeviceReportBo {
+ /** 链路追踪ID */
+ private String traceId;
+
/*设备编号或IMEI号*/
private String serialNumber;
/*产品ID*/
diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceStatusBo.java b/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceStatusBo.java
index 6bcd9adc..68567cf2 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceStatusBo.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceStatusBo.java
@@ -18,6 +18,10 @@ import java.util.Date;
@Data
@Builder
public class DeviceStatusBo {
+
+ /** 链路追踪ID */
+ private String traceId;
+
/**
* 设备客户端id
*/
diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceTestReportBo.java b/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceTestReportBo.java
index b7e3e7ff..cd43f370 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceTestReportBo.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/core/mq/DeviceTestReportBo.java
@@ -11,6 +11,9 @@ import java.util.List;
@Data
public class DeviceTestReportBo {
+ /** 链路追踪ID */
+ private String traceId;
+
private Long productId;
/**
* 设备编号
diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/mq/InvokeReqDto.java b/fastbee-common/src/main/java/com/fastbee/common/core/mq/InvokeReqDto.java
index acbc8ed5..386c7457 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/core/mq/InvokeReqDto.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/core/mq/InvokeReqDto.java
@@ -17,6 +17,9 @@ import java.util.Map;
@Data
public class InvokeReqDto {
+ /** 链路追踪ID */
+ private String traceId;
+
@ApiModelProperty(value = "设备编号")
private String serialNumber;
diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/mq/MQSendMessageBo.java b/fastbee-common/src/main/java/com/fastbee/common/core/mq/MQSendMessageBo.java
index f302c489..c5d2f9cc 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/core/mq/MQSendMessageBo.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/core/mq/MQSendMessageBo.java
@@ -14,6 +14,9 @@ import lombok.NoArgsConstructor;
@NoArgsConstructor
public class MQSendMessageBo {
+ /** 链路追踪ID */
+ private String traceId;
+
public Boolean isShadow;
public Long userId;
public Long delay;
diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeBo.java b/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeBo.java
index fc6db91a..9ddf34ed 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeBo.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeBo.java
@@ -17,6 +17,9 @@ import java.math.BigDecimal;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class OtaUpgradeBo {
+ /** 链路追踪ID */
+ private String traceId;
+
// 设备编码
private String serialNumber;
// 升级任务ID
diff --git a/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeDelayTask.java b/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeDelayTask.java
index ae5e3d50..96585439 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeDelayTask.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/core/mq/ota/OtaUpgradeDelayTask.java
@@ -17,6 +17,11 @@ import java.util.concurrent.TimeUnit;
@Data
public class OtaUpgradeDelayTask implements Delayed {
+ /**
+ * 链路追踪ID
+ */
+ private String traceId;
+
/**
* 固件id
*/
diff --git a/fastbee-common/src/main/java/com/fastbee/common/trace/MdcTaskDecorator.java b/fastbee-common/src/main/java/com/fastbee/common/trace/MdcTaskDecorator.java
new file mode 100644
index 00000000..26758f30
--- /dev/null
+++ b/fastbee-common/src/main/java/com/fastbee/common/trace/MdcTaskDecorator.java
@@ -0,0 +1,75 @@
+package com.fastbee.common.trace;
+
+import com.fastbee.common.trace.TraceContext.TraceSnapshot;
+import org.springframework.core.task.TaskDecorator;
+
+import java.util.concurrent.Callable;
+
+/**
+ * 线程池 TraceId 传播装饰器
+ * 解决 @Async 异步任务 TraceId 丢失问题
+ *
+ *
使用方法:
+ *
+ * ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
+ * executor.setTaskDecorator(new MdcTaskDecorator());
+ *
+ *
+ * 工作原理:
+ *
+ * - 在任务提交时,捕获当前线程的 TraceContext 快照
+ * - 在任务执行时,恢复快照到新线程(MDC 由 TraceContext 统一同步)
+ * - 在任务完成后,清理上下文防止线程池污染
+ *
+ *
+ * 说明:MDC 的写入/清理全部由 {@link TraceContext} 统一管理,
+ * 本装饰器只负责快照与恢复,避免 MDC 与 TraceContext 双重操作导致的不一致。
+ *
+ * @author FastBee
+ */
+public class MdcTaskDecorator implements TaskDecorator {
+
+ @Override
+ public Runnable decorate(Runnable runnable) {
+ // 捕获当前线程的 TraceContext 快照(跨线程传播的唯一依据)
+ TraceSnapshot snapshot = TraceContext.snapshot();
+
+ return () -> {
+ if (snapshot != null) {
+ TraceContext.restore(snapshot);
+ } else {
+ // 父线程无上下文,生成新的 TraceId
+ TraceContext.getOrCreateTraceId();
+ }
+ try {
+ runnable.run();
+ } finally {
+ // 清理上下文,防止线程池复用造成污染
+ TraceContext.clear();
+ }
+ };
+ }
+
+ /**
+ * 装饰 Callable
+ *
+ * @param task Callable 任务
+ * @return 装饰后的 Callable
+ */
+ public Callable decorate(Callable task) {
+ TraceSnapshot snapshot = TraceContext.snapshot();
+
+ return () -> {
+ if (snapshot != null) {
+ TraceContext.restore(snapshot);
+ } else {
+ TraceContext.getOrCreateTraceId();
+ }
+ try {
+ return task.call();
+ } finally {
+ TraceContext.clear();
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/fastbee-common/src/main/java/com/fastbee/common/trace/TraceContext.java b/fastbee-common/src/main/java/com/fastbee/common/trace/TraceContext.java
new file mode 100644
index 00000000..7c807628
--- /dev/null
+++ b/fastbee-common/src/main/java/com/fastbee/common/trace/TraceContext.java
@@ -0,0 +1,270 @@
+package com.fastbee.common.trace;
+
+import lombok.Data;
+import org.slf4j.MDC;
+
+import java.io.Serializable;
+
+/**
+ * 统一追踪上下文
+ * 管理 traceId, tenantId, userId 等追踪信息
+ *
+ * 核心特性:
+ *
+ * - 基于 ThreadLocal 实现线程隔离
+ * - 自动同步到 MDC 用于日志输出
+ * - 支持 snapshot/restore 用于跨线程传播
+ *
+ *
+ * 使用示例:
+ *
+ * // Web请求入口
+ * TraceContext.setTraceId(TraceIdGenerator.generate());
+ *
+ * // 异步任务(通过 TaskDecorator 自动传播)
+ * @Async
+ * public void asyncTask() {
+ * log.info("处理中"); // 日志自动包含 traceId
+ * }
+ *
+ * // 手动传播(用于非 Spring 管理的线程池)
+ * TraceSnapshot snapshot = TraceContext.snapshot();
+ * executor.submit(() -> {
+ * TraceContext.restore(snapshot);
+ * try {
+ * // 业务逻辑
+ * } finally {
+ * TraceContext.clear();
+ * }
+ * });
+ *
+ *
+ * @author FastBee
+ */
+public class TraceContext {
+
+ /**
+ * TraceId 在 MDC 中的 key
+ */
+ public static final String TRACE_ID_KEY = "traceId";
+
+ /**
+ * TenantId 在 MDC 中的 key
+ * 预留字段,暂未使用
+ */
+ public static final String TENANT_ID_KEY = "tenantId";
+
+ /**
+ * UserId 在 MDC 中的 key
+ * 预留字段,暂未使用
+ */
+ public static final String USER_ID_KEY = "userId";
+
+ /**
+ * 线程本地上下文存储
+ */
+ private static final ThreadLocal CONTEXT = new ThreadLocal<>();
+
+ /**
+ * 追踪上下文快照
+ * 用于跨线程传递
+ */
+ @Data
+ public static class TraceSnapshot implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String traceId;
+ private Long tenantId; // 预留字段
+ private Long userId; // 预留字段
+
+ /**
+ * 将快照应用到 MDC
+ * 先移除旧值再写入,保证 MDC 状态与快照精确一致(包括 null 字段)
+ */
+ public void applyToMdc() {
+ MDC.remove(TRACE_ID_KEY);
+ MDC.remove(TENANT_ID_KEY);
+ MDC.remove(USER_ID_KEY);
+ if (traceId != null) {
+ MDC.put(TRACE_ID_KEY, traceId);
+ }
+ if (tenantId != null) {
+ MDC.put(TENANT_ID_KEY, String.valueOf(tenantId));
+ }
+ if (userId != null) {
+ MDC.put(USER_ID_KEY, String.valueOf(userId));
+ }
+ }
+ }
+
+ // ================== Get Methods ==================
+
+ /**
+ * 获取当前 TraceId
+ *
+ * @return TraceId,如果不存在返回 null
+ */
+ public static String getTraceId() {
+ TraceSnapshot snapshot = CONTEXT.get();
+ return snapshot != null ? snapshot.getTraceId() : null;
+ }
+
+ /**
+ * 获取当前 TenantId(预留)
+ *
+ * @return TenantId,如果不存在返回 null
+ */
+ public static Long getTenantId() {
+ TraceSnapshot snapshot = CONTEXT.get();
+ return snapshot != null ? snapshot.getTenantId() : null;
+ }
+
+ /**
+ * 获取当前 UserId(预留)
+ *
+ * @return UserId,如果不存在返回 null
+ */
+ public static Long getUserId() {
+ TraceSnapshot snapshot = CONTEXT.get();
+ return snapshot != null ? snapshot.getUserId() : null;
+ }
+
+ // ================== Set Methods ==================
+
+ /**
+ * 设置 TraceId
+ * 同时更新 ThreadLocal 和 MDC
+ *
+ * @param traceId TraceId
+ */
+ public static void setTraceId(String traceId) {
+ TraceSnapshot snapshot = CONTEXT.get();
+ if (snapshot == null) {
+ snapshot = new TraceSnapshot();
+ CONTEXT.set(snapshot);
+ }
+ snapshot.setTraceId(traceId);
+ if (traceId != null) {
+ MDC.put(TRACE_ID_KEY, traceId);
+ } else {
+ MDC.remove(TRACE_ID_KEY);
+ }
+ }
+
+ /**
+ * 设置 TenantId(预留)
+ * 同时更新 ThreadLocal 和 MDC
+ *
+ * @param tenantId TenantId
+ */
+ public static void setTenantId(Long tenantId) {
+ TraceSnapshot snapshot = CONTEXT.get();
+ if (snapshot == null) {
+ snapshot = new TraceSnapshot();
+ CONTEXT.set(snapshot);
+ }
+ snapshot.setTenantId(tenantId);
+ if (tenantId != null) {
+ MDC.put(TENANT_ID_KEY, String.valueOf(tenantId));
+ } else {
+ MDC.remove(TENANT_ID_KEY);
+ }
+ }
+
+ /**
+ * 设置 UserId(预留)
+ * 同时更新 ThreadLocal 和 MDC
+ *
+ * @param userId UserId
+ */
+ public static void setUserId(Long userId) {
+ TraceSnapshot snapshot = CONTEXT.get();
+ if (snapshot == null) {
+ snapshot = new TraceSnapshot();
+ CONTEXT.set(snapshot);
+ }
+ snapshot.setUserId(userId);
+ if (userId != null) {
+ MDC.put(USER_ID_KEY, String.valueOf(userId));
+ } else {
+ MDC.remove(USER_ID_KEY);
+ }
+ }
+
+ // ================== Convenience Methods ==================
+
+ /**
+ * 获取或创建 TraceId
+ * 如果当前没有 TraceId,自动生成一个新的
+ *
+ * @return TraceId
+ */
+ public static String getOrCreateTraceId() {
+ String traceId = getTraceId();
+ if (traceId == null || traceId.isEmpty()) {
+ traceId = TraceIdGenerator.generate();
+ setTraceId(traceId);
+ }
+ return traceId;
+ }
+
+ /**
+ * 确保当前有 TraceId
+ * 如果传入的 traceId 不为空,使用传入的;否则生成新的
+ *
+ * @param traceId 可选的 TraceId
+ * @return 最终的 TraceId
+ */
+ public static String ensureTraceId(String traceId) {
+ if (traceId != null && !traceId.isEmpty()) {
+ setTraceId(traceId);
+ return traceId;
+ }
+ return getOrCreateTraceId();
+ }
+
+ // ================== Snapshot & Restore ==================
+
+ /**
+ * 创建当前上下文的快照
+ * 用于跨线程传递
+ *
+ * @return TraceSnapshot 快照,如果当前无上下文返回 null
+ */
+ public static TraceSnapshot snapshot() {
+ TraceSnapshot current = CONTEXT.get();
+ if (current == null) {
+ return null;
+ }
+ TraceSnapshot copy = new TraceSnapshot();
+ copy.setTraceId(current.getTraceId());
+ copy.setTenantId(current.getTenantId());
+ copy.setUserId(current.getUserId());
+ return copy;
+ }
+
+ /**
+ * 从快照恢复上下文
+ *
+ * @param snapshot 快照,如果为 null 则清理上下文
+ */
+ public static void restore(TraceSnapshot snapshot) {
+ if (snapshot == null) {
+ clear();
+ return;
+ }
+ CONTEXT.set(snapshot);
+ snapshot.applyToMdc();
+ }
+
+ /**
+ * 清除当前上下文
+ * 同时清除 ThreadLocal 和 MDC
+ */
+ public static void clear() {
+ CONTEXT.remove();
+ MDC.remove(TRACE_ID_KEY);
+ MDC.remove(TENANT_ID_KEY);
+ MDC.remove(USER_ID_KEY);
+ }
+}
\ No newline at end of file
diff --git a/fastbee-common/src/main/java/com/fastbee/common/trace/TraceIdGenerator.java b/fastbee-common/src/main/java/com/fastbee/common/trace/TraceIdGenerator.java
new file mode 100644
index 00000000..755780bb
--- /dev/null
+++ b/fastbee-common/src/main/java/com/fastbee/common/trace/TraceIdGenerator.java
@@ -0,0 +1,106 @@
+package com.fastbee.common.trace;
+
+import org.slf4j.MDC;
+
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * TraceId 生成器
+ * 生成纯数字格式的 TraceId
+ *
+ * 格式:基于时间戳 + 序列号 + 随机数
+ *
示例:752431931275776594
+ *
+ *
特点:
+ *
+ * - 纯数字格式,便于存储和索引
+ * - 时间有序,大致按生成时间排序
+ * - 线程安全,使用 synchronized 保证
+ * - 高并发友好,单机每秒可生成百万级 ID
+ *
+ *
+ * @author FastBee
+ */
+public class TraceIdGenerator {
+
+ /**
+ * 起始时间戳 (2023-01-01 00:00:00)
+ * 用于减少时间戳位数,延长 ID 可用年限
+ */
+ private static final long START_TIMESTAMP = 1672531200000L;
+
+ /**
+ * 序列号掩码:16 bit(0 ~ 65535)
+ */
+ private static final long SEQUENCE_MASK = 0xFFFFL;
+
+ /**
+ * 序列号,同一毫秒内递增
+ */
+ private static final AtomicLong sequence = new AtomicLong(0);
+
+ /**
+ * 上次生成时间戳
+ */
+ private static volatile long lastTimestamp = -1L;
+
+ /**
+ * 生成纯数字格式的 TraceId
+ *
+ * @return 纯数字 TraceId 字符串
+ */
+ public static String generate() {
+ return String.valueOf(generateLong());
+ }
+
+ /**
+ * 生成 long 类型的 TraceId
+ * 使用 synchronized 保证线程安全
+ *
+ * 位域分配(低到高):
+ *
+ * - 随机数 6 bit(第 0~5 位)
+ * - 序列号 16 bit(第 6~21 位)
+ * - 时间差 剩余位(第 22 位及以上)
+ *
+ *
+ * @return long 类型 TraceId
+ */
+ public static synchronized long generateLong() {
+ long timestamp = System.currentTimeMillis();
+
+ // 时钟回拨保护:时间不允许倒退,沿用上次时间戳
+ if (timestamp < lastTimestamp) {
+ timestamp = lastTimestamp;
+ }
+
+ if (timestamp == lastTimestamp) {
+ // 同毫秒:序列号递增并回写(& MASK 防止溢出侵入时间位域)
+ sequence.set((sequence.get() + 1) & SEQUENCE_MASK);
+ if (sequence.get() == 0) {
+ // 序列号用尽,等待下一毫秒
+ timestamp = tilNextMillis(lastTimestamp);
+ }
+ } else {
+ sequence.set(0);
+ }
+ lastTimestamp = timestamp;
+
+ // 时间差(高位) | 序列号(中位) | 随机数(低位 6 bit)
+ long timeDiff = timestamp - START_TIMESTAMP;
+ long random = ThreadLocalRandom.current().nextInt(64);
+ return (timeDiff << 22) | (sequence.get() << 6) | random;
+ }
+
+ /**
+ * 等待下一毫秒
+ */
+ private static long tilNextMillis(long lastTimestamp) {
+ long timestamp = System.currentTimeMillis();
+ while (timestamp <= lastTimestamp) {
+ timestamp = System.currentTimeMillis();
+ }
+ return timestamp;
+ }
+}
\ No newline at end of file
diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/StringUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/StringUtils.java
index 3b5b0e7b..ce9e00d4 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/utils/StringUtils.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/utils/StringUtils.java
@@ -295,21 +295,19 @@ public class StringUtils extends org.apache.commons.lang3.StringUtils {
/**
* 判断给定的set列表中是否包含数组array 判断给定的数组array中是否包含给定的元素value
*
- * @param set 给定的集合
+ * @param collection 给定的集合
* @param array 给定的数组
* @return boolean 结果
*/
public static boolean containsAny(Collection collection, String... array) {
- if (isEmpty(collection) || isEmpty(array)) {
- return false;
- } else {
+ if (!isEmpty(collection) && !isEmpty(array)) {
for (String str : array) {
if (collection.contains(str)) {
return true;
}
}
- return false;
}
+ return false;
}
/**
diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java
index fdd7138c..c05442f7 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/utils/TraceIdUtils.java
@@ -1,188 +1,64 @@
package com.fastbee.common.utils;
-import com.fastbee.common.utils.uuid.IdUtils;
-import org.slf4j.MDC;
+import com.fastbee.common.trace.TraceContext;
+import com.fastbee.common.trace.TraceContext.TraceSnapshot;
-import java.util.Map;
import java.util.concurrent.Callable;
-import java.util.concurrent.ThreadLocalRandom;
/**
* 日志追踪ID工具类
- * 统一生成TraceId的逻辑,使用IdWorker和ThreadLocalRandom
+ * 提供向后兼容的 API,内部委托给 TraceContext
*
* @author Sumuxi
+ * 建议直接使用 {@link TraceContext}
*/
public class TraceIdUtils {
/**
- * TraceId在MDC中的key
- */
- public static final String TRACE_ID_KEY = "traceId";
-
- /**
- * IdWorker实例(单例模式)
- */
- private static volatile IdUtils idWorker;
-
- /**
- * 获取IdWorker实例(懒加载单例)
+ * 包装 Runnable:透传 TraceContext(MDC 由 TraceContext 统一同步)
+ * 用于非 Spring 管理的线程池(如 ScheduledExecutorService)
*
- * @return IdWorker实例
- */
- private static IdUtils getIdWorker() {
- if (idWorker == null) {
- synchronized (TraceIdUtils.class) {
- if (idWorker == null) {
- // 使用机器ID,可以根据实际情况配置
- // 这里使用1作为默认workerId,可以通过配置文件读取
- // IdUtils构造函数:workerId, datacenterId,-1表示随机生成
- idWorker = new IdUtils(-1L, -1L);
- }
- }
- }
- return idWorker;
- }
-
- /**
- * 生成TraceId
- * 格式:IdWorker.nextId() * 1000 + ThreadLocalRandom.nextInt(1000)
- * 参考:generateClueId() 的实现方式
- * 使用Math.abs确保TraceId为正数
- *
- * @return TraceId
- */
- public static long generateTraceId() {
- long id = getIdWorker().nextId();
- // 使用绝对值确保TraceId为正数,避免负数显示
- // 注意:Math.abs(id) * 1000 可能会溢出,所以需要对最终结果再次使用Math.abs()
- long traceId = Math.abs(id) * 1000 + ThreadLocalRandom.current().nextInt(1000);
- // 确保最终结果为非负数(处理溢出情况)
- traceId = Math.abs(traceId);
- MDC.put(TRACE_ID_KEY, String.valueOf(traceId));
- return traceId;
- }
-
- /**
- * 生成TraceId并放入MDC
- * 返回字符串格式的TraceId
- *
- * @return TraceId字符串
- */
- public static String generateTraceIdString() {
- long traceId = generateTraceId();
- return String.valueOf(traceId);
- }
-
- /**
- * 设置TraceId到MDC
- *
- * @param traceId TraceId
- */
- public static void setTraceId(String traceId) {
- if (traceId != null && !traceId.isEmpty()) {
- MDC.put(TRACE_ID_KEY, traceId);
- }
- }
-
- /**
- * 设置TraceId到MDC(long类型)
- *
- * @param traceId TraceId
- */
- public static void setTraceId(long traceId) {
- MDC.put(TRACE_ID_KEY, String.valueOf(traceId));
- }
-
- /**
- * 从MDC获取TraceId
- *
- * @return TraceId
- */
- public static String getTraceId() {
- return MDC.get(TRACE_ID_KEY);
- }
-
- /**
- * 确保当前线程 MDC 中存在 TraceId。
- * - 已存在:直接返回
- * - 不存在:生成并写入 MDC
- */
- public static String ensureTraceId() {
- String traceId = getTraceId();
- if (traceId == null || traceId.isEmpty()) {
- traceId = generateTraceIdString();
- }
- return traceId;
- }
-
- /**
- * 使用指定 traceId 覆盖当前线程 MDC(为空则生成)。
- * 主要用于跨线程/跨进程透传后的落地。
- */
- public static String ensureTraceId(String traceId) {
- if (traceId == null || traceId.isEmpty()) {
- return ensureTraceId();
- }
- setTraceId(traceId);
- return traceId;
- }
-
- /**
- * 包装 Runnable:透传提交线程的 MDC,上下文中无 traceId 则补齐;执行完自动清理/还原。
+ * @param task Runnable 任务
+ * @return 包装后的 Runnable
*/
public static Runnable wrapWithMdc(Runnable task) {
- final Map captured = MDC.getCopyOfContextMap();
+ final TraceSnapshot snapshot = TraceContext.snapshot();
+
return () -> {
- Map previous = MDC.getCopyOfContextMap();
+ if (snapshot != null) {
+ TraceContext.restore(snapshot);
+ } else {
+ TraceContext.getOrCreateTraceId();
+ }
try {
- if (captured == null || captured.isEmpty()) {
- MDC.clear();
- } else {
- MDC.setContextMap(captured);
- }
- ensureTraceId();
task.run();
} finally {
- if (previous == null || previous.isEmpty()) {
- MDC.clear();
- } else {
- MDC.setContextMap(previous);
- }
+ TraceContext.clear();
}
};
}
/**
- * 包装 Callable:透传提交线程的 MDC,上下文中无 traceId 则补齐;执行完自动清理/还原。
+ * 包装 Callable:透传 TraceContext(MDC 由 TraceContext 统一同步)
+ * 用于非 Spring 管理的线程池(如 ScheduledExecutorService)
+ *
+ * @param task Callable 任务
+ * @return 包装后的 Callable
*/
public static Callable wrapWithMdc(Callable task) {
- final Map captured = MDC.getCopyOfContextMap();
+ final TraceSnapshot snapshot = TraceContext.snapshot();
+
return () -> {
- Map previous = MDC.getCopyOfContextMap();
+ if (snapshot != null) {
+ TraceContext.restore(snapshot);
+ } else {
+ TraceContext.getOrCreateTraceId();
+ }
try {
- if (captured == null || captured.isEmpty()) {
- MDC.clear();
- } else {
- MDC.setContextMap(captured);
- }
- ensureTraceId();
return task.call();
} finally {
- if (previous == null || previous.isEmpty()) {
- MDC.clear();
- } else {
- MDC.setContextMap(previous);
- }
+ TraceContext.clear();
}
};
}
-
- /**
- * 清除MDC中的TraceId
- */
- public static void clearTraceId() {
- MDC.remove(TRACE_ID_KEY);
- }
-}
-
+}
\ No newline at end of file
diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC8Utils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC8Utils.java
index 48a26cc5..35e98b4c 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC8Utils.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/CRC8Utils.java
@@ -25,12 +25,12 @@ public class CRC8Utils {
*/
public static byte calcCrc8_E5(byte[] data) {
byte crc = 0;
- for (int j = 0; j < data.length; j++) {
- crc ^= data[j];
+ for (byte datum : data) {
+ crc ^= datum;
for (int i = 0; i < 8; i++) {
if ((crc & 0x80) != 0) {
crc = (byte) ((crc) << 1);
- crc ^= 0xE5;
+ crc ^= (byte) 0xE5;
} else {
crc = (byte) ((crc) << 1);
}
diff --git a/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/protocol/ByteUtils.java b/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/protocol/ByteUtils.java
index c6ce73e0..1ac13ad5 100644
--- a/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/protocol/ByteUtils.java
+++ b/fastbee-common/src/main/java/com/fastbee/common/utils/gateway/protocol/ByteUtils.java
@@ -78,8 +78,7 @@ public class ByteUtils {
byte[] write = new byte[length];
int index = 0;
- for (int i = 0; i < bytes.size(); i++) {
- byte[] values = bytes.get(i);
+ for (byte[] values : bytes) {
addBytes(write, values, index);
index += values.length;
}
@@ -312,8 +311,9 @@ public class ByteUtils {
if (StringUtils.isBlank(hexStr)) {
return null;
}
- if (hexStr.length() % 2 != 0) {//长度为单数
- hexStr = "0" + hexStr;//前面补0
+ // 长度为单数
+ if (hexStr.length() % 2 != 0) {
+ hexStr = "0" + hexStr; // 前面补0
}
char[] chars = hexStr.toCharArray();
diff --git a/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/ScheduledTraceAspect.java b/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/ScheduledTraceAspect.java
new file mode 100644
index 00000000..e4691c91
--- /dev/null
+++ b/fastbee-framework/src/main/java/com/fastbee/framework/aspectj/ScheduledTraceAspect.java
@@ -0,0 +1,47 @@
+package com.fastbee.framework.aspectj;
+
+import com.fastbee.common.trace.TraceContext;
+import com.fastbee.common.trace.TraceIdGenerator;
+import lombok.extern.slf4j.Slf4j;
+import org.aspectj.lang.ProceedingJoinPoint;
+import org.aspectj.lang.annotation.Around;
+import org.aspectj.lang.annotation.Aspect;
+import org.springframework.core.annotation.Order;
+import org.springframework.stereotype.Component;
+
+/**
+ * @Scheduled 定时任务 TraceId 切面
+ * 自动为所有 Spring 定时任务方法生成 TraceId
+ *
+ * @author FastBee
+ */
+@Slf4j
+@Aspect
+@Component
+@Order(1) // 高优先级,确保最先执行
+public class ScheduledTraceAspect {
+
+ /**
+ * 拦截所有 @Scheduled 方法
+ */
+ @Around("@annotation(org.springframework.scheduling.annotation.Scheduled)")
+ public Object aroundScheduled(ProceedingJoinPoint joinPoint) throws Throwable {
+ String methodName = joinPoint.getSignature().toShortString();
+
+ // 生成 TraceId
+ String traceId = TraceIdGenerator.generate();
+ TraceContext.setTraceId(traceId);
+
+ log.debug("定时任务开始执行: {}", methodName);
+
+ try {
+ return joinPoint.proceed();
+ } catch (Throwable e) {
+ log.error("定时任务执行异常: {}", methodName, e);
+ throw e;
+ } finally {
+ log.debug("定时任务执行完成: {}", methodName);
+ TraceContext.clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/fastbee-framework/src/main/java/com/fastbee/framework/config/OtaThreadPoolConfig.java b/fastbee-framework/src/main/java/com/fastbee/framework/config/OtaThreadPoolConfig.java
index d460de10..6a4859e5 100644
--- a/fastbee-framework/src/main/java/com/fastbee/framework/config/OtaThreadPoolConfig.java
+++ b/fastbee-framework/src/main/java/com/fastbee/framework/config/OtaThreadPoolConfig.java
@@ -1,6 +1,7 @@
package com.fastbee.framework.config;
import com.fastbee.common.constant.FastBeeConstant;
+import com.fastbee.common.trace.MdcTaskDecorator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
@@ -30,6 +31,10 @@ public class OtaThreadPoolConfig {
executor.setThreadNamePrefix(threadNamePrefix);
// 线程池对拒绝任务(无线程可用)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+
+ // 新增:配置 TaskDecorator,自动传播 TraceId
+ executor.setTaskDecorator(new MdcTaskDecorator());
+
// 进行加载
executor.initialize();
return executor;
diff --git a/fastbee-framework/src/main/java/com/fastbee/framework/config/ThreadPoolConfig.java b/fastbee-framework/src/main/java/com/fastbee/framework/config/ThreadPoolConfig.java
index cf18f388..6cbeeb7b 100644
--- a/fastbee-framework/src/main/java/com/fastbee/framework/config/ThreadPoolConfig.java
+++ b/fastbee-framework/src/main/java/com/fastbee/framework/config/ThreadPoolConfig.java
@@ -1,5 +1,6 @@
package com.fastbee.framework.config;
+import com.fastbee.common.trace.MdcTaskDecorator;
import com.fastbee.common.utils.Threads;
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
import org.springframework.context.annotation.Bean;
@@ -38,6 +39,10 @@ public class ThreadPoolConfig {
executor.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用)的处理策略
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
+
+ // 新增:配置 TaskDecorator,自动传播 TraceId
+ executor.setTaskDecorator(new MdcTaskDecorator());
+
return executor;
}
diff --git a/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/TraceIdInterceptor.java b/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/TraceIdInterceptor.java
index b892f4ea..af9675c8 100644
--- a/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/TraceIdInterceptor.java
+++ b/fastbee-framework/src/main/java/com/fastbee/framework/interceptor/TraceIdInterceptor.java
@@ -1,9 +1,9 @@
package com.fastbee.framework.interceptor;
-import com.fastbee.common.utils.TraceIdUtils;
+import com.fastbee.common.trace.TraceContext;
+import com.fastbee.common.trace.TraceIdGenerator;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
-
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -16,24 +16,26 @@ import javax.servlet.http.HttpServletResponse;
@Component
public class TraceIdInterceptor implements HandlerInterceptor {
+ private static final String TRACE_ID_HEADER = "X-Trace-Id";
+
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 获取请求URI
String requestURI = request.getRequestURI();
// 尝试从请求头中获取TraceId(支持分布式追踪)
- String traceId = request.getHeader("X-Trace-Id");
+ String traceId = request.getHeader(TRACE_ID_HEADER);
// 如果请求头中没有TraceId,则生成一个新的
if (traceId == null || traceId.isEmpty()) {
- traceId = TraceIdUtils.generateTraceIdString();
- } else {
- // 如果请求头中有TraceId,设置到MDC
- TraceIdUtils.setTraceId(traceId);
+ traceId = TraceIdGenerator.generate();
}
+ // 设置到 TraceContext(自动同步到 MDC)
+ TraceContext.setTraceId(traceId);
+
// 将TraceId放入响应头,方便前端获取
- response.setHeader("X-Trace-Id", traceId);
+ response.setHeader(TRACE_ID_HEADER, traceId);
// 打印请求路径日志
org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(TraceIdInterceptor.class);
@@ -44,8 +46,8 @@ public class TraceIdInterceptor implements HandlerInterceptor {
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
- // 请求完成后清除MDC中的TraceId,避免内存泄漏
- TraceIdUtils.clearTraceId();
+ // 请求完成后清除 TraceContext,避免内存泄漏
+ TraceContext.clear();
}
}
diff --git a/fastbee-framework/src/main/java/com/fastbee/framework/manager/AsyncManager.java b/fastbee-framework/src/main/java/com/fastbee/framework/manager/AsyncManager.java
index 7fe96dcd..5d18fff9 100644
--- a/fastbee-framework/src/main/java/com/fastbee/framework/manager/AsyncManager.java
+++ b/fastbee-framework/src/main/java/com/fastbee/framework/manager/AsyncManager.java
@@ -1,6 +1,7 @@
package com.fastbee.framework.manager;
import com.fastbee.common.utils.Threads;
+import com.fastbee.common.utils.TraceIdUtils;
import com.fastbee.common.utils.spring.SpringUtils;
import java.util.TimerTask;
@@ -36,10 +37,17 @@ public class AsyncManager {
/**
* 执行任务
*
+ * 包装任务以透传 TraceId:{@code scheduledExecutorService} 是裸
+ * {@link java.util.concurrent.ScheduledThreadPoolExecutor},不支持
+ * {@code setTaskDecorator},因此必须在提交点包装任务本身。
+ * {@link TraceIdUtils#wrapWithMdc} 会捕获调用方线程的 TraceContext
+ * 快照并在 schedule-pool 线程内恢复,使登录/登出/操作日志等
+ * 异步落库链路的日志携带同一 TraceId。
+ *
* @param task 任务
*/
public void execute(TimerTask task) {
- executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
+ executor.schedule(TraceIdUtils.wrapWithMdc(task), OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);
}
/**
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/consumer/DevicePropFetchConsumer.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/consumer/DevicePropFetchConsumer.java
index e95e3dc0..3870fb90 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/consumer/DevicePropFetchConsumer.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/consumer/DevicePropFetchConsumer.java
@@ -99,7 +99,7 @@ public class DevicePropFetchConsumer {
// 不进行格式校验,直接转换数据
byte[] commandData = ByteUtils.convertToHexByteArray(command);
switch (serverType) {
- //通过mqtt内部客户端 下发指令
+ // 通过mqtt内部客户端 下发指令
case MQTT:
publish(topic, commandData);
log.info("=>MQTT-线程=[{}],设备=[{}],轮询指令:[{}],主题:[{}]", Thread.currentThread().getName(), serialNumber, command, topic);
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceAbstractQuartzJob.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceAbstractQuartzJob.java
index c24fd805..b3b31169 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceAbstractQuartzJob.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceAbstractQuartzJob.java
@@ -2,9 +2,10 @@ package com.fastbee.iot.data.job;
import com.fastbee.common.constant.Constants;
import com.fastbee.common.constant.ScheduleConstants;
+import com.fastbee.common.trace.TraceContext;
+import com.fastbee.common.trace.TraceIdGenerator;
import com.fastbee.common.utils.ExceptionUtil;
import com.fastbee.common.utils.StringUtils;
-import com.fastbee.common.utils.TraceIdUtils;
import com.fastbee.common.utils.bean.BeanUtils;
import com.fastbee.common.utils.spring.SpringUtils;
import com.fastbee.iot.domain.DeviceJob;
@@ -29,7 +30,7 @@ public abstract class DeviceAbstractQuartzJob implements Job {
/**
* 线程本地变量
*/
- private static ThreadLocal threadLocal = new ThreadLocal<>();
+ private static final ThreadLocal threadLocal = new ThreadLocal<>();
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
@@ -37,9 +38,7 @@ public abstract class DeviceAbstractQuartzJob implements Job {
BeanUtils.copyBeanProp(deviceJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
try {
before(context, deviceJob);
- if (deviceJob != null) {
- doExecute(context, deviceJob);
- }
+ doExecute(context, deviceJob);
after(context, deviceJob, null);
} catch (Exception e) {
log.error("任务执行异常 - :", e);
@@ -56,8 +55,8 @@ public abstract class DeviceAbstractQuartzJob implements Job {
protected void before(JobExecutionContext context, DeviceJob deviceJob) {
threadLocal.set(new Date());
- // 为设备定时任务生成TraceId,使用统一的生成逻辑
- TraceIdUtils.generateTraceId();
+ // 为设备定时任务生成 TraceId
+ TraceContext.setTraceId(TraceIdGenerator.generate());
log.info("设备定时任务开始执行: {} - {}", deviceJob.getJobName(), deviceJob.getJobGroup());
}
@@ -91,10 +90,6 @@ public abstract class DeviceAbstractQuartzJob implements Job {
startTime = new Date();
}
sysJobLog.setStartTime(startTime);
- Date stopTime = new Date();
- sysJobLog.setStopTime(stopTime);
- long runMs = stopTime.getTime() - startTime.getTime();
- sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
if (e != null) {
sysJobLog.setStatus(Constants.FAIL);
String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
@@ -103,13 +98,18 @@ public abstract class DeviceAbstractQuartzJob implements Job {
sysJobLog.setStatus(Constants.SUCCESS);
}
+ Date stopTime = new Date();
+ sysJobLog.setStopTime(stopTime);
+ long runMs = stopTime.getTime() - startTime.getTime();
+ sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
+
// 写入数据库当中
SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
- // 清除TraceId,避免内存泄漏
- TraceIdUtils.clearTraceId();
-
log.info("设备定时任务执行完成: {} - {}, 耗时: {}ms", deviceJob.getJobName(), deviceJob.getJobGroup(), runMs);
+
+ // 清除 TraceContext
+ TraceContext.clear();
}
/**
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceDisallowConcurrentExecution.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceDisallowConcurrentExecution.java
index 62c5bb74..e45a4196 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceDisallowConcurrentExecution.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceDisallowConcurrentExecution.java
@@ -11,6 +11,7 @@ import org.quartz.JobExecutionContext;
*/
@DisallowConcurrentExecution
public class DeviceDisallowConcurrentExecution extends DeviceAbstractQuartzJob {
+
@Override
protected void doExecute(JobExecutionContext context, DeviceJob deviceJob) throws Exception {
DeviceJobInvoke.invokeMethod(deviceJob);
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceJobExecution.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceJobExecution.java
index 87788c0b..5f9af7c2 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceJobExecution.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceJobExecution.java
@@ -9,6 +9,7 @@ import org.quartz.JobExecutionContext;
* @author sumuxi, 2026-04-16
*/
public class DeviceJobExecution extends DeviceAbstractQuartzJob {
+
@Override
protected void doExecute(JobExecutionContext context, DeviceJob deviceJob) throws Exception {
DeviceJobInvoke.invokeMethod(deviceJob);
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceJobInvoke.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceJobInvoke.java
index 7f0f4332..cff7e810 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceJobInvoke.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/DeviceJobInvoke.java
@@ -29,13 +29,13 @@ public class DeviceJobInvoke {
/**
* 获取消息推送接口
*/
- private static IMqttMessagePublish messagePublish = SpringUtils.getBean(IMqttMessagePublish.class);
+ private static final IMqttMessagePublish messagePublish = SpringUtils.getBean(IMqttMessagePublish.class);
- private static SceneMapper sceneMapper = SpringUtils.getBean(SceneMapper.class);
+ private static final SceneMapper sceneMapper = SpringUtils.getBean(SceneMapper.class);
- private static FlowLogExecutor flowExecutor = SpringUtils.getBean(FlowLogExecutor.class);
+ private static final FlowLogExecutor flowExecutor = SpringUtils.getBean(FlowLogExecutor.class);
- private static IDataHandler dataHandler = SpringUtils.getBean(IDataHandler.class);
+ private static final IDataHandler dataHandler = SpringUtils.getBean(IDataHandler.class);
/**
* 执行方法
@@ -44,45 +44,45 @@ public class DeviceJobInvoke {
*/
public static void invokeMethod(DeviceJob deviceJob) throws Exception {
if (deviceJob.getJobType() == 1) {
- log.info("------------------------1、执行设备定时任务-----------------------------");
+ log.info("1、[执行设备定时任务]");
List actions = JSON.parseArray(deviceJob.getActions(), Action.class);
- List propertys = new ArrayList<>();
+ List properties = new ArrayList<>();
List functions = new ArrayList<>();
- for (int i = 0; i < actions.size(); i++) {
+ for (Action action : actions) {
ThingsModelSimpleItem model = new ThingsModelSimpleItem();
- model.setId(actions.get(i).getId());
- model.setValue(actions.get(i).getValue());
+ model.setId(action.getId());
+ model.setValue(action.getValue());
model.setRemark("设备定时");
- if (actions.get(i).getType() == 1) {
- propertys.add(model);
- } else if (actions.get(i).getType() == 2) {
+ if (action.getType() == 1) {
+ properties.add(model);
+ } else if (action.getType() == 2) {
functions.add(model);
}
}
// 发布属性
- if (propertys.size() > 0) {
- messagePublish.publishProperty(deviceJob.getProductId(), deviceJob.getSerialNumber(), propertys, 0);
+ if (!properties.isEmpty()) {
+ messagePublish.publishProperty(deviceJob.getProductId(), deviceJob.getSerialNumber(), properties, 0);
}
// 发布功能
- if (functions.size() > 0) {
+ if (!functions.isEmpty()) {
messagePublish.publishFunction(deviceJob.getProductId(), deviceJob.getSerialNumber(), functions, 0);
}
} else if (deviceJob.getJobType() == 3) {
- log.info("------------------3、[定时执行场景联动]---------------------");
+ log.info("3、[定时执行场景联动]");
Scene scene = sceneMapper.selectSceneBySceneId(deviceJob.getSceneId());
// 执行场景规则,异步非阻塞
SceneContext context = new SceneContext("", 0L, 0, null);
String requestId = "scene/" + scene.getChainName();
flowExecutor.execute2FutureWithRid(scene.getChainName(), null, requestId, context);
} else if (4 == deviceJob.getJobType()) {
- log.info("------------------4、[定时执行场景运算型变量]---------------------");
+ log.info("4、[定时执行场景运算型变量]");
String s = dataHandler.calculateSceneModelTagValue(deviceJob.getDatasourceId());
if (StringUtils.isEmpty(s)) {
- log.info("------------------[定时执行场景运算型变量失败:+{}]---------------------", s);
+ log.info("[定时执行场景运算型变量失败:+{}]", s);
}
} else if (5 == deviceJob.getJobType()) {
- log.info("----------------5、执行modbus轮训指令----------------------");
+ log.info("5、[执行Modbus轮训指令]");
ModbusPollJob job = new ModbusPollJob();
job.setType(2);
job.setDeviceJob(deviceJob);
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/SyncDeviceStatusJob.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/SyncDeviceStatusJob.java
index 6f113113..5b299e10 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/SyncDeviceStatusJob.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/job/SyncDeviceStatusJob.java
@@ -38,7 +38,7 @@ public class SyncDeviceStatusJob {
*/
public void syncDeviceStatus() {
if (enabled) {
- //获取所有已激活并不是禁用的设备
+ // 获取所有已激活并不是禁用的设备
List deviceStatusVOList = deviceService.selectDeviceActive();
if (!CollectionUtils.isEmpty(deviceStatusVOList)) {
for (DeviceStatusVO statusVO : deviceStatusVOList) {
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceOtherListen.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceOtherListen.java
index 0b84e340..8679a1af 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceOtherListen.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceOtherListen.java
@@ -2,6 +2,7 @@ package com.fastbee.iot.data.listen;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.mq.DeviceReportBo;
+import com.fastbee.common.trace.TraceContext;
import com.fastbee.iot.data.consumer.DeviceOtherMsgConsumer;
import com.fastbee.mq.queue.DeviceOtherQueue;
import lombok.extern.slf4j.Slf4j;
@@ -26,7 +27,15 @@ public class DeviceOtherListen {
while (true) {
try {
DeviceReportBo reportBo = DeviceOtherQueue.take();
- otherMsgConsumer.consume(reportBo);
+
+ // 恢复 TraceId
+ TraceContext.ensureTraceId(reportBo.getTraceId());
+
+ try {
+ otherMsgConsumer.consume(reportBo);
+ } finally {
+ TraceContext.clear();
+ }
} catch (Exception e) {
log.error("=>emq数据转发异常");
}
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DevicePropFetchListen.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DevicePropFetchListen.java
index 167a9b41..06388c3c 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DevicePropFetchListen.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DevicePropFetchListen.java
@@ -1,6 +1,7 @@
package com.fastbee.iot.data.listen;
import com.fastbee.common.constant.FastBeeConstant;
+import com.fastbee.common.trace.TraceContext;
import com.fastbee.iot.data.consumer.DevicePropFetchConsumer;
import com.fastbee.iot.model.modbus.ModbusPollJob;
import com.fastbee.mq.queue.DevicePropFetchQueue;
@@ -27,9 +28,20 @@ public class DevicePropFetchListen {
while (true) {
try {
ModbusPollJob take = DevicePropFetchQueue.take();
- devicePropFetchConsumer.consume(take);
+
+ // 恢复 TraceId
+ TraceContext.ensureTraceId(take.getTraceId());
+
+ try {
+ devicePropFetchConsumer.consume(take);
+ } finally {
+ TraceContext.clear();
+ }
} catch (Exception e) {
log.error("=>设备属性获取异常", e);
+ } finally {
+ // 确保任何情况下都清理
+ TraceContext.clear();
}
}
}
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceReplyListen.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceReplyListen.java
index fee5854f..9109c799 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceReplyListen.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceReplyListen.java
@@ -2,6 +2,7 @@ package com.fastbee.iot.data.listen;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.mq.DeviceReportBo;
+import com.fastbee.common.trace.TraceContext;
import com.fastbee.iot.data.consumer.DeviceReplyMsgConsumer;
import com.fastbee.mq.queue.DeviceReplyQueue;
import lombok.extern.slf4j.Slf4j;
@@ -27,10 +28,21 @@ public class DeviceReplyListen {
try {
/*读队列消息*/
DeviceReportBo reportBo = DeviceReplyQueue.take();
- /*处理消息*/
- deviceReplyMsgHandler.consume(reportBo);
+
+ // 恢复 TraceId
+ TraceContext.ensureTraceId(reportBo.getTraceId());
+
+ try {
+ /*处理消息*/
+ deviceReplyMsgHandler.consume(reportBo);
+ } finally {
+ TraceContext.clear();
+ }
} catch (Exception e) {
log.error("=>设备回调消息监听异常", e);
+ } finally {
+ // 确保任何情况下都清理上下文
+ TraceContext.clear();
}
}
}
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceReportListen.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceReportListen.java
index caad7495..fa28bb03 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceReportListen.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceReportListen.java
@@ -2,6 +2,7 @@ package com.fastbee.iot.data.listen;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.mq.DeviceReportBo;
+import com.fastbee.common.trace.TraceContext;
import com.fastbee.iot.data.consumer.DeviceReportMsgConsumer;
import com.fastbee.mq.queue.DeviceReportQueue;
import lombok.extern.slf4j.Slf4j;
@@ -27,10 +28,21 @@ public class DeviceReportListen {
try {
/*取出数据*/
DeviceReportBo reportBo = DeviceReportQueue.take();
- /*处理数据*/
- reportMsgConsumer.consume(reportBo);
+
+ // 恢复 TraceId
+ TraceContext.ensureTraceId(reportBo.getTraceId());
+
+ try {
+ /*处理数据*/
+ reportMsgConsumer.consume(reportBo);
+ } finally {
+ TraceContext.clear();
+ }
} catch (Exception e) {
log.error("=>设备上报数据监听异常", e);
+ } finally {
+ // 确保任何情况下都清理上下文
+ TraceContext.clear();
}
}
}
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceStatusListen.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceStatusListen.java
index 6252c0cb..07bdf6b9 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceStatusListen.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceStatusListen.java
@@ -2,6 +2,7 @@ package com.fastbee.iot.data.listen;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.mq.DeviceStatusBo;
+import com.fastbee.common.trace.TraceContext;
import com.fastbee.iot.data.consumer.DeviceStatusConsumer;
import com.fastbee.mq.queue.DeviceStatusQueue;
import lombok.extern.slf4j.Slf4j;
@@ -26,10 +27,20 @@ public class DeviceStatusListen {
try {
while (true) {
DeviceStatusBo status = DeviceStatusQueue.take();
- deviceStatusConsumer.consume(status);
+
+ // 恢复 TraceId
+ TraceContext.ensureTraceId(status.getTraceId());
+
+ try {
+ deviceStatusConsumer.consume(status);
+ } finally {
+ TraceContext.clear();
+ }
}
} catch (Exception e) {
log.error("设备状态监听错误", e);
+ } finally {
+ TraceContext.clear();
}
}
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceTestListen.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceTestListen.java
index 1b4de8a7..c3cd81e8 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceTestListen.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/DeviceTestListen.java
@@ -2,6 +2,7 @@ package com.fastbee.iot.data.listen;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.mq.DeviceTestReportBo;
+import com.fastbee.common.trace.TraceContext;
import com.fastbee.iot.data.consumer.DeviceTestConsumer;
import com.fastbee.mq.queue.DeviceTestQueue;
import lombok.extern.slf4j.Slf4j;
@@ -26,9 +27,19 @@ public class DeviceTestListen {
try {
DeviceTestReportBo take = DeviceTestQueue.take();
- deviceTestConsumer.consume(take);
+ // 恢复 TraceId
+ TraceContext.ensureTraceId(take.getTraceId());
+
+ try {
+ deviceTestConsumer.consume(take);
+ } finally {
+ TraceContext.clear();
+ }
} catch (Exception e) {
log.error("=>emq数据转发异常");
+ } finally {
+ // 确保任何情况下都清理上下文
+ TraceContext.clear();
}
}
}
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/FunctionInvokeListen.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/FunctionInvokeListen.java
index a631bd22..aa4473ae 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/FunctionInvokeListen.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/FunctionInvokeListen.java
@@ -2,6 +2,7 @@ package com.fastbee.iot.data.listen;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.mq.MQSendMessageBo;
+import com.fastbee.common.trace.TraceContext;
import com.fastbee.iot.data.consumer.FunctionInvokeConsumer;
import com.fastbee.mq.queue.FunctionInvokeQueue;
import lombok.extern.slf4j.Slf4j;
@@ -26,9 +27,20 @@ public class FunctionInvokeListen {
while (true) {
try {
MQSendMessageBo sendBo = FunctionInvokeQueue.take();
- functionInvokeConsumer.handler(sendBo);
+
+ // 恢复 TraceId
+ TraceContext.ensureTraceId(sendBo.getTraceId());
+
+ try {
+ functionInvokeConsumer.handler(sendBo);
+ } finally {
+ TraceContext.clear();
+ }
} catch (Exception e) {
log.error("=>下发服务消费异常", e);
+ } finally {
+ // 确保任何情况下都清理上下文
+ TraceContext.clear();
}
}
}
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/UpGradeListener.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/UpGradeListener.java
index 56a6ded2..94a7d13c 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/UpGradeListener.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/UpGradeListener.java
@@ -4,6 +4,7 @@ package com.fastbee.iot.data.listen;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.domain.entity.SysMenu;
import com.fastbee.common.core.mq.ota.OtaUpgradeDelayTask;
+import com.fastbee.common.trace.TraceContext;
import com.fastbee.common.utils.StringUtils;
import com.fastbee.iot.data.service.IOtaUpgradeService;
import com.fastbee.mq.queue.DelayUpgradeQueue;
@@ -48,12 +49,22 @@ public class UpGradeListener implements ApplicationRunner {
OtaUpgradeDelayTask task = DelayUpgradeQueue.task();
if (StringUtils.isNotNull(task)) {
Date startTime = task.getStartTime();
- otaUpgradeService.upgrade(task);
- log.info("=>开始OTA升级时间{}", startTime);
+
+ // 恢复 TraceId
+ TraceContext.ensureTraceId(task.getTraceId());
+
+ try {
+ otaUpgradeService.upgrade(task);
+ log.info("=>开始OTA升级时间{}", startTime);
+ } finally {
+ TraceContext.clear();
+ }
}
continue;
} catch (Exception e) {
log.error("=>OTA升级监听异常", e);
+ } finally {
+ TraceContext.clear();
}
}
}
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/UpgradeListen.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/UpgradeListen.java
index e7ea9a22..eb59af71 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/UpgradeListen.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/listen/UpgradeListen.java
@@ -2,6 +2,7 @@ package com.fastbee.iot.data.listen;
import com.fastbee.common.constant.FastBeeConstant;
import com.fastbee.common.core.mq.ota.OtaUpgradeBo;
+import com.fastbee.common.trace.TraceContext;
import com.fastbee.iot.data.service.IMqttMessagePublish;
import com.fastbee.mq.queue.OtaUpgradeQueue;
import lombok.extern.slf4j.Slf4j;
@@ -28,10 +29,20 @@ public class UpgradeListen {
try {
/*获取队列中的OTA升级消息*/
OtaUpgradeBo upgradeBo = OtaUpgradeQueue.take();
- // OTA升级处理
- functionSendService.upGradeOTA(upgradeBo);
+
+ // 恢复 TraceId
+ TraceContext.ensureTraceId(upgradeBo.getTraceId());
+
+ try {
+ // OTA升级处理
+ functionSendService.upGradeOTA(upgradeBo);
+ } finally {
+ TraceContext.clear();
+ }
} catch (Exception e) {
log.error("->OTA消息监听异常", e);
+ } finally {
+ TraceContext.clear();
}
}
}
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceJobServiceImpl.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceJobServiceImpl.java
index 687a9dbb..fe52ccfb 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceJobServiceImpl.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceJobServiceImpl.java
@@ -47,6 +47,10 @@ public class DeviceJobServiceImpl implements IDeviceJobService {
@PostConstruct
public void init() throws SchedulerException, TaskException {
scheduler.clear();
+ // 打印实际类型
+// log.info("Scheduler实际类型: {}", scheduler.getClass().getName());
+// log.info("Scheduler元数据: {}", scheduler.getMetaData().getSummary());
+// log.info("Scheduler class: {}", scheduler.getClass());
// 设备定时任务
List jobList = jobMapper.selectJobAll();
for (DeviceJob deviceJob : jobList) {
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceMessageServiceImpl.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceMessageServiceImpl.java
index 009c6e8b..20f45d25 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceMessageServiceImpl.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/DeviceMessageServiceImpl.java
@@ -222,7 +222,7 @@ public class DeviceMessageServiceImpl implements IDeviceMessageService {
/**
* 变量读取
*
- * @param readVO
+ * @param readVO 前端入参
*/
@Override
public void readVariableValue(VariableReadVO readVO) {
@@ -233,7 +233,7 @@ public class DeviceMessageServiceImpl implements IDeviceMessageService {
deviceAndProtocol.getProtocolCode().equals(FastBeeConstant.PROTOCOL.ModbusTcp)))
throw new ServiceException("非modbus协议请先配置主动采集协议");
Long productId = deviceAndProtocol.getProductId();
- //如果是子设备,则获取网关子设备的产品id和设备编号
+ // 如果是子设备,则获取网关子设备的产品id和设备编号
Integer deviceType = deviceAndProtocol.getDeviceType();
if (deviceType == DeviceType.SUB_GATEWAY.getCode()) {
serialNumber = deviceAndProtocol.getGwSerialNumber();
@@ -242,7 +242,7 @@ public class DeviceMessageServiceImpl implements IDeviceMessageService {
Integer type = readVO.getType();
type = Objects.isNull(type) ? 1 : type;
if (type == 1) {
- //单个变量获取
+ // 单个变量获取
String identifier = readVO.getIdentifier();
ThingsModelValueItem thingModels = itslCache.getSingleThingModels(deviceAndProtocol.getProductId(), identifier);
ModbusConfig config = thingModels.getConfig();
@@ -275,7 +275,7 @@ public class DeviceMessageServiceImpl implements IDeviceMessageService {
job.setType(1);
MessageProducer.sendPropFetch(job);
} else {
- //读取当前设备的所有变量,这里读取所有的,判断传递的设备是网关设备、网关子设备、直连设备
+ // 读取当前设备的所有变量,这里读取所有的,判断传递的设备是网关设备、网关子设备、直连设备
DeviceType code = DeviceType.transfer(deviceType);
List modbusJobList = modbusJobService.selectDevicesJobByDeviceType(code, readVO.getSerialNumber());
List commandList = modbusJobList.stream().map(ModbusJob::getCommand).collect(Collectors.toList());
diff --git a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/FirmwareTaskServiceImpl.java b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/FirmwareTaskServiceImpl.java
index abe92afd..e522d24a 100644
--- a/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/FirmwareTaskServiceImpl.java
+++ b/fastbee-iot-data/src/main/java/com/fastbee/iot/data/service/impl/FirmwareTaskServiceImpl.java
@@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fastbee.common.core.mq.ota.OtaUpgradeDelayTask;
import com.fastbee.common.enums.OTAUpgrade;
import com.fastbee.common.exception.ServiceException;
+import com.fastbee.common.trace.TraceContext;
import com.fastbee.common.utils.DateUtils;
import com.fastbee.common.utils.MessageUtils;
import com.fastbee.common.utils.StringUtils;
@@ -140,7 +141,9 @@ public class FirmwareTaskServiceImpl extends ServiceImpl
+
+
+ DELETE FROM notify_log
+ WHERE create_time < DATE_SUB(NOW(), INTERVAL #{days} DAY)
+
diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformDeviceMappingController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformDeviceMappingController.java
index ed857816..306bae39 100644
--- a/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformDeviceMappingController.java
+++ b/fastbee-open-api/src/main/java/com/fastbee/controller/platform/PlatformDeviceMappingController.java
@@ -75,6 +75,13 @@ public class PlatformDeviceMappingController extends BaseController {
// ==================== 子映射管理 ====================
+ @PreAuthorize("@ss.hasAnyPermi('platform:deviceMapping:query,platform:deviceMapping:list')")
+ @GetMapping("/sub-mappings/batch")
+ @ApiOperation("批量查询多个设备绑定的子设备映射(列表页展开行一次拉取,避免 N 次请求)")
+ public AjaxResult listSubMappingsBatch(@RequestParam List parentIds) {
+ return success(subMappingService.selectByParentIds(parentIds));
+ }
+
@PreAuthorize("@ss.hasAnyPermi('platform:deviceMapping:query,platform:deviceMapping:list')")
@GetMapping("/{id}/sub-mappings")
@ApiOperation("查询子设备映射列表")
diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/system/LogCleanController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/system/LogCleanController.java
new file mode 100644
index 00000000..5d66c39c
--- /dev/null
+++ b/fastbee-open-api/src/main/java/com/fastbee/controller/system/LogCleanController.java
@@ -0,0 +1,96 @@
+package com.fastbee.controller.system;
+
+import com.fastbee.common.annotation.Log;
+import com.fastbee.common.core.controller.BaseController;
+import com.fastbee.common.core.domain.AjaxResult;
+import com.fastbee.common.enums.BusinessType;
+import com.fastbee.iot.service.IAlertLogService;
+import com.fastbee.iot.service.IEventLogService;
+import com.fastbee.notify.service.INotifyLogService;
+import com.fastbee.quartz.service.ISysJobLogService;
+import com.fastbee.system.service.ISysLogininforService;
+import com.fastbee.system.service.ISysOperLogService;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import io.swagger.annotations.ApiParam;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 日志清理Controller
+ *
+ * @author fastbee
+ */
+@Api(tags = "系统管理:日志清理")
+@RestController
+@RequestMapping("/system/log")
+public class LogCleanController extends BaseController {
+
+ @Autowired
+ private IEventLogService eventLogService;
+
+ @Autowired
+ private ISysOperLogService operLogService;
+
+ @Autowired
+ private ISysLogininforService logininforService;
+
+ @Autowired
+ private IAlertLogService alertLogService;
+
+ @Autowired
+ private ISysJobLogService jobLogService;
+
+ @Autowired
+ private INotifyLogService notifyLogService;
+
+ /**
+ * 清理日志数据
+ *
+ * @param days 保留天数,默认15天
+ * @return 清理结果
+ */
+ @ApiOperation("清理日志数据")
+ @PreAuthorize("@ss.hasRole('admin')")
+ @Log(title = "日志清理", businessType = BusinessType.CLEAN)
+ @PostMapping("/clean")
+ public AjaxResult cleanLogs(
+ @ApiParam("保留天数,默认15天")
+ @RequestParam(value = "days", defaultValue = "15") Integer days) {
+ try {
+ logger.info("手动触发日志清理,保留最近 {} 天的记录", days);
+
+ if (days < 1) {
+ return error("保留天数必须大于等于1天");
+ }
+
+ // 清理 IoT 事件日志
+ eventLogService.cleanEventLog(days);
+
+ // 清理系统操作日志
+ operLogService.cleanOperLog(days);
+
+ // 清理登录日志
+ logininforService.cleanLogininfor(days);
+
+ // 清理设备告警日志
+ alertLogService.cleanAlertLog(days);
+
+ // 清理定时任务日志
+ jobLogService.cleanJobLog(days);
+
+ // 清理通知日志
+ notifyLogService.cleanNotifyLog(days);
+
+ logger.info("日志清理完成,保留最近 {} 天的记录", days);
+ return success(String.format("日志清理完成,已删除 %d 天前的历史数据", days));
+ } catch (Exception e) {
+ logger.error("日志清理失败", e);
+ return error("日志清理失败:" + e.getMessage());
+ }
+ }
+}
\ No newline at end of file
diff --git a/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java b/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java
index 5254dd68..337dd926 100644
--- a/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java
+++ b/fastbee-open-api/src/main/java/com/fastbee/controller/tool/ToolController.java
@@ -254,20 +254,19 @@ public class ToolController extends BaseController {
}
} catch (Exception ex) {
- ex.printStackTrace();
log.error("发生错误:{}", ex.getMessage(), ex);
}
}
@ApiOperation("mqtt钩子处理")
@PostMapping("/mqtt/webhookv5")
- public void webHookProcessv5(@RequestBody JSONObject json) {
+ public void webHookProcessV5(@RequestBody JSONObject json) {
try {
String clientid = json.getString("clientid");
String event = json.getString("event");
String peername = json.getString("peername"); // 类似 127.0.0.1:8812
String ipAddress = peername;
- if (peername.indexOf(":") != -1) {
+ if (peername.contains(":")) {
ipAddress = peername.substring(0, peername.indexOf(":"));
}
log.info("webhook:{},clientid:{},peername:{}", event, clientid, peername);
@@ -310,18 +309,17 @@ public class ToolController extends BaseController {
// 影子模式,发布属性和功能
if (device.getIsShadow() == 1) {
ThingsModelShadow shadow = deviceService.getDeviceShadowThingsModel(device);
- if (shadow.getProperties().size() > 0) {
+ if (!shadow.getProperties().isEmpty()) {
messagePublish.publishProperty(device.getProductId(), device.getSerialNumber(), shadow.getProperties(), 3);
}
- if (shadow.getFunctions().size() > 0) {
+ if (!shadow.getFunctions().isEmpty()) {
messagePublish.publishFunction(device.getProductId(), device.getSerialNumber(), shadow.getFunctions(), 3);
}
}
}
} catch (Exception ex) {
- ex.printStackTrace();
- log.error("发生错误:" + ex.getMessage());
+ log.error("发生错误:{}", ex.getMessage());
}
}
diff --git a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/controller/SysJobController.java b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/controller/SysJobController.java
index b6f01656..bf0ddd6e 100644
--- a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/controller/SysJobController.java
+++ b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/controller/SysJobController.java
@@ -83,9 +83,9 @@ public class SysJobController extends BaseController {
return error(StringUtils.format(MessageUtils.message("job.add.failed.cron.not.valid"), job.getJobName()));
} else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) {
return error(StringUtils.format(MessageUtils.message("job.add.failed.rmi.not.valid"), job.getJobName()));
- } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS})) {
+ } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS)) {
return error(StringUtils.format(MessageUtils.message("job.add.failed.ldap.not.valid"), job.getJobName()));
- } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{Constants.HTTP, Constants.HTTPS})) {
+ } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.HTTP, Constants.HTTPS)) {
return error(StringUtils.format(MessageUtils.message("job.add.failed.http.not.valid"), job.getJobName()));
} else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) {
return error(StringUtils.format(MessageUtils.message("job.add.failed.string.error"), job.getJobName()));
@@ -108,9 +108,9 @@ public class SysJobController extends BaseController {
return error(StringUtils.format(MessageUtils.message("job.update.failed.cron.not.valid"), job.getJobName()));
} else if (StringUtils.containsIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_RMI)) {
return error(StringUtils.format(MessageUtils.message("job.update.failed.rmi.not.valid"), job.getJobName()));
- } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS})) {
+ } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.LOOKUP_LDAP, Constants.LOOKUP_LDAPS)) {
return error(StringUtils.format(MessageUtils.message("job.update.failed.ldap.not.valid"), job.getJobName()));
- } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{Constants.HTTP, Constants.HTTPS})) {
+ } else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.HTTP, Constants.HTTPS)) {
return error(StringUtils.format(MessageUtils.message("job.update.failed.http.not.valid"), job.getJobName()));
} else if (StringUtils.containsAnyIgnoreCase(job.getInvokeTarget(), Constants.JOB_ERROR_STR)) {
return error(StringUtils.format(MessageUtils.message("job.update.failed.string.error"), job.getJobName()));
diff --git a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/mapper/SysJobLogMapper.java b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/mapper/SysJobLogMapper.java
index 8b35eefe..cf40842d 100644
--- a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/mapper/SysJobLogMapper.java
+++ b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/mapper/SysJobLogMapper.java
@@ -61,4 +61,12 @@ public interface SysJobLogMapper {
* 清空任务日志
*/
public void cleanJobLog();
+
+ /**
+ * 删除指定天数前的任务日志
+ *
+ * @param days 保留天数
+ * @return 删除记录数
+ */
+ public int deleteJobLogBeforeDays(int days);
}
diff --git a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/ISysJobLogService.java b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/ISysJobLogService.java
index 50893074..a0bea432 100644
--- a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/ISysJobLogService.java
+++ b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/ISysJobLogService.java
@@ -53,4 +53,11 @@ public interface ISysJobLogService {
* 清空任务日志
*/
public void cleanJobLog();
+
+ /**
+ * 清理指定天数前的任务日志
+ *
+ * @param days 保留天数
+ */
+ public void cleanJobLog(int days);
}
diff --git a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/impl/SysJobLogServiceImpl.java b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/impl/SysJobLogServiceImpl.java
index a5dfbe5e..8ba0f9dc 100644
--- a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/impl/SysJobLogServiceImpl.java
+++ b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/service/impl/SysJobLogServiceImpl.java
@@ -78,4 +78,14 @@ public class SysJobLogServiceImpl implements ISysJobLogService {
public void cleanJobLog() {
jobLogMapper.cleanJobLog();
}
+
+ /**
+ * 清理指定天数前的任务日志
+ *
+ * @param days 保留天数
+ */
+ @Override
+ public void cleanJobLog(int days) {
+ jobLogMapper.deleteJobLogBeforeDays(days);
+ }
}
diff --git a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java
index 6789a91b..30c6f2fe 100644
--- a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java
+++ b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/AbstractQuartzJob.java
@@ -2,9 +2,10 @@ package com.fastbee.quartz.util;
import com.fastbee.common.constant.Constants;
import com.fastbee.common.constant.ScheduleConstants;
+import com.fastbee.common.trace.TraceContext;
+import com.fastbee.common.trace.TraceIdGenerator;
import com.fastbee.common.utils.ExceptionUtil;
import com.fastbee.common.utils.StringUtils;
-import com.fastbee.common.utils.TraceIdUtils;
import com.fastbee.common.utils.bean.BeanUtils;
import com.fastbee.common.utils.spring.SpringUtils;
import com.fastbee.quartz.domain.SysJob;
@@ -29,7 +30,7 @@ public abstract class AbstractQuartzJob implements Job {
/**
* 线程本地变量
*/
- private static ThreadLocal threadLocal = new ThreadLocal<>();
+ private static final ThreadLocal threadLocal = new ThreadLocal<>();
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
@@ -37,9 +38,7 @@ public abstract class AbstractQuartzJob implements Job {
BeanUtils.copyBeanProp(sysJob, context.getMergedJobDataMap().get(ScheduleConstants.TASK_PROPERTIES));
try {
before(context, sysJob);
- if (sysJob != null) {
- doExecute(context, sysJob);
- }
+ doExecute(context, sysJob);
after(context, sysJob, null);
} catch (Exception e) {
log.error("任务执行异常 - :", e);
@@ -56,8 +55,8 @@ public abstract class AbstractQuartzJob implements Job {
protected void before(JobExecutionContext context, SysJob sysJob) {
threadLocal.set(new Date());
- // 为定时任务生成TraceId,使用统一的生成逻辑
- TraceIdUtils.generateTraceId();
+ // 为定时任务生成 TraceId
+ TraceContext.setTraceId(TraceIdGenerator.generate());
log.info("定时任务开始执行: {} - {}", sysJob.getJobName(), sysJob.getJobGroup());
}
@@ -77,9 +76,6 @@ public abstract class AbstractQuartzJob implements Job {
sysJobLog.setJobGroup(sysJob.getJobGroup());
sysJobLog.setInvokeTarget(sysJob.getInvokeTarget());
sysJobLog.setStartTime(startTime);
- sysJobLog.setStopTime(new Date());
- long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
- sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
if (e != null) {
sysJobLog.setStatus(Constants.FAIL);
String errorMsg = StringUtils.substring(ExceptionUtil.getExceptionMessage(e), 0, 2000);
@@ -87,14 +83,17 @@ public abstract class AbstractQuartzJob implements Job {
} else {
sysJobLog.setStatus(Constants.SUCCESS);
}
-
+ sysJobLog.setStopTime(new Date());
+ long runMs = sysJobLog.getStopTime().getTime() - sysJobLog.getStartTime().getTime();
+ sysJobLog.setJobMessage(sysJobLog.getJobName() + " 总共耗时:" + runMs + "毫秒");
// 写入数据库当中
SpringUtils.getBean(ISysJobLogService.class).addJobLog(sysJobLog);
- // 清除TraceId,避免内存泄漏
- TraceIdUtils.clearTraceId();
-
log.info("定时任务执行完成: {} - {}, 耗时: {}ms", sysJob.getJobName(), sysJob.getJobGroup(), runMs);
+
+ // 清除 TraceContext
+ TraceContext.clear();
+
}
/**
diff --git a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/JobInvokeUtil.java b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/JobInvokeUtil.java
index 1e78616a..02a36297 100644
--- a/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/JobInvokeUtil.java
+++ b/fastbee-plugs/fastbee-quartz/src/main/java/com/fastbee/quartz/util/JobInvokeUtil.java
@@ -99,8 +99,8 @@ public class JobInvokeUtil {
}
String[] methodParams = methodStr.split(",(?=([^\"']*[\"'][^\"']*[\"'])*[^\"']*$)");
List