前言
Github地址:https://github.com/YuyanCai/mall-study
简单上传测试
OSS是对象存储服务,有什么用呢?把图片存储到云服务器上能让所有人都访问到!
详细操作可查官方文档,下面只写关键代码
[SDK示例 (aliyun.com)](https://help.aliyun.com/document_detail/32008.html)
一、创建子用户测试用例
官方推荐使用子账户的AccessID和SecurityID,因为如果直接给账户的AccessID和SecurityID的话,如果不小心被其他人获取到了,那账户可是有全部权限的!!!
所以这里通过建立子账户,给子账户分配部分权限实习。
这里通过子账户管理OSS的时候,要给子账户添加操控OSS资源的权限
这里是必须要做的,因为子账户默认是没有任何权限的,必须手动给他赋予权限
二、引入依赖
<dependency> <groupId>com.aliyun.oss</groupId> <artifactId>aliyun-sdk-oss</artifactId> <version>3.1.0</version> </dependency>
三、测试用例
@SpringBootTest class MallProductApplicationTests { @Test public void testUploads() throws FileNotFoundException { // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。 String endpoint = "https://oss-cn-hangzhou.aliyuncs.com"; // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。 String accessKeyId = "。。。"; String accessKeySecret = "。。。"; // 填写Bucket名称,例如examplebucket。 String bucketName = "pyy-mall"; // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。 String objectName = "2022/testPhoto.txt"; // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。 // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。 String filePath= "C:\\Users\\Jack\\Desktop\\R-C.jfif"; // 创建OSSClient实例。 OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret); try { InputStream inputStream = new FileInputStream(filePath); // 创建PutObject请求。 ossClient.putObject(bucketName, objectName, inputStream); } catch (OSSException oe) { System.out.println("Caught an OSSException, which means your request made it to OSS, " + "but was rejected with an error response for some reason."); System.out.println("Error Message:" + oe.getErrorMessage()); System.out.println("Error Code:" + oe.getErrorCode()); System.out.println("Request ID:" + oe.getRequestId()); System.out.println("Host ID:" + oe.getHostId()); } catch (ClientException ce) { System.out.println("Caught an ClientException, which means the client encountered " + "a serious internal problem while trying to communicate with OSS, " + "such as not being able to access the network."); System.out.println("Error Message:" + ce.getMessage()); } finally { if (ossClient != null) { ossClient.shutdown(); } } } }
Aliyun Spring Boot OSS
一、引入依赖
我们不是进行依赖管理了吗?为什么还要显示写出2.1.1版本
这是因为这个包没有最新的包,只有和2.1.1匹配的
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alicloud-oss</artifactId> <version>2.1.1.RELEASE</version> </dependency>
二、在配置文件中配置 OSS 服务对应的 accessKey、secretKey 和 endpoint
alicloud: access-key: xxx secret-key: xxx oss: endpoint: oss-cn-hangzhou.aliyuncs.com
三、注入OSSClient测试
@Resource private OSSClient ossClient; @Test public void testUploads() throws FileNotFoundException { // 上传文件流。 InputStream inputStream = new FileInputStream("C:\\Users\\Jack\\Desktop\\LeetCode_Sharing.png"); ossClient.putObject("pyy-mall", "2022/testPhoto2.png", inputStream); // 关闭OSSClient。 ossClient.shutdown(); System.out.println("上传完成..."); }
新模块mall-third-service
新模块存放所有的第三方服务,像短信服务、图片服务、视频服务等等
引入依赖如下:
这里去除了mp的依赖,因为引入mp就需要配置数据库服务器地址
接口开发
一、配置文件
<dependencies> <dependency> <groupId>com.caq.mall</groupId> <artifactId>mall-common</artifactId> <version>0.0.1-SNAPSHOT</version> <exclusions> <exclusion> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alicloud-oss</artifactId> <version>2.1.1.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring-boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
二、启动类添加注解
将第三方的包也注册到注册中心
- 主启动类@EnableDiscoveryClient
- application.yml配置文件如下:
#用来指定注册中心地址 spring: cloud: nacos: discovery: server-addr: localhost:8848 #nacos地址 alicloud: access-key: ... secret-key: ... oss: endpoint: oss-cn-hangzhou.aliyuncs.com bucket: pyy-mall
- bootstrap.yml文件指定注册中心
#用来指定配置中心地址 spring: application: name: mall-third-service cloud: nacos: config: server-addr: 127.0.0.1:8848 namespace: 2bbd2076-36c8-44b2-9c2e-17ce5406afb7 file-extension: yaml extension-configs: - data-id: mall-third-service.yml group: DEFAULT_GROUP refresh: true
三、测试
@SpringBootTest class MallThirdServiceApplicationTests { @Resource OSSClient ossClient; @Test void contextLoads() throws FileNotFoundException { // 上传文件流。 InputStream inputStream = new FileInputStream("C:\\Users\\Jack\\Desktop\\LeetCode_Sharing.png"); ossClient.putObject("pyy-mall", "2022/testPhoto3.png", inputStream); // 关闭OSSClient。 ossClient.shutdown(); System.out.println("上传完成..."); } }
没问题!
四、改善上传
服务端签名后直传
采用JavaScript客户端直接签名(参见JavaScript客户端签名直传)时,AccessKeyID和AcessKeySecret会暴露在前端页面,因此存在严重的安全隐患。
因此,OSS提供了服务端签名后直传的方案。
- 向服务器获取到签名,再去请求oss服务器
controller如下:
这里定义返回类为R是为了统一返回结果,到后面也会用到
package com.caq.mall.thirdservice.controller; @RestController @RequestMapping("oss") public class OssController { @Resource private OSS ossClient; @Value("${spring.cloud.alicloud.oss.endpoint}") public String endpoint; @Value("${spring.cloud.alicloud.oss.bucket}") public String bucket; @Value("${spring.cloud.alicloud.access-key}") public String accessId; private final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd"); @GetMapping("/policy") public R getPolicy(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String host = "https://" + bucket + "." + endpoint; // host的格式为 bucketname.endpoint // callbackUrl为上传回调服务器的URL,请将下面的IP和Port配置为您自己的真实信息。 // String callbackUrl = "http://88.88.88.88:8888"; String dir = format.format(new Date())+"/"; // 用户上传文件时指定的前缀。以日期格式存储 // 创建OSSClient实例。 Map<String, String> respMap= null; try { long expireTime = 30; long expireEndTime = System.currentTimeMillis() + expireTime * 1000; Date expiration = new Date(expireEndTime); // PostObject请求最大可支持的文件大小为5 GB,即CONTENT_LENGTH_RANGE为5*1024*1024*1024。 PolicyConditions policyConds = new PolicyConditions(); policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0, 1048576000); policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, dir); String postPolicy = ossClient.generatePostPolicy(expiration, policyConds);//生成协议秘钥 byte[] binaryData = postPolicy.getBytes("utf-8"); String encodedPolicy = BinaryUtil.toBase64String(binaryData); String postSignature = ossClient.calculatePostSignature(postPolicy); respMap = new LinkedHashMap<String, String>(); respMap.put("accessid", accessId); respMap.put("policy", encodedPolicy);//生成的协议秘钥 respMap.put("signature", postSignature); respMap.put("dir", dir); respMap.put("host", host); respMap.put("expire", String.valueOf(expireEndTime / 1000)); // respMap.put("expire", formatISO8601Date(expiration)); } catch (Exception e) { // Assert.fail(e.getMessage()); System.out.println(e.getMessage()); } finally { ossClient.shutdown(); } return R.ok().put("data",respMap); } }
测试这个请求,http://localhost:9988/oss/policy,成功获取
五、设置网关代理
- id: mall-third-service uri: lb://mall-third-service predicates: - Path=/api/thirdservice/** filters: - RewritePath= /api/thirdservice/(?<segment>.*),/$\{segment}
测试这个请求,http://localhost:88/api/thirdservice/oss/policy
至此,我们的功能都没问题了,那么现在就来前端的代码
singleUpload.vue
单文件上传组件
<template> <div> <el-upload action="http://gulimall-hello.oss-cn-beijing.aliyuncs.com" :data="dataObj" list-type="picture" :multiple="false" :show-file-list="showFileList" :file-list="fileList" :before-upload="beforeUpload" :on-remove="handleRemove" :on-success="handleUploadSuccess" :on-preview="handlePreview"> <el-button size="small" type="primary">点击上传</el-button> <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10MB</div> </el-upload> <el-dialog :visible.sync="dialogVisible"> <img width="100%" :src="fileList[0].url" alt=""> </el-dialog> </div> </template> <script> import {policy} from './policy' import { getUUID } from '@/utils' export default { name: 'singleUpload', props: { value: String }, computed: { imageUrl() { return this.value; }, imageName() { if (this.value != null && this.value !== '') { return this.value.substr(this.value.lastIndexOf("/") + 1); } else { return null; } }, fileList() { return [{ name: this.imageName, url: this.imageUrl }] }, showFileList: { get: function () { return this.value !== null && this.value !== ''&& this.value!==undefined; }, set: function (newValue) { } } }, data() { return { dataObj: { policy: '', signature: '', key: '', ossaccessKeyId: '', dir: '', host: '', // callback:'', }, dialogVisible: false }; }, methods: { emitInput(val) { this.$emit('input', val) }, handleRemove(file, fileList) { this.emitInput(''); }, handlePreview(file) { this.dialogVisible = true; }, beforeUpload(file) { let _self = this; return new Promise((resolve, reject) => { policy().then(response => { console.log("响应的数据",response); _self.dataObj.policy = response.data.policy; _self.dataObj.signature = response.data.signature; _self.dataObj.ossaccessKeyId = response.data.accessid; _self.dataObj.key = response.data.dir +getUUID()+'_${filename}'; _self.dataObj.dir = response.data.dir; _self.dataObj.host = response.data.host; console.log("响应的数据222。。。",_self.dataObj); resolve(true) }).catch(err => { reject(false) }) }) }, handleUploadSuccess(res, file) { console.log("上传成功...") this.showFileList = true; this.fileList.pop(); this.fileList.push({name: file.name, url: this.dataObj.host + '/' + this.dataObj.key.replace("${filename}",file.name) }); this.emitInput(this.fileList[0].url); } } } </script> <style> </style>