| doc/20260508_采购多文件分析附件存储与历史回显联调说明.md | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 | |
| src/main/resources/application-dev.yml | ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史 |
doc/20260508_²É¹º¶àÎļþ·ÖÎö¸½¼þ´æ´¢ÓëÀúÊ·»ØÏÔÁªµ÷˵Ã÷.md
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,149 @@ # éè´å¤æä»¶åæéä»¶åå¨ä¸åå²åæ¾èè°è¯´æ > æ´æ°æ¶é´ï¼2026-05-08 > éç¨èå´ï¼éè´æºè½ä½å¤æä»¶åæ + åå²ä¼è¯éä»¶åæ¾ ## 1. åæ´èæ¯ å端已补é½ä»¥ä¸è½åï¼ 1. `POST /purchase-ai/analyze-files` ä¸ä¼ æ¶å åéä»¶å°æå¡å¨ï¼å ¬å ±è®¿é®ï¼ã 2. ææä»¶ç±»åè¿åå¯è®¿é®è·¯å¾ï¼ - å¾ç / PDFï¼ä¼å é¢è§è·¯å¾ - å ¶å®æä»¶ï¼ä¼å ä¸è½½è·¯å¾ 3. ç¨æ·æé®ä¸éä»¶è·¯å¾åå¼åå ¥ Mongoã 4. å岿¶æ¯æ¥å£å¯æâæ¶æ¯ç»´åº¦âåä¼ éä»¶è·¯å¾ï¼å端å¯ç´æ¥åæ¾éä»¶å¡çã --- ## 2. æ¥å£è¡ä¸º ### 2.1 夿件忿¥å£ ```http POST /purchase-ai/analyze-files Content-Type: multipart/form-data ``` 请æ±åæ°ï¼ - `files`: `MultipartFile[]`ï¼å¿ å¡«ï¼ - `message`: `string`ï¼å¯éï¼ - `memoryId`: `string`ï¼å¯éï¼ å端å¤çæµç¨ï¼ 1. è°ç¨ `StorageBlobService.upload(files, true)` ä¸ä¼ å¹¶ä¿åéä»¶ã 2. çæé件访é®è·¯å¾ï¼é¢è§ / ä¸è½½ï¼ã 3. å°âæ¬æ¬¡æé® + æ¬æ¬¡éä»¶è·¯å¾å表âåå ¥ Mongoã 4. ç»§ç»æ§è¡åææä»¶è§£æå AI åææµç¨ã å¯è½éè¯¯ï¼æµå¼ææ¬ï¼ï¼ - `æä»¶ä¸ä¼ 失败` - `ä¼è¯æä»¶ä¿¡æ¯ä¿å失败` ### 2.2 å岿¶æ¯æ¥å£ï¼å端éç¹ï¼ ```http GET /purchase-ai/history/messages/{memoryId} ``` æ¶æ¯å¯¹è±¡æ°å¢å¯éåæ®µ `filePaths`ï¼ä» ç¨æ·æ¶æ¯å¯è½æå¼ï¼ï¼ ```json { "role": "user | assistant | system | tool | unknown", "content": "æ¶æ¯ææ¬", "filePaths": [ "/common/preview/xxx?publicKey=...", "/common/download/yyy?publicKey=..." ] } ``` 说æï¼ 1. `filePaths` å¯è½ç¼ºå¤±æä¸ºç©ºï¼èä¼è¯ / æ®é对è¯ï¼ã 2. åæ¡ç¨æ·æ¶æ¯å¯è½å å«å¤ä¸ªéä»¶è·¯å¾ã 3. è¯¥åæ®µå·²ææ¶æ¯ç»´åº¦å¯¹é½ï¼å¯ç´æ¥ç¨äºåå²åæ¾ã --- ## 3. å端æ¹é å»ºè®®ï¼æå½åå®ç°è½å°ï¼ ### 3.1 åå²ååºæ¨¡å 建议å¨å岿¶æ¯åå§ç»æä¸ä¿ç `filePaths`ï¼ ```ts type AiHistoryMessage = { role: string; content: string; filePaths?: string[]; }; ``` ### 3.2 UI æ¶æ¯æ¨¡åæ å° è¥å端页é¢ç¨çæ¯ `localUploadFiles` 渲æéä»¶å¡çï¼å¦ `AIChatSidebar` å½åå®ç°ï¼ï¼å岿¶æ¯éå䏿¬¡æ å°ï¼ ```ts type LocalUploadFileItem = { previewId: string; name: string; size: number; type: string; isImage: boolean; previewUrl: string; rawFile: null; }; ``` æ å°è§åå»ºè®®ï¼ 1. ä» `role === 'user'` ä¸ `filePaths?.length > 0` æ¶çæ `localUploadFiles`ã 2. `previewId` ç¨ `${memoryId}-${messageIndex}-${fileIndex}` çæç¨³å®å¼ã 3. `name` å¯ä» URL è·¯å¾è§£æï¼è§£æå¤±è´¥ç¨ `file-{n}`ã 4. `isImage` 坿æ©å±å夿ï¼`png/jpg/jpeg/gif/webp/bmp/svg`ï¼ã 5. å¾çç `previewUrl` ç´æ¥ä½¿ç¨è·¯å¾ï¼éå¾çå¯ç½®ç©ºå¹¶èµ°å¾æ å±ç¤ºã ### 3.3 æ¶æ¯æ¸²æ 对äºç¨æ·æ¶æ¯ï¼ 1. æ£å¸¸æ¸²æ `content`ã 2. è¥åå¨ `localUploadFiles`ï¼æç´æ¥ä½¿ç¨ `filePaths`ï¼ï¼å¨æ¶æ¯ä¸æ¹æ¸²æéä»¶å表/å¡çã 3. 龿¥ç´æ¥ä½¿ç¨å端è¿åè·¯å¾ï¼ä¸åæ¼æ¥æäºæ¬¡æ¹åã > åç«¯å·²å®æâé¢è§/ä¸è½½è·¯å¾âéæ©ï¼å端åªè´è´£å±ç¤ºä¸æå¼ã ### 3.4 å ¼å®¹è¦æ± - `filePaths` 缺失ï¼ä¸æ¥éï¼ä¸æ¸²æéä»¶åºåã - èä¼è¯ï¼ç»§ç»æ `role/content` å±ç¤ºï¼ä¸å½±ååå²è®°å½æ¥çã - å¤éä»¶ï¼ä¿æé¡ºåºå±ç¤ºï¼é¿å æä¹±ç¨æ·ä¸ä¼ 顺åºã --- ## 4. Mongo åæ®µè¯´æï¼å端已å®ç°ï¼ `chat_messages` ææ¡£æ°å¢/使ç¨åæ®µï¼ - `analyzeUserQuestions: string[]` - `analyzeFilePaths: string[]`ï¼å ¼å®¹æ§åæ®µï¼ - `analyzeFilePathGroups: string[][]`ï¼æ¨èï¼ææé®åç»ï¼ å岿¶æ¯åä¼ æ¶ï¼å端ä¼å 读å `analyzeFilePathGroups`ï¼å¹¶å ¼å®¹ `analyzeFilePaths`ã --- ## 5. èè°éªæ¶æ¸ å 1. æ°å»ºä¼è¯ï¼ä¸ä¼ 1 å¼ å¾ç + 1 个 Excelï¼åéåæè¯·æ±ã 2. è°ç¨ `GET /purchase-ai/history/messages/{memoryId}`ï¼ç¡®è®¤ç¨æ·æ¶æ¯å« `filePaths` 䏿°éæ£ç¡®ã 3. å·æ°é¡µé¢åéæ°è¿å ¥åä¸ä¼è¯ï¼ç¡®è®¤éä»¶å¡çå¯åæ¾ã 4. åå«éªè¯ï¼ - å¾ç/PDF å¯é¢è§è®¿é® - å ¶å®æä»¶å¯ä¸è½½è®¿é® 5. éªè¯èä¼è¯ï¼æ `filePaths`ï¼å¯æ£å¸¸å±ç¤ºï¼é¡µé¢ä¸æ¥éã src/main/java/com/ruoyi/ai/controller/PurchaseAiController.java
@@ -11,8 +11,10 @@ import com.ruoyi.ai.service.AiChatSessionService; import com.ruoyi.ai.service.AiFileTextExtractor; import com.ruoyi.ai.store.MongoChatMemoryStore; import com.ruoyi.basic.dto.StorageBlobVO; import com.ruoyi.basic.mapper.SupplierManageMapper; import com.ruoyi.basic.pojo.SupplierManage; import com.ruoyi.basic.service.StorageBlobService; import com.ruoyi.common.utils.SecurityUtils; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.framework.security.LoginUser; @@ -50,10 +52,14 @@ import org.springframework.web.multipart.MultipartFile; import reactor.core.publisher.Flux; import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.Base64; import java.util.Arrays; import java.time.LocalDate; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -63,9 +69,11 @@ import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; import java.util.UUID; import java.nio.file.Files; @Tag(name = "éè´æºè½ä½") @RestController @@ -87,6 +95,7 @@ private final IPurchaseLedgerService purchaseLedgerService; private final IPaymentRegistrationService paymentRegistrationService; private final PurchaseReturnOrdersService purchaseReturnOrdersService; private final StorageBlobService storageBlobService; private final SupplierManageMapper supplierManageMapper; private final StreamingChatLanguageModel purchaseVisionStreamingChatModel; @@ -96,12 +105,13 @@ MongoChatMemoryStore mongoChatMemoryStore, AiChatSessionService aiChatSessionService, AiFileTextExtractor aiFileTextExtractor, ObjectMapper objectMapper, IPurchaseLedgerService purchaseLedgerService, IPaymentRegistrationService paymentRegistrationService, PurchaseReturnOrdersService purchaseReturnOrdersService, SupplierManageMapper supplierManageMapper, @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) { ObjectMapper objectMapper, IPurchaseLedgerService purchaseLedgerService, IPaymentRegistrationService paymentRegistrationService, PurchaseReturnOrdersService purchaseReturnOrdersService, StorageBlobService storageBlobService, SupplierManageMapper supplierManageMapper, @Qualifier("purchaseVisionStreamingChatModel") StreamingChatLanguageModel purchaseVisionStreamingChatModel) { this.purchaseAgent = purchaseAgent; this.purchaseIntentExecutor = purchaseIntentExecutor; this.aiSessionUserContext = aiSessionUserContext; @@ -112,6 +122,7 @@ this.purchaseLedgerService = purchaseLedgerService; this.paymentRegistrationService = paymentRegistrationService; this.purchaseReturnOrdersService = purchaseReturnOrdersService; this.storageBlobService = storageBlobService; this.supplierManageMapper = supplierManageMapper; this.purchaseVisionStreamingChatModel = purchaseVisionStreamingChatModel; } @@ -172,6 +183,19 @@ ? message : "请åæè¿äºéè´æä»¶ï¼æåå¯ç¨äºä¸å¡å¤ççæ°æ®ï¼å¹¶æ´çæå¾ 客æ·ç¡®è®¤çæ ¼å¼"; List<String> filePaths; try { List<StorageBlobVO> uploadedFiles = storageBlobService.upload(copyFilesForUpload(files), true); filePaths = resolveFileAccessPaths(uploadedFiles); } catch (Exception ex) { return Flux.just("æä»¶ä¸ä¼ 失败"); } try { mongoChatMemoryStore.appendAnalyzeFileContext(finalMemoryId, finalMessage, filePaths); } catch (Exception ex) { return Flux.just("ä¼è¯æä»¶ä¿¡æ¯ä¿å失败"); } String fileContent; try { fileContent = buildMultiFileContent(files); @@ -189,7 +213,7 @@ aiChatSessionService.touchSession(finalMemoryId, loginUser, "éè´å¤æä»¶åæ: " + finalMessage); if (containsImageFile(files)) { return chatWithPurchaseVisionModel(finalMemoryId, userPrompt, files) return chatWithPurchaseVisionModel(finalMemoryId, finalMessage, userPrompt, files) .doOnComplete(() -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)) .doOnError(ex -> aiChatSessionService.refreshSessionStats(finalMemoryId, loginUser)); } @@ -282,8 +306,121 @@ return false; } private Flux<String> chatWithPurchaseVisionModel(String memoryId, String userPrompt, MultipartFile[] files) { private List<String> resolveFileAccessPaths(List<StorageBlobVO> uploadedFiles) { if (StringUtils.isEmpty(uploadedFiles)) { return Collections.emptyList(); } List<String> filePaths = new ArrayList<>(); for (StorageBlobVO uploadedFile : uploadedFiles) { if (uploadedFile == null) { continue; } String selectedPath; if (shouldUsePreviewPath(uploadedFile)) { selectedPath = StringUtils.hasText(uploadedFile.getPreviewURL()) ? uploadedFile.getPreviewURL() : uploadedFile.getDownloadURL(); } else { selectedPath = StringUtils.hasText(uploadedFile.getDownloadURL()) ? uploadedFile.getDownloadURL() : uploadedFile.getPreviewURL(); } if (StringUtils.hasText(selectedPath)) { filePaths.add(selectedPath); } } return filePaths; } private boolean shouldUsePreviewPath(StorageBlobVO uploadedFile) { String contentType = uploadedFile.getContentType(); if (StringUtils.hasText(contentType)) { String normalized = contentType.toLowerCase(Locale.ROOT); if (normalized.startsWith("image/") || "application/pdf".equals(normalized)) { return true; } } String filename = uploadedFile.getOriginalFilename(); if (!StringUtils.hasText(filename) || !filename.contains(".")) { return false; } String ext = filename.substring(filename.lastIndexOf('.') + 1).toLowerCase(Locale.ROOT); return StringUtils.inStringIgnoreCase(ext, "png", "jpg", "jpeg", "webp", "bmp", "pdf"); } private List<MultipartFile> copyFilesForUpload(MultipartFile[] files) throws IOException { List<MultipartFile> copies = new ArrayList<>(); for (MultipartFile file : files) { copies.add(new InMemoryMultipartFile( file.getName(), file.getOriginalFilename(), file.getContentType(), file.getBytes() )); } return copies; } private static final class InMemoryMultipartFile implements MultipartFile { private final String name; private final String originalFilename; private final String contentType; private final byte[] bytes; private InMemoryMultipartFile(String name, String originalFilename, String contentType, byte[] bytes) { this.name = name; this.originalFilename = originalFilename; this.contentType = contentType; this.bytes = bytes == null ? new byte[0] : bytes; } @Override public String getName() { return name; } @Override public String getOriginalFilename() { return originalFilename; } @Override public String getContentType() { return contentType; } @Override public boolean isEmpty() { return bytes.length == 0; } @Override public long getSize() { return bytes.length; } @Override public byte[] getBytes() { return bytes.clone(); } @Override public InputStream getInputStream() { return new ByteArrayInputStream(bytes); } @Override public void transferTo(File dest) throws IOException, IllegalStateException { Files.write(dest.toPath(), bytes); } } private Flux<String> chatWithPurchaseVisionModel(String memoryId, String userMessage, String userPrompt, MultipartFile[] files) { return Flux.create(sink -> { StringBuilder assistantReply = new StringBuilder(); try { List<Content> contents = new ArrayList<>(); contents.add(TextContent.from(userPrompt)); @@ -302,14 +439,21 @@ SystemMessage.from("ä½ æ¯éè´ä¸å¡æä»¶åæå©æãè¯·ä»ææ¬åå¾çä¸è¯å«éè´å°è´¦ãéè´äº§åæç»ã仿¬¾æéè´§ä¿¡æ¯ï¼åªè¾åºåæ³ JSONã"), UserMessage.from(contents) ); safeAppendMessages(memoryId, List.of(UserMessage.from("éè´å¤æä»¶åæ: " + userMessage))); purchaseVisionStreamingChatModel.chat(messages, new StreamingChatResponseHandler() { @Override public void onPartialResponse(String partialResponse) { sink.next(partialResponse); if (partialResponse != null) { assistantReply.append(partialResponse); sink.next(partialResponse); } } @Override public void onCompleteResponse(ChatResponse completeResponse) { if (StringUtils.hasText(assistantReply.toString())) { safeAppendMessages(memoryId, List.of(AiMessage.from(assistantReply.toString()))); } sink.complete(); } @@ -325,6 +469,16 @@ }); } private void safeAppendMessages(String memoryId, List<ChatMessage> messages) { if (!StringUtils.hasText(memoryId) || StringUtils.isEmpty(messages)) { return; } try { mongoChatMemoryStore.appendMessages(memoryId, messages); } catch (Exception ignored) { } } private String resolveImageMimeType(MultipartFile file) { String contentType = file.getContentType(); if (StringUtils.hasText(contentType) && contentType.startsWith("image/")) { src/main/java/com/ruoyi/ai/dto/AiChatMessageDto.java
@@ -1,15 +1,28 @@ package com.ruoyi.ai.dto; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.util.List; @Data @NoArgsConstructor @AllArgsConstructor public class AiChatMessageDto { private String role; private String content; private List<String> filePaths; public AiChatMessageDto(String role, String content) { this.role = role; this.content = content; } public AiChatMessageDto(String role, String content, List<String> filePaths) { this.role = role; this.content = content; this.filePaths = filePaths; } } src/main/java/com/ruoyi/ai/mongodbBean/ChatMessages.java
@@ -9,6 +9,7 @@ import org.springframework.data.mongodb.core.mapping.Document; import java.util.Date; import java.util.List; @Data @AllArgsConstructor @@ -24,6 +25,21 @@ private String content; /** * 夿件åæç¨æ·æé®ä¿¡æ¯ï¼ä¸æä»¶è·¯å¾åå¼åå¨ï¼ */ private List<String> analyzeUserQuestions; /** * 夿件åæä¸ä¼ æä»¶è·¯å¾ï¼å¾çå pdf 使ç¨é¢è§å°åï¼å ¶ä»ä½¿ç¨ä¸è½½å°åï¼ */ private List<String> analyzeFilePaths; /** * 夿件忿¯æ¬¡æé®å¯¹åºçæä»¶è·¯å¾åç» */ private List<List<String>> analyzeFilePathGroups; private Date createTime; private Date updateTime; src/main/java/com/ruoyi/ai/service/impl/AiChatSessionServiceImpl.java
@@ -114,7 +114,10 @@ return new LinkedList<>(); } List<ChatMessage> messages = mongoChatMemoryStore.getMessages(memoryId); return messages.stream().map(this::convertMessage).collect(Collectors.toList()); List<AiChatMessageDto> messageDtos = messages.stream().map(this::convertMessage).collect(Collectors.toList()); List<List<String>> analyzeFilePathGroups = mongoChatMemoryStore.getAnalyzeFilePathGroups(memoryId); attachAnalyzeFilePaths(messageDtos, analyzeFilePathGroups); return messageDtos; } @Override @@ -188,4 +191,22 @@ } return new AiChatMessageDto("unknown", String.valueOf(message)); } private void attachAnalyzeFilePaths(List<AiChatMessageDto> messages, List<List<String>> analyzeFilePathGroups) { if (StringUtils.isEmpty(messages) || StringUtils.isEmpty(analyzeFilePathGroups)) { return; } int analyzeIndex = 0; for (AiChatMessageDto message : messages) { if (!"user".equals(message.getRole()) || analyzeIndex >= analyzeFilePathGroups.size()) { continue; } List<String> filePaths = analyzeFilePathGroups.get(analyzeIndex); if (!StringUtils.isEmpty(filePaths)) { message.setFilePaths(filePaths); } analyzeIndex++; } } } src/main/java/com/ruoyi/ai/store/MongoChatMemoryStore.java
@@ -11,6 +11,8 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import java.util.Date; import java.util.LinkedList; @@ -24,8 +26,7 @@ @Override public List<ChatMessage> getMessages(Object memoryId) { Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId))); ChatMessages chatMessages = mongoTemplate.findOne(query, ChatMessages.class); ChatMessages chatMessages = findChatMessages(memoryId); if (chatMessages == null || chatMessages.getContent() == null) { return new LinkedList<>(); } @@ -56,7 +57,75 @@ updateMessages(memoryId, messages); } public void appendAnalyzeFileContext(Object memoryId, String userQuestion, List<String> filePaths) { String memoryIdValue = memoryIdString(memoryId); if (!StringUtils.hasText(memoryIdValue)) { return; } List<String> validFilePaths = new LinkedList<>(); if (!CollectionUtils.isEmpty(filePaths)) { for (String filePath : filePaths) { if (StringUtils.hasText(filePath)) { validFilePaths.add(filePath); } } } if (!StringUtils.hasText(userQuestion) && validFilePaths.isEmpty()) { return; } Query query = Query.query(Criteria.where("memoryId").is(memoryIdValue)); Update update = new Update(); update.set("memoryId", memoryIdValue); update.set("updateTime", new Date()); update.setOnInsert("createTime", new Date()); if (StringUtils.hasText(userQuestion)) { update.push("analyzeUserQuestions", userQuestion); } if (!validFilePaths.isEmpty()) { update.push("analyzeFilePaths").each(validFilePaths.toArray()); update.push("analyzeFilePathGroups", validFilePaths); } mongoTemplate.upsert(query, update, ChatMessages.class); } public List<String> getAnalyzeUserQuestions(Object memoryId) { ChatMessages chatMessages = findChatMessages(memoryId); if (chatMessages == null || CollectionUtils.isEmpty(chatMessages.getAnalyzeUserQuestions())) { return new LinkedList<>(); } return new LinkedList<>(chatMessages.getAnalyzeUserQuestions()); } public List<List<String>> getAnalyzeFilePathGroups(Object memoryId) { ChatMessages chatMessages = findChatMessages(memoryId); if (chatMessages == null) { return new LinkedList<>(); } if (CollectionUtils.isEmpty(chatMessages.getAnalyzeFilePathGroups())) { if (CollectionUtils.isEmpty(chatMessages.getAnalyzeFilePaths())) { return new LinkedList<>(); } List<List<String>> fallback = new LinkedList<>(); fallback.add(new LinkedList<>(chatMessages.getAnalyzeFilePaths())); return fallback; } List<List<String>> groups = new LinkedList<>(); for (List<String> group : chatMessages.getAnalyzeFilePathGroups()) { if (CollectionUtils.isEmpty(group)) { groups.add(new LinkedList<>()); } else { groups.add(new LinkedList<>(group)); } } return groups; } private String memoryIdString(Object memoryId) { return memoryId == null ? "" : memoryId.toString(); } private ChatMessages findChatMessages(Object memoryId) { Query query = Query.query(Criteria.where("memoryId").is(memoryIdString(memoryId))); return mongoTemplate.findOne(query, ChatMessages.class); } } src/main/resources/application-dev.yml
@@ -254,8 +254,18 @@ # æ¯å¦å è®¸çææä»¶è¦çå°æ¬å°ï¼èªå®ä¹è·¯å¾ï¼ï¼é»è®¤ä¸å 许 allowOverwrite: false # æä»¶ä¸ä¼ é ç½® file: temp-dir: D:/ruoyi/temp/uploads # 临æ¶ç®å½ upload-dir: D:/ruoyi/prod/uploads # æ£å¼ç®å½ temp-dir: D:/ruoyi/temp/uploads # 临æ¶ç®å½ åæå é¤ upload-dir: D:/ruoyi/prod/uploads # æ£å¼ç®å½ åæå é¤ path: D:/ruoyi/prod/uploads # ä¸ä¼ ç®å½ urlPrefix: /common # 龿¥åç¼ domain: http://127.0.0.1:7005 # åååç¼ expired: 120 # è¿ææ¶é´(åä½:åé) useLimit: 10 # ä½¿ç¨æ¬¡æ° compress: true # æ¯å¦å缩 needCompressSize: 10MB # å缩éå¼ compressQuality: 0.5 # å缩质é(0.0-1.0) knowledge: one: D:\æ°ç大ç½ç´ ä¼ä¸äº§åä½ç³»è¯´æææ¡£.md