Skip to content

一、背景与问题

文件上传是一个很基本的需求,但是每个公司使用的云计算可能不一样,比如有些公司用的是阿里云、华为云、腾讯云、联通、电信等等;但是无论是哪一种云,其实他们都是遵循的 亚马逊的 S3 文件存储协议。

  • 支持阿里云、华为云、腾讯云等各类云
  • 同时支持 minio,因为minio也是支持 S3协议的
  • 如果是私有化部署,还得支持 本地存储

二、架构与思想

  • 使用S3协议作为基础进行设计,目的为了满足所有云。
  • 记录所有的上传文件,存到数据库
  • 保留文件的基础信息: 大小,文件名,时间等等
  • 区分 公共文件和私有文件

三、具体使用

3.1、修改配置

SmartAdmin支持的文件上传模式有local、cloud两种,文件上传接口参考:FileController

  • local:为本地文件上传,文件存储在服务器本地
  • cloud: 为云文件存储,目前支持主流的云存储厂商阿里云、华为云、七牛云、minio等支持S3协议

1)修改为本地 local存储

第一步将 file.storage.mode 改为 local

yaml
# 文件上传 配置
file:
  storage:
    mode: local

第二步,配置具体的 local 参数: upload-pathurl-prefix

  • upload-path 为上传文件的存放路径,可以自行根据需要修改 ; url-prefix为访问这些文件的url前缀:
  • url-prefix默认可以为空,为空情况下会使用springbootaddResourceHandler进行静态资源映射,默认映射到/upload的url路径,比如http://192.168.3.188:1024/upload/public/common/a1.png ,具体配置请看类FileConfig, 配置如下
yaml
# 默认为空
file:
  storage:
    mode: local
    local:
      upload-path: ${localPath:/home}/smart_admin_v2/upload/  #上传路径
      url-prefix:                                             #url前缀,可以为空,系统会默认为 http://[ip][port]/upload/[fileKey]
  • url-prefix 可以为nginx映射路径等,比如在nginx配置了映射,映射到upload-path配置的路径
yaml
file:
  storage:
    mode: local
    local:
      upload-path: ${localPath:/home}/smart_admin_v2/upload/  #上传路径
      url-prefix: https://smartadmin.vip/upload               #使用nginx映射的路径

2)修改为本地 云或者minio存储

yaml
# 文件上传 配置
file:
  storage:
    mode: cloud                                    # cloud 为云存储;local 为本地存储,则下面的cloud配置将失效
    cloud:
      region: oss-cn-qingdao                       # 自行修改
      endpoint: oss-cn-qingdao.aliyuncs.com        # 自行修改
      bucket-name: bucket-1024lab                  # 自行修改  
      access-key:                                  # 自行修改
      secret-key:                                  # 自行修改   
      url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
      private-url-expire-seconds: 3600              # url 失效时间

3.2、定义文件存放位置

如果所有文件都存放到一起,那么后续想做个分类都好方便,所以这里需要定义一下 文件目录 FileFolderTypeEnum.java,建议按照功能业务大块拆分:

java
public enum FileFolderTypeEnum implements BaseEnum {
    COMMON(1, FileFolderTypeEnum.FOLDER_PUBLIC + "/common/", "通用"),
    NOTICE(2, FileFolderTypeEnum.FOLDER_PUBLIC + "/notice/", "公告"),
    HELP_DOC(3, FileFolderTypeEnum.FOLDER_PUBLIC + "help-doc", "帮助中心"),
    FEEDBACK(4, FileFolderTypeEnum.FOLDER_PUBLIC + "/feedback/", "意见反馈"),
    ;

3.3、上传

对应前端组件
在前端提供了file-previewfile-preview-modalfile-upload 三个文件相关的组件可供使用;

序列化与反序列化
@JsonDeserialize(using = FileKeyVoDeserializer.class) 此反序列化,用于将前端传输的fileVO的JSON数组转化为可供数据库直接存储的fileKey字符串。
@JsonSerialize(using = FileKeyVoDeserializer.class) 此序列化,用于将fileKey字符串转化为可供前端直接使用的fileVO的JSON数组。
在业务上逗号分割
在数据存储上我们一般在对应的商品表中创建cover_pic字段,此字段用于存储文件key信息,多张图片的话,我们一般采用逗号分割的方式存储此字段。 字段定义方式:

java
    @ApiModelProperty("商品封面")
    @Length(max = 250, message = "商品封面最多250字符")
    @JsonSerialize(using = FileKeyVoSerializer.class)
    @JsonDeserialize(using = FileKeyVoDeserializer.class)
    private String coverPic;

SmartAdmin的前端文件上传组件,返回的JSON数据是以fileVOJSON数组的方式返回的,为了减少前后端数据二次处理的繁琐工作,特意增加了JSON的序列化和反序列化处理。
除了上面的两个序列化类外,我们还提供了FileKeySerializer.class,此类可将fileKey字符串转化为文件请求全路径地址。

比如 反序列化:

java
@Data
public class NoticeUpdateFormVO extends NoticeVO {

