Parcourir la source

feat(game-event): 实现自定义字体加载和二维码生成功能

- 移除本地路径配置,统一使用生产环境路径
- 将字体加载功能从NumberController迁移至GameEventService
- 添加loadCustomFont方法支持自定义字体文件加载
- 优化二维码生成算法,提升图像质量
- 统一字体处理逻辑,确保线上环境字体一致性
- 删除过时的本周改动文档文件
zhou il y a 3 mois
Parent
commit
846d2db1a4

+ 1 - 2
ruoyi-admin/src/main/resources/application-prod.yml

@@ -275,5 +275,4 @@ justauth:
 
 file:
   upload:
-#    path: /www/wwwroot/game_event/files
-    path: D:\A_projects\gameEvent\game_event\files
+    path: /www/wwwroot/game_event/files

+ 2 - 34
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/controller/NumberController.java

@@ -47,9 +47,6 @@ public class NumberController {
     // 预览框尺寸(固定)
     private static final int previewWidth = 600;
 
-    // 字体缓存,避免重复加载
-    private static Font simheiFont = null;
-
     /**
      *
      * @return key:队伍名称 value:队伍运动员
@@ -413,35 +410,6 @@ public class NumberController {
         }
     }
 
-    /**
-     * 在模版上绘制赛事名称
-     */
-    /**
-     * 加载黑体字体
-     * @return 加载的黑体字体,如果加载失败则返回系统默认字体
-     */
-    private Font loadCustomFont(String fontPath, int fontSize) {
-        try {
-            if (simheiFont == null) {
-                // 从资源目录加载字体文件
-                try (InputStream is = getClass().getClassLoader().getResourceAsStream("fonts/"+fontPath+".ttf")) {
-                    if (is != null) {
-                        simheiFont = Font.createFont(Font.TRUETYPE_FONT, is);
-                        log.info("成功加载{}字体文件", fontPath);
-                    } else {
-                        log.warn("未找到字体文件: fonts/{}.ttf", fontPath);
-                        return new Font("SimHei", Font.BOLD, fontSize);
-                    }
-                }
-            }
-            // 调整字体大小并返回
-            return simheiFont.deriveFont(Font.BOLD, fontSize);
-        } catch (Exception e) {
-            log.error("加载字体失败,使用系统默认字体", e);
-            return new Font("SimHei", Font.BOLD, fontSize);
-        }
-    }
-
     private void drawEventNameOnTemplate(Graphics2D g2d, String eventName, GenerateBibBo bibParam, int canvasWidth, int canvasHeight) {
         try {
             // 使用前端传递的像素坐标
@@ -456,10 +424,10 @@ public class NumberController {
             Font font;
             if (fontName == null || "simhei".equalsIgnoreCase(fontName) || "SimHei".equals(fontName)) {
                 // 如果指定了黑体或未指定字体,则使用自定义加载的黑体字体,确保线上环境字体一致
-                font = loadCustomFont("simhei", fontSize);
+                font = gameEventService.loadCustomFont("simhei", fontSize);
                 log.warn("使用自定义黑体字体: {}", font);
             } else {
-                font = loadCustomFont(fontName, fontSize);
+                font = gameEventService.loadCustomFont(fontName, fontSize);
                 log.warn("使用自定义字体: {}", font);
             }
             g2d.setFont(font);

+ 4 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/IGameEventService.java

@@ -11,6 +11,7 @@ import org.dromara.system.domain.vo.GameEventVo;
 import org.dromara.system.domain.vo.ProjectProgressVo;
 import org.springframework.web.multipart.MultipartFile;
 
+import java.awt.*;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -160,4 +161,7 @@ public interface IGameEventService {
      */
     Boolean updateEventQrCode(Long eventId);
 
+
+    Font loadCustomFont(String fontPath, int fontSize);
+
 }

+ 41 - 18
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/service/impl/GameEventServiceImpl.java

@@ -25,6 +25,7 @@ import org.dromara.common.json.utils.JsonUtils;
 import org.dromara.common.mybatis.core.page.PageQuery;
 import org.dromara.common.mybatis.core.page.TableDataInfo;
 import org.dromara.common.redis.utils.RedisUtils;
+import org.dromara.system.controller.NumberController;
 import org.dromara.system.domain.GameEvent;
 import org.dromara.system.domain.bo.*;
 import org.dromara.system.domain.constant.GameEventConstant;
@@ -117,6 +118,28 @@ public class GameEventServiceImpl implements IGameEventService {
         }
     }
 
+    @Override
+    public Font loadCustomFont(String fontPath, int fontSize) {
+        Font simheiFont = null;
+        try {
+            // 从资源目录加载字体文件
+            try (InputStream is = NumberController.class.getClassLoader().getResourceAsStream("fonts/"+fontPath+".ttf")) {
+                if (is != null) {
+                    simheiFont = Font.createFont(Font.TRUETYPE_FONT, is);
+                    log.info("成功加载{}字体文件", fontPath);
+                } else {
+                    log.warn("未找到字体文件: fonts/{}.ttf", fontPath);
+                    return new Font("SimHei", Font.BOLD, fontSize);
+                }
+            }
+            // 调整字体大小并返回
+            return simheiFont.deriveFont(Font.BOLD, fontSize);
+        } catch (Exception e) {
+            log.error("加载字体失败,使用系统默认字体", e);
+            return new Font("SimHei", Font.BOLD, fontSize);
+        }
+    }
+
     // 预览框尺寸(固定)
     private static final int previewWidth = 600;
     private static final int previewHeight = 400;
@@ -770,28 +793,26 @@ public class GameEventServiceImpl implements IGameEventService {
     private static byte[] generateQRCode(String data, int width, int height) throws Exception {
         // 使用QRCodeWriter而不是MultiFormatWriter,以便更好地控制二维码生成
         QRCodeWriter qrCodeWriter = new QRCodeWriter();
-        
+
         Map<EncodeHintType, Object> hints = new HashMap<>();
         hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
         // 设置纠错级别为L(最小纠错,最大数据容量)
         hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);
         // 设置边距为0,确保二维码内容填满指定尺寸
         hints.put(EncodeHintType.MARGIN, 0);
-        
-        // 计算合适的二维码版本,确保所有二维码使用相同的版本
-        // 版本5对应37x37模块,足够容纳所有运动员信息
+
         // 使用QRCodeWriter.encode方法直接生成指定尺寸的矩阵
         BitMatrix matrix = qrCodeWriter.encode(
-            data, 
-            BarcodeFormat.QR_CODE, 
-            width, 
-            height, 
+            data,
+            BarcodeFormat.QR_CODE,
+            width,
+            height,
             hints
         );
-        
+
         // 创建最终尺寸的图像
         BufferedImage qrImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
-        
+
         // 直接在最终尺寸的图像上绘制二维码数据
         // 确保每个像素都被正确设置,避免缩放差异
         for (int x = 0; x < width; x++) {
@@ -801,7 +822,7 @@ public class GameEventServiceImpl implements IGameEventService {
                 qrImage.setRGB(x, y, color);
             }
         }
-        
+
         // 将图像写入ByteArrayOutputStream
         ByteArrayOutputStream out = new ByteArrayOutputStream();
         ImageIO.write(qrImage, "png", out);
@@ -1195,13 +1216,15 @@ public class GameEventServiceImpl implements IGameEventService {
 
             // 确保字体名称匹配系统可用字体
             String fontName = bibParam.getFontName();
-            if (fontName == null) {
-                fontName = "SimHei";
-            } else if ("simhei".equalsIgnoreCase(fontName)) {
-                fontName = "SimHei";
+            Font font;
+            if (fontName == null || "simhei".equalsIgnoreCase(fontName) || "SimHei".equals(fontName)) {
+                // 如果指定了黑体或未指定字体,则使用自定义加载的黑体字体,确保线上环境字体一致
+                font = loadCustomFont("simhei", fontSize);
+                log.info("号码使用自定义黑体字体: {}", font);
+            } else {
+                font = loadCustomFont(fontName, fontSize);
+                log.info("号码使用自定义字体: {}", font);
             }
-
-            Font font = new Font(fontName, Font.BOLD, fontSize);
             g2d.setFont(font);
 
             // 设置颜色
@@ -1249,7 +1272,7 @@ public class GameEventServiceImpl implements IGameEventService {
                 scaledHeight = initialQrSize * 3;
                 log.info("使用默认二维码尺寸: {}x{}", scaledWidth, scaledHeight);
             }
-            
+
             // 直接使用最终尺寸生成二维码,避免二次缩放
             byte[] qrCodeBytes = generateQRCode(qrData, scaledWidth, scaledHeight);
             BufferedImage qrImage = ImageIO.read(new ByteArrayInputStream(qrCodeBytes));

+ 0 - 58
本周改动.md

@@ -1,58 +0,0 @@
-<h1>本周改动</h1>
-
-<li>完成赛事与菜单的关联</li>
-<li>完成部分App端查询接口</li>
-<li>完成App端用户建议相关接口</li>
-<li>完善菜单栏相关接口</li>
-<li>统一活动菜单管理的图标颜色的传值类型</li>
-<li>完善活动菜单管理的搜索条件区域功能</li>
-<li>增加全局默认赛事显示功能</li>
-<li>美化赛事操作列样式</li>
-<li>完成用户反馈</li>
-<li>完成app端赛事相关的富文本编辑</li>
-<li>完善app端部分接口</li>
-<li>修改赛事列表操作列位置</li>
-<li>修改菜单栏图标显示</li>
-<li>还原赛事-备份、恢复</li>
-<li>补充数据测试功能</li>
-<li>修改各模块列表项映射格式,方便后期维护</li>
-<li>修复成绩模块显示逻辑bug</li>
-<li>完善运动员模块与队伍的关联</li>
-<li>优化赛事界面布局和操作流程</li>
-<li>添加组别详细信息,如人数、组数、道数等</li>
-<li>实现分组自动计算和时间验证功能</li>
-<li>增加报名表导入校验-仅允许无报名数据的赛事进行导入</li>
-<li>号码对照表生成</li>
-<li>号码布开发</li>
-<li>优化成绩编辑界面,适配不同项目类型</li>
-<li>重构个人和团体项目的排名和积分计算方法</li>
-<li>生成二维码</li>
-<li>完成号码布开发导出zip</li>
-<li>增加权限校验</li>
-<li>修复部分bug</li>
-<li>增加字体</li>
-
-
-
-<h2>8.20</h2>
-
-<li>完善运动员模块与队伍的关联</li>
-<li>优化赛事界面布局和操作流程</li>
-<li>添加组别详细信息,如人数、组数、道数等</li>
-<li>实现分组自动计算和时间验证功能</li>
-
-
-<h2>8.21</h2>
-
-<li>增加报名表导入校验-仅允许无报名数据的赛事进行导入</li>
-<li>号码对照表生成</li>
-<li>号码布开发</li>
-<li>优化成绩编辑界面,适配不同项目类型</li>
-<li>重构个人和团体项目的排名和积分计算方法</li>
-
-<h2>8.22</h2>
-
-<li>生成二维码</li>
-<li>完成号码布开发导出zip</li>
-<li>增加权限校验</li>
-<li>修复部分bug</li>