ソースを参照

fileDownloader 新增获取文件 base64编码接口

WangKang 1 ヶ月 前
コミット
f5d1d1b5e0

+ 215 - 0
docs/2026-03-26-code-changes-summary.md

@@ -0,0 +1,215 @@
+# 代码变更说明文档
+
+**日期**: 2026-03-26  
+**提交次数**: 5 次  
+**涉及文件**: 200+ 个文件
+
+---
+
+## 一、提交概览
+
+| 提交ID | 提交说明 | 时间 |
+|--------|----------|------|
+| 4de959d0 | 新增表结构的增删改查接口 | 20:49 |
+| d2bd9e28 | 基础对话;SSE 流式响应;会话创建+历史查选接口 | 20:16 |
+| 4dc0df0f | ai agent | 18:25 |
+| 9b5a845e | add docs | 17:37 |
+| 93cc7508 | 收敛多余项目,非 admin、openplatform 外的功能收敛进 emoon-extend; 基础设施收敛进 emoon-infra | 16:21 |
+
+---
+
+## 二、主要功能变更
+
+### 1. AI Agent 引擎配置管理 (4de959d0)
+
+**新增功能**: AI Agent 引擎配置的完整 CRUD 接口
+
+**涉及模块**: `emoon-infra/emoon-modules-api/emoon-system-api`
+
+**新增文件**:
+- `AiAgentEngineConfigBo.java` - 业务对象
+- `AiAgentEngineConfigVo.java` - 视图对象
+- `AiAgentEngineConfigMapper.java` - 数据访问层
+- `IAiAgentEngineConfigService.java` - 服务接口
+- `AiAgentEngineConfigServiceImpl.java` - 服务实现
+- `AiAgentEngineConfigController.java` - 控制器层
+
+**功能说明**: 提供 AI Agent 引擎配置的增删改查接口,支持配置不同 AI 引擎的参数。
+
+---
+
+### 2. 智能对话系统 (d2bd9e28)
+
+**新增功能**: 基础对话能力、SSE 流式响应、会话管理
+
+**涉及模块**: `emoon-openplatform`
+
+**核心功能**:
+
+#### 2.1 Agent 对话控制器
+- 文件: `AgentController.java`
+- 功能: 提供对话接口,支持流式响应
+
+#### 2.2 DTO/VO 对象
+- `AgentChatRequest.java` - 对话请求对象
+- `AgentChatResponse.java` - 对话响应对象
+- `ConversationCreateRequest.java` - 会话创建请求
+- `ConversationVo.java` - 会话视图对象
+
+#### 2.3 服务层
+- `IAgentChatService.java` / `AgentChatServiceImpl.java` - 对话服务
+- `IConversationService.java` / `ConversationServiceImpl.java` - 会话管理服务
+
+#### 2.4 工具类
+- `SignUtil.java` - 签名工具,用于接口安全验证
+
+**技术特点**:
+- 使用 SSE (Server-Sent Events) 实现流式响应
+- 支持会话创建和历史记录查询
+
+---
+
+### 3. AI Agent 核心引擎 (4dc0df0f)
+
+**新增功能**: AI Agent 核心引擎架构
+
+**涉及模块**: `emoon-infra/emoon-modules-api/emoon-mcp-api` 和 `emoon-system-api`
+
+#### 3.1 引擎核心 (mcp-api)
+
+| 文件 | 说明 |
+|------|------|
+| `AgentEngine.java` | 引擎接口定义 |
+| `AgentEngineFactory.java` | 引擎工厂,用于创建引擎实例 |
+| `AgentRequest.java` / `AgentResponse.java` | 请求/响应对象 |
+| `MockAgentEngine.java` | 模拟引擎实现(用于测试) |
+
+#### 3.2 领域模型 (新增 8 个实体)
+
+**AI Agent 应用相关**:
+- `AiAgentApp.java` - AI 应用定义
+- `AiAgentEngineConfig.java` - 引擎配置
+
+**AI 卡片相关**:
+- `AiCardInstance.java` - 卡片实例
+- `AiCardDefinition.java` - 卡片定义
+- `AiCardCategory.java` - 卡片分类
+- `AiCardPlugin.java` - 卡片插件
+- `AiCardGrayConfig.java` - 灰度配置
+- `AiCardActionLog.java` - 操作日志
+
+**对话相关**:
+- `AiConversation.java` - 对话记录
+
+**使用统计**:
+- `AiUsageLog.java` - 使用日志
+
+#### 3.3 数据访问层
+每个实体对应一个 Mapper 接口,共 8 个 Mapper。
+
+#### 3.4 服务层
+每个实体对应一个 Service 接口和实现,共 8 个 Service。
+
+---
+
+### 4. 项目架构重构 (93cc7508)
+
+**重构内容**: 项目结构收敛整理
+
+#### 4.1 收敛到 emoon-extend 的模块
+
+**emoon-file-downloader** - 文件下载服务
+- 独立的文件下载微服务
+- 包含 Dockerfile 支持容器化部署
+
+**emoon-tongue** - 舌诊服务
+- 舌诊 AI 诊断功能
+- 包含 H5 页面支持
+- 主要组件:
+  - 舌诊控制器 (CreateRecordController, DiagnosisController 等)
+  - 舌诊服务实现 (TongueDiagnosisServiceImpl, TongueAiDiagnosisServiceImpl 等)
+  - MinIO 文件存储集成
+  - JWT 认证工具
+
+#### 4.2 收敛到 emoon-infra 的基础设施
+
+**emoon-common 公共模块**:
+- `emoon-common-core` - 核心工具类、常量、异常定义
+- `emoon-common-doc` - SpringDoc 接口文档配置
+- `emoon-common-encrypt` - 数据加密解密 (AES、RSA、SM2、SM4)
+- `emoon-common-excel` - Excel 导入导出
+- `emoon-common-httpclient` - HTTP 客户端封装
+- `emoon-common-idempotent` - 幂等性控制
+- `emoon-common-job` - 定时任务配置
+- `emoon-common-json` - JSON 处理
+- `emoon-common-log` - 日志注解和切面
+
+**emoon-modules-api 模块**:
+- `emoon-system-api` - 系统服务 API
+- `emoon-mcp-api` - MCP 服务 API
+
+---
+
+### 5. 文档更新 (9b5a845e)
+
+**新增文档**: `integrated-medical-agent-dify-card-solution.md`
+
+**内容**: 医疗智能体 Dify 卡片集成方案说明
+
+---
+
+## 三、技术栈说明
+
+### 新增依赖
+- SSE 流式响应支持
+- 签名验证工具
+
+### 架构特点
+1. **分层架构**: Controller -> Service -> Mapper 清晰分层
+2. **工厂模式**: AgentEngineFactory 用于引擎实例创建
+3. **模块化设计**: 功能按模块划分,便于维护扩展
+4. **基础设施复用**: 通用功能收敛到 emoon-infra
+
+---
+
+## 四、接口清单
+
+### 新增 Controller 接口
+
+| 模块 | Controller | 说明 |
+|------|------------|------|
+| emoon-system | AiAgentEngineConfigController | 引擎配置 CRUD |
+| emoon-openplatform | AgentController | 对话接口、会话管理 |
+
+---
+
+## 五、数据库表结构
+
+### 新增表 (8 张)
+
+| 表名 | 说明 |
+|------|------|
+| ai_agent_app | AI 应用表 |
+| ai_agent_engine_config | 引擎配置表 |
+| ai_card_instance | 卡片实例表 |
+| ai_card_definition | 卡片定义表 |
+| ai_card_category | 卡片分类表 |
+| ai_card_plugin | 卡片插件表 |
+| ai_card_gray_config | 灰度配置表 |
+| ai_card_action_log | 操作日志表 |
+| ai_conversation | 对话记录表 |
+| ai_usage_log | 使用日志表 |
+
+---
+
+## 六、后续建议
+
+1. **接口文档**: 建议为新增接口补充 Swagger 注解
+2. **单元测试**: 为 Service 层添加单元测试
+3. **权限控制**: 检查 Controller 接口的权限注解配置
+4. **日志规范**: 统一使用 @Log 注解记录操作日志
+
+---
+
+**文档生成时间**: 2026-03-27  
+**生成工具**: QoderWork