    @ApiModelProperty("附件")
    @JsonSerialize(using = FileKeyVoSerializer.class)
    private String attachment;

    @ApiModelProperty("可见范围")
    private List<NoticeVisibleRangeVO> visibleRangeList;
}

比如 序列化:

java
@Data
public class NoticeDetailVO {

    @ApiModelProperty("id")
    private Long noticeId;

    @ApiModelProperty("标题")
    private String title;

    @ApiModelProperty("附件")
    @JsonSerialize(using = FileKeyVoSerializer.class)
    private String attachment;

四、实现原理

4.1、表结构

表结构用于记录:文件基本信息:大小、文件名、时间、上传人信息,表设计如下

4.2、亚马逊S3协议

亚马逊S3协议有java库:

xml
<dependency>
  <groupId>com.amazonaws</groupId>
  <artifactId>aws-java-sdk-s3</artifactId>
  <version>1.11.842</version>
</dependency>

4.3、文件实现类

为了满足 本地、云存储 多种方式,系统中定义了 接口IFileStorageService 文件接口;
并提供两种实现类:

java
FileStorageCloudServiceImpl.java   使用亚马逊S3协议的实现类
FileStorageLocalServiceImpl.java   本地存储实现类

具体如何判断使用本地存储实现类还是使用 云S3存储实现类,请看 sa-base项目中的 FileCloudConfig,使用了条件注解@ConditionalOnProperty,代码如下:

java
   @Bean
    @ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = "cloud")
    public IFileStorageService initCloudFileService() {
        return new FileStorageCloudServiceImpl();
    }

    @Bean
    @ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = "local")
    public IFileStorageService initLocalFileService() {
        return new FileStorageLocalServiceImpl();
    }

4.4、 缓存

我们知道,对于某些私有化的文件,当我们访问的时候需要后端请求云计算生成一个可以访问的 url地址,并且这个url地址有个过期时间
但是文件服务又是一个很基础的服务,获取访问地址,后端需要发请求,是阻塞的,如果我们频繁的调用,会很慢,所以我们做了一个redis缓存;

java
    private String getCacheUrl(String fileKey) {
        String redisKey = redisService.generateRedisKey(RedisKeyConst.Support.FILE_URL, fileKey);
        String fileUrl = redisService.get(redisKey);
        if (null != fileUrl) {
            return fileUrl;
        }
        ResponseDTO<String> responseDTO = fileStorageService.getFileUrl(fileKey);
        if (!responseDTO.getOk()) {
            return null;
        }
        fileUrl = responseDTO.getData();
        redisService.set(redisKey, fileUrl, fileStorageService.cacheExpireSecond());
        return fileUrl;
    }

4.5、 文件key生成规则

32位 uuid + 文件格式后缀

比如:c0e6e9340a8c4c4aa8c8062bdc5f8bcc.png


outline: 'deep'

联系我们

1024创新实验室-主任:卓大,混迹于各个技术圈,研究过计算机,熟悉点 java,略懂点前端。
1024创新实验室(河南·洛阳) 致力于成为中原领先、国内一流的技术团队,以技术创新为驱动,合作各类项目(软件外包、技术顾问、培训等等)。

加微信: 卓大
拉你入群,一起学习
公众号 :六边形工程师
分享:赚钱、代码、生活
请 “1024创新实验室”
“烩面里加肉”
“ 咖啡配胡辣汤,提神又饱腹”
抖音 : 六边形工程师
直播:赚钱、代码、中医