宿舍卫生检查导出导入设计方案.md 13 KB

宿舍卫生检查导出导入设计方案

📊 业务背景

宿舍卫生检查系统采用主子表分离设计:

  • 主表:xg_dorm_hygiene_check - 宿舍整体检查信息
  • 子表:xg_dorm_hygiene_student_score - 学生个人评分信息

🎯 设计方案

方案一:平铺式导出导入(推荐)

📋 导出格式

检查编号 | 检查日期 | 宿舍楼 | 房间号 | 宿舍总分 | 卫生等级 | 学生1学号 | 学生1姓名 | 学生1得分 | 学生1评级 | 学生2学号 | 学生2姓名 | 学生2得分 | 学生2评级 | ...

💻 Controller层实现

/**
 * 导出宿舍卫生检查数据(平铺格式)
 */
@SaCheckPermission("xg:hygiene:export")
@PostMapping("/export")
public void export(HttpServletResponse response, XgDormHygieneCheckBo bo) {
    List<XgDormHygieneCheckFlatVo> list = hygieneCheckService.queryFlatList(bo);
    ExcelUtil.exportExcel(list, "宿舍卫生检查数据", XgDormHygieneCheckFlatVo.class, response);
}

/**
 * 导入宿舍卫生检查数据(平铺格式)
 */
@SaCheckPermission("xg:hygiene:import")
@PostMapping("/importData")
public R<Void> importData(MultipartFile file) throws Exception {
    ExcelResult<XgDormHygieneCheckFlatVo> result = ExcelUtil.importExcel(file.getInputStream(), XgDormHygieneCheckFlatVo.class);
    List<XgDormHygieneCheckFlatVo> flatList = result.getList();
    String message = hygieneCheckService.importFlatData(flatList);
    return R.ok(message);
}

📝 平铺式VO类设计

@Data
@ExcelIgnoreUnannotated
public class XgDormHygieneCheckFlatVo {
    
    // ========== 宿舍检查基本信息 ==========
    @ExcelProperty(value = "检查编号")
    private String checkNo;
    
    @ExcelProperty(value = "检查日期")
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date checkDate;
    
    @ExcelProperty(value = "宿舍楼")
    private String buildingName;
    
    @ExcelProperty(value = "房间号")
    private String roomCode;
    
    @ExcelProperty(value = "宿舍总分")
    private BigDecimal totalScore;
    
    @ExcelProperty(value = "卫生等级")
    private String hygieneGrade;
    
    // ========== 学生1信息 ==========
    @ExcelProperty(value = "学生1学号")
    private String student1No;
    
    @ExcelProperty(value = "学生1姓名")
    private String student1Name;
    
    @ExcelProperty(value = "学生1得分")
    private BigDecimal student1Score;
    
    @ExcelProperty(value = "学生1评级")
    private String student1Grade;
    
    // ========== 学生2信息 ==========
    @ExcelProperty(value = "学生2学号")
    private String student2No;
    
    @ExcelProperty(value = "学生2姓名")
    private String student2Name;
    
    @ExcelProperty(value = "学生2得分")
    private BigDecimal student2Score;
    
    @ExcelProperty(value = "学生2评级")
    private String student2Grade;
    
    // ========== 学生3信息 ==========
    @ExcelProperty(value = "学生3学号")
    private String student3No;
    
    @ExcelProperty(value = "学生3姓名")
    private String student3Name;
    
    @ExcelProperty(value = "学生3得分")
    private BigDecimal student3Score;
    
    @ExcelProperty(value = "学生3评级")
    private String student3Grade;
    
    // ========== 学生4信息 ==========
    @ExcelProperty(value = "学生4学号")
    private String student4No;
    
    @ExcelProperty(value = "学生4姓名")
    private String student4Name;
    
    @ExcelProperty(value = "学生4得分")
    private BigDecimal student4Score;
    
    @ExcelProperty(value = "学生4评级")
    private String student4Grade;
    
    // ========== 备注信息 ==========
    @ExcelProperty(value = "检查总结")
    private String checkSummary;
    
    @ExcelProperty(value = "整改建议")
    private String improvementSuggestions;
    
    // ========== 基础字段 ==========
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date updateTime;
    
    private Long createBy;
    
    private Long updateBy;
}

🔧 Service层数据转换

/**
 * 查询平铺格式的卫生检查列表
 */
