初始化

This commit is contained in:
zg
2026-07-15 16:13:27 +08:00
parent 6d6f559793
commit c4023c1623
276 changed files with 42622 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
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;
}
}
}