Skip to content

文件存储

名词解释

  • 热更新:修改nacos中配置的参数后,无需重启项目实时生效。
  • 文件服务目前支持本地存储、阿里云OSS、FastDFS存储、华为云OBS、MinIO、七牛云OSS,可以通过配置切换。

配置

yaml
# acuity.file 相关的配置会注入到 FileServerProperties 对象,使用时注入即可。
acuity:
  file:
    storageType: MIN_IO                               # 默认存储类型 支持 LOCAL FAST_DFS MIN_IO ALI_OSS HUAWEI_OSS QINIU_OSS
    delFile: false                                    # 调用删除接口是否删除文件,  设置为false只删除数据库记录
    publicBucket:                                     # 文件访问地址无需权限的桶名
       - public
    local:                                            # 文件存储到服务器本机
      storage-path: /data/projects/uploadfile/file/   # 文件存储到服务器的绝对路径(需要启动项目前创建好,并且需要有读和写的权限!)
      urlPrefix: http://127.0.0.1/file/               # 外网文件访问前缀 (部署nginx后,配置为nginx的访问地址,并需要在nginx配置文件中静态代理storage-path)
      bucket: ''                                      # 桶 用于隔离文件存储位置
      inner-uri-prefix: null  		                    # 内网通道前缀 主要用于解决文件下载时,文件服务无法通过urlPrefix访问文件时,通过此参数转换
    fastdfs:      
      urlPrefix: http://ip:port/                      # fastdfs 配置的nginx地址,用于访问文件
    ali:    # 文件存储到阿里云OSS
      uriPrefix: "http://acuity-admin-cloud.oss-cn-beijing.aliyuncs.com/"
      bucket: "acuity-admin-cloud"
      endpoint: "oss-cn-beijing.aliyuncs.com"
      access-key-id: "填写你的id"
      access-key-secret: "填写你的秘钥"
      expiry: 3600          													# 默认 URL有效期 2小时, 单位秒
    minIo:  # 文件存储到MINIO
      endpoint: "https://127.0.0.1:9000/"
      accessKey: "acuity"
      secretKey: "acuity"
      bucket: "dev"
      expiry: 7200																		#	 默认 URL有效期 2小时, 单位秒
    huawei:  # 文件存储到华为云OBS
      uriPrefix: "dev.obs.cn-southwest-2.myhuaweicloud.com"
      endpoint: "obs.cn-southwest-2.myhuaweicloud.com"
      accessKey: "1"
      secretKey: "2"
      location: "cn-southwest-2"
      bucket: "dev" 
      expiry: 3600          													# 默认 URL有效期 2小时, 单位秒
    qiNiu:    # 文件存储到七牛云OSS
      domain: 'qiniu.acuity.top'
      useHttps: false
      zone: "z0"
      accessKey: "1"
      secretKey: "2"
      bucket: "acuity_admin_cloud"
      expiry: 7200																		#	 默认 URL有效期 2小时, 单位秒
      
# acuity.file.storageType=FAST_DFS 时,需要配置
fdfs:
  soTimeout: 1500
  connectTimeout: 600
  thumb-image:
    width: 150
    height: 150
  tracker-list:
    - 192.168.1.2:22122
  pool:
    #从池中借出的对象的最大数目
    max-total: 153
    max-wait-millis: 102
    jmx-name-base: 1
    jmx-name-prefix: 1
