package cn.com.qmth.am.service.impl; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.qmth.boot.core.ai.model.llm.StandardAnswer; import com.qmth.boot.core.exception.StatusException; import com.qmth.boot.tools.excel.ExcelReader; import com.qmth.boot.tools.excel.enums.ExcelType; import com.qmth.boot.tools.excel.model.DataMap; import cn.com.qmth.am.bean.ImageSlice; import cn.com.qmth.am.bean.ImportResult; import cn.com.qmth.am.config.SysProperty; import cn.com.qmth.am.dao.QuestionDao; import cn.com.qmth.am.entity.QuestionEntity; import cn.com.qmth.am.enums.ImportFileName; import cn.com.qmth.am.service.QuestionService; @Service public class QuestionServiceImpl extends ServiceImpl implements QuestionService { private Pattern scoreRex = Pattern.compile("\\[\\[([0-9][0-9]*(.[0-9]+){0,1})分\\]\\]"); private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码", "科目名称", "大题号", "小题号", "满分", "试题内容", "试题答案", "作答坐标" }; @Autowired private SysProperty sysProperty; @Autowired private QuestionService questionService; @Override public void importQuestion() { File dir = new File(sysProperty.getDataDir()); File[] fs = dir.listFiles(); if (fs == null || fs.length == 0) { return; } for (File file : fs) { if (!file.isFile() || !file.getName().equals(ImportFileName.QUESTION_IMPORT.getName())) { continue; } InputStream inputStream = null; ImportResult ret = null; try { inputStream = new FileInputStream(file); ret = questionService.disposeFile(inputStream); } catch (Exception e) { String errMsg; if (e instanceof FileNotFoundException) { errMsg = "未找到文件:" + file.getAbsolutePath(); } else { errMsg = "系统错误:" + e.getMessage(); } ret = new ImportResult(errMsg); } finally { if (inputStream != null) { try { inputStream.close(); } catch (IOException e) { } } } moveFile(dir, file, ret); } } private void moveFile(File dir, File file, ImportResult ret) { try { boolean succss = CollectionUtils.isEmpty(ret.getErrMsg()); if (succss) { File sucDir = new File(dir.getAbsoluteFile() + "/success/"); if (!sucDir.exists()) { sucDir.mkdir(); } File targetFile = new File(sucDir.getAbsoluteFile() + "/" + file.getName()); if (targetFile.exists()) { targetFile.delete(); } FileUtils.copyFile(file, targetFile); file.delete(); String fname = file.getName().substring(0, file.getName().lastIndexOf(".")); File msgFile = new File(sucDir.getAbsoluteFile() + "/" + fname + "_info.txt"); if (msgFile.exists()) { msgFile.delete(); } FileUtils.write(msgFile, ret.getCountInfo(), "utf-8"); } else { File sucDir = new File(dir.getAbsoluteFile() + "/failed/"); if (!sucDir.exists()) { sucDir.mkdir(); } File targetFile = new File(sucDir.getAbsoluteFile() + "/" + file.getName()); if (targetFile.exists()) { targetFile.delete(); } FileUtils.copyFile(file, targetFile); file.delete(); String fname = file.getName().substring(0, file.getName().lastIndexOf(".")); File msgFile = new File(sucDir.getAbsoluteFile() + "/" + fname + "_info.txt"); if (msgFile.exists()) { msgFile.delete(); } FileUtils.writeLines(msgFile, StandardCharsets.UTF_8.name(), ret.getErrMsg()); } } catch (IOException e) { throw new StatusException("文件处理出错", e); } } private String errorMsg(int lineNum, String msg) { return "第" + lineNum + "行 " + msg; } private String trimAndNullIfBlank(String s) { if (StringUtils.isBlank(s)) { return null; } return s.trim(); } @Transactional @Override public ImportResult disposeFile(InputStream inputStream) { List lineList = null; ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0); try { lineList = reader.getDataMapList(); } catch (Exception e) { throw new StatusException("Excel 解析失败"); } if (!Arrays.equals(EXCEL_HEADER, reader.getColumnNames())) { throw new StatusException("Excel表头错误"); } if (CollectionUtils.isEmpty(lineList)) { throw new StatusException("Excel无内容"); } if (100001 < lineList.size()) { throw new StatusException("数据行数不能超过100000"); } List ss = new ArrayList<>(); ImportResult ret = new ImportResult(); List failRecords = new ArrayList<>(); ret.setErrMsg(failRecords); for (int i = 0; i < lineList.size(); i++) { DataMap line = lineList.get(i); StringBuilder msg = new StringBuilder(); QuestionEntity imp = new QuestionEntity(); String examId = trimAndNullIfBlank(line.get(EXCEL_HEADER[0])); if (StringUtils.isBlank(examId)) { msg.append(" 考试ID不能为空"); } else if (examId.length() > 20) { msg.append(" 考试ID不能超过20个字符"); } else { try { Long examIdVal = Long.parseLong(examId); imp.setExamId(examIdVal); } catch (NumberFormatException e) { msg.append(" 考试ID只能是数字"); } } String subjectCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[1])); if (StringUtils.isBlank(subjectCode)) { msg.append(" 科目代码不能为空"); } else if (subjectCode.length() > 100) { msg.append(" 科目代码不能超过100个字符"); } imp.setSubjectCode(subjectCode); String subjectName = trimAndNullIfBlank(line.get(EXCEL_HEADER[2])); if (StringUtils.isBlank(subjectName)) { msg.append(" 科目名称不能为空"); } else if (subjectName.length() > 100) { msg.append(" 科目名称不能超过100个字符"); } imp.setSubjectName(subjectName); String mainNum = trimAndNullIfBlank(line.get(EXCEL_HEADER[3])); if (StringUtils.isBlank(mainNum)) { msg.append(" 大题号不能为空"); } else if (mainNum.length() > 10) { msg.append(" 大题号不能超过10个字符"); } else { try { Integer mainNumVal = Integer.parseInt(mainNum); if (mainNumVal <= 0) { msg.append(" 大题号必须大于0"); } imp.setMainNumber(mainNumVal); } catch (NumberFormatException e) { msg.append(" 大题号格式错误"); } } String subNum = trimAndNullIfBlank(line.get(EXCEL_HEADER[4])); if (StringUtils.isBlank(subNum)) { msg.append(" 小题号不能为空"); } else if (subNum.length() > 10) { msg.append(" 小题号不能超过10个字符"); } imp.setSubNumber(subNum); String fullScore = trimAndNullIfBlank(line.get(EXCEL_HEADER[5])); if (StringUtils.isBlank(fullScore)) { msg.append(" 满分不能为空"); } else if (fullScore.length() > 10) { msg.append(" 满分不能超过10个字符"); } else { try { Double fullScoreVal = Double.parseDouble(fullScore); if (fullScoreVal <= 0) { msg.append(" 满分必须大于0"); } imp.setFullScore(fullScoreVal); } catch (NumberFormatException e) { msg.append(" 满分格式错误"); } } String content = trimAndNullIfBlank(line.get(EXCEL_HEADER[6])); if (StringUtils.isBlank(content)) { msg.append(" 试题内容不能为空"); } imp.setContent(content); String answer = trimAndNullIfBlank(line.get(EXCEL_HEADER[7])); if (StringUtils.isBlank(answer)) { msg.append(" 试题答案不能为空"); } imp.setAnswer(getStandardAnswer(answer)); String imageSlice = trimAndNullIfBlank(line.get(EXCEL_HEADER[8])); if (StringUtils.isBlank(imageSlice)) { msg.append(" 作答坐标不能为空"); } else if (imageSlice.length() > 1000) { msg.append(" 作答坐标不能超过1000个字符"); } else { List val = getImageSlice(imageSlice); if (val == null) { msg.append(" 作答坐标格式有误"); } else { imp.setImageSlice(val); } } if (msg.length() > 0) { failRecords.add(errorMsg(i + 1, msg.toString())); } else { ss.add(imp); } } if (CollectionUtils.isNotEmpty(failRecords)) { return ret; } try { saveQuestionBatch(ret, ss); } catch (Exception e) { failRecords.add("系统错误:" + e.getMessage()); } return ret; } private List getImageSlice(String s) { if (StringUtils.isBlank(s)) { return null; } s = s.trim(); if (StringUtils.isBlank(s)) { return null; } try { List list = new ArrayList<>(); String[] items = s.split(","); for (int i = 0; i < items.length; i++) { String item = items[i]; item = item.trim(); String[] config = item.split(":"); if (config.length != 5) { return null; } int iVal = Integer.valueOf(config[0]); int x = Integer.valueOf(config[1]); int y = Integer.valueOf(config[2]); int w = Integer.valueOf(config[3]); int h = Integer.valueOf(config[4]); ImageSlice ret = new ImageSlice(); ret.setH(h); ret.setI(iVal); ret.setW(w); ret.setX(x); ret.setY(y); if (ret.getH() == null || ret.getI() == null || ret.getW() == null || ret.getX() == null || ret.getY() == null || ret.getI() < 0) { return null; } list.add(ret); } if (list.size() == 0) { return null; } list.sort(new Comparator() { @Override public int compare(ImageSlice o1, ImageSlice o2) { long c1 = o1.getI(); long c2 = o2.getI(); if (c1 < c2) { return -1; } else if (c1 > c2) { return 1; } else { return 0; } } }); return list; } catch (Exception e) { return null; } } private List getStandardAnswer(String s) { if (StringUtils.isBlank(s)) { return null; } s = s.trim(); if (StringUtils.isBlank(s)) { return null; } List list = new ArrayList<>(); Matcher matcher = scoreRex.matcher(s); int start=0; Double score = null; while (matcher.find()) { if(start!=0) { StandardAnswer a=new StandardAnswer(); list.add(a); a.setScore(score); a.setContent(s.substring(start,matcher.start())); } try { score=Double.valueOf(matcher.group(1)); } catch (NumberFormatException e) { throw new StatusException("分数格式有误"); } checkScore(score); start=matcher.end(); } if(start ss) { if (CollectionUtils.isEmpty(ss)) { ret.setCountInfo("新增数量:0,更新数量:0"); return; } List all = this.list(); Map old = new HashMap<>(); Map addMap = new HashMap<>(); if (CollectionUtils.isNotEmpty(all)) { for (QuestionEntity s : all) { String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getMainNumber() + "-" + s.getSubNumber(); old.put(key, s); } } List adds = new ArrayList<>(); List updates = new ArrayList<>(); for (QuestionEntity s : ss) { String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getMainNumber() + "-" + s.getSubNumber(); if (old.get(key) == null) { QuestionEntity add = addMap.get(key); if (add != null) { add.setSubjectName(s.getSubjectName()); add.setFullScore(s.getFullScore()); add.setImageSlice(s.getImageSlice()); add.setContent(s.getContent()); add.setAnswer(s.getAnswer()); } else { addMap.put(key, s); adds.add(s); } } else { QuestionEntity up = old.get(key); up.setSubjectName(s.getSubjectName()); up.setFullScore(s.getFullScore()); up.setImageSlice(s.getImageSlice()); up.setContent(s.getContent()); up.setAnswer(s.getAnswer()); updates.add(up); } } if (CollectionUtils.isNotEmpty(adds)) { saveBatch(adds); } if (CollectionUtils.isNotEmpty(updates)) { updateBatchById(updates); } ret.setCountInfo("新增数量:" + adds.size() + ",更新数量:" + updates.size()); } @Override public List findByExamId(Long examId) { QueryWrapper wrapper = new QueryWrapper<>(); LambdaQueryWrapper lw = wrapper.lambda(); lw.eq(QuestionEntity::getExamId, examId); lw.orderByAsc(QuestionEntity::getSubjectCode).orderByAsc(QuestionEntity::getMainNumber) .orderByAsc(QuestionEntity::getSubNumber); return this.list(wrapper); } @Transactional @Override public void removeBy(Long examId, String subjectCode) { QueryWrapper wrapper = new QueryWrapper<>(); LambdaQueryWrapper lw = wrapper.lambda(); if (subjectCode != null) { lw.eq(QuestionEntity::getSubjectCode, subjectCode); } lw.eq(QuestionEntity::getExamId, examId); this.remove(wrapper); } }