Browse Source

修改传参及回复内容格式

zhaojinyu 1 month ago
parent
commit
8d97deb0eb
17 changed files with 740 additions and 65 deletions
  1. 83 50
      base-modules/service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/AiChatController.java
  2. 8 5
      base-modules/service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/AiSessionController.java
  3. 4 0
      base-modules/service-ai/service-ai-biz/src/main/java/com/usky/ai/mapper/AiSessionMapper.java
  4. 22 0
      base-modules/service-ai/service-ai-biz/src/main/java/com/usky/ai/service/vo/AiStreamOutputVO.java
  5. 20 3
      base-modules/service-ai/service-ai-biz/src/main/resources/static/dpsk.html
  6. 20 3
      base-modules/service-ai/service-ai-biz/src/main/resources/static/tyqw.html
  7. 10 0
      base-modules/service-file/pom.xml
  8. 41 4
      base-modules/service-file/src/main/java/com/ruoyi/file/RuoYiFileApplication.java
  9. 37 0
      base-modules/service-file/src/main/java/com/ruoyi/file/controller/UploadFileController.java
  10. 53 0
      base-modules/service-file/src/main/java/com/ruoyi/file/enums/AppHttpCodeEnum.java
  11. 16 0
      base-modules/service-file/src/main/java/com/ruoyi/file/mapper/UploadFileMapper.java
  12. 48 0
      base-modules/service-file/src/main/java/com/ruoyi/file/service/UploadFile.java
  13. 13 0
      base-modules/service-file/src/main/java/com/ruoyi/file/service/UploadFileService.java
  14. 131 0
      base-modules/service-file/src/main/java/com/ruoyi/file/service/UploadFileServiceImpl.java
  15. 104 0
      base-modules/service-file/src/main/java/com/ruoyi/file/utils/ResponseResult.java
  16. 48 0
      base-modules/service-file/src/main/java/com/ruoyi/file/utils/Tools.java
  17. 82 0
      base-modules/service-file/src/main/resources/mapper.file/UploadFileMapper.xml

+ 83 - 50
base-modules/service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/AiChatController.java

@@ -7,12 +7,13 @@ import com.alibaba.dashscope.common.Role;
 import com.alibaba.dashscope.exception.ApiException;
 import com.alibaba.dashscope.exception.InputRequiredException;
 import com.alibaba.dashscope.exception.NoApiKeyException;
-import com.fasterxml.jackson.databind.JsonNode;
+import com.alibaba.fastjson.JSONObject;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.usky.ai.mapper.AiQuestionMapper;
 import com.usky.ai.mapper.AiSessionMapper;
 import com.usky.ai.service.AiQuestion;
 import com.usky.ai.service.AiSession;
