前端直传OSS
之前开发系统一般服务器的性能都是够的,所以资源都是直接上传服务器,然后再由后端把资源上传OSS。
这次因为服务器的带宽比较拉,加上上传的图片资源比较大,所以所用了这种方案。
使用技术是:
首先,需要让前端来上传数据,你需要后端给你提供 api 返回签名数据。
如果不是后端返回,直接使用前端上传,会暴露出去你的 秘钥,不安全
后端返回数据:
后端返回的数据不需要依赖OSS的SDK数据,这个具体我也没去研究
/**
* @param string $module 用于对模块区分,方便管理
* @return array
*/
public function getToken(string $module): array
{
$id = $this->accessKeyId; // 请填写您的AccessKeyId。
$key = $this->accessKeySecret; // 请填写您的AccessKeySecret。
$host = 'https://' . $this->bucket . "." . $this->endpoint;//域名
$dir = "$module/" . date("Ymd"); // 用户上传文件时指定的前缀。这里是 模块名+日期
// 回调参数,因为我是前段调用,我直接使用了,如果是后端调用,可以使用回调参数
$callbackParam = [
'callbackUrl' => "",
'callbackBody' => 'filename=${object}&size=${size}&mimeType=${mimeType}&height=${imageInfo.height}&width=${imageInfo.width}',
'callbackBodyType' => "application/x-www-form-urlencoded"
];
$callbackString = json_encode($callbackParam);
$base64CallbackBody = base64_encode($callbackString);
// 设置到期时间 表示签名有效期
$now = time();
$expire = 600; //设置该policy超时时间. 即这个policy过了这个有效时间,将不能访问。
$end = $now + $expire;
$expiration = str_replace('+00:00', '.000Z', gmdate('c', $end));
//最大文件大小.用户可以自己设置
$condition = [0 => 'content-length-range', 1 => 0, 2 => 1048576000];
$conditions[] = $condition;
// 表示用户上传的数据,以$dir开始,这一步不是必须项,只是为了安全防止用户通过policy上传到别的目录。
$start = [0 => 'starts-with', 1 => '$key', 2 => $dir];
$conditions[] = $start;
$arr = ['expiration' => $expiration, 'conditions' => $conditions];
$policy = json_encode($arr);
$base64Policy = base64_encode($policy);
$stringToSign = $base64Policy;
$signature = base64_encode(hash_hmac('sha1', $stringToSign, $key, true));
$response = [];
$response['domain'] = $this->cdnDomain;//自定义域名,用于前端拼接
$response['OSSAccessKeyId'] = $id;
$response['host'] = $host;
$response['policy'] = $base64Policy;
$response['signature'] = $signature;
$response['expire'] = $end;
$response['callback'] = $base64CallbackBody;
$response['dir'] = $dir;
return ["code"=>0,"message"=>"success","data"=>$response];
}
然后后端返回的数据结构就是:
{
"code": 0,
"message": "success",
"data": {
"domain": "https://---.com",
"OSSAccessKeyId": "LTA----9EdA",
"host": "https://----.oss-cn-shanghai.aliyuncs.com",
"policy": "eyJleHBpcmF0a----9",
"signature": "++l6m5co00----T/mGdFw=",
"expire": 1690612382,
"callback": "eyJjYWxsYmFja1----C94LXd3dy1mb3JtLXVybGVuY29kZWQifQ==",
"dir": "ce--te/20230729"
}
}
其中部分敏感数据做了处理,用 --- 代替
前端在使用的时候,框架组件是 element-plus 的 upload , 因为页面有多个,所有我封装成了组件。
<template>
<el-upload
:file-list="fileList"
:action="uploadHost"
list-type="picture-card"
:on-preview="handlePictureCardPreview"
:on-remove="handleRemove"
:before-upload="ossPolicy"
:on-success="handleSuccess"
:data="oss"
:on-change="handleChange"
>
<el-icon>
<Plus/>
</el-icon>
</el-upload>
<el-dialog v-model="dialogVisible">
<img :src="dialogImageUrl" alt="Preview Image" style="width: 100%"/>
</el-dialog>
</template>
<script>
import { defineComponent, onMounted, ref } from 'vue';
import { Plus } from '@element-plus/icons-vue';
export default defineComponent({
name: 'Upload',
components: { Plus },
props: {
fileList: {
type: Array,
},
index: {
type: String
},
oss: {
type: Object,
default: {
host: '',
dir: '',
policy: '',
OSSAccessKeyId: '',
success_action_status: '',
signature: '',
callback: '',
key: ''
},
required: true
}
},
emits: ['updateImage'],
setup(props, { emit }) {
const dialogVisible = ref(false);
const dialogImageUrl = ref('');
const filename = ref('');
const uploadHost = ref('');
const handleRemove = (file, fileList) => {
emit('update-image', props.index, fileList);
};
const handleChange = (file, fileList) => {
emit('update-image', props.index, fileList);
};
const handlePictureCardPreview = (file) => {
dialogImageUrl.value = file.url;
dialogVisible.value = true;
};
const handleSuccess = (response, file, fileList) => {
const uploadedFile = fileList.find((item) => item.uid === file.uid);
if (uploadedFile) {
uploadedFile.url = props.oss.domain + '/' + props.oss.dir + '/' + filename.value;
uploadedFile.name = filename.value;
}
filename.value = '';
};
const ossPolicy = (file) => {
filename.value = file.uid + '.' + file.name.substr(file.name.lastIndexOf('.') + 1);
props.oss.key = props.oss.dir + '/' + filename.value;
};
onMounted(() => {
uploadHost.value = props.oss.host;
});
return {
dialogVisible,
dialogImageUrl,
uploadHost,
handleRemove,
handlePictureCardPreview,
ossPolicy,
handleSuccess,
handleChange
};
}
});
</script>
<style scoped lang="less">
</style>
组件需要传递的参数有3个,可以自行扩展。
文件上传之后,没有任何返回值,在 success 里返回,则表示上传完成,直接拼接文件就好。
需要注意:这样实现编辑页面时间过长,会导致失效,注意控制时间,或者可以在组件里请求和存储 oss 这个对象,再者可以延长签名有效期。
上一篇: 微信机器人-2...
下一篇: thinkphp6自定义模块异常...