Skip to content

文件存储 ​

这篇解决什么 ​

头像、附件、导出文件要落到对象存储或磁盘,业务表只存 URL。读完能改 FileClient、文件配置、useUpload,以及业务模块里的 FileApi.createFile。

默认端口 48080,管理端前缀 /admin-api。菜单在基础设施 → 文件管理。

上传成功后返回完整访问 URL。业务字段保存这条 URL,不要再拷一份字节进业务表。

存储器 ​

FileStorageEnum 五种实现,共用 FileClient。上传、删除、读内容都走主配置对应的客户端。

名称说明仓库路径
S3(20)兼容 S3 协议:MinIO、阿里云 OSS、腾讯云 COS、七牛、华为 OBS、火山 TOS.../client/s3/S3FileClient.java
数据库(1)字节进 infra_file_content,适合少量小文件.../client/db/DBFileClient.java
本地磁盘(10)写到 basePath,高可用和故障转移都弱.../client/local/LocalFileClient.java
FTP(11) / SFTP(12)远程目录,同样难做故障转移.../client/ftp/、.../client/sftp/

优先用 S3。没有云账号时自建 MinIO。数据库能靠主从和备份撑一小撮文件。本地和 FTP 只适合单机试跑。

不要把本地磁盘当生产主存储

多实例时文件落在不同机器上,下载会 404。也没有现成的故障转移。生产用 S3 或 MinIO,并把该配置设成主配置。

代码位置 ​

名称说明仓库路径
后端模块配置、上传、下载、客户端工厂ruoyi-office/yudao-module-infra/
管理端接口/admin-api/infra/file、/infra/file-configFileController、FileConfigController
RPC业务模块调 createFile / presignGetUrlyudao-module-infra-api/.../FileApi.java
文件表每次上传一条元数据;@TenantIgnoreinfra_file → FileDO
配置表存储器 JSON;master 表示默认上传通道infra_file_config → FileConfigDO
内容表仅数据库存储器使用infra_file_content → FileContentDO
前端页面文件列表、文件配置web-antd/src/views/infra/file/、fileConfig/
上传组件VITE_UPLOAD_TYPE 决定走后端还是直传 S3web-antd/src/components/upload/use-upload.ts

菜单路径:文件列表 /infra/file/file,文件配置 /infra/file/file-config。权限字:infra:file:query、infra:file:delete、infra:file-config:*。

单体 application.yaml 里 spring.servlet.multipart 默认单文件、整请求都是 100MB。前端 uploadFile 超时 300000 毫秒。

文件配置 ​

打开基础设施 → 文件管理 → 文件配置。新增一条,测通后再点「主配置」。后续 FileService.createFile 只打主配置。

文件配置列表

本系统截图:文件配置列表,主配置一行会标「是」。

名称说明仓库路径
新增 / 修改按存储器校验 config JSONPOST/PUT /infra/file-config
测试上传内置 file/erweima.jpg,返回可访问 URLGET /infra/file-config/test
主配置先把其它行的 master 清掉,再把当前行设为 truePUT /infra/file-config/update-master
客户端缓存LoadingCache,10 秒异步刷新;主配置 key 是 0LFileConfigServiceImpl

主配置不能删,会抛 FILE_CONFIG_DELETE_FAIL_MASTER。演示站会锁住新增、改主配置和测试,避免密钥进表单 DOM。

S3 行要填节点、bucket、密钥、是否 Path Style、是否公开、自定义域名。七牛必须填域名。MinIO 走 Nginx 独立域名时,把 Path Style 打开。region 一般只给 AWS 填,其它云可留空由 endpoint 识别。

文件上传 ​

两种入口:浏览器走 FileController,Java 业务走 FileApi。最终都进主配置的 FileClient.upload,并往 infra_file 插一条。

路径默认带日期目录:{directory}/yyyyMMdd/{原文件名}。FileServiceImpl.PATH_PREFIX_DATE_ENABLE 为 true。文件名不能带 /,目录不能 ..,校验在 FilePathUtils。

前端走后端 ​

FileController 的 /upload 读 MultipartFile,再交给 createFile:

java
@PostMapping("/upload")
@Operation(summary = "上传文件", description = "模式一:后端上传文件")
public CommonResult<String> uploadFile(@Valid FileUploadReqVO uploadReqVO) throws Exception {
    MultipartFile file = uploadReqVO.getFile();
    byte[] content = IoUtil.readBytes(file.getInputStream());
    return success(fileService.createFile(content, file.getOriginalFilename(),
            uploadReqVO.getDirectory(), file.getContentType()));
}

useUpload 默认 VITE_UPLOAD_TYPE=server,调用 uploadFile。文件列表页、FileUpload、ImageUpload、个人中心头像都走这一套。

文件列表

本系统截图:文件列表,可上传、复制链接、预览或删除。

业务模块调用 FileApi ​

其它模块不要直接碰 FileService。引入 yudao-module-infra-api,注入 FileApi:

java
default String createFile(@NotEmpty(message = "文件内容不能为空") byte[] content,
                          String name, String directory, String type) {
    return createFile(new FileCreateReqDTO()
            .setName(name).setDirectory(directory).setType(type).setContent(content))
            .getCheckedData();
}

资产二维码、OA / 合同 OnlyOffice 回写、培训证书都是这个方法。directory 建议按业务写,例如 asset/qr、oa/cloud、contract。

文件下载 ​

返回的 URL 规则随存储器变。

名称说明仓库路径
S3 公开桶直接拼自定义域名 + 对象路径,浏览器自己拉S3FileClient.presignGetUrl
本地 / DB / FTP没有公网对象地址,走后端读字节AbstractFileClient.formatFileUrl
同源预览前端用已存 URL 预览时走后端转发,躲开 OSS 跨域GET /infra/file/preview-by-url

