项目初始化
This commit is contained in:
51
.gitignore
vendored
Normal file
51
.gitignore
vendored
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Compiled class file
|
||||||
|
*.class
|
||||||
|
*.iml
|
||||||
|
*.idea
|
||||||
|
target/
|
||||||
|
logs/
|
||||||
|
docs/
|
||||||
|
|
||||||
|
# Log file
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# BlueJ files
|
||||||
|
*.ctxt
|
||||||
|
|
||||||
|
# Mobile Tools for Java (J2ME)
|
||||||
|
.mtj.tmp/
|
||||||
|
|
||||||
|
# Package Files #
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
*.tar.gz
|
||||||
|
*.rar
|
||||||
|
|
||||||
|
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
|
||||||
|
hs_err_pid*
|
||||||
|
|
||||||
|
*velocity.log*
|
||||||
|
|
||||||
|
# Eclipse #
|
||||||
|
.classpath
|
||||||
|
.project
|
||||||
|
.settings/
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
_dockerCerts/
|
||||||
|
|
||||||
|
.factorypath
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
|
rebel.xml
|
||||||
|
|
||||||
|
!DmJdbcDriver18.jar
|
||||||
|
!kingbase8-8.6.0.jar
|
||||||
|
/.fastRequest/collections/Root/Default Group/directory.json
|
||||||
|
/.fastRequest/collections/Root/directory.json
|
||||||
|
/.fastRequest/config/fastRequestCurrentProjectConfig.json
|
||||||
41
AGENTS.md
Normal file
41
AGENTS.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Repository Guidelines
|
||||||
|
|
||||||
|
## Agent 工作方式
|
||||||
|
进入本仓库后,先阅读本文件,再开始分析、修改或输出结论。不要跳过现有文档直接下判断,至少先结合根目录 `README.md`、`docs/`、目标模块代码和相关配置确认上下文。
|
||||||
|
|
||||||
|
日常交互遵循以下习惯:
|
||||||
|
|
||||||
|
- 先整理执行方案,说明目标、涉及模块、预计修改点和验证方式,待用户评审确认后再执行。
|
||||||
|
- 不要想当然;如果需求存在歧义、前提不清或有多种实现路径,先说清假设与取舍,再继续。
|
||||||
|
- 先确认任务位于 `entrance`、`system`、`user`、`detection` 还是 `tools/activate-tool`,再沿配置、Controller、Service、Mapper、XML 和调用链向下分析。
|
||||||
|
- 涉及认证、字典、日志、注册资源、WebSocket 或 Netty 通信链路时,先核对已有实现和 `docs/` 中的说明,避免只看局部代码就下结论。
|
||||||
|
- 回复风格保持简洁、直接,优先给出可执行结果;如果存在限制、风险或未验证部分,需要明确说明。
|
||||||
|
|
||||||
|
## 执行与修改原则
|
||||||
|
- 简单优先:只做当前需求所需的最小改动,不额外引入新功能、抽象层、配置项或“顺手优化”。
|
||||||
|
- 外科手术式修改:只改与任务直接相关的文件和代码行,不重构无关模块,不调整无关格式或注释。
|
||||||
|
- 保持现有风格:遵循仓库已有包结构、分层方式、命名和写法,不按个人偏好重写。
|
||||||
|
- 只清理自己造成的问题:可以删除因本次修改而产生的未使用 `import`、变量或方法;不要删除仓库中原本就存在的死代码,除非用户明确要求。
|
||||||
|
- 先定义验证方式:执行方案里要写清楚“改哪里、怎么判断改对了”;默认通过代码路径、配置一致性和受影响范围检查进行验证。
|
||||||
|
- 除非用户明确要求,否则不执行任何 `mvn` 编译、打包、测试或其他构建命令。
|
||||||
|
|
||||||
|
## 项目结构与模块划分
|
||||||
|
`CN_Tool` 是一个 Maven 多模块后端项目,根目录的 [`pom.xml`](C:/code/gitea/cn_tool/CN_Tool/pom.xml) 聚合了 `entrance`、`system`、`user`、`detection` 和 `tools`。
|
||||||
|
|
||||||
|
- `entrance`:Spring Boot 启动模块,入口类为 `entrance/src/main/java/com/njcn/gather/EntranceApplication.java`。
|
||||||
|
- `system`:系统字典、日志、配置、注册资源等公共能力。
|
||||||
|
- `user`:认证、用户、角色、功能资源及相关过滤逻辑。
|
||||||
|
- `detection`:Netty / WebSocket 通信与连接生命周期管理。
|
||||||
|
- `tools/activate-tool`:激活码与许可能力。
|
||||||
|
- `docs/`:项目基线、配置和运行说明文档。
|
||||||
|
|
||||||
|
Java 源码位于 `src/main/java`,配置文件位于 `src/main/resources`,MyBatis XML 映射文件按包结构存放在 `**/mapper/mapping/*.xml`。
|
||||||
|
|
||||||
|
## 代码风格与命名规范
|
||||||
|
保持现有 Java 风格:4 空格缩进、UTF-8 文件编码、基础包名使用 `com.njcn.gather`。命名沿用分层后缀,如 `*Controller`、`*Service`、`*ServiceImpl`、`*Mapper`、`*Param`、`*PO`、`*VO`。优先复用现有 Lombok 注解,如 `@Data`、`@RequiredArgsConstructor`、`@Slf4j`。Mapper XML 文件名应与接口名保持一致。业务代码中,关键流程、分支判断、状态流转或容易误解的节点需要补充简洁的中文注释,但不要添加无信息量的注释。
|
||||||
|
|
||||||
|
## 提交与合并请求规范
|
||||||
|
当前 `main` 分支尚无可参考的提交历史,仓库内也没有既有提交规范。建议使用“模块前缀 + 动词短句”的提交格式,例如 `user: 优化登录会话校验`、`system: 增加字典参数校验`。提交 PR 时应说明影响模块、配置或数据结构变更、人工验证步骤;若接口行为有变化,附上请求与响应示例。
|
||||||
|
|
||||||
|
## 安全与配置提示
|
||||||
|
将 `application.yml` 视为环境配置文件处理,不要在提交中新增明文密钥、数据库口令或许可证材料。本地运行时需保证 `D:\logs` 可写;如部署环境不同,应通过配置覆盖日志目录。
|
||||||
65
CLAUDE.md
Normal file
65
CLAUDE.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
用于减少 LLM 常见编码失误的行为准则。可按需与项目特定说明合并使用。
|
||||||
|
|
||||||
|
**权衡:** 这些准则更偏向谨慎而非速度。对于非常简单的任务,请自行判断。
|
||||||
|
|
||||||
|
## 1. 编码前先思考
|
||||||
|
|
||||||
|
**不要想当然。不要掩饰困惑。把权衡点说出来。**
|
||||||
|
|
||||||
|
开始实现前:
|
||||||
|
- 明确说明你的假设;如果不确定,就提问。
|
||||||
|
- 如果存在多种理解方式,请列出来,不要默默自行选择。
|
||||||
|
- 如果有更简单的方案,请直接指出;必要时应当提出不同意见。
|
||||||
|
- 如果有内容不清楚,就先停下来;说清楚困惑点,然后提问。
|
||||||
|
|
||||||
|
## 2. 简单优先
|
||||||
|
|
||||||
|
**只写解决问题所需的最少代码。不要做猜测式扩展。**
|
||||||
|
|
||||||
|
- 不要添加用户未要求的功能。
|
||||||
|
- 不要为一次性代码引入抽象。
|
||||||
|
- 不要加入未被要求的“灵活性”或“可配置性”。
|
||||||
|
- 不要为不可能发生的场景补充错误处理。
|
||||||
|
- 如果你写了 200 行,而实际上 50 行就能解决,就重写。
|
||||||
|
|
||||||
|
问问自己:“一个资深工程师会认为这太复杂了吗?”如果答案是会,那就继续简化。
|
||||||
|
|
||||||
|
## 3. 外科手术式修改
|
||||||
|
|
||||||
|
**只改必须改的地方。只清理自己造成的问题。**
|
||||||
|
|
||||||
|
修改现有代码时:
|
||||||
|
- 不要顺手“优化”相邻代码、注释或格式。
|
||||||
|
- 不要重构没有问题的部分。
|
||||||
|
- 保持与现有风格一致,即使你个人会写成别的样子。
|
||||||
|
- 如果发现无关的死代码,可以指出,但不要直接删除。
|
||||||
|
|
||||||
|
当你的改动产生“遗留物”时:
|
||||||
|
- 删除因你的改动而变成未使用的 `import`、变量或函数。
|
||||||
|
- 不要删除原本就存在的死代码,除非用户明确要求。
|
||||||
|
|
||||||
|
检验标准:每一行变更都应该能直接对应到用户的请求。
|
||||||
|
|
||||||
|
## 4. 以目标驱动执行
|
||||||
|
|
||||||
|
**先定义成功标准,再循环推进直到验证完成。**
|
||||||
|
|
||||||
|
把任务转换成可验证的目标:
|
||||||
|
- “增加校验” → “先为非法输入编写测试,再让测试通过”
|
||||||
|
- “修复 bug” → “先写出能复现问题的测试,再让测试通过”
|
||||||
|
- “重构 X” → “确保改动前后测试都通过”
|
||||||
|
|
||||||
|
对于多步骤任务,先给出简短计划:
|
||||||
|
```
|
||||||
|
1. [步骤] → 验证方式:[检查项]
|
||||||
|
2. [步骤] → 验证方式:[检查项]
|
||||||
|
3. [步骤] → 验证方式:[检查项]
|
||||||
|
```
|
||||||
|
|
||||||
|
明确的成功标准可以支持你独立推进;模糊的标准(例如“把它弄好”)则会导致反复确认。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**如果这些准则有效,你会看到:** diff 中不必要的改动更少,因过度设计导致的返工更少,而且澄清问题会发生在实现之前,而不是出错之后。
|
||||||
84
README.md
Normal file
84
README.md
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
# CN_Tool
|
||||||
|
|
||||||
|
CN_Tool 是一个基于 Spring Boot 的多模块后端聚合工程,当前仓库内保留的核心能力包括:
|
||||||
|
|
||||||
|
- 用户认证、用户/角色/菜单资源管理
|
||||||
|
- 系统字典、日志、系统配置、注册资源管理
|
||||||
|
- WebSocket / Netty 通信基础设施
|
||||||
|
- 激活码与许可证能力
|
||||||
|
|
||||||
|
## 当前真实模块
|
||||||
|
|
||||||
|
根聚合模块下当前包含以下子模块:
|
||||||
|
|
||||||
|
- `entrance`
|
||||||
|
- `system`
|
||||||
|
- `user`
|
||||||
|
- `detection`
|
||||||
|
- `tools`
|
||||||
|
|
||||||
|
其中 `tools` 当前仅保留:
|
||||||
|
|
||||||
|
- `activate-tool`
|
||||||
|
|
||||||
|
## 启动入口
|
||||||
|
|
||||||
|
当前主启动入口位于:
|
||||||
|
|
||||||
|
- `entrance/src/main/java/com/njcn/gather/EntranceApplication.java`
|
||||||
|
|
||||||
|
`entrance` 模块聚合了 `system`、`user`、`detection`、`activate-tool`,是当前运行时主入口。
|
||||||
|
|
||||||
|
## 技术基线
|
||||||
|
|
||||||
|
- Java:源码目标版本为 `1.8`
|
||||||
|
- Spring Boot:`2.3.12.RELEASE`
|
||||||
|
- 构建方式:Maven 多模块工程
|
||||||
|
- ORM:MyBatis-Plus
|
||||||
|
- 数据库:MySQL
|
||||||
|
|
||||||
|
## 运行与构建前提
|
||||||
|
|
||||||
|
当前项目存在以下前提条件:
|
||||||
|
|
||||||
|
- 需要可用的 JDK 8 环境
|
||||||
|
- 需要 Maven 环境
|
||||||
|
- 当前仓库未发现 `mvnw`
|
||||||
|
- 依赖私有 `com.njcn` 组件
|
||||||
|
- 根 `pom.xml` 中存在内网 Nexus 发布仓库配置
|
||||||
|
- 运行前通常需要可访问的 MySQL 数据库和基础表数据
|
||||||
|
|
||||||
|
说明:
|
||||||
|
|
||||||
|
当前这份仓库并不保证在任意外部环境下可直接编译运行。
|
||||||
|
如果要做真实构建和启动,需要先满足内部依赖和环境条件。
|
||||||
|
|
||||||
|
## 文档入口
|
||||||
|
|
||||||
|
P0 已补齐基线文档,建议按以下顺序阅读:
|
||||||
|
|
||||||
|
1. [docs/01-项目总览.md](./docs/01-项目总览.md)
|
||||||
|
2. [docs/02-配置清单.md](./docs/02-配置清单.md)
|
||||||
|
3. [docs/03-构建与运行前提.md](./docs/03-构建与运行前提.md)
|
||||||
|
4. [docs/04-过时文档说明.md](./docs/04-过时文档说明.md)
|
||||||
|
|
||||||
|
## 模块说明
|
||||||
|
|
||||||
|
- `user`
|
||||||
|
- 负责认证、用户、角色、菜单资源相关能力
|
||||||
|
- `system`
|
||||||
|
- 负责字典、日志、系统配置、注册资源相关能力
|
||||||
|
- `detection`
|
||||||
|
- 当前以通信基础设施为主,包含 WebSocket / Netty 相关组件
|
||||||
|
- `tools/activate-tool`
|
||||||
|
- 负责激活码生成、激活码验证、许可证读取等能力
|
||||||
|
|
||||||
|
## 文档使用规则
|
||||||
|
|
||||||
|
当前仓库中部分历史说明仍然存在。
|
||||||
|
如文档之间出现冲突,建议按以下优先级理解:
|
||||||
|
|
||||||
|
1. `docs/` 下的基线文档
|
||||||
|
2. 根 `README.md`
|
||||||
|
3. 各模块下的 `Readme.md`
|
||||||
|
4. 最终以源码和配置为准
|
||||||
9
detection/Readme.md
Normal file
9
detection/Readme.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#### 简介
|
||||||
|
设备模块主要包含以下功能:
|
||||||
|
* 检测计划管理
|
||||||
|
* 被检设备管理
|
||||||
|
* 被检设备下监测点管理
|
||||||
|
* 误差体系管理
|
||||||
|
* 检测脚本管理
|
||||||
|
* 检测源管理
|
||||||
|
* 检测报告管理
|
||||||
38
detection/pom.xml
Normal file
38
detection/pom.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?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.njcn.gather</groupId>
|
||||||
|
<artifactId>CN_Tool</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>detection</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn</groupId>
|
||||||
|
<artifactId>njcn-common</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn</groupId>
|
||||||
|
<artifactId>spingboot2.3.12</artifactId>
|
||||||
|
<version>2.3.12</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- Key refactor point: detection now retains only transport-foundation
|
||||||
|
dependencies required by the generic Netty/WebSocket stack. -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>1.2.83</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.netty</groupId>
|
||||||
|
<artifactId>netty-all</artifactId>
|
||||||
|
<version>4.1.68.Final</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.vo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description
|
||||||
|
* @date 2024/12/13 9:09
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SocketDataMsg {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 标识不同业务
|
||||||
|
*/
|
||||||
|
private String type = "aaa";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求id,确保接收到响应时,知晓是针对的哪次请求的应答
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 1)
|
||||||
|
private String requestId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源初始化 INIT_GATHER$01 INIT_GATHER采集初始化,01 统计采集、02 暂态采集、03 实时采集
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 2)
|
||||||
|
private String operateCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据体,传输前需要将对象、Array等转为String
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 4)
|
||||||
|
private String data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* code码
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 3)
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.vo;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author wr
|
||||||
|
* @description socket 通用发送报文请求
|
||||||
|
* @date 2024/12/11 15:57
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SocketMsg<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求id,确保接收到响应时,知晓是针对的哪次请求的应答
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 1)
|
||||||
|
private String requestId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 源初始化 INIT_GATHER$01 INIT_GATHER采集初始化,01 统计采集、02 暂态采集、03 实时采集
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 2)
|
||||||
|
private String operateCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据体,传输前需要将对象、Array等转为String
|
||||||
|
*/
|
||||||
|
@JSONField(ordinal = 3)
|
||||||
|
private T data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.njcn.gather.detection.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic WebSocket payload wrapper.
|
||||||
|
*
|
||||||
|
* @author chendaofei
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/08
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class WebSocketVO<T> {
|
||||||
|
|
||||||
|
private String type = "transport";
|
||||||
|
|
||||||
|
private String requestId;
|
||||||
|
|
||||||
|
private String operateCode;
|
||||||
|
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
private String desc;
|
||||||
|
|
||||||
|
private T data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
|
import com.njcn.gather.detection.pojo.vo.SocketDataMsg;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic socket message helper retained by the communication foundation.
|
||||||
|
* Stage 4-B removes detection-specific text assembly helpers and keeps only
|
||||||
|
* payload parsing and JSON framing methods used by the base transport layer.
|
||||||
|
*
|
||||||
|
* @author wr
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/08
|
||||||
|
*/
|
||||||
|
public final class MsgUtil {
|
||||||
|
|
||||||
|
private MsgUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SocketDataMsg socketDataMsg(String textMsg) {
|
||||||
|
return JSON.parseObject(textMsg, SocketDataMsg.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJsonWithNewLine(Object obj) {
|
||||||
|
return JSON.toJSONString(obj, SerializerFeature.PrettyFormat) + "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String toJsonWithNewLinePlain(Object obj) {
|
||||||
|
return JSON.toJSONString(obj) + "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import com.alibaba.fastjson.JSONObject;
|
||||||
|
import com.njcn.gather.detection.util.socket.cilent.NettyClient;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionType;
|
||||||
|
import com.njcn.gather.detection.util.socket.config.SocketConnectionConfig;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic socket session manager.
|
||||||
|
* Stage 4-A removes detection-only caches from this class and keeps only the
|
||||||
|
* retained transport responsibilities: session registry, auto-connect and
|
||||||
|
* outbound dispatch.
|
||||||
|
*
|
||||||
|
* @author wr
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class SocketManager {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private SocketConnectionConfig socketConnectionConfig;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private NettyClient nettyClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key: sessionKey(userId + connection tag), value: active channel.
|
||||||
|
*/
|
||||||
|
private static final Map<String, Channel> SOCKET_SESSIONS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key: sessionKey(userId + connection tag), value: event loop group.
|
||||||
|
*/
|
||||||
|
private static final Map<String, NioEventLoopGroup> SOCKET_GROUPS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public static void addUser(String sessionKey, Channel channel) {
|
||||||
|
SOCKET_SESSIONS.put(sessionKey, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addGroup(String sessionKey, NioEventLoopGroup group) {
|
||||||
|
SOCKET_GROUPS.put(sessionKey, group);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void removeUser(String sessionKey) {
|
||||||
|
Channel channel = SOCKET_SESSIONS.remove(sessionKey);
|
||||||
|
NioEventLoopGroup eventLoopGroup = SOCKET_GROUPS.remove(sessionKey);
|
||||||
|
|
||||||
|
if (ObjectUtil.isNotNull(channel)) {
|
||||||
|
try {
|
||||||
|
channel.close().sync();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
log.warn("Close socket channel interrupted: sessionKey={}", sessionKey, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ObjectUtil.isNotNull(eventLoopGroup)) {
|
||||||
|
eventLoopGroup.shutdownGracefully();
|
||||||
|
log.info("Socket connection closed: sessionKey={}", sessionKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Channel getChannelByUserId(String sessionKey) {
|
||||||
|
return SOCKET_SESSIONS.get(sessionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NioEventLoopGroup getGroupByUserId(String sessionKey) {
|
||||||
|
return SOCKET_GROUPS.get(sessionKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isChannelActive(String sessionKey) {
|
||||||
|
Channel channel = getChannelByUserId(sessionKey);
|
||||||
|
return ObjectUtil.isNotNull(channel) && channel.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMsg(String sessionKey, String msg) {
|
||||||
|
Channel channel = SOCKET_SESSIONS.get(sessionKey);
|
||||||
|
if (ObjectUtil.isNull(channel)) {
|
||||||
|
log.warn("Send socket message failed because channel does not exist: sessionKey={}, message={}",
|
||||||
|
sessionKey, msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
channel.writeAndFlush(msg + '\n');
|
||||||
|
log.info("{}__{} -> {} : {}", sessionKey, channel.id(), channel.remoteAddress(), msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key refactor point: auto-connect now depends only on connection context
|
||||||
|
* and transport callbacks attached to that context.
|
||||||
|
*/
|
||||||
|
public void smartSend(ConnectionContext context, String msg) {
|
||||||
|
if (ObjectUtil.isNull(context) || ObjectUtil.isNull(context.getConnectionType())) {
|
||||||
|
log.warn("smartSend skipped because connection context is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
String sessionKey = context.getSessionKey();
|
||||||
|
String requestId = extractRequestId(msg);
|
||||||
|
if (StrUtil.isBlank(sessionKey)) {
|
||||||
|
log.warn("smartSend skipped because sessionKey is blank, requestId={}", requestId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsAutoConnect(context.getConnectionType(), requestId) && !isChannelActive(sessionKey)) {
|
||||||
|
String ip = resolveIp(context.getConnectionType());
|
||||||
|
Integer port = resolvePort(context.getConnectionType());
|
||||||
|
log.info("Socket auto connect triggered: type={}, sessionKey={}, requestId={}",
|
||||||
|
context.getConnectionType(), sessionKey, requestId);
|
||||||
|
CompletableFuture.runAsync(() -> nettyClient.connect(ip, port, context, msg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendMsg(sessionKey, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extractRequestId(String msg) {
|
||||||
|
try {
|
||||||
|
if (StrUtil.isBlank(msg)) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
JSONObject jsonObject = JSON.parseObject(msg);
|
||||||
|
String requestId = jsonObject.getString("requestId");
|
||||||
|
if (StrUtil.isNotBlank(requestId)) {
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
requestId = jsonObject.getString("request_id");
|
||||||
|
if (StrUtil.isNotBlank(requestId)) {
|
||||||
|
return requestId;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.debug("Extract requestId from socket message failed: {}", msg, e);
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean needsAutoConnect(ConnectionType connectionType, String requestId) {
|
||||||
|
if (connectionType == ConnectionType.SOURCE) {
|
||||||
|
return SocketConnectionConfig.needsSourceConnection(requestId);
|
||||||
|
}
|
||||||
|
return SocketConnectionConfig.needsDeviceConnection(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveIp(ConnectionType connectionType) {
|
||||||
|
if (connectionType == ConnectionType.SOURCE) {
|
||||||
|
return socketConnectionConfig.getSource().getIp();
|
||||||
|
}
|
||||||
|
return socketConnectionConfig.getDevice().getIp();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer resolvePort(ConnectionType connectionType) {
|
||||||
|
if (connectionType == ConnectionType.SOURCE) {
|
||||||
|
return socketConnectionConfig.getSource().getPort();
|
||||||
|
}
|
||||||
|
return socketConnectionConfig.getDevice().getPort();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.cilent;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njcn.gather.detection.util.socket.SocketManager;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.ConnectionLifecycleHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.SocketMessageHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.handler.timeout.IdleState;
|
||||||
|
import io.netty.handler.timeout.IdleStateEvent;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shared client handler skeleton for the retained Netty communication layer.
|
||||||
|
* Stage 4-A centralizes common session registration, message delegation and
|
||||||
|
* idle cleanup so concrete handlers stay transport-oriented.
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AbstractNettyClientHandler extends SimpleChannelInboundHandler<String> {
|
||||||
|
|
||||||
|
private final String handlerName;
|
||||||
|
|
||||||
|
protected final ConnectionContext connectionContext;
|
||||||
|
|
||||||
|
private final SocketMessageHandler socketMessageHandler;
|
||||||
|
|
||||||
|
private final ConnectionLifecycleHandler lifecycleHandler;
|
||||||
|
|
||||||
|
protected AbstractNettyClientHandler(String handlerName, ConnectionContext connectionContext,
|
||||||
|
SocketMessageHandler socketMessageHandler,
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler) {
|
||||||
|
this.handlerName = handlerName;
|
||||||
|
this.connectionContext = connectionContext;
|
||||||
|
this.socketMessageHandler = socketMessageHandler;
|
||||||
|
this.lifecycleHandler = lifecycleHandler == null ? ConnectionLifecycleHandler.NO_OP : lifecycleHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
String sessionKey = resolveSessionKey();
|
||||||
|
log.info("{} channel active: channelId={}, sessionKey={}", handlerName, ctx.channel().id(), sessionKey);
|
||||||
|
if (StrUtil.isNotBlank(sessionKey)) {
|
||||||
|
SocketManager.addUser(sessionKey, ctx.channel());
|
||||||
|
} else {
|
||||||
|
log.warn("{} channel active without sessionKey, skip registration", handlerName);
|
||||||
|
}
|
||||||
|
lifecycleHandler.onConnected(connectionContext);
|
||||||
|
super.channelActive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
String sessionKey = resolveSessionKey();
|
||||||
|
log.warn("{} channel inactive: channelId={}, sessionKey={}", handlerName, ctx.channel().id(), sessionKey);
|
||||||
|
if (StrUtil.isNotBlank(sessionKey)) {
|
||||||
|
SocketManager.removeUser(sessionKey);
|
||||||
|
}
|
||||||
|
lifecycleHandler.onDisconnected(connectionContext);
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||||
|
if (socketMessageHandler == null) {
|
||||||
|
log.warn("{} receive message but handler is null: sessionKey={}, message={}",
|
||||||
|
handlerName, resolveSessionKey(), msg);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
socketMessageHandler.handle(connectionContext, msg);
|
||||||
|
} catch (Exception e) {
|
||||||
|
lifecycleHandler.onMessageHandlingError(connectionContext, msg, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
if (evt instanceof IdleStateEvent && ((IdleStateEvent) evt).state() == IdleState.READER_IDLE) {
|
||||||
|
log.warn("{} trigger reader idle timeout: sessionKey={}", handlerName, resolveSessionKey());
|
||||||
|
lifecycleHandler.onIdleTimeout(connectionContext);
|
||||||
|
ctx.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
super.userEventTriggered(ctx, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
log.error("{} catch transport exception: sessionKey={}, message={}",
|
||||||
|
handlerName, resolveSessionKey(), cause.getMessage(), cause);
|
||||||
|
lifecycleHandler.onException(connectionContext, cause);
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String resolveSessionKey() {
|
||||||
|
return connectionContext == null ? null : connectionContext.getSessionKey();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,138 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.cilent;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njcn.gather.detection.util.socket.SocketManager;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.ConnectionLifecycleHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.HeartbeatMessageStrategy;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic Netty heartbeat handler.
|
||||||
|
* Stage 4-A moves heartbeat framing behind a strategy interface so the
|
||||||
|
* retained transport layer can keep heartbeat capability without embedding
|
||||||
|
* detection-specific packet structures.
|
||||||
|
*
|
||||||
|
* @author cdf
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class HeartbeatHandler extends SimpleChannelInboundHandler<String> {
|
||||||
|
|
||||||
|
private static final int MAX_HEARTBEAT_MISSES = 3;
|
||||||
|
|
||||||
|
private final ScheduledExecutorService heartbeatExecutor = Executors.newScheduledThreadPool(1);
|
||||||
|
|
||||||
|
private final ConnectionContext connectionContext;
|
||||||
|
|
||||||
|
private final HeartbeatMessageStrategy heartbeatMessageStrategy;
|
||||||
|
|
||||||
|
private final ConnectionLifecycleHandler lifecycleHandler;
|
||||||
|
|
||||||
|
private ScheduledFuture<?> heartbeatFuture;
|
||||||
|
|
||||||
|
private int consecutiveHeartbeatMisses;
|
||||||
|
|
||||||
|
public HeartbeatHandler(ConnectionContext connectionContext, HeartbeatMessageStrategy heartbeatMessageStrategy,
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler) {
|
||||||
|
this.connectionContext = connectionContext;
|
||||||
|
this.heartbeatMessageStrategy = heartbeatMessageStrategy;
|
||||||
|
this.lifecycleHandler = lifecycleHandler == null ? ConnectionLifecycleHandler.NO_OP : lifecycleHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
scheduleHeartbeat(ctx);
|
||||||
|
super.channelActive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
shutdownExecutorGracefully();
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleHeartbeat(ChannelHandlerContext ctx) {
|
||||||
|
if (heartbeatMessageStrategy == null) {
|
||||||
|
log.debug("Skip heartbeat scheduling because strategy is null: sessionKey={}", resolveSessionKey());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
heartbeatFuture = heartbeatExecutor.scheduleAtFixedRate(() -> {
|
||||||
|
if (!ctx.channel().isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String heartbeatMessage = heartbeatMessageStrategy.buildHeartbeatMessage(connectionContext);
|
||||||
|
if (StrUtil.isBlank(heartbeatMessage)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// The client pipeline still uses line based frames, so the
|
||||||
|
// generic heartbeat writer normalizes the trailing separator.
|
||||||
|
if (!heartbeatMessage.endsWith("\n")) {
|
||||||
|
heartbeatMessage = heartbeatMessage + "\n";
|
||||||
|
}
|
||||||
|
ctx.channel().writeAndFlush(heartbeatMessage);
|
||||||
|
consecutiveHeartbeatMisses++;
|
||||||
|
log.debug("Send heartbeat packet: sessionKey={}, time={}, misses={}",
|
||||||
|
resolveSessionKey(), LocalDateTime.now(), consecutiveHeartbeatMisses);
|
||||||
|
if (consecutiveHeartbeatMisses >= MAX_HEARTBEAT_MISSES) {
|
||||||
|
handleHeartbeatTimeout(ctx);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Send heartbeat packet failed: sessionKey={}", resolveSessionKey(), e);
|
||||||
|
}
|
||||||
|
}, 3, 10, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleHeartbeatTimeout(ChannelHandlerContext ctx) {
|
||||||
|
log.warn("Heartbeat timeout reached: sessionKey={}, misses={}",
|
||||||
|
resolveSessionKey(), consecutiveHeartbeatMisses);
|
||||||
|
lifecycleHandler.onIdleTimeout(connectionContext);
|
||||||
|
String sessionKey = resolveSessionKey();
|
||||||
|
if (StrUtil.isNotBlank(sessionKey)) {
|
||||||
|
SocketManager.removeUser(sessionKey);
|
||||||
|
}
|
||||||
|
consecutiveHeartbeatMisses = 0;
|
||||||
|
if (ctx.channel().isActive()) {
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
|
||||||
|
if (heartbeatMessageStrategy != null && heartbeatMessageStrategy.isHeartbeatResponse(connectionContext, msg)) {
|
||||||
|
consecutiveHeartbeatMisses = 0;
|
||||||
|
log.debug("Receive heartbeat response: sessionKey={}, time={}", resolveSessionKey(), LocalDateTime.now());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ctx.fireChannelRead(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveSessionKey() {
|
||||||
|
return connectionContext == null ? null : connectionContext.getSessionKey();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shutdownExecutorGracefully() {
|
||||||
|
try {
|
||||||
|
if (heartbeatFuture != null && !heartbeatFuture.isCancelled()) {
|
||||||
|
heartbeatFuture.cancel(false);
|
||||||
|
}
|
||||||
|
heartbeatExecutor.shutdown();
|
||||||
|
if (!heartbeatExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
|
heartbeatExecutor.shutdownNow();
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
heartbeatExecutor.shutdownNow();
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.cilent;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.njcn.gather.detection.util.socket.SocketManager;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.ConnectionLifecycleHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.HeartbeatMessageStrategy;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.SocketMessageHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionType;
|
||||||
|
import io.netty.bootstrap.Bootstrap;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelFutureListener;
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||||
|
import io.netty.handler.codec.LineBasedFrameDecoder;
|
||||||
|
import io.netty.handler.codec.string.StringDecoder;
|
||||||
|
import io.netty.handler.codec.string.StringEncoder;
|
||||||
|
import io.netty.handler.timeout.IdleStateHandler;
|
||||||
|
import io.netty.util.CharsetUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic Netty client entry.
|
||||||
|
* Stage 4-A removes direct dependencies on detection handlers and services.
|
||||||
|
* Message parsing, heartbeat framing and lifecycle side effects now come from
|
||||||
|
* callbacks attached to {@link ConnectionContext}.
|
||||||
|
*
|
||||||
|
* @author wr
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class NettyClient {
|
||||||
|
|
||||||
|
public void connect(String ip, Integer port, ConnectionContext context, String msg) {
|
||||||
|
if (ObjectUtil.isNull(context) || ObjectUtil.isNull(context.getConnectionType())) {
|
||||||
|
log.warn("Skip socket connect because connection context is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SocketMessageHandler messageHandler = context.getMessageHandler();
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler = resolveLifecycleHandler(context);
|
||||||
|
HeartbeatMessageStrategy heartbeatMessageStrategy = context.getHeartbeatStrategy();
|
||||||
|
SimpleChannelInboundHandler<String> handler = createHandler(context, messageHandler, lifecycleHandler);
|
||||||
|
executeSocketConnection(ip, port, context, msg, handler, lifecycleHandler, heartbeatMessageStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private SimpleChannelInboundHandler<String> createHandler(ConnectionContext context,
|
||||||
|
SocketMessageHandler messageHandler,
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler) {
|
||||||
|
if (context.getConnectionType() == ConnectionType.SOURCE) {
|
||||||
|
return new NettySourceClientHandler(context, messageHandler, lifecycleHandler);
|
||||||
|
}
|
||||||
|
if (context.getConnectionType() == ConnectionType.DEVICE) {
|
||||||
|
return new NettyDevClientHandler(context, messageHandler, lifecycleHandler);
|
||||||
|
}
|
||||||
|
return new NettyContrastClientHandler(context, messageHandler, lifecycleHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConnectionLifecycleHandler resolveLifecycleHandler(ConnectionContext context) {
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler = context.getLifecycleHandler();
|
||||||
|
return lifecycleHandler == null ? ConnectionLifecycleHandler.NO_OP : lifecycleHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void executeSocketConnection(String ip, Integer port, ConnectionContext context, String msg,
|
||||||
|
SimpleChannelInboundHandler<String> handler,
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler,
|
||||||
|
HeartbeatMessageStrategy heartbeatMessageStrategy) {
|
||||||
|
NioEventLoopGroup group = new NioEventLoopGroup();
|
||||||
|
try {
|
||||||
|
Bootstrap bootstrap = new Bootstrap()
|
||||||
|
.group(group)
|
||||||
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||||
|
.channel(NioSocketChannel.class)
|
||||||
|
.handler(createChannelInitializer(context, handler, lifecycleHandler, heartbeatMessageStrategy));
|
||||||
|
ChannelFuture channelFuture = bootstrap.connect(ip, port).sync();
|
||||||
|
handleConnectionResult(channelFuture, context, group, msg, lifecycleHandler);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("Connect socket server error: type={}, sessionKey={}",
|
||||||
|
context.getConnectionType(), context.getSessionKey(), e);
|
||||||
|
group.shutdownGracefully();
|
||||||
|
lifecycleHandler.onConnectFailed(context, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ChannelInitializer<NioSocketChannel> createChannelInitializer(ConnectionContext context,
|
||||||
|
SimpleChannelInboundHandler<String> handler,
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler,
|
||||||
|
HeartbeatMessageStrategy heartbeatMessageStrategy) {
|
||||||
|
return new ChannelInitializer<NioSocketChannel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(NioSocketChannel ch) {
|
||||||
|
setupPipeline(ch.pipeline(), context, handler, lifecycleHandler, heartbeatMessageStrategy);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key refactor point: pipeline extension now comes from generic strategy
|
||||||
|
* and lifecycle callbacks instead of fixed detection business classes.
|
||||||
|
*/
|
||||||
|
private void setupPipeline(ChannelPipeline pipeline, ConnectionContext context,
|
||||||
|
SimpleChannelInboundHandler<String> handler,
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler,
|
||||||
|
HeartbeatMessageStrategy heartbeatMessageStrategy) {
|
||||||
|
pipeline.addLast(new LineBasedFrameDecoder(10240 * 2))
|
||||||
|
.addLast(new StringDecoder(CharsetUtil.UTF_8))
|
||||||
|
.addLast(new StringEncoder(CharsetUtil.UTF_8))
|
||||||
|
.addLast(new HeartbeatHandler(context, heartbeatMessageStrategy, lifecycleHandler));
|
||||||
|
if (context.getConnectionType().isEnableIdleMonitor()) {
|
||||||
|
pipeline.addLast(new IdleStateHandler(60, 0, 0, TimeUnit.SECONDS));
|
||||||
|
}
|
||||||
|
pipeline.addLast(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleConnectionResult(ChannelFuture channelFuture, ConnectionContext context,
|
||||||
|
NioEventLoopGroup group, String msg,
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler) {
|
||||||
|
channelFuture.addListener((ChannelFutureListener) future -> {
|
||||||
|
if (!future.isSuccess()) {
|
||||||
|
log.warn("Connect socket server failed: type={}, sessionKey={}",
|
||||||
|
context.getConnectionType(), context.getSessionKey(), future.cause());
|
||||||
|
group.shutdownGracefully();
|
||||||
|
lifecycleHandler.onConnectFailed(context, future.cause());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.info("Connect socket server success: type={}, sessionKey={}",
|
||||||
|
context.getConnectionType(), context.getSessionKey());
|
||||||
|
manageSocketConnection(context, group);
|
||||||
|
SocketManager.addUser(context.getSessionKey(), future.channel());
|
||||||
|
SocketManager.sendMsg(context.getSessionKey(), msg);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void manageSocketConnection(ConnectionContext context, NioEventLoopGroup group) {
|
||||||
|
String sessionKey = context.getSessionKey();
|
||||||
|
NioEventLoopGroup existingGroup = SocketManager.getGroupByUserId(sessionKey);
|
||||||
|
if (ObjectUtil.isNotNull(existingGroup)) {
|
||||||
|
try {
|
||||||
|
existingGroup.shutdownGracefully().sync();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SocketManager.addGroup(sessionKey, group);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.cilent;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.ConnectionLifecycleHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.SocketMessageHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contrast device client transport handler.
|
||||||
|
*
|
||||||
|
* @author caozehui
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
public class NettyContrastClientHandler extends AbstractNettyClientHandler {
|
||||||
|
|
||||||
|
public NettyContrastClientHandler(ConnectionContext connectionContext, SocketMessageHandler socketMessageHandler,
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler) {
|
||||||
|
super("contrast-device-client", connectionContext, socketMessageHandler, lifecycleHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.cilent;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.ConnectionLifecycleHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.SocketMessageHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Device client transport handler.
|
||||||
|
*
|
||||||
|
* @author wr
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
public class NettyDevClientHandler extends AbstractNettyClientHandler {
|
||||||
|
|
||||||
|
public NettyDevClientHandler(ConnectionContext connectionContext, SocketMessageHandler socketMessageHandler,
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler) {
|
||||||
|
super("device-client", connectionContext, socketMessageHandler, lifecycleHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.cilent;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.ConnectionLifecycleHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.SocketMessageHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source client transport handler.
|
||||||
|
*
|
||||||
|
* @author wr
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
public class NettySourceClientHandler extends AbstractNettyClientHandler {
|
||||||
|
|
||||||
|
public NettySourceClientHandler(ConnectionContext connectionContext, SocketMessageHandler socketMessageHandler,
|
||||||
|
ConnectionLifecycleHandler lifecycleHandler) {
|
||||||
|
super("source-client", connectionContext, socketMessageHandler, lifecycleHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.communication.constants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket transport constants kept by the communication foundation.
|
||||||
|
* Stage 4-A extracts these values from detection-only helpers so the
|
||||||
|
* retained transport layer does not depend on business utility classes.
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
public final class SocketTransportConstants {
|
||||||
|
|
||||||
|
public static final String SOURCE_SESSION_TAG = "_Source";
|
||||||
|
|
||||||
|
public static final String DEVICE_SESSION_TAG = "_Dev";
|
||||||
|
|
||||||
|
public static final String CONTRAST_SESSION_TAG = "_Contrast_Dev";
|
||||||
|
|
||||||
|
private SocketTransportConstants() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.communication.handler;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Connection lifecycle callback for the retained communication foundation.
|
||||||
|
* Business modules can attach optional callbacks here without leaking their
|
||||||
|
* own service types into Netty and WebSocket infrastructure.
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
public interface ConnectionLifecycleHandler {
|
||||||
|
|
||||||
|
ConnectionLifecycleHandler NO_OP = new ConnectionLifecycleHandler() {
|
||||||
|
};
|
||||||
|
|
||||||
|
default void onConnected(ConnectionContext context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onDisconnected(ConnectionContext context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onConnectFailed(ConnectionContext context, Throwable cause) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onIdleTimeout(ConnectionContext context) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onMessageHandlingError(ConnectionContext context, String message, Throwable cause) {
|
||||||
|
}
|
||||||
|
|
||||||
|
default void onException(ConnectionContext context, Throwable cause) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.communication.handler;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Heartbeat protocol strategy for generic client connections.
|
||||||
|
* The transport layer only knows when to send and detect heartbeat frames;
|
||||||
|
* the concrete heartbeat payload is provided by the business side.
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
public interface HeartbeatMessageStrategy {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the outbound heartbeat packet. Return {@code null} or blank to
|
||||||
|
* disable heartbeat sending for the current connection.
|
||||||
|
*/
|
||||||
|
String buildHeartbeatMessage(ConnectionContext context);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether the inbound message is a heartbeat response frame.
|
||||||
|
*/
|
||||||
|
boolean isHeartbeatResponse(ConnectionContext context, String message);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.communication.handler;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket 消息处理接口。
|
||||||
|
* 第一阶段先把消息回调从具体业务 Service 中抽离成统一入口,后续可以继续沉淀为独立通讯模块。
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface SocketMessageHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理收到的 Socket 消息。
|
||||||
|
*
|
||||||
|
* @param context 连接上下文
|
||||||
|
* @param message 文本消息
|
||||||
|
* @throws Exception 处理异常
|
||||||
|
*/
|
||||||
|
void handle(ConnectionContext context, String message) throws Exception;
|
||||||
|
}
|
||||||
@@ -0,0 +1,111 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.communication.model;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.ConnectionLifecycleHandler;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.HeartbeatMessageStrategy;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.handler.SocketMessageHandler;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic communication connection context.
|
||||||
|
* Stage 4-A keeps transport identity and optional callbacks in one place so
|
||||||
|
* Netty client/server code does not need to know detection business types.
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
public class ConnectionContext {
|
||||||
|
|
||||||
|
public static final String ATTR_PRE_DETECTION_PARAM = "preDetectionParam";
|
||||||
|
|
||||||
|
public static final String ATTR_CONTRAST_PARAM = "contrastDetectionParam";
|
||||||
|
|
||||||
|
public static final String ATTR_SOCKET_MESSAGE_HANDLER = "socketMessageHandler";
|
||||||
|
|
||||||
|
public static final String ATTR_CONNECTION_LIFECYCLE_HANDLER = "connectionLifecycleHandler";
|
||||||
|
|
||||||
|
public static final String ATTR_HEARTBEAT_MESSAGE_STRATEGY = "heartbeatMessageStrategy";
|
||||||
|
|
||||||
|
private final String userId;
|
||||||
|
|
||||||
|
private final ConnectionType connectionType;
|
||||||
|
|
||||||
|
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
public ConnectionContext(String userId, ConnectionType connectionType) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.connectionType = connectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConnectionContext of(String userId, ConnectionType connectionType) {
|
||||||
|
return new ConnectionContext(userId, connectionType);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionType getConnectionType() {
|
||||||
|
return connectionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key refactor point: the transport foundation now resolves the session
|
||||||
|
* key from one place instead of reassembling it across multiple classes.
|
||||||
|
*/
|
||||||
|
public String getSessionKey() {
|
||||||
|
if (StrUtil.isBlank(userId) || connectionType == null) {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
return userId + connectionType.getSessionTag();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionContext addAttribute(String key, Object value) {
|
||||||
|
if (StrUtil.isNotBlank(key) && value != null) {
|
||||||
|
attributes.put(key, value);
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionContext addMessageHandler(SocketMessageHandler handler) {
|
||||||
|
return addAttribute(ATTR_SOCKET_MESSAGE_HANDLER, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SocketMessageHandler getMessageHandler() {
|
||||||
|
return getAttribute(ATTR_SOCKET_MESSAGE_HANDLER, SocketMessageHandler.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionContext addLifecycleHandler(ConnectionLifecycleHandler lifecycleHandler) {
|
||||||
|
return addAttribute(ATTR_CONNECTION_LIFECYCLE_HANDLER, lifecycleHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionLifecycleHandler getLifecycleHandler() {
|
||||||
|
return getAttribute(ATTR_CONNECTION_LIFECYCLE_HANDLER, ConnectionLifecycleHandler.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ConnectionContext addHeartbeatStrategy(HeartbeatMessageStrategy heartbeatMessageStrategy) {
|
||||||
|
return addAttribute(ATTR_HEARTBEAT_MESSAGE_STRATEGY, heartbeatMessageStrategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public HeartbeatMessageStrategy getHeartbeatStrategy() {
|
||||||
|
return getAttribute(ATTR_HEARTBEAT_MESSAGE_STRATEGY, HeartbeatMessageStrategy.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Object getAttribute(String key) {
|
||||||
|
return attributes.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getAttribute(String key, Class<T> type) {
|
||||||
|
Object value = attributes.get(key);
|
||||||
|
if (type.isInstance(value)) {
|
||||||
|
return type.cast(value);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.communication.model;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.constants.SocketTransportConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Communication connection types retained by the transport foundation.
|
||||||
|
* Stage 4-A keeps the session tags in a neutral constants class so the
|
||||||
|
* Netty/WebSocket base layer no longer depends on detection helpers.
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
public enum ConnectionType {
|
||||||
|
|
||||||
|
SOURCE(SocketTransportConstants.SOURCE_SESSION_TAG, "程控源", false),
|
||||||
|
DEVICE(SocketTransportConstants.DEVICE_SESSION_TAG, "被检设备", true),
|
||||||
|
CONTRAST(SocketTransportConstants.CONTRAST_SESSION_TAG, "比对被检设备", true);
|
||||||
|
|
||||||
|
private final String sessionTag;
|
||||||
|
|
||||||
|
private final String displayName;
|
||||||
|
|
||||||
|
private final boolean enableIdleMonitor;
|
||||||
|
|
||||||
|
ConnectionType(String sessionTag, String displayName, boolean enableIdleMonitor) {
|
||||||
|
this.sessionTag = sessionTag;
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.enableIdleMonitor = enableIdleMonitor;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSessionTag() {
|
||||||
|
return sessionTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableIdleMonitor() {
|
||||||
|
return enableIdleMonitor;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.config;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Socket连接配置管理类
|
||||||
|
* 定义哪些requestId需要建立通道连接,以及IP/PORT配置
|
||||||
|
*
|
||||||
|
* @Author: hongawen
|
||||||
|
* @Date: 2024/12/10
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@ConfigurationProperties(prefix = "socket")
|
||||||
|
public class SocketConnectionConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 程控源设备配置
|
||||||
|
*/
|
||||||
|
private SourceConfig source = new SourceConfig();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被检设备配置
|
||||||
|
*/
|
||||||
|
private DeviceConfig device = new DeviceConfig();
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class SourceConfig {
|
||||||
|
/**
|
||||||
|
* 程控源IP地址
|
||||||
|
*/
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 程控源端口号
|
||||||
|
*/
|
||||||
|
private Integer port;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DeviceConfig {
|
||||||
|
/**
|
||||||
|
* 被检设备IP地址
|
||||||
|
*/
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 被检设备端口号
|
||||||
|
*/
|
||||||
|
private Integer port;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取程控源配置
|
||||||
|
*/
|
||||||
|
public SourceConfig getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取被检设备配置
|
||||||
|
*/
|
||||||
|
public DeviceConfig getDevice() {
|
||||||
|
return device;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要建立程控源通道的requestId集合
|
||||||
|
* 这些requestId在发送消息时,如果程控源通道不存在,会自动建立连接
|
||||||
|
*/
|
||||||
|
private static final Set<String> SOURCE_CONNECTION_REQUEST_IDS = new HashSet<>(Arrays.asList(
|
||||||
|
// 源通讯检测
|
||||||
|
"yjc_ytxjy"
|
||||||
|
// 可以根据实际业务需求添加更多requestId
|
||||||
|
));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 需要建立被检设备通道的requestId集合
|
||||||
|
* 这些requestId在发送消息时,如果被检设备通道不存在,会自动建立连接
|
||||||
|
*/
|
||||||
|
private static final Set<String> DEVICE_CONNECTION_REQUEST_IDS = new HashSet<>(Arrays.asList(
|
||||||
|
// 连接建立
|
||||||
|
"yjc_sbtxjy",
|
||||||
|
// ftp文件传送指令
|
||||||
|
"FTP_SEND$01"
|
||||||
|
// 可以根据实际业务需求添加更多requestId
|
||||||
|
));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查指定的requestId是否需要建立程控源连接
|
||||||
|
*
|
||||||
|
* @param requestId 请求ID
|
||||||
|
* @return boolean true:需要建立连接, false:不需要建立连接
|
||||||
|
*/
|
||||||
|
public static boolean needsSourceConnection(String requestId) {
|
||||||
|
return SOURCE_CONNECTION_REQUEST_IDS.contains(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查指定的requestId是否需要建立被检设备连接
|
||||||
|
*
|
||||||
|
* @param requestId 请求ID
|
||||||
|
* @return boolean true:需要建立连接, false:不需要建立连接
|
||||||
|
*/
|
||||||
|
public static boolean needsDeviceConnection(String requestId) {
|
||||||
|
return DEVICE_CONNECTION_REQUEST_IDS.contains(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加需要建立程控源连接的requestId
|
||||||
|
* 支持运行时动态添加
|
||||||
|
*
|
||||||
|
* @param requestId 请求ID
|
||||||
|
*/
|
||||||
|
public static void addSourceConnectionRequestId(String requestId) {
|
||||||
|
SOURCE_CONNECTION_REQUEST_IDS.add(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加需要建立被检设备连接的requestId
|
||||||
|
* 支持运行时动态添加
|
||||||
|
*
|
||||||
|
* @param requestId 请求ID
|
||||||
|
*/
|
||||||
|
public static void addDeviceConnectionRequestId(String requestId) {
|
||||||
|
DEVICE_CONNECTION_REQUEST_IDS.add(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除程控源连接requestId
|
||||||
|
*
|
||||||
|
* @param requestId 请求ID
|
||||||
|
*/
|
||||||
|
public static void removeSourceConnectionRequestId(String requestId) {
|
||||||
|
SOURCE_CONNECTION_REQUEST_IDS.remove(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除被检设备连接requestId
|
||||||
|
*
|
||||||
|
* @param requestId 请求ID
|
||||||
|
*/
|
||||||
|
public static void removeDeviceConnectionRequestId(String requestId) {
|
||||||
|
DEVICE_CONNECTION_REQUEST_IDS.remove(requestId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有需要建立程控源连接的requestId集合(只读)
|
||||||
|
*
|
||||||
|
* @return Set<String> requestId集合
|
||||||
|
*/
|
||||||
|
public static Set<String> getSourceConnectionRequestIds() {
|
||||||
|
return new HashSet<>(SOURCE_CONNECTION_REQUEST_IDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有需要建立被检设备连接的requestId集合(只读)
|
||||||
|
*
|
||||||
|
* @return Set<String> requestId集合
|
||||||
|
*/
|
||||||
|
public static Set<String> getDeviceConnectionRequestIds() {
|
||||||
|
return new HashSet<>(DEVICE_CONNECTION_REQUEST_IDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.websocket;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.JSON;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic WebSocket session manager.
|
||||||
|
* Stage 4-A removes detection payload conventions and detection parameter
|
||||||
|
* caches from this class so it remains a pure WebSocket session registry.
|
||||||
|
*
|
||||||
|
* @author wr
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class WebServiceManager {
|
||||||
|
|
||||||
|
private static final Map<String, Channel> USER_SESSIONS = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
private WebServiceManager() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void addUser(String userId, Channel channel) {
|
||||||
|
USER_SESSIONS.put(userId, channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Channel removeByUserId(String userId) {
|
||||||
|
return USER_SESSIONS.remove(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
|
public static void removeChannel(String channelId) {
|
||||||
|
Iterator<Map.Entry<String, Channel>> iterator = USER_SESSIONS.entrySet().iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
Map.Entry<String, Channel> entry = iterator.next();
|
||||||
|
if (entry.getValue().id().toString().equals(channelId)) {
|
||||||
|
iterator.remove();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendMsg(String userId, String msg) {
|
||||||
|
Channel channel = USER_SESSIONS.get(userId);
|
||||||
|
if (Objects.nonNull(channel) && channel.isActive()) {
|
||||||
|
channel.writeAndFlush(new TextWebSocketFrame(msg));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.error("WebSocket push failed because session is offline, time={}, userId={}",
|
||||||
|
LocalDateTime.now(), userId);
|
||||||
|
WebSocketHandler.cleanupSocketResources(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sendJson(String userId, Object payload) {
|
||||||
|
Channel channel = USER_SESSIONS.get(userId);
|
||||||
|
if (Objects.nonNull(channel) && channel.isActive()) {
|
||||||
|
channel.writeAndFlush(new TextWebSocketFrame(JSON.toJSONString(payload)));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.error("WebSocket json push failed because session is offline, time={}, userId={}",
|
||||||
|
LocalDateTime.now(), userId);
|
||||||
|
WebSocketHandler.cleanupSocketResources(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int getOnlineUserCount() {
|
||||||
|
return USER_SESSIONS.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isUserOnline(String userId) {
|
||||||
|
Channel channel = USER_SESSIONS.get(userId);
|
||||||
|
return Objects.nonNull(channel) && channel.isActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Set<String> getOnlineUserIds() {
|
||||||
|
return new HashSet<>(USER_SESSIONS.keySet());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.websocket;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket常量管理类
|
||||||
|
*
|
||||||
|
* @author wr
|
||||||
|
* @date 2024/12/10
|
||||||
|
*/
|
||||||
|
public final class WebSocketConstants {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL参数分隔符
|
||||||
|
*/
|
||||||
|
public static final String QUESTION_MARK = "?";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* URL参数等号分隔符
|
||||||
|
*/
|
||||||
|
public static final String EQUAL_TO = "=";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端心跳消息
|
||||||
|
*/
|
||||||
|
public static final String HEARTBEAT_PING = "alive";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端心跳响应
|
||||||
|
*/
|
||||||
|
public static final String HEARTBEAT_PONG = "over";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 心跳超时最大次数
|
||||||
|
*/
|
||||||
|
public static final int MAX_HEARTBEAT_MISS_COUNT = 3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket握手失败状态码
|
||||||
|
*/
|
||||||
|
public static final int HANDSHAKE_FAILED_STATUS = 4000;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket握手失败原因
|
||||||
|
*/
|
||||||
|
public static final String HANDSHAKE_FAILED_REASON = "Missing required userId parameter";
|
||||||
|
|
||||||
|
private WebSocketConstants() {
|
||||||
|
// 私有构造函数,防止实例化
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.websocket;
|
||||||
|
|
||||||
|
import com.njcn.gather.detection.util.socket.SocketManager;
|
||||||
|
import com.njcn.gather.detection.util.socket.communication.model.ConnectionType;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
|
import io.netty.handler.codec.CorruptedFrameException;
|
||||||
|
import io.netty.handler.codec.DecoderException;
|
||||||
|
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketHandshakeException;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||||
|
import io.netty.handler.timeout.IdleStateEvent;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static com.njcn.gather.detection.util.socket.websocket.WebSocketConstants.HEARTBEAT_PING;
|
||||||
|
import static com.njcn.gather.detection.util.socket.websocket.WebSocketConstants.HEARTBEAT_PONG;
|
||||||
|
import static com.njcn.gather.detection.util.socket.websocket.WebSocketConstants.MAX_HEARTBEAT_MISS_COUNT;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic WebSocket handler retained by the communication foundation.
|
||||||
|
* Stage 4-A keeps only handshake, heartbeat, session registry and transport
|
||||||
|
* cleanup. Detection-specific quit flows are removed from this class.
|
||||||
|
*
|
||||||
|
* @author wr
|
||||||
|
* @author hongawen
|
||||||
|
* @date 2026/04/07
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class WebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
|
||||||
|
|
||||||
|
private static final String HEARTBEAT_RESPONSE_TEXT = HEARTBEAT_PONG;
|
||||||
|
|
||||||
|
private int times;
|
||||||
|
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
log.info("WebSocket channel active: channelId={}", ctx.channel().id());
|
||||||
|
super.channelActive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) {
|
||||||
|
String messageText = msg.text();
|
||||||
|
if (HEARTBEAT_PING.equals(messageText)) {
|
||||||
|
handleHeartbeat(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.debug("Receive WebSocket business message: userId={}, channelId={}, message={}",
|
||||||
|
userId, ctx.channel().id(), messageText);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerAdded(ChannelHandlerContext ctx) {
|
||||||
|
log.info("WebSocket handler added: channelId={}", ctx.channel().id());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handlerRemoved(ChannelHandlerContext ctx) {
|
||||||
|
log.info("WebSocket handler removed: channelId={}, userId={}", ctx.channel().id(), userId);
|
||||||
|
if (userId != null) {
|
||||||
|
WebServiceManager.removeByUserId(userId);
|
||||||
|
} else {
|
||||||
|
WebServiceManager.removeChannel(ctx.channel().id().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
log.info("WebSocket channel inactive: channelId={}, userId={}", ctx.channel().id(), userId);
|
||||||
|
cleanupSocketResources(userId);
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
|
||||||
|
if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
|
||||||
|
WebSocketServerProtocolHandler.HandshakeComplete handshakeComplete =
|
||||||
|
(WebSocketServerProtocolHandler.HandshakeComplete) evt;
|
||||||
|
userId = ctx.channel().attr(AttributeKey.<String>valueOf("userId")).get();
|
||||||
|
log.info("WebSocket handshake complete: userId={}, channelId={}, requestUri={}",
|
||||||
|
userId, ctx.channel().id(), handshakeComplete.requestUri());
|
||||||
|
if (userId != null) {
|
||||||
|
WebServiceManager.addUser(userId, ctx.channel());
|
||||||
|
}
|
||||||
|
sendConnectionSuccessMessage(ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (evt instanceof IdleStateEvent) {
|
||||||
|
times++;
|
||||||
|
log.warn("WebSocket heartbeat miss: channelId={}, userId={}, missCount={}",
|
||||||
|
ctx.channel().id(), userId, times);
|
||||||
|
if (times > MAX_HEARTBEAT_MISS_COUNT) {
|
||||||
|
cleanupSocketResources(userId);
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
super.userEventTriggered(ctx, evt);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
logExceptionByType(ctx.channel().id().toString(), cause);
|
||||||
|
cleanupSocketResources(userId);
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendConnectionSuccessMessage(ChannelHandlerContext ctx) {
|
||||||
|
String welcomeMessage = String.format(
|
||||||
|
"{\"type\":\"connection\",\"status\":\"success\",\"message\":\"WebSocket连接建立成功\",\"userId\":\"%s\",\"timestamp\":%d}",
|
||||||
|
userId, System.currentTimeMillis());
|
||||||
|
ctx.channel().writeAndFlush(new TextWebSocketFrame(welcomeMessage));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleHeartbeat(ChannelHandlerContext ctx) {
|
||||||
|
times = 0;
|
||||||
|
ctx.channel().writeAndFlush(new TextWebSocketFrame(HEARTBEAT_RESPONSE_TEXT));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void logExceptionByType(String channelId, Throwable cause) {
|
||||||
|
if (cause instanceof IOException) {
|
||||||
|
log.info("WebSocket network exception: channelId={}, message={}", channelId, cause.getMessage());
|
||||||
|
} else if (cause instanceof WebSocketHandshakeException) {
|
||||||
|
log.warn("WebSocket handshake exception: channelId={}, message={}", channelId, cause.getMessage());
|
||||||
|
} else if (cause instanceof DecoderException || cause instanceof CorruptedFrameException) {
|
||||||
|
log.error("WebSocket decode exception: channelId={}, message={}", channelId, cause.getMessage(), cause);
|
||||||
|
} else if (cause instanceof IllegalArgumentException) {
|
||||||
|
log.warn("WebSocket argument exception: channelId={}, message={}", channelId, cause.getMessage());
|
||||||
|
} else {
|
||||||
|
log.error("WebSocket unclassified exception: channelId={}, message={}", channelId, cause.getMessage(), cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key refactor point: websocket disconnect now performs generic transport
|
||||||
|
* cleanup only, which makes this layer independent from detection flows.
|
||||||
|
*/
|
||||||
|
public static void cleanupSocketResources(String userId) {
|
||||||
|
if (userId == null || userId.trim().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WebServiceManager.removeByUserId(userId);
|
||||||
|
for (ConnectionType connectionType : ConnectionType.values()) {
|
||||||
|
SocketManager.removeUser(userId + connectionType.getSessionTag());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,184 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.websocket;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import io.netty.channel.ChannelPipeline;
|
||||||
|
import io.netty.channel.socket.SocketChannel;
|
||||||
|
import io.netty.handler.codec.http.*;
|
||||||
|
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
|
||||||
|
import io.netty.handler.stream.ChunkedWriteHandler;
|
||||||
|
import io.netty.handler.timeout.IdleStateHandler;
|
||||||
|
import io.netty.util.AttributeKey;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket服务端管道初始化器
|
||||||
|
*
|
||||||
|
* 职责:
|
||||||
|
* 1. 为每个新的WebSocket连接配置处理器链(Pipeline)
|
||||||
|
* 2. 按正确顺序添加各种Handler,确保数据流正确处理
|
||||||
|
* 3. 配置HTTP到WebSocket的协议升级
|
||||||
|
* 4. 设置心跳检测和异常处理机制
|
||||||
|
*
|
||||||
|
* 处理流程:
|
||||||
|
* HTTP请求 → HTTP编解码 → 分块处理 → 消息聚合 → 协议升级 → 心跳检测 → 业务处理 → 异常处理
|
||||||
|
*
|
||||||
|
* @Description: webSocket服务端自定义配置
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/10 14:20
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class WebSocketInitializer extends ChannelInitializer<SocketChannel> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket访问路径
|
||||||
|
*/
|
||||||
|
private static final String WEBSOCKET_PATH = "/hello";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTTP消息最大聚合大小:512KB
|
||||||
|
* 用于WebSocket握手和消息传输
|
||||||
|
*/
|
||||||
|
private static final int MAX_CONTENT_LENGTH = 512 * 1024;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 心跳检测间隔:13秒
|
||||||
|
* 13秒内没有收到客户端消息则触发空闲事件
|
||||||
|
*/
|
||||||
|
private static final int READER_IDLE_TIME_SECONDS = 13;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 为每个新连接初始化处理器管道
|
||||||
|
* 注意:Handler的添加顺序非常重要,决定了数据的处理流向
|
||||||
|
*
|
||||||
|
* @param ch 新建立的Socket通道
|
||||||
|
* @throws Exception 初始化过程中的异常
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected void initChannel(SocketChannel ch) throws Exception {
|
||||||
|
ChannelPipeline pipeline = ch.pipeline();
|
||||||
|
|
||||||
|
// 1. HTTP协议处理器
|
||||||
|
// HttpServerCodec = HttpRequestDecoder + HttpResponseEncoder
|
||||||
|
// 负责HTTP请求解码和HTTP响应编码
|
||||||
|
pipeline.addLast("http-codec", new HttpServerCodec());
|
||||||
|
|
||||||
|
// 2. 分块写入处理器
|
||||||
|
// 用于处理大文件的分块传输,防止内存溢出
|
||||||
|
// 支持ChunkedInput,如ChunkedFile、ChunkedNioFile等
|
||||||
|
pipeline.addLast("chunked-write", new ChunkedWriteHandler());
|
||||||
|
|
||||||
|
// 3. HTTP消息聚合器
|
||||||
|
// 将分片的HTTP消息重新组装成完整的FullHttpRequest或FullHttpResponse
|
||||||
|
// WebSocket握手需要完整的HTTP请求,所以这个Handler必须添加
|
||||||
|
pipeline.addLast("http-aggregator", new HttpObjectAggregator(MAX_CONTENT_LENGTH));
|
||||||
|
|
||||||
|
// 4. WebSocket URL预处理器
|
||||||
|
// 在WebSocket握手之前处理URL参数,验证用户ID
|
||||||
|
pipeline.addLast("websocket-preprocessor", new WebSocketPreprocessor());
|
||||||
|
|
||||||
|
// 5. WebSocket协议升级处理器
|
||||||
|
// 处理WebSocket握手,将HTTP协议升级为WebSocket协议
|
||||||
|
// 只有访问指定路径(WEBSOCKET_PATH)的请求才会被升级
|
||||||
|
// 升级后会移除HTTP相关的Handler,添加WebSocket相关的Handler
|
||||||
|
pipeline.addLast("websocket-protocol", new WebSocketServerProtocolHandler(WEBSOCKET_PATH));
|
||||||
|
|
||||||
|
// 6. 空闲状态检测器
|
||||||
|
// 检测连接的空闲状态,用于心跳机制
|
||||||
|
// readerIdleTime: 读空闲时间,writerIdleTime: 写空闲时间,allIdleTime: 读写空闲时间
|
||||||
|
pipeline.addLast("idle-state", new IdleStateHandler(READER_IDLE_TIME_SECONDS, 0, 0, TimeUnit.SECONDS));
|
||||||
|
|
||||||
|
// 7. 自定义WebSocket业务处理器
|
||||||
|
// 处理WebSocket帧,实现具体的业务逻辑
|
||||||
|
// 包括心跳处理、消息路由、连接管理等
|
||||||
|
pipeline.addLast("websocket-handler", new WebSocketHandler());
|
||||||
|
|
||||||
|
// 7. 全局异常处理器
|
||||||
|
// 处理整个管道中未被捕获的异常,作为最后的异常处理兜底
|
||||||
|
pipeline.addLast("exception-handler", new GlobalExceptionHandler());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket预处理器
|
||||||
|
* 在WebSocket握手之前验证URL参数并清理URL
|
||||||
|
*/
|
||||||
|
private static class WebSocketPreprocessor extends ChannelInboundHandlerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
if (msg instanceof FullHttpRequest) {
|
||||||
|
FullHttpRequest request = (FullHttpRequest) msg;
|
||||||
|
String uri = request.uri();
|
||||||
|
|
||||||
|
log.debug("WebSocket预处理器收到HTTP请求:{}", uri);
|
||||||
|
|
||||||
|
// 验证并提取userId
|
||||||
|
String userId = extractUserId(uri);
|
||||||
|
if (userId == null || userId.trim().isEmpty()) {
|
||||||
|
log.warn("WebSocket连接被拒绝:缺少userId参数, uri: {}", uri);
|
||||||
|
FullHttpResponse response = new DefaultFullHttpResponse(
|
||||||
|
HttpVersion.HTTP_1_1,
|
||||||
|
HttpResponseStatus.BAD_REQUEST
|
||||||
|
);
|
||||||
|
ctx.writeAndFlush(response).addListener(f -> ctx.close());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将userId存储到Channel属性中
|
||||||
|
ctx.channel().attr(AttributeKey.<String>valueOf("userId")).set(userId);
|
||||||
|
|
||||||
|
// 清理URL参数
|
||||||
|
if (uri.contains("?")) {
|
||||||
|
String cleanUri = uri.substring(0, uri.indexOf("?"));
|
||||||
|
request.setUri(cleanUri);
|
||||||
|
log.debug("URL已清理,原始: {}, 清理后: {}, userId: {}", uri, cleanUri, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 继续传递给下一个Handler
|
||||||
|
super.channelRead(ctx, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractUserId(String uri) {
|
||||||
|
if (!uri.contains("name=")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
int start = uri.indexOf("name=") + 5;
|
||||||
|
int end = uri.indexOf("&", start);
|
||||||
|
if (end == -1) {
|
||||||
|
return uri.substring(start);
|
||||||
|
} else {
|
||||||
|
return uri.substring(start, end);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局异常处理器
|
||||||
|
* 作为管道中的最后一个Handler,捕获所有未处理的异常
|
||||||
|
*/
|
||||||
|
private static class GlobalExceptionHandler extends ChannelInboundHandlerAdapter {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||||
|
// 记录异常详情,便于问题排查
|
||||||
|
log.error("WebSocket连接发生未处理异常,远程地址:{},异常信息:{}",
|
||||||
|
ctx.channel().remoteAddress(), cause.getMessage(), cause);
|
||||||
|
|
||||||
|
// 优雅关闭连接
|
||||||
|
if (ctx.channel().isActive()) {
|
||||||
|
ctx.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
log.debug("WebSocket连接断开,远程地址:{}", ctx.channel().remoteAddress());
|
||||||
|
super.channelInactive(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,237 @@
|
|||||||
|
package com.njcn.gather.detection.util.socket.websocket;
|
||||||
|
|
||||||
|
|
||||||
|
import io.netty.bootstrap.ServerBootstrap;
|
||||||
|
import io.netty.channel.Channel;
|
||||||
|
import io.netty.channel.ChannelFuture;
|
||||||
|
import io.netty.channel.ChannelOption;
|
||||||
|
import io.netty.channel.EventLoopGroup;
|
||||||
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
|
import io.netty.handler.logging.LoggingHandler;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.boot.ApplicationArguments;
|
||||||
|
import org.springframework.boot.ApplicationRunner;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import javax.annotation.PreDestroy;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket服务端核心类
|
||||||
|
*
|
||||||
|
* 职责:
|
||||||
|
* 1. 启动基于Netty的WebSocket服务器
|
||||||
|
* 2. 管理服务器生命周期(启动/关闭)
|
||||||
|
* 3. 提供高性能的WebSocket通信支持
|
||||||
|
*
|
||||||
|
* 特性:
|
||||||
|
* - 使用ApplicationRunner确保在Spring容器完全启动后再启动WebSocket服务
|
||||||
|
* - 使用CompletableFuture异步启动,避免阻塞Spring Boot主线程
|
||||||
|
* - 支持优雅关闭,确保资源正确释放
|
||||||
|
* - 完善的异常处理和日志记录
|
||||||
|
*
|
||||||
|
* @Description: websocket服务端
|
||||||
|
* @Author: wr
|
||||||
|
* @Date: 2024/12/10 13:59
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class WebSocketService implements ApplicationRunner {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WebSocket服务器监听端口
|
||||||
|
* 默认7777端口,可通过配置文件webSocket.port自定义
|
||||||
|
* 客户端连接地址:ws://host:port/hello?name=userId
|
||||||
|
*/
|
||||||
|
@Value("${webSocket.port:7777}")
|
||||||
|
int port;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Netty Boss线程组
|
||||||
|
* 专门负责接受新的客户端连接请求
|
||||||
|
* 通常配置1个线程即可,因为接受连接的操作相对简单
|
||||||
|
*/
|
||||||
|
EventLoopGroup bossGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Netty Worker线程组
|
||||||
|
* 专门负责处理已建立连接的I/O操作和业务逻辑
|
||||||
|
* 默认线程数 = CPU核心数 * 2,用于并发处理多个客户端
|
||||||
|
*/
|
||||||
|
EventLoopGroup workerGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务器通道引用
|
||||||
|
* 保存绑定端口后的Channel,用于服务器关闭时释放资源
|
||||||
|
*/
|
||||||
|
private Channel serverChannel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步启动任务的Future对象
|
||||||
|
* 用于管理WebSocket服务器的异步启动过程
|
||||||
|
* 可以用来取消启动任务或检查启动状态
|
||||||
|
*/
|
||||||
|
private CompletableFuture<Void> serverFuture;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring Boot应用启动完成后自动调用此方法
|
||||||
|
* 使用ApplicationRunner确保在所有Bean初始化完成后再启动WebSocket服务
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void run(ApplicationArguments args){
|
||||||
|
// 使用CompletableFuture异步启动WebSocket服务,避免阻塞Spring Boot主线程
|
||||||
|
// 这样可以让应用快速启动完成,WebSocket服务在后台异步启动
|
||||||
|
serverFuture = CompletableFuture.runAsync(this::startWebSocketServer)
|
||||||
|
.exceptionally(throwable -> {
|
||||||
|
// 如果启动过程中发生异常,记录日志但不影响应用启动
|
||||||
|
log.error("WebSocket服务启动异常", throwable);
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 启动WebSocket服务器的核心方法
|
||||||
|
* 此方法会一直阻塞直到服务器关闭,所以需要在异步线程中执行
|
||||||
|
*/
|
||||||
|
private void startWebSocketServer() {
|
||||||
|
try {
|
||||||
|
// 1. 创建线程组
|
||||||
|
// bossGroup: 专门负责接受新的客户端连接请求
|
||||||
|
// 可以自定义线程的数量,这里使用默认值(通常为1个线程)
|
||||||
|
bossGroup = new NioEventLoopGroup(1);
|
||||||
|
|
||||||
|
// workerGroup: 专门负责处理已建立连接的I/O操作
|
||||||
|
// 默认创建的线程数量 = CPU 处理器数量 * 2,用于处理业务逻辑
|
||||||
|
workerGroup = new NioEventLoopGroup();
|
||||||
|
|
||||||
|
// 2. 配置服务器启动参数
|
||||||
|
ServerBootstrap serverBootstrap = new ServerBootstrap();
|
||||||
|
serverBootstrap.group(bossGroup, workerGroup)
|
||||||
|
.channel(NioServerSocketChannel.class)
|
||||||
|
.handler(new LoggingHandler())
|
||||||
|
// 网络配置参数
|
||||||
|
.option(ChannelOption.SO_BACKLOG, 128)
|
||||||
|
// TCP连接建立超时时间5秒
|
||||||
|
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
|
||||||
|
// 子通道配置(针对每个客户端连接)
|
||||||
|
// 启用TCP keepalive机制,检测死连接
|
||||||
|
.childOption(ChannelOption.SO_KEEPALIVE, true)
|
||||||
|
.childHandler(new WebSocketInitializer());
|
||||||
|
|
||||||
|
// 3. 绑定端口并启动服务器
|
||||||
|
ChannelFuture future = serverBootstrap.bind(port).sync();
|
||||||
|
// 保存服务器通道引用,用于后续关闭操作
|
||||||
|
serverChannel = future.channel();
|
||||||
|
// 4. 监听绑定结果并记录日志
|
||||||
|
future.addListener(f -> {
|
||||||
|
if (future.isSuccess()) {
|
||||||
|
log.info("webSocket服务启动成功,端口:{}", port);
|
||||||
|
} else {
|
||||||
|
log.error("webSocket服务启动失败,端口:{}", port);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 5. 等待服务器关闭
|
||||||
|
// 这里会一直阻塞,直到serverChannel被外部关闭
|
||||||
|
// 这就是为什么需要在异步线程中执行此方法的原因
|
||||||
|
future.channel().closeFuture().sync();
|
||||||
|
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// 如果线程被中断(比如应用关闭),记录日志并恢复中断状态
|
||||||
|
log.error("WebSocket服务启动过程中被中断", e);
|
||||||
|
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||||
|
} catch (Exception e) {
|
||||||
|
// 捕获其他所有异常,记录日志并抛出运行时异常
|
||||||
|
log.error("WebSocket服务启动失败", e);
|
||||||
|
throw new RuntimeException("WebSocket服务启动失败", e);
|
||||||
|
} finally {
|
||||||
|
// 无论成功还是失败,都要清理资源
|
||||||
|
shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 优雅关闭Netty线程组资源
|
||||||
|
* 私有方法,用于在服务器启动异常时清理资源
|
||||||
|
*/
|
||||||
|
private void shutdownGracefully() {
|
||||||
|
// 优雅关闭接收连接的线程组
|
||||||
|
if (bossGroup != null) {
|
||||||
|
bossGroup.shutdownGracefully();
|
||||||
|
}
|
||||||
|
// 优雅关闭处理I/O的线程组
|
||||||
|
if (workerGroup != null) {
|
||||||
|
workerGroup.shutdownGracefully();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spring容器销毁时自动调用此方法释放资源
|
||||||
|
* 使用@PreDestroy确保在应用关闭时优雅地关闭WebSocket服务
|
||||||
|
*/
|
||||||
|
@PreDestroy
|
||||||
|
public void destroy() throws InterruptedException {
|
||||||
|
log.info("正在关闭WebSocket服务...");
|
||||||
|
|
||||||
|
// 步骤1: 首先关闭服务器通道,停止接受新的连接请求
|
||||||
|
// 这样可以确保不会有新的客户端连接进来
|
||||||
|
if (serverChannel != null) {
|
||||||
|
try {
|
||||||
|
// 等待最多5秒让服务器通道关闭
|
||||||
|
serverChannel.close().awaitUninterruptibly(5, TimeUnit.SECONDS);
|
||||||
|
log.debug("服务器通道已关闭");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("关闭服务器通道时发生异常", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤2: 关闭bossGroup线程组
|
||||||
|
// bossGroup负责接受连接,现在可以安全关闭了
|
||||||
|
if (bossGroup != null) {
|
||||||
|
try {
|
||||||
|
// 优雅关闭:静默期0秒,超时时间5秒
|
||||||
|
// 静默期0秒意味着立即开始关闭,超时5秒后强制关闭
|
||||||
|
bossGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS).sync();
|
||||||
|
log.debug("bossGroup线程组已关闭");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.warn("关闭bossGroup时被中断", e);
|
||||||
|
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤3: 关闭workerGroup线程组
|
||||||
|
// workerGroup负责处理I/O,需要等待现有连接处理完成
|
||||||
|
if (workerGroup != null) {
|
||||||
|
try {
|
||||||
|
// 等待现有任务完成,但最多等待5秒
|
||||||
|
workerGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS).sync();
|
||||||
|
log.debug("workerGroup线程组已关闭");
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
log.warn("关闭workerGroup时被中断", e);
|
||||||
|
Thread.currentThread().interrupt(); // 恢复中断状态
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 步骤4: 取消异步启动任务(如果还在运行)
|
||||||
|
// 这可以避免在应用关闭后还有线程在后台运行
|
||||||
|
if (serverFuture != null && !serverFuture.isDone()) {
|
||||||
|
// true表示允许中断正在执行的任务
|
||||||
|
boolean cancelled = serverFuture.cancel(true);
|
||||||
|
if (cancelled) {
|
||||||
|
log.debug("异步启动任务已取消");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("webSocket服务已销毁");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
74
entrance/pom.xml
Normal file
74
entrance/pom.xml
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?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.njcn.gather</groupId>
|
||||||
|
<artifactId>CN_Tool</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>entrance</artifactId>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn.gather</groupId>
|
||||||
|
<artifactId>system</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn.gather</groupId>
|
||||||
|
<artifactId>detection</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn.gather</groupId>
|
||||||
|
<artifactId>user</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- Key refactor point: retain activation as a platform capability,
|
||||||
|
but stop pulling it transitively through detection. -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn.gather</groupId>
|
||||||
|
<artifactId>activate-tool</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<finalName>entrance</finalName>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<phase>package</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.8.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.njcn.gather;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.mybatis.spring.annotation.MapperScan;
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
|
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@MapperScan("com.njcn.**.mapper")
|
||||||
|
@SpringBootApplication(scanBasePackages = "com.njcn")
|
||||||
|
//@EnableAspectJAutoProxy
|
||||||
|
public class EntranceApplication {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(EntranceApplication.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
54
entrance/src/main/resources/application.yml
Normal file
54
entrance/src/main/resources/application.yml
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
server:
|
||||||
|
port: 18192
|
||||||
|
|
||||||
|
spring:
|
||||||
|
application:
|
||||||
|
name: entrance
|
||||||
|
datasource:
|
||||||
|
druid:
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
url: jdbc:mysql://192.168.1.22:13306/cn_tool?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
|
||||||
|
username: root
|
||||||
|
password: njcnpqs
|
||||||
|
initial-size: 5
|
||||||
|
min-idle: 5
|
||||||
|
max-active: 50
|
||||||
|
max-wait: 60000
|
||||||
|
min-evictable-idle-time-millis: 300000
|
||||||
|
validation-query: select 1
|
||||||
|
test-while-idle: true
|
||||||
|
test-on-borrow: false
|
||||||
|
test-on-return: false
|
||||||
|
pool-prepared-statements: true
|
||||||
|
max-pool-prepared-statement-per-connection-size: 20
|
||||||
|
|
||||||
|
mybatis-plus:
|
||||||
|
mapper-locations: classpath*:com/njcn/**/mapping/*.xml
|
||||||
|
# Key refactor point: remove stale business alias packages and retain only
|
||||||
|
# the surviving foundational modules.
|
||||||
|
type-aliases-package: com.njcn.gather.system.dictionary.pojo.po
|
||||||
|
configuration:
|
||||||
|
map-underscore-to-camel-case: true
|
||||||
|
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||||
|
global-config:
|
||||||
|
db-config:
|
||||||
|
id-type: assign_uuid
|
||||||
|
|
||||||
|
socket:
|
||||||
|
source:
|
||||||
|
ip: 127.0.0.1
|
||||||
|
port: 62000
|
||||||
|
device:
|
||||||
|
ip: 127.0.0.1
|
||||||
|
port: 61000
|
||||||
|
|
||||||
|
webSocket:
|
||||||
|
port: 7777
|
||||||
|
|
||||||
|
log:
|
||||||
|
homeDir: D:\logs
|
||||||
|
commonLevel: info
|
||||||
|
|
||||||
|
activate:
|
||||||
|
private-key: "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCcUyYhVqczGxblL+o/xZzF/8nf+LjrfUE/dS1aRHM7uMDD0cgCArhjtfneFePrMxt+Z7W8yNBzSarub8qsfhaVNikV7Es7oaeTygfjQXTi2n4AFkir3fM07J08RpWhl5M8f8uWTCuvFUYAw00gq55typqmnbkmJa2VIUy/iQf+cMCP7abz4/jNhUzUR3qA7TV4oMRgTdIEDUp63YF8dOC+JH8XxYrCVeHXV6fLCwmesdMzl0lB2VTEKMfLbXhOmF5g7P9y/16VCcN8UBuZlbyYfn+GAxJOSbeHi5HshOKfoSuD7Jz+3WQZpNavOWjIFExKIU38/CvnJCOP7XBCqpSTAgMBAAECggEAYeWokWRE3TpvwiOZnUpR/aVMdVi75a3ROL5XIpqPV61B+t/bU3cEpl0GF9C5pUeiRi0IoStZb3mI9D1KPW/REKyUWkhabQO1gFYbTnRlkNOn6MILzKX4cwJjDaZeeo4EBPU7N+qHyOOXrU6hdH5FfxhMdV983ajm5eeuupxER1C2kAcIklTeVpTX6EKOgZb5LBp5ssOVm2P42pOauvcRozRcvZmqnErXmukv0H4l3EVNt4rHpTn9riHUC63e8JfiYzVaF6zuNUxv6nHEft0/SRMw11XSTnNfDzcKqgjz6ksFBS/6eQQYKESk+ONC53HUuYHFAknkwsPupDCT2W8FIQKBgQDLHT/xCU3nxGr4vFKBDNaO2D5oK20ECbBO4oDvLWWmQG7f+6TsMy8PgVdMnoL4RfqGlwFAKEpS6KVFHnBVqnNEhcdy9uCI7x7Xx8UnyUtxj1EDTm76uta9Ki9OrlqB6tImDM9+Ya3vGktW37ht4WOx2OsJRhG1dbf6RLwFlH7DWwKBgQDFBxvi5I1BR6hg6Tj7xd2SqOT2Y+BED3xuSYENhWbmMhLJDResaB7mjztbxlYaY2mOE0holWm2uDmVFFhMh4jYXik4hYH8nmDzq9mDpZCZ9pyjYqnAP8THoAa8EbgrUWB8A6BPH4iL3KbMnBfBKY0pIr2xrvnjQjNBAgta7KDRKQKBgCe6oe4wxrdF2TKsC2tIqpMoQxS3Icy/ZGgZr+SYuaBKTCWtoDW/UT40K3JGMxIDBhzbXphBCUCsVt9tM8Xd4EwP6tJW7dZ7B0pnve2pVwNwaAVAiz6p2yUHIle+jN+Koe5lZRSwYIg7WW81tWpwwsJfzqFyvjYDP6hJV4mz4ROvAoGAaRcdnKvjXApomShMqJ4lTPChD3q+SA8qg3jZSOj6tZXHx00gb2kp8jg7pPvpOTIFPy6x1Ha9aCRjMk0ju84fA6lVuzwa1S907wOehUVuF3Eeo1cgy9Y3k3KbpPyeixxgpkUY4JslLdSHc2NemD0dee951qhJyRmqVOZOQDUuoeECgYEAqBw2cAFk3vM97WY06TSldGA8ajVHx3BYRjj+zl62NTQthy8fw3tqxb3c5e8toOmZWKjZvDhg2TRLhsDDQWEYg3LZG87REqVIjgEPcpjNLidjygGX8n3JF2o0O5I/EMvl0s/+LVQONfduOBvhwDqr8QNisbLsyneiAq7umewMolo="
|
||||||
|
public-key: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnFMmIVanMxsW5S/qP8Wcxf/J3/i4631BP3UtWkRzO7jAw9HIAgK4Y7X53hXj6zMbfme1vMjQc0mq7m/KrH4WlTYpFexLO6Gnk8oH40F04tp+ABZIq93zNOydPEaVoZeTPH/LlkwrrxVGAMNNIKuebcqapp25JiWtlSFMv4kH/nDAj+2m8+P4zYVM1Ed6gO01eKDEYE3SBA1Ket2BfHTgviR/F8WKwlXh11enywsJnrHTM5dJQdlUxCjHy214TpheYOz/cv9elQnDfFAbmZW8mH5/hgMSTkm3h4uR7ITin6Erg+yc/t1kGaTWrzloyBRMSiFN/Pwr5yQjj+1wQqqUkwIDAQAB"
|
||||||
140
entrance/src/main/resources/logback.xml
Normal file
140
entrance/src/main/resources/logback.xml
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration scan="true" scanPeriod="20 seconds" debug="false">
|
||||||
|
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
||||||
|
|
||||||
|
<!-- 直接使用固定配置,避免Spring配置解析时机问题 -->
|
||||||
|
<property name="log.projectName" value="entrance"/>
|
||||||
|
<property name="logCommonLevel" value="info"/>
|
||||||
|
<property name="logHomeDir" value="${logHomeDir:-D:\logs}"/>
|
||||||
|
|
||||||
|
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
|
||||||
|
<conversionRule conversionWord="wex"
|
||||||
|
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
|
||||||
|
<conversionRule conversionWord="ec"
|
||||||
|
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
|
||||||
|
|
||||||
|
|
||||||
|
<!--日志输出格式-->
|
||||||
|
<property name="log.pattern"
|
||||||
|
value="|-%d{yyyy-MM-dd HH:mm:ss.SSS} ${LOG_LEVEL_PATTERN:-%level} ${log.projectName} -- %t %logger{100}.%M ==> %m%n${Log_EXCEPTION_CONVERSION_WORD:-%ec}}}"/>
|
||||||
|
<property name="log.maxHistory" value="30"/>
|
||||||
|
|
||||||
|
<!--客户端输出日志-->
|
||||||
|
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||||
|
<encoder>
|
||||||
|
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!--系统中常规的debug日志-->
|
||||||
|
<!-- 滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 RollingFileAppender -->
|
||||||
|
<appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>
|
||||||
|
${logHomeDir}/${log.projectName}/debug/debug.log
|
||||||
|
</file>
|
||||||
|
<!-- 如果日志级别等于配置级别,过滤器会根据onMath 和 onMismatch接收或拒绝日志。 -->
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<!-- 设置过滤级别 -->
|
||||||
|
<level>DEBUG</level>
|
||||||
|
<!-- 用于配置符合过滤条件的操作 -->
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<!-- 用于配置不符合过滤条件的操作 -->
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
<!-- 最常用的滚动策略,它根据时间来制定滚动策略.既负责滚动也负责触发滚动 SizeAndTimeBasedRollingPolicy-->
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<!--日志输出位置 可相对、和绝对路径 -->
|
||||||
|
<fileNamePattern>
|
||||||
|
${logHomeDir}/${log.projectName}/debug/debug.log.%d{yyyy-MM-dd}.%i.log
|
||||||
|
</fileNamePattern>
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
<!-- 可选节点,控制保留的归档文件的最大数量,超出数量就删除旧文件,假设设置每个月滚动,且<maxHistory>是6,
|
||||||
|
则只保存最近6个月的文件,删除之前的旧文件。注意,删除旧文件是,那些为了归档而创建的目录也会被删除 -->
|
||||||
|
<maxHistory>${log.maxHistory:-30}</maxHistory>
|
||||||
|
<!--重启清理日志文件-->
|
||||||
|
<!-- <cleanHistoryOnStart>true</cleanHistoryOnStart>-->
|
||||||
|
<!--每个文件最多100MB,保留N天的历史记录,但最多20GB-->
|
||||||
|
<!--<totalSizeCap>20GB</totalSizeCap>-->
|
||||||
|
<!--日志文件最大的大小-->
|
||||||
|
<!--<MaxFileSize>${log.maxSize}</MaxFileSize>-->
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder>
|
||||||
|
<pattern>
|
||||||
|
${log.pattern}
|
||||||
|
</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<!--系统中常规的info日志-->
|
||||||
|
<appender name="INFO" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>INFO</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
<file>
|
||||||
|
${logHomeDir}/${log.projectName}/info/info.log
|
||||||
|
</file>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>
|
||||||
|
${logHomeDir}/${log.projectName}/info/info.log.%d{yyyy-MM-dd}.%i.log
|
||||||
|
</fileNamePattern>
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
<maxHistory>${log.maxHistory:-30}</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>
|
||||||
|
${log.pattern}
|
||||||
|
</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
|
||||||
|
<!--系统中常规的error日志-->
|
||||||
|
<appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||||
|
<file>
|
||||||
|
${logHomeDir}/${log.projectName}/error/error.log
|
||||||
|
</file>
|
||||||
|
<filter class="ch.qos.logback.classic.filter.LevelFilter">
|
||||||
|
<level>ERROR</level>
|
||||||
|
<onMatch>ACCEPT</onMatch>
|
||||||
|
<onMismatch>DENY</onMismatch>
|
||||||
|
</filter>
|
||||||
|
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||||
|
<fileNamePattern>
|
||||||
|
${logHomeDir}/${log.projectName}/error/error.log.%d{yyyy-MM-dd}.%i.log
|
||||||
|
</fileNamePattern>
|
||||||
|
<maxFileSize>10MB</maxFileSize>
|
||||||
|
<maxHistory>${log.maxHistory:-30}</maxHistory>
|
||||||
|
</rollingPolicy>
|
||||||
|
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||||
|
<pattern>
|
||||||
|
${log.pattern}
|
||||||
|
</pattern>
|
||||||
|
<charset>UTF-8</charset>
|
||||||
|
</encoder>
|
||||||
|
</appender>
|
||||||
|
|
||||||
|
<logger name="org.apache.catalina.startup.DigesterFactory" level="ERROR"/>
|
||||||
|
<logger name="org.apache.catalina.util.LifecycleBase" level="ERROR"/>
|
||||||
|
<logger name="org.apache.coyote.http11.Http11NioProtocol" level="WARN"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<logger name="com.njcn" level="INFO" additivity="false">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
<appender-ref ref="DEBUG"/>
|
||||||
|
<appender-ref ref="INFO"/>
|
||||||
|
<appender-ref ref="ERROR"/>
|
||||||
|
</logger>
|
||||||
|
|
||||||
|
<root level="${logCommonLevel}">
|
||||||
|
<appender-ref ref="CONSOLE"/>
|
||||||
|
<appender-ref ref="DEBUG"/>
|
||||||
|
<appender-ref ref="INFO"/>
|
||||||
|
<appender-ref ref="ERROR"/>
|
||||||
|
</root>
|
||||||
|
</configuration>
|
||||||
87
pom.xml
Normal file
87
pom.xml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?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>
|
||||||
|
<groupId>com.njcn.gather</groupId>
|
||||||
|
<artifactId>CN_Tool</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
<name>CN_Tool</name>
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>entrance</module>
|
||||||
|
<module>system</module>
|
||||||
|
<module>user</module>
|
||||||
|
<module>detection</module>
|
||||||
|
<module>tools</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
<distributionManagement>
|
||||||
|
<repository>
|
||||||
|
<id>nexus-releases</id>
|
||||||
|
<name>Nexus Release Repository</name>
|
||||||
|
<url>http://192.168.1.22:8001/nexus/content/repositories/releases/</url>
|
||||||
|
</repository>
|
||||||
|
<snapshotRepository>
|
||||||
|
<id>nexus-snapshots</id>
|
||||||
|
<name>Nexus Snapshot Repository</name>
|
||||||
|
<url>http://192.168.1.22:8001/nexus/content/repositories/snapshots/</url>
|
||||||
|
</snapshotRepository>
|
||||||
|
</distributionManagement>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<spring-boot.version>2.3.12.RELEASE</spring-boot.version>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||||
|
<maven.compiler.encoding>UTF-8</maven.compiler.encoding>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
|
<version>${spring-boot.version}</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<version>2.2.2.RELEASE</version>
|
||||||
|
<configuration>
|
||||||
|
<fork>true</fork>
|
||||||
|
<addResources>true</addResources>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.1</version>
|
||||||
|
<configuration>
|
||||||
|
<source>1.8</source>
|
||||||
|
<target>1.8</target>
|
||||||
|
<encoding>UTF-8</encoding>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>true</filtering>
|
||||||
|
</resource>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/java</directory>
|
||||||
|
<includes>
|
||||||
|
<include>**/*.xml</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
9
system/Readme.md
Normal file
9
system/Readme.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
#### 简介
|
||||||
|
系统模块主要包含以下功能:
|
||||||
|
* 审计日志管理
|
||||||
|
* 字典、树形字典管理
|
||||||
|
* 版本注册
|
||||||
|
* 主题管理
|
||||||
|
* 系统文件资源管理
|
||||||
|
* 定时任务管理
|
||||||
|
|
||||||
55
system/pom.xml
Normal file
55
system/pom.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?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.njcn.gather</groupId>
|
||||||
|
<artifactId>CN_Tool</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>system</artifactId>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn</groupId>
|
||||||
|
<artifactId>njcn-common</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn</groupId>
|
||||||
|
<artifactId>mybatis-plus</artifactId>
|
||||||
|
<version>0.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn</groupId>
|
||||||
|
<artifactId>spingboot2.3.12</artifactId>
|
||||||
|
<version>2.3.12</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.njcn.gather</groupId>
|
||||||
|
<artifactId>user</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>fastjson</artifactId>
|
||||||
|
<version>1.2.83</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.apache.poi</groupId>-->
|
||||||
|
<!-- <artifactId>poi-ooxml</artifactId>-->
|
||||||
|
<!-- <version>5.2.3</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
<!-- <dependency>-->
|
||||||
|
<!-- <groupId>org.apache.poi</groupId>-->
|
||||||
|
<!-- <artifactId>poi-ooxml-full</artifactId>-->
|
||||||
|
<!-- <version>5.2.3</version>-->
|
||||||
|
<!-- </dependency>-->
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package com.njcn.gather.system.cfg.controller;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.common.utils.LogUtil;
|
||||||
|
import com.njcn.gather.system.cfg.pojo.param.SysTestConfigParam;
|
||||||
|
import com.njcn.gather.system.cfg.pojo.po.SysTestConfig;
|
||||||
|
import com.njcn.gather.system.cfg.service.ISysTestConfigService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-16
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "检测相关配置")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/sysTestConfig")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysTestConfigController extends BaseController {
|
||||||
|
private final ISysTestConfigService sysTestConfigService;
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@GetMapping("/getConfig")
|
||||||
|
@ApiOperation("获取检测相关配置信息")
|
||||||
|
public HttpResult<SysTestConfig> getConfig() {
|
||||||
|
String methodDescribe = getMethodDescribe("getConfig");
|
||||||
|
LogUtil.njcnDebug(log, "{}", methodDescribe);
|
||||||
|
SysTestConfig sysTestConfig = sysTestConfigService.getOneConfig();
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, sysTestConfig, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
|
||||||
|
@PostMapping("/update")
|
||||||
|
@ApiOperation("修改检测相关配置信息")
|
||||||
|
@ApiImplicitParam(name = "sysTestConfig", value = "检测相关配置信息", required = true)
|
||||||
|
public HttpResult<Boolean> update(@RequestBody @Validated SysTestConfigParam.UpdateParam sysTestConfig) {
|
||||||
|
String methodDescribe = getMethodDescribe("update");
|
||||||
|
LogUtil.njcnDebug(log, "{}", methodDescribe);
|
||||||
|
boolean result = sysTestConfigService.updateTestConfig(sysTestConfig);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@ApiOperation("获取当前场景")
|
||||||
|
@GetMapping("/getCurrentScene")
|
||||||
|
public HttpResult<String> getCurrentScene() {
|
||||||
|
String methodDescribe = getMethodDescribe("getCurrentScene");
|
||||||
|
LogUtil.njcnDebug(log, "{}", methodDescribe);
|
||||||
|
String currrentScene = sysTestConfigService.getCurrrentScene();
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, currrentScene, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@ApiOperation("获取是否在检测时同时生成报告")
|
||||||
|
@GetMapping("/getAutoGenerate")
|
||||||
|
public HttpResult<Integer> getAutoGenerate() {
|
||||||
|
String methodDescribe = getMethodDescribe("getAutoGenerate");
|
||||||
|
LogUtil.njcnDebug(log, "{}", methodDescribe);
|
||||||
|
Integer autoGenerate = sysTestConfigService.getAutoGenerate();
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, autoGenerate, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.njcn.gather.system.cfg.mapper;
|
||||||
|
|
||||||
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
|
import com.njcn.gather.system.cfg.pojo.po.SysTestConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-16
|
||||||
|
*/
|
||||||
|
public interface SysTestConfigMapper extends MPJBaseMapper<SysTestConfig> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<?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.njcn.gather.system.cfg.mapper.SysTestConfigMapper">
|
||||||
|
|
||||||
|
</mapper>
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.njcn.gather.system.cfg.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-03-25
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum SceneEnum {
|
||||||
|
/**
|
||||||
|
* 省级平台
|
||||||
|
*/
|
||||||
|
PROVINCE_PLATFORM("0", "province_platform"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设备出场
|
||||||
|
*/
|
||||||
|
LEAVE_FACTORY_TEST("1", "leave_factory_test"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 研发自测
|
||||||
|
*/
|
||||||
|
SELF_TEST("2", "self_test");
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
private String msg;
|
||||||
|
|
||||||
|
SceneEnum(String value, String msg) {
|
||||||
|
this.value = value;
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static SceneEnum getSceneEnum(String value) {
|
||||||
|
for (SceneEnum sceneEnum : SceneEnum.values()) {
|
||||||
|
if (sceneEnum.getValue().equals(value)) {
|
||||||
|
return sceneEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package com.njcn.gather.system.cfg.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.system.pojo.constant.SystemValidMessage;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import javax.validation.constraints.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/16
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SysTestConfigParam {
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "检测报告是否自动生成0 否;1是")
|
||||||
|
@Min(value = 0, message = SystemValidMessage.AUTO_GENERATE_FORMAT_ERROR)
|
||||||
|
@Max(value = 1, message = SystemValidMessage.AUTO_GENERATE_FORMAT_ERROR)
|
||||||
|
private Integer autoGenerate;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "最大检测次数")
|
||||||
|
private Integer maxTime;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "数据精度")
|
||||||
|
private Integer scale;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "场景")
|
||||||
|
private String scene;
|
||||||
|
|
||||||
|
@ApiModelProperty(value = "比对监测后,当电压、电流不符合时,是否对标准设备进行系数校准")
|
||||||
|
private Integer coefficient;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class UpdateParam extends SysTestConfigParam {
|
||||||
|
@ApiModelProperty("id")
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = SystemValidMessage.ID_FORMAT_ERROR)
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.njcn.gather.system.cfg.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.njcn.db.mybatisplus.bo.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-16
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("sys_test_config")
|
||||||
|
public class SysTestConfig extends BaseEntity implements Serializable {
|
||||||
|
private static final long serialVersionUID = 352471858515754310L;
|
||||||
|
/**
|
||||||
|
* 系统配置表Id
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检测报告是否自动生成: 0 否;1 是
|
||||||
|
*/
|
||||||
|
@TableField("Auto_Generate")
|
||||||
|
private Integer autoGenerate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 最大检测次数,默认3次
|
||||||
|
*/
|
||||||
|
@TableField("Max_Time")
|
||||||
|
private Integer maxTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 数据处理规则, 关联字典(所有值、部分值、cp95值、平均值、任意值),默认任意值
|
||||||
|
*/
|
||||||
|
// @TableField("Data_Rule")
|
||||||
|
// private String dataRule;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务场景
|
||||||
|
*/
|
||||||
|
@TableField("Scene")
|
||||||
|
private String scene;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 小数点精度
|
||||||
|
*/
|
||||||
|
private Integer scale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 比对监测后,当电压、电流不符合时,是否对标准设备进行系数校准
|
||||||
|
*/
|
||||||
|
private Integer coefficient;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态:0-删除 1-正常
|
||||||
|
*/
|
||||||
|
private Integer state;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package com.njcn.gather.system.cfg.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.system.cfg.pojo.param.SysTestConfigParam;
|
||||||
|
import com.njcn.gather.system.cfg.pojo.po.SysTestConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-16
|
||||||
|
*/
|
||||||
|
public interface ISysTestConfigService extends IService<SysTestConfig> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加检测配置
|
||||||
|
* @param scene 场景
|
||||||
|
* @return 是否添加成功
|
||||||
|
*/
|
||||||
|
boolean addTestConfig(String scene);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新检测配置
|
||||||
|
* @param param 检测配置
|
||||||
|
* @return 是否更新成功
|
||||||
|
*/
|
||||||
|
boolean updateTestConfig(SysTestConfigParam.UpdateParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取检测配置
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
SysTestConfig getOneConfig();
|
||||||
|
|
||||||
|
String getCurrrentScene();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取是否在检测时自动生成报告
|
||||||
|
*
|
||||||
|
* @return 0-否,1-是
|
||||||
|
*/
|
||||||
|
Integer getAutoGenerate();
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.njcn.gather.system.cfg.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.njcn.common.pojo.enums.common.DataStateEnum;
|
||||||
|
import com.njcn.gather.system.cfg.mapper.SysTestConfigMapper;
|
||||||
|
import com.njcn.gather.system.cfg.pojo.param.SysTestConfigParam;
|
||||||
|
import com.njcn.gather.system.cfg.pojo.po.SysTestConfig;
|
||||||
|
import com.njcn.gather.system.cfg.service.ISysTestConfigService;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||||
|
import com.njcn.gather.system.dictionary.service.IDictDataService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-16
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysTestConfigServiceImpl extends ServiceImpl<SysTestConfigMapper, SysTestConfig> implements ISysTestConfigService {
|
||||||
|
|
||||||
|
private final IDictDataService dictDataService;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean addTestConfig(String scene) {
|
||||||
|
SysTestConfig sysTestConfig = new SysTestConfig();
|
||||||
|
sysTestConfig.setAutoGenerate(1);
|
||||||
|
// 最大被检次数默认为3次
|
||||||
|
sysTestConfig.setMaxTime(3);
|
||||||
|
//sysTestConfig.setDataRule("46cf964bd76fb12a19cfb1700442eeeb"); // 任意值
|
||||||
|
sysTestConfig.setScene(scene);
|
||||||
|
sysTestConfig.setState(DataStateEnum.ENABLE.getCode());
|
||||||
|
return this.save(sysTestConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean updateTestConfig(SysTestConfigParam.UpdateParam param) {
|
||||||
|
SysTestConfig oneConfig = this.getOneConfig();
|
||||||
|
oneConfig.setAutoGenerate(ObjectUtil.isNotNull(param.getAutoGenerate()) ? param.getAutoGenerate() : oneConfig.getAutoGenerate());
|
||||||
|
oneConfig.setScale(ObjectUtil.isNotNull(param.getScale()) ? param.getScale() : oneConfig.getScale());
|
||||||
|
oneConfig.setMaxTime(ObjectUtil.isNotNull(param.getMaxTime()) ? param.getMaxTime() : oneConfig.getMaxTime());
|
||||||
|
oneConfig.setScene(StringUtils.isNotBlank(param.getScene()) ? param.getScene() : oneConfig.getScene());
|
||||||
|
oneConfig.setCoefficient(param.getCoefficient());
|
||||||
|
return this.updateById(oneConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SysTestConfig getOneConfig() {
|
||||||
|
QueryWrapper<SysTestConfig> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("state", DataStateEnum.ENABLE.getCode());
|
||||||
|
queryWrapper.last("LIMIT 1");
|
||||||
|
return this.getOne(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCurrrentScene() {
|
||||||
|
String scene = getOneConfig().getScene();
|
||||||
|
DictData dictData = dictDataService.getDictDataById(scene);
|
||||||
|
if (ObjectUtil.isNotNull(dictData)) {
|
||||||
|
return dictData.getValue();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Integer getAutoGenerate() {
|
||||||
|
return getOneConfig().getAutoGenerate();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.njcn.gather.system.config;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
public class LogExecutorConfig {
|
||||||
|
|
||||||
|
@Bean(name = "logAuditExecutor", destroyMethod = "shutdown")
|
||||||
|
public ExecutorService logAuditExecutor() {
|
||||||
|
AtomicInteger threadIndex = new AtomicInteger(1);
|
||||||
|
return new ThreadPoolExecutor(
|
||||||
|
4, 8, 30, TimeUnit.SECONDS,
|
||||||
|
new LinkedBlockingQueue<>(100),
|
||||||
|
runnable -> {
|
||||||
|
Thread thread = new Thread(runnable);
|
||||||
|
thread.setName("log-audit-" + threadIndex.getAndIncrement());
|
||||||
|
return thread;
|
||||||
|
},
|
||||||
|
(runnable, executor) -> log.warn("审计日志线程池已满,丢弃本次日志任务")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.njcn.gather.system.config;
|
||||||
|
|
||||||
|
import cn.hutool.extra.spring.SpringUtil;
|
||||||
|
import com.njcn.common.bean.CustomCacheUtil;
|
||||||
|
import org.springframework.boot.web.servlet.MultipartConfigFactory;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.util.unit.DataSize;
|
||||||
|
|
||||||
|
import javax.servlet.MultipartConfigElement;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-03-24
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class WebConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将自定缓存工具类注入到spring容器中
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public CustomCacheUtil customCacheUtil() {
|
||||||
|
CustomCacheUtil customCacheUtil = SpringUtil.getBean(CustomCacheUtil.CACHE_NAME);
|
||||||
|
return customCacheUtil;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置上传文件大小限制
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public MultipartConfigElement multipartConfigElement() {
|
||||||
|
MultipartConfigFactory factory = new MultipartConfigFactory();
|
||||||
|
// 单个文件最大6MB
|
||||||
|
factory.setMaxFileSize(DataSize.ofMegabytes(1024));
|
||||||
|
// 整个请求最大12MB
|
||||||
|
factory.setMaxRequestSize(DataSize.ofMegabytes(2048));
|
||||||
|
return factory.createMultipartConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,182 @@
|
|||||||
|
package com.njcn.gather.system.config.advice;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.gather.system.log.pojo.dto.SysLogAuditRecord;
|
||||||
|
import com.njcn.gather.system.log.service.ISysLogAuditService;
|
||||||
|
import com.njcn.web.utils.ReflectCommonUtil;
|
||||||
|
import com.njcn.web.utils.RequestUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.core.MethodParameter;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.http.server.ServerHttpRequest;
|
||||||
|
import org.springframework.http.server.ServerHttpResponse;
|
||||||
|
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
|
||||||
|
|
||||||
|
import javax.annotation.Nonnull;
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-2
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@ControllerAdvice
|
||||||
|
public class LogAdvice implements ResponseBodyAdvice<Object> {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ISysLogAuditService logService;
|
||||||
|
|
||||||
|
@Resource(name = "logAuditExecutor")
|
||||||
|
private Executor logAuditExecutor;
|
||||||
|
|
||||||
|
private static final List<String> UN_LOG_INFO = Collections.singletonList("未知业务");
|
||||||
|
|
||||||
|
private static final List<String> FILTER_CODE = Arrays.asList(
|
||||||
|
CommonResponseEnum.SUCCESS.getCode(),
|
||||||
|
CommonResponseEnum.FAIL.getCode(),
|
||||||
|
CommonResponseEnum.NO_DATA.getCode()
|
||||||
|
);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(MethodParameter returnType, Class converterType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object beforeBodyWrite(Object body, @Nonnull MethodParameter returnType, @Nonnull MediaType selectedContentType,
|
||||||
|
@Nonnull Class selectedConverterType, @Nonnull ServerHttpRequest request,
|
||||||
|
@Nonnull ServerHttpResponse response) {
|
||||||
|
if (body instanceof HttpResult) {
|
||||||
|
HttpResult<?> httpResult = (HttpResult<?>) body;
|
||||||
|
if (FILTER_CODE.contains(httpResult.getCode())) {
|
||||||
|
Method method = returnType.getMethod();
|
||||||
|
String methodDescribe = resolveMethodDescribe(method);
|
||||||
|
if (!UN_LOG_INFO.contains(methodDescribe)) {
|
||||||
|
SysLogAuditRecord logRecord = buildAdviceLogRecord(method, httpResult, methodDescribe);
|
||||||
|
submitLogTask(() -> logService.recodeAdviceLog(logRecord));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
private SysLogAuditRecord buildAdviceLogRecord(Method method, HttpResult<?> httpResult, String methodDescribe) {
|
||||||
|
Integer level = resolveOperateLevel(method);
|
||||||
|
return SysLogAuditRecord.builder()
|
||||||
|
.userId(resolveUserId())
|
||||||
|
.loginName(resolveLoginName())
|
||||||
|
.ip(resolveUserIp())
|
||||||
|
.operate(methodDescribe)
|
||||||
|
.operateType(resolveOperateType(method))
|
||||||
|
.result(CommonResponseEnum.FAIL.getCode().equalsIgnoreCase(httpResult.getCode())
|
||||||
|
? CommonResponseEnum.FAIL.getMessage()
|
||||||
|
: CommonResponseEnum.SUCCESS.getMessage())
|
||||||
|
.type(resolveEventType(method))
|
||||||
|
.level(level)
|
||||||
|
.warn(level == 1 ? 1 : 0)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void submitLogTask(Runnable task) {
|
||||||
|
try {
|
||||||
|
logAuditExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
task.run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("异步记录审计日志失败", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
log.error("提交审计日志任务失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveMethodDescribe(Method method) {
|
||||||
|
if (method == null) {
|
||||||
|
return "未知业务";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String methodDescribe = ReflectCommonUtil.getMethodDescribeByMethod(method);
|
||||||
|
return StrUtil.isBlank(methodDescribe) ? "未知业务" : methodDescribe;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析审计日志方法描述失败,method={}", method.getName(), e);
|
||||||
|
return "未知业务";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer resolveEventType(Method method) {
|
||||||
|
try {
|
||||||
|
if (method == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
String type = ReflectCommonUtil.getOperateInfoByMethod(method).getOperateType();
|
||||||
|
return "业务事件".equalsIgnoreCase(type) ? 0 : 1;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer resolveOperateLevel(Method method) {
|
||||||
|
try {
|
||||||
|
if (method == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
String level = ReflectCommonUtil.getOperateInfoByMethod(method).getOperateLevel();
|
||||||
|
if ("中等".equals(level)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if ("严重".equals(level)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveOperateType(Method method) {
|
||||||
|
try {
|
||||||
|
if (method == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return ReflectCommonUtil.getOperateTypeByMethod(method);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveUserId() {
|
||||||
|
try {
|
||||||
|
String userId = RequestUtil.getUserId();
|
||||||
|
return StrUtil.isBlank(userId) ? "" : userId;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveLoginName() {
|
||||||
|
try {
|
||||||
|
String loginName = RequestUtil.getLoginName();
|
||||||
|
return StrUtil.isBlank(loginName) ? "" : loginName;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveUserIp() {
|
||||||
|
try {
|
||||||
|
String userIp = RequestUtil.getUserIp();
|
||||||
|
return StrUtil.isBlank(userIp) ? "" : userIp;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package com.njcn.gather.system.config.handler;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.constant.LogInfo;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @version 1.0.0
|
||||||
|
* @date 2021年06月22日 10:25
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ControllerUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 针对methodArgumentNotValidException 异常的处理
|
||||||
|
* @author cdf
|
||||||
|
*/
|
||||||
|
public static String getMethodArgumentNotValidException(MethodArgumentNotValidException methodArgumentNotValidException) {
|
||||||
|
String operate = LogInfo.UNKNOWN_OPERATE;
|
||||||
|
Method method = null;
|
||||||
|
try {
|
||||||
|
method = methodArgumentNotValidException.getParameter().getMethod();
|
||||||
|
if (!Objects.isNull(method) && method.isAnnotationPresent(ApiOperation.class)) {
|
||||||
|
ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
|
||||||
|
operate = apiOperation.value();
|
||||||
|
}
|
||||||
|
}catch (Exception e){
|
||||||
|
log.error("根据方法参数非法异常获取@ApiOperation注解值失败,参数非法异常信息:{},方法名:{},异常信息:{}",methodArgumentNotValidException.getMessage(),method,e.getMessage());
|
||||||
|
}
|
||||||
|
return operate;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,335 @@
|
|||||||
|
package com.njcn.gather.system.config.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.text.StrFormatter;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.common.utils.LogUtil;
|
||||||
|
import com.njcn.gather.system.log.pojo.dto.SysLogAuditRecord;
|
||||||
|
import com.njcn.gather.system.log.service.ISysLogAuditService;
|
||||||
|
import com.njcn.gather.system.pojo.enums.SystemResponseEnum;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import com.njcn.web.utils.ReflectCommonUtil;
|
||||||
|
import com.njcn.web.utils.RequestUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.springframework.validation.ObjectError;
|
||||||
|
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||||
|
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||||
|
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||||
|
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||||
|
import org.springframework.web.util.NestedServletException;
|
||||||
|
|
||||||
|
import javax.annotation.Resource;
|
||||||
|
import javax.validation.ConstraintViolation;
|
||||||
|
import javax.validation.ConstraintViolationException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.file.NoSuchFileException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局通用业务异常处理器
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @version 1.0.0
|
||||||
|
* @date 2021年04月20日 18:04
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestControllerAdvice
|
||||||
|
public class GlobalBusinessExceptionHandler {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ISysLogAuditService sysLogAuditService;
|
||||||
|
|
||||||
|
@Resource(name = "logAuditExecutor")
|
||||||
|
private Executor logAuditExecutor;
|
||||||
|
|
||||||
|
@ExceptionHandler(BusinessException.class)
|
||||||
|
public HttpResult<String> handleBusinessException(BusinessException businessException) {
|
||||||
|
String operate = resolveMethodDescribeByException(businessException);
|
||||||
|
recodeBusinessExceptionLog(businessException, businessException.getMessage());
|
||||||
|
return HttpResultUtil.assembleBusinessExceptionResult(businessException, null, operate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(NullPointerException.class)
|
||||||
|
public HttpResult<String> handleNullPointerException(NullPointerException nullPointerException) {
|
||||||
|
LogUtil.logExceptionStackInfo(CommonResponseEnum.NULL_POINTER_EXCEPTION.getMessage(), nullPointerException);
|
||||||
|
recodeBusinessExceptionLog(nullPointerException, CommonResponseEnum.NULL_POINTER_EXCEPTION.getMessage());
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.NULL_POINTER_EXCEPTION, null, resolveMethodDescribeByException(nullPointerException));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ArithmeticException.class)
|
||||||
|
public HttpResult<String> handleArithmeticException(ArithmeticException arithmeticException) {
|
||||||
|
LogUtil.logExceptionStackInfo(CommonResponseEnum.ARITHMETIC_EXCEPTION.getMessage(), arithmeticException);
|
||||||
|
recodeBusinessExceptionLog(arithmeticException, CommonResponseEnum.ARITHMETIC_EXCEPTION.getMessage());
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.ARITHMETIC_EXCEPTION, null, resolveMethodDescribeByException(arithmeticException));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ClassCastException.class)
|
||||||
|
public HttpResult<String> handleClassCastException(ClassCastException classCastException) {
|
||||||
|
LogUtil.logExceptionStackInfo(CommonResponseEnum.CLASS_CAST_EXCEPTION.getMessage(), classCastException);
|
||||||
|
recodeBusinessExceptionLog(classCastException, CommonResponseEnum.CLASS_CAST_EXCEPTION.getMessage());
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.CLASS_CAST_EXCEPTION, null, resolveMethodDescribeByException(classCastException));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(IndexOutOfBoundsException.class)
|
||||||
|
public HttpResult<String> handleIndexOutOfBoundsException(IndexOutOfBoundsException indexOutOfBoundsException) {
|
||||||
|
LogUtil.logExceptionStackInfo(CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION.getMessage(), indexOutOfBoundsException);
|
||||||
|
recodeBusinessExceptionLog(indexOutOfBoundsException, CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION.getMessage());
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.INDEX_OUT_OF_BOUNDS_EXCEPTION, null, resolveMethodDescribeByException(indexOutOfBoundsException));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(NoSuchFileException.class)
|
||||||
|
public HttpResult<String> handleNoSuchFileException(NoSuchFileException noSuchFileException) {
|
||||||
|
String filePath = noSuchFileException.getFile();
|
||||||
|
log.warn("文件未找到异常 - 文件路径: {}", filePath, noSuchFileException);
|
||||||
|
recodeBusinessExceptionLog(noSuchFileException, SystemResponseEnum.FILE_NOT_FOUND.getMessage());
|
||||||
|
return HttpResultUtil.assembleResult(SystemResponseEnum.FILE_NOT_FOUND.getCode(), null,
|
||||||
|
StrFormatter.format("{}{}{}", resolveMethodDescribeByException(noSuchFileException),
|
||||||
|
StrUtil.C_COMMA, SystemResponseEnum.FILE_NOT_FOUND.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(IOException.class)
|
||||||
|
public HttpResult<String> handleIOException(IOException ioException) {
|
||||||
|
if (ioException instanceof NoSuchFileException) {
|
||||||
|
return handleNoSuchFileException((NoSuchFileException) ioException);
|
||||||
|
}
|
||||||
|
LogUtil.logExceptionStackInfo(SystemResponseEnum.FILE_IO_ERROR.getMessage(), ioException);
|
||||||
|
recodeBusinessExceptionLog(ioException, SystemResponseEnum.FILE_IO_ERROR.getMessage());
|
||||||
|
return HttpResultUtil.assembleResult(SystemResponseEnum.FILE_IO_ERROR.getCode(), null,
|
||||||
|
StrFormatter.format("{}{}{}", resolveMethodDescribeByException(ioException),
|
||||||
|
StrUtil.C_COMMA, SystemResponseEnum.FILE_IO_ERROR.getMessage()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
|
||||||
|
public HttpResult<String> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException httpMediaTypeNotSupportedException) {
|
||||||
|
LogUtil.logExceptionStackInfo(CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION.getMessage(), httpMediaTypeNotSupportedException);
|
||||||
|
recodeBusinessExceptionLog(httpMediaTypeNotSupportedException, CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION.getMessage());
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.HTTP_MEDIA_TYPE_NOT_SUPPORTED_EXCEPTION, null, resolveMethodDescribeByException(httpMediaTypeNotSupportedException));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||||
|
public HttpResult<String> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException methodArgumentNotValidException) {
|
||||||
|
String messages = methodArgumentNotValidException.getBindingResult().getAllErrors()
|
||||||
|
.stream().map(ObjectError::getDefaultMessage).collect(Collectors.joining(";"));
|
||||||
|
LogUtil.njcnDebug(log, "参数校验异常,异常为:{}", messages);
|
||||||
|
recodeBusinessExceptionLog(methodArgumentNotValidException, CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION.getMessage());
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages, ControllerUtil.getMethodArgumentNotValidException(methodArgumentNotValidException));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(ConstraintViolationException.class)
|
||||||
|
public HttpResult<String> constraintViolationExceptionExceptionHandler(ConstraintViolationException constraintViolationException) {
|
||||||
|
String exceptionMessage = constraintViolationException.getMessage();
|
||||||
|
StringBuilder messages = new StringBuilder();
|
||||||
|
if (exceptionMessage.indexOf(StrUtil.COMMA) > 0) {
|
||||||
|
String[] tempMessage = exceptionMessage.split(StrUtil.COMMA);
|
||||||
|
Stream.of(tempMessage).forEach(message -> messages.append(message.substring(message.indexOf(StrUtil.COLON) + 2)).append(';'));
|
||||||
|
} else {
|
||||||
|
messages.append(exceptionMessage.substring(exceptionMessage.indexOf(StrUtil.COLON) + 2));
|
||||||
|
}
|
||||||
|
LogUtil.njcnDebug(log, "参数校验异常,异常为:{}", messages);
|
||||||
|
recodeBusinessExceptionLog(constraintViolationException, CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION.getMessage());
|
||||||
|
List<ConstraintViolation<?>> constraintViolationList = new ArrayList<>(constraintViolationException.getConstraintViolations());
|
||||||
|
ConstraintViolation<?> constraintViolation = constraintViolationList.get(0);
|
||||||
|
Class<?> rootBeanClass = constraintViolation.getRootBeanClass();
|
||||||
|
if (rootBeanClass.getName().endsWith("Controller")) {
|
||||||
|
String methodName = constraintViolation.getPropertyPath().toString().substring(0, constraintViolation.getPropertyPath().toString().indexOf(StrUtil.DOT));
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages.toString(), resolveMethodDescribeByClassAndMethodName(rootBeanClass, methodName));
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.METHOD_ARGUMENT_NOT_VALID_EXCEPTION, messages.toString(), resolveMethodDescribeByException(constraintViolationException));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(IllegalArgumentException.class)
|
||||||
|
public HttpResult<String> handleIndexOutOfBoundsException(IllegalArgumentException illegalArgumentException) {
|
||||||
|
LogUtil.logExceptionStackInfo(CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION.getMessage(), illegalArgumentException);
|
||||||
|
recodeBusinessExceptionLog(illegalArgumentException, CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION.getMessage());
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.ILLEGAL_ARGUMENT_EXCEPTION, illegalArgumentException.getMessage(), resolveMethodDescribeByException(illegalArgumentException));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(Exception.class)
|
||||||
|
public HttpResult<String> handleException(Exception exception) {
|
||||||
|
Exception tempException = exception;
|
||||||
|
String exceptionCause = CommonResponseEnum.UN_DECLARE.getMessage();
|
||||||
|
String code = CommonResponseEnum.UN_DECLARE.getCode();
|
||||||
|
if (exception instanceof NestedServletException) {
|
||||||
|
Throwable cause = exception.getCause();
|
||||||
|
if (cause instanceof AssertionError) {
|
||||||
|
if (cause.getCause() instanceof BusinessException) {
|
||||||
|
tempException = (BusinessException) cause.getCause();
|
||||||
|
BusinessException tempBusinessException = (BusinessException) cause.getCause();
|
||||||
|
exceptionCause = tempBusinessException.getMessage();
|
||||||
|
code = tempBusinessException.getCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LogUtil.logExceptionStackInfo(exceptionCause, tempException);
|
||||||
|
recodeBusinessExceptionLog(exception, exceptionCause);
|
||||||
|
return HttpResultUtil.assembleResult(code, null, StrFormatter.format("{}{}{}", resolveMethodDescribeByException(tempException), StrUtil.C_COMMA, exceptionCause));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExceptionHandler(JSONException.class)
|
||||||
|
public HttpResult<String> handleIndexOutOfBoundsException(JSONException jsonException) {
|
||||||
|
LogUtil.logExceptionStackInfo(CommonResponseEnum.JSON_CONVERT_EXCEPTION.getMessage(), jsonException);
|
||||||
|
recodeBusinessExceptionLog(jsonException, CommonResponseEnum.JSON_CONVERT_EXCEPTION.getMessage());
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.JSON_CONVERT_EXCEPTION, jsonException.getMessage(), resolveMethodDescribeByException(jsonException));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recodeBusinessExceptionLog(Exception businessException, String message) {
|
||||||
|
SysLogAuditRecord logRecord = buildExceptionLogRecord(businessException, message);
|
||||||
|
submitLogTask(() -> sysLogAuditService.recodeBusinessExceptionLog(logRecord));
|
||||||
|
}
|
||||||
|
|
||||||
|
private SysLogAuditRecord buildExceptionLogRecord(Exception exception, String message) {
|
||||||
|
Method method = resolveMethod(exception);
|
||||||
|
Integer level = resolveOperateLevel(method);
|
||||||
|
return SysLogAuditRecord.builder()
|
||||||
|
.userId(resolveUserId())
|
||||||
|
.loginName(resolveLoginName())
|
||||||
|
.ip(resolveUserIp())
|
||||||
|
.operate(resolveExceptionOperate(method, exception))
|
||||||
|
.operateType(resolveOperateType(method))
|
||||||
|
.result(CommonResponseEnum.FAIL.getMessage())
|
||||||
|
.reason(message)
|
||||||
|
.type(resolveEventType(method))
|
||||||
|
.level(level)
|
||||||
|
.warn(level == 1 ? 1 : 0)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Method resolveMethod(Exception exception) {
|
||||||
|
if (exception instanceof MethodArgumentNotValidException) {
|
||||||
|
MethodArgumentNotValidException methodArgumentNotValidException = (MethodArgumentNotValidException) exception;
|
||||||
|
return methodArgumentNotValidException.getParameter().getMethod();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return ReflectCommonUtil.getMethod(exception);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveExceptionOperate(Method method, Exception exception) {
|
||||||
|
if (method != null) {
|
||||||
|
try {
|
||||||
|
String methodDescribe = ReflectCommonUtil.getMethodDescribeByMethod(method);
|
||||||
|
if (StrUtil.isNotBlank(methodDescribe)) {
|
||||||
|
return methodDescribe;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("解析异常日志方法描述失败,method={}", method.getName(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return resolveMethodDescribeByException(exception);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "未知业务";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer resolveEventType(Method method) {
|
||||||
|
try {
|
||||||
|
if (method == null) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
String type = ReflectCommonUtil.getOperateInfoByMethod(method).getOperateType();
|
||||||
|
return "业务事件".equalsIgnoreCase(type) ? 0 : 1;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Integer resolveOperateLevel(Method method) {
|
||||||
|
try {
|
||||||
|
if (method == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
String level = ReflectCommonUtil.getOperateInfoByMethod(method).getOperateLevel();
|
||||||
|
if ("中等".equals(level)) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if ("严重".equals(level)) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveOperateType(Method method) {
|
||||||
|
try {
|
||||||
|
if (method == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return ReflectCommonUtil.getOperateTypeByMethod(method);
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveMethodDescribeByException(Exception exception) {
|
||||||
|
try {
|
||||||
|
String methodDescribe = ReflectCommonUtil.getMethodDescribeByException(exception);
|
||||||
|
return StrUtil.isBlank(methodDescribe) ? "未知业务" : methodDescribe;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "未知业务";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveMethodDescribeByClassAndMethodName(Class<?> rootBeanClass, String methodName) {
|
||||||
|
try {
|
||||||
|
String methodDescribe = ReflectCommonUtil.getMethodDescribeByClassAndMethodName(rootBeanClass, methodName);
|
||||||
|
return StrUtil.isBlank(methodDescribe) ? "未知业务" : methodDescribe;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "未知业务";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveUserId() {
|
||||||
|
try {
|
||||||
|
String userId = RequestUtil.getUserId();
|
||||||
|
return StrUtil.isBlank(userId) ? "" : userId;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveLoginName() {
|
||||||
|
try {
|
||||||
|
String loginName = RequestUtil.getLoginName();
|
||||||
|
return StrUtil.isBlank(loginName) ? "" : loginName;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveUserIp() {
|
||||||
|
try {
|
||||||
|
String userIp = RequestUtil.getUserIp();
|
||||||
|
return StrUtil.isBlank(userIp) ? "" : userIp;
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void submitLogTask(Runnable task) {
|
||||||
|
try {
|
||||||
|
logAuditExecutor.execute(() -> {
|
||||||
|
try {
|
||||||
|
task.run();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("异步记录异常审计日志失败", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
log.error("提交异常审计日志任务失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.njcn.gather.system.config.handler;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.db.mybatisplus.handler.AutoFillValueHandler;
|
||||||
|
import com.njcn.web.utils.RequestUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
@Primary
|
||||||
|
@Component
|
||||||
|
@Slf4j
|
||||||
|
public class NonWebAutoFillValueHandler extends AutoFillValueHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前用户ID的线程本地变量,用于非Web环境
|
||||||
|
*/
|
||||||
|
private static final ThreadLocal<String> CURRENT_USER_ID = new ThreadLocal<>();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Supplier<String> getUserIdSupplier() {
|
||||||
|
return () -> {
|
||||||
|
try {
|
||||||
|
// 首先尝试从Web环境获取用户ID
|
||||||
|
String userId = RequestUtil.getUserId();
|
||||||
|
String actualUserId = StrUtil.isBlank(userId) ? "未知用户" : userId;
|
||||||
|
return actualUserId;
|
||||||
|
} catch (BusinessException e) {
|
||||||
|
// 如果是"当前请求web环境为空"异常,则尝试从线程本地变量获取
|
||||||
|
if (e.getMessage().contains("当前请求web环境为空")) {
|
||||||
|
String userId = CURRENT_USER_ID.get();
|
||||||
|
if (userId != null) {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
// 如果线程本地变量中也没有用户ID,则返回默认值
|
||||||
|
log.warn("无法获取当前用户ID");
|
||||||
|
return "未知用户";
|
||||||
|
}
|
||||||
|
// 其他异常直接抛出
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在非Web环境中设置当前用户ID
|
||||||
|
*
|
||||||
|
* @param userId 用户ID
|
||||||
|
*/
|
||||||
|
public static void setCurrentUserId(String userId) {
|
||||||
|
CURRENT_USER_ID.set(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清除当前线程的用户ID设置
|
||||||
|
*/
|
||||||
|
public static void clearCurrentUserId() {
|
||||||
|
CURRENT_USER_ID.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.common.utils.LogUtil;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.param.DictDataParam;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||||
|
import com.njcn.gather.system.dictionary.service.IDictDataService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.pojo.dto.SimpleTreeDTO;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "字典数据操作")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/dictData")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DictDataController extends BaseController {
|
||||||
|
|
||||||
|
private final IDictDataService dictDataService;
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@PostMapping("/listByTypeId")
|
||||||
|
@ApiOperation("根据字典类型id查询字典数据")
|
||||||
|
@ApiImplicitParam(name = "queryParam", value = "查询参数", required = true)
|
||||||
|
public HttpResult<Page<DictData>> listByTypeId(@RequestBody @Validated DictDataParam.QueryParam queryParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("listByTypeId");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam);
|
||||||
|
Page<DictData> result = dictDataService.getDictDataByTypeId(queryParam);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD)
|
||||||
|
@PostMapping("/add")
|
||||||
|
@ApiOperation("新增字典数据")
|
||||||
|
@ApiImplicitParam(name = "dictDataParam", value = "字典数据", required = true)
|
||||||
|
public HttpResult<Boolean> add(@RequestBody @Validated DictDataParam dictDataParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("add");
|
||||||
|
LogUtil.njcnDebug(log, "{},字典数据为:{}", methodDescribe, dictDataParam);
|
||||||
|
boolean result = dictDataService.addDictData(dictDataParam);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
|
||||||
|
@PostMapping("/update")
|
||||||
|
@ApiOperation("修改字典数据")
|
||||||
|
@ApiImplicitParam(name = "updateParam", value = "字典数据", required = true)
|
||||||
|
public HttpResult<Boolean> update(@RequestBody @Validated DictDataParam.UpdateParam updateParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("update");
|
||||||
|
LogUtil.njcnDebug(log, "{},字典数据为:{}", methodDescribe, updateParam);
|
||||||
|
boolean result = dictDataService.updateDictData(updateParam);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
|
||||||
|
@PostMapping("/delete")
|
||||||
|
@ApiOperation("删除字典数据")
|
||||||
|
@ApiImplicitParam(name = "ids", value = "字典索引", required = true, dataTypeClass = List.class)
|
||||||
|
public HttpResult<Boolean> delete(@RequestBody List<String> ids) {
|
||||||
|
String methodDescribe = getMethodDescribe("delete");
|
||||||
|
LogUtil.njcnDebug(log, "{},字典ID数据为:{}", methodDescribe, String.join(StrUtil.COMMA, ids));
|
||||||
|
boolean result = dictDataService.deleteDictData(ids);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@GetMapping("/getDicDataById")
|
||||||
|
@ApiOperation("根据字典id查询字典数据")
|
||||||
|
@ApiImplicitParam(name = "dicIndex", value = "字典id", required = true)
|
||||||
|
public HttpResult<DictData> getDicDataById(@RequestParam("dicIndex") String dicIndex) {
|
||||||
|
String methodDescribe = getMethodDescribe("getDicDataById");
|
||||||
|
DictData result = dictDataService.getDictDataById(dicIndex);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@GetMapping("/getDicDataByCode")
|
||||||
|
@ApiOperation("根据字典code查询字典数据")
|
||||||
|
@ApiImplicitParam(name = "code", value = "字典code", required = true)
|
||||||
|
public HttpResult<DictData> getDicDataByCode(@RequestParam("code") String code) {
|
||||||
|
String methodDescribe = getMethodDescribe("getDicDataByCode");
|
||||||
|
DictData result = dictDataService.getDictDataByCode(code);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@GetMapping("/dictDataCache")
|
||||||
|
@ApiOperation("获取所有字典数据缓存到前端")
|
||||||
|
public HttpResult<List<SimpleTreeDTO>> dictDataCache() {
|
||||||
|
String methodDescribe = getMethodDescribe("dictDataCache");
|
||||||
|
LogUtil.njcnDebug(log, "{},获取所有字典数据缓存到前端", methodDescribe);
|
||||||
|
List<SimpleTreeDTO> dictData = dictDataService.dictDataCache();
|
||||||
|
if (CollectionUtil.isNotEmpty(dictData)) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, dictData, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.NO_DATA, null, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DOWNLOAD)
|
||||||
|
@PostMapping("/export")
|
||||||
|
@ApiOperation("导出字典数据")
|
||||||
|
@ApiImplicitParam(name = "queryParam", value = "查询参数", required = true)
|
||||||
|
public void export(@RequestBody @Validated DictDataParam.QueryParam queryParam) {
|
||||||
|
dictDataService.exportDictData(queryParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.common.utils.LogUtil;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.param.DictTreeParam;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictTree;
|
||||||
|
import com.njcn.gather.system.dictionary.service.IDictTreeService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiImplicitParams;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 前端控制器
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "字典树操作")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/dictTree")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DictTreeController extends BaseController {
|
||||||
|
|
||||||
|
private final IDictTreeService dictTreeService;
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@GetMapping("/getTreeByCode")
|
||||||
|
@ApiOperation("按照code查询字典树")
|
||||||
|
@ApiImplicitParam(name = "code", value = "查询参数", required = true)
|
||||||
|
public HttpResult<List<DictTree>> getTreeByCode(@RequestParam("code") String code) {
|
||||||
|
String methodDescribe = getMethodDescribe("getTreeByCode");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, code);
|
||||||
|
List<DictTree> result = dictTreeService.getTreeByCode(code);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@GetMapping("/getTreeByName")
|
||||||
|
@ApiOperation("按照name模糊查询字典树")
|
||||||
|
@ApiImplicitParam(name = "keyword", value = "查询参数", required = true)
|
||||||
|
public HttpResult<List<DictTree>> getTreeByName(@RequestParam("name") String name) {
|
||||||
|
String methodDescribe = getMethodDescribe("getTreeByName");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, name);
|
||||||
|
List<DictTree> result = dictTreeService.getTreeByName(name);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD)
|
||||||
|
@PostMapping("/add")
|
||||||
|
@ApiOperation("新增字典树数据")
|
||||||
|
@ApiImplicitParam(name = "dictTreeParam", value = "字典数据", required = true)
|
||||||
|
public HttpResult<Boolean> add(@RequestBody @Validated DictTreeParam dictTreeParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("add");
|
||||||
|
LogUtil.njcnDebug(log, "{},字典数据为:{}", methodDescribe, dictTreeParam);
|
||||||
|
boolean result = dictTreeService.addDictTree(dictTreeParam);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
|
||||||
|
@PostMapping("/update")
|
||||||
|
@ApiOperation("修改字典树数据")
|
||||||
|
@ApiImplicitParam(name = "dicParam", value = "数据", required = true)
|
||||||
|
public HttpResult<Boolean> update(@RequestBody @Validated DictTreeParam.UpdateParam dicParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("update");
|
||||||
|
LogUtil.njcnDebug(log, "{},更新的信息为:{}", methodDescribe, dicParam);
|
||||||
|
boolean result = dictTreeService.updateDictTree(dicParam);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DELETE)
|
||||||
|
@PostMapping("/delete")
|
||||||
|
@ApiOperation("删除字典树数据")
|
||||||
|
@ApiImplicitParam(name = "id", value = "id", required = true)
|
||||||
|
public HttpResult<Boolean> delete(@RequestParam @Validated String id) {
|
||||||
|
String methodDescribe = getMethodDescribe("delete");
|
||||||
|
LogUtil.njcnDebug(log, "{},删除的id为:{}", methodDescribe, id);
|
||||||
|
boolean result = dictTreeService.deleteDictTree(id);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.DataStateEnum;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.common.utils.LogUtil;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.param.DictTypeParam;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictType;
|
||||||
|
import com.njcn.gather.system.dictionary.service.IDictTypeService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "字典类型表操作")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/dictType")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DictTypeController extends BaseController {
|
||||||
|
|
||||||
|
private final IDictTypeService dictTypeService;
|
||||||
|
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@PostMapping("/list")
|
||||||
|
@ApiOperation("查询字典类型")
|
||||||
|
@ApiImplicitParam(name = "queryParam", value = "查询参数", required = true)
|
||||||
|
public HttpResult<Page<DictType>> list(@RequestBody @Validated DictTypeParam.QueryParam queryParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("list");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, queryParam);
|
||||||
|
Page<DictType> result = dictTypeService.listDictTypes(queryParam);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@GetMapping("/listAll")
|
||||||
|
@ApiOperation("查询所有字典类型数据")
|
||||||
|
public HttpResult<List<DictType>> listAll() {
|
||||||
|
String methodDescribe = getMethodDescribe("listAll");
|
||||||
|
LogUtil.njcnDebug(log, "{}", methodDescribe);
|
||||||
|
List<DictType> dictTypeList = dictTypeService.list(new LambdaQueryWrapper<DictType>().eq(DictType::getState, DataStateEnum.ENABLE.getCode()));
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, dictTypeList, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD)
|
||||||
|
@PostMapping("/add")
|
||||||
|
@ApiOperation("新增字典类型")
|
||||||
|
@ApiImplicitParam(name = "dictTypeParam", value = "字典类型数据", required = true)
|
||||||
|
public HttpResult<Boolean> add(@RequestBody @Validated DictTypeParam dictTypeParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("add");
|
||||||
|
LogUtil.njcnDebug(log, "{},字典类型数据为:{}", methodDescribe, dictTypeParam);
|
||||||
|
boolean result = dictTypeService.addDictType(dictTypeParam);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
|
||||||
|
@PostMapping("/update")
|
||||||
|
@ApiOperation("修改字典类型")
|
||||||
|
@ApiImplicitParam(name = "updateParam", value = "字典类型数据", required = true)
|
||||||
|
public HttpResult<Boolean> update(@RequestBody @Validated DictTypeParam.UpdateParam updateParam) {
|
||||||
|
String methodDescribe = getMethodDescribe("update");
|
||||||
|
LogUtil.njcnDebug(log, "{},字典类型数据为:{}", methodDescribe, updateParam);
|
||||||
|
boolean result = dictTypeService.updateDictType(updateParam);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DELETE)
|
||||||
|
@PostMapping("/delete")
|
||||||
|
@ApiOperation("删除字典类型")
|
||||||
|
@ApiImplicitParam(name = "ids", value = "字典索引", required = true)
|
||||||
|
public HttpResult<Boolean> delete(@RequestBody List<String> ids) {
|
||||||
|
String methodDescribe = getMethodDescribe("delete");
|
||||||
|
LogUtil.njcnDebug(log, "{},字典ID数据为:{}", methodDescribe, String.join(StrUtil.COMMA, ids));
|
||||||
|
boolean result = dictTypeService.deleteDictType(ids);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DOWNLOAD)
|
||||||
|
@PostMapping("/export")
|
||||||
|
@ApiOperation("导出字典类型数据")
|
||||||
|
@ApiImplicitParam(name = "queryParam", value = "查询参数", required = true)
|
||||||
|
public void export(@RequestBody @Validated DictTypeParam.QueryParam queryParam) {
|
||||||
|
dictTypeService.exportDictType(queryParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.mapper;
|
||||||
|
|
||||||
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Mapper 接口
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
public interface DictDataMapper extends MPJBaseMapper<DictData> {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictTree;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.vo.DictTreeVO;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/8
|
||||||
|
*/
|
||||||
|
public interface DictTreeMapper extends BaseMapper<DictTree> {
|
||||||
|
List<DictTreeVO> queryLastLevelById(@Param("id") String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取电压相角、电流相角的id列表
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<String> getPhaseAngleIds();
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.mapper;
|
||||||
|
|
||||||
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictType;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
public interface DictTypeMapper extends MPJBaseMapper<DictType> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?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.njcn.gather.system.dictionary.mapper.DictDataMapper">
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?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.njcn.gather.system.dictionary.mapper.DictTreeMapper">
|
||||||
|
<select id="queryLastLevelById" resultType="com.njcn.gather.system.dictionary.pojo.vo.DictTreeVO">
|
||||||
|
SELECT
|
||||||
|
*
|
||||||
|
FROM
|
||||||
|
sys_dict_tree a
|
||||||
|
WHERE
|
||||||
|
a.pids LIKE concat('%',#{id},'%')
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT
|
||||||
|
1
|
||||||
|
FROM
|
||||||
|
sys_dict_tree b
|
||||||
|
WHERE
|
||||||
|
b.pids LIKE concat('%',#{id},'%') and a.id = b.pid)
|
||||||
|
</select>
|
||||||
|
<select id="getPhaseAngleIds" resultType="java.lang.String">
|
||||||
|
select id from sys_dict_tree where name like '电压相角' or name like '电流相角'
|
||||||
|
</select>
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?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.njcn.gather.system.dictionary.mapper.DictTypeMapper">
|
||||||
|
|
||||||
|
</mapper>
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @version 1.0
|
||||||
|
* @data 2024/10/30 15:52
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DictDataCache implements Serializable {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
private int sort;
|
||||||
|
|
||||||
|
private String typeId;
|
||||||
|
|
||||||
|
private String typeName;
|
||||||
|
|
||||||
|
private String typeCode;
|
||||||
|
|
||||||
|
private Integer algoDescribe;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-12
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum DictDataEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Key cleanup point: only keep registration-related platform capability
|
||||||
|
* types that are still referenced by the retained activation flow.
|
||||||
|
*/
|
||||||
|
DIGITAL("数字式", "Digital"),
|
||||||
|
SIMULATE("模拟式", "Simulate"),
|
||||||
|
CONTRAST("比对式", "Contrast");
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
DictDataEnum(String name, String code) {
|
||||||
|
this.name = name;
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMsgByValue(Integer name) {
|
||||||
|
for (DictDataEnum state : DictDataEnum.values()) {
|
||||||
|
if (state.getName().equals(name)) {
|
||||||
|
return state.getCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DictDataEnum getDictDataEnumByCode(String code) {
|
||||||
|
for (DictDataEnum steadyIndicatorEnum : DictDataEnum.values()) {
|
||||||
|
if (StringUtils.equals(code, steadyIndicatorEnum.getCode())) {
|
||||||
|
return steadyIndicatorEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.system.pojo.constant.SystemValidMessage;
|
||||||
|
import com.njcn.web.pojo.param.BaseParam;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import javax.validation.constraints.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @version 1.0.0
|
||||||
|
* @date 2021年12月17日 15:49
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DictDataParam {
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("字典类型id")
|
||||||
|
@NotBlank(message = SystemValidMessage.DICT_TYPE_ID_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = SystemValidMessage.DICT_TYPE_ID_FORMAT_ERROR)
|
||||||
|
private String typeId;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("名称")
|
||||||
|
@NotBlank(message = SystemValidMessage.NAME_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.DICT_DATA_NAME_REGEX, message = SystemValidMessage.DICT_DATA_NAME_FORMAT_ERROR)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("编码")
|
||||||
|
@NotBlank(message = SystemValidMessage.CODE_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.DICT_DATA_CODE_REGEX, message = SystemValidMessage.DICT_DATA_CODE_FORMAT_ERROR)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("排序")
|
||||||
|
@NotNull(message = SystemValidMessage.SORT_NOT_NULL)
|
||||||
|
@Min(value = 1, message = SystemValidMessage.SORT_FORMAT_ERROR)
|
||||||
|
@Max(value = 999, message = SystemValidMessage.SORT_FORMAT_ERROR)
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("事件等级:0-普通;1-中等;2-严重(默认为0)")
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
@ApiModelProperty("与高级算法内部Id描述对应")
|
||||||
|
private Integer algoDescribe;
|
||||||
|
|
||||||
|
//todo 待定
|
||||||
|
@ApiModelProperty("字典值")
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启使用Value值:0-关闭;1-开启(默认为0)
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("是否开启使用Value值")
|
||||||
|
private Integer openValue;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新操作实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class UpdateParam extends DictDataParam {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 表Id
|
||||||
|
*/
|
||||||
|
@ApiModelProperty("id")
|
||||||
|
@NotBlank(message = SystemValidMessage.ID_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = SystemValidMessage.ID_FORMAT_ERROR)
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型id分页查询字典数据
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class QueryParam extends BaseParam {
|
||||||
|
@ApiModelProperty("字典类型id")
|
||||||
|
@NotBlank(message = SystemValidMessage.DICT_TYPE_ID_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = SystemValidMessage.DICT_TYPE_ID_FORMAT_ERROR)
|
||||||
|
private String typeId;
|
||||||
|
|
||||||
|
@ApiModelProperty("名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ApiModelProperty("编码")
|
||||||
|
private String code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.param;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.system.pojo.constant.SystemValidMessage;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/8
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DictTreeParam {
|
||||||
|
/**
|
||||||
|
* 父id
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "父id")
|
||||||
|
private String pid;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "名称")
|
||||||
|
@NotBlank(message = SystemValidMessage.NAME_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.DICT_NAME_REGEX, message = SystemValidMessage.DICT_TYPE_NAME_FORMAT_ERROR)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "编码")
|
||||||
|
@TableField(value = "编码")
|
||||||
|
@NotBlank(message = SystemValidMessage.CODE_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.DICT_CODE_REGEX, message = SystemValidMessage.DICT_TYPE_CODE_FORMAT_ERROR)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于区分多种类型的字典树 0.台账对象类型 1.自定义报表指标类型
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "0.台账对象类型 1.自定义报表指标类型")
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据type自定义内容,type:0用于区分对象类型是101电网侧 102用户侧
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "根据type自定义内容,type:0用于区分对象类型是101电网侧 102用户侧")
|
||||||
|
private String extend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "排序")
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
@ApiModelProperty(value = "描述")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新操作实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class UpdateParam extends DictTreeParam {
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("id")
|
||||||
|
@NotBlank(message = SystemValidMessage.ID_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = SystemValidMessage.ID_FORMAT_ERROR)
|
||||||
|
private String id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.system.pojo.constant.SystemValidMessage;
|
||||||
|
import com.njcn.web.pojo.param.BaseParam;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import javax.validation.constraints.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @version 1.0
|
||||||
|
* @data 2024/10/30 14:39
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DictTypeParam {
|
||||||
|
|
||||||
|
@ApiModelProperty("名称")
|
||||||
|
@NotBlank(message = SystemValidMessage.NAME_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.DICT_NAME_REGEX, message = SystemValidMessage.DICT_TYPE_NAME_FORMAT_ERROR)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ApiModelProperty("编码")
|
||||||
|
@NotBlank(message = SystemValidMessage.CODE_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.DICT_CODE_REGEX, message = SystemValidMessage.DICT_TYPE_CODE_FORMAT_ERROR)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("排序")
|
||||||
|
@NotNull(message = SystemValidMessage.SORT_NOT_NULL)
|
||||||
|
@Min(value = 1, message = SystemValidMessage.SORT_FORMAT_ERROR)
|
||||||
|
@Max(value = 999, message = SystemValidMessage.SORT_FORMAT_ERROR)
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("开启等级:0-不开启;1-开启,默认不开启")
|
||||||
|
@NotNull(message = SystemValidMessage.OPEN_LEVEL_NOT_NULL)
|
||||||
|
@Min(value = 0, message = SystemValidMessage.OPEN_LEVEL_FORMAT_ERROR)
|
||||||
|
@Max(value = 1, message = SystemValidMessage.OPEN_LEVEL_FORMAT_ERROR)
|
||||||
|
private Integer openLevel;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("开启算法描述:0-不开启;1-开启,默认不开启")
|
||||||
|
@NotNull(message = SystemValidMessage.OPEN_DESCRIBE_NOT_NULL)
|
||||||
|
@Min(value = 0, message = SystemValidMessage.OPEN_DESCRIBE_FORMAT_ERROR)
|
||||||
|
@Max(value = 1, message = SystemValidMessage.OPEN_DESCRIBE_FORMAT_ERROR)
|
||||||
|
private Integer openDescribe;
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("描述")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新操作实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class UpdateParam extends DictTypeParam {
|
||||||
|
|
||||||
|
|
||||||
|
@ApiModelProperty("id")
|
||||||
|
@NotBlank(message = SystemValidMessage.ID_NOT_BLANK)
|
||||||
|
@Pattern(regexp = PatternRegex.SYSTEM_ID, message = SystemValidMessage.ID_FORMAT_ERROR)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询实体
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class QueryParam extends BaseParam {
|
||||||
|
@ApiModelProperty("名称")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
@ApiModelProperty("编码")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.njcn.db.mybatisplus.bo.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("sys_dict_data")
|
||||||
|
public class DictData extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典数据表Id
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型表Id
|
||||||
|
*/
|
||||||
|
private String typeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码
|
||||||
|
*/
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件等级:0-普通;1-中等;2-严重(默认为0)
|
||||||
|
*/
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与高级算法内部Id描述对应;
|
||||||
|
*/
|
||||||
|
private Integer algoDescribe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目前只用于表示电压等级数值
|
||||||
|
*/
|
||||||
|
@TableField(fill = FieldFill.UPDATE)
|
||||||
|
private String value;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否开启使用Value值:0-关闭;1-开启(默认为0)
|
||||||
|
*/
|
||||||
|
private Integer openValue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态:0-删除 1-正常
|
||||||
|
*/
|
||||||
|
private Integer state;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.njcn.db.mybatisplus.bo.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/8
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName(value = "sys_dict_tree")
|
||||||
|
public class DictTree extends BaseEntity {
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
@TableId(value = "id", type = IdType.ASSIGN_UUID)
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父id
|
||||||
|
*/
|
||||||
|
@TableField(value = "pid")
|
||||||
|
private String pid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父ids
|
||||||
|
*/
|
||||||
|
@TableField(value = "pids")
|
||||||
|
private String pids;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
@TableField(value = "name")
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码
|
||||||
|
*/
|
||||||
|
@TableField(value = "code")
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于区分多种类型的字典树 0.台账对象类型 1.自定义报表指标类型
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据type自定义内容,type:0用于区分对象类型是101电网侧 102用户侧
|
||||||
|
*/
|
||||||
|
private String extend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
@TableField(value = "sort")
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
@TableField(value = "remark")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态(字典 0正常 1停用 2删除)
|
||||||
|
*/
|
||||||
|
@TableField(value = "state")
|
||||||
|
private Integer state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 子类
|
||||||
|
*/
|
||||||
|
@TableField(exist = false)
|
||||||
|
private List<DictTree> children;
|
||||||
|
|
||||||
|
// @TableField(exist = false)
|
||||||
|
// private Integer level;
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.njcn.db.mybatisplus.bo.BaseEntity;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@TableName("sys_dict_type")
|
||||||
|
public class DictType extends BaseEntity {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型表Id
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码
|
||||||
|
*/
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启等级:0-不开启;1-开启,默认不开启
|
||||||
|
*/
|
||||||
|
private Integer openLevel;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启描述:0-不开启;1-开启,默认不开启
|
||||||
|
*/
|
||||||
|
private Integer openDescribe;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态:0-删除 1-正常
|
||||||
|
*/
|
||||||
|
private Integer state;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.vo;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/6
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DictDataExcel implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典数据表Id
|
||||||
|
*/
|
||||||
|
// @Excel(name = "字典数据id", width = 40)
|
||||||
|
// private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 字典类型表Id
|
||||||
|
*/
|
||||||
|
// @Excel(name = "字典类型id", width = 40)
|
||||||
|
// private String typeId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
@Excel(name = "名称", width = 20)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码
|
||||||
|
*/
|
||||||
|
@Excel(name = "编码", width = 20)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
@Excel(name = "排序", width = 15)
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件等级:0-普通;1-中等;2-严重(默认为0)
|
||||||
|
*/
|
||||||
|
@Excel(name = "事件等级", width = 15, replace = {"普通_0", "中等_1", "严重_2"})
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 与高级算法内部Id描述对应;
|
||||||
|
*/
|
||||||
|
@Excel(name = "高级算法内部id", width = 15)
|
||||||
|
private Integer algoDescribe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 目前只用于表示电压等级数值
|
||||||
|
*/
|
||||||
|
@Excel(name = "数值", width = 15)
|
||||||
|
private String value;
|
||||||
|
}
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/8
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DictTreeVO implements Serializable {
|
||||||
|
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 主键
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 父id
|
||||||
|
*/
|
||||||
|
private String pid;
|
||||||
|
/**
|
||||||
|
* 父类名称
|
||||||
|
*/
|
||||||
|
private String pname;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码
|
||||||
|
*/
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用于区分多种类型的字典树 0.台账对象类型 1.自定义报表指标类型
|
||||||
|
*/
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据type自定义内容,type:0用于区分对象类型是101电网侧 102用户侧
|
||||||
|
*/
|
||||||
|
private String extend;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 状态(字典 0正常 1停用 2删除)
|
||||||
|
*/
|
||||||
|
private String state;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.pojo.vo;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||||
|
import cn.afterturn.easypoi.excel.annotation.ExcelCollection;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024/11/5
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class DictTypeExcel implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 名称
|
||||||
|
*/
|
||||||
|
@Excel(name = "名称", width = 20, needMerge = true)
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 编码
|
||||||
|
*/
|
||||||
|
@Excel(name = "编码", width = 20, needMerge = true)
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 排序
|
||||||
|
*/
|
||||||
|
@Excel(name = "排序", width = 15, needMerge = true)
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启等级:0-不开启;1-开启,默认不开启
|
||||||
|
*/
|
||||||
|
@Excel(name = "开启等级", width = 15, replace = {"开启_1", "不开启_0"}, needMerge = true)
|
||||||
|
private Integer openLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开启描述:0-不开启;1-开启,默认不开启
|
||||||
|
*/
|
||||||
|
@Excel(name = "开启描述", width = 15, replace = {"开启_1", "不开启_0"}, needMerge = true)
|
||||||
|
private Integer openDescribe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 描述
|
||||||
|
*/
|
||||||
|
@Excel(name = "描述", width = 50, needMerge = true)
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
@ExcelCollection(name = "字典内容")
|
||||||
|
private List<DictDataExcel> dictDataExcels;
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.param.DictDataParam;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||||
|
import com.njcn.web.pojo.dto.SimpleTreeDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* 服务类
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
public interface IDictDataService extends IService<DictData> {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型id查询字典信息
|
||||||
|
* @param queryParam 查询参数
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
Page<DictData> getDictDataByTypeId(DictDataParam.QueryParam queryParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型id查询该类型下所有字典数据
|
||||||
|
*
|
||||||
|
* @param typeId
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<DictData> listDictDataByTypeId(String typeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型id查询字典信息
|
||||||
|
* @param typeId 字典类型id
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
List<DictData> getDictDataByTypeId(String typeId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增数据字典
|
||||||
|
* @param dictDataParam 字典数据
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
boolean addDictData(DictDataParam dictDataParam);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新字典数据
|
||||||
|
* @param updateParam 字典数据
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
boolean updateDictData(DictDataParam.UpdateParam updateParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量逻辑删除字典数据
|
||||||
|
* @param ids 字典id集合
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
boolean deleteDictData(List<String> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典id获取字典数据
|
||||||
|
* @param id 查询参数
|
||||||
|
* @return 根据字典id查询字典数据
|
||||||
|
*/
|
||||||
|
DictData getDictDataById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典名称获取字典数据
|
||||||
|
* @param name 字典名称
|
||||||
|
* @return 根据字典名称查询字典数据
|
||||||
|
*/
|
||||||
|
DictData getDictDataByName(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典code获取字典数据
|
||||||
|
* @param code 字典code
|
||||||
|
* @return 根据字典code查询字典数据
|
||||||
|
*/
|
||||||
|
DictData getDictDataByCode(String code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有字典数据基础信息
|
||||||
|
* @return 返回所有字典数据
|
||||||
|
*/
|
||||||
|
List<SimpleTreeDTO> dictDataCache();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字典数据
|
||||||
|
* @param queryParam 查询参数
|
||||||
|
*/
|
||||||
|
void exportDictData(DictDataParam.QueryParam queryParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典类型id删除字典数据
|
||||||
|
* @param ids 字典类型id集合
|
||||||
|
* @return 成功返回true,失败返回false
|
||||||
|
*/
|
||||||
|
boolean deleteDictDataByDictTypeId(List<String> ids);
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.service;
|
||||||
|
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.param.DictTreeParam;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictTree;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.vo.DictTreeVO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/8
|
||||||
|
*/
|
||||||
|
public interface IDictTreeService extends IService<DictTree> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据code查询字典树
|
||||||
|
*
|
||||||
|
* @param code 编码
|
||||||
|
* @return 字典树
|
||||||
|
*/
|
||||||
|
List<DictTree> getTreeByCode(String code);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据name查询字典树
|
||||||
|
*
|
||||||
|
* @param name 编码
|
||||||
|
* @return 字典树
|
||||||
|
*/
|
||||||
|
List<DictTree> getTreeByName(String name);
|
||||||
|
|
||||||
|
boolean addDictTree(DictTreeParam dictTreeParam);
|
||||||
|
|
||||||
|
boolean updateDictTree(DictTreeParam.UpdateParam param);
|
||||||
|
|
||||||
|
boolean deleteDictTree(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id查询字典数据
|
||||||
|
*
|
||||||
|
* @param id id
|
||||||
|
*/
|
||||||
|
DictTree queryById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有字典树
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<DictTree> queryTree();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据字典树id查询字典树
|
||||||
|
*
|
||||||
|
* @param ids
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
List<DictTree> getDictTreeById(List<String> ids);
|
||||||
|
|
||||||
|
DictTree getDictTreeByCode(String code);
|
||||||
|
|
||||||
|
List<DictTree> listByFatherIds(List<String> fatherIdList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取父级字典树
|
||||||
|
* @param id 字典树ID
|
||||||
|
* @return 父级字典树
|
||||||
|
*/
|
||||||
|
DictTree queryParentById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据id获取所有子节点的ID
|
||||||
|
* @param scriptId 字典树ID
|
||||||
|
* @return 所有子节点ID
|
||||||
|
*/
|
||||||
|
List<String> getChildIds(String scriptId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 测试项排个序
|
||||||
|
* @param scriptList 测试项
|
||||||
|
* @return 有序的测试项
|
||||||
|
*/
|
||||||
|
List<String> sort(List<String> scriptList);
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.param.DictTypeParam;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictType;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
public interface IDictTypeService extends IService<DictType> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据前台传递参数,分页查询字典类型数据
|
||||||
|
* @param queryParam 查询参数
|
||||||
|
* @return 字典列表
|
||||||
|
*/
|
||||||
|
Page<DictType> listDictTypes(DictTypeParam.QueryParam queryParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新增字典类型数据
|
||||||
|
*
|
||||||
|
* @param dictTypeParam 字典类型数据
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
boolean addDictType(DictTypeParam dictTypeParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 修改字典类型
|
||||||
|
*
|
||||||
|
* @param updateParam 字典类型数据
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
boolean updateDictType(DictTypeParam.UpdateParam updateParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量逻辑删除字典类型数据
|
||||||
|
* @param ids id集合
|
||||||
|
* @return 操作结果
|
||||||
|
*/
|
||||||
|
boolean deleteDictType(List<String> ids);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出字典类型数据
|
||||||
|
* @param queryParam 查询参数
|
||||||
|
*/
|
||||||
|
void exportDictType(DictTypeParam.QueryParam queryParam);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据code获取字典类型数据
|
||||||
|
*
|
||||||
|
* @param code 字典类型code
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
DictType getByCode(String code);
|
||||||
|
}
|
||||||
@@ -0,0 +1,212 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.github.yulichang.wrapper.MPJLambdaWrapper;
|
||||||
|
import com.njcn.common.pojo.enums.common.DataStateEnum;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.db.mybatisplus.constant.DbConstant;
|
||||||
|
import com.njcn.gather.system.dictionary.mapper.DictDataMapper;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.dto.DictDataCache;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.param.DictDataParam;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictType;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.vo.DictDataExcel;
|
||||||
|
import com.njcn.gather.system.dictionary.service.IDictDataService;
|
||||||
|
import com.njcn.gather.system.pojo.enums.SystemResponseEnum;
|
||||||
|
import com.njcn.web.factory.PageFactory;
|
||||||
|
import com.njcn.web.pojo.dto.SimpleDTO;
|
||||||
|
import com.njcn.web.pojo.dto.SimpleTreeDTO;
|
||||||
|
import com.njcn.web.utils.ExcelUtil;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DictDataServiceImpl extends ServiceImpl<DictDataMapper, DictData> implements IDictDataService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<DictData> getDictDataByTypeId(DictDataParam.QueryParam queryParam) {
|
||||||
|
QueryWrapper<DictData> queryWrapper = new QueryWrapper<>();
|
||||||
|
if (ObjectUtil.isNotNull(queryParam)) {
|
||||||
|
queryWrapper.like(StrUtil.isNotBlank(queryParam.getName()), "sys_dict_data.name", queryParam.getName())
|
||||||
|
.like(StrUtil.isNotBlank(queryParam.getCode()), "sys_dict_data.code", queryParam.getCode());
|
||||||
|
//排序
|
||||||
|
if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) {
|
||||||
|
queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy()));
|
||||||
|
} else {
|
||||||
|
//没有排序参数,默认根据sort字段排序,没有排序字段的,根据updateTime更新时间排序
|
||||||
|
queryWrapper.orderBy(true, true, "sys_dict_data.sort").orderByDesc("sys_dict_data.update_time");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queryWrapper.orderBy(true, true, "sys_dict_data.sort").orderByDesc("sys_dict_data.update_time");
|
||||||
|
}
|
||||||
|
queryWrapper.ne("sys_dict_data.state", DataStateEnum.DELETED.getCode())
|
||||||
|
.eq("sys_dict_data.type_id", queryParam.getTypeId());
|
||||||
|
//初始化分页数据
|
||||||
|
return this.baseMapper.selectPage(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DictData> listDictDataByTypeId(String typeId) {
|
||||||
|
return this.lambdaQuery().eq(DictData::getTypeId, typeId).eq(DictData::getState, DataStateEnum.ENABLE.getCode()).list();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DictData> getDictDataByTypeId(String typeId) {
|
||||||
|
return this.lambdaQuery().eq(DictData::getTypeId, typeId).eq(DictData::getState, DataStateEnum.ENABLE.getCode()).list();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean addDictData(DictDataParam dictDataParam) {
|
||||||
|
dictDataParam.setName(dictDataParam.getName().trim());
|
||||||
|
checkDicDataName(dictDataParam, false);
|
||||||
|
DictData dictData = new DictData();
|
||||||
|
BeanUtil.copyProperties(dictDataParam, dictData);
|
||||||
|
//默认为正常状态
|
||||||
|
dictData.setState(DataStateEnum.ENABLE.getCode());
|
||||||
|
return this.save(dictData);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean updateDictData(DictDataParam.UpdateParam updateParam) {
|
||||||
|
updateParam.setName(updateParam.getName().trim());
|
||||||
|
checkDicDataName(updateParam, true);
|
||||||
|
DictData dictData = new DictData();
|
||||||
|
BeanUtil.copyProperties(updateParam, dictData);
|
||||||
|
return this.updateById(dictData);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean deleteDictData(List<String> ids) {
|
||||||
|
return this.lambdaUpdate()
|
||||||
|
.set(DictData::getState, DataStateEnum.DELETED.getCode())
|
||||||
|
.in(DictData::getId, ids)
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DictData getDictDataById(String id) {
|
||||||
|
return this.lambdaQuery().eq(DictData::getId, id).eq(DictData::getState, DataStateEnum.ENABLE.getCode()).one();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DictData getDictDataByName(String name) {
|
||||||
|
return this.lambdaQuery().eq(DictData::getName, name).eq(DictData::getState, DataStateEnum.ENABLE.getCode()).one();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DictData getDictDataByCode(String code) {
|
||||||
|
LambdaQueryWrapper<DictData> queryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
queryWrapper.eq(DictData::getCode, code)
|
||||||
|
.eq(DictData::getState, DataStateEnum.ENABLE.getCode());
|
||||||
|
return this.baseMapper.selectOne(queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<SimpleTreeDTO> dictDataCache() {
|
||||||
|
MPJLambdaWrapper<DictData> dictTypeWrapper = new MPJLambdaWrapper<DictData>()
|
||||||
|
.eq(DictData::getState, DataStateEnum.ENABLE.getCode())
|
||||||
|
.selectAll(DictData.class)
|
||||||
|
.selectAs(DictType::getId, DictDataCache::getTypeId)
|
||||||
|
.selectAs(DictType::getName, DictDataCache::getTypeName)
|
||||||
|
.selectAs(DictType::getCode, DictDataCache::getTypeCode)
|
||||||
|
.leftJoin(DictType.class, DictType::getId, DictData::getTypeId)
|
||||||
|
.eq(DictType::getState, DataStateEnum.ENABLE.getCode());
|
||||||
|
List<DictDataCache> allDictData = this.getBaseMapper().selectJoinList(DictDataCache.class, dictTypeWrapper);
|
||||||
|
|
||||||
|
Map<Object, List<DictDataCache>> dictDataCacheMap = allDictData.stream()
|
||||||
|
.collect(Collectors.groupingBy(DictDataCache::getTypeId));
|
||||||
|
return dictDataCacheMap.keySet().stream().map(typeId -> {
|
||||||
|
SimpleTreeDTO simpleTreeDTO = new SimpleTreeDTO();
|
||||||
|
List<DictDataCache> dictDataCaches = dictDataCacheMap.get(typeId);
|
||||||
|
List<SimpleDTO> simpleDTOList = dictDataCaches.stream().map(dictDataCache -> {
|
||||||
|
simpleTreeDTO.setCode(dictDataCache.getTypeCode());
|
||||||
|
simpleTreeDTO.setId(dictDataCache.getTypeId());
|
||||||
|
simpleTreeDTO.setName(dictDataCache.getTypeName());
|
||||||
|
SimpleDTO simpleDTO = new SimpleDTO();
|
||||||
|
simpleDTO.setCode(dictDataCache.getCode());
|
||||||
|
simpleDTO.setId(dictDataCache.getId());
|
||||||
|
simpleDTO.setName(dictDataCache.getName());
|
||||||
|
simpleDTO.setSort(dictDataCache.getSort());
|
||||||
|
simpleDTO.setValue(dictDataCache.getValue());
|
||||||
|
simpleDTO.setAlgoDescribe(dictDataCache.getAlgoDescribe());
|
||||||
|
return simpleDTO;
|
||||||
|
}).sorted(Comparator.comparing(SimpleDTO::getSort)).collect(Collectors.toList());
|
||||||
|
simpleTreeDTO.setChildren(simpleDTOList);
|
||||||
|
return simpleTreeDTO;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void exportDictData(DictDataParam.QueryParam queryParam) {
|
||||||
|
QueryWrapper<DictData> queryWrapper = new QueryWrapper<>();
|
||||||
|
if (ObjectUtil.isNotNull(queryParam)) {
|
||||||
|
queryWrapper.like(StrUtil.isNotBlank(queryParam.getName()), "sys_dict_data.name", queryParam.getName())
|
||||||
|
.like(StrUtil.isNotBlank(queryParam.getCode()), "sys_dict_data.code", queryParam.getCode());
|
||||||
|
//排序
|
||||||
|
if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) {
|
||||||
|
queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy()));
|
||||||
|
} else {
|
||||||
|
//没有排序参数,默认根据sort字段排序,没有排序字段的,根据updateTime更新时间排序
|
||||||
|
queryWrapper.orderBy(true, true, "sys_dict_data.sort").orderByDesc("sys_dict_data.update_time");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryWrapper.ne("sys_dict_data.state", DataStateEnum.DELETED.getCode())
|
||||||
|
.eq("sys_dict_data.type_id", queryParam.getTypeId());
|
||||||
|
List<DictData> dictDatas = this.list(queryWrapper);
|
||||||
|
List<DictDataExcel> dictDataExcels = BeanUtil.copyToList(dictDatas, DictDataExcel.class);
|
||||||
|
ExcelUtil.exportExcel("字典数据导出数据.xlsx", "字典数据", DictDataExcel.class, dictDataExcels);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean deleteDictDataByDictTypeId(List<String> ids) {
|
||||||
|
return this.lambdaUpdate().in(DictData::getTypeId, ids).set(DictData::getState, DataStateEnum.DELETED.getCode()).update();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验参数,检查是否存在相同名称的字典类型
|
||||||
|
*/
|
||||||
|
private void checkDicDataName(DictDataParam dictDataParam, boolean isExcludeSelf) {
|
||||||
|
LambdaQueryWrapper<DictData> dictDataLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
dictDataLambdaQueryWrapper.eq(DictData::getTypeId, dictDataParam.getTypeId())
|
||||||
|
.eq(DictData::getState, DataStateEnum.ENABLE.getCode())
|
||||||
|
.and(w -> w.eq(DictData::getName, dictDataParam.getName()).or().eq(DictData::getCode, dictDataParam.getCode()));
|
||||||
|
//更新的时候,需排除当前记录
|
||||||
|
if (isExcludeSelf) {
|
||||||
|
if (dictDataParam instanceof DictDataParam.UpdateParam) {
|
||||||
|
dictDataLambdaQueryWrapper.ne(DictData::getId, ((DictDataParam.UpdateParam) dictDataParam).getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int countByAccount = this.count(dictDataLambdaQueryWrapper);
|
||||||
|
//大于等于1个则表示重复
|
||||||
|
if (countByAccount >= 1) {
|
||||||
|
throw new BusinessException(SystemResponseEnum.DICT_DATA_REPEAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.collection.CollUtil;
|
||||||
|
import cn.hutool.core.collection.CollectionUtil;
|
||||||
|
import cn.hutool.core.text.StrPool;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.gather.system.dictionary.mapper.DictTreeMapper;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.param.DictTreeParam;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictTree;
|
||||||
|
import com.njcn.gather.system.dictionary.service.IDictTreeService;
|
||||||
|
import com.njcn.gather.system.pojo.constant.DictConst;
|
||||||
|
import com.njcn.gather.system.pojo.enums.SystemResponseEnum;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/8
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DictTreeServiceImpl extends ServiceImpl<DictTreeMapper, DictTree> implements IDictTreeService {
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DictTree> getTreeByCode(String code) {
|
||||||
|
List<DictTree> dictTree = this.queryTree();
|
||||||
|
|
||||||
|
if (ObjectUtil.isNotEmpty(dictTree)) {
|
||||||
|
dictTree = dictTree.stream().filter(item -> item.getCode().equals(code)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
return dictTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DictTree> getTreeByName(String name) {
|
||||||
|
List<DictTree> dictTree = this.queryTree();
|
||||||
|
|
||||||
|
if (ObjectUtil.isNotEmpty(dictTree) && StrUtil.isNotBlank(name)) {
|
||||||
|
dictTree = dictTree.stream().filter(item -> item.getName().contains(name)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return dictTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean addDictTree(DictTreeParam dictTreeParam) {
|
||||||
|
dictTreeParam.setName(dictTreeParam.getName().trim());
|
||||||
|
checkRepeat(dictTreeParam, false);
|
||||||
|
boolean result;
|
||||||
|
DictTree dictTree = new DictTree();
|
||||||
|
BeanUtils.copyProperties(dictTreeParam, dictTree);
|
||||||
|
if (!Objects.equals(dictTree.getPid(), DictConst.FATHER_ID)) {
|
||||||
|
QueryWrapper<DictTree> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("id", dictTree.getPid());
|
||||||
|
DictTree instance = this.baseMapper.selectOne(queryWrapper);
|
||||||
|
dictTree.setPids(instance.getPids() + StrPool.COMMA + instance.getId());
|
||||||
|
} else {
|
||||||
|
dictTree.setPids(DictConst.FATHER_ID);
|
||||||
|
}
|
||||||
|
dictTree.setState(DictConst.ENABLE);
|
||||||
|
result = this.save(dictTree);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean updateDictTree(DictTreeParam.UpdateParam param) {
|
||||||
|
param.setName(param.getName().trim());
|
||||||
|
DictTree dictTree = this.getById(param.getId());
|
||||||
|
if ("975f63baeb6f653c54fca226a9ae36ca".equals(param.getId()) || dictTree.getPids().contains("975f63baeb6f653c54fca226a9ae36ca")) {
|
||||||
|
throw new BusinessException(SystemResponseEnum.CAN_NOT_UPDATE_USED_DICT);
|
||||||
|
}
|
||||||
|
checkRepeat(param, true);
|
||||||
|
DictTree copyDictTree = new DictTree();
|
||||||
|
BeanUtils.copyProperties(param, copyDictTree);
|
||||||
|
return this.updateById(copyDictTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean deleteDictTree(String id) {
|
||||||
|
boolean result = false;
|
||||||
|
DictTree dictTree = this.getById(id);
|
||||||
|
if ("975f63baeb6f653c54fca226a9ae36ca".equals(id) || dictTree.getPids().contains("975f63baeb6f653c54fca226a9ae36ca")) {
|
||||||
|
throw new BusinessException(SystemResponseEnum.CAN_NOT_DELETE_USED_DICT);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DictTree> childrenList = this.lambdaQuery().eq(DictTree::getState, DictConst.ENABLE).eq(DictTree::getPid, id).list();
|
||||||
|
if (CollectionUtils.isEmpty(childrenList)) {
|
||||||
|
result = this.lambdaUpdate().set(DictTree::getState, DictConst.DELETE).in(DictTree::getId, id).update();
|
||||||
|
} else {
|
||||||
|
throw new BusinessException(SystemResponseEnum.EXISTS_CHILDREN_NOT_DELETE);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DictTree queryById(String id) {
|
||||||
|
return this.lambdaQuery().eq(DictTree::getId, id).eq(DictTree::getState, DictConst.ENABLE).one();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DictTree> queryTree() {
|
||||||
|
LambdaQueryWrapper<DictTree> lambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
lambdaQueryWrapper.eq(DictTree::getState, DictConst.ENABLE);
|
||||||
|
List<DictTree> dictTreeList = this.list(lambdaQueryWrapper);
|
||||||
|
return dictTreeList.stream().filter(item -> DictConst.FATHER_ID.equals(item.getPid())).peek(item -> {
|
||||||
|
// item.setLevel(0);
|
||||||
|
item.setChildren(getChildren(item, dictTreeList));
|
||||||
|
}).sorted(Comparator.comparingInt(DictTree::getSort)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DictTree> getDictTreeById(List<String> ids) {
|
||||||
|
return this.list(new LambdaQueryWrapper<DictTree>()
|
||||||
|
.in(CollUtil.isNotEmpty(ids), DictTree::getId, ids)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DictTree getDictTreeByCode(String code) {
|
||||||
|
return this.getOne(new LambdaQueryWrapper<DictTree>()
|
||||||
|
.eq(DictTree::getCode, code)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<DictTree> listByFatherIds(List<String> fatherIdList) {
|
||||||
|
if (CollUtil.isNotEmpty(fatherIdList)) {
|
||||||
|
return this.lambdaQuery().in(DictTree::getPid, fatherIdList).eq(DictTree::getState, DictConst.ENABLE).list();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DictTree queryParentById(String id) {
|
||||||
|
DictTree temp = this.lambdaQuery().eq(DictTree::getId, id).eq(DictTree::getState, DictConst.ENABLE).one();
|
||||||
|
return this.lambdaQuery().eq(DictTree::getId, temp.getPid()).one();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> getChildIds(String scriptId) {
|
||||||
|
List<DictTree> subTree = this.lambdaQuery().eq(DictTree::getPid, scriptId)
|
||||||
|
.eq(DictTree::getState, DictConst.ENABLE)
|
||||||
|
.list();
|
||||||
|
if(CollectionUtil.isNotEmpty(subTree)){
|
||||||
|
return subTree.stream().map(DictTree::getId).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<String> sort(List<String> scriptList) {
|
||||||
|
if (CollectionUtil.isEmpty(scriptList)) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.lambdaQuery()
|
||||||
|
.in(DictTree::getId, scriptList)
|
||||||
|
.orderByAsc(DictTree::getSort)
|
||||||
|
.list()
|
||||||
|
.stream()
|
||||||
|
.map(DictTree::getId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkRepeat(DictTreeParam dictTreeParam, boolean isExcludeSelf) {
|
||||||
|
LambdaQueryWrapper<DictTree> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.eq(DictTree::getPid, dictTreeParam.getPid()) // 同一父节点下不能有相同的code
|
||||||
|
.eq(DictTree::getCode, dictTreeParam.getCode())
|
||||||
|
.eq(DictTree::getState, DictConst.ENABLE);
|
||||||
|
if (isExcludeSelf) {
|
||||||
|
if (dictTreeParam instanceof DictTreeParam.UpdateParam) {
|
||||||
|
wrapper.ne(DictTree::getId, ((DictTreeParam.UpdateParam) dictTreeParam).getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int count = this.count(wrapper);
|
||||||
|
if (count > 0) {
|
||||||
|
throw new BusinessException(SystemResponseEnum.CODE_REPEAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DictTree> filterTreeByName(List<DictTree> tree, String keyword) {
|
||||||
|
if (CollectionUtils.isEmpty(tree) || !StrUtil.isNotBlank(keyword)) {
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
filter(tree, keyword);
|
||||||
|
return tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void filter(List<DictTree> list, String keyword) {
|
||||||
|
for (int i = list.size() - 1; i >= 0; i--) {
|
||||||
|
DictTree dictTree = list.get(i);
|
||||||
|
List<DictTree> children = dictTree.getChildren();
|
||||||
|
if (!dictTree.getName().contains(keyword)) {
|
||||||
|
if (!CollectionUtils.isEmpty(children)) {
|
||||||
|
filter(children, keyword);
|
||||||
|
}
|
||||||
|
if (CollectionUtils.isEmpty(dictTree.getChildren())) {
|
||||||
|
list.remove(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
// if (!CollectionUtils.isEmpty(children)) {
|
||||||
|
// filter(children, keyword);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<DictTree> getChildren(DictTree dictTree, List<DictTree> all) {
|
||||||
|
return all.stream().filter(item -> item.getPid().equals(dictTree.getId())).peek(item -> {
|
||||||
|
// item.setLevel(dictTree.getLevel() + 1);
|
||||||
|
item.setChildren(getChildren(item, all));
|
||||||
|
}).sorted(Comparator.comparingInt(DictTree::getSort)).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,145 @@
|
|||||||
|
package com.njcn.gather.system.dictionary.service.impl;
|
||||||
|
|
||||||
|
import cn.hutool.core.bean.BeanUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.njcn.common.pojo.enums.common.DataStateEnum;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.db.mybatisplus.constant.DbConstant;
|
||||||
|
import com.njcn.gather.system.dictionary.mapper.DictTypeMapper;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.param.DictTypeParam;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictData;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.po.DictType;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.vo.DictDataExcel;
|
||||||
|
import com.njcn.gather.system.dictionary.pojo.vo.DictTypeExcel;
|
||||||
|
import com.njcn.gather.system.dictionary.service.IDictDataService;
|
||||||
|
import com.njcn.gather.system.dictionary.service.IDictTypeService;
|
||||||
|
import com.njcn.gather.system.pojo.enums.SystemResponseEnum;
|
||||||
|
import com.njcn.web.factory.PageFactory;
|
||||||
|
import com.njcn.web.utils.ExcelUtil;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @since 2021-12-13
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class DictTypeServiceImpl extends ServiceImpl<DictTypeMapper, DictType> implements IDictTypeService {
|
||||||
|
private final IDictDataService dictDataService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<DictType> listDictTypes(DictTypeParam.QueryParam queryParam) {
|
||||||
|
QueryWrapper<DictType> queryWrapper = new QueryWrapper<>();
|
||||||
|
if (ObjectUtil.isNotNull(queryParam)) {
|
||||||
|
queryWrapper.like(StrUtil.isNotBlank(queryParam.getName()), "sys_dict_type.name", queryParam.getName())
|
||||||
|
.like(StrUtil.isNotBlank(queryParam.getCode()), "sys_dict_type.code", queryParam.getCode());
|
||||||
|
//排序
|
||||||
|
if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) {
|
||||||
|
queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy()));
|
||||||
|
} else {
|
||||||
|
//没有排序参数,默认根据sort字段排序,没有排序字段的,根据updateTime更新时间排序
|
||||||
|
queryWrapper.orderBy(true, true, "sys_dict_type.sort").orderByDesc("sys_dict_type.update_time");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
queryWrapper.orderBy(true, true, "sys_dict_type.sort").orderByDesc("sys_dict_type.update_time");
|
||||||
|
}
|
||||||
|
queryWrapper.ne("sys_dict_type.state", DataStateEnum.DELETED.getCode());
|
||||||
|
return this.baseMapper.selectPage(new Page<>(PageFactory.getPageNum(queryParam), PageFactory.getPageSize(queryParam)), queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean addDictType(DictTypeParam dictTypeParam) {
|
||||||
|
dictTypeParam.setName(dictTypeParam.getName().trim());
|
||||||
|
checkDicTypeName(dictTypeParam, false);
|
||||||
|
DictType dictType = new DictType();
|
||||||
|
BeanUtil.copyProperties(dictTypeParam, dictType);
|
||||||
|
//默认为正常状态
|
||||||
|
dictType.setState(DataStateEnum.ENABLE.getCode());
|
||||||
|
return this.save(dictType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean updateDictType(DictTypeParam.UpdateParam updateParam) {
|
||||||
|
updateParam.setName(updateParam.getName().trim());
|
||||||
|
checkDicTypeName(updateParam, true);
|
||||||
|
DictType dictType = new DictType();
|
||||||
|
BeanUtil.copyProperties(updateParam, dictType);
|
||||||
|
return this.updateById(dictType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public boolean deleteDictType(List<String> ids) {
|
||||||
|
dictDataService.deleteDictDataByDictTypeId(ids);
|
||||||
|
return this.lambdaUpdate()
|
||||||
|
.set(DictType::getState, DataStateEnum.DELETED.getCode())
|
||||||
|
.in(DictType::getId, ids)
|
||||||
|
.update();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportDictType(DictTypeParam.QueryParam queryParam) {
|
||||||
|
QueryWrapper<DictType> queryWrapper = new QueryWrapper<>();
|
||||||
|
if (ObjectUtil.isNotNull(queryParam)) {
|
||||||
|
queryWrapper.like(StrUtil.isNotBlank(queryParam.getName()), "sys_dict_type.name", queryParam.getName())
|
||||||
|
.like(StrUtil.isNotBlank(queryParam.getCode()), "sys_dict_type.code", queryParam.getCode());
|
||||||
|
//排序
|
||||||
|
if (ObjectUtil.isAllNotEmpty(queryParam.getSortBy(), queryParam.getOrderBy())) {
|
||||||
|
queryWrapper.orderBy(true, queryParam.getOrderBy().equals(DbConstant.ASC), StrUtil.toUnderlineCase(queryParam.getSortBy()));
|
||||||
|
} else {
|
||||||
|
//没有排序参数,默认根据sort字段排序,没有排序字段的,根据updateTime更新时间排序
|
||||||
|
queryWrapper.orderBy(true, true, "sys_dict_type.sort").orderByDesc("sys_dict_type.update_time");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
queryWrapper.ne("sys_dict_type.state", DataStateEnum.DELETED.getCode());
|
||||||
|
List<DictType> dictTypes = this.list(queryWrapper);
|
||||||
|
|
||||||
|
List<DictTypeExcel> dictTypeVOS = new ArrayList<>();
|
||||||
|
dictTypes.forEach(dictType -> {
|
||||||
|
DictTypeExcel dictTypeExcel = BeanUtil.copyProperties(dictType, DictTypeExcel.class);
|
||||||
|
List<DictData> dictDataList = dictDataService.listDictDataByTypeId(dictType.getId());
|
||||||
|
List<DictDataExcel> dictDataExcels = BeanUtil.copyToList(dictDataList, DictDataExcel.class);
|
||||||
|
dictTypeExcel.setDictDataExcels(dictDataExcels);
|
||||||
|
dictTypeVOS.add(dictTypeExcel);
|
||||||
|
});
|
||||||
|
|
||||||
|
ExcelUtil.exportExcel("字典类型导出数据.xlsx", "字典类型", DictTypeExcel.class, dictTypeVOS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DictType getByCode(String code) {
|
||||||
|
return this.lambdaQuery().eq(DictType::getCode, code).eq(DictType::getState, DataStateEnum.ENABLE.getCode()).one();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验参数,检查是否存在相同名称的字典类型
|
||||||
|
*/
|
||||||
|
private void checkDicTypeName(DictTypeParam dictTypeParam, boolean isExcludeSelf) {
|
||||||
|
LambdaQueryWrapper<DictType> dictTypeLambdaQueryWrapper = new LambdaQueryWrapper<>();
|
||||||
|
dictTypeLambdaQueryWrapper.eq(DictType::getState, DataStateEnum.ENABLE.getCode())
|
||||||
|
.and(w -> w.eq(DictType::getCode, dictTypeParam.getCode()).or().eq(DictType::getName, dictTypeParam.getName()));
|
||||||
|
//更新的时候,需排除当前记录
|
||||||
|
if (isExcludeSelf) {
|
||||||
|
if (dictTypeParam instanceof DictTypeParam.UpdateParam) {
|
||||||
|
dictTypeLambdaQueryWrapper.ne(DictType::getId, ((DictTypeParam.UpdateParam) dictTypeParam).getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int countByAccount = this.count(dictTypeLambdaQueryWrapper);
|
||||||
|
//大于等于1个则表示重复
|
||||||
|
if (countByAccount >= 1) {
|
||||||
|
throw new BusinessException(SystemResponseEnum.DICT_TYPE_REPEAT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package com.njcn.gather.system.log.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.common.utils.LogUtil;
|
||||||
|
import com.njcn.gather.system.log.pojo.param.SysLogParam;
|
||||||
|
import com.njcn.gather.system.log.pojo.po.SysLogAudit;
|
||||||
|
import com.njcn.gather.system.log.service.ISysLogAuditService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-29
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "日志管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/sysLog")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysLogController extends BaseController {
|
||||||
|
private final ISysLogAuditService sysLogAuditService;
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@PostMapping("/list")
|
||||||
|
@ApiOperation(value = "获取日志列表")
|
||||||
|
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||||
|
public HttpResult<Page<SysLogAudit>> list(@RequestBody @Validated SysLogParam.QueryParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("list");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, param);
|
||||||
|
Page<SysLogAudit> result = sysLogAuditService.listSysLogAudit(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DOWNLOAD)
|
||||||
|
@PostMapping("/exportCSV")
|
||||||
|
@ApiOperation("日志导出为csv文件")
|
||||||
|
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||||
|
public void exportCSV(@RequestBody @Validated SysLogParam.QueryParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("export");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, param);
|
||||||
|
sysLogAuditService.exportCSV(param);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.DOWNLOAD)
|
||||||
|
@PostMapping("/analyse")
|
||||||
|
@ApiOperation("日志分析")
|
||||||
|
@ApiImplicitParam(name = "param", value = "查询参数", required = true)
|
||||||
|
public void analyse(@RequestBody @Validated SysLogParam.QueryParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("analyze");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询数据为:{}", methodDescribe, param);
|
||||||
|
sysLogAuditService.analyse(param);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.njcn.gather.system.log.mapper;
|
||||||
|
|
||||||
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
|
import com.njcn.gather.system.log.pojo.po.SysLogAudit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-29
|
||||||
|
*/
|
||||||
|
public interface SysLogAuditMapper extends MPJBaseMapper<SysLogAudit> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.njcn.gather.system.log.mapper;
|
||||||
|
|
||||||
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
|
import com.njcn.gather.system.log.pojo.po.SysLogRun;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-29
|
||||||
|
*/
|
||||||
|
public interface SysLogRunMapper extends MPJBaseMapper<SysLogRun> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?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.njcn.gather.system.log.mapper.SysLogAuditMapper">
|
||||||
|
|
||||||
|
|
||||||
|
</mapper>
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?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.njcn.gather.system.log.mapper.SysLogRunMapper">
|
||||||
|
|
||||||
|
|
||||||
|
</mapper>
|
||||||
|
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package com.njcn.gather.system.log.pojo.dto;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SysLogAuditRecord {
|
||||||
|
|
||||||
|
private String userId;
|
||||||
|
|
||||||
|
private String loginName;
|
||||||
|
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
private String operate;
|
||||||
|
|
||||||
|
private String operateType;
|
||||||
|
|
||||||
|
private String result;
|
||||||
|
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
private Integer warn;
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
package com.njcn.gather.system.log.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-02-12
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum LogLevelEnum {
|
||||||
|
DEBUG(1, "DEBUG"),
|
||||||
|
INFO(2, "INFO"),
|
||||||
|
WARN(3, "WARN"),
|
||||||
|
ERROR(4, "ERROR"),
|
||||||
|
FATAL(5, "FATAL");
|
||||||
|
|
||||||
|
private final int code;
|
||||||
|
private final String msg;
|
||||||
|
|
||||||
|
LogLevelEnum(int code, String msg) {
|
||||||
|
this.code = code;
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static LogLevelEnum getEnum(int code) {
|
||||||
|
return LogLevelEnum.values()[code - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package com.njcn.gather.system.log.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2025-02-12
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum LogOperationTypeEnum {
|
||||||
|
OPERATE("操作日志"),
|
||||||
|
WARNING("告警日志");
|
||||||
|
|
||||||
|
private String msg;
|
||||||
|
LogOperationTypeEnum(String msg) {
|
||||||
|
this.msg = msg;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.njcn.gather.system.log.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.constant.PatternRegex;
|
||||||
|
import com.njcn.gather.system.pojo.constant.SystemValidMessage;
|
||||||
|
import com.njcn.web.pojo.param.BaseParam;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-11-25
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SysLogParam {
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class QueryParam extends BaseParam {
|
||||||
|
|
||||||
|
@ApiModelProperty("操作用户")
|
||||||
|
private String userName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package com.njcn.gather.system.log.pojo.po;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||||
|
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-29
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("sys_log_audit")
|
||||||
|
public class SysLogAudit implements Serializable {
|
||||||
|
private static final long serialVersionUID = -56962081010894238L;
|
||||||
|
/**
|
||||||
|
* 审计日志Id
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录名
|
||||||
|
*/
|
||||||
|
@Excel(name = "登录名")
|
||||||
|
private String loginName;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
@Excel(name = "用户名")
|
||||||
|
private String userName;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP
|
||||||
|
*/
|
||||||
|
@Excel(name = "IP", width = 20)
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作内容
|
||||||
|
*/
|
||||||
|
@Excel(name = "操作内容", width = 80)
|
||||||
|
private String operate;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作类型 (比如:查询、新增、删除、下载等等)
|
||||||
|
*/
|
||||||
|
@Excel(name = "操作类型")
|
||||||
|
private String operateType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件结果 0失败 1成功
|
||||||
|
*/
|
||||||
|
@Excel(name = "事件结果", replace = {"失败_0", "成功_1"})
|
||||||
|
private String result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 失败原因
|
||||||
|
*/
|
||||||
|
@Excel(name = "失败原因")
|
||||||
|
private String reason;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件类型
|
||||||
|
*/
|
||||||
|
@Excel(name = "事件类型", replace = {"业务事件_0", "系统事件_1"})
|
||||||
|
private Integer type;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件严重度(0.普通 1.中等 2.严重)
|
||||||
|
*/
|
||||||
|
@Excel(name = "事件严重度", replace = {"普通_0", "中等_1", "严重_2"})
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警标志(0:未告警;1:已告警),默认未告警
|
||||||
|
*/
|
||||||
|
@Excel(name = "告警标志", replace = {"未告警_0", "已告警_1"})
|
||||||
|
private Integer warn;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 日志发生时间
|
||||||
|
*/
|
||||||
|
@Excel(name = "日志发生时间", width = 30, exportFormat = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
@TableField(fill = FieldFill.INSERT)
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
|
||||||
|
@JsonSerialize(using = LocalDateTimeSerializer.class)
|
||||||
|
private LocalDateTime logTime;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
package com.njcn.gather.system.log.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-29
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@TableName("sys_log_run")
|
||||||
|
public class SysLogRun implements Serializable {
|
||||||
|
private static final long serialVersionUID = -37126398654461376L;
|
||||||
|
/**
|
||||||
|
* 运行日志Id
|
||||||
|
*/
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型(数据源、被检设备)
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* IP
|
||||||
|
*/
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件结果
|
||||||
|
*/
|
||||||
|
private String result;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件描述
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 告警标志(0:未告警;1:已告警),默认未告警
|
||||||
|
*/
|
||||||
|
private Integer warn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建用户
|
||||||
|
*/
|
||||||
|
private String createBy;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.njcn.gather.system.log.pojo.vo;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-11-25
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class SysLogVO {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 操作类型,审计日志独有字段。
|
||||||
|
*/
|
||||||
|
private String operateType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 类型(数据源、被检设备),运行日志独有字段。
|
||||||
|
*/
|
||||||
|
private String type;
|
||||||
|
|
||||||
|
private String ip;
|
||||||
|
|
||||||
|
private String result;
|
||||||
|
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 事件严重度(0.普通 1.中等 2.严重),审计日志独有字段。
|
||||||
|
*/
|
||||||
|
private Integer level;
|
||||||
|
|
||||||
|
private Integer warn;
|
||||||
|
|
||||||
|
private String createBy;
|
||||||
|
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package com.njcn.gather.system.log.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.system.log.pojo.dto.SysLogAuditRecord;
|
||||||
|
import com.njcn.gather.system.log.pojo.param.SysLogParam;
|
||||||
|
import com.njcn.gather.system.log.pojo.po.SysLogAudit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-29
|
||||||
|
*/
|
||||||
|
public interface ISysLogAuditService extends IService<SysLogAudit> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页查询审计日志
|
||||||
|
*
|
||||||
|
* @param param 查询参数
|
||||||
|
* @return 分页结果
|
||||||
|
*/
|
||||||
|
Page<SysLogAudit> listSysLogAudit(SysLogParam.QueryParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出审计日志数据
|
||||||
|
*
|
||||||
|
* @param param 查询参数
|
||||||
|
*/
|
||||||
|
void exportCSV(SysLogParam.QueryParam param);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分析日志
|
||||||
|
*
|
||||||
|
* @param param 查询参数
|
||||||
|
*/
|
||||||
|
void analyse(SysLogParam.QueryParam param);
|
||||||
|
|
||||||
|
void recodeAdviceLog(SysLogAuditRecord logRecord);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 全局异常拦截器捕获异常后的日志记录入库
|
||||||
|
*
|
||||||
|
* @param logRecord 日志记录
|
||||||
|
*/
|
||||||
|
void recodeBusinessExceptionLog(SysLogAuditRecord logRecord);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
package com.njcn.gather.system.log.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.system.log.pojo.po.SysLogRun;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-29
|
||||||
|
*/
|
||||||
|
public interface ISysLogRunService extends IService<SysLogRun> {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package com.njcn.gather.system.log.service.impl;
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.csv.entity.CsvExportParams;
|
||||||
|
import cn.hutool.core.date.DatePattern;
|
||||||
|
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||||
|
import cn.hutool.core.util.CharsetUtil;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.njcn.common.pojo.constant.LogInfo;
|
||||||
|
import com.njcn.db.mybatisplus.constant.UserConstant;
|
||||||
|
import com.njcn.gather.system.log.mapper.SysLogAuditMapper;
|
||||||
|
import com.njcn.gather.system.log.pojo.dto.SysLogAuditRecord;
|
||||||
|
import com.njcn.gather.system.log.pojo.param.SysLogParam;
|
||||||
|
import com.njcn.gather.system.log.pojo.po.SysLogAudit;
|
||||||
|
import com.njcn.gather.system.log.service.ISysLogAuditService;
|
||||||
|
import com.njcn.gather.system.log.util.CSVUtil;
|
||||||
|
import com.njcn.gather.user.user.pojo.po.SysUser;
|
||||||
|
import com.njcn.gather.user.user.service.ISysUserService;
|
||||||
|
import com.njcn.web.factory.PageFactory;
|
||||||
|
import com.njcn.web.utils.HttpServletUtil;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFSheet;
|
||||||
|
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-29
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysLogAuditServiceImpl extends ServiceImpl<SysLogAuditMapper, SysLogAudit> implements ISysLogAuditService {
|
||||||
|
|
||||||
|
private final ISysUserService sysUserService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Page<SysLogAudit> listSysLogAudit(SysLogParam.QueryParam param) {
|
||||||
|
QueryWrapper<SysLogAudit> queryWrapper = new QueryWrapper<>();
|
||||||
|
if (ObjectUtil.isNotNull(param)) {
|
||||||
|
queryWrapper.like(StrUtil.isNotBlank(param.getUserName()), "sys_log_audit.User_name", param.getUserName())
|
||||||
|
.ge(StrUtil.isNotBlank(param.getSearchBeginTime()), "sys_log_audit.Log_time", LocalDateTimeUtil.parse(param.getSearchBeginTime() + " 00:00:00", DatePattern.NORM_DATETIME_FORMATTER))
|
||||||
|
.le(StrUtil.isNotBlank(param.getSearchEndTime()), "sys_log_audit.Log_time", LocalDateTimeUtil.parse(param.getSearchEndTime() + " 23:59:59", DatePattern.NORM_DATETIME_FORMATTER));
|
||||||
|
}
|
||||||
|
queryWrapper.orderByDesc("sys_log_audit.Log_time");
|
||||||
|
return this.page(new Page<>(PageFactory.getPageNum(param), PageFactory.getPageSize(param)), queryWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exportCSV(SysLogParam.QueryParam param) {
|
||||||
|
QueryWrapper<SysLogAudit> queryWrapper = new QueryWrapper<>();
|
||||||
|
if (ObjectUtil.isNotNull(param)) {
|
||||||
|
queryWrapper.like(StrUtil.isNotBlank(param.getUserName()), "sys_log_audit.User_name", param.getUserName())
|
||||||
|
.ge(StrUtil.isNotBlank(param.getSearchBeginTime()), "sys_log_audit.Log_time", LocalDateTimeUtil.parse(param.getSearchBeginTime() + " 00:00:00", DatePattern.NORM_DATETIME_FORMATTER))
|
||||||
|
.le(StrUtil.isNotBlank(param.getSearchEndTime()), "sys_log_audit.Log_time", LocalDateTimeUtil.parse(param.getSearchEndTime() + " 23:59:59", DatePattern.NORM_DATETIME_FORMATTER));
|
||||||
|
}
|
||||||
|
queryWrapper.orderByDesc("sys_log_audit.Log_time");
|
||||||
|
List<SysLogAudit> list = this.list(queryWrapper);
|
||||||
|
CsvExportParams params = new CsvExportParams();
|
||||||
|
CSVUtil.exportCsv("日志数据.csv", params, SysLogAudit.class, list);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 暂时废弃,有需要再重新写
|
||||||
|
*
|
||||||
|
* @param param .
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
@Deprecated
|
||||||
|
public void analyse(SysLogParam.QueryParam param) {
|
||||||
|
QueryWrapper<SysLogAudit> queryWrapper = new QueryWrapper<>();
|
||||||
|
if (ObjectUtil.isNotNull(param)) {
|
||||||
|
queryWrapper.like(StrUtil.isNotBlank(param.getUserName()), "sys_log_audit.User_name", param.getUserName())
|
||||||
|
.between(StrUtil.isAllNotBlank(param.getSearchBeginTime(), param.getSearchEndTime()), "sys_log_audit.Log_time", param.getSearchBeginTime(), param.getSearchEndTime());
|
||||||
|
}
|
||||||
|
List<SysLogAudit> list = this.list(queryWrapper);
|
||||||
|
Map<String, Long> collect = list.stream().collect(Collectors.groupingBy(SysLogAudit::getLoginName, Collectors.counting()));
|
||||||
|
|
||||||
|
this.exportAnalyseExcel("分析结果", collect);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recodeAdviceLog(SysLogAuditRecord logRecord) {
|
||||||
|
saveAuditLog(logRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void recodeBusinessExceptionLog(SysLogAuditRecord logRecord) {
|
||||||
|
saveAuditLog(logRecord);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveAuditLog(SysLogAuditRecord logRecord) {
|
||||||
|
if (ObjectUtil.isNull(logRecord)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SysLogAudit sysLogAudit = new SysLogAudit();
|
||||||
|
fillOperator(sysLogAudit, logRecord);
|
||||||
|
sysLogAudit.setIp(logRecord.getIp());
|
||||||
|
sysLogAudit.setOperate(logRecord.getOperate());
|
||||||
|
sysLogAudit.setOperateType(logRecord.getOperateType());
|
||||||
|
sysLogAudit.setResult(logRecord.getResult());
|
||||||
|
sysLogAudit.setReason(logRecord.getReason());
|
||||||
|
sysLogAudit.setType(logRecord.getType() == null ? 1 : logRecord.getType());
|
||||||
|
Integer level = logRecord.getLevel() == null ? 0 : logRecord.getLevel();
|
||||||
|
sysLogAudit.setLevel(level);
|
||||||
|
Integer warn = logRecord.getWarn() == null ? (level == 1 ? 1 : 0) : logRecord.getWarn();
|
||||||
|
sysLogAudit.setWarn(warn);
|
||||||
|
sysLogAudit.setLogTime(LocalDateTime.now());
|
||||||
|
this.save(sysLogAudit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillOperator(SysLogAudit sysLogAudit, SysLogAuditRecord logRecord) {
|
||||||
|
String userId = logRecord.getUserId();
|
||||||
|
String loginName = logRecord.getLoginName();
|
||||||
|
|
||||||
|
if (StrUtil.isNotBlank(userId)) {
|
||||||
|
SysUser sysUser = sysUserService.getById(userId);
|
||||||
|
if (sysUser != null) {
|
||||||
|
sysLogAudit.setUserName(sysUser.getName());
|
||||||
|
sysLogAudit.setLoginName(sysUser.getLoginName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (StrUtil.isNotBlank(loginName) && !LogInfo.UNKNOWN_USER.equalsIgnoreCase(loginName)) {
|
||||||
|
sysLogAudit.setUserName(loginName);
|
||||||
|
sysLogAudit.setLoginName(loginName);
|
||||||
|
} else {
|
||||||
|
sysLogAudit.setUserName(UserConstant.UN_LOGIN);
|
||||||
|
sysLogAudit.setLoginName(UserConstant.UN_LOGIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void exportAnalyseExcel(String fileName, Map<String, Long> collect) {
|
||||||
|
HttpServletResponse response = HttpServletUtil.getResponse();
|
||||||
|
XSSFWorkbook wb = new XSSFWorkbook();
|
||||||
|
|
||||||
|
try (ServletOutputStream outputStream = response.getOutputStream()) {
|
||||||
|
fileName = URLEncoder.encode(fileName, CharsetUtil.UTF_8);
|
||||||
|
response.reset();
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
|
||||||
|
response.setContentType("application/octet-stream;charset=UTF-8");
|
||||||
|
|
||||||
|
XSSFSheet sheet = wb.createSheet("sheet1");
|
||||||
|
|
||||||
|
//createPieChart(sheet, collect.keySet().stream().collect(Collectors.toList()), collect.values().stream().collect(Collectors.toList()));
|
||||||
|
|
||||||
|
wb.write(outputStream);
|
||||||
|
wb.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
log.error(">>> 导出数据异常:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package com.njcn.gather.system.log.service.impl;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
|
import com.njcn.gather.system.log.pojo.po.SysLogRun;
|
||||||
|
import com.njcn.gather.system.log.mapper.SysLogRunMapper;
|
||||||
|
import com.njcn.gather.system.log.service.ISysLogRunService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-29
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysLogRunServiceImpl extends ServiceImpl<SysLogRunMapper, SysLogRun> implements ISysLogRunService {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package com.njcn.gather.system.log.util;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.afterturn.easypoi.csv.CsvExportUtil;
|
||||||
|
import cn.afterturn.easypoi.csv.entity.CsvExportParams;
|
||||||
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||||
|
import com.njcn.web.utils.HttpServletUtil;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.servlet.ServletOutputStream;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024-12-2
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class CSVUtil {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSV文件列分隔符
|
||||||
|
*/
|
||||||
|
private static final String CSV_COLUMN_SEPARATOR = ",";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSV文件行分隔符
|
||||||
|
*/
|
||||||
|
private static final String CSV_ROW_SEPARATOR = System.lineSeparator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出CSV文件
|
||||||
|
*
|
||||||
|
* @param fileName 文件名
|
||||||
|
* @param titles 表头
|
||||||
|
* @param keys 字段名
|
||||||
|
* @param dataList 数据集
|
||||||
|
*/
|
||||||
|
public static void export(String fileName, String[] titles, String[] keys, List<Map<String, Object>> dataList) {
|
||||||
|
// 保证线程安全
|
||||||
|
StringBuffer buf = new StringBuffer();
|
||||||
|
|
||||||
|
// 组装表头
|
||||||
|
for (String title : titles) {
|
||||||
|
buf.append(title).append(CSV_COLUMN_SEPARATOR);
|
||||||
|
}
|
||||||
|
buf.append(CSV_ROW_SEPARATOR);
|
||||||
|
// 组装数据
|
||||||
|
if (CollectionUtils.isNotEmpty(dataList)) {
|
||||||
|
for (Map<String, Object> map : dataList) {
|
||||||
|
for (String key : keys) {
|
||||||
|
if (ObjectUtil.isEmpty(map.get(key))) {
|
||||||
|
buf.append("").append(CSV_COLUMN_SEPARATOR);
|
||||||
|
} else {
|
||||||
|
// 如果数据内容中包含逗号,则进行使用!替换
|
||||||
|
buf.append(map.get(key).toString().replaceAll(",", "!")).append(CSV_COLUMN_SEPARATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.deleteCharAt(buf.length() - 1).append(CSV_ROW_SEPARATOR);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpServletResponse response = HttpServletUtil.getResponse();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ServletOutputStream os = response.getOutputStream();
|
||||||
|
Throwable var1 = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
fileName = URLEncoder.encode(fileName, "UTF-8");
|
||||||
|
response.reset();
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
|
||||||
|
response.setContentType("application/octet-stream;charset=UTF-8");
|
||||||
|
// 写出响应
|
||||||
|
os.write(buf.toString().getBytes("UTF-8"));
|
||||||
|
os.flush();
|
||||||
|
} catch (Throwable var2) {
|
||||||
|
var1 = var2;
|
||||||
|
throw var2;
|
||||||
|
} finally {
|
||||||
|
if (os != null) {
|
||||||
|
if (var1 != null) {
|
||||||
|
try {
|
||||||
|
os.close();
|
||||||
|
} catch (Throwable var3) {
|
||||||
|
var1.addSuppressed(var3);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
os.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException var4) {
|
||||||
|
IOException e = var4;
|
||||||
|
log.error(">>> 导出数据异常:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void exportCsv(String fileName, CsvExportParams params, Class<?> pojoClass, Collection<?> dataSet) {
|
||||||
|
HttpServletResponse response = HttpServletUtil.getResponse();
|
||||||
|
ServletOutputStream os = null;
|
||||||
|
try {
|
||||||
|
os = response.getOutputStream();
|
||||||
|
fileName = URLEncoder.encode(fileName, "UTF-8");
|
||||||
|
response.reset();
|
||||||
|
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"");
|
||||||
|
response.setContentType("application/octet-stream;charset=UTF-8");
|
||||||
|
|
||||||
|
CsvExportUtil.exportCsv(params, pojoClass, dataSet, os);
|
||||||
|
os.flush();
|
||||||
|
os.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(">>> 导出数据异常:{}", e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.njcn.gather.system.pojo.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @data 2024/11/8
|
||||||
|
*/
|
||||||
|
public interface DictConst {
|
||||||
|
/**
|
||||||
|
* 状态 0-正常;1-停用;2-删除 默认正常
|
||||||
|
*/
|
||||||
|
int ENABLE = 0;
|
||||||
|
|
||||||
|
int PAUSE = 1;
|
||||||
|
|
||||||
|
int DELETE = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 顶层父类的pid
|
||||||
|
*/
|
||||||
|
String FATHER_ID = "0";
|
||||||
|
}
|
||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package com.njcn.gather.system.pojo.constant;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @version 1.0
|
||||||
|
* @data 2024/10/30 14:46
|
||||||
|
*/
|
||||||
|
public interface SystemValidMessage {
|
||||||
|
|
||||||
|
|
||||||
|
String MISS_PREFIX = "字段不能为空,请检查";
|
||||||
|
|
||||||
|
|
||||||
|
String ID_NOT_BLANK = "id不能为空,请检查id参数";
|
||||||
|
|
||||||
|
String ID_FORMAT_ERROR = "id格式错误,请检查id参数";
|
||||||
|
|
||||||
|
String DICT_TYPE_ID_NOT_BLANK = "typeId不能为空,请检查typeId参数";
|
||||||
|
|
||||||
|
String DICT_TYPE_ID_FORMAT_ERROR = "typeId格式错误,请检查typeId参数";
|
||||||
|
|
||||||
|
String NAME_NOT_BLANK = "名称不能为空,请检查name参数";
|
||||||
|
|
||||||
|
String DICT_TYPE_NAME_FORMAT_ERROR = "名称格式错误,只能包含字母、数字、中文、下划线、中划线、空格,长度为1~32个字符";
|
||||||
|
String DICT_TYPE_CODE_FORMAT_ERROR = "编码格式错误,只能包含字母、数字、下划线,长度为1~30个字符";
|
||||||
|
|
||||||
|
String INDUSTRY_NOT_BLANK = "行业不能为空,请检查industry参数";
|
||||||
|
String INDUSTRY_FORMAT_ERROR = "行业格式错误,请检查industry参数";
|
||||||
|
String ADDR_NOT_BLANK = "所属区域不能为空,请检查addr参数";
|
||||||
|
|
||||||
|
String CODE_NOT_BLANK = "编码不能为空,请检查code参数";
|
||||||
|
|
||||||
|
String SORT_NOT_NULL = "排序不能为空,请检查sort参数";
|
||||||
|
|
||||||
|
String SORT_FORMAT_ERROR = "排序范围在1至999,请检查sort参数";
|
||||||
|
|
||||||
|
String OPEN_LEVEL_NOT_NULL = "开启等级不能为空,请检查openLevel参数";
|
||||||
|
|
||||||
|
String OPEN_LEVEL_FORMAT_ERROR = "开启等级格式错误,请检查openLevel参数";
|
||||||
|
|
||||||
|
String OPEN_DESCRIBE_NOT_NULL = "开启描述不能为空,请检查openDescribe参数";
|
||||||
|
|
||||||
|
String OPEN_DESCRIBE_FORMAT_ERROR = "开启描述格式错误,请检查openDescribe参数";
|
||||||
|
|
||||||
|
String AREA_NOT_BLANK = "行政区域不能为空,请检查area参数";
|
||||||
|
|
||||||
|
String AREA_FORMAT_ERROR = "行政区域格式错误,请检查area参数";
|
||||||
|
|
||||||
|
String PID_NOT_BLANK = "父节点不能为空,请检查pid参数";
|
||||||
|
|
||||||
|
String PID_FORMAT_ERROR = "父节点格式错误,请检查pid参数";
|
||||||
|
|
||||||
|
String COLOR_NOT_BLANK = "主题色不能为空,请检查color参数";
|
||||||
|
|
||||||
|
String COLOR_FORMAT_ERROR = "主题色格式错误,请检查color参数";
|
||||||
|
|
||||||
|
String LOGO_NOT_BLANK = "iconUrl不能为空,请检查iconUrl参数";
|
||||||
|
|
||||||
|
String FAVICON_NOT_BLANK = "faviconUrl不能为空,请检查faviconUrl参数";
|
||||||
|
|
||||||
|
String REMARK_NOT_BLANK = "描述不能为空,请检查remark参数";
|
||||||
|
|
||||||
|
String REMARK_FORMAT_ERROR = "描述格式错误,请检查remark参数";
|
||||||
|
|
||||||
|
String PARAM_FORMAT_ERROR = "参数值非法";
|
||||||
|
|
||||||
|
String IP_FORMAT_ERROR = "IP格式非法";
|
||||||
|
|
||||||
|
String DEVICE_VERSION_NOT_BLANK = "装置版本json文件不能为空,请检查deviceVersionFile参数";
|
||||||
|
|
||||||
|
String PHASE_NOT_BLANK = "相别不能为空,请检查phase参数";
|
||||||
|
|
||||||
|
String DATA_TYPE_NOT_BLANK = "数据模型不能为空,请检查dataType参数";
|
||||||
|
|
||||||
|
String CLASS_ID_NOT_BLANK = "数据表表名不能为空,请检查classId参数";
|
||||||
|
|
||||||
|
String AUTO_GENERATE_NOT_NULL = "是否自动生成不能为空,请检查autoGenerate参数";
|
||||||
|
|
||||||
|
String MAX_RECHECK_NOT_NULL = "最大检测次数不能为空,请检查maxRecheck参数";
|
||||||
|
|
||||||
|
String DATA_RULE_NOT_BLANK = "数据处理规则不能为空,请检查dataRule参数";
|
||||||
|
|
||||||
|
String DATA_RULE_FORMAT_ERROR = "数据处理规则格式错误,请检查dataRule参数";
|
||||||
|
|
||||||
|
String TYPE_NOT_BLANK = "版本类型不能为空,请检查type参数";
|
||||||
|
|
||||||
|
String AUTO_GENERATE_FORMAT_ERROR = "是否自动生成格式错误,请检查autoGenerate参数";
|
||||||
|
|
||||||
|
String USER_ID_FORMAT_ERROR = "用户id格式错误,请检查userId参数";
|
||||||
|
String DICT_DATA_NAME_FORMAT_ERROR = "字典数据名称格式错误,只能包含字母、数字、中文、下划线、中划线、空格、点、斜线、反斜线、百分号、摄氏度符号,长度为1~32个字符";
|
||||||
|
String DICT_DATA_CODE_FORMAT_ERROR = "字典数据编码格式错误,只能包含字母、数字、中文、下划线、中划线、空格、点、斜线、反斜线、百分号、摄氏度符号,长度为1~30个字符";
|
||||||
|
String DICT_PQ_OTHER_NAME_FORMAT_ERROR = "别名格式错误,只能包含字母、数字、中文、下划线、中划线、空格,长度最大为32个字符";
|
||||||
|
String DICT_PQ_SHOW_NAME_FORMAT_ERROR = "显示名称格式错误,只能包含字母、数字、中文、下划线、中划线、空格,长度最大为32个字符";
|
||||||
|
String SCALE_NOT_NULL = "数据精度不能为空,请检查scale参数";
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.njcn.gather.system.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author cdf
|
||||||
|
* @version 1.0.0
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum DicDataEnum {
|
||||||
|
|
||||||
|
|
||||||
|
FREQ("FREQ","频率"),
|
||||||
|
V("V","电压"),
|
||||||
|
I("I","电流"),
|
||||||
|
IMBV("IMBV","三相电压不平衡度"),
|
||||||
|
IMBA("IMBA","三相电流不平衡度"),
|
||||||
|
|
||||||
|
HV("HV","谐波电压"),
|
||||||
|
HI("HI","谐波电流"),
|
||||||
|
HP("HP","谐波有功功率"),
|
||||||
|
HSV("HSV","间谐波电压"),
|
||||||
|
HSI("HSI","间谐波电流"),
|
||||||
|
VOLTAGE("VOLTAGE","暂态"),
|
||||||
|
F("F","闪变"),
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
DicDataEnum(String code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DicDataEnum getEnumByCode(String code) {
|
||||||
|
for (DicDataEnum e : DicDataEnum.values()) {
|
||||||
|
if (e.getCode().equals(code)) {
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMessageByCode(String code) {
|
||||||
|
DicDataEnum e = getEnumByCode(code);
|
||||||
|
if (e!= null) {
|
||||||
|
return e.getMessage();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package com.njcn.gather.system.pojo.enums;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author hongawen
|
||||||
|
* @version 1.0.0
|
||||||
|
* @date 2021年12月20日 09:56
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
public enum SystemResponseEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统模块异常响应码的范围:
|
||||||
|
* A01000 ~ A01
|
||||||
|
*/
|
||||||
|
DICT_TYPE_REPEAT("A01000", "字典类型名称或编码重复"),
|
||||||
|
DICT_DATA_REPEAT("A01002", "字典数据名称或编码重复"),
|
||||||
|
DICT_PQ_NAME_EXIST("A01003", "当前数据模型及相别下已存在相同名称"),
|
||||||
|
EXISTS_CHILDREN_NOT_DELETE("A01004", "当前字典下存在子字典,不能删除"),
|
||||||
|
CODE_REPEAT("A01005", "该层级下已存在相同的编码"),
|
||||||
|
CAN_NOT_DELETE_USED_DICT("A01006", "该字典在使用中,不能删除"),
|
||||||
|
CAN_NOT_UPDATE_USED_DICT("A01007", "该字典在使用中,不能修改"),
|
||||||
|
LOG_RECORD_FAILED("A01008", "日志记录失败"),
|
||||||
|
|
||||||
|
|
||||||
|
GET_MAC_ADDRESS_FAILED("A01040", "获取本机MAC地址失败"),
|
||||||
|
REGISTRATION_CODE_FORMAT_ERROR("A01041", "注册码格式错误"),
|
||||||
|
MAC_ADDRESS_NOT_MATCH("A01042","注册码中的MAC地址与本机MAC地址不匹配"),
|
||||||
|
REGISTRATION_CODE_EXPIRED("A01043","注册码已过期"),
|
||||||
|
FILE_NOT_FOUND("A01050", "模板文件不存在"),
|
||||||
|
FILE_IO_ERROR("A01051", "文件读写异常"),;
|
||||||
|
|
||||||
|
private final String code;
|
||||||
|
|
||||||
|
private final String message;
|
||||||
|
|
||||||
|
SystemResponseEnum(String code, String message) {
|
||||||
|
this.code = code;
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.njcn.gather.system.reg.controller;
|
||||||
|
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.common.utils.LogUtil;
|
||||||
|
import com.njcn.gather.system.reg.pojo.param.SysRegResParam;
|
||||||
|
import com.njcn.gather.system.reg.pojo.po.SysRegRes;
|
||||||
|
import com.njcn.gather.system.reg.pojo.vo.SysRegResVO;
|
||||||
|
import com.njcn.gather.system.reg.service.ISysRegResService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-21
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Api(tags = "注册版本管理")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/sysRegRes")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SysRegResController extends BaseController {
|
||||||
|
private final ISysRegResService sysRegResService;
|
||||||
|
|
||||||
|
// @OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
// @GetMapping("/list")
|
||||||
|
// @ApiOperation("查询注册版本列表")
|
||||||
|
// public HttpResult<SysRegResVO> listRegRes() {
|
||||||
|
// String methodDescribe = getMethodDescribe("listRegRes");
|
||||||
|
// LogUtil.njcnDebug(log, "{},查询参数为空", methodDescribe);
|
||||||
|
// SysRegResVO result = sysRegResService.listRegRes();
|
||||||
|
// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
// }
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON)
|
||||||
|
@GetMapping("/getRegResByType")
|
||||||
|
@ApiOperation("根据类型id查询配置")
|
||||||
|
@ApiImplicitParam(name = "typeId", value = "类型id,字典值", required = true)
|
||||||
|
public HttpResult<SysRegRes> getRegResByType(@RequestParam("typeId") String typeId) {
|
||||||
|
String methodDescribe = getMethodDescribe("listByTypeId");
|
||||||
|
LogUtil.njcnDebug(log, "{},查询参数为:{}", methodDescribe, typeId);
|
||||||
|
SysRegRes result = sysRegResService.getRegResByType(typeId);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
// @OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.ADD)
|
||||||
|
// @PostMapping("/add")
|
||||||
|
// @ApiOperation("新增注册版本")
|
||||||
|
// @ApiImplicitParam(name = "sysRegRes", value = "注册版本对象", required = true)
|
||||||
|
// public HttpResult<Boolean> addRegRes(@RequestBody @Validated SysRegResParam param) {
|
||||||
|
// String methodDescribe = getMethodDescribe("addRegRes");
|
||||||
|
// LogUtil.njcnDebug(log, "{},新增参数为:{}", methodDescribe, param);
|
||||||
|
// boolean result = sysRegResService.addRegRes(param);
|
||||||
|
// if (result) {
|
||||||
|
// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
// } else {
|
||||||
|
// return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.SYSTEM_COMMON, operateType = OperateType.UPDATE)
|
||||||
|
@PostMapping("/update")
|
||||||
|
@ApiOperation("修改配置")
|
||||||
|
@ApiImplicitParam(name = "param", value = "注册版本更新对象", required = true)
|
||||||
|
public HttpResult<Boolean> updateRegRes(@RequestBody @Validated SysRegResParam.UpdateParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("updateRegRes");
|
||||||
|
LogUtil.njcnDebug(log, "{},更新参数为:{}", methodDescribe, param);
|
||||||
|
boolean result = sysRegResService.updateRegRes(param);
|
||||||
|
if (result) {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS, true, methodDescribe);
|
||||||
|
} else {
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.FAIL, false, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.njcn.gather.system.reg.mapper;
|
||||||
|
|
||||||
|
import com.github.yulichang.base.MPJBaseMapper;
|
||||||
|
import com.njcn.gather.system.reg.pojo.po.SysRegRes;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author caozehui
|
||||||
|
* @date 2024-11-21
|
||||||
|
*/
|
||||||
|
public interface SysRegResMapper extends MPJBaseMapper<SysRegRes> {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?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.njcn.gather.system.reg.mapper.SysRegResMapper">
|
||||||
|
|
||||||
|
|
||||||
|
</mapper>
|
||||||
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user