Browse Source

fix:word转图片

master
wang 2 months ago
parent
commit
b41a98fa92
  1. 536
      yun-admin/src/main/java/net/lab1024/sa/admin/module/word/WordApplicationsController.java
  2. 3
      数据同步

536
yun-admin/src/main/java/net/lab1024/sa/admin/module/word/WordApplicationsController.java

@ -4,26 +4,25 @@ 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.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.math.BigDecimal;
import java.util.List;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
/**
* 无处罚证明开具 Controller
@ -32,58 +31,489 @@ import java.util.List;
* @Date 2025-12-20 14:44:06
* @Copyright 1.0
*/
@RestController
@Tag(name = "无处罚证明开具")
@RequestMapping("/wordCertificate")
public class WordApplicationsController {
@Resource
WordCertificateService wordCertificateService;
@Resource
@Autowired
private WordCertificateService wordCertificateService;
@Autowired
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 {
//查询出当前的开具证明信息
@Autowired
private EmployeeService employeeService;
@Autowired
private DepartmentService departmentService;
// 字体配置 - 使用文泉驿字体
private static final int TITLE_FONT_SIZE = 100; // 标题
private static final int BODY_FONT_SIZE = 78; // 正文
private static final int SIGNATURE_FONT_SIZE = 72; // 落款
// 页面布局配置
private static final int PAGE_WIDTH = 2480; // A4宽度 (300 DPI)
private static final int PAGE_HEIGHT = 3508; // A4高度 (300 DPI)
private static final int LEFT_MARGIN = 380; // 左边距
private static final int RIGHT_MARGIN = 380; // 右边距
private static final int TOP_MARGIN = 500; // 顶部边距
private static final int LINE_SPACING = 40; // 行间距
private static final int PARAGRAPH_SPACING = 100; // 段落间距
// 印章配置
private static final String SEAL_IMAGE_PATH = "templates/official_seal.png";
private static final int SEAL_WIDTH = 580;
private static final int SEAL_HEIGHT = 580;
// 字体缓存
private static Font titleFont;
private static Font bodyFont;
private static Font signatureFont;
// 文泉驿字体映射
private static final Map<String, String> WENQUANYI_FONTS = new HashMap<>();
static {
// 文泉驿字体名称映射
WENQUANYI_FONTS.put("title", "WenQuanYi Zen Hei"); // 标题用正黑(较粗)
WENQUANYI_FONTS.put("body", "WenQuanYi Micro Hei"); // 正文用微米黑
WENQUANYI_FONTS.put("bold", "WenQuanYi Zen Hei Sharp"); // 加粗用点阵正黑
}
/**
* 静态初始化字体
*/
static {
try {
initFonts();
System.out.println("字体初始化完成:");
System.out.println("标题字体: " + titleFont.getFontName());
System.out.println("正文字体: " + bodyFont.getFontName());
System.out.println("落款字体: " + signatureFont.getFontName());
} catch (Exception e) {
System.err.println("字体初始化失败,使用默认字体: " + e.getMessage());
setFallbackFonts();
}
}
/**
* 初始化文泉驿字体
*/
private static void initFonts() {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] availableFonts = ge.getAvailableFontFamilyNames();
System.out.println("可用字体列表:");
for (String font : availableFonts) {
if (font.contains("WenQuanYi") || font.contains("文泉驿")) {
System.out.println(" - " + font);
}
}
// 尝试加载文泉驿正黑作为标题字体
titleFont = findAndCreateFont(availableFonts,
new String[]{"WenQuanYi Zen Hei", "文泉驿正黑", "WenQuanYi Zen Hei Sharp"},
Font.BOLD, TITLE_FONT_SIZE);
// 尝试加载文泉驿微米黑作为正文字体
bodyFont = findAndCreateFont(availableFonts,
new String[]{"WenQuanYi Micro Hei", "文泉驿微米黑", "WenQuanYi Zen Hei"},
Font.PLAIN, BODY_FONT_SIZE);
// 落款字体使用正文字体但字号稍小
if (bodyFont != null) {
signatureFont = bodyFont.deriveFont((float) SIGNATURE_FONT_SIZE);
} else {
signatureFont = new Font("SansSerif", Font.PLAIN, SIGNATURE_FONT_SIZE);
}
// 如果字体未找到,使用默认字体
if (titleFont == null) {
titleFont = new Font("SansSerif", Font.BOLD, TITLE_FONT_SIZE);
}
if (bodyFont == null) {
bodyFont = new Font("SansSerif", Font.PLAIN, BODY_FONT_SIZE);
signatureFont = new Font("SansSerif", Font.PLAIN, SIGNATURE_FONT_SIZE);
}
}
/**
* 查找并创建字体
*/
private static Font findAndCreateFont(String[] availableFonts, String[] preferredFonts, int style, int size) {
for (String preferred : preferredFonts) {
for (String available : availableFonts) {
if (available.equalsIgnoreCase(preferred) || available.contains(preferred)) {
System.out.println("找到字体: " + available + " -> 使用: " + preferred);
try {
Font font = new Font(available, style, size);
// 测试字体是否能显示中文
if (testFontCanDisplayChinese(font)) {
return font;
}
} catch (Exception e) {
System.err.println("创建字体失败: " + available + ", 错误: " + e.getMessage());
}
}
}
}
return null;
}
/**
* 测试字体是否能显示中文
*/
private static boolean testFontCanDisplayChinese(Font font) {
try {
return font.canDisplay('中') && font.canDisplay('文') && font.canDisplay('证');
} catch (Exception e) {
return false;
}
}
/**
* 设置备选字体
*/
private static void setFallbackFonts() {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] fonts = ge.getAvailableFontFamilyNames();
// 寻找任何可用的中文字体
String fallbackFont = "SansSerif";
for (String font : fonts) {
if (font.matches(".*[\\u4e00-\\u9fa5].*") ||
font.contains("Song") || font.contains("Hei") ||
font.contains("Kai") || font.contains("Fang")) {
fallbackFont = font;
break;
}
}
titleFont = new Font(fallbackFont, Font.BOLD, TITLE_FONT_SIZE);
bodyFont = new Font(fallbackFont, Font.PLAIN, BODY_FONT_SIZE);
signatureFont = new Font(fallbackFont, Font.PLAIN, SIGNATURE_FONT_SIZE);
}
/**
* 预览无处罚证明图片格式
*/
@Operation(summary = "无处罚证明预览(图片格式) @author wzh")
@GetMapping("/export/{id}")
public void previewImage(HttpServletResponse response, @PathVariable Integer id) throws Exception {
try {
WordCertificateService.CertificateData certificateData = getCertificateData(id);
byte[] imageBytes = generateCertificateImage(certificateData);
response.setContentType("image/png");
String fileName = LocalDateTime.now()+".png";
response.setHeader("Content-Disposition", "inline; filename=\"" + fileName + "\"");
response.setHeader("Cache-Control", "max-age=3600");
response.setContentLength(imageBytes.length);
try (OutputStream out = response.getOutputStream()) {
out.write(imageBytes);
out.flush();
}
} catch (Exception e) {
// 生成错误图片
byte[] errorImage = generateErrorImage("生成证明失败: " + e.getMessage());
response.setContentType("image/png");
response.setHeader("Content-Disposition", "inline; filename=\"error.png\"");
response.setContentLength(errorImage.length);
try (OutputStream out = response.getOutputStream()) {
out.write(errorImage);
out.flush();
}
}
}
/**
* 生成错误提示图片
*/
private byte[] generateErrorImage(String errorMessage) throws IOException {
BufferedImage image = new BufferedImage(800, 400, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
try {
setupGraphics(g2d);
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, 800, 400);
g2d.setColor(Color.RED);
Font errorFont = new Font("SansSerif", Font.BOLD, 20);
g2d.setFont(errorFont);
// 绘制错误信息
g2d.drawString("错误:无法生成证明", 50, 100);
g2d.drawString("原因:" + errorMessage, 50, 140);
} finally {
g2d.dispose();
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", baos);
return baos.toByteArray();
}
/**
* 根据ID获取证书数据
*/
private WordCertificateService.CertificateData getCertificateData(Integer id) {
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(),
EmployeeEntity employee = employeeService.getById(penaltyApplyEntity.getUserId());
DepartmentVO department = departmentService.getById(employee.getDepartmentId());
return new WordCertificateService.CertificateData(
department.getDepartmentName(),
department.getCreditCode(),
employee.getActualName(),
employee.getCertificateNumber(),
penaltyApplyEntity.getCreateTime()
);
}
byte[] fileContent;
String fileName;
String contentType;
/**
* 生成证书图片
*/
private byte[] generateCertificateImage(WordCertificateService.CertificateData data) throws IOException {
BufferedImage image = new BufferedImage(PAGE_WIDTH, PAGE_HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
try {
setupGraphics(g2d);
// 生成Word文档
fileContent = wordCertificateService.generateCertificate(certificateData);
// fileName = "certificate.docx";
//contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
// 填充白色背景
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, PAGE_WIDTH, PAGE_HEIGHT);
g2d.setColor(Color.BLACK);
// 绘制证书内容
drawCertificateContent(g2d, data);
// 设置文档响应头
//response.setContentType(contentType);
//response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + fileName);
// 设置响应头
response.setContentType("image/png");
response.setHeader("Content-Disposition", "inline; filename=\"preview.png\"");
response.setHeader("Cache-Control", "max-age=3600");
response.setContentLength(fileContent.length);
} finally {
g2d.dispose();
}
return convertImageToBytes(image);
}
/**
* 设置Graphics2D参数
*/
private void setupGraphics(Graphics2D g2d) {
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
}
/**
* 绘制证书内容
*/
private void drawCertificateContent(Graphics2D g2d, WordCertificateService.CertificateData data) {
// 1. 绘制标题"证明" - 居中对齐
g2d.setFont(titleFont);
String title = "证 明";
FontMetrics titleMetrics = g2d.getFontMetrics();
// 测试字体是否能显示
if (titleMetrics.stringWidth(title) == 0) {
throw new RuntimeException("标题字体不可用,可能显示为方框");
}
int titleWidth = titleMetrics.stringWidth(title);
int titleX = (PAGE_WIDTH - titleWidth) / 2;
int currentY = TOP_MARGIN;
g2d.drawString(title, titleX, currentY);
currentY += titleMetrics.getHeight() + PARAGRAPH_SPACING;
// 2. 绘制正文 - 带首行缩进
g2d.setFont(bodyFont);
FontMetrics bodyMetrics = g2d.getFontMetrics();
int lineHeight = bodyMetrics.getHeight();
int maxLineWidth = PAGE_WIDTH - LEFT_MARGIN - RIGHT_MARGIN;
// 构建完整内容
String fullContent = "兹证明" + data.getCertificateNo() + "(统一社会信用代码:" +
data.getPurpose() + ")" + data.getName() +
"律师(执业证号:" + data.getIdCard() +
")近五年在我市执业期间未受到律师协会行业处分。";
// 首行缩进(两个全角空格)
String indent = "  ";
String firstLine = indent + fullContent;
// 绘制正文(带自动换行)
currentY = drawWrappedText(g2d, firstLine, LEFT_MARGIN, currentY, maxLineWidth, lineHeight);
// 3. 绘制"特此证明。" - 同样缩进
currentY += lineHeight;
String herebyText = indent + "特此证明。";
g2d.drawString(herebyText, LEFT_MARGIN, currentY);
currentY += lineHeight + PARAGRAPH_SPACING * 2;
// 4. 绘制落款和印章
drawSignatureWithSeal(g2d, currentY, data.getLocalDateTime());
}
/**
* 绘制自动换行文本
*/
private int drawWrappedText(Graphics2D g2d, String text, int x, int y, int maxWidth, int lineHeight) {
FontMetrics metrics = g2d.getFontMetrics();
StringBuilder currentLine = new StringBuilder();
int lineIndex = 0;
int currentY = y;
for (int i = 0; i < text.length(); i++) {
char ch = text.charAt(i);
currentLine.append(ch);
// 检查当前行宽度
int lineWidth = metrics.stringWidth(currentLine.toString());
if (lineWidth > maxWidth) {
// 绘制当前行(除了最后一个字符)
String lineToDraw = currentLine.substring(0, currentLine.length() - 1);
int drawX = x;
g2d.drawString(lineToDraw, drawX, currentY);
// 新行开始(从最后一个字符开始)
currentLine = new StringBuilder(String.valueOf(ch));
currentY += lineHeight + LINE_SPACING;
lineIndex++;
}
}
// 绘制最后一行
if (currentLine.length() > 0) {
int drawX = x;
g2d.drawString(currentLine.toString(), drawX, currentY);
currentY += lineHeight + LINE_SPACING;
}
return currentY;
}
/**
* 绘制落款和印章
*/
private void drawSignatureWithSeal(Graphics2D g2d, int startY, LocalDateTime localDateTime) {
g2d.setFont(signatureFont);
FontMetrics metrics = g2d.getFontMetrics();
int lineHeight = metrics.getHeight();
String associationName = "合肥市律师协会";
int textWidth = metrics.stringWidth(associationName);
// 测试字体是否能显示
if (textWidth == 0) {
throw new RuntimeException("落款字体不可用,可能显示为方框");
}
int textX = PAGE_WIDTH - RIGHT_MARGIN - textWidth;
int textY = startY + lineHeight * 2;
// 绘制印章
drawSeal(g2d, textX, textY, textWidth, lineHeight);
// 绘制文字
g2d.drawString(associationName, textX, textY);
// 绘制日期
String dateText = localDateTime.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日"));
int dateWidth = metrics.stringWidth(dateText);
int dateX = PAGE_WIDTH - RIGHT_MARGIN - dateWidth;
int dateY = textY + lineHeight + LINE_SPACING * 2;
g2d.drawString(dateText, dateX, dateY);
}
/**
* 绘制印章
*/
private void drawSeal(Graphics2D g2d, int textX, int textY, int textWidth, int lineHeight) {
try {
int sealX = textX + (textWidth - SEAL_WIDTH) / 2;
int sealY = textY - lineHeight * 2;
Resource sealResource = new ClassPathResource(SEAL_IMAGE_PATH);
if (sealResource.exists()) {
BufferedImage sealImage = ImageIO.read(sealResource.getInputStream());
BufferedImage transparentSeal = removeWhiteBackground(sealImage);
g2d.drawImage(transparentSeal, sealX, sealY, SEAL_WIDTH, SEAL_HEIGHT, null);
}
} catch (Exception e) {
// 忽略印章错误
}
}
/**
* 移除图片白色背景
*/
private BufferedImage removeWhiteBackground(BufferedImage originalImage) {
int width = originalImage.getWidth();
int height = originalImage.getHeight();
BufferedImage result = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int rgb = originalImage.getRGB(x, y);
Color color = new Color(rgb, true);
if (color.getRed() > 240 && color.getGreen() > 240 && color.getBlue() > 240) {
result.setRGB(x, y, 0x00FFFFFF);
} else {
result.setRGB(x, y, rgb);
}
}
}
return result;
}
/**
* 将图片转换为字节数组
*/
private byte[] convertImageToBytes(BufferedImage image) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(image, "PNG", baos);
return baos.toByteArray();
}
/**
* 字体测试接口用于调试
*/
@Operation(summary = "字体测试接口 @author wzh")
@GetMapping("/test/fonts")
public String testFonts() {
StringBuilder result = new StringBuilder();
result.append("<h1>字体测试</h1>");
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
String[] fonts = ge.getAvailableFontFamilyNames();
result.append("<h2>可用字体 (").append(fonts.length).append("):</h2>");
result.append("<ul>");
for (String font : fonts) {
if (font.contains("WenQuanYi") || font.contains("文泉驿") ||
font.contains("Song") || font.contains("Hei")) {
result.append("<li><b>").append(font).append("</b></li>");
} else {
result.append("<li>").append(font).append("</li>");
}
}
result.append("</ul>");
result.append("<h2>当前使用的字体:</h2>");
result.append("<ul>");
result.append("<li>标题字体: ").append(titleFont.getFontName()).append("</li>");
result.append("<li>正文字体: ").append(bodyFont.getFontName()).append("</li>");
result.append("<li>落款字体: ").append(signatureFont.getFontName()).append("</li>");
result.append("</ul>");
// 将文件内容写入响应输出流
response.getOutputStream().write(fileContent);
response.getOutputStream().flush();
return result.toString();
}
}

3
数据同步

@ -14,3 +14,6 @@ WHERE EXISTS (
AND te.employee_id IS NOT NULL
AND te.employee_id = re.employee_id
);
启动脚本
nohup java -server -Xms1024m -Xmx1024m -jar yun-admin-prod-3.0.0.jar > yun-admin-nohup.out 2>&1 &

Loading…
Cancel
Save