Commit 33e38bec by zhangxingmin

push

parent cbec905d
......@@ -3,6 +3,8 @@ package com.yd.oss.api.controller;
import com.yd.common.result.Result;
import com.yd.oss.api.service.ApiOssStsService;
import com.yd.oss.feign.client.ApiOssStsFeignClient;
import com.yd.oss.feign.request.ApiBatchSaveFilesRequest;
import com.yd.oss.feign.response.ApiBatchSaveFilesResponse;
import com.yd.oss.feign.response.ApiGetStsTokenResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
......@@ -11,7 +13,7 @@ import org.springframework.web.bind.annotation.RestController;
/**
* OSS-STS接口信息
* OSS-分片接口信息
*
* @author zxm
* @since 2025-07-31
......@@ -28,7 +30,16 @@ public class ApiOssStsController implements ApiOssStsFeignClient {
* 获取STS凭证
* @return
*/
public Result<ApiGetStsTokenResponse> getStsToken(){
return apiOssStsService.getStsToken();
public Result<ApiGetStsTokenResponse> getStsToken(String projectBizId){
return apiOssStsService.getStsToken(projectBizId);
}
/**
* 批量保存文件列表
* @return
*/
@Override
public Result<ApiBatchSaveFilesResponse> batchSaveFiles(ApiBatchSaveFilesRequest request) {
return apiOssStsService.batchSaveFiles(request);
}
}
\ No newline at end of file
package com.yd.oss.api.service;
import com.yd.common.result.Result;
import com.yd.oss.feign.request.ApiBatchSaveFilesRequest;
import com.yd.oss.feign.response.ApiBatchSaveFilesResponse;
import com.yd.oss.feign.response.ApiGetStsTokenResponse;
public interface ApiOssStsService {
Result<ApiGetStsTokenResponse> getStsToken();
Result<ApiGetStsTokenResponse> getStsToken(String projectBizId);
Result<ApiBatchSaveFilesResponse> batchSaveFiles(ApiBatchSaveFilesRequest request);
}
package com.yd.oss.api.service.impl;
import com.aliyun.oss.OSS;
import com.aliyuncs.DefaultAcsClient;
import com.aliyuncs.IAcsClient;
import com.aliyuncs.exceptions.ClientException;
import com.aliyuncs.profile.DefaultProfile;
import com.aliyuncs.sts.model.v20150401.AssumeRoleRequest;
import com.aliyuncs.sts.model.v20150401.AssumeRoleResponse;
import com.yd.auth.core.dto.AuthUserDto;
import com.yd.auth.core.utils.SecurityUtil;
import com.yd.common.enums.CommonEnum;
import com.yd.common.result.Result;
import com.yd.common.utils.RandomStringGenerator;
import com.yd.oss.api.service.ApiOssStsService;
import com.yd.oss.feign.dto.ApiOssFileDto;
import com.yd.oss.feign.request.ApiBatchSaveFilesRequest;
import com.yd.oss.feign.response.ApiBatchSaveFilesResponse;
import com.yd.oss.feign.response.ApiGetStsTokenResponse;
import com.yd.oss.feign.response.ApiOssFileResponse;
import com.yd.oss.service.model.OssFile;
import com.yd.oss.service.model.OssProvider;
import com.yd.oss.service.service.IOssFileService;
import com.yd.oss.service.service.OssService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.MalformedURLException;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@Slf4j
@Service
public class ApiOssStsServiceImpl implements ApiOssStsService {
@Value("${aliyun.oss.access-key-id:LTAI5tFNiaUM3B4vC2bjqvtB}")
private String accessKeyId;
@Value("${aliyun.oss.access-key-secret:yH2L5YYmUayBYpQXhYevJ6Ghba349D}")
private String accessKeySecret;
// @Value("${aliyun.oss.access-key-id:LTAI5tFNiaUM3B4vC2bjqvtB}")
// private String accessKeyId;
//
// @Value("${aliyun.oss.access-key-secret:yH2L5YYmUayBYpQXhYevJ6Ghba349D}")
// private String accessKeySecret;
//
@Value("${aliyun.oss.role-arn:acs:ram::1939592679479894:role/oss-upload-role}")
private String roleArn;
@Value("${aliyun.oss.region:cn-shanghai}")
private String region;
@Value("${aliyun.oss.bucket:yd-ali-oss}")
private String bucket;
@Value("${aliyun.oss.endpoint:oss-cn-shanghai-finance-1-pub.aliyuncs.com}")
private String endpoint;
//
// @Value("${aliyun.oss.region:cn-shanghai}")
// private String region;
//
// @Value("${aliyun.oss.bucket:yd-ali-oss}")
// private String bucket;
//
// @Value("${aliyun.oss.endpoint:oss-cn-shanghai-finance-1-pub.aliyuncs.com}")
// private String endpoint;
//
// 注入最大文件大小配置,默认100MB
@Value("${aliyun.oss.max-size:104857600}")
private Long maxSize;
@Autowired
private OssService ossService;
// 动态代理的 OSS 客户端,会自动切换到当前服务商
@Autowired
private OSS ossClient;
@Autowired
private IOssFileService iOssFileService;
/**
* getStsToken如何根据传入的projectBizId查询对应服务商然后切换对应OSS服务商
* @param projectBizId
* @return
*/
@Override
public Result<ApiGetStsTokenResponse> getStsToken() {
public Result<ApiGetStsTokenResponse> getStsToken(String projectBizId) {
// 切换服务商
ossService.switchProviderByProjectId(projectBizId);
// 获取切换后的服务商信息
OssProvider currentProvider = ossService.getCurrentProvider();
String region = currentProvider.getRegion();
String accessKeyId = currentProvider.getAccessKey();
String accessKeySecret = currentProvider.getSecretKey();
// 或 ossService.getDefaultBucket()
String bucket = currentProvider.getBucketName();
// 或 ossService.getDefaultEndpoint()
String endpoint = currentProvider.getEndpoint();
//现在 ossClient 已经指向了切换后的服务商,可以直接使用
if (!ossClient.doesBucketExist(bucket)) {
log.warn("当前服务商的桶 {} 不存在,将尝试创建", bucket);
// 可以创建桶,但通常桶应提前准备好
}
// 构建STS客户端
DefaultProfile profile = DefaultProfile.getProfile(region, accessKeyId, accessKeySecret);
IAcsClient client = new DefaultAcsClient(profile);
......@@ -49,32 +108,128 @@ public class ApiOssStsServiceImpl implements ApiOssStsService {
AssumeRoleRequest request = new AssumeRoleRequest();
request.setRoleArn(roleArn);
request.setRoleSessionName("vue-upload-session");
request.setDurationSeconds(3600L); // 1小时有效期
request.setDurationSeconds(3600L);
AssumeRoleResponse response = null;
try {
response = client.getAcsResponse(request);
AssumeRoleResponse response = client.getAcsResponse(request);
if (response == null || response.getCredentials() == null) {
return Result.fail("获取STS凭证失败:返回凭证为空");
}
AssumeRoleResponse.Credentials credentials = response.getCredentials();
ApiGetStsTokenResponse tokenResponse = new ApiGetStsTokenResponse();
tokenResponse.setAccessKeyId(credentials.getAccessKeyId());
tokenResponse.setAccessKeySecret(credentials.getAccessKeySecret());
tokenResponse.setStsToken(credentials.getSecurityToken());
tokenResponse.setRegion(region);
tokenResponse.setBucket(bucket);
tokenResponse.setEndpoint(endpoint);
tokenResponse.setMaxSize(maxSize);
return Result.success(tokenResponse);
} catch (ClientException e) {
log.error("获取STS凭证失败", e);
return Result.fail("获取STS凭证失败: " + e.getMessage());
}
}
if (response == null || response.getCredentials() == null) {
return Result.fail("获取STS凭证失败:返回凭证为空");
/**
* 批量保存文件列表
*
* @return
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<ApiBatchSaveFilesResponse> batchSaveFiles(ApiBatchSaveFilesRequest request) {
// 根据 projectBizId 切换服务商,获取当前服务商信息
ossService.switchProviderByProjectId(request.getProjectBizId());
OssProvider currentProvider = ossService.getCurrentProvider();
String providerBizId = currentProvider.getProviderBizId();
String bucketName = currentProvider.getBucketName();
// 获取当前登录用户信息
AuthUserDto authUserDto = SecurityUtil.getCurrentLoginUser();
String userId = authUserDto.getUserBizId();
String userName = authUserDto.getUsername();
List<OssFile> saveOssFileList = new ArrayList<>();
List<ApiOssFileResponse> fileResponses = new ArrayList<>();
// 遍历处理文件列表
for (ApiOssFileDto fileDto : request.getApiOssFileDtoList()) {
try {
// 从完整 URL 中提取 fileKey(相对路径)
String fileUrl = fileDto.getFileUrl();
if (StringUtils.isBlank(fileUrl)) {
log.warn("文件URL为空,跳过:{}", fileDto);
continue;
}
URL url = new URL(fileUrl);
String path = url.getPath();
String fileKey = path.startsWith("/") ? path.substring(1) : path;
// 生成文件业务ID
String fileBizId = RandomStringGenerator.generateBizId16(CommonEnum.UID_TYPE_OSS_FILE.getCode());
// 获取文件类型(扩展名)
String originalName = fileDto.getFileName();
String fileType = FilenameUtils.getExtension(originalName);
if (StringUtils.isBlank(fileType)) {
fileType = FilenameUtils.getExtension(fileKey);
}
// 构建实体
OssFile file = new OssFile();
file.setTenantBizId(request.getTenantBizId());
file.setProjectBizId(request.getProjectBizId());
file.setFileBizId(fileBizId);
file.setObjectType(request.getObjectType());
file.setObjectTableName(request.getObjectTableName());
file.setObjectName(request.getObjectName());
file.setObjectBizId(request.getObjectBizId());
file.setFileKey(fileKey);
file.setOriginalName(originalName);
file.setFileSize(fileDto.getFileSize());
file.setFileType(fileType);
file.setProviderBizId(providerBizId);
file.setBucketName(bucketName);
file.setUploadTime(LocalDateTime.now());
file.setUploadUser(userId);
file.setCreatorId(userId);
file.setCreatorName(userName);
file.setUpdaterId(userId);
file.setCreateTime(LocalDateTime.now());
file.setUpdateTime(LocalDateTime.now());
file.setIsDeleted(0);
saveOssFileList.add(file);
// 构建响应对象(直接使用传入的完整URL)
ApiOssFileResponse ossFileResponse = new ApiOssFileResponse();
ossFileResponse.setFileBizId(file.getFileBizId());
ossFileResponse.setFileName(file.getOriginalName());
ossFileResponse.setFileSize(file.getFileSize());
ossFileResponse.setFileUrl(fileDto.getFileUrl()); // 设置完整URL
fileResponses.add(ossFileResponse);
} catch (MalformedURLException e) {
log.error("文件URL格式错误: {}", fileDto.getFileUrl(), e);
return Result.fail("文件URL格式错误: " + fileDto.getFileUrl());
} catch (Exception e) {
log.error("保存文件记录失败: {}", fileDto, e);
return Result.fail("保存文件记录失败: " + e.getMessage());
}
}
AssumeRoleResponse.Credentials credentials = response.getCredentials();
ApiGetStsTokenResponse tokenResponse = new ApiGetStsTokenResponse();
tokenResponse.setAccessKeyId(credentials.getAccessKeyId());
tokenResponse.setAccessKeySecret(credentials.getAccessKeySecret());
tokenResponse.setStsToken(credentials.getSecurityToken());
tokenResponse.setRegion(region);
tokenResponse.setBucket(bucket);
tokenResponse.setEndpoint(endpoint);
// 设置大小限制
tokenResponse.setMaxSize(maxSize);
return Result.success(tokenResponse);
// 批量保存(修复条件判断:列表不为空时才保存)
if (CollectionUtils.isNotEmpty(saveOssFileList)) {
iOssFileService.saveOrUpdateBatch(saveOssFileList);
// 构建响应
ApiBatchSaveFilesResponse response = new ApiBatchSaveFilesResponse();
response.setFileResponses(fileResponses);
return Result.success(response);
}
return Result.success();
}
}
\ No newline at end of file
......@@ -2,20 +2,35 @@ package com.yd.oss.feign.client;
import com.yd.common.result.Result;
import com.yd.oss.feign.fallback.ApiOssStsFeignFallbackFactory;
import com.yd.oss.feign.request.ApiBatchSaveFilesRequest;
import com.yd.oss.feign.response.ApiBatchSaveFilesResponse;
import com.yd.oss.feign.response.ApiGetStsTokenResponse;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import javax.validation.constraints.NotBlank;
/**
* OSS服务信息Feign客户端
* OSS-分片服务信息Feign客户端
*/
@FeignClient(name = "yd-oss-api", fallbackFactory = ApiOssStsFeignFallbackFactory.class)
public interface ApiOssStsFeignClient {
/**
* 获取STS凭证
* @param projectBizId
* @return
*/
@GetMapping("/sts-token")
Result<ApiGetStsTokenResponse> getStsToken();
Result<ApiGetStsTokenResponse> getStsToken(@NotBlank(message = "项目ID不能为空") @RequestParam(value = "projectBizId") String projectBizId);
/**
* 批量保存文件列表
* @return
*/
@PostMapping("/batch/save/files")
Result<ApiBatchSaveFilesResponse> batchSaveFiles(@Validated @RequestBody ApiBatchSaveFilesRequest request);
}
package com.yd.oss.feign.dto;
import lombok.Data;
@Data
public class ApiOssFileDto {
/**
* 文件名(原始文件名)
*/
private String fileName;
/**
* 文件大小
*/
private Long fileSize;
/**
* 上传文件路径(完整路径)
*/
private String fileUrl;
}
......@@ -2,10 +2,13 @@ package com.yd.oss.feign.fallback;
import com.yd.common.result.Result;
import com.yd.oss.feign.client.ApiOssStsFeignClient;
import com.yd.oss.feign.request.ApiBatchSaveFilesRequest;
import com.yd.oss.feign.response.ApiBatchSaveFilesResponse;
import com.yd.oss.feign.response.ApiGetStsTokenResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.openfeign.FallbackFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestParam;
/**
* OSS服务信息Feign降级处理
......@@ -17,7 +20,12 @@ public class ApiOssStsFeignFallbackFactory implements FallbackFactory<ApiOssStsF
public ApiOssStsFeignClient create(Throwable cause) {
return new ApiOssStsFeignClient() {
@Override
public Result<ApiGetStsTokenResponse> getStsToken() {
public Result<ApiGetStsTokenResponse> getStsToken(String projectBizId) {
return null;
}
@Override
public Result<ApiBatchSaveFilesResponse> batchSaveFiles(ApiBatchSaveFilesRequest request) {
return null;
}
};
......
package com.yd.oss.feign.request;
import com.yd.oss.feign.dto.ApiOssFileDto;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotEmpty;
import java.util.List;
@Data
public class ApiBatchSaveFilesRequest {
/**
* 所属租户ID(业务id)
*/
@NotBlank(message = "所属租户ID不能为空")
private String tenantBizId;
/**
* 所属项目ID(业务id)
*/
@NotBlank(message = "所属项目ID不能为空")
private String projectBizId;
/**
* 对象类型
*/
private String objectType;
/**
* 对象所属表名(预约表、新单跟进表等)
*/
private String objectTableName;
/**
* 对象名
*/
private String objectName;
/**
* 对象业务ID
*/
@NotBlank(message = "对象业务ID不能为空")
private String objectBizId;
/**
* 上传的文件对象列表
*/
@NotEmpty(message = "上传的文件对象列表不能为空")
private List<ApiOssFileDto> apiOssFileDtoList;
}
package com.yd.oss.feign.response;
import lombok.Data;
import java.util.List;
@Data
public class ApiBatchSaveFilesResponse {
private List<ApiOssFileResponse> fileResponses;
}
package com.yd.oss.feign.response;
import lombok.Data;
@Data
public class ApiOssFileResponse {
/**
* 文件元数据表唯一业务ID(不传值)
*/
private String fileBizId;
/**
* 文件名(原始文件名)
*/
private String fileName;
/**
* 文件大小
*/
private Long fileSize;
/**
* 上传文件路径(完整路径)
*/
private String fileUrl;
}
package com.yd.oss.service.config;
import com.aliyun.oss.OSS;
import com.yd.oss.service.service.OssService;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicOssClientProxy implements InvocationHandler {
private final OssService ossService;
public DynamicOssClientProxy(OssService ossService) {
this.ossService = ossService;
}
public static OSS createProxy(OssService ossService) {
return (OSS) Proxy.newProxyInstance(
DynamicOssClientProxy.class.getClassLoader(),
new Class[]{OSS.class},
new DynamicOssClientProxy(ossService)
);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 每次方法调用都从 OssService 获取当前客户端
OSS currentClient = ossService.getOssClient();
return method.invoke(currentClient, args);
}
}
\ No newline at end of file
......@@ -4,6 +4,7 @@ import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.yd.oss.service.model.OssProvider;
import com.yd.oss.service.service.IOssProviderService;
import com.yd.oss.service.service.OssService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
......@@ -38,14 +39,20 @@ public class OssConfig {
}
}
// @Bean
// @Primary
// public OSS ossClient() {
// return new OSSClientBuilder().build(
// currentProvider.getEndpoint(),
// currentProvider.getAccessKey(),
// currentProvider.getSecretKey()
// );
// }
@Bean
@Primary
public OSS ossClient() {
return new OSSClientBuilder().build(
currentProvider.getEndpoint(),
currentProvider.getAccessKey(),
currentProvider.getSecretKey()
);
public OSS ossClient(OssService ossService) {
// 返回动态代理,每次调用都委托给 ossService.getOssClient()
return DynamicOssClientProxy.createProxy(ossService);
}
@Bean
......
......@@ -28,6 +28,18 @@ public class OssFile implements Serializable {
private Long id;
/**
* 所属租户ID(业务id)
*/
@TableField("tenant_biz_id")
private String tenantBizId;
/**
* 所属项目ID(业务id)
*/
@TableField("project_biz_id")
private String projectBizId;
/**
* 对象类型
*/
@TableField("object_type")
......
......@@ -34,6 +34,18 @@ public class OssProvider implements Serializable {
private String providerBizId;
/**
* 所属租户ID(业务id)
*/
@TableField("tenant_biz_id")
private String tenantBizId;
/**
* 所属项目ID(业务id)
*/
@TableField("project_biz_id")
private String projectBizId;
/**
* 服务商编码(业务编码)
*/
@TableField("code")
......
......@@ -26,4 +26,6 @@ public interface IOssProviderService extends IService<OssProvider> {
boolean saveProvider(OssProvider provider);
boolean deleteProvider(Long id);
OssProvider getProviderByProjectId(String projectBizId);
}
package com.yd.oss.service.service;
import com.yd.oss.feign.request.ApiUploadFileRequest;
import com.aliyun.oss.OSS;
import com.yd.oss.service.dto.FileMetadata;
import com.yd.oss.service.dto.OssUploadFileReqDto;
import com.yd.oss.service.dto.OssUploadFileResDto;
......@@ -53,12 +53,17 @@ public interface OssService {
// 获取文件元数据(使用默认存储桶)
FileMetadata getFileMetadata(String fileKey);
void switchProviderByProjectId(String projectBizId);
// 切换OSS提供商
void switchProvider(String providerName);
// 切换OSS提供商(通过ID)
void switchProvider(Long providerId);
//获取当前使用的 OSS 客户端
OSS getOssClient();
// 获取当前使用的提供商
OssProvider getCurrentProvider();
......
......@@ -588,6 +588,26 @@ public class AliYunOssServiceImpl implements OssService {
}
/**
* 切换OSS提供商-根据项目ID切换
* @param projectBizId
*/
@Override
public void switchProviderByProjectId(String projectBizId) {
try {
// 根据项目ID获取OSS提供商
OssProvider provider = ossProviderService.getProviderByProjectId(projectBizId);
// 切换到新的提供商
switchToProvider(provider);
log.info("已切换到OSS提供商: {}", provider.getName());
} catch (Exception e) {
log.error("切换OSS提供商失败: {}", e.getMessage());
throw new RuntimeException("切换OSS提供商失败");
}
}
/**
* 切换OSS提供商
* @param providerName
*/
......@@ -628,6 +648,16 @@ public class AliYunOssServiceImpl implements OssService {
}
/**
* 获取当前使用的 OSS 客户端
* @return
*/
@Override
public OSS getOssClient() {
// 返回当前实例持有的客户端
return this.ossClient;
}
/**
* 切换到指定的OSS提供商
* @param provider
*/
......@@ -637,14 +667,18 @@ public class AliYunOssServiceImpl implements OssService {
this.ossClient.shutdown();
}
// 创建新的客户端
// 注入服务商
this.currentProvider = provider;
// 注入服务商对应的OSS客户端
this.ossClient = new OSSClientBuilder().build(
provider.getEndpoint(),
provider.getAccessKey(),
provider.getSecretKey()
);
// 注入桶
this.defaultBucket = provider.getBucketName();
// 注入服务端点
this.defaultEndpoint = provider.getEndpoint();
}
/**
......
package com.yd.oss.service.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.yd.common.exception.BusinessException;
import com.yd.oss.service.model.OssProvider;
import com.yd.oss.service.dao.OssProviderMapper;
......@@ -113,4 +114,17 @@ public class OssProviderServiceImpl extends ServiceImpl<OssProviderMapper, OssPr
public boolean deleteProvider(Long id) {
return ossProviderMapper.deleteById(id) > 0;
}
/**
* 根据项目ID查询对应OSS服务商
* @param projectBizId
* @return
*/
@Override
public OssProvider getProviderByProjectId(String projectBizId) {
return baseMapper.selectOne(new LambdaQueryWrapper<OssProvider>()
.eq(OssProvider::getProjectBizId,projectBizId)
.last(" limit 1 ")
);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment