修正部分业务逻辑

This commit is contained in:
Alatus Lee 2025-10-05 16:36:05 +08:00
parent deafe6dc63
commit 7815cac8a5
54 changed files with 4257 additions and 1935 deletions

View File

@ -306,12 +306,6 @@
<version>${hutool-all.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${springboottest.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>

View File

@ -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);
KEY idx_device_id (device_id),
KEY idx_create_time (create_time)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='设备按头使用统计表';

140
sql/storm-AfterSales.sql Normal file
View File

@ -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='工单知识库关联表';

111
storm-afterSales/pom.xml Normal file
View File

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.storm</groupId>
<artifactId>storm</artifactId>
<version>3.6.6</version>
</parent>
<artifactId>storm-afterSales</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>storm-afterSales</name>
<description>storm-afterSales工单管理</description>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2.2.6.RELEASE</version>
</dependency>
<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- storm Common -->
<dependency>
<groupId>com.storm</groupId>
<artifactId>storm-common-security</artifactId>
</dependency>
<dependency>
<groupId>com.storm</groupId>
<artifactId>storm-common-redis</artifactId>
</dependency>
<dependency>
<groupId>com.storm</groupId>
<artifactId>storm-common-datascope</artifactId>
</dependency>
<dependency>
<groupId>com.storm</groupId>
<artifactId>storm-api-system</artifactId>
</dependency>
<!-- Mysql Connector -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- storm Common Log -->
<dependency>
<groupId>com.storm</groupId>
<artifactId>storm-common-log</artifactId>
</dependency>
<!-- storm Common Swagger -->
<dependency>
<groupId>com.storm</groupId>
<artifactId>storm-common-swagger</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>com.storm</groupId>
<artifactId>storm-common-oss</artifactId>
<version>3.6.6</version>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -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);
}
}

View File

@ -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<SysKnowledgeRel>> list(@Parameter(description = "工单知识库关联查询条件") SysKnowledgeRel sysKnowledgeRel)
{
startPage();
List<SysKnowledgeRel> 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<SysKnowledgeRel> list = sysKnowledgeRelService.selectSysKnowledgeRelList(sysKnowledgeRel);
ExcelUtil<SysKnowledgeRel> util = new ExcelUtil<SysKnowledgeRel>(SysKnowledgeRel.class);
util.exportExcel(response, list, "工单知识库关联数据");
}
/**
* 获取工单知识库关联详细信息
*/
@RequiresPermissions("afterSales:afterSales:query")
@GetMapping(value = "/{id}")
@Operation(summary = "获取工单知识库关联详细信息")
public R<SysKnowledgeRel> 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));
}
}

View File

@ -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<SysWorkOrderChatRel>> list(@Parameter(description = "工单聊天关联查询条件") SysWorkOrderChatRel sysWorkOrderChatRel)
{
startPage();
List<SysWorkOrderChatRel> 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<SysWorkOrderChatRel> list = sysWorkOrderChatRelService.selectSysWorkOrderChatRelList(sysWorkOrderChatRel);
ExcelUtil<SysWorkOrderChatRel> util = new ExcelUtil<SysWorkOrderChatRel>(SysWorkOrderChatRel.class);
util.exportExcel(response, list, "工单聊天关联数据");
}
/**
* 获取工单聊天关联详细信息
*/
@RequiresPermissions("afterSales:afterSales:query")
@GetMapping(value = "/{id}")
@Operation(summary = "获取工单聊天关联详细信息")
public R<SysWorkOrderChatRel> 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));
}
}

View File

@ -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<SysWorkOrder>> list(@Parameter(description = "工单主查询条件") SysWorkOrder sysWorkOrder)
{
startPage();
List<SysWorkOrder> 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<SysWorkOrder> list = sysWorkOrderService.selectSysWorkOrderList(sysWorkOrder);
ExcelUtil<SysWorkOrder> util = new ExcelUtil<SysWorkOrder>(SysWorkOrder.class);
util.exportExcel(response, list, "工单主数据");
}
/**
* 获取工单主详细信息
*/
@RequiresPermissions("afterSales:afterSales:query")
@GetMapping(value = "/{orderId}")
@Operation(summary = "获取工单主详细信息")
public R<SysWorkOrder> 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));
}
}

View File

@ -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<SysWorkOrderRecord>> list(@Parameter(description = "工单处理记录查询条件") SysWorkOrderRecord sysWorkOrderRecord)
{
startPage();
List<SysWorkOrderRecord> 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<SysWorkOrderRecord> list = sysWorkOrderRecordService.selectSysWorkOrderRecordList(sysWorkOrderRecord);
ExcelUtil<SysWorkOrderRecord> util = new ExcelUtil<SysWorkOrderRecord>(SysWorkOrderRecord.class);
util.exportExcel(response, list, "工单处理记录数据");
}
/**
* 获取工单处理记录详细信息
*/
@RequiresPermissions("afterSales:afterSales:query")
@GetMapping(value = "/{recordId}")
@Operation(summary = "获取工单处理记录详细信息")
public R<SysWorkOrderRecord> 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));
}
}

View File

@ -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<SysWorkOrderType>> list(@Parameter(description = "工单类型查询条件") SysWorkOrderType sysWorkOrderType)
{
startPage();
List<SysWorkOrderType> 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<SysWorkOrderType> list = sysWorkOrderTypeService.selectSysWorkOrderTypeList(sysWorkOrderType);
ExcelUtil<SysWorkOrderType> util = new ExcelUtil<SysWorkOrderType>(SysWorkOrderType.class);
util.exportExcel(response, list, "工单类型数据");
}
/**
* 获取工单类型详细信息
*/
@RequiresPermissions("afterSales:type:query")
@GetMapping(value = "/{typeId}")
@Operation(summary = "获取工单类型详细信息")
public R<SysWorkOrderType> 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));
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<SysKnowledgeRel>
{
/**
* 查询工单知识库关联
*
* @param id 工单知识库关联主键
* @return 工单知识库关联
*/
public SysKnowledgeRel selectSysKnowledgeRelById(Long id);
/**
* 查询工单知识库关联列表
*
* @param sysKnowledgeRel 工单知识库关联
* @return 工单知识库关联集合
*/
public List<SysKnowledgeRel> 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);
}

View File

@ -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<SysWorkOrderChatRel>
{
/**
* 查询工单聊天关联
*
* @param id 工单聊天关联主键
* @return 工单聊天关联
*/
public SysWorkOrderChatRel selectSysWorkOrderChatRelById(Long id);
/**
* 查询工单聊天关联列表
*
* @param sysWorkOrderChatRel 工单聊天关联
* @return 工单聊天关联集合
*/
public List<SysWorkOrderChatRel> 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);
}

View File

@ -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<SysWorkOrder>
{
/**
* 查询工单主
*
* @param orderId 工单主主键
* @return 工单主
*/
public SysWorkOrder selectSysWorkOrderByOrderId(Long orderId);
/**
* 查询工单主列表
*
* @param sysWorkOrder 工单主
* @return 工单主集合
*/
public List<SysWorkOrder> 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);
}

View File

@ -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<SysWorkOrderRecord>
{
/**
* 查询工单处理记录
*
* @param recordId 工单处理记录主键
* @return 工单处理记录
*/
public SysWorkOrderRecord selectSysWorkOrderRecordByRecordId(Long recordId);
/**
* 查询工单处理记录列表
*
* @param sysWorkOrderRecord 工单处理记录
* @return 工单处理记录集合
*/
public List<SysWorkOrderRecord> 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);
}

View File

@ -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<SysWorkOrderType>
{
/**
* 查询工单类型
*
* @param typeId 工单类型主键
* @return 工单类型
*/
public SysWorkOrderType selectSysWorkOrderTypeByTypeId(Long typeId);
/**
* 查询工单类型列表
*
* @param sysWorkOrderType 工单类型
* @return 工单类型集合
*/
public List<SysWorkOrderType> 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);
}

View File

@ -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<SysKnowledgeRel>
{
/**
* 查询工单知识库关联
*
* @param id 工单知识库关联主键
* @return 工单知识库关联
*/
public SysKnowledgeRel selectSysKnowledgeRelById(Long id);
/**
* 查询工单知识库关联列表
*
* @param sysKnowledgeRel 工单知识库关联
* @return 工单知识库关联集合
*/
public List<SysKnowledgeRel> 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);
}

View File

@ -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<SysWorkOrderChatRel>
{
/**
* 查询工单聊天关联
*
* @param id 工单聊天关联主键
* @return 工单聊天关联
*/
public SysWorkOrderChatRel selectSysWorkOrderChatRelById(Long id);
/**
* 查询工单聊天关联列表
*
* @param sysWorkOrderChatRel 工单聊天关联
* @return 工单聊天关联集合
*/
public List<SysWorkOrderChatRel> 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);
}

View File

@ -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<SysWorkOrderRecord>
{
/**
* 查询工单处理记录
*
* @param recordId 工单处理记录主键
* @return 工单处理记录
*/
public SysWorkOrderRecord selectSysWorkOrderRecordByRecordId(Long recordId);
/**
* 查询工单处理记录列表
*
* @param sysWorkOrderRecord 工单处理记录
* @return 工单处理记录集合
*/
public List<SysWorkOrderRecord> 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);
}

View File

@ -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<SysWorkOrder>
{
/**
* 查询工单主
*
* @param orderId 工单主主键
* @return 工单主
*/
public SysWorkOrder selectSysWorkOrderByOrderId(Long orderId);
/**
* 查询工单主列表
*
* @param sysWorkOrder 工单主
* @return 工单主集合
*/
public List<SysWorkOrder> 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);
}

View File

@ -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<SysWorkOrderType>
{
/**
* 查询工单类型
*
* @param typeId 工单类型主键
* @return 工单类型
*/
public SysWorkOrderType selectSysWorkOrderTypeByTypeId(Long typeId);
/**
* 查询工单类型列表
*
* @param sysWorkOrderType 工单类型
* @return 工单类型集合
*/
public List<SysWorkOrderType> 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);
}

View File

@ -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<SysKnowledgeRelMapper, SysKnowledgeRel> implements ISysKnowledgeRelService
{
@Autowired
private SysKnowledgeRelMapper sysKnowledgeRelMapper;
/**
* 查询工单知识库关联
*
* @param id 工单知识库关联主键
* @return 工单知识库关联
*/
@Override
public SysKnowledgeRel selectSysKnowledgeRelById(Long id)
{
return getById(id);
}
/**
* 查询工单知识库关联列表
*
* @param sysKnowledgeRel 工单知识库关联
* @return 工单知识库关联
*/
@Override
public List<SysKnowledgeRel> 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;
}
}

View File

@ -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<SysWorkOrderChatRelMapper, SysWorkOrderChatRel> implements ISysWorkOrderChatRelService
{
@Autowired
private SysWorkOrderChatRelMapper sysWorkOrderChatRelMapper;
/**
* 查询工单聊天关联
*
* @param id 工单聊天关联主键
* @return 工单聊天关联
*/
@Override
public SysWorkOrderChatRel selectSysWorkOrderChatRelById(Long id)
{
return getById(id);
}
/**
* 查询工单聊天关联列表
*
* @param sysWorkOrderChatRel 工单聊天关联
* @return 工单聊天关联
*/
@Override
public List<SysWorkOrderChatRel> 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;
}
}

View File

@ -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<SysWorkOrderRecordMapper, SysWorkOrderRecord> implements ISysWorkOrderRecordService
{
@Autowired
private SysWorkOrderRecordMapper sysWorkOrderRecordMapper;
/**
* 查询工单处理记录
*
* @param recordId 工单处理记录主键
* @return 工单处理记录
*/
@Override
public SysWorkOrderRecord selectSysWorkOrderRecordByRecordId(Long recordId)
{
return getById(recordId);
}
/**
* 查询工单处理记录列表
*
* @param sysWorkOrderRecord 工单处理记录
* @return 工单处理记录
*/
@Override
public List<SysWorkOrderRecord> 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;
}
}

View File

@ -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<SysWorkOrderMapper, SysWorkOrder> implements ISysWorkOrderService
{
@Autowired
private SysWorkOrderMapper sysWorkOrderMapper;
/**
* 查询工单主
*
* @param orderId 工单主主键
* @return 工单主
*/
@Override
public SysWorkOrder selectSysWorkOrderByOrderId(Long orderId)
{
return getById(orderId);
}
/**
* 查询工单主列表
*
* @param sysWorkOrder 工单主
* @return 工单主
*/
@Override
public List<SysWorkOrder> 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;
}
}

View File

@ -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<SysWorkOrderTypeMapper, SysWorkOrderType> implements ISysWorkOrderTypeService
{
@Autowired
private SysWorkOrderTypeMapper sysWorkOrderTypeMapper;
/**
* 查询工单类型
*
* @param typeId 工单类型主键
* @return 工单类型
*/
@Override
public SysWorkOrderType selectSysWorkOrderTypeByTypeId(Long typeId)
{
return getById(typeId);
}
/**
* 查询工单类型列表
*
* @param sysWorkOrderType 工单类型
* @return 工单类型
*/
@Override
public List<SysWorkOrderType> 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;
}
}

View File

@ -0,0 +1 @@
spring.application.name=storm-afterSales

View File

@ -0,0 +1,75 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.storm.afterSales.mapper.SysKnowledgeRelMapper">
<resultMap type="SysKnowledgeRel" id="SysKnowledgeRelResult">
<result property="id" column="id" />
<result property="orderId" column="order_id" />
<result property="knowledgeId" column="knowledge_id" />
<result property="knowledgeTitle" column="knowledge_title" />
<result property="usedCount" column="used_count" />
<result property="createTime" column="create_time" />
</resultMap>
<sql id="selectSysKnowledgeRelVo">
select id, order_id, knowledge_id, knowledge_title, used_count, create_time from sys_knowledge_rel
</sql>
<select id="selectSysKnowledgeRelList" parameterType="SysKnowledgeRel" resultMap="SysKnowledgeRelResult">
<include refid="selectSysKnowledgeRelVo"/>
<where>
<if test="orderId != null "> and order_id = #{orderId}</if>
<if test="knowledgeId != null "> and knowledge_id = #{knowledgeId}</if>
<if test="knowledgeTitle != null and knowledgeTitle != ''"> and knowledge_title = #{knowledgeTitle}</if>
<if test="usedCount != null "> and used_count = #{usedCount}</if>
</where>
</select>
<select id="selectSysKnowledgeRelById" parameterType="Long" resultMap="SysKnowledgeRelResult">
<include refid="selectSysKnowledgeRelVo"/>
where id = #{id}
</select>
<insert id="insertSysKnowledgeRel" parameterType="SysKnowledgeRel" useGeneratedKeys="true" keyProperty="id">
insert into sys_knowledge_rel
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="orderId != null">order_id,</if>
<if test="knowledgeId != null">knowledge_id,</if>
<if test="knowledgeTitle != null">knowledge_title,</if>
<if test="usedCount != null">used_count,</if>
<if test="createTime != null">create_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="orderId != null">#{orderId},</if>
<if test="knowledgeId != null">#{knowledgeId},</if>
<if test="knowledgeTitle != null">#{knowledgeTitle},</if>
<if test="usedCount != null">#{usedCount},</if>
<if test="createTime != null">#{createTime},</if>
</trim>
</insert>
<update id="updateSysKnowledgeRel" parameterType="SysKnowledgeRel">
update sys_knowledge_rel
<trim prefix="SET" suffixOverrides=",">
<if test="orderId != null">order_id = #{orderId},</if>
<if test="knowledgeId != null">knowledge_id = #{knowledgeId},</if>
<if test="knowledgeTitle != null">knowledge_title = #{knowledgeTitle},</if>
<if test="usedCount != null">used_count = #{usedCount},</if>
<if test="createTime != null">create_time = #{createTime},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteSysKnowledgeRelById" parameterType="Long">
delete from sys_knowledge_rel where id = #{id}
</delete>
<delete id="deleteSysKnowledgeRelByIds" parameterType="String">
delete from sys_knowledge_rel where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.storm.afterSales.mapper.SysWorkOrderChatRelMapper">
<resultMap type="SysWorkOrderChatRel" id="SysWorkOrderChatRelResult">
<result property="id" column="id" />
<result property="orderId" column="order_id" />
<result property="chatSessionId" column="chat_session_id" />
<result property="chatPlatform" column="chat_platform" />
<result property="messageId" column="message_id" />
<result property="chatContent" column="chat_content" />
<result property="chatDuration" column="chat_duration" />
<result property="createTime" column="create_time" />
</resultMap>
<sql id="selectSysWorkOrderChatRelVo">
select id, order_id, chat_session_id, chat_platform, message_id, chat_content, chat_duration, create_time from sys_work_order_chat_rel
</sql>
<select id="selectSysWorkOrderChatRelList" parameterType="SysWorkOrderChatRel" resultMap="SysWorkOrderChatRelResult">
<include refid="selectSysWorkOrderChatRelVo"/>
<where>
<if test="orderId != null "> and order_id = #{orderId}</if>
<if test="chatSessionId != null and chatSessionId != ''"> and chat_session_id = #{chatSessionId}</if>
<if test="chatPlatform != null and chatPlatform != ''"> and chat_platform = #{chatPlatform}</if>
<if test="messageId != null and messageId != ''"> and message_id = #{messageId}</if>
<if test="chatContent != null and chatContent != ''"> and chat_content = #{chatContent}</if>
<if test="chatDuration != null "> and chat_duration = #{chatDuration}</if>
</where>
</select>
<select id="selectSysWorkOrderChatRelById" parameterType="Long" resultMap="SysWorkOrderChatRelResult">
<include refid="selectSysWorkOrderChatRelVo"/>
where id = #{id}
</select>
<insert id="insertSysWorkOrderChatRel" parameterType="SysWorkOrderChatRel" useGeneratedKeys="true" keyProperty="id">
insert into sys_work_order_chat_rel
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="orderId != null">order_id,</if>
<if test="chatSessionId != null">chat_session_id,</if>
<if test="chatPlatform != null">chat_platform,</if>
<if test="messageId != null">message_id,</if>
<if test="chatContent != null">chat_content,</if>
<if test="chatDuration != null">chat_duration,</if>
<if test="createTime != null">create_time,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="orderId != null">#{orderId},</if>
<if test="chatSessionId != null">#{chatSessionId},</if>
<if test="chatPlatform != null">#{chatPlatform},</if>
<if test="messageId != null">#{messageId},</if>
<if test="chatContent != null">#{chatContent},</if>
<if test="chatDuration != null">#{chatDuration},</if>
<if test="createTime != null">#{createTime},</if>
</trim>
</insert>
<update id="updateSysWorkOrderChatRel" parameterType="SysWorkOrderChatRel">
update sys_work_order_chat_rel
<trim prefix="SET" suffixOverrides=",">
<if test="orderId != null">order_id = #{orderId},</if>
<if test="chatSessionId != null">chat_session_id = #{chatSessionId},</if>
<if test="chatPlatform != null">chat_platform = #{chatPlatform},</if>
<if test="messageId != null">message_id = #{messageId},</if>
<if test="chatContent != null">chat_content = #{chatContent},</if>
<if test="chatDuration != null">chat_duration = #{chatDuration},</if>
<if test="createTime != null">create_time = #{createTime},</if>
</trim>
where id = #{id}
</update>
<delete id="deleteSysWorkOrderChatRelById" parameterType="Long">
delete from sys_work_order_chat_rel where id = #{id}
</delete>
<delete id="deleteSysWorkOrderChatRelByIds" parameterType="String">
delete from sys_work_order_chat_rel where id in
<foreach item="id" collection="array" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,241 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.storm.afterSales.mapper.SysWorkOrderMapper">
<resultMap type="SysWorkOrder" id="SysWorkOrderResult">
<result property="orderId" column="order_id" />
<result property="orderNo" column="order_no" />
<result property="orderTitle" column="order_title" />
<result property="orderType" column="order_type" />
<result property="orderStatus" column="order_status" />
<result property="priority" column="priority" />
<result property="customerId" column="customer_id" />
<result property="customerName" column="customer_name" />
<result property="customerContact" column="customer_contact" />
<result property="storeAddress" column="store_address" />
<result property="deviceName" column="device_name" />
<result property="deviceSerialNo" column="device_serial_no" />
<result property="deviceModel" column="device_model" />
<result property="productionDate" column="production_date" />
<result property="problemDescription" column="problem_description" />
<result property="attachmentUrls" column="attachment_urls" />
<result property="problemCategory" column="problem_category" />
<result property="assigneeId" column="assignee_id" />
<result property="assigneeName" column="assignee_name" />
<result property="assignTime" column="assign_time" />
<result property="currentHandlerId" column="current_handler_id" />
<result property="currentHandlerName" column="current_handler_name" />
<result property="rootCause" column="root_cause" />
<result property="solutionPlan" column="solution_plan" />
<result property="finalSolution" column="final_solution" />
<result property="logisticsNumber" column="logistics_number" />
<result property="logisticsProgress" column="logistics_progress" />
<result property="customerFeedback" column="customer_feedback" />
<result property="satisfactionLevel" column="satisfaction_level" />
<result property="feedbackType" column="feedback_type" />
<result property="planFinishTime" column="plan_finish_time" />
<result property="actualFinishTime" column="actual_finish_time" />
<result property="responseDeadline" column="response_deadline" />
<result property="sourceChannel" column="source_channel" />
<result property="feishuChatId" column="feishu_chat_id" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectSysWorkOrderVo">
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
</sql>
<select id="selectSysWorkOrderList" parameterType="SysWorkOrder" resultMap="SysWorkOrderResult">
<include refid="selectSysWorkOrderVo"/>
<where>
<if test="orderNo != null and orderNo != ''"> and order_no = #{orderNo}</if>
<if test="orderTitle != null and orderTitle != ''"> and order_title = #{orderTitle}</if>
<if test="orderType != null and orderType != ''"> and order_type = #{orderType}</if>
<if test="orderStatus != null and orderStatus != ''"> and order_status = #{orderStatus}</if>
<if test="priority != null and priority != ''"> and priority = #{priority}</if>
<if test="customerId != null "> and customer_id = #{customerId}</if>
<if test="customerName != null and customerName != ''"> and customer_name like concat('%', #{customerName}, '%')</if>
<if test="customerContact != null and customerContact != ''"> and customer_contact = #{customerContact}</if>
<if test="storeAddress != null and storeAddress != ''"> and store_address = #{storeAddress}</if>
<if test="deviceName != null and deviceName != ''"> and device_name like concat('%', #{deviceName}, '%')</if>
<if test="deviceSerialNo != null and deviceSerialNo != ''"> and device_serial_no = #{deviceSerialNo}</if>
<if test="deviceModel != null and deviceModel != ''"> and device_model = #{deviceModel}</if>
<if test="productionDate != null "> and production_date = #{productionDate}</if>
<if test="problemDescription != null and problemDescription != ''"> and problem_description = #{problemDescription}</if>
<if test="attachmentUrls != null and attachmentUrls != ''"> and attachment_urls = #{attachmentUrls}</if>
<if test="problemCategory != null and problemCategory != ''"> and problem_category = #{problemCategory}</if>
<if test="assigneeId != null "> and assignee_id = #{assigneeId}</if>
<if test="assigneeName != null and assigneeName != ''"> and assignee_name like concat('%', #{assigneeName}, '%')</if>
<if test="assignTime != null "> and assign_time = #{assignTime}</if>
<if test="currentHandlerId != null "> and current_handler_id = #{currentHandlerId}</if>
<if test="currentHandlerName != null and currentHandlerName != ''"> and current_handler_name like concat('%', #{currentHandlerName}, '%')</if>
<if test="rootCause != null and rootCause != ''"> and root_cause = #{rootCause}</if>
<if test="solutionPlan != null and solutionPlan != ''"> and solution_plan = #{solutionPlan}</if>
<if test="finalSolution != null and finalSolution != ''"> and final_solution = #{finalSolution}</if>
<if test="logisticsNumber != null and logisticsNumber != ''"> and logistics_number = #{logisticsNumber}</if>
<if test="logisticsProgress != null and logisticsProgress != ''"> and logistics_progress = #{logisticsProgress}</if>
<if test="customerFeedback != null and customerFeedback != ''"> and customer_feedback = #{customerFeedback}</if>
<if test="satisfactionLevel != null "> and satisfaction_level = #{satisfactionLevel}</if>
<if test="feedbackType != null and feedbackType != ''"> and feedback_type = #{feedbackType}</if>
<if test="planFinishTime != null "> and plan_finish_time = #{planFinishTime}</if>
<if test="actualFinishTime != null "> and actual_finish_time = #{actualFinishTime}</if>
<if test="responseDeadline != null "> and response_deadline = #{responseDeadline}</if>
<if test="sourceChannel != null and sourceChannel != ''"> and source_channel = #{sourceChannel}</if>
<if test="feishuChatId != null and feishuChatId != ''"> and feishu_chat_id = #{feishuChatId}</if>
</where>
</select>
<select id="selectSysWorkOrderByOrderId" parameterType="Long" resultMap="SysWorkOrderResult">
<include refid="selectSysWorkOrderVo"/>
where order_id = #{orderId}
</select>
<insert id="insertSysWorkOrder" parameterType="SysWorkOrder" useGeneratedKeys="true" keyProperty="orderId">
insert into sys_work_order
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="orderNo != null and orderNo != ''">order_no,</if>
<if test="orderTitle != null and orderTitle != ''">order_title,</if>
<if test="orderType != null">order_type,</if>
<if test="orderStatus != null">order_status,</if>
<if test="priority != null">priority,</if>
<if test="customerId != null">customer_id,</if>
<if test="customerName != null">customer_name,</if>
<if test="customerContact != null">customer_contact,</if>
<if test="storeAddress != null">store_address,</if>
<if test="deviceName != null">device_name,</if>
<if test="deviceSerialNo != null">device_serial_no,</if>
<if test="deviceModel != null">device_model,</if>
<if test="productionDate != null">production_date,</if>
<if test="problemDescription != null">problem_description,</if>
<if test="attachmentUrls != null">attachment_urls,</if>
<if test="problemCategory != null">problem_category,</if>
<if test="assigneeId != null">assignee_id,</if>
<if test="assigneeName != null">assignee_name,</if>
<if test="assignTime != null">assign_time,</if>
<if test="currentHandlerId != null">current_handler_id,</if>
<if test="currentHandlerName != null">current_handler_name,</if>
<if test="rootCause != null">root_cause,</if>
<if test="solutionPlan != null">solution_plan,</if>
<if test="finalSolution != null">final_solution,</if>
<if test="logisticsNumber != null">logistics_number,</if>
<if test="logisticsProgress != null">logistics_progress,</if>
<if test="customerFeedback != null">customer_feedback,</if>
<if test="satisfactionLevel != null">satisfaction_level,</if>
<if test="feedbackType != null">feedback_type,</if>
<if test="planFinishTime != null">plan_finish_time,</if>
<if test="actualFinishTime != null">actual_finish_time,</if>
<if test="responseDeadline != null">response_deadline,</if>
<if test="sourceChannel != null">source_channel,</if>
<if test="feishuChatId != null">feishu_chat_id,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="orderNo != null and orderNo != ''">#{orderNo},</if>
<if test="orderTitle != null and orderTitle != ''">#{orderTitle},</if>
<if test="orderType != null">#{orderType},</if>
<if test="orderStatus != null">#{orderStatus},</if>
<if test="priority != null">#{priority},</if>
<if test="customerId != null">#{customerId},</if>
<if test="customerName != null">#{customerName},</if>
<if test="customerContact != null">#{customerContact},</if>
<if test="storeAddress != null">#{storeAddress},</if>
<if test="deviceName != null">#{deviceName},</if>
<if test="deviceSerialNo != null">#{deviceSerialNo},</if>
<if test="deviceModel != null">#{deviceModel},</if>
<if test="productionDate != null">#{productionDate},</if>
<if test="problemDescription != null">#{problemDescription},</if>
<if test="attachmentUrls != null">#{attachmentUrls},</if>
<if test="problemCategory != null">#{problemCategory},</if>
<if test="assigneeId != null">#{assigneeId},</if>
<if test="assigneeName != null">#{assigneeName},</if>
<if test="assignTime != null">#{assignTime},</if>
<if test="currentHandlerId != null">#{currentHandlerId},</if>
<if test="currentHandlerName != null">#{currentHandlerName},</if>
<if test="rootCause != null">#{rootCause},</if>
<if test="solutionPlan != null">#{solutionPlan},</if>
<if test="finalSolution != null">#{finalSolution},</if>
<if test="logisticsNumber != null">#{logisticsNumber},</if>
<if test="logisticsProgress != null">#{logisticsProgress},</if>
<if test="customerFeedback != null">#{customerFeedback},</if>
<if test="satisfactionLevel != null">#{satisfactionLevel},</if>
<if test="feedbackType != null">#{feedbackType},</if>
<if test="planFinishTime != null">#{planFinishTime},</if>
<if test="actualFinishTime != null">#{actualFinishTime},</if>
<if test="responseDeadline != null">#{responseDeadline},</if>
<if test="sourceChannel != null">#{sourceChannel},</if>
<if test="feishuChatId != null">#{feishuChatId},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateSysWorkOrder" parameterType="SysWorkOrder">
update sys_work_order
<trim prefix="SET" suffixOverrides=",">
<if test="orderNo != null and orderNo != ''">order_no = #{orderNo},</if>
<if test="orderTitle != null and orderTitle != ''">order_title = #{orderTitle},</if>
<if test="orderType != null">order_type = #{orderType},</if>
<if test="orderStatus != null">order_status = #{orderStatus},</if>
<if test="priority != null">priority = #{priority},</if>
<if test="customerId != null">customer_id = #{customerId},</if>
<if test="customerName != null">customer_name = #{customerName},</if>
<if test="customerContact != null">customer_contact = #{customerContact},</if>
<if test="storeAddress != null">store_address = #{storeAddress},</if>
<if test="deviceName != null">device_name = #{deviceName},</if>
<if test="deviceSerialNo != null">device_serial_no = #{deviceSerialNo},</if>
<if test="deviceModel != null">device_model = #{deviceModel},</if>
<if test="productionDate != null">production_date = #{productionDate},</if>
<if test="problemDescription != null">problem_description = #{problemDescription},</if>
<if test="attachmentUrls != null">attachment_urls = #{attachmentUrls},</if>
<if test="problemCategory != null">problem_category = #{problemCategory},</if>
<if test="assigneeId != null">assignee_id = #{assigneeId},</if>
<if test="assigneeName != null">assignee_name = #{assigneeName},</if>
<if test="assignTime != null">assign_time = #{assignTime},</if>
<if test="currentHandlerId != null">current_handler_id = #{currentHandlerId},</if>
<if test="currentHandlerName != null">current_handler_name = #{currentHandlerName},</if>
<if test="rootCause != null">root_cause = #{rootCause},</if>
<if test="solutionPlan != null">solution_plan = #{solutionPlan},</if>
<if test="finalSolution != null">final_solution = #{finalSolution},</if>
<if test="logisticsNumber != null">logistics_number = #{logisticsNumber},</if>
<if test="logisticsProgress != null">logistics_progress = #{logisticsProgress},</if>
<if test="customerFeedback != null">customer_feedback = #{customerFeedback},</if>
<if test="satisfactionLevel != null">satisfaction_level = #{satisfactionLevel},</if>
<if test="feedbackType != null">feedback_type = #{feedbackType},</if>
<if test="planFinishTime != null">plan_finish_time = #{planFinishTime},</if>
<if test="actualFinishTime != null">actual_finish_time = #{actualFinishTime},</if>
<if test="responseDeadline != null">response_deadline = #{responseDeadline},</if>
<if test="sourceChannel != null">source_channel = #{sourceChannel},</if>
<if test="feishuChatId != null">feishu_chat_id = #{feishuChatId},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where order_id = #{orderId}
</update>
<delete id="deleteSysWorkOrderByOrderId" parameterType="Long">
delete from sys_work_order where order_id = #{orderId}
</delete>
<delete id="deleteSysWorkOrderByOrderIds" parameterType="String">
delete from sys_work_order where order_id in
<foreach item="orderId" collection="array" open="(" separator="," close=")">
#{orderId}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.storm.afterSales.mapper.SysWorkOrderRecordMapper">
<resultMap type="SysWorkOrderRecord" id="SysWorkOrderRecordResult">
<result property="recordId" column="record_id" />
<result property="orderId" column="order_id" />
<result property="processType" column="process_type" />
<result property="processContent" column="process_content" />
<result property="progressType" column="progress_type" />
<result property="progressValue" column="progress_value" />
<result property="progressPercent" column="progress_percent" />
<result property="processUserId" column="process_user_id" />
<result property="processUserName" column="process_user_name" />
<result property="processTime" column="process_time" />
<result property="attachmentUrl" column="attachment_url" />
</resultMap>
<sql id="selectSysWorkOrderRecordVo">
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
</sql>
<select id="selectSysWorkOrderRecordList" parameterType="SysWorkOrderRecord" resultMap="SysWorkOrderRecordResult">
<include refid="selectSysWorkOrderRecordVo"/>
<where>
<if test="orderId != null "> and order_id = #{orderId}</if>
<if test="processType != null and processType != ''"> and process_type = #{processType}</if>
<if test="processContent != null and processContent != ''"> and process_content = #{processContent}</if>
<if test="progressType != null and progressType != ''"> and progress_type = #{progressType}</if>
<if test="progressValue != null and progressValue != ''"> and progress_value = #{progressValue}</if>
<if test="progressPercent != null "> and progress_percent = #{progressPercent}</if>
<if test="processUserId != null "> and process_user_id = #{processUserId}</if>
<if test="processUserName != null and processUserName != ''"> and process_user_name like concat('%', #{processUserName}, '%')</if>
<if test="processTime != null "> and process_time = #{processTime}</if>
<if test="attachmentUrl != null and attachmentUrl != ''"> and attachment_url = #{attachmentUrl}</if>
</where>
</select>
<select id="selectSysWorkOrderRecordByRecordId" parameterType="Long" resultMap="SysWorkOrderRecordResult">
<include refid="selectSysWorkOrderRecordVo"/>
where record_id = #{recordId}
</select>
<insert id="insertSysWorkOrderRecord" parameterType="SysWorkOrderRecord" useGeneratedKeys="true" keyProperty="recordId">
insert into sys_work_order_record
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="orderId != null">order_id,</if>
<if test="processType != null">process_type,</if>
<if test="processContent != null">process_content,</if>
<if test="progressType != null">progress_type,</if>
<if test="progressValue != null">progress_value,</if>
<if test="progressPercent != null">progress_percent,</if>
<if test="processUserId != null">process_user_id,</if>
<if test="processUserName != null">process_user_name,</if>
<if test="processTime != null">process_time,</if>
<if test="attachmentUrl != null">attachment_url,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="orderId != null">#{orderId},</if>
<if test="processType != null">#{processType},</if>
<if test="processContent != null">#{processContent},</if>
<if test="progressType != null">#{progressType},</if>
<if test="progressValue != null">#{progressValue},</if>
<if test="progressPercent != null">#{progressPercent},</if>
<if test="processUserId != null">#{processUserId},</if>
<if test="processUserName != null">#{processUserName},</if>
<if test="processTime != null">#{processTime},</if>
<if test="attachmentUrl != null">#{attachmentUrl},</if>
</trim>
</insert>
<update id="updateSysWorkOrderRecord" parameterType="SysWorkOrderRecord">
update sys_work_order_record
<trim prefix="SET" suffixOverrides=",">
<if test="orderId != null">order_id = #{orderId},</if>
<if test="processType != null">process_type = #{processType},</if>
<if test="processContent != null">process_content = #{processContent},</if>
<if test="progressType != null">progress_type = #{progressType},</if>
<if test="progressValue != null">progress_value = #{progressValue},</if>
<if test="progressPercent != null">progress_percent = #{progressPercent},</if>
<if test="processUserId != null">process_user_id = #{processUserId},</if>
<if test="processUserName != null">process_user_name = #{processUserName},</if>
<if test="processTime != null">process_time = #{processTime},</if>
<if test="attachmentUrl != null">attachment_url = #{attachmentUrl},</if>
</trim>
where record_id = #{recordId}
</update>
<delete id="deleteSysWorkOrderRecordByRecordId" parameterType="Long">
delete from sys_work_order_record where record_id = #{recordId}
</delete>
<delete id="deleteSysWorkOrderRecordByRecordIds" parameterType="String">
delete from sys_work_order_record where record_id in
<foreach item="recordId" collection="array" open="(" separator="," close=")">
#{recordId}
</foreach>
</delete>
</mapper>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.storm.afterSales.mapper.SysWorkOrderTypeMapper">
<resultMap type="SysWorkOrderType" id="SysWorkOrderTypeResult">
<result property="typeId" column="type_id" />
<result property="typeName" column="type_name" />
<result property="typeKey" column="type_key" />
<result property="STATUS" column="STATUS" />
<result property="orderNum" column="order_num" />
<result property="createBy" column="create_by" />
<result property="createTime" column="create_time" />
<result property="updateBy" column="update_by" />
<result property="updateTime" column="update_time" />
<result property="remark" column="remark" />
</resultMap>
<sql id="selectSysWorkOrderTypeVo">
select type_id, type_name, type_key, STATUS, order_num, create_by, create_time, update_by, update_time, remark from sys_work_order_type
</sql>
<select id="selectSysWorkOrderTypeList" parameterType="SysWorkOrderType" resultMap="SysWorkOrderTypeResult">
<include refid="selectSysWorkOrderTypeVo"/>
<where>
<if test="typeName != null and typeName != ''"> and type_name like concat('%', #{typeName}, '%')</if>
<if test="typeKey != null and typeKey != ''"> and type_key = #{typeKey}</if>
<if test="STATUS != null and STATUS != ''"> and STATUS = #{STATUS}</if>
<if test="orderNum != null "> and order_num = #{orderNum}</if>
</where>
</select>
<select id="selectSysWorkOrderTypeByTypeId" parameterType="Long" resultMap="SysWorkOrderTypeResult">
<include refid="selectSysWorkOrderTypeVo"/>
where type_id = #{typeId}
</select>
<insert id="insertSysWorkOrderType" parameterType="SysWorkOrderType" useGeneratedKeys="true" keyProperty="typeId">
insert into sys_work_order_type
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="typeName != null and typeName != ''">type_name,</if>
<if test="typeKey != null and typeKey != ''">type_key,</if>
<if test="STATUS != null">STATUS,</if>
<if test="orderNum != null">order_num,</if>
<if test="createBy != null">create_by,</if>
<if test="createTime != null">create_time,</if>
<if test="updateBy != null">update_by,</if>
<if test="updateTime != null">update_time,</if>
<if test="remark != null">remark,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="typeName != null and typeName != ''">#{typeName},</if>
<if test="typeKey != null and typeKey != ''">#{typeKey},</if>
<if test="STATUS != null">#{STATUS},</if>
<if test="orderNum != null">#{orderNum},</if>
<if test="createBy != null">#{createBy},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateBy != null">#{updateBy},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="remark != null">#{remark},</if>
</trim>
</insert>
<update id="updateSysWorkOrderType" parameterType="SysWorkOrderType">
update sys_work_order_type
<trim prefix="SET" suffixOverrides=",">
<if test="typeName != null and typeName != ''">type_name = #{typeName},</if>
<if test="typeKey != null and typeKey != ''">type_key = #{typeKey},</if>
<if test="STATUS != null">STATUS = #{STATUS},</if>
<if test="orderNum != null">order_num = #{orderNum},</if>
<if test="createBy != null">create_by = #{createBy},</if>
<if test="createTime != null">create_time = #{createTime},</if>
<if test="updateBy != null">update_by = #{updateBy},</if>
<if test="updateTime != null">update_time = #{updateTime},</if>
<if test="remark != null">remark = #{remark},</if>
</trim>
where type_id = #{typeId}
</update>
<delete id="deleteSysWorkOrderTypeByTypeId" parameterType="Long">
delete from sys_work_order_type where type_id = #{typeId}
</delete>
<delete id="deleteSysWorkOrderTypeByTypeIds" parameterType="String">
delete from sys_work_order_type where type_id in
<foreach item="typeId" collection="array" open="(" separator="," close=")">
#{typeId}
</foreach>
</delete>
</mapper>

View File

@ -13,8 +13,8 @@
<artifactId>storm-common-oss</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

View File

@ -12,8 +12,8 @@
<artifactId>storm-device</artifactId>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
@ -104,12 +104,6 @@
<artifactId>huaweicloud-sdk-iotda</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.qpid</groupId>
<artifactId>qpid-jms-client</artifactId>

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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<DeviceRuntimeStatsMapper, DeviceRuntimeStats> implements IDeviceRuntimeStatsService
{
@Service
public class DeviceRuntimeStatsServiceImpl extends ServiceImpl<DeviceRuntimeStatsMapper, DeviceRuntimeStats> implements IDeviceRuntimeStatsService {
@Autowired
private DeviceRuntimeStatsMapper deviceRuntimeStatsMapper;
/**
* 查询设备运行时长统计
*
* @param id 设备运行时长统计主键
* @return 设备运行时长统计
*/
@Override
public DeviceRuntimeStats selectDeviceRuntimeStatsById(Long id)
{
return getById(id);
}
@Autowired
private RedisTemplate<String, String> 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<DeviceRuntimeStats> 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<String, Object> 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<DeviceRuntimeStat
Long endTime = safeParseTimestamp(props.get("massage_end_time"));
if (startTime != null && endTime != null && startTime < endTime && startTime > 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<DeviceRuntimeStat
return 0L;
}
/**
* 判断是否应该计数为上线
*/
private boolean shouldCountOnline(IotMsgNotifyDataPro iotMsgNotifyData) {
return iotMsgNotifyData.getBody() != null &&
"ONLINE".equalsIgnoreCase(iotMsgNotifyData.getBody().getStatus());
}
/**
* 验证时长是否合理最大24小时
*/
private boolean isDurationReasonable(long durationSeconds) {
return durationSeconds >= 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<DeviceRuntimeStats> 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;
}
}

View File

@ -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<DeviceStatusLogMappe
@Autowired
private DeviceStatusLogMapper deviceStatusLogMapper;
@Autowired
private DeviceManagerService deviceManagerService;
private RedisTemplate<String, String> redisTemplate;
private static final String STATUS_LOG_PREFIX = "status:log:id:";
private static final long STATUS_LOG_EXPIRE_HOURS = 24;
@Override
public List<DeviceStatusLog> selectDeviceStatusLogList(DeviceStatusLog deviceStatusLog) {
return deviceStatusLogMapper.selectList(Wrappers.<DeviceStatusLog>lambdaQuery()
@ -41,93 +45,32 @@ public class DeviceStatusLogServiceImpl extends ServiceImpl<DeviceStatusLogMappe
}
/**
* 处理服务属性
* 安全插入状态日志 - 增强版确保事件类型正确设置
*/
private void processServiceProperties(DeviceStatusLog statusLog, List<IotMsgNotifyDataPro.IotMsgService> services) {
for (IotMsgNotifyDataPro.IotMsgService service : services) {
if (service.getProperties() == null) continue;
Map<String, Object> 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<DeviceStatusLogMappe
} catch (Exception e) {
log.error("状态日志插入异常,尝试次数: {},设备: {}", attempt + 1, deviceId, e);
if (attempt == 2) {
if (StrUtil.isNotBlank(statusLogUniqueId)) {
// 插入失败时删除幂等键允许重试
redisTemplate.delete(STATUS_LOG_PREFIX + statusLogUniqueId);
}
return false;
}
}
@ -151,6 +98,39 @@ public class DeviceStatusLogServiceImpl extends ServiceImpl<DeviceStatusLogMappe
return false;
}
/**
* 生成状态日志唯一标识
*/
private String generateStatusLogUniqueId(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());
}
return deviceId + ":" + eventTime + ":" + iotMsgNotifyData.getBody().getStatus();
}
private boolean doInsertStatusLog(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData, int attempt) {
// 先检查是否已存在相同记录
if (attempt == 0 && isStatusLogExists(deviceId, iotMsgNotifyData)) {
@ -210,128 +190,6 @@ public class DeviceStatusLogServiceImpl extends ServiceImpl<DeviceStatusLogMappe
}
}
/**
* 从消息数据创建状态日志对象
*/
private DeviceStatusLog createStatusLogFromData(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData) {
DeviceStatusLog statusLog = new DeviceStatusLog();
statusLog.setDeviceId(deviceId);
statusLog.setCreateTime(new Date());
statusLog.setEventType("STATUS_CHANGE");
if (iotMsgNotifyData.getBody() == null) {
return statusLog;
}
// 设置状态
if (iotMsgNotifyData.getBody().getStatus() != null) {
String status = iotMsgNotifyData.getBody().getStatus();
statusLog.setStatus("ONLINE".equalsIgnoreCase(status) ? "ONLINE" : "OFFLINE");
}
// 设置事件时间
if (iotMsgNotifyData.getBody().getStatusUpdateTime() != null) {
statusLog.setEventTime(iotMsgNotifyData.getBody().getStatusUpdateTime());
} else {
statusLog.setEventTime(new Date());
}
// 设置最后在线时间
if (iotMsgNotifyData.getBody().getLastOnlineTime() != null) {
statusLog.setLastOnlineTime(iotMsgNotifyData.getBody().getLastOnlineTime());
}
// 设置其他字段
statusLog.setCreateBy("系统自动创建");
List<IotMsgNotifyDataPro.IotMsgService> services = iotMsgNotifyData.getBody().getServices();
if(services!=null&&services.size()>0){
for (IotMsgNotifyDataPro.IotMsgService service : services) {
if ("StatusChange".equals(service.getServiceId()) && service.getProperties() != null) {
Map<String, Object> 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<String, Object> 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<String, Object> 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<DeviceStatusLogMappe
}
/**
* 安全解析时间戳 - 修复版本
* 安全解析时间戳
*/
private Long safeParseTimestamp(Object timestampObj) {
if (timestampObj == null) return null;
try {
long timestamp;
if (timestampObj instanceof Number) {
@ -401,12 +258,6 @@ public class DeviceStatusLogServiceImpl extends ServiceImpl<DeviceStatusLogMappe
return null;
}
// 验证时间戳的有效性
if (!isValidTimestamp(timestamp)) {
log.warn("无效的时间戳: {}", timestamp);
return null;
}
// 判断是秒级还是毫秒级
if (timestamp < 10000000000L) { // 秒级时间戳小于 2286-11-21
return timestamp * 1000;
@ -427,12 +278,6 @@ public class DeviceStatusLogServiceImpl extends ServiceImpl<DeviceStatusLogMappe
return null;
}
// 验证时间戳有效性
if (!isValidTimestamp(startTime) || !isValidTimestamp(endTime)) {
log.warn("无效的时间戳startTime: {}, endTime: {}", startTime, endTime);
return null;
}
// 确保 startTime endTime 都是毫秒级
if (startTime < 10000000000L) {
startTime = startTime * 1000;
@ -456,27 +301,151 @@ public class DeviceStatusLogServiceImpl extends ServiceImpl<DeviceStatusLogMappe
return null;
}
return durationMs / 1000; // 返回秒数
return durationMs;
}
// DeviceStatusLogServiceImpl 类中添加以下方法
/**
* 从消息数据创建状态日志对象 - 增强版补全缺失字段
*/
private DeviceStatusLog createStatusLogFromData(String deviceId, IotMsgNotifyDataPro iotMsgNotifyData) {
DeviceStatusLog statusLog = new DeviceStatusLog();
statusLog.setDeviceId(deviceId);
statusLog.setCreateTime(new Date());
statusLog.setEventType("STATUS_CHANGE"); // 默认类型
if (iotMsgNotifyData.getBody() == null) {
return statusLog;
}
// 设置状态
if (iotMsgNotifyData.getBody().getStatus() != null) {
String status = iotMsgNotifyData.getBody().getStatus();
statusLog.setStatus("ONLINE".equalsIgnoreCase(status) ? "ONLINE" : "OFFLINE");
} else {
statusLog.setStatus("OFFLINE"); // 默认状态
}
// 设置事件时间
if (iotMsgNotifyData.getBody().getStatusUpdateTime() != null) {
statusLog.setEventTime(iotMsgNotifyData.getBody().getStatusUpdateTime());
} else {
statusLog.setEventTime(new Date()); // 默认当前时间
}
// 设置最后在线时间
if (iotMsgNotifyData.getBody().getLastOnlineTime() != null) {
statusLog.setLastOnlineTime(iotMsgNotifyData.getBody().getLastOnlineTime());
}
statusLog.setCreateBy("系统自动创建");
// 处理服务数据
processServiceData(statusLog, iotMsgNotifyData);
return statusLog;
}
/**
* 验证时间戳是否有效
* 处理服务数据 - 增强判断逻辑补全缺失字段
*/
private boolean isValidTimestamp(long timestamp) {
// 检查是否为0或负数
if (timestamp <= 0) {
return false;
private void processServiceData(DeviceStatusLog statusLog, IotMsgNotifyDataPro iotMsgNotifyData) {
if (iotMsgNotifyData.getBody() == null || iotMsgNotifyData.getBody().getServices() == null) {
return;
}
// 检查是否在合理范围内1970年 - 2100年
long minValidTimestamp = 0L; // 1970-01-01
long maxValidTimestamp = 4102444800000L; // 2100-01-01 的毫秒时间戳
for (IotMsgNotifyDataPro.IotMsgService service : iotMsgNotifyData.getBody().getServices()) {
Map<String, Object> 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<String, Object> 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<String, Object> 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);
}
}
}

View File

@ -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<String, String> redisTemplate;
private static final String MESSAGE_IDEMPOTENT_PREFIX = "iot:message:id:";
private static final long MESSAGE_IDEMPOTENT_EXPIRE_MINUTES = 5;
//控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数
//建议使用机器UUIDMAC地址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);
}
}
}

View File

@ -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<String, String> redisTemplate;
private static final String MESSAGE_IDEMPOTENT_PREFIX = "iot:message:id:";
private static final long MESSAGE_IDEMPOTENT_EXPIRE_MINUTES = 5;
//控制台服务端订阅中消费组状态页客户端ID一栏将显示clientId参数
//建议使用机器UUIDMAC地址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);
}
}
}

View File

@ -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<String, String> 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参数
//建议使用机器UUIDMAC地址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);

View File

@ -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"
}

View File

@ -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
}

View File

@ -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
}
]

View File

@ -1,12 +0,0 @@
{
"pageNo": 1,
"pageSize": 10,
"isAsc": true,
"sortBy": "id",
"deviceNameParam": "",
"statusParam": "",
"softwareVersionParam": "",
"vtxdbVersionParam": "",
"beginTime": "",
"endTime": ""
}

View File

@ -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)]))

View File

@ -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<Device> 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<DeviceShadowData> 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;
}
}