fix: word转PDF内置宋体资源并显式配置aspose字体源

- 内置 simsun.ttf 资源,首次转换解压到临时字体目录,
  与系统字体目录一起交给 aspose,服务器无中文字体也能正常转换
- 字体配置线程安全(双重检查),按文档设置字体设置
- 学生/教师参考答案预览链路适配
main
chenyuan 5 hours ago
parent 2ca158d330
commit 9e4decf8cc

@ -29,6 +29,8 @@ import com.sztzjy.linkCommerce.util.pdfUtils.PDFConvertUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
@ -51,6 +53,8 @@ import java.util.stream.Collectors;
@RequestMapping("api/weight")
@RestController
public class TeaScoreController {
private static final Logger log = LoggerFactory.getLogger(TeaScoreController.class);
@Autowired
WeightMapper weightMapper;
@Autowired
@ -265,10 +269,16 @@ public class TeaScoreController {
public ResponseEntity<byte[]> previewTaskReferenceAnswer(@RequestParam String classId,
@RequestParam String taskId,
HttpServletRequest request) {
long startAt = System.currentTimeMillis();
JwtUser operator = currentUser(request);
try {
teacherScoreService.requireAccessibleClass(currentUser(request), classId);
teacherScoreService.requireAccessibleClass(operator, classId);
TrainingTask task = trainingTaskMapper.selectByPrimaryKey(taskId);
if (task == null) return ResponseEntity.notFound().build();
if (task == null) {
log.warn("参考答案预览失败:任务不存在 classId={} taskId={} userId={}", classId, taskId,
operator == null ? null : operator.getUserId());
return ResponseEntity.notFound().build();
}
TrainingTaskClassConfig classConfig = trainingTaskClassConfigMapper == null ? null
: trainingTaskClassConfigMapper.selectByTeachingClassAndTaskKey(classId, task.getTaskKey());
String referenceUrl = classConfig != null && StringUtils.isNotBlank(classConfig.getReferenceAnswerUrl())
@ -276,6 +286,8 @@ public class TeaScoreController {
String referenceName = classConfig != null && StringUtils.isNotBlank(classConfig.getReferenceAnswerName())
? classConfig.getReferenceAnswerName() : task == null ? null : task.getReferenceAnswerName();
if (StringUtils.isBlank(referenceUrl)) {
log.warn("参考答案预览失败:未配置参考答案 classId={} taskId={} taskKey={} userId={}",
classId, taskId, task.getTaskKey(), operator == null ? null : operator.getUserId());
return ResponseEntity.notFound().build();
}
String name = StringUtils.defaultIfBlank(referenceName, referenceUrl);
@ -283,19 +295,41 @@ public class TeaScoreController {
int slash = path.indexOf("/file/");
if (slash >= 0) path = path.substring(slash + 1);
File source = new File(fileUtil.getFullPath(path));
if (!source.exists() || !PDFConvertUtil.isCreatePdf(name)) return ResponseEntity.notFound().build();
if (!source.exists() || !PDFConvertUtil.isCreatePdf(name)) {
log.warn("参考答案预览失败:文件不存在或类型不支持转换 classId={} taskKey={} userId={} name={} url={} path={} exists={}",
classId, task.getTaskKey(), operator == null ? null : operator.getUserId(),
name, referenceUrl, source.getAbsolutePath(), source.exists());
return ResponseEntity.notFound().build();
}
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (InputStream input = Files.newInputStream(source.toPath())) {
if (!PDFConvertUtil.convertToPDFByFileNameSuffix(name, input, output)) return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build();
}
if (!PDFConvertUtil.convertToPDFByFileNameSuffix(name, input, output)) {
log.warn("参考答案预览失败:文档转换未成功 classId={} taskKey={} userId={} name={} path={}",
classId, task.getTaskKey(), operator == null ? null : operator.getUserId(), name, source.getAbsolutePath());
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build();
}
}
log.info("参考答案预览成功 classId={} taskKey={} userId={} name={} pdfBytes={} elapsed={}ms",
classId, task.getTaskKey(), operator == null ? null : operator.getUserId(),
name, output.size(), System.currentTimeMillis() - startAt);
return ResponseEntity.ok().contentType(MediaType.APPLICATION_PDF).header("Content-Disposition", "inline; filename=reference-answer.pdf").body(output.toByteArray());
} catch (com.sztzjy.linkCommerce.config.exception.handler.ServiceException e) {
log.warn("参考答案预览失败:权限或参数校验未通过 classId={} taskId={} userId={} status={} reason={}",
classId, taskId, operator == null ? null : operator.getUserId(), e.getCode(), e.getMessage());
return ResponseEntity.status(e.getCode()).build();
} catch (RuntimeException | java.io.IOException e) {
log.warn("参考答案预览失败:转换异常 classId={} taskId={} userId={} name={}",
classId, taskId, operator == null ? null : operator.getUserId(),
StringUtils.defaultIfBlank(currentTaskKey(taskId), "-"), e);
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).build();
}
}
private String currentTaskKey(String taskId) {
TrainingTask task = trainingTaskMapper == null ? null : trainingTaskMapper.selectByPrimaryKey(taskId);
return task == null ? null : task.getTaskKey();
}
@PostMapping("/batchSaveTaskTeacherScore")
@ApiOperation("批量保存教师任务评分")
@Transactional

@ -17,7 +17,11 @@ import org.slf4j.LoggerFactory;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -35,6 +39,8 @@ import static com.sztzjy.linkCommerce.util.pdfUtils.PDFConvertUtil.TypeEnum.*;
public final class PDFConvertUtil {
private static final Logger log = LoggerFactory.getLogger(PDFConvertUtil.class);
private static final String BUNDLED_FONT_RESOURCE = "/simsun.ttf";
private static volatile boolean chineseFontsConfigured;
final private static Map<TypeEnum, List<String>> types = new HashMap<>();
@ -130,17 +136,10 @@ public final class PDFConvertUtil {
return false;
}
try {
//此处处理乱码和小方块
//如果在本地运行,此处报错,请注释这个这是字体,主要是为了解决linux环境下面运行jar时找不到中文字体的问题
//指定文件库内容路径
// 字体目录同时包含 Windows 系统字体与 Linux 常见目录:
// setFontsFolders 会替换 aspose 的默认字体源,漏掉 Windows 字体目录时
// 宋体等中文字体不可见,转换出的 PDF 中文全部变成方框
FontSettings.getDefaultInstance().setFontsFolders(
new String[] {"C:\\Windows\\Fonts", "/usr/share/fonts", "/usr/share/fonts/win"}
, true);
FontSettings fontSettings = configureChineseFonts();
// 将源文件保存在com.aspose.words.Document中,具体的转换格式依靠里面的save方法
com.aspose.words.Document doc = new com.aspose.words.Document(inputStream);
doc.setFontSettings(fontSettings);
// 全面支持DOC, DOCX, OOXML, RTF HTML, OpenDocument, PDF,EPUB, XPS, SWF 相互转换
doc.save(outputStream, SaveFormat.PDF);
@ -156,6 +155,67 @@ public final class PDFConvertUtil {
return true;
}
/**
* 配置 Word 转 PDF 使用的中文字体。
*
* 服务器通常没有 Windows 的宋体,单纯扫描系统字体目录会导致 DOC/DOCX
* 中的中文被替换成方框或乱码。项目内置 simsun.ttf,首次转换时解压到
* 临时字体目录,并和常见系统字体目录一起交给 Aspose。
*/
private static FontSettings configureChineseFonts() {
FontSettings settings = FontSettings.getDefaultInstance();
if (chineseFontsConfigured) {
return settings;
}
synchronized (PDFConvertUtil.class) {
if (chineseFontsConfigured) {
return settings;
}
List<String> fontFolders = new ArrayList<>();
addExistingFontFolder(fontFolders, "C:\\Windows\\Fonts");
addExistingFontFolder(fontFolders, "/usr/share/fonts");
addExistingFontFolder(fontFolders, "/usr/local/share/fonts");
addExistingFontFolder(fontFolders, "/usr/share/fonts/chinese");
addExistingFontFolder(fontFolders, "/usr/share/fonts/win");
try {
Path bundledFontFolder = Paths.get(System.getProperty("java.io.tmpdir"), "link-commerce-fonts");
Files.createDirectories(bundledFontFolder);
Path bundledFont = bundledFontFolder.resolve("simsun.ttf");
if (!Files.exists(bundledFont) || Files.size(bundledFont) == 0) {
try (InputStream input = PDFConvertUtil.class.getResourceAsStream(BUNDLED_FONT_RESOURCE)) {
if (input == null) {
throw new FileNotFoundException("Missing bundled font " + BUNDLED_FONT_RESOURCE);
}
Files.copy(input, bundledFont, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
}
}
addExistingFontFolder(fontFolders, bundledFontFolder.toString());
} catch (IOException | SecurityException e) {
log.warn("内置中文字体加载失败,将继续使用系统字体目录", e);
}
if (!fontFolders.isEmpty()) {
settings.setFontsFolders(fontFolders.toArray(new String[0]), true);
}
settings.setDefaultFontName("SimSun");
settings.setFontSubstitutes("宋体", "SimSun", "Noto Sans CJK SC", "Microsoft YaHei");
settings.setFontSubstitutes("等线", "SimSun", "Noto Sans CJK SC", "Microsoft YaHei");
settings.setFontSubstitutes("微软雅黑", "SimSun", "Noto Sans CJK SC", "Microsoft YaHei");
settings.setFontSubstitutes("黑体", "SimSun", "Noto Sans CJK SC", "Microsoft YaHei");
settings.setFontSubstitutes("仿宋", "SimSun", "Noto Sans CJK SC", "Microsoft YaHei");
chineseFontsConfigured = true;
return settings;
}
}
private static void addExistingFontFolder(List<String> folders, String folder) {
if (folder != null && Files.isDirectory(Paths.get(folder))) {
folders.add(folder);
}
}
/**
* 关闭输出流
* @param outputStream
@ -552,4 +612,4 @@ public final class PDFConvertUtil {
String[] split = fileName.split("\\.");
return split[split.length - 1];
}
}
}

Loading…
Cancel
Save