Skip to content

小程序中使用 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
})

最后更新于:

评论区
评论区空空如也
发送评论
名字
0 / 20
邮箱
0 / 100
评论内容
0 / 140
由于是非实名评论,所以不提供删除功能。如果你需要删除你发送的评论,或者是其他人的评论对你造成了困扰,请 发邮件给我 。同时评论区会使用 AI + 人工的方式进行审核,以达到合规要求。