博主最近接入阿里云的OSS对象存储,踩了两个很无语的坑。官方文档地址

  1. 问题一
    文件上传报错SignatureDoesNotMatch:The request signature we calculated does not match the signature you provided. Check your key and signing method
  2. 问题二
    object_name问题,加上/object_name后上传失败。(这里的路径不能以 ‘/’ 开头,需要以 '路径/路径/路径…/文件名称.后缀’格式)

排查了跨域设置(没问题),排查了防盗链(没问题)。最后访问官方给的问题排查页面,也没有找到问题。官方问题参考文档地址

最后联系阿里技术才摸到问题

总结一下上述问题,在接入COS时
其一:COS创建授权链接时,不需要设置’Content-type’,而OSS是必须要设置的。
其二:COS配置project_name时,需要以’ / '开头,而OSS不能以’ / '开头。


下面会以Java+前端axios为例进行OSS集成。通过调用后端接口获取授权上传链接,前端使用授权链接进行文件上传。

优点:使用授权后,上传是通过前端上传的,将流量压力全部交给了对象存储服务器,不再需要将压力放在业务服务器上面。OSS服务器的流量费用比业务服务器等便宜的多。

  • 加入maven依赖
<!--阿里OSS对象存储-->
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.10.2</version>
</dependency>
  • 写入配置到yml
  aliOss:
    endpoint: https://oss-cn-beijing.aliyuncs.com # 上传到的OSS地址
    access_key_id: 你的账号keyid# 主账户的keyId
    access_key_secret: 你账号的keySecret
    bucket_name: 你创建的bucket的name
    path: data 你要存储的路径(可选)
  • Config实体

@Component
public class AliOssConfig {

    public static String endpoint;

    @Value("${sdk.aliOss.endpoint}")
    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }

    public static String accessKeyId;

    @Value("${sdk.aliOss.access_key_id}")
    public void setAccessKeyId(String accessKeyId) {
        this.accessKeyId = accessKeyId;
    }

    public static String accessKeySecret;

    @Value("${sdk.aliOss.access_key_secret}")
    public void setAccessKeySecret(String accessKeySecret) {
        this.accessKeySecret = accessKeySecret;
    }

    public static String bucketName;

    @Value("${sdk.aliOss.bucket_name}")
    public void setBucketName(String bucketName) {
        this.bucketName = bucketName;
    }

    public static String path;

    @Value("${sdk.aliOss.path}")
    public void setPath(String path) {
        this.path = path;
    }
}

  • 公共实体
@Data
public class AliOssPublicEntity {

    private String endpoint;

    private String accessKeyId;

    private String accessKeySecret;

    private String bucketName;

    private String objectKey;

    public static AliOssPublicEntity build(String objectKey) {
        AliOssPublicEntity entity = new AliOssPublicEntity();
        entity.setEndpoint(AliOssConfig.endpoint);
        entity.setAccessKeyId(AliOssConfig.accessKeyId);
        entity.setAccessKeySecret(AliOssConfig.accessKeySecret);
        entity.setBucketName(AliOssConfig.bucketName);
        entity.setObjectKey(objectKey);
        return entity;
    }
}

  • 工具类
package com.ych.SDK.alibaba.ossSDK;

import com.aliyun.oss.HttpMethod;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.GeneratePresignedUrlRequest;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import com.ych.SDK.alibaba.ossSDK.entity.AliOssPublicEntity;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Created with IntelliJ IDEA.
 * Author: Usopp.tsui
 * Date: 2021/1/20
 * Time: 11:46
 * Description:阿里云OSS工具类
 */
@Component
public class AliOssUtil {

