Browse Source

fix:word转图片

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

550
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 io.swagger.v3.oas.annotations.tags.Tag;
import net.lab1024.sa.admin.module.penalty.domain.entity.PenaltyApplyEntity; 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.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.domain.vo.DepartmentVO;
import net.lab1024.sa.admin.module.system.department.service.DepartmentService; 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.domain.entity.EmployeeEntity;
import net.lab1024.sa.admin.module.system.employee.service.EmployeeService; import net.lab1024.sa.admin.module.system.employee.service.EmployeeService;
import net.lab1024.sa.base.common.domain.PageResult; import org.springframework.beans.factory.annotation.Autowired;
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.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.servlet.http.HttpServletResponse;
import javax.validation.Valid; import java.awt.*;
import java.math.BigDecimal; import java.awt.image.BufferedImage;
import java.util.List; 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 * 无处罚证明开具 Controller
@ -32,58 +31,489 @@ import java.util.List;
* @Date 2025-12-20 14:44:06 * @Date 2025-12-20 14:44:06
* @Copyright 1.0 * @Copyright 1.0
*/ */
@RestController @RestController
@Tag(name = "无处罚证明开具") @Tag(name = "无处罚证明开具")
@RequestMapping("/wordCertificate")
public class WordApplicationsController { public class WordApplicationsController {
@Resource
WordCertificateService wordCertificateService; @Autowired
@Resource private WordCertificateService wordCertificateService;
@Autowired
private PenaltyApplyService penaltyApplyService; private PenaltyApplyService penaltyApplyService;
@Resource
EmployeeService employeeService; @Autowired
@Resource private EmployeeService employeeService;
DepartmentService departmentService;
@Operation(summary = "无处罚证明下载 @author wzh") @Autowired
@GetMapping("/wordCertificate/export/{id}") private DepartmentService departmentService;
public void wordCertificateExport(HttpServletResponse response, @PathVariable Integer id,
@RequestParam(defaultValue = "word") String format) throws Exception { // 字体配置 - 使用文泉驿字体
//查询出当前的开具证明信息 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); PenaltyApplyEntity penaltyApplyEntity = penaltyApplyService.selectOne(id);
//查询用户姓名执业证号 EmployeeEntity employee = employeeService.getById(penaltyApplyEntity.getUserId());
EmployeeEntity byId = employeeService.getById(penaltyApplyEntity.getUserId()); DepartmentVO department = departmentService.getById(employee.getDepartmentId());
//机构信息
DepartmentVO departmentVO = departmentService.getById(byId.getDepartmentId()); return new WordCertificateService.CertificateData(
WordCertificateService.CertificateData certificateData = new WordCertificateService.CertificateData( department.getDepartmentName(),
departmentVO.getDepartmentName(), department.getCreditCode(),
departmentVO.getCreditCode(), employee.getActualName(),
byId.getActualName(), employee.getCertificateNumber(),
byId.getCertificateNumber(),
penaltyApplyEntity.getCreateTime() penaltyApplyEntity.getCreateTime()
); );
}
/**
* 生成证书图片
*/
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);
// 填充白色背景
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, PAGE_WIDTH, PAGE_HEIGHT);
g2d.setColor(Color.BLACK);
// 绘制证书内容
drawCertificateContent(g2d, data);
byte[] fileContent; } finally {
String fileName; g2d.dispose();
String contentType; }
return convertImageToBytes(image);
// 生成Word文档 }
fileContent = wordCertificateService.generateCertificate(certificateData);
// fileName = "certificate.docx"; /**
//contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; * 设置Graphics2D参数
*/
private void setupGraphics(Graphics2D g2d) {
// 设置文档响应头 g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//response.setContentType(contentType); g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
//response.setHeader("Content-Disposition", "attachment; filename=\"" + fileName + "\"; filename*=UTF-8''" + fileName); g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
// 设置响应头 g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
response.setContentType("image/png"); g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
response.setHeader("Content-Disposition", "inline; filename=\"preview.png\""); }
response.setHeader("Cache-Control", "max-age=3600");
response.setContentLength(fileContent.length); /**
* 绘制证书内容
// 将文件内容写入响应输出流 */
response.getOutputStream().write(fileContent); private void drawCertificateContent(Graphics2D g2d, WordCertificateService.CertificateData data) {
response.getOutputStream().flush(); // 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>");
return result.toString();
}
}

5
数据同步

@ -13,4 +13,7 @@ WHERE EXISTS (
WHERE ti.principal IS NOT NULL WHERE ti.principal IS NOT NULL
AND te.employee_id IS NOT NULL AND te.employee_id IS NOT NULL
AND te.employee_id = re.employee_id 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