From 81508a1fdc6692ec3d9477274810f62079a6a2d3 Mon Sep 17 00:00:00 2001 From: wangys <3401275564@qq.com> Date: Sat, 20 Dec 2025 15:14:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E4=BB=B6=E4=B8=8A=E4=BC=A0=E4=B8=8B?= =?UTF-8?q?=E8=BD=BD=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../database/postgres/sql/createTableFile.sql | 4 +- .../ai/controller/KnowledgeController.java | 18 +- .../xyzh/ai/mapper/TbKnowledgeFileMapper.java | 9 +- .../ai/service/impl/KnowledgeServiceImpl.java | 54 ++-- .../mapper/TbKnowledgeFileMapper.xml | 60 +++- .../xyzh/api/ai/service/KnowledgeService.java | 14 +- .../org/xyzh/api/ai/vo/KnowledgeFileVO.java | 60 ++++ .../xyzh/file/controller/FileController.java | 6 +- .../java/org/xyzh/file/mapper/FileMapper.java | 14 +- .../file/service/impl/FileServiceImpl.java | 23 +- .../file/src/main/resources/application.yml | 25 +- .../src/main/resources/application.yml | 7 + .../packages/bidding/public/app-config.js | 4 +- .../packages/bidding/src/config/index.ts | 253 +++++++++++++++++ .../packages/platform/public/app-config.js | 4 +- .../packages/platform/src/config/index.ts | 262 ++++++++++++++++- .../packages/shared/src/api/ai/aiKnowledge.ts | 15 +- .../packages/shared/src/api/file/file.ts | 2 +- .../ai/DocumentDetail/DocumentDetail.scss | 0 .../ai/DocumentDetail/DocumentDetail.vue | 9 + .../shared/src/components/ai/index.ts | 1 + .../packages/shared/src/components/index.ts | 1 + .../packages/shared/src/config/index.ts | 4 +- .../packages/workcase/public/app-config.js | 4 +- .../packages/workcase/src/config.ts | 34 --- .../packages/workcase/src/config/index.ts | 265 ++++++++++++++++++ .../packages/workcase/src/main.ts | 2 +- .../views/admin/knowledge/KnowLedgeView.vue | 97 +++---- 28 files changed, 1059 insertions(+), 192 deletions(-) create mode 100644 urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/vo/KnowledgeFileVO.java create mode 100644 urbanLifelineWeb/packages/bidding/src/config/index.ts create mode 100644 urbanLifelineWeb/packages/shared/src/components/ai/DocumentDetail/DocumentDetail.scss create mode 100644 urbanLifelineWeb/packages/shared/src/components/ai/DocumentDetail/DocumentDetail.vue create mode 100644 urbanLifelineWeb/packages/shared/src/components/ai/index.ts delete mode 100644 urbanLifelineWeb/packages/workcase/src/config.ts create mode 100644 urbanLifelineWeb/packages/workcase/src/config/index.ts diff --git a/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql b/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql index dc85a220..06b2f7ac 100644 --- a/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql +++ b/urbanLifelineServ/.bin/database/postgres/sql/createTableFile.sql @@ -23,7 +23,7 @@ CREATE TABLE file.tb_sys_file ( type VARCHAR(50) DEFAULT NULL, -- 文件类型 storage_type VARCHAR(50) DEFAULT NULL, -- 存储类型 mime_type VARCHAR(255) DEFAULT NULL, -- MIME 类型 - url VARCHAR(500) DEFAULT NULL, -- 文件访问 URL + url VARCHAR(500) DEFAULT NULL, -- 后端下载接口路径(保留用于扩展,建议使用 /api/file/download/{fileId}) status VARCHAR(50) DEFAULT NULL, -- 文件状态 module VARCHAR(100) DEFAULT NULL, -- 所属模块 business_id VARCHAR(50) DEFAULT NULL, -- 业务ID @@ -60,7 +60,7 @@ COMMENT ON COLUMN file.tb_sys_file.size IS '文件大小(字节)'; COMMENT ON COLUMN file.tb_sys_file.type IS '文件类型'; COMMENT ON COLUMN file.tb_sys_file.storage_type IS '存储类型'; COMMENT ON COLUMN file.tb_sys_file.mime_type IS 'MIME 类型'; -COMMENT ON COLUMN file.tb_sys_file.url IS '文件访问 URL'; +COMMENT ON COLUMN file.tb_sys_file.url IS '后端下载接口路径(保留用于扩展,建议使用 /api/file/download/{fileId})'; COMMENT ON COLUMN file.tb_sys_file.status IS '文件状态'; COMMENT ON COLUMN file.tb_sys_file.module IS '所属模块'; COMMENT ON COLUMN file.tb_sys_file.business_id IS '业务ID'; diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/controller/KnowledgeController.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/controller/KnowledgeController.java index 9d09bb61..2b4a6d9b 100644 --- a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/controller/KnowledgeController.java +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/controller/KnowledgeController.java @@ -19,6 +19,7 @@ import org.xyzh.api.ai.service.DifyProxyService; import org.xyzh.api.ai.service.KnowledgeService; import org.xyzh.api.ai.dto.TbKnowledge; import org.xyzh.api.ai.dto.TbKnowledgeFile; +import org.xyzh.api.ai.vo.KnowledgeFileVO; import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.utils.validation.ValidationResult; @@ -161,19 +162,16 @@ public class KnowledgeController { // ====================== 文件管理 ====================== /** - * @description 获取知识库文档列表 - * @param knowledgeId 知识库id + * @description 获取知识库文档列表(含文件详细信息) + * @param pageRequest 分页请求 * @author yslg - * @since 2025-12-18 + * @since 2025-12-20 */ @PreAuthorize("hasAuthority('ai:knowledge:file:view')") - @GetMapping("/{knowledgeId}/documents") - public ResultDomain> getDocumentList( - @PathVariable("knowledgeId") @NotBlank String knowledgeId, - @RequestParam(value = "page", defaultValue = "1") Integer page, - @RequestParam(value = "limit", defaultValue = "20") Integer limit) { - logger.info("获取文档列表: knowledgeId={}", knowledgeId); - return knowledgeService.getDocumentList(knowledgeId, page, limit); + @PostMapping("/{knowledgeId}/documents") + public ResultDomain getDocumentList(@RequestBody PageRequest pageRequest) { + logger.info("获取文档列表: knowledgeId={}", pageRequest.getFilter().getKnowledgeId()); + return knowledgeService.getDocumentList(pageRequest); } /** diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/mapper/TbKnowledgeFileMapper.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/mapper/TbKnowledgeFileMapper.java index 9b1090de..ab5b60fc 100644 --- a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/mapper/TbKnowledgeFileMapper.java +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/mapper/TbKnowledgeFileMapper.java @@ -3,6 +3,7 @@ package org.xyzh.ai.mapper; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.xyzh.api.ai.dto.TbKnowledgeFile; +import org.xyzh.api.ai.vo.KnowledgeFileVO; import org.xyzh.common.core.page.PageParam; import java.util.List; @@ -41,9 +42,9 @@ public interface TbKnowledgeFileMapper { ); /** - * 根据知识库ID查询文件列表 + * 根据知识库ID查询文件列表(关联文件详细信息) */ - List selectFilesByKnowledgeId(@Param("knowledgeId") String knowledgeId); + List selectFilesByKnowledgeId(@Param("knowledgeId") String knowledgeId); /** * 根据文件根ID查询所有版本 @@ -51,9 +52,9 @@ public interface TbKnowledgeFileMapper { List selectFileVersions(@Param("fileRootId") String fileRootId); /** - * 分页查询知识库文件 + * 分页查询知识库文件(关联文件详细信息) */ - List selectFilePage( + List selectFilePage( @Param("knowledgeId") String knowledgeId, @Param("pageParam") PageParam pageParam ); diff --git a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/service/impl/KnowledgeServiceImpl.java b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/service/impl/KnowledgeServiceImpl.java index cd791b30..51e4203d 100644 --- a/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/service/impl/KnowledgeServiceImpl.java +++ b/urbanLifelineServ/ai/src/main/java/org/xyzh/ai/service/impl/KnowledgeServiceImpl.java @@ -18,11 +18,14 @@ import org.xyzh.api.ai.dto.TbKnowledge; import org.xyzh.api.ai.dto.TbKnowledgeFile; import org.xyzh.api.ai.service.AIFileUploadService; import org.xyzh.api.ai.service.KnowledgeService; +import org.xyzh.api.ai.vo.KnowledgeFileVO; import org.xyzh.api.file.dto.TbSysFileDTO; import org.xyzh.api.file.service.FileService; import org.xyzh.common.core.domain.LoginDomain; import org.xyzh.common.core.domain.ResultDomain; +import org.xyzh.common.core.page.PageDomain; import org.xyzh.common.core.page.PageParam; +import org.xyzh.common.core.page.PageRequest; import org.xyzh.common.utils.id.IdUtil; import org.xyzh.common.auth.utils.LoginUtil; @@ -405,32 +408,43 @@ public class KnowledgeServiceImpl implements KnowledgeService { } /** - * @description 获取知识库文档列表(从Dify获取) - * @param knowledgeId 知识库ID - * @param page 页码(从1开始) - * @param limit 每页数量 - * @return ResultDomain> 文档列表 + * @description 获取知识库文档列表(查询本地数据库文件) + * @param pageRequest 分页请求,filter 中包含 knowledgeId + * @return ResultDomain 文档列表 * @author yslg - * @since 2025-12-18 + * @since 2025-12-20 */ @Override - public ResultDomain> getDocumentList(String knowledgeId, Integer page, Integer limit) { - TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId); - if (knowledge == null || !StringUtils.hasText(knowledge.getDifyDatasetId())) { - return ResultDomain.failure("知识库不存在或未关联Dify"); + public ResultDomain getDocumentList(PageRequest pageRequest) { + TbKnowledgeFile filter = pageRequest.getFilter(); + if (filter == null || !StringUtils.hasText(filter.getKnowledgeId())) { + return ResultDomain.failure("知识库ID不能为空"); } - + + String knowledgeId = filter.getKnowledgeId(); + + // 验证知识库是否存在 + TbKnowledge knowledge = knowledgeMapper.selectKnowledgeById(knowledgeId); + if (knowledge == null) { + return ResultDomain.failure("知识库不存在"); + } + try { - DocumentListResponse response = difyApiClient.listDocuments(knowledge.getDifyDatasetId(), page, limit); - Map result = new HashMap<>(); - result.put("data", response.getData()); - result.put("total", response.getTotal()); - result.put("page", response.getPage()); - result.put("limit", response.getLimit()); - return ResultDomain.success("查询成功", result); + // 分页查询知识库文件(已通过 SQL JOIN 关联文件详细信息) + PageParam pageParam = pageRequest.getPageParam(); + List files = knowledgeFileMapper.selectFilePage(knowledgeId, pageParam); + long total = knowledgeFileMapper.countFiles(knowledgeId); + + // 设置总数 + pageParam.setTotal((int) total); + + // 创建分页结果 + PageDomain pageDomain = new PageDomain<>(pageParam, files); + + return ResultDomain.success("查询成功", pageDomain); } catch (Exception e) { - logger.error("获取Dify文档列表失败: {}", e.getMessage(), e); - return ResultDomain.failure("获取文档列表失败: " + e.getMessage()); + logger.error("获取知识库文件列表失败: knowledgeId={}, error={}", knowledgeId, e.getMessage(), e); + return ResultDomain.failure("获取文件列表失败: " + e.getMessage()); } } diff --git a/urbanLifelineServ/ai/src/main/resources/mapper/TbKnowledgeFileMapper.xml b/urbanLifelineServ/ai/src/main/resources/mapper/TbKnowledgeFileMapper.xml index 1588226c..85cfc5bf 100644 --- a/urbanLifelineServ/ai/src/main/resources/mapper/TbKnowledgeFileMapper.xml +++ b/urbanLifelineServ/ai/src/main/resources/mapper/TbKnowledgeFileMapper.xml @@ -15,6 +15,26 @@ + + + + + + + + + + + + + + + + + + + + optsn, knowledge_id, file_root_id, file_id, dify_file_id, version, create_time, update_time, delete_time, deleted @@ -53,11 +73,21 @@ WHERE knowledge_id = #{knowledgeId} AND file_id = #{fileId} AND deleted = false - + SELECT + kf.optsn, kf.knowledge_id, kf.file_root_id, kf.file_id, kf.dify_file_id, kf.version, + kf.create_time, kf.update_time, kf.delete_time, kf.deleted, + f.name as file_name, + f.path as file_path, + f.size as file_size, + f.mime_type as file_mime_type, + f.url as file_url, + f.extension as file_extension, + f.md5_hash as file_md5_hash + FROM ai.tb_knowledge_file kf + LEFT JOIN file.tb_sys_file f ON kf.file_id = f.file_id AND f.deleted = false + WHERE kf.knowledge_id = #{knowledgeId} AND kf.deleted = false + ORDER BY kf.create_time DESC - + SELECT + kf.optsn, kf.knowledge_id, kf.file_root_id, kf.file_id, kf.dify_file_id, kf.version, + kf.create_time, kf.update_time, kf.delete_time, kf.deleted, + f.name as file_name, + f.path as file_path, + f.size as file_size, + f.mime_type as file_mime_type, + f.url as file_url, + f.extension as file_extension, + f.md5_hash as file_md5_hash + FROM ai.tb_knowledge_file kf + LEFT JOIN file.tb_sys_file f ON kf.file_id = f.file_id AND f.deleted = false + WHERE kf.knowledge_id = #{knowledgeId} AND kf.deleted = false + ORDER BY kf.create_time DESC LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset} diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/service/KnowledgeService.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/service/KnowledgeService.java index 44c26dba..6dd975dc 100644 --- a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/service/KnowledgeService.java +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/service/KnowledgeService.java @@ -6,8 +6,10 @@ import java.util.Map; import org.springframework.web.multipart.MultipartFile; import org.xyzh.api.ai.dto.TbKnowledge; import org.xyzh.api.ai.dto.TbKnowledgeFile; +import org.xyzh.api.ai.vo.KnowledgeFileVO; import org.xyzh.common.core.domain.ResultDomain; import org.xyzh.common.core.page.PageParam; +import org.xyzh.common.core.page.PageRequest; public interface KnowledgeService { @@ -107,15 +109,13 @@ public interface KnowledgeService { // ================================= 文件管理 ================================= /** - * @description 获取知识库文档列表(从Dify获取) - * @param knowledgeId 知识库ID - * @param page 页码(从1开始) - * @param limit 每页数量 - * @return ResultDomain> 文档列表 + * @description 获取知识库文档列表(查询本地数据库,关联文件详细信息) + * @param pageRequest 分页请求,filter 中包含 knowledgeId + * @return ResultDomain 文档列表(含文件详细信息) * @author yslg - * @since 2025-12-18 + * @since 2025-12-20 */ - ResultDomain> getDocumentList(String knowledgeId, Integer page, Integer limit); + ResultDomain getDocumentList(PageRequest pageRequest); /** * @description 上传文件到知识库(完整流程:minio + Dify + 数据库) diff --git a/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/vo/KnowledgeFileVO.java b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/vo/KnowledgeFileVO.java new file mode 100644 index 00000000..e6286a78 --- /dev/null +++ b/urbanLifelineServ/apis/api-ai/src/main/java/org/xyzh/api/ai/vo/KnowledgeFileVO.java @@ -0,0 +1,60 @@ +package org.xyzh.api.ai.vo; + +import org.xyzh.api.ai.dto.TbKnowledgeFile; +import org.xyzh.common.vo.BaseVO; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @description 知识库文件视图对象(关联文件信息) + * @filename KnowledgeFileVO.java + * @author yslg + * @copyright xyzh + * @since 2025-12-20 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Schema(description = "知识库文件视图对象") +public class KnowledgeFileVO extends BaseVO { + private static final long serialVersionUID = 1L; + + // TbKnowledgeFile 的字段 + @Schema(description = "知识库ID") + private String knowledgeId; + + @Schema(description = "文件ID") + private String fileId; + + @Schema(description = "文件根ID") + private String fileRootId; + + @Schema(description = "Dify文件ID") + private String difyFileId; + + @Schema(description = "文件版本") + private Integer version; + + // TbSysFile 的字段 + @Schema(description = "文件名") + private String fileName; + + @Schema(description = "文件路径") + private String filePath; + + @Schema(description = "文件大小(字节)") + private Long fileSize; + + @Schema(description = "文件MIME类型") + private String fileMimeType; + + @Schema(description = "文件访问URL") + private String fileUrl; + + @Schema(description = "文件扩展名") + private String fileExtension; + + @Schema(description = "文件MD5值") + private String fileMd5Hash; +} diff --git a/urbanLifelineServ/file/src/main/java/org/xyzh/file/controller/FileController.java b/urbanLifelineServ/file/src/main/java/org/xyzh/file/controller/FileController.java index bedd430a..fece2621 100644 --- a/urbanLifelineServ/file/src/main/java/org/xyzh/file/controller/FileController.java +++ b/urbanLifelineServ/file/src/main/java/org/xyzh/file/controller/FileController.java @@ -78,7 +78,7 @@ public class FileController { @Operation(summary = "获取文件信息") @GetMapping("/{fileId}") - public ResultDomain getFileById(@PathVariable String fileId) { + public ResultDomain getFileById(@PathVariable("fileId") String fileId) { return fileService.getFileById(fileId); } @@ -86,7 +86,7 @@ public class FileController { @Operation(summary = "下载文件") @GetMapping("/download/{fileId}") - public ResponseEntity downloadFile(@PathVariable String fileId) { + public ResponseEntity downloadFile(@PathVariable("fileId") String fileId) { ResultDomain result = fileService.downloadFile(fileId); if (!result.getSuccess() || result.getData() == null) { return ResponseEntity.notFound().build(); @@ -105,7 +105,7 @@ public class FileController { @Operation(summary = "删除文件") @DeleteMapping("/{fileId}") - public ResultDomain deleteFile(@PathVariable String fileId) { + public ResultDomain deleteFile(@PathVariable("fileId") String fileId) { return fileService.deleteFile(fileId); } diff --git a/urbanLifelineServ/file/src/main/java/org/xyzh/file/mapper/FileMapper.java b/urbanLifelineServ/file/src/main/java/org/xyzh/file/mapper/FileMapper.java index 3922188e..8ad585a8 100644 --- a/urbanLifelineServ/file/src/main/java/org/xyzh/file/mapper/FileMapper.java +++ b/urbanLifelineServ/file/src/main/java/org/xyzh/file/mapper/FileMapper.java @@ -25,7 +25,7 @@ public interface FileMapper extends BaseMapper { * @param fileId 文件ID * @return 文件信息 */ - @Select("SELECT * FROM file.tb_sys_file WHERE file_id = #{fileId} AND deleted = 0") + @Select("SELECT * FROM file.tb_sys_file WHERE file_id = #{fileId} AND deleted = false") TbSysFileDTO selectByFileId(@Param("fileId") String fileId); /** @@ -48,7 +48,7 @@ public interface FileMapper extends BaseMapper { * @param updater 更新者 * @return 影响行数 */ - @Update("UPDATE file.tb_sys_file SET deleted = 1, delete_time = CURRENT_TIMESTAMP, updater = #{updater} WHERE file_id = #{fileId}") + @Update("UPDATE file.tb_sys_file SET deleted = true, delete_time = CURRENT_TIMESTAMP, updater = #{updater} WHERE file_id = #{fileId}") int deleteByFileId(@Param("fileId") String fileId, @Param("updater") String updater); /** @@ -57,7 +57,7 @@ public interface FileMapper extends BaseMapper { * @param businessId 业务ID * @return 文件列表 */ - @Select("SELECT * FROM file.tb_sys_file WHERE module = #{module} AND business_id = #{businessId} AND deleted = 0 ORDER BY create_time DESC") + @Select("SELECT * FROM file.tb_sys_file WHERE module = #{module} AND business_id = #{businessId} AND deleted = false ORDER BY create_time DESC") List selectByModuleAndBusinessId(@Param("module") String module, @Param("businessId") String businessId); /** @@ -65,7 +65,7 @@ public interface FileMapper extends BaseMapper { * @param uploader 上传者用户ID * @return 文件列表 */ - @Select("SELECT * FROM file.tb_sys_file WHERE uploader = #{uploader} AND deleted = 0 ORDER BY create_time DESC") + @Select("SELECT * FROM file.tb_sys_file WHERE uploader = #{uploader} AND deleted = false ORDER BY create_time DESC") List selectByUploader(@Param("uploader") String uploader); /** @@ -73,7 +73,7 @@ public interface FileMapper extends BaseMapper { * @param md5Hash MD5哈希值 * @return 文件信息 */ - @Select("SELECT * FROM file.tb_sys_file WHERE md5_hash = #{md5Hash} AND deleted = 0 LIMIT 1") + @Select("SELECT * FROM file.tb_sys_file WHERE md5_hash = #{md5Hash} AND deleted = false LIMIT 1") TbSysFileDTO selectByMd5Hash(@Param("md5Hash") String md5Hash); /** @@ -82,7 +82,7 @@ public interface FileMapper extends BaseMapper { * @param objectName 对象名称 * @return 文件信息 */ - @Select("SELECT * FROM file.tb_sys_file WHERE bucket_name = #{bucketName} AND object_name = #{objectName} AND deleted = 0") + @Select("SELECT * FROM file.tb_sys_file WHERE bucket_name = #{bucketName} AND object_name = #{objectName} AND deleted = false") TbSysFileDTO selectByMinioObject(@Param("bucketName") String bucketName, @Param("objectName") String objectName); /** @@ -92,6 +92,6 @@ public interface FileMapper extends BaseMapper { * @author yslg * @since 2025-12-18 */ - @Select("SELECT MAX(version) FROM file.tb_sys_file WHERE file_root_id = #{fileRootId} AND deleted = 0") + @Select("SELECT MAX(version) FROM file.tb_sys_file WHERE file_root_id = #{fileRootId} AND deleted = false") Integer selectMaxVersionByFileRootId(@Param("fileRootId") String fileRootId); } diff --git a/urbanLifelineServ/file/src/main/java/org/xyzh/file/service/impl/FileServiceImpl.java b/urbanLifelineServ/file/src/main/java/org/xyzh/file/service/impl/FileServiceImpl.java index 195ed4ad..fc56e728 100644 --- a/urbanLifelineServ/file/src/main/java/org/xyzh/file/service/impl/FileServiceImpl.java +++ b/urbanLifelineServ/file/src/main/java/org/xyzh/file/service/impl/FileServiceImpl.java @@ -80,20 +80,19 @@ public class FileServiceImpl implements FileService { return ResultDomain.failure("文件上传到MinIO失败"); } - // 构建文件访问URL - String fileUrl = minioConfig.buildFileUrl(objectName); - // 保存到数据库 TbSysFileDTO fileDTO = new TbSysFileDTO(); + String fileId = UUID.randomUUID().toString(); fileDTO.setOptsn(UUID.randomUUID().toString()); - fileDTO.setFileId(UUID.randomUUID().toString()); + fileDTO.setFileId(fileId); fileDTO.setName(originalFilename); fileDTO.setPath(objectName); fileDTO.setSize(size); fileDTO.setType(extension); fileDTO.setStorageType("MINIO"); fileDTO.setMimeType(contentType); - fileDTO.setUrl(fileUrl); + // URL 设为 NULL,前端通过后端接口 /api/file/download/{fileId} 下载 + fileDTO.setUrl(null); fileDTO.setStatus("NORMAL"); fileDTO.setModule(module); fileDTO.setBusinessId(businessId); @@ -407,24 +406,26 @@ public class FileServiceImpl implements FileService { return ResultDomain.failure("文件上传到MinIO失败"); } - // 生成文件URL - String fileUrl = minioConfig.getEndpoint() + "/" + bucketName + "/" + objectName; - // 创建文件DTO TbSysFileDTO fileDTO = new TbSysFileDTO(); + String fileId = UUID.randomUUID().toString().replace("-", ""); fileDTO.setOptsn(UUID.randomUUID().toString()); - fileDTO.setFileId(UUID.randomUUID().toString().replace("-", "")); + fileDTO.setFileId(fileId); fileDTO.setName(fileName); fileDTO.setPath(objectName); - fileDTO.setUrl(fileUrl); + // URL 设为 NULL,前端通过后端接口 /api/file/download/{fileId} 下载 + fileDTO.setUrl(null); fileDTO.setSize(size); fileDTO.setMimeType(contentType); fileDTO.setExtension(extension); fileDTO.setMd5Hash(md5Hash); fileDTO.setModule(module); fileDTO.setBusinessId(businessId); + fileDTO.setStorageType("MINIO"); + fileDTO.setObjectName(objectName); + fileDTO.setBucketName(bucketName); fileDTO.setVersion(1); - fileDTO.setFileRootId(fileDTO.getFileId()); + fileDTO.setFileRootId(fileId); fileDTO.setCreateTime(new java.util.Date()); // 保存到数据库 diff --git a/urbanLifelineServ/file/src/main/resources/application.yml b/urbanLifelineServ/file/src/main/resources/application.yml index 8f62f8e2..cdcc31d0 100644 --- a/urbanLifelineServ/file/src/main/resources/application.yml +++ b/urbanLifelineServ/file/src/main/resources/application.yml @@ -5,18 +5,19 @@ server: # context-path: /urban-lifeline/file # 微服务架构下,context-path由Gateway管理 # ================== Auth ==================== -urban-lifeline: - auth: - enabled: true - whitelist: - - /swagger-ui/** - - /swagger-ui.html - - /v3/api-docs/** - - /webjars/** - - /favicon.ico - - /error - - /actuator/health - - /actuator/info +auth: + enabled: true + gateway-mode: true + whitelist: + - /swagger-ui/** + - /swagger-ui.html + - /v3/api-docs/** + - /webjars/** + - /favicon.ico + - /error + - /actuator/health + - /actuator/info + - /file/download/** security: aes: diff --git a/urbanLifelineServ/gateway/src/main/resources/application.yml b/urbanLifelineServ/gateway/src/main/resources/application.yml index a5e865cd..4a8d3770 100644 --- a/urbanLifelineServ/gateway/src/main/resources/application.yml +++ b/urbanLifelineServ/gateway/src/main/resources/application.yml @@ -172,6 +172,13 @@ auth: - /doc.html - /favicon.ico - /error + # 各服务的 Swagger 文档 + - /urban-lifeline/*/v3/api-docs/** + - /urban-lifeline/*/swagger-ui/** + # file 服务白名单 + - /urban-lifeline/file/download/** + # ai 服务白名单 + - /urban-lifeline/ai/chat/** security: aes: secret-key: MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI= # Base64 编码,32字节(256位) diff --git a/urbanLifelineWeb/packages/bidding/public/app-config.js b/urbanLifelineWeb/packages/bidding/public/app-config.js index fb0f4f03..327e13e4 100644 --- a/urbanLifelineWeb/packages/bidding/public/app-config.js +++ b/urbanLifelineWeb/packages/bidding/public/app-config.js @@ -27,8 +27,8 @@ window.APP_RUNTIME_CONFIG = { // 文件配置 file: { - downloadUrl: '/api/file/download/', - uploadUrl: '/api/file/upload', + downloadUrl: '/api/urban-lifeline/file/download/', + uploadUrl: '/api/urban-lifeline/file/upload', maxSize: { image: 5, // MB video: 100, // MB diff --git a/urbanLifelineWeb/packages/bidding/src/config/index.ts b/urbanLifelineWeb/packages/bidding/src/config/index.ts new file mode 100644 index 00000000..a0e90ff6 --- /dev/null +++ b/urbanLifelineWeb/packages/bidding/src/config/index.ts @@ -0,0 +1,253 @@ +/** + * @description Bidding 应用运行时配置 + * @author yslg + * @since 2025-12-06 + * + * 配置加载策略: + * 1. 开发环境:使用下面定义的开发配置 + * 2. 生产环境:从 window.APP_RUNTIME_CONFIG 读取(来自 app-config.js) + * 3. Docker部署:替换 app-config.js 文件实现配置外挂 + * + * 配置结构说明: + * 此文件的配置结构与 app-config.js 完全对应 + * 修改 app-config.js 后,这里的配置会自动应用 + */ + +// ============================================ +// 类型定义 +// ============================================ +export interface AppRuntimeConfig { + env?: string; + api: { + baseUrl: string; + timeout: number; + }; + baseUrl: string; + file: { + downloadUrl: string; + uploadUrl: string; + maxSize: { + image: number; + video: number; + document: number; + }; + acceptTypes: { + image: string; + video: string; + document: string; + }; + }; + token: { + key: string; + refreshThreshold: number; + }; + publicImgPath: string; + publicWebPath: string; + // 单点登录配置 + sso?: { + platformUrl: string; // platform 平台地址 + workcaseUrl: string; // workcase 服务地址 + biddingUrl: string; // bidding 服务地址 + }; + features?: { + enableDebug?: boolean; + enableMockData?: boolean; + [key: string]: any; + }; +} + +// ============================================ +// 配置定义(与 app-config.js 结构一致) +// ============================================ +const isDev = (import.meta as any).env?.DEV ?? false; + +// 开发环境配置 +const devConfig: AppRuntimeConfig = { + env: 'development', + + api: { + // 开发环境通过 Vite 代理转发到后端,避免浏览器直接跨域 + // 实际请求路径示例:/api/... → 由代理转发到实际后端 + baseUrl: '/api', + timeout: 30000 + }, + + baseUrl: '/', + + file: { + // 通过 Nginx → Gateway 访问文件服务,使用 /urban-lifeline 前缀 + downloadUrl: '/api/urban-lifeline/file/download/', + uploadUrl: '/api/urban-lifeline/file/upload', + maxSize: { + image: 5, + video: 100, + document: 10 + }, + acceptTypes: { + image: 'image/*', + video: 'video/*', + document: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx' + } + }, + + token: { + key: 'token', + refreshThreshold: 300000 + }, + + publicImgPath: 'http://localhost:7002/img', + publicWebPath: 'http://localhost:7002', + + // 单点登录配置 + sso: { + platformUrl: '/', // 通过nginx访问platform + workcaseUrl: '/workcase', // 通过nginx访问workcase + biddingUrl: '/bidding' // 通过nginx访问bidding + }, + + features: { + enableDebug: true, + enableMockData: false + } +}; + +// 生产环境默认配置(兜底) +const prodDefaultConfig: AppRuntimeConfig = { + env: 'production', + + api: { + baseUrl: '/api', + timeout: 30000 + }, + + baseUrl: '/', + + file: { + downloadUrl: '/api/urban-lifeline/file/download/', + uploadUrl: '/api/urban-lifeline/file/upload', + maxSize: { + image: 5, + video: 100, + document: 10 + }, + acceptTypes: { + image: 'image/*', + video: 'video/*', + document: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx' + } + }, + + token: { + key: 'token', + refreshThreshold: 300000 + }, + + publicImgPath: '/img', + publicWebPath: '/', + + // 单点登录配置(生产环境通过nginx代理) + sso: { + platformUrl: '/', + workcaseUrl: '/workcase', + biddingUrl: '/bidding' + }, + + features: { + enableDebug: false, + enableMockData: false + } +}; + +// ============================================ +// 配置加载 +// ============================================ + +/** + * 获取运行时配置 + * 生产环境优先从 window.APP_RUNTIME_CONFIG 读取(app-config.js 注入) + */ +const getRuntimeConfig = (): AppRuntimeConfig => { + if (isDev) { + console.log('[配置] 开发环境,使用内置配置'); + return devConfig; + } + + // 生产环境:尝试读取外部配置 + try { + const runtimeConfig = (window as any).APP_RUNTIME_CONFIG; + if (runtimeConfig && typeof runtimeConfig === 'object') { + console.log('[配置] 加载外部配置 app-config.js'); + console.log('[配置] API地址:', runtimeConfig.api?.baseUrl); + console.log('[配置] 环境:', runtimeConfig.env || 'production'); + return runtimeConfig as AppRuntimeConfig; + } + } catch (e) { + console.warn('[配置] 无法读取外部配置,使用默认配置', e); + } + + console.log('[配置] 使用默认生产配置'); + return prodDefaultConfig; +}; + +// 当前应用配置 +const config = getRuntimeConfig(); +console.log('[配置] 当前配置', config); + +// ============================================ +// 导出配置(向后兼容) +// ============================================ + +// 单独导出常用配置项 +export const API_BASE_URL = config.api.baseUrl; +export const FILE_DOWNLOAD_URL = config.file.downloadUrl; +export const PUBLIC_IMG_PATH = config.publicImgPath; +export const PUBLIC_WEB_PATH = config.publicWebPath; + +// 导出完整配置对象 +export const APP_CONFIG = { + // 应用标题 + title: '泰豪电源招投标系统', + + // 环境标识 + env: config.env || 'production', + + // 应用基础路径 + baseUrl: config.baseUrl, + + // API 配置 + api: { + baseUrl: config.api.baseUrl, + timeout: config.api.timeout + }, + + // 文件配置 + file: { + downloadUrl: config.file.downloadUrl, + uploadUrl: config.file.uploadUrl, + maxSize: config.file.maxSize, + acceptTypes: config.file.acceptTypes + }, + + // Token 配置 + token: { + key: config.token.key, + refreshThreshold: config.token.refreshThreshold + }, + + // 公共路径 + publicImgPath: config.publicImgPath, + publicWebPath: config.publicWebPath, + + // 单点登录配置 + sso: config.sso || { + platformUrl: '/', + workcaseUrl: '/workcase', + biddingUrl: '/bidding' + }, + + // 功能开关 + features: config.features || {} +}; + +// 默认导出 +export default APP_CONFIG; diff --git a/urbanLifelineWeb/packages/platform/public/app-config.js b/urbanLifelineWeb/packages/platform/public/app-config.js index fb0f4f03..327e13e4 100644 --- a/urbanLifelineWeb/packages/platform/public/app-config.js +++ b/urbanLifelineWeb/packages/platform/public/app-config.js @@ -27,8 +27,8 @@ window.APP_RUNTIME_CONFIG = { // 文件配置 file: { - downloadUrl: '/api/file/download/', - uploadUrl: '/api/file/upload', + downloadUrl: '/api/urban-lifeline/file/download/', + uploadUrl: '/api/urban-lifeline/file/upload', maxSize: { image: 5, // MB video: 100, // MB diff --git a/urbanLifelineWeb/packages/platform/src/config/index.ts b/urbanLifelineWeb/packages/platform/src/config/index.ts index 49490d2f..7960152d 100644 --- a/urbanLifelineWeb/packages/platform/src/config/index.ts +++ b/urbanLifelineWeb/packages/platform/src/config/index.ts @@ -1,7 +1,21 @@ /** - * Platform 应用配置 + * @description Platform 应用运行时配置 + * @author yslg + * @since 2025-12-06 + * + * 配置加载策略: + * 1. 开发环境:使用下面定义的开发配置 + * 2. 生产环境:从 window.APP_RUNTIME_CONFIG 读取(来自 app-config.js) + * 3. Docker部署:替换 app-config.js 文件实现配置外挂 + * + * 配置结构说明: + * 此文件的配置结构与 app-config.js 完全对应 + * 修改 app-config.js 后,这里的配置会自动应用 */ +// ============================================ +// AES 加密密钥 +// ============================================ /** * AES 加密密钥(与后端保持一致) * 对应后端配置:security.aes.secret-key @@ -9,16 +23,244 @@ */ export const AES_SECRET_KEY = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=' // Base64 编码,解码后是 "12345678901234567890123456789012" (32字节) -/** - * API 基础地址 - */ -export const API_BASE_URL = (import.meta as any).env?.VITE_API_BASE_URL || 'http://localhost:8180' +// ============================================ +// 类型定义 +// ============================================ +export interface AppRuntimeConfig { + env?: string; + api: { + baseUrl: string; + timeout: number; + }; + baseUrl: string; + file: { + downloadUrl: string; + uploadUrl: string; + maxSize: { + image: number; + video: number; + document: number; + }; + acceptTypes: { + image: string; + video: string; + document: string; + }; + }; + token: { + key: string; + refreshThreshold: number; + }; + publicImgPath: string; + publicWebPath: string; + // 单点登录配置 + sso?: { + platformUrl: string; // platform 平台地址 + workcaseUrl: string; // workcase 服务地址 + biddingUrl: string; // bidding 服务地址 + }; + features?: { + enableDebug?: boolean; + enableMockData?: boolean; + [key: string]: any; + }; +} + +// ============================================ +// 配置定义(与 app-config.js 结构一致) +// ============================================ +const isDev = (import.meta as any).env?.DEV ?? false; + +// 开发环境配置 +const devConfig: AppRuntimeConfig = { + env: 'development', + + api: { + // 开发环境通过 Vite 代理转发到后端,避免浏览器直接跨域 + // 实际请求路径示例:/api/... → 由代理转发到实际后端 + baseUrl: '/api', + timeout: 30000 + }, + + baseUrl: '/', + + file: { + // 通过 Nginx → Gateway 访问文件服务,使用 /urban-lifeline 前缀 + downloadUrl: '/api/urban-lifeline/file/download/', + uploadUrl: '/api/urban-lifeline/file/upload', + maxSize: { + image: 5, + video: 100, + document: 10 + }, + acceptTypes: { + image: 'image/*', + video: 'video/*', + document: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx' + } + }, + + token: { + key: 'token', + refreshThreshold: 300000 + }, + + publicImgPath: 'http://localhost:7001/img', + publicWebPath: 'http://localhost:7001', + + // 单点登录配置 + sso: { + platformUrl: '/', // 通过nginx访问platform + workcaseUrl: '/workcase', // 通过nginx访问workcase + biddingUrl: '/bidding' // 通过nginx访问bidding + }, + + features: { + enableDebug: true, + enableMockData: false + } +}; + +// 生产环境默认配置(兜底) +const prodDefaultConfig: AppRuntimeConfig = { + env: 'production', + + api: { + baseUrl: '/api', + timeout: 30000 + }, + + baseUrl: '/', + + file: { + downloadUrl: '/api/urban-lifeline/file/download/', + uploadUrl: '/api/urban-lifeline/file/upload', + maxSize: { + image: 5, + video: 100, + document: 10 + }, + acceptTypes: { + image: 'image/*', + video: 'video/*', + document: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx' + } + }, + + token: { + key: 'token', + refreshThreshold: 300000 + }, + + publicImgPath: '/img', + publicWebPath: '/', + + // 单点登录配置(生产环境通过nginx代理) + sso: { + platformUrl: '/', + workcaseUrl: '/workcase', + biddingUrl: '/bidding' + }, + + features: { + enableDebug: false, + enableMockData: false + } +}; + +// ============================================ +// 配置加载 +// ============================================ /** - * 应用配置 + * 获取运行时配置 + * 生产环境优先从 window.APP_RUNTIME_CONFIG 读取(app-config.js 注入) */ +const getRuntimeConfig = (): AppRuntimeConfig => { + if (isDev) { + console.log('[配置] 开发环境,使用内置配置'); + return devConfig; + } + + // 生产环境:尝试读取外部配置 + try { + const runtimeConfig = (window as any).APP_RUNTIME_CONFIG; + if (runtimeConfig && typeof runtimeConfig === 'object') { + console.log('[配置] 加载外部配置 app-config.js'); + console.log('[配置] API地址:', runtimeConfig.api?.baseUrl); + console.log('[配置] 环境:', runtimeConfig.env || 'production'); + return runtimeConfig as AppRuntimeConfig; + } + } catch (e) { + console.warn('[配置] 无法读取外部配置,使用默认配置', e); + } + + console.log('[配置] 使用默认生产配置'); + return prodDefaultConfig; +}; + +// 当前应用配置 +const config = getRuntimeConfig(); +console.log('[配置] 当前配置', config); + +// ============================================ +// 导出配置(向后兼容) +// ============================================ + +// 单独导出常用配置项 +export const API_BASE_URL = config.api.baseUrl; +export const FILE_DOWNLOAD_URL = config.file.downloadUrl; +export const PUBLIC_IMG_PATH = config.publicImgPath; +export const PUBLIC_WEB_PATH = config.publicWebPath; + +// 导出完整配置对象 export const APP_CONFIG = { - name: '泰豪电源 AI 数智化平台', - version: '1.0.0', - copyright: '泰豪电源' -} + // 应用标题 + title: '泰豪电源 AI 数智化平台', + name: '泰豪电源 AI 数智化平台', + version: '1.0.0', + copyright: '泰豪电源', + + // 环境标识 + env: config.env || 'production', + + // 应用基础路径 + baseUrl: config.baseUrl, + + // API 配置 + api: { + baseUrl: config.api.baseUrl, + timeout: config.api.timeout + }, + + // 文件配置 + file: { + downloadUrl: config.file.downloadUrl, + uploadUrl: config.file.uploadUrl, + maxSize: config.file.maxSize, + acceptTypes: config.file.acceptTypes + }, + + // Token 配置 + token: { + key: config.token.key, + refreshThreshold: config.token.refreshThreshold + }, + + // 公共路径 + publicImgPath: config.publicImgPath, + publicWebPath: config.publicWebPath, + + // 单点登录配置 + sso: config.sso || { + platformUrl: '/', + workcaseUrl: '/workcase', + biddingUrl: '/bidding' + }, + + // 功能开关 + features: config.features || {} +}; + +// 默认导出 +export default APP_CONFIG; diff --git a/urbanLifelineWeb/packages/shared/src/api/ai/aiKnowledge.ts b/urbanLifelineWeb/packages/shared/src/api/ai/aiKnowledge.ts index 1e715c64..b59010ce 100644 --- a/urbanLifelineWeb/packages/shared/src/api/ai/aiKnowledge.ts +++ b/urbanLifelineWeb/packages/shared/src/api/ai/aiKnowledge.ts @@ -80,14 +80,19 @@ export const aiKnowledgeAPI = { // ====================== 文件管理 ====================== /** - * 获取知识库文档列表 + * 获取知识库文档列表(查询本地数据库文件) * @param knowledgeId 知识库ID * @param page 页码 - * @param limit 每页条数 + * @param pageSize 每页条数 */ - async getDocumentList(knowledgeId: string, page = 1, limit = 20): Promise>> { - const response = await api.get>(`${this.baseUrl}/${knowledgeId}/documents`, { - params: { page, limit } + async getDocumentList(knowledgeId: string, page = 1, pageSize = 20): Promise> { + const response = await api.post(`${this.baseUrl}/${knowledgeId}/documents`, { + filter: { knowledgeId }, + pageParam: { + page, + pageSize, + total: 0 + } }) return response.data }, diff --git a/urbanLifelineWeb/packages/shared/src/api/file/file.ts b/urbanLifelineWeb/packages/shared/src/api/file/file.ts index 7d54bb08..8f88500a 100644 --- a/urbanLifelineWeb/packages/shared/src/api/file/file.ts +++ b/urbanLifelineWeb/packages/shared/src/api/file/file.ts @@ -2,7 +2,7 @@ import { api } from '@/api/index' import { BatchFileUploadParam, FileUploadParam, ResultDomain, TbSysFileDTO } from '@/types'; export const fileAPI = { - baseUrl: "/file", + baseUrl: "/urban-lifeline/file", /** * 上传文件 diff --git a/urbanLifelineWeb/packages/shared/src/components/ai/DocumentDetail/DocumentDetail.scss b/urbanLifelineWeb/packages/shared/src/components/ai/DocumentDetail/DocumentDetail.scss new file mode 100644 index 00000000..e69de29b diff --git a/urbanLifelineWeb/packages/shared/src/components/ai/DocumentDetail/DocumentDetail.vue b/urbanLifelineWeb/packages/shared/src/components/ai/DocumentDetail/DocumentDetail.vue new file mode 100644 index 00000000..857ee867 --- /dev/null +++ b/urbanLifelineWeb/packages/shared/src/components/ai/DocumentDetail/DocumentDetail.vue @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/urbanLifelineWeb/packages/shared/src/components/ai/index.ts b/urbanLifelineWeb/packages/shared/src/components/ai/index.ts new file mode 100644 index 00000000..100dc89e --- /dev/null +++ b/urbanLifelineWeb/packages/shared/src/components/ai/index.ts @@ -0,0 +1 @@ +export {default as DocumentDetail} from './DocumentDetail/DocumentDetail.vue' \ No newline at end of file diff --git a/urbanLifelineWeb/packages/shared/src/components/index.ts b/urbanLifelineWeb/packages/shared/src/components/index.ts index 2e8c9f19..bcedc31d 100644 --- a/urbanLifelineWeb/packages/shared/src/components/index.ts +++ b/urbanLifelineWeb/packages/shared/src/components/index.ts @@ -1,6 +1,7 @@ export * from './fileupload' export * from './base' export * from './dynamicFormItem' +export * from './ai' // 通用视图组件 export { default as IframeView } from './iframe/IframeView.vue' \ No newline at end of file diff --git a/urbanLifelineWeb/packages/shared/src/config/index.ts b/urbanLifelineWeb/packages/shared/src/config/index.ts index 4721e4b4..99634995 100644 --- a/urbanLifelineWeb/packages/shared/src/config/index.ts +++ b/urbanLifelineWeb/packages/shared/src/config/index.ts @@ -76,8 +76,8 @@ const devConfig: AppRuntimeConfig = { file: { // 同样走代理,保持与 api.baseUrl 一致 - downloadUrl: '/api/file/download/', - uploadUrl: '/api/file/upload', + downloadUrl: '/api/urban-lifeline/file/download/', + uploadUrl: '/api/urban-lifeline/file/upload', maxSize: { image: 5, video: 100, diff --git a/urbanLifelineWeb/packages/workcase/public/app-config.js b/urbanLifelineWeb/packages/workcase/public/app-config.js index fb0f4f03..327e13e4 100644 --- a/urbanLifelineWeb/packages/workcase/public/app-config.js +++ b/urbanLifelineWeb/packages/workcase/public/app-config.js @@ -27,8 +27,8 @@ window.APP_RUNTIME_CONFIG = { // 文件配置 file: { - downloadUrl: '/api/file/download/', - uploadUrl: '/api/file/upload', + downloadUrl: '/api/urban-lifeline/file/download/', + uploadUrl: '/api/urban-lifeline/file/upload', maxSize: { image: 5, // MB video: 100, // MB diff --git a/urbanLifelineWeb/packages/workcase/src/config.ts b/urbanLifelineWeb/packages/workcase/src/config.ts deleted file mode 100644 index ba62b42b..00000000 --- a/urbanLifelineWeb/packages/workcase/src/config.ts +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Workcase 应用配置 - */ - -/** - * AES 加密密钥(与后端保持一致) - * 对应后端配置:security.aes.secret-key - * Base64 编码的 32 字节密钥(256 位) - */ -export const AES_SECRET_KEY = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=' // Base64 编码,解码后是 "12345678901234567890123456789012" (32字节) - -/** - * API 基础地址 - * 注意:使用 shared 的 APP_CONFIG 统一管理,这里保留用于特殊场景 - */ -export const API_BASE_URL = (import.meta as any).env?.VITE_API_BASE_URL || '/api' - -/** - * Platform URL(单点登录入口) - * 开发环境: - * - 通过nginx访问时使用 '/'(推荐) - * - 直接访问各服务时使用 'http://localhost:7001' - * 生产环境:统一使用 '/' - */ -export const PLATFORM_URL = (import.meta as any).env?.VITE_PLATFORM_URL || '/' - -/** - * 应用配置 - */ -export const APP_CONFIG = { - name: '泰豪小电', - version: '1.0.0', - copyright: '泰豪电源' -} \ No newline at end of file diff --git a/urbanLifelineWeb/packages/workcase/src/config/index.ts b/urbanLifelineWeb/packages/workcase/src/config/index.ts new file mode 100644 index 00000000..27fa0b63 --- /dev/null +++ b/urbanLifelineWeb/packages/workcase/src/config/index.ts @@ -0,0 +1,265 @@ +/** + * @description 应用运行时配置 + * @author yslg + * @since 2025-12-06 + * + * 配置加载策略: + * 1. 开发环境:使用下面定义的开发配置 + * 2. 生产环境:从 window.APP_RUNTIME_CONFIG 读取(来自 app-config.js) + * 3. Docker部署:替换 app-config.js 文件实现配置外挂 + * + * 配置结构说明: + * 此文件的配置结构与 app-config.js 完全对应 + * 修改 app-config.js 后,这里的配置会自动应用 + */ + +// ============================================ +// AES 加密密钥 +// ============================================ +/** + * AES 加密密钥(与后端保持一致) + * 对应后端配置:security.aes.secret-key + * Base64 编码的 32 字节密钥(256 位) + */ +export const AES_SECRET_KEY = 'MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI=' // Base64 编码,解码后是 "12345678901234567890123456789012" (32字节) + +// ============================================ +// 类型定义 +// ============================================ +export interface AppRuntimeConfig { + env?: string; + api: { + baseUrl: string; + timeout: number; + }; + baseUrl: string; + file: { + downloadUrl: string; + uploadUrl: string; + maxSize: { + image: number; + video: number; + document: number; + }; + acceptTypes: { + image: string; + video: string; + document: string; + }; + }; + token: { + key: string; + refreshThreshold: number; + }; + publicImgPath: string; + publicWebPath: string; + // 单点登录配置 + sso?: { + platformUrl: string; // platform 平台地址 + workcaseUrl: string; // workcase 服务地址 + biddingUrl: string; // bidding 服务地址 + }; + features?: { + enableDebug?: boolean; + enableMockData?: boolean; + [key: string]: any; + }; +} + +// ============================================ +// 配置定义(与 app-config.js 结构一致) +// ============================================ +const isDev = (import.meta as any).env?.DEV ?? false; + +// 开发环境配置 +const devConfig: AppRuntimeConfig = { + env: 'development', + + api: { + // 开发环境通过 Vite 代理转发到后端,避免浏览器直接跨域 + // 实际请求路径示例:/api/... → 由代理转发到实际后端 + baseUrl: '/api', + timeout: 30000 + }, + + baseUrl: '/', + + file: { + // 通过 Nginx → Gateway 访问文件服务,使用 /urban-lifeline 前缀 + downloadUrl: '/api/urban-lifeline/file/download/', + uploadUrl: '/api/urban-lifeline/file/upload', + maxSize: { + image: 5, + video: 100, + document: 10 + }, + acceptTypes: { + image: 'image/*', + video: 'video/*', + document: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx' + } + }, + + token: { + key: 'token', + refreshThreshold: 300000 + }, + + publicImgPath: 'http://localhost:5173/img', + publicWebPath: 'http://localhost:5173', + + // 单点登录配置 + // 推荐:开发环境也通过nginx访问(http://localhost) + // 备选:直接访问各服务端口(platformUrl: 'http://localhost:7001') + sso: { + platformUrl: '/', // 通过nginx访问platform + workcaseUrl: '/workcase', // 通过nginx访问workcase + biddingUrl: '/bidding' // 通过nginx访问bidding + }, + + features: { + enableDebug: true, + enableMockData: false + } +}; + +// 生产环境默认配置(兜底) +const prodDefaultConfig: AppRuntimeConfig = { + env: 'production', + + api: { + baseUrl: '/api', + timeout: 30000 + }, + + baseUrl: '/', + + file: { + downloadUrl: '/api/file/download/', + uploadUrl: '/api/file/upload', + maxSize: { + image: 5, + video: 100, + document: 10 + }, + acceptTypes: { + image: 'image/*', + video: 'video/*', + document: '.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx' + } + }, + + token: { + key: 'token', + refreshThreshold: 300000 + }, + + publicImgPath: '/img', + publicWebPath: '/', + + // 单点登录配置(生产环境通过nginx代理) + sso: { + platformUrl: '/', + workcaseUrl: '/workcase', + biddingUrl: '/bidding' + }, + + features: { + enableDebug: false, + enableMockData: false + } +}; + +// ============================================ +// 配置加载 +// ============================================ + +/** + * 获取运行时配置 + * 生产环境优先从 window.APP_RUNTIME_CONFIG 读取(app-config.js 注入) + */ +const getRuntimeConfig = (): AppRuntimeConfig => { + if (isDev) { + console.log('[配置] 开发环境,使用内置配置'); + return devConfig; + } + + // 生产环境:尝试读取外部配置 + try { + const runtimeConfig = (window as any).APP_RUNTIME_CONFIG; + if (runtimeConfig && typeof runtimeConfig === 'object') { + console.log('[配置] 加载外部配置 app-config.js'); + console.log('[配置] API地址:', runtimeConfig.api?.baseUrl); + console.log('[配置] 环境:', runtimeConfig.env || 'production'); + return runtimeConfig as AppRuntimeConfig; + } + } catch (e) { + console.warn('[配置] 无法读取外部配置,使用默认配置', e); + } + + console.log('[配置] 使用默认生产配置'); + return prodDefaultConfig; +}; + +// 当前应用配置 +const config = getRuntimeConfig(); +console.log('[配置] 当前配置', config); + +// ============================================ +// 导出配置(向后兼容) +// ============================================ + +// 单独导出常用配置项 +export const API_BASE_URL = config.api.baseUrl; +export const FILE_DOWNLOAD_URL = config.file.downloadUrl; +export const PUBLIC_IMG_PATH = config.publicImgPath; +export const PUBLIC_WEB_PATH = config.publicWebPath; + +// 导出完整配置对象 +export const APP_CONFIG = { + // 应用标题 + title: '泰豪电源 AI 数智化平台', + + // 环境标识 + env: config.env || 'production', + + // 应用基础路径 + baseUrl: config.baseUrl, + + // API 配置 + api: { + baseUrl: config.api.baseUrl, + timeout: config.api.timeout + }, + + // 文件配置 + file: { + downloadUrl: config.file.downloadUrl, + uploadUrl: config.file.uploadUrl, + maxSize: config.file.maxSize, + acceptTypes: config.file.acceptTypes + }, + + // Token 配置 + token: { + key: config.token.key, + refreshThreshold: config.token.refreshThreshold + }, + + // 公共路径 + publicImgPath: config.publicImgPath, + publicWebPath: config.publicWebPath, + + // 单点登录配置 + sso: config.sso || { + platformUrl: '/', + workcaseUrl: '/workcase', + biddingUrl: '/bidding' + }, + + // 功能开关 + features: config.features || {} +}; + +// 默认导出 +export default APP_CONFIG; diff --git a/urbanLifelineWeb/packages/workcase/src/main.ts b/urbanLifelineWeb/packages/workcase/src/main.ts index 35be8e88..01eab3e0 100644 --- a/urbanLifelineWeb/packages/workcase/src/main.ts +++ b/urbanLifelineWeb/packages/workcase/src/main.ts @@ -7,7 +7,7 @@ import './assets/css/common.scss' import App from './App.vue' import router from './router/' -import { AES_SECRET_KEY } from './config' +import { AES_SECRET_KEY } from './config/index' // @ts-ignore import { initAesEncrypt } from 'shared/utils' diff --git a/urbanLifelineWeb/packages/workcase/src/views/admin/knowledge/KnowLedgeView.vue b/urbanLifelineWeb/packages/workcase/src/views/admin/knowledge/KnowLedgeView.vue index 9d65d940..f8cf26d6 100644 --- a/urbanLifelineWeb/packages/workcase/src/views/admin/knowledge/KnowLedgeView.vue +++ b/urbanLifelineWeb/packages/workcase/src/views/admin/knowledge/KnowLedgeView.vue @@ -1,10 +1,18 @@ @@ -90,6 +85,7 @@ import { Upload, Search, Document, View, Download, Delete } from '@element-plus/ import { ElMessage, ElMessageBox } from 'element-plus' import { aiKnowledgeAPI } from 'shared/api/ai' import { FileUpload } from 'shared/components' +import { FILE_DOWNLOAD_URL } from '@/config/index' import type { TbKnowledge } from 'shared/types' // Tab 配置 @@ -113,12 +109,11 @@ interface DocumentItem { name: string uploader: string uploadTime: string - position: number - dataSourceType: string - wordCount: number - hitCount: number - indexingStatus: string - enabled: boolean + fileId?: string + fileRootId?: string + knowledgeId?: string + difyFileId?: string + version?: number } const documents = ref([]) @@ -185,18 +180,17 @@ const fetchDocuments = async (knowledgeId: string) => { loading.value = true try { const result = await aiKnowledgeAPI.getDocumentList(knowledgeId, 1, 100) - if (result.success && result.data) { - documents.value = (result.data.data || []).map((doc: any) => ({ - id: doc.id, - name: doc.name, - uploader: doc.created_by || '-', - uploadTime: doc.created_at ? new Date(doc.created_at * 1000).toLocaleString() : '-', - position: doc.position, - dataSourceType: doc.data_source_type, - wordCount: doc.word_count || 0, - hitCount: doc.hit_count || 0, - indexingStatus: doc.indexing_status, - enabled: doc.enabled + if (result.success && result.pageDomain) { + documents.value = (result.pageDomain.dataList || []).map((file: any) => ({ + id: file.fileId, + name: file.fileName || '-', + uploader: file.creator || '-', + uploadTime: file.createTime ? new Date(file.createTime).toLocaleString() : '-', + fileId: file.fileId, + fileRootId: file.fileRootId, + knowledgeId: file.knowledgeId, + difyFileId: file.difyFileId, + version: file.version })) } } catch (error) { @@ -226,12 +220,30 @@ watch(activeKnowledgeId, (newVal) => { if (newVal) fetchDocuments(newVal) }) -const previewFile = (row: DocumentItem) => { - ElMessage.info(`预览文件: ${row.name}`) +const previewFile = async (row: DocumentItem) => { + if (!row.fileId) { + ElMessage.warning('文件信息不完整') + return + } + // 使用 FILE_DOWNLOAD_URL 构建文件 URL 并在新窗口打开 + const fileUrl = `${FILE_DOWNLOAD_URL}${row.fileId}` + window.open(fileUrl, '_blank') } const downloadFile = (row: DocumentItem) => { - ElMessage.success(`下载文件: ${row.name}`) + if (!row.fileId) { + ElMessage.warning('文件信息不完整') + return + } + // 创建隐藏的下载链接并触发下载 + const fileUrl = `${FILE_DOWNLOAD_URL}${row.fileId}` + const link = document.createElement('a') + link.href = fileUrl + link.download = row.name || 'file' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + ElMessage.success('开始下载') } const deleteFile = async (row: DocumentItem) => { @@ -291,15 +303,6 @@ const handleUploadError = (error: string) => { ElMessage.error(error) } -// 打开上传弹窗 -const openUploadDialog = () => { - if (!activeKnowledgeId.value) { - ElMessage.warning('请先选择一个知识库') - return - } - fileUploadRef.value?.openDialog() -} - onMounted(() => { fetchKnowledges() })