docs: 完善单位转换系统使用文档,添加Feign跨模块调用示例
- 补充Feign客户端接口定义示例 - 补充跨服务调用的具体实现示例 - 修正常见问题Q1中的API端点路径为正确的 /unt-info/page 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
package com.zt.plat.module.base.service.doctemplate;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 文档渲染API服务
|
||||
* 供业务模块调用,支持多种渲染方式
|
||||
*
|
||||
* @author system
|
||||
*/
|
||||
public interface DocumentRenderApiService {
|
||||
|
||||
/**
|
||||
* 根据模板ID渲染 (方式1:直接模板渲染)
|
||||
*
|
||||
* @param templateId 模板ID
|
||||
* @param dataMap 数据Map
|
||||
* @return 渲染后的HTML
|
||||
*/
|
||||
String renderByTemplate(Long templateId, Map<String, Object> dataMap);
|
||||
|
||||
/**
|
||||
* 根据实例ID渲染 (方式2:实例渲染)
|
||||
* 优先使用实例的editedContent,如果为空则使用模板内容
|
||||
*
|
||||
* @param instanceId 实例ID
|
||||
* @param dataMap 数据Map
|
||||
* @return 渲染后的HTML
|
||||
*/
|
||||
String renderByInstance(Long instanceId, Map<String, Object> dataMap);
|
||||
|
||||
/**
|
||||
* 根据业务类型渲染 (方式3:业务接入渲染)
|
||||
* 业务系统可根据业务类型自定义数据集和渲染逻辑
|
||||
*
|
||||
* @param instanceId 实例ID
|
||||
* @param businessType 业务类型 (如: 'PURCHASE_ORDER', 'SALES_ORDER' 等)
|
||||
* @param businessDataMap 业务数据Map (由业务系统自己组织)
|
||||
* @return 渲染后的HTML
|
||||
*/
|
||||
String renderByBusinessType(Long instanceId, String businessType, Map<String, Object> businessDataMap);
|
||||
|
||||
/**
|
||||
* 根据直接内容渲染 (方式4:前端预览)
|
||||
* 用于前端编辑时的实时预览,使用标签默认值
|
||||
*
|
||||
* @param content 模板内容 (HTML/Velocity语法)
|
||||
* @param dataMap 数据Map (标签默认值)
|
||||
* @return 渲染后的HTML
|
||||
*/
|
||||
String renderByContent(String content, Map<String, Object> dataMap);
|
||||
|
||||
/**
|
||||
* 将HTML导出为Word文档
|
||||
*
|
||||
* @param html HTML内容
|
||||
* @param fileName 文件名 (不需要后缀,自动添加.docx)
|
||||
* @return Word文件字节数组
|
||||
*/
|
||||
byte[] exportToWord(String html, String fileName);
|
||||
|
||||
/**
|
||||
* 渲染并导出为Word (一步完成)
|
||||
*
|
||||
* @param instanceId 实例ID
|
||||
* @param dataMap 数据Map
|
||||
* @param fileName 导出文件名
|
||||
* @return Word文件字节数组
|
||||
*/
|
||||
byte[] renderAndExportToWord(Long instanceId, Map<String, Object> dataMap, String fileName);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
package com.zt.plat.module.base.service.doctemplate;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.zt.plat.module.base.dal.dataobject.doctemplate.DocTemplateDO;
|
||||
import com.zt.plat.module.base.dal.dataobject.doctemplate.DocTemplateInstanceDO;
|
||||
import com.zt.plat.module.base.dal.dao.doctemplate.DocTemplateMapper;
|
||||
import com.zt.plat.module.base.dal.dao.doctemplate.DocTemplateInstanceMapper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.docx4j.Docx4J;
|
||||
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
|
||||
import org.docx4j.wml.*;
|
||||
import org.jsoup.Jsoup;
|
||||
import org.jsoup.nodes.Document;
|
||||
import org.jsoup.nodes.Element;
|
||||
import org.jsoup.nodes.Node;
|
||||
import org.jsoup.nodes.TextNode;
|
||||
import org.jsoup.select.Elements;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static com.zt.plat.module.base.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
* 文档渲染API服务实现类
|
||||
* 使用 docx4j 库处理 Word 导出,支持多种渲染方式
|
||||
*
|
||||
* @author system
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DocumentRenderApiServiceImpl implements DocumentRenderApiService {
|
||||
|
||||
@Resource
|
||||
private DocTemplateRenderService templateRenderService;
|
||||
|
||||
@Resource
|
||||
private DocTemplateMapper templateMapper;
|
||||
|
||||
@Resource
|
||||
private DocTemplateInstanceMapper instanceMapper;
|
||||
|
||||
@Override
|
||||
public String renderByTemplate(Long templateId, Map<String, Object> dataMap) {
|
||||
if (templateId == null) {
|
||||
throw new IllegalArgumentException("模板ID不能为空");
|
||||
}
|
||||
DocTemplateDO template = templateMapper.selectById(templateId);
|
||||
if (template == null) {
|
||||
throw exception(TEMPLATE_NOT_EXISTS);
|
||||
}
|
||||
return templateRenderService.render(templateId, null, null, dataMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renderByInstance(Long instanceId, Map<String, Object> dataMap) {
|
||||
if (instanceId == null) {
|
||||
throw new IllegalArgumentException("实例ID不能为空");
|
||||
}
|
||||
DocTemplateInstanceDO instance = instanceMapper.selectById(instanceId);
|
||||
if (instance == null) {
|
||||
throw exception(TEMPLATE_INSTANCE_NOT_EXISTS);
|
||||
}
|
||||
return templateRenderService.render(null, instanceId, null, dataMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renderByBusinessType(Long instanceId, String businessType, Map<String, Object> businessDataMap) {
|
||||
if (instanceId == null) {
|
||||
throw new IllegalArgumentException("实例ID不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(businessType)) {
|
||||
throw new IllegalArgumentException("业务类型不能为空");
|
||||
}
|
||||
|
||||
// 获取实例信息
|
||||
DocTemplateInstanceDO instance = instanceMapper.selectById(instanceId);
|
||||
if (instance == null) {
|
||||
throw exception(TEMPLATE_INSTANCE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
// 业务系统自己组织的dataMap,可以包含SQL查询结果、业务数据等
|
||||
// 直接使用该dataMap进行渲染
|
||||
if (businessDataMap == null || businessDataMap.isEmpty()) {
|
||||
log.warn("业务数据集为空,instanceId: {}, businessType: {}", instanceId, businessType);
|
||||
businessDataMap = Map.of();
|
||||
}
|
||||
|
||||
return templateRenderService.render(null, instanceId, null, businessDataMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renderByContent(String content, Map<String, Object> dataMap) {
|
||||
if (StrUtil.isBlank(content)) {
|
||||
throw new IllegalArgumentException("模板内容不能为空");
|
||||
}
|
||||
return templateRenderService.render(null, null, content, dataMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] exportToWord(String html, String fileName) {
|
||||
if (StrUtil.isBlank(html)) {
|
||||
throw new IllegalArgumentException("HTML内容不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(fileName)) {
|
||||
fileName = "document";
|
||||
}
|
||||
|
||||
try {
|
||||
// 创建 Word 文档
|
||||
WordprocessingMLPackage wordPackage = WordprocessingMLPackage.createPackage();
|
||||
MainDocumentPart mainDocumentPart = wordPackage.getMainDocumentPart();
|
||||
|
||||
// 解析 HTML
|
||||
Document htmlDoc = Jsoup.parse(html);
|
||||
|
||||
// 处理 HTML 内容并添加到 Word 文档
|
||||
processHtmlToWord(mainDocumentPart, htmlDoc.body());
|
||||
|
||||
// 转换为字节数组
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
wordPackage.save(baos);
|
||||
|
||||
log.info("Word导出成功,文件名: {}.docx, 大小: {} bytes", fileName, baos.size());
|
||||
return baos.toByteArray();
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Word导出失败,fileName: {}", fileName, e);
|
||||
throw new RuntimeException("Word导出失败: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] renderAndExportToWord(Long instanceId, Map<String, Object> dataMap, String fileName) {
|
||||
// 先渲染获取HTML
|
||||
String html = renderByInstance(instanceId, dataMap);
|
||||
// 再导出为Word
|
||||
return exportToWord(html, fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 HTML 转换为 Word 文档内容
|
||||
* 递归处理 HTML 节点,使用 docx4j API 构建完整的 Word 文档
|
||||
*/
|
||||
private void processHtmlToWord(MainDocumentPart mainDocumentPart, Element element) throws Exception {
|
||||
for (Node node : element.childNodes()) {
|
||||
if (node instanceof TextNode) {
|
||||
String text = ((TextNode) node).getWholeText();
|
||||
if (StrUtil.isNotBlank(text.trim())) {
|
||||
mainDocumentPart.addStyledParagraphOfText("Normal", text.trim());
|
||||
}
|
||||
} else if (node instanceof Element) {
|
||||
Element element1 = (Element) node;
|
||||
String tagName = element1.tagName().toLowerCase();
|
||||
|
||||
switch (tagName) {
|
||||
case "h1", "h2", "h3", "h4", "h5", "h6" -> {
|
||||
String styleId = "Heading" + tagName.substring(1);
|
||||
mainDocumentPart.addStyledParagraphOfText(styleId, element1.text());
|
||||
}
|
||||
case "p" -> {
|
||||
mainDocumentPart.addStyledParagraphOfText("Normal", element1.text());
|
||||
}
|
||||
case "br" -> {
|
||||
mainDocumentPart.addParagraphOfText("");
|
||||
}
|
||||
case "table" -> {
|
||||
processTable(mainDocumentPart, (Element) node);
|
||||
}
|
||||
case "ul", "ol" -> {
|
||||
processHtmlToWord(mainDocumentPart, element1);
|
||||
}
|
||||
case "li" -> {
|
||||
mainDocumentPart.addParagraphOfText("• " + element1.text());
|
||||
}
|
||||
case "strong", "b" -> {
|
||||
mainDocumentPart.addStyledParagraphOfText("Normal", element1.text());
|
||||
}
|
||||
case "i", "em" -> {
|
||||
mainDocumentPart.addStyledParagraphOfText("Normal", element1.text());
|
||||
}
|
||||
case "div", "section", "article" -> {
|
||||
processHtmlToWord(mainDocumentPart, element1);
|
||||
}
|
||||
default -> {
|
||||
processHtmlToWord(mainDocumentPart, element1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理表格
|
||||
* 使用 docx4j 的 Table 和 Tbl API 创建 Word 表格
|
||||
*/
|
||||
private void processTable(MainDocumentPart mainDocumentPart, Element tableElement) throws Exception {
|
||||
Elements rows = tableElement.select("tr");
|
||||
if (rows.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
ObjectFactory factory = new ObjectFactory();
|
||||
Tbl tbl = factory.createTbl();
|
||||
|
||||
// 设置表格属性
|
||||
TblPr tblPr = factory.createTblPr();
|
||||
tbl.setTblPr(tblPr);
|
||||
|
||||
// 处理每一行
|
||||
for (Element row : rows) {
|
||||
Elements cells = row.select("td, th");
|
||||
Tr tr = factory.createTr();
|
||||
|
||||
for (Element cell : cells) {
|
||||
Tc tc = factory.createTc();
|
||||
TcPr tcPr = factory.createTcPr();
|
||||
tc.setTcPr(tcPr);
|
||||
|
||||
// 添加单元格内容
|
||||
P cellParagraph = factory.createP();
|
||||
R cellRun = factory.createR();
|
||||
Text cellText = factory.createText();
|
||||
cellText.setValue(cell.text());
|
||||
cellRun.getContent().add(cellText);
|
||||
cellParagraph.getContent().add(cellRun);
|
||||
tc.getContent().add(cellParagraph);
|
||||
|
||||
tr.getContent().add(tc);
|
||||
}
|
||||
|
||||
tbl.getContent().add(tr);
|
||||
}
|
||||
|
||||
// 将表格添加到文档
|
||||
mainDocumentPart.getContent().add(tbl);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.warn("表格处理失败,跳过表格内容", e);
|
||||
// 降级处理:将表格内容作为文本添加
|
||||
for (Element row : rows) {
|
||||
Elements cells = row.select("td, th");
|
||||
StringBuilder rowText = new StringBuilder();
|
||||
for (Element cell : cells) {
|
||||
rowText.append(cell.text()).append(" | ");
|
||||
}
|
||||
mainDocumentPart.addParagraphOfText(rowText.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user