diff --git a/backend/xt-server/src/main/java/cn/tianbaotong/fill/config/FillProperties.java b/backend/xt-server/src/main/java/cn/tianbaotong/fill/config/FillProperties.java
index 4b691c8..b7442dc 100644
--- a/backend/xt-server/src/main/java/cn/tianbaotong/fill/config/FillProperties.java
+++ b/backend/xt-server/src/main/java/cn/tianbaotong/fill/config/FillProperties.java
@@ -92,11 +92,19 @@ public record FillProperties(
/**
* @param maxFileSizeMb 单文件上传上限(MB),默认 50(PRD §10.1)
+ * @param dir 文件存放目录。默认 {@code ./data/uploads}(相对后端工作目录)。
+ * 本地磁盘不是最终方案 —— 多实例部署时各存各的会丢文件;
+ * 接入对象存储(MinIO/OSS)后这里换成 bucket 配置。
+ * 但现在先用它能跑通整条链路,不阻塞。
*/
- public record Upload(Integer maxFileSizeMb) {
+ public record Upload(Integer maxFileSizeMb, String dir) {
public int maxFileSizeMbOrDefault() {
return maxFileSizeMb == null ? 50 : maxFileSizeMb;
}
+
+ public String dirOrDefault() {
+ return dir == null || dir.isBlank() ? "./data/uploads" : dir;
+ }
}
/**
diff --git a/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/file/controller/MaterialFileController.java b/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/file/controller/MaterialFileController.java
new file mode 100644
index 0000000..e65ad0d
--- /dev/null
+++ b/backend/xt-server/src/main/java/cn/tianbaotong/fill/module/file/controller/MaterialFileController.java
@@ -0,0 +1,80 @@
+package cn.tianbaotong.fill.module.file.controller;
+
+import cn.tianbaotong.fill.common.api.ApiResult;
+import cn.tianbaotong.fill.module.file.entity.CollectFile;
+import cn.tianbaotong.fill.module.file.service.MaterialFileService;
+import cn.tianbaotong.fill.support.ClientIp;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * 材料上传与规范化(《03-接口设计》§9.5~§9.7)。
+ *
+ *
⚠️ 与契约的一处偏差(已知、有意):契约写的是
+ * {@code POST /mp/files/upload-token}(OSS 直传凭证),而我们当前
+ * 由后端代理接收(multipart)。
+ *
+ *
原因:后端还没接对象存储。直传凭证要先有 bucket 与签名机制,
+ * 而现在连存文件的地方都是本地磁盘。硬做一个"返回假凭证"的接口只会让人以为能直传。
+ *
+ *
两种方式的取舍很清楚:直传省后端带宽(几十 MB 的翻拍件不经过服务器),
+ * 但代理接收能在服务端做**格式白名单、份数校验、体积预检** ——
+ * 而这些是我们现在就要的。接入对象存储后可以两者并存:
+ * 小文件走代理、大文件走直传。
+ */
+@Tag(name = "小程序/H5 · 材料上传")
+@RestController
+@RequestMapping("/api/v1/mp/files")
+@RequiredArgsConstructor
+public class MaterialFileController {
+
+ private final MaterialFileService fileService;
+
+ @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
+ @Operation(summary = "上传材料(后端代理)",
+ description = "先原样存下来再处理 —— 上传中断是最常见的失败,先落盘学生就不必重传。"
+ + "格式白名单与份数上限在这里校验")
+ public ApiResult