package cn.lili.utils; import cn.lili.cos.COSConfigProperty; import cn.lili.cos.CosSTS; import cn.lili.modules.lmk.domain.entity.LmkFile; import com.qcloud.cos.COSClient; import com.qcloud.cos.ClientConfig; import com.qcloud.cos.auth.BasicSessionCredentials; import com.qcloud.cos.exception.CosClientException; import com.qcloud.cos.exception.CosServiceException; import com.qcloud.cos.http.HttpMethodName; import com.qcloud.cos.model.*; import com.qcloud.cos.region.Region; import com.tencent.cloud.CosStsClient; import com.tencent.cloud.Policy; import com.tencent.cloud.Response; import com.tencent.cloud.Statement; import com.tencent.cloud.cos.util.Jackson; import lombok.RequiredArgsConstructor; import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.*; import java.net.URL; import java.net.URLEncoder; import java.util.*; import java.util.stream.Collectors; /** * @author:xp * @date:2025/5/16 16:31 */ @Component @RequiredArgsConstructor public class COSUtil { private final COSConfigProperty cosConfigProperty; /** * 获取sts临时访问凭证 * * @return */ public CosSTS getSTS() { TreeMap config = new TreeMap(); try { config.put("secretId", cosConfigProperty.getSecretId()); config.put("secretKey", cosConfigProperty.getSecretKey()); // 初始化 policy Policy policy = new Policy(); // 设置域名: // 如果您使用了腾讯云 cvm,可以设置内部域名 //config.put("host", "sts.internal.tencentcloudapi.com"); // 临时密钥有效时长,单位是秒,默认 1800 秒,目前主账号最长 2 小时(即 7200 秒),子账号最长 36 小时(即 129600)秒 config.put("durationSeconds", cosConfigProperty.getDurationSeconds()); // 换成您的 bucket config.put("bucket", cosConfigProperty.getBucket()); // 换成 bucket 所在地区 config.put("region", cosConfigProperty.getRegion()); // 开始构建一条 statement Statement statement = new Statement(); // 声明设置的结果是允许操作 statement.setEffect("allow"); /** * 密钥的权限列表。必须在这里指定本次临时密钥所需要的权限。 * 权限列表请参见 https://cloud.tencent.com/document/product/436/31923 * 规则为 {project}:{interfaceName} * project : 产品缩写 cos相关授权为值为cos,数据万象(数据处理)相关授权值为ci * 授权所有接口用*表示,例如 cos:*,ci:* * 添加一批操作权限 : */ statement.addActions(cosConfigProperty.getActions()); /** * 这里改成允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径 * 资源表达式规则分对象存储(cos)和数据万象(ci)两种 * 数据处理、审核相关接口需要授予ci资源权限 * cos : qcs::cos:{region}:uid/{appid}:{bucket}/{path} * ci : qcs::ci:{region}:uid/{appid}:bucket/{bucket}/{path} * 列举几种典型的{path}授权场景: * 1、允许访问所有对象:"*" * 2、允许访问指定的对象:"a/a1.txt", "b/b1.txt" * 3、允许访问指定前缀的对象:"a*", "a/*", "b/*" * 如果填写了“*”,将允许用户访问所有资源;除非业务需要,否则请按照最小权限原则授予用户相应的访问权限范围。 * * 示例:授权examplebucket-1250000000 bucket目录下的所有资源给cos和ci 授权两条Resource */ statement.addResources(cosConfigProperty.getResources()); // 把一条 statement 添加到 policy // 可以添加多条 policy.addStatement(statement); // 将 Policy 示例转化成 String,可以使用任何 json 转化方式,这里是本 SDK 自带的推荐方式 config.put("policy", Jackson.toJsonPrettyString(policy)); Response response = CosStsClient.getCredential(config); System.out.println(response.credentials.tmpSecretId); System.out.println(response.credentials.tmpSecretKey); System.out.println(response.credentials.sessionToken); CosSTS cosSTS = new CosSTS(); cosSTS.setTmpSecretId(response.credentials.tmpSecretId); cosSTS.setTmpSecretKey(response.credentials.tmpSecretKey); cosSTS.setSessionToken(response.credentials.sessionToken); Date now = new Date(); cosSTS.setStsStartTime(now.getTime() / 1000); // 预留30s的请求时间,防止给小程序的结束时间超过实际的结束时间 cosSTS.setStsEndTime(cosSTS.getStsStartTime() + cosConfigProperty.getDurationSeconds() - 30); cosSTS.setBucket(cosConfigProperty.getBucket()); cosSTS.setRegion(cosConfigProperty.getRegion()); return cosSTS; } catch (Exception e) { e.printStackTrace(); throw new IllegalArgumentException("get sts error"); } } /** * 初始化cos客户端 * * @return COSClient cos客户端 */ public COSClient initClient() { CosSTS sts = this.getSTS(); BasicSessionCredentials cred = new BasicSessionCredentials(sts.getTmpSecretId(), sts.getTmpSecretKey(), sts.getSessionToken()); // 2 设置 bucket 的地域 // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分 Region region = new Region(cosConfigProperty.getRegion()); //COS_REGION 参数:配置成存储桶 bucket 的实际地域,例如 ap-beijing,更多 COS 地域的简称请参见 https://cloud.tencent.com/document/product/436/6224 ClientConfig clientConfig = new ClientConfig(region); // 3 生成 cos 客户端 COSClient cosClient = new COSClient(cred, clientConfig); return cosClient; } /** * 最简单的上传文件 * * @param fileInput 文件 * @param fileInfo 文件信息 * @return fileKey 文件路径,或者叫唯一标识 */ public void upload(InputStream fileInput, LmkFile fileInfo) { COSClient cosClient = this.initClient(); ObjectMetadata objectMetadata = new ObjectMetadata(); objectMetadata.setContentType(fileInfo.getFileType()); objectMetadata.setContentLength(fileInfo.getFileSize()); // objectMetadata.setContentDisposition("attachment;filename=" + URLEncoder.encode(fileInfo.getOriginalFilename(), "UTF-8")); // 指定文件上传到 COS 上的路径,即对象键。例如对象键为 folder/picture.jpg,则表示将文件 picture.jpg 上传到 folder 路径下 PutObjectRequest putObjectRequest = new PutObjectRequest(cosConfigProperty.getBucket(), fileInfo.getFileKey(), fileInput, objectMetadata); PutObjectResult putObjectResult = cosClient.putObject(putObjectRequest); System.out.println(putObjectResult); cosClient.shutdown(); } /** * 下载文件 * * @param fileKey 文件路径,或者叫唯一标识 * @param response */ public void download(String fileKey, HttpServletResponse response) { COSClient cosClient = this.initClient(); GetObjectRequest getObjectRequest = new GetObjectRequest(cosConfigProperty.getBucket(), fileKey); try { // 获取COS对象 COSObject cosObject = cosClient.getObject(getObjectRequest); // 获取对象元数据 ObjectMetadata metadata = cosObject.getObjectMetadata(); String contentType = metadata.getContentType(); long contentLength = metadata.getContentLength(); String filename = fileKey.substring(fileKey.lastIndexOf('/') + 1); // 设置响应头 response.setContentType(contentType != null ? contentType : "application/octet-stream"); response.setContentLengthLong(contentLength); response.setHeader("Content-Disposition", "attachment; filename=\"" + URLEncoder.encode(filename, "UTF-8") + "\""); // 获取输入流和输出流 try (InputStream cosObjectInput = cosObject.getObjectContent(); OutputStream responseOutputStream = response.getOutputStream()) { // 使用缓冲区传输数据 byte[] buffer = new byte[4096]; int bytesRead; while ((bytesRead = cosObjectInput.read(buffer)) != -1) { responseOutputStream.write(buffer, 0, bytesRead); } responseOutputStream.flush(); } } catch (CosServiceException e) { // COS服务异常 e.printStackTrace(); throw new RuntimeException("存储服务异常"); } catch (CosClientException e) { // COS客户端异常 e.printStackTrace(); throw new RuntimeException("存储服务客户端异常"); } catch (IOException e) { // IO异常 e.printStackTrace(); throw new RuntimeException("文件读取异常"); } finally { // 确保COS客户端关闭 if (cosClient != null) { cosClient.shutdown(); } } } /** * 获取在线访问文件地址 * * @param fileKey * @return */ public String getPreviewUrl(String fileKey) { return cosConfigProperty.getEndpoint() + "/" + fileKey; } /** * 删除单个文件 * * @param fileKey */ public void deleteFile(String fileKey) { COSClient cosClient = this.initClient(); try { cosClient.deleteObject(cosConfigProperty.getBucket(), fileKey); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("文件删除失败"); } finally { cosClient.shutdown(); } } /** * 删除多个文件 * * @param fileKeys */ public void deleteFiles(List fileKeys) { if (CollectionUtils.isEmpty(fileKeys)) { return; } List keys = fileKeys.stream().map(key -> new DeleteObjectsRequest.KeyVersion(key)).collect(Collectors.toList()); COSClient cosClient = this.initClient(); try { DeleteObjectsRequest deleteObjectsRequest = new DeleteObjectsRequest(cosConfigProperty.getBucket()); deleteObjectsRequest.setKeys(keys); cosClient.deleteObjects(deleteObjectsRequest); } catch (Exception e) { e.printStackTrace(); throw new RuntimeException("文件删除失败"); } finally { cosClient.shutdown(); } } }