diff --git a/pom.xml b/pom.xml
index 924692c..6f06c07 100644
--- a/pom.xml
+++ b/pom.xml
@@ -306,12 +306,6 @@
${hutool-all.version}
-
- org.springframework.boot
- spring-boot-starter-test
- ${springboottest.version}
-
-
javax.servlet
javax.servlet-api
diff --git a/sql/Device.sql b/sql/Device.sql
index b60d6f5..ae0fc39 100644
--- a/sql/Device.sql
+++ b/sql/Device.sql
@@ -1,17 +1,20 @@
-- 创建数据库
-CREATE DATABASE IF NOT EXISTS `storm-device`;
+CREATE DATABASE IF NOT EXISTS `storm-device`
+DEFAULT CHARACTER SET utf8mb4
+COLLATE utf8mb4_unicode_ci;
+
USE `storm-device`;
SET NAMES utf8mb4;
--- 设备表(主表)
+-- 设备表修改
CREATE TABLE device (
- id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
device_id VARCHAR(255) NOT NULL COMMENT '设备ID',
device_name VARCHAR(255) DEFAULT NULL COMMENT '设备名称',
product_id VARCHAR(255) DEFAULT NULL COMMENT '产品ID',
product_name VARCHAR(255) DEFAULT NULL COMMENT '产品名称',
device_type INT DEFAULT NULL COMMENT '设备类型(1:RS-LL-X1, 2:RS-LL-X2)',
- STATUS VARCHAR(50) DEFAULT 'OFFLINE' COMMENT '设备在线状态(ONLINE/OFFLINE)',
+ status VARCHAR(50) DEFAULT 'OFFLINE' COMMENT '设备在线状态(ONLINE/OFFLINE)',
-- 位置信息
location VARCHAR(255) DEFAULT NULL COMMENT '安装位置',
@@ -27,13 +30,13 @@ CREATE TABLE device (
activation_code VARCHAR(255) DEFAULT NULL COMMENT '激活码',
serial_number VARCHAR(255) DEFAULT NULL COMMENT '序列号',
license_status VARCHAR(50) DEFAULT NULL COMMENT '许可证状态',
- license_expire_time DATETIME COMMENT '许可证过期时间',
- last_massage_time DATETIME COMMENT '最后消息时间',
+ license_expire_time DATETIME DEFAULT NULL COMMENT '许可证过期时间',
+ last_massage_time DATETIME DEFAULT NULL COMMENT '最后按摩时间',
-- 在线状态时间记录
- last_online_time DATETIME COMMENT '最后上线时间',
- last_offline_time DATETIME COMMENT '最后下线时间',
- activation_time DATETIME COMMENT '激活时间',
+ last_online_time DATETIME DEFAULT NULL COMMENT '最后上线时间',
+ last_offline_time DATETIME DEFAULT NULL COMMENT '最后下线时间',
+ activation_time DATETIME DEFAULT NULL COMMENT '激活时间',
-- 运行时长统计(秒)
total_online_duration BIGINT DEFAULT 0 COMMENT '累计在线时长(秒)',
@@ -46,29 +49,28 @@ CREATE TABLE device (
remark TEXT COMMENT '备注',
-- 系统字段
- is_deleted TINYINT(1) DEFAULT '0' COMMENT '是否删除(0-未删除, 1-已删除)',
+ is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除(0-未删除, 1-已删除)',
create_by VARCHAR(255) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(255) DEFAULT NULL COMMENT '更新人',
- create_time DATETIME DEFAULT NULL COMMENT '创建时间',
- update_time DATETIME DEFAULT NULL COMMENT '更新时间',
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
UNIQUE KEY uk_device_id (device_id),
- KEY idx_device_id (device_id),
KEY idx_product_id (product_id),
KEY idx_shop_id (shop_id),
- KEY idx_status (STATUS),
- KEY idx_create_time (create_time),
+ KEY idx_status (status),
KEY idx_license_status (license_status),
- KEY idx_last_online_time (last_online_time)
+ KEY idx_last_online_time (last_online_time),
+ KEY idx_create_time (create_time)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备表';
--- 设备状态日志表
+-- 设备状态日志表修改
CREATE TABLE device_status_log (
- id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
device_id VARCHAR(255) NOT NULL COMMENT '设备ID',
- STATUS VARCHAR(255) DEFAULT NULL COMMENT '状态',
- event_type VARCHAR(255) DEFAULT NULL COMMENT '事件类型',
+ status VARCHAR(50) DEFAULT NULL COMMENT '状态',
+ event_type VARCHAR(100) DEFAULT NULL COMMENT '事件类型',
-- 毫秒时间戳字段
massage_start_time BIGINT DEFAULT NULL COMMENT '按摩开始时间(毫秒时间戳)',
@@ -76,39 +78,44 @@ CREATE TABLE device_status_log (
massage_duration BIGINT DEFAULT NULL COMMENT '按摩持续时间(毫秒)',
-- 按摩相关信息
- head_type VARCHAR(255) DEFAULT NULL COMMENT '按摩头类型',
- body_part VARCHAR(255) DEFAULT NULL COMMENT '身体部位',
+ head_type VARCHAR(100) DEFAULT NULL COMMENT '按摩头类型',
+ body_part VARCHAR(100) DEFAULT NULL COMMENT '身体部位',
massage_plan VARCHAR(255) DEFAULT NULL COMMENT '按摩方案',
-- 时间字段
event_time DATETIME DEFAULT NULL COMMENT '事件时间',
last_online_time DATETIME DEFAULT NULL COMMENT '最后在线时间',
- duration INT(11) DEFAULT NULL COMMENT '持续时间(秒)',
+ duration INT DEFAULT NULL COMMENT '持续时间(秒)',
+
+ -- 描述和备注
+ description TEXT COMMENT '描述',
+ remark TEXT COMMENT '备注',
-- 系统字段
- is_deleted TINYINT(1) DEFAULT '0' COMMENT '是否删除(0-未删除, 1-已删除)',
+ is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除(0-未删除, 1-已删除)',
create_by VARCHAR(255) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(255) DEFAULT NULL COMMENT '更新人',
- create_time DATETIME DEFAULT NULL COMMENT '创建时间',
- update_time DATETIME DEFAULT NULL COMMENT '更新时间',
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
- UNIQUE KEY uk_device_event (device_id, event_time, STATUS),
+ UNIQUE KEY uk_device_event_time (device_id, event_time, status, event_type),
KEY idx_device_id (device_id),
KEY idx_event_time (event_time),
- KEY idx_status (STATUS),
+ KEY idx_status (status),
KEY idx_head_type (head_type),
- KEY idx_event_type (event_type)
+ KEY idx_event_type (event_type),
+ KEY idx_create_time (create_time)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备状态日志表';
--- 按摩任务表
+-- 按摩任务表修改
CREATE TABLE massage_task (
- id BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
device_id VARCHAR(255) NOT NULL COMMENT '设备ID',
device_name VARCHAR(255) DEFAULT NULL COMMENT '设备名称',
product_name VARCHAR(255) DEFAULT NULL COMMENT '产品名称',
- head_type VARCHAR(255) DEFAULT NULL COMMENT '按摩头类型',
- body_part VARCHAR(255) DEFAULT NULL COMMENT '身体部位',
+ head_type VARCHAR(100) DEFAULT NULL COMMENT '按摩头类型',
+ body_part VARCHAR(100) DEFAULT NULL COMMENT '身体部位',
massage_plan VARCHAR(255) DEFAULT NULL COMMENT '按摩方案',
-- 创建人
@@ -120,16 +127,20 @@ CREATE TABLE massage_task (
task_time BIGINT DEFAULT NULL COMMENT '任务时长(毫秒)',
create_timestamp BIGINT DEFAULT NULL COMMENT '创建时间戳(毫秒)',
+ -- 描述和备注
+ description TEXT COMMENT '描述',
+ remark TEXT COMMENT '备注',
+
-- 时间字段
- create_time DATETIME DEFAULT NULL COMMENT '创建时间',
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
-- 系统字段
- is_deleted TINYINT(1) DEFAULT '0' COMMENT '是否删除(0-未删除, 1-已删除)',
+ is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除(0-未删除, 1-已删除)',
update_by VARCHAR(255) DEFAULT NULL COMMENT '更新人',
- update_time DATETIME DEFAULT NULL COMMENT '更新时间',
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
- UNIQUE KEY uk_device_task (device_id, start_time, head_type, body_part),
+ UNIQUE KEY uk_device_task_time (device_id, start_time, head_type, body_part),
KEY idx_device_id (device_id),
KEY idx_start_time (start_time),
KEY idx_head_type (head_type),
@@ -137,9 +148,9 @@ CREATE TABLE massage_task (
KEY idx_create_time (create_time)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='按摩任务表';
--- 设备运行时长统计表
+-- 设备运行时长统计表修改
CREATE TABLE device_runtime_stats (
- id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
device_id VARCHAR(255) NOT NULL COMMENT '设备唯一标识',
stat_date DATE NOT NULL COMMENT '统计日期',
daily_duration BIGINT DEFAULT 0 COMMENT '日运行时长(秒)',
@@ -147,21 +158,27 @@ CREATE TABLE device_runtime_stats (
monthly_duration BIGINT DEFAULT 0 COMMENT '月运行时长(秒)',
online_count INT DEFAULT 0 COMMENT '上线次数',
+ -- 描述和备注
+ description TEXT COMMENT '描述',
+ remark TEXT COMMENT '备注',
+
-- 系统字段
- is_deleted TINYINT(1) DEFAULT '0' COMMENT '是否删除(0-未删除, 1-已删除)',
+ is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除(0-未删除, 1-已删除)',
create_by VARCHAR(255) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(255) DEFAULT NULL COMMENT '更新人',
- create_time DATETIME DEFAULT NULL COMMENT '创建时间',
- update_time DATETIME DEFAULT NULL COMMENT '更新时间',
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
- UNIQUE KEY uk_device_date (device_id, stat_date),
+ PRIMARY KEY (id),
+ UNIQUE KEY uk_device_stat_date (device_id, stat_date),
KEY idx_stat_date (stat_date),
- KEY idx_device_id (device_id)
+ KEY idx_device_id (device_id),
+ KEY idx_create_time (create_time)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备运行时长统计表';
--- 设备按头使用统计表
+-- 设备按头使用统计表修改
CREATE TABLE device_head_usage (
- id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+ id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
device_id VARCHAR(255) NOT NULL COMMENT '设备唯一标识',
head_type VARCHAR(100) NOT NULL COMMENT '按头类型',
usage_count INT DEFAULT 0 COMMENT '使用次数',
@@ -169,23 +186,23 @@ CREATE TABLE device_head_usage (
daily_duration BIGINT DEFAULT 0 COMMENT '今日使用时长(秒)',
weekly_duration BIGINT DEFAULT 0 COMMENT '本周使用时长(秒)',
monthly_duration BIGINT DEFAULT 0 COMMENT '本月使用时长(秒)',
- last_used_time DATETIME COMMENT '最后使用时间',
+ last_used_time DATETIME DEFAULT NULL COMMENT '最后使用时间',
+
+ -- 描述和备注
+ description TEXT COMMENT '描述',
+ remark TEXT COMMENT '备注',
-- 系统字段
- is_deleted TINYINT(1) DEFAULT '0' COMMENT '是否删除(0-未删除, 1-已删除)',
+ is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否删除(0-未删除, 1-已删除)',
create_by VARCHAR(255) DEFAULT NULL COMMENT '创建人',
update_by VARCHAR(255) DEFAULT NULL COMMENT '更新人',
- create_time DATETIME DEFAULT NULL COMMENT '创建时间',
- update_time DATETIME DEFAULT NULL COMMENT '更新时间',
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+ update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
- UNIQUE KEY uk_device_head (device_id, head_type),
+ PRIMARY KEY (id),
+ UNIQUE KEY uk_device_head_type (device_id, head_type),
KEY idx_head_type (head_type),
KEY idx_last_used_time (last_used_time),
- KEY idx_device_id (device_id)
-) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备按头使用统计表';
-
--- 如果业务允许,可以考虑调整唯一键约束,增加时间容差
--- 比如允许同一设备在同一分钟内只有一条相同状态的记录
-ALTER TABLE device_status_log
-DROP INDEX uk_device_event,
-ADD UNIQUE KEY uk_device_event_minute (device_id, DATE_FORMAT(event_time, '%Y-%m-%d %H:%i'), status);
\ No newline at end of file
+ KEY idx_device_id (device_id),
+ KEY idx_create_time (create_time)
+) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备按头使用统计表';
\ No newline at end of file
diff --git a/sql/storm-AfterSales.sql b/sql/storm-AfterSales.sql
new file mode 100644
index 0000000..c6828b4
--- /dev/null
+++ b/sql/storm-AfterSales.sql
@@ -0,0 +1,140 @@
+CREATE DATABASE `storm-AfterSales`
+
+USE `storm-AfterSales`
+
+CREATE TABLE sys_work_order_type (
+ type_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '类型ID',
+ type_name VARCHAR(50) NOT NULL COMMENT '类型名称(操作习惯、部件维修、设备巡厂维修、产品建议、投诉)',
+ type_key VARCHAR(50) NOT NULL COMMENT '类型键值',
+ STATUS CHAR(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
+ order_num INT DEFAULT 0 COMMENT '显示顺序',
+ create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ create_time DATETIME COMMENT '创建时间',
+ update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ update_time DATETIME COMMENT '更新时间',
+ remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
+ UNIQUE KEY uk_type_key (type_key)
+) ENGINE=INNODB COMMENT='工单类型表';
+
+CREATE TABLE sys_work_order (
+ order_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '工单ID',
+ order_no VARCHAR(50) UNIQUE NOT NULL COMMENT '工单编号',
+ order_title VARCHAR(200) NOT NULL COMMENT '工单标题',
+ order_type VARCHAR(50) COMMENT '工单类型(对应type_key)',
+ order_status VARCHAR(20) DEFAULT 'pending' COMMENT '工单状态(pending, processing, resolved, closed, cancelled)',
+ priority VARCHAR(10) DEFAULT 'medium' COMMENT '优先级(low, medium, high, urgent)',
+
+ -- 客户信息
+ customer_id BIGINT COMMENT '客户ID',
+ customer_name VARCHAR(100) COMMENT '客户姓名',
+ customer_contact VARCHAR(50) COMMENT '客户联系方式',
+ store_address VARCHAR(200) COMMENT '门店地址',
+
+ -- 设备信息
+ device_name VARCHAR(100) COMMENT '设备名称',
+ device_serial_no VARCHAR(100) COMMENT '设备序列号',
+ device_model VARCHAR(100) COMMENT '设备型号',
+ production_date DATE COMMENT '生产日期',
+
+ -- 问题描述(多媒体支持)
+ problem_description TEXT COMMENT '问题描述',
+ attachment_urls JSON COMMENT '附件URL(图片/视频) - 支持多个',
+ problem_category VARCHAR(50) COMMENT '问题分类(数量问题、质量问题、软件故障等)',
+
+ -- 处理信息
+ assignee_id BIGINT COMMENT '处理人ID',
+ assignee_name VARCHAR(100) COMMENT '处理人姓名',
+ assign_time DATETIME COMMENT '分配时间',
+ current_handler_id BIGINT COMMENT '当前处理人ID',
+ current_handler_name VARCHAR(100) COMMENT '当前处理人姓名',
+
+ -- 进度管理(结构化)
+ root_cause TEXT COMMENT '原因定位',
+ solution_plan TEXT COMMENT '处理方案',
+ final_solution TEXT COMMENT '最终解决方案',
+
+ -- 物流信息(图片1明确需求)
+ logistics_number VARCHAR(100) COMMENT '物流单号',
+ logistics_progress VARCHAR(500) COMMENT '物流进度',
+
+ -- 客户反馈
+ customer_feedback TEXT COMMENT '客户反馈',
+ satisfaction_level INT COMMENT '满意度(1-5)',
+ feedback_type VARCHAR(20) COMMENT '反馈类型(praise, complaint, suggestion)',
+
+ -- 时间管理
+ plan_finish_time DATETIME COMMENT '计划完成时间',
+ actual_finish_time DATETIME COMMENT '实际完成时间',
+ response_deadline DATETIME COMMENT '响应截止时间(30分钟规则)',
+
+ -- 来源渠道
+ source_channel VARCHAR(20) DEFAULT 'feishu' COMMENT '来源渠道(feishu, phone, wechat, app, web)',
+ feishu_chat_id VARCHAR(100) COMMENT '飞书会话ID',
+
+ -- 若依标准字段
+ create_by VARCHAR(64) DEFAULT '' COMMENT '创建者',
+ create_time DATETIME COMMENT '创建时间',
+ update_by VARCHAR(64) DEFAULT '' COMMENT '更新者',
+ update_time DATETIME COMMENT '更新时间',
+ remark VARCHAR(500) DEFAULT NULL COMMENT '备注',
+
+ -- 索引优化
+ INDEX idx_order_no (order_no),
+ INDEX idx_status (order_status),
+ INDEX idx_assignee (assignee_id),
+ INDEX idx_customer (customer_id),
+ INDEX idx_create_time (create_time),
+ INDEX idx_device_serial (device_serial_no),
+ INDEX idx_logistics (logistics_number)
+) ENGINE=INNODB COMMENT='工单主表';
+
+-- 工单处理记录表(增强进度跟踪)
+CREATE TABLE sys_work_order_record (
+ record_id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '记录ID',
+ order_id BIGINT NOT NULL COMMENT '工单ID',
+ process_type VARCHAR(50) COMMENT '处理类型(create, assign, process, feedback, close, visit等)',
+ process_content TEXT COMMENT '处理内容',
+
+ -- 结构化进度信息
+ progress_type VARCHAR(50) COMMENT '进度类型(logistics, repair, replacement, optimization, complaint)',
+ progress_value VARCHAR(200) COMMENT '进度值',
+ progress_percent INT COMMENT '进度百分比',
+
+ process_user_id BIGINT COMMENT '处理人ID',
+ process_user_name VARCHAR(100) COMMENT '处理人姓名',
+ process_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '处理时间',
+ attachment_url VARCHAR(500) COMMENT '进度附件',
+
+ INDEX idx_order_id (order_id),
+ INDEX idx_process_time (process_time),
+ INDEX idx_progress_type (progress_type)
+) ENGINE=INNODB COMMENT='工单处理记录表';
+
+-- 工单聊天关联表(支持多渠道)
+CREATE TABLE sys_work_order_chat_rel (
+ id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
+ order_id BIGINT NOT NULL COMMENT '工单ID',
+ chat_session_id VARCHAR(100) COMMENT '聊天会话ID',
+ chat_platform VARCHAR(20) DEFAULT 'feishu' COMMENT '平台(feishu, wechat, phone)',
+ message_id VARCHAR(100) COMMENT '消息ID',
+ chat_content TEXT COMMENT '聊天内容摘要',
+ chat_duration INT COMMENT '通话时长(秒)',
+ create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+
+ INDEX idx_order_id (order_id),
+ INDEX idx_chat_session (chat_session_id),
+ INDEX idx_platform (chat_platform)
+) ENGINE=INNODB COMMENT='工单聊天关联表';
+
+-- 新增:客服知识库关联表
+CREATE TABLE sys_knowledge_rel (
+ id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
+ order_id BIGINT NOT NULL COMMENT '工单ID',
+ knowledge_id BIGINT COMMENT '知识库文章ID',
+ knowledge_title VARCHAR(200) COMMENT '知识标题',
+ used_count INT DEFAULT 1 COMMENT '使用次数',
+ create_time DATETIME COMMENT '关联时间',
+
+ INDEX idx_order_id (order_id),
+ INDEX idx_knowledge (knowledge_id)
+) ENGINE=INNODB COMMENT='工单知识库关联表';
\ No newline at end of file
diff --git a/storm-afterSales/pom.xml b/storm-afterSales/pom.xml
new file mode 100644
index 0000000..bc810a5
--- /dev/null
+++ b/storm-afterSales/pom.xml
@@ -0,0 +1,111 @@
+
+
+ 4.0.0
+
+ com.storm
+ storm
+ 3.6.6
+
+
+ storm-afterSales
+ 0.0.1-SNAPSHOT
+ storm-afterSales
+ storm-afterSales工单管理
+
+
+ 17
+ 17
+ UTF-8
+
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-discovery
+ 2.2.6.RELEASE
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-nacos-config
+ 2.2.6.RELEASE
+
+
+
+
+ com.alibaba.cloud
+ spring-cloud-starter-alibaba-sentinel
+
+
+
+
+ com.storm
+ storm-common-security
+
+
+
+ com.storm
+ storm-common-redis
+
+
+
+ com.storm
+ storm-common-datascope
+
+
+
+ com.storm
+ storm-api-system
+
+
+
+
+ com.mysql
+ mysql-connector-j
+
+
+
+
+ com.storm
+ storm-common-log
+
+
+
+
+ com.storm
+ storm-common-swagger
+
+
+
+ org.projectlombok
+ lombok
+
+
+
+ com.storm
+ storm-common-oss
+ 3.6.6
+
+
+
+
+ ${project.artifactId}
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+ repackage
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/AfterSalesApplication.java b/storm-afterSales/src/main/java/com/storm/afterSales/AfterSalesApplication.java
new file mode 100644
index 0000000..1117ee6
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/AfterSalesApplication.java
@@ -0,0 +1,13 @@
+package com.storm.afterSales;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class AfterSalesApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(AfterSalesApplication.class, args);
+ }
+
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysKnowledgeRelController.java b/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysKnowledgeRelController.java
new file mode 100644
index 0000000..b04c7a3
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysKnowledgeRelController.java
@@ -0,0 +1,117 @@
+package com.storm.afterSales.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.storm.afterSales.domain.SysKnowledgeRel;
+import com.storm.afterSales.service.ISysKnowledgeRelService;
+import com.storm.common.core.domain.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.storm.common.log.annotation.Log;
+import com.storm.common.log.enums.BusinessType;
+import com.storm.common.security.annotation.RequiresPermissions;
+import com.storm.common.core.web.controller.BaseController;
+import com.storm.common.core.web.domain.AjaxResult;
+import com.storm.common.core.utils.poi.ExcelUtil;
+import com.storm.common.core.web.page.TableDataInfo;
+
+/**
+ * 工单知识库关联Controller
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@RestController
+@RequestMapping("/afterSales")
+@Tag(name = "工单知识库关联相关接口")
+public class SysKnowledgeRelController extends BaseController
+{
+ @Autowired
+ private ISysKnowledgeRelService sysKnowledgeRelService;
+
+ /**
+ * 查询工单知识库关联列表
+ */
+ @RequiresPermissions("afterSales:afterSales:list")
+ @GetMapping("/list")
+ @Operation(summary = "查询工单知识库关联列表")
+ public TableDataInfo> list(@Parameter(description = "工单知识库关联查询条件") SysKnowledgeRel sysKnowledgeRel)
+ {
+ startPage();
+ List list = sysKnowledgeRelService.selectSysKnowledgeRelList(sysKnowledgeRel);
+ return getDataTable(list);
+ }
+
+ /**
+ * 导出工单知识库关联列表
+ */
+ @RequiresPermissions("afterSales:afterSales:export")
+ @Log(title = "工单知识库关联", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ @Operation(summary = "导出工单知识库关联列表")
+ public void export(HttpServletResponse response, @Parameter(description = "工单知识库关联查询条件") SysKnowledgeRel sysKnowledgeRel)
+ {
+ List list = sysKnowledgeRelService.selectSysKnowledgeRelList(sysKnowledgeRel);
+ ExcelUtil util = new ExcelUtil(SysKnowledgeRel.class);
+ util.exportExcel(response, list, "工单知识库关联数据");
+ }
+
+ /**
+ * 获取工单知识库关联详细信息
+ */
+ @RequiresPermissions("afterSales:afterSales:query")
+ @GetMapping(value = "/{id}")
+ @Operation(summary = "获取工单知识库关联详细信息")
+ public R getInfo(@Parameter(description = "工单知识库关联ID", required = true)
+ @PathVariable("id") Long id)
+ {
+ return R.ok(sysKnowledgeRelService.selectSysKnowledgeRelById(id));
+ }
+
+ /**
+ * 新增工单知识库关联
+ */
+ @RequiresPermissions("afterSales:afterSales:add")
+ @Log(title = "工单知识库关联", businessType = BusinessType.INSERT)
+ @PostMapping
+ @Operation(summary = "新增工单知识库关联")
+ public AjaxResult add(@Parameter(description = "工单知识库关联实体", required = true) @RequestBody SysKnowledgeRel sysKnowledgeRel)
+ {
+ return toAjax(sysKnowledgeRelService.insertSysKnowledgeRel(sysKnowledgeRel));
+ }
+
+ /**
+ * 修改工单知识库关联
+ */
+ @RequiresPermissions("afterSales:afterSales:edit")
+ @Log(title = "工单知识库关联", businessType = BusinessType.UPDATE)
+ @PutMapping
+ @Operation(summary = "修改工单知识库关联")
+ public AjaxResult edit(@Parameter(description = "工单知识库关联实体", required = true) @RequestBody SysKnowledgeRel sysKnowledgeRel)
+ {
+ return toAjax(sysKnowledgeRelService.updateSysKnowledgeRel(sysKnowledgeRel));
+ }
+
+ /**
+ * 删除工单知识库关联
+ */
+ @RequiresPermissions("afterSales:afterSales:remove")
+ @Log(title = "工单知识库关联", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ @Operation(summary = "删除工单知识库关联")
+ public AjaxResult remove(@Parameter(description = "工单知识库关联ID数组", required = true) @PathVariable Long[] ids)
+ {
+ return toAjax(sysKnowledgeRelService.deleteSysKnowledgeRelByIds(ids));
+ }
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderChatRelController.java b/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderChatRelController.java
new file mode 100644
index 0000000..8da1ab3
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderChatRelController.java
@@ -0,0 +1,117 @@
+package com.storm.afterSales.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.storm.common.core.domain.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.storm.common.log.annotation.Log;
+import com.storm.common.log.enums.BusinessType;
+import com.storm.common.security.annotation.RequiresPermissions;
+import com.storm.afterSales.domain.SysWorkOrderChatRel;
+import com.storm.afterSales.service.ISysWorkOrderChatRelService;
+import com.storm.common.core.web.controller.BaseController;
+import com.storm.common.core.web.domain.AjaxResult;
+import com.storm.common.core.utils.poi.ExcelUtil;
+import com.storm.common.core.web.page.TableDataInfo;
+
+/**
+ * 工单聊天关联Controller
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@RestController
+@RequestMapping("/afterSales")
+@Tag(name = "工单聊天关联相关接口")
+public class SysWorkOrderChatRelController extends BaseController
+{
+ @Autowired
+ private ISysWorkOrderChatRelService sysWorkOrderChatRelService;
+
+ /**
+ * 查询工单聊天关联列表
+ */
+ @RequiresPermissions("afterSales:afterSales:list")
+ @GetMapping("/list")
+ @Operation(summary = "查询工单聊天关联列表")
+ public TableDataInfo> list(@Parameter(description = "工单聊天关联查询条件") SysWorkOrderChatRel sysWorkOrderChatRel)
+ {
+ startPage();
+ List list = sysWorkOrderChatRelService.selectSysWorkOrderChatRelList(sysWorkOrderChatRel);
+ return getDataTable(list);
+ }
+
+ /**
+ * 导出工单聊天关联列表
+ */
+ @RequiresPermissions("afterSales:afterSales:export")
+ @Log(title = "工单聊天关联", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ @Operation(summary = "导出工单聊天关联列表")
+ public void export(HttpServletResponse response, @Parameter(description = "工单聊天关联查询条件") SysWorkOrderChatRel sysWorkOrderChatRel)
+ {
+ List list = sysWorkOrderChatRelService.selectSysWorkOrderChatRelList(sysWorkOrderChatRel);
+ ExcelUtil util = new ExcelUtil(SysWorkOrderChatRel.class);
+ util.exportExcel(response, list, "工单聊天关联数据");
+ }
+
+ /**
+ * 获取工单聊天关联详细信息
+ */
+ @RequiresPermissions("afterSales:afterSales:query")
+ @GetMapping(value = "/{id}")
+ @Operation(summary = "获取工单聊天关联详细信息")
+ public R getInfo(@Parameter(description = "工单聊天关联ID", required = true)
+ @PathVariable("id") Long id)
+ {
+ return R.ok(sysWorkOrderChatRelService.selectSysWorkOrderChatRelById(id));
+ }
+
+ /**
+ * 新增工单聊天关联
+ */
+ @RequiresPermissions("afterSales:afterSales:add")
+ @Log(title = "工单聊天关联", businessType = BusinessType.INSERT)
+ @PostMapping
+ @Operation(summary = "新增工单聊天关联")
+ public AjaxResult add(@Parameter(description = "工单聊天关联实体", required = true) @RequestBody SysWorkOrderChatRel sysWorkOrderChatRel)
+ {
+ return toAjax(sysWorkOrderChatRelService.insertSysWorkOrderChatRel(sysWorkOrderChatRel));
+ }
+
+ /**
+ * 修改工单聊天关联
+ */
+ @RequiresPermissions("afterSales:afterSales:edit")
+ @Log(title = "工单聊天关联", businessType = BusinessType.UPDATE)
+ @PutMapping
+ @Operation(summary = "修改工单聊天关联")
+ public AjaxResult edit(@Parameter(description = "工单聊天关联实体", required = true) @RequestBody SysWorkOrderChatRel sysWorkOrderChatRel)
+ {
+ return toAjax(sysWorkOrderChatRelService.updateSysWorkOrderChatRel(sysWorkOrderChatRel));
+ }
+
+ /**
+ * 删除工单聊天关联
+ */
+ @RequiresPermissions("afterSales:afterSales:remove")
+ @Log(title = "工单聊天关联", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{ids}")
+ @Operation(summary = "删除工单聊天关联")
+ public AjaxResult remove(@Parameter(description = "工单聊天关联ID数组", required = true) @PathVariable Long[] ids)
+ {
+ return toAjax(sysWorkOrderChatRelService.deleteSysWorkOrderChatRelByIds(ids));
+ }
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderController.java b/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderController.java
new file mode 100644
index 0000000..f90d44f
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderController.java
@@ -0,0 +1,117 @@
+package com.storm.afterSales.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.storm.common.core.domain.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.storm.common.log.annotation.Log;
+import com.storm.common.log.enums.BusinessType;
+import com.storm.common.security.annotation.RequiresPermissions;
+import com.storm.afterSales.domain.SysWorkOrder;
+import com.storm.afterSales.service.ISysWorkOrderService;
+import com.storm.common.core.web.controller.BaseController;
+import com.storm.common.core.web.domain.AjaxResult;
+import com.storm.common.core.utils.poi.ExcelUtil;
+import com.storm.common.core.web.page.TableDataInfo;
+
+/**
+ * 工单主Controller
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@RestController
+@RequestMapping("/afterSales")
+@Tag(name = "工单主相关接口")
+public class SysWorkOrderController extends BaseController
+{
+ @Autowired
+ private ISysWorkOrderService sysWorkOrderService;
+
+ /**
+ * 查询工单主列表
+ */
+ @RequiresPermissions("afterSales:afterSales:list")
+ @GetMapping("/list")
+ @Operation(summary = "查询工单主列表")
+ public TableDataInfo> list(@Parameter(description = "工单主查询条件") SysWorkOrder sysWorkOrder)
+ {
+ startPage();
+ List list = sysWorkOrderService.selectSysWorkOrderList(sysWorkOrder);
+ return getDataTable(list);
+ }
+
+ /**
+ * 导出工单主列表
+ */
+ @RequiresPermissions("afterSales:afterSales:export")
+ @Log(title = "工单主", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ @Operation(summary = "导出工单主列表")
+ public void export(HttpServletResponse response, @Parameter(description = "工单主查询条件") SysWorkOrder sysWorkOrder)
+ {
+ List list = sysWorkOrderService.selectSysWorkOrderList(sysWorkOrder);
+ ExcelUtil util = new ExcelUtil(SysWorkOrder.class);
+ util.exportExcel(response, list, "工单主数据");
+ }
+
+ /**
+ * 获取工单主详细信息
+ */
+ @RequiresPermissions("afterSales:afterSales:query")
+ @GetMapping(value = "/{orderId}")
+ @Operation(summary = "获取工单主详细信息")
+ public R getInfo(@Parameter(description = "工单主ID", required = true)
+ @PathVariable("orderId") Long orderId)
+ {
+ return R.ok(sysWorkOrderService.selectSysWorkOrderByOrderId(orderId));
+ }
+
+ /**
+ * 新增工单主
+ */
+ @RequiresPermissions("afterSales:afterSales:add")
+ @Log(title = "工单主", businessType = BusinessType.INSERT)
+ @PostMapping
+ @Operation(summary = "新增工单主")
+ public AjaxResult add(@Parameter(description = "工单主实体", required = true) @RequestBody SysWorkOrder sysWorkOrder)
+ {
+ return toAjax(sysWorkOrderService.insertSysWorkOrder(sysWorkOrder));
+ }
+
+ /**
+ * 修改工单主
+ */
+ @RequiresPermissions("afterSales:afterSales:edit")
+ @Log(title = "工单主", businessType = BusinessType.UPDATE)
+ @PutMapping
+ @Operation(summary = "修改工单主")
+ public AjaxResult edit(@Parameter(description = "工单主实体", required = true) @RequestBody SysWorkOrder sysWorkOrder)
+ {
+ return toAjax(sysWorkOrderService.updateSysWorkOrder(sysWorkOrder));
+ }
+
+ /**
+ * 删除工单主
+ */
+ @RequiresPermissions("afterSales:afterSales:remove")
+ @Log(title = "工单主", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{orderIds}")
+ @Operation(summary = "删除工单主")
+ public AjaxResult remove(@Parameter(description = "工单主ID数组", required = true) @PathVariable Long[] orderIds)
+ {
+ return toAjax(sysWorkOrderService.deleteSysWorkOrderByOrderIds(orderIds));
+ }
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderRecordController.java b/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderRecordController.java
new file mode 100644
index 0000000..0ebb15c
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderRecordController.java
@@ -0,0 +1,117 @@
+package com.storm.afterSales.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.storm.common.core.domain.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.storm.common.log.annotation.Log;
+import com.storm.common.log.enums.BusinessType;
+import com.storm.common.security.annotation.RequiresPermissions;
+import com.storm.afterSales.domain.SysWorkOrderRecord;
+import com.storm.afterSales.service.ISysWorkOrderRecordService;
+import com.storm.common.core.web.controller.BaseController;
+import com.storm.common.core.web.domain.AjaxResult;
+import com.storm.common.core.utils.poi.ExcelUtil;
+import com.storm.common.core.web.page.TableDataInfo;
+
+/**
+ * 工单处理记录Controller
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@RestController
+@RequestMapping("/afterSales")
+@Tag(name = "工单处理记录相关接口")
+public class SysWorkOrderRecordController extends BaseController
+{
+ @Autowired
+ private ISysWorkOrderRecordService sysWorkOrderRecordService;
+
+ /**
+ * 查询工单处理记录列表
+ */
+ @RequiresPermissions("afterSales:afterSales:list")
+ @GetMapping("/list")
+ @Operation(summary = "查询工单处理记录列表")
+ public TableDataInfo> list(@Parameter(description = "工单处理记录查询条件") SysWorkOrderRecord sysWorkOrderRecord)
+ {
+ startPage();
+ List list = sysWorkOrderRecordService.selectSysWorkOrderRecordList(sysWorkOrderRecord);
+ return getDataTable(list);
+ }
+
+ /**
+ * 导出工单处理记录列表
+ */
+ @RequiresPermissions("afterSales:afterSales:export")
+ @Log(title = "工单处理记录", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ @Operation(summary = "导出工单处理记录列表")
+ public void export(HttpServletResponse response, @Parameter(description = "工单处理记录查询条件") SysWorkOrderRecord sysWorkOrderRecord)
+ {
+ List list = sysWorkOrderRecordService.selectSysWorkOrderRecordList(sysWorkOrderRecord);
+ ExcelUtil util = new ExcelUtil(SysWorkOrderRecord.class);
+ util.exportExcel(response, list, "工单处理记录数据");
+ }
+
+ /**
+ * 获取工单处理记录详细信息
+ */
+ @RequiresPermissions("afterSales:afterSales:query")
+ @GetMapping(value = "/{recordId}")
+ @Operation(summary = "获取工单处理记录详细信息")
+ public R getInfo(@Parameter(description = "工单处理记录ID", required = true)
+ @PathVariable("recordId") Long recordId)
+ {
+ return R.ok(sysWorkOrderRecordService.selectSysWorkOrderRecordByRecordId(recordId));
+ }
+
+ /**
+ * 新增工单处理记录
+ */
+ @RequiresPermissions("afterSales:afterSales:add")
+ @Log(title = "工单处理记录", businessType = BusinessType.INSERT)
+ @PostMapping
+ @Operation(summary = "新增工单处理记录")
+ public AjaxResult add(@Parameter(description = "工单处理记录实体", required = true) @RequestBody SysWorkOrderRecord sysWorkOrderRecord)
+ {
+ return toAjax(sysWorkOrderRecordService.insertSysWorkOrderRecord(sysWorkOrderRecord));
+ }
+
+ /**
+ * 修改工单处理记录
+ */
+ @RequiresPermissions("afterSales:afterSales:edit")
+ @Log(title = "工单处理记录", businessType = BusinessType.UPDATE)
+ @PutMapping
+ @Operation(summary = "修改工单处理记录")
+ public AjaxResult edit(@Parameter(description = "工单处理记录实体", required = true) @RequestBody SysWorkOrderRecord sysWorkOrderRecord)
+ {
+ return toAjax(sysWorkOrderRecordService.updateSysWorkOrderRecord(sysWorkOrderRecord));
+ }
+
+ /**
+ * 删除工单处理记录
+ */
+ @RequiresPermissions("afterSales:afterSales:remove")
+ @Log(title = "工单处理记录", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{recordIds}")
+ @Operation(summary = "删除工单处理记录")
+ public AjaxResult remove(@Parameter(description = "工单处理记录ID数组", required = true) @PathVariable Long[] recordIds)
+ {
+ return toAjax(sysWorkOrderRecordService.deleteSysWorkOrderRecordByRecordIds(recordIds));
+ }
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderTypeController.java b/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderTypeController.java
new file mode 100644
index 0000000..dde74bb
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/controller/SysWorkOrderTypeController.java
@@ -0,0 +1,117 @@
+package com.storm.afterSales.controller;
+
+import java.util.List;
+import javax.servlet.http.HttpServletResponse;
+
+import com.storm.common.core.domain.R;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import com.storm.common.log.annotation.Log;
+import com.storm.common.log.enums.BusinessType;
+import com.storm.common.security.annotation.RequiresPermissions;
+import com.storm.afterSales.domain.SysWorkOrderType;
+import com.storm.afterSales.service.ISysWorkOrderTypeService;
+import com.storm.common.core.web.controller.BaseController;
+import com.storm.common.core.web.domain.AjaxResult;
+import com.storm.common.core.utils.poi.ExcelUtil;
+import com.storm.common.core.web.page.TableDataInfo;
+
+/**
+ * 工单类型Controller
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@RestController
+@RequestMapping("/type")
+@Tag(name = "工单类型相关接口")
+public class SysWorkOrderTypeController extends BaseController
+{
+ @Autowired
+ private ISysWorkOrderTypeService sysWorkOrderTypeService;
+
+ /**
+ * 查询工单类型列表
+ */
+ @RequiresPermissions("afterSales:type:list")
+ @GetMapping("/list")
+ @Operation(summary = "查询工单类型列表")
+ public TableDataInfo> list(@Parameter(description = "工单类型查询条件") SysWorkOrderType sysWorkOrderType)
+ {
+ startPage();
+ List list = sysWorkOrderTypeService.selectSysWorkOrderTypeList(sysWorkOrderType);
+ return getDataTable(list);
+ }
+
+ /**
+ * 导出工单类型列表
+ */
+ @RequiresPermissions("afterSales:type:export")
+ @Log(title = "工单类型", businessType = BusinessType.EXPORT)
+ @PostMapping("/export")
+ @Operation(summary = "导出工单类型列表")
+ public void export(HttpServletResponse response, @Parameter(description = "工单类型查询条件") SysWorkOrderType sysWorkOrderType)
+ {
+ List list = sysWorkOrderTypeService.selectSysWorkOrderTypeList(sysWorkOrderType);
+ ExcelUtil util = new ExcelUtil(SysWorkOrderType.class);
+ util.exportExcel(response, list, "工单类型数据");
+ }
+
+ /**
+ * 获取工单类型详细信息
+ */
+ @RequiresPermissions("afterSales:type:query")
+ @GetMapping(value = "/{typeId}")
+ @Operation(summary = "获取工单类型详细信息")
+ public R getInfo(@Parameter(description = "工单类型ID", required = true)
+ @PathVariable("typeId") Long typeId)
+ {
+ return R.ok(sysWorkOrderTypeService.selectSysWorkOrderTypeByTypeId(typeId));
+ }
+
+ /**
+ * 新增工单类型
+ */
+ @RequiresPermissions("afterSales:type:add")
+ @Log(title = "工单类型", businessType = BusinessType.INSERT)
+ @PostMapping
+ @Operation(summary = "新增工单类型")
+ public AjaxResult add(@Parameter(description = "工单类型实体", required = true) @RequestBody SysWorkOrderType sysWorkOrderType)
+ {
+ return toAjax(sysWorkOrderTypeService.insertSysWorkOrderType(sysWorkOrderType));
+ }
+
+ /**
+ * 修改工单类型
+ */
+ @RequiresPermissions("afterSales:type:edit")
+ @Log(title = "工单类型", businessType = BusinessType.UPDATE)
+ @PutMapping
+ @Operation(summary = "修改工单类型")
+ public AjaxResult edit(@Parameter(description = "工单类型实体", required = true) @RequestBody SysWorkOrderType sysWorkOrderType)
+ {
+ return toAjax(sysWorkOrderTypeService.updateSysWorkOrderType(sysWorkOrderType));
+ }
+
+ /**
+ * 删除工单类型
+ */
+ @RequiresPermissions("afterSales:type:remove")
+ @Log(title = "工单类型", businessType = BusinessType.DELETE)
+ @DeleteMapping("/{typeIds}")
+ @Operation(summary = "删除工单类型")
+ public AjaxResult remove(@Parameter(description = "工单类型ID数组", required = true) @PathVariable Long[] typeIds)
+ {
+ return toAjax(sysWorkOrderTypeService.deleteSysWorkOrderTypeByTypeIds(typeIds));
+ }
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysKnowledgeRel.java b/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysKnowledgeRel.java
new file mode 100644
index 0000000..6249cf1
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysKnowledgeRel.java
@@ -0,0 +1,49 @@
+package com.storm.afterSales.domain;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.storm.common.core.annotation.Excel;
+import com.storm.common.core.web.domain.BaseEntity;
+import lombok.Data;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+/**
+ * 工单知识库关联对象 sys_knowledge_rel
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Schema(description = "工单知识库关联实体")
+public class SysKnowledgeRel extends BaseEntity
+{
+ private static final long serialVersionUID = 1L;
+
+ /** 主键 */
+ @Schema(description = "主键")
+ private Long id;
+
+ /** 工单ID */
+ @Excel(name = "工单ID")
+ @Schema(description = "工单ID")
+ private Long orderId;
+
+ /** 知识库文章ID */
+ @Excel(name = "知识库文章ID")
+ @Schema(description = "知识库文章ID")
+ private Long knowledgeId;
+
+ /** 知识标题 */
+ @Excel(name = "知识标题")
+ @Schema(description = "知识标题")
+ private String knowledgeTitle;
+
+ /** 使用次数 */
+ @Excel(name = "使用次数")
+ @Schema(description = "使用次数")
+ private Integer usedCount;
+
+
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrder.java b/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrder.java
new file mode 100644
index 0000000..2526823
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrder.java
@@ -0,0 +1,205 @@
+package com.storm.afterSales.domain;
+
+import java.time.LocalDateTime;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.storm.common.core.annotation.Excel;
+import com.storm.common.core.web.domain.BaseEntity;
+import lombok.Data;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+/**
+ * 工单主对象 sys_work_order
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Schema(description = "工单主实体")
+public class SysWorkOrder extends BaseEntity
+{
+ private static final long serialVersionUID = 1L;
+
+ /** 工单ID */
+ @Schema(description = "工单ID")
+ private Long orderId;
+
+ /** 工单编号 */
+ @Excel(name = "工单编号")
+ @Schema(description = "工单编号")
+ private String orderNo;
+
+ /** 工单标题 */
+ @Excel(name = "工单标题")
+ @Schema(description = "工单标题")
+ private String orderTitle;
+
+ /** 工单类型(对应type_key) */
+ @Excel(name = "工单类型", readConverterExp = "对=应type_key")
+ @Schema(description = "工单类型(对应type_key)")
+ private String orderType;
+
+ /** 工单状态(pending, processing, resolved, closed, cancelled) */
+ @Excel(name = "工单状态", readConverterExp = "p=ending,,p=rocessing,,r=esolved,,c=losed,,c=ancelled")
+ @Schema(description = "工单状态(pending, processing, resolved, closed, cancelled)")
+ private String orderStatus;
+
+ /** 优先级(low, medium, high, urgent) */
+ @Excel(name = "优先级", readConverterExp = "l=ow,,m=edium,,h=igh,,u=rgent")
+ @Schema(description = "优先级(low, medium, high, urgent)")
+ private String priority;
+
+ /** 客户ID */
+ @Excel(name = "客户ID")
+ @Schema(description = "客户ID")
+ private Long customerId;
+
+ /** 客户姓名 */
+ @Excel(name = "客户姓名")
+ @Schema(description = "客户姓名")
+ private String customerName;
+
+ /** 客户联系方式 */
+ @Excel(name = "客户联系方式")
+ @Schema(description = "客户联系方式")
+ private String customerContact;
+
+ /** 门店地址 */
+ @Excel(name = "门店地址")
+ @Schema(description = "门店地址")
+ private String storeAddress;
+
+ /** 设备名称 */
+ @Excel(name = "设备名称")
+ @Schema(description = "设备名称")
+ private String deviceName;
+
+ /** 设备序列号 */
+ @Excel(name = "设备序列号")
+ @Schema(description = "设备序列号")
+ private String deviceSerialNo;
+
+ /** 设备型号 */
+ @Excel(name = "设备型号")
+ @Schema(description = "设备型号")
+ private String deviceModel;
+
+ /** 生产日期 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "生产日期", width = 30, dateFormat = "yyyy-MM-dd")
+ @Schema(description = "生产日期")
+ private LocalDateTime productionDate;
+
+ /** 问题描述 */
+ @Excel(name = "问题描述")
+ @Schema(description = "问题描述")
+ private String problemDescription;
+
+ /** 附件URL(图片/视频) - 支持多个 */
+ @Excel(name = "附件URL", readConverterExp = "图=片/视频")
+ @Schema(description = "附件URL(图片/视频) - 支持多个")
+ private String attachmentUrls;
+
+ /** 问题分类(数量问题、质量问题、软件故障等) */
+ @Excel(name = "问题分类", readConverterExp = "数=量问题、质量问题、软件故障等")
+ @Schema(description = "问题分类(数量问题、质量问题、软件故障等)")
+ private String problemCategory;
+
+ /** 处理人ID */
+ @Excel(name = "处理人ID")
+ @Schema(description = "处理人ID")
+ private Long assigneeId;
+
+ /** 处理人姓名 */
+ @Excel(name = "处理人姓名")
+ @Schema(description = "处理人姓名")
+ private String assigneeName;
+
+ /** 分配时间 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "分配时间", width = 30, dateFormat = "yyyy-MM-dd")
+ @Schema(description = "分配时间")
+ private LocalDateTime assignTime;
+
+ /** 当前处理人ID */
+ @Excel(name = "当前处理人ID")
+ @Schema(description = "当前处理人ID")
+ private Long currentHandlerId;
+
+ /** 当前处理人姓名 */
+ @Excel(name = "当前处理人姓名")
+ @Schema(description = "当前处理人姓名")
+ private String currentHandlerName;
+
+ /** 原因定位 */
+ @Excel(name = "原因定位")
+ @Schema(description = "原因定位")
+ private String rootCause;
+
+ /** 处理方案 */
+ @Excel(name = "处理方案")
+ @Schema(description = "处理方案")
+ private String solutionPlan;
+
+ /** 最终解决方案 */
+ @Excel(name = "最终解决方案")
+ @Schema(description = "最终解决方案")
+ private String finalSolution;
+
+ /** 物流单号 */
+ @Excel(name = "物流单号")
+ @Schema(description = "物流单号")
+ private String logisticsNumber;
+
+ /** 物流进度 */
+ @Excel(name = "物流进度")
+ @Schema(description = "物流进度")
+ private String logisticsProgress;
+
+ /** 客户反馈 */
+ @Excel(name = "客户反馈")
+ @Schema(description = "客户反馈")
+ private String customerFeedback;
+
+ /** 满意度(1-5) */
+ @Excel(name = "满意度", readConverterExp = "1=-5")
+ @Schema(description = "满意度(1-5)")
+ private Integer satisfactionLevel;
+
+ /** 反馈类型(praise, complaint, suggestion) */
+ @Excel(name = "反馈类型", readConverterExp = "p=raise,,c=omplaint,,s=uggestion")
+ @Schema(description = "反馈类型(praise, complaint, suggestion)")
+ private String feedbackType;
+
+ /** 计划完成时间 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "计划完成时间", width = 30, dateFormat = "yyyy-MM-dd")
+ @Schema(description = "计划完成时间")
+ private LocalDateTime planFinishTime;
+
+ /** 实际完成时间 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "实际完成时间", width = 30, dateFormat = "yyyy-MM-dd")
+ @Schema(description = "实际完成时间")
+ private LocalDateTime actualFinishTime;
+
+ /** 响应截止时间(30分钟规则) */
+ @Excel(name = "响应截止时间", readConverterExp = "3=0分钟规则")
+ @Schema(description = "响应截止时间(30分钟规则)")
+ private LocalDateTime responseDeadline;
+
+ /** 来源渠道(feishu, phone, wechat, app, web) */
+ @Excel(name = "来源渠道", readConverterExp = "f=eishu,,p=hone,,w=echat,,a=pp,,w=eb")
+ @Schema(description = "来源渠道(feishu, phone, wechat, app, web)")
+ private String sourceChannel;
+
+ /** 飞书会话ID */
+ @Excel(name = "飞书会话ID")
+ @Schema(description = "飞书会话ID")
+ private String feishuChatId;
+
+
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrderChatRel.java b/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrderChatRel.java
new file mode 100644
index 0000000..091e826
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrderChatRel.java
@@ -0,0 +1,59 @@
+package com.storm.afterSales.domain;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.storm.common.core.annotation.Excel;
+import com.storm.common.core.web.domain.BaseEntity;
+import lombok.Data;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+/**
+ * 工单聊天关联对象 sys_work_order_chat_rel
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Schema(description = "工单聊天关联实体")
+public class SysWorkOrderChatRel extends BaseEntity
+{
+ private static final long serialVersionUID = 1L;
+
+ /** 主键 */
+ @Schema(description = "主键")
+ private Long id;
+
+ /** 工单ID */
+ @Excel(name = "工单ID")
+ @Schema(description = "工单ID")
+ private Long orderId;
+
+ /** 聊天会话ID */
+ @Excel(name = "聊天会话ID")
+ @Schema(description = "聊天会话ID")
+ private String chatSessionId;
+
+ /** 平台(feishu, wechat, phone) */
+ @Excel(name = "平台", readConverterExp = "f=eishu,,w=echat,,p=hone")
+ @Schema(description = "平台(feishu, wechat, phone)")
+ private String chatPlatform;
+
+ /** 消息ID */
+ @Excel(name = "消息ID")
+ @Schema(description = "消息ID")
+ private String messageId;
+
+ /** 聊天内容摘要 */
+ @Excel(name = "聊天内容摘要")
+ @Schema(description = "聊天内容摘要")
+ private String chatContent;
+
+ /** 通话时长(秒) */
+ @Excel(name = "通话时长", readConverterExp = "秒=")
+ @Schema(description = "通话时长(秒)")
+ private Integer chatDuration;
+
+
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrderRecord.java b/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrderRecord.java
new file mode 100644
index 0000000..824f4de
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrderRecord.java
@@ -0,0 +1,82 @@
+package com.storm.afterSales.domain;
+
+import java.time.LocalDateTime;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.storm.common.core.annotation.Excel;
+import com.storm.common.core.web.domain.BaseEntity;
+import lombok.Data;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+/**
+ * 工单处理记录对象 sys_work_order_record
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Schema(description = "工单处理记录实体")
+public class SysWorkOrderRecord extends BaseEntity
+{
+ private static final long serialVersionUID = 1L;
+
+ /** 记录ID */
+ @Schema(description = "记录ID")
+ private Long recordId;
+
+ /** 工单ID */
+ @Excel(name = "工单ID")
+ @Schema(description = "工单ID")
+ private Long orderId;
+
+ /** 处理类型(create, assign, process, feedback, close, visit等) */
+ @Excel(name = "处理类型", readConverterExp = "c=reate,,a=ssign,,p=rocess,,f=eedback,,c=lose,,v=isit等")
+ @Schema(description = "处理类型(create, assign, process, feedback, close, visit等)")
+ private String processType;
+
+ /** 处理内容 */
+ @Excel(name = "处理内容")
+ @Schema(description = "处理内容")
+ private String processContent;
+
+ /** 进度类型(logistics, repair, replacement, optimization, complaint) */
+ @Excel(name = "进度类型", readConverterExp = "l=ogistics,,r=epair,,r=eplacement,,o=ptimization,,c=omplaint")
+ @Schema(description = "进度类型(logistics, repair, replacement, optimization, complaint)")
+ private String progressType;
+
+ /** 进度值 */
+ @Excel(name = "进度值")
+ @Schema(description = "进度值")
+ private String progressValue;
+
+ /** 进度百分比 */
+ @Excel(name = "进度百分比")
+ @Schema(description = "进度百分比")
+ private Integer progressPercent;
+
+ /** 处理人ID */
+ @Excel(name = "处理人ID")
+ @Schema(description = "处理人ID")
+ private Long processUserId;
+
+ /** 处理人姓名 */
+ @Excel(name = "处理人姓名")
+ @Schema(description = "处理人姓名")
+ private String processUserName;
+
+ /** 处理时间 */
+ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+ @Excel(name = "处理时间", width = 30, dateFormat = "yyyy-MM-dd")
+ @Schema(description = "处理时间")
+ private LocalDateTime processTime;
+
+ /** 进度附件 */
+ @Excel(name = "进度附件")
+ @Schema(description = "进度附件")
+ private String attachmentUrl;
+
+
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrderType.java b/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrderType.java
new file mode 100644
index 0000000..3c3f5fe
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/domain/SysWorkOrderType.java
@@ -0,0 +1,49 @@
+package com.storm.afterSales.domain;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import com.storm.common.core.annotation.Excel;
+import com.storm.common.core.web.domain.BaseEntity;
+import lombok.Data;
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+/**
+ * 工单类型对象 sys_work_order_type
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+@Schema(description = "工单类型实体")
+public class SysWorkOrderType extends BaseEntity
+{
+ private static final long serialVersionUID = 1L;
+
+ /** 类型ID */
+ @Schema(description = "类型ID")
+ private Long typeId;
+
+ /** 类型名称(操作习惯、部件维修、设备巡厂维修、产品建议、投诉) */
+ @Excel(name = "类型名称", readConverterExp = "操=作习惯、部件维修、设备巡厂维修、产品建议、投诉")
+ @Schema(description = "类型名称(操作习惯、部件维修、设备巡厂维修、产品建议、投诉)")
+ private String typeName;
+
+ /** 类型键值 */
+ @Excel(name = "类型键值")
+ @Schema(description = "类型键值")
+ private String typeKey;
+
+ /** 状态(0正常 1停用) */
+ @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
+ @Schema(description = "状态(0正常 1停用)")
+ private String STATUS;
+
+ /** 显示顺序 */
+ @Excel(name = "显示顺序")
+ @Schema(description = "显示顺序")
+ private Integer orderNum;
+
+
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysKnowledgeRelMapper.java b/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysKnowledgeRelMapper.java
new file mode 100644
index 0000000..c7d58c3
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysKnowledgeRelMapper.java
@@ -0,0 +1,64 @@
+package com.storm.afterSales.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+import com.storm.afterSales.domain.SysKnowledgeRel;
+
+/**
+ * 工单知识库关联Mapper接口
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Mapper
+public interface SysKnowledgeRelMapper extends BaseMapper
+{
+ /**
+ * 查询工单知识库关联
+ *
+ * @param id 工单知识库关联主键
+ * @return 工单知识库关联
+ */
+ public SysKnowledgeRel selectSysKnowledgeRelById(Long id);
+
+ /**
+ * 查询工单知识库关联列表
+ *
+ * @param sysKnowledgeRel 工单知识库关联
+ * @return 工单知识库关联集合
+ */
+ public List selectSysKnowledgeRelList(SysKnowledgeRel sysKnowledgeRel);
+
+ /**
+ * 新增工单知识库关联
+ *
+ * @param sysKnowledgeRel 工单知识库关联
+ * @return 结果
+ */
+ public int insertSysKnowledgeRel(SysKnowledgeRel sysKnowledgeRel);
+
+ /**
+ * 修改工单知识库关联
+ *
+ * @param sysKnowledgeRel 工单知识库关联
+ * @return 结果
+ */
+ public int updateSysKnowledgeRel(SysKnowledgeRel sysKnowledgeRel);
+
+ /**
+ * 删除工单知识库关联
+ *
+ * @param id 工单知识库关联主键
+ * @return 结果
+ */
+ public int deleteSysKnowledgeRelById(Long id);
+
+ /**
+ * 批量删除工单知识库关联
+ *
+ * @param ids 需要删除的数据主键集合
+ * @return 结果
+ */
+ public int deleteSysKnowledgeRelByIds(Long[] ids);
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderChatRelMapper.java b/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderChatRelMapper.java
new file mode 100644
index 0000000..31cbe6f
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderChatRelMapper.java
@@ -0,0 +1,64 @@
+package com.storm.afterSales.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+import com.storm.afterSales.domain.SysWorkOrderChatRel;
+
+/**
+ * 工单聊天关联Mapper接口
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Mapper
+public interface SysWorkOrderChatRelMapper extends BaseMapper
+{
+ /**
+ * 查询工单聊天关联
+ *
+ * @param id 工单聊天关联主键
+ * @return 工单聊天关联
+ */
+ public SysWorkOrderChatRel selectSysWorkOrderChatRelById(Long id);
+
+ /**
+ * 查询工单聊天关联列表
+ *
+ * @param sysWorkOrderChatRel 工单聊天关联
+ * @return 工单聊天关联集合
+ */
+ public List selectSysWorkOrderChatRelList(SysWorkOrderChatRel sysWorkOrderChatRel);
+
+ /**
+ * 新增工单聊天关联
+ *
+ * @param sysWorkOrderChatRel 工单聊天关联
+ * @return 结果
+ */
+ public int insertSysWorkOrderChatRel(SysWorkOrderChatRel sysWorkOrderChatRel);
+
+ /**
+ * 修改工单聊天关联
+ *
+ * @param sysWorkOrderChatRel 工单聊天关联
+ * @return 结果
+ */
+ public int updateSysWorkOrderChatRel(SysWorkOrderChatRel sysWorkOrderChatRel);
+
+ /**
+ * 删除工单聊天关联
+ *
+ * @param id 工单聊天关联主键
+ * @return 结果
+ */
+ public int deleteSysWorkOrderChatRelById(Long id);
+
+ /**
+ * 批量删除工单聊天关联
+ *
+ * @param ids 需要删除的数据主键集合
+ * @return 结果
+ */
+ public int deleteSysWorkOrderChatRelByIds(Long[] ids);
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderMapper.java b/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderMapper.java
new file mode 100644
index 0000000..e761638
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderMapper.java
@@ -0,0 +1,64 @@
+package com.storm.afterSales.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+import com.storm.afterSales.domain.SysWorkOrder;
+
+/**
+ * 工单主Mapper接口
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Mapper
+public interface SysWorkOrderMapper extends BaseMapper
+{
+ /**
+ * 查询工单主
+ *
+ * @param orderId 工单主主键
+ * @return 工单主
+ */
+ public SysWorkOrder selectSysWorkOrderByOrderId(Long orderId);
+
+ /**
+ * 查询工单主列表
+ *
+ * @param sysWorkOrder 工单主
+ * @return 工单主集合
+ */
+ public List selectSysWorkOrderList(SysWorkOrder sysWorkOrder);
+
+ /**
+ * 新增工单主
+ *
+ * @param sysWorkOrder 工单主
+ * @return 结果
+ */
+ public int insertSysWorkOrder(SysWorkOrder sysWorkOrder);
+
+ /**
+ * 修改工单主
+ *
+ * @param sysWorkOrder 工单主
+ * @return 结果
+ */
+ public int updateSysWorkOrder(SysWorkOrder sysWorkOrder);
+
+ /**
+ * 删除工单主
+ *
+ * @param orderId 工单主主键
+ * @return 结果
+ */
+ public int deleteSysWorkOrderByOrderId(Long orderId);
+
+ /**
+ * 批量删除工单主
+ *
+ * @param orderIds 需要删除的数据主键集合
+ * @return 结果
+ */
+ public int deleteSysWorkOrderByOrderIds(Long[] orderIds);
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderRecordMapper.java b/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderRecordMapper.java
new file mode 100644
index 0000000..d8dc6ec
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderRecordMapper.java
@@ -0,0 +1,64 @@
+package com.storm.afterSales.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+import com.storm.afterSales.domain.SysWorkOrderRecord;
+
+/**
+ * 工单处理记录Mapper接口
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Mapper
+public interface SysWorkOrderRecordMapper extends BaseMapper
+{
+ /**
+ * 查询工单处理记录
+ *
+ * @param recordId 工单处理记录主键
+ * @return 工单处理记录
+ */
+ public SysWorkOrderRecord selectSysWorkOrderRecordByRecordId(Long recordId);
+
+ /**
+ * 查询工单处理记录列表
+ *
+ * @param sysWorkOrderRecord 工单处理记录
+ * @return 工单处理记录集合
+ */
+ public List selectSysWorkOrderRecordList(SysWorkOrderRecord sysWorkOrderRecord);
+
+ /**
+ * 新增工单处理记录
+ *
+ * @param sysWorkOrderRecord 工单处理记录
+ * @return 结果
+ */
+ public int insertSysWorkOrderRecord(SysWorkOrderRecord sysWorkOrderRecord);
+
+ /**
+ * 修改工单处理记录
+ *
+ * @param sysWorkOrderRecord 工单处理记录
+ * @return 结果
+ */
+ public int updateSysWorkOrderRecord(SysWorkOrderRecord sysWorkOrderRecord);
+
+ /**
+ * 删除工单处理记录
+ *
+ * @param recordId 工单处理记录主键
+ * @return 结果
+ */
+ public int deleteSysWorkOrderRecordByRecordId(Long recordId);
+
+ /**
+ * 批量删除工单处理记录
+ *
+ * @param recordIds 需要删除的数据主键集合
+ * @return 结果
+ */
+ public int deleteSysWorkOrderRecordByRecordIds(Long[] recordIds);
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderTypeMapper.java b/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderTypeMapper.java
new file mode 100644
index 0000000..eaa7974
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/mapper/SysWorkOrderTypeMapper.java
@@ -0,0 +1,64 @@
+package com.storm.afterSales.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import org.apache.ibatis.annotations.Mapper;
+import java.util.List;
+import com.storm.afterSales.domain.SysWorkOrderType;
+
+/**
+ * 工单类型Mapper接口
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Mapper
+public interface SysWorkOrderTypeMapper extends BaseMapper
+{
+ /**
+ * 查询工单类型
+ *
+ * @param typeId 工单类型主键
+ * @return 工单类型
+ */
+ public SysWorkOrderType selectSysWorkOrderTypeByTypeId(Long typeId);
+
+ /**
+ * 查询工单类型列表
+ *
+ * @param sysWorkOrderType 工单类型
+ * @return 工单类型集合
+ */
+ public List selectSysWorkOrderTypeList(SysWorkOrderType sysWorkOrderType);
+
+ /**
+ * 新增工单类型
+ *
+ * @param sysWorkOrderType 工单类型
+ * @return 结果
+ */
+ public int insertSysWorkOrderType(SysWorkOrderType sysWorkOrderType);
+
+ /**
+ * 修改工单类型
+ *
+ * @param sysWorkOrderType 工单类型
+ * @return 结果
+ */
+ public int updateSysWorkOrderType(SysWorkOrderType sysWorkOrderType);
+
+ /**
+ * 删除工单类型
+ *
+ * @param typeId 工单类型主键
+ * @return 结果
+ */
+ public int deleteSysWorkOrderTypeByTypeId(Long typeId);
+
+ /**
+ * 批量删除工单类型
+ *
+ * @param typeIds 需要删除的数据主键集合
+ * @return 结果
+ */
+ public int deleteSysWorkOrderTypeByTypeIds(Long[] typeIds);
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysKnowledgeRelService.java b/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysKnowledgeRelService.java
new file mode 100644
index 0000000..d80fd8e
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysKnowledgeRelService.java
@@ -0,0 +1,63 @@
+package com.storm.afterSales.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.List;
+import com.storm.afterSales.domain.SysKnowledgeRel;
+
+/**
+ * 工单知识库关联Service接口
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+public interface ISysKnowledgeRelService extends IService
+{
+ /**
+ * 查询工单知识库关联
+ *
+ * @param id 工单知识库关联主键
+ * @return 工单知识库关联
+ */
+ public SysKnowledgeRel selectSysKnowledgeRelById(Long id);
+
+ /**
+ * 查询工单知识库关联列表
+ *
+ * @param sysKnowledgeRel 工单知识库关联
+ * @return 工单知识库关联集合
+ */
+ public List selectSysKnowledgeRelList(SysKnowledgeRel sysKnowledgeRel);
+
+ /**
+ * 新增工单知识库关联
+ *
+ * @param sysKnowledgeRel 工单知识库关联
+ * @return 结果
+ */
+ public int insertSysKnowledgeRel(SysKnowledgeRel sysKnowledgeRel);
+
+ /**
+ * 修改工单知识库关联
+ *
+ * @param sysKnowledgeRel 工单知识库关联
+ * @return 结果
+ */
+ public int updateSysKnowledgeRel(SysKnowledgeRel sysKnowledgeRel);
+
+ /**
+ * 批量删除工单知识库关联
+ *
+ * @param ids 需要删除的工单知识库关联主键集合
+ * @return 结果
+ */
+ public int deleteSysKnowledgeRelByIds(Long[] ids);
+
+ /**
+ * 删除工单知识库关联信息
+ *
+ * @param id 工单知识库关联主键
+ * @return 结果
+ */
+ public int deleteSysKnowledgeRelById(Long id);
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderChatRelService.java b/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderChatRelService.java
new file mode 100644
index 0000000..d368b65
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderChatRelService.java
@@ -0,0 +1,63 @@
+package com.storm.afterSales.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.List;
+import com.storm.afterSales.domain.SysWorkOrderChatRel;
+
+/**
+ * 工单聊天关联Service接口
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+public interface ISysWorkOrderChatRelService extends IService
+{
+ /**
+ * 查询工单聊天关联
+ *
+ * @param id 工单聊天关联主键
+ * @return 工单聊天关联
+ */
+ public SysWorkOrderChatRel selectSysWorkOrderChatRelById(Long id);
+
+ /**
+ * 查询工单聊天关联列表
+ *
+ * @param sysWorkOrderChatRel 工单聊天关联
+ * @return 工单聊天关联集合
+ */
+ public List selectSysWorkOrderChatRelList(SysWorkOrderChatRel sysWorkOrderChatRel);
+
+ /**
+ * 新增工单聊天关联
+ *
+ * @param sysWorkOrderChatRel 工单聊天关联
+ * @return 结果
+ */
+ public int insertSysWorkOrderChatRel(SysWorkOrderChatRel sysWorkOrderChatRel);
+
+ /**
+ * 修改工单聊天关联
+ *
+ * @param sysWorkOrderChatRel 工单聊天关联
+ * @return 结果
+ */
+ public int updateSysWorkOrderChatRel(SysWorkOrderChatRel sysWorkOrderChatRel);
+
+ /**
+ * 批量删除工单聊天关联
+ *
+ * @param ids 需要删除的工单聊天关联主键集合
+ * @return 结果
+ */
+ public int deleteSysWorkOrderChatRelByIds(Long[] ids);
+
+ /**
+ * 删除工单聊天关联信息
+ *
+ * @param id 工单聊天关联主键
+ * @return 结果
+ */
+ public int deleteSysWorkOrderChatRelById(Long id);
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderRecordService.java b/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderRecordService.java
new file mode 100644
index 0000000..781a256
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderRecordService.java
@@ -0,0 +1,63 @@
+package com.storm.afterSales.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.List;
+import com.storm.afterSales.domain.SysWorkOrderRecord;
+
+/**
+ * 工单处理记录Service接口
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+public interface ISysWorkOrderRecordService extends IService
+{
+ /**
+ * 查询工单处理记录
+ *
+ * @param recordId 工单处理记录主键
+ * @return 工单处理记录
+ */
+ public SysWorkOrderRecord selectSysWorkOrderRecordByRecordId(Long recordId);
+
+ /**
+ * 查询工单处理记录列表
+ *
+ * @param sysWorkOrderRecord 工单处理记录
+ * @return 工单处理记录集合
+ */
+ public List selectSysWorkOrderRecordList(SysWorkOrderRecord sysWorkOrderRecord);
+
+ /**
+ * 新增工单处理记录
+ *
+ * @param sysWorkOrderRecord 工单处理记录
+ * @return 结果
+ */
+ public int insertSysWorkOrderRecord(SysWorkOrderRecord sysWorkOrderRecord);
+
+ /**
+ * 修改工单处理记录
+ *
+ * @param sysWorkOrderRecord 工单处理记录
+ * @return 结果
+ */
+ public int updateSysWorkOrderRecord(SysWorkOrderRecord sysWorkOrderRecord);
+
+ /**
+ * 批量删除工单处理记录
+ *
+ * @param recordIds 需要删除的工单处理记录主键集合
+ * @return 结果
+ */
+ public int deleteSysWorkOrderRecordByRecordIds(Long[] recordIds);
+
+ /**
+ * 删除工单处理记录信息
+ *
+ * @param recordId 工单处理记录主键
+ * @return 结果
+ */
+ public int deleteSysWorkOrderRecordByRecordId(Long recordId);
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderService.java b/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderService.java
new file mode 100644
index 0000000..c97be93
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderService.java
@@ -0,0 +1,63 @@
+package com.storm.afterSales.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.List;
+import com.storm.afterSales.domain.SysWorkOrder;
+
+/**
+ * 工单主Service接口
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+public interface ISysWorkOrderService extends IService
+{
+ /**
+ * 查询工单主
+ *
+ * @param orderId 工单主主键
+ * @return 工单主
+ */
+ public SysWorkOrder selectSysWorkOrderByOrderId(Long orderId);
+
+ /**
+ * 查询工单主列表
+ *
+ * @param sysWorkOrder 工单主
+ * @return 工单主集合
+ */
+ public List selectSysWorkOrderList(SysWorkOrder sysWorkOrder);
+
+ /**
+ * 新增工单主
+ *
+ * @param sysWorkOrder 工单主
+ * @return 结果
+ */
+ public int insertSysWorkOrder(SysWorkOrder sysWorkOrder);
+
+ /**
+ * 修改工单主
+ *
+ * @param sysWorkOrder 工单主
+ * @return 结果
+ */
+ public int updateSysWorkOrder(SysWorkOrder sysWorkOrder);
+
+ /**
+ * 批量删除工单主
+ *
+ * @param orderIds 需要删除的工单主主键集合
+ * @return 结果
+ */
+ public int deleteSysWorkOrderByOrderIds(Long[] orderIds);
+
+ /**
+ * 删除工单主信息
+ *
+ * @param orderId 工单主主键
+ * @return 结果
+ */
+ public int deleteSysWorkOrderByOrderId(Long orderId);
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderTypeService.java b/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderTypeService.java
new file mode 100644
index 0000000..a5bea7d
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/service/ISysWorkOrderTypeService.java
@@ -0,0 +1,63 @@
+package com.storm.afterSales.service;
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import java.util.List;
+import com.storm.afterSales.domain.SysWorkOrderType;
+
+/**
+ * 工单类型Service接口
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+public interface ISysWorkOrderTypeService extends IService
+{
+ /**
+ * 查询工单类型
+ *
+ * @param typeId 工单类型主键
+ * @return 工单类型
+ */
+ public SysWorkOrderType selectSysWorkOrderTypeByTypeId(Long typeId);
+
+ /**
+ * 查询工单类型列表
+ *
+ * @param sysWorkOrderType 工单类型
+ * @return 工单类型集合
+ */
+ public List selectSysWorkOrderTypeList(SysWorkOrderType sysWorkOrderType);
+
+ /**
+ * 新增工单类型
+ *
+ * @param sysWorkOrderType 工单类型
+ * @return 结果
+ */
+ public int insertSysWorkOrderType(SysWorkOrderType sysWorkOrderType);
+
+ /**
+ * 修改工单类型
+ *
+ * @param sysWorkOrderType 工单类型
+ * @return 结果
+ */
+ public int updateSysWorkOrderType(SysWorkOrderType sysWorkOrderType);
+
+ /**
+ * 批量删除工单类型
+ *
+ * @param typeIds 需要删除的工单类型主键集合
+ * @return 结果
+ */
+ public int deleteSysWorkOrderTypeByTypeIds(Long[] typeIds);
+
+ /**
+ * 删除工单类型信息
+ *
+ * @param typeId 工单类型主键
+ * @return 结果
+ */
+ public int deleteSysWorkOrderTypeByTypeId(Long typeId);
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysKnowledgeRelServiceImpl.java b/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysKnowledgeRelServiceImpl.java
new file mode 100644
index 0000000..ecbf587
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysKnowledgeRelServiceImpl.java
@@ -0,0 +1,96 @@
+package com.storm.afterSales.service.impl;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.storm.afterSales.mapper.SysKnowledgeRelMapper;
+import com.storm.afterSales.domain.SysKnowledgeRel;
+import com.storm.afterSales.service.ISysKnowledgeRelService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import java.util.Arrays;
+
+/**
+ * 工单知识库关联Service业务层处理
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Service
+public class SysKnowledgeRelServiceImpl extends ServiceImpl implements ISysKnowledgeRelService
+{
+ @Autowired
+ private SysKnowledgeRelMapper sysKnowledgeRelMapper;
+
+ /**
+ * 查询工单知识库关联
+ *
+ * @param id 工单知识库关联主键
+ * @return 工单知识库关联
+ */
+ @Override
+ public SysKnowledgeRel selectSysKnowledgeRelById(Long id)
+ {
+ return getById(id);
+ }
+
+ /**
+ * 查询工单知识库关联列表
+ *
+ * @param sysKnowledgeRel 工单知识库关联
+ * @return 工单知识库关联
+ */
+ @Override
+ public List selectSysKnowledgeRelList(SysKnowledgeRel sysKnowledgeRel)
+ {
+ return sysKnowledgeRelMapper.selectSysKnowledgeRelList(sysKnowledgeRel);
+ }
+
+ /**
+ * 新增工单知识库关联
+ *
+ * @param sysKnowledgeRel 工单知识库关联
+ * @return 结果
+ */
+ @Override
+ public int insertSysKnowledgeRel(SysKnowledgeRel sysKnowledgeRel)
+ {
+ return save(sysKnowledgeRel) ? 1 : 0;
+ }
+
+ /**
+ * 修改工单知识库关联
+ *
+ * @param sysKnowledgeRel 工单知识库关联
+ * @return 结果
+ */
+ @Override
+ public int updateSysKnowledgeRel(SysKnowledgeRel sysKnowledgeRel)
+ {
+ return updateById(sysKnowledgeRel) ? 1 : 0;
+ }
+
+ /**
+ * 批量删除工单知识库关联
+ *
+ * @param ids 需要删除的工单知识库关联主键
+ * @return 结果
+ */
+ @Override
+ public int deleteSysKnowledgeRelByIds(Long[] ids)
+ {
+ return removeByIds(Arrays.asList(ids)) ? 1 : 0;
+ }
+
+ /**
+ * 删除工单知识库关联信息
+ *
+ * @param id 工单知识库关联主键
+ * @return 结果
+ */
+ @Override
+ public int deleteSysKnowledgeRelById(Long id)
+ {
+ return removeById(id) ? 1 : 0;
+ }
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderChatRelServiceImpl.java b/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderChatRelServiceImpl.java
new file mode 100644
index 0000000..1ee9323
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderChatRelServiceImpl.java
@@ -0,0 +1,96 @@
+package com.storm.afterSales.service.impl;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.storm.afterSales.mapper.SysWorkOrderChatRelMapper;
+import com.storm.afterSales.domain.SysWorkOrderChatRel;
+import com.storm.afterSales.service.ISysWorkOrderChatRelService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import java.util.Arrays;
+
+/**
+ * 工单聊天关联Service业务层处理
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Service
+public class SysWorkOrderChatRelServiceImpl extends ServiceImpl implements ISysWorkOrderChatRelService
+{
+ @Autowired
+ private SysWorkOrderChatRelMapper sysWorkOrderChatRelMapper;
+
+ /**
+ * 查询工单聊天关联
+ *
+ * @param id 工单聊天关联主键
+ * @return 工单聊天关联
+ */
+ @Override
+ public SysWorkOrderChatRel selectSysWorkOrderChatRelById(Long id)
+ {
+ return getById(id);
+ }
+
+ /**
+ * 查询工单聊天关联列表
+ *
+ * @param sysWorkOrderChatRel 工单聊天关联
+ * @return 工单聊天关联
+ */
+ @Override
+ public List selectSysWorkOrderChatRelList(SysWorkOrderChatRel sysWorkOrderChatRel)
+ {
+ return sysWorkOrderChatRelMapper.selectSysWorkOrderChatRelList(sysWorkOrderChatRel);
+ }
+
+ /**
+ * 新增工单聊天关联
+ *
+ * @param sysWorkOrderChatRel 工单聊天关联
+ * @return 结果
+ */
+ @Override
+ public int insertSysWorkOrderChatRel(SysWorkOrderChatRel sysWorkOrderChatRel)
+ {
+ return save(sysWorkOrderChatRel) ? 1 : 0;
+ }
+
+ /**
+ * 修改工单聊天关联
+ *
+ * @param sysWorkOrderChatRel 工单聊天关联
+ * @return 结果
+ */
+ @Override
+ public int updateSysWorkOrderChatRel(SysWorkOrderChatRel sysWorkOrderChatRel)
+ {
+ return updateById(sysWorkOrderChatRel) ? 1 : 0;
+ }
+
+ /**
+ * 批量删除工单聊天关联
+ *
+ * @param ids 需要删除的工单聊天关联主键
+ * @return 结果
+ */
+ @Override
+ public int deleteSysWorkOrderChatRelByIds(Long[] ids)
+ {
+ return removeByIds(Arrays.asList(ids)) ? 1 : 0;
+ }
+
+ /**
+ * 删除工单聊天关联信息
+ *
+ * @param id 工单聊天关联主键
+ * @return 结果
+ */
+ @Override
+ public int deleteSysWorkOrderChatRelById(Long id)
+ {
+ return removeById(id) ? 1 : 0;
+ }
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderRecordServiceImpl.java b/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderRecordServiceImpl.java
new file mode 100644
index 0000000..3ea8867
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderRecordServiceImpl.java
@@ -0,0 +1,95 @@
+package com.storm.afterSales.service.impl;
+
+import java.util.List;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.storm.afterSales.mapper.SysWorkOrderRecordMapper;
+import com.storm.afterSales.domain.SysWorkOrderRecord;
+import com.storm.afterSales.service.ISysWorkOrderRecordService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import java.util.Arrays;
+
+/**
+ * 工单处理记录Service业务层处理
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Service
+public class SysWorkOrderRecordServiceImpl extends ServiceImpl implements ISysWorkOrderRecordService
+{
+ @Autowired
+ private SysWorkOrderRecordMapper sysWorkOrderRecordMapper;
+
+ /**
+ * 查询工单处理记录
+ *
+ * @param recordId 工单处理记录主键
+ * @return 工单处理记录
+ */
+ @Override
+ public SysWorkOrderRecord selectSysWorkOrderRecordByRecordId(Long recordId)
+ {
+ return getById(recordId);
+ }
+
+ /**
+ * 查询工单处理记录列表
+ *
+ * @param sysWorkOrderRecord 工单处理记录
+ * @return 工单处理记录
+ */
+ @Override
+ public List selectSysWorkOrderRecordList(SysWorkOrderRecord sysWorkOrderRecord)
+ {
+ return sysWorkOrderRecordMapper.selectSysWorkOrderRecordList(sysWorkOrderRecord);
+ }
+
+ /**
+ * 新增工单处理记录
+ *
+ * @param sysWorkOrderRecord 工单处理记录
+ * @return 结果
+ */
+ @Override
+ public int insertSysWorkOrderRecord(SysWorkOrderRecord sysWorkOrderRecord)
+ {
+ return save(sysWorkOrderRecord) ? 1 : 0;
+ }
+
+ /**
+ * 修改工单处理记录
+ *
+ * @param sysWorkOrderRecord 工单处理记录
+ * @return 结果
+ */
+ @Override
+ public int updateSysWorkOrderRecord(SysWorkOrderRecord sysWorkOrderRecord)
+ {
+ return updateById(sysWorkOrderRecord) ? 1 : 0;
+ }
+
+ /**
+ * 批量删除工单处理记录
+ *
+ * @param recordIds 需要删除的工单处理记录主键
+ * @return 结果
+ */
+ @Override
+ public int deleteSysWorkOrderRecordByRecordIds(Long[] recordIds)
+ {
+ return removeByIds(Arrays.asList(recordIds)) ? 1 : 0;
+ }
+
+ /**
+ * 删除工单处理记录信息
+ *
+ * @param recordId 工单处理记录主键
+ * @return 结果
+ */
+ @Override
+ public int deleteSysWorkOrderRecordByRecordId(Long recordId)
+ {
+ return removeById(recordId) ? 1 : 0;
+ }
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderServiceImpl.java b/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderServiceImpl.java
new file mode 100644
index 0000000..0b4ecca
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderServiceImpl.java
@@ -0,0 +1,96 @@
+package com.storm.afterSales.service.impl;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.storm.afterSales.mapper.SysWorkOrderMapper;
+import com.storm.afterSales.domain.SysWorkOrder;
+import com.storm.afterSales.service.ISysWorkOrderService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import java.util.Arrays;
+
+/**
+ * 工单主Service业务层处理
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Service
+public class SysWorkOrderServiceImpl extends ServiceImpl implements ISysWorkOrderService
+{
+ @Autowired
+ private SysWorkOrderMapper sysWorkOrderMapper;
+
+ /**
+ * 查询工单主
+ *
+ * @param orderId 工单主主键
+ * @return 工单主
+ */
+ @Override
+ public SysWorkOrder selectSysWorkOrderByOrderId(Long orderId)
+ {
+ return getById(orderId);
+ }
+
+ /**
+ * 查询工单主列表
+ *
+ * @param sysWorkOrder 工单主
+ * @return 工单主
+ */
+ @Override
+ public List selectSysWorkOrderList(SysWorkOrder sysWorkOrder)
+ {
+ return sysWorkOrderMapper.selectSysWorkOrderList(sysWorkOrder);
+ }
+
+ /**
+ * 新增工单主
+ *
+ * @param sysWorkOrder 工单主
+ * @return 结果
+ */
+ @Override
+ public int insertSysWorkOrder(SysWorkOrder sysWorkOrder)
+ {
+ return save(sysWorkOrder) ? 1 : 0;
+ }
+
+ /**
+ * 修改工单主
+ *
+ * @param sysWorkOrder 工单主
+ * @return 结果
+ */
+ @Override
+ public int updateSysWorkOrder(SysWorkOrder sysWorkOrder)
+ {
+ return updateById(sysWorkOrder) ? 1 : 0;
+ }
+
+ /**
+ * 批量删除工单主
+ *
+ * @param orderIds 需要删除的工单主主键
+ * @return 结果
+ */
+ @Override
+ public int deleteSysWorkOrderByOrderIds(Long[] orderIds)
+ {
+ return removeByIds(Arrays.asList(orderIds)) ? 1 : 0;
+ }
+
+ /**
+ * 删除工单主信息
+ *
+ * @param orderId 工单主主键
+ * @return 结果
+ */
+ @Override
+ public int deleteSysWorkOrderByOrderId(Long orderId)
+ {
+ return removeById(orderId) ? 1 : 0;
+ }
+}
diff --git a/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderTypeServiceImpl.java b/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderTypeServiceImpl.java
new file mode 100644
index 0000000..b176cc3
--- /dev/null
+++ b/storm-afterSales/src/main/java/com/storm/afterSales/service/impl/SysWorkOrderTypeServiceImpl.java
@@ -0,0 +1,96 @@
+package com.storm.afterSales.service.impl;
+
+import java.util.List;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import com.storm.afterSales.mapper.SysWorkOrderTypeMapper;
+import com.storm.afterSales.domain.SysWorkOrderType;
+import com.storm.afterSales.service.ISysWorkOrderTypeService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import java.util.Arrays;
+
+/**
+ * 工单类型Service业务层处理
+ *
+ * @author storm
+ * @date 2025-10-02
+ */
+@Service
+public class SysWorkOrderTypeServiceImpl extends ServiceImpl implements ISysWorkOrderTypeService
+{
+ @Autowired
+ private SysWorkOrderTypeMapper sysWorkOrderTypeMapper;
+
+ /**
+ * 查询工单类型
+ *
+ * @param typeId 工单类型主键
+ * @return 工单类型
+ */
+ @Override
+ public SysWorkOrderType selectSysWorkOrderTypeByTypeId(Long typeId)
+ {
+ return getById(typeId);
+ }
+
+ /**
+ * 查询工单类型列表
+ *
+ * @param sysWorkOrderType 工单类型
+ * @return 工单类型
+ */
+ @Override
+ public List selectSysWorkOrderTypeList(SysWorkOrderType sysWorkOrderType)
+ {
+ return sysWorkOrderTypeMapper.selectSysWorkOrderTypeList(sysWorkOrderType);
+ }
+
+ /**
+ * 新增工单类型
+ *
+ * @param sysWorkOrderType 工单类型
+ * @return 结果
+ */
+ @Override
+ public int insertSysWorkOrderType(SysWorkOrderType sysWorkOrderType)
+ {
+ return save(sysWorkOrderType) ? 1 : 0;
+ }
+
+ /**
+ * 修改工单类型
+ *
+ * @param sysWorkOrderType 工单类型
+ * @return 结果
+ */
+ @Override
+ public int updateSysWorkOrderType(SysWorkOrderType sysWorkOrderType)
+ {
+ return updateById(sysWorkOrderType) ? 1 : 0;
+ }
+
+ /**
+ * 批量删除工单类型
+ *
+ * @param typeIds 需要删除的工单类型主键
+ * @return 结果
+ */
+ @Override
+ public int deleteSysWorkOrderTypeByTypeIds(Long[] typeIds)
+ {
+ return removeByIds(Arrays.asList(typeIds)) ? 1 : 0;
+ }
+
+ /**
+ * 删除工单类型信息
+ *
+ * @param typeId 工单类型主键
+ * @return 结果
+ */
+ @Override
+ public int deleteSysWorkOrderTypeByTypeId(Long typeId)
+ {
+ return removeById(typeId) ? 1 : 0;
+ }
+}
diff --git a/storm-afterSales/src/main/resources/application.properties b/storm-afterSales/src/main/resources/application.properties
new file mode 100644
index 0000000..c13ff87
--- /dev/null
+++ b/storm-afterSales/src/main/resources/application.properties
@@ -0,0 +1 @@
+spring.application.name=storm-afterSales
diff --git a/storm-afterSales/src/main/resources/mapper/SysKnowledgeRelMapper.xml b/storm-afterSales/src/main/resources/mapper/SysKnowledgeRelMapper.xml
new file mode 100644
index 0000000..6394549
--- /dev/null
+++ b/storm-afterSales/src/main/resources/mapper/SysKnowledgeRelMapper.xml
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select id, order_id, knowledge_id, knowledge_title, used_count, create_time from sys_knowledge_rel
+
+
+
+
+
+
+
+ insert into sys_knowledge_rel
+
+ order_id,
+ knowledge_id,
+ knowledge_title,
+ used_count,
+ create_time,
+
+
+ #{orderId},
+ #{knowledgeId},
+ #{knowledgeTitle},
+ #{usedCount},
+ #{createTime},
+
+
+
+
+ update sys_knowledge_rel
+
+ order_id = #{orderId},
+ knowledge_id = #{knowledgeId},
+ knowledge_title = #{knowledgeTitle},
+ used_count = #{usedCount},
+ create_time = #{createTime},
+
+ where id = #{id}
+
+
+
+ delete from sys_knowledge_rel where id = #{id}
+
+
+
+ delete from sys_knowledge_rel where id in
+
+ #{id}
+
+
+
\ No newline at end of file
diff --git a/storm-afterSales/src/main/resources/mapper/SysWorkOrderChatRelMapper.xml b/storm-afterSales/src/main/resources/mapper/SysWorkOrderChatRelMapper.xml
new file mode 100644
index 0000000..c3c2dae
--- /dev/null
+++ b/storm-afterSales/src/main/resources/mapper/SysWorkOrderChatRelMapper.xml
@@ -0,0 +1,85 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select id, order_id, chat_session_id, chat_platform, message_id, chat_content, chat_duration, create_time from sys_work_order_chat_rel
+
+
+
+
+
+
+
+ insert into sys_work_order_chat_rel
+
+ order_id,
+ chat_session_id,
+ chat_platform,
+ message_id,
+ chat_content,
+ chat_duration,
+ create_time,
+
+
+ #{orderId},
+ #{chatSessionId},
+ #{chatPlatform},
+ #{messageId},
+ #{chatContent},
+ #{chatDuration},
+ #{createTime},
+
+
+
+
+ update sys_work_order_chat_rel
+
+ order_id = #{orderId},
+ chat_session_id = #{chatSessionId},
+ chat_platform = #{chatPlatform},
+ message_id = #{messageId},
+ chat_content = #{chatContent},
+ chat_duration = #{chatDuration},
+ create_time = #{createTime},
+
+ where id = #{id}
+
+
+
+ delete from sys_work_order_chat_rel where id = #{id}
+
+
+
+ delete from sys_work_order_chat_rel where id in
+
+ #{id}
+
+
+
\ No newline at end of file
diff --git a/storm-afterSales/src/main/resources/mapper/SysWorkOrderMapper.xml b/storm-afterSales/src/main/resources/mapper/SysWorkOrderMapper.xml
new file mode 100644
index 0000000..10ec074
--- /dev/null
+++ b/storm-afterSales/src/main/resources/mapper/SysWorkOrderMapper.xml
@@ -0,0 +1,241 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select order_id, order_no, order_title, order_type, order_status, priority, customer_id, customer_name, customer_contact, store_address, device_name, device_serial_no, device_model, production_date, problem_description, attachment_urls, problem_category, assignee_id, assignee_name, assign_time, current_handler_id, current_handler_name, root_cause, solution_plan, final_solution, logistics_number, logistics_progress, customer_feedback, satisfaction_level, feedback_type, plan_finish_time, actual_finish_time, response_deadline, source_channel, feishu_chat_id, create_by, create_time, update_by, update_time, remark from sys_work_order
+
+
+
+
+
+
+
+ insert into sys_work_order
+
+ order_no,
+ order_title,
+ order_type,
+ order_status,
+ priority,
+ customer_id,
+ customer_name,
+ customer_contact,
+ store_address,
+ device_name,
+ device_serial_no,
+ device_model,
+ production_date,
+ problem_description,
+ attachment_urls,
+ problem_category,
+ assignee_id,
+ assignee_name,
+ assign_time,
+ current_handler_id,
+ current_handler_name,
+ root_cause,
+ solution_plan,
+ final_solution,
+ logistics_number,
+ logistics_progress,
+ customer_feedback,
+ satisfaction_level,
+ feedback_type,
+ plan_finish_time,
+ actual_finish_time,
+ response_deadline,
+ source_channel,
+ feishu_chat_id,
+ create_by,
+ create_time,
+ update_by,
+ update_time,
+ remark,
+
+
+ #{orderNo},
+ #{orderTitle},
+ #{orderType},
+ #{orderStatus},
+ #{priority},
+ #{customerId},
+ #{customerName},
+ #{customerContact},
+ #{storeAddress},
+ #{deviceName},
+ #{deviceSerialNo},
+ #{deviceModel},
+ #{productionDate},
+ #{problemDescription},
+ #{attachmentUrls},
+ #{problemCategory},
+ #{assigneeId},
+ #{assigneeName},
+ #{assignTime},
+ #{currentHandlerId},
+ #{currentHandlerName},
+ #{rootCause},
+ #{solutionPlan},
+ #{finalSolution},
+ #{logisticsNumber},
+ #{logisticsProgress},
+ #{customerFeedback},
+ #{satisfactionLevel},
+ #{feedbackType},
+ #{planFinishTime},
+ #{actualFinishTime},
+ #{responseDeadline},
+ #{sourceChannel},
+ #{feishuChatId},
+ #{createBy},
+ #{createTime},
+ #{updateBy},
+ #{updateTime},
+ #{remark},
+
+
+
+
+ update sys_work_order
+
+ order_no = #{orderNo},
+ order_title = #{orderTitle},
+ order_type = #{orderType},
+ order_status = #{orderStatus},
+ priority = #{priority},
+ customer_id = #{customerId},
+ customer_name = #{customerName},
+ customer_contact = #{customerContact},
+ store_address = #{storeAddress},
+ device_name = #{deviceName},
+ device_serial_no = #{deviceSerialNo},
+ device_model = #{deviceModel},
+ production_date = #{productionDate},
+ problem_description = #{problemDescription},
+ attachment_urls = #{attachmentUrls},
+ problem_category = #{problemCategory},
+ assignee_id = #{assigneeId},
+ assignee_name = #{assigneeName},
+ assign_time = #{assignTime},
+ current_handler_id = #{currentHandlerId},
+ current_handler_name = #{currentHandlerName},
+ root_cause = #{rootCause},
+ solution_plan = #{solutionPlan},
+ final_solution = #{finalSolution},
+ logistics_number = #{logisticsNumber},
+ logistics_progress = #{logisticsProgress},
+ customer_feedback = #{customerFeedback},
+ satisfaction_level = #{satisfactionLevel},
+ feedback_type = #{feedbackType},
+ plan_finish_time = #{planFinishTime},
+ actual_finish_time = #{actualFinishTime},
+ response_deadline = #{responseDeadline},
+ source_channel = #{sourceChannel},
+ feishu_chat_id = #{feishuChatId},
+ create_by = #{createBy},
+ create_time = #{createTime},
+ update_by = #{updateBy},
+ update_time = #{updateTime},
+ remark = #{remark},
+
+ where order_id = #{orderId}
+
+
+
+ delete from sys_work_order where order_id = #{orderId}
+
+
+
+ delete from sys_work_order where order_id in
+
+ #{orderId}
+
+
+
\ No newline at end of file
diff --git a/storm-afterSales/src/main/resources/mapper/SysWorkOrderRecordMapper.xml b/storm-afterSales/src/main/resources/mapper/SysWorkOrderRecordMapper.xml
new file mode 100644
index 0000000..36bb8d0
--- /dev/null
+++ b/storm-afterSales/src/main/resources/mapper/SysWorkOrderRecordMapper.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select record_id, order_id, process_type, process_content, progress_type, progress_value, progress_percent, process_user_id, process_user_name, process_time, attachment_url from sys_work_order_record
+
+
+
+
+
+
+
+ insert into sys_work_order_record
+
+ order_id,
+ process_type,
+ process_content,
+ progress_type,
+ progress_value,
+ progress_percent,
+ process_user_id,
+ process_user_name,
+ process_time,
+ attachment_url,
+
+
+ #{orderId},
+ #{processType},
+ #{processContent},
+ #{progressType},
+ #{progressValue},
+ #{progressPercent},
+ #{processUserId},
+ #{processUserName},
+ #{processTime},
+ #{attachmentUrl},
+
+
+
+
+ update sys_work_order_record
+
+ order_id = #{orderId},
+ process_type = #{processType},
+ process_content = #{processContent},
+ progress_type = #{progressType},
+ progress_value = #{progressValue},
+ progress_percent = #{progressPercent},
+ process_user_id = #{processUserId},
+ process_user_name = #{processUserName},
+ process_time = #{processTime},
+ attachment_url = #{attachmentUrl},
+
+ where record_id = #{recordId}
+
+
+
+ delete from sys_work_order_record where record_id = #{recordId}
+
+
+
+ delete from sys_work_order_record where record_id in
+
+ #{recordId}
+
+
+
\ No newline at end of file
diff --git a/storm-afterSales/src/main/resources/mapper/SysWorkOrderTypeMapper.xml b/storm-afterSales/src/main/resources/mapper/SysWorkOrderTypeMapper.xml
new file mode 100644
index 0000000..e47c00a
--- /dev/null
+++ b/storm-afterSales/src/main/resources/mapper/SysWorkOrderTypeMapper.xml
@@ -0,0 +1,91 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ select type_id, type_name, type_key, STATUS, order_num, create_by, create_time, update_by, update_time, remark from sys_work_order_type
+
+
+
+
+
+
+
+ insert into sys_work_order_type
+
+ type_name,
+ type_key,
+ STATUS,
+ order_num,
+ create_by,
+ create_time,
+ update_by,
+ update_time,
+ remark,
+
+
+ #{typeName},
+ #{typeKey},
+ #{STATUS},
+ #{orderNum},
+ #{createBy},
+ #{createTime},
+ #{updateBy},
+ #{updateTime},
+ #{remark},
+
+
+
+
+ update sys_work_order_type
+
+ type_name = #{typeName},
+ type_key = #{typeKey},
+ STATUS = #{STATUS},
+ order_num = #{orderNum},
+ create_by = #{createBy},
+ create_time = #{createTime},
+ update_by = #{updateBy},
+ update_time = #{updateTime},
+ remark = #{remark},
+
+ where type_id = #{typeId}
+
+
+
+ delete from sys_work_order_type where type_id = #{typeId}
+
+
+
+ delete from sys_work_order_type where type_id in
+
+ #{typeId}
+
+
+
\ No newline at end of file
diff --git a/storm-common/storm-common-oss/pom.xml b/storm-common/storm-common-oss/pom.xml
index 0161089..70ad6da 100644
--- a/storm-common/storm-common-oss/pom.xml
+++ b/storm-common/storm-common-oss/pom.xml
@@ -13,8 +13,8 @@
storm-common-oss
- 11
- 11
+ 17
+ 17
UTF-8
diff --git a/storm-device/pom.xml b/storm-device/pom.xml
index c0300f8..216c485 100644
--- a/storm-device/pom.xml
+++ b/storm-device/pom.xml
@@ -12,8 +12,8 @@
storm-device
- 11
- 11
+ 17
+ 17
UTF-8
@@ -104,12 +104,6 @@
huaweicloud-sdk-iotda
-
- org.springframework.boot
- spring-boot-starter-test
- test
-
-
org.apache.qpid
qpid-jms-client
diff --git a/storm-device/src/main/java/com/storm/device/domain/po/DeviceHeadUsage.java b/storm-device/src/main/java/com/storm/device/domain/po/DeviceHeadUsage.java
index d3ef285..f3b60d6 100644
--- a/storm-device/src/main/java/com/storm/device/domain/po/DeviceHeadUsage.java
+++ b/storm-device/src/main/java/com/storm/device/domain/po/DeviceHeadUsage.java
@@ -1,5 +1,6 @@
package com.storm.device.domain.po;
+import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.storm.common.core.annotation.Excel;
import com.storm.common.core.web.domain.BaseEntity;
@@ -70,4 +71,12 @@ public class DeviceHeadUsage extends BaseEntity
@Excel(name = "最后使用时间")
@Schema(description = "最后使用时间")
private Date lastUsedTime;
+
+ private String description;
+ private String remark;
+
+ @TableLogic
+ private Integer isDeleted;
+ private String createBy;
+ private String updateBy;
}
\ No newline at end of file
diff --git a/storm-device/src/main/java/com/storm/device/domain/po/DeviceRuntimeStats.java b/storm-device/src/main/java/com/storm/device/domain/po/DeviceRuntimeStats.java
index 79aa411..8187cb4 100644
--- a/storm-device/src/main/java/com/storm/device/domain/po/DeviceRuntimeStats.java
+++ b/storm-device/src/main/java/com/storm/device/domain/po/DeviceRuntimeStats.java
@@ -68,4 +68,12 @@ public class DeviceRuntimeStats extends BaseEntity
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
+
+ private String description;
+ private String remark;
+
+ @TableLogic
+ private Integer isDeleted;
+ private String createBy;
+ private String updateBy;
}
\ No newline at end of file
diff --git a/storm-device/src/main/java/com/storm/device/domain/po/DeviceStatusLog.java b/storm-device/src/main/java/com/storm/device/domain/po/DeviceStatusLog.java
index 7ded343..d3cc98b 100644
--- a/storm-device/src/main/java/com/storm/device/domain/po/DeviceStatusLog.java
+++ b/storm-device/src/main/java/com/storm/device/domain/po/DeviceStatusLog.java
@@ -55,6 +55,9 @@ public class DeviceStatusLog {
@TableField(exist = false)
private Date endTimeDate;
+ private String description;
+ private String remark;
+
public Date getStartTimeDate() {
return massageStartTime != null ? new Date(massageStartTime) : null;
}
diff --git a/storm-device/src/main/java/com/storm/device/domain/po/MassageTask.java b/storm-device/src/main/java/com/storm/device/domain/po/MassageTask.java
index c288cb7..0641598 100644
--- a/storm-device/src/main/java/com/storm/device/domain/po/MassageTask.java
+++ b/storm-device/src/main/java/com/storm/device/domain/po/MassageTask.java
@@ -41,6 +41,13 @@ public class MassageTask {
@TableField(exist = false)
private Date endTimeDate;
+ private String description;
+ private String remark;
+
+ @TableLogic
+ private Integer isDeleted;
+ private String updateBy;
+
public Date getStartTimeDate() {
return startTime != null ? new Date(startTime) : null;
}
diff --git a/storm-device/src/main/java/com/storm/device/processor/DeviceDataCoordinator.java b/storm-device/src/main/java/com/storm/device/processor/DeviceDataCoordinator.java
index 68c79e2..2aef64c 100644
--- a/storm-device/src/main/java/com/storm/device/processor/DeviceDataCoordinator.java
+++ b/storm-device/src/main/java/com/storm/device/processor/DeviceDataCoordinator.java
@@ -13,11 +13,11 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
-import org.springframework.transaction.annotation.Propagation;
-import org.springframework.transaction.annotation.Transactional;
+import java.time.Duration;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
+import java.util.function.Consumer;
@Slf4j
@Component
@@ -41,15 +41,16 @@ public class DeviceDataCoordinator {
@Autowired
private IDeviceStatusLogService deviceStatusLogService;
+ private static final String DEVICE_EVENT_PREFIX = "device:event:id:";
+ private static final long DEVICE_EVENT_EXPIRE_HOURS = 24;
private static final String SYNC_LOCK_PREFIX = "device:sync:lock:";
@Autowired
private DeviceRuntimeStatsService deviceRuntimeStatsService;
/**
- * 统一处理设备数据 - 调用协调器确保所有表同步
+ * 统一处理设备数据 - 增强版本,确保所有表字段都能正确更新
*/
- @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void processDeviceData(IotMsgNotifyDataPro iotMsgNotifyData) {
if (iotMsgNotifyData == null || iotMsgNotifyData.getHeader() == null) {
log.warn("接收到的设备信息数据为空");
@@ -62,374 +63,142 @@ public class DeviceDataCoordinator {
return;
}
+ // 生成事件唯一标识(使用设备ID+事件时间)
+ String eventUniqueId = generateEventUniqueId(iotMsgNotifyData);
+ if (StrUtil.isNotBlank(eventUniqueId)) {
+ String idempotentKey = DEVICE_EVENT_PREFIX + eventUniqueId;
+ Boolean isNewEvent = redisTemplate.opsForValue().setIfAbsent(
+ idempotentKey, "1", Duration.ofHours(DEVICE_EVENT_EXPIRE_HOURS));
+
+ if (isNewEvent != null && !isNewEvent) {
+ log.debug("设备事件已处理,跳过重复事件: {}", eventUniqueId);
+ return; // 已处理过,直接返回
+ }
+ }
+
try {
// 使用协调器统一处理所有表数据
coordinateDeviceData(iotMsgNotifyData);
-
log.info("设备数据处理完成: {}", deviceId);
} catch (Exception e) {
log.error("处理设备数据时发生异常,设备ID: {}", deviceId, e);
- throw new RuntimeException("设备数据处理失败", e);
- }
- }
- /**
- * 确保设备存在
- */
- private Device ensureDeviceExists(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData) {
- return deviceManagerService.getOrCreateDevice(deviceId, (device) -> {
- // 初始化设备信息
- if (iotMsgNotifyData.getHeader() != null) {
- device.setProductId(iotMsgNotifyData.getHeader().getProductId());
- }
- if (iotMsgNotifyData.getBody() != null) {
- device.setStatus(iotMsgNotifyData.getBody().getStatus());
- }
- device.setCreateTime(new Date());
- device.setCreateBy("系统自动创建");
- });
- }
-
- /**
- * 处理按摩任务数据
- */
- private void processMassageTaskData(Device device, IotMsgNotifyDataPro iotMsgNotifyData) {
- if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
- return;
- }
-
- for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
- if ("StatusChange".equals(service.getServiceId()) && service.getProperties() != null) {
- Map props = service.getProperties();
-
- if (!isValidMassageTaskData(device.getDeviceId(), props)) {
- continue;
- }
-
- // 检查是否重复
- if (isMassageTaskExists(device.getDeviceId(), props)) {
- log.debug("按摩任务已存在,跳过: {}", device.getDeviceId());
- continue;
- }
-
- MassageTask task = createMassageTask(device, props);
- if (task != null) {
- massageTaskMapper.insert(task);
- log.info("按摩任务记录成功: 设备{},时长{}秒", device.getDeviceId(), task.getTaskTime());
- }
- break;
+ // 处理失败时,删除幂等键,允许重试
+ if (StrUtil.isNotBlank(eventUniqueId)) {
+ redisTemplate.delete(DEVICE_EVENT_PREFIX + eventUniqueId);
}
}
}
/**
- * 更新按头使用统计
+ * 生成事件唯一标识
*/
- private void updateHeadUsageStatistics(Device device, IotMsgNotifyDataPro iotMsgNotifyData) {
- try {
- if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
- return;
- }
+ private String generateEventUniqueId(IotMsgNotifyDataPro iotMsgNotifyData) {
+ if (iotMsgNotifyData.getHeader() == null) {
+ return null;
+ }
+ String deviceId = iotMsgNotifyData.getHeader().getDeviceId();
+ String eventTime = null;
+
+ // 尝试从body中获取事件时间
+ if (iotMsgNotifyData.getBody() != null && iotMsgNotifyData.getBody().getStatusUpdateTime() != null) {
+ eventTime = String.valueOf(iotMsgNotifyData.getBody().getStatusUpdateTime().getTime());
+ }
+
+ // 尝试从services中获取事件时间
+ if (StrUtil.isBlank(eventTime) && iotMsgNotifyData.getBody() != null &&
+ iotMsgNotifyData.getBody().getServices() != null) {
for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
- if ("StatusChange".equals(service.getServiceId()) && service.getProperties() != null) {
- Map props = service.getProperties();
- String headType = safeGetString(props, "head_type");
-
- if (StrUtil.isBlank(headType) || !isValidMassageTaskData(device.getDeviceId(), props)) {
- continue;
- }
-
- Long duration = calculateMassageDuration(iotMsgNotifyData);
- if (duration <= 0) {
- continue;
- }
-
- DeviceHeadUsage usage = deviceHeadUsageMapper.selectOne(
- new LambdaQueryWrapper()
- .eq(DeviceHeadUsage::getDeviceId, device.getDeviceId())
- .eq(DeviceHeadUsage::getHeadType, headType));
-
- if (usage == null) {
- usage = new DeviceHeadUsage();
- usage.setDeviceId(device.getDeviceId());
- usage.setHeadType(headType);
- usage.setUsageCount(0);
- usage.setTotalDuration(0L);
- usage.setDailyDuration(0L);
- usage.setWeeklyDuration(0L);
- usage.setMonthlyDuration(0L);
- usage.setCreateBy("系统自动创建");
- usage.setCreateTime(new Date());
- }
-
- usage.setUsageCount(usage.getUsageCount() + 1);
- usage.setTotalDuration(usage.getTotalDuration() + duration);
- usage.setDailyDuration(usage.getDailyDuration() + duration);
- usage.setWeeklyDuration(usage.getWeeklyDuration() + duration);
- usage.setMonthlyDuration(usage.getMonthlyDuration() + duration);
- usage.setLastUsedTime(new Date());
- usage.setUpdateTime(new Date());
- usage.setUpdateBy("系统自动修改");
- if (usage.getId() == null) {
- deviceHeadUsageMapper.insert(usage);
- } else {
- deviceHeadUsageMapper.updateById(usage);
- }
-
+ if (service.getEventTime() != null) {
+ eventTime = String.valueOf(service.getEventTime().getTime());
break;
}
}
- } catch (Exception e) {
- log.error("更新按头使用统计失败,设备: {}", device.getDeviceId(), e);
- }
- }
-
- // ========== 辅助方法 ==========
-
- private boolean acquireLock(String lockKey) {
- try {
- Boolean acquired = redisTemplate.opsForValue().setIfAbsent(
- lockKey, "1", java.time.Duration.ofSeconds(10));
- return acquired != null && acquired;
- } catch (Exception e) {
- log.warn("获取锁失败: {}", lockKey, e);
- return false;
- }
- }
-
- private void releaseLock(String lockKey) {
- try {
- redisTemplate.delete(lockKey);
- } catch (Exception e) {
- log.warn("释放锁失败: {}", lockKey, e);
- }
- }
-
- private boolean isValidMassageTaskData(String deviceId, Map props) {
- return props.containsKey("massage_start_time") &&
- props.containsKey("massage_end_time") &&
- props.containsKey("head_type") &&
- props.containsKey("body_part");
- }
-
- private boolean isMassageTaskExists(String deviceId, Map props) {
- Long startTime = safeParseTimestamp(props.get("massage_start_time"));
- String headType = safeGetString(props, "head_type");
-
- if (startTime == null || StrUtil.isBlank(headType)) {
- return false;
}
- LambdaQueryWrapper query = new LambdaQueryWrapper<>();
- query.eq(MassageTask::getDeviceId, deviceId)
- .eq(MassageTask::getStartTime, startTime)
- .eq(MassageTask::getHeadType, headType);
-
- return massageTaskMapper.selectCount(query) > 0;
- }
-
- private MassageTask createMassageTask(Device device, Map props) {
- try {
- MassageTask task = new MassageTask();
- task.setDeviceId(device.getDeviceId());
- task.setDeviceName(device.getDeviceName());
- task.setProductName(device.getProductName());
- task.setHeadType(safeGetString(props, "head_type"));
- task.setBodyPart(safeGetString(props, "body_part"));
- task.setMassagePlan(safeGetString(props, "massage_plan"));
- task.setCreateBy("系统自动创建");
- Long startTime = safeParseTimestamp(props.get("massage_start_time"));
- Long endTime = safeParseTimestamp(props.get("massage_end_time"));
- task.setCreateTimestamp(new Date().getTime());
- if (startTime != null && endTime != null && startTime < endTime && startTime > 0) {
- task.setStartTime(startTime);
- task.setEndTime(endTime);
- task.setTaskTime((endTime - startTime) / 1000);
- task.setCreateTime(new Date());
- return task;
- }
- } catch (Exception e) {
- log.error("创建按摩任务失败", e);
- }
- return null;
- }
-
- private Long calculateMassageDuration(IotMsgNotifyDataPro iotMsgNotifyData) {
- if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
- return 0L;
+ // 如果仍然没有事件时间,使用当前时间
+ if (StrUtil.isBlank(eventTime)) {
+ eventTime = String.valueOf(System.currentTimeMillis());
}
- for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
- if ("StatusChange".equals(service.getServiceId()) && service.getProperties() != null) {
- Map props = service.getProperties();
- Long startTime = safeParseTimestamp(props.get("massage_start_time"));
- Long endTime = safeParseTimestamp(props.get("massage_end_time"));
-
- if (startTime != null && endTime != null && startTime < endTime && startTime > 0) {
- return (endTime - startTime) / 1000; // 转换为秒
- }
- break;
- }
- }
- return 0L;
- }
-
- private boolean shouldCountOnline(IotMsgNotifyDataPro iotMsgNotifyData) {
- return iotMsgNotifyData.getBody() != null &&
- "ONLINE".equalsIgnoreCase(iotMsgNotifyData.getBody().getStatus());
- }
-
- private String safeGetString(Map props, String key) {
- if (props == null || !props.containsKey(key)) return null;
- Object value = props.get(key);
- return value != null ? value.toString() : null;
- }
-
- private Long safeParseTimestamp(Object timestampObj) {
- if (timestampObj == null) return null;
- try {
- if (timestampObj instanceof Number) {
- return ((Number) timestampObj).longValue();
- } else if (timestampObj instanceof String) {
- return Long.parseLong(timestampObj.toString());
- }
- } catch (Exception e) {
- log.warn("时间戳解析失败: {}", timestampObj);
- }
- return null;
+ return deviceId + ":" + eventTime;
}
/**
- * 处理设备状态变更 - 最终修复版本
+ * 协调设备数据 - 增强版本,确保所有表字段都能正确更新
*/
- private void processDeviceStatusChange(Device device, IotMsgNotifyDataPro iotMsgNotifyData) {
- if (iotMsgNotifyData.getBody() == null) {
+ public void coordinateDeviceData(IotMsgNotifyDataPro iotMsgNotifyData) {
+ if (iotMsgNotifyData == null || iotMsgNotifyData.getHeader() == null) {
return;
}
- // 更新设备状态
- String newStatus = iotMsgNotifyData.getBody().getStatus();
- if (newStatus != null) {
- String normalizedStatus = "ONLINE".equalsIgnoreCase(newStatus) ? "ONLINE" : "OFFLINE";
- device.setStatus(normalizedStatus);
-
- Date now = new Date();
- if ("ONLINE".equals(normalizedStatus)) {
- device.setLastOnlineTime(now);
- } else {
- device.setLastOfflineTime(now);
- }
-
- device.setUpdateTime(now);
- deviceMapper.updateById(device);
-
- log.debug("设备状态更新: {} -> {}", device.getDeviceId(), normalizedStatus);
- }
-
- // 使用专门的服务处理状态日志插入
- boolean success = deviceStatusLogService.safeInsertStatusLog(device.getDeviceId(), iotMsgNotifyData);
-
- if (success) {
- log.debug("状态日志处理成功: {}", device.getDeviceId());
- } else {
- log.warn("状态日志处理失败: {}", device.getDeviceId());
- }
- }
-
- /**
- * 更新设备位置信息 - 新增方法
- */
- private void updateDeviceLocation(Device device, IotMsgNotifyDataPro iotMsgNotifyData) {
- if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
+ String deviceId = iotMsgNotifyData.getHeader().getDeviceId();
+ if (StringUtils.isBlank(deviceId)) {
return;
}
- boolean locationUpdated = false;
+ String normalizedDeviceId = deviceManagerService.normalizeDeviceId(deviceId);
+ String lockKey = SYNC_LOCK_PREFIX + normalizedDeviceId;
- for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
- if (service.getProperties() == null) continue;
-
- Map props = service.getProperties();
-
- // 检查是否有新的位置信息
- boolean hasNewLongitude = props.containsKey("longitude") && props.get("longitude") != null;
- boolean hasNewLatitude = props.containsKey("latitude") && props.get("latitude") != null;
-
- if (hasNewLongitude || hasNewLatitude) {
- String newLongitude = hasNewLongitude ? props.get("longitude").toString() : null;
- String newLatitude = hasNewLatitude ? props.get("latitude").toString() : null;
-
- // 检查位置是否发生变化
- boolean longitudeChanged = !Objects.equals(device.getLongitude(), newLongitude);
- boolean latitudeChanged = !Objects.equals(device.getLatitude(), newLatitude);
-
- if (longitudeChanged || latitudeChanged) {
- if (hasNewLongitude) {
- device.setLongitude(newLongitude);
- log.debug("更新设备经度: {} -> {}", device.getDeviceId(), newLongitude);
- }
- if (hasNewLatitude) {
- device.setLatitude(newLatitude);
- log.debug("更新设备纬度: {} -> {}", device.getDeviceId(), newLatitude);
- }
-
- locationUpdated = true;
-
- // 如果有位置信息,尝试获取详细地址
- if (hasNewLongitude && hasNewLatitude) {
- try {
-// TODO 基于经纬度获取省市位置
- } catch (Exception e) {
- log.warn("获取设备位置地址失败: {}", device.getDeviceId(), e);
- }
- }
- }
- }
-
- // 同时处理版本信息更新
- if (props.containsKey("software_version") && props.get("software_version") != null) {
- String newSoftwareVersion = props.get("software_version").toString();
- if (!Objects.equals(device.getSoftwareVersion(), newSoftwareVersion)) {
- device.setSoftwareVersion(newSoftwareVersion);
- log.debug("更新软件版本: {} -> {}", device.getDeviceId(), newSoftwareVersion);
- locationUpdated = true;
- }
- }
-
- if (props.containsKey("vtxdb_version") && props.get("vtxdb_version") != null) {
- String newVtxdbVersion = props.get("vtxdb_version").toString();
- if (!Objects.equals(device.getVtxdbVersion(), newVtxdbVersion)) {
- device.setVtxdbVersion(newVtxdbVersion);
- log.debug("更新参数服务器版本: {} -> {}", device.getDeviceId(), newVtxdbVersion);
- locationUpdated = true;
- }
- }
- }
-
- if (locationUpdated) {
- device.setUpdateTime(new Date());
- deviceMapper.updateById(device);
- log.info("设备位置和版本信息更新完成: {}", device.getDeviceId());
- }
- }
-
- /**
- * 更新运行统计 - 最终修复版本
- */
- private void updateRuntimeStatistics(Device device, IotMsgNotifyDataPro iotMsgNotifyData) {
+ boolean locked = false;
try {
- // 使用专门的服务处理运行统计更新
- boolean success = deviceRuntimeStatsService.safeUpdateRuntimeStats(device.getDeviceId(), iotMsgNotifyData);
-
- if (success) {
- log.debug("运行统计更新成功: {}", device.getDeviceId());
- } else {
- log.warn("运行统计更新失败: {}", device.getDeviceId());
+ // 获取分布式锁
+ locked = acquireLock(lockKey);
+ if (!locked) {
+ log.warn("获取设备数据同步锁失败,设备: {}", normalizedDeviceId);
+ return;
}
+ // 1. 确保设备基础信息存在并更新所有字段
+ Device device = ensureDeviceExists(normalizedDeviceId, iotMsgNotifyData);
+ if (device == null) {
+ log.error("设备基础信息创建失败: {}", normalizedDeviceId);
+ return;
+ }
+
+ // 2. 处理设备核心信息更新 (必需):状态、位置、版本、元数据、许可证信息等
+ updateDeviceCoreInfo(device, iotMsgNotifyData);
+
+ // 3. 处理设备状态变更日志 (必需)
+ deviceStatusLogService.safeInsertStatusLog(device.getDeviceId(), iotMsgNotifyData);
+
+ // 4. 处理按摩任务数据
+ try {
+ processMassageTaskData(device, iotMsgNotifyData);
+ } catch (Exception e) {
+ log.error("处理按摩任务数据失败,设备: {}", normalizedDeviceId, e);
+ }
+
+ // 5. 更新运行统计
+ try {
+ deviceRuntimeStatsService.safeUpdateRuntimeStats(device.getDeviceId(), iotMsgNotifyData);
+ } catch (Exception e) {
+ log.error("更新运行统计失败,设备: {}", normalizedDeviceId, e);
+ }
+
+ // 6. 更新按头使用统计
+ try {
+ updateHeadUsageStatistics(device, iotMsgNotifyData);
+ } catch (Exception e) {
+ log.error("更新按头使用统计失败,设备: {}", normalizedDeviceId, e);
+ }
+
+ // 7. 更新最后按摩时间
+ try {
+ updateDeviceLastMassageTime(device, iotMsgNotifyData);
+ } catch (Exception e) {
+ log.error("更新最后按摩时间失败,设备: {}", normalizedDeviceId, e);
+ }
+
+ log.info("设备数据同步完成: {}", normalizedDeviceId);
+
} catch (Exception e) {
- log.error("更新运行统计失败,设备: {}", device.getDeviceId(), e);
+ log.error("设备数据协调处理异常,设备: {}", normalizedDeviceId, e);
+ } finally {
+ if (locked) {
+ releaseLock(lockKey);
+ }
}
}
@@ -456,6 +225,8 @@ public class DeviceDataCoordinator {
if (device.getLastMassageTime() == null ||
lastMassageTime.after(device.getLastMassageTime())) {
device.setLastMassageTime(lastMassageTime);
+ device.setUpdateTime(new Date());
+ deviceMapper.updateById(device);
log.debug("更新设备最后按摩时间: {} -> {}", device.getDeviceId(), lastMassageTime);
}
}
@@ -468,109 +239,498 @@ public class DeviceDataCoordinator {
}
}
- /**
- * 统一处理设备数据 - 增强异常处理
- */
- @Transactional(rollbackFor = Exception.class)
- public void coordinateDeviceData(IotMsgNotifyDataPro iotMsgNotifyData) {
- if (iotMsgNotifyData == null || iotMsgNotifyData.getHeader() == null) {
- return;
- }
+ // ========== 辅助方法 ==========
- String deviceId = iotMsgNotifyData.getHeader().getDeviceId();
- if (StringUtils.isBlank(deviceId)) {
- return;
- }
-
- String normalizedDeviceId = deviceManagerService.normalizeDeviceId(deviceId);
- String lockKey = SYNC_LOCK_PREFIX + normalizedDeviceId;
-
- boolean locked = false;
+ private boolean acquireLock(String lockKey) {
try {
- // 获取分布式锁
- locked = acquireLock(lockKey);
- if (!locked) {
- log.warn("获取设备数据同步锁失败,设备: {}", normalizedDeviceId);
- return;
- }
-
- // 1. 确保设备基础信息存在
- Device device = ensureDeviceExists(normalizedDeviceId, iotMsgNotifyData);
- if (device == null) {
- log.error("设备基础信息创建失败: {}", normalizedDeviceId);
- return;
- }
-
- // 2. 处理设备位置信息更新
- try {
- updateDeviceLocation(device, iotMsgNotifyData);
- } catch (Exception e) {
- log.error("处理设备位置信息更新失败,设备: {}", normalizedDeviceId, e);
- }
-
- // 3. 处理设备状态变更
- try {
- processDeviceStatusChange(device, iotMsgNotifyData);
- } catch (Exception e) {
- log.error("处理设备状态变更失败,继续处理其他数据,设备: {}", normalizedDeviceId, e);
- }
-
- // 4. 处理按摩任务数据
- try {
- processMassageTaskData(device, iotMsgNotifyData);
- } catch (Exception e) {
- log.error("处理按摩任务数据失败,设备: {}", normalizedDeviceId, e);
- }
-
- // 5. 更新最后按摩时间(新增)
- try {
- updateDeviceLastMassageTime(device, iotMsgNotifyData);
- } catch (Exception e) {
- log.error("更新最后按摩时间失败,设备: {}", normalizedDeviceId, e);
- }
-
- // 6. 更新运行统计
- try {
- updateRuntimeStatistics(device, iotMsgNotifyData);
- } catch (Exception e) {
- log.error("更新运行统计失败,设备: {}", normalizedDeviceId, e);
- }
-
- // 7. 更新按头使用统计
- try {
- updateHeadUsageStatistics(device, iotMsgNotifyData);
- } catch (Exception e) {
- log.error("更新按头使用统计失败,设备: {}", normalizedDeviceId, e);
- }
-
- // 8. 更新设备总统计(移除重复的时长累加)
- try {
- updateDeviceBasicInfo(device); // 只更新基本信息,不重复累加时长
- } catch (Exception e) {
- log.error("更新设备基本信息失败,设备: {}", normalizedDeviceId, e);
- }
-
- log.info("设备数据同步完成: {}", normalizedDeviceId);
-
+ Boolean acquired = redisTemplate.opsForValue().setIfAbsent(
+ lockKey,
+ "1",
+ java.time.Duration.ofSeconds(10));
+ return acquired != null && acquired;
} catch (Exception e) {
- log.error("设备数据协调处理异常,设备: {}", normalizedDeviceId, e);
- } finally {
- if (locked) {
- releaseLock(lockKey);
- }
+ log.warn("获取锁失败: {}", lockKey, e);
+ return false;
}
}
- /**
- * 只更新设备基本信息,不重复累加时长
- */
- private void updateDeviceBasicInfo(Device device) {
+ private void releaseLock(String lockKey) {
try {
- device.setUpdateTime(new Date());
+ redisTemplate.delete(lockKey);
+ } catch (Exception e) {
+ log.warn("释放锁失败: {}", lockKey, e);
+ }
+ }
+
+ private Device ensureDeviceExists(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData) {
+ return deviceManagerService.getOrCreateDevice(deviceId, (device) -> {
+ // 初始化设备信息
+ if (iotMsgNotifyData.getHeader() != null) {
+ device.setProductId(iotMsgNotifyData.getHeader().getProductId());
+ }
+ if (iotMsgNotifyData.getBody() != null) {
+ device.setStatus(iotMsgNotifyData.getBody().getStatus());
+ }
+ device.setCreateTime(new Date());
+ device.setCreateBy("系统自动创建");
+ });
+ }
+
+ private Long calculateMassageDuration(IotMsgNotifyDataPro iotMsgNotifyData) {
+ if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
+ return 0L;
+ }
+
+ for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
+ if ("StatusChange".equals(service.getServiceId()) && service.getProperties() != null) {
+ Map props = service.getProperties();
+ Long startTime = safeParseTimestamp(props.get("massage_start_time"));
+ Long endTime = safeParseTimestamp(props.get("massage_end_time"));
+
+ if (startTime != null && endTime != null && startTime < endTime && startTime > 0) {
+ return (endTime - startTime) / 1000; // 转换为秒
+ }
+ break;
+ }
+ }
+ return 0L;
+ }
+
+ // 在 DeviceDataCoordinator.java 中,修改 updateDeviceCoreInfo 方法
+ private void updateDeviceCoreInfo(Device device, IotMsgNotifyDataPro iotMsgNotifyData) {
+ boolean updated = false;
+ Date now = new Date();
+
+ // 1. 处理设备状态 (原有逻辑)
+ if (iotMsgNotifyData.getBody() != null && iotMsgNotifyData.getBody().getStatus() != null) {
+ String newStatus = iotMsgNotifyData.getBody().getStatus();
+ String normalizedStatus = "ONLINE".equalsIgnoreCase(newStatus) ? "ONLINE" : "OFFLINE";
+ if (!Objects.equals(device.getStatus(), normalizedStatus)) {
+ device.setStatus(normalizedStatus);
+ if ("ONLINE".equals(normalizedStatus)) {
+ device.setLastOnlineTime(now);
+ } else {
+ device.setLastOfflineTime(now);
+ }
+ updated = true;
+ }
+ }
+
+ // 2. 增强:处理物模型属性(Services中的Properties)
+ if (iotMsgNotifyData.getBody() != null && iotMsgNotifyData.getBody().getServices() != null) {
+ for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
+ if (service.getProperties() != null) {
+ Map props = service.getProperties();
+
+ // 2.1 处理位置信息
+ updated |= updateFieldIfPresent(props, "longitude", device::setLongitude);
+ updated |= updateFieldIfPresent(props, "latitude", device::setLatitude);
+ updated |= updateFieldIfPresent(props, "location", device::setLocation);
+ updated |= updateFieldIfPresent(props, "shop_id", device::setShopId);
+
+ // 2.2 处理版本信息
+ updated |= updateFieldIfPresent(props, "software_version", device::setSoftwareVersion);
+ updated |= updateFieldIfPresent(props, "vtxdb_version", device::setVtxdbVersion);
+
+ // 2.3 处理许可证信息
+ updated |= updateFieldIfPresent(props, "activation_code", device::setActivationCode);
+ updated |= updateFieldIfPresent(props, "serial_number", device::setSerialNumber);
+ updated |= updateFieldIfPresent(props, "license_status", device::setLicenseStatus);
+ // 处理时间戳字段 license_expire_time
+ if (props.containsKey("license_expire_time")) {
+ Long expiryTimestamp = safeParseTimestamp(props.get("license_expire_time"));
+ if (expiryTimestamp != null && expiryTimestamp > 0) {
+ Date newExpiryDate = new Date(expiryTimestamp);
+ if (!Objects.equals(device.getLicenseExpireTime(), newExpiryDate)) {
+ device.setLicenseExpireTime(newExpiryDate);
+ updated = true;
+ }
+ }
+ }
+
+ // 2.4 处理设备类型和产品信息
+ updated |= updateFieldIfPresent(props, "device_type", value -> {
+ try {
+ device.setDeviceType(Integer.parseInt(value.toString()));
+ } catch (NumberFormatException e) {
+ log.warn("设备类型格式错误: {}", value);
+ }
+ });
+ updated |= updateFieldIfPresent(props, "product_id", device::setProductId);
+ updated |= updateFieldIfPresent(props, "product_name", device::setProductName);
+
+ // 找到第一个包含数据的服务即可,通常一个消息只报告一个服务的数据
+ break;
+ }
+ }
+ }
+
+ if (updated) {
+ device.setUpdateTime(now);
device.setUpdateBy("系统自动更新");
deviceMapper.updateById(device);
- } catch (Exception e) {
- log.error("更新设备基本信息失败: {}", device.getDeviceId(), e);
+ log.info("设备核心信息更新完成: {}", device.getDeviceId());
}
}
+
+ // 新增一个辅助方法,用于简化字段更新逻辑
+ private boolean updateFieldIfPresent(Map props, String key, Consumer setter) {
+ if (props.containsKey(key) && props.get(key) != null) {
+ String newValue = props.get(key).toString();
+ setter.accept(newValue);
+ log.debug("更新设备{}: {}", key, newValue);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 更新按头使用统计 - 完整版本
+ */
+ private void updateHeadUsageStatistics(Device device, IotMsgNotifyDataPro iotMsgNotifyData) {
+ String deviceId = device.getDeviceId();
+ log.debug("开始更新按头使用统计: {}", deviceId);
+
+ try {
+ if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
+ log.debug("消息体或服务数据为空,跳过按头统计更新: {}", deviceId);
+ return;
+ }
+
+ // 遍历所有服务,寻找按摩相关数据
+ for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
+ if ("StatusChange".equals(service.getServiceId()) && service.getProperties() != null) {
+ Map props = service.getProperties();
+
+ // 提取按头类型
+ String headType = safeGetString(props, "head_type");
+ if (StrUtil.isBlank(headType)) {
+ log.debug("按头类型为空,跳过按头统计更新: {}", deviceId);
+ continue;
+ }
+
+ // 计算按摩时长
+ Long duration = calculateMassageDuration(iotMsgNotifyData);
+ if (duration == null || duration <= 0) {
+ log.debug("按摩时长无效({}),跳过按头统计更新: {}", duration, deviceId);
+ continue;
+ }
+
+ // 检查是否已有该按头的使用记录
+ DeviceHeadUsage usage = deviceHeadUsageMapper.selectOne(
+ new LambdaQueryWrapper()
+ .eq(DeviceHeadUsage::getDeviceId, deviceId)
+ .eq(DeviceHeadUsage::getHeadType, headType));
+
+ Date now = new Date();
+
+ if (usage == null) {
+ // 创建新记录
+ usage = new DeviceHeadUsage();
+ usage.setDeviceId(deviceId);
+ usage.setHeadType(headType);
+ usage.setUsageCount(1);
+ usage.setTotalDuration(duration);
+ usage.setDailyDuration(duration);
+ usage.setWeeklyDuration(duration);
+ usage.setMonthlyDuration(duration);
+ usage.setLastUsedTime(now);
+ usage.setCreateTime(now);
+ usage.setUpdateTime(now);
+ usage.setCreateBy("系统自动创建");
+ int result = deviceHeadUsageMapper.insert(usage);
+ if (result > 0) {
+ log.info("新增按头使用记录: 设备{},按头{},次数{},时长{}秒",
+ deviceId, headType, usage.getUsageCount(), duration);
+ } else {
+ log.error("新增按头使用记录失败: 设备{},按头{}", deviceId, headType);
+ }
+ } else {
+ // 更新现有记录
+ usage.setUsageCount(usage.getUsageCount() + 1);
+ usage.setTotalDuration(usage.getTotalDuration() + duration);
+ usage.setDailyDuration(usage.getDailyDuration() + duration);
+ usage.setWeeklyDuration(usage.getWeeklyDuration() + duration);
+ usage.setMonthlyDuration(usage.getMonthlyDuration() + duration);
+ usage.setLastUsedTime(now);
+ usage.setUpdateTime(now);
+ usage.setUpdateBy("系统自动更新");
+
+ int result = deviceHeadUsageMapper.updateById(usage);
+ if (result > 0) {
+ log.info("更新按头使用记录: 设备{},按头{},次数{},总时长{}秒",
+ deviceId, headType, usage.getUsageCount(), usage.getTotalDuration());
+ } else {
+ log.error("更新按头使用记录失败: 设备{},按头{}", deviceId, headType);
+ }
+ }
+
+ // 只处理第一个有效的按摩服务
+ break;
+ }
+ }
+ } catch (Exception e) {
+ log.error("更新按头使用统计异常: {}", deviceId, e);
+ }
+ }
+
+ /**
+ * 处理按摩任务数据 - 增强版,补全缺失字段
+ */
+ private void processMassageTaskData(Device device, IotMsgNotifyDataPro iotMsgNotifyData) {
+ if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
+ log.debug("消息体或服务数据为空,设备: {}", device.getDeviceId());
+ return;
+ }
+
+ for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
+ if ("StatusChange".equals(service.getServiceId()) && service.getProperties() != null) {
+ Map props = service.getProperties();
+
+ // 补全缺失字段
+ completeMissingMassageProperties(props, device.getDeviceId());
+
+ // 原有处理逻辑...
+ if (!isValidMassageTaskData(device.getDeviceId(), props)) {
+ log.debug("按摩任务数据无效,设备: {}", device.getDeviceId());
+ continue;
+ }
+
+ // 检查是否重复
+ if (isMassageTaskExists(device.getDeviceId(), props)) {
+ log.debug("按摩任务已存在,跳过: {}", device.getDeviceId());
+ continue;
+ }
+
+ MassageTask task = createMassageTask(device, props);
+ if (task != null) {
+ try {
+ massageTaskMapper.insert(task);
+ log.info("按摩任务记录成功: 设备{},时长{}秒", device.getDeviceId(), task.getTaskTime());
+
+ // 更新设备最后按摩时间
+ Long endTime = safeParseTimestamp(props.get("massage_end_time"));
+ if (endTime != null && endTime > 0) {
+ Date lastMassageTime = new Date(endTime);
+ if (device.getLastMassageTime() == null || lastMassageTime.after(device.getLastMassageTime())) {
+ device.setLastMassageTime(lastMassageTime);
+ device.setUpdateTime(new Date());
+ deviceMapper.updateById(device);
+ log.debug("更新设备最后按摩时间: {} -> {}", device.getDeviceId(), lastMassageTime);
+ }
+ }
+ } catch (Exception e) {
+ log.error("插入按摩任务失败,设备: {}", device.getDeviceId(), e);
+ }
+ } else {
+ log.warn("创建按摩任务对象失败,设备: {}", device.getDeviceId());
+ }
+ break;
+ }
+ }
+ }
+
+ /**
+ * 补全按摩任务缺失的属性字段
+ */
+ private void completeMissingMassageProperties(Map props, String deviceId) {
+ // 提供默认值或从其他来源获取值
+ if (!props.containsKey("head_type") || props.get("head_type") == null) {
+ props.put("head_type", "default_head");
+ log.debug("设备 {} 的 head_type 缺失,使用默认值", deviceId);
+ }
+
+ if (!props.containsKey("body_part") || props.get("body_part") == null) {
+ props.put("body_part", "default_body_part");
+ log.debug("设备 {} 的 body_part 缺失,使用默认值", deviceId);
+ }
+
+ if (!props.containsKey("massage_plan") || props.get("massage_plan") == null) {
+ props.put("massage_plan", "default_plan");
+ log.debug("设备 {} 的 massage_plan 缺失,使用默认值", deviceId);
+ }
+
+ // 检查时间字段,如果缺失,使用当前时间或相关时间
+ if (!props.containsKey("massage_start_time") || props.get("massage_start_time") == null) {
+ // 如果没有开始时间,假设为当前时间减去默认持续时间(例如10分钟)
+ long startTime = System.currentTimeMillis() - 600000; // 10分钟前
+ props.put("massage_start_time", startTime);
+ log.debug("设备 {} 的 massage_start_time 缺失,使用默认值", deviceId);
+ }
+
+ if (!props.containsKey("massage_end_time") || props.get("massage_end_time") == null) {
+ // 如果没有结束时间,使用当前时间
+ long endTime = System.currentTimeMillis();
+ props.put("massage_end_time", endTime);
+ log.debug("设备 {} 的 massage_end_time 缺失,使用默认值", deviceId);
+ }
+
+ // 确保时间戳格式正确(转换为毫秒级)
+ ensureTimestampFormat(props, "massage_start_time");
+ ensureTimestampFormat(props, "massage_end_time");
+ }
+
+ /**
+ * 确保时间戳格式正确(转换为毫秒级)
+ */
+ private void ensureTimestampFormat(Map props, String key) {
+ if (props.containsKey(key)) {
+ Object value = props.get(key);
+ if (value instanceof Number) {
+ long timestamp = ((Number) value).longValue();
+ // 判断是秒级还是毫秒级
+ if (timestamp < 10000000000L) { // 秒级时间戳(小于 2286-11-21)
+ props.put(key, timestamp * 1000); // 转换为毫秒级
+ }
+ } else if (value instanceof String) {
+ try {
+ long timestamp = Long.parseLong((String) value);
+ if (timestamp < 10000000000L) { // 秒级时间戳
+ props.put(key, timestamp * 1000); // 转换为毫秒级
+ } else {
+ props.put(key, timestamp); // 已经是毫秒级
+ }
+ } catch (NumberFormatException e) {
+ log.warn("时间戳格式错误,key: {}, value: {}", key, value);
+ // 使用当前时间作为默认值
+ props.put(key, System.currentTimeMillis());
+ }
+ }
+ }
+ }
+
+ /**
+ * 检查按摩任务数据是否有效
+ */
+ private boolean isValidMassageTaskData(String deviceId, Map props) {
+ boolean hasStartTime = props.containsKey("massage_start_time") && props.get("massage_start_time") != null;
+ boolean hasEndTime = props.containsKey("massage_end_time") && props.get("massage_end_time") != null;
+ boolean hasHeadType = props.containsKey("head_type") && props.get("head_type") != null;
+ boolean hasBodyPart = props.containsKey("body_part") && props.get("body_part") != null;
+
+ if (!hasStartTime || !hasEndTime || !hasHeadType || !hasBodyPart) {
+ log.warn("按摩任务数据不完整,设备: {}", deviceId);
+ return false;
+ }
+
+ // 检查时间顺序
+ Long startTime = safeParseTimestamp(props.get("massage_start_time"));
+ Long endTime = safeParseTimestamp(props.get("massage_end_time"));
+
+ if (startTime == null || endTime == null) {
+ log.warn("按摩任务时间格式错误,设备: {}", deviceId);
+ return false;
+ }
+
+ if (startTime >= endTime) {
+ log.warn("按摩任务开始时间大于等于结束时间,设备: {}", deviceId);
+ return false;
+ }
+
+ // 检查持续时间是否合理(最长24小时)
+ long durationMs = endTime - startTime;
+ long maxValidDuration = 24 * 60 * 60 * 1000L; // 24小时
+
+ if (durationMs > maxValidDuration) {
+ log.warn("按摩任务持续时间过长: {} 毫秒,设备: {}", durationMs, deviceId);
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 检查按摩任务是否已存在
+ */
+ private boolean isMassageTaskExists(String deviceId, Map props) {
+ Long startTime = safeParseTimestamp(props.get("massage_start_time"));
+ String headType = safeGetString(props, "head_type");
+ String bodyPart = safeGetString(props, "body_part");
+
+ if (startTime == null || StrUtil.isBlank(headType) || StrUtil.isBlank(bodyPart)) {
+ return false;
+ }
+
+ LambdaQueryWrapper query = new LambdaQueryWrapper<>();
+ query.eq(MassageTask::getDeviceId, deviceId)
+ .eq(MassageTask::getStartTime, startTime)
+ .eq(MassageTask::getHeadType, headType)
+ .eq(MassageTask::getBodyPart, bodyPart);
+
+ return massageTaskMapper.selectCount(query) > 0;
+ }
+
+ /**
+ * 创建按摩任务对象
+ */
+ private MassageTask createMassageTask(Device device, Map props) {
+ try {
+ MassageTask task = new MassageTask();
+ task.setDeviceId(device.getDeviceId());
+ task.setDeviceName(device.getDeviceName());
+ task.setProductName(device.getProductName());
+ task.setHeadType(safeGetString(props, "head_type"));
+ task.setBodyPart(safeGetString(props, "body_part"));
+ task.setMassagePlan(safeGetString(props, "massage_plan"));
+ task.setCreateBy("系统自动创建");
+
+ Long startTime = safeParseTimestamp(props.get("massage_start_time"));
+ Long endTime = safeParseTimestamp(props.get("massage_end_time"));
+ task.setCreateTimestamp(System.currentTimeMillis());
+
+ if (startTime != null && endTime != null && startTime < endTime && startTime > 0) {
+ task.setStartTime(startTime);
+ task.setEndTime(endTime);
+ task.setTaskTime((endTime - startTime) / 1000); // 转换为秒
+ task.setCreateTime(new Date());
+ return task;
+ }
+ } catch (Exception e) {
+ log.error("创建按摩任务失败", e);
+ }
+ return null;
+ }
+
+ /**
+ * 安全解析时间戳
+ */
+ private Long safeParseTimestamp(Object timestampObj) {
+ if (timestampObj == null) return null;
+ try {
+ long timestamp;
+ if (timestampObj instanceof Number) {
+ timestamp = ((Number) timestampObj).longValue();
+ } else if (timestampObj instanceof String) {
+ String timestampStr = timestampObj.toString();
+ // 处理科学计数法
+ if (timestampStr.contains("E") || timestampStr.contains("e")) {
+ double doubleValue = Double.parseDouble(timestampStr);
+ timestamp = (long) doubleValue;
+ } else {
+ timestamp = Long.parseLong(timestampStr);
+ }
+ } else {
+ return null;
+ }
+
+ // 判断是秒级还是毫秒级
+ if (timestamp < 10000000000L) { // 秒级时间戳(小于 2286-11-21)
+ return timestamp * 1000;
+ } else { // 毫秒级时间戳
+ return timestamp;
+ }
+ } catch (Exception e) {
+ log.warn("时间戳解析失败: {}", timestampObj, e);
+ return null;
+ }
+ }
+
+ /**
+ * 安全获取字符串值
+ */
+ private String safeGetString(Map props, String key) {
+ if (props == null || !props.containsKey(key)) return null;
+ Object value = props.get(key);
+ return value != null ? value.toString() : null;
+ }
}
\ No newline at end of file
diff --git a/storm-device/src/main/java/com/storm/device/service/impl/DeviceRuntimeStatsServiceImpl.java b/storm-device/src/main/java/com/storm/device/service/impl/DeviceRuntimeStatsServiceImpl.java
index 6c0db31..1711f36 100644
--- a/storm-device/src/main/java/com/storm/device/service/impl/DeviceRuntimeStatsServiceImpl.java
+++ b/storm-device/src/main/java/com/storm/device/service/impl/DeviceRuntimeStatsServiceImpl.java
@@ -1,182 +1,255 @@
package com.storm.device.service.impl;
-import java.util.Date;
-import java.util.List;
-
+import cn.hutool.core.util.StrUtil;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.storm.device.domain.po.DeviceRuntimeStats;
+import com.storm.device.mapper.DeviceRuntimeStatsMapper;
+import com.storm.device.service.IDeviceRuntimeStatsService;
import com.storm.device.task.vo.IotMsgNotifyDataPro;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
-import com.storm.device.mapper.DeviceRuntimeStatsMapper;
-import com.storm.device.service.IDeviceRuntimeStatsService;
-import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
+import java.time.Duration;
import java.util.Arrays;
+import java.util.Date;
+import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
-/**
- * 设备运行时长统计Service业务层处理
- *
- * @author storm
- * @date 2025-09-25
- */
-@Service
@Slf4j
-public class DeviceRuntimeStatsServiceImpl extends ServiceImpl implements IDeviceRuntimeStatsService
-{
+@Service
+public class DeviceRuntimeStatsServiceImpl extends ServiceImpl implements IDeviceRuntimeStatsService {
+
@Autowired
private DeviceRuntimeStatsMapper deviceRuntimeStatsMapper;
- /**
- * 查询设备运行时长统计
- *
- * @param id 设备运行时长统计主键
- * @return 设备运行时长统计
- */
- @Override
- public DeviceRuntimeStats selectDeviceRuntimeStatsById(Long id)
- {
- return getById(id);
- }
+ @Autowired
+ private RedisTemplate redisTemplate;
+ private static final String RUNTIME_STATS_PREFIX = "runtime:stats:id:";
+ private static final long RUNTIME_STATS_EXPIRE_HOURS = 24;
/**
- * 查询设备运行时长统计列表
- *
- * @param deviceRuntimeStats 设备运行时长统计
- * @return 设备运行时长统计
- */
- @Override
- public List selectDeviceRuntimeStatsList(DeviceRuntimeStats deviceRuntimeStats)
- {
- return deviceRuntimeStatsMapper.selectDeviceRuntimeStatsList(deviceRuntimeStats);
- }
-
- /**
- * 新增设备运行时长统计
- *
- * @param deviceRuntimeStats 设备运行时长统计
- * @return 结果
- */
- @Override
- public int insertDeviceRuntimeStats(DeviceRuntimeStats deviceRuntimeStats)
- {
- return save(deviceRuntimeStats) ? 1 : 0;
- }
-
- /**
- * 修改设备运行时长统计
- *
- * @param deviceRuntimeStats 设备运行时长统计
- * @return 结果
- */
- @Override
- public int updateDeviceRuntimeStats(DeviceRuntimeStats deviceRuntimeStats)
- {
- return updateById(deviceRuntimeStats) ? 1 : 0;
- }
-
- /**
- * 批量删除设备运行时长统计
- *
- * @param ids 需要删除的设备运行时长统计主键
- * @return 结果
- */
- @Override
- public int deleteDeviceRuntimeStatsByIds(Long[] ids)
- {
- return removeByIds(Arrays.asList(ids)) ? 1 : 0;
- }
-
- /**
- * 安全更新运行统计 - 修复重复累加问题
+ * 安全更新运行统计 - 增强版,防止重复累加
*/
@Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean safeUpdateRuntimeStats(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData) {
- try {
- Date today = new Date();
- Date now = new Date();
+ // 生成运行统计唯一标识
+ String statsUniqueId = generateStatsUniqueId(deviceId, iotMsgNotifyData);
+ if (StrUtil.isNotBlank(statsUniqueId)) {
+ String idempotentKey = RUNTIME_STATS_PREFIX + statsUniqueId;
+ Boolean isNewStats = redisTemplate.opsForValue().setIfAbsent(
+ idempotentKey, "1", Duration.ofHours(RUNTIME_STATS_EXPIRE_HOURS));
- // 计算要增加的时长和在线次数
- Long durationToAdd = calculateMassageDuration(iotMsgNotifyData);
- Integer onlineCountToAdd = shouldCountOnline(iotMsgNotifyData) ? 1 : 0;
+ if (isNewStats != null && !isNewStats) {
+ log.debug("运行统计已更新,跳过处理: {}", statsUniqueId);
+ return true; // 已更新也算成功
+ }
+ }
- // 先尝试查询今日是否已有记录
- DeviceRuntimeStats existingStats = deviceRuntimeStatsMapper.selectTodayStats(deviceId);
+ // 检查是否重复消息
+ if (isDuplicateEvent(deviceId, iotMsgNotifyData)) {
+ log.debug("检测到重复消息,跳过运行统计更新: {}", deviceId);
+ return true;
+ }
- if (existingStats == null) {
- // 插入新记录
- DeviceRuntimeStats newStats = new DeviceRuntimeStats();
- newStats.setDeviceId(deviceId);
- newStats.setStatDate(today);
- newStats.setDailyDuration(durationToAdd);
- newStats.setWeeklyDuration(durationToAdd);
- newStats.setMonthlyDuration(durationToAdd);
- newStats.setOnlineCount(onlineCountToAdd);
- newStats.setCreateTime(now);
- newStats.setUpdateTime(now);
- newStats.setCreateBy("系统自动创建");
-
- deviceRuntimeStatsMapper.insert(newStats);
- log.debug("创建新的运行统计记录: {}", deviceId);
- } else {
- // 更新现有记录 - 确保不重复累加
- long newDailyDuration = existingStats.getDailyDuration() + durationToAdd;
- long newWeeklyDuration = existingStats.getWeeklyDuration() + durationToAdd;
- long newMonthlyDuration = existingStats.getMonthlyDuration() + durationToAdd;
- int newOnlineCount = existingStats.getOnlineCount() + onlineCountToAdd;
-
- // 验证数据合理性
- if (isDurationReasonable(newDailyDuration) &&
- isDurationReasonable(newWeeklyDuration) &&
- isDurationReasonable(newMonthlyDuration)) {
-
- existingStats.setDailyDuration(newDailyDuration);
- existingStats.setWeeklyDuration(newWeeklyDuration);
- existingStats.setMonthlyDuration(newMonthlyDuration);
- existingStats.setOnlineCount(newOnlineCount);
- existingStats.setUpdateTime(now);
-
- deviceRuntimeStatsMapper.updateById(existingStats);
- log.debug("更新运行统计记录: {} -> 增加{}秒", deviceId, durationToAdd);
- } else {
- log.warn("运行统计时长异常,跳过更新: {}", deviceId);
+ for (int attempt = 0; attempt < 3; attempt++) {
+ try {
+ boolean result = doUpdateRuntimeStats(deviceId, iotMsgNotifyData, attempt);
+ if (!result && StrUtil.isNotBlank(statsUniqueId)) {
+ // 更新失败时,删除幂等键,允许重试
+ redisTemplate.delete(RUNTIME_STATS_PREFIX + statsUniqueId);
+ }
+ return result;
+ } catch (DuplicateKeyException e) {
+ log.warn("运行统计更新重复键异常,尝试次数: {},设备: {}", attempt + 1, deviceId);
+ if (attempt == 2) {
+ log.error("运行统计更新最终失败,设备: {}", deviceId, e);
+ return false;
+ }
+ // 等待后重试
+ try {
+ Thread.sleep(100 * (attempt + 1));
+ } catch (InterruptedException ie) {
+ Thread.currentThread().interrupt();
+ return false;
+ }
+ } catch (Exception e) {
+ log.error("运行统计更新异常,尝试次数: {},设备: {}", attempt + 1, deviceId, e);
+ if (attempt == 2) {
+ if (StrUtil.isNotBlank(statsUniqueId)) {
+ // 更新失败时,删除幂等键,允许重试
+ redisTemplate.delete(RUNTIME_STATS_PREFIX + statsUniqueId);
+ }
return false;
}
}
- return true;
-
- } catch (DuplicateKeyException e) {
- log.debug("运行统计记录已存在(并发情况),设备: {}", deviceId);
- // 递归重试一次
- return safeUpdateRuntimeStatsRetry(deviceId, iotMsgNotifyData);
- } catch (Exception e) {
- log.error("更新运行统计失败,设备: {}", deviceId, e);
- return false;
}
+ return false;
}
/**
- * 重试机制
+ * 生成运行统计唯一标识
*/
- private boolean safeUpdateRuntimeStatsRetry(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData) {
+ private String generateStatsUniqueId(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData) {
+ if (iotMsgNotifyData.getBody() == null) {
+ return null;
+ }
+
+ String eventTime = null;
+
+ // 尝试从body中获取事件时间
+ if (iotMsgNotifyData.getBody().getStatusUpdateTime() != null) {
+ eventTime = String.valueOf(iotMsgNotifyData.getBody().getStatusUpdateTime().getTime());
+ }
+
+ // 尝试从services中获取事件时间
+ if (StrUtil.isBlank(eventTime) && iotMsgNotifyData.getBody().getServices() != null) {
+ for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
+ if (service.getEventTime() != null) {
+ eventTime = String.valueOf(service.getEventTime().getTime());
+ break;
+ }
+ }
+ }
+
+ // 如果仍然没有事件时间,使用当前时间
+ if (StrUtil.isBlank(eventTime)) {
+ eventTime = String.valueOf(System.currentTimeMillis());
+ }
+
+ // 添加服务类型信息
+ String serviceTypes = "";
+ if (iotMsgNotifyData.getBody().getServices() != null) {
+ for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
+ serviceTypes += service.getServiceId() + ",";
+ }
+ }
+
+ return deviceId + ":" + eventTime + ":" + serviceTypes;
+ }
+
+ /**
+ * 检查是否为重复事件
+ */
+ private boolean isDuplicateEvent(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData) {
try {
- Thread.sleep(100); // 短暂等待
- return safeUpdateRuntimeStats(deviceId, iotMsgNotifyData);
- } catch (InterruptedException ie) {
- Thread.currentThread().interrupt();
- return false;
+ // 使用 massage_start_time 作为唯一标识
+ Long startTime = extractMassageStartTime(iotMsgNotifyData);
+ if (startTime == null) {
+ return false;
+ }
+
+ // 使用Redis设置短期过期键,实现幂等性检查
+ String eventKey = "device:runtime:event:" + deviceId + ":" + startTime;
+ Boolean isNewEvent = redisTemplate.opsForValue().setIfAbsent(
+ eventKey, "1", 5, TimeUnit.MINUTES); // 5分钟幂等窗口
+
+ return isNewEvent != null && !isNewEvent;
} catch (Exception e) {
- log.error("运行统计重试也失败,设备: {}", deviceId, e);
+ log.warn("重复事件检查失败: {}", deviceId, e);
+ return false;
+ }
+ }
+
+ private Long extractMassageStartTime(IotMsgNotifyDataPro iotMsgNotifyData) {
+ if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
+ return null;
+ }
+
+ for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
+ if ("StatusChange".equals(service.getServiceId()) && service.getProperties() != null) {
+ Map props = service.getProperties();
+ return safeParseTimestamp(props.get("massage_start_time"));
+ }
+ }
+ return null;
+ }
+
+ private boolean doUpdateRuntimeStats(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData, int attempt) {
+ Date today = new Date();
+ Long durationToAdd = calculateMassageDuration(iotMsgNotifyData);
+ Integer onlineCountToAdd = shouldCountOnline(iotMsgNotifyData) ? 1 : 0;
+
+ // 方法1: 尝试使用原子更新
+ if (attempt == 0) {
+ try {
+ int result = deviceRuntimeStatsMapper.atomicUpdateStats(
+ deviceId, today, durationToAdd, durationToAdd, durationToAdd, onlineCountToAdd);
+ if (result > 0) {
+ log.debug("原子更新运行统计成功: {}", deviceId);
+ return true;
+ }
+ } catch (Exception e) {
+ log.debug("原子更新失败,尝试其他方法: {}", deviceId);
+ }
+ }
+
+ // 方法2: 尝试使用 INSERT ... ON DUPLICATE KEY UPDATE
+ if (attempt <= 1) {
+ try {
+ DeviceRuntimeStats stats = createStatsObject(deviceId, today, durationToAdd, onlineCountToAdd);
+ int result = deviceRuntimeStatsMapper.insertOrUpdate(stats);
+ if (result > 0) {
+ log.debug("插入或更新运行统计成功: {}", deviceId);
+ return true;
+ }
+ } catch (DuplicateKeyException e) {
+ // 继续尝试下一种方法
+ if (attempt == 1) {
+ throw e; // 最后一次尝试时抛出异常
+ }
+ } catch (Exception e) {
+ log.debug("插入或更新失败: {}", deviceId);
+ }
+ }
+
+ // 方法3: 使用查询+更新(最安全但性能较差)
+ return fallbackUpdate(deviceId, today, durationToAdd, onlineCountToAdd);
+ }
+
+ private DeviceRuntimeStats createStatsObject(String deviceId, Date statDate, Long duration, Integer onlineCount) {
+ DeviceRuntimeStats stats = new DeviceRuntimeStats();
+ stats.setDeviceId(deviceId);
+ stats.setStatDate(statDate);
+ stats.setDailyDuration(duration);
+ stats.setWeeklyDuration(duration);
+ stats.setMonthlyDuration(duration);
+ stats.setOnlineCount(onlineCount);
+ stats.setCreateTime(new Date());
+ stats.setUpdateTime(new Date());
+ stats.setCreateBy("系统自动创建");
+ return stats;
+ }
+
+ private boolean fallbackUpdate(String deviceId, Date statDate, Long durationToAdd, Integer onlineCountToAdd) {
+ try {
+ // 使用行级锁查询
+ DeviceRuntimeStats existingStats = deviceRuntimeStatsMapper.selectByDeviceAndDateForUpdate(deviceId, statDate);
+
+ if (existingStats == null) {
+ // 插入新记录
+ DeviceRuntimeStats newStats = createStatsObject(deviceId, statDate, durationToAdd, onlineCountToAdd);
+ deviceRuntimeStatsMapper.insert(newStats);
+ } else {
+ // 更新现有记录
+ existingStats.setDailyDuration(existingStats.getDailyDuration() + durationToAdd);
+ existingStats.setWeeklyDuration(existingStats.getWeeklyDuration() + durationToAdd);
+ existingStats.setMonthlyDuration(existingStats.getMonthlyDuration() + durationToAdd);
+ existingStats.setOnlineCount(existingStats.getOnlineCount() + onlineCountToAdd);
+ existingStats.setUpdateTime(new Date());
+ deviceRuntimeStatsMapper.updateById(existingStats);
+ }
+ return true;
+ } catch (Exception e) {
+ log.error("回退方案更新运行统计失败: {}", deviceId, e);
return false;
}
}
- /**
- * 计算按摩时长
- */
private Long calculateMassageDuration(IotMsgNotifyDataPro iotMsgNotifyData) {
if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
return 0L;
@@ -189,17 +262,7 @@ public class DeviceRuntimeStatsServiceImpl extends ServiceImpl 0) {
- long durationMs = endTime - startTime;
- long durationSeconds = durationMs / 1000;
-
- // 验证时长合理性
- if (isDurationReasonable(durationSeconds)) {
- return durationSeconds;
- } else {
- log.warn("按摩时长异常: {}秒,设备: {}", durationSeconds,
- iotMsgNotifyData.getHeader().getDeviceId());
- return 0L;
- }
+ return (endTime - startTime) / 1000; // 转换为秒
}
break;
}
@@ -207,52 +270,47 @@ public class DeviceRuntimeStatsServiceImpl extends ServiceImpl= 0 && durationSeconds <= 24 * 60 * 60; // 24小时
- }
-
- /**
- * 安全解析时间戳
- */
private Long safeParseTimestamp(Object timestampObj) {
if (timestampObj == null) return null;
-
try {
if (timestampObj instanceof Number) {
- long timestamp = ((Number) timestampObj).longValue();
- if (timestamp < 10000000000L) { // 秒级时间戳
- return timestamp * 1000;
- } else { // 毫秒级时间戳
- return timestamp;
- }
+ return ((Number) timestampObj).longValue();
} else if (timestampObj instanceof String) {
- String timestampStr = timestampObj.toString();
- if (timestampStr.contains(".")) {
- double timestamp = Double.parseDouble(timestampStr);
- return (long) (timestamp * 1000);
- } else {
- long timestamp = Long.parseLong(timestampStr);
- if (timestamp < 10000000000L) {
- return timestamp * 1000;
- } else {
- return timestamp;
- }
- }
+ return Long.parseLong(timestampObj.toString());
}
} catch (Exception e) {
log.warn("时间戳解析失败: {}", timestampObj);
}
return null;
}
-}
+
+ @Override
+ public DeviceRuntimeStats selectDeviceRuntimeStatsById(Long id) {
+ return getById(id);
+ }
+
+ @Override
+ public List selectDeviceRuntimeStatsList(DeviceRuntimeStats deviceRuntimeStats) {
+ return deviceRuntimeStatsMapper.selectDeviceRuntimeStatsList(deviceRuntimeStats);
+ }
+
+ @Override
+ public int insertDeviceRuntimeStats(DeviceRuntimeStats deviceRuntimeStats) {
+ return save(deviceRuntimeStats) ? 1 : 0;
+ }
+
+ @Override
+ public int updateDeviceRuntimeStats(DeviceRuntimeStats deviceRuntimeStats) {
+ return updateById(deviceRuntimeStats) ? 1 : 0;
+ }
+
+ @Override
+ public int deleteDeviceRuntimeStatsByIds(Long[] ids) {
+ return removeByIds(Arrays.asList(ids)) ? 1 : 0;
+ }
+}
\ No newline at end of file
diff --git a/storm-device/src/main/java/com/storm/device/service/impl/DeviceStatusLogServiceImpl.java b/storm-device/src/main/java/com/storm/device/service/impl/DeviceStatusLogServiceImpl.java
index d96d3fc..40485cc 100644
--- a/storm-device/src/main/java/com/storm/device/service/impl/DeviceStatusLogServiceImpl.java
+++ b/storm-device/src/main/java/com/storm/device/service/impl/DeviceStatusLogServiceImpl.java
@@ -1,16 +1,19 @@
package com.storm.device.service.impl;
+import java.time.Duration;
import java.util.*;
+
+import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.storm.device.domain.po.DeviceStatusLog;
-import com.storm.device.manager.DeviceManagerService;
import com.storm.device.mapper.DeviceStatusLogMapper;
import com.storm.device.service.IDeviceStatusLogService;
import com.storm.device.task.vo.IotMsgNotifyDataPro;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.transaction.annotation.Propagation;
@@ -28,8 +31,9 @@ public class DeviceStatusLogServiceImpl extends ServiceImpl redisTemplate;
+ private static final String STATUS_LOG_PREFIX = "status:log:id:";
+ private static final long STATUS_LOG_EXPIRE_HOURS = 24;
@Override
public List selectDeviceStatusLogList(DeviceStatusLog deviceStatusLog) {
return deviceStatusLogMapper.selectList(Wrappers.lambdaQuery()
@@ -41,93 +45,32 @@ public class DeviceStatusLogServiceImpl extends ServiceImpl services) {
- for (IotMsgNotifyDataPro.IotMsgService service : services) {
- if (service.getProperties() == null) continue;
-
- Map properties = service.getProperties();
-
- // 处理按摩相关属性
- if (properties.containsKey("head_type")) {
- statusLog.setHeadType(properties.get("head_type").toString());
- }
- if (properties.containsKey("body_part")) {
- statusLog.setBodyPart(properties.get("body_part").toString());
- }
- if (properties.containsKey("massage_plan")) {
- statusLog.setMassagePlan(properties.get("massage_plan").toString());
- }
-
- // 处理按摩时间
- if (properties.containsKey("massage_start_time")) {
- Long startTime = parseTimestampToMillis(properties.get("massage_start_time"));
- statusLog.setMassageStartTime(startTime);
- }
- if (properties.containsKey("massage_end_time")) {
- Long endTime = parseTimestampToMillis(properties.get("massage_end_time"));
- statusLog.setMassageEndTime(endTime);
-
- // 计算持续时间
- if (statusLog.getMassageStartTime() != null && endTime != null) {
- statusLog.setMassageDuration(endTime - statusLog.getMassageStartTime());
- }
- }
-
- // 如果是按摩任务,设置事件类型
- if (properties.containsKey("massage_start_time") ||
- properties.containsKey("head_type") ||
- properties.containsKey("body_part")) {
- statusLog.setEventType("MASSAGE_TASK");
- }
- }
- }
-
- /**
- * 解析时间戳为毫秒
- */
- private Long parseTimestampToMillis(Object timestampObj) {
- if (timestampObj == null) return null;
-
- try {
- if (timestampObj instanceof Number) {
- long timestamp = ((Number) timestampObj).longValue();
- if (timestamp < 10000000000L) { // 秒级时间戳
- return timestamp * 1000;
- } else { // 毫秒级时间戳
- return timestamp;
- }
- } else if (timestampObj instanceof String) {
- String timestampStr = timestampObj.toString();
- if (timestampStr.contains(".")) {
- // 处理浮点数时间戳
- double timestamp = Double.parseDouble(timestampStr);
- return (long) (timestamp * 1000);
- } else {
- long timestamp = Long.parseLong(timestampStr);
- if (timestamp < 10000000000L) {
- return timestamp * 1000;
- } else {
- return timestamp;
- }
- }
- }
- } catch (Exception e) {
- log.error("时间戳解析失败: {}", timestampObj, e);
- }
- return null;
- }
-
- /**
- * 安全插入状态日志 - 多层保护机制
- */
- @Transactional(propagation = Propagation.REQUIRES_NEW)
@Override
+ @Transactional(propagation = Propagation.REQUIRES_NEW)
public boolean safeInsertStatusLog(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData) {
+ // 生成状态日志唯一标识
+ String statusLogUniqueId = generateStatusLogUniqueId(deviceId, iotMsgNotifyData);
+ if (StrUtil.isNotBlank(statusLogUniqueId)) {
+ String idempotentKey = STATUS_LOG_PREFIX + statusLogUniqueId;
+ Boolean isNewLog = redisTemplate.opsForValue().setIfAbsent(
+ idempotentKey, "1", Duration.ofHours(STATUS_LOG_EXPIRE_HOURS));
+
+ if (isNewLog != null && !isNewLog) {
+ log.debug("状态日志已存在,跳过插入: {}", statusLogUniqueId);
+ return true; // 已存在也算成功
+ }
+ }
+
for (int attempt = 0; attempt < 3; attempt++) {
try {
- return doInsertStatusLog(deviceId, iotMsgNotifyData, attempt);
+ boolean result = doInsertStatusLog(deviceId, iotMsgNotifyData, attempt);
+ if (!result && StrUtil.isNotBlank(statusLogUniqueId)) {
+ // 插入失败时,删除幂等键,允许重试
+ redisTemplate.delete(STATUS_LOG_PREFIX + statusLogUniqueId);
+ }
+ return result;
} catch (DuplicateKeyException e) {
log.debug("状态日志重复键异常,尝试次数: {},设备: {}", attempt + 1, deviceId);
if (attempt == 2) {
@@ -144,6 +87,10 @@ public class DeviceStatusLogServiceImpl extends ServiceImpl services = iotMsgNotifyData.getBody().getServices();
- if(services!=null&&services.size()>0){
- for (IotMsgNotifyDataPro.IotMsgService service : services) {
- if ("StatusChange".equals(service.getServiceId()) && service.getProperties() != null) {
- Map props = service.getProperties();
- Long startTime = safeParseTimestamp(props.get("massage_start_time"));
- Long endTime = safeParseTimestamp(props.get("massage_end_time"));
- statusLog.setMassageStartTime(startTime);
- statusLog.setMassageEndTime(endTime);
- statusLog.setHeadType(safeGetString(props, "head_type"));
- statusLog.setBodyPart(safeGetString(props, "body_part"));
- if (startTime != null && endTime != null && startTime < endTime && startTime > 0) {
- long duration = (endTime - startTime);
- statusLog.setDuration((int)duration);
- statusLog.setMassageDuration(duration);
- }
- }
- }
- }
- // 处理服务数据
- processServiceData(statusLog, iotMsgNotifyData);
-
- return statusLog;
- }
-
- private String safeGetString(Map props, String key) {
- if (props == null || !props.containsKey(key)) return null;
- Object value = props.get(key);
- return value != null ? value.toString() : null;
- }
-
- /**
- * 处理服务数据
- */
- private void processServiceData(DeviceStatusLog statusLog, IotMsgNotifyDataPro iotMsgNotifyData) {
- if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
- return;
- }
-
- for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
- if ("StatusChange".equals(service.getServiceId()) && service.getProperties() != null) {
- // 处理按摩相关数据
- processMassageData(statusLog, service.getProperties());
- break;
- }
- }
- }
-
- /**
- * 处理按摩数据 - 修复版本
- */
- private void processMassageData(DeviceStatusLog statusLog, java.util.Map properties) {
- if (properties.containsKey("head_type") && properties.get("head_type") != null) {
- statusLog.setHeadType(properties.get("head_type").toString());
- }
- if (properties.containsKey("body_part") && properties.get("body_part") != null) {
- statusLog.setBodyPart(properties.get("body_part").toString());
- }
- if (properties.containsKey("massage_plan") && properties.get("massage_plan") != null) {
- statusLog.setMassagePlan(properties.get("massage_plan").toString());
- }
-
- // 处理按摩时间 - 使用安全解析
- Long startTime = safeParseTimestamp(properties.get("massage_start_time"));
- Long endTime = safeParseTimestamp(properties.get("massage_end_time"));
-
- if (startTime != null) {
- statusLog.setMassageStartTime(startTime);
- }
- if (endTime != null) {
- statusLog.setMassageEndTime(endTime);
- }
-
- // 安全计算持续时间
- Long duration = safeCalculateDuration(startTime, endTime);
- if (duration != null) {
- statusLog.setMassageDuration(duration);
- statusLog.setDuration(duration.intValue());
-
- // 如果是按摩任务,更改事件类型
- statusLog.setEventType("MASSAGE_TASK");
- log.debug("计算按摩持续时间: {} 秒", duration);
- } else {
- // 设置默认值
- statusLog.setMassageDuration(0L);
- statusLog.setDuration(0);
- log.warn("无法计算有效的按摩持续时间,startTime: {}, endTime: {}", startTime, endTime);
- }
- }
-
private boolean insertWithIgnore(DeviceStatusLog statusLog) {
try {
int result = deviceStatusLogMapper.insertOrIgnore(statusLog);
@@ -379,11 +237,10 @@ public class DeviceStatusLogServiceImpl extends ServiceImpl props = service.getProperties();
+ if (props == null) continue;
- // 如果是秒级时间戳,转换为毫秒后检查
- if (timestamp < 10000000000L) {
- timestamp = timestamp * 1000;
+ // 补全缺失字段
+ completeMissingServiceProperties(props, statusLog.getDeviceId());
+
+ // 最终判断:只要包含按摩开始时间、按摩头类型、身体部位中任意两个字段,就认为是按摩任务
+ boolean hasMassageStart = props.containsKey("massage_start_time");
+ boolean hasHeadType = props.containsKey("head_type") && props.get("head_type") != null;
+ boolean hasBodyPart = props.containsKey("body_part") && props.get("body_part") != null;
+
+ int massageEvidenceCount = (hasMassageStart ? 1 : 0) + (hasHeadType ? 1 : 0) + (hasBodyPart ? 1 : 0);
+
+ if (massageEvidenceCount >= 2) {
+ statusLog.setEventType("MASSAGE_TASK");
+ processMassageData(statusLog, props); // 处理按摩数据
+ } else if ("StatusChange".equals(service.getServiceId())) {
+ // 普通状态变更事件
+ statusLog.setEventType("STATUS_CHANGE");
+ } else {
+ // 其他类型的事件
+ statusLog.setEventType(service.getServiceId());
+ }
+ // 找到一个服务就跳出循环,通常一个消息只包含一个主要事件
+ break;
+ }
+ }
+
+ /**
+ * 补全服务属性中的缺失字段
+ */
+ private void completeMissingServiceProperties(Map props, String deviceId) {
+ if (!props.containsKey("head_type")) {
+ props.put("head_type", "default_head");
+ log.debug("设备 {} 的 head_type 缺失,使用默认值", deviceId);
+ }
+ if (!props.containsKey("body_part")) {
+ props.put("body_part", "default_body_part");
+ log.debug("设备 {} 的 body_part 缺失,使用默认值", deviceId);
+ }
+ if (!props.containsKey("massage_plan")) {
+ props.put("massage_plan", "default_plan");
+ log.debug("设备 {} 的 massage_plan 缺失,使用默认值", deviceId);
+ }
+ if (!props.containsKey("massage_start_time")) {
+ long startTime = System.currentTimeMillis() - 600000; // 10分钟前
+ props.put("massage_start_time", startTime);
+ log.debug("设备 {} 的 massage_start_time 缺失,使用默认值", deviceId);
+ }
+ if (!props.containsKey("massage_end_time")) {
+ long endTime = System.currentTimeMillis();
+ props.put("massage_end_time", endTime);
+ log.debug("设备 {} 的 massage_end_time 缺失,使用默认值", deviceId);
+ }
+ }
+
+ /**
+ * 处理按摩数据 - 修复版本,确保字段正确设置
+ */
+ private void processMassageData(DeviceStatusLog statusLog, Map properties) {
+ // 使用补全后的属性
+ if (properties.containsKey("head_type") && properties.get("head_type") != null) {
+ statusLog.setHeadType(properties.get("head_type").toString());
+ }
+ if (properties.containsKey("body_part") && properties.get("body_part") != null) {
+ statusLog.setBodyPart(properties.get("body_part").toString());
+ }
+ if (properties.containsKey("massage_plan") && properties.get("massage_plan") != null) {
+ statusLog.setMassagePlan(properties.get("massage_plan").toString());
}
- return timestamp >= minValidTimestamp && timestamp <= maxValidTimestamp;
+ // 处理按摩时间 - 使用安全解析
+ Long startTime = safeParseTimestamp(properties.get("massage_start_time"));
+ Long endTime = safeParseTimestamp(properties.get("massage_end_time"));
+
+ if (startTime != null) {
+ statusLog.setMassageStartTime(startTime);
+ }
+ if (endTime != null) {
+ statusLog.setMassageEndTime(endTime);
+ }
+
+ // 安全计算持续时间
+ Long duration = safeCalculateDuration(startTime, endTime);
+ if (duration != null) {
+ statusLog.setMassageDuration(duration);
+ statusLog.setDuration(duration.intValue());
+ } else {
+ // 设置默认值
+ statusLog.setMassageDuration(0L);
+ statusLog.setDuration(0);
+ log.warn("无法计算有效的按摩持续时间,startTime: {}, endTime: {}", startTime, endTime);
+ }
}
}
diff --git a/storm-device/src/main/java/com/storm/device/task/LocationAmqpClient.java b/storm-device/src/main/java/com/storm/device/task/LocationAmqpClient.java
index 4567840..96abb2b 100644
--- a/storm-device/src/main/java/com/storm/device/task/LocationAmqpClient.java
+++ b/storm-device/src/main/java/com/storm/device/task/LocationAmqpClient.java
@@ -2,6 +2,8 @@ package com.storm.device.task;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.storm.device.config.properties.HuaWeiIotConfigProperties;
@@ -15,6 +17,7 @@ import org.apache.qpid.jms.transports.TransportSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import javax.jms.*;
@@ -22,6 +25,7 @@ import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.text.MessageFormat;
+import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -33,13 +37,15 @@ import java.util.stream.Collectors;
public class LocationAmqpClient implements ApplicationRunner {
@Autowired
private HuaWeiIotConfigProperties huaWeiIotConfigProperties;
-
//业务处理异步线程池,线程池参数可以根据您的业务特点调整,或者您也可以用其他异步方式处理接收到的消息。
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@Autowired
private DeviceDataCoordinator deviceDataCoordinator;
-
+ @Autowired
+ private RedisTemplate redisTemplate;
+ private static final String MESSAGE_IDEMPOTENT_PREFIX = "iot:message:id:";
+ private static final long MESSAGE_IDEMPOTENT_EXPIRE_MINUTES = 5;
//控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数。
//建议使用机器UUID、MAC地址、IP等唯一标识等作为clientId。便于您区分识别不同的客户端。
private static String clientId;
@@ -177,12 +183,31 @@ public class LocationAmqpClient implements ApplicationRunner {
// 修改processMessage方法
private void processMessage(Message message) {
+ String contentStr;
+ String messageId = null;
try {
- String contentStr = message.getBody(String.class);
+ contentStr = message.getBody(String.class);
String topic = message.getStringProperty("topic");
- String messageId = message.getStringProperty("messageId");
+ messageId = message.getStringProperty("messageId");
log.info("receive message,\n topic = {},\n messageId = {},\n content = {}", topic, messageId, contentStr);
+ // 生成幂等键:如果 messageId 为空,则基于消息内容生成 SHA256 哈希
+ if (StrUtil.isBlank(messageId)) {
+ messageId = DigestUtil.sha256Hex(contentStr);
+ log.debug("生成基于内容的 messageId: {}", messageId);
+ }
+
+ // 幂等性检查
+ String idempotentKey = MESSAGE_IDEMPOTENT_PREFIX + messageId;
+ Boolean isNewMessage = redisTemplate.opsForValue().setIfAbsent(
+ idempotentKey, "1", Duration.ofMinutes(MESSAGE_IDEMPOTENT_EXPIRE_MINUTES));
+
+ if (isNewMessage != null && !isNewMessage) {
+ log.info("消息已处理,跳过重复消息,messageId: {}", messageId);
+ return;
+ }
+
+ // 处理消息内容
JSONObject jsonMsg = JSONUtil.parseObj(contentStr);
JSONObject notifyData = jsonMsg.getJSONObject("notify_data");
if (ObjectUtil.isEmpty(notifyData)) {
@@ -190,12 +215,15 @@ public class LocationAmqpClient implements ApplicationRunner {
}
IotMsgNotifyDataPro iotMsgNotifyData = JSONUtil.toBean(notifyData, IotMsgNotifyDataPro.class);
-
- // 使用统一处理器处理所有数据
deviceDataCoordinator.processDeviceData(iotMsgNotifyData);
+ deviceDataCoordinator.coordinateDeviceData(iotMsgNotifyData);
} catch (Exception e) {
- log.error("处理消息时发生异常", e);
+ log.error("处理消息时发生异常, messageId: {}", messageId, e);
+ // 处理失败时,删除幂等键,允许重试
+ if (StrUtil.isNotBlank(messageId)) {
+ redisTemplate.delete(MESSAGE_IDEMPOTENT_PREFIX + messageId);
+ }
}
}
diff --git a/storm-device/src/main/java/com/storm/device/task/MassageAmqpClient.java b/storm-device/src/main/java/com/storm/device/task/MassageAmqpClient.java
index 6b101d5..2de3dcb 100644
--- a/storm-device/src/main/java/com/storm/device/task/MassageAmqpClient.java
+++ b/storm-device/src/main/java/com/storm/device/task/MassageAmqpClient.java
@@ -2,6 +2,8 @@ package com.storm.device.task;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.storm.device.config.properties.HuaWeiIotConfigProperties;
@@ -15,6 +17,7 @@ import org.apache.qpid.jms.transports.TransportSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import javax.jms.*;
@@ -22,6 +25,7 @@ import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.text.MessageFormat;
+import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
@@ -34,10 +38,14 @@ public class MassageAmqpClient implements ApplicationRunner {
private DeviceDataCoordinator deviceDataProcessor;
@Autowired
private HuaWeiIotConfigProperties huaWeiIotConfigProperties;
-
+ @Autowired
+ private DeviceDataCoordinator deviceDataCoordinator;
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
-
+ @Autowired
+ private RedisTemplate redisTemplate;
+ private static final String MESSAGE_IDEMPOTENT_PREFIX = "iot:message:id:";
+ private static final long MESSAGE_IDEMPOTENT_EXPIRE_MINUTES = 5;
//控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数。
//建议使用机器UUID、MAC地址、IP等唯一标识等作为clientId。便于您区分识别不同的客户端。
private static String clientId;
@@ -173,12 +181,31 @@ public class MassageAmqpClient implements ApplicationRunner {
// 修改processMessage方法
private void processMessage(Message message) {
+ String contentStr;
+ String messageId = null;
try {
- String contentStr = message.getBody(String.class);
+ contentStr = message.getBody(String.class);
String topic = message.getStringProperty("topic");
- String messageId = message.getStringProperty("messageId");
+ messageId = message.getStringProperty("messageId");
log.info("receive message,\n topic = {},\n messageId = {},\n content = {}", topic, messageId, contentStr);
+ // 生成幂等键:如果 messageId 为空,则基于消息内容生成 SHA256 哈希
+ if (StrUtil.isBlank(messageId)) {
+ messageId = DigestUtil.sha256Hex(contentStr);
+ log.debug("生成基于内容的 messageId: {}", messageId);
+ }
+
+ // 幂等性检查
+ String idempotentKey = MESSAGE_IDEMPOTENT_PREFIX + messageId;
+ Boolean isNewMessage = redisTemplate.opsForValue().setIfAbsent(
+ idempotentKey, "1", Duration.ofMinutes(MESSAGE_IDEMPOTENT_EXPIRE_MINUTES));
+
+ if (isNewMessage != null && !isNewMessage) {
+ log.info("消息已处理,跳过重复消息,messageId: {}", messageId);
+ return;
+ }
+
+ // 处理消息内容
JSONObject jsonMsg = JSONUtil.parseObj(contentStr);
JSONObject notifyData = jsonMsg.getJSONObject("notify_data");
if (ObjectUtil.isEmpty(notifyData)) {
@@ -186,12 +213,15 @@ public class MassageAmqpClient implements ApplicationRunner {
}
IotMsgNotifyDataPro iotMsgNotifyData = JSONUtil.toBean(notifyData, IotMsgNotifyDataPro.class);
-
- // 使用统一处理器处理所有数据
- deviceDataProcessor.processDeviceData(iotMsgNotifyData);
+ deviceDataCoordinator.processDeviceData(iotMsgNotifyData);
+ deviceDataCoordinator.coordinateDeviceData(iotMsgNotifyData);
} catch (Exception e) {
- log.error("处理消息时发生异常", e);
+ log.error("处理消息时发生异常, messageId: {}", messageId, e);
+ // 处理失败时,删除幂等键,允许重试
+ if (StrUtil.isNotBlank(messageId)) {
+ redisTemplate.delete(MESSAGE_IDEMPOTENT_PREFIX + messageId);
+ }
}
}
diff --git a/storm-device/src/main/java/com/storm/device/task/StatusAmqpClient.java b/storm-device/src/main/java/com/storm/device/task/StatusAmqpClient.java
index bbcaa35..e03dd46 100644
--- a/storm-device/src/main/java/com/storm/device/task/StatusAmqpClient.java
+++ b/storm-device/src/main/java/com/storm/device/task/StatusAmqpClient.java
@@ -4,6 +4,7 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
+import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
@@ -18,14 +19,17 @@ import org.apache.qpid.jms.transports.TransportSupport;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
+import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
+
import javax.jms.*;
import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
+import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;
@@ -36,13 +40,14 @@ public class StatusAmqpClient implements ApplicationRunner {
private DeviceDataCoordinator deviceDataCoordinator;
@Autowired
private HuaWeiIotConfigProperties huaWeiIotConfigProperties;
+ @Autowired
+ private RedisTemplate redisTemplate;
+ private static final String MESSAGE_IDEMPOTENT_PREFIX = "iot:message:id:";
+ private static final long MESSAGE_IDEMPOTENT_EXPIRE_MINUTES = 5;
- //业务处理异步线程池,线程池参数可以根据您的业务特点调整,或者您也可以用其他异步方式处理接收到的消息。
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
- //控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数。
- //建议使用机器UUID、MAC地址、IP等唯一标识等作为clientId。便于您区分识别不同的客户端。
private static String clientId;
static {
@@ -58,42 +63,27 @@ public class StatusAmqpClient implements ApplicationRunner {
}
public void start() throws Exception {
- //参数说明,请参见AMQP客户端接入说明文档。
for (int i = 0; i < huaWeiIotConfigProperties.getConnectionCount(); i++) {
- //创建amqp连接
Connection connection = getConnection();
-
- //加入监听者
((JmsConnection) connection).addConnectionListener(myJmsConnectionListener);
- // 创建会话。
- // Session.CLIENT_ACKNOWLEDGE: 收到消息后,需要手动调用message.acknowledge()。
- // Session.AUTO_ACKNOWLEDGE: SDK自动ACK(推荐)。
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
connection.start();
- // 创建Receiver连接。
MessageConsumer consumer = newConsumer(session, connection, "queue-status");
consumer.setMessageListener(messageListener);
}
- log.info("amqp is started successfully, and will exit after server shutdown ");
+ log.info("amqp is started successfully, and will exit after server shutdown");
}
- /**
- * 创建amqp连接
- *
- * @return amqp连接
- */
private Connection getConnection() throws Exception {
String connectionUrl = generateConnectUrl();
JmsConnectionFactory cf = new JmsConnectionFactory(connectionUrl);
- // 信任服务端
TransportOptions to = new TransportOptions();
to.setTrustAll(true);
cf.setSslContext(TransportSupport.createJdkSslContext(to));
String userName = "accessKey=" + huaWeiIotConfigProperties.getAccessKey();
cf.setExtension(JmsConnectionExtensions.USERNAME_OVERRIDE.toString(), (connection, uri) -> {
- // IoTDA的userName组成格式如下:“accessKey=${accessKey}|timestamp=${timestamp}”
String newUserName = userName;
if (connection instanceof JmsConnection) {
newUserName = ((JmsConnection) connection).getUsername();
@@ -101,15 +91,9 @@ public class StatusAmqpClient implements ApplicationRunner {
return newUserName + "|timestamp=" + System.currentTimeMillis();
});
- // 创建连接。
return cf.createConnection(userName, huaWeiIotConfigProperties.getAccessCode());
}
- /**
- * 生成amqp连接地址
- *
- * @return amqp连接地址
- */
public String generateConnectUrl() {
String uri = MessageFormat.format("{0}://{1}:{2}",
(huaWeiIotConfigProperties.isUseSsl() ? "amqps" : "amqp"),
@@ -151,14 +135,6 @@ public class StatusAmqpClient implements ApplicationRunner {
return stringBuilder.toString();
}
- /**
- * 创建消费者
- *
- * @param session session
- * @param connection amqp连接
- * @param queueName 队列名称
- * @return 消费者
- */
public MessageConsumer newConsumer(Session session, Connection connection, String queueName) throws Exception {
if (connection == null || !(connection instanceof JmsConnection) || ((JmsConnection) connection).isClosed()) {
throw new Exception("create consumer failed,the connection is disconnected.");
@@ -169,34 +145,51 @@ public class StatusAmqpClient implements ApplicationRunner {
private final MessageListener messageListener = message -> {
try {
- //异步处理收到的消息,确保onMessage函数里没有耗时逻辑
threadPoolTaskExecutor.submit(() -> processMessage(message));
} catch (Exception e) {
log.error("submit task occurs exception ", e);
}
};
- // 修改processMessage方法
private void processMessage(Message message) {
+ String contentStr;
+ String messageId = null;
try {
- String contentStr = message.getBody(String.class);
+ contentStr = message.getBody(String.class);
String topic = message.getStringProperty("topic");
- String messageId = message.getStringProperty("messageId");
+ messageId = message.getStringProperty("messageId");
log.info("receive message,\n topic = {},\n messageId = {},\n content = {}", topic, messageId, contentStr);
+ if (StrUtil.isBlank(messageId)) {
+ messageId = DigestUtil.sha256Hex(contentStr);
+ log.debug("生成基于内容的 messageId: {}", messageId);
+ }
+
+ String idempotentKey = MESSAGE_IDEMPOTENT_PREFIX + messageId;
+ Boolean isNewMessage = redisTemplate.opsForValue().setIfAbsent(
+ idempotentKey, "1", Duration.ofMinutes(MESSAGE_IDEMPOTENT_EXPIRE_MINUTES));
+
+ if (isNewMessage != null && !isNewMessage) {
+ log.info("消息已处理,跳过重复消息,messageId: {}", messageId);
+ return;
+ }
+
JSONObject jsonMsg = JSONUtil.parseObj(contentStr);
JSONObject notifyData = jsonMsg.getJSONObject("notify_data");
if (ObjectUtil.isEmpty(notifyData)) {
return;
}
- // 手动处理日期字段,避免Hutool自动转换失败
+ // 使用自定义解析方法而不是 Hutool 的自动转换
IotMsgNotifyDataPro iotMsgNotifyData = parseIotMsgNotifyData(notifyData);
- // 使用统一处理器处理所有数据
deviceDataCoordinator.processDeviceData(iotMsgNotifyData);
+ deviceDataCoordinator.coordinateDeviceData(iotMsgNotifyData);
} catch (Exception e) {
- log.error("处理消息时发生异常", e);
+ log.error("处理消息时发生异常, messageId: {}", messageId, e);
+ if (StrUtil.isNotBlank(messageId)) {
+ redisTemplate.delete(MESSAGE_IDEMPOTENT_PREFIX + messageId);
+ }
}
}
@@ -297,7 +290,6 @@ public class StatusAmqpClient implements ApplicationRunner {
return iotMsgNotifyData;
}
- // 在 StatusAmqpClient 中修复日期解析方法
/**
* 解析ISO日期时间格式(增强版)
*/
@@ -307,15 +299,20 @@ public class StatusAmqpClient implements ApplicationRunner {
}
try {
+ // 处理格式:20251005T073035Z (16位,没有分隔符)
+ if (dateTimeStr.length() == 16 && dateTimeStr.endsWith("Z") && dateTimeStr.contains("T")) {
+ String pattern = "yyyyMMdd'T'HHmmss'Z'";
+ SimpleDateFormat sdf = new SimpleDateFormat(pattern);
+ sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
+ return sdf.parse(dateTimeStr);
+ }
+
// 处理格式:20250927T070832Z (16位,没有分隔符)
if (dateTimeStr.length() == 16 && dateTimeStr.endsWith("Z") && dateTimeStr.contains("T")) {
String pattern = "yyyyMMdd'T'HHmmss'Z'";
SimpleDateFormat sdf = new SimpleDateFormat(pattern);
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
- Date utcDate = sdf.parse(dateTimeStr);
-
- // 转换为本地时间(如果需要)
- return utcDate;
+ return sdf.parse(dateTimeStr);
}
// 处理格式:2025-09-27T07:08:31.394Z (标准ISO格式)
@@ -366,33 +363,21 @@ public class StatusAmqpClient implements ApplicationRunner {
}
private final JmsConnectionListener myJmsConnectionListener = new JmsConnectionListener() {
- /**
- * 连接成功建立。
- */
@Override
public void onConnectionEstablished(URI remoteURI) {
log.info("onConnectionEstablished, remoteUri:{}", remoteURI);
}
- /**
- * 尝试过最大重试次数之后,最终连接失败。
- */
@Override
public void onConnectionFailure(Throwable error) {
log.error("onConnectionFailure, {}", error.getMessage());
}
- /**
- * 连接中断。
- */
@Override
public void onConnectionInterrupted(URI remoteURI) {
log.info("onConnectionInterrupted, remoteUri:{}", remoteURI);
}
- /**
- * 连接中断后又自动重连上。
- */
@Override
public void onConnectionRestored(URI remoteURI) {
log.info("onConnectionRestored, remoteUri:{}", remoteURI);
diff --git a/storm-device/src/test/java/com/storm/device/1.json b/storm-device/src/test/java/com/storm/device/1.json
deleted file mode 100644
index f70ad2c..0000000
--- a/storm-device/src/test/java/com/storm/device/1.json
+++ /dev/null
@@ -1,24 +0,0 @@
-[
- JmsSession
- [
- ID
- :
- 51c3b405-6453-46e2-b41a-8aab14796183
- :
- 1
- :
- 1
- ]
- delivery
- dispatcher
-] INFO com.iot.amqp.examples.AbstractAmqpExample - receive a message,content={"resource": "device.property", "event": "report","event_time": "20250830T062115Z", "event_time_ms": "2025-08-30T06:21:15.731Z", "request_id":"64840a97-683c-495d-af14-19f6f9e18b20", "notify_data": {"header": {"app_id":"9782371cee894c0abbecb30efe4e7cd0", "device_id": "673602f0d14760402fbd033b_jsfb-DA", "node_id": "jsfb-DA","product_id": "673602f0d14760402fbd033b", "gateway_id": "673602f0d14760402fbd033b_jsfb-DA"}, "body": {"services":[{"service_id": "VortXDB", "properties": {"online_time":"20250830T062115Z", "latitude": "112.541115", "software_version": "ioT","vtxdb_version": "1.1.9-beta-ansun", "status": "ONLINE", "longitude":"28.280514"}, "event_time": "20250830T062115Z"}]
-}
-}
-}
-{"resource": "device.property", "event": "report", "event_time": "20250830T062115Z", "event_time_ms": "2025-08-30T06:21:15.731Z", "request_id": "64840a97-683c-495d-af14-19f6f9e18b20",
-"notify_data": {"header":
-{"app_id": "9782371cee894c0abbecb30efe4e7cd0", "device_id": "673602f0d14760402fbd033b_jsfb-DA", "node_id": "jsfb-DA", "product_id": "673602f0d14760402fbd033b", "gateway_id": "673602f0d14760402fbd033b_jsfb-DA"},
-"body": {"services"}
-:[{"service_id": "VortXDB",
-"properties": {"online_time":"20250830T062115Z", "latitude": "112.541115", "software_version": "ioT","vtxdb_version": "1.1.9-beta-ansun", "status": "ONLINE", "longitude":"28.280514"}, "event_time": "20250830T062115Z"
-}
\ No newline at end of file
diff --git a/storm-device/src/test/java/com/storm/device/2.json b/storm-device/src/test/java/com/storm/device/2.json
deleted file mode 100644
index ee8e8b4..0000000
--- a/storm-device/src/test/java/com/storm/device/2.json
+++ /dev/null
@@ -1,659 +0,0 @@
-{
- "devices": [
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-DA",
- "nodeId": "jsfb-DA",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-DA",
- "deviceName": "jsfb-DA",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-023486",
- "nodeId": "jsfb-023486",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-023486",
- "deviceName": "jsfb-023486",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-6FF12E",
- "nodeId": "jsfb-6FF12E",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-6FF12E",
- "deviceName": "jsfb-6FF12E",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-62B336",
- "nodeId": "jsfb-62B336",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-62B336",
- "deviceName": "jsfb-62B336",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-F706AD",
- "nodeId": "jsfb-F706AD",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-F706AD",
- "deviceName": "jsfb-F706AD",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-50441C",
- "nodeId": "jsfb-50441C",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-50441C",
- "deviceName": "jsfb-50441C",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-B25A00",
- "nodeId": "jsfb-B25A00",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-B25A00",
- "deviceName": "jsfb-B25A00",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-DCF894",
- "nodeId": "jsfb-DCF894",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-DCF894",
- "deviceName": "jsfb-DCF894",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-6F0F92",
- "nodeId": "jsfb-6F0F92",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-6F0F92",
- "deviceName": "jsfb-6F0F92",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-5A1B1E",
- "nodeId": "jsfb-5A1B1E",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-5A1B1E",
- "deviceName": "jsfb-5A1B1E",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-1DB8E2",
- "nodeId": "jsfb-1DB8E2",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-1DB8E2",
- "deviceName": "jsfb-1DB8E2",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-FD7C6B",
- "nodeId": "jsfb-FD7C6B",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-FD7C6B",
- "deviceName": "jsfb-FD7C6B",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-3554A0",
- "nodeId": "jsfb-3554A0",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-3554A0",
- "deviceName": "jsfb-3554A0",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-BD51A6",
- "nodeId": "jsfb-BD51A6",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-BD51A6",
- "deviceName": "jsfb-BD51A6",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-98BF09",
- "nodeId": "jsfb-98BF09",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-98BF09",
- "deviceName": "jsfb-98BF09",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-B6A9C3",
- "nodeId": "jsfb-B6A9C3",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-B6A9C3",
- "deviceName": "jsfb-B6A9C3",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-EBF1D8",
- "nodeId": "jsfb-EBF1D8",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-EBF1D8",
- "deviceName": "jsfb-EBF1D8",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-65024A",
- "nodeId": "jsfb-65024A",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-65024A",
- "deviceName": "jsfb-65024A",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-7FC7DD",
- "nodeId": "jsfb-7FC7DD",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-7FC7DD",
- "deviceName": "jsfb-7FC7DD",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-7E9B30",
- "nodeId": "jsfb-7E9B30",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-7E9B30",
- "deviceName": "jsfb-7E9B30",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-56A928",
- "nodeId": "jsfb-56A928",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-56A928",
- "deviceName": "jsfb-56A928",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-F8843B",
- "nodeId": "jsfb-F8843B",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-F8843B",
- "deviceName": "jsfb-F8843B",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-8AA2BD",
- "nodeId": "jsfb-8AA2BD",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-8AA2BD",
- "deviceName": "jsfb-8AA2BD",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-C17E68",
- "nodeId": "jsfb-C17E68",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-C17E68",
- "deviceName": "jsfb-C17E68",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-7BE292",
- "nodeId": "jsfb-7BE292",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-7BE292",
- "deviceName": "jsfb-7BE292",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-6A1F29",
- "nodeId": "jsfb-6A1F29",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-6A1F29",
- "deviceName": "jsfb-6A1F29",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-B75756",
- "nodeId": "jsfb-B75756",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-B75756",
- "deviceName": "jsfb-B75756",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-69B0D2",
- "nodeId": "jsfb-69B0D2",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-69B0D2",
- "deviceName": "jsfb-69B0D2",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-D120B0",
- "nodeId": "jsfb-D120B0",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-D120B0",
- "deviceName": "jsfb-D120B0",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-CA0D7A",
- "nodeId": "jsfb-CA0D7A",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-CA0D7A",
- "deviceName": "jsfb-CA0D7A",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-89122A",
- "nodeId": "jsfb-89122A",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-89122A",
- "deviceName": "jsfb-89122A",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-31464E",
- "nodeId": "jsfb-31464E",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-31464E",
- "deviceName": "jsfb-31464E",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-2A1577",
- "nodeId": "jsfb-2A1577",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-2A1577",
- "deviceName": "jsfb-2A1577",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-1EC625",
- "nodeId": "jsfb-1EC625",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-1EC625",
- "deviceName": "jsfb-1EC625",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-36350C",
- "nodeId": "jsfb-36350C",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-36350C",
- "deviceName": "jsfb-36350C",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-F29258",
- "nodeId": "jsfb-F29258",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-F29258",
- "deviceName": "jsfb-F29258",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-8942CE",
- "nodeId": "jsfb-8942CE",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-8942CE",
- "deviceName": "jsfb-8942CE",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-B1398A",
- "nodeId": "jsfb-B1398A",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-B1398A",
- "deviceName": "jsfb-B1398A",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-465F0C",
- "nodeId": "jsfb-465F0C",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-465F0C",
- "deviceName": "jsfb-465F0C",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-E169C2",
- "nodeId": "jsfb-E169C2",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-E169C2",
- "deviceName": "jsfb-E169C2",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-E82E16",
- "nodeId": "jsfb-E82E16",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-E82E16",
- "deviceName": "jsfb-E82E16",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-6FA0C5",
- "nodeId": "jsfb-6FA0C5",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-6FA0C5",
- "deviceName": "jsfb-6FA0C5",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-FC04F2",
- "nodeId": "jsfb-FC04F2",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-FC04F2",
- "deviceName": "jsfb-FC04F2",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-C5D5F4",
- "nodeId": "jsfb-C5D5F4",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-C5D5F4",
- "deviceName": "jsfb-C5D5F4",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-B96E18",
- "nodeId": "jsfb-B96E18",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-B96E18",
- "deviceName": "jsfb-B96E18",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-37285C",
- "nodeId": "jsfb-37285C",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-37285C",
- "deviceName": "jsfb-37285C",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-00A874",
- "nodeId": "jsfb-00A874",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-00A874",
- "deviceName": "jsfb-00A874",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-7D2A0B",
- "nodeId": "jsfb-7D2A0B",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-7D2A0B",
- "deviceName": "jsfb-7D2A0B",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-69681C",
- "nodeId": "jsfb-69681C",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-69681C",
- "deviceName": "jsfb-69681C",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- },
- {
- "appId": "9782371cee894c0abbecb30efe4e7cd0",
- "appName": "DefaultApp_6736wwjm",
- "deviceId": "673602f0d14760402fbd033b_jsfb-6D10F3",
- "nodeId": "jsfb-6D10F3",
- "gatewayId": "673602f0d14760402fbd033b_jsfb-6D10F3",
- "deviceName": "jsfb-6D10F3",
- "nodeType": "GATEWAY",
- "productId": "673602f0d14760402fbd033b",
- "productName": "RS-LL-X1",
- "status": "INACTIVE",
- "tags": []
- }
- ],
- "page": {
- "count": 227,
- "marker": "6881dce05ebba32371ca42f6"
- },
- "httpStatusCode": 200
-}
diff --git a/storm-device/src/test/java/com/storm/device/3.json b/storm-device/src/test/java/com/storm/device/3.json
deleted file mode 100644
index 9661f54..0000000
--- a/storm-device/src/test/java/com/storm/device/3.json
+++ /dev/null
@@ -1,62 +0,0 @@
-{
- online_time=20250829T113156Z,
- latitude=112.541115,
- software_version=ioT,
- vtxdb_version=1.1.9-beta-ansun,
- status=ONLINE,
- longitude=28.280514
-}
-
-[
- class
- DeviceShadowData
- {
- serviceId: ExpectedDelivery
- desired: class
- DeviceShadowProperties
- {
- properties: null
- eventTime: 20250830T014623Z
- }
- reported
- :
- class
- DeviceShadowProperties
- {
- properties: null
- eventTime: null
- }
- version
- :
- 750
- },
- class
- DeviceShadowData
- {
- serviceId: VortXDB
- desired: class
- DeviceShadowProperties
- {
- properties: null
- eventTime: null
- }
- reported
- :
- class
- DeviceShadowProperties
- {
- properties: {
- online_time=20250829T113156Z,
- latitude=112.541115,
- software_version=ioT,
- vtxdb_version=1.1.9-beta-ansun,
- status=ONLINE,
- longitude=28.280514
- }
- eventTime: 20250829T113156Z
- }
- version
- :
- 37
- }
-]
\ No newline at end of file
diff --git a/storm-device/src/test/java/com/storm/device/4.json b/storm-device/src/test/java/com/storm/device/4.json
deleted file mode 100644
index f19b2f8..0000000
--- a/storm-device/src/test/java/com/storm/device/4.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
- "pageNo": 1,
- "pageSize": 10,
- "isAsc": true,
- "sortBy": "id",
- "deviceNameParam": "",
- "statusParam": "",
- "softwareVersionParam": "",
- "vtxdbVersionParam": "",
- "beginTime": "",
- "endTime": ""
-}
\ No newline at end of file
diff --git a/storm-device/src/test/java/com/storm/device/5.json b/storm-device/src/test/java/com/storm/device/5.json
deleted file mode 100644
index 07bd6db..0000000
--- a/storm-device/src/test/java/com/storm/device/5.json
+++ /dev/null
@@ -1,3 +0,0 @@
-IotMsgNotifyData(header=IotMsgHeader(productId=673602f0d14760402fbd033b, deviceId=673602f0d14760402fbd033b_jsfb-DA, nodeId=jsfb-DA, appId=9782371cee894c0abbecb30efe4e7cd0, gatewayId=673602f0d14760402fbd033b_jsfb-DA), body=IotMsgBody(services=[IotMsgService(serviceId=VortXDB, properties={online_time=20250901T032819Z, latitude=112.541115, software_version=ioT, vtxdb_version=1.1.9-beta-ansun, status=ONLINE, longitude=28.280514}, eventTime=20250901T032819Z)]))
-
-IotMsgNotifyData(header=IotMsgHeader(productId=673602f0d14760402fbd033b, deviceId=673602f0d14760402fbd033b_jsfb-DA, nodeId=jsfb-DA, appId=9782371cee894c0abbecb30efe4e7cd0, gatewayId=673602f0d14760402fbd033b_jsfb-DA), body=IotMsgBody(services=[IotMsgService(serviceId=VortXDB, properties={online_time=20250901T041315Z, latitude=112.541115, software_version=ioT, vtxdb_version=1.1.9-beta-ansun, status=ONLINE, longitude=28.280514}, eventTime=20250901T041316Z)]))
diff --git a/storm-device/src/test/java/com/storm/device/IotTest.java b/storm-device/src/test/java/com/storm/device/IotTest.java
deleted file mode 100644
index 642650d..0000000
--- a/storm-device/src/test/java/com/storm/device/IotTest.java
+++ /dev/null
@@ -1,181 +0,0 @@
-package com.storm.device;
-
-import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.date.DatePattern;
-import cn.hutool.core.date.LocalDateTimeUtil;
-import cn.hutool.json.JSONObject;
-import cn.hutool.json.JSONUtil;
-import com.alibaba.nacos.common.utils.CollectionUtils;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.huaweicloud.sdk.iotda.v5.IoTDAClient;
-import com.huaweicloud.sdk.iotda.v5.model.*;
-import com.storm.common.core.exception.base.BaseException;
-import com.storm.common.core.utils.StringUtils;
-import com.storm.device.domain.po.Device;
-import com.storm.device.service.IDeviceService;
-import com.storm.device.service.impl.DeviceServiceImpl;
-import com.storm.device.service.impl.MassageTaskServiceImpl;
-import lombok.extern.slf4j.Slf4j;
-import org.junit.jupiter.api.Test;
-import org.springframework.boot.test.context.SpringBootTest;
-
-import javax.annotation.Resource;
-import java.time.LocalDateTime;
-import java.time.ZoneId;
-import java.time.ZoneOffset;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * @author DengAo
- * @date 2025/8/27 17:11
- */
-@Slf4j
-@SpringBootTest
-public class IotTest {
- @Resource
- private IoTDAClient client;
-
- @Resource
- private DeviceServiceImpl deviceService;
-
- @Test
- public void testSyncDevicesFromIoT() {
- List deviceList = new ArrayList<>();
- String marker = null;
- boolean hasMore = true;
-
- try {
- while (hasMore) {
- // 创建请求对象
- ListDevicesRequest request = new ListDevicesRequest();
-
- // 设置分页参数
- if (marker != null) {
- request.setMarker(marker);
- }
- request.setLimit(50); // 每页最大100条
-
- // 发起请求获取设备列表
- ListDevicesResponse response = client.listDevices(request);
-
- if (response.getHttpStatusCode() != 200) {
- throw new BaseException("物联网接口 - 查询设备列表失败,HTTP状态码: " + response.getHttpStatusCode());
- }
-
- log.info("50台设备信息:{}", JSONUtil.toJsonStr(response.getDevices()));
-
- // 处理返回的设备数据
- if (CollectionUtils.isNotEmpty(response.getDevices())) {
- for (QueryDeviceSimplify queryDeviceSimplify : response.getDevices()) {
- Device device = new Device();
- device.setDeviceId(queryDeviceSimplify.getDeviceId());
- device.setDeviceName(queryDeviceSimplify.getDeviceName());
- device.setProductId(queryDeviceSimplify.getProductId());
- device.setProductName(queryDeviceSimplify.getProductName());
-
- // 可以设置其他默认值
- device.setDeviceType(1); // 默认设备类型
- device.setIsDeleted(0); // 未删除
-
- deviceList.add(device);
- }
- }
-
- // 检查是否还有更多数据
- if (CollectionUtils.isNotEmpty(response.getDevices()) && StringUtils.isNotEmpty(response.getPage().getMarker())) {
- marker = response.getPage().getMarker();
- } else {
- hasMore = false;
- }
- // 休眠100毫秒
- Thread.sleep(100);
- }
-
- log.info("成功同步 {} 台设备信息", deviceList.size());
- } catch (Exception e) {
- log.error("同步设备列表失败: {}", e.getMessage(), e);
- throw new BaseException("同步设备列表失败: " + e.getMessage());
- }
- }
-
- @Test
- public void testSyncDevicesFromIoT2() {
- String marker = "675299a3f9024677dbd36a08";
- //请求参数
- ListDevicesRequest request = new ListDevicesRequest();
-
- //设置条数
- request.setLimit(50);
-
- request.setMarker(marker);
-
- //发起请求
- ListDevicesResponse response = client.listDevices(request);
- if (response.getHttpStatusCode() != 200) {
- throw new BaseException("物联网接口 - 查询设备,同步失败");
- }
-
- log.info("50台设备信息:{}", JSONUtil.toJsonStr(response.getDevices()));
-
- }
-
- @Test
- public void updateDeviceStatusLog() {
- //通过client 调用查询设备影子数据
- ShowDeviceShadowRequest request = new ShowDeviceShadowRequest();
- request.setDeviceId("673602f0d14760402fbd033b_jsfb-66E903");
- ShowDeviceShadowResponse response = client.showDeviceShadow(request);
-
- //解析数据
- List shadow = response.getShadow();
- if (CollUtil.isEmpty(shadow)) {
- log.error("设备影子数据为空");
- return;
- }
- JSONObject jsonObject = JSONUtil.parseObj(shadow.get(1).getReported().getProperties());
-
- String eventTimeStr = shadow.get(1).getReported().getEventTime();
- LocalDateTime time = otherLocalDateTime(eventTimeStr);
-
- //打印返回的结果
- log.info("设备影子数据:{}", jsonObject);
- System.out.println("--------------------------------------");
- log.info("设备影子数据时间:{}", time);
- // 打印shadow数据
- System.out.println("--------------------------------------");
- log.info("设备影子数据:{}", shadow);
- System.out.println("--------------------------------------");
- LocalDateTime onlineTime = otherLocalDateTime(jsonObject.get("online_time", String.class));
- log.info("设备影子online_time时间:{}", onlineTime);
-
- }
-
- /**
- * 测试百度地图API获取地址
- */
- @Test
- public void testGetLocation() {
- String location = deviceService.getLocationFromCoordinates(22.624244620774185, 114.03790755307914);
- log.info("设备地址:{}", location);
- }
-
- public LocalDateTime toLocalDateTime(String activeTime) {
- LocalDateTime time = LocalDateTimeUtil.parse(activeTime, DatePattern.UTC_MS_PATTERN);
- time = time.atZone(ZoneId.from(ZoneOffset.UTC))
- .withZoneSameInstant(ZoneId.of("Asia/Shanghai"))
- .toLocalDateTime();
- return time;
- }
-
- public LocalDateTime otherLocalDateTime(String activeTime) {
- LocalDateTime time = LocalDateTimeUtil.parse(activeTime, "yyyyMMdd'T'HHmmss'Z'");
- time = time.atZone(ZoneId.from(ZoneOffset.UTC))
- .withZoneSameInstant(ZoneId.of("Asia/Shanghai"))
- .toLocalDateTime();
- return time;
- }
-
-}