Appearance
小程序中使用 Pinia 统一加载 CDN 中的素材
背景
我们的小程序项目前端使用 Taro + Vue3,CDN 使用阿里云 OSS,由于前端素材特别多,而素材都存到了私有的 OSS 存储桶中,这就出现了一个问题:每次打开小程序,素材都必须从接口获取,并且每个素材都需要走一遍加密算法,来获取当前要用的 token。
方案
后端给 CDN 这件事单独开个接口,接口中管理全部 CDN 素材。
js
var OSS = require('ali-oss')
const BaseService = require('./base')
class CdnService extends BaseService {
_sources = {
homepageBackground: 'homepage_bg.png',
homepageBgFront: 'homepage_bg_front_1.png',
homepageMonster: 'homepage_monster.png',
// ... 还有很多
}
async getClient() {
const client = new OSS({
accessKeyId: this.app.config.aliyunOss.ak,
accessKeySecret: this.app.config.aliyunOss.sk,
bucket: this.app.config.aliyunOss.buckets.mpSources.bucket,
region: this.app.config.aliyunOss.buckets.mpSources.region,
secure: true
})
return client
}
async getPrivateUrl(filename) {
const client = await this.getClient()
return await client.signatureUrlV4('GET', 3600, {
headers: {} // 请根据实际发送的请求头设置此处的请求头
}, filename);
}
async sources() {
const distMap = {}
const keys = Object.keys(this._sources)
for (let i = 0; i < keys.length; i ++) {
const k = keys[i]
const v = this._sources[k]
distMap[k] = await this.getPrivateUrl(v)
}
return distMap
}
}
module.exports = CdnService前端使用 Pinia 来管理这些素材。我们的需求是,希望这些素材 URL 在一次打开小程序的时候只加载一次,由于签名算法计算出的 URL 有效期是一小时,提供给前端单次使用足够了。
js
import { defineStore } from 'pinia'
import api from '../api'
import { ref } from 'vue'
export const useCdnStore = defineStore('cdn', () => {
const sources = ref([])
const progressing = ref(false)
const init = () => {
return new Promise((resolve, reject) => {
if (sources.value.length === 0 && !progressing.value) {
progressing.value = true
api.cdnSource.cdnSource({}).then(res => {
sources.value = res.data
progressing.value = false
resolve(sources.value)
})
} else {
resolve(sources.value)
}
})
}
return { init, sources }
})在需要使用 CDN 素材的页面上要先进行初始化。
vue
<script setup>
import { useCdnStore } from '../../stores/cdn'
const cdn = useCdnStore()
onMounted(() => {
cdn.init()
})
</script>在这之后,cdn.sources 就可以像任意 vue 响应式数据一样使用就可以了。
wxml
<!-- 在页面元素中使用 -->
<image :src="cdn.sources.chatbox"></image>js
// 在脚本中使用
const titleImg = cdn.sources.newsTitle需要注意的是,如果响应式数据中需要保持 cdn.sources 结果的响应式,需要使用 computed,不然只会在响应式初始化时获得一次值。更严重的,如果响应式的值初始化时,cdn 的结果还未返回,该值则永远无法获得 cdn 的返回结果。为了让有些逻辑在 cdn 初始化完成后进行,cdn.init 返回了一个 Promise,可以在 cdn.init().then 中进行这些逻辑。
js
const titleImg = ref('')
cdn.init().then(res => {
titleImg.value = res.sources.titleImg
})