springboot实现合同生成
摘要:本文介绍了基于若依前后端分离项目的合同文档处理功能实现,主要包括用户上传含占位符({{$.}}格式)的docx模板、自动解析占位符、生成填充后的合同文档,以及格式转换流程。系统使用poi-tl和docx4j处理Word文档,通过PDFBox实现PDF转图片功能。核心功能包括:1)上传模板时解析占位符并存储;2)生成合同时替换占位符为实际内容;3)将生成的docx转为PDF,再拼接为长图。代码
·
项目:若依前后端分离项目
用户上传合同docx,用户填写占位符,实现生成docx。
docx转pdf,pdf再转图片
代码不全,但是关键代码基本都在
依赖:
<dependency>
<groupId>com.deepoove</groupId>
<artifactId>poi-tl</artifactId>
<version>1.9.1</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>6.1.0</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>6.1.0</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>1.21</version>
</dependency>
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>2.0.29</version>
</dependency>
上传合同,占位符为{{$.}}
新增合同的时候解析占位符
public int insertAxjfContractTemplate(AxjfContractTemplate axjfContractTemplate)
{
axjfContractTemplate.setCreateTime(DateUtils.getNowDate());
String fileUrl = axjfContractTemplate.getFileUrl();
Set<String> fillFields;
fileUrl = fileUrl.replace(Constants.RESOURCE_PREFIX, "");
try {
fillFields = extractPlaceholders(profile+fileUrl);
} catch (Exception e) {
throw new ServiceException("合同模板解析失败!");
}
axjfContractTemplate.setTemplateParams(JSON.toJSONString(fillFields));
axjfContractTemplate.setUserId(SecurityUtils.getUserId());
return axjfContractTemplateMapper.insertAxjfContractTemplate(axjfContractTemplate);
}
public static Set<String> extractPlaceholders(String filePath) throws Exception {
Set<String> placeholders = new HashSet<>();
FileInputStream fis = new FileInputStream(filePath);
XWPFDocument doc = new XWPFDocument(fis);
Pattern pattern = Pattern.compile("\\{\\$\\.(.+?)}}");
for (XWPFParagraph paragraph : doc.getParagraphs()) {
Matcher matcher = pattern.matcher(paragraph.getText());
while (matcher.find()) {
placeholders.add(matcher.group(1));
}
}
for (XWPFTable table : doc.getTables()) {
table.getRows().forEach(row ->
row.getTableCells().forEach(cell -> {
Matcher matcher = pattern.matcher(cell.getText());
while (matcher.find()) {
placeholders.add(matcher.group(1));
}
})
);
}
doc.close();
return placeholders;
}
上传合同后,用户进行合同生成
@Override
public Map<String,String> insertAxjfContractRecord(AxjfContractRecord axjfContractRecord)
{
axjfContractRecord.setCreateTime(DateUtils.getNowDate());
axjfContractRecord.setUserId(SecurityUtils.getUserId());
String fileUrl;
String picUrl;
try {
fileUrl = handlerCreateContracr(axjfContractRecord);
picUrl = handlerCreatePic(fileUrl.replace(Constants.RESOURCE_PREFIX, ""));
} catch (Exception e) {
e.printStackTrace();
throw new ServiceException("生成失败!请稍后再试!");
}
axjfContractRecord.setDocxUrl(fileUrl);
axjfContractRecord.setImgUrl(picUrl);
threadPoolTaskExecutor.execute(() -> axjfContractRecordMapper.insertAxjfContractRecord(axjfContractRecord));
Map<String, String> map = new HashMap<>();
map.put("docxFileUrl",fileUrl);
map.put("picUrl", picUrl);
return map;
}
// word转pdf、pdf转图片
private String handlerCreatePic(String fileUrl) throws Exception {
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(new File(uploadBasePath + fileUrl));
FOSettings foSettings = Docx4J.createFOSettings();
foSettings.setWmlPackage(wordMLPackage);
foSettings.setApacheFopMime("application/pdf"); // 指定输出类型
Mapper fontMapper = new IdentityPlusMapper();
String fontsDir = "Fonts";
String[] fontFiles = {"simsun.ttc","CALIBRI.ttf"};
for (String fontFile : fontFiles) {
URL fontUrl = this.getClass().getClassLoader().getResource(fontsDir + "/" + fontFile);
if (fontUrl != null) {
String fontName = fontFile.split("\\.")[0];
PhysicalFonts.addPhysicalFonts(fontName, fontUrl);
fontMapper.put(fontName, PhysicalFonts.get(fontName));
}
}
wordMLPackage.setFontMapper(fontMapper);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Docx4J.toFO(foSettings, baos, Docx4J.FLAG_EXPORT_PREFER_XSL);
byte[] pdfBytes = baos.toByteArray();
String newFile = "/contract/record/" + DateUtils.datePath() + "/" + IdUtils.fastUUID() + "_" + "合同图片.png";
try (PDDocument document = PDDocument.load(new ByteArrayInputStream(pdfBytes))) {
PDFRenderer pdfRenderer = new PDFRenderer(document);
List<BufferedImage> images = new ArrayList<>();
int totalHeight = 0;
int maxWidth = 0;
for (int page = 0; page < document.getNumberOfPages(); page++) {
BufferedImage bim = pdfRenderer.renderImageWithDPI(page, 200);
images.add(bim);
totalHeight += bim.getHeight();
maxWidth = Math.max(maxWidth, bim.getWidth());
}
BufferedImage longImage = new BufferedImage(maxWidth, totalHeight, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = longImage.createGraphics();
int currentHeight = 0;
for (BufferedImage img : images) {
g2d.drawImage(img, 0, currentHeight, null);
currentHeight += img.getHeight();
}
g2d.dispose();
String outputPath = uploadBasePath + newFile;
ImageIO.write(longImage, "png", new File(outputPath));
}
return Constants.RESOURCE_PREFIX + newFile;
}
private String handlerCreateContracr(AxjfContractRecord axjfContractRecord) throws IOException {
AxjfContractTemplate axjfContractTemplate = axjfContractTemplateMapper.selectAxjfContractTemplateById(axjfContractRecord.getTemplateId());
if (null == axjfContractTemplate){
throw new ServiceException("合同模板不存在!刷新页面后重试");
}
String templateFile = axjfContractTemplate.getFileUrl();
String newFile = "/contract/record/" + DateUtils.datePath() + "/" + IdUtils.fastUUID()+ "_"+ axjfContractTemplate.getTitle() + ".docx";
// 上传文件路径
String outputPath = uploadBasePath + newFile;
File outFile = new File(outputPath);
File parentDir = outFile.getParentFile();
if (!parentDir.exists()) {
boolean created = parentDir.mkdirs();
if (!created) throw new RuntimeException("无法创建目录:" + parentDir.getAbsolutePath());
}
templateFile = uploadBasePath + templateFile.replace(Constants.RESOURCE_PREFIX, "");
try (FileInputStream fis = new FileInputStream(templateFile);
XWPFDocument document = new XWPFDocument(fis);
FileOutputStream fos = new FileOutputStream(outFile)) {
for (XWPFParagraph paragraph : document.getParagraphs()) {
if (paragraph.getRuns().isEmpty()) {
continue;
}
StringBuilder sb = new StringBuilder();
for (XWPFRun run : paragraph.getRuns()) {
String text = run.getText(0);
if (text != null) {
sb.append(text);
}
}
XWPFRun sourceRun = paragraph.getRuns().get(0);
CTRPr rPrCopy = null;
if (sourceRun.getCTR().getRPr() != null) {
CTRPr rPr = sourceRun.getCTR().getRPr();
rPrCopy = CTRPr.Factory.newInstance();
rPrCopy.set(rPr);
}
String replaced = sb.toString();
for (Map.Entry<String, String> entry : axjfContractRecord.getTemplateParams().entrySet()) {
String key = "{{$." + entry.getKey() + "}}";
replaced = replaced.replace(key, entry.getValue());
}
int runCount = paragraph.getRuns().size();
for (int i = runCount - 1; i >= 0; i--) {
paragraph.removeRun(i);
}
XWPFRun run = paragraph.createRun();
run.getCTR().setRPr(rPrCopy);
run.setText(replaced);
}
document.getTables().forEach(table -> table.getRows().forEach(row ->
row.getTableCells().forEach(cell ->
cell.getParagraphs().forEach(paragraph -> {
List<XWPFRun> runs = paragraph.getRuns();
if (runs.isEmpty()) return;
StringBuilder paragraphText = new StringBuilder();
List<Integer> runIndexes = new ArrayList<>();
for (int i = 0; i < runs.size(); i++) {
String text = runs.get(i).getText(0);
if (text != null) {
paragraphText.append(text);
for (int j = 0; j < text.length(); j++) {
runIndexes.add(i);
}
}
}
String newText = paragraphText.toString();
for (Map.Entry<String, String> entry : axjfContractRecord.getTemplateParams().entrySet()) {
String key = "{{$." + entry.getKey() + "}}";
newText = newText.replace(key, entry.getValue());
}
runs.forEach(run -> run.setText("", 0));
int textPos = 0;
while (textPos < newText.length()) {
int runIndex = runIndexes.get(textPos);
XWPFRun run = runs.get(runIndex);
StringBuilder runText = new StringBuilder();
while (textPos < newText.length() && runIndexes.get(textPos) == runIndex) {
runText.append(newText.charAt(textPos));
textPos++;
}
run.setText(runText.toString(), 0);
}
})
)
));
document.write(fos);
}
return Constants.RESOURCE_PREFIX + newFile;
}
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)