Переглянути джерело

feat(game-event): 添加云端字体加载功能支持OSS字体文件

- 引入OssFactory和OssClient依赖用于OSS文件操作
- 实现loadCustomFont方法支持通过ossId从OSS加载字体文件
- 在GenerateBibBo中添加fontOssId字段用于传递云端字体ID
- 修改号码绘制逻辑优先使用云端字体当fontOssId不为空时
- 更新赛事名称绘制逻辑支持云端字体加载
- 移除调试日志减少控制台输出
- 删除预览框尺寸常量定义优化代码结构
zhou 2 тижнів тому
батько
коміт
b03ffa82bf

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

@@ -43,9 +43,6 @@ public class NumberController {
     private final IGameBibTaskService gameBibTaskService;
     private final ISysOssService sysOssService;
 
-    // 预览框尺寸(固定)
-    private static final int previewWidth = 600;
-
     /**
      *
      * @return key:队伍名称 value:队伍运动员
@@ -82,12 +79,12 @@ public class NumberController {
         Long eventId = Long.valueOf(cacheObject.toString());
 
         // 调试日志
-        log.info("创建参赛证任务,taskName: {}, bibParam: {}", taskName, bibParam);
-        log.info("bibParam详情 - fontName: {}, fontSize: {}, fontColor: {}, logoX: {}, logoY: {}",
-                bibParam.getFontName(), bibParam.getFontSize(), bibParam.getFontColor(),
-                bibParam.getLogoX(), bibParam.getLogoY());
-        log.info("二维码坐标详情 - qRCodeX: {}, qRCodeY: {}",
-                bibParam.getQRCodeX(), bibParam.getQRCodeY());
+//        log.info("创建参赛证任务,taskName: {}, bibParam: {}", taskName, bibParam);
+//        log.info("bibParam详情 - fontName: {}, fontSize: {}, fontColor: {}, logoX: {}, logoY: {}",
+//                bibParam.getFontName(), bibParam.getFontSize(), bibParam.getFontColor(),
+//                bibParam.getLogoX(), bibParam.getLogoY());
+//        log.info("二维码坐标详情 - qRCodeX: {}, qRCodeY: {}",
+//                bibParam.getQRCodeX(), bibParam.getQRCodeY());
         // log.info("Logo缩放: {}, 二维码缩放: {}", bibParam.getLogoScale(), bibParam.getBarcodeScale());
 
         // 先保存图片文件,再创建任务
@@ -178,9 +175,9 @@ public class NumberController {
             }
 
             // 生成1小时有效的预签名URL
-            log.info("开始生成预签名URL,taskId: {}, ossId: {}", taskId, task.getOssId());
+//            log.info("开始生成预签名URL,taskId: {}, ossId: {}", taskId, task.getOssId());
             String presignedUrl = sysOssService.generatePresignedUrl(task.getOssId(), 3600);
-            log.info("预签名URL生成成功: {}", presignedUrl);
+//            log.info("预签名URL生成成功: {}", presignedUrl);
             return R.ok(presignedUrl);
         } catch (Exception e) {
             log.error("获取下载URL失败,taskId: {}", taskId, e);
@@ -195,7 +192,7 @@ public class NumberController {
         try {
             // 读取背景图片
             BufferedImage originalBgImage = ImageIO.read(new File(bgImagePath));
-            log.info("原始背景图片尺寸: {}x{}", originalBgImage.getWidth(), originalBgImage.getHeight());
+//            log.info("原始背景图片尺寸: {}x{}", originalBgImage.getWidth(), originalBgImage.getHeight());
 
             // 获取目标画布尺寸
             int targetWidth = bibParam.getCanvasWidth() != null ? bibParam.getCanvasWidth() : originalBgImage.getWidth();
@@ -212,11 +209,11 @@ public class NumberController {
 
             // 将原始背景图片拉伸/缩放到目标画布尺寸
             g2d.drawImage(originalBgImage, 0, 0, targetWidth, targetHeight, null);
-            log.info("背景图片已调整到目标尺寸: {}x{}", targetWidth, targetHeight);
+//            log.info("背景图片已调整到目标尺寸: {}x{}", targetWidth, targetHeight);
 
             // 绘制Logo(如果存在)
             if (logoImagePath != null && new File(logoImagePath).exists()) {
-                log.info("开始绘制Logo - 路径: {}", logoImagePath);
+//                log.info("开始绘制Logo - 路径: {}", logoImagePath);
                 drawLogoOnTemplate(g2d, logoImagePath, bibParam, bgImage.getWidth(), bgImage.getHeight());
             } else {
                 log.warn("Logo图片不存在或路径为空: {}", logoImagePath);
@@ -224,7 +221,7 @@ public class NumberController {
 
             // 绘制赛事名称(如果存在)
             if (StringUtils.isNotBlank(bibParam.getEventName())) {
-                log.info("开始绘制赛事名称: {}", bibParam.getEventName());
+//                log.info("开始绘制赛事名称: {}", bibParam.getEventName());
                 drawEventNameOnTemplate(g2d, bibParam.getEventName(), bibParam, bgImage.getWidth(), bgImage.getHeight());
             } else {
                 log.warn("赛事名称为空");
@@ -235,7 +232,7 @@ public class NumberController {
             // 转换为字节数组
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
             ImageIO.write(bgImage, "PNG", baos);
-            log.info("画布模版生成完成,大小: {} bytes", baos.size());
+//            log.info("画布模版生成完成,大小: {} bytes", baos.size());
             return baos.toByteArray();
 
         } catch (Exception e) {
@@ -251,7 +248,7 @@ public class NumberController {
         try {
             // 检查Logo图片路径是否为空
             if (logoImagePath == null || logoImagePath.isEmpty()) {
-                log.info("Logo图片路径为空,跳过Logo绘制");
+//                log.info("Logo图片路径为空,跳过Logo绘制");
                 return;
             }
 
@@ -269,7 +266,7 @@ public class NumberController {
                 return;
             }
 
-            log.info("Logo图片尺寸: {}x{}", logoImage.getWidth(), logoImage.getHeight());
+//            log.info("Logo图片尺寸: {}x{}", logoImage.getWidth(), logoImage.getHeight());
 
             // 计算Logo位置(直接使用前端传入的百分比坐标)
             Double logoX = bibParam.getLogoX();
@@ -280,13 +277,13 @@ public class NumberController {
                 // 直接使用前端传入的像素坐标
                 x = logoX.intValue();
                 y = logoY.intValue();
-                log.info("Logo坐标转换 - 前端传入像素坐标: ({}, {}), 实际图片坐标: ({}, {})",
-                    logoX, logoY, x, y);
+//                log.info("Logo坐标转换 - 前端传入像素坐标: ({}, {}), 实际图片坐标: ({}, {})",
+//                    logoX, logoY, x, y);
             } else {
                 // 使用默认位置(左上角)
                 x = (int) (4.17 * canvasWidth / 100.0);  // 4.17%
                 y = (int) (6.25 * canvasHeight / 100.0); // 6.25%
-                log.info("使用默认Logo位置: ({}, {})", x, y);
+//                log.info("使用默认Logo位置: ({}, {})", x, y);
             }
 
             // 使用传入的Logo尺寸(前端已计算好最终尺寸)
@@ -295,7 +292,7 @@ public class NumberController {
                 // 使用前端传入的Logo尺寸(已包含所有缩放计算)
                 scaledWidth = bibParam.getLogoWidth();
                 scaledHeight = bibParam.getLogoHeight();
-                log.info("使用前端计算的Logo尺寸: {}x{}", scaledWidth, scaledHeight);
+//                log.info("使用前端计算的Logo尺寸: {}x{}", scaledWidth, scaledHeight);
             }
 
             // 实现前端的translateX(-50%)水平居中效果
@@ -311,8 +308,8 @@ public class NumberController {
 
             // 绘制Logo
             g2d.drawImage(logoImage, x, y, scaledWidth, scaledHeight, null);
-            log.info("Logo绘制完成 - 位置: ({}, {}), 尺寸: {}x{}, 画布尺寸: {}x{}",
-                x, y, scaledWidth, scaledHeight, canvasWidth, canvasHeight);
+//            log.info("Logo绘制完成 - 位置: ({}, {}), 尺寸: {}x{}, 画布尺寸: {}x{}",
+//                x, y, scaledWidth, scaledHeight, canvasWidth, canvasHeight);
 
         } catch (Exception e) {
             log.error("绘制Logo失败", e);
@@ -330,14 +327,19 @@ public class NumberController {
 
             // 使用前端传递的字体名称,否则使用默认字体
             String fontName = bibParam.getFontName();
+            Long fontOssId = bibParam.getFontOssId();
             Font font;
-            if (fontName == null || "simhei".equalsIgnoreCase(fontName) || "SimHei".equals(fontName)) {
+            if (fontOssId != null) {
+                // 如果指定了云端字体,优先从 OSS 加载
+                font = gameEventService.loadCustomFont(fontOssId, fontSize);
+//                log.info("赛事名称使用云端字体: {}, ossId: {}", font, fontOssId);
+            } else if (fontName == null || "simhei".equalsIgnoreCase(fontName) || "SimHei".equals(fontName)) {
                 // 如果指定了黑体或未指定字体,则使用自定义加载的黑体字体,确保线上环境字体一致
                 font = gameEventService.loadCustomFont("simhei", fontSize);
-                log.warn("使用自定义黑体字体: {}", font);
+//                log.info("赛事名称使用自定义黑体字体: {}", font);
             } else {
                 font = gameEventService.loadCustomFont(fontName, fontSize);
-                log.warn("使用自定义字体: {}", font);
+//                log.info("赛事名称使用自定义字体: {}", font);
             }
             g2d.setFont(font);
 
@@ -352,8 +354,8 @@ public class NumberController {
 
             // 居中绘制 - 与前端 transform: translateX(-50%) 和 transformOrigin: 'top center' 保持一致
             g2d.drawString(eventName, x - textWidth / 2, y + textAscent);
-            log.info("赛事名称绘制完成 - 位置: ({}, {}), 字体大小: {}, 画布尺寸: {}x{}",
-                x, y, fontSize, canvasWidth, canvasHeight);
+//            log.info("赛事名称绘制完成 - 位置: ({}, {}), 字体大小: {}, 画布尺寸: {}x{}",
+//                x, y, fontSize, canvasWidth, canvasHeight);
 
         } catch (Exception e) {
             log.error("绘制赛事名称失败", e);

+ 2 - 0
ruoyi-modules/ruoyi-game-event/src/main/java/org/dromara/system/domain/bo/GenerateBibBo.java

@@ -35,6 +35,8 @@ public class GenerateBibBo implements Serializable {
     private String fontName;
     private Integer fontSize;
     private Integer fontColor;
+    @JsonProperty("fontOssId")
+    private Long fontOssId;
     @JsonProperty("numberFontSize")
     private Integer numberFontSize;
 

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

@@ -164,4 +164,6 @@ public interface IGameEventService {
 
     Font loadCustomFont(String fontPath, int fontSize);
 
+    Font loadCustomFont(Long ossId, int fontSize);
+
 }

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

@@ -34,6 +34,8 @@ import org.dromara.system.mapper.GameEventMapper;
 import org.dromara.system.config.FileUploadConfig;
 import org.dromara.system.service.*;
 import org.dromara.system.domain.vo.SysOssVo;
+import org.dromara.common.oss.factory.OssFactory;
+import org.dromara.common.oss.core.OssClient;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.scheduling.annotation.Async;
@@ -140,6 +142,29 @@ public class GameEventServiceImpl implements IGameEventService {
         }
     }
 
+    @Override
+    public Font loadCustomFont(Long ossId, int fontSize) {
+        if (ossId == null) {
+            return new Font("SimHei", Font.BOLD, fontSize);
+        }
+        try {
+            SysOssVo sysOss = sysOssService.getById(ossId);
+            if (sysOss == null) {
+                log.warn("未找到OSS字体文件, ossId: {}", ossId);
+                return new Font("SimHei", Font.BOLD, fontSize);
+            }
+            OssClient storage = OssFactory.instance(sysOss.getService());
+            try (InputStream is = storage.getObjectContent(sysOss.getFileName())) {
+                Font font = Font.createFont(Font.TRUETYPE_FONT, is);
+                log.info("成功从OSS加载字体文件: {}", sysOss.getOriginalName());
+                return font.deriveFont(Font.BOLD, fontSize);
+            }
+        } catch (Exception e) {
+            log.error("从OSS加载字体失败,ossId: {}", ossId, e);
+            return new Font("SimHei", Font.BOLD, fontSize);
+        }
+    }
+
     // 预览框尺寸(固定)
     private static final int previewWidth = 600;
     private static final int previewHeight = 400;
@@ -1216,8 +1241,13 @@ public class GameEventServiceImpl implements IGameEventService {
 
             // 确保字体名称匹配系统可用字体
             String fontName = bibParam.getFontName();
+            Long fontOssId = bibParam.getFontOssId();
             Font font;
-            if (fontName == null || "simhei".equalsIgnoreCase(fontName) || "SimHei".equals(fontName)) {
+            if (fontOssId != null) {
+                // 如果指定了云端字体,优先从 OSS 加载
+                font = loadCustomFont(fontOssId, fontSize);
+                log.info("号码使用云端字体: {}, ossId: {}", font, fontOssId);
+            } else if (fontName == null || "simhei".equalsIgnoreCase(fontName) || "SimHei".equals(fontName)) {
                 // 如果指定了黑体或未指定字体,则使用自定义加载的黑体字体,确保线上环境字体一致
                 font = loadCustomFont("simhei", fontSize);
                 log.info("号码使用自定义黑体字体: {}", font);