+import com.usky.ai.service.vo.AiStreamOutputVO;
 import com.usky.common.security.utils.SecurityUtils;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -59,28 +60,24 @@ public class AiChatController {
 
     // 阿里百炼通义千问大模型
     @PostMapping(value = "/aliTyqw", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
-    public ResponseEntity<StreamingResponseBody> send1(@RequestBody String content, @RequestParam(required = false) String sessionId) throws NoApiKeyException, InputRequiredException {
+    public ResponseEntity<StreamingResponseBody> send1(@RequestBody JSONObject object )throws NoApiKeyException, InputRequiredException {
         // 获取当前登录用户的信息
         Long userId = SecurityUtils.getUserId();
         String userName = SecurityUtils.getLoginUser().getSysUser().getNickName();
+        String sessionId = null;
+        String content = object.get("content").toString();
 
         // 如果没有传入 sessionId,则创建一个新的会话ID
-        if (sessionId == null || sessionId.isEmpty()) {
+        if (object.containsKey("sessionId")) {
+            sessionId = object.get("sessionId").toString();
+        } else {
             sessionId = java.util.UUID.randomUUID().toString();
         }
-
+//        log.info("会话ID:{}", sessionId);
+//        System.out.println("会话ID:" + sessionId);
         // 解析 JSON 并提取 "content" 字段的值
         String questionText;
-        try {
-            JsonNode jsonNode = objectMapper.readTree(content);
-            questionText = jsonNode.get("content").asText(); // 提取 "content" 字段的值
-        } catch (IOException e) {
-            log.error("Error parsing JSON content", e);
-            return ResponseEntity.badRequest().body(outputStream -> {
-                outputStream.write("data: Invalid JSON format\n\n".getBytes(StandardCharsets.UTF_8));
-                outputStream.flush();
-            });
-        }
+        questionText = content; // 提取 "content" 字段的值
 
         // 检查是否已经存在相同的 sessionId
         boolean exists = aiSessionMapper.existsBySessionId(sessionId);
@@ -128,7 +125,7 @@ public class AiChatController {
                 .messages(messages)
                 .resultFormat(GenerationParam.ResultFormat.MESSAGE)
                 .apiKey(apiKey)
-                .incrementalOutput(true) // 开启增量输出[^1^]
+                .incrementalOutput(true) // 开启增量输出
                 .build();
 
         String finalSessionId = sessionId;
@@ -139,13 +136,35 @@ public class AiChatController {
                     try {
                         // 调用流式接口
                         generation.streamCall(param).blockingForEach(chunk -> {
-                            // 获取每次生成的内容
-                            String partialAnswer = chunk.getOutput().getChoices().get(0).getMessage().getContent();
-                            // 将部分内容写入输出流
-                            outputStream.write(("data: " + partialAnswer + "\n\n").getBytes(StandardCharsets.UTF_8));
-                            outputStream.flush();
-                            // 累加到完整回答内容中
-                            completeAnswer.append(partialAnswer);
+                            try {
+                                // 获取每次生成的内容
+                                String partialAnswer = chunk.getOutput().getChoices().get(0).getMessage().getContent();
+
+                                // 检查内容是否为空,如果为空则跳过
+                                if (partialAnswer == null || partialAnswer.trim().isEmpty()) {
+                                    return; // 如果内容为空,直接返回,不进行后续操作
+                                }
+
+                                // 构建输出对象
+                                AiStreamOutputVO aiStreamOutputVO = new AiStreamOutputVO();
+                                aiStreamOutputVO.setSessionId(finalSessionId);
+                                aiStreamOutputVO.setReasoningContent(partialAnswer);
+
+                                // 转换为 JSON 字符串
+                                String newString = objectMapper.writeValueAsString(aiStreamOutputVO);
+
+                                // 写入输出流
+                                outputStream.write(("data: " + newString + "\n\n").getBytes(StandardCharsets.UTF_8));
+                                outputStream.flush(); // 确保立即发送到前端
+
+                                // 累加到完整回答内容中
+                                completeAnswer.append(partialAnswer);
+
+                            } catch (Exception e) {
+                                log.error("Error processing chunk", e);
+                                outputStream.write(("data: Error processing chunk\n\n").getBytes(StandardCharsets.UTF_8));
+                                outputStream.flush();
+                            }
                         });
 
                         // 流式接口调用完成后,将完整回答存入数据库
@@ -154,7 +173,7 @@ public class AiChatController {
                         aiQuestion.setSessionId(finalSessionId);
                         aiQuestion.setUserId(userId);
                         aiQuestion.setUserName(userName);
-                        aiQuestion.setQuestion(questionText); // 存入提取的文本
+                        aiQuestion.setQuestion(questionText);
                         aiQuestion.setAnswer(completeAnswer.toString());
                         aiQuestion.setAskTime(LocalDateTime.now());
                         aiQuestionMapper.save(aiQuestion);
@@ -163,9 +182,7 @@ public class AiChatController {
                         log.error("Error processing request", e);
                         outputStream.write(("data: Error processing request\n\n").getBytes(StandardCharsets.UTF_8));
                         outputStream.flush();
-                    } catch (NoApiKeyException e) {
-                        throw new RuntimeException(e);
-                    } catch (InputRequiredException e) {
+                    } catch (NoApiKeyException | InputRequiredException e) {
                         throw new RuntimeException(e);
                     }
                 });
@@ -173,28 +190,24 @@ public class AiChatController {
 
     // 阿里百炼DeepSeek大模型
     @PostMapping(value = "/aliDeepSeek", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
-    public ResponseEntity<StreamingResponseBody> send2(@RequestBody String content, @RequestParam(required = false) String sessionId) throws NoApiKeyException, InputRequiredException {
+    public ResponseEntity<StreamingResponseBody> send2(@RequestBody JSONObject object )throws NoApiKeyException, InputRequiredException {
         // 获取当前登录用户的信息
         Long userId = SecurityUtils.getUserId();
         String userName = SecurityUtils.getLoginUser().getSysUser().getNickName();
+        String sessionId = null;
+        String content = object.get("content").toString();
 
         // 如果没有传入 sessionId,则创建一个新的会话ID
-        if (sessionId == null || sessionId.isEmpty()) {
+        if (object.containsKey("sessionId")) {
+            sessionId = object.get("sessionId").toString();
+        } else {
             sessionId = java.util.UUID.randomUUID().toString();
         }
-
+//        log.info("会话ID:{}", sessionId);
+//        System.out.println("会话ID:" + sessionId);
         // 解析 JSON 并提取 "content" 字段的值
         String questionText;
-        try {
-            JsonNode jsonNode = objectMapper.readTree(content);
-            questionText = jsonNode.get("content").asText(); // 提取 "content" 字段的值
-        } catch (IOException e) {
-            log.error("Error parsing JSON content", e);
-            return ResponseEntity.badRequest().body(outputStream -> {
-                outputStream.write("data: Invalid JSON format\n\n".getBytes(StandardCharsets.UTF_8));
-                outputStream.flush();
-            });
-        }
+        questionText = content; // 提取 "content" 字段的值
 
         // 检查是否已经存在相同的 sessionId
         boolean exists = aiSessionMapper.existsBySessionId(sessionId);
@@ -253,13 +266,35 @@ public class AiChatController {
                     try {
                         // 调用流式接口
                         generation.streamCall(param).blockingForEach(chunk -> {
-                            // 获取每次生成的内容
-                            String partialAnswer = chunk.getOutput().getChoices().get(0).getMessage().getContent();
-                            // 将部分内容写入输出流
-                            outputStream.write(("data: " + partialAnswer + "\n\n").getBytes(StandardCharsets.UTF_8));
-                            outputStream.flush();
-                            // 累加到完整回答内容中
-                            completeAnswer.append(partialAnswer);
+                            try {
+                                // 获取每次生成的内容
+                                String partialAnswer = chunk.getOutput().getChoices().get(0).getMessage().getContent();
+
+                                // 检查内容是否为空,如果为空则跳过
+                                if (partialAnswer == null || partialAnswer.trim().isEmpty()) {
+                                    return; // 如果内容为空,直接返回,不进行后续操作
+                                }
+
+                                // 构建输出对象
+                                AiStreamOutputVO aiStreamOutputVO = new AiStreamOutputVO();
+                                aiStreamOutputVO.setSessionId(finalSessionId);
+                                aiStreamOutputVO.setReasoningContent(partialAnswer);
+
+                                // 转换为 JSON 字符串
+                                String newString = objectMapper.writeValueAsString(aiStreamOutputVO);
+
+                                // 写入输出流
+                                outputStream.write(("data: " + newString + "\n\n").getBytes(StandardCharsets.UTF_8));
+                                outputStream.flush(); // 确保立即发送到前端
+
+                                // 累加到完整回答内容中
+                                completeAnswer.append(partialAnswer);
+
+                            } catch (Exception e) {
+                                log.error("Error processing chunk", e);
+                                outputStream.write(("data: Error processing chunk\n\n").getBytes(StandardCharsets.UTF_8));
+                                outputStream.flush();
+                            }
                         });
 
                         // 流式接口调用完成后,将完整回答存入数据库
@@ -268,7 +303,7 @@ public class AiChatController {
                         aiQuestion.setSessionId(finalSessionId);
                         aiQuestion.setUserId(userId);
                         aiQuestion.setUserName(userName);
-                        aiQuestion.setQuestion(questionText); // 存入提取的文本
+                        aiQuestion.setQuestion(questionText);
                         aiQuestion.setAnswer(completeAnswer.toString());
                         aiQuestion.setAskTime(LocalDateTime.now());
                         aiQuestionMapper.save(aiQuestion);
@@ -277,9 +312,7 @@ public class AiChatController {
                         log.error("Error processing request", e);
                         outputStream.write(("data: Error processing request\n\n").getBytes(StandardCharsets.UTF_8));
                         outputStream.flush();
-                    } catch (NoApiKeyException e) {
-                        throw new RuntimeException(e);
-                    } catch (InputRequiredException e) {
+                    } catch (NoApiKeyException | InputRequiredException e) {
                         throw new RuntimeException(e);
                     }
                 });

+ 8 - 5
base-modules/service-ai/service-ai-biz/src/main/java/com/usky/ai/controller/web/AiSessionController.java

@@ -1,6 +1,7 @@
 package com.usky.ai.controller.web;
 
 import com.usky.ai.mapper.AiSessionMapper;
+import com.usky.ai.service.AiSession;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -8,6 +9,8 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 
+import java.util.List;
+
 @Slf4j
 @RestController
 @RequestMapping("/session")
@@ -17,12 +20,12 @@ public class AiSessionController {
     private AiSessionMapper aiSessionMapper;
 
     @GetMapping("/all")
-    public String getAllSessions() {
-        return aiSessionMapper.findAll().toString();
+    public List<AiSession> getAllSessions() {
+        return aiSessionMapper.findAll();
     }
 
-    @GetMapping("/session/{sessionId}")
-    public String getSessionsBySessionId(@PathVariable String sessionId) {
-        return aiSessionMapper.findBySessionId(sessionId).toString();
+    @GetMapping("/{sessionId}")
+    public List<AiSession> getSessionsBySessionId(@PathVariable String sessionId) {
+        return aiSessionMapper.findBySessionId(sessionId);
     }
 }

+ 4 - 0
base-modules/service-ai/service-ai-biz/src/main/java/com/usky/ai/mapper/AiSessionMapper.java

@@ -21,6 +21,10 @@ public interface AiSessionMapper {
     @Select("SELECT * FROM ai_sessions WHERE session_id = #{sessionId} ORDER BY ask_time ASC")
     List<AiSession> findBySessionId(String sessionId);
 
+    //根据user_id 查询数据
+    @Select("SELECT * FROM ai_sessions WHERE user_id = #{userId} ORDER BY ask_time ASC")
+    List<AiSession> findByUserId(Long userId);
+
     // 检查是否存在指定的 session_id
     @Select("SELECT COUNT(*) FROM ai_sessions WHERE session_id = #{sessionId}")
     boolean existsBySessionId(String sessionId);

+ 22 - 0
base-modules/service-ai/service-ai-biz/src/main/java/com/usky/ai/service/vo/AiStreamOutputVO.java

@@ -0,0 +1,22 @@
+package com.usky.ai.service.vo;
+
+import lombok.Data;
+
+@Data
+public class AiStreamOutputVO {
+
+    /**
+     * 会话id
+     */
+    private String sessionId;
+
+    /**
+     * 输入内容
+     */
+    private String content;
+
+    /**
+     * 输出内容
+     */
+    private String reasoningContent;
+}

+ 20 - 3
base-modules/service-ai/service-ai-biz/src/main/resources/static/dpsk.html

@@ -80,6 +80,7 @@
         <button type="submit">发送</button>
     </form>
     <div id="response"></div>
+    <div id="sessionId" style="margin-top: 10px; font-size: 0.9em; color: #666;"></div>
 </div>
 
 <script>
@@ -88,7 +89,7 @@
 
         const content = document.getElementById('content').value;
 
-        const requestBody = JSON.stringify({ content: content });
+        const requestBody = JSON.stringify({content: content});
 
         const token = "eyJhbGciOiJIUzUxMiJ9.eyIiOjEwMDMsInVzZXJfaWQiOjIxMywidXNlcl9rZXkiOiJlYzUxODMzNjdmYTk0ODgwOGQwZjEwODEyOWVmNjgwOSIsInVzZXJuYW1lIjoi6LW16YeR6ZuoIn0.zWulXcesI1TRcDmiAHuQ9P2WHDE2l7mDmuunx13TmVl6E5Yvs8nZvu1ddtINdw0lrnnR3Q5lZaRH3mJJTaDhig";
 
@@ -109,9 +110,25 @@
             .then(data => {
                 document.getElementById('response').innerText = '';
 
+                // 解析数据,提取 reasoningContent
                 const lines = data.split('\n');
-                const fullResponse = lines.map(line => line.replace('data: ', '')).join('');
+                let responseLines = [];
 
+                for (let line of lines) {
+                    try {
+                        const parsedLine = JSON.parse(line.replace('data: ', '').trim());
+                        if (parsedLine.reasoningContent !== null) {
+                            responseLines.push(parsedLine.reasoningContent);
+                        }
+                    } catch (e) {
+                        console.error('Error parsing line:', e);
+                    }
+                }
+
+                // 将 reasoningContent 的内容拼接成完整字符串
+                const fullResponse = responseLines.join('');
+
+                // 逐字显示响应内容
                 let index = 0;
                 const responseElement = document.getElementById('response');
                 const interval = setInterval(() => {
@@ -126,7 +143,7 @@
             .catch(error => {
                 document.getElementById('response').innerText = 'Error: ' + error.message;
             });
-    });
+    })
 </script>
 </body>
 </html>

+ 20 - 3
base-modules/service-ai/service-ai-biz/src/main/resources/static/tyqw.html

@@ -80,6 +80,7 @@
         <button type="submit">发送</button>
     </form>
     <div id="response"></div>
+    <div id="sessionId" style="margin-top: 10px; font-size: 0.9em; color: #666;"></div>
 </div>
 
 <script>
@@ -88,7 +89,7 @@
 
         const content = document.getElementById('content').value;
 
-        const requestBody = JSON.stringify({ content: content });
+        const requestBody = JSON.stringify({content: content});
 
         const token = "eyJhbGciOiJIUzUxMiJ9.eyIiOjEwMDMsInVzZXJfaWQiOjIxMywidXNlcl9rZXkiOiJlYzUxODMzNjdmYTk0ODgwOGQwZjEwODEyOWVmNjgwOSIsInVzZXJuYW1lIjoi6LW16YeR6ZuoIn0.zWulXcesI1TRcDmiAHuQ9P2WHDE2l7mDmuunx13TmVl6E5Yvs8nZvu1ddtINdw0lrnnR3Q5lZaRH3mJJTaDhig";
 
@@ -109,9 +110,25 @@
             .then(data => {
                 document.getElementById('response').innerText = '';
 
+                // 解析数据,提取 reasoningContent
                 const lines = data.split('\n');
-                const fullResponse = lines.map(line => line.replace('data: ', '')).join('');
+                let responseLines = [];
 
+                for (let line of lines) {
+                    try {
+                        const parsedLine = JSON.parse(line.replace('data: ', '').trim());
+                        if (parsedLine.reasoningContent !== null) {
+                            responseLines.push(parsedLine.reasoningContent);
+                        }
+                    } catch (e) {
+                        console.error('Error parsing line:', e);
+                    }
+                }
+
+                // 将 reasoningContent 的内容拼接成完整字符串
+                const fullResponse = responseLines.join('');
+
+                // 逐字显示响应内容
                 let index = 0;
                 const responseElement = document.getElementById('response');
                 const interval = setInterval(() => {
@@ -126,7 +143,7 @@
             .catch(error => {
                 document.getElementById('response').innerText = 'Error: ' + error.message;
             });
-    });
+    })
 </script>
 </body>
 </html>

+ 10 - 0
base-modules/service-file/pom.xml

@@ -74,6 +74,16 @@
             <groupId>com.usky</groupId>
             <artifactId>ruoyi-common-core</artifactId>
         </dependency>
+        <dependency>
+            <groupId>javax.validation</groupId>
+            <artifactId>validation-api</artifactId>
+            <version>2.0.1.Final</version>
+        </dependency>
+        <dependency>
+            <groupId>com.h2database</groupId>
+            <artifactId>h2</artifactId>
+            <scope>runtime</scope>
+        </dependency>
 
     </dependencies>
 

+ 41 - 4
base-modules/service-file/src/main/java/com/ruoyi/file/RuoYiFileApplication.java

@@ -1,21 +1,58 @@
 package com.ruoyi.file;
 
+import org.mybatis.spring.annotation.MapperScan;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import com.ruoyi.common.swagger.annotation.EnableCustomSwagger2;
+import org.springframework.cloud.openfeign.EnableFeignClients;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.EnableAspectJAutoProxy;
+import org.springframework.core.env.Environment;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
 
 /**
  * 文件服务
  * 
  * @author ruoyi
  */
+//@EnableCustomSwagger2
+//@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
+//public class RuoYiFileApplication
+//{
+//    public static void main(String[] args)
+//    {
+//        SpringApplication.run(RuoYiFileApplication.class, args);
+//    }
+//}
+
 @EnableCustomSwagger2
-@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class })
+@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
+@EnableFeignClients(basePackages = {"com.ruoyi"})
+@MapperScan(value = "com.ruoyi.file.mapper")
+@ComponentScan(basePackages = {"com.ruoyi"})
+@SpringBootApplication
 public class RuoYiFileApplication
 {
-    public static void main(String[] args)
-    {
-        SpringApplication.run(RuoYiFileApplication.class, args);
+    private static final Logger LOGGER = LoggerFactory.getLogger(RuoYiFileApplication.class);
+
+    public static void main(String[] args) throws UnknownHostException {
+        ConfigurableApplicationContext application = SpringApplication.run(RuoYiFileApplication.class, args);
+        Environment env = application.getEnvironment();
+        String ip = InetAddress.getLocalHost().getHostAddress();
+        String port = env.getProperty("server.port");
+        String path = env.getProperty("server.servlet.context-path");
+        LOGGER.info("\n----------------------------------------------------------\n\t" +
+                "Application is running! Access URLs:\n\t" +
+                "Local: \t\thttp://localhost:" + port + (null==path?"":path) + "/\n\t" +
+                "External: \thttp://" + ip + ":" + port + (null==path?"":path) + "/\n\t" +
+                "Api: \t\thttp://" + ip + ":" + port + (null==path?"":path) + "/swagger-ui/index.html\n\t" +
+                "----------------------------------------------------------");
     }
 }
+

+ 37 - 0
base-modules/service-file/src/main/java/com/ruoyi/file/controller/UploadFileController.java

@@ -0,0 +1,37 @@
+package com.ruoyi.file.controller;
+
+import com.ruoyi.file.service.UploadFileService;
+import com.ruoyi.file.utils.ResponseResult;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import javax.validation.Valid;
+import javax.validation.constraints.NotBlank;
+
+@Api(tags = "附件管理")
+@RestController
+@RequestMapping("/attach")
+public class UploadFileController {
+
+    @Autowired
+    private UploadFileService uploadFileService;
+
+    @PostMapping("/upload")
+    @ApiOperation(value="上传文件")
+    public ResponseResult upload(@RequestPart("file") MultipartFile file,
+                                 @RequestParam(value = "bussinessId") @Valid @NotBlank(message = "业务id不能为空") String bussinessId) throws Exception {
+        return uploadFileService.upload(file, bussinessId);
+    }
+
+    @GetMapping("/download")
+    @ApiOperation(value = "下载文件")
+    public void download(HttpServletResponse response,
+                         @Valid @NotBlank(message = "id不能为空") String id) throws Exception {
+        uploadFileService.download(response, id);
+    }
+
+}

+ 53 - 0
base-modules/service-file/src/main/java/com/ruoyi/file/enums/AppHttpCodeEnum.java

@@ -0,0 +1,53 @@
+package com.ruoyi.file.enums;
+
+public enum AppHttpCodeEnum {
+
+    // 成功段固定为200
+    SUCCESS(200,"操作成功"),
+
+    // 登录段1~50
+    NEED_LOGIN(1,"需要登录后操作"),
+    LOGIN_PASSWORD_ERROR(2,"密码错误"),
+
+    // TOKEN50~100
+    TOKEN_INVALID(50,"无效的TOKEN"),
+    TOKEN_EXPIRE(51,"TOKEN已过期"),
+    TOKEN_REQUIRE(52,"TOKEN是必须的"),
+
+    // SIGN验签 100~120
+    SIGN_INVALID(100,"无效的SIGN"),
+    SIG_TIMEOUT(101,"SIGN已过期"),
+
+    // 参数错误 500~1000
+    PARAM_REQUIRE(500,"缺少参数"),
+    PARAM_INVALID(501,"无效参数"),
+    PARAM_IMAGE_FORMAT_ERROR(502,"图片格式有误"),
+    SERVER_ERROR(503,"服务器内部错误"),
+
+    // 数据错误 1000~2000
+    DATA_EXIST(1000,"数据已经存在"),
+    AP_USER_DATA_NOT_EXIST(1001,"ApUser数据不存在"),
+    DATA_NOT_EXIST(1002,"数据不存在"),
+    DATA_DUPLICATE(1003,"数据重复"),
+    OPERATION_FAILED(1004,"操作失败"),
+
+    // 数据错误 3000~3500
+    NO_OPERATOR_AUTH(3000,"无权限操作"),
+    NEED_ADMIND(3001,"需要管理员权限");
+
+    int code;
+    String message;
+
+    AppHttpCodeEnum(int code, String errorMessage){
+        this.code = code;
+        this.message = errorMessage;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+}

+ 16 - 0
base-modules/service-file/src/main/java/com/ruoyi/file/mapper/UploadFileMapper.java

@@ -0,0 +1,16 @@
+package com.ruoyi.file.mapper;
+
+import com.ruoyi.file.service.UploadFile;
+import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+
+import java.util.List;
+
+@Mapper
+public interface UploadFileMapper {
+
+    int insert(@Param("uploadFile") UploadFile uploadFile);
+
+    List<UploadFile> selectOne(@Param("id") String id, @Param("bussinessId") String bussinessId, @Param("status") Integer status);
+
+}

+ 48 - 0
base-modules/service-file/src/main/java/com/ruoyi/file/service/UploadFile.java

@@ -0,0 +1,48 @@
+package com.ruoyi.file.service;
+
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Data
+public class UploadFile {
+    private String id;
+
+    //文件在系统中的名称
+    private String name;
+
+    //文件名称
+    private String fileName;
+
+    //文件后缀
+    private String fileSuffix;
+
+    //文件类型
+    private Integer type;
+
+    //主目录
+    private String domain;
+
+    //完整目录
+    private String path;
+
+    //扩展目录
+    private String pathExt;
+
+    //文件大小
+    private Integer size;
+
+    //关联的业务id
+    private String bussinessId;
+
+    //逻辑删除状态
+    private Integer status;
+
+    private LocalDateTime createTime;
+
+    private String createBy;
+
+    private LocalDateTime updateTime;
+
+    private String updateBy;
+}

+ 13 - 0
base-modules/service-file/src/main/java/com/ruoyi/file/service/UploadFileService.java

@@ -0,0 +1,13 @@
+package com.ruoyi.file.service;
+
+import com.ruoyi.file.utils.ResponseResult;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+
+public interface UploadFileService {
+
+    ResponseResult upload(MultipartFile file, String bussinessId) throws Exception;
+
+    ResponseResult download(HttpServletResponse response, String id) throws Exception;
+}

+ 131 - 0
base-modules/service-file/src/main/java/com/ruoyi/file/service/UploadFileServiceImpl.java

@@ -0,0 +1,131 @@
+package com.ruoyi.file.service;
+
+import com.ruoyi.file.enums.AppHttpCodeEnum;
+import com.ruoyi.file.mapper.UploadFileMapper;
+import com.ruoyi.file.utils.ResponseResult;
+import com.ruoyi.file.utils.Tools;
+import org.apache.commons.collections4.CollectionUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.*;
+import java.util.*;
+
+import lombok.extern.slf4j.Slf4j;
+import java.time.LocalDateTime;
+
+@Slf4j
+@Service
+public class UploadFileServiceImpl implements UploadFileService {
+
+    private static final String LOCAL_HOST = "https://file.usky.cn/static/";
+
+    @Autowired
+    private UploadFileMapper uploadFileMapper;
+
+    @Override
+    @Transactional
+    public ResponseResult upload(MultipartFile multipartFile, String bussinessId) throws Exception {
+        if (multipartFile.isEmpty()) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID,"上传文件为空");
+        }
+        UploadFile uploadFile = new UploadFile();
+        // 保存原文件名称,文件列表展示需要用到
+        uploadFile.setFileName(multipartFile.getOriginalFilename());
+        // 生成系统文件名称,不可重复,防止同名文件上传覆盖问题
+        String name = RandomStringUtils.randomAlphanumeric(32) + multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().lastIndexOf(".")).toLowerCase();
+        uploadFile.setName(name.substring(0, name.lastIndexOf(".")));
+        uploadFile.setFileSuffix(multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().lastIndexOf(".")).toLowerCase());
+        // 判断文件类型
+        int fileType = Tools.fileType(name);
+        uploadFile.setType(fileType);
+        uploadFile.setDomain(LOCAL_HOST + "resource/files/");
+        //这种方式比类型强转效率更高
+        String pathExt = System.currentTimeMillis()+"";
+        uploadFile.setPath(LOCAL_HOST + "resource/files/" + pathExt);
+        uploadFile.setPathExt(pathExt);
+        // 获取文件大小
+        uploadFile.setSize((int)multipartFile.getSize());
+        uploadFile.setBussinessId(bussinessId);
+        uploadFile.setStatus(1);
+        uploadFile.setCreateTime(LocalDateTime.now());
+        uploadFile.setId(Tools.getCode32());
+        uploadFileMapper.insert(uploadFile);
+
+        // 将文件保存到本目录/resources/files/下
+        // DateUtil.today()得到得是当天日期如:20230715,这个会在/resources/files/下再以日期生成一层目录
+        File newFile = new File("./resources/files/"+ pathExt +  "/" + name);
+        // 保证这个文件的父文件夹必须要存在
+        if (!newFile.getParentFile().exists()) {
+            newFile.getParentFile().mkdirs();
+        }
+        newFile.createNewFile();
+        // 将文件内容写入到这个文件中
+        InputStream is = multipartFile.getInputStream();
+        FileOutputStream fos = new FileOutputStream(newFile);
+        try {
+            int len;
+            byte[] buf = new byte[1024];
+            while ((len = is.read(buf)) != -1) {
+                fos.write(buf, 0, len);
+            }
+        } finally {
+            // 关流顺序,先打开的后关闭
+            fos.close();
+            is.close();
+        }
+
+        // 返回文件信息给前端
+        Map resultMap = new HashMap();
+        resultMap.put("id", uploadFile.getId());
+        resultMap.put("path", uploadFile.getPath());
+        return ResponseResult.okResult(resultMap);
+    }
+
+    @Override
+    public ResponseResult download(HttpServletResponse response, String id) throws Exception {
+        List<UploadFile> uploadFiles = uploadFileMapper.selectOne(id, null, 1);
+        if (CollectionUtils.isEmpty(uploadFiles)) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "查询文件为空");
+        }
+
+        // 去./resources/files/目录下取出文件
+        File downloadFile = new File("./resources/files/" +uploadFiles.get(0).getPathExt() +  "/" + uploadFiles.get(0).getName() + uploadFiles.get(0).getFileSuffix());
+        if (!downloadFile.exists() || downloadFile.length() == 0) {
+            return ResponseResult.errorResult(AppHttpCodeEnum.PARAM_INVALID, "查询文件为空");
+        }
+
+        InputStream is = null;
+        OutputStream os = null;
+        try {
+            // 判断是否是图片,如果是图片加上 response.setContentType("image/jpeg"),这样就可以直接在浏览器打开而不是下载
+            if (uploadFiles.get(0).getType() == 1) {
+                response.setContentType("image/jpeg");
+            }
+            response.addHeader("Content-Length", "" + downloadFile.length());
+            is = new FileInputStream(downloadFile);
+            os = response.getOutputStream();
+            IOUtils.copy(is, os);
+        } catch (Exception e) {
+            log.error("下载文件发生异常", e);
+        } finally {
+            try {
+                if (os != null) {
+                    os.flush();
+                    os.close();
+                }
+                if (is != null) {
+                    is.close();
+                }
+            } catch (IOException e) {
+                log.error("关闭流发生异常", e);
+            }
+        }
+        return ResponseResult.okResult("下载成功");
+    }
+}

+ 104 - 0
base-modules/service-file/src/main/java/com/ruoyi/file/utils/ResponseResult.java

@@ -0,0 +1,104 @@
+package com.ruoyi.file.utils;
+
+import com.ruoyi.file.enums.AppHttpCodeEnum;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 通用的结果返回类
+ * @param <T>
+ */
+@Data
+public class ResponseResult<T> implements Serializable {
+
+    private String token;
+
+    private Integer code;
+
+    private String message;
+
+    private T data;
+
+    private String status;
+
+    public ResponseResult() {
+        this.code = 200;
+    }
+
+    public ResponseResult(Integer code, T data) {
+        this.code = code;
+        this.data = data;
+    }
+
+    public ResponseResult(Integer code, String msg, T data) {
+        this.code = code;
+        this.message = msg;
+        this.data = data;
+    }
+
+    public ResponseResult(Integer code, String msg) {
+        this.code = code;
+        this.message = msg;
+    }
+
+    public static ResponseResult okResult(int code, String msg) {
+        ResponseResult result = new ResponseResult();
+        return result.ok(code, null, msg);
+    }
+
+    public static ResponseResult okResult(int code, Object data,String msg) {
+        ResponseResult result = new ResponseResult();
+        return result.ok(code, data, msg);
+    }
+
+    public static ResponseResult okResult(Object data) {
+        ResponseResult result = setAppHttpCodeEnum(AppHttpCodeEnum.SUCCESS, AppHttpCodeEnum.SUCCESS.getMessage());
+        if(data!=null) {
+            result.setData(data);
+        }
+        return result;
+    }
+
+    public static ResponseResult errorResult(int code, String msg) {
+        ResponseResult result = new ResponseResult();
+        return result.error(code, msg);
+    }
+
+    public static ResponseResult errorResult(AppHttpCodeEnum enums){
+        return setAppHttpCodeEnum(enums,enums.getMessage());
+    }
+
+    public static ResponseResult errorResult(AppHttpCodeEnum enums, String message){
+        return setAppHttpCodeEnum(enums,message);
+    }
+
+    public static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums){
+        return okResult(enums.getCode(),enums.getMessage());
+    }
+
+    private static ResponseResult setAppHttpCodeEnum(AppHttpCodeEnum enums, String message){
+        return okResult(enums.getCode(),message);
+    }
+
+    public ResponseResult<?> error(Integer code, String msg) {
+        this.code = code;
+        this.message = msg;
+        return this;
+    }
+    public ResponseResult<?> ok(Integer code, T data) {
+        this.code = code;
+        this.data = data;
+        return this;
+    }
+    public ResponseResult<?> ok(Integer code, T data, String msg) {
+        this.code = code;
+        this.data = data;
+        this.message = msg;
+        return this;
+    }
+    public ResponseResult<?> ok(T data) {
+        this.data = data;
+        return this;
+    }
+}

+ 48 - 0
base-modules/service-file/src/main/java/com/ruoyi/file/utils/Tools.java

@@ -0,0 +1,48 @@
+package com.ruoyi.file.utils;
+
+public class Tools {
+    public static int fileType(String fileName) {
+        if (fileName == null) {
+            return 0;
+        } else {
+            // 获取文件后缀名并转化为写,用于后续比较
+            String fileType = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
+            // 创建图片类型数组
+            String img[] = {"bmp", "jpg", "jpeg", "png", "tiff", "gif", "pcx", "tga", "exif", "fpx", "svg", "psd",
+                    "cdr", "pcd", "dxf", "ufo", "eps", "ai", "raw", "wmf"};
+            for (int i = 0; i < img.length; i++) {
+                if (img[i].equals(fileType)) {
+                    return 1;
+                }
+            }
+            // 创建文档类型数组
+            String document[] = {"txt", "doc", "docx", "xls", "htm", "html", "jsp", "rtf", "wpd", "pdf", "ppt"};
+            for (int i = 0; i < document.length; i++) {
+                if (document[i].equals(fileType)) {
+                    return 2;
+                }
+            }
+            // 创建视频类型数组
+            String video[] = {"mp4", "avi", "mov", "wmv", "asf", "navi", "3 gp", "mkv", "f4v", "rmvb", "webm"};
+            for (int i = 0; i < video.length; i++) {
+                if (video[i].equals(fileType)) {
+                    return 3;
+                }
+            }
+            // 创建音乐类型数组
+            String music[] = {"mp3", "wma", "wav", "mod", "ra", "cd", "md", "asf", "aac", "vqf", "ape", "mid", "ogg",
+                    "m4a", "vqf"};
+            for (int i = 0; i < music.length; i++) {
+                if (music[i].equals(fileType)) {
+                    return 4;
+                }
+            }
+        }
+        return 0;
+    }
+
+
+    public static String getCode32() {
+        return java.util.UUID.randomUUID().toString().replace("-", "").toLowerCase();
+    }
+}

+ 82 - 0
base-modules/service-file/src/main/resources/mapper.file/UploadFileMapper.xml

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.file.mapper.UploadFileMapper">
+
+    <resultMap type="com.ruoyi.file.service.UploadFile" id="UploadFileResult">
+        <result property="id" column="id"/>
+        <result property="name" column="name"/>
+        <result property="fileName" column="file_name"/>
+        <result property="fileSuffix" column="file_suffix"/>
+        <result property="type" column="type"/>
+        <result property="domain" column="domain"/>
+        <result property="path" column="path"/>
+        <result property="pathExt" column="path_ext"/>
+        <result property="size" column="size"/>
+        <result property="bussinessId" column="bussiness_id"/>
+        <result property="status" column="status"/>
+        <result property="createTime" column="create_time"/>
+        <result property="createBy" column="create_by"/>
+        <result property="updateTime" column="update_time"/>
+        <result property="updateBy" column="update_by"/>
+    </resultMap>
+
+    <insert id="insertUploadFile" parameterType="com.ruoyi.file.service.UploadFile">
+        insert into upload_file
+        <trim prefix="(" suffix=")" suffixOverrides=",">
+            <if test="id != null">id,</if>
+            <if test="name != null">name,</if>
+            <if test="fileName != null">file_name,</if>
+            <if test="fileSuffix != null">file_suffix,</if>
+            <if test="type != null">type,</if>
+            <if test="domain != null">domain,</if>
+            <if test="path != null">path,</if>
+            <if test="pathExt != null">path_ext,</if>
+            <if test="size != null">size,</if>
+            <if test="bussinessId != null">bussiness_id,</if>
+            <if test="status != null">status,</if>
+            <if test="createTime != null">create_time,</if>
+            <if test="createBy != null">create_by,</if>
+            <if test="updateTime != null">update_time,</if>
+            <if test="updateBy != null">update_by,</if>
+        </trim>
+        values
+        <trim prefix="values (" suffix=")" suffixOverrides=",">
+            <if test="id != null">#{id},</if>
+            <if test="name != null">#{name},</if>
+            <if test="fileName != null">#{fileName},</if>
+            <if test="fileSuffix != null">#{fileSuffix},</if>
+            <if test="type != null">#{type},</if>
+            <if test="domain != null">#{domain},</if>
+            <if test="path != null">#{path},</if>
+            <if test="pathExt != null">#{pathExt},</if>
+            <if test="size != null">#{size},</if>
+            <if test="bussinessId != null">#{bussinessId},</if>
+            <if test="status != null">#{status},</if>
+            <if test="createTime != null">#{createTime},</if>
+            <if test="createBy != null">#{createBy},</if>
+            <if test="updateTime != null">#{updateTime},</if>
+            <if test="updateBy != null">#{updateBy},</if>
+        </trim>
+    </insert>
+
+    <select id="selectOne" resultMap="UploadFileResult">
+        select
+        <include refid="UploadFile_Column_List"/>
+        from upload_file
+        where 1=1
+        <if test="id != null">
+            and id = #{id}
+        </if>
+        <if test="bussinessId != null">
+            and bussiness_id = #{bussinessId}
+        </if>
+        <if test="status != null">
+            and status = #{status}
+        </if>
+    </select>
+
+
+
+</mapper>