# acuity.file 相关的配置会注入到 FileServerProperties 对象,使用时注入即可。
acuity:
  file:
    storageType: MIN_IO                               # 默认存储类型 支持 LOCAL FAST_DFS MIN_IO ALI_OSS HUAWEI_OSS QINIU_OSS
    delFile: false                                    # 调用删除接口是否删除文件,  设置为false只删除数据库记录
    publicBucket:                                     # 文件访问地址无需权限的桶名
       - public
    local:                                            # 文件存储到服务器本机
      storage-path: /data/projects/uploadfile/file/   # 文件存储到服务器的绝对路径(需要启动项目前创建好,并且需要有读和写的权限!)
      urlPrefix: http://127.0.0.1/file/               # 外网文件访问前缀 (部署nginx后,配置为nginx的访问地址,并需要在nginx配置文件中静态代理storage-path)
      bucket: ''                                      # 桶 用于隔离文件存储位置
      inner-uri-prefix: null  		                    # 内网通道前缀 主要用于解决文件下载时,文件服务无法通过urlPrefix访问文件时,通过此参数转换
    fastdfs:      
      urlPrefix: http://ip:port/                      # fastdfs 配置的nginx地址,用于访问文件
    ali:    # 文件存储到阿里云OSS
      uriPrefix: "http://acuity-admin-cloud.oss-cn-beijing.aliyuncs.com/"
      bucket: "acuity-admin-cloud"
      endpoint: "oss-cn-beijing.aliyuncs.com"
      access-key-id: "填写你的id"
      access-key-secret: "填写你的秘钥"
      expiry: 3600          													# 默认 URL有效期 2小时, 单位秒
    minIo:  # 文件存储到MINIO
      endpoint: "https://127.0.0.1:9000/"
      accessKey: "acuity"
      secretKey: "acuity"
      bucket: "dev"
      expiry: 7200																		#	 默认 URL有效期 2小时, 单位秒
    huawei:  # 文件存储到华为云OBS
      uriPrefix: "dev.obs.cn-southwest-2.myhuaweicloud.com"
      endpoint: "obs.cn-southwest-2.myhuaweicloud.com"
      accessKey: "1"
      secretKey: "2"
      location: "cn-southwest-2"
      bucket: "dev" 
      expiry: 3600          													# 默认 URL有效期 2小时, 单位秒
    qiNiu:    # 文件存储到七牛云OSS
      domain: 'qiniu.acuity.top'
      useHttps: false
      zone: "z0"
      accessKey: "1"
      secretKey: "2"
      bucket: "acuity_admin_cloud"
      expiry: 7200																		#	 默认 URL有效期 2小时, 单位秒
      
# acuity.file.storageType=FAST_DFS 时,需要配置
fdfs:
  soTimeout: 1500
  connectTimeout: 600
  thumb-image:
    width: 150
    height: 150
  tracker-list:
    - 192.168.1.2:22122
  pool:
    #从池中借出的对象的最大数目
    max-total: 153
    max-wait-millis: 102
    jmx-name-base: 1
    jmx-name-prefix: 1

修改配置服务器存储类型

存储类型支持LOCAL、FAST_DFS、MIN_IO、ALI_OSS、HUAWEI_OSS、QINIU_OSS,详见:FileStorageType.java。 存储类型支持热更新

yaml
acuity:
 file:
   storageType: MIN_IO
acuity:
 file:
   storageType: MIN_IO

示例

程序启动时,会将所有存储类型的文件存储都初始化到内存。在执行上传接口时,根据上传接口或yaml中配置的存储类型参数,选择使用那个实现类。

java
public FileContext(Map<String, FileStrategy> map,
                       Map<String, FileChunkStrategy> chunkMap,
                       FileServerProperties fileServerProperties,
                       FileMapper fileMapper) {
    // 利用 spring 构造器注入 功能,在程序启动时,将6种类型的存储实现注入到map
    map.forEach(this.contextStrategyMap::put);
    //...
}
private FileStrategy getFileStrategy(FileStorageType storageType) {
    // 上传接口若没有传递storageType参数,则使用yml中配置的acuity.file.storageType参数。
    storageType = storageType == null ? fileServerProperties.getStorageType() : storageType;
    FileStrategy fileStrategy = contextStrategyMap.get(storageType.name());
    ArgumentAssert.notNull(fileStrategy, "请配置正确的文件存储类型");
    return fileStrategy;
}
public FileContext(Map<String, FileStrategy> map,
                       Map<String, FileChunkStrategy> chunkMap,
                       FileServerProperties fileServerProperties,
                       FileMapper fileMapper) {
    // 利用 spring 构造器注入 功能,在程序启动时,将6种类型的存储实现注入到map
    map.forEach(this.contextStrategyMap::put);
    //...
}
private FileStrategy getFileStrategy(FileStorageType storageType) {
    // 上传接口若没有传递storageType参数,则使用yml中配置的acuity.file.storageType参数。
    storageType = storageType == null ? fileServerProperties.getStorageType() : storageType;
    FileStrategy fileStrategy = contextStrategyMap.get(storageType.name());
    ArgumentAssert.notNull(fileStrategy, "请配置正确的文件存储类型");
    return fileStrategy;
}

