目录
开始
OSS 相关术语须知
阿里云 OSS 开通
阿里云 OSS 使用
官方文档教程
实战开发
阿里云 OSS 自动配置
环境配置
实战开发
服务端签名直传
概述
代码实现
开始
OSS 相关术语须知
中文 | 英文 | 说明 |
存储空间 | Bucket | 存储空间是您用于存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。(一般来讲,一个项目一个 Bucket) |
对象/文件 | Object | 对象是OSS存储数据的基本单元,也被称为OSS的文件。对象由元数据(Object Meta)、用户数据(Data)和文件名(Key)组成。对象由存储空间内部唯一的Key来标识。 |
地域 | Region | 地域表示OSS的数据中心所在物理位置。您可以根据费用、请求来源等综合选择数据存储的地域。详情请查看OSS已经开通的Region。 |
访问域名 | Endpoint | Endpoint表示OSS对外服务的访问域名。OSS以HTTP RESTful API的形式对外提供服务,当访问不同地域的时候,需要不同的域名。通过内网和外网访问同一个地域所需要的域名也是不同的。具体的内容请参见各个Region对应的Endpoint。 |
访问密钥 | AccessKey | AccessKey,简称AK,指的是访问身份验证中用到的AccessKeyId和AccessKeySecret。OSS通过使用AccessKeyId和AccessKeySecret对称加密的方法来验证某个请求的发送者身份。AccessKeyId用于标识用户,AccessKeySecret是用户用于加密签名字符串和OSS用来验证签名字符串的密钥,其中AccessKeySecret 必须保密。 |
阿里云 OSS 开通
a)进入 阿里云官网,在搜索框中输入 OSS ,点击 “对象存储 OSS”
b)点击立即开通
c)点击管理控制台
d)创建一个 Bucket
阿里云 OSS 使用
官方文档教程
a)先创建 AccessKey(之后通过 OSS 上传文件都需要用到这里的 AccessKeyId 和 AccessKeySecret)
Ps: 子用户创建好之后,就可以拿到 AccessKeyId 和 AccessKeySecret,但是这个东西只能查看一次,所以一定要自己保管好!
接着还需要给这个子用户开通 Bucket 访问权限,如下:
b)API 文档教程
实战开发
a)引入依赖
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.17.4</version>
</dependency>
如果使用的是Java 9及以上的版本,则需要添加JAXB相关依赖。添加JAXB相关依赖示例代码如下:
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>
<dependency>
<groupId>javax.activation</groupId>
<artifactId>activation</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>jaxb-runtime</artifactId>
<version>2.3.3</version>
</dependency>
b)代码如下:
@SpringBootTest(classes = [ProductApplication::class])
class OSSTest {
@Test
fun test() {
//endpoint 地域节点(bucket 的 概览中可以找到)
val endpoint = "oss-cn-cnmdb.aliyuncs.com"
val accessKeyId = "JLUOIUsuoUSobPUnoPJSF%&YTFSkl{op6mJvC"
val accessKeySecret = "uJOIU989hUNIsadfjhoUIOGE1y42BJIOPHsfN"
//创建 OSSClient 实例
val ossClient = OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret)
//上传文件流
val inputStream = FileInputStream("D:/tmp/滑稽.gif")
ossClient.putObject("gulimall-byd", "滑稽.gif", inputStream)
//关闭 OSSClient
ossClient.shutdown()
println("上传完成")
}
}
运行之后,就可以看到 OSS 上传成功,通过 URL 就可以直接访问,如下:
阿里云 OSS 自动配置
环境配置
本来想使用 SpringCloudAlibaba OSS 的,但是发现这玩意在 2023 版本里根本没有... 就自己写自动配置吧~
a)配置文件如下:
alibaba:
oss:
access-key: JLUOIUsuoUSobPUnoPJSF%&YTFSkl{op6mJvC
secret-key: uJOIU989hUNIsadfjhoUIOGE1y42BJIOPHsfN
endpoint: oss-cn-cnmdb.aliyuncs.com
b)自动配置如下:
import com.aliyun.oss.OSS
import com.aliyun.oss.OSSClientBuilder
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
@Configuration
class OSSConfig {
@Value("\${alibaba.oss.access-key}")
private lateinit var accessKey: String
@Value("\${alibaba.oss.secret-key}")
private lateinit var secretKey: String
@Value("\${alibaba.oss.endpoint}")
private lateinit var endpoint: String
@Bean
fun ossClient(): OSS = OSSClientBuilder()
.build(endpoint, accessKey, secretKey) //注意参数顺序
}
实战开发
import com.aliyun.oss.OSS
import jakarta.annotation.Resource
import org.cyk.gulimall.product.ProductApplication
import org.junit.jupiter.api.Test
import org.springframework.boot.test.context.SpringBootTest
import java.io.FileInputStream
@SpringBootTest(classes = [ProductApplication::class])
class OSSTest {
@Resource
private lateinit var ossClient: OSS
@Test
fun test() {
//上传文件流
val inputStream = FileInputStream("D:/tmp/滑稽.gif")
ossClient.putObject("gulimall-cyk", "滑稽.gif", inputStream)
println("上传完成")
}
}
服务端签名直传
概述
传统的,我们有两种方式将图片上传到 OSS:
a)前端请求 -> 后端服务器 -> OSS
好处:在服务端上传,更加安全.
坏处:给服务器带来压力.
b)直接写在前端 js 代码中
好处:效率高,不用给服务器带来额外压力.
坏处:危险,用户直接可以看得到密钥信息.
因此最好的方式就是 服务端签名直传:用户直接去服务器请求获取上传签名(账号密码生成的防伪签名,一般有过期时间),校验安全,服务器就返回该用户的防伪签名,然后用户就可以拿着签名和要上传的文件,通过表单直接上传到 OSS 中.
代码实现
a)后端如下:
@RestController
@RequestMapping("/third/oss")
class OSSApi(
private val ossClient: OSS
) {
@Value("\${alibaba.oss.bucket}")
private lateinit var bucket: String
@Value("\${alibaba.oss.endpoint}")
private lateinit var endpoint: String
@Value("\${alibaba.oss.access-key}")
private lateinit var accessKey: String
@RequestMapping("/policy")
fun policy(): Map<String, String> {
val host = "https://$bucket.$endpoint"
//这里可以配置上传文件到 以时间为前缀的文件夹 中
val format = SimpleDateFormat("yyyy-MM-dd").format(Date())
val dir = "$format/" //用户上传文件时指定前缀
try {
val expireTime = 30
val expireEndTime = System.currentTimeMillis() + expireTime * 1000
val expiration = Date(expireEndTime)
// PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。
val policyConds = PolicyConditions();
policyConds.run {
addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000);
addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir);
}
val postPolicy = ossClient.generatePostPolicy(expiration, policyConds);
val binaryData = postPolicy.toByteArray(Charset.forName("utf-8"))
val accessId = accessKey
val encodedPolicy = BinaryUtil.toBase64String(binaryData);
val postSignature = ossClient.calculatePostSignature(postPolicy);
val respMap = LinkedHashMap<String, String>()
respMap.run {
put("accessid", accessId);
put("policy", encodedPolicy);
put("signature", postSignature);
put("dir", dir);
put("host", host);
put("expire", (expireEndTime / 1000).toString());
}
return respMap
} catch (e: Exception) {
e.printStackTrace()
}
return mapOf("msg" to "fail")
}
}
b)前端如下:
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>OSS web直传</title>
<meta
name="viewport"
content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
/>
</head>
<body>
<h2>OSS web直传---在服务端用Node.js签名</h2>
<ol>
<li>Bucket必须设置跨域,且允许Methods必须选中POST,否则无法执行表单上传。</li>
</ol>
<br />
<div>
<input type="file" id="fileInput" name="fileInput" />
<input type="button" value="开始上传" onclick="upload()" />
</div>
<script>
function upload() {
const tokenUrl = "http://127.0.0.1:10010/third/oss/policy";
fetch(tokenUrl).then(async (res) => {
const { policy, signature, accessid, host, dir, stsToken } =
await res.json();
let formData = new FormData();
formData.append("success_action_status", "200"); // 指定成功上传时,服务端返回状态码200,默认返回204。
formData.append("policy", policy);
formData.append("signature", signature);
formData.append("OSSAccessKeyId", accessid);
if (stsToken) formData.append("x-oss-security-token", stsToken);
const files = document.getElementById("fileInput").files;
if (files.length === 0) {
alert("请选择文件");
return;
}
formData.append("key", dir + files[0].name); // 文件名
formData.append("file", files[0]); // file必须为最后一个表单域
const param = {
method: "POST",
body: formData,
};
fetch(host, param)
.then((data) => {
console.log(data);
alert("上传成功");
})
.catch((error) => {
console.error("Error:", error);
});
});
}
</script>
</body>
</html>
Ps:上述代码中的跨域问题,前面的步骤处理过~ 忘了,可以再往前看看
c)效果演示
返回阿里云,就可以看到如下: