OSS

273 阅读4分钟

最近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经过实践的检验、优化后的速度十分快,非常适合中小企业使用。