v3.30.0 【增加】字典项增加回显样式;【优化】文件S3协议对最新minio的支持;

This commit is contained in:
zhuoda
2026-03-01 21:34:21 +08:00
parent 36de38fda3
commit 2c661120ca
132 changed files with 975 additions and 540 deletions

View File

@@ -15,6 +15,7 @@ import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3Configuration;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import java.net.URI;
@@ -31,55 +32,70 @@ import java.net.URI;
@Configuration
public class FileConfig implements WebMvcConfigurer {
private static final String HTTPS = "https://";
private static final String HTTP = "http://";
private static final String MODE_CLOUD = "cloud";
private static final String MODE_LOCAL = "local";
@Value("${file.storage.cloud.region}")
private String region;
@Value("${file.storage.cloud.endpoint}")
private String endpoint;
@Value("${file.storage.cloud.bucket-name}")
private String bucketName;
@Value("${file.storage.cloud.access-key}")
private String accessKey;
@Value("${file.storage.cloud.secret-key}")
private String secretKey;
@Value("${file.storage.cloud.private-url-expire-seconds}")
private Long privateUrlExpireSeconds;
@Value("${file.storage.cloud.url-prefix}")
private String urlPrefix;
@Value("${file.storage.local.upload-path}")
private String uploadPath;
@Value("${file.storage.mode}")
private String mode;
@Value("${file.storage.cloud.region}")
private String cloudRegion;
@Value("${file.storage.cloud.endpoint}")
private String cloudEndpoint;
@Value("${file.storage.cloud.bucket-name}")
private String cloudBucketName;
@Value("${file.storage.cloud.access-key}")
private String cloudAccessKey;
@Value("${file.storage.cloud.secret-key}")
private String cloudSecretKey;
@Value("${file.storage.cloud.private-url-expire-seconds}")
private Long cloudPrivateUrlExpireSeconds;
@Value("${file.storage.cloud.public-url-prefix}")
private String cloudPublicUrlPrefix;
@Value("${file.storage.local.upload-path}")
private String localUploadPath;
/**
* 初始化 云oss client 配置
*
* @return
* 初始化 s3 client 配置
*/
@Bean
@ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_CLOUD)
public S3Client initAmazonS3() {
public S3Client initS3Client() {
return S3Client.builder()
.region(Region.AWS_GLOBAL)
.endpointOverride(URI.create((urlPrefix.startsWith(HTTPS) ? HTTPS : HTTP) + endpoint))
.region(Region.of(cloudRegion))
.endpointOverride(URI.create(cloudEndpoint))
.credentialsProvider(
StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKey, secretKey)))
AwsBasicCredentials.create(cloudAccessKey, cloudSecretKey)))
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(false)
.chunkedEncodingEnabled(false)
.build())
.build();
}
/**
* 初始化 s3 预签名
*/
@Bean
@ConditionalOnProperty(prefix = "file.storage", name = {"mode"}, havingValue = MODE_CLOUD)
public S3Presigner initS3Presigner() {
return S3Presigner
.builder()
.region(Region.of(cloudRegion))
.endpointOverride(URI.create(cloudEndpoint))
.credentialsProvider(
StaticCredentialsProvider.create(
AwsBasicCredentials.create(cloudAccessKey, cloudSecretKey)))
.serviceConfiguration(S3Configuration.builder()
.pathStyleAccessEnabled(false)
.chunkedEncodingEnabled(false)
@@ -102,7 +118,7 @@ public class FileConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
if (MODE_LOCAL.equals(mode)) {
String path = uploadPath.endsWith("/") ? uploadPath : uploadPath + "/";
String path = localUploadPath.endsWith("/") ? localUploadPath : localUploadPath + "/";
registry.addResourceHandler(FileStorageLocalServiceImpl.UPLOAD_MAPPING + "/**").addResourceLocations("file:" + path);
}
}

View File

@@ -0,0 +1,27 @@
package net.lab1024.sa.base.module.support.dict.constant;
import lombok.Getter;
import net.lab1024.sa.base.common.enumeration.BaseEnum;
/**
* 字典回显样式 枚举
*/
@Getter
public enum DictDataStyleEnum implements BaseEnum {
DEFAULT("默认", "default"),
PRIMARY("主要", "primary"),
SUCCESS("成功", "success"),
INFO("信息", "info"),
WARNING("警告", "warning"),
DANGER("危险", "danger");
private final String value;
private final String desc;
DictDataStyleEnum(String desc, String value) {
this.desc = desc;
this.value = value;
}
}

View File

@@ -40,6 +40,11 @@ public class DictDataEntity {
*/
private String dataLabel;
/**
* 字典项样式
*/
private String dataStyle;
/**
* 备注
*/

View File

@@ -4,6 +4,10 @@ import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import net.lab1024.sa.base.common.swagger.SchemaEnum;
import net.lab1024.sa.base.common.validator.enumeration.CheckEnum;
import net.lab1024.sa.base.module.support.dict.constant.DictDataStyleEnum;
/**
* 字典数据表 新建表单
@@ -28,6 +32,10 @@ public class DictDataAddForm {
@NotBlank(message = "字典项显示名称 不能为空")
private String dataLabel;
@SchemaEnum(value = DictDataStyleEnum.class, desc = "数据样式")
@CheckEnum(message = "样式参数错误", value = DictDataStyleEnum.class)
private String dataStyle;
@Schema(description = "备注")
private String remark;

View File

@@ -38,6 +38,9 @@ public class DictDataVO implements Serializable {
@Schema(description = "字典项显示名称")
private String dataLabel;
@Schema(description = "字典项回显")
private String dataStyle;
@Schema(description = "备注")
private String remark;

View File

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

View File

@@ -71,6 +71,9 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
@Resource
private S3Client s3Client;
@Resource
private S3Presigner s3Presigner;
@Resource
private FileConfig cloudConfig;
@@ -104,7 +107,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
// 根据文件路径获取并设置访问权限
ObjectCannedACL acl = this.getACL(path);
PutObjectRequest putObjectRequest = PutObjectRequest.builder()
.bucket(cloudConfig.getBucketName())
.bucket(cloudConfig.getCloudBucketName())
.key(fileKey)
.metadata(userMetadata)
.contentLength(file.getSize())
@@ -128,7 +131,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
uploadVO.setFileName(originalFileName);
uploadVO.setFileType(fileType);
// 根据 访问权限 返回不同的 URL
String url = cloudConfig.getUrlPrefix() + fileKey;
String url = cloudConfig.getCloudPublicUrlPrefix() + fileKey;
if (ObjectCannedACL.PRIVATE.equals(acl)) {
// 获取临时访问的URL
url = this.getFileUrl(fileKey).getData();
@@ -153,11 +156,10 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
if (!fileKey.startsWith(FileFolderTypeEnum.FOLDER_PRIVATE)) {
// 不是私有的 都公共读
return ResponseDTO.ok(cloudConfig.getUrlPrefix() + fileKey);
return ResponseDTO.ok(cloudConfig.getCloudPublicUrlPrefix() + fileKey);
}
// 如果是私有的,则规定时间内可以访问,超过规定时间,则连接失效
String fileRedisKey = RedisKeyConst.Support.FILE_PRIVATE_VO + fileKey;
FileVO fileVO = redisService.getObject(fileRedisKey, FileVO.class);
if (fileVO == null) {
@@ -165,15 +167,22 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
if (fileVO == null) {
return ResponseDTO.userErrorParam("文件不存在");
}
GetObjectRequest getUrlRequest = GetObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).build();
GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest.builder().signatureDuration(Duration.ofSeconds(cloudConfig.getPrivateUrlExpireSeconds())).getObjectRequest(getUrlRequest).build();
GetObjectRequest getUrlRequest = GetObjectRequest
.builder()
.bucket(cloudConfig.getCloudBucketName())
.key(fileKey)
.build();
S3Presigner presigner = S3Presigner.builder().region(Region.of(cloudConfig.getRegion())).build();
GetObjectPresignRequest getObjectPresignRequest = GetObjectPresignRequest
.builder()
.signatureDuration(Duration.ofSeconds(cloudConfig.getCloudPrivateUrlExpireSeconds()))
.getObjectRequest(getUrlRequest)
.build();
PresignedGetObjectRequest presignedGetObjectRequest = presigner.presignGetObject(getObjectPresignRequest);
PresignedGetObjectRequest presignedGetObjectRequest = s3Presigner.presignGetObject(getObjectPresignRequest);
String url = presignedGetObjectRequest.url().toString();
fileVO.setFileUrl(url);
redisService.set(fileRedisKey, fileVO, cloudConfig.getPrivateUrlExpireSeconds() - 5);
redisService.set(fileRedisKey, fileVO, cloudConfig.getCloudPrivateUrlExpireSeconds() - 5);
}
return ResponseDTO.ok(fileVO.getFileUrl());
@@ -187,7 +196,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
public ResponseDTO<FileDownloadVO> download(String key) {
// 获取文件 meta
HeadObjectRequest objectRequest = HeadObjectRequest.builder().bucket(this.cloudConfig.getBucketName()).key(key).build();
HeadObjectRequest objectRequest = HeadObjectRequest.builder().bucket(this.cloudConfig.getCloudBucketName()).key(key).build();
HeadObjectResponse headObjectResponse = s3Client.headObject(objectRequest);
Map<String, String> userMetadata = headObjectResponse.metadata();
FileMetadataVO metadataDTO = null;
@@ -201,7 +210,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
}
//获取oss对象
GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(key).build();
GetObjectRequest getObjectRequest = GetObjectRequest.builder().bucket(cloudConfig.getCloudBucketName()).key(key).build();
ResponseBytes<GetObjectResponse> s3ClientObject = s3Client.getObject(getObjectRequest, ResponseTransformer.toBytes());
// 输入流转换为字节流
@@ -236,7 +245,7 @@ public class FileStorageCloudServiceImpl implements IFileStorageService {
*/
@Override
public ResponseDTO<String> delete(String fileKey) {
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder().bucket(cloudConfig.getBucketName()).key(fileKey).build();
DeleteObjectRequest deleteObjectRequest = DeleteObjectRequest.builder().bucket(cloudConfig.getCloudBucketName()).key(fileKey).build();
s3Client.deleteObject(deleteObjectRequest);
return ResponseDTO.ok();
}

View File

@@ -103,12 +103,13 @@ file:
url-prefix:
cloud:
region: oss-cn-hangzhou
endpoint: oss-cn-hangzhou.aliyuncs.com
endpoint: https://oss-cn-hangzhou.aliyuncs.com
bucket-name: 1024lab-smart-admin
access-key:
secret-key:
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
private-url-expire-seconds: 3600
# 云计算厂商支持公开的文件访问模式minio默认是不支持的对于minio用户可以配置为空
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
# open api配置
springdoc:

View File

@@ -103,12 +103,13 @@ file:
url-prefix:
cloud:
region: oss-cn-hangzhou
endpoint: oss-cn-hangzhou.aliyuncs.com
endpoint: https://oss-cn-hangzhou.aliyuncs.com
bucket-name: 1024lab-smart-admin
access-key:
secret-key:
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
private-url-expire-seconds: 3600
# 云计算厂商支持公开的文件访问模式minio默认是不支持的对于minio用户可以配置为空
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
# open api配置
springdoc:

View File

@@ -103,12 +103,13 @@ file:
url-prefix:
cloud:
region: oss-cn-hangzhou
endpoint: oss-cn-hangzhou.aliyuncs.com
endpoint: https://oss-cn-hangzhou.aliyuncs.com
bucket-name: 1024lab-smart-admin
access-key:
secret-key:
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
private-url-expire-seconds: 3600
# 云计算厂商支持公开的文件访问模式minio默认是不支持的对于minio用户可以配置为空
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
# open api配置
springdoc:

View File

@@ -103,12 +103,13 @@ file:
url-prefix:
cloud:
region: oss-cn-hangzhou
endpoint: oss-cn-hangzhou.aliyuncs.com
endpoint: https://oss-cn-hangzhou.aliyuncs.com
bucket-name: 1024lab-smart-admin
access-key:
secret-key:
url-prefix: https://${file.storage.cloud.bucket-name}.${file.storage.cloud.endpoint}/
private-url-expire-seconds: 3600
# 云计算厂商支持公开的文件访问模式minio默认是不支持的对于minio用户可以配置为空
public-url-prefix: https://1024lab-smart-admin.oss-cn-hangzhou.aliyuncs.com/
# open api配置
springdoc: