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<String, Object> config = new TreeMap<String, Object>();
|
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<String> fileKeys) {
|
if (CollectionUtils.isEmpty(fileKeys)) {
|
return;
|
}
|
List<DeleteObjectsRequest.KeyVersion> 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();
|
}
|
}
|
|
}
|