在后台项目开发过程中,客户反应图片加载太慢,限于服务器带宽只有1M,大的图片加载会比较慢,于是将图片上传到七牛云,这样记在速度就会明显提升。
后台开发
后端使用的是lin-cms-koa框架
方法封装,我们安装qiniu插件,通过key和密钥换取token返回给前端,用于文件上传
// src/extension/file/qiniu-uploader.js
import qiniu from 'qiniu';
import { config } from 'lin-mizar';
class QiniuUploader {
constructor() {
this.accessKey = config.getItem('qiniu.accessKey');
this.secretKey = config.getItem('qiniu.secretKey');
this.bucket = config.getItem('qiniu.bucket');
this.mac = new qiniu.auth.digest.Mac(this.accessKey, this.secretKey);
}
generateUploadToken() {
const options = {
scope: this.bucket,
expires: 7200 // token 有效期,单位:秒
};
const putPolicy = new qiniu.rs.PutPolicy(options);
return putPolicy.uploadToken(this.mac);
}
}
export { QiniuUploader };
接口封装
import {LinRouter, ParametersException} from 'lin-mizar';
import {loginRequired} from '../../middleware/jwt';
import {QiniuUploader} from '../../extension/file/qiniu-uploader';
const file = new LinRouter({
prefix: '/cms/file'
});
file.get('getQiniuToken', '/get-qiniu-token', loginRequired, async ctx => {
const qiniuUploader = new QiniuUploader();
const token = qiniuUploader.generateUploadToken();
ctx.json({
token: token
});
});
export {file};
前端开发
插件版本
这里使用的qiniu-js是3.x,升级4x后api发生了变化,并且没有明确说明文件如何上传到指定目录,所以这里我们使用3.x的版本。
"qiniu-js": "3.4.2",
接口封装
// 获取七牛云token
export function getQiniuToken(data) {
return request({
url: '/cms/file/get-qiniu-token',
method: 'get',
data
})
}
上传组件代码
<template>
<div class="uploadImgList">
<div v-for="(image, index) in computedImages" :key="index" class="avatar">
<img :src="image" class="avatar"/>
<el-icon class="delete-icon" @click.stop="removeImage(index)">
<Delete/>
</el-icon>
</div>
<div class="avatar-uploader" @click="handleUpload">
<el-icon v-if="images.length < maxImages" class="avatar-uploader-icon">
<Plus/>
</el-icon>
<input
ref="fileInput"
type="file"
class="file-input"
@change="handleFileChange"
style="display: none;"
multiple
/>
</div>
</div>
</template>
<script setup>
import {defineProps, defineEmits, computed, ref} from 'vue'
import {ElMessage} from 'element-plus'
import {Plus, Delete} from '@element-plus/icons-vue'
import * as qiniu from 'qiniu-js'
import {qiniuConfig} from '@/api/ipConfig.js'
import {getQiniuToken} from '@/api/api/cms.js'
import {v4 as uuidv4} from 'uuid'
const props = defineProps({
avatar: {
type: String,
default: ''
},
maxImages: {
type: Number,
default: 3
}
})
const emit = defineEmits(['update:avatar'])
const fileInput = ref(null)
const images = ref(props.avatar ? props.avatar.split(',') : [])
const computedImages = computed(() => {
return images.value.map(img => `${qiniuConfig.qiuniuUploadUrl}/${img}`)
})
const handleUpload = () => {
fileInput.value.value = null;
fileInput.value.click()
}
const getFileExtension = (filename) => {
return filename.slice((filename.lastIndexOf(".") - 1 >>> 0) + 2);
}
const handleFileChange = async (e) => {
const files = e.target.files
if (!files.length) return;
for (const file of files) {
const isJPG = file.type === 'image/jpeg'
const isPNG = file.type === 'image/png'
const isLt2M = file.size / 1024 / 1024 < 2
if (!isJPG && !isPNG) {
ElMessage.error('图片只能是 JPG 或 PNG 格式!')
continue
}
if (!isLt2M) {
ElMessage.error('图片大小不能超过 2MB!')
continue
}
try {
const fileExtension = getFileExtension(file.name);
const key = `rencai/``{uuidv4()}.``{fileExtension}`;
const tokenResponse = await getQiniuToken();
if (typeof tokenResponse.token !== 'string') {
throw new Error('Invalid upload token');
}
const token = tokenResponse.token;
const putExtra = {
fname: file.name,
params: {},
mimeType: null
};
const config = {
useCdnDomain: true,
region: 'z2', // 修改这里
domain: 'up-z2.qiniup.com' // 添加这行
};
const observable = qiniu.upload(file, key, token, putExtra, config);
observable.subscribe({
next: (res) => {
console.log('上传进度', res.total.percent + '%');
},
error: (err) => {
ElMessage.error('上传失败,请重试');
console.error(err);
},
complete: (res) => {
images.value.push(res.key);
emit('update:avatar', images.value.join(','));
ElMessage.success('图片上传成功');
}
});
} catch (error) {
ElMessage.error('上传失败,请重试');
console.error(error);
}
}
}
const removeImage = (index) => {
images.value.splice(index, 1);
emit('update:avatar', images.value.join(','));
}
</script>
<style scoped>
.uploadImgList {
display: flex;
justify-content: flex-start;
align-items: flex-start;
flex-wrap: wrap;
flex-direction: row;
align-content: flex-start;
}
.avatar-uploader {
text-align: center;
width: 150px;
height: 150px;
align-items: center;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
flex-direction: row;
align-content: flex-start;
}
.avatar {
width: 150px;
position: relative;
height: 150px;
display: block;
margin-right: 10px;
border: 1px solid #ddd;
}
.delete-icon {
position: absolute;
top: 0;
right: 0;
cursor: pointer;
color: red;
}
</style>