1、簡介
對象存儲服務(Object Storage Service,OSS)是一種海量、安全、低成本、高可靠的雲存儲服務,適合存放任務類型的文件。容量和處理能力彈性擴展,多種存儲類型供選擇,全面優化存儲成本。
2、文件存儲形式
文件存儲形式主要是存儲的位置不同,一個是存儲到我們自己的服務器,另一個是存儲到雲服務器,比如阿里雲存儲,七牛等一些雲存儲。我們系統中主要是使用雲存儲的方式,這樣是比較好擴展的,而且現在的存儲文件的服務器也比較便宜,如果放在我們本地服務器,後面存儲的文件增多了,還要擴容,比較麻煩,所以我建議放到雲存儲上,主要的對比圖如下:
3、阿里雲的存儲服務
- 第一種方式
這種上傳的方式是:直接把文件上傳到我們的服務器,再由我們服務器上傳到雲存儲上,這樣我們服務器的壓力會比較大,所以不推薦,這裏主要是說明我們以前上傳文件的形式。
- 第二種方式
這種方式是:我們要上傳文件的時候,用戶先要嚮應用服務器請求上傳簽名,然後直接上傳到雲存儲服務器上,文件不經過我們服務器,這樣服務器壓力會小很多。
服務端簽名後直傳的原理如下:
- 用戶發送上傳Policy請求到應用服務器。
- 應用服務器返回上傳Policy和簽名給用戶。
- 用戶直接上傳數據到OSS。
4、在阿里雲上申請祕鑰
具體申請祕鑰的操作查看官網文檔
我們申請完密鑰後必須當時就要記錄下來,只顯示一次,後面要用到。
一般情況下,我們需要創建一個子用戶,給他授權操作所有文件存儲的權限
5、在程序中使用
-
前提條件
應用服務器對應的域名可通過公網訪問。
確保應用服務器已經安裝
Java 1.6
以上版本(執行命令java -version
進行查看)。確保PC端瀏覽器支持JavaScript。
-
首先我們創建一個工程,這個工程專門用來集成第三方應用的工程,我起名叫hslmall-third-party
-
創建好後在pom文件中加入如下依賴
<dependencies>
<dependency>
<!--加入公共依賴-->
<groupId>com.hsl.halmall</groupId>
<artifactId>hslmall-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-starter-alicloud-oss</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<!--阿里管理 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>2.2.0.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
- 創建application.yml文件,加入如下內容
server:
port: 30000
spring:
application:
name: hslmall-third-party
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
-
在nacos註冊中心和配置中心配置相關內容
-
創建第三方集成的命名空間 thirdParty,並把生成的 862b6e4c-3c31-490f-bddf-c419e2be76b9配置到我們的bootstrap.properties文件中
-
在配置列表的thirdParty添加oss.yml配置
spring: cloud: alicloud: access-key: 自己申請的access-key secret-key: 自己申請的secret-key oss: endpoint: oss-cn-beijing.aliyuncs.com bucket: hslmall
-
-
新建bootstrap.properties文件,加入如下內容
spring.cloud.nacos.config.server-addr=127.0.0.1:8848
spring.cloud.nacos.config.namespace=862b6e4c-3c31-490f-bddf-c419e2be76b9
spring.application.name=hslmall-third-party
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
-
在啓動主程序中添加**@EnableDiscoveryClient**註解,註冊到註冊中心
-
創建一個controller
package com.hsl.hslmall.thirdparty.controller; import com.aliyun.oss.OSS; import com.aliyun.oss.common.utils.BinaryUtil; import com.aliyun.oss.model.MatchMode; import com.aliyun.oss.model.PolicyConditions; import com.hsl.hslmall.common.utils.R; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; 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.LinkedHashMap; import java.util.Map; @RequestMapping("oss") @RestController @Log4j2 public class OSSController { @Autowired OSS ossClient; @Value("${spring.cloud.alicloud.oss.endpoint}") private String endpoint; @Value("${spring.cloud.alicloud.access-key}") private String accessId; @Value("${spring.cloud.alicloud.secret-key}") private String secretKey; @Value("${spring.cloud.alicloud.oss.bucket}") private String bucket; /** * 上傳文件之前,需要先請求一個簽名,然後在直接上傳到阿里雲上 * @return */ @GetMapping("/policy") public R policy(){ //host的格式爲bucketname.endpoint String host="https://"+bucket+"."+endpoint; SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd"); //用戶上傳文件時指定的前綴 String dir=sdf.format(new Date())+"/"; Map<String,String> respMap=null; try { long expireTime=30; long expireEndTime=System.currentTimeMillis()+expireTime*1000; Date expiration=new Date(expireEndTime); PolicyConditions policyConditions=new PolicyConditions(); //設置上傳文件的大小,這個大小可以自己設定 policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE,0,1048576000); //設置保存文件的文件夾 policyConditions.addConditionItem(MatchMode.StartWith,PolicyConditions.COND_KEY,dir); String postPolicy=ossClient.generatePostPolicy(expiration,policyConditions); byte[] binaryPolicy=postPolicy.getBytes("utf-8"); String encodedPolicy= BinaryUtil.toBase64String(binaryPolicy); String postSignature=ossClient.calculatePostSignature(postPolicy); respMap=new LinkedHashMap<>(); 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){ log.error("獲取上傳文件簽名失敗,錯誤信息:"+e.getMessage()); e.printStackTrace(); } return R.ok().put("data",respMap); } }
-
我們配置網關
spring: application: name: hslmall-gateway cloud: nacos: # 配置註冊中心的地址和命名空間 discovery: server-addr: 127.0.0.1:8848 # namespace: 52a5124d-5f65-4b41-bdca-01caacb3f3c4 # 配置網關路由信息 gateway: routes: # 配置第三方服務路由 - id: third_party_route uri: lb://hslmall-third-party predicates: - Path=/api/thirdparty/** filters: - RewritePath=/api/thirdparty/(?<segment>.*), /$\{segment}
我們的訪問路徑是:localhost:88/api/thirdparty/oss/policy,啓動註冊中心,網關和第三方服務
-
前端vue使用element-ui的upoad組件
- singleUpload.vue 單文件上傳
複製這個文件內容到自己本地/components/upload/下,後面頁面會應用這個組件
<template> <div> <el-upload action="https://hslmall.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>
- singleUpload.vue 單文件上傳
-
多文件上傳
<template>
<div>
<el-upload
action="https://hslmall.oss-cn-beijing.aliyuncs.com"
:data="dataObj"
list-type="picture-card"
:file-list="fileList"
:before-upload="beforeUpload"
:on-remove="handleRemove"
:on-success="handleUploadSuccess"
:on-preview="handlePreview"
:limit="maxCount"
:on-exceed="handleExceed"
>
<i class="el-icon-plus"></i>
</el-upload>
<el-dialog :visible.sync="dialogVisible">
<img width="100%" :src="dialogImageUrl" alt />
</el-dialog>
</div>
</template>
<script>
import { policy } from "./policy";
import { getUUID } from '@/utils'
export default {
name: "multiUpload",
props: {
//圖片屬性數組
value: Array,
//最大上傳圖片數量
maxCount: {
type: Number,
default: 30
}
},
data() {
return {
dataObj: {
policy: "",
signature: "",
key: "",
ossaccessKeyId: "",
dir: "",
host: "",
uuid: ""
},
dialogVisible: false,
dialogImageUrl: null
};
},
computed: {
fileList() {
let fileList = [];
for (let i = 0; i < this.value.length; i++) {
fileList.push({ url: this.value[i] });
}
return fileList;
}
},
mounted() {},
methods: {
emitInput(fileList) {
let value = [];
for (let i = 0; i < fileList.length; i++) {
value.push(fileList[i].url);
}
this.$emit("input", value);
},
handleRemove(file, fileList) {
this.emitInput(fileList);
},
handlePreview(file) {
this.dialogVisible = true;
this.dialogImageUrl = file.url;
},
beforeUpload(file) {
let _self = this;
return new Promise((resolve, reject) => {
policy()
.then(response => {
console.log("這是什麼${filename}");
_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 => {
console.log("出錯了...",err)
reject(false);
});
});
},
handleUploadSuccess(res, file) {
this.fileList.push({
name: file.name,
// url: this.dataObj.host + "/" + this.dataObj.dir + "/" + file.name; 替換${filename}爲真正的文件名
url: this.dataObj.host + "/" + this.dataObj.key.replace("${filename}",file.name)
});
this.emitInput(this.fileList);
},
handleExceed(files, fileList) {
this.$message({
message: "最多隻能上傳" + this.maxCount + "張圖片",
type: "warning",
duration: 1000
});
}
}
};
</script>
<style>
</style>
- policy.js 引用的js
import http from '@/utils/httpRequest.js'
export function policy () {
return new Promise((resolve, reject) => {
http({
url: http.adornUrl('/thirdparty/oss/policy'),
method: 'get',
params: http.adornParams({})
}).then(({ data }) => {
resolve(data)
})
})
}
-
在我們的頁面中使用,並引用剛纔寫的組件
<template> <el-dialog :title="!dataForm.id ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible"> <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="140px"> <el-form-item label="品牌名" prop="name"> <el-input v-model="dataForm.name" placeholder="品牌名"></el-input> </el-form-item> <el-form-item label="品牌logo地址" prop="logo"> <!-- <el-input v-model="dataForm.logo" placeholder="品牌logo地址"></el-input> --> <singleUpload v-model="dataForm.logo"></singleUpload> </el-form-item> <el-form-item label="介紹" prop="descript"> <el-input v-model="dataForm.descript" placeholder="介紹"></el-input> </el-form-item> <el-form-item label="顯示狀態" prop="showStatus"> <el-switch v-model="dataForm.showStatus" :active-value="1" :inactive-value="0" active-color="#13ce66" inactive-color="#ff4949"></el-switch> <!-- <el-input v-model="dataForm.showStatus" placeholder="顯示狀態">!--> </el-form-item> <el-form-item label="檢索首字母" prop="firstLetter"> <el-input v-model="dataForm.firstLetter" placeholder="檢索首字母"></el-input> </el-form-item> <el-form-item label="排序" prop="sort"> <el-input v-model="dataForm.sort" placeholder="排序"></el-input> </el-form-item> </el-form> <span slot="footer" class="dialog-footer"> <el-button @click="visible = false">取消</el-button> <el-button type="primary" @click="dataFormSubmit()">確定</el-button> </span> </el-dialog> </template> <script> import singleUpload from '@/components/upload/singleUpload'; export default { data () { return { visible: false, dataForm: { brandId: 0, name: '', logo: '', descript: '', showStatus: '', firstLetter: '', sort: '' }, dataRule: { name: [ { required: true, message: '品牌名不能爲空', trigger: 'blur' } ], logo: [ { required: true, message: '品牌logo地址不能爲空', trigger: 'blur' } ], descript: [ { required: true, message: '介紹不能爲空', trigger: 'blur' } ], showStatus: [ { required: true, message: '顯示狀態不能爲空', trigger: 'blur' } ], firstLetter: [ { required: true, message: '檢索首字母不能爲空', trigger: 'blur' } ], sort: [ { required: true, message: '排序不能爲空', trigger: 'blur' } ] } } }, components:{ singleUpload }, methods: { init (id) { this.dataForm.brandId = id || 0 this.visible = true this.$nextTick(() => { this.$refs['dataForm'].resetFields() if (this.dataForm.brandId) { this.$http({ url: this.$http.adornUrl(`/product/brand/info/${this.dataForm.brandId}`), method: 'get', params: this.$http.adornParams() }).then(({data}) => { if (data && data.code === 0) { this.dataForm.name = data.brand.name this.dataForm.logo = data.brand.logo this.dataForm.descript = data.brand.descript this.dataForm.showStatus = data.brand.showStatus this.dataForm.firstLetter = data.brand.firstLetter this.dataForm.sort = data.brand.sort } }) } }) }, // 表單提交 dataFormSubmit () { this.$refs['dataForm'].validate((valid) => { if (valid) { this.$http({ url: this.$http.adornUrl(`/product/brand/${!this.dataForm.brandId ? 'save' : 'update'}`), method: 'post', data: this.$http.adornData({ 'brandId': this.dataForm.brandId || undefined, 'name': this.dataForm.name, 'logo': this.dataForm.logo, 'descript': this.dataForm.descript, 'showStatus': this.dataForm.showStatus, 'firstLetter': this.dataForm.firstLetter, 'sort': this.dataForm.sort }) }).then(({data}) => { if (data && data.code === 0) { this.$message({ message: '操作成功', type: 'success', duration: 1500, onClose: () => { this.visible = false this.$emit('refreshDataList') } }) } else { this.$message.error(data.msg) } }) } }) } } } </script>
6、遇到的問題
-
跨域請求報錯
根據官方文檔,配置跨域相關的內容
-
上傳文件報400 Bad Response,返回內容提示上傳內容超過最大限制
糾結了半小時,不知道哪裏有問題,上網查也沒查到,最後在官網中也有碰到類似問題的,囉裏囉嗦一大堆,也沒解決,最後看到我寫的controller中的代碼是自己敲的,有個配置
policyConditions.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE,0,1048576000);
這是配置上傳文件的大小的,我當時寫的最大是1,鬱悶了,後來根據官網實例,寫成了和它官網一樣的就可以了,
說明:
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));
返回的簽名數據的key和value一定不能寫錯,否則無法上傳
好了,以上就是在繼承OOS服務到我們系統中的過程,希望有使用同樣方法的猿們看到可以有共鳴