|
|
@@ -2,15 +2,30 @@
|
|
|
package com.zsjz.ai.module.plat.controller;
|
|
|
|
|
|
import com.zsjz.ai.common.config.Result;
|
|
|
+import com.zsjz.ai.common.exception.ServerException;
|
|
|
import com.zsjz.ai.common.model.plat.entity.SystemInfo;
|
|
|
import com.zsjz.ai.module.plat.service.SystemService;
|
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
|
+import org.springframework.core.io.FileSystemResource;
|
|
|
+import org.springframework.core.io.Resource;
|
|
|
+import org.springframework.http.CacheControl;
|
|
|
+import org.springframework.http.ContentDisposition;
|
|
|
+import org.springframework.http.HttpHeaders;
|
|
|
+import org.springframework.http.MediaType;
|
|
|
+import org.springframework.http.ResponseEntity;
|
|
|
import org.springframework.web.bind.annotation.GetMapping;
|
|
|
import org.springframework.web.bind.annotation.RequestMapping;
|
|
|
import org.springframework.web.bind.annotation.RestController;
|
|
|
|
|
|
+import java.io.IOException;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.util.Locale;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
/**
|
|
|
* 系统控制器
|
|
|
* 提供系统授权、版本管理、缓存管理等系统级接口
|
|
|
@@ -20,6 +35,9 @@ import org.springframework.web.bind.annotation.RestController;
|
|
|
* 当成"视图名"去转发(日志表现为 {@code View name [sys/health]} → {@code Forwarding to [...]}),
|
|
|
* 最终一律 404;而前端 `main.ts` 期望 `Result` 包装(`res?.code === 200`),
|
|
|
* 会导致启动健康检查空转 2 分钟并总是跳转到授权页。
|
|
|
+ *
|
|
|
+ * <p>例外:{@code /sys/openLocalFile}、{@code /sys/openOfficeFile} 是**文件下载**接口,
|
|
|
+ * 刻意不返回 {@code Result},响应体是文件流(前端按 blob 读取)。
|
|
|
*/
|
|
|
@RestController
|
|
|
@RequestMapping("/sys")
|
|
|
@@ -103,21 +121,62 @@ public class SystemController {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 打开本地Office文件
|
|
|
- * 支持CSV、XLS、XLSX格式文件
|
|
|
+ * 下载本地文件(原「调用系统程序打开本地文件」的 Web 化改造)
|
|
|
+ * 支持 CSV、XLS、XLSX;以附件形式返回文件流,由浏览器保存或交给本地应用打开
|
|
|
*
|
|
|
- * @param filePath 文件路径
|
|
|
+ * @param filePath 文件在服务端的绝对路径
|
|
|
*/
|
|
|
@GetMapping("/openLocalFile")
|
|
|
- public Result<Void> openLocalFile(String filePath) {
|
|
|
- systemService.openLocalOfficeFile(filePath);
|
|
|
- return Result.succeed();
|
|
|
+ public ResponseEntity<Resource> openLocalFile(String filePath) {
|
|
|
+ return buildDownloadResponse(systemService.resolveDownloadFile(filePath));
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * 按文件ID下载文件(原「调用系统程序打开已归档表格」的 Web 化改造)
|
|
|
+ *
|
|
|
+ * @param fid file_info.id(文件或工作表)
|
|
|
+ */
|
|
|
@GetMapping("/openOfficeFile")
|
|
|
- public Result<Void> openOfficeFile(Long fid) {
|
|
|
- systemService.openOfficeFile(fid);
|
|
|
- return Result.succeed();
|
|
|
+ public ResponseEntity<Resource> openOfficeFile(Long fid) {
|
|
|
+ return buildDownloadResponse(systemService.resolveDownloadFileByFid(fid));
|
|
|
+ }
|
|
|
+
|
|
|
+ /** 可下载文件扩展名 → Content-Type */
|
|
|
+ private static final Map<String, MediaType> DOWNLOAD_MEDIA_TYPES = Map.of(
|
|
|
+ "csv", new MediaType("text", "csv", StandardCharsets.UTF_8),
|
|
|
+ "xls", MediaType.parseMediaType("application/vnd.ms-excel"),
|
|
|
+ "xlsx", MediaType.parseMediaType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"));
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 组装下载响应:附件(attachment)+ RFC 5987 文件名(中文名不乱码)+ 内容长度,且禁止中间层缓存。
|
|
|
+ *
|
|
|
+ * <p>注意:本接口刻意不返回 {@code Result} 包装 —— 响应体是文件流本身。
|
|
|
+ */
|
|
|
+ private static ResponseEntity<Resource> buildDownloadResponse(SystemService.DownloadFile file) {
|
|
|
+ Path path = file.path();
|
|
|
+ String fileName = file.fileName();
|
|
|
+ long contentLength;
|
|
|
+ try {
|
|
|
+ contentLength = Files.size(path);
|
|
|
+ } catch (IOException e) {
|
|
|
+ // 校验通过后文件被清理(清洗完成后会递归删除临时目录)
|
|
|
+ throw new ServerException(404, "文件不存在或已被清理");
|
|
|
+ }
|
|
|
+ ContentDisposition disposition = ContentDisposition.attachment()
|
|
|
+ .filename(fileName, StandardCharsets.UTF_8)
|
|
|
+ .build();
|
|
|
+ return ResponseEntity.ok()
|
|
|
+ .contentType(DOWNLOAD_MEDIA_TYPES.getOrDefault(fileSuffix(fileName), MediaType.APPLICATION_OCTET_STREAM))
|
|
|
+ .contentLength(contentLength)
|
|
|
+ // 案件原始数据,禁止浏览器/代理缓存
|
|
|
+ .cacheControl(CacheControl.noStore())
|
|
|
+ .header(HttpHeaders.CONTENT_DISPOSITION, disposition.toString())
|
|
|
+ .body(new FileSystemResource(path));
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String fileSuffix(String fileName) {
|
|
|
+ int idx = fileName == null ? -1 : fileName.lastIndexOf('.');
|
|
|
+ return idx < 0 ? "" : fileName.substring(idx + 1).toLowerCase(Locale.ROOT);
|
|
|
}
|
|
|
|
|
|
/**
|