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服务到我们系统中的过程,希望有使用同样方法的猿们看到可以有共鸣