删除文件时,是否真删除存储到服务器或OSS中的文件

yaml
acuity:
  file:
    delFile: false        # true:删除数据库记录的同时,调用oss接口删除文件 ;false: 只删除数据库记录,保留文件
acuity:
  file:
    delFile: false        # true:删除数据库记录的同时,调用oss接口删除文件 ;false: 只删除数据库记录,保留文件

热更新

提示

虽然FileServerProperties类上加了@RefreshScope注解,但仅以下参数支持热更新,没在下面的参数不支持。 原因是MinIO、FastDFS、七牛云等在启动时就初始化好了oss client,在运行过程中改了参数,只会影响FileServerProperties实例中的参数,并不会影响已经初始化好了的 oss client 内部的参数,所以MinIO、FastDFS、七牛云等无法热更新。

yaml
acuity:
  file:
    storageType: MIN_IO   
    delFile: false   
    local: 
      storage-path: /data/projects/uploadfile/file/  
      endpoint: http://127.0.0.1/file/  
      inner-uri-prefix: null  	
    ali: 
      uriPrefix: "http://acuity-admin-cloud.oss-cn-beijing.aliyuncs.com/"
      bucket: "acuity-admin-cloud"
      endpoint: "oss-cn-beijing.aliyuncs.com"
      access-key-id: "填写你的id"
      access-key-secret: "填写你的秘钥"
    huawei:   
      uriPrefix: "dev.obs.cn-southwest-2.myhuaweicloud.com"
      endpoint: "obs.cn-southwest-2.myhuaweicloud.com"
      accessKey: "1"
      secretKey: "2"
      location: "cn-southwest-2"
      bucket: "dev"
acuity:
  file:
    storageType: MIN_IO   
    delFile: false   
    local: 
      storage-path: /data/projects/uploadfile/file/  
      endpoint: http://127.0.0.1/file/  
      inner-uri-prefix: null  	
    ali: 
      uriPrefix: "http://acuity-admin-cloud.oss-cn-beijing.aliyuncs.com/"
      bucket: "acuity-admin-cloud"
      endpoint: "oss-cn-beijing.aliyuncs.com"
      access-key-id: "填写你的id"
      access-key-secret: "填写你的秘钥"
    huawei:   
      uriPrefix: "dev.obs.cn-southwest-2.myhuaweicloud.com"
      endpoint: "obs.cn-southwest-2.myhuaweicloud.com"
      accessKey: "1"
      secretKey: "2"
      location: "cn-southwest-2"
      bucket: "dev"

当然,你也可以自行修改代码让MinIO、FastDFS、七牛云等实现热更新。以MinIO举例:

  • 注释FileAutoConfigure中的MinioClient
java
// FileAutoConfigure 注释以下代码
@Bean
public MinioClient minioClient(FileServerProperties properties) {
  return new MinioClient.Builder()
  .endpoint(properties.getMinIo().getEndpoint())
  .credentials(properties.getMinIo().getAccessKey(), properties.getMinIo().getSecretKey())
  .build();
}
// FileAutoConfigure 注释以下代码
@Bean
public MinioClient minioClient(FileServerProperties properties) {
  return new MinioClient.Builder()
  .endpoint(properties.getMinIo().getEndpoint())
  .credentials(properties.getMinIo().getAccessKey(), properties.getMinIo().getSecretKey())
  .build();
}
  • 修改MinIoFileStrategyImpl代码
