QuestionServiceImpl.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. package cn.com.qmth.am.service.impl;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.FileNotFoundException;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.nio.charset.StandardCharsets;
  8. import java.util.ArrayList;
  9. import java.util.Arrays;
  10. import java.util.Comparator;
  11. import java.util.HashMap;
  12. import java.util.List;
  13. import java.util.Map;
  14. import java.util.regex.Matcher;
  15. import java.util.regex.Pattern;
  16. import org.apache.commons.collections4.CollectionUtils;
  17. import org.apache.commons.io.FileUtils;
  18. import org.apache.commons.lang3.StringUtils;
  19. import org.springframework.beans.factory.annotation.Autowired;
  20. import org.springframework.stereotype.Service;
  21. import org.springframework.transaction.annotation.Transactional;
  22. import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
  23. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  24. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  25. import com.qmth.boot.core.ai.model.llm.StandardAnswer;
  26. import com.qmth.boot.core.exception.StatusException;
  27. import com.qmth.boot.tools.excel.ExcelReader;
  28. import com.qmth.boot.tools.excel.enums.ExcelType;
  29. import com.qmth.boot.tools.excel.model.DataMap;
  30. import cn.com.qmth.am.bean.ImageSlice;
  31. import cn.com.qmth.am.bean.ImportResult;
  32. import cn.com.qmth.am.config.SysProperty;
  33. import cn.com.qmth.am.dao.QuestionDao;
  34. import cn.com.qmth.am.entity.QuestionEntity;
  35. import cn.com.qmth.am.enums.ImportFileName;
  36. import cn.com.qmth.am.service.QuestionService;
  37. @Service
  38. public class QuestionServiceImpl extends ServiceImpl<QuestionDao, QuestionEntity> implements QuestionService {
  39. private Pattern scoreRex = Pattern.compile("\\[\\[([0-9][0-9]*(.[0-9]+){0,1})分\\]\\]");
  40. private static final String[] EXCEL_HEADER = new String[] { "考试ID", "科目代码", "科目名称", "大题号", "小题号", "满分", "试题内容",
  41. "试题答案", "作答坐标" };
  42. @Autowired
  43. private SysProperty sysProperty;
  44. @Autowired
  45. private QuestionService questionService;
  46. @Override
  47. public void importQuestion() {
  48. File dir = new File(sysProperty.getDataDir());
  49. File[] fs = dir.listFiles();
  50. if (fs == null || fs.length == 0) {
  51. return;
  52. }
  53. for (File file : fs) {
  54. if (!file.isFile() || !file.getName().equals(ImportFileName.QUESTION_IMPORT.getName())) {
  55. continue;
  56. }
  57. InputStream inputStream = null;
  58. ImportResult ret = null;
  59. try {
  60. inputStream = new FileInputStream(file);
  61. ret = questionService.disposeFile(inputStream);
  62. } catch (Exception e) {
  63. String errMsg;
  64. if (e instanceof FileNotFoundException) {
  65. errMsg = "未找到文件:" + file.getAbsolutePath();
  66. } else {
  67. errMsg = "系统错误:" + e.getMessage();
  68. }
  69. ret = new ImportResult(errMsg);
  70. } finally {
  71. if (inputStream != null) {
  72. try {
  73. inputStream.close();
  74. } catch (IOException e) {
  75. }
  76. }
  77. }
  78. moveFile(dir, file, ret);
  79. }
  80. }
  81. private void moveFile(File dir, File file, ImportResult ret) {
  82. try {
  83. boolean succss = CollectionUtils.isEmpty(ret.getErrMsg());
  84. if (succss) {
  85. File sucDir = new File(dir.getAbsoluteFile() + "/success/");
  86. if (!sucDir.exists()) {
  87. sucDir.mkdir();
  88. }
  89. File targetFile = new File(sucDir.getAbsoluteFile() + "/" + file.getName());
  90. if (targetFile.exists()) {
  91. targetFile.delete();
  92. }
  93. FileUtils.copyFile(file, targetFile);
  94. file.delete();
  95. String fname = file.getName().substring(0, file.getName().lastIndexOf("."));
  96. File msgFile = new File(sucDir.getAbsoluteFile() + "/" + fname + "_info.txt");
  97. if (msgFile.exists()) {
  98. msgFile.delete();
  99. }
  100. FileUtils.write(msgFile, ret.getCountInfo(), "utf-8");
  101. } else {
  102. File sucDir = new File(dir.getAbsoluteFile() + "/failed/");
  103. if (!sucDir.exists()) {
  104. sucDir.mkdir();
  105. }
  106. File targetFile = new File(sucDir.getAbsoluteFile() + "/" + file.getName());
  107. if (targetFile.exists()) {
  108. targetFile.delete();
  109. }
  110. FileUtils.copyFile(file, targetFile);
  111. file.delete();
  112. String fname = file.getName().substring(0, file.getName().lastIndexOf("."));
  113. File msgFile = new File(sucDir.getAbsoluteFile() + "/" + fname + "_info.txt");
  114. if (msgFile.exists()) {
  115. msgFile.delete();
  116. }
  117. FileUtils.writeLines(msgFile, StandardCharsets.UTF_8.name(), ret.getErrMsg());
  118. }
  119. } catch (IOException e) {
  120. throw new StatusException("文件处理出错", e);
  121. }
  122. }
  123. private String errorMsg(int lineNum, String msg) {
  124. return "第" + lineNum + "行 " + msg;
  125. }
  126. private String trimAndNullIfBlank(String s) {
  127. if (StringUtils.isBlank(s)) {
  128. return null;
  129. }
  130. return s.trim();
  131. }
  132. @Transactional
  133. @Override
  134. public ImportResult disposeFile(InputStream inputStream) {
  135. List<DataMap> lineList = null;
  136. ExcelReader reader = ExcelReader.create(ExcelType.XLSX, inputStream, 0);
  137. try {
  138. lineList = reader.getDataMapList();
  139. } catch (Exception e) {
  140. throw new StatusException("Excel 解析失败");
  141. }
  142. if (!Arrays.equals(EXCEL_HEADER, reader.getColumnNames())) {
  143. throw new StatusException("Excel表头错误");
  144. }
  145. if (CollectionUtils.isEmpty(lineList)) {
  146. throw new StatusException("Excel无内容");
  147. }
  148. if (100001 < lineList.size()) {
  149. throw new StatusException("数据行数不能超过100000");
  150. }
  151. List<QuestionEntity> ss = new ArrayList<>();
  152. ImportResult ret = new ImportResult();
  153. List<String> failRecords = new ArrayList<>();
  154. ret.setErrMsg(failRecords);
  155. for (int i = 0; i < lineList.size(); i++) {
  156. DataMap line = lineList.get(i);
  157. StringBuilder msg = new StringBuilder();
  158. QuestionEntity imp = new QuestionEntity();
  159. String examId = trimAndNullIfBlank(line.get(EXCEL_HEADER[0]));
  160. if (StringUtils.isBlank(examId)) {
  161. msg.append(" 考试ID不能为空");
  162. } else if (examId.length() > 20) {
  163. msg.append(" 考试ID不能超过20个字符");
  164. } else {
  165. try {
  166. Long examIdVal = Long.parseLong(examId);
  167. imp.setExamId(examIdVal);
  168. } catch (NumberFormatException e) {
  169. msg.append(" 考试ID只能是数字");
  170. }
  171. }
  172. String subjectCode = trimAndNullIfBlank(line.get(EXCEL_HEADER[1]));
  173. if (StringUtils.isBlank(subjectCode)) {
  174. msg.append(" 科目代码不能为空");
  175. } else if (subjectCode.length() > 100) {
  176. msg.append(" 科目代码不能超过100个字符");
  177. }
  178. imp.setSubjectCode(subjectCode);
  179. String subjectName = trimAndNullIfBlank(line.get(EXCEL_HEADER[2]));
  180. if (StringUtils.isBlank(subjectName)) {
  181. msg.append(" 科目名称不能为空");
  182. } else if (subjectName.length() > 100) {
  183. msg.append(" 科目名称不能超过100个字符");
  184. }
  185. imp.setSubjectName(subjectName);
  186. String mainNum = trimAndNullIfBlank(line.get(EXCEL_HEADER[3]));
  187. if (StringUtils.isBlank(mainNum)) {
  188. msg.append(" 大题号不能为空");
  189. } else if (mainNum.length() > 10) {
  190. msg.append(" 大题号不能超过10个字符");
  191. } else {
  192. try {
  193. Integer mainNumVal = Integer.parseInt(mainNum);
  194. if (mainNumVal <= 0) {
  195. msg.append(" 大题号必须大于0");
  196. }
  197. imp.setMainNumber(mainNumVal);
  198. } catch (NumberFormatException e) {
  199. msg.append(" 大题号格式错误");
  200. }
  201. }
  202. String subNum = trimAndNullIfBlank(line.get(EXCEL_HEADER[4]));
  203. if (StringUtils.isBlank(subNum)) {
  204. msg.append(" 小题号不能为空");
  205. } else if (subNum.length() > 10) {
  206. msg.append(" 小题号不能超过10个字符");
  207. }
  208. imp.setSubNumber(subNum);
  209. String fullScore = trimAndNullIfBlank(line.get(EXCEL_HEADER[5]));
  210. if (StringUtils.isBlank(fullScore)) {
  211. msg.append(" 满分不能为空");
  212. } else if (fullScore.length() > 10) {
  213. msg.append(" 满分不能超过10个字符");
  214. } else {
  215. try {
  216. Double fullScoreVal = Double.parseDouble(fullScore);
  217. if (fullScoreVal <= 0) {
  218. msg.append(" 满分必须大于0");
  219. }
  220. imp.setFullScore(fullScoreVal);
  221. } catch (NumberFormatException e) {
  222. msg.append(" 满分格式错误");
  223. }
  224. }
  225. String content = trimAndNullIfBlank(line.get(EXCEL_HEADER[6]));
  226. if (StringUtils.isBlank(content)) {
  227. msg.append(" 试题内容不能为空");
  228. }
  229. imp.setContent(content);
  230. String answer = trimAndNullIfBlank(line.get(EXCEL_HEADER[7]));
  231. if (StringUtils.isBlank(answer)) {
  232. msg.append(" 试题答案不能为空");
  233. }
  234. imp.setAnswer(getStandardAnswer(answer));
  235. String imageSlice = trimAndNullIfBlank(line.get(EXCEL_HEADER[8]));
  236. if (StringUtils.isBlank(imageSlice)) {
  237. msg.append(" 作答坐标不能为空");
  238. } else if (imageSlice.length() > 1000) {
  239. msg.append(" 作答坐标不能超过1000个字符");
  240. } else {
  241. List<ImageSlice> val = getImageSlice(imageSlice);
  242. if (val == null) {
  243. msg.append(" 作答坐标格式有误");
  244. } else {
  245. imp.setImageSlice(val);
  246. }
  247. }
  248. if (msg.length() > 0) {
  249. failRecords.add(errorMsg(i + 1, msg.toString()));
  250. } else {
  251. ss.add(imp);
  252. }
  253. }
  254. if (CollectionUtils.isNotEmpty(failRecords)) {
  255. return ret;
  256. }
  257. try {
  258. saveQuestionBatch(ret, ss);
  259. } catch (Exception e) {
  260. failRecords.add("系统错误:" + e.getMessage());
  261. }
  262. return ret;
  263. }
  264. private List<ImageSlice> getImageSlice(String s) {
  265. if (StringUtils.isBlank(s)) {
  266. return null;
  267. }
  268. s = s.trim();
  269. if (StringUtils.isBlank(s)) {
  270. return null;
  271. }
  272. try {
  273. List<ImageSlice> list = new ArrayList<>();
  274. String[] items = s.split(",");
  275. for (int i = 0; i < items.length; i++) {
  276. String item = items[i];
  277. item = item.trim();
  278. String[] config = item.split(":");
  279. if (config.length != 5) {
  280. return null;
  281. }
  282. int iVal = Integer.valueOf(config[0]);
  283. int x = Integer.valueOf(config[1]);
  284. int y = Integer.valueOf(config[2]);
  285. int w = Integer.valueOf(config[3]);
  286. int h = Integer.valueOf(config[4]);
  287. ImageSlice ret = new ImageSlice();
  288. ret.setH(h);
  289. ret.setI(iVal);
  290. ret.setW(w);
  291. ret.setX(x);
  292. ret.setY(y);
  293. if (ret.getH() == null || ret.getI() == null || ret.getW() == null || ret.getX() == null
  294. || ret.getY() == null || ret.getI() < 0) {
  295. return null;
  296. }
  297. list.add(ret);
  298. }
  299. if (list.size() == 0) {
  300. return null;
  301. }
  302. list.sort(new Comparator<ImageSlice>() {
  303. @Override
  304. public int compare(ImageSlice o1, ImageSlice o2) {
  305. long c1 = o1.getI();
  306. long c2 = o2.getI();
  307. if (c1 < c2) {
  308. return -1;
  309. } else if (c1 > c2) {
  310. return 1;
  311. } else {
  312. return 0;
  313. }
  314. }
  315. });
  316. return list;
  317. } catch (Exception e) {
  318. return null;
  319. }
  320. }
  321. private List<StandardAnswer> getStandardAnswer(String s) {
  322. if (StringUtils.isBlank(s)) {
  323. return null;
  324. }
  325. s = s.trim();
  326. if (StringUtils.isBlank(s)) {
  327. return null;
  328. }
  329. List<StandardAnswer> list = new ArrayList<>();
  330. Matcher matcher = scoreRex.matcher(s);
  331. int start=0;
  332. Double score = null;
  333. while (matcher.find()) {
  334. if(start!=0) {
  335. StandardAnswer a=new StandardAnswer();
  336. list.add(a);
  337. a.setScore(score);
  338. a.setContent(s.substring(start,matcher.start()));
  339. }
  340. try {
  341. score=Double.valueOf(matcher.group(1));
  342. } catch (NumberFormatException e) {
  343. throw new StatusException("分数格式有误");
  344. }
  345. checkScore(score);
  346. start=matcher.end();
  347. }
  348. if(start<s.length()) {
  349. StandardAnswer a=new StandardAnswer();
  350. list.add(a);
  351. a.setScore(score);
  352. checkScore(score);
  353. a.setContent(s.substring(start,s.length()));
  354. }
  355. return list;
  356. }
  357. private void checkScore(Double score) {
  358. if(score==null) {
  359. throw new StatusException("分数不能为空");
  360. }
  361. }
  362. private void saveQuestionBatch(ImportResult ret, List<QuestionEntity> ss) {
  363. if (CollectionUtils.isEmpty(ss)) {
  364. ret.setCountInfo("新增数量:0,更新数量:0");
  365. return;
  366. }
  367. List<QuestionEntity> all = this.list();
  368. Map<String, QuestionEntity> old = new HashMap<>();
  369. Map<String, QuestionEntity> addMap = new HashMap<>();
  370. if (CollectionUtils.isNotEmpty(all)) {
  371. for (QuestionEntity s : all) {
  372. String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getMainNumber() + "-"
  373. + s.getSubNumber();
  374. old.put(key, s);
  375. }
  376. }
  377. List<QuestionEntity> adds = new ArrayList<>();
  378. List<QuestionEntity> updates = new ArrayList<>();
  379. for (QuestionEntity s : ss) {
  380. String key = s.getExamId() + "-" + s.getSubjectCode() + "-" + s.getMainNumber() + "-" + s.getSubNumber();
  381. if (old.get(key) == null) {
  382. QuestionEntity add = addMap.get(key);
  383. if (add != null) {
  384. add.setSubjectName(s.getSubjectName());
  385. add.setFullScore(s.getFullScore());
  386. add.setImageSlice(s.getImageSlice());
  387. add.setContent(s.getContent());
  388. add.setAnswer(s.getAnswer());
  389. } else {
  390. addMap.put(key, s);
  391. adds.add(s);
  392. }
  393. } else {
  394. QuestionEntity up = old.get(key);
  395. up.setSubjectName(s.getSubjectName());
  396. up.setFullScore(s.getFullScore());
  397. up.setImageSlice(s.getImageSlice());
  398. up.setContent(s.getContent());
  399. up.setAnswer(s.getAnswer());
  400. updates.add(up);
  401. }
  402. }
  403. if (CollectionUtils.isNotEmpty(adds)) {
  404. saveBatch(adds);
  405. }
  406. if (CollectionUtils.isNotEmpty(updates)) {
  407. updateBatchById(updates);
  408. }
  409. ret.setCountInfo("新增数量:" + adds.size() + ",更新数量:" + updates.size());
  410. }
  411. @Override
  412. public List<QuestionEntity> findByExamId(Long examId) {
  413. QueryWrapper<QuestionEntity> wrapper = new QueryWrapper<>();
  414. LambdaQueryWrapper<QuestionEntity> lw = wrapper.lambda();
  415. lw.eq(QuestionEntity::getExamId, examId);
  416. lw.orderByAsc(QuestionEntity::getSubjectCode).orderByAsc(QuestionEntity::getMainNumber)
  417. .orderByAsc(QuestionEntity::getSubNumber);
  418. return this.list(wrapper);
  419. }
  420. @Transactional
  421. @Override
  422. public void removeBy(Long examId, String subjectCode) {
  423. QueryWrapper<QuestionEntity> wrapper = new QueryWrapper<>();
  424. LambdaQueryWrapper<QuestionEntity> lw = wrapper.lambda();
  425. if (subjectCode != null) {
  426. lw.eq(QuestionEntity::getSubjectCode, subjectCode);
  427. }
  428. lw.eq(QuestionEntity::getExamId, examId);
  429. this.remove(wrapper);
  430. }
  431. }