    /**
     * 文件直传
     *
     * @param objectKey   上传路径
     * @param inputStream 上传流
     * @throws RuntimeException
     */
    public void fileUpload(String objectKey, InputStream inputStream) throws RuntimeException {
        Map map = getCommon(objectKey);
        OSS ossClient = null;
        try {
            ossClient = (OSS) map.get(0);
            AliOssPublicEntity model = (AliOssPublicEntity) map.get(1);
            if (ossClient.doesObjectExist(model.getBucketName(), model.getObjectKey())) {
                throw new RuntimeException("此文件重名,请更改文件名重试!");
            }
            PutObjectRequest putObjectRequest = new PutObjectRequest(model.getBucketName(), model.getObjectKey(), inputStream);
            PutObjectResult putObjectResult = ossClient.putObject(putObjectRequest);
            String eTag = putObjectResult.getETag();
            if (StringUtils.isBlank(eTag)) {
                throw new RuntimeException("文件直传失败");
            }
        } catch (Exception e) {
            throw new RuntimeException("文件直传失败:" + e.getMessage());
        } finally {
            ossClient.shutdown();
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * OSS获取下载签名URL
     *
     * @param objectKey 文件对象key
     * @return 签名URL
     */
    public String getOssObjectDownAuthUrl(String objectKey) throws RuntimeException {
        Map map = getCommon(objectKey);
        OSS ossClient = null;
        try {
            ossClient = (OSS) map.get(0);
            AliOssPublicEntity model = (AliOssPublicEntity) map.get(1);
            GeneratePresignedUrlRequest req =
                    new GeneratePresignedUrlRequest(model.getBucketName(), model.getObjectKey(), HttpMethod.GET);
            //这里设置签名在半个小时后过期
            Date expireDate = new Date(System.currentTimeMillis() + 30L * 60L * 1000L);
            req.setExpiration(expireDate);
            URL url = ossClient.generatePresignedUrl(req);
            String urlStr = url.toString();
            return urlStr;
        } catch (Exception e) {
            throw new RuntimeException("获取下载签名URL失败");
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * OSS获取下载签名URL
     *
     * @param objectKey  文件对象key
     * @param expireTime 当前时间加多少毫秒后过期,过期时间(毫秒)
     * @return 签名URL
     */
    public String getOssObjectDownAuthUrl(String objectKey, long expireTime) throws RuntimeException {
        Map map = getCommon(objectKey);
        OSS ossClient = null;
        try {
            ossClient = (OSS) map.get(0);
            AliOssPublicEntity model = (AliOssPublicEntity) map.get(1);
            GeneratePresignedUrlRequest req =
                    new GeneratePresignedUrlRequest(model.getBucketName(), model.getObjectKey(), HttpMethod.GET);
            //这里设置签名在半个小时后过期
            Date expireDate = new Date(System.currentTimeMillis() + expireTime);
            req.setExpiration(expireDate);
            URL url = ossClient.generatePresignedUrl(req);
            String urlStr = url.toString();
            return urlStr;
        } catch (Exception e) {
            throw new RuntimeException("获取下载签名URL失败");
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * OSS获取上传签名URL
     *
     * @param objectKey 文件对象key
     * @return 签名URL
     */
    public String getOssObjectUploadAuthUrl(String objectKey) throws RuntimeException {
        Map map = getCommon(objectKey);
        OSS ossClient = null;
        try {
            ossClient = (OSS) map.get(0);
            AliOssPublicEntity model = (AliOssPublicEntity) map.get(1);
            if (ossClient.doesObjectExist(model.getBucketName(), model.getObjectKey())) {
                throw new RuntimeException("此文件重名,请更改文件名重试!");
            }
            Date expirationTime = new Date(System.currentTimeMillis() + 30L * 60L * 1000L);
            GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(model.getBucketName(), model.getObjectKey(), HttpMethod.PUT);
            request.setExpiration(expirationTime);
            //必须要!!!!!!,而且前端上传时,也需要在header里面设置,content-type为"application/octet-stream"
            request.setContentType("application/octet-stream");
            URL url = ossClient.generatePresignedUrl(request);
            String urlstr = url.toString();
            return urlstr;
        } catch (Exception e) {
            throw new RuntimeException("获取上传签名URL失败" + e.getMessage());
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * 删除存储对象
     *
     * @param objectKey 文件对象key
     * @return 签名URL
     */
    public void deleteObject(String objectKey) throws RuntimeException {
        Map map = getCommon(objectKey);
        OSS ossClient = null;
        try {
            ossClient = (OSS) map.get(0);
            AliOssPublicEntity model = (AliOssPublicEntity) map.get(1);
            // 指定对象所在的存储桶
            ossClient.deleteObject(model.getBucketName(), model.getObjectKey());
        } catch (RuntimeException clientException) {
            throw new RuntimeException("删除存储对象失败");
        } finally {
            ossClient.shutdown();
        }
    }

    /**
     * 绝对路径更换为相对路径
     *
     * @param url 绝对路径
     * @return 相对路径
     */
    public String getRelativePath(String url) {
        url = url.substring(url.indexOf(".com") + 5, url.indexOf("?"));
        return url;
    }

    /**
     * client公共参数
     *
     * @param objectKey
     * @return
     */
    private Map getCommon(String objectKey) {
        AliOssPublicEntity entity = AliOssPublicEntity.build(objectKey);
        OSS ossClient = new OSSClientBuilder().build(entity.getEndpoint(), entity.getAccessKeyId(), entity.getAccessKeySecret());
        Map map = new HashMap();
        map.put(0, ossClient);
        map.put(1, entity);
        return map;
    }
}

前端代码:

axios({
		method: 'put',
		url: fileUrl,
		data: file.file,
		headers: {
			//必须要!!!
			'Content-Type': 'application/octet-stream' 
		}
	}).then(() => {
	// 成功后你要进行的操作
	})

阿里云上OSS配置:

  1. 进入控制台
  2. 新增一个Bucket (这里的name就会用作你的yml配置中的name)
    在这里插入图片描述
  3. 进入bucket设置跨域(必须设置,不然会报没有权限访问
  4. 其他设置:根据个人需要进行防盗链等的设置

至此,整合接入完成,传入以后进行上传即可。

附上Controller:

package com.ych.modules.cms.authorInfc.fileAuth.controller;

import com.ych.SDK.alibaba.ossSDK.AliOssUtil;
import com.ych.SDK.alibaba.ossSDK.config.AliOssConfig;
import com.ych.redis.RedisUtil;
import com.ych.redis.config.RedisKeyConfig;
import com.ych.utils.TLMap;
import com.ych.utils.UUID;
import com.ych.utils.serviceReturn.R;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;


@Api(tags = {"CMS--文件上传相关"}, description = "CMS--文件上传相关")
@RestController
@RequestMapping("/cms/auth/fileAuth")
public class FileAuthController {
    @Autowired
    private              RedisUtil        redisUtil;
    @Autowired
    private              AliOssUtil       aliOssUtil;
    private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd");

    @ApiOperation(value = "获取文件上传地址", notes = "授权地址", position = 1)
    @PostMapping("/getUploadUrl")
    public R<String> getUploadUrl(@RequestBody String fileName) {
        try {
            String userId = TLMap.getUserId();//可空
            String suffix = fileName.substring(fileName.lastIndexOf("."));//后缀名
            String newFileName = UUID.randomUUID() + suffix; //重新用uuid命名
            String path;
            if (StringUtils.isBlank(userId)) {
                path = AliOssConfig.path + "/public/" + sdf.format(new Date()) + "/" + newFileName;
            } else {
                path = AliOssConfig.path + "/private/" + userId + "/" + sdf.format(new Date()) + "/" + newFileName;
            }
            String url = aliOssUtil.getOssObjectUploadAuthUrl(path);
            String key = RedisKeyConfig.fileTokenKey + path;
            redisUtil.redisTemplate().opsForValue().set(key, key);
            redisUtil.redisTemplate().expire(key, 3600, TimeUnit.SECONDS);//30分钟
            return new R(0, "授权成功", url);
        } catch (RuntimeException e) {
            return new R(1, e.getMessage(), null);
        } catch (Exception e) {
            return new R(2, "系统未知异常" + e.getMessage(), null);
        }
    }

    @ApiOperation(value = "* 马上获取文件下载路径", notes = "条件:首先调用过上传授权接口", position = 2)
    @PostMapping("/getDownloadUrl")
    @ApiImplicitParam(name = "path", value = "绝对路径", required = true, dataType = "String")
    public R<String> getFileUrl(@RequestBody String path) {
        path = aliOssUtil.getRelativePath(path);//转换成相对路径
        String key = RedisKeyConfig.fileTokenKey + path;
        try {
            String one = (String) redisUtil.redisTemplate().opsForValue().get(key);
            if (StringUtils.isBlank(one)) {
                throw new RuntimeException("未授权,获取下载连接失败");
            }
            String url = aliOssUtil.getOssObjectDownAuthUrl(path);
            if (StringUtils.isBlank(url)) {
                throw new RuntimeException("地址获取失败");
            }
            redisUtil.redisTemplate().delete(key);
            return new R(0, "地址获取成功", url);
        } catch (Exception e) {
            return new R(1, e.getMessage(), null);
        }
    }
}

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