本地、数据库拼出来的地址是:

text
{domain}/admin-api/infra/file/{configId}/get/{path}

对应接口匿名可访问,方便 <img> 和下载链接:

java
@GetMapping("/{configId}/get/**")
@PermitAll
@TenantIgnore
public void getFileContent(HttpServletRequest request,
                           HttpServletResponse response,
                           @PathVariable("configId") Long configId) throws Exception {
    String path = StrUtil.subAfter(request.getRequestURI(), "/get/", false);
    path = HttpUtils.decodeUrlPath(path);
    byte[] content = fileService.getFileContent(configId, path);
    // ...
}

安全放行写在 yudao-module-infra 的 SecurityConfiguration:/infra/file/*/get/**。列表页预览图片用 getFilePreviewUrlByUrl,不要直接把外链塞进 <img>。

文件客户端 ​

framework/file 包把存储器差抽象掉。工厂按 storage 反射出实现,配置变更会 refresh。

名称说明仓库路径
FileClientupload / delete / getContent;S3 另有预签名.../client/FileClient.java
AbstractFileClient初始化、刷新、拼 /get/ 下载地址同包
FileClientFactoryImplConcurrentHashMap<configId, client>同包
FileConfigService读库、校验、缓存、测试上传service/file/FileConfigServiceImpl.java
java
public interface FileClient {

    Long getId();

    String upload(byte[] content, String path, String type) throws Exception;

    void delete(String path) throws Exception;

    byte[] getContent(String path) throws Exception;

    default String presignPutUrl(String path) {
        throw new UnsupportedOperationException("不支持的操作");
    }

    default String presignGetUrl(String url, Integer expirationSeconds) {
        throw new UnsupportedOperationException("不支持的操作");
    }
}

只有 S3FileClient 实现预签名。其它存储器调用会抛「不支持的操作」。

按本仓库生成的 ER:infra_file 只存元数据;字节只在数据库存储器进 infra_file_content。

S3 对象存储 ​

云厂商只要兼容 S3,就填同一套字段。实现用 AWS SDK v2 的 S3Client / S3Presigner。

名称说明仓库路径
endpoint节点。MinIO 写 http://127.0.0.1:9000S3FileClientConfig
bucket / accessKey / accessSecret桶和密钥同上
domain自定义域名;七牛必填同上
enablePathStyleAccessMinIO + Nginx 独立域名时打开同上
enablePublicAccesstrue 公开读;false 走 GET 预签名同上
regionAWS 必填;其它云可空同上

公开桶才能用裸 URL 访问。自定义域名方便以后换云,业务表里的 URL 不用批量改 host。

官方跨域说明:阿里云 OSS、腾讯云 COS、七牛 Kodo。

前端直传 S3 ​

默认仍是「浏览器 → 后端 → S3」。大文件会占满应用机带宽。web-antd 已支持前端直传。

把环境变量改成 client:

bash
# apps/web-antd/.env.development
VITE_UPLOAD_TYPE=client

改完要重启 Vite。useUpload 会先要预签名,再 PUT 到对象存储,最后异步记一条 infra_file:

ts
if (isClientUpload) {
  const fileName = await generateFileName(file);
  const presignedInfo = await getFilePresignedUrl(fileName, directory);
  return baseRequestClient
    .put(presignedInfo.uploadUrl, file, {
      headers: { 'Content-Type': file.type },
    })
    .then(() => {
      createFile0(presignedInfo, file);
      return { url: removeUrlQuery(presignedInfo.url) };
    });
}

直传只认 S3 主配置

VITE_UPLOAD_TYPE=client 时,主配置必须是存储器 20。本地、FTP、数据库没有 presignPutUrl。桶上要放行浏览器 Origin 的 CORS(允许 PUT、暴露 ETag)。

名称说明仓库路径
预签名上传返回 uploadUrl、访问 url、path、configIdGET /infra/file/presigned-url
记元数据直传成功后再插 infra_filePOST /infra/file/create
前端直传或走后端二选一use-upload.ts、api/infra/file/index.ts

预签名默认 24 小时(S3FileClient.EXPIRATION_DEFAULT)。写入 infra_file.url 前会 HttpUtils.removeUrlQuery,去掉签名参数。

私有桶 ​

enablePublicAccess=false 时,裸 URL 打不开。读文件用 GET 预签名。

名称说明仓库路径
刷新临时地址管理端按已存 URL 换签名GET /infra/file/presigned-get-url
业务 RPC返回给前端的也必须是签名后的FileApi.presignGetUrl
落库去掉 Query,避免过期串写进字段、撑爆长度HttpUtils.removeUrlQuery
java
public String presignGetUrl(String url, Integer expirationSeconds) {
    FileClient fileClient = fileConfigService.getMasterFileClient();
    return fileClient.presignGetUrl(url, expirationSeconds);
}

合同预览、发票 OCR 已经按这个刷新临时地址。新增或修改业务单据时,提交的如果是带签名的 URL,后端先剥 Query 再入库。

私有桶 URL 不要原样入库

签名 Query 有过期时间,过期即 403;整串也往往超过 url 列长。库里只留去参后的地址,展示时再 presignGetUrl。

配置与操作 ​

管理员在 基础设施 → 文件配置 选存储器并设默认,上传走文件列表。库里只存去参后的地址。FileClient 在 infra,不在 framework。

开启见 框架层。截图见上文配置和列表。

相关篇 ​

联系我们

获取报价、演示和二开方案

微信咨询二维码

微信咨询

17156169080

添加时备注「RuoYi Office」

在线体验商业版