最近OSS相关项目完成,借此好好总结了一下OSS开发细节。
项目前景:
公司系统涉及到许多报表导入导出,以前公司导入导出报表、模板、凭证都是针对直接提供服务的服务器。一旦当高并发请求过来,有可能会导致服务器进行大量IO操作以及内存频繁修改,会导致系统资源耗尽,使得提供服务的微服务的服务器挂掉。因此,公司提出了进行了微服务OSS开造的需求,专门开发一个用来进行文件导入导出功能的微服务,减轻各个服务导出导入所导致的压力。
OSS核心细节:
首先进行阿里云OSS参数配置,如endpoint、AK、SK等。
@Configuration
public class OssConfig {
@Value("${aliyun.oss.endpoint}")
private String ALIYUN_OSS_ENDPOINT;
@Value("${aliyun.oss.accessKeyId}")
private String ALIYUN_OSS_ACCESSKEYID;
@Value("${aliyun.oss.accessKeySecret}")
private String ALIYUN_OSS_ACCESSKEYSECRET;
@Value("${aliyun.oss.internalEndpoint}")
private String ALIYUN_OSS_INTERNAL_ENDPOINT;
@Bean
public OSSClient ossClient(){
return new OSSClient(ALIYUN_OSS_ENDPOINT,ALIYUN_OSS_ACCESSKEYID,ALIYUN_OSS_ACCESSKEYSECRET);
}
@Bean
public OSSClient ossInternalClient(){
return new OSSClient(ALIYUN_OSS_INTERNAL_ENDPOINT,ALIYUN_OSS_ACCESSKEYID,ALIYUN_OSS_ACCESSKEYSECRET);
}
}
如代码所示,配置了两个OSS客户端连接,一个通过外网访问,一个通过内网访问,之所以配置了一个内网传输连接,当然是因为不要钱(战术后仰)。
阿里云提供了多种传输方式有服务器直传、服务器签名直传、前端直传。为了安全性我们可以采用服务签名直传以及服务直传。
- 服务签名直传:
controller:
@ApiOperation(value = "oss上传签名生成" , tags = {"服务外部调用"})
@GetMapping(value = "/policy")
public ReturnResult<OssPolicyResult> policy(HttpServletRequest request) {
//可以进行自己的逻辑业务判断
OssPolicyResult result = ossService.policy();
return new ReturnResult<>(ReturnEnum.SUCCESS, result);
}
dto:
@Data
public class OssPolicyResult {
private String accessKeyId;
private String policy;
private String signature;
private String dir;
private String host;
private String callback;
private String expire;
}
service:
/**
* 签名生成
*/
@Override
public OssPolicyResult policy() {
// 存储目录
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
String companyId = (String) request.getSession().getAttribute("companyId");
String userUuid = (String) request.getSession().getAttribute("userUuid");
if (null == companyId || null == userUuid) {
throw new XxxException(10002);
}
String dir = companyId + "/" + userUuid + "/";
// 签名有效期
long expireEndTime = System.currentTimeMillis() + ALIYUN_OSS_EXPIRE * 1000;
Date expiration = new Date(expireEndTime);
// 文件大小
long maxSize = ALIYUN_OSS_MAX_SIZE * 1024 * 1024;
// 回调
OssCallbackParam callback = new OssCallbackParam();
callback.setCallbackUrl(ALIYUN_OSS_CALLBACK);
callback.setCallbackBody("filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}");
callback.setCallbackBodyType("application/x-www-form-urlencoded");
// 提交节点
String action = "https://" + ALIYUN_OSS_BUCKET_NAME + "." + ALIYUN_OSS_ENDPOINT;
OssPolicyResult result = new OssPolicyResult();
try {
PolicyConditions policyConds = new PolicyConditions();
policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, maxSize);
policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
byte[] binaryData = postPolicy.getBytes("utf-8");
String policy = BinaryUtil.toBase64String(binaryData);
String signature = ossClient.calculatePostSignature(postPolicy);
String callbackData = BinaryUtil.toBase64String(JSON.toJSONString(callback).getBytes("utf-8"));
// 返回结果
result.setAccessKeyId(ossClient.getCredentialsProvider().getCredentials().getAccessKeyId());
result.setPolicy(policy);
result.setSignature(signature);
result.setDir(dir);
result.setCallback(callbackData);
result.setHost(action);
result.setExpire(String.valueOf(expireEndTime / 1000));
} catch (Exception e) {
// 发送钉消息
clientService.sendErrMsgByRobot("OSS签名异常 :" + e.getMessage());
}
return result;
}
大家可能发现了,前端进行签名请求的时候服务器可以设置回调接口路径,这个接口主要用于当我们文件成功上传至OSS,我们可以获取文件的相关信息如大小、类型等。
回调接口类似:
public OssCallbackResult callback(HttpServletRequest request) {
OssCallbackResult result= new OssCallbackResult();
String filename = request.getParameter("filename");
filename = "http://".concat(ALIYUN_OSS_BUCKET_NAME).concat(".").concat(ALIYUN_OSS_ENDPOINT).concat("/").concat(filename);
result.setEnclosurePath(filename);
result.setSize(request.getParameter("size"));
result.setMimeType(request.getParameter("mimeType"));
result.setWidth(request.getParameter("width"));
result.setHeight(request.getParameter("height"));
//这里进行相应的业务处理比如存到数据库等
return result;
}
- 服务器直传
当然这个直接可以我们服务自己内部捣鼓不需要前端配合请求,适用场景可以用来将已存在服务器的文件进行打包压缩然后上传OSS然后进行下载,这样可以避免我们导出多个文件给视觉带来的繁杂。
这个实现来说也相当简单,利用阿里云给我们提供的SDK就可以轻松完成文件上传。
service:
@Override
public void putFile() {
String content = "Hello OSS";
ossClient.putObject(ALIYUN_OSS_BUCKET_NAME, "填写自定义的文件标识,根据这个标识找到这个文件", new ByteArrayInputStream(content.getBytes()));
}
- 文件下载
阿里云OSS文件那也是十分容易,一般由服务器对文件进行签名,生成一个可以直接访问的文件链接,用户可以通过这个链接进行预览以及下载。当然,强大的阿里云已经给我们封装好这个接口了,我们只要进行一些参数配置,调用这个SDK就能完成这个功能。
@Override
public String listExportSignedUrl(String key) {
// 指定过期时间为1小时
long expireEndTime = System.currentTimeMillis() + 1000 * 60 * 60 ;
Date expiration = new Date(expireEndTime);
//获取文件签名URL
GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(ALIYUN_OSS_BUCKET_NAME, key, HttpMethod.GET);
req.setExpiration(expiration);
URL signedUrl = ossClient.generatePresignedUrl(req);
return signedUrl.toString();
}
}
-
oss其他功能接口
/** * 搜索指定前缀的文件 */ @Override public void listFiles() { ObjectListing objectListing = ossClient.listObjects(ALIYUN_OSS_BUCKET_NAME, "sob_5e098e893aba463c8146096f0ddda99920181114113228/5e098e893aba463c8146096f0ddda999"); List<OSSObjectSummary> sums = objectListing.getObjectSummaries(); for (OSSObjectSummary s : sums) { System.out.println("\t" + s.getKey()); } } /** * 图片缩小、放大、裁剪等编辑处理 * @param fileName */ @Override public void getScaledImage(String fileName) { // 设置图片处理样式。 String style = "image/resize,m_lfit,w_50,h_50"; // 指定过期时间为10分钟。 Date expiration = new Date(System.currentTimeMillis() + 1000 * 60 * 10 ); GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(ALIYUN_OSS_BUCKET_NAME, fileName, HttpMethod.GET); req.setExpiration(expiration); req.setProcess(style); URL signedUrl = ossClient.generatePresignedUrl(req); } /** * 设置文件生命周期(定时自动删除) */ @Override public void setFileLifeCycle() { SetBucketLifecycleRequest request = new SetBucketLifecycleRequest(ALIYUN_OSS_BUCKET_NAME); // 设置规则ID和文件前缀。 String ruleId0 = "rule0"; String matchPrefix0 = "tmp/"; // 距最后修改时间1天后过期。 request.AddLifecycleRule(new LifecycleRule(ruleId0, matchPrefix0, LifecycleRule.RuleStatus.Enabled, 1)); ossClient.setBucketLifecycle(request); }
总结
总之,利用OSS可以轻松解放我们服务器压力,我们除了利用它进行文件上传下载、还可以用来作为图床处理(如电商商品图片存储),而且阿里云OSS经过实践的检验、优化后的速度十分快,非常适合中小企业使用。