From 0f11bea64e963b2231875760bd4e9103e99b8a63 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 09 十月 2026 16:00:17 +0800
Subject: [PATCH] refactor(mes): 重构二维码标签生成逻辑
---
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/QrCodeImageUtils.java | 184 ++++++++++++++++-----------------------------
1 files changed, 65 insertions(+), 119 deletions(-)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/QrCodeImageUtils.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/QrCodeImageUtils.java
index 864066c..c35c287 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/QrCodeImageUtils.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/framework/qrcode/QrCodeImageUtils.java
@@ -17,10 +17,7 @@
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
-import java.util.ArrayList;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
@@ -28,8 +25,7 @@
/**
* 浜岀淮鐮佹爣绛惧浘鐗囧伐鍏风被
* <p>
- * 鐢熸垚"浜岀淮鐮� + 鍙充晶鍙鏂囨湰"鐨勫悎鎴愬浘鐗囷細浜岀淮鐮佸彧缂栫爜绾爜鍊硷紝鏂囨湰鍙犲姞鍏抽敭淇℃伅锛�
- * 鍗充娇鏁版嵁搴撲涪澶变篃鑳戒粠鎵撳嵃鍥剧墖杩樺師鍏抽敭淇℃伅銆�
+ * 鐢熸垚浜岀淮鐮佸湪涓娿�佺爜鍊煎湪涓嬬殑绱у噾鏍囩銆備簩缁寸爜鍐呭浠嶄娇鐢ㄨ皟鐢ㄦ柟浼犲叆鐨勬函婧愰摼鎺ャ��
*/
@Slf4j
public final class QrCodeImageUtils {
@@ -45,61 +41,65 @@
/** 鍥剧墖鍥涘懆鐣欑櫧 */
private static final int PADDING = 16;
/** 浜岀淮鐮佷笌鏂囨湰闂磋窛 */
- private static final int GAP = 24;
- /** 鏂囨湰鍖哄煙瀹藉害 */
- private static final int TEXT_WIDTH = 380;
+ private static final int GAP = 4;
/** 瀛椾綋澶у皬 */
- private static final int FONT_SIZE = 15;
+ private static final int FONT_SIZE = 24;
/** 琛岄珮 */
- private static final int LINE_HEIGHT = 24;
- /** 瀛楁闂磋窛 */
- private static final int FIELD_GAP = 6;
+ private static final int LINE_HEIGHT = 32;
- /** 涓枃瀛椾綋鍊欓�夛紙鎸変紭鍏堢骇锛夛紝閬垮厤 Linux headless 涓嬫枃瀛楀彉鏂规 */
+ /**
+ * 涓枃瀛椾綋鍊欓�夛紙鎸変紭鍏堢骇锛夈��
+ * <p>
+ * 鍙厑璁告敹褰曞彲娓叉煋涓枃鐨勫瓧浣擄細绾媺涓佸瓧浣擄紙濡� Arial锛変竴鏃﹀叆閫夛紝涓枃浼氬叏閮ㄦ覆鏌撴垚鏂规锛�
+ * 涓斿鍑鸿繃绋嬩笉鎶ラ敊锛屽睘浜庢渶闅愯斀鐨勪竴绫讳簨鏁呫��
+ */
private static final String[] FONT_CANDIDATES = {
- "Noto Sans CJK SC", "WenQuanYi Zen Hei", "Microsoft YaHei", "SimSun", "Arial"
+ "Noto Sans CJK SC", "Source Han Sans SC", "WenQuanYi Zen Hei", "PingFang SC",
+ "Hiragino Sans GB", "Microsoft YaHei UI", "Microsoft YaHei", "SimSun"
};
+ /** 涓枃瀛楀舰鎺㈤拡瀛楃锛堢敤浜庢帰娴嬪瓧浣撴槸鍚﹀叿澶囦腑鏂囧瓧褰級 */
+ private static final char CJK_PROBE = '琚�';
+
+ /** 瑙f瀽鍑虹殑涓枃瀛椾綋鏃忓悕锛涗负 null 琛ㄧず褰撳墠鐜娌℃湁浠讳綍鍙覆鏌撲腑鏂囩殑瀛椾綋 */
private static final String FONT_NAME = resolveFontName();
/**
* 鐢熸垚浜岀淮鐮佹爣绛惧浘鐗�
*
- * @param code 浜岀淮鐮佸唴瀹癸紙绾爜鍊硷級
- * @param keyValues 鍙犲姞鐨勫彲璇绘枃鏈紙椤哄簭鍗崇粯鍒堕『搴忥級
+ * @param code 浜岀淮鐮佸唴瀹癸紙婧簮閾炬帴鎴栫函鐮佸�硷級
+ * @param displayCode 浜岀淮鐮佷笅鏂规樉绀虹殑鍘熷鐮佸��
* @return 鍚堟垚鍥剧墖
*/
- public static BufferedImage createQrLabel(String code, LinkedHashMap<String, String> keyValues) {
+ public static BufferedImage createQrLabel(String code, String displayCode) {
QrConfig config = new QrConfig();
- config.setErrorCorrection(ErrorCorrectionLevel.H); // 鎵撳嵃鏍囩鏄撶(鎹燂紝鐢ㄩ珮瀹归敊
- config.setMargin(1); // 淇濊瘉闈欏尯锛屾壂鐮佺巼
+ config.setErrorCorrection(ErrorCorrectionLevel.H);
+ config.setMargin(4); // 淇濈暀浜岀淮鐮侀潤鍖猴紝淇濊瘉鎵撳嵃鍚庣殑璇嗚鐜�
config.setWidth(QR_SIZE);
config.setHeight(QR_SIZE);
BufferedImage qrImage = QrCodeUtil.generate(code, config);
- Font labelFont = new Font(FONT_NAME, Font.BOLD, FONT_SIZE);
- Font valueFont = new Font(FONT_NAME, Font.PLAIN, FONT_SIZE);
-
- // 绗竴閬嶏細娴嬮噺鏂囨湰鍧楁�婚珮搴︼紝鐢ㄤ簬纭畾鍥剧墖楂樺害
- int textHeight = measureTextHeight(keyValues, labelFont, valueFont);
-
- int height = PADDING * 2 + Math.max(QR_SIZE, textHeight);
- int width = PADDING * 2 + QR_SIZE + GAP + TEXT_WIDTH;
-
+ int width = PADDING * 2 + QR_SIZE;
+ int height = PADDING * 2 + QR_SIZE + GAP + LINE_HEIGHT;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
try {
- g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
- // 浜岀淮鐮侊紙鍨傜洿灞呬腑锛�
- int qrY = (height - QR_SIZE) / 2;
- g.drawImage(qrImage, PADDING, qrY, null);
- // 鍙鏂囨湰
- int textX = PADDING + QR_SIZE + GAP;
- int textY = (height - textHeight) / 2;
- drawKeyValues(g, keyValues, labelFont, valueFont, textX, textY);
+ g.drawImage(qrImage, PADDING, PADDING, null);
+ String text = StrUtil.nullToEmpty(displayCode);
+ int fontSize = FONT_SIZE;
+ g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, fontSize));
+ while (g.getFontMetrics().stringWidth(text) > QR_SIZE && fontSize > 1) {
+ g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, --fontSize));
+ }
+ FontMetrics metrics = g.getFontMetrics();
+ int textX = (width - metrics.stringWidth(text)) / 2;
+ int textY = PADDING + QR_SIZE + GAP
+ + (LINE_HEIGHT - metrics.getHeight()) / 2 + metrics.getAscent();
+ g.setColor(Color.BLACK);
+ g.drawString(text, textX, textY);
} finally {
g.dispose();
}
@@ -120,7 +120,7 @@
ZipOutputStream zip = new ZipOutputStream(response.getOutputStream());
try {
for (QrLabelInfo label : labels) {
- BufferedImage image = createQrLabel(label.getCode(), label.getKeyValues());
+ BufferedImage image = createQrLabel(label.getCode(), label.getFileName());
zip.putNextEntry(new ZipEntry(label.getFileName() + ".png"));
ImageIO.write(image, "png", zip);
zip.closeEntry();
@@ -134,100 +134,46 @@
// ==================== 绉佹湁鏂规硶 ====================
+ /**
+ * 瑙f瀽褰撳墠鐜鍙敤鐨勪腑鏂囧瓧浣撴棌鍚嶃��
+ * <p>
+ * 涓嶈兘鎸夊瓧浣撴棌鍚嶅仛绮剧‘鍖归厤锛氬悓涓�瀛椾綋鍦ㄤ笉鍚岀郴缁熶笂鐨勬棌鍚嶅彲鑳戒笉鍚岋紙渚嬪 Windows 涓婂疄闄呮敞鍐屼负
+ * {@code Microsoft YaHei UI} 鑰岄潪 {@code Microsoft YaHei}锛夛紝绮剧‘鍖归厤钀界┖鍚庝細涓�璺��鍒扮函鎷変竵瀛椾綋锛�
+ * 涓枃鍗虫覆鏌撲负鏂规銆傚洜姝よ繖閲岀粺涓�鐢� {@link Font#canDisplay(char)} 鎺㈡祴涓枃瀛楀舰銆�
+ *
+ * @return 鍙覆鏌撲腑鏂囩殑瀛椾綋鏃忓悕锛涙棤鍙敤瀛椾綋鏃惰繑鍥� {@code null}
+ */
private static String resolveFontName() {
Set<String> families = Set.of(GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames());
for (String candidate : FONT_CANDIDATES) {
- if (families.contains(candidate)) {
+ if (families.contains(candidate) && canDisplayCjk(candidate)) {
return candidate;
}
}
- return Font.SANS_SERIF;
- }
-
- private static int measureTextHeight(LinkedHashMap<String, String> keyValues, Font labelFont, Font valueFont) {
- BufferedImage scratch = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
- Graphics2D g = scratch.createGraphics();
- try {
- return measureKeyValuesHeight(g, keyValues, labelFont, valueFont);
- } finally {
- g.dispose();
+ // 閫昏緫瀛椾綋鐢� JDK 璐熻矗瀛楀舰鍥為��锛屽鏁扮幆澧冨彲娓叉煋涓枃
+ if (canDisplayCjk(Font.SANS_SERIF)) {
+ return Font.SANS_SERIF;
}
- }
-
- private static int measureKeyValuesHeight(Graphics2D g, LinkedHashMap<String, String> keyValues,
- Font labelFont, Font valueFont) {
- int total = 0;
- for (Map.Entry<String, String> entry : keyValues.entrySet()) {
- String label = StrUtil.emptyToDefault(entry.getKey(), "") + "锛�";
- String value = StrUtil.emptyToDefault(entry.getValue(), "");
- g.setFont(labelFont);
- int labelWidth = g.getFontMetrics().stringWidth(label);
- g.setFont(valueFont);
- List<String> lines = wrapText(g, value, TEXT_WIDTH - labelWidth);
- total += Math.max(1, lines.size()) * LINE_HEIGHT + FIELD_GAP;
- }
- return total;
- }
-
- private static void drawKeyValues(Graphics2D g, LinkedHashMap<String, String> keyValues,
- Font labelFont, Font valueFont, int x, int y) {
- FontMetrics labelMetrics = g.getFontMetrics(labelFont);
- int baselineOffset = labelMetrics.getAscent(); // drawString 鐨� y 鏄熀绾匡紝鍏堜笅绉讳竴涓� ascent
- int baseline = y + baselineOffset;
- for (Map.Entry<String, String> entry : keyValues.entrySet()) {
- String label = StrUtil.emptyToDefault(entry.getKey(), "") + "锛�";
- String value = StrUtil.emptyToDefault(entry.getValue(), "");
-
- g.setFont(labelFont);
- int labelWidth = g.getFontMetrics().stringWidth(label);
- g.setColor(Color.BLACK);
- g.drawString(label, x, baseline);
-
- g.setFont(valueFont);
- int remainingWidth = TEXT_WIDTH - labelWidth;
- List<String> lines = wrapText(g, value, remainingWidth);
- if (!lines.isEmpty()) {
- g.drawString(lines.get(0), x + labelWidth, baseline);
- baseline += LINE_HEIGHT;
- for (int i = 1; i < lines.size(); i++) {
- g.drawString(lines.get(i), x, baseline);
- baseline += LINE_HEIGHT;
- }
- } else {
- baseline += LINE_HEIGHT;
- }
- baseline += FIELD_GAP;
- }
+ return null;
}
/**
- * 鏂囨湰鎸夋渶澶у搴︽崲琛岋紙閫愬瓧娴嬮噺锛屽吋瀹逛腑鏂囷級
+ * 鎺㈡祴鎸囧畾瀛椾綋鏃忚兘鍚︽覆鏌撲腑鏂�
*/
- private static List<String> wrapText(Graphics2D g, String text, int maxWidth) {
- List<String> lines = new ArrayList<>();
- if (StrUtil.isEmpty(text)) {
- return lines;
+ private static boolean canDisplayCjk(String family) {
+ return new Font(family, Font.PLAIN, FONT_SIZE).canDisplay(CJK_PROBE);
+ }
+
+ /**
+ * 鏍¢獙褰撳墠鐜瀛樺湪鍙覆鏌撲腑鏂囩殑瀛椾綋锛屽惁鍒欐姏鍑哄紓甯搞��
+ * <p>
+ * 渚涘惎鍔ㄦ湡璋冪敤锛氬畞鍙湪鍚姩鏃跺け璐ワ紝涔熶笉鑳芥妸涓枃娓叉煋鎴愭柟妗嗙殑鏍囩浜や粯鍗板埛銆�
+ */
+ public static void assertCjkFontAvailable() {
+ if (FONT_NAME == null) {
+ throw new IllegalStateException("褰撳墠鐜娌℃湁浠讳綍鍙覆鏌撲腑鏂囩殑瀛椾綋锛屼簩缁寸爜鏍囩鐨勪腑鏂囧皢鏄剧ず涓烘柟妗嗭紝"
+ + "鎷掔粷鐢熸垚鏍囩銆傝鍦ㄦ湇鍔″櫒瀹夎涓枃瀛椾綋锛堝 fonts-noto-cjk锛夊悗閲嶅惎銆�");
}
- if (maxWidth <= 0) {
- lines.add(text);
- return lines;
- }
- StringBuilder current = new StringBuilder();
- FontMetrics metrics = g.getFontMetrics();
- for (int i = 0; i < text.length(); i++) {
- char ch = text.charAt(i);
- String test = current.toString() + ch;
- if (metrics.stringWidth(test) > maxWidth && current.length() > 0) {
- lines.add(current.toString());
- current = new StringBuilder().append(ch);
- } else {
- current.append(ch);
- }
- }
- if (current.length() > 0) {
- lines.add(current.toString());
- }
- return lines;
}
}
--
Gitblit v1.9.3