PaperBasicInfo
parent
94d60d59b2
commit
7d0a559a84
|
|
@ -43,6 +43,13 @@
|
|||
<artifactId>easyexcel</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- OpenPDF:试卷PDF导出(核心jar内置中文CJK字体cmaps) -->
|
||||
<dependency>
|
||||
<groupId>com.github.librepdf</groupId>
|
||||
<artifactId>openpdf</artifactId>
|
||||
<version>1.3.34</version>
|
||||
</dependency>
|
||||
|
||||
<!-- JSR-303 校验 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
|
|
|||
|
|
@ -11,9 +11,16 @@ import org.qinan.cedu.service.PaperService;
|
|||
import org.qinan.cedu.service.QuestionService;
|
||||
import org.qinan.safetyeval.client.dto.PageResponse;
|
||||
import org.qinan.safetyeval.client.dto.SingleResponse;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/**
|
||||
* 试卷管理
|
||||
*/
|
||||
|
|
@ -85,4 +92,23 @@ public class PaperController {
|
|||
PaperExamInfoVO pageInfo = paperService.paperExamInfo(paperId);
|
||||
return SingleResponse.success(pageInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载试卷PDF:查询试卷考试信息(同paperExamInfo)后渲染为PDF文件流,浏览器直接访问即触发下载
|
||||
*
|
||||
* @param paperId 试卷id
|
||||
* @return PDF文件字节流(文件名为试卷名称)
|
||||
*/
|
||||
@ApiOperation("下载试卷PDF")
|
||||
@GetMapping(value = "/paper/exam/download", produces = MediaType.APPLICATION_PDF_VALUE)
|
||||
public ResponseEntity<byte[]> downloadPaperPdf(@RequestParam("paperId") @ApiParam(value = "试卷id", required = true) Long paperId) throws IOException {
|
||||
PaperExamInfoVO examInfo = paperService.paperExamInfo(paperId);
|
||||
byte[] pdfBytes = paperService.renderExamInfoPdf(examInfo);
|
||||
// RFC5987编码文件名,避免中文试卷名称乱码
|
||||
String fileName = URLEncoder.encode(examInfo.getPaperBasicInfoVO().getPaperName() + ".pdf", StandardCharsets.UTF_8.name());
|
||||
return ResponseEntity.ok()
|
||||
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename*=UTF-8''" + fileName)
|
||||
.contentType(MediaType.APPLICATION_PDF)
|
||||
.body(pdfBytes);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,4 +107,12 @@ public interface PaperService extends IService<PaperDO> {
|
|||
* @return 试卷考试信息
|
||||
*/
|
||||
PaperExamInfoVO paperExamInfo(Long paperId);
|
||||
|
||||
/**
|
||||
* 将试卷考试信息渲染为PDF字节数组(用于试卷下载)
|
||||
*
|
||||
* @param examInfo 试卷考试信息(试卷基础信息+试题列表含选项)
|
||||
* @return PDF文件字节数组
|
||||
*/
|
||||
byte[] renderExamInfoPdf(PaperExamInfoVO examInfo);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,14 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
|||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.baomidou.mybatisplus.extension.toolkit.Db;
|
||||
import com.lowagie.text.Document;
|
||||
import com.lowagie.text.DocumentException;
|
||||
import com.lowagie.text.Element;
|
||||
import com.lowagie.text.Font;
|
||||
import com.lowagie.text.PageSize;
|
||||
import com.lowagie.text.Paragraph;
|
||||
import com.lowagie.text.pdf.BaseFont;
|
||||
import com.lowagie.text.pdf.PdfWriter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.qinan.cedu.convertor.PaperConvertor;
|
||||
|
|
@ -45,6 +53,8 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
|
@ -372,4 +382,75 @@ public class PaperServiceImpl extends ServiceImpl<PaperMapper, PaperDO> implemen
|
|||
questionVOList.forEach(question -> question.setQuestionOptionVOList(
|
||||
optionMap.getOrDefault(Long.valueOf(question.getId()), new ArrayList<>())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] renderExamInfoPdf(PaperExamInfoVO examInfo) {
|
||||
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
||||
Document document = new Document(PageSize.A4, 50, 50, 50, 50);
|
||||
PdfWriter.getInstance(document, out);
|
||||
document.open();
|
||||
// STSong-Light 为阅读器内置中文字体(cmaps已内置于openpdf jar),无需嵌入字体文件
|
||||
BaseFont baseFont = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
|
||||
addPaperPdfHeader(document, baseFont, examInfo.getPaperBasicInfoVO());
|
||||
if (!CollectionUtils.isEmpty(examInfo.getQuestionVOList())) {
|
||||
int questionNum = 1;
|
||||
for (QuestionVO question : examInfo.getQuestionVOList()) {
|
||||
addPdfQuestion(document, baseFont, questionNum++, question);
|
||||
}
|
||||
}
|
||||
document.close();
|
||||
return out.toByteArray();
|
||||
} catch (DocumentException | IOException e) {
|
||||
throw new IllegalStateException("试卷PDF渲染失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染试卷PDF头部:居中试卷名称 + 居中满分行
|
||||
*
|
||||
* @param document PDF文档
|
||||
* @param baseFont 中文字体
|
||||
* @param basicInfo 试卷基础信息
|
||||
* @throws DocumentException 渲染异常
|
||||
*/
|
||||
private void addPaperPdfHeader(Document document, BaseFont baseFont, PaperBasicInfoVO basicInfo) throws DocumentException {
|
||||
Paragraph title = new Paragraph(basicInfo.getPaperName(), new Font(baseFont, 18, Font.BOLD));
|
||||
title.setAlignment(Element.ALIGN_CENTER);
|
||||
title.setSpacingAfter(8f);
|
||||
document.add(title);
|
||||
String totalScore = basicInfo.getPaperTotalScore() == null ? "0"
|
||||
: basicInfo.getPaperTotalScore().stripTrailingZeros().toPlainString();
|
||||
Paragraph scoreLine = new Paragraph("(满分:" + totalScore + " 分)", new Font(baseFont, 12, Font.NORMAL));
|
||||
scoreLine.setAlignment(Element.ALIGN_CENTER);
|
||||
scoreLine.setSpacingAfter(10f);
|
||||
document.add(scoreLine);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染单道试题:题干(题型+序号+分值)及其选项列表
|
||||
*
|
||||
* @param document PDF文档
|
||||
* @param baseFont 中文字体
|
||||
* @param questionNum 题目序号(从1开始)
|
||||
* @param question 试题信息
|
||||
* @throws DocumentException 渲染异常
|
||||
*/
|
||||
private void addPdfQuestion(Document document, BaseFont baseFont, int questionNum, QuestionVO question) throws DocumentException {
|
||||
QuestionTypeEnum typeEnum = QuestionTypeEnum.fromCode(question.getQuestionType());
|
||||
String typeLabel = typeEnum == null ? question.getQuestionType() : typeEnum.getDesc() + "题";
|
||||
String score = question.getScore() == null ? "0" : question.getScore().toPlainString();
|
||||
Paragraph questionLine = new Paragraph("(" + typeLabel + ")" + questionNum + "." + question.getTitle()
|
||||
+ "(题目分值:" + score + ")", new Font(baseFont, 10, Font.NORMAL));
|
||||
questionLine.setSpacingBefore(8f);
|
||||
document.add(questionLine);
|
||||
if (CollectionUtils.isEmpty(question.getQuestionOptionVOList())) {
|
||||
return;
|
||||
}
|
||||
for (QuestionOptionVO option : question.getQuestionOptionVOList()) {
|
||||
Paragraph optionLine = new Paragraph(option.getOptionKey() + ":" + option.getOptionText(),
|
||||
new Font(baseFont, 10, Font.NORMAL));
|
||||
optionLine.setIndentationLeft(20f);
|
||||
document.add(optionLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue