Appearance
支持所有云的文件上传 
一、背景与问题 
文件上传是一个很基本的需求,但是每个公司使用的云计算可能不一样,比如有些公司用的是阿里云、华为云、腾讯云、联通、电信等等;但是无论是哪一种云,其实他们都是遵循的 亚马逊的 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-path 和 url-prefix
- upload-path为上传文件的存放路径,可以自行根据需要修改 ;- url-prefix为访问这些文件的url前缀:
- url-prefix默认可以为空,为空情况下会使用- springboot的- addResourceHandler进行静态资源映射,默认映射到- /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-preview、file-preview-modal、file-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
联系我们 
1024创新实验室-主任:卓大,混迹于各个技术圈,研究过计算机,熟悉点 java,略懂点前端。
1024创新实验室, 卓大的软件公司,致力于成为中原领先、国内一流的技术团队, 以AI+数字化为驱动,用技术为产业互联网提供无限可能, 业务如下: 
- 供应链(网络货运、大宗贸易进销存ERP、物流TMS、B2B电商、仓储WMS、AI提效等)
- 教育(就业创业大数据平台、继续教育平台、在线教育系统、题库等)
- AI+软件(软件定制外包、数据大屏、国产化改造、人员外包、技术顾问、技术培训等)
- 欢迎各类合作哦~
|  |  |  |  | 
| 加微信: 卓大 拉你入群,一起学习 | 公众号 :六边形工程师 分享:赚钱、代码、生活 | 请 “1024创新实验室” 烩面里加肉 咖啡配胡辣汤,提神又饱腹 | 抖音 : 六边形工程师 直播:赚钱、代码、中医 | 
