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.bytedeco.javacv.*; //import org.bytedeco.opencv.global.opencv_imgproc; //import org.bytedeco.opencv.opencv_core.Mat; //import org.bytedeco.opencv.opencv_core.Size; //import org.springframework.mock.web.MockMultipartFile; 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; /** * 从网络视频URL截取第一秒画面,返回MultipartFile类型 * @param videoUrl 网络视频地址 * @param width 封面宽度 * @param height 封面高度 * @return 封面图片的MultipartFile对象 * @throws Exception 处理异常 */ public MultipartFile captureVideoCoverAsMultipart(String videoUrl, Integer width, Integer height) throws Exception { // // 设置默认宽高 // int targetWidth = width != null && width > 0 ? width : 800; // int targetHeight = height != null && height > 0 ? height : 600; // // // 生成唯一文件名(用于MultipartFile的原始文件名) // String fileName = UUID.randomUUID().toString() + ".jpg"; // // // 使用内存流处理图片,避免临时文件 // ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); // FFmpegFrameGrabber grabber = null; // // try { // // 初始化视频抓取器 // grabber = new FFmpegFrameGrabber(videoUrl); // grabber.start(); // // // 定位到第一秒 // grabber.setTimestamp(1000000); // 1秒 = 1,000,000微秒 // // // 获取视频帧 // Frame frame = grabber.grabImage(); // if (frame == null) { // throw new RuntimeException("无法获取视频帧,可能视频格式不支持或URL无效"); // } // // // 转换为Mat并调整尺寸 // OpenCVFrameConverter.ToMat converter = new OpenCVFrameConverter.ToMat(); // Mat mat = converter.convert(frame); // Mat resizedMat = new Mat(); // opencv_imgproc.resize(mat, resizedMat, new Size(targetWidth, targetHeight)); // // // 将处理后的帧写入内存流 // Java2DFrameConverter java2dConverter = new Java2DFrameConverter(); // ImageIO.write( // java2dConverter.getBufferedImage(converter.convert(resizedMat)), // "jpg", // outputStream // ); // // // 将内存流转换为MultipartFile // ByteArrayInputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); // return new MockMultipartFile( // "file", // 表单字段名(可自定义) // fileName, // 原始文件名 // "image/jpeg", // 文件类型 // inputStream // 文件流 // ); // } finally { // // 释放资源 // if (grabber != null) { // try { // grabber.stop(); // grabber.release(); // } catch (Exception e) { // e.printStackTrace(); // } // } // outputStream.close(); // } return null; } /** * 从完整URL中提取COS文件key * @param fullUrl 完整URL * @return COS文件key */ public String extractFileKeyFromUrl(String fullUrl) { // 去除协议和域名部分 String endpoint = cosConfigProperty.getEndpoint(); if (fullUrl.startsWith(endpoint)) { return fullUrl.substring(endpoint.length() + 1); } // 如果URL包含bucket名称 String bucketUrl = "https://" + cosConfigProperty.getBucket() + "." + endpoint; if (fullUrl.startsWith(bucketUrl)) { return fullUrl.substring(bucketUrl.length() + 1); } // 如果已经是相对路径,直接返回 return fullUrl; } /** * 获取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()); cosSTS.setEndpoint(cosConfigProperty.getEndpoint()); 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(); } } }