目录
4. 注解处理类COSPreSignedUrlHandler
此文章需要先看Jackson自定义序列化注解 (支持同时使用多个自定义序列化注解)
-
腾讯COS对象存储配置类
import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Configuration; import java.util.List; /** * 腾讯COS对象存储配置 * * @author ds */ @Data @RefreshScope @Configuration @ConfigurationProperties(prefix = "cos") public class COSProperties { /** * 开发者访问 COS 服务时拥有的用户维度唯一资源标识,用以标识资源,可在 <a href="https://console.cloud.tencent.com/capi">API 密钥管理</a> 页面获取 */ private String appid; /** * 开发者拥有的项目身份识别 ID,用于身份认证,可在 <a href="https://console.cloud.tencent.com/capi">API 密钥管理</a> 页面获取 */ private String secretId; /** * 开发者拥有的项目身份密钥,可在 <a href="https://console.cloud.tencent.com/capi">API 密钥管理</a> 页面获取 */ private String secretKey; /** * 存储桶,COS 中用于存储数据的容器。有关存储桶的进一步说明,请参见 <a href="https://cloud.tencent.com/document/product/436/13312">存储桶概述</a> 文档 */ private String bucket; /** * {@link COSProperties#bucket}-{@link COSProperties#appid} */ private String bucketName; /** * 地域信息,枚举值可参见 <a href="https://cloud.tencent.com/document/product/436/6224">可用地域</a> 文档,例如:ap-beijing、ap-hongkong、eu-frankfurt 等 */ private String region; /** * {@link COSProperties#bucketName}.cos.{@link COSProperties#region}.myqcloud.com */ private String url; /** * 临时密钥有效时长,单位是秒,默认 1800 秒,目前主账号最长 2 小时(即 7200 秒),子账号最长 36 小时(即 129600)秒 */ private Integer durationSeconds; /** * 允许的路径前缀,可以根据自己网站的用户登录态判断允许上传的具体路径 * 列举几种典型的前缀授权场景: * 1、允许访问所有对象:"*" * 2、允许访问指定的对象:"a/a1.txt", "b/b1.txt" * 3、允许访问指定前缀的对象:"a*", "a/*", "b/*" * 如果填写了“*”,将允许用户访问所有资源;除非业务需要,否则请按照最小权限原则授予用户相应的访问权限范围。 */ private List<String> allowPrefixes; /** * 密钥的权限列表。必须在这里指定本次临时密钥所需要的权限。 * 简单上传、表单上传和分块上传需要以下的权限,其他权限列表请参见 <a href="https://cloud.tencent.com/document/product/436/31923">COS API 授权策略使用指引</a> */ private List<String> allowActions; }
-
腾讯COS对象存储工具类
import cn.hutool.core.date.DateTime; import cn.hutool.core.date.DateUtil; import cn.hutool.core.util.StrUtil; import com.qcloud.cos.COSClient; import com.qcloud.cos.ClientConfig; import com.qcloud.cos.auth.BasicSessionCredentials; import com.qcloud.cos.auth.COSCredentials; import com.qcloud.cos.http.HttpProtocol; import com.qcloud.cos.region.Region; import com.tencent.cloud.CosStsClient; import com.tencent.cloud.Credentials; import com.tencent.cloud.Response; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.HmacAlgorithms; import org.apache.commons.codec.digest.HmacUtils; import java.text.SimpleDateFormat; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.TimeZone; import java.util.TreeMap; /** * 腾讯COS对象存储工具类 * * @author ds */ @Slf4j public class COSUtils { /** * 腾讯COS对象存储配置 */ private static final COSProperties cosProperties = SpringUtils.getBean(COSProperties.class); /** * 创建 COSClient 实例,这个实例用来后续调用请求 * * @return COSClient */ private static COSClient createCOSClient() { Response credentialResponse = getTemporaryCredentialResponse(); Credentials credentials = credentialResponse.credentials; COSCredentials cred = new BasicSessionCredentials(credentials.tmpSecretId, credentials.tmpSecretKey, credentials.sessionToken); // ClientConfig 中包含了后续请求 COS 的客户端设置: ClientConfig clientConfig = new ClientConfig(); // 设置 bucket 的地域 clientConfig.setRegion(new Region(cosProperties.getRegion())); // 设置请求协议, http 或者 https // 5.6.53 及更低的版本,建议设置使用 https 协议 // 5.6.54 及更高版本,默认使用了 https clientConfig.setHttpProtocol(HttpProtocol.https); // 以下的设置,是可选的: // 设置 socket 读取超时,默认 30s clientConfig.setSocketTimeout(30 * 1000); // 设置建立连接超时,默认 30s clientConfig.setConnectionTimeout(30 * 1000); // 如果需要的话,设置 http 代理,ip 以及 port // clientConfig.setHttpProxyIp("httpProxyIp"); // clientConfig.setHttpProxyPort(80); // 生成 cos 客户端。 return new COSClient(cred, clientConfig); } /** * 获取存储桶上传表单参数 fixme 需要做缓存 * * @return 秘钥相关 */ public static Map<String, Object> getUploadFormData() { Map<String, Object> map = new HashMap<>(); map.put("q-sign-algorithm", "sha1"); map.put("url", cosProperties.getUrl()); map.put("q-ak", cosProperties.getSecretId()); DateTime now = DateUtil.date(); DateTime expiration = DateUtil.offsetHour(now, 1); long nowTime = now.getTime() / 1000; long expirationTime = expiration.getTime() / 1000; String keyTime = nowTime + ";" + expirationTime; map.put("q-key-time", keyTime); String policy = getPolicy(expiration, keyTime); map.put("policy", Base64.getEncoder().encodeToString(policy.getBytes())); String signKey = new HmacUtils(HmacAlgorithms.HMAC_SHA_1, cosProperties.getSecretKey()).hmacHex(keyTime); String stringToSign = DigestUtils.sha1Hex(policy); String signature = new HmacUtils(HmacAlgorithms.HMAC_SHA_1, signKey).hmacHex(stringToSign); map.put("q-signature", signature); return map; } /** * 构造“策略”(Policy) * * @param expiration 该策略的过期时间 * @param keyTime 时间戳和期望的签名有效时长算出签名过期时间对应的 Unix 时间戳 EndTimestamp * @return Policy */ private static String getPolicy(DateTime expiration, String keyTime) { JSONObject policyObj = new JSONObject(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); dateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); // 该策略的过期时间 ISO8601 格式字符串 policyObj.put("expiration", dateFormat.format(expiration)); JSONArray conditionsArray = new JSONArray(); policyObj.put("conditions", conditionsArray); JSONObject qSignAlgorithmObj = new JSONObject(); qSignAlgorithmObj.put("q-sign-algorithm", "sha1"); conditionsArray.add(qSignAlgorithmObj); JSONObject qAkObj = new JSONObject(); qAkObj.put("q-ak", cosProperties.getSecretId()); conditionsArray.add(qAkObj); JSONObject qSignTimeObj = new JSONObject(); qSignTimeObj.put("q-sign-time", keyTime); conditionsArray.add(qSignTimeObj); return JSON.toJSONString(policyObj); } /** * 获取临时秘钥 * * @return 临时秘钥 */ public static Response getTemporaryCredentialResponse() { Response credential; try { TreeMap<String, Object> config = new TreeMap<>(); config.put("secretId", cosProperties.getSecretId()); config.put("secretKey", cosProperties.getSecretKey()); config.put("durationSeconds", cosProperties.getDurationSeconds()); config.put("bucket", cosProperties.getBucketName()); config.put("region", cosProperties.getRegion()); config.put("allowPrefixes", cosProperties.getAllowPrefixes().toArray(new String[0])); config.put("allowActions", cosProperties.getAllowActions().toArray(new String[0])); // 设置域名: // 如果您使用了腾讯云 cvm,可以设置内部域名 // config.put("host", "sts.internal.tencentcloudapi.com"); credential = CosStsClient.getCredential(config); } catch (Exception e) { e.printStackTrace(); throw new IllegalArgumentException("no valid secret !"); } return credential; } /** * 获取预签名地址 * * @param key 文件key * @return 预签名地址 */ public static String generatePreSignedUrl(String key) { if (StrUtil.isBlank(key)) { return StrUtil.EMPTY; } // 调用 COS 接口之前必须保证本进程存在一个 COSClient 实例,如果没有则创建 // 详细代码参见本页:简单操作 -> 创建 COSClient COSClient cosClient = createCOSClient(); try { // 设置签名过期时间(可选), 若未进行设置则默认使用 ClientConfig 中的签名过期时间(1小时) // 这里设置签名在半个小时后过期 Date expirationDate = new Date(System.currentTimeMillis() + 30 * 60 * 1000); return cosClient.generatePresignedUrl(cosProperties.getBucketName(), key, expirationDate).toString(); } finally { // 确认本进程不再使用 cosClient 实例之后,关闭即可 cosClient.shutdown(); } } }
-
COSPreSignedUrl注解类
import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** * 获取COS预签名地址,标注在属性上 * <p/> * 会多返回个属性存放预签名地址 属性名为 原属性名 + PreSigned * * @author ds */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD) @JacksonAnnotationsInside @JacksonBaseSerialize(handlerClazz = COSPreSignedUrlHandler.class) public @interface COSPreSignedUrl { }
-
注解处理类COSPreSignedUrlHandler
import cn.hutool.core.util.StrUtil; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.SerializerProvider; import java.io.IOException; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; /** * Jackson 序列化处理类 * 获取COS预签名地址,标注在属性上 * <p/> * 会多返回个属性存放预签名地址 属性名为 原属性名 + PreSigned * * @author ds */ public class COSPreSignedUrlHandler implements IJacksonBaseSerializeHandler<COSPreSignedUrl, Object> { /** * Method that can be called to ask implementation to serialize * values of type this serializer handles. * * @param value Value to serialize; can <b>not</b> be null. * @param gen Generator used to output resulting Json content * @param serializers Provider that can be used to get serializers for * serializing Objects value contains, if any. */ @Override public void serializeHandler(BeanProperty beanProperty, COSPreSignedUrl annotation, Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException { // 为空直接跳过 if (beanProperty == null) { return; } if (value instanceof String) { String str = (String) value; if (StrUtil.isNotBlank(str)) { // 只针对String类型属性进行 if (Objects.nonNull(annotation) && Objects.equals(String.class, beanProperty.getType().getRawClass())) { gen.writeStringField(beanProperty.getName() + "PreSigned", COSUtils.generatePreSignedUrl(str)); } } } else if (value instanceof List) { List<String> list = (List<String>) value; // 只针对String类型属性进行 if (Objects.nonNull(annotation) && Objects.equals(List.class, beanProperty.getType().getRawClass())) { List<String> resultList = list.stream().map(COSUtils::generatePreSignedUrl).collect(Collectors.toList()); gen.writeObjectField(beanProperty.getName() + "PreSigned", resultList); } } } }