java
// MinIoFileStrategyImpl

// 1. 构造函数不在注入 MinioClient
// private final MinioClient minioClient;
public MinIoFileStrategyImpl(FileServerProperties fileProperties/*, MinioClient minioClient*/,
                              FileMapper fileMapper) {
  super(fileProperties, fileMapper);
  // this.minioClient = minioClient;
}

@Override
protected void uploadFile(File file, MultipartFile multipartFile, String bucket) throws Exception {
	// 划重点: 七牛云 和 FastDFS 也类似,在需要 oss client 的地方,取properties中的参数重新构建一个client,就能实现热更新。
  // 2. 在需要minioClient的地方,每次都重新构建
  MinioClient minioClient = new MinioClient.Builder()
                .endpoint(properties.getMinIo().getEndpoint())
                .credentials(properties.getMinIo().getAccessKey(), properties.getMinIo().getSecretKey())
                .build();

  boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
  // ...
}
// MinIoFileStrategyImpl

// 1. 构造函数不在注入 MinioClient
// private final MinioClient minioClient;
public MinIoFileStrategyImpl(FileServerProperties fileProperties/*, MinioClient minioClient*/,
                              FileMapper fileMapper) {
  super(fileProperties, fileMapper);
  // this.minioClient = minioClient;
}

@Override
protected void uploadFile(File file, MultipartFile multipartFile, String bucket) throws Exception {
	// 划重点: 七牛云 和 FastDFS 也类似,在需要 oss client 的地方,取properties中的参数重新构建一个client,就能实现热更新。
  // 2. 在需要minioClient的地方,每次都重新构建
  MinioClient minioClient = new MinioClient.Builder()
                .endpoint(properties.getMinIo().getEndpoint())
                .credentials(properties.getMinIo().getAccessKey(), properties.getMinIo().getSecretKey())
                .build();

  boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucket).build());
  // ...
}

上传

上传流程

附件、图片等上传,一般都会跟业务表单进行关联(如:新增应用、编辑应用),为了使业务逻辑和文件存储逻辑尽可能的解耦,文件逻辑被独立封装成文件服务和文件sdk。 img

  • 文件实时上传
ts
{
  label: t('devOperation.application.defApplication.logo'),
  field: 'appendixIcon',  				// 应用Logo
  component: 'CropperAvatar',     // 头像组件
  componentProps: {
    uploadParams: { bizType: FileBizTypeEnum.DEF_APPLICATION_LOGO },  // DEF__APPLICATION__LOGO 业务类型
    circled: false,
    isDef: true,   								// true:调用 /file/anyTenant/upload;false:调用/file/anyone/upload
  }, 
},
{
  label: t('devOperation.application.defApplication.logo'),
  field: 'appendixIcon',  				// 应用Logo
  component: 'CropperAvatar',     // 头像组件
  componentProps: {
    uploadParams: { bizType: FileBizTypeEnum.DEF_APPLICATION_LOGO },  // DEF__APPLICATION__LOGO 业务类型
    circled: false,
    isDef: true,   								// true:调用 /file/anyTenant/upload;false:调用/file/anyone/upload
  }, 
},
ts
{
  field: 'logos',
  label: t('devOperation.tenant.defTenant.logo'),
  component: 'Upload',													// 上传组件
  componentProps: {
    uploadParams: {
      bizType: FileBizTypeEnum.DEF_TENANT_LOGO,  // DEF__TENANT__LOGO
    },
    multiple: false,
    maxNumber: 1,
    accept: ['image/*', '.xlsx', 'docx'],
    isDef: true,
  },
},
{
  field: 'logos',
  label: t('devOperation.tenant.defTenant.logo'),
  component: 'Upload',													// 上传组件
  componentProps: {
    uploadParams: {
      bizType: FileBizTypeEnum.DEF_TENANT_LOGO,  // DEF__TENANT__LOGO
    },
    multiple: false,
    maxNumber: 1,
    accept: ['image/*', '.xlsx', 'docx'],
    isDef: true,
  },
},

