项目开发中,需要将图片文件上传至网易公有云的Nos,并且结合CDN做加速服务,记录一下开发过程。
流程图:
1. 文件上传到公有云Nos
网易对象存储服务(Netease Object Storage,简称NOS)是网易数帆提供的高可用、高可靠、高性能的云端存储服务。
本次项目由于是和外部企业合作,上传文件是上传到公有云Nos,但和部门之前上传到私有云Nos的方式一样。都是采用计算token的方式,前端直传。(私有云和公有云签名计算方式一样)
需要注意的点有:
前端私有云上传文件的请求地址是:https://wanproxy-web.127.net。而公有云的上传请求地址是:https://nosup-eastchina1.126.net。如果请求地址不对的话,则会一直返回"AccessDenied"。
配置桶名及ak_sk的时候,一定要仔细检查,是否有错误,例如多个空格啥的。这种也会返回错误信息"AccessDenied"。
2. 认识CDN
内容分发网络(Content Delivery Network,简称CDN)是建立并覆盖在承载网之上,由分布在不同区域的边缘节点服务器群组成的分布式网络。一般应用场景在于:网页、小文件加速;大文件下载加速;视频点播加速等。
可以这样理解,cdn的作用就是将源站内容发布到不同区域的边缘节点,这样在不同地方的用户请求都能被分配最适合他的节点,就近获取节点上缓存的资源。
一方面防止每个用户请求都访问源站,避免造成网络拥塞、缓解源站压力。
另一方面,使用户可以以最快的速度取得他所需的内容,解决网络带宽小、用户访问量大、网点分布不均等问题,提高用户访问的响应速度。
3. 接入CDN
关于对接网易cdn,基本参考官方文档:https://sf.163.com/help/documents/68827624361873408。
首先是在控制台配置好加速域名,回源地址等信息;
其次由于此次项目中不仅是想完成回源访问,更想着主动预热到cdn各节点,所以需要在代码里,调用CDN提供的接口。
CDN接口功能介绍:
- 刷新:把CDN所有节点上对应的缓存资源标记为失效,当用户再次请求时,CDN会直接回源站获取对应的资源并返回给用户,同时将资源重新缓存到CDN节点。刷新功能会降低缓存命中率。
- 预热:源站主动将对应的资源缓存到CDN节点,当您首次请求资源时,即可直接从CDN节点获取到最新的资源,无需再回源站获取。预热功能会提高缓存命中率。调用接口肯定需要鉴权,
这次开发中,在签名认证上花了较多时间,官方文档上没有示例,所以在格式上耽误了许久,下面给出签名格式代码:
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
@Service
@Slf4j
public class CdnAuthorizationServiceImpl implements CdnAuthorizationService {
@Value("${cdn.config.accessId}")
private String accessId;
@Value("${cdn.config.secretKey}")
private String secretKey;
@Value("${cdn.config.domainName}")
private String domainName;
@Resource
private CdnAuthorizationService cdnAuthorizationService;
@Override
public String getAuthorization(String signature) {
return "NCDN " + accessId + ":" + signature;
}
@Override
public String getSignnature(String httpVerb, String contentMd5, String contentType, String date, String canonicalizedResource) throws NoSuchAlgorithmException, InvalidKeyException {
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
sha256_HMAC.init(secret_key);
String message = httpVerb + "\n" + contentMd5 + "\n" + contentType + "\n" + date + "\n" + canonicalizedResource;
byte[] bytes = sha256_HMAC.doFinal(message.getBytes(StandardCharsets.UTF_8));
return java.util.Base64.getEncoder().encodeToString(bytes);
}
@Override
public String getRfctime() {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
return simpleDateFormat.format(new Date());
}
@Override
public String getPreheatAuthorization() throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
String canonicalizedResource = "/domain/" + domainName;
String rfctime = cdnAuthorizationService.getRfctime();
String signnature = cdnAuthorizationService.getSignnature("PUT", "", "application/json", rfctime, canonicalizedResource);
return cdnAuthorizationService.getAuthorization(signnature);
}
@Override
public String getPreheatResultAuthorization(String orderType, Integer pageNum, Integer pageSize, String status) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
String canonicalizedResource = "/domain/" + domainName + "?orderType=" + orderType + "&pageNum=" + pageNum + "&pageSize=" + pageSize + "&status=" + status;
String rfctime = cdnAuthorizationService.getRfctime();
String signnature = cdnAuthorizationService.getSignnature("GET", "", "application/json", rfctime, canonicalizedResource);
return cdnAuthorizationService.getAuthorization(signnature);
}
}
注意点:
- 每个接口调用时,所需要的Authorization中内容是不同的,要看文档去分别改动签名计算内容。
- 获取预热结果列表接口,Authorization里的参数需要和调用接口时的参数保持一致,否则会返回签名不匹配的报错信息。
- 头信息除了Authorization之外,还需要Content-Type以及Date。注意Content-Type也要与签名里的Signnature保持一致。Date也要保持格式的统一。
鉴权通过后,就可以调用接口了,这里给出调用成功后的返回信息。
预热接口调用成功后会返回prehot-id:
预热结果列表调用成功后会返回全部预热个数以及每个预热url的结果: