Files
hzdyjs/docs/开发文档/API/宿舍卫生检查管理API.md
T
2025-09-06 20:42:14 +08:00

17 KiB
Raw Blame History

宿舍卫生检查管理API文档

概述

宿舍卫生检查管理模块提供完整的宿舍卫生检查功能,包括检查记录管理和学生个人评分管理。该模块支持同时操作两个数据表:xg_dorm_hygiene_check(主表)和 xg_dorm_hygiene_student_score(学生评分表)。

数据库表结构

主表:xg_dorm_hygiene_check

  • 存储宿舍卫生检查的基本信息
  • 包含检查标题、日期、检查员、宿舍信息、总分、等级等

子表:xg_dorm_hygiene_student_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,
  "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": "建议整理桌面物品"
    }
  ]
}

实现要求:

  1. 事务处理:使用数据库事务确保两个表的操作原子性
  2. 主表插入:插入宿舍卫生检查记录,获取生成的主键ID
  3. 子表插入:为每个学生插入评分记录,关联主表ID
  4. 数据验证:验证必填字段、数据格式、分数范围等
  5. 权限控制:检查用户权限,记录操作日志

响应格式:

{
  "code": 200,
  "msg": "新增成功",
  "data": {
    "id": 1,
    "checkTitle": "2025年1月宿舍卫生检查",
    "studentCount": 4
  }
}

2. 更新宿舍卫生检查(包含学生评分)

接口地址: PUT /dormHygieneCheck/dormHygieneCheck/updateWithStudentScores

请求参数: 与新增接口相同,但必须包含 id 字段

实现要求:

  1. 事务处理:使用数据库事务确保操作的原子性
  2. 主表更新:更新宿舍卫生检查记录
  3. 子表处理:
    • 将原有学生评分记录的 deleted 字段设置为 1(逻辑删除)
    • 插入新的学生评分记录
  4. 数据验证:验证记录存在性、权限等
  5. 历史保留:保留历史数据,便于追溯

响应格式:

{
  "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分
  • hygieneGrade: 可选,A/B/C/D

学生评分表验证

  • studentId: 必填,关联学生表
  • studentScore: 可选,0-100分
  • attendanceStatus: 必填,1/2/3/4
  • studentGrade: 可选,A/B/C/D

错误处理

常见错误码

  • 400: 参数验证失败
  • 401: 权限不足
  • 404: 记录不存在
  • 500: 服务器内部错误

错误响应格式

{
  "code": 400,
  "msg": "检查标题不能为空",
  "data": null
}

注意事项

  1. 事务一致性:确保两个表的操作在同一事务中完成
  2. 数据完整性:更新时保留历史数据,便于追溯
  3. 性能优化:批量操作时考虑性能影响
  4. 权限控制:严格检查用户权限
  5. 日志记录:记录重要操作日志
  6. 数据验证:前端和后端都要进行数据验证

测试用例

新增测试

  1. 正常新增:提供完整有效数据
  2. 数据验证:测试必填字段、格式验证
  3. 权限测试:测试不同用户权限
  4. 异常测试:测试网络异常、数据库异常

更新测试

  1. 正常更新:更新现有记录
  2. 记录不存在:更新不存在的记录
  3. 学生数据变更:测试学生数据的增删改
  4. 历史数据:验证历史数据保留

文档版本: 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); // 第二优先级:按排序号升序

修复效果

  1. 问题一:新增时个人扣分直接显示0,用户体验更好
  2. 问题二:宿舍扣分与个人扣分完全同步,包括0分的情况
  3. 问题三:房间列表按楼层顺序显示,每层内部按sort排序,符合用户期望

涉及文件

  • web/src/views/xg/xgss/dormHygieneCheck/dormHygieneCheck/DormHygieneCheckDialog.vue
  • backend/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",
      // ... 其他字段
    }
  ]
}

修复效果

  1. 准确识别更新操作:系统能正确识别哪些违纪记录需要更新,哪些需要新增
  2. 避免重复数据:不再错误地删除现有记录并新增重复记录
  3. 向后兼容:保持对旧版本数据格式的兼容性
  4. 数据完整性:确保违纪记录的历史数据完整性

测试验证

使用修复后的接口,当入参包含 disciplineRecordId 时:

  • ✅ 系统识别为更新操作,直接更新现有违纪记录
  • ✅ 不再执行逻辑删除 + 新增的错误流程
  • ✅ 数据库中不会产生重复的违纪记录