public List<XgDormHygieneCheckFlatVo> queryFlatList(XgDormHygieneCheckBo bo) {
    // 1. 查询主表数据
    List<XgDormHygieneCheck> checkList = baseMapper.selectList(buildQueryWrapper(bo));
    
    List<XgDormHygieneCheckFlatVo> flatList = new ArrayList<>();
    
    for (XgDormHygieneCheck check : checkList) {
        // 2. 查询对应的学生评分数据
        List<XgDormHygieneStudentScore> studentScores = studentScoreMapper.selectList(
            new LambdaQueryWrapper<XgDormHygieneStudentScore>()
                .eq(XgDormHygieneStudentScore::getHygieneCheckId, check.getId())
                .eq(XgDormHygieneStudentScore::getDeleted, 0)
                .orderByAsc(XgDormHygieneStudentScore::getStudentNo)
        );
        
        // 3. 数据转换为平铺格式
        XgDormHygieneCheckFlatVo flatVo = new XgDormHygieneCheckFlatVo();
        
        // 填充主表信息
        BeanUtils.copyProperties(check, flatVo);
        
        // 填充学生信息(最多支持4个学生)
        for (int i = 0; i < Math.min(studentScores.size(), 4); i++) {
            XgDormHygieneStudentScore student = studentScores.get(i);
            switch (i) {
                case 0:
                    flatVo.setStudent1No(student.getStudentNo());
                    flatVo.setStudent1Name(student.getStudentName());
                    flatVo.setStudent1Score(student.getStudentScore());
                    flatVo.setStudent1Grade(student.getStudentGrade());
                    break;
                case 1:
                    flatVo.setStudent2No(student.getStudentNo());
                    flatVo.setStudent2Name(student.getStudentName());
                    flatVo.setStudent2Score(student.getStudentScore());
                    flatVo.setStudent2Grade(student.getStudentGrade());
                    break;
                case 2:
                    flatVo.setStudent3No(student.getStudentNo());
                    flatVo.setStudent3Name(student.getStudentName());
                    flatVo.setStudent3Score(student.getStudentScore());
                    flatVo.setStudent3Grade(student.getStudentGrade());
                    break;
                case 3:
                    flatVo.setStudent4No(student.getStudentNo());
                    flatVo.setStudent4Name(student.getStudentName());
                    flatVo.setStudent4Score(student.getStudentScore());
                    flatVo.setStudent4Grade(student.getStudentGrade());
                    break;
            }
        }
        
        flatList.add(flatVo);
    }
    
    return flatList;
}

/**
 * 导入平铺格式数据
 */
@Transactional(rollbackFor = Exception.class)
public String importFlatData(List<XgDormHygieneCheckFlatVo> flatList) {
    if (CollUtil.isEmpty(flatList)) {
        throw new ServiceException("导入数据不能为空");
    }
    
    int successCount = 0;
    int failureCount = 0;
    StringBuilder failureMsg = new StringBuilder();
    
    for (XgDormHygieneCheckFlatVo flatVo : flatList) {
        try {
            // 1. 转换并保存主表数据
            XgDormHygieneCheck check = convertToCheck(flatVo);
            baseMapper.insert(check);
            
            // 2. 转换并保存学生评分数据
            List<XgDormHygieneStudentScore> studentScores = convertToStudentScores(flatVo, check.getId());
            for (XgDormHygieneStudentScore score : studentScores) {
                studentScoreMapper.insert(score);
            }
            
            successCount++;
        } catch (Exception e) {
            failureCount++;
            String msg = "<br/>" + failureCount + "、检查编号 " + flatVo.getCheckNo() + " 导入失败:" + e.getMessage();
            failureMsg.append(msg);
        }
    }
    
    if (failureCount > 0) {
        failureMsg.insert(0, "很抱歉,导入失败!共 " + failureCount + " 条数据格式不正确,错误如下:");
        throw new ServiceException(failureMsg.toString());
    } else {
        return "恭喜您,数据已全部导入成功!共 " + successCount + " 条";
    }
}

方案二:多Sheet导出导入

📋 导出格式

  • Sheet1: 宿舍检查主数据
  • Sheet2: 学生评分详细数据

💻 实现代码

/**
 * 导出多Sheet格式数据
 */
