init: WordFileParse docx→HTML 解析服务(含 Word EQ 域公式修复)

服务:Spring Boot 3.2 + docx4j 8.3.9,POST /api/v3/server_office_file/file/parse
      接受 .docx,返回 HTML(公式 → LaTeX,图片 → RustFS 外链)

公式支持三类来源:
- OMML(Word 2007+ 公式)      → Python omml_mathml2latex
- MathType OLE 对象            → Ruby mathtype2mathml → MathML → LaTeX
- Word EQ 域(老教辅主力)      → EqToLatexUtil(本次新增)

本次改动:
1. 新增 java-svc/.../core/EqToLatexUtil.java:
   移植自 jby backend-services office-core,EQ 语法 → LaTeX
   (\f→\dfrac、\r→\sqrt、\i→\int/\sum/\prod、\s→^{}/_{}、\b→\left\right、
     \a→array、\x→\boxed/\overline、\o\al 堆叠、圈码、化学符号、极限、连等式)
   品牌标记 JBYU* → WPU*,与采集端一致
2. CtoMathFinder:接上 EQ 分支(原为 not-ported 桩),域代码经
   EqToLatexUtil.convert 转 LaTeX 写入 eqLatexMap(WPEQNUM 占位符)
3. WordParseUtil.dealPlaceholder:EQ 的 LaTeX 同样加 \( \) 包裹
   (原先因 WPEQNUM 例外不加包裹,前端/MathJax 不渲染)

修前症状:教辅 Word 里分式/方程组/上下标整段丢失(如 {−1/3, 1} 变成 {-,1})
修后实测:46 份教辅 2908 处 EQ 域全部还原,公式总数 2954 处、零未包裹残留

安全:application.yml(含 RustFS 密钥)不入库,提供 application.yml.example;
      OssService 的 @Value 默认密钥已清空
This commit is contained in:
2026-09-29 09:43:13 +08:00
commit 3271fee5cc
360 changed files with 86082 additions and 0 deletions

View File

