xiangpei
2025-06-12 baa730b5518b5f73c14d0af5868641299b3fe2e4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
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<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();
        }
    }
 
}