Appearance
文件存储
名词解释
- 热更新:修改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。
- 文件实时上传
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的规则建议为:{库}{表}
- 前端将文件实时上传接口调用返回的数据封装到业务接口的参数。
上传成功后,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());
}
}
更详细参见 常用配置-文件相关