@@ -0,0 +1,152 @@
/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* com.alibaba.fastjson.JSON
* com.jby.common.advisor.annotation.ParamBarrier
* com.jby.common.result.Result
* com.jby.office.api.FileServiceApi
* com.jby.office.domain.param.DownloadFileParam
* com.jby.office.domain.param.DownloadParam
* com.jby.office.domain.param.FileParam
* com.jby.office.domain.param.GenerateFileParam
* com.jby.office.domain.param.OfficeFileParseParam
* com.jby.office.domain.param.OfficeFileParseRecordParam
* com.jby.office.domain.vo.OfficeFileDownloadScheduleVO
* com.jby.office.domain.vo.OfficeFileInfoVO
* com.jby.office.domain.vo.OfficeFileParseRecordVO
* com.jby.office.domain.vo.OfficeFileParseVO
* com.jby.officecore.annotation.CostTime
* com.jby.officecore.domain.dto.CreateWordDTO
* com.jby.officecore.utils.WordExportUtil
* com.jby.system.domain.param.NodeParam
* javax.annotation.Resource
* org.map.utils.BeanMapping
* org.slf4j.Logger
* org.slf4j.LoggerFactory
* org.springframework.web.bind.annotation.GetMapping
* org.springframework.web.bind.annotation.PathVariable
* org.springframework.web.bind.annotation.PostMapping
* org.springframework.web.bind.annotation.RequestBody
* org.springframework.web.bind.annotation.RequestMapping
* org.springframework.web.bind.annotation.RestController
*/
package com.jby.officefile.controller;
import com.alibaba.fastjson.JSON;
import com.jby.common.advisor.annotation.ParamBarrier;
import com.jby.common.result.Result;
import com.jby.office.api.FileServiceApi;
import com.jby.office.domain.param.DownloadFileParam;
import com.jby.office.domain.param.DownloadParam;
import com.jby.office.domain.param.FileParam;
import com.jby.office.domain.param.GenerateFileParam;
import com.jby.office.domain.param.OfficeFileParseParam;
import com.jby.office.domain.param.OfficeFileParseRecordParam;
import com.jby.office.domain.vo.OfficeFileDownloadScheduleVO;
import com.jby.office.domain.vo.OfficeFileInfoVO;
import com.jby.office.domain.vo.OfficeFileParseRecordVO;
import com.jby.office.domain.vo.OfficeFileParseVO;
import com.jby.officecore.annotation.CostTime;
import com.jby.officecore.domain.dto.CreateWordDTO;
import com.jby.officecore.utils.WordExportUtil;
import com.jby.officefile.service.FileService;
import com.jby.system.domain.param.NodeParam;
import java.util.List;
import java.util.Objects;
import javax.annotation.Resource;
import org.map.utils.BeanMapping;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping(value={"/file"})
public class FileController
implements FileServiceApi {
private static final Logger log = LoggerFactory.getLogger(FileController.class);
@Resource
private FileService fileService;
@CostTime
@PostMapping(value={"/generate/by/url"})
public Result<OfficeFileInfoVO> generateFileByUrl(@RequestBody GenerateFileParam param) {
log.info("\u901a\u8fc7word\u8d44\u6e90\u5730\u5740\u751f\u6210\u5728\u7ebf\u7f16\u8f91\u6587\u4ef6(\u5b8c\u6574\u6587\u6863), param={}", (Object)JSON.toJSONString((Object)param));
OfficeFileInfoVO result = this.fileService.generateFileByUrl(param);
return Result.success((Object)result);
}
@CostTime
@PostMapping(value={"/generate/by/file"}, consumes={"multipart/form-data"})
public Result<OfficeFileInfoVO> generateFileByFile(FileParam param) {
log.info("\u901a\u8fc7word\u8d44\u6e90\u6587\u4ef6\u751f\u6210\u5728\u7ebf\u7f16\u8f91\u6587\u4ef6(\u5b8c\u6574\u6587\u6863), param={}", (Object)JSON.toJSONString((Object)param));
OfficeFileInfoVO result = this.fileService.generateFileByFile(param);
return Result.success((Object)result);
}
@CostTime
@PostMapping(value={"/parse"}, consumes={"multipart/form-data"})
public Result<OfficeFileParseVO> parseOfficeFile(OfficeFileParseParam param) {
OfficeFileParseVO parseVO = this.fileService.parseOfficeFile(param);
return Result.success((Object)parseVO);
}
@CostTime
@PostMapping(value={"/download"}, consumes={"multipart/form-data"})
public Result<String> downloadOfficeFile(DownloadFileParam param) {
log.info("\u4e0b\u8f7d\u5728\u7ebf\u7f16\u8f91\u6587\u4ef6, file={}, param={}", (Object)Objects.nonNull(param.getPdfFile()), (Object)JSON.toJSONString((Object)param));
String fileUrl = this.fileService.downloadOfficeFile(param);
return Result.success((Object)fileUrl);
}
@CostTime
@PostMapping(value={"/v2/download"})
public Result<String> downloadFile(@RequestBody DownloadParam param) {
log.info("\u4e0b\u8f7d\u5728\u7ebf\u7f16\u8f91\u6587\u4ef6V2, param={}", (Object)JSON.toJSONString((Object)param));
DownloadFileParam fileParam = (DownloadFileParam)BeanMapping.toBean((Object)param, DownloadFileParam.class);
String fileUrl = this.fileService.downloadOfficeFile(fileParam);
return Result.success((Object)fileUrl);
}
@Deprecated
@CostTime
@PostMapping(value={"/batch/download"})
public Result<OfficeFileDownloadScheduleVO> batchDownloadOfficeFile(@RequestBody NodeParam rootNode) {
log.info("\u6279\u91cf\u4e0b\u8f7d\u5728\u7ebf\u6587\u4ef6, \u8282\u70b9\u53c2\u6570rootNode={}", (Object)JSON.toJSONString((Object)rootNode));
OfficeFileDownloadScheduleVO result = this.fileService.batchDownloadOfficeFile(rootNode);
return Result.success((Object)result);
}
@CostTime
@PostMapping(value={"/download/word"})
public Result<String> downloadWord(@RequestBody CreateWordDTO param) {
log.info("\u4e0b\u8f7dword\u6587\u4ef6, param={}", (Object)JSON.toJSONString((Object)param));
String result = WordExportUtil.createWord((CreateWordDTO)param);
return Result.success((Object)result);
}
@GetMapping(value={"/parse/record/{md5}"})
public Result<OfficeFileParseRecordVO> getOfficeFileParseRecordByMd5(@PathVariable String md5) {
OfficeFileParseRecordVO result = this.fileService.getOfficeFileParseRecordByMd5(md5);
return Result.success((Object)result);
}
@PostMapping(value={"/parse/record"})
public Result<?> saveOfficeFileParseRecord(@RequestBody OfficeFileParseRecordParam param) {
this.fileService.saveOfficeFileParseRecord(param);
return Result.success();
}
@PostMapping(value={"/parse/record/query"})
@ParamBarrier(value={"fileIds"})
public Result<List<OfficeFileParseRecordVO>> queryFileUrlByFileIds(@RequestBody OfficeFileParseRecordParam param) {
List<OfficeFileParseRecordVO> result = this.fileService.queryFileUrlByFileIds(param);
return Result.success(result);
}
}