@SaCheckPermission("xg:hygiene:exportMultiSheet")
@PostMapping("/exportMultiSheet")
public void exportMultiSheet(HttpServletResponse response, XgDormHygieneCheckBo bo) {
    // 1. 查询主表数据
    List<XgDormHygieneCheckVo> checkList = hygieneCheckService.queryList(bo);
    
    // 2. 查询子表数据
    List<XgDormHygieneStudentScoreVo> studentScoreList = hygieneCheckService.queryStudentScoreList(bo);
    
    // 3. 多Sheet导出
    ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream())
        .autoCloseStream(Boolean.FALSE)
        .build();
    
    try {
        // Sheet1: 宿舍检查数据
        WriteSheet writeSheet1 = EasyExcel.writerSheet(0, "宿舍检查数据")
            .head(XgDormHygieneCheckVo.class)
            .build();
        excelWriter.write(checkList, writeSheet1);
        
        // Sheet2: 学生评分数据
        WriteSheet writeSheet2 = EasyExcel.writerSheet(1, "学生评分数据")
            .head(XgDormHygieneStudentScoreVo.class)
            .build();
        excelWriter.write(studentScoreList, writeSheet2);
        
    } finally {
        excelWriter.finish();
    }
}

方案三:嵌套式导出导入

📋 导出格式(纵向展开)

检查编号 | 检查日期 | 宿舍楼 | 房间号 | 宿舍总分 | 卫生等级 | 学号 | 姓名 | 个人得分 | 个人评级
WSCC001 | 2025-01-28 | A栋 | 101 | 85.5 | B | 2023001 | 张三 | 88 | B
WSCC001 | 2025-01-28 | A栋 | 101 | 85.5 | B | 2023002 | 李四 | 83 | C

🔧 技术实现要点

1. 数据完整性校验

// 导入时校验数据完整性
private void validateImportData(XgDormHygieneCheckFlatVo flatVo) {
    // 必填字段校验
    if (StringUtils.isBlank(flatVo.getCheckNo())) {
        throw new ServiceException("检查编号不能为空");
    }
    
    // 宿舍信息校验
    if (StringUtils.isBlank(flatVo.getRoomCode())) {
        throw new ServiceException("房间号不能为空");
    }
    
    // 至少需要一个学生信息
    if (StringUtils.isAllBlank(flatVo.getStudent1No(), flatVo.getStudent2No(), 
                              flatVo.getStudent3No(), flatVo.getStudent4No())) {
        throw new ServiceException("至少需要填写一个学生信息");
    }
}

2. 模板下载功能

/**
 * 下载导入模板
 */
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
    // 生成模板数据
    List<XgDormHygieneCheckFlatVo> templateList = generateTemplateData();
    ExcelUtil.exportExcel(templateList, "宿舍卫生检查导入模板", XgDormHygieneCheckFlatVo.class, response);
}

3. 批量操作优化

// 使用批量插入优化性能
@Transactional(rollbackFor = Exception.class)
public void batchInsertStudentScores(List<XgDormHygieneStudentScore> studentScores) {
    if (CollUtil.isNotEmpty(studentScores)) {
        // 分批插入,每批500条
        List<List<XgDormHygieneStudentScore>> partition = CollUtil.split(studentScores, 500);
        for (List<XgDormHygieneStudentScore> batch : partition) {
            studentScoreMapper.insertBatch(batch);
        }
    }
}

📊 推荐使用方案

🎯 方案选择建议

使用场景 推荐方案 理由
日常数据录入 平铺式 操作简单,一个Excel搞定
数据分析报表 嵌套式 便于透视表分析
系统间数据交换 多Sheet式 结构清晰,数据完整
打印存档 平铺式 格式紧凑,便于查看

⚡ 性能优化建议

  1. 导出时分页处理:大数据量时分批导出
  2. 导入时异步处理:超过1000条数据使用异步导入
  3. 缓存优化:字典数据、宿舍信息等使用缓存
  4. 索引优化:确保查询字段都有适当索引

🚨 注意事项

  1. 数据一致性:导入时需要保证主子表数据的一致性
  2. 权限控制:导出导入功能需要适当的权限控制
  3. 错误处理:提供详细的错误信息和导入结果反馈
  4. 模板维护:定期更新导入模板,保持与业务同步

📝 相关配置

Excel导出配置

# application.yml
ruoyi:
  excel:
    # 导出最大行数
    max-rows: 10000
    # 导入最大行数  
    import-max-rows: 5000

权限配置

-- 菜单权限配置
INSERT INTO sys_menu VALUES (NULL, '宿舍卫生检查导出', 导出, 'xg:hygiene:export', '', '', 1, 0, 'C', '0', '0', '', 'export', 1, NOW(), NULL, NULL, '');
INSERT INTO sys_menu VALUES (NULL, '宿舍卫生检查导入', 导入, 'xg:hygiene:import', '', '', 1, 0, 'C', '0', '0', '', 'upload', 1, NOW(), NULL, NULL, '');