Sfoglia il codice sorgente

1、更新获取数字人接口(根据年级取数字人)
2、给视频做切片请求
3、视频图片做缓存

liyanbo 11 mesi fa
parent
commit
6c15b9961d

+ 0 - 1
byzs-module-ai/src/main/java/cn/iocoder/byzs/module/ai/controller/admin/model/AiChatRoleController.java

@@ -36,7 +36,6 @@ public class AiChatRoleController {
     @GetMapping("/my-page")
     @Operation(summary = "获得【我的】聊天角色分页")
     public CommonResult<PageResult<AiChatRoleRespVO>> getChatRoleMyPage(@Valid AiChatRolePageReqVO pageReqVO) {
-        pageReqVO.setCategory("老师");
         PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, getLoginUserId());
         return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class));
     }

+ 45 - 0
byzs-module-infra/src/main/java/cn/iocoder/byzs/module/infra/controller/admin/file/FileController.java

@@ -106,6 +106,51 @@ public class FileController {
             response.setStatus(HttpStatus.NOT_FOUND.value());
             return;
         }
+
+        // 新增:处理 Range 分片请求(关键)
+        String rangeHeader = request.getHeader("Range");
+        if (StrUtil.isNotBlank(rangeHeader) && rangeHeader.startsWith("bytes=")) {
+            // 解析 Range 格式:bytes=start-end
+            String range = rangeHeader.substring("bytes=".length());
+            String[] rangeParts = range.split("-");
+            long contentLength = content.length;
+            long start = 0;
+            long end = contentLength - 1;
+
+            // 解析 start
+            if (StrUtil.isNotBlank(rangeParts[0])) {
+                start = Long.parseLong(rangeParts[0]);
+                if (start < 0 || start >= contentLength) {
+                    response.setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value());
+                    return;
+                }
+            }
+            // 解析 end
+            if (rangeParts.length > 1 && StrUtil.isNotBlank(rangeParts[1])) {
+                end = Long.parseLong(rangeParts[1]);
+                if (end >= contentLength) {
+                    end = contentLength - 1;
+                }
+            }
+
+            // 截取分片内容
+            int startInt = (int) start;
+            int endInt = (int) end;
+            byte[] rangeContent = new byte[endInt - startInt + 1];
+            System.arraycopy(content, startInt, rangeContent, 0, rangeContent.length);
+
+            // 设置 206 状态码(部分内容)
+            response.setStatus(HttpStatus.PARTIAL_CONTENT.value());
+            // 设置分片响应头
+            response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + contentLength);
+            response.setContentLength(rangeContent.length);
+
+            // 输出分片内容(复用 writeAttachment 但调整参数)
+            writeAttachment(response, path, rangeContent);
+            return;
+        }
+
+        // 非分片请求:输出完整内容
         writeAttachment(response, path, content);
     }
 

+ 22 - 9
byzs-module-infra/src/main/java/cn/iocoder/byzs/module/infra/framework/file/core/utils/FileTypeUtils.java

@@ -72,24 +72,37 @@ public class FileTypeUtils {
     }
 
     /**
-     * 返回附件
+     * 返回附件(优化版:支持缓存和视频分片)
      *
      * @param response 响应
      * @param filename 文件名
-     * @param content  附件内容
+     * @param content  内容(完整或分片)
      */
     public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {
-        // 设置 header 和 contentType
-        response.setHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
+        // 1. 基础头设置
         String contentType = getMineType(content, filename);
         response.setContentType(contentType);
-        // 针对 video 的特殊处理,解决视频地址在移动端播放的兼容性问题
+        response.setContentLength(content.length);
+
+        // 2. 缓存设置(核心:添加缓存头)
+        // 视频缓存30天,其他文件缓存7天(可按需调整)
+        long maxAge = StrUtil.containsIgnoreCase(contentType, "video") ? 30 * 24 * 60 * 60 : 7 * 24 * 60 * 60;
+        response.setHeader("Cache-Control", "public, max-age=" + maxAge); // 公共缓存,有效期30天
+        response.setHeader("Pragma", "public"); // 兼容老浏览器
+
+        // 3. 文件名处理(视频用 inline 播放,非视频用 attachment 下载)
+        String disposition = StrUtil.containsIgnoreCase(contentType, "video")
+                ? "inline;filename=" + HttpUtils.encodeUtf8(filename) // 视频:在线播放
+                : "attachment;filename=" + HttpUtils.encodeUtf8(filename); // 其他:下载
+        response.setHeader("Content-Disposition", disposition);
+
+        // 4. 视频特殊处理(修正原逻辑错误)
         if (StrUtil.containsIgnoreCase(contentType, "video")) {
-            response.setHeader("Content-Length", String.valueOf(content.length - 1));
-            response.setHeader("Content-Range", String.valueOf(content.length - 1));
-            response.setHeader("Accept-Ranges", "bytes");
+            response.setHeader("Accept-Ranges", "bytes"); // 支持分片
+            // 原代码的 Content-Range 格式错误,已在 getFileContent 中正确设置,此处无需重复
         }
-        // 输出附件
+
+        // 5. 输出内容
         IoUtil.write(response.getOutputStream(), false, content);
     }
 

