本系统提供了**统一的文件上传服务**,支持多种存储方式(本地、S3/OSS、数据库、FTP/SFTP)。
重要:所有业务模块必须使用通用文件上传接口,**禁止为每个模块新增上传接口**。
| 方法 | 路径 | 说明 |
|---|---|---|
| POST | /infra/file/upload |
通用文件上传 |
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
| file | MultipartFile | 是 | 上传的文件 |
| directory | String | 否 | 存储目录,如 purchase-request、contract |
{
"code": 0,
"data": "https://xxx.com/xxx.pdf",
"msg": "操作成功"
}
返回值 data 即为文件访问 URL,可直接存储到数据库。
<el-upload
:action="uploadUrl"
:headers="headers"
:data="{ directory: 'purchase-request' }"
:on-success="handleUploadSuccess"
:before-upload="beforeUpload"
>
<el-button type="primary">点击上传</el-button>
</el-upload>
data() {
return {
// 通用上传接口
uploadUrl: process.env.VUE_APP_BASE_API + '/infra/file/upload',
// 认证头
headers: { Authorization: 'Bearer ' + getToken() },
}
},
methods: {
handleUploadSuccess(response) {
if (response.code === 0) {
this.form.fileUrl = response.data // 保存文件 URL
}
},
beforeUpload(file) {
// 业务层校验文件类型和大小
const isLt10M = file.size / 1024 / 1024 < 10
if (!isLt10M) {
this.$message.error('文件大小不能超过 10MB!')
}
return isLt10M
}
}
各业务模块通过 directory 参数区分存储目录,**禁止新增业务上传接口**:
| 模块 | directory | 说明 |
|---|---|---|
| 用户头像 | avatar |
用户头像图片 |
| 合同附件 | contract |
合同相关文件 |
| 采购申请 | purchase-request |
采购申请附件 |
| 采购订单 | purchase-order |
采购订单附件 |
| 产品图片 | product |
产品相关图片 |
| 通用附件 | attachment |
通用业务附件 |
命名规范:使用小写字母和连字符,如
purchase-request,与模块名保持一致。
极少数场景下,后端需要主动上传文件(如生成报表后保存),可注入 FileApi:
@Resource
private FileApi fileApi;
// 上传文件
String url = fileApi.createFile(bytes, "报表.xlsx", "report", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
// 生成临时访问地址,1小时有效
String presignedUrl = fileApi.presignGetUrl(fileUrl, 3600);
存储配置通过管理后台动态配置,无需修改代码:
| 类型 | 说明 | 适用场景 |
|---|---|---|
| DB | 存储到数据库 | 小文件、临时文件 |
| LOCAL | 本地磁盘存储 | 内网部署、开发环境 |
| FTP/SFTP | FTP 服务器 | 兼容老系统 |
| S3 | 阿里云 OSS / 腾讯云 COS / MinIO | 生产环境 |
使用 List<String> + StringListTypeHandler,参考 CRM 跟进记录模块:
实体类:
@TableName(value = "crm_follow_up_record", autoResultMap = true)
public class CrmFollowUpRecordDO extends BaseDO {
/**
* 图片
*/
@TableField(typeHandler = StringListTypeHandler.class)
private List<String> picUrls;
/**
* 附件
*/
@TableField(typeHandler = StringListTypeHandler.class)
private List<String> fileUrls;
}
数据库字段:VARCHAR,存储 JSON 数组格式如 ["url1","url2","url3"]
VO 类:
@Schema(description = "附件")
private List<String> fileUrls;
前端使用:
// 上传成功后追加
handleUploadSuccess(response) {
if (response.code === 0) {
if (!this.form.fileUrls) {
this.form.fileUrls = []
}
this.form.fileUrls.push(response.data)
}
}
// 删除文件
handleRemove(index) {
this.form.fileUrls.splice(index, 1)
}
// 实体类
private String fileUrl;
// VO 类
@Schema(description = "附件地址")
private String fileUrl;
infra_file 表的作用系统 infra_file 表记录所有上传文件的元数据(name、path、url、type、size),不与业务关联,仅用于:
- 文件管理后台查看上传记录
- 统计存储使用情况
/infra/file/upload,禁止新增业务上传接口directory 参数区分业务模块,便于管理和清理fileUrl 字段Q: 为什么不能为每个模块新增上传接口?
A: 统一接口便于:
- 统一管理存储配置
- 统一权限控制和审计
- 避免代码重复
- 后续维护和迁移
Q: 如何区分不同模块的文件?
A: 使用 directory 参数,文件会存储在对应目录下。
Q: 前端如何限制文件类型?
A: 在 beforeUpload 方法中校验 file.type 或 file.name 后缀。