This commit is contained in:
zlwu committed 2025-08-05 14:00:23 +08:00
1 parent 109577a578
commit 4cf21648f5
10 files changed
+900 -11

No files matched your search

@@ -21,14 +21,15 @@ public class DbInitializer implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
try {
logger.info("开始执行数据库升级脚本...");
// 吴自龙 注释了
// logger.info("开始执行数据库升级脚本...");
// 执行升级脚本
sqlScriptRunner.executeScript("mapper/rs/update-rs-application-table.sql");
logger.info("数据库升级脚本执行完成");
// sqlScriptRunner.executeScript("mapper/rs/update-rs-application-table.sql");
// logger.info("数据库升级脚本执行完成");
} catch (Exception e) {
logger.error("执行数据库升级脚本失败", e);
// 这里选择记录错误但不中断应用启动,因为可能是表结构已经更新过
// 如果需要严格控制,可以抛出异常中断应用启动
}
}
}
}
@@ -98,7 +98,8 @@ public class XgStudentLeaveServiceImpl implements IXgStudentLeaveService {
private LambdaQueryWrapper<XgStudentLeave> buildQueryWrapper(XgStudentLeaveBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<XgStudentLeave> lqw = Wrappers.lambdaQuery();
lqw.orderByAsc(XgStudentLeave::getId);
// lqw.orderByAsc(XgStudentLeave::getId);
lqw.orderByDesc(XgStudentLeave::getCreateTime);
lqw.eq(bo.getDeleted() != null, XgStudentLeave::getDeleted, bo.getDeleted());
lqw.eq(bo.getStudentId() != null, XgStudentLeave::getStudentId, bo.getStudentId());
lqw.like(StringUtils.isNotBlank(bo.getStudentName()), XgStudentLeave::getStudentName, bo.getStudentName());
@@ -112,8 +113,8 @@ public class XgStudentLeaveServiceImpl implements IXgStudentLeaveService {
lqw.eq(StringUtils.isNotBlank(bo.getLeaveTypeCode()), XgStudentLeave::getLeaveTypeCode, bo.getLeaveTypeCode());
lqw.like(StringUtils.isNotBlank(bo.getLeaveTypeName()), XgStudentLeave::getLeaveTypeName,
bo.getLeaveTypeName());
lqw.eq(bo.getStartTime() != null, XgStudentLeave::getStartTime, bo.getStartTime());
lqw.eq(bo.getEndTime() != null, XgStudentLeave::getEndTime, bo.getEndTime());
lqw.ge(bo.getStartTime() != null, XgStudentLeave::getStartTime, bo.getStartTime());
lqw.le(bo.getEndTime() != null, XgStudentLeave::getEndTime, bo.getEndTime());
lqw.eq(bo.getLeaveDuration() != null, XgStudentLeave::getLeaveDuration, bo.getLeaveDuration());
lqw.eq(StringUtils.isNotBlank(bo.getIsLeaveSchool()), XgStudentLeave::getIsLeaveSchool, bo.getIsLeaveSchool());
lqw.eq(StringUtils.isNotBlank(bo.getNeedCancel()), XgStudentLeave::getNeedCancel, bo.getNeedCancel());
@@ -0,0 +1,250 @@
# 学院请假审批人查询API接口实现变更记录
**变更日期**: 2025-01-27
**变更人员**: AI Assistant
**变更类型**: 功能新增
## 📋 变更概述
在学生信息管理模块中新增了一个API接口,用于根据学生ID查询所在学院的请假审批人列表。
## 🎯 业务需求
- 接口入参:studentId(学生ID)
- 业务逻辑:
1. 通过xg_student_msg.id关联入参studentId学生id,deleted = 0,获取到college_id
2. 从sys_user表中,找到college_id对应的组织及子组织下,具备xsqj-ejxyspr这个角色的人员list
3. 查询条件:
- sys_dept是组织表:del_flag = 0, status = 0
- sys_user是用户表:del_flag = 0, status = 0, account_type = 1
- sys_user_role是用户与角色的关联表
- sys_role是角色表:role_key = 'xsqj-ejxyspr'
- 返回结果:请假审批人用户ID列表
## 🔧 技术实现
### 1. Mapper接口扩展
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java`
```java
/**
* 根据学生ID查询学院ID
*
* @param studentId 学生ID
* @return 学院ID
*/
Long selectCollegeIdByStudentId(@Param("studentId") Long studentId);
/**
* 根据学院ID和角色Key查询具备该角色的用户列表
*
* @param collegeId 学院ID
* @param roleKey 角色Key
* @return 用户ID列表
*/
List<Long> selectUserIdsByCollegeIdAndRoleKey(@Param("collegeId") Long collegeId, @Param("roleKey") String roleKey);
```
### 2. Mapper XML SQL实现
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml`
```xml
<!-- 根据学生ID查询学院ID -->
<select id="selectCollegeIdByStudentId" resultType="java.lang.Long">
SELECT college_id
FROM xg_student_msg
WHERE id = #{studentId}
AND deleted = 0
</select>
<!-- 根据学院ID和角色Key查询具备该角色的用户列表 -->
<select id="selectUserIdsByCollegeIdAndRoleKey" resultType="java.lang.Long">
SELECT DISTINCT u.user_id
FROM sys_user u
INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
INNER JOIN sys_role r ON ur.role_id = r.role_id
INNER JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.del_flag = '0'
AND u.status = '0'
AND u.account_type = '1'
AND d.del_flag = '0'
AND d.status = '0'
AND r.role_key = #{roleKey}
AND (d.dept_id = #{collegeId} OR FIND_IN_SET(#{collegeId}, d.ancestors))
</select>
```
### 3. Service接口扩展
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java`
```java
/**
* 根据学生ID获取学院请假审批人列表
*
* @param studentId 学生ID
* @return 请假审批人用户ID列表
*/
List<Long> getCollegeLeaveApproversByStudentId(Long studentId);
```
### 4. Service实现类扩展
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java`
```java
/**
* 根据学生ID获取学院请假审批人列表
*
* @param studentId 学生ID
* @return 请假审批人用户ID列表
*/
@Override
public List<Long> getCollegeLeaveApproversByStudentId(Long studentId) {
log.info("根据学生ID获取学院请假审批人列表 - 学生ID: {}", studentId);
// 1. 根据学生ID查询学院ID
Long collegeId = baseMapper.selectCollegeIdByStudentId(studentId);
if (collegeId == null) {
log.warn("未找到学生对应的学院信息 - 学生ID: {}", studentId);
return new ArrayList<>();
}
log.info("学生所在学院ID: {}", collegeId);
// 2. 根据学院ID和角色Key查询具备请假审批角色的用户列表
List<Long> approverUserIds = baseMapper.selectUserIdsByCollegeIdAndRoleKey(collegeId, "xsqj-ejxyspr");
log.info("找到的请假审批人数量: {}", approverUserIds.size());
return approverUserIds;
}
```
### 5. Controller API接口
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java`
```java
/**
* 根据学生ID获取学院请假审批人列表
*
* @param studentId 学生ID
* @return 请假审批人用户ID列表
*/
@SaCheckPermission("studentMsg:studentMsg:query")
@GetMapping("/getCollegeLeaveApprovers/{studentId}")
public R<List<Long>> getCollegeLeaveApprovers(@NotNull(message = "学生ID不能为空") @PathVariable Long studentId) {
log.info("根据学生ID获取学院请假审批人列表 - 学生ID: {}", studentId);
List<Long> approverUserIds = xgStudentMsgService.getCollegeLeaveApproversByStudentId(studentId);
if (approverUserIds.isEmpty()) {
return R.fail("未找到该学生所在学院的请假审批人");
}
return R.ok(approverUserIds);
}
```
## 📡 API接口详情
### 接口信息
- **请求路径**: `GET /studentMsg/studentMsg/getCollegeLeaveApprovers/{studentId}`
- **权限要求**: `studentMsg:studentMsg:query`
- **入参**: studentId (路径参数,Long类型)
- **返回**: R<List<Long>> 统一响应格式
### 请求示例
```
GET /studentMsg/studentMsg/getCollegeLeaveApprovers/123456
```
### 响应示例
**成功响应**:
```json
{
"code": 200,
"msg": "操作成功",
"data": [789012, 789013, 789014]
}
```
**失败响应**:
```json
{
"code": 500,
"msg": "未找到该学生所在学院的请假审批人",
"data": null
}
```
## 🔍 数据库查询逻辑
### 第一步:查询学生所在学院ID
```sql
SELECT college_id
FROM xg_student_msg
WHERE id = #{studentId}
AND deleted = 0
```
### 第二步:查询学院及子组织下具备请假审批角色的用户
```sql
SELECT DISTINCT u.user_id
FROM sys_user u
INNER JOIN sys_user_role ur ON u.user_id = ur.user_id
INNER JOIN sys_role r ON ur.role_id = r.role_id
INNER JOIN sys_dept d ON u.dept_id = d.dept_id
WHERE u.del_flag = '0'
AND u.status = '0'
AND u.account_type = '1'
AND d.del_flag = '0'
AND d.status = '0'
AND r.role_key = 'xsqj-ejxyspr'
AND (d.dept_id = #{collegeId} OR FIND_IN_SET(#{collegeId}, d.ancestors))
```
## 📊 表关联关系
1. **xg_student_msg** (学生信息表) → college_id
2. **sys_dept** (部门表) → dept_id, ancestors (祖级列表)
3. **sys_user** (用户表) → dept_id, user_id
4. **sys_user_role** (用户角色关联表) → user_id, role_id
5. **sys_role** (角色表) → role_id, role_key
## ✅ 实现特点
1. **数据完整性**: 确保只查询未删除的学生、用户、部门和角色信息
2. **权限控制**: 使用@SaCheckPermission注解进行权限验证
3. **参数验证**: 使用@NotNull注解验证入参
4. **日志记录**: 添加详细的日志记录便于调试
5. **错误处理**: 当未找到审批人时返回友好的错误提示
6. **统一响应**: 使用R<List<Long>>统一响应格式
7. **组织层级**: 支持查询学院及其子组织下的审批人
8. **角色过滤**: 精确匹配角色Key为'xsqj-ejxyspr'的用户
## 🧪 测试建议
1. **正常场景测试**:
- 使用存在的学生ID测试
- 验证返回的审批人列表
2. **异常场景测试**:
- 使用不存在的学生ID
- 使用已删除的学生ID
- 使用空值或无效值
- 测试学院下没有审批人的情况
3. **权限测试**: 验证权限控制是否生效
4. **数据完整性测试**: 验证查询条件是否正确过滤
## 📝 相关文档
- 学生信息管理API文档: `docs/开发文档/API/学生信息管理API.md`
- 学生信息表结构: `docs/设计文档/database/学工管理-学生信息管理新版/xg_student_msg 学生信息表.sql`
- 系统用户表结构: `docs/设计文档/database/学工管理-学生信息管理/05-system-management-module.sql`
## 🔧 技术要点
1. **FIND_IN_SET函数**: 用于查询ancestors字段中是否包含指定的学院ID
2. **DISTINCT去重**: 避免重复的用户ID
3. **多表关联**: 通过INNER JOIN关联用户、角色、部门表
4. **条件过滤**: 严格按照业务要求过滤有效数据
---
**变更完成时间**: 2025-01-27
**变更状态**: ✅ 已完成
**下一步**: 进行功能测试和API文档更新
@@ -0,0 +1,169 @@
# 学生班主任ID查询API接口实现变更记录
**变更日期**: 2025-01-27
**变更人员**: AI Assistant
**变更类型**: 功能新增
## 📋 变更概述
在学生信息管理模块中新增了一个API接口,用于根据学生ID查询对应的班主任ID。
## 🎯 业务需求
- 接口入参:studentId(学生ID)
- 业务逻辑:通过xg_student_msg的id关联入参studentId,deleted = 0,获取class_id。然后通过xg_class_msg的.id关联class_id,deleted = 0获取class_teacher_id
- 返回结果:班主任ID
## 🔧 技术实现
### 1. Mapper接口扩展
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/mapper/XgStudentMsgMapper.java`
```java
/**
* 根据学生ID查询班主任ID
*
* @param studentId 学生ID
* @return 班主任ID
*/
Long selectClassTeacherIdByStudentId(@Param("studentId") Long studentId);
```
### 2. Mapper XML SQL实现
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/resources/mapper/studentMsg/XgStudentMsgMapper.xml`
```xml
<!-- 根据学生ID查询班主任ID -->
<select id="selectClassTeacherIdByStudentId" resultType="java.lang.Long">
SELECT c.class_teacher_id
FROM xg_student_msg s
INNER JOIN xg_class_msg c ON s.class_id = c.id
WHERE s.id = #{studentId}
AND s.deleted = 0
AND c.deleted = 0
</select>
```
### 3. Service接口扩展
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/IXgStudentMsgService.java`
```java
/**
* 根据学生ID查询班主任ID
*
* @param studentId 学生ID
* @return 班主任ID
*/
Long getClassTeacherIdByStudentId(Long studentId);
```
### 4. Service实现类扩展
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgStudentMsgServiceImpl.java`
```java
/**
* 根据学生ID查询班主任ID
*
* @param studentId 学生ID
* @return 班主任ID
*/
@Override
public Long getClassTeacherIdByStudentId(Long studentId) {
log.info("根据学生ID查询班主任ID - 学生ID: {}", studentId);
return baseMapper.selectClassTeacherIdByStudentId(studentId);
}
```
### 5. Controller API接口
**文件**: `backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/controller/XgStudentMsgController.java`
```java
/**
* 根据学生ID获取班主任ID
*
* @param studentId 学生ID
* @return 班主任ID
*/
@SaCheckPermission("studentMsg:studentMsg:query")
@GetMapping("/getClassTeacherId/{studentId}")
public R<Long> getClassTeacherId(@NotNull(message = "学生ID不能为空") @PathVariable Long studentId) {
log.info("根据学生ID获取班主任ID - 学生ID: {}", studentId);
Long classTeacherId = xgStudentMsgService.getClassTeacherIdByStudentId(studentId);
if (classTeacherId == null) {
return R.fail("未找到该学生对应的班主任信息");
}
return R.ok(classTeacherId);
}
```
## 📡 API接口详情
### 接口信息
- **请求路径**: `GET /studentMsg/studentMsg/getClassTeacherId/{studentId}`
- **权限要求**: `studentMsg:studentMsg:query`
- **入参**: studentId (路径参数,Long类型)
- **返回**: R<Long> 统一响应格式
### 请求示例
```
GET /studentMsg/studentMsg/getClassTeacherId/123456
```
### 响应示例
**成功响应**:
```json
{
"code": 200,
"msg": "操作成功",
"data": 789012
}
```
**失败响应**:
```json
{
"code": 500,
"msg": "未找到该学生对应的班主任信息",
"data": null
}
```
## 🔍 数据库查询逻辑
1. **表关联**: xg_student_msg (学生信息表) INNER JOIN xg_class_msg (班级信息表)
2. **关联条件**: s.class_id = c.id
3. **查询条件**:
- s.id = #{studentId} (学生ID)
- s.deleted = 0 (学生信息未删除)
- c.deleted = 0 (班级信息未删除)
4. **返回字段**: c.class_teacher_id (班主任ID)
## ✅ 实现特点
1. **数据完整性**: 确保只查询未删除的学生和班级信息
2. **权限控制**: 使用@SaCheckPermission注解进行权限验证
3. **参数验证**: 使用@NotNull注解验证入参
4. **日志记录**: 添加详细的日志记录便于调试
5. **错误处理**: 当未找到班主任信息时返回友好的错误提示
6. **统一响应**: 使用R<Long>统一响应格式
## 🧪 测试建议
1. **正常场景测试**: 使用存在的学生ID测试
2. **异常场景测试**:
- 使用不存在的学生ID
- 使用已删除的学生ID
- 使用空值或无效值
3. **权限测试**: 验证权限控制是否生效
## 📝 相关文档
- 学生信息管理API文档: `docs/开发文档/API/学生信息管理API.md`
- 班级信息表结构: `docs/设计文档/database/学工管理-学生信息管理新版/xg_class_msg 班级信息表.sql`
- 学生信息表结构: `docs/设计文档/database/学工管理-学生信息管理新版/xg_student_msg 学生信息表.sql`
---
**变更完成时间**: 2025-01-27
**变更状态**: ✅ 已完成
**下一步**: 进行功能测试和API文档更新
@@ -0,0 +1,156 @@
# 学生请假审批状态字典配置变更记录
**日期**: 2025-01-27
**操作人员**: AI Assistant
**模块**: 学工管理 - 学生请假管理 - 审批状态显示优化
## 📋 变更概述
### 🎯 **变更目标**
将学生请假管理页面中的审批状态列从直接显示状态值改为使用字典标签显示,提升用户体验。
### 📊 **变更内容**
- **新增字典类型**: `student_leave_approval_status` (学生请假审批状态)
- **新增字典数据**: 7个审批状态映射
- **前端优化**: 审批状态列使用 `dict-tag` 组件显示
---
## 🔧 技术实现
### **1. 数据库字典配置**
#### **字典类型创建**
```sql
INSERT INTO sys_dict_type(dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark)
VALUES (nextval('sys_dict_type_seq'), '000000', '学生请假审批状态', 'student_leave_approval_status', 1, now(), '学生请假申请审批状态字典');
```
#### **字典数据配置**
| 排序 | 标签 | 值 | 样式类 | 说明 |
|------|------|----|--------|------|
| 1 | 已撤销 | cancel | info | 已撤销状态 |
| 2 | 草稿 | draft | info | 草稿状态 |
| 3 | 待审核 | waiting | warning | 待审核状态 |
| 4 | 已完成 | finish | success | 已完成状态 |
| 5 | 已作废 | invalid | danger | 已作废状态 |
| 6 | 已退回 | back | warning | 已退回状态 |
| 7 | 已终止 | termination | danger | 已终止状态 |
### **2. 前端代码修改**
#### **文件**: `web/src/views/xg/xsjcxx/xsqj/index.vue`
**修改前**:
```vue
<el-table-column label="审批状态" align="center" prop="approvalStatus" />
```
**修改后**:
```vue
<el-table-column label="审批状态" align="center" prop="approvalStatus">
<template #default="scope">
<dict-tag :options="student_leave_approval_status" :value="scope.row.approvalStatus" />
</template>
</el-table-column>
```
**字典数据加载**:
```typescript
// 加载字典数据
const { sys_yesno, student_leave_approval_status } = toRefs<any>(proxy?.useDict('sys_yesno', 'student_leave_approval_status'));
```
---
## 📁 新增文件
### **1. SQL脚本文件**
- **文件路径**: `script/sql/add-student-leave-approval-status-dict.sql`
- **功能**: 创建学生请假审批状态字典配置
- **内容**: 包含字典类型创建、字典数据插入和验证查询
- **文件路径**: `script/sql/supplement-student-leave-approval-status-dict.sql`
- **功能**: 补充缺失的学生请假审批状态字典数据
- **内容**: 由于ID冲突导致部分数据未插入,使用更大ID补充完整数据
---
## 🔄 变更影响
### **✅ 正面影响**
1. **用户体验提升**: 审批状态显示更加直观,使用中文标签替代代码值
2. **界面一致性**: 与其他模块的审批状态显示保持一致
3. **维护性增强**: 通过字典配置统一管理状态显示
4. **扩展性**: 新增状态只需在字典中配置,无需修改代码
### **⚠️ 注意事项**
1. **数据兼容性**: 需要确保数据库中的 `approval_status` 字段值与字典值匹配
2. **缓存刷新**: 字典配置更新后需要刷新前端缓存
3. **权限控制**: 确保用户有访问字典数据的权限
---
## 🧪 验证步骤
### **1. 数据库验证**
```sql
-- 验证字典类型
SELECT dict_name, dict_type FROM sys_dict_type WHERE dict_type = 'student_leave_approval_status';
-- 验证字典数据
SELECT dict_sort, dict_label, dict_value, list_class
FROM sys_dict_data
WHERE dict_type = 'student_leave_approval_status'
ORDER BY dict_sort;
```
### **2. 前端验证**
1. 访问学生请假管理页面
2. 检查审批状态列是否正确显示中文标签
3. 验证不同状态的颜色样式是否正确
4. 确认字典数据加载正常
### **3. 功能验证**
1. 创建新的请假申请,验证状态显示
2. 修改请假状态,验证显示更新
3. 搜索和筛选功能正常
---
## 📚 相关文档
### **参考文档**
- **字典配置规范**: `.cursor/cursorrules-backend.md` → "字典数据管理规范"
- **前端开发规范**: `.cursor/cursorrules-frontend.md` → "组件使用规范"
- **API开发规范**: `docs/开发文档/API/API文档基础规范.md`
### **相关模块**
- **财务项目申报**: 已实现类似的审批状态字典配置
- **工作流管理**: 使用统一的审批状态规范
---
## 🎯 后续优化建议
### **1. 数据迁移**
- 检查现有请假记录的审批状态值
- 如有需要,进行数据格式统一化处理
### **2. 功能扩展**
- 考虑添加状态变更历史记录
- 实现状态变更通知功能
### **3. 性能优化**
- 字典数据缓存优化
- 前端组件渲染性能优化
---
**📝 变更完成时间**: 2025-01-27
**✅ 变更状态**: 已完成
**🔗 相关文件**:
- `script/sql/add-student-leave-approval-status-dict.sql`
- `script/sql/supplement-student-leave-approval-status-dict.sql`
- `web/src/views/xg/xsjcxx/xsqj/index.vue`
- `docs/变更管理/code-adjust-record-2025-01-27-student-leave-approval-status-dict.md`
@@ -0,0 +1,126 @@
# 数据库连接池优化变更记录
**变更日期**: 2025-08-04 18:30:00
**变更人员**: Cursor AI
**变更类型**: 性能优化
**影响范围**: 后端服务启动日志
## 🔍 问题描述
启动日志中持续打印 `SELECT 1` 查询,每10秒左右出现一次,消耗时间约19-22ms,影响日志可读性和系统性能。
## 📊 问题分析
### 根本原因
1. **HikariCP连接池心跳检测**: `SELECT 1` 是数据库连接池的心跳检测查询
2. **配置过于频繁**: `keepaliveTime: 30000` (30秒) 检测间隔过短
3. **空闲连接过多**: `minIdle: 15` 保持15个空闲连接
4. **日志级别过细**: SQL日志级别设置为DEBUG,记录所有查询详情
### 影响分析
- 日志文件快速增长
- 控制台输出混乱
- 系统资源浪费
- 影响开发调试体验
## 🛠️ 解决方案
### 1. 优化连接池配置
**文件**: `backend/ruoyi-admin/src/main/resources/application-dev.yml`
```yaml
hikari:
# 最大连接池数量(从30减少到20)
maxPoolSize: 20
# 最小空闲线程数量(从15减少到5)
minIdle: 5
# 空闲连接存活最大时间(从3分钟增加到5分钟)
idleTimeout: 300000
# 连接最长生命周期(从10分钟增加到15分钟)
maxLifetime: 900000
# 心跳检测间隔(从30秒增加到60秒)
keepaliveTime: 60000
# 连接测试查询(保持不变)
connectionTestQuery: SELECT 1
```
### 2. 调整日志级别
**文件**: `backend/ruoyi-admin/src/main/resources/application.yml`
```yaml
logging:
level:
# 关闭SQL日志调试,减少SELECT 1心跳检测日志
org.dromara.cw.mapper.CwApprovalRecordMapper: info
org.dromara.cw.service.impl.CwApprovalRecordServiceImpl: info
org.dromara.cw.listener.ApprovalProcessEventListener: info
```
**文件**: `backend/ruoyi-admin/src/main/resources/logback-plus.xml`
```xml
<!-- 设置MyBatis的日志级别为INFO,减少SELECT 1心跳检测日志 -->
<logger name="org.dromara.xg.mapper" level="INFO">
<logger name="org.dromara.xg.mapper.XgSysUserMapper" level="INFO">
<logger name="org.apache.ibatis" level="INFO">
<logger name="java.sql" level="INFO">
<logger name="java.sql.Connection" level="INFO">
<logger name="java.sql.Statement" level="INFO">
<logger name="java.sql.PreparedStatement" level="INFO">
```
## 📈 优化效果
### 预期改善
1. **日志输出减少**: 心跳检测日志频率降低50%
2. **资源消耗降低**: 空闲连接数减少67%
3. **性能提升**: 连接池管理开销减少
4. **日志清晰**: 减少无关的心跳检测日志干扰
### 配置对比
| 配置项 | 优化前 | 优化后 | 改善效果 |
|--------|--------|--------|----------|
| maxPoolSize | 30 | 20 | 减少33%连接数 |
| minIdle | 15 | 5 | 减少67%空闲连接 |
| keepaliveTime | 30秒 | 60秒 | 减少50%检测频率 |
| idleTimeout | 3分钟 | 5分钟 | 延长连接生命周期 |
| maxLifetime | 10分钟 | 15分钟 | 延长连接生命周期 |
| SQL日志级别 | DEBUG | INFO | 减少详细日志输出 |
## 🔄 重启要求
**需要重启服务**: ✅ 是
**重启命令**:
```bash
# 停止当前服务
# 重新启动后端服务
cd backend
./start-all.sh
```
## 📋 验证方法
1. **日志检查**: 重启后观察控制台日志,`SELECT 1` 出现频率应明显降低
2. **性能监控**: 检查数据库连接数和系统资源使用情况
3. **功能测试**: 确保所有业务功能正常运行
## ⚠️ 注意事项
1. **监控连接池**: 如果出现连接不足,可以适当增加 `maxPoolSize`
2. **日志调试**: 如需调试SQL,可临时将日志级别改回DEBUG
3. **生产环境**: 生产环境建议进一步优化连接池配置
## 📚 相关文档
- [HikariCP连接池配置指南](https://github.com/brettwooldridge/HikariCP)
- [Spring Boot数据库配置最佳实践](https://spring.io/guides/gs/accessing-data-mysql/)
- [项目数据库规范](docs/设计文档/database/mysql-database-standard.md)
---
**变更状态**: ✅ 已完成
**测试状态**: ⏳ 待验证
**文档状态**: ✅ 已更新
@@ -0,0 +1,96 @@
# 工作流状态同步问题修复记录
**变更时间**: 2025-08-04 19:30:00
**变更人**: 吴自龙
**变更类型**: Bug修复
**影响范围**: 工作流引擎状态管理
## 问题描述
在流程运行完成后,发现 `flow_instance` 表中的 `flow_status` 字段出现了状态不一致的问题:
1. 第一次更新:`flow_status` 被正确设置为 `finish`
2. 第二次更新:`flow_status` 又被错误地覆盖为 `waiting`
## 问题分析
### 根本原因
在 `WorkflowGlobalListener.determineFlowStatus()` 方法中:
1. 当流程到达结束节点时,系统检查没有待办任务后,调用 `instanceService.updateStatus()` 将数据库中的状态更新为 `finish`
2. 但是在后续的 `insService.updateById(instance)` 调用中,内存中的 `instance` 对象状态仍然是原始的 `waiting`,导致状态被覆盖
### 影响范围
- 流程状态显示不正确
- 可能导致流程状态判断错误
- 影响流程监控和统计
## 修复方案
### 修复方法
采用第二种方案:在 `determineFlowStatus()` 方法中,不仅更新数据库状态,同时更新内存中的实例状态。
### 修改内容
**文件**: `backend/ruoyi-modules/ruoyi-workflow/src/main/java/org/dromara/workflow/listener/WorkflowGlobalListener.java`
**修改位置**: `determineFlowStatus()` 方法
**修改前**:
```java
if (CollUtil.isEmpty(flowTasks)) {
String status = BusinessStatusEnum.FINISH.getStatus();
// 更新流程状态为已完成
instanceService.updateStatus(instanceId, status);
log.info("流程已结束,状态更新为: {}", status);
return status;
}
```
**修改后**:
```java
if (CollUtil.isEmpty(flowTasks)) {
String status = BusinessStatusEnum.FINISH.getStatus();
// 更新流程状态为已完成
instanceService.updateStatus(instanceId, status);
// 同时更新内存中的实例状态,避免后续insService.updateById时状态被覆盖
// 修复人:吴自龙 - 解决流程结束时状态不一致问题
instance.setFlowStatus(status);
log.info("流程已结束,状态更新为: {}", status);
return status;
}
```
## 修复效果
### 预期效果
1. **状态一致性**: 确保数据库和内存中的流程状态保持一致
2. **避免覆盖**: 防止后续的 `insService.updateById()` 调用覆盖正确的状态
3. **流程完整性**: 保证流程结束状态的正确性
### 验证方法
1. 启动一个完整的流程实例
2. 完成所有审批节点
3. 检查 `flow_instance` 表的 `flow_status` 字段是否正确设置为 `finish`
4. 确认日志中不再出现状态被覆盖的情况
## 技术要点
### 关键概念
- **状态同步**: 数据库状态与内存对象状态的一致性
- **流程生命周期**: 流程从开始到结束的完整状态管理
- **事件监听**: 工作流引擎的事件驱动机制
### 最佳实践
1. **状态更新原则**: 同时更新数据库和内存对象
2. **日志记录**: 详细记录状态变更过程
3. **注释规范**: 明确标注修复原因和责任人
## 相关文档
- [Warm-Flow工作流开发最佳实践总结](../开发文档/Warm-Flow工作流开发最佳实践总结.md)
- [工作流开发规范](../.cursor/cursorrules-workflow.md)
## 变更记录
| 时间 | 变更人 | 变更内容 | 备注 |
|------|--------|----------|------|
| 2025-08-04 19:30:00 | 吴自龙 | 修复工作流状态同步问题 | 解决流程结束时状态被覆盖的Bug |
@@ -0,0 +1,55 @@
-- 学生请假审批状态字典配置脚本
-- 日期: 2025-01-27
-- 说明: 创建学生请假审批状态字典类型和数据
-- 1. 创建字典类型
INSERT IGNORE INTO sys_dict_type(dict_id, tenant_id, dict_name, dict_type, create_by, create_time, remark)
VALUES (2000000000000000001, '000000', '学生请假审批状态', 'student_leave_approval_status', 1, now(), '学生请假申请审批状态字典');
-- 2. 创建字典数据
-- 已撤销
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001001, '000000', 1, '已撤销', 'cancel', 'student_leave_approval_status', '', 'info', 'N', 1, now(), '已撤销');
-- 草稿
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001002, '000000', 2, '草稿', 'draft', 'student_leave_approval_status', '', 'info', 'N', 1, now(), '草稿状态');
-- 待审核
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001003, '000000', 3, '待审核', 'waiting', 'student_leave_approval_status', '', 'warning', 'N', 1, now(), '待审核状态');
-- 已完成
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001004, '000000', 4, '已完成', 'finish', 'student_leave_approval_status', '', 'success', 'N', 1, now(), '已完成状态');
-- 已作废
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001005, '000000', 5, '已作废', 'invalid', 'student_leave_approval_status', '', 'danger', 'N', 1, now(), '已作废状态');
-- 已退回
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001006, '000000', 6, '已退回', 'back', 'student_leave_approval_status', '', 'warning', 'N', 1, now(), '已退回状态');
-- 已终止
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000001007, '000000', 7, '已终止', 'termination', 'student_leave_approval_status', '', 'danger', 'N', 1, now(), '已终止状态');
-- 3. 验证创建结果
SELECT
'字典类型' as check_item,
dict_name,
dict_type,
create_time
FROM sys_dict_type
WHERE dict_type = 'student_leave_approval_status';
SELECT
'字典数据' as check_item,
dict_sort,
dict_label,
dict_value,
list_class
FROM sys_dict_data
WHERE dict_type = 'student_leave_approval_status'
ORDER BY dict_sort;
@@ -0,0 +1,31 @@
-- 补充学生请假审批状态字典数据脚本
-- 日期: 2025-01-27
-- 说明: 补充缺失的学生请假审批状态字典数据
-- 补充缺失的字典数据
-- 已撤销
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000009032, '000000', 1, '已撤销', 'cancel', 'student_leave_approval_status', '', 'info', 'N', 1, now(), '已撤销');
-- 草稿
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000009033, '000000', 2, '草稿', 'draft', 'student_leave_approval_status', '', 'info', 'N', 1, now(), '草稿状态');
-- 待审核
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000009034, '000000', 3, '待审核', 'waiting', 'student_leave_approval_status', '', 'warning', 'N', 1, now(), '待审核状态');
-- 已完成
INSERT IGNORE INTO sys_dict_data(dict_code, tenant_id, dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, create_by, create_time, remark)
VALUES (2000000000000009035, '000000', 4, '已完成', 'finish', 'student_leave_approval_status', '', 'success', 'N', 1, now(), '已完成状态');
-- 验证补充结果
SELECT
'补充后完整字典数据' as check_item,
dict_sort,
dict_label,
dict_value,
list_class
FROM sys_dict_data
WHERE dict_type = 'student_leave_approval_status'
ORDER BY dict_sort;
+8 -4
View File
@@ -211,8 +211,12 @@
<el-table-column label="请假原因" align="center" prop="leaveReason" />
<!-- <el-table-column label="请假时地理位置" align="center" prop="leaveLocation" /> -->
<el-table-column label="附件OSS文件ID" align="center" prop="attachmentOssIds" width="280" />
<!-- <el-table-column label="审批状态" align="center" prop="approvalStatus" />
<el-table-column label="审批意见" align="center" prop="approvalOpinion" />
<el-table-column label="审批状态" align="center" prop="approvalStatus">
<template #default="scope">
<dict-tag :options="student_leave_approval_status" :value="scope.row.approvalStatus" />
</template>
</el-table-column>
<!--<el-table-column label="审批意见" align="center" prop="approvalOpinion" />
<el-table-column label="审批时间" align="center" prop="approvalTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.approvalTime, '{y}-{m}-{d}') }}</span>
@@ -278,7 +282,7 @@ interface PageData<T, Q> {
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 加载字典数据
const { sys_yesno } = toRefs<any>(proxy?.useDict('sys_yesno'));
const { sys_yesno, student_leave_approval_status } = toRefs<any>(proxy?.useDict('sys_yesno', 'student_leave_approval_status'));
const studentLeaveList = ref<StudentLeaveVO[]>([]);
const buttonLoading = ref(false);
@@ -384,7 +388,7 @@ const data = reactive<PageData<StudentLeaveForm, StudentLeaveQuery>>({
isLeaveSchool: [{ required: true, message: '是否离校:0-否,1-是不能为空', trigger: 'blur' }],
needCancel: [{ required: true, message: '是否需要销假:0-否,1-是不能为空', trigger: 'blur' }],
leaveReason: [{ required: true, message: '请假原因不能为空', trigger: 'blur' }],
approvalStatus: [{ required: true, message: '审批状态:0-待审批,1-已通过,2-已拒绝,3-已取消不能为空', trigger: 'change' }],
approvalStatus: [{ required: true, message: '审批状态不能为空', trigger: 'change' }],
cancelStatus: [{ required: true, message: '销假状态:0-未销假,1-已销假,2-逾期未销假不能为空', trigger: 'change' }]
}
});