之前做了题库的word导入以及在线文本导入功能。其中word导入是采用的Apache POI技术,直接读取文件段落,然后解析成对应题目。由于格式过于固化,且反复修改响应时间较长操作不方便,后面直接由前端实现了在线文本导入功能。可视化展示题目解析效果,方便快捷。
有了导入功能之后,又要增加导出功能,将系统中的题目以word形式导出到文件,并且最好要兼容文本导入的格式。
于是有了以下几种选择:
1、采用前端导出,使用docxtemplater插件。
2、后端使用easypoi模板导出。
3、后端使用apachepoi导出。
经过技术验证
方案一
优点:
1、前端现成组件,开发成本低。
2、导出支持图片。
缺点:
1、实际场景下题目很多,导出很慢,甚至卡死。
方案二
优点:
1、支持模板导出,样式方便调整。
缺点:
1、模板条件表达式仅支持表格下生效,而实际导出效果不需要表格。
2、图片支持情况未验证。
方案三:
优点:
1、直接在代码中编写逻辑,无需处理表达式问题。
2、支持图片导出。
3、速度比较快。
缺点:
1、没有可视化界面直观看效果,调整样式需要修改后端代码。
最终选择了方案三
实现代码:
controller
/** * 导出题目word */ @ApiOperation(value = "导出题目word", notes = "导出题目word") @GetMapping(value = "/exportSubjectsByWord") public void exportSubjectsByWord(@RequestParam("subjectCategory") Integer subjectCategory,@RequestParam(value = "subjectType",required = false) String subjectType, HttpServletResponse response) { try { examSubjectService.exportSubjectsByWord(subjectCategory,subjectType,response); }catch (Exception ex) { LogPrintUtil.formatOutput(ex, "[ExamSubjectController.exportSubjectsByWord]导出题目word错误,联系系统管理员处理!"); } }
service
public void exportSubjectsByWord(Integer subjectCategory,String subjectType,HttpServletResponse response) throws IOException, InvalidFormatException { // 根据分类id查询全部题库数据 List<ExamSubjectSaveDTO> examSubjectSaveDTOS = queryExamSubjectSaveDTOByCategory(subjectCategory,subjectType); // 查询题库 ExamSubjectCategory category = subjectCategoryService.getById(subjectCategory); if(Objects.isNull(category)){ throw new BizException("未找到题库!"); } // 生成文件名称 String fileName = category.getCategoryName() + ".docx"; // 创建一个新的Word文档 XWPFDocument document = new XWPFDocument(); // 生成标题段落 XWPFParagraph paragraphTitle = document.createParagraph(); paragraphTitle.setAlignment(ParagraphAlignment.CENTER); XWPFRun titleRun = paragraphTitle.createRun(); titleRun.setBold(true); titleRun.setText(category.getCategoryName()); // 遍历题目数据添加题目内容段落 for (int i = 0; i < examSubjectSaveDTOS.size(); i++) { ExamSubjectSaveDTO examSubjectSaveDTO = examSubjectSaveDTOS.get(i); generateXWPFParagraphBySaveDTO(examSubjectSaveDTO,document,i,false); } // 输出文件流 try { response.setContentType("application/octet-stream;charset=UTF-8"); OutputStream out = null; ByteArrayOutputStream bout = new ByteArrayOutputStream(); try{ fileName = URLEncoder.encode(fileName, "UTF-8"); fileName = StringUtils.replace(fileName, "+", "%20"); if (fileName.length() > 150) { fileName = new String(fileName.getBytes("GB2312"), "ISO8859-1"); fileName = StringUtils.replace(fileName, " ", "%20"); } document.write(bout); bout.flush(); response.setHeader("Content-disposition", "attachment; filename=" + fileName+ "; " +"size="+bout.size()); response.setCharacterEncoding("UTF-8"); response.setHeader("fileName",fileName); response.setHeader("Access-Control-Expose-Headers", "fileName,Content-disposition"); response.setHeader("fileLength", String.valueOf(bout.size())); out = response.getOutputStream(); out.write(bout.toByteArray()); out.flush(); }catch (Exception ex){ ex.printStackTrace(); log.error(ex.getMessage()); throw new BizException(ex.getMessage()); } finally { if(Objects.nonNull(out)) { out.close(); } bout.close(); document.close(); } } catch (IOException e) { log.info(e.getMessage()); } }
依赖方法
private void generateXWPFParagraphBySaveDTO(ExamSubjectSaveDTO examSubjectSaveDTO,XWPFDocument document,Integer index,Boolean isChild) throws IOException, InvalidFormatException { // 题目内容 String subjectContent = examSubjectSaveDTO.getSubjectContent(); // 题目类型 String subjectType = examSubjectSaveDTO.getSubjectType(); // 题干段落 XWPFParagraph paragraphContent = document.createParagraph(); XWPFRun contentRun = paragraphContent.createRun(); // 题目类型字符串 String subjectTypeString = SubjectTypeEnum.CASE.getType().equals(subjectType) ? "[案例题]" : ""; // 题目索引 String subjectIndex = isChild ? "(" + (index + 1) + ")" : String.valueOf((index + 1)); // 拼接题干 题目索引.题目类型 题干内容 contentRun.setText(subjectIndex + "." + subjectTypeString + subjectContent); // 获取附件 List<AttachInfo> examSubjectAttachs = Optional.ofNullable(examSubjectSaveDTO.getExamSubjectAttach()).orElse(new ArrayList<>()); try { // 图片段落 if(CollectionUtils.isNotEmpty(examSubjectAttachs)){ for (AttachInfo examSubjectAttach : examSubjectAttachs) { // 获取图片路径 String imagePath = uploadBasePath + examSubjectAttach.getPathName(); // 获取图片的宽高 BufferedImage bufferedImage = ImageIO.read(new FileInputStream(imagePath)); // 获取字节 byte[] imageBytes = IOUtils.toByteArray(new FileInputStream(imagePath)); int pictureType = Document.PICTURE_TYPE_PNG; switch (examSubjectAttach.getFileType()) { case "jpg": pictureType = Document.PICTURE_TYPE_JPEG; break; case "jpeg": pictureType = Document.PICTURE_TYPE_JPEG; break; case "png": pictureType = Document.PICTURE_TYPE_PNG; break; } XWPFParagraph paragraphImg = document.createParagraph(); paragraphImg.setAlignment(ParagraphAlignment.CENTER); XWPFRun runImg = paragraphImg.createRun(); runImg.addPicture(new ByteArrayInputStream(imageBytes), pictureType, examSubjectAttach.getNickName(), Units.toEMU(bufferedImage.getWidth()), Units.toEMU(bufferedImage.getHeight())); } } }catch (Exception e){ LogPrintUtil.printout(e,e.getMessage()); } // 子题目 List<ExamSubjectSaveDTO> children = Optional.ofNullable(examSubjectSaveDTO.getChildren()).orElse(new ArrayList<>()); // 不为空 则按照子题目段落处理 if(CollectionUtils.isNotEmpty(children)){ // 子题目段落 for (int j = 0; j < children.size(); j++) { ExamSubjectSaveDTO childSaveDTO = children.get(j); // 处理子题目 generateXWPFParagraphBySaveDTO(childSaveDTO,document,j,true); } } else { // 获取题目选项 String subjectOptions = examSubjectSaveDTO.getSubjectOptions(); // 有选项且是选择题 if(StringUtils.isNotBlank(subjectOptions) && (SubjectTypeEnum.RADIO.getType().equals(subjectType) || SubjectTypeEnum.MULTIPLE.getType().equals(subjectType))){ // 拆分数组 List<String> subjectOptionsList = new ArrayList<String>(StrHelper.str2ArrayListBySplit(subjectOptions,",")); // 遍历选项 for (String option : subjectOptionsList) { // 替换符号 String tempOption = option.replace("&","、"); // 选项段落 XWPFParagraph paragraphOption = document.createParagraph(); XWPFRun optionRun = paragraphOption.createRun(); optionRun.setText(tempOption); } } // 获取题目答案 String subjectAnswer = examSubjectSaveDTO.getSubjectAnswer(); if(StringUtils.isNotBlank(subjectAnswer)){ // 按照逗号拆分 List<String> subjectAnswerList =new ArrayList<String>(StrHelper.str2ArrayListBySplit(subjectAnswer,",")); // 选择题和判断题 答案关键词与内容在一行 if(SubjectTypeEnum.RADIO.getType().equals(subjectType) || SubjectTypeEnum.MULTIPLE.getType().equals(subjectType) || SubjectTypeEnum.JUDGE.getType().equals(subjectType)){ // 答案段落 XWPFParagraph paragraphAnswer = document.createParagraph(); XWPFRun answerRun = paragraphAnswer.createRun(); answerRun.setText("答案:"+ StringUtils.join(subjectAnswerList,"、")); // 其他类型 需要换行显示 } else { // 答案段落 XWPFParagraph paragraphAnswer = document.createParagraph(); XWPFRun answerRun = paragraphAnswer.createRun(); answerRun.setText("答案:"); // 关键词 for (String answerKey : subjectAnswerList) { // 答案段落 XWPFParagraph paragraphAnswerKey = document.createParagraph(); XWPFRun answerKeyRun = paragraphAnswerKey.createRun(); answerKeyRun.setText(answerKey); } } } // 获取答案解析 String subjectAnswerAnalysis = examSubjectSaveDTO.getSubjectAnswerAnalysis(); if(StringUtils.isNotBlank(subjectAnswerAnalysis)){ // 解析段落 XWPFParagraph paragraphAnswerAnalysis = document.createParagraph(); XWPFRun answerAnalysisRun = paragraphAnswerAnalysis.createRun(); answerAnalysisRun.setText("解析:"+subjectAnswerAnalysis); } } // 空行段落 XWPFParagraph paragraphBlank = document.createParagraph(); XWPFRun blankRun = paragraphBlank.createRun(); blankRun.setText(""); }
发表评论