21 KiB
宿舍卫生检查管理API文档
概述
宿舍卫生检查管理模块提供完整的宿舍卫生检查功能,包括检查记录管理和学生个人评分管理。该模块支持同时操作两个数据表:xg_dorm_hygiene_check(主表)和 xg_dorm_hygiene_student_score(学生评分表)。
数据库表结构
主表:xg_dorm_hygiene_check
- 存储宿舍卫生检查的基本信息
- 包含检查标题、日期、检查员、宿舍信息、总分、等级等
子表:xg_dorm_hygiene_student_score
- 存储每个学生的个人评分信息
- 关联主表,包含学生信息、个人得分、出勤状态、评级等
扣分明细表:xg_dorm_hygiene_check_deduction(2026-06-13 新增)
- 存储宿舍整体扣分的多条明细
- 关联主表
hygiene_check_id,每条记录对应一个违纪类型及扣分值 - 主表
total_score为明细合计;discipline_type_name为多项名称用「、」拼接(兼容旧列表/导出)
deductionItems 字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| disciplineTypeId | Long | 违纪类型ID |
| disciplineTypeName | String | 违纪类型名称 |
| score | Number | 该项扣分,默认取类型 minScore,可在 min-max 范围内手动调整 |
| sortOrder | Integer | 排序序号 |
业务规则:
- 扣分项可选(允许无扣分项,totalScore=0)
- 每项默认分数为违纪类型的
minScore - 单项分数必须在对应类型的 min-max 范围内
- 合计扣分上限 100 分
- 历史单条数据迁移:以主表
total_score作为唯一明细的 score
API接口规范
1. 新增宿舍卫生检查(包含学生评分)
接口地址: POST /dormHygieneCheck/dormHygieneCheck/addWithStudentScores
请求参数:
{
"checkTitle": "2025年1月宿舍卫生检查",
"checkDate": "2025-01-28 09:00:00",
"checkerId": 1,
"checkerName": "张检查员",
"checkType": "1",
"checkLevel": "1",
"roomId": 1,
"roomCode": "A101",
"roomName": "A栋101室",
"buildingId": 1,
"buildingName": "A栋宿舍楼",
"areaId": 1,
"areaName": "东区",
"campusId": 1,
"campusName": "主校区",
"totalScore": 85.5,
"deductionItems": [
{
"disciplineTypeId": 1963793291531026434,
"disciplineTypeName": "地面未打扫",
"score": 5,
"sortOrder": 1
},
{
"disciplineTypeId": 1963793291531026435,
"disciplineTypeName": "物品摆放凌乱",
"score": 3,
"sortOrder": 2
}
],
"hygieneGrade": "B",
"attachmentIds": "oss1,oss2,oss3",
"checkSummary": "宿舍整体卫生良好",
"improvementSuggestions": "建议加强个人物品整理",
"checkRemarks": "检查顺利完成",
"studentData": [
{
"studentId": 1,
"studentName": "张三",
"studentNo": "2021001",
"className": "计算机科学1班",
"majorName": "计算机科学与技术",
"collegeName": "信息工程学院",
"bedId": 1,
"bedCode": "A101-1",
"studentScore": 85.5,
"attendanceStatus": "1",
"studentGrade": "B",
"isResponsiblePerson": 0,
"scoreRemarks": "个人卫生良好",
"improvementNotes": "建议整理桌面物品"
}
]
}
实现要求:
- 事务处理:使用数据库事务确保两个表的操作原子性
- 主表插入:插入宿舍卫生检查记录,获取生成的主键ID
- 子表插入:为每个学生插入评分记录,关联主表ID
- 数据验证:验证必填字段、数据格式、分数范围等
- 权限控制:检查用户权限,记录操作日志
响应格式:
{
"code": 200,
"msg": "新增成功",
"data": {
"id": 1,
"checkTitle": "2025年1月宿舍卫生检查",
"studentCount": 4
}
}
2. 更新宿舍卫生检查(包含学生评分)
接口地址: PUT /dormHygieneCheck/dormHygieneCheck/updateWithStudentScores
请求参数: 与新增接口相同,但必须包含 id 字段
实现要求:
- 事务处理:使用数据库事务确保操作的原子性
- 主表更新:更新宿舍卫生检查记录
- 子表处理:
- 将原有学生评分记录的
deleted字段设置为 1(逻辑删除) - 插入新的学生评分记录
- 将原有学生评分记录的
- 数据验证:验证记录存在性、权限等
- 历史保留:保留历史数据,便于追溯
响应格式:
{
"code": 200,
"msg": "更新成功",
"data": {
"id": 1,
"checkTitle": "2025年1月宿舍卫生检查(更新)",
"studentCount": 4,
"updatedTime": "2025-01-28 10:30:00"
}
}
后端实现示例
Controller层
@RestController
@RequestMapping("/dormHygieneCheck/dormHygieneCheck")
public class DormHygieneCheckController {
@Autowired
private DormHygieneCheckService dormHygieneCheckService;
/**
* 新增宿舍卫生检查(包含学生评分)
*/
@PostMapping("/addWithStudentScores")
@SaCheckPermission("dormHygieneCheck:dormHygieneCheck:add")
public R<Void> addWithStudentScores(@RequestBody @Valid DormHygieneCheckCompleteForm form) {
dormHygieneCheckService.addWithStudentScores(form);
return R.ok();
}
/**
* 更新宿舍卫生检查(包含学生评分)
*/
@PutMapping("/updateWithStudentScores")
@SaCheckPermission("dormHygieneCheck:dormHygieneCheck:edit")
public R<Void> updateWithStudentScores(@RequestBody @Valid DormHygieneCheckCompleteForm form) {
dormHygieneCheckService.updateWithStudentScores(form);
return R.ok();
}
}
Service层
@Service
public class DormHygieneCheckServiceImpl implements DormHygieneCheckService {
@Autowired
private DormHygieneCheckMapper dormHygieneCheckMapper;
@Autowired
private DormHygieneStudentScoreMapper studentScoreMapper;
/**
* 新增宿舍卫生检查(包含学生评分)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void addWithStudentScores(DormHygieneCheckCompleteForm form) {
// 1. 验证数据
validateFormData(form);
// 2. 插入主表
DormHygieneCheck entity = convertToEntity(form);
dormHygieneCheckMapper.insert(entity);
// 3. 插入学生评分数据
if (form.getStudentData() != null && !form.getStudentData().isEmpty()) {
for (DormHygieneStudentScoreForm studentForm : form.getStudentData()) {
DormHygieneStudentScore studentEntity = convertToStudentEntity(studentForm);
studentEntity.setHygieneCheckId(entity.getId());
studentScoreMapper.insert(studentEntity);
}
}
}
/**
* 更新宿舍卫生检查(包含学生评分)
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void updateWithStudentScores(DormHygieneCheckCompleteForm form) {
// 1. 验证数据
validateFormData(form);
validateRecordExists(form.getId());
// 2. 更新主表
DormHygieneCheck entity = convertToEntity(form);
dormHygieneCheckMapper.updateById(entity);
// 3. 逻辑删除原有学生评分记录
studentScoreMapper.logicDeleteByCheckId(entity.getId());
// 4. 插入新的学生评分数据
if (form.getStudentData() != null && !form.getStudentData().isEmpty()) {
for (DormHygieneStudentScoreForm studentForm : form.getStudentData()) {
DormHygieneStudentScore studentEntity = convertToStudentEntity(studentForm);
studentEntity.setHygieneCheckId(entity.getId());
studentScoreMapper.insert(studentEntity);
}
}
}
/**
* 验证表单数据
*/
private void validateFormData(DormHygieneCheckCompleteForm form) {
// 验证必填字段
if (StringUtils.isBlank(form.getCheckTitle())) {
throw new ServiceException("检查标题不能为空");
}
if (form.getRoomId() == null) {
throw new ServiceException("房间ID不能为空");
}
// 验证分数范围
if (form.getTotalScore() != null) {
if (form.getTotalScore() < 0 || form.getTotalScore() > 100) {
throw new ServiceException("宿舍总分必须在0-100分之间");
}
}
// 验证学生数据
if (form.getStudentData() != null) {
for (DormHygieneStudentScoreForm student : form.getStudentData()) {
if (student.getStudentScore() != null) {
if (student.getStudentScore() < 0 || student.getStudentScore() > 100) {
throw new ServiceException("学生个人得分必须在0-100分之间");
}
}
}
}
}
/**
* 验证记录是否存在
*/
private void validateRecordExists(Long id) {
DormHygieneCheck existing = dormHygieneCheckMapper.selectById(id);
if (existing == null) {
throw new ServiceException("记录不存在");
}
}
}
Mapper层
@Mapper
public interface DormHygieneStudentScoreMapper extends BaseMapperPlus<DormHygieneStudentScore, DormHygieneStudentScoreVO> {
/**
* 根据检查ID逻辑删除学生评分记录
*/
@Update("UPDATE xg_dorm_hygiene_student_score SET deleted = 1, update_time = NOW(), update_by = #{updateBy} WHERE hygiene_check_id = #{checkId} AND deleted = 0")
int logicDeleteByCheckId(@Param("checkId") Long checkId, @Param("updateBy") Long updateBy);
/**
* 根据检查ID查询学生评分列表
*/
@Select("SELECT * FROM xg_dorm_hygiene_student_score WHERE hygiene_check_id = #{checkId} AND deleted = 0")
List<DormHygieneStudentScore> selectByCheckId(@Param("checkId") Long checkId);
}
数据验证规则
主表验证
checkTitle: 必填,长度1-200字符checkDate: 必填,日期时间格式checkerId: 必填,关联用户表roomId: 必填,关联房间表totalScore: 可选,0-100分(由 deductionItems 合计自动计算,只读)deductionItems: 可选,扣分明细数组;每项 score 默认 minScore,可在类型 min-max 内调整;合计不超过100hygieneGrade: 可选,A/B/C/D
学生评分表验证
studentId: 必填,关联学生表studentScore: 可选,0-100分attendanceStatus: 必填,1/2/3/4studentGrade: 可选,A/B/C/D
错误处理
常见错误码
400: 参数验证失败401: 权限不足404: 记录不存在500: 服务器内部错误
错误响应格式
{
"code": 400,
"msg": "检查标题不能为空",
"data": null
}
注意事项
- 事务一致性:确保两个表的操作在同一事务中完成
- 数据完整性:更新时保留历史数据,便于追溯
- 性能优化:批量操作时考虑性能影响
- 权限控制:严格检查用户权限
- 日志记录:记录重要操作日志
- 数据验证:前端和后端都要进行数据验证
测试用例
新增测试
- 正常新增:提供完整有效数据
- 数据验证:测试必填字段、格式验证
- 权限测试:测试不同用户权限
- 异常测试:测试网络异常、数据库异常
更新测试
- 正常更新:更新现有记录
- 记录不存在:更新不存在的记录
- 学生数据变更:测试学生数据的增删改
- 历史数据:验证历史数据保留
3. 删除宿舍卫生检查(四表级联删除)
接口地址: DELETE /dormHygieneCheck/dormHygieneCheck/{ids}
接口描述: 删除宿舍卫生检查记录,自动级联删除四个相关表的数据
涉及表结构:
xg_dorm_hygiene_check- 宿舍卫生检查主表xg_dorm_hygiene_student_score- 学生卫生评分表xg_hygiene_check_violation- 卫生检查违纪关联表xskh_discipline_record- 学生违纪处分记录表(统一违纪表)
请求参数:
ids: 待删除的检查记录ID,支持单个或多个,用逗号分隔
请求示例:
DELETE /dormHygieneCheck/dormHygieneCheck/1
DELETE /dormHygieneCheck/dormHygieneCheck/1,2,3
删除逻辑顺序:
- 查询关联违纪记录 - 根据检查ID查询
xg_hygiene_check_violation表 - 删除统一违纪记录 - 逻辑删除
xskh_discipline_record表中的违纪处分记录 - 删除违纪关联记录 - 逻辑删除
xg_hygiene_check_violation表中的关联记录 - 删除学生评分记录 - 逻辑删除
xg_dorm_hygiene_student_score表中的评分记录 - 删除主检查记录 - 逻辑删除
xg_dorm_hygiene_check主表记录
技术实现要点:
- ✅ 事务管理: 使用
@Transactional确保四表删除的原子性 - ✅ 逻辑删除: 所有表都使用逻辑删除(
deleted = 1),不物理删除数据 - ✅ 跨模块调用: 通过反射动态调用学生考核管理模块的删除方法
- ✅ 异常处理: 优雅处理学生考核管理模块未启用的情况
- ✅ 日志记录: 详细记录每个步骤的删除过程和结果
响应格式:
{
"code": 200,
"msg": "删除成功",
"data": null
}
错误响应:
{
"code": 500,
"msg": "删除宿舍卫生检查记录失败: 具体错误信息",
"data": null
}
权限要求:
- 需要
dormHygieneCheck:dormHygieneCheck:remove权限
注意事项:
- 数据完整性: 删除操作会级联删除所有相关数据,请谨慎操作
- 跨模块依赖: 需要确保学生考核管理模块(xskh)正常运行
- 事务回滚: 任何一个表删除失败都会导致整个操作回滚
- 日志追踪: 所有删除操作都有详细日志记录,便于问题排查
删除测试
- 正常删除: 删除存在关联数据的检查记录
- 级联验证: 验证四个表的数据都已正确删除
- 事务测试: 测试删除过程中的异常回滚
- 权限测试: 测试不同用户的删除权限
- 跨模块测试: 测试学生考核管理模块未启用时的降级处理
文档版本: v1.0
创建时间: 2025-01-28
维护人员: 系统开发团队
修复记录
2025-01-29 界面问题修复
修复问题一:新增时个人扣分默认值显示
问题现象: 点击新增按钮时,弹窗中房间学生信息列表的个人扣分列显示默认0分placeholder,而不是直接填入0。
解决方案: 修改DormHygieneCheckDialog.vue中loadRoomStudents方法,将新增模式下学生的student_score默认值从null改为0。
// 修改前
student_score: student.student_score || null, // 个人得分,默认为空
// 修改后
student_score: 0, // 个人得分,新增时默认为0
修复问题二:宿舍扣分变为0时个人扣分不同步更新
问题现象: 当宿舍扣分从1改成0时,房间学生信息列表中个人扣分还是1,没有变成0。
解决方案: 修改handleTotalScoreChange方法,添加对0分的特别处理逻辑。
// 修改后的逻辑
const handleTotalScoreChange = () => {
// 验证宿舍总分范围
if (form.totalScore !== null && form.totalScore !== undefined && form.totalScore !== '') {
// 处理非0分情况
const score = parseFloat(form.totalScore.toString());
if (isNaN(score) || score < 0 || score > 100) {
ElMessage.warning('宿舍总分必须在0-100分之间');
form.totalScore = 0;
return;
}
// 自动为所有学生设置相同的个人得分(包括0分)
if (roomStudents.value.length > 0) {
roomStudents.value.forEach((student: any) => {
student.student_score = score;
});
ElMessage.success(`已自动为${roomStudents.value.length}名学生设置个人得分:${score}分`);
}
} else if (form.totalScore === 0 || form.totalScore === '0') {
// 特别处理0分的情况
if (roomStudents.value.length > 0) {
roomStudents.value.forEach((student: any) => {
student.student_score = 0;
});
ElMessage.success(`已自动为${roomStudents.value.length}名学生设置个人得分:0分`);
}
}
};
修复问题三:房间排序问题
问题现象: 选择宿舍楼名称后,房间名称按照sort序号进行排序返回,但因为每层房间的sort都从0开始编码,导致返回数据把每个楼层的第一个房间先返回。
解决方案: 修改后端XgDormRoomServiceImpl.java中的buildQueryWrapper方法,调整排序逻辑为先按楼层再按sort排序。
// 修改前
lqw.orderByAsc(XgDormRoom::getSort); // 第一优先级:按排序号升序
lqw.orderByAsc(XgDormRoom::getFloorNum); // 第二优先级:按楼层升序
// 修改后
lqw.orderByAsc(XgDormRoom::getFloorNum); // 第一优先级:按楼层升序
lqw.orderByAsc(XgDormRoom::getSort); // 第二优先级:按排序号升序
修复效果
- 问题一:新增时个人扣分直接显示0,用户体验更好
- 问题二:宿舍扣分与个人扣分完全同步,包括0分的情况
- 问题三:房间列表按楼层顺序显示,每层内部按sort排序,符合用户期望
涉及文件
web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckDialog.vuebackend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormRoomServiceImpl.java
违纪记录更新修复 (2025-01-03)
问题描述
在调用 updateCompleteData 接口更新宿舍卫生检查数据时,即使违纪记录入参中包含有效的 disciplineRecordId,系统仍然执行了"逻辑删除旧记录 + 新增新记录"的操作,而不是直接更新现有记录。
根本原因
数据字段映射错误:
XgViolationDataBo中的id字段被误认为是违纪记录ID (disciplineRecordId)- 实际上
id字段是关联表xg_hygiene_check_violation的主键ID - 真正的违纪记录ID应该通过
disciplineRecordId字段传递
修复方案
1. 扩展 XgViolationDataBo 字段
文件: backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/domain/bo/XgViolationDataBo.java
/**
* 违纪记录ID(用于更新操作,新增时为null)
*/
private Long id;
/**
* 违纪记录ID,关联xskh_discipline_record.id(用于更新违纪记录)
*/
private Long disciplineRecordId;
2. 修复 handleViolationRecords 方法逻辑
文件: backend/ruoyi-modules/ruoyi-xg/src/main/java/org/dromara/xg/service/impl/XgDormHygieneCheckServiceImpl.java
修复前:
newDisciplineIds = newViolationData.stream()
.map(XgViolationDataBo::getId) // 错误:使用关联表ID
.filter(Objects::nonNull)
.collect(Collectors.toSet());
修复后:
newDisciplineIds = newViolationData.stream()
.map(violationBo -> {
// 优先使用disciplineRecordId字段,如果为空则使用id字段(向后兼容)
return violationBo.getDisciplineRecordId() != null
? violationBo.getDisciplineRecordId()
: violationBo.getId();
})
.filter(Objects::nonNull)
.collect(Collectors.toSet());
3. 修复判断逻辑
修复前:
if (violationBo.getId() != null && existingDisciplineIds.contains(violationBo.getId())) {
// 更新逻辑
}
修复后:
Long currentDisciplineRecordId = violationBo.getDisciplineRecordId() != null
? violationBo.getDisciplineRecordId()
: violationBo.getId();
if (currentDisciplineRecordId != null && existingDisciplineIds.contains(currentDisciplineRecordId)) {
// 更新逻辑
}
正确的入参格式
{
"violationData": [
{
"id": "1964301377652428802", // 关联表ID (可选)
"disciplineRecordId": "1964301377522405377", // 违纪记录ID (用于更新判断)
"studentId": "1949114038914949121",
"studentNo": "2025DQ21121",
"studentName": "梁文清",
"disciplineTypeId": "1963793291531026434",
"disciplineTypeName": "地面未打扫",
"disciplineReason": "未按要求打扫地面",
"disciplineContent": "地面有明显垃圾",
"scoreDeduction": "2.00",
// ... 其他字段
}
]
}
修复效果
- 准确识别更新操作:系统能正确识别哪些违纪记录需要更新,哪些需要新增
- 避免重复数据:不再错误地删除现有记录并新增重复记录
- 向后兼容:保持对旧版本数据格式的兼容性
- 数据完整性:确保违纪记录的历史数据完整性
测试验证
使用修复后的接口,当入参包含 disciplineRecordId 时:
- ✅ 系统识别为更新操作,直接更新现有违纪记录
- ✅ 不再执行逻辑删除 + 新增的错误流程
- ✅ 数据库中不会产生重复的违纪记录