Files
mokeegateway/_backup_20260613/FileController.java
2026-07-15 16:13:27 +08:00

177 lines
6.9 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.mokee.admin.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.mokee.admin.service.FileService;
import com.mokee.common.Result;
import com.mokee.common.annotation.OperationLog;
import com.mokee.common.entity.SysFile;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
/**
* 文件管理控制器
* <p>
* 文件二进制数据存储于本地磁盘,数据库仅保存元信息。
* 向下兼容存量 BLOB 数据file_data 非空时优先读磁盘,磁盘无文件则回退 DB BLOB
* <p>
* 请求路径前缀:{@code /zgapi/v1/admin/file}
*
* @author mokee
*/
@RestController
@RequestMapping("/zgapi/v1/admin/file")
@RequiredArgsConstructor
public class FileController {
private final FileService fileService;
/**
* 上传文件(管理后台)
* <p>
* 上传者显示名从请求头 X-User-Name 获取(由网关 HeaderInjectFilter 注入,已 URL 解码)。
*/
@OperationLog(module = "文件管理", action = "上传")
@PostMapping("/upload")
public Result<SysFile> upload(@RequestParam("file") MultipartFile file,
@RequestParam(defaultValue = "other") String fileType,
@RequestParam(required = false) String systemId,
HttpServletRequest request) throws IOException {
String userId = request.getHeader("X-User-Id");
// X-User-Name 经网关 URL 编码(防 HTTP 转发中文损坏),需解码
String userName = decodeHeader(request.getHeader("X-User-Name"));
if (userName == null || userName.isEmpty()) {
userName = request.getHeader("X-User-Account");
}
SysFile sf = fileService.upload(file, fileType, systemId, userId, userName);
sf.setFileData(null); // 不返回二进制数据
return Result.ok(sf);
}
/**
* 分页查询文件列表(不含 file_data
*/
@PostMapping("/page")
public Result<IPage<SysFile>> page(@RequestBody java.util.Map<String, Object> params) {
Integer page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
Integer size = params.get("pageSize") != null ? ((Number) params.get("pageSize")).intValue() : 10;
String fileType = (String) params.get("fileType");
String fileName = (String) params.get("fileName");
String uploadUserName = (String) params.get("uploadUserName");
String systemId = params.get("systemId") != null ? params.get("systemId").toString() : null;
return Result.ok(fileService.page(page, size, fileType, fileName, uploadUserName, systemId));
}
/**
* 文件列表查询(不分页,不含 file_data
*/
@GetMapping("/list")
public Result<List<SysFile>> list(@RequestParam(required = false) String fileType,
@RequestParam(required = false) String systemId) {
return Result.ok(fileService.list(fileType, systemId));
}
/**
* 获取文件元信息(不含二进制数据)
*/
@GetMapping("/{id}")
public Result<SysFile> getById(@PathVariable String id) {
return Result.ok(fileService.getMetaById(id));
}
/**
* 预览文件 — 直接输出文件流
* <p>
* 优先读磁盘文件;磁盘文件不存在时回退到 DB BLOB存量兼容
* 设置 CORS 和缓存头,支持被 img 标签跨域引用。
*/
@GetMapping("/{id}/view")
public void view(@PathVariable String id, HttpServletResponse response) throws IOException {
SysFile sf = fileService.getById(id);
if (sf == null) { response.setStatus(404); return; }
Path diskPath = fileService.getDiskPath(sf);
if (!Files.exists(diskPath)) {
response.setStatus(404);
response.getWriter().write("File not found");
return;
}
byte[] data = Files.readAllBytes(diskPath);
response.setHeader(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*");
String mimeType = sf.getMimeType() != null ? sf.getMimeType() : "application/octet-stream";
response.setContentType(mimeType);
response.setHeader(HttpHeaders.CACHE_CONTROL, "public, max-age=86400");
response.setContentLengthLong(data.length);
response.getOutputStream().write(data);
response.getOutputStream().flush();
}
/**
* 下载文件 — 触发浏览器下载
* <p>
* 与 view 逻辑相同,额外设置 Content-Disposition: attachment 头。
*/
@GetMapping("/{id}/download")
public void download(@PathVariable String id, HttpServletResponse response) throws IOException {
SysFile sf = fileService.getById(id);
if (sf == null) { response.setStatus(404); return; }
Path diskPath = fileService.getDiskPath(sf);
if (!Files.exists(diskPath)) {
response.setStatus(404);
response.getWriter().write("File not found");
return;
}
byte[] data = Files.readAllBytes(diskPath);
// Content-Disposition: filename=ASCII兜底, filename*=UTF-8编码的原始文件名
String safeName = "download" + ext(sf.getFileName());
String encodedName = URLEncoder.encode(sf.getFileName(), StandardCharsets.UTF_8)
.replace("+", "%20");
response.setContentType("application/octet-stream");
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + safeName + "\"; filename*=UTF-8''" + encodedName);
response.setContentLengthLong(data.length);
response.getOutputStream().write(data);
response.getOutputStream().flush();
}
/** 从文件名提取扩展名(含点) */
private static String ext(String name) {
if (name == null) return "";
int i = name.lastIndexOf('.');
return i > 0 ? name.substring(i) : "";
}
/**
* 删除文件 — 删磁盘文件 + DB 记录
*/
@OperationLog(module = "文件管理", action = "删除")
@DeleteMapping("/{id}")
public Result<?> delete(@PathVariable String id) throws IOException {
fileService.delete(id);
return Result.ok();
}
/** URL 解码 HTTP 头值(网关对中文值做了 URL 编码) */
private String decodeHeader(String value) {
if (value == null) return null;
try {
return URLDecoder.decode(value, StandardCharsets.UTF_8);
} catch (Exception e) {
return value;
}
}
}