提示

  • 若上传N次,com_file表会立即新增N条数据,此时不会存入com_appendix表。
  • 若一个表单有多个字段都需要上传附件,则需要为每个字段定义一个业务类型(bizType),bizType的规则建议为:{库}{表}
  • 前端将文件实时上传接口调用返回的数据封装到业务接口的参数。 img

上传成功后,CropperAvatar组件会自动将文件数据封装到步骤1例子中的appendixIcon字段,Upload组件会自动将文件数据封装到步骤1例子中的logos字段。 java实体类需要使用对象来接收此参数。

java
public class DefApplicationSaveVO implements Serializable {
  @ApiModelProperty("图标")
  @Valid  // 表示需要校验此对象
  private AppendixSaveVO appendixIcon;
}
public class DefApplicationSaveVO implements Serializable {
  @ApiModelProperty("图标")
  @Valid  // 表示需要校验此对象
  private AppendixSaveVO appendixIcon;
}
java
public class DefTenantSaveVO implements Serializable {
    @ApiModelProperty(value = "企业logo")
    @Size(max = 1, message = "只能上传{max}个企业logo")
    private List<AppendixSaveVO> logos;
}
public class DefTenantSaveVO implements Serializable {
    @ApiModelProperty(value = "企业logo")
    @Size(max = 1, message = "只能上传{max}个企业logo")
    private List<AppendixSaveVO> logos;
}
  • 调用业务接口保存业务数据 + 附件数据

调用appendixService.save后,会在com_appendix表存入一条数据,com_file的N条数据仍然保留,defApplication.getId() 存入 biz_id 字段,FileBizTypeEnum.DEF_APPLICATION_LOGO 存入 biz_type字段。

java
// defaults 库 def_appendix 表操作接口
@Resource
private DefAppendixService appendixService;

@Override
@Transactional(rollbackFor = Exception.class)
public DefApplication save(DefApplicationSaveVO saveVO) {
		// ...
  	// 保存业务数据
    superManager.save(defApplication);
    // 保存附件数据到 defaults 库
    appendixService.save(defApplication.getId(), saveVO.getAppendixIcon());
    return defApplication;
}
// defaults 库 def_appendix 表操作接口
@Resource
private DefAppendixService appendixService;

@Override
@Transactional(rollbackFor = Exception.class)
public DefApplication save(DefApplicationSaveVO saveVO) {
		// ...
  	// 保存业务数据
    superManager.save(defApplication);
    // 保存附件数据到 defaults 库
    appendixService.save(defApplication.getId(), saveVO.getAppendixIcon());
    return defApplication;
}
java
@Slf4j
@Service
@DS(DsConstant.DEFAULTS)
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class DefTenantServiceImpl extends SuperCacheServiceImpl<DefTenantManager, Long, DefTenant, DefTenantSaveVO, DefTenantUpdateVO, DefTenantPageQuery, DefTenantResultVO> implements DefTenantService {
  	@Resource
		private DefAppendixService appendixService;


		@Override
    protected void saveAfter(DefTenantSaveVO defTenantSaveVO, DefTenant defTenant) {
      	// 保存业务数据后,在保存附件
        appendixService.save(defTenant.getId(), defTenantSaveVO.getLogos());
    }
}
@Slf4j
@Service
@DS(DsConstant.DEFAULTS)
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class DefTenantServiceImpl extends SuperCacheServiceImpl<DefTenantManager, Long, DefTenant, DefTenantSaveVO, DefTenantUpdateVO, DefTenantPageQuery, DefTenantResultVO> implements DefTenantService {
  	@Resource
		private DefAppendixService appendixService;


		@Override
    protected void saveAfter(DefTenantSaveVO defTenantSaveVO, DefTenant defTenant) {
      	// 保存业务数据后,在保存附件
        appendixService.save(defTenant.getId(), defTenantSaveVO.getLogos());
    }
}

更详细参见 常用配置-文件相关

欢迎使用天源云Saas快速开发系统