65ebf8de90
文件以前直接写本地磁盘:两个实例各存各的,学生这次传到 A、下次请求落到 B,
文件就"不见了"。这不是性能问题,是正确性问题 —— 它一直卡着不上多副本。
新增:
· module/file/storage/{StorageService,LocalStorageService,QiniuStorageService}
· config/StorageConfig(装配 + 可选启动自检)
· FillProperties 新增 fill.storage
· MaterialFileController 新增 GET /{id}/url(签名链接)与 GET /{id}/content
· scripts/e2e-m1-storage.py(存储自检,没配七牛时跳过而非失败)
关键设计:
① 业务只认 path(std/1/17/张同学-…-身份证.jpg),各实现自己映射成真实位置:
本地 ./data/uploads/std/...、七牛 xiaotutongxue/std/...
**换存储不用改数据库、不用改业务代码**
② 空间私有 → 下载必须签名。GET /{id}/url 每次现签(默认 30 分钟),
所以这个 URL 绝不能长期保存:写进库、塞进导出的 Excel、发给学生明天再打开,都是 403
③ 对象键里有中文,拼 URL **必须逐段编码** —— 整串 encode 会把 / 编成 %2F,
那在七牛那边是另一个键,表现是"传上去了却打不开",且报错什么都没有
④ 区域默认 auto:华东-浙江在新旧控制台对应 z0 与 cn-east-2,
写错了的症状是**上传超时**——一个从报错完全看不出原因的失败
⑤ AK/SK 不进仓库也不进 jar:dev.yml 只放占位符;
真密钥在 backend/application-local.yml(已 gitignore),
用 spring.config.import: optional:**file**:./application-local.yml
—— 刻意用 file: 而不是 classpath:,后者会被打进 jar,密钥就跟着构建产物走了
⑥ 配了 qiniu 却没密钥 → 启动就炸(fail-fast):起得来的话第一个受害的是
正在填材料的学生,而排查要从"文件保存失败"一路追到环境变量
已验证:存储抽象改造没破坏任何现有链路 —— 本地磁盘路径下
e2e-m3-file 29/29、m3-submit 31/31、m1-materials 68/68、m3-draft 27/27 全绿;
启动日志打出「本地磁盘(多副本部署下会丢文件)」;git check-ignore 确认
application-local.yml 被忽略而 .example 不被忽略。
⏳ 未验证:**七牛的真实链路没跑过** —— 用户给了域名/空间/区域/私有/目录前缀,
但没给 AccessKey/SecretKey,没有它生成不了上传凭证。
补上后跑 python scripts/e2e-m1-storage.py 即可(它会验:配置齐全 →
上传处理落进七牛 → 取签名链接 → 真的下载回来比字节 → 不带签名被拒
→ 对象键带前缀 → 原始件一字不差)。没配时脚本跳过并 exit 0,不误报失败。
148 lines
5.2 KiB
XML
148 lines
5.2 KiB
XML
<?xml version="1.0" encoding="UTF-8"?>
|
||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||
<modelVersion>4.0.0</modelVersion>
|
||
|
||
<parent>
|
||
<groupId>org.springframework.boot</groupId>
|
||
<artifactId>spring-boot-starter-parent</artifactId>
|
||
<version>4.1.1</version>
|
||
<relativePath/>
|
||
</parent>
|
||
|
||
<groupId>cn.tianbaotong</groupId>
|
||
<artifactId>fill-parent</artifactId>
|
||
<version>1.0.0-SNAPSHOT</version>
|
||
<packaging>pom</packaging>
|
||
<name>数据信息填报系统</name>
|
||
<description>职业技能等级认定 · 报名材料收集 SaaS</description>
|
||
|
||
<modules>
|
||
<module>xt-common</module>
|
||
<module>xt-server</module>
|
||
</modules>
|
||
|
||
<properties>
|
||
<java.version>21</java.version>
|
||
<maven.compiler.release>21</maven.compiler.release>
|
||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||
|
||
<!-- ⚠️ 版本已在 Maven Central 核对,勿凭记忆改 -->
|
||
<mybatis-plus.version>3.5.17</mybatis-plus.version>
|
||
<jjwt.version>0.13.0</jjwt.version>
|
||
<hutool.version>5.8.47</hutool.version>
|
||
<easyexcel.version>4.0.3</easyexcel.version>
|
||
<!-- springdoc 3.x 对应 Boot 4;2.x 是给 Boot 3 的,别混 -->
|
||
<springdoc.version>3.1.1</springdoc.version>
|
||
<!-- 七牛云对象存储 SDK(材料原图/规范件)。本地仓库已缓存 7.16.0 -->
|
||
<qiniu.version>7.16.0</qiniu.version>
|
||
</properties>
|
||
|
||
<dependencyManagement>
|
||
<dependencies>
|
||
<!-- 本项目模块 -->
|
||
<dependency>
|
||
<groupId>cn.tianbaotong</groupId>
|
||
<artifactId>xt-common</artifactId>
|
||
<version>${project.version}</version>
|
||
</dependency>
|
||
|
||
<!-- MyBatis-Plus:Boot 4 专用 starter -->
|
||
<dependency>
|
||
<groupId>com.baomidou</groupId>
|
||
<artifactId>mybatis-plus-spring-boot4-starter</artifactId>
|
||
<version>${mybatis-plus.version}</version>
|
||
</dependency>
|
||
<!--
|
||
⚠️ MP 3.5.9+ 把分页/多租户/防全表更新这些插件抽到了独立模块,
|
||
它们依赖 jsqlparser。不加这个,starter 能启动但用不了插件,
|
||
编译期报「找不到 TenantLineInnerInterceptor」。
|
||
两个变体:mybatis-plus-jsqlparser(jsqlparser 5.x,需 Java 11+)
|
||
mybatis-plus-jsqlparser-4.9(jsqlparser 4.9,兼容老项目)
|
||
本项目 Java 21,用前者。
|
||
-->
|
||
<dependency>
|
||
<groupId>com.baomidou</groupId>
|
||
<artifactId>mybatis-plus-jsqlparser</artifactId>
|
||
<version>${mybatis-plus.version}</version>
|
||
</dependency>
|
||
|
||
<!--
|
||
七牛云对象存储。为什么用官方 SDK 而不是 S3 协议:
|
||
空间是**私有**的,下载必须走七牛的签名规则(`e` + `token`),
|
||
上传凭证也是七牛特有的;用 SDK 能直接拿到 privateDownloadUrl/uploadToken,
|
||
自己拼签名等于把一套加密规则抄一遍,抄错了表现为"文件传上去了但打不开"。
|
||
-->
|
||
<dependency>
|
||
<groupId>com.qiniu</groupId>
|
||
<artifactId>qiniu-java-sdk</artifactId>
|
||
<version>${qiniu.version}</version>
|
||
</dependency>
|
||
|
||
<!-- JWT -->
|
||
<dependency>
|
||
<groupId>io.jsonwebtoken</groupId>
|
||
<artifactId>jjwt-api</artifactId>
|
||
<version>${jjwt.version}</version>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>io.jsonwebtoken</groupId>
|
||
<artifactId>jjwt-impl</artifactId>
|
||
<version>${jjwt.version}</version>
|
||
<scope>runtime</scope>
|
||
</dependency>
|
||
<dependency>
|
||
<groupId>io.jsonwebtoken</groupId>
|
||
<artifactId>jjwt-jackson</artifactId>
|
||
<version>${jjwt.version}</version>
|
||
<scope>runtime</scope>
|
||
</dependency>
|
||
|
||
<!-- 图形验证码、加解密工具 -->
|
||
<dependency>
|
||
<groupId>cn.hutool</groupId>
|
||
<artifactId>hutool-all</artifactId>
|
||
<version>${hutool.version}</version>
|
||
</dependency>
|
||
|
||
<!-- Excel 导入导出 -->
|
||
<dependency>
|
||
<groupId>com.alibaba</groupId>
|
||
<artifactId>easyexcel</artifactId>
|
||
<version>${easyexcel.version}</version>
|
||
</dependency>
|
||
|
||
<!-- OpenAPI 文档 -->
|
||
<dependency>
|
||
<groupId>org.springdoc</groupId>
|
||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||
<version>${springdoc.version}</version>
|
||
</dependency>
|
||
</dependencies>
|
||
</dependencyManagement>
|
||
|
||
<build>
|
||
<pluginManagement>
|
||
<plugins>
|
||
<plugin>
|
||
<groupId>org.apache.maven.plugins</groupId>
|
||
<artifactId>maven-compiler-plugin</artifactId>
|
||
<configuration>
|
||
<release>${maven.compiler.release}</release>
|
||
<parameters>true</parameters>
|
||
<annotationProcessorPaths>
|
||
<path>
|
||
<groupId>org.projectlombok</groupId>
|
||
<artifactId>lombok</artifactId>
|
||
<version>${lombok.version}</version>
|
||
</path>
|
||
</annotationProcessorPaths>
|
||
</configuration>
|
||
</plugin>
|
||
</plugins>
|
||
</pluginManagement>
|
||
</build>
|
||
</project>
|