PaperBasicInfo

dev-tmp1
luotaiqian 2026-08-20 11:33:27 +08:00
parent 94d60d59b2
commit 7d0a559a84
4 changed files with 122 additions and 0 deletions

View File

@ -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>

View File

@ -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);
}
/**
* PDFpaperExamInfoPDF访
*
* @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);
}
}

View File

@ -107,4 +107,12 @@ public interface PaperService extends IService<PaperDO> {
* @return
*/
PaperExamInfoVO paperExamInfo(Long paperId);
/**
* PDF
*
* @param examInfo +
* @return PDF
*/
byte[] renderExamInfoPdf(PaperExamInfoVO examInfo);
}

View File

@ -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);
}
}
}