+ 3 - 3
byzs-server/src/main/resources/application.yaml

@@ -45,10 +45,10 @@ springdoc:
   swagger-ui:
     enabled: true
     path: /swagger-ui
-  default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档
+  default-flat-param-object: true
 
 knife4j:
-  enable: false # TODO lyb:需要关闭增强,具体原因见:https://github.com/xiaoymin/knife4j/issues/874
+  enable: false
   setting:
     language: zh_cn
 
@@ -321,6 +321,6 @@ byzs:
         customer: E77DF18BE109F454A5CD319E44BF5177
 
 debug: false
-# 插件配置 TODO lyb:【IOT】需要处理下
+# 插件配置
 pf4j:
   pluginsDir: /Users/anhaohao/code/gitee/ruoyi-vue-pro/plugins # 插件目录

+ 6 - 5
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/ai/WebAiController.java

@@ -18,6 +18,7 @@ import cn.iocoder.byzs.module.ai.service.chat.AiChatConversationService;
 import cn.iocoder.byzs.module.ai.service.chat.AiChatMessageService;
 import cn.iocoder.byzs.module.ai.service.image.AiImageService;
 import cn.iocoder.byzs.module.ai.service.model.AiChatRoleService;
+import cn.iocoder.byzs.module.web.controller.admin.ai.vo.WebAiChatRoleVO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -113,12 +114,12 @@ public class WebAiController {
     }
 
 
+    @PermitAll
+    @TenantIgnore
     @GetMapping("/selectRoleModel")
     @Operation(summary = "获得聊天角色分页")
-    public CommonResult<PageResult<AiChatRoleRespVO>> selectRoleModel() {
-        AiChatRolePageReqVO pageReqVO = new AiChatRolePageReqVO();
-        pageReqVO.setCategory("老师");
-        PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, getLoginUserId());
-        return success(BeanUtils.toBean(pageResult, AiChatRoleRespVO.class));
+    public CommonResult<PageResult<WebAiChatRoleVO>> selectRoleModel(@Valid AiChatRolePageReqVO pageReqVO) {
+        PageResult<AiChatRoleDO> pageResult = chatRoleService.getChatRoleMyPage(pageReqVO, userId);
+        return success(BeanUtils.toBean(pageResult, WebAiChatRoleVO.class));
     }
 }

+ 44 - 0
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/ai/vo/WebAiChatRoleVO.java

@@ -0,0 +1,44 @@
+package cn.iocoder.byzs.module.web.controller.admin.ai.vo;
+
+import cn.iocoder.byzs.module.ai.dal.dataobject.model.AiModelDO;
+import com.fhs.core.trans.anno.Trans;
+import com.fhs.core.trans.constant.TransType;
+import com.fhs.core.trans.vo.VO;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Schema(description = "管理后台 - AI 聊天角色 Response VO")
+@Data
+public class WebAiChatRoleVO implements VO {
+
+    @Schema(description = "角色编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "32746")
+    private Long id;
+
+    @Schema(description = "模型编号", example = "17640")
+    private Long modelId;
+    @Schema(description = "模型2d路径", example = "**.glb")
+    private String model2dPath;
+    @Schema(description = "模型3d路径", example = "**.glb")
+    private String model3dPath;
+
+    @Schema(description = "角色名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "李四")
+    private String name;
+
+    @Schema(description = "角色头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/1.png")
+    private String avatar;
+
+    @Schema(description = "角色类别", requiredMode = Schema.RequiredMode.REQUIRED, example = "创作")
+    private String category;
+
+    @Schema(description = "角色排序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer sort;
+
+    @Schema(description = "角色描述", requiredMode = Schema.RequiredMode.REQUIRED, example = "你说的对")
+    private String description;
+
+    @Schema(description = "角色设定", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String systemMessage;
+}