18 changed files with 476 additions and 16 deletions
@ -0,0 +1,91 @@ |
|||||
|
package net.lab1024.sa.admin.module.word; |
||||
|
|
||||
|
import io.swagger.v3.oas.annotations.Operation; |
||||
|
import io.swagger.v3.oas.annotations.tags.Tag; |
||||
|
import net.lab1024.sa.admin.module.penalty.domain.entity.PenaltyApplyEntity; |
||||
|
import net.lab1024.sa.admin.module.penalty.service.PenaltyApplyService; |
||||
|
import net.lab1024.sa.admin.module.service.domain.form.*; |
||||
|
import net.lab1024.sa.admin.module.service.domain.vo.LawyerStatisticsVO; |
||||
|
import net.lab1024.sa.admin.module.service.domain.vo.ServiceApplicationsVO; |
||||
|
import net.lab1024.sa.admin.module.service.domain.vo.ServiceReportStatisticsVO; |
||||
|
import net.lab1024.sa.admin.module.service.service.ServiceApplicationsService; |
||||
|
import net.lab1024.sa.admin.module.system.department.domain.vo.DepartmentVO; |
||||
|
import net.lab1024.sa.admin.module.system.department.service.DepartmentService; |
||||
|
import net.lab1024.sa.admin.module.system.employee.domain.entity.EmployeeEntity; |
||||
|
import net.lab1024.sa.admin.module.system.employee.service.EmployeeService; |
||||
|
import net.lab1024.sa.base.common.domain.PageResult; |
||||
|
import net.lab1024.sa.base.common.domain.ResponseDTO; |
||||
|
import net.lab1024.sa.base.common.domain.ValidateList; |
||||
|
import org.springframework.web.bind.annotation.*; |
||||
|
import org.springframework.web.multipart.MultipartFile; |
||||
|
|
||||
|
import javax.annotation.Resource; |
||||
|
import javax.servlet.http.HttpServletResponse; |
||||
|
import javax.validation.Valid; |
||||
|
import java.math.BigDecimal; |
||||
|
import java.util.List; |
||||
|
|
||||
|
/** |
||||
|
* 无处罚证明开具 Controller |
||||
|
* |
||||
|
* @Author wzh |
||||
|
* @Date 2025-12-20 14:44:06 |
||||
|
* @Copyright 1.0 |
||||
|
*/ |
||||
|
|
||||
|
@RestController |
||||
|
@Tag(name = "无处罚证明开具") |
||||
|
public class WordApplicationsController { |
||||
|
@Resource |
||||
|
WordCertificateService wordCertificateService; |
||||
|
@Resource |
||||
|
private PenaltyApplyService penaltyApplyService; |
||||
|
@Resource |
||||
|
EmployeeService employeeService; |
||||
|
@Resource |
||||
|
DepartmentService departmentService; |
||||
|
@Operation(summary = "无处罚证明下载 @author wzh") |
||||
|
@GetMapping("/wordCertificate/export/{id}") |
||||
|
public void wordCertificateExport(HttpServletResponse response, @PathVariable Integer id, |
||||
|
@RequestParam(defaultValue = "word") String format) throws Exception { |
||||
|
//查询出当前的开具证明信息
|
||||
|
PenaltyApplyEntity penaltyApplyEntity = penaltyApplyService.selectOne(id); |
||||
|
//查询用户姓名执业证号
|
||||
|
EmployeeEntity byId = employeeService.getById(penaltyApplyEntity.getUserId()); |
||||
|
//机构信息
|
||||
|
DepartmentVO departmentVO = departmentService.getById(byId.getDepartmentId()); |
||||
|
WordCertificateService.CertificateData certificateData = new WordCertificateService.CertificateData( |
||||
|
departmentVO.getDepartmentName(), |
||||
|
departmentVO.getCreditCode(), |
||||
|
byId.getActualName(), |
||||
|
byId.getCertificateNumber(), |
||||
|
penaltyApplyEntity.getCreateTime() |
||||
|
); |
||||
|
|
||||
|
byte[] fileContent; |
||||
|
String fileName; |
||||
|
String contentType; |
||||
|
|
||||
|
if ("pdf".equalsIgnoreCase(format)) { |
||||
|
// 生成PDF文档
|
||||
|
fileContent = wordCertificateService.generateCertificateAsPdf(certificateData); |
||||
|
fileName = "certificate.pdf"; |
||||
|
contentType = "application/pdf"; |
||||
|
} else { |
||||
|
// 生成Word文档
|
||||
|
fileContent = wordCertificateService.generateCertificate(certificateData); |
||||
|
fileName = "certificate.docx"; |
||||
|
contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; |
||||
|
} |
||||
|
|
||||
|
// 设置文档响应头
|
||||
|
response.setContentType(contentType); |
||||
|
response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + fileName); |
||||
|
|
||||
|
response.setContentLength(fileContent.length); |
||||
|
|
||||
|
// 将文件内容写入响应输出流
|
||||
|
response.getOutputStream().write(fileContent); |
||||
|
response.getOutputStream().flush(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,269 @@ |
|||||
|
package net.lab1024.sa.admin.module.word; |
||||
|
|
||||
|
import lombok.AllArgsConstructor; |
||||
|
import lombok.Data; |
||||
|
import lombok.NoArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.apache.poi.util.Units; |
||||
|
import org.apache.poi.xwpf.converter.pdf.PdfConverter; |
||||
|
import org.apache.poi.xwpf.converter.pdf.PdfOptions; |
||||
|
import org.apache.poi.xwpf.usermodel.ParagraphAlignment; |
||||
|
import org.apache.poi.xwpf.usermodel.XWPFDocument; |
||||
|
import org.apache.poi.xwpf.usermodel.XWPFParagraph; |
||||
|
import org.apache.poi.xwpf.usermodel.XWPFRun; |
||||
|
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.*; |
||||
|
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTDrawing; |
||||
|
import org.springframework.core.io.ClassPathResource; |
||||
|
import org.springframework.core.io.Resource; |
||||
|
import org.springframework.util.FileCopyUtils; |
||||
|
import org.springframework.stereotype.Service; |
||||
|
|
||||
|
import java.io.ByteArrayInputStream; |
||||
|
import java.io.ByteArrayOutputStream; |
||||
|
import java.io.InputStream; |
||||
|
import java.math.BigInteger; |
||||
|
import java.time.LocalDate; |
||||
|
import java.time.LocalDateTime; |
||||
|
import java.time.format.DateTimeFormatter; |
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* @program: yun-parent |
||||
|
* @description: 模板生成 |
||||
|
* @author: Mr.Wang |
||||
|
* @create: 2026-01-13 16:38 |
||||
|
**/ |
||||
|
@Service |
||||
|
@Slf4j |
||||
|
public class WordCertificateService { |
||||
|
|
||||
|
private static final String TEMPLATE_PATH = "templates/certificate_template.docx"; |
||||
|
private static final String SEAL_PATH = "templates/official_seal.png"; |
||||
|
/** |
||||
|
* 生成Word格式的证书 |
||||
|
*/ |
||||
|
public byte[] generateCertificate(CertificateData data) throws Exception { |
||||
|
XWPFDocument document = prepareDocument(data); |
||||
|
|
||||
|
// 保存到字节数组
|
||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); |
||||
|
document.write(outputStream); |
||||
|
document.close(); |
||||
|
|
||||
|
return outputStream.toByteArray(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 生成PDF格式的证书 |
||||
|
*/ |
||||
|
public byte[] generateCertificateAsPdf(CertificateData data) throws Exception { |
||||
|
XWPFDocument document = prepareDocument(data); |
||||
|
|
||||
|
// 转换为PDF
|
||||
|
ByteArrayOutputStream pdfOutputStream = new ByteArrayOutputStream(); |
||||
|
PdfConverter.getInstance().convert(document, pdfOutputStream, PdfOptions.create()); |
||||
|
document.close(); |
||||
|
|
||||
|
return pdfOutputStream.toByteArray(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 准备文档(用于Word或PDF生成) |
||||
|
*/ |
||||
|
private XWPFDocument prepareDocument(CertificateData data) throws Exception { |
||||
|
Resource templateResource = new ClassPathResource(TEMPLATE_PATH); |
||||
|
XWPFDocument document = new XWPFDocument(templateResource.getInputStream()); |
||||
|
|
||||
|
// 1. 替换文本占位符
|
||||
|
replaceTextPlaceholders(document, data); |
||||
|
|
||||
|
// 2. 插入电子章图片
|
||||
|
insertSealImage(document); |
||||
|
|
||||
|
return document; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 替换文本占位符 |
||||
|
*/ |
||||
|
private void replaceTextPlaceholders(XWPFDocument document, CertificateData data) { |
||||
|
// 准备替换数据
|
||||
|
Map<String, String> replacements = new HashMap<>(); |
||||
|
//律所名称
|
||||
|
replacements.put("${certificateNo}", data.getCertificateNo()); |
||||
|
//信用社代码
|
||||
|
replacements.put("${purpose}", data.getPurpose()); |
||||
|
//律师名称
|
||||
|
replacements.put("${name}", data.getName()); |
||||
|
//身份证号码
|
||||
|
replacements.put("${idCard}", data.getIdCard()); |
||||
|
//时间
|
||||
|
replacements.put("${queryTime}", data.getLocalDateTime().format( |
||||
|
DateTimeFormatter.ofPattern("yyyy年MM月dd日"))); |
||||
|
|
||||
|
// 遍历所有段落进行替换
|
||||
|
for (XWPFParagraph paragraph : document.getParagraphs()) { |
||||
|
String text = paragraph.getText(); |
||||
|
if (text != null && text.contains("${")) { |
||||
|
for (Map.Entry<String, String> entry : replacements.entrySet()) { |
||||
|
text = text.replace(entry.getKey(), entry.getValue()); |
||||
|
} |
||||
|
|
||||
|
// 清空原有内容
|
||||
|
for (int i = paragraph.getRuns().size() - 1; i >= 0; i--) { |
||||
|
paragraph.removeRun(i); |
||||
|
} |
||||
|
|
||||
|
// 添加新内容
|
||||
|
XWPFRun run = paragraph.createRun(); |
||||
|
run.setText(text); |
||||
|
run.setFontFamily("宋体"); |
||||
|
run.setFontSize(12); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 插入电子章图片(正确的方法) |
||||
|
*/ |
||||
|
private void insertSealImage(XWPFDocument document) throws Exception { |
||||
|
try { |
||||
|
// 1. 读取电子章图片
|
||||
|
Resource sealResource = new ClassPathResource(SEAL_PATH); |
||||
|
if (!sealResource.exists()) { |
||||
|
log.warn("电子章图片不存在"); |
||||
|
insertTextSeal(document); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
byte[] sealBytes = FileCopyUtils.copyToByteArray(sealResource.getInputStream()); |
||||
|
|
||||
|
// 2. 查找插入位置
|
||||
|
for (XWPFParagraph paragraph : document.getParagraphs()) { |
||||
|
String text = paragraph.getText(); |
||||
|
if (text != null && text.contains("${电子章}")) { |
||||
|
// 清除占位符文本
|
||||
|
for (int i = paragraph.getRuns().size() - 1; i >= 0; i--) { |
||||
|
paragraph.removeRun(i); |
||||
|
} |
||||
|
|
||||
|
// 创建居中的运行来放置图片
|
||||
|
XWPFRun run = paragraph.createRun(); |
||||
|
run.getParagraph().setAlignment(ParagraphAlignment.CENTER); |
||||
|
|
||||
|
// 添加图片数据并获取关系ID
|
||||
|
String blipId = document.addPictureData( |
||||
|
new ByteArrayInputStream(sealBytes), |
||||
|
XWPFDocument.PICTURE_TYPE_PNG |
||||
|
); |
||||
|
|
||||
|
// 设置图片尺寸(150×150像素)
|
||||
|
int width = 150; |
||||
|
int height = 150; |
||||
|
|
||||
|
// 使用addPicture方法插入图片
|
||||
|
run.addPicture( |
||||
|
new ByteArrayInputStream(sealBytes), |
||||
|
XWPFDocument.PICTURE_TYPE_PNG, |
||||
|
"official_seal.png", |
||||
|
Units.toEMU(width), |
||||
|
Units.toEMU(height) |
||||
|
); |
||||
|
|
||||
|
// 获取图片对象并设置为浮于文字上方
|
||||
|
CTDrawing drawing = run.getCTR().getDrawingArray(0); |
||||
|
if (drawing.sizeOfAnchorArray() > 0) { |
||||
|
CTInline inline = drawing.getInlineArray(0); |
||||
|
|
||||
|
// 创建锚定对象
|
||||
|
CTAnchor anchor = drawing.addNewAnchor(); |
||||
|
|
||||
|
// 设置位置
|
||||
|
anchor.addNewSimplePos().setX(0); |
||||
|
anchor.addNewSimplePos().setY(0); |
||||
|
|
||||
|
// 设置相对于页面
|
||||
|
CTPosH posH = anchor.addNewPositionH(); |
||||
|
posH.setRelativeFrom(STRelFromH.PAGE); |
||||
|
posH.setPosOffset(0); |
||||
|
|
||||
|
CTPosV posV = anchor.addNewPositionV(); |
||||
|
posV.setRelativeFrom(STRelFromV.PAGE); |
||||
|
posV.setPosOffset(0); |
||||
|
|
||||
|
// 设置环绕方式为无(浮于文字上方)
|
||||
|
anchor.addNewWrapNone(); |
||||
|
|
||||
|
// 设置在文字上方
|
||||
|
anchor.setBehindDoc(false); |
||||
|
// 删除原来的内联对象
|
||||
|
drawing.removeInline(0); |
||||
|
} |
||||
|
// 添加图片说明文字
|
||||
|
addSealCaption(paragraph); |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} catch (Exception e) { |
||||
|
log.error("插入电子章失败: {}", e.getMessage(), e); |
||||
|
insertTextSeal(document); |
||||
|
} |
||||
|
} |
||||
|
/** |
||||
|
* 添加图片说明文字到指定段落 |
||||
|
*/ |
||||
|
private void addSealCaption(XWPFParagraph paragraph) { |
||||
|
XWPFRun run = paragraph.createRun(); |
||||
|
run.setFontFamily("宋体"); |
||||
|
run.setFontSize(11); |
||||
|
run.setColor("000000"); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 插入文字替代的电子章 |
||||
|
*/ |
||||
|
private void insertTextSeal(XWPFDocument document) { |
||||
|
for (XWPFParagraph paragraph : document.getParagraphs()) { |
||||
|
String text = paragraph.getText(); |
||||
|
if (text != null && text.contains("(此处插入电子章)")) { |
||||
|
for (int i = paragraph.getRuns().size() - 1; i >= 0; i--) { |
||||
|
paragraph.removeRun(i); |
||||
|
} |
||||
|
|
||||
|
XWPFRun run = paragraph.createRun(); |
||||
|
run.setText("(电子公章)"); |
||||
|
run.setFontFamily("宋体"); |
||||
|
run.setFontSize(12); |
||||
|
run.setColor("FF0000"); |
||||
|
run.setBold(true); |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
/** |
||||
|
* 格式化日期 |
||||
|
*/ |
||||
|
private String formatDate(LocalDate date) { |
||||
|
if (date == null) return ""; |
||||
|
return date.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日")); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 证书数据类 |
||||
|
*/ |
||||
|
@Data |
||||
|
@AllArgsConstructor |
||||
|
@NoArgsConstructor |
||||
|
public static class CertificateData { |
||||
|
private String certificateNo; |
||||
|
private String purpose; |
||||
|
private String name; |
||||
|
private String idCard; |
||||
|
private LocalDateTime localDateTime; // 修正字段名为驼峰命名
|
||||
|
} |
||||
|
|
||||
|
} |
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.4 KiB |
Loading…
Reference in new issue