+ 80 - 0
emoon-extend/emoon-file-downloader/src/main/java/com/emoon/filedownloader/controller/DownloadController.java

@@ -166,6 +166,86 @@ public class DownloadController {
         }
     }
 
+    /**
+     * 从服务器获取文件内容(返回base64编码),供上游Java服务调用
+     * <p>
+     * // 上游拿到 content 字段后直接解码
+     * byte[] fileBytes = Base64.getDecoder().decode(data.get("content"));
+     * // 写入本地 / 传给前端 / 进一步处理
+     * </p>
+     * // 拿到 content 字段后直接解码
+     * byte[] fileBytes = Base64.getDecoder().decode(data.get("content"));
+     * // 写入本地 / 传给前端 / 进一步处理
+     *
+     * @param fileName 文件名(由 /download 接口返回的 fileName 字段)
+     * @return 文件的base64内容及元信息
+     * 响应示例:
+     * <p>
+     * {
+     * "code": 200,
+     * "msg": "获取文件成功",
+     * "data": {
+     * "fileName": "report_abc123.pdf",
+     * "filePath": "/tmp/downloads/report_abc123.pdf",
+     * "fileSize": 102400,
+     * "contentType": "application/pdf",
+     * "content": "JVBERi0xLjQK..."
+     * }
+     * }
+     * </p>
+     */
+    @GetMapping("/fetch/{fileName}")
+    public R<Map<String, Object>> fetchFile(@PathVariable String fileName) {
+        try {
+            // 安全性检查
+            if (StringUtils.isEmpty(fileName) || fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) {
+                return R.fail("文件名包含非法字符");
+            }
+
+            Path filePath = Paths.get(basePath, fileName);
+            File file = filePath.toFile();
+
+            if (!file.exists()) {
+                return R.fail("文件不存在");
+            }
+
+            // 检查文件是否在允许的目录下
+            String canonicalBasePath = new File(basePath).getCanonicalPath();
+            String canonicalFilePath = file.getCanonicalPath();
+            if (!canonicalFilePath.startsWith(canonicalBasePath)) {
+                log.warn("尝试访问不在允许目录下的文件: {}", canonicalFilePath);
+                return R.fail("无权限访问该文件");
+            }
+
+            // 读取文件并编码为base64
+            byte[] fileBytes = Files.readAllBytes(filePath);
+            String base64Content = Base64.getEncoder().encodeToString(fileBytes);
+
+            // 探测 Content-Type
+            String contentType = Files.probeContentType(filePath);
+            if (contentType == null) {
+                contentType = "application/octet-stream";
+            }
+
+            Map<String, Object> result = new HashMap<>();
+            result.put("fileName", fileName);
+            result.put("filePath", filePath.toString());
+            result.put("fileSize", fileBytes.length);
+            result.put("contentType", contentType);
+            result.put("content", base64Content);
+
+            log.info("文件读取成功: {}, 大小={}bytes", filePath, fileBytes.length);
+            return R.ok("获取文件成功", result);
+
+        } catch (IOException e) {
+            log.error("读取文件失败", e);
+            return R.fail("读取文件失败:" + e.getMessage());
+        } catch (Exception e) {
+            log.error("系统异常", e);
+            return R.fail("系统异常,请稍后重试");
+        }
+    }
+
     /**
      * 获取文件信息(可选功能,用于检查文件是否存在)
      *