22.SpringCloud實戰項目-整合OSS對象存儲
- 2020 年 4 月 28 日
- 筆記
- 00-SpringCloud實戰項目
SpringCloud實戰項目全套學習教程連載中
PassJava 學習教程
簡介
- PassJava-Learning項目是PassJava(佳必過)項目的學習教程。對架構、業務、技術要點進行講解。
- PassJava 是一款Java
面試刷題
的開源系統,可以用零碎時間利用小程式查看常見面試題,夯實Java基礎。 - PassJava 項目可以教會你如何搭建SpringBoot項目,Spring Cloud項目
- 採用流行的技術,如 SpringBoot、MyBatis、Redis、 MySql、 MongoDB、 RabbitMQ、Elasticsearch,採用Docker容器化部署。
更好的閱讀體驗
文檔連載目錄
- 打造一款 刷Java 知識的小程式
- 打造一款 刷Java 知識的小程式(二)
- 01.五分鐘搞懂分散式基礎概念
- 02.快速搭建Linux環境-運維必備
- 03.配置虛擬機網路
- 04.安裝Docker
- 05.Docker安裝mysql
- 06.Docker安裝redis
- 07.本地開發環境配置
- 08.配置Git
- 09.初始化項目和添加微服務
- 10.PassJava-微服務劃分圖
- 11.初始化資料庫和表
- 12.搭建管理後台
- 13.自動生成前後端程式碼
- 14.整合MyBatis-Plus實現CRUD
- 15.生成所有微服務的CRUD程式碼
- 16.Spring Cloud Alibaba 組件簡介
- 17.SpringCloud整合Alibaba-Nacos組件
- 18.SpringCloud整合OpenFeign組件
- 19.SpringCloud整合Alibaba-Nacos配置中心
- 20.SpringCloud整合Gateway網關
- 21.管理後台-題目類型功能
- 22.SpringCloud整合OSS對象存儲
整合OSS對象存儲
一、緣起
文件上傳在系統中用的很頻繁,所以我們需要將上傳的文件進行存儲,傳統的將文件上傳到本機已不適用分散式系統。自己搭建文件伺服器有複雜性和維護成本。所以我們可以採用市面上成熟的文件存儲服務,如阿里雲的OSS對象存儲服務。
每個 OSS 的用戶都會用到上傳服務。Web 端常見的上傳方法是用戶在瀏覽器或 APP 端上傳文件到應用伺服器,應用伺服器再把文件上傳到 OSS。具體流程如下圖所示。
和數據直傳到 OSS 相比,以上方法有三個缺點:
- 上傳慢:用戶數據需先上傳到應用伺服器,之後再上傳到OSS。網路傳輸時間比直傳到OSS多一倍。如果用戶數據不通過應用伺服器中轉,而是直傳到OSS,速度將大大提升。而且OSS採用BGP頻寬,能保證各地各運營商之間的傳輸速度。
- 擴展性差:如果後續用戶多了,應用伺服器會成為瓶頸。
- 費用高:需要準備多台應用伺服器。由於OSS上傳流量是免費的,如果數據直傳到OSS,不通過應用伺服器,那麼將能省下幾台應用伺服器。
二、技術方案
服務端簽名後直傳
背景
採用JavaScript客戶端直接簽名時,AccessKeyID和AcessKeySecret會暴露在前端頁面,因此存在嚴重的安全隱患。因此,OSS提供了服務端簽名後直傳的方案。
原理介紹
服務端簽名後直傳的原理如下:
- 用戶發送上傳Policy請求到應用伺服器。
- 應用伺服器返回上傳Policy和簽名給用戶。
- 用戶直接上傳數據到OSS。
三、實現案例
1.開通阿里雲OSS
-
創建Bucket 存儲桶
-
獲取accesskey id和secret
-
分配許可權
分配 管理對象存儲服務(OSS)許可權
2.使用OSS SDK
1) 安裝SDK
在Maven項目中加入依賴項
//help.aliyun.com/document_detail/32009.html?spm=a2c4g.11186623.6.769.2c5145dc4TUgTa
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
<version>3.8.0</version>
</dependency>
2) 上傳文件到OSS
@Test
void testUploadByOss() throws FileNotFoundException {
// Endpoint以杭州為例,其它Region請按實際情況填寫。
String endpoint = "//oss-cn-beijing.aliyuncs.com";
// 阿里雲主帳號AccessKey擁有所有API的訪問許可權,風險很高。強烈建議您創建並使用RAM帳號進行API訪問或日常運維,請登錄 //ram.console.aliyun.com 創建RAM帳號。
String accessKeyId = "LTAI4G3KxBJ26EUbWsenmqhP";
String accessKeySecret = "RHtADVlvlKJvVBQnFNNvnne9p4NwnA";
String bucketName = "passjava";
// <yourObjectName>上傳文件到OSS時需要指定包含文件後綴在內的完整路徑,例如abc/efg/123.jpg。
String localFile = "C:\\Users\\Administrator\\Pictures\\coding_java.png";
String fileKeyName = "coding_java.png";
// 創建OSSClient實例。
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
InputStream inputStream = new FileInputStream(localFile);
ossClient.putObject(bucketName, fileKeyName, inputStream);
// 關閉OSSClient。
ossClient.shutdown();
}
3.整合Spring Cloud Alicloud OSS
1) passjava-common項目引入spring-cloud-starter-alicloud-oss依賴
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alicloud-oss</artifactId>
</dependency>
2) 配置alicloud oss
spring:
cloud:
alicloud:
access-key: xxxx
secret-key: xxxx
oss:
endpoint: oss-cn-beijing.aliyuncs.com
3)測試上傳
@Autowired
OSSClient ossClient;
@Test
void testUploadByAlicloudOss() throws FileNotFoundException {
String bucketName = "passjava";
String localFile = "C:\\Users\\Administrator\\Pictures\\coding_java.png";
String fileKeyName = "coding_java.png";
InputStream inputStream = new FileInputStream(localFile);
ossClient.putObject(bucketName, fileKeyName, inputStream);
ossClient.shutdown();
}
4.獲取服務端簽名
4.1 準備工作:
- 創建一個第三方服務passjava-thirdparty
- 引入passjava-common模組,並且排除mybatis-plus依賴
<dependency>
<groupId>com.jackson0714.passjava</groupId>
<artifactId>passjava-common</artifactId>
<version>0.0.1-SNAPSHOT</version>
<exclusions>
<exclusion>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</exclusion>
</exclusions>
</dependency>
- 配置服務發現和埠
spring:
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
application:
name: passjava-thirdparty
server:
port: 14000
- 配置配置中心
spring.application.name=passjava-thirdparty
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=passjava-thirdparty
spring.cloud.nacos.config.extension-configs[0].data-id=oss.yml
spring.cloud.nacos.config.extension-configs[0].group=DEFAULT_GROUP
spring.cloud.nacos.config.extension-configs[0].refresh=true
- 配置Nacos命名空間和oss.yml
spring:
cloud:
alicloud:
access-key: LTAI4G3KxBJ26EUbWsenmqhP
secret-key: RHtADVlvlKJvVBQnFNNvnne9p4NwnA
oss:
endpoint: oss-cn-beijing.aliyuncs.com
- 開啟服務發現
@EnableDiscoveryClient
@EnableDiscoveryClient
@SpringBootApplication
public class PassjavaThirdpartyApplication {
public static void main(String[] args) {
SpringApplication.run(PassjavaThirdpartyApplication.class, args);
}
}
4.2 獲取簽名類
@RestController
@RequestMapping("/thirdparty/v1/admin/oss")
public class OssController {
@Autowired
OSS ossClient;
@Value("${spring.cloud.alicloud.access-key}")
private String accessId;
@Value("${spring.cloud.alicloud.secret-key}")
private String accessKey;
@Value("${spring.cloud.alicloud.oss.endpoint}")
private String endpoint;
@Value("${spring.cloud.alicloud.oss.bucket}")
private String bucket;
@RequestMapping("/getPolicy")
public Map<String, String> getPolicy() {
String host = "//" + bucket + "." + endpoint; // host的格式為 bucketname.endpoint
// callbackUrl為 上傳回調伺服器的URL,請將下面的IP和Port配置為您自己的真實資訊。
// String callbackUrl = "//88.88.88.88:8888";
String formatDate = new SimpleDateFormat("yyyy-MM-dd").format(new Date());
String dir = formatDate + "/"; // 用戶上傳文件時指定的前綴。
Map<String, String> respMap = new LinkedHashMap<String, String>();
try {
long expireTime = 30;
long expireEndTime = System.currentTimeMillis() + expireTime * 1000;
Date expiration = new Date(expireEndTime);
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.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));
} catch (Exception e) {
// Assert.fail(e.getMessage());
System.out.println(e.getMessage());
} finally {
ossClient.shutdown();
}
return respMap;
}
}
測試介面
//localhost:14000/api/thirdparty/v1/admin/oss/getPolicy
{
"accessid": "LTAI4G3KxBJ26EUbWsenmqhP",
"policy": "eyJleHBpcmF0aW9uIjoiMjAyMC0wNC0yOFQwMjozMzowNy42NzNaIiwiY29uZGl0aW9ucyI6W1siY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjAwMF0sWyJzdGFydHMtd2l0aCIsIiRrZXkiLCIyMDIwLTA0LTI4LyJdXX0=",
"signature": "pfn4cggFTMMNqTs+qUnDN5c+k5M=",
"dir": "2020-04-28/",
"host": "//passjava.oss-cn-beijing.aliyuncs.com",
"expire": "1588041187"
}
4.3 配置網關路由
因為前端頁面配置的統一訪問路徑是//localhost:8060/api/,所以需要將訪問thirdparty的服務通過網關路由到thirdparty服務
將請求
//localhost:8060/api/thirdparty/v1/admin/oss/getPolicy
轉發到
//localhost:14000/api/thirdparty/v1/admin/oss/getPolicy
配置網關:
spring:
cloud:
gateway:
routes:
- id: route_thirdparty # 題目微服務路由規則
uri: lb://passjava-thirdparty # 負載均衡,將請求轉發到註冊中心註冊的assjava-thirdparty服務
predicates: # 斷言
- Path=/api/thirdparty/** # 如果前端請求路徑包含 api/thirdparty,則應用這條路由規則
filters: #過濾器
- RewritePath=/api/(?<segment>.*),/$\{segment} # 將跳轉路徑中包含的api替換成空
測試可以上傳成功
4.4 配置跨域訪問
配置跨域訪問,所有post請求都可以跨域訪問
4.5 Web端上傳組件
- 單文件上傳組件
singleUpload.vue
<template>
<div>
<el-upload
action="//passjava.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 => {
_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;
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>
- 獲取簽名的JS文件
import http from '@/utils/httpRequest.js'
export function policy () {
return new Promise((resolve) => {
http({
url: http.adornUrl('/thirdparty/v1/admin/oss/getPolicy'),
method: 'get',
params: http.adornParams({})
}).then(({ data }) => {
resolve(data)
})
})
}
- 使用單文件上傳組件
使用上傳圖片組件
<el-form-item label="類型logo路徑" prop="logoUrl">
<single-upload v-model="dataForm.logoUrl"></single-upload>
</el-form-item>
<script>
import SingleUpload from "@/components/upload/singleUpload" // 引入單文件上傳組件
export default {
components:{ SingleUpload }
}
</script>
上傳文件成功
下節預告
- 數據校驗
程式碼地址
//github.com/Jackson0714/PassJava-Platform