Compare commits
10 Commits
e8d7b1314b
...
7898dbebe3
| Author | SHA1 | Date |
|---|---|---|
|
|
7898dbebe3 | |
|
|
2ca07a12aa | |
|
|
26445b86f7 | |
|
|
a12d628b8f | |
|
|
fbcd766e36 | |
|
|
45d8d0679e | |
|
|
c42364cd9b | |
|
|
dca0e3201c | |
|
|
4d62fa66b4 | |
|
|
702600e3c6 |
|
|
@ -3,4 +3,16 @@
|
|||
/.idea/
|
||||
*-backup-*.chnr.json
|
||||
.back_devops
|
||||
.smarttomcat
|
||||
.smarttomcat
|
||||
/logs/
|
||||
/.claude/
|
||||
/CLAUDE.md
|
||||
/*.bat
|
||||
/setup_database.sql
|
||||
/PowerShell启动说明.md
|
||||
/DEPLOYMENT.md
|
||||
/ERROR_CAPTURE_GUIDE.md
|
||||
/*.sql
|
||||
/JDK21_UPGRADE_NOTES.md
|
||||
/start-jdk21.sh
|
||||
/PROJECT_README.md
|
||||
|
|
@ -0,0 +1,228 @@
|
|||
# 错误捕获和日志系统使用指南
|
||||
|
||||
## 🎯 系统功能概述
|
||||
|
||||
本系统已集成了完整的错误捕获和日志记录功能,能够自动捕获、分类保存和查看各种类型的错误信息。
|
||||
|
||||
## 📁 错误日志分类
|
||||
|
||||
### 自动分类保存
|
||||
所有错误信息会自动保存到 `logs/errors/` 目录,按类型分类:
|
||||
|
||||
1. **启动错误** (`startup-error-*.log`)
|
||||
- 应用启动失败
|
||||
- Spring Boot初始化异常
|
||||
- 配置加载错误
|
||||
|
||||
2. **运行时错误** (`runtime-error-*.log`)
|
||||
- 空指针异常
|
||||
- 非法参数异常
|
||||
- Java模块访问异常
|
||||
|
||||
3. **数据库错误** (`database-error-*.log`)
|
||||
- 数据库连接失败
|
||||
- SQL执行异常
|
||||
- MyBatis持久化错误
|
||||
|
||||
4. **业务错误** (`business-error-*.log`)
|
||||
- 监控服务异常
|
||||
- 微信通知发送失败
|
||||
- 业务逻辑处理错误
|
||||
|
||||
5. **启动信息** (`startup-info.log`)
|
||||
- 应用启动过程记录
|
||||
- 数据库健康检查结果
|
||||
- 系统配置信息
|
||||
|
||||
## 🚀 启动和查看错误
|
||||
|
||||
### 启动应用
|
||||
```cmd
|
||||
# 使用增强的调试脚本启动(推荐)
|
||||
start_debug.bat
|
||||
|
||||
# 或使用简单启动脚本
|
||||
start_with_logging.bat
|
||||
```
|
||||
|
||||
### 查看错误信息
|
||||
|
||||
#### 1. 文件系统方式
|
||||
```cmd
|
||||
# 查看所有错误日志文件
|
||||
dir logs\errors\*.log
|
||||
|
||||
# 查看启动错误(最重要)
|
||||
type logs\errors\startup-error-*.log
|
||||
|
||||
# 查看启动信息
|
||||
type logs\errors\startup-info.log
|
||||
|
||||
# 查看最新的运行时错误
|
||||
type logs\errors\runtime-error-*.log
|
||||
```
|
||||
|
||||
#### 2. Web接口方式
|
||||
应用启动后,访问以下接口:
|
||||
|
||||
- **错误状态概览**: http://localhost:8080/api/error-logs/status
|
||||
- **错误文件列表**: http://localhost:8080/api/error-logs/files
|
||||
- **读取具体文件**: http://localhost:8080/api/error-logs/content?fileName=startup-error-2025-08-12.log
|
||||
|
||||
## 🔍 错误诊断流程
|
||||
|
||||
### 1. 应用启动失败
|
||||
```cmd
|
||||
# 运行调试脚本
|
||||
start_debug.bat
|
||||
|
||||
# 检查启动错误日志
|
||||
type logs\errors\startup-error-*.log
|
||||
|
||||
# 检查启动信息
|
||||
type logs\errors\startup-info.log
|
||||
|
||||
# 检查数据库连接
|
||||
netstat -an | findstr 5432
|
||||
```
|
||||
|
||||
### 2. 运行时错误
|
||||
```cmd
|
||||
# 查看运行时错误日志
|
||||
type logs\errors\runtime-error-*.log
|
||||
|
||||
# 查看业务错误日志
|
||||
type logs\errors\business-error-*.log
|
||||
|
||||
# 通过Web接口查看
|
||||
# 访问: http://localhost:8080/api/error-logs/status
|
||||
```
|
||||
|
||||
### 3. 数据库相关错误
|
||||
```cmd
|
||||
# 查看数据库错误日志
|
||||
type logs\errors\database-error-*.log
|
||||
|
||||
# 查看数据库健康检查结果
|
||||
type logs\errors\startup-info.log | findstr "数据库\|健康检查"
|
||||
```
|
||||
|
||||
## 📊 Web错误监控界面
|
||||
|
||||
### API接口说明
|
||||
|
||||
1. **GET /api/error-logs/status**
|
||||
- 获取错误统计信息
|
||||
- 返回各类错误数量和日志文件大小
|
||||
|
||||
2. **GET /api/error-logs/files**
|
||||
- 获取所有错误日志文件列表
|
||||
- 包含文件大小、修改时间等信息
|
||||
|
||||
3. **GET /api/error-logs/content?fileName=xxx&lines=100**
|
||||
- 读取指定日志文件内容
|
||||
- 默认显示最后100行
|
||||
|
||||
4. **DELETE /api/error-logs/cleanup?keepDays=7**
|
||||
- 清理超过指定天数的旧日志文件
|
||||
- 默认保留7天
|
||||
|
||||
### 使用示例
|
||||
```bash
|
||||
# 获取错误状态
|
||||
curl http://localhost:8080/api/error-logs/status
|
||||
|
||||
# 获取文件列表
|
||||
curl http://localhost:8080/api/error-logs/files
|
||||
|
||||
# 读取启动错误日志
|
||||
curl "http://localhost:8080/api/error-logs/content?fileName=startup-error-2025-08-12.log&lines=50"
|
||||
```
|
||||
|
||||
## 🛠️ 故障排除
|
||||
|
||||
### 常见问题及解决方案
|
||||
|
||||
#### 1. Java模块系统兼容性错误
|
||||
```
|
||||
错误: java.lang.reflect.InaccessibleObjectException
|
||||
解决: 应用已自动设置所需的JVM参数,无需手动处理
|
||||
```
|
||||
|
||||
#### 2. 数据库连接失败
|
||||
```
|
||||
错误: 数据库连接异常
|
||||
检查:
|
||||
- PostgreSQL是否启动 (netstat -an | findstr 5432)
|
||||
- 数据库是否已创建 (devops_gd)
|
||||
- 用户名密码是否正确 (postgres/123456)
|
||||
```
|
||||
|
||||
#### 3. 端口占用
|
||||
```
|
||||
错误: 端口8080已被占用
|
||||
解决: netstat -ano | findstr 8080 找到进程并结束
|
||||
```
|
||||
|
||||
#### 4. Maven参数解析错误
|
||||
```
|
||||
错误: Unknown lifecycle phase
|
||||
解决: 使用提供的批处理脚本启动,避免PowerShell参数解析问题
|
||||
```
|
||||
|
||||
## 📝 日志维护
|
||||
|
||||
### 自动清理
|
||||
```bash
|
||||
# 通过Web接口清理7天前的日志
|
||||
curl -X DELETE "http://localhost:8080/api/error-logs/cleanup?keepDays=7"
|
||||
```
|
||||
|
||||
### 手动清理
|
||||
```cmd
|
||||
# 删除所有错误日志(谨慎操作)
|
||||
del logs\errors\*.log
|
||||
|
||||
# 删除超过7天的日志文件
|
||||
forfiles /p logs\errors /s /m *.log /d -7 /c "cmd /c del @path"
|
||||
```
|
||||
|
||||
## 🔧 系统配置
|
||||
|
||||
### 错误日志配置
|
||||
- **位置**: `src/main/resources/logback-spring.xml`
|
||||
- **错误工具类**: `com.chinaweal.youfool.devops.util.ErrorLogUtils`
|
||||
- **全局异常处理**: `com.chinaweal.youfool.devops.config.GlobalExceptionHandler`
|
||||
|
||||
### 数据库健康检查
|
||||
- **组件**: `com.chinaweal.youfool.devops.config.DatabaseHealthChecker`
|
||||
- **执行时机**: 应用启动完成后自动执行
|
||||
- **检查内容**: 数据库连接、基本信息获取
|
||||
|
||||
## 📋 最佳实践
|
||||
|
||||
1. **定期检查错误状态**
|
||||
- 访问 http://localhost:8080/api/error-logs/status
|
||||
- 关注错误数量变化趋势
|
||||
|
||||
2. **及时处理启动错误**
|
||||
- 启动失败时优先查看 `startup-error-*.log`
|
||||
- 根据错误信息进行针对性修复
|
||||
|
||||
3. **监控数据库健康**
|
||||
- 关注 `database-error-*.log` 中的连接失败记录
|
||||
- 定期检查数据库健康检查结果
|
||||
|
||||
4. **日志文件管理**
|
||||
- 定期清理旧日志文件避免磁盘空间不足
|
||||
- 保留重要的错误日志用于问题分析
|
||||
|
||||
## 🆘 紧急情况处理
|
||||
|
||||
如果遇到严重错误无法启动:
|
||||
|
||||
1. **查看最新的启动错误日志**
|
||||
2. **检查数据库服务状态**
|
||||
3. **确认Java环境和Maven配置**
|
||||
4. **使用Web接口(如果应用部分可用)获取详细错误信息**
|
||||
5. **将错误日志文件提供给开发人员进行进一步分析**
|
||||
153
README.md
153
README.md
|
|
@ -1,3 +1,152 @@
|
|||
**运维管理系统(CW)**
|
||||
**相关文档**
|
||||
|
||||
# YouFool DevOps 广东运维管理系统 (CW)
|
||||
|
||||
[](https://spring.io/projects/spring-boot)
|
||||
[](https://openjdk.org/)
|
||||
[](https://www.postgresql.org/)
|
||||
[](https://springdoc.org/)
|
||||
|
||||
## 🎯 项目概览
|
||||
|
||||
YouFool DevOps 运维管理系统(广东版)是基于 Spring Boot 框架开发的企业级运维管理平台。
|
||||
### 核心特性
|
||||
|
||||
- 🔧 **运维报障管理**: 完整的故障报告、处理流程和状态跟踪
|
||||
- 👥 **组织架构管理**: 用户、工程师信息管理和权限控制
|
||||
- 📋 **任务分配系统**: 领导任务分配和工作流管理
|
||||
- 📊 **数据统计分析**: 多维度数据统计和报表生成
|
||||
- 🔄 **实时通信**: WebSocket 支持的实时消息推送
|
||||
- 📝 **错误日志管理**: 可配置的错误捕获和日志管理系统
|
||||
|
||||
## 🏗️ 技术架构
|
||||
|
||||
### 运行环境
|
||||
- **Java**: JDK 21 (OpenJDK)
|
||||
- **框架**: Spring Boot 2.7.18
|
||||
- **构建工具**: Maven 3.x
|
||||
- **容器**: Apache Tomcat 9.0.83
|
||||
|
||||
### 数据层
|
||||
- **数据库**: PostgreSQL 11.7
|
||||
- **连接池**: Druid 1.2.23
|
||||
- **ORM**: MyBatis-Plus 3.5.7
|
||||
- **数据源**: 双数据源架构 (devops + youfool)
|
||||
|
||||
### 核心依赖
|
||||
- **企业框架**: youfool-framework-springboot 1.1.1-SNAPSHOT
|
||||
- **安全框架**: Apache Shiro 1.12.0
|
||||
- **API文档**: SpringDoc OpenAPI 1.8.0
|
||||
- **HTTP客户端**: Forest 1.5.16
|
||||
- **Excel处理**: EasyExcel 2.2.6 + Spire.XLS 3.9.1
|
||||
|
||||
### 技术亮点
|
||||
- ✅ **JDK 21 现代化升级**: 从 JDK 1.8 升级至 JDK 21,支持最新 Java 特性
|
||||
- ✅ **SpringDoc OpenAPI**: 从 SpringFox 迁移至现代化 API 文档解决方案
|
||||
- ✅ **双数据源架构**: 支持 devops 和 youfool 两个数据源的事务管理
|
||||
- ✅ **异步错误日志**: 高性能的错误日志捕获和管理系统
|
||||
|
||||
## 📂 项目结构
|
||||
|
||||
```
|
||||
src/main/java/com/chinaweal/youfool/devops/
|
||||
├── base/ # 基础环境模块
|
||||
│ ├── controller/ # 基础数据控制器 (字典、任务、错误日志等)
|
||||
│ ├── entity/ # 基础实体类
|
||||
│ └── service/ # 基础服务层
|
||||
├── repair/ # 运维报障模块 (核心业务)
|
||||
│ ├── controller/ # 报障管理控制器
|
||||
│ ├── entity/ # 报障相关实体类
|
||||
│ ├── excel/ # Excel 导入导出
|
||||
│ ├── scheduled/ # 定时任务
|
||||
│ └── service/ # 业务服务层
|
||||
├── org/ # 组织架构模块
|
||||
│ ├── controller/ # 用户和工程师管理
|
||||
│ ├── entity/ # 用户实体类
|
||||
│ └── business/ # 业务系统集成
|
||||
├── leaderassign/ # 领导分配模块
|
||||
│ ├── controller/ # 任务分配控制器
|
||||
│ └── entity/ # 分配任务实体
|
||||
├── websocket/ # WebSocket 模块
|
||||
│ └── server/ # 实时通信服务
|
||||
├── config/ # 配置类
|
||||
├── util/ # 工具类
|
||||
└── DevOpsApplication.java # 应用主类
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 环境要求
|
||||
- JDK 21+
|
||||
- PostgreSQL 11+
|
||||
- Maven 3.6+
|
||||
|
||||
### 启动应用
|
||||
```bash
|
||||
# 开发环境启动
|
||||
mvn spring-boot:run -Dspring-boot.run.profiles=dev
|
||||
|
||||
# 或使用优化的JDK 21启动脚本
|
||||
./start-jdk21.sh # Linux/Mac
|
||||
start-jdk21.bat # Windows
|
||||
```
|
||||
|
||||
### 访问地址
|
||||
- **应用主页**: http://localhost:8080
|
||||
- **API文档**: http://localhost:8080/swagger-ui.html
|
||||
- **Druid监控**: http://localhost:8080/druid (admin/123456)
|
||||
- **错误日志管理**: http://localhost:8080/api/error-logs/
|
||||
|
||||
### API文档分组
|
||||
- **组织架构**: 用户和工程师管理接口
|
||||
- **运维报障**: 核心业务功能接口
|
||||
- **基础环境**: 字典和基础数据接口
|
||||
- **WebSocket测试环境**: 实时通信测试接口
|
||||
- **领导分配**: 任务分配相关接口
|
||||
|
||||
## 📊 配置说明
|
||||
|
||||
### 环境配置
|
||||
- `application-dev.yml`: 开发环境配置
|
||||
- `application-prod.yml`: 生产环境配置
|
||||
- `application-local.yml`: 本地开发配置
|
||||
|
||||
### 错误日志配置
|
||||
系统支持完整的错误日志管理,可通过 `error-log.*` 配置项控制:
|
||||
|
||||
```yaml
|
||||
error-log:
|
||||
enabled: true # 全局开关
|
||||
startup-enabled: true # 启动错误日志
|
||||
runtime-enabled: true # 运行时错误日志
|
||||
database-enabled: true # 数据库错误日志
|
||||
business-enabled: true # 业务错误日志
|
||||
log-directory: logs/errors # 日志目录
|
||||
async-write: true # 异步写入
|
||||
```
|
||||
|
||||
## 🛡️ 系统监控
|
||||
|
||||
### 错误日志管理
|
||||
- **Web界面**: `/api/error-logs/` 提供完整的日志管理功能
|
||||
- **日志分类**: 启动、运行时、数据库、业务错误分类存储
|
||||
- **自动清理**: 支持按天数自动清理历史日志
|
||||
- **性能优化**: 异步写入,不影响业务性能
|
||||
|
||||
### 数据库健康检查
|
||||
- **双数据源监控**: devops 和 youfool 数据源健康状态检查
|
||||
- **连接信息展示**: 启动时显示数据库连接详情
|
||||
- **表访问测试**: ENGINEER 表数据访问验证
|
||||
|
||||
### Druid 连接池监控
|
||||
- **访问地址**: http://localhost:8080/druid
|
||||
- **监控内容**: SQL执行统计、连接池状态、慢SQL分析
|
||||
- **登录信息**: admin / 123456
|
||||
|
||||
## 🔒 安全特性
|
||||
|
||||
- **Apache Shiro**: 基于角色的访问控制 (RBAC)
|
||||
- **JWT Token**: 10小时后端令牌生命周期
|
||||
- **RSA 加密**: 敏感数据加密传输
|
||||
- **SM3 哈希**: 密码安全哈希存储
|
||||
- **请求拦截**: 全局登录状态验证
|
||||
|
||||
|
|
|
|||
1228
db/devops.pdma.json
1228
db/devops.pdma.json
File diff suppressed because it is too large
Load Diff
131
pom.xml
131
pom.xml
|
|
@ -14,17 +14,32 @@
|
|||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.2.6.RELEASE</version>
|
||||
<version>2.7.18</version>
|
||||
<relativePath/> <!-- lookup parent from repository -->
|
||||
</parent>
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<java.version>21</java.version>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<maven.compiler.release>21</maven.compiler.release>
|
||||
<skipTests>true</skipTests>
|
||||
<log4j.version>2.17.1</log4j.version>
|
||||
<logback.version>1.2.9</logback.version>
|
||||
<shiro.version>1.12.0</shiro.version>
|
||||
<!-- JDK 21兼容版本 -->
|
||||
<lombok.version>1.18.34</lombok.version>
|
||||
<mybatis.version>3.5.16</mybatis.version>
|
||||
<druid.version>1.2.23</druid.version>
|
||||
<mybatis-plus.version>3.5.7</mybatis-plus.version>
|
||||
<!-- SpringDoc OpenAPI for Swagger替换SpringFox -->
|
||||
<springdoc.version>1.8.0</springdoc.version>
|
||||
</properties>
|
||||
<dependencies>
|
||||
<!--Spring Boot Web Starter-->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<!--基础框架youfool-framework-boot-->
|
||||
<dependency>
|
||||
<groupId>com.chinaweal.youfool</groupId>
|
||||
|
|
@ -43,19 +58,36 @@
|
|||
<groupId>org.apache.shiro</groupId>
|
||||
<artifactId>shiro-spring</artifactId>
|
||||
</exclusion>
|
||||
<!-- 排除SpringFox相关依赖,使用SpringDoc OpenAPI替代 -->
|
||||
<exclusion>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-spring-boot-starter</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger-ui</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-boot-starter</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<!--druid-->
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>druid-spring-boot-starter</artifactId>
|
||||
<version>1.1.21</version>
|
||||
<version>${druid.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>1.18.12</version>
|
||||
<version>${lombok.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
@ -130,7 +162,7 @@
|
|||
<dependency>
|
||||
<groupId>org.mybatis</groupId>
|
||||
<artifactId>mybatis</artifactId>
|
||||
<version>3.5.6</version>
|
||||
<version>${mybatis.version}</version>
|
||||
</dependency>
|
||||
<!-- shiro -->
|
||||
<dependency>
|
||||
|
|
@ -138,13 +170,100 @@
|
|||
<artifactId>shiro-spring</artifactId>
|
||||
<version>${shiro.version}</version>
|
||||
</dependency>
|
||||
<!-- Spring Boot validation starter (required for @Valid annotations) -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<!-- SpringDoc OpenAPI 3 (替换SpringFox/Knife4j) -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<!-- SpringDoc OpenAPI WebMVC支持 -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-webmvc-core</artifactId>
|
||||
<version>${springdoc.version}</version>
|
||||
</dependency>
|
||||
<!-- 临时依赖:SpringFox注解兼容(仅用于编译,将逐步迁移到SpringDoc) -->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
<artifactId>springfox-swagger2</artifactId>
|
||||
<version>2.9.2</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<!-- 临时依赖:Knife4j注解兼容 -->
|
||||
<dependency>
|
||||
<groupId>com.github.xiaoymin</groupId>
|
||||
<artifactId>knife4j-annotations</artifactId>
|
||||
<version>2.0.9</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<finalName>devops-api</finalName>
|
||||
<finalName>devops-api-gd</finalName>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<jvmArguments>
|
||||
--add-opens java.base/java.util=ALL-UNNAMED
|
||||
--add-opens java.base/java.lang=ALL-UNNAMED
|
||||
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
|
||||
--add-opens java.base/java.time=ALL-UNNAMED
|
||||
--add-opens java.desktop/java.beans=ALL-UNNAMED
|
||||
</jvmArguments>
|
||||
<excludes>
|
||||
<exclude>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</exclude>
|
||||
</excludes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.12.1</version>
|
||||
<configuration>
|
||||
<source>21</source>
|
||||
<target>21</target>
|
||||
<release>21</release>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<version>${lombok.version}</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
<arg>--add-opens</arg>
|
||||
<arg>java.base/java.util=ALL-UNNAMED</arg>
|
||||
<arg>--add-opens</arg>
|
||||
<arg>java.base/java.lang=ALL-UNNAMED</arg>
|
||||
<arg>--add-opens</arg>
|
||||
<arg>java.base/java.lang.reflect=ALL-UNNAMED</arg>
|
||||
<arg>-Xlint:deprecation</arg>
|
||||
<arg>-Xlint:unchecked</arg>
|
||||
<arg>-Xlint:-options</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<version>3.2.5</version>
|
||||
<configuration>
|
||||
<argLine>
|
||||
--add-opens java.base/java.util=ALL-UNNAMED
|
||||
--add-opens java.base/java.lang=ALL-UNNAMED
|
||||
--add-opens java.base/java.lang.reflect=ALL-UNNAMED
|
||||
--add-opens java.base/java.time=ALL-UNNAMED
|
||||
</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
package com.chinaweal.youfool.devops;
|
||||
|
||||
import com.chinaweal.youfool.devops.config.ErrorLogProperties;
|
||||
import com.chinaweal.youfool.devops.util.ErrorLogUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.builder.SpringApplicationBuilder;
|
||||
|
|
@ -10,16 +17,29 @@ import org.springframework.context.ApplicationListener;
|
|||
import org.springframework.context.event.ContextRefreshedEvent;
|
||||
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@SpringBootApplication(scanBasePackages = {"com.chinaweal"})
|
||||
@Slf4j
|
||||
@EnableScheduling
|
||||
public class DevOpsApplication extends SpringBootServletInitializer implements ApplicationListener<ContextRefreshedEvent> {
|
||||
@Value("${applicationName}")
|
||||
@Value("${applicationName:devOps}")
|
||||
private String applicationName;
|
||||
@Value("${version}")
|
||||
@Value("${version:1.0.0}")
|
||||
private String version;
|
||||
@Value("${description}")
|
||||
@Value("${description:运维管理系统}")
|
||||
private String description;
|
||||
|
||||
@Autowired
|
||||
private ErrorLogProperties errorLogProperties;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("devopsDS")
|
||||
private DataSource devopsDataSource;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("youfoolDS")
|
||||
private DataSource youfoolDataSource;
|
||||
|
||||
@Override
|
||||
protected SpringApplicationBuilder configure(SpringApplicationBuilder applicationBuilder) {
|
||||
|
|
@ -29,17 +49,229 @@ public class DevOpsApplication extends SpringBootServletInitializer implements A
|
|||
@Override
|
||||
public void onApplicationEvent(ContextRefreshedEvent event) {
|
||||
if (event.getApplicationContext().getParent() == null) {
|
||||
log.info("========================== 程序启动成功! ==========================");
|
||||
log.info("====== 程 序:{} !", applicationName);
|
||||
log.info("====== 版本号:{} ", version);
|
||||
log.info("====== 描 述:{} ", description);
|
||||
log.info("====== 接口文档路径:/doc.html,账号:admin、密码:123456。注:如果乱码请指定VM -Dfile.encoding=UTF-8");
|
||||
log.info("====== Druid Monitor路径:/druid,账号:admin、密码:123456");
|
||||
log.info("====================================================================");
|
||||
try {
|
||||
log.info("========================== 程序启动成功! ==========================");
|
||||
log.info("====== 程 序:{} !", applicationName);
|
||||
log.info("====== 版本号:{} ", version);
|
||||
log.info("====== 描 述:{} ", description);
|
||||
log.info("====== Java环境:{}", getJavaVersionInfo());
|
||||
log.info("====== 接口文档路径:/doc.html,账号:admin、密码:123456。注:如果乱码请指定VM -Dfile.encoding=UTF-8");
|
||||
log.info("====== Druid Monitor路径:/druid,账号:admin、密码:123456");
|
||||
|
||||
// 显示错误日志配置状态
|
||||
if (errorLogProperties != null) {
|
||||
log.info("====== 错误日志配置:启用={}, 目录={}",
|
||||
errorLogProperties.isEnabled(), errorLogProperties.getLogDirectory());
|
||||
log.info("====== 错误日志类型:启动={}, 运行时={}, 数据库={}, 业务={}",
|
||||
errorLogProperties.isStartupEnabled(),
|
||||
errorLogProperties.isRuntimeEnabled(),
|
||||
errorLogProperties.isDatabaseEnabled(),
|
||||
errorLogProperties.isBusinessEnabled());
|
||||
}
|
||||
|
||||
// 打印数据库连接信息
|
||||
printDatabaseConnectionInfo();
|
||||
|
||||
log.info("====================================================================");
|
||||
|
||||
// 记录启动成功信息
|
||||
ErrorLogUtils.logStartupInfo("应用启动成功 - " + applicationName + " v" + version);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("启动成功回调处理异常", e);
|
||||
ErrorLogUtils.saveStartupError("启动成功回调异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(DevOpsApplication.class, args);
|
||||
try {
|
||||
// 记录启动开始
|
||||
ErrorLogUtils.logStartupInfo("开始启动应用,参数: " + Arrays.toString(args));
|
||||
|
||||
// 设置默认的JVM参数以解决Java模块系统兼容性问题
|
||||
setJavaModuleOptions();
|
||||
|
||||
// 启动Spring Boot应用
|
||||
SpringApplication app = new SpringApplication(DevOpsApplication.class);
|
||||
|
||||
// 添加启动失败监听器
|
||||
app.addListeners(event -> {
|
||||
if (event instanceof org.springframework.boot.context.event.ApplicationFailedEvent) {
|
||||
org.springframework.boot.context.event.ApplicationFailedEvent failedEvent =
|
||||
(org.springframework.boot.context.event.ApplicationFailedEvent) event;
|
||||
Throwable exception = failedEvent.getException();
|
||||
|
||||
log.error("应用启动失败", exception);
|
||||
ErrorLogUtils.saveStartupError("应用启动失败", exception);
|
||||
|
||||
// 输出友好的错误信息
|
||||
System.err.println("\n==================== 应用启动失败 ====================");
|
||||
System.err.println("错误信息已保存到: logs/errors/startup-error-*.log");
|
||||
System.err.println("详细错误信息: " + ErrorLogUtils.formatErrorInfo("启动失败", exception));
|
||||
System.err.println("=================================================\n");
|
||||
}
|
||||
});
|
||||
|
||||
app.run(args);
|
||||
|
||||
// 添加JVM关闭钩子以确保异步日志写入器正确关闭
|
||||
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||||
log.info("应用正在关闭,清理错误日志资源...");
|
||||
ErrorLogUtils.shutdown();
|
||||
}));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("应用启动异常", e);
|
||||
ErrorLogUtils.saveStartupError("主方法启动异常", e);
|
||||
|
||||
// 输出友好的错误信息到控制台
|
||||
System.err.println("\n==================== 应用启动异常 ====================");
|
||||
System.err.println("错误信息已保存到: logs/errors/startup-error-*.log");
|
||||
System.err.println("详细错误信息: " + ErrorLogUtils.formatErrorInfo("启动异常", e));
|
||||
System.err.println("=================================================\n");
|
||||
|
||||
// 重新抛出异常以确保程序正确退出
|
||||
throw new RuntimeException("应用启动失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置Java版本兼容性参数
|
||||
*/
|
||||
private static void setJavaModuleOptions() {
|
||||
try {
|
||||
String javaVersion = System.getProperty("java.version");
|
||||
ErrorLogUtils.logStartupInfo("当前Java版本: " + javaVersion);
|
||||
|
||||
// 只在Java 9+版本设置模块参数
|
||||
if (isJava9OrHigher()) {
|
||||
String javaToolOptions = System.getProperty("JAVA_TOOL_OPTIONS", "");
|
||||
|
||||
// 检查是否已经设置了必要的模块参数
|
||||
if (!javaToolOptions.contains("--add-opens java.base/java.util=ALL-UNNAMED")) {
|
||||
String newOptions = javaToolOptions +
|
||||
" --add-opens java.base/java.util=ALL-UNNAMED" +
|
||||
" --add-opens java.base/java.lang=ALL-UNNAMED" +
|
||||
" --add-opens java.base/java.lang.reflect=ALL-UNNAMED" +
|
||||
" --add-opens java.base/java.time=ALL-UNNAMED";
|
||||
|
||||
System.setProperty("JAVA_TOOL_OPTIONS", newOptions.trim());
|
||||
ErrorLogUtils.logStartupInfo("已设置Java模块兼容性参数: " + newOptions.trim());
|
||||
}
|
||||
} else {
|
||||
ErrorLogUtils.logStartupInfo("检测到Java 8,跳过模块系统参数设置");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("检查Java版本失败", e);
|
||||
ErrorLogUtils.saveStartupError("Java版本检查失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否为Java 9或更高版本
|
||||
*/
|
||||
private static boolean isJava9OrHigher() {
|
||||
try {
|
||||
String version = System.getProperty("java.version");
|
||||
// Java 8: 1.8.x, Java 9+: 9.x, 10.x, 11.x, 21.x, etc.
|
||||
if (version.startsWith("1.8")) {
|
||||
return false;
|
||||
}
|
||||
// 尝试解析主版本号
|
||||
String[] parts = version.split("\\.");
|
||||
if (parts.length > 0) {
|
||||
int majorVersion = Integer.parseInt(parts[0]);
|
||||
return majorVersion >= 9;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("解析Java版本失败: " + System.getProperty("java.version"), e);
|
||||
}
|
||||
return true; // JDK 21环境下默认为true
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前Java版本信息
|
||||
*/
|
||||
private static String getJavaVersionInfo() {
|
||||
try {
|
||||
String version = System.getProperty("java.version");
|
||||
String vendor = System.getProperty("java.vendor");
|
||||
String vmName = System.getProperty("java.vm.name");
|
||||
return String.format("Java %s (%s - %s)", version, vendor, vmName);
|
||||
} catch (Exception e) {
|
||||
return "Java version unknown";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印数据库连接信息
|
||||
*/
|
||||
private void printDatabaseConnectionInfo() {
|
||||
try {
|
||||
log.info("========== 数据库连接信息 ==========");
|
||||
|
||||
// 打印devops数据源信息
|
||||
try (Connection conn = devopsDataSource.getConnection()) {
|
||||
String url = conn.getMetaData().getURL();
|
||||
String username = conn.getMetaData().getUserName();
|
||||
String databaseName = conn.getCatalog();
|
||||
|
||||
log.info("🔗 Devops数据源:");
|
||||
log.info(" URL: {}", url);
|
||||
log.info(" 用户: {}", username);
|
||||
log.info(" 数据库: {}", databaseName);
|
||||
|
||||
ErrorLogUtils.logStartupInfo("Devops数据源连接: " + url + ", 用户: " + username + ", 数据库: " + databaseName);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ 获取devops数据源信息失败", e);
|
||||
ErrorLogUtils.saveStartupError("获取devops数据源信息失败", e);
|
||||
}
|
||||
|
||||
// 打印youfool数据源信息
|
||||
try (Connection conn = youfoolDataSource.getConnection()) {
|
||||
String url = conn.getMetaData().getURL();
|
||||
String username = conn.getMetaData().getUserName();
|
||||
String databaseName = conn.getCatalog();
|
||||
|
||||
log.info("🔗 Youfool数据源:");
|
||||
log.info(" URL: {}", url);
|
||||
log.info(" 用户: {}", username);
|
||||
log.info(" 数据库: {}", databaseName);
|
||||
|
||||
ErrorLogUtils.logStartupInfo("Youfool数据源连接: " + url + ", 用户: " + username + ", 数据库: " + databaseName);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ 获取youfool数据源信息失败", e);
|
||||
ErrorLogUtils.saveStartupError("获取youfool数据源信息失败", e);
|
||||
}
|
||||
|
||||
// 测试ENGINEER表访问
|
||||
try (Connection conn = devopsDataSource.getConnection()) {
|
||||
// 简单测试查询
|
||||
String testSql = "SELECT COUNT(*) as total FROM engineer";
|
||||
try (java.sql.PreparedStatement ps = conn.prepareStatement(testSql);
|
||||
java.sql.ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
int total = rs.getInt("total");
|
||||
log.info("✅ ENGINEER表测试: 共{}条记录", total);
|
||||
ErrorLogUtils.logStartupInfo("ENGINEER表访问测试成功,共" + total + "条记录");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("❌ ENGINEER表访问测试失败", e);
|
||||
ErrorLogUtils.saveStartupError("ENGINEER表访问测试失败", e);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("❌ 数据库连接测试失败", e);
|
||||
ErrorLogUtils.saveStartupError("数据库连接测试失败", e);
|
||||
}
|
||||
|
||||
log.info("=====================================");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("❌ 打印数据库连接信息时发生异常", e);
|
||||
ErrorLogUtils.saveStartupError("打印数据库连接信息异常", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,238 @@
|
|||
package com.chinaweal.youfool.devops.base.controller;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据库测试控制器
|
||||
* 用于测试ENGINEER表的SQL查询问题
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/test")
|
||||
@Api(tags = "数据库测试接口")
|
||||
@Slf4j
|
||||
public class DatabaseTestController {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("devopsDS")
|
||||
private DataSource devopsDataSource;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("youfoolDS")
|
||||
private DataSource youfoolDataSource;
|
||||
|
||||
/**
|
||||
* 测试数据库连接信息
|
||||
*/
|
||||
@GetMapping("/connection")
|
||||
@ApiOperation("测试数据库连接信息")
|
||||
public Map<String, Object> testConnection() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 测试devops数据源
|
||||
try (Connection conn = devopsDataSource.getConnection()) {
|
||||
result.put("devops_url", conn.getMetaData().getURL());
|
||||
result.put("devops_username", conn.getMetaData().getUserName());
|
||||
result.put("devops_database", conn.getCatalog());
|
||||
}
|
||||
|
||||
// 测试youfool数据源
|
||||
try (Connection conn = youfoolDataSource.getConnection()) {
|
||||
result.put("youfool_url", conn.getMetaData().getURL());
|
||||
result.put("youfool_username", conn.getMetaData().getUserName());
|
||||
result.put("youfool_database", conn.getCatalog());
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
result.put("message", "数据库连接测试成功");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("数据库连接测试失败", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "连接测试失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试ENGINEER表结构
|
||||
*/
|
||||
@GetMapping("/engineer-structure")
|
||||
@ApiOperation("测试ENGINEER表结构")
|
||||
public Map<String, Object> testEngineerStructure() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 使用devops数据源测试
|
||||
try (Connection conn = devopsDataSource.getConnection()) {
|
||||
// 检查表是否存在
|
||||
String checkTableSql = "SELECT COUNT(*) as table_count FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'engineer'";
|
||||
try (PreparedStatement ps = conn.prepareStatement(checkTableSql);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
result.put("table_exists", rs.getInt("table_count") > 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 获取字段信息
|
||||
String fieldsSql = "SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_schema = 'public' AND table_name = 'engineer' ORDER BY ordinal_position";
|
||||
List<Map<String, Object>> fields = new ArrayList<>();
|
||||
try (PreparedStatement ps = conn.prepareStatement(fieldsSql);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
while (rs.next()) {
|
||||
Map<String, Object> field = new HashMap<>();
|
||||
field.put("name", rs.getString("column_name"));
|
||||
field.put("type", rs.getString("data_type"));
|
||||
field.put("nullable", rs.getString("is_nullable"));
|
||||
fields.add(field);
|
||||
}
|
||||
}
|
||||
result.put("fields", fields);
|
||||
|
||||
result.put("datasource", "devops");
|
||||
result.put("success", true);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("ENGINEER表结构测试失败", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "测试失败: " + e.getMessage());
|
||||
result.put("error", e.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试问题SQL - 分步执行
|
||||
*/
|
||||
@GetMapping("/engineer-query")
|
||||
@ApiOperation("测试ENGINEER表查询")
|
||||
public Map<String, Object> testEngineerQuery() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 使用devops数据源测试
|
||||
try (Connection conn = devopsDataSource.getConnection()) {
|
||||
result.put("connection_url", conn.getMetaData().getURL());
|
||||
result.put("connection_user", conn.getMetaData().getUserName());
|
||||
|
||||
// 测试1: 简单查询
|
||||
String simpleSql = "SELECT COUNT(*) as total FROM ENGINEER";
|
||||
try (PreparedStatement ps = conn.prepareStatement(simpleSql);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
if (rs.next()) {
|
||||
result.put("total_records", rs.getInt("total"));
|
||||
}
|
||||
result.put("simple_query", "✓ 成功");
|
||||
} catch (Exception e) {
|
||||
result.put("simple_query", "✗ 失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 测试2: 测试USER_ID字段
|
||||
String userIdSql = "SELECT USER_ID FROM ENGINEER LIMIT 1";
|
||||
try (PreparedStatement ps = conn.prepareStatement(userIdSql);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
result.put("user_id_query", "✓ 成功");
|
||||
if (rs.next()) {
|
||||
result.put("sample_user_id", rs.getString("USER_ID"));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.put("user_id_query", "✗ 失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
// 测试3: 测试完整的问题SQL
|
||||
String fullSql = "SELECT USER_ID, USERNAME, PASSWORD, NICKNAME, SEX, PHONE, EMAIL, STATUS, " +
|
||||
"DESCRIPTION, SORT, ROLES, SOURCE, IS_DELETED, create_by, " +
|
||||
"create_time, update_by, update_time FROM ENGINEER WHERE IS_DELETED='0' LIMIT 1";
|
||||
try (PreparedStatement ps = conn.prepareStatement(fullSql);
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
result.put("full_query", "✓ 成功");
|
||||
if (rs.next()) {
|
||||
Map<String, Object> sampleData = new HashMap<>();
|
||||
sampleData.put("USER_ID", rs.getString("USER_ID"));
|
||||
sampleData.put("USERNAME", rs.getString("USERNAME"));
|
||||
sampleData.put("IS_DELETED", rs.getString("IS_DELETED"));
|
||||
result.put("sample_data", sampleData);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.put("full_query", "✗ 失败: " + e.getMessage());
|
||||
result.put("full_query_error", e.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
// 测试4: 测试带参数的查询(模拟实际应用场景)
|
||||
String paramSql = "SELECT USER_ID, USERNAME FROM ENGINEER WHERE IS_DELETED='0' AND USERNAME = ?";
|
||||
try (PreparedStatement ps = conn.prepareStatement(paramSql)) {
|
||||
ps.setString(1, "test");
|
||||
try (ResultSet rs = ps.executeQuery()) {
|
||||
result.put("param_query", "✓ 成功");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
result.put("param_query", "✗ 失败: " + e.getMessage());
|
||||
result.put("param_query_error", e.getClass().getSimpleName());
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("ENGINEER查询测试失败", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "查询测试失败: " + e.getMessage());
|
||||
result.put("error_class", e.getClass().getSimpleName());
|
||||
result.put("error_cause", e.getCause() != null ? e.getCause().getMessage() : null);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 测试MyBatis查询(使用相同的数据源配置)
|
||||
*/
|
||||
@GetMapping("/mybatis-test")
|
||||
@ApiOperation("测试MyBatis数据源配置")
|
||||
public Map<String, Object> testMyBatisDataSource() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 检查数据源配置
|
||||
result.put("devops_datasource_class", devopsDataSource.getClass().getSimpleName());
|
||||
result.put("youfool_datasource_class", youfoolDataSource.getClass().getSimpleName());
|
||||
|
||||
// 测试两个数据源是否指向同一个数据库
|
||||
try (Connection devopsConn = devopsDataSource.getConnection();
|
||||
Connection youfoolConn = youfoolDataSource.getConnection()) {
|
||||
|
||||
result.put("devops_url", devopsConn.getMetaData().getURL());
|
||||
result.put("youfool_url", youfoolConn.getMetaData().getURL());
|
||||
result.put("same_database", devopsConn.getMetaData().getURL().equals(youfoolConn.getMetaData().getURL()));
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("MyBatis数据源测试失败", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "测试失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
package com.chinaweal.youfool.devops.base.controller;
|
||||
|
||||
import com.chinaweal.youfool.devops.config.ErrorLogProperties;
|
||||
import com.chinaweal.youfool.devops.util.ErrorLogUtils;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.annotations.ApiParam;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 错误日志查看控制器
|
||||
* 提供Web接口查看应用错误日志
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/api/error-logs")
|
||||
@Api(tags = "错误日志管理")
|
||||
@Slf4j
|
||||
public class ErrorLogController {
|
||||
|
||||
@Autowired
|
||||
private ErrorLogProperties errorLogProperties;
|
||||
|
||||
private static final String MAIN_LOG_DIR = "logs";
|
||||
|
||||
/**
|
||||
* 获取错误日志配置信息
|
||||
*/
|
||||
@GetMapping("/config")
|
||||
@ApiOperation("获取错误日志配置信息")
|
||||
public Map<String, Object> getErrorLogConfig() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
result.put("success", true);
|
||||
result.put("configInfo", ErrorLogUtils.getConfigurationInfo());
|
||||
result.put("properties", errorLogProperties);
|
||||
result.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取错误日志配置失败", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "获取配置失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取错误日志文件列表
|
||||
*/
|
||||
@GetMapping("/files")
|
||||
@ApiOperation("获取错误日志文件列表")
|
||||
public Map<String, Object> getErrorLogFiles() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
String errorLogDir = errorLogProperties != null ?
|
||||
errorLogProperties.getLogDirectory() : "logs/errors";
|
||||
File errorDir = new File(errorLogDir);
|
||||
List<Map<String, Object>> errorFiles = new ArrayList<>();
|
||||
|
||||
if (errorDir.exists() && errorDir.isDirectory()) {
|
||||
File[] files = errorDir.listFiles((dir, name) -> name.endsWith(".log"));
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
Map<String, Object> fileInfo = new HashMap<>();
|
||||
fileInfo.put("name", file.getName());
|
||||
fileInfo.put("size", file.length());
|
||||
fileInfo.put("lastModified", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(file.lastModified())));
|
||||
fileInfo.put("path", file.getAbsolutePath());
|
||||
errorFiles.add(fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// 按修改时间倒序排列
|
||||
errorFiles.sort((a, b) -> b.get("lastModified").toString().compareTo(a.get("lastModified").toString()));
|
||||
}
|
||||
|
||||
// 同时获取主日志目录的错误相关文件
|
||||
File mainLogDir = new File(MAIN_LOG_DIR);
|
||||
List<Map<String, Object>> mainLogFiles = new ArrayList<>();
|
||||
|
||||
if (mainLogDir.exists() && mainLogDir.isDirectory()) {
|
||||
File[] files = mainLogDir.listFiles((dir, name) ->
|
||||
name.contains("error") || name.contains("startup") || name.contains("warn"));
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
Map<String, Object> fileInfo = new HashMap<>();
|
||||
fileInfo.put("name", file.getName());
|
||||
fileInfo.put("size", file.length());
|
||||
fileInfo.put("lastModified", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(file.lastModified())));
|
||||
fileInfo.put("path", file.getAbsolutePath());
|
||||
mainLogFiles.add(fileInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// 按修改时间倒序排列
|
||||
mainLogFiles.sort((a, b) -> b.get("lastModified").toString().compareTo(a.get("lastModified").toString()));
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
result.put("errorLogFiles", errorFiles);
|
||||
result.put("mainLogFiles", mainLogFiles);
|
||||
result.put("errorLogDir", errorLogDir);
|
||||
result.put("mainLogDir", MAIN_LOG_DIR);
|
||||
result.put("errorLogEnabled", errorLogProperties != null && errorLogProperties.isEnabled());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取错误日志文件列表失败", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "获取日志文件列表失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取指定错误日志文件内容
|
||||
*/
|
||||
@GetMapping("/content")
|
||||
@ApiOperation("读取错误日志文件内容")
|
||||
public Map<String, Object> getErrorLogContent(
|
||||
@ApiParam("文件名") @RequestParam String fileName,
|
||||
@ApiParam("从末尾开始读取的行数,默认100") @RequestParam(defaultValue = "100") int lines) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
// 安全检查:防止路径遍历攻击
|
||||
if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
|
||||
result.put("success", false);
|
||||
result.put("message", "文件名包含非法字符");
|
||||
return result;
|
||||
}
|
||||
|
||||
String errorLogDir = errorLogProperties != null ?
|
||||
errorLogProperties.getLogDirectory() : "logs/errors";
|
||||
|
||||
// 先在错误日志目录查找
|
||||
Path filePath = Paths.get(errorLogDir, fileName);
|
||||
if (!Files.exists(filePath)) {
|
||||
// 如果错误日志目录没有,再在主日志目录查找
|
||||
filePath = Paths.get(MAIN_LOG_DIR, fileName);
|
||||
}
|
||||
|
||||
if (!Files.exists(filePath)) {
|
||||
result.put("success", false);
|
||||
result.put("message", "文件不存在: " + fileName);
|
||||
return result;
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
List<String> allLines = Files.readAllLines(filePath);
|
||||
|
||||
// 获取最后指定行数的内容
|
||||
List<String> resultLines;
|
||||
if (allLines.size() <= lines) {
|
||||
resultLines = allLines;
|
||||
} else {
|
||||
resultLines = allLines.subList(allLines.size() - lines, allLines.size());
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
result.put("fileName", fileName);
|
||||
result.put("content", String.join("\n", resultLines));
|
||||
result.put("totalLines", allLines.size());
|
||||
result.put("displayedLines", resultLines.size());
|
||||
result.put("filePath", filePath.toString());
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("读取错误日志文件失败: {}", fileName, e);
|
||||
result.put("success", false);
|
||||
result.put("message", "读取文件失败: " + e.getMessage());
|
||||
} catch (Exception e) {
|
||||
log.error("处理错误日志请求失败: {}", fileName, e);
|
||||
result.put("success", false);
|
||||
result.put("message", "处理请求失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理旧的错误日志文件
|
||||
*/
|
||||
@DeleteMapping("/cleanup")
|
||||
@ApiOperation("清理旧的错误日志文件")
|
||||
public Map<String, Object> cleanupOldLogs(
|
||||
@ApiParam("保留最近几天的日志,默认7天") @RequestParam(defaultValue = "7") int keepDays) {
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
String errorLogDir = errorLogProperties != null ?
|
||||
errorLogProperties.getLogDirectory() : "logs/errors";
|
||||
long cutoffTime = System.currentTimeMillis() - (keepDays * 24 * 60 * 60 * 1000L);
|
||||
|
||||
File errorDir = new File(errorLogDir);
|
||||
int deletedCount = 0;
|
||||
List<String> deletedFiles = new ArrayList<>();
|
||||
|
||||
if (errorDir.exists() && errorDir.isDirectory()) {
|
||||
File[] files = errorDir.listFiles((dir, name) -> name.endsWith(".log"));
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.lastModified() < cutoffTime) {
|
||||
String fileName = file.getName();
|
||||
if (file.delete()) {
|
||||
deletedCount++;
|
||||
deletedFiles.add(fileName);
|
||||
log.info("删除旧的错误日志文件: {}", fileName);
|
||||
} else {
|
||||
log.warn("删除文件失败: {}", fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
result.put("deletedCount", deletedCount);
|
||||
result.put("deletedFiles", deletedFiles);
|
||||
result.put("keepDays", keepDays);
|
||||
result.put("message", "成功清理 " + deletedCount + " 个旧日志文件");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("清理错误日志文件失败", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "清理失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取系统状态和错误统计
|
||||
*/
|
||||
@GetMapping("/status")
|
||||
@ApiOperation("获取系统错误状态统计")
|
||||
public Map<String, Object> getErrorStatus() {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
|
||||
try {
|
||||
String errorLogDir = errorLogProperties != null ?
|
||||
errorLogProperties.getLogDirectory() : "logs/errors";
|
||||
File errorDir = new File(errorLogDir);
|
||||
Map<String, Integer> errorCounts = new HashMap<>();
|
||||
long totalErrorLogSize = 0;
|
||||
|
||||
if (errorDir.exists() && errorDir.isDirectory()) {
|
||||
File[] files = errorDir.listFiles((dir, name) -> name.endsWith(".log"));
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
totalErrorLogSize += file.length();
|
||||
|
||||
// 根据文件名统计错误类型
|
||||
String fileName = file.getName();
|
||||
if (fileName.contains("startup-error")) {
|
||||
errorCounts.put("启动错误", errorCounts.getOrDefault("启动错误", 0) + 1);
|
||||
} else if (fileName.contains("runtime-error")) {
|
||||
errorCounts.put("运行时错误", errorCounts.getOrDefault("运行时错误", 0) + 1);
|
||||
} else if (fileName.contains("database-error")) {
|
||||
errorCounts.put("数据库错误", errorCounts.getOrDefault("数据库错误", 0) + 1);
|
||||
} else if (fileName.contains("business-error")) {
|
||||
errorCounts.put("业务错误", errorCounts.getOrDefault("业务错误", 0) + 1);
|
||||
} else {
|
||||
errorCounts.put("其他错误", errorCounts.getOrDefault("其他错误", 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.put("success", true);
|
||||
result.put("errorCounts", errorCounts);
|
||||
result.put("totalErrorLogSize", totalErrorLogSize);
|
||||
result.put("errorLogDir", errorLogDir);
|
||||
result.put("errorLogEnabled", errorLogProperties != null && errorLogProperties.isEnabled());
|
||||
result.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("获取错误状态统计失败", e);
|
||||
result.put("success", false);
|
||||
result.put("message", "获取状态失败: " + e.getMessage());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.chinaweal.youfool.devops.base.service.impl;
|
|||
|
||||
import com.chinaweal.youfool.devops.base.service.IMonitorService;
|
||||
import com.chinaweal.youfool.devops.repair.api.RobotApi;
|
||||
import com.chinaweal.youfool.devops.util.ErrorLogUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||
|
|
@ -30,37 +31,59 @@ public class MonitorServiceImpl implements IMonitorService {
|
|||
|
||||
@Override
|
||||
public void countDbPwdExpireAndSendWx() throws Exception {
|
||||
List<String> list = new ArrayList<>();
|
||||
//145数据库
|
||||
list = findExpireUserInfo("jdbc:oracle:thin:@19.130.241.145:1521:FSAMRDATA", "system", "Chinaweal", "19.130.241.145");
|
||||
//147数据库
|
||||
list.addAll(findExpireUserInfo("jdbc:oracle:thin:@19.130.241.147:1521:FSAMRDATA", "system", "Chinaweal_2022", "19.130.241.147"));
|
||||
//152数据库
|
||||
list.addAll(findExpireUserInfo("jdbc:oracle:thin:@19.130.241.152:1521:FSAMRDATA", "system", "Chinaweal", "19.130.241.152"));
|
||||
//顺德133数据库
|
||||
list.addAll(findExpireUserInfo("jdbc:oracle:thin:@19.202.179.133:1521:SDAMRDATA", "system", "ChinaWeal_2020", "19.202.179.133"));
|
||||
try {
|
||||
log.info("开始执行数据库密码过期检查和微信通知");
|
||||
|
||||
List<String> list = new ArrayList<>();
|
||||
//145数据库
|
||||
list = findExpireUserInfo("jdbc:oracle:thin:@19.130.241.145:1521:FSAMRDATA", "system", "Chinaweal", "19.130.241.145");
|
||||
//147数据库
|
||||
list.addAll(findExpireUserInfo("jdbc:oracle:thin:@19.130.241.147:1521:FSAMRDATA", "system", "Chinaweal_2022", "19.130.241.147"));
|
||||
//152数据库
|
||||
list.addAll(findExpireUserInfo("jdbc:oracle:thin:@19.130.241.152:1521:FSAMRDATA", "system", "Chinaweal", "19.130.241.152"));
|
||||
//顺德133数据库
|
||||
list.addAll(findExpireUserInfo("jdbc:oracle:thin:@19.202.179.133:1521:SDAMRDATA", "system", "ChinaWeal_2020", "19.202.179.133"));
|
||||
|
||||
if (list.size() == 0) {
|
||||
//没有账号过期,则不需要提醒
|
||||
return;
|
||||
}
|
||||
log.info("数据库密码过期检查完成,发现 {} 个即将过期的账号", list.size());
|
||||
|
||||
String tip = "数据库账号密码过期提醒,有" + list.size() + "个账号即将过期!\n" +
|
||||
"主机 -> 账号 -> 过期时间\n";
|
||||
for (String s : list) {
|
||||
//拼接账号
|
||||
tip += s;
|
||||
}
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
content.put("content", tip);
|
||||
content.put("mentioned_mobile_list", new String[]{"13827173481"});
|
||||
if (list.size() == 0) {
|
||||
//没有账号过期,则不需要提醒
|
||||
log.info("没有发现即将过期的数据库账号");
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> query = new HashMap<>();
|
||||
query.put("msgtype", "text");
|
||||
query.put("text", content);
|
||||
String tip = "数据库账号密码过期提醒,有" + list.size() + "个账号即将过期!\n" +
|
||||
"主机 -> 账号 -> 过期时间\n";
|
||||
for (String s : list) {
|
||||
//拼接账号
|
||||
tip += s;
|
||||
}
|
||||
Map<String, Object> content = new HashMap<>();
|
||||
content.put("content", tip);
|
||||
content.put("mentioned_mobile_list", new String[]{"13827173481"});
|
||||
|
||||
for (String dbWebhookKey : dbWebhookKeys) {
|
||||
robotApi.webhookSend(query, dbWebhookKey);
|
||||
Map<String, Object> query = new HashMap<>();
|
||||
query.put("msgtype", "text");
|
||||
query.put("text", content);
|
||||
|
||||
log.info("准备发送微信通知,目标webhook数量: {}", dbWebhookKeys.length);
|
||||
|
||||
for (String dbWebhookKey : dbWebhookKeys) {
|
||||
try {
|
||||
robotApi.webhookSend(query, dbWebhookKey);
|
||||
log.debug("微信通知发送成功,webhook: {}", dbWebhookKey);
|
||||
} catch (Exception e) {
|
||||
log.error("微信通知发送失败,webhook: {}", dbWebhookKey, e);
|
||||
ErrorLogUtils.saveBusinessError("监控服务", "微信通知发送", e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("数据库密码过期检查和微信通知执行完成");
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("数据库密码过期检查过程中发生异常", e);
|
||||
ErrorLogUtils.saveBusinessError("监控服务", "数据库密码过期检查", e);
|
||||
throw e; // 重新抛出异常以保持原有的异常处理逻辑
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,187 @@
|
|||
package com.chinaweal.youfool.devops.config;
|
||||
|
||||
import com.chinaweal.youfool.devops.util.ErrorLogUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 数据库健康检查组件
|
||||
* 在应用启动时检查数据库连接状态
|
||||
* 支持配置开关控制
|
||||
*/
|
||||
@Component
|
||||
@ConditionalOnProperty(name = "error-log.database-enabled", havingValue = "true", matchIfMissing = true)
|
||||
@Slf4j
|
||||
public class DatabaseHealthChecker implements ApplicationRunner {
|
||||
|
||||
@Autowired
|
||||
private ErrorLogProperties errorLogProperties;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("devopsDS")
|
||||
private DataSource devopsDataSource;
|
||||
|
||||
@Autowired
|
||||
@Qualifier("youfoolDS")
|
||||
private DataSource youfoolDataSource;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
// 检查是否启用了数据库健康检查
|
||||
if (errorLogProperties == null || !errorLogProperties.isEnabled() || !errorLogProperties.isDatabaseEnabled()) {
|
||||
log.info("数据库健康检查已禁用");
|
||||
return;
|
||||
}
|
||||
|
||||
log.info("开始数据库健康检查...");
|
||||
ErrorLogUtils.logStartupInfo("开始数据库健康检查");
|
||||
|
||||
List<String> checkResults = new ArrayList<>();
|
||||
boolean allHealthy = true;
|
||||
|
||||
// 检查DevOps数据源
|
||||
boolean devopsHealthy = checkDataSource("devopsDS", devopsDataSource);
|
||||
checkResults.add("DevOps数据源: " + (devopsHealthy ? "正常" : "异常"));
|
||||
if (!devopsHealthy) allHealthy = false;
|
||||
|
||||
// 检查YouFool数据源
|
||||
boolean youfoolHealthy = checkDataSource("youfoolDS", youfoolDataSource);
|
||||
checkResults.add("YouFool数据源: " + (youfoolHealthy ? "正常" : "异常"));
|
||||
if (!youfoolHealthy) allHealthy = false;
|
||||
|
||||
// 记录检查结果
|
||||
String resultSummary = "数据库健康检查完成 - " +
|
||||
(allHealthy ? "所有数据源正常" : "存在异常数据源");
|
||||
|
||||
log.info("数据库健康检查结果:");
|
||||
for (String result : checkResults) {
|
||||
log.info(" - {}", result);
|
||||
}
|
||||
|
||||
ErrorLogUtils.logStartupInfo(resultSummary + ": " + String.join(", ", checkResults));
|
||||
|
||||
if (!allHealthy) {
|
||||
log.warn("数据库健康检查发现问题,请检查数据库配置和连接状态");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查单个数据源的健康状态
|
||||
*/
|
||||
private boolean checkDataSource(String dataSourceName, DataSource dataSource) {
|
||||
try {
|
||||
log.debug("检查数据源: {}", dataSourceName);
|
||||
|
||||
try (Connection connection = dataSource.getConnection()) {
|
||||
// 检查连接是否有效
|
||||
if (connection == null || connection.isClosed()) {
|
||||
log.error("数据源 {} 连接无效", dataSourceName);
|
||||
ErrorLogUtils.saveDatabaseError(dataSourceName + "连接无效",
|
||||
new RuntimeException("数据库连接为null或已关闭"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// 执行简单查询测试连接
|
||||
try (PreparedStatement ps = connection.prepareStatement("SELECT 1");
|
||||
ResultSet rs = ps.executeQuery()) {
|
||||
|
||||
if (rs.next() && rs.getInt(1) == 1) {
|
||||
log.debug("数据源 {} 连接测试成功", dataSourceName);
|
||||
|
||||
// 获取数据库基本信息
|
||||
String dbInfo = getDatabaseInfo(connection);
|
||||
log.info("数据源 {} 信息: {}", dataSourceName, dbInfo);
|
||||
ErrorLogUtils.logStartupInfo(dataSourceName + " 连接成功 - " + dbInfo);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
log.error("数据源 {} 查询测试失败", dataSourceName);
|
||||
ErrorLogUtils.saveDatabaseError(dataSourceName + "查询测试失败",
|
||||
new RuntimeException("SELECT 1 查询返回异常结果"));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("数据源 {} 健康检查失败", dataSourceName, e);
|
||||
ErrorLogUtils.saveDatabaseError(dataSourceName + "健康检查失败", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取数据库基本信息
|
||||
*/
|
||||
private String getDatabaseInfo(Connection connection) {
|
||||
try {
|
||||
String dbName = connection.getMetaData().getDatabaseProductName();
|
||||
String dbVersion = connection.getMetaData().getDatabaseProductVersion();
|
||||
String driverName = connection.getMetaData().getDriverName();
|
||||
String driverVersion = connection.getMetaData().getDriverVersion();
|
||||
String url = connection.getMetaData().getURL();
|
||||
String userName = connection.getMetaData().getUserName();
|
||||
|
||||
return String.format("%s %s (Driver: %s %s, URL: %s, User: %s)",
|
||||
dbName, dbVersion, driverName, driverVersion, url, userName);
|
||||
} catch (Exception e) {
|
||||
log.warn("获取数据库信息失败", e);
|
||||
return "无法获取数据库信息";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 手动触发数据库健康检查(供其他组件调用)
|
||||
*/
|
||||
public boolean performHealthCheck() {
|
||||
// 检查是否启用了数据库健康检查
|
||||
if (errorLogProperties == null || !errorLogProperties.isEnabled() || !errorLogProperties.isDatabaseEnabled()) {
|
||||
log.warn("数据库健康检查已禁用,跳过手动检查");
|
||||
return true; // 返回true表示没有问题(因为检查被禁用)
|
||||
}
|
||||
|
||||
log.info("手动触发数据库健康检查");
|
||||
|
||||
boolean devopsHealthy = checkDataSource("devopsDS", devopsDataSource);
|
||||
boolean youfoolHealthy = checkDataSource("youfoolDS", youfoolDataSource);
|
||||
|
||||
boolean allHealthy = devopsHealthy && youfoolHealthy;
|
||||
|
||||
String result = "手动健康检查结果: DevOps=" +
|
||||
(devopsHealthy ? "正常" : "异常") + ", YouFool=" +
|
||||
(youfoolHealthy ? "正常" : "异常");
|
||||
|
||||
log.info(result);
|
||||
ErrorLogUtils.logStartupInfo(result);
|
||||
|
||||
return allHealthy;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取健康检查配置状态
|
||||
*/
|
||||
public Map<String, Object> getHealthCheckStatus() {
|
||||
Map<String, Object> status = new HashMap<>();
|
||||
status.put("enabled", errorLogProperties != null &&
|
||||
errorLogProperties.isEnabled() &&
|
||||
errorLogProperties.isDatabaseEnabled());
|
||||
status.put("errorLogEnabled", errorLogProperties != null && errorLogProperties.isEnabled());
|
||||
status.put("databaseCheckEnabled", errorLogProperties != null && errorLogProperties.isDatabaseEnabled());
|
||||
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
package com.chinaweal.youfool.devops.config;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 错误日志配置属性
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "error-log")
|
||||
@Data
|
||||
public class ErrorLogProperties {
|
||||
|
||||
/**
|
||||
* 是否启用错误日志文件写入功能
|
||||
* 默认启用
|
||||
*/
|
||||
private boolean enabled = true;
|
||||
|
||||
/**
|
||||
* 是否启用启动错误日志
|
||||
* 默认启用
|
||||
*/
|
||||
private boolean startupEnabled = true;
|
||||
|
||||
/**
|
||||
* 是否启用运行时错误日志
|
||||
* 默认启用
|
||||
*/
|
||||
private boolean runtimeEnabled = true;
|
||||
|
||||
/**
|
||||
* 是否启用数据库错误日志
|
||||
* 默认启用
|
||||
*/
|
||||
private boolean databaseEnabled = true;
|
||||
|
||||
/**
|
||||
* 是否启用业务错误日志
|
||||
* 默认启用
|
||||
*/
|
||||
private boolean businessEnabled = true;
|
||||
|
||||
/**
|
||||
* 是否启用启动信息日志
|
||||
* 默认启用
|
||||
*/
|
||||
private boolean startupInfoEnabled = true;
|
||||
|
||||
/**
|
||||
* 错误日志目录
|
||||
* 默认为 logs/errors
|
||||
*/
|
||||
private String logDirectory = "logs/errors";
|
||||
|
||||
/**
|
||||
* 日志文件最大大小
|
||||
* 默认为 10MB
|
||||
*/
|
||||
private String maxFileSize = "10MB";
|
||||
|
||||
/**
|
||||
* 保留日志文件的最大天数
|
||||
* 默认为 30天
|
||||
*/
|
||||
private int maxHistory = 30;
|
||||
|
||||
/**
|
||||
* 是否在控制台同时输出错误信息
|
||||
* 默认启用
|
||||
*/
|
||||
private boolean consoleOutput = true;
|
||||
|
||||
/**
|
||||
* 错误日志的详细级别
|
||||
* 0: 仅错误消息
|
||||
* 1: 错误消息 + 异常类型
|
||||
* 2: 错误消息 + 异常类型 + 简化堆栈
|
||||
* 3: 完整详细信息(默认)
|
||||
*/
|
||||
private int detailLevel = 3;
|
||||
|
||||
/**
|
||||
* 是否异步写入日志文件
|
||||
* 默认启用以提高性能
|
||||
*/
|
||||
private boolean asyncWrite = true;
|
||||
|
||||
/**
|
||||
* 异步写入的队列大小
|
||||
* 默认 1000
|
||||
*/
|
||||
private int asyncQueueSize = 1000;
|
||||
|
||||
/**
|
||||
* 检查是否启用了任何错误日志功能
|
||||
*/
|
||||
public boolean isAnyErrorLogEnabled() {
|
||||
return enabled && (startupEnabled || runtimeEnabled || databaseEnabled || businessEnabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否启用了启动相关日志
|
||||
*/
|
||||
public boolean isStartupLogEnabled() {
|
||||
return enabled && (startupEnabled || startupInfoEnabled);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
package com.chinaweal.youfool.devops.config;
|
||||
|
||||
import com.chinaweal.youfool.devops.util.ErrorLogUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.ControllerAdvice;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.sql.SQLException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局异常处理器
|
||||
* 捕获所有未处理的异常并保存到错误日志
|
||||
* 支持配置开关控制
|
||||
*/
|
||||
@ControllerAdvice
|
||||
@Slf4j
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@Autowired
|
||||
private ErrorLogProperties errorLogProperties;
|
||||
|
||||
/**
|
||||
* 创建标准错误响应
|
||||
*/
|
||||
private Map<String, Object> createErrorResponse(String message, String errorType,
|
||||
HttpServletRequest request,
|
||||
boolean includeErrorLogConfig) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", false);
|
||||
result.put("message", message);
|
||||
result.put("error", errorType);
|
||||
result.put("timestamp", System.currentTimeMillis());
|
||||
result.put("path", request.getRequestURI());
|
||||
|
||||
if (includeErrorLogConfig && errorLogProperties != null) {
|
||||
result.put("errorLogEnabled", errorLogProperties.isEnabled());
|
||||
result.put("errorLogConfig", "配置详情请访问 /api/error-logs/config");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理数据库相关异常
|
||||
*/
|
||||
@ExceptionHandler({SQLException.class, DataAccessException.class})
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> handleDatabaseException(Exception e, HttpServletRequest request) {
|
||||
log.error("数据库异常 - URL: {}", request.getRequestURI(), e);
|
||||
ErrorLogUtils.saveDatabaseError("数据库操作异常", e);
|
||||
|
||||
Map<String, Object> result = createErrorResponse(
|
||||
"数据库操作失败,请联系管理员",
|
||||
"数据库异常",
|
||||
request,
|
||||
true
|
||||
);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理空指针异常
|
||||
*/
|
||||
@ExceptionHandler(NullPointerException.class)
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> handleNullPointerException(NullPointerException e, HttpServletRequest request) {
|
||||
log.error("空指针异常 - URL: {}", request.getRequestURI(), e);
|
||||
ErrorLogUtils.saveRuntimeError("空指针异常", e, "请求URL: " + request.getRequestURI());
|
||||
|
||||
Map<String, Object> result = createErrorResponse(
|
||||
"系统内部错误,请联系管理员",
|
||||
"空指针异常",
|
||||
request,
|
||||
false
|
||||
);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理非法参数异常
|
||||
*/
|
||||
@ExceptionHandler(IllegalArgumentException.class)
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> handleIllegalArgumentException(IllegalArgumentException e, HttpServletRequest request) {
|
||||
log.error("非法参数异常 - URL: {}", request.getRequestURI(), e);
|
||||
ErrorLogUtils.saveRuntimeError("非法参数异常", e, "请求URL: " + request.getRequestURI());
|
||||
|
||||
Map<String, Object> result = createErrorResponse(
|
||||
e.getMessage() != null ? e.getMessage() : "参数错误",
|
||||
"参数异常",
|
||||
request,
|
||||
false
|
||||
);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException e, HttpServletRequest request) {
|
||||
log.error("运行时异常 - URL: {}", request.getRequestURI(), e);
|
||||
ErrorLogUtils.saveRuntimeError("运行时异常", e, "请求URL: " + request.getRequestURI());
|
||||
|
||||
Map<String, Object> result = createErrorResponse(
|
||||
"系统运行异常,请联系管理员",
|
||||
"运行时异常",
|
||||
request,
|
||||
false
|
||||
);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理所有其他异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> handleGenericException(Exception e, HttpServletRequest request) {
|
||||
log.error("未知异常 - URL: {}", request.getRequestURI(), e);
|
||||
ErrorLogUtils.saveRuntimeError("未知异常", e, "请求URL: " + request.getRequestURI());
|
||||
|
||||
Map<String, Object> result = createErrorResponse(
|
||||
"系统异常,请联系管理员",
|
||||
"系统异常",
|
||||
request,
|
||||
false
|
||||
);
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理MyBatis相关异常
|
||||
*/
|
||||
@ExceptionHandler(org.apache.ibatis.exceptions.PersistenceException.class)
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> handleMyBatisException(org.apache.ibatis.exceptions.PersistenceException e, HttpServletRequest request) {
|
||||
log.error("MyBatis异常 - URL: {}", request.getRequestURI(), e);
|
||||
ErrorLogUtils.saveDatabaseError("MyBatis持久化异常", e);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", false);
|
||||
result.put("message", "数据访问异常,请联系管理员");
|
||||
result.put("error", "MyBatis异常");
|
||||
result.put("timestamp", System.currentTimeMillis());
|
||||
result.put("path", request.getRequestURI());
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理反射相关异常(Java模块系统相关)
|
||||
*/
|
||||
@ExceptionHandler(java.lang.reflect.InaccessibleObjectException.class)
|
||||
@ResponseBody
|
||||
public ResponseEntity<Map<String, Object>> handleInaccessibleObjectException(java.lang.reflect.InaccessibleObjectException e, HttpServletRequest request) {
|
||||
log.error("Java模块访问异常 - URL: {}", request.getRequestURI(), e);
|
||||
ErrorLogUtils.saveRuntimeError("Java模块访问异常", e,
|
||||
"请求URL: " + request.getRequestURI() + ", 建议添加JVM参数: --add-opens java.base/java.util=ALL-UNNAMED");
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("success", false);
|
||||
result.put("message", "Java版本兼容性问题,请联系管理员");
|
||||
result.put("error", "模块访问异常");
|
||||
result.put("timestamp", System.currentTimeMillis());
|
||||
result.put("path", request.getRequestURI());
|
||||
result.put("suggestion", "需要添加JVM参数以解决Java模块系统兼容性问题");
|
||||
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(result);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,6 +44,8 @@ public class InterceptorConfig implements WebMvcConfigurer {
|
|||
excludes.add("/statistic/countRepairStepByUserId");
|
||||
excludes.add("/base/taskFile/uploadFile");
|
||||
excludes.add("/base/taskFile/downloadFile");
|
||||
// 错误日志管理接口免认证访问(用于系统监控)
|
||||
excludes.add("/api/error-logs/**");
|
||||
|
||||
registration.excludePathPatterns(excludes);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package com.chinaweal.youfool.devops.config;
|
||||
|
||||
import com.chinaweal.youfool.devops.repair.api.RobotApi;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 本地开发环境RobotApi配置
|
||||
* 当Forest被禁用时提供Mock实现
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(name = "forest.enabled", havingValue = "false", matchIfMissing = true)
|
||||
@Slf4j
|
||||
public class LocalRobotApiConfig {
|
||||
|
||||
@Bean
|
||||
public RobotApi robotApi() {
|
||||
return new RobotApi() {
|
||||
@Override
|
||||
public Map<String, Object> webhookSend(Map<String, Object> query, String webhookKey) {
|
||||
log.info("本地模拟机器人消息发送 - webhookKey: {}, 消息内容: {}", webhookKey, query);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("errcode", 0);
|
||||
result.put("errmsg", "ok");
|
||||
result.put("mock", true);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
package com.chinaweal.youfool.devops.config;
|
||||
|
||||
import io.swagger.v3.oas.models.Components;
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Contact;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.info.License;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springdoc.core.GroupedOpenApi;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* SpringDoc OpenAPI 配置类 (替换SpringFox)
|
||||
* @author itluck
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnProperty(value = "swagger.enable", havingValue = "true")
|
||||
public class SpringDocOpenApiConfig {
|
||||
|
||||
@Value("${applicationName}")
|
||||
private String applicationName;
|
||||
|
||||
@Value("${version:1.0.0}")
|
||||
private String version;
|
||||
|
||||
@Value("${description:DevOps运维管理系统}")
|
||||
private String description;
|
||||
|
||||
@Value("${license:Apache 2.0}")
|
||||
private String license;
|
||||
|
||||
/**
|
||||
* 配置OpenAPI基本信息
|
||||
*/
|
||||
@Bean
|
||||
public OpenAPI customOpenAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title(applicationName)
|
||||
.description(description)
|
||||
.version(version)
|
||||
.contact(new Contact()
|
||||
.name("chinaweal")
|
||||
.url("https://www.chinaweal.com.cn")
|
||||
.email(""))
|
||||
.license(new License()
|
||||
.name(license)
|
||||
.url("https://www.chinaweal.com.cn")))
|
||||
.components(new Components()
|
||||
.addSecuritySchemes("token", new SecurityScheme()
|
||||
.type(SecurityScheme.Type.APIKEY)
|
||||
.in(SecurityScheme.In.HEADER)
|
||||
.name("token")
|
||||
.description("令牌")));
|
||||
}
|
||||
|
||||
/**
|
||||
* 组织架构模块API分组
|
||||
*/
|
||||
@Bean
|
||||
public GroupedOpenApi orgApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("组织架构")
|
||||
.packagesToScan("com.chinaweal.youfool.devops.org")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 运维报障模块API分组
|
||||
*/
|
||||
@Bean
|
||||
public GroupedOpenApi devopsApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("运维报障")
|
||||
.packagesToScan("com.chinaweal.youfool.devops.repair")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 基础环境模块API分组
|
||||
*/
|
||||
@Bean
|
||||
public GroupedOpenApi basisApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("基础环境")
|
||||
.packagesToScan("com.chinaweal.youfool.devops.base")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* WebSocket测试环境API分组
|
||||
*/
|
||||
@Bean
|
||||
public GroupedOpenApi websocketApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("WebSocket测试环境")
|
||||
.packagesToScan("com.chinaweal.youfool.devops.websocket")
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 领导分配模块API分组
|
||||
*/
|
||||
@Bean
|
||||
public GroupedOpenApi leaderAssignApi() {
|
||||
return GroupedOpenApi.builder()
|
||||
.group("领导分配")
|
||||
.packagesToScan("com.chinaweal.youfool.devops.leaderassign")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
package com.chinaweal.youfool.devops.config;
|
||||
|
||||
import com.github.xiaoymin.knife4j.spring.annotations.EnableKnife4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.ApiInfoBuilder;
|
||||
import springfox.documentation.builders.ParameterBuilder;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.schema.ModelRef;
|
||||
import springfox.documentation.service.ApiInfo;
|
||||
import springfox.documentation.service.Contact;
|
||||
import springfox.documentation.service.Parameter;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
import springfox.documentation.swagger2.annotations.EnableSwagger2;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author itluck
|
||||
*/
|
||||
@Configuration
|
||||
@EnableSwagger2
|
||||
@EnableKnife4j
|
||||
@ConditionalOnProperty(value = "swagger.enable", havingValue = "true")
|
||||
public class SwaggerKnife4j {
|
||||
@Value("${applicationName}")
|
||||
private String applicationName;
|
||||
@Value("${version}")
|
||||
private String version;
|
||||
@Value("${description}")
|
||||
private String description;
|
||||
@Value("${license}")
|
||||
private String license;
|
||||
|
||||
@Bean("orgApi")
|
||||
public Docket orgApi() {
|
||||
ParameterBuilder tokenPar = new ParameterBuilder();
|
||||
List<Parameter> headers = new ArrayList<>();
|
||||
tokenPar.name("token").description("令牌").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
||||
headers.add(tokenPar.build());
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.enable(true)
|
||||
.apiInfo(apiInfo())
|
||||
.groupName("组织架构")
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.chinaweal.youfool.devops.org"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.globalOperationParameters(headers);
|
||||
}
|
||||
|
||||
@Bean("devopsApi")
|
||||
public Docket devopsApi() {
|
||||
//添加head参数start
|
||||
ParameterBuilder tokenPar = new ParameterBuilder();
|
||||
List<Parameter> headers = new ArrayList<>();
|
||||
tokenPar.name("token").description("令牌").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
||||
headers.add(tokenPar.build());
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.enable(true)
|
||||
.apiInfo(apiInfo())
|
||||
.groupName("运维报障")
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.chinaweal.youfool.devops.repair"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.globalOperationParameters(headers);
|
||||
}
|
||||
|
||||
@Bean("basisApi")
|
||||
public Docket basisApi() {
|
||||
//添加head参数start
|
||||
ParameterBuilder tokenPar = new ParameterBuilder();
|
||||
List<Parameter> headers = new ArrayList<>();
|
||||
tokenPar.name("token").description("令牌").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
||||
headers.add(tokenPar.build());
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.enable(true)
|
||||
.apiInfo(apiInfo())
|
||||
.groupName("基础环境")
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.chinaweal.youfool.devops.base"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.globalOperationParameters(headers);
|
||||
}
|
||||
|
||||
@Bean("websocketApi")
|
||||
public Docket websocketApi() {
|
||||
//添加head参数start
|
||||
ParameterBuilder tokenPar = new ParameterBuilder();
|
||||
List<Parameter> headers = new ArrayList<>();
|
||||
tokenPar.name("token").description("令牌").modelRef(new ModelRef("string")).parameterType("header").required(false).build();
|
||||
headers.add(tokenPar.build());
|
||||
return new Docket(DocumentationType.SWAGGER_2)
|
||||
.enable(true)
|
||||
.apiInfo(apiInfo())
|
||||
.groupName("WebSocket测试环境")
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.chinaweal.youfool.devops.websocket"))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.globalOperationParameters(headers);
|
||||
}
|
||||
|
||||
|
||||
private ApiInfo apiInfo() {
|
||||
return new ApiInfoBuilder()
|
||||
.title(applicationName)
|
||||
.description(description)
|
||||
.termsOfServiceUrl("https://www.chinaweal.com.cn")
|
||||
.version(version)
|
||||
.contact(new Contact("chinaweal", "https://www.chinaweal.com.cn", ""))
|
||||
.license(license)
|
||||
.licenseUrl("https://www.chinaweal.com.cn")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -26,7 +26,7 @@ import javax.validation.constraints.NotBlank;
|
|||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
@TableName("ENGINEER")
|
||||
@TableName("engineer")
|
||||
@ApiModel(value = "Engineer对象", description = "运维工程师")
|
||||
public class Engineer extends SuperEntity {
|
||||
|
||||
|
|
@ -36,7 +36,7 @@ public class Engineer extends SuperEntity {
|
|||
* 用户ID
|
||||
*/
|
||||
@ApiModelProperty(value = "用户ID")
|
||||
@TableId(value = "USER_ID", type = IdType.ASSIGN_UUID)
|
||||
@TableId(value = "user_id", type = IdType.ASSIGN_UUID)
|
||||
private String userId;
|
||||
|
||||
/**
|
||||
|
|
@ -44,7 +44,7 @@ public class Engineer extends SuperEntity {
|
|||
*/
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
@ApiModelProperty(value = "用户名 登陆账号")
|
||||
@TableField("USERNAME")
|
||||
@TableField("username")
|
||||
private String username;
|
||||
|
||||
/**
|
||||
|
|
@ -52,7 +52,7 @@ public class Engineer extends SuperEntity {
|
|||
*/
|
||||
@NotBlank(message = "密码不能为空")
|
||||
@ApiModelProperty(value = "密码 采用国产SM3")
|
||||
@TableField("PASSWORD")
|
||||
@TableField("password")
|
||||
private String password;
|
||||
|
||||
/**
|
||||
|
|
@ -60,7 +60,7 @@ public class Engineer extends SuperEntity {
|
|||
*/
|
||||
@NotBlank(message = "昵称不能为空")
|
||||
@ApiModelProperty(value = "昵称")
|
||||
@TableField("NICKNAME")
|
||||
@TableField("nickname")
|
||||
private String nickname;
|
||||
|
||||
/**
|
||||
|
|
@ -68,21 +68,21 @@ public class Engineer extends SuperEntity {
|
|||
*/
|
||||
@ApiModelProperty(value = "性别 1:男;2:女;")
|
||||
@NotBlank(message = "性别不能为空")
|
||||
@TableField("SEX")
|
||||
@TableField("sex")
|
||||
private String sex;
|
||||
|
||||
/**
|
||||
* 手机号
|
||||
*/
|
||||
@ApiModelProperty(value = "手机号")
|
||||
@TableField("PHONE")
|
||||
@TableField("phone")
|
||||
private String phone;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
@ApiModelProperty(value = "邮箱")
|
||||
@TableField("EMAIL")
|
||||
@TableField("email")
|
||||
private String email;
|
||||
|
||||
/**
|
||||
|
|
@ -90,35 +90,35 @@ public class Engineer extends SuperEntity {
|
|||
*/
|
||||
@NotBlank(message = "状态不能为空")
|
||||
@ApiModelProperty(value = "状态 1:正常;2:冻结;3:离职")
|
||||
@TableField("STATUS")
|
||||
@TableField("status")
|
||||
private String status;
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@ApiModelProperty(value = "描述")
|
||||
@TableField("DESCRIPTION")
|
||||
@TableField("description")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 排序 越小则优先级更高
|
||||
*/
|
||||
@ApiModelProperty(value = "排序 越小则优先级更高")
|
||||
@TableField("SORT")
|
||||
@TableField("sort")
|
||||
private BigDecimal sort;
|
||||
|
||||
/**
|
||||
* 角色 角色代码集合
|
||||
*/
|
||||
@ApiModelProperty(value = "角色 角色代码集合")
|
||||
@TableField("ROLES")
|
||||
@TableField("roles")
|
||||
private String roles;
|
||||
|
||||
/**
|
||||
* 所负责的系统 集合
|
||||
*/
|
||||
@ApiModelProperty(value = "所负责的系统 集合")
|
||||
@TableField("SOURCE")
|
||||
@TableField("source")
|
||||
private String source;
|
||||
|
||||
/**
|
||||
|
|
@ -126,7 +126,7 @@ public class Engineer extends SuperEntity {
|
|||
*/
|
||||
@ApiModelProperty(value = "是否删除 0:未删除、1:已删除")
|
||||
@TableLogic(value = ConstantsUtil.NOT_DELETED, delval = ConstantsUtil.DELETED)
|
||||
@TableField("IS_DELETED")
|
||||
@TableField("is_deleted")
|
||||
private String isDeleted;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import java.util.List;
|
|||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Accessors(chain = true)
|
||||
@TableName(schema = "DEVOPS", value = "REPAIR_TODO")
|
||||
@TableName(value = "REPAIR_TODO")
|
||||
@ApiModel(value = "RepairTodo对象", description = "运维报障待办")
|
||||
@JsonIgnoreProperties(value = {"handler"})
|
||||
public class RepairTodo extends SuperEntity {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import com.alibaba.excel.write.metadata.fill.FillWrapper;
|
|||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.chinaweal.youfool.devops.base.entity.Dict;
|
||||
import com.chinaweal.youfool.devops.base.service.IDictService;
|
||||
import com.chinaweal.youfool.devops.org.business.entity.BusinessUser;
|
||||
import com.chinaweal.youfool.devops.org.business.service.BusinessUserService;
|
||||
import com.chinaweal.youfool.devops.repair.controller.query.RepairTodoListQuery;
|
||||
import com.chinaweal.youfool.devops.repair.entity.*;
|
||||
|
|
@ -20,7 +20,6 @@ import com.chinaweal.youfool.devops.repair.mapper.RepairMapper;
|
|||
import com.chinaweal.youfool.devops.repair.mapper.RepairTodoMapper;
|
||||
import com.chinaweal.youfool.devops.repair.service.*;
|
||||
import com.chinaweal.youfool.framework.springboot.rest.RestResult;
|
||||
import com.chinaweal.youfool.framework.springboot.rest.ResultCode;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.logging.log4j.util.Strings;
|
||||
|
|
@ -31,6 +30,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
|
@ -71,42 +71,15 @@ public class RepairServiceImpl extends ServiceImpl<RepairMapper, Repair> impleme
|
|||
|
||||
@Override
|
||||
public RestResult<String> saveRepair(Repair repair) {
|
||||
if (StringUtils.isBlank(repair.getSource())) {
|
||||
repair.setSource("1");
|
||||
}
|
||||
|
||||
//判断来源
|
||||
if ("2".equals(repair.getSource())) {
|
||||
repair.setOrgId("特设网办系统");
|
||||
repair.setOrg("特设网办系统");
|
||||
} else if ("3".equals(repair.getSource())) {
|
||||
repair.setOrgId("佛山年报系统");
|
||||
repair.setOrg("佛山年报系统");
|
||||
} else if ("4".equals(repair.getSource())) {
|
||||
repair.setOrgId("佛山行政执法办案平台");
|
||||
repair.setOrg("佛山行政执法办案平台");
|
||||
} else if ("5".equals(repair.getSource())) {
|
||||
repair.setOrgId("佛山消保系统");
|
||||
repair.setOrg("佛山消保系统");
|
||||
} else {
|
||||
//内网发起,查询业务系统的用户信息
|
||||
BusinessUser user;
|
||||
if (StringUtils.isNotBlank(repair.getUserId())) {
|
||||
user = businessUserService.getById(repair.getUserId());
|
||||
} else {
|
||||
user = businessUserService.getByUsername(repair.getUsername());
|
||||
}
|
||||
if (user == null) {
|
||||
return RestResult.error(ResultCode.USCID_INVALID, "找不到申报账号信息");
|
||||
}
|
||||
repair.setUserId(user.getUserId());
|
||||
repair.setUsername(user.getUsername());
|
||||
repair.setDeptId(user.getUnitId());
|
||||
repair.setDept(user.getUnitName());
|
||||
repair.setOrgId(user.getOrgId());
|
||||
repair.setOrg(user.getOrgName());
|
||||
repair.setNickname(user.getNickname());
|
||||
if (StringUtils.isBlank(repair.getPhone())) {
|
||||
repair.setPhone(user.getMobile());
|
||||
}
|
||||
repair.setSource("1");//内网
|
||||
Dict source = iDictService.getByTypeAndCode("source", repair.getSource());
|
||||
if (source != null) {
|
||||
repair.setOrg(source.getName());
|
||||
repair.setOrgId(source.getName());
|
||||
}
|
||||
|
||||
String oldRepairUUID = repair.getRepairId();
|
||||
|
|
@ -277,7 +250,7 @@ public class RepairServiceImpl extends ServiceImpl<RepairMapper, Repair> impleme
|
|||
processRaterDate.put("id", "processRate");
|
||||
processRaterDate.put("name", "故障处理率(已确认)");
|
||||
processRaterDate.put("value", total.compareTo(BigDecimal.valueOf(0)) > 0 ? (resolved.multiply(BigDecimal.valueOf(100))
|
||||
.divide(total, 2, BigDecimal.ROUND_HALF_UP) + "%") : 0);
|
||||
.divide(total, 2, RoundingMode.HALF_UP) + "%") : 0);
|
||||
|
||||
|
||||
//已解决+待确认 故障处理率
|
||||
|
|
@ -285,7 +258,7 @@ public class RepairServiceImpl extends ServiceImpl<RepairMapper, Repair> impleme
|
|||
confirmedProcessRaterDate.put("id", "confirmedProcessRate");
|
||||
confirmedProcessRaterDate.put("name", "故障处理率(已确认+待确认)");
|
||||
confirmedProcessRaterDate.put("value", total.compareTo(BigDecimal.valueOf(0)) > 0 ? (resolved.add(confirmed).multiply(BigDecimal.valueOf(100))
|
||||
.divide(total, 2, BigDecimal.ROUND_HALF_UP) + "%") : 0);
|
||||
.divide(total, 2, RoundingMode.HALF_UP) + "%") : 0);
|
||||
|
||||
|
||||
//处理故障数量
|
||||
|
|
@ -303,7 +276,7 @@ public class RepairServiceImpl extends ServiceImpl<RepairMapper, Repair> impleme
|
|||
oneRate.put("id", "oneSuccessRate");
|
||||
oneRate.put("name", "处理故障1次成功率");
|
||||
oneRate.put("value", handleTotal.compareTo(BigDecimal.valueOf(0)) > 0 ? (oneSuccess.multiply(BigDecimal.valueOf(100))
|
||||
.divide(handleTotal, 2, BigDecimal.ROUND_HALF_UP) + "%") : 0);
|
||||
.divide(handleTotal, 2, RoundingMode.HALF_UP) + "%") : 0);
|
||||
|
||||
|
||||
Map<String, Object> two = new LinkedHashMap<>();
|
||||
|
|
@ -315,7 +288,7 @@ public class RepairServiceImpl extends ServiceImpl<RepairMapper, Repair> impleme
|
|||
twoRate.put("id", "twoSuccessRate");
|
||||
twoRate.put("name", "处理故障2次成功率");
|
||||
twoRate.put("value", handleTotal.compareTo(BigDecimal.valueOf(0)) > 0 ? (twoSuccess.multiply(BigDecimal.valueOf(100))
|
||||
.divide(handleTotal, 2, BigDecimal.ROUND_HALF_UP) + "%") : 0);
|
||||
.divide(handleTotal, 2, RoundingMode.HALF_UP) + "%") : 0);
|
||||
|
||||
|
||||
Map<String, Object> three = new LinkedHashMap<>();
|
||||
|
|
@ -327,23 +300,27 @@ public class RepairServiceImpl extends ServiceImpl<RepairMapper, Repair> impleme
|
|||
threeRate.put("id", "threeSuccessRate");
|
||||
threeRate.put("name", "处理故障3次及以上成功率");
|
||||
threeRate.put("value", handleTotal.compareTo(BigDecimal.valueOf(0)) > 0 ? (threeSuccess.multiply(BigDecimal.valueOf(100))
|
||||
.divide(handleTotal, 2, BigDecimal.ROUND_HALF_UP) + "%") : 0);
|
||||
.divide(handleTotal, 2, RoundingMode.HALF_UP) + "%") : 0);
|
||||
|
||||
Map<String, Object> avgResolvedMap = repairHandleMapper.getAvgResolvedTime(query);
|
||||
Map<String, Object> avgResolvedTime = new LinkedHashMap<>();
|
||||
BigDecimal resolvedHour = (BigDecimal) avgResolvedMap.get("resolvedHour");
|
||||
BigDecimal resolvedNumber = (BigDecimal) avgResolvedMap.get("resolvedNumber");
|
||||
Object resolvedHourObj = avgResolvedMap.get("resolvedHour");
|
||||
Object resolvedNumberObj = avgResolvedMap.get("resolvedNumber");
|
||||
BigDecimal resolvedHour = new BigDecimal(resolvedHourObj != null ? resolvedHourObj.toString() : "0");
|
||||
BigDecimal resolvedNumber = new BigDecimal(resolvedNumberObj != null ? resolvedNumberObj.toString() : "0");
|
||||
avgResolvedTime.put("id", "avgResolvedTime");
|
||||
avgResolvedTime.put("name", "故障平均解决时长");
|
||||
avgResolvedTime.put("value", resolvedNumber.compareTo(BigDecimal.valueOf(0)) > 0 ? (resolvedHour.divide(resolvedNumber, 2, BigDecimal.ROUND_HALF_UP)) + " 小时" : 0);
|
||||
avgResolvedTime.put("value", resolvedNumber.compareTo(BigDecimal.valueOf(0)) > 0 ? (resolvedHour.divide(resolvedNumber, 2, RoundingMode.HALF_UP)) + " 小时" : 0);
|
||||
|
||||
Map<String, Object> avgFeedbackMap = repairHandleMapper.getAvgFeedbackTime(query);
|
||||
Map<String, Object> avgFeedbackTime = new LinkedHashMap<>();
|
||||
BigDecimal feedbackHour = (BigDecimal) avgFeedbackMap.get("feedbackHour");
|
||||
BigDecimal feedbackNumber = (BigDecimal) avgFeedbackMap.get("feedbackNumber");
|
||||
Object feedbackHourObj = avgFeedbackMap.get("feedbackHour");
|
||||
Object feedbackNumberObj = avgFeedbackMap.get("feedbackNumber");
|
||||
BigDecimal feedbackHour = new BigDecimal(feedbackHourObj != null ? feedbackHourObj.toString() : "0");
|
||||
BigDecimal feedbackNumber = new BigDecimal(feedbackNumberObj != null ? feedbackNumberObj.toString() : "0");
|
||||
avgFeedbackTime.put("id", "avgFeedbackTime");
|
||||
avgFeedbackTime.put("name", "故障平均处理时长");
|
||||
avgFeedbackTime.put("value", feedbackNumber.compareTo(BigDecimal.valueOf(0)) > 0 ? (feedbackHour.divide(feedbackNumber, 2, BigDecimal.ROUND_HALF_UP)) + " 小时" : 0);
|
||||
avgFeedbackTime.put("value", feedbackNumber.compareTo(BigDecimal.valueOf(0)) > 0 ? (feedbackHour.divide(feedbackNumber, 2, RoundingMode.HALF_UP)) + " 小时" : 0);
|
||||
|
||||
list.add(totalData);
|
||||
list.add(feedbackData);
|
||||
|
|
@ -375,7 +352,7 @@ public class RepairServiceImpl extends ServiceImpl<RepairMapper, Repair> impleme
|
|||
}
|
||||
for (Map<String, Object> map : labelList) {
|
||||
String name = (String) map.get("name");
|
||||
BigDecimal num = (BigDecimal) map.get("num");
|
||||
BigDecimal num = new BigDecimal(map.get("num").toString());
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("id", labelType + name);
|
||||
data.put("name", name);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import javax.servlet.http.HttpServletResponse;
|
|||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
|
|
@ -51,26 +52,26 @@ public class StatisticServiceImpl {
|
|||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
query.setOrg(org);
|
||||
Map<String, Object> data = repairTodoMapper.getWhole(query);
|
||||
BigDecimal total = (BigDecimal) data.get("total");
|
||||
BigDecimal resolved = (BigDecimal) data.get("resolved");
|
||||
BigDecimal feedback = (BigDecimal) data.get("feedback");
|
||||
BigDecimal unresolved = (BigDecimal) data.get("unresolved");
|
||||
BigDecimal handleAndDeclare = (BigDecimal) data.get("handleAndDeclare");
|
||||
BigDecimal total = new BigDecimal(data.get("total").toString());
|
||||
BigDecimal resolved = new BigDecimal(data.get("resolved").toString());
|
||||
BigDecimal feedback = new BigDecimal(data.get("feedback").toString());
|
||||
BigDecimal unresolved = new BigDecimal(data.get("unresolved").toString());
|
||||
BigDecimal handleAndDeclare = new BigDecimal(data.get("handleAndDeclare").toString());
|
||||
map.put("no", i + 1);
|
||||
map.put("name", org);
|
||||
map.put("total", total);
|
||||
map.put("resolved", resolved);
|
||||
map.put("resolvedRate", total.compareTo(BigDecimal.valueOf(0)) > 0 ? (resolved.multiply(BigDecimal.valueOf(100))
|
||||
.divide(total, 1, BigDecimal.ROUND_HALF_UP) + "%") : 0);
|
||||
.divide(total, 1, RoundingMode.HALF_UP) + "%") : 0);
|
||||
map.put("feedback", feedback);
|
||||
map.put("feedbackRate", total.compareTo(BigDecimal.valueOf(0)) > 0 ? (feedback.multiply(BigDecimal.valueOf(100))
|
||||
.divide(total, 1, BigDecimal.ROUND_HALF_UP) + "%") : 0);
|
||||
.divide(total, 1, RoundingMode.HALF_UP) + "%") : 0);
|
||||
map.put("unresolved", unresolved);
|
||||
map.put("unresolvedRate", total.compareTo(BigDecimal.valueOf(0)) > 0 ? (unresolved.multiply(BigDecimal.valueOf(100))
|
||||
.divide(total, 1, BigDecimal.ROUND_HALF_UP) + "%") : 0);
|
||||
.divide(total, 1, RoundingMode.HALF_UP) + "%") : 0);
|
||||
map.put("handleAndDeclare", handleAndDeclare);
|
||||
map.put("handleAndDeclareRate", total.compareTo(BigDecimal.valueOf(0)) > 0 ? (handleAndDeclare.multiply(BigDecimal.valueOf(100))
|
||||
.divide(total, 1, BigDecimal.ROUND_HALF_UP) + "%") : 0);
|
||||
.divide(total, 1, RoundingMode.HALF_UP) + "%") : 0);
|
||||
list.add(map);
|
||||
}
|
||||
return list;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,395 @@
|
|||
package com.chinaweal.youfool.devops.util;
|
||||
|
||||
import com.chinaweal.youfool.devops.config.ErrorLogProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* 错误日志工具类
|
||||
* 用于统一处理和保存各种错误信息
|
||||
* 支持配置开关控制
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class ErrorLogUtils {
|
||||
|
||||
private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
|
||||
private static final SimpleDateFormat SDF = new SimpleDateFormat(DATE_FORMAT);
|
||||
|
||||
private static ErrorLogProperties errorLogProperties;
|
||||
private static ThreadPoolExecutor asyncExecutor;
|
||||
|
||||
@Autowired
|
||||
public void setErrorLogProperties(ErrorLogProperties properties) {
|
||||
ErrorLogUtils.errorLogProperties = properties;
|
||||
|
||||
// 如果启用异步写入,初始化线程池
|
||||
if (properties.isAsyncWrite()) {
|
||||
initAsyncExecutor();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化异步执行器
|
||||
*/
|
||||
private static void initAsyncExecutor() {
|
||||
if (asyncExecutor == null || asyncExecutor.isShutdown()) {
|
||||
asyncExecutor = new ThreadPoolExecutor(
|
||||
1, 2, 60L, TimeUnit.SECONDS,
|
||||
new ArrayBlockingQueue<>(errorLogProperties.getAsyncQueueSize()),
|
||||
r -> {
|
||||
Thread t = new Thread(r, "ErrorLog-Writer");
|
||||
t.setDaemon(true);
|
||||
return t;
|
||||
},
|
||||
new ThreadPoolExecutor.CallerRunsPolicy()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存启动错误信息
|
||||
*/
|
||||
public static void saveStartupError(String errorType, Throwable throwable) {
|
||||
if (isEnabled() && isStartupErrorEnabled()) {
|
||||
saveError("startup-error", errorType, throwable, null);
|
||||
} else {
|
||||
logToConsoleIfEnabled("启动错误", errorType, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存运行时错误信息
|
||||
*/
|
||||
public static void saveRuntimeError(String errorType, Throwable throwable, String additionalInfo) {
|
||||
if (isEnabled() && isRuntimeErrorEnabled()) {
|
||||
saveError("runtime-error", errorType, throwable, additionalInfo);
|
||||
} else {
|
||||
logToConsoleIfEnabled("运行时错误", errorType, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存数据库错误信息
|
||||
*/
|
||||
public static void saveDatabaseError(String operation, Throwable throwable) {
|
||||
if (isEnabled() && isDatabaseErrorEnabled()) {
|
||||
saveError("database-error", operation, throwable, null);
|
||||
} else {
|
||||
logToConsoleIfEnabled("数据库错误", operation, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存业务错误信息
|
||||
*/
|
||||
public static void saveBusinessError(String module, String operation, Throwable throwable) {
|
||||
if (isEnabled() && isBusinessErrorEnabled()) {
|
||||
saveError("business-error", module + ":" + operation, throwable, null);
|
||||
} else {
|
||||
logToConsoleIfEnabled("业务错误", module + ":" + operation, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录系统启动信息
|
||||
*/
|
||||
public static void logStartupInfo(String message) {
|
||||
if (isEnabled() && isStartupInfoEnabled()) {
|
||||
saveStartupInfoToFile(message);
|
||||
} else {
|
||||
logToConsoleIfEnabled("启动信息", message, null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 配置开关检查方法
|
||||
*/
|
||||
private static boolean isEnabled() {
|
||||
return errorLogProperties != null && errorLogProperties.isEnabled();
|
||||
}
|
||||
|
||||
private static boolean isStartupErrorEnabled() {
|
||||
return errorLogProperties != null && errorLogProperties.isStartupEnabled();
|
||||
}
|
||||
|
||||
private static boolean isRuntimeErrorEnabled() {
|
||||
return errorLogProperties != null && errorLogProperties.isRuntimeEnabled();
|
||||
}
|
||||
|
||||
private static boolean isDatabaseErrorEnabled() {
|
||||
return errorLogProperties != null && errorLogProperties.isDatabaseEnabled();
|
||||
}
|
||||
|
||||
private static boolean isBusinessErrorEnabled() {
|
||||
return errorLogProperties != null && errorLogProperties.isBusinessEnabled();
|
||||
}
|
||||
|
||||
private static boolean isStartupInfoEnabled() {
|
||||
return errorLogProperties != null && errorLogProperties.isStartupInfoEnabled();
|
||||
}
|
||||
|
||||
private static boolean isConsoleOutputEnabled() {
|
||||
return errorLogProperties == null || errorLogProperties.isConsoleOutput();
|
||||
}
|
||||
|
||||
private static String getLogDirectory() {
|
||||
return errorLogProperties != null ? errorLogProperties.getLogDirectory() : "logs/errors";
|
||||
}
|
||||
|
||||
private static int getDetailLevel() {
|
||||
return errorLogProperties != null ? errorLogProperties.getDetailLevel() : 3;
|
||||
}
|
||||
|
||||
/**
|
||||
* 仅输出到控制台(当文件写入被禁用时)
|
||||
*/
|
||||
private static void logToConsoleIfEnabled(String category, String errorType, Throwable throwable) {
|
||||
if (isConsoleOutputEnabled()) {
|
||||
if (throwable != null) {
|
||||
log.error("[{}] {}: {}", category, errorType, throwable.getMessage(), throwable);
|
||||
} else {
|
||||
log.info("[{}] {}", category, errorType);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存启动信息到文件
|
||||
*/
|
||||
private static void saveStartupInfoToFile(String message) {
|
||||
executeLogWrite(() -> {
|
||||
try {
|
||||
File errorDir = new File(getLogDirectory());
|
||||
if (!errorDir.exists()) {
|
||||
errorDir.mkdirs();
|
||||
}
|
||||
|
||||
File startupFile = new File(errorDir, "startup-info.log");
|
||||
|
||||
try (FileWriter fw = new FileWriter(startupFile, true);
|
||||
PrintWriter pw = new PrintWriter(fw)) {
|
||||
|
||||
pw.println(SDF.format(new Date()) + " - " + message);
|
||||
pw.flush();
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("记录启动信息失败", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行日志写入(支持同步/异步)
|
||||
*/
|
||||
private static void executeLogWrite(Runnable writeTask) {
|
||||
if (errorLogProperties != null && errorLogProperties.isAsyncWrite() && asyncExecutor != null) {
|
||||
asyncExecutor.execute(writeTask);
|
||||
} else {
|
||||
writeTask.run();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用错误保存方法
|
||||
*/
|
||||
private static void saveError(String category, String errorType, Throwable throwable, String additionalInfo) {
|
||||
executeLogWrite(() -> saveErrorSync(category, errorType, throwable, additionalInfo));
|
||||
}
|
||||
|
||||
/**
|
||||
* 同步保存错误信息
|
||||
*/
|
||||
private static void saveErrorSync(String category, String errorType, Throwable throwable, String additionalInfo) {
|
||||
try {
|
||||
// 确保错误日志目录存在
|
||||
File errorDir = new File(getLogDirectory());
|
||||
if (!errorDir.exists()) {
|
||||
errorDir.mkdirs();
|
||||
}
|
||||
|
||||
// 创建错误日志文件
|
||||
String fileName = String.format("%s-%s.log", category,
|
||||
new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
|
||||
File errorFile = new File(errorDir, fileName);
|
||||
|
||||
// 写入错误信息
|
||||
try (FileWriter fw = new FileWriter(errorFile, true);
|
||||
PrintWriter pw = new PrintWriter(fw)) {
|
||||
|
||||
writeErrorDetails(pw, category, errorType, throwable, additionalInfo);
|
||||
pw.flush();
|
||||
}
|
||||
|
||||
if (isConsoleOutputEnabled()) {
|
||||
log.error("错误信息已保存到: {}", errorFile.getAbsolutePath());
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("保存错误日志失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据详细级别写入错误信息
|
||||
*/
|
||||
private static void writeErrorDetails(PrintWriter pw, String category, String errorType,
|
||||
Throwable throwable, String additionalInfo) {
|
||||
int detailLevel = getDetailLevel();
|
||||
|
||||
pw.println("==================== 错误记录 ====================");
|
||||
pw.println("时间: " + SDF.format(new Date()));
|
||||
pw.println("类别: " + category);
|
||||
pw.println("类型: " + errorType);
|
||||
|
||||
if (additionalInfo != null && !additionalInfo.isEmpty()) {
|
||||
pw.println("附加信息: " + additionalInfo);
|
||||
}
|
||||
|
||||
if (throwable != null) {
|
||||
if (detailLevel >= 1) {
|
||||
pw.println("异常类型: " + throwable.getClass().getSimpleName());
|
||||
pw.println("错误消息: " + throwable.getMessage());
|
||||
}
|
||||
|
||||
if (detailLevel >= 2) {
|
||||
// 获取根本原因
|
||||
Throwable rootCause = getRootCause(throwable);
|
||||
if (rootCause != throwable) {
|
||||
pw.println("根本原因: " + rootCause.getClass().getSimpleName());
|
||||
pw.println("根本消息: " + rootCause.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
if (detailLevel >= 3) {
|
||||
pw.println("详细堆栈:");
|
||||
throwable.printStackTrace(pw);
|
||||
} else if (detailLevel == 2) {
|
||||
pw.println("简化堆栈:");
|
||||
writeSimplifiedStackTrace(pw, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
pw.println("================================================");
|
||||
pw.println();
|
||||
}
|
||||
|
||||
/**
|
||||
* 写入简化的堆栈跟踪
|
||||
*/
|
||||
private static void writeSimplifiedStackTrace(PrintWriter pw, Throwable throwable) {
|
||||
StackTraceElement[] elements = throwable.getStackTrace();
|
||||
int maxLines = 10; // 限制显示的堆栈行数
|
||||
|
||||
for (int i = 0; i < Math.min(elements.length, maxLines); i++) {
|
||||
StackTraceElement element = elements[i];
|
||||
// 只显示项目相关的堆栈
|
||||
if (element.getClassName().startsWith("com.chinaweal.youfool.devops")) {
|
||||
pw.println(" at " + element.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (elements.length > maxLines) {
|
||||
pw.println(" ... " + (elements.length - maxLines) + " more");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置信息(供外部查询)
|
||||
*/
|
||||
public static String getConfigurationInfo() {
|
||||
if (errorLogProperties == null) {
|
||||
return "错误日志配置未初始化";
|
||||
}
|
||||
|
||||
StringBuilder info = new StringBuilder();
|
||||
info.append("错误日志配置信息:\n");
|
||||
info.append("- 全局开关: ").append(errorLogProperties.isEnabled()).append("\n");
|
||||
info.append("- 启动错误: ").append(errorLogProperties.isStartupEnabled()).append("\n");
|
||||
info.append("- 运行时错误: ").append(errorLogProperties.isRuntimeEnabled()).append("\n");
|
||||
info.append("- 数据库错误: ").append(errorLogProperties.isDatabaseEnabled()).append("\n");
|
||||
info.append("- 业务错误: ").append(errorLogProperties.isBusinessEnabled()).append("\n");
|
||||
info.append("- 启动信息: ").append(errorLogProperties.isStartupInfoEnabled()).append("\n");
|
||||
info.append("- 日志目录: ").append(errorLogProperties.getLogDirectory()).append("\n");
|
||||
info.append("- 详细级别: ").append(errorLogProperties.getDetailLevel()).append("\n");
|
||||
info.append("- 异步写入: ").append(errorLogProperties.isAsyncWrite()).append("\n");
|
||||
info.append("- 控制台输出: ").append(errorLogProperties.isConsoleOutput()).append("\n");
|
||||
|
||||
return info.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭异步执行器(应用关闭时调用)
|
||||
*/
|
||||
public static void shutdown() {
|
||||
if (asyncExecutor != null && !asyncExecutor.isShutdown()) {
|
||||
asyncExecutor.shutdown();
|
||||
try {
|
||||
if (!asyncExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||
asyncExecutor.shutdownNow();
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
asyncExecutor.shutdownNow();
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常的完整堆栈信息
|
||||
*/
|
||||
public static String getStackTrace(Throwable throwable) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw);
|
||||
throwable.printStackTrace(pw);
|
||||
return sw.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化错误信息用于显示
|
||||
*/
|
||||
public static String formatErrorInfo(String errorType, Throwable throwable) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("错误类型: ").append(errorType).append("\n");
|
||||
sb.append("时间: ").append(SDF.format(new Date())).append("\n");
|
||||
|
||||
if (throwable != null) {
|
||||
sb.append("异常: ").append(throwable.getClass().getSimpleName()).append("\n");
|
||||
sb.append("消息: ").append(throwable.getMessage()).append("\n");
|
||||
|
||||
// 获取根本原因
|
||||
Throwable rootCause = getRootCause(throwable);
|
||||
if (rootCause != throwable) {
|
||||
sb.append("根本原因: ").append(rootCause.getClass().getSimpleName()).append("\n");
|
||||
sb.append("根本消息: ").append(rootCause.getMessage()).append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取异常的根本原因
|
||||
*/
|
||||
private static Throwable getRootCause(Throwable throwable) {
|
||||
Throwable cause = throwable;
|
||||
while (cause.getCause() != null && cause.getCause() != cause) {
|
||||
cause = cause.getCause();
|
||||
}
|
||||
return cause;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -14,6 +14,9 @@ spring:
|
|||
url: jdbc:postgresql://172.22.80.157:5432/devops_gd
|
||||
username: devops_gd
|
||||
password: ChinaWeal@2024
|
||||
main:
|
||||
# Spring Boot 2.7+新增:允许循环依赖(临时修复,后续需重构代码消除循环依赖)
|
||||
allow-circular-references: true
|
||||
file:
|
||||
devopsDir: D:\chinaweal\gitea-code\youfool-project\youfool-devops\upload
|
||||
|
||||
|
|
@ -32,3 +35,46 @@ applicationName: devOps
|
|||
|
||||
swagger:
|
||||
enable: true
|
||||
|
||||
# SpringDoc OpenAPI 配置 (替换SpringFox)
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: true
|
||||
path: /v3/api-docs
|
||||
swagger-ui:
|
||||
enabled: true
|
||||
path: /swagger-ui.html
|
||||
operations-sorter: alpha
|
||||
tags-sorter: alpha
|
||||
|
||||
version: "1.0.0"
|
||||
description: "DevOps运维管理系统API文档"
|
||||
license: "Apache 2.0"
|
||||
# 错误日志配置
|
||||
error-log:
|
||||
# 是否启用错误日志文件写入功能
|
||||
enabled: true
|
||||
# 启动错误日志
|
||||
startup-enabled: true
|
||||
# 运行时错误日志
|
||||
runtime-enabled: true
|
||||
# 数据库错误日志
|
||||
database-enabled: true
|
||||
# 业务错误日志
|
||||
business-enabled: true
|
||||
# 启动信息日志
|
||||
startup-info-enabled: true
|
||||
# 日志目录
|
||||
log-directory: logs/errors
|
||||
# 单个日志文件最大大小
|
||||
max-file-size: 10MB
|
||||
# 保留日志文件的最大天数
|
||||
max-history: 30
|
||||
# 是否在控制台同时输出
|
||||
console-output: true
|
||||
# 详细级别 (0-3, 3为最详细)
|
||||
detail-level: 3
|
||||
# 是否异步写入
|
||||
async-write: true
|
||||
# 异步队列大小
|
||||
async-queue-size: 1000
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
server:
|
||||
port: 8080
|
||||
|
||||
logging:
|
||||
level:
|
||||
dao: debug
|
||||
youfool.dao: info
|
||||
com.chinaweal.youfool.framework.springboot.log: debug
|
||||
com.chinaweal.youfool.devops: debug
|
||||
spring:
|
||||
autoconfigure:
|
||||
exclude: com.dtflys.forest.springboot.ForestAutoConfiguration
|
||||
datasource:
|
||||
youfool:
|
||||
url: jdbc:postgresql://localhost:5432/devops_gd
|
||||
username: postgres
|
||||
password: 123456
|
||||
devops:
|
||||
url: jdbc:postgresql://localhost:5432/devops_gd
|
||||
username: postgres
|
||||
password: 123456
|
||||
file:
|
||||
devopsDir: ./upload
|
||||
|
||||
business:
|
||||
fsLoginUrl: http://121.8.152.130:9888/tzrysb/user/loginDevops
|
||||
sdLoginUrl: http://121.8.152.130:9888/tzrysb/user/loginDevops
|
||||
fsUserInfoUrl: http://121.8.152.130:9888/tzrysb/user/searchUserDevops
|
||||
sdUserInfoUrl: http://121.8.152.130:9888/tzrysb/user/searchUserDevops
|
||||
|
||||
# 本地测试用的webhook密钥
|
||||
noticeWebhookKeys: test-webhook-key
|
||||
# 数据账号密码过期机器人
|
||||
dbWebhookKeys: test-db-webhook-key
|
||||
|
||||
applicationName: devOps-Local
|
||||
|
||||
swagger:
|
||||
enable: true
|
||||
|
||||
# 禁用Forest相关配置
|
||||
forest:
|
||||
enabled: false
|
||||
|
||||
# 错误日志配置
|
||||
error-log:
|
||||
# 是否启用错误日志文件写入功能
|
||||
enabled: true
|
||||
# 启动错误日志
|
||||
startup-enabled: true
|
||||
# 运行时错误日志
|
||||
runtime-enabled: true
|
||||
# 数据库错误日志
|
||||
database-enabled: true
|
||||
# 业务错误日志
|
||||
business-enabled: true
|
||||
# 启动信息日志
|
||||
startup-info-enabled: true
|
||||
# 日志目录
|
||||
log-directory: logs/errors
|
||||
# 单个日志文件最大大小
|
||||
max-file-size: 10MB
|
||||
# 保留日志文件的最大天数
|
||||
max-history: 30
|
||||
# 是否在控制台同时输出
|
||||
console-output: true
|
||||
# 详细级别 (0-3, 3为最详细)
|
||||
detail-level: 3
|
||||
# 是否异步写入
|
||||
async-write: true
|
||||
# 异步队列大小
|
||||
async-queue-size: 1000
|
||||
|
|
@ -17,7 +17,7 @@ spring:
|
|||
password: ChinaWeal@2024
|
||||
|
||||
file:
|
||||
devopsDir: ..\webapps\upload
|
||||
devopsDir: D:\project\devops-gd\upload
|
||||
|
||||
business:
|
||||
fsLoginUrl: http://121.8.152.130:9888/tzrysb/user/loginDevops
|
||||
|
|
@ -32,3 +32,10 @@ dbWebhookKeys: 45d1bda2-c0b9-45c3-b640-be77f7a0726d,2eacc126-74d2-4360-a88e-369c
|
|||
|
||||
swagger:
|
||||
enable: false
|
||||
|
||||
# SpringDoc OpenAPI 配置 (生产环境禁用)
|
||||
springdoc:
|
||||
api-docs:
|
||||
enabled: false
|
||||
swagger-ui:
|
||||
enabled: false
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ logging:
|
|||
root: info
|
||||
spring:
|
||||
profiles:
|
||||
active: prod
|
||||
active: dev
|
||||
application:
|
||||
name: youfoo-devops
|
||||
datasource:
|
||||
|
|
@ -17,7 +17,7 @@ spring:
|
|||
max-wait: 30000
|
||||
time-between-eviction-runs-millis: 60000
|
||||
min-evictable-idle-time-millis: 300000
|
||||
validation-query: select 1 from dual
|
||||
validation-query: select 1
|
||||
devops:
|
||||
filters: stat
|
||||
initial-size: 2
|
||||
|
|
@ -26,7 +26,7 @@ spring:
|
|||
max-wait: 30000
|
||||
time-between-eviction-runs-millis: 60000
|
||||
min-evictable-idle-time-millis: 300000
|
||||
validation-query: select 1 from dual
|
||||
validation-query: select 1
|
||||
druid:
|
||||
web-stat-filter:
|
||||
enabled: true
|
||||
|
|
@ -79,4 +79,23 @@ forest:
|
|||
log-response-content: true # 打开/关闭Forest响应内容日志(默认为 false)
|
||||
|
||||
#数据库密码监控账号列表
|
||||
dbUsernames: AICSCR,AICORG,SPEEQU,ZKRUSER,GOLDENGATE,DEVOPS,SYSTEM,SPEE_OS,DWDDATA,DIMDATA,AICBLACK,CRUSER,ZSJUSER,FJYUSER,CXAICSCR,XIAOBAO,FSREPORT
|
||||
dbUsernames: AICSCR,AICORG,SPEEQU,ZKRUSER,GOLDENGATE,DEVOPS,SYSTEM,SPEE_OS,DWDDATA,DIMDATA,AICBLACK,CRUSER,ZSJUSER,FJYUSER,CXAICSCR,XIAOBAO,FSREPORT
|
||||
|
||||
# 错误日志默认配置
|
||||
error-log:
|
||||
# 全局开关,默认启用
|
||||
enabled: false
|
||||
# 各类型日志开关
|
||||
startup-enabled: true
|
||||
runtime-enabled: true
|
||||
database-enabled: true
|
||||
business-enabled: true
|
||||
startup-info-enabled: true
|
||||
# 基础配置
|
||||
log-directory: logs/errors
|
||||
max-file-size: 5MB
|
||||
max-history: 7
|
||||
console-output: true
|
||||
detail-level: 2
|
||||
async-write: true
|
||||
async-queue-size: 500
|
||||
|
|
@ -96,8 +96,22 @@
|
|||
</filter>
|
||||
</appender>
|
||||
|
||||
<!-- 启动错误专用日志 -->
|
||||
<appender name="STARTUP_ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<encoder>
|
||||
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %level %logger{36} - %msg%n</pattern>
|
||||
</encoder>
|
||||
<file>${logFilePath}/startup-error.log</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
|
||||
<fileNamePattern>${logFilePath}/startup-error.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
|
||||
<maxFileSize>10MB</maxFileSize>
|
||||
<maxHistory>7</maxHistory>
|
||||
</rollingPolicy>
|
||||
</appender>
|
||||
|
||||
<root>
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<!-- 生产环境启用所有文件日志 -->
|
||||
<springProfile name="prod">
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="FILE_DEBUG"/>
|
||||
|
|
@ -105,7 +119,20 @@
|
|||
<appender-ref ref="FILE_WARN"/>
|
||||
<appender-ref ref="FILE_ERROR"/>
|
||||
</springProfile>
|
||||
|
||||
<!-- 本地环境启用错误和警告日志 -->
|
||||
<springProfile name="local">
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="FILE_WARN"/>
|
||||
<appender-ref ref="FILE_ERROR"/>
|
||||
</springProfile>
|
||||
<!-- 开发环境启用调试日志 -->
|
||||
<springProfile name="dev">
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="FILE_DEBUG"/>
|
||||
<appender-ref ref="FILE_INFO"/>
|
||||
<appender-ref ref="FILE_WARN"/>
|
||||
<appender-ref ref="FILE_ERROR"/>
|
||||
</springProfile>
|
||||
</root>
|
||||
|
||||
<logger name="com.baomidou.mybatisplus.generator" level="debug"/>
|
||||
|
|
|
|||
|
|
@ -73,7 +73,7 @@
|
|||
and count( case when rh.step = 'feedback' then 1 else null end ) >2
|
||||
and count( case when rh.step = 'unresolved' then 1 else null end ) > 0
|
||||
</if>
|
||||
)
|
||||
) a
|
||||
</select>
|
||||
|
||||
<select id="listResultSecond"
|
||||
|
|
|
|||
Loading…
Reference in New Issue