小程序这个也经常开发要掌握(二)

举报
龙哥手记 发表于 2022/09/17 14:57:47 2022/09/17
【摘要】 《JavaScript》系列,第二十八篇希望你持续关注哦!

方法二:

消除样式隔离

组件内:
Component({
  options: {
    styleIsolation: 'apply-shared'
  }
})

组件插槽slot

单个插槽

父组件调用传入插槽内容:
<组件标签>
    <view>
      <view>插槽内容</view>
      <view>插槽内容</view>
    </view>
</组件标签>

 组件内部定义slot标签:
 <view>
    <!-- slot插槽 -->
    <slot></slot>
</view>

如果需要实现多个插槽

父组件调用传入插槽内容:
<组件标签>
    <view slot="slot2">
      <view>插槽1内容</view>
      <view>插槽1内容</view>
    </view>

     <view slot="slot1">
      <view>插槽2内容</view>
      <view>插槽2内容</view>
    </view>
</组件标签>

组件js :
options: {// 设置
    multipleSlots: true // 打开多个插槽功能
},


组件内部定义slot标签:
<view>
    <!-- slot插槽 具名插槽-->
    <slot name="slot1"></slot>
    <slot name="slot2"></slot>
</view>

判断用户授权

授权

// 判断用户是否授权
    wx.getSetting({
      success: (res) => { // 这里使用箭头函数可改变内部this指向为外部的this
        console.log(res)
        if (res.authSetting['scope.userInfo']) { // 已授权
         wx.getUserInfo({ // 获取用户信息
           success(res) {
             console.log(res)
           }
         })
        } else { // 未授权

        }
      }
    })

button的开发能力(获取用户信息)1

<button class="login"
    open-type="getUserInfo"
    bindgetuserinfo="onGetUserInfo"     // bindgetuserinfo 为固定的
>
    获取微信授权信息
</button>


bindgetuserinfo 事件会询问用户是否同意授权


js中:
    onGetUserInfo(event) { // 获取用户信息
      const userInfo = event.detail.userInfo
      if (userInfo) { // 用户允许授权
        this.setData({
          modalShow: false
        })
        this.triggerEvent('loginSuccess', userInfo) // 给父组件传用户数据
      } else { // 用户拒绝授权
        this.triggerEvent('loginFail')
      }
    }

原生组件

原生组件

auto-focus 自动获取焦点

<textarea
    class="content"
    placeholder="分享新鲜事..."
    maxlength="140"
    auto-focus
    bindinput="onInput"
    bindfocus="onFocus"
    bindblur="onBlur"
  ></textarea>

选择上传图片

上传图片

let max = 9 - this.data.images.length // 还能再选几张图片
wx.chooseImage({
      count: max, // 还能再选几张图片
      sizeType: ['original', 'compressed'], // 初始值 and 压缩过的
      sourceType: ['album', 'camera'], // 手机相册选择 and 拍照选择
      success: (res) => { // 箭头函数改变this指向
        console.log(res)
      },
    })

图片裁剪

图片裁剪

<!-- mode 图片裁剪 aspectFill 保证短边完整显示 -->
<image class="image" src="{{item}}" mode="aspectFill"></image>

获取标签自定义属性data-* (删除图片的实现)

<!-- 显示图片 -->
    <block wx:for="{{images}}" wx:key="*this">
      <view class="image-wrap">
        <!-- mode 图片裁剪 aspectFill 保证短边完整显示 -->
        <image class="image" src="{{item}}" mode="aspectFill"></image>
        <icon class="iconfont icon-shanchu" bindtap="onDelImage" data-index="{{index}}"></icon>
      </view>
    </block>


    // 删除图片
  onDelImage(event) {
      // event.target.dataset.index 获取标签属性data-index的值
    this.data.images.splice(event.target.dataset.index, 1) // splice会改变原有数组
    this.setData({
      images: this.data.images
    })
  },

全屏预览图片(点击图片放大预览)

全屏预览图片

// 全屏预览图片
  onPreviewImage(event) {
    wx.previewImage({
      urls: this.data.images, // 图片地址列表
      current: event.target.dataset.imgsrc // 当前预览图片地址
    })
  },

文件上传云存储(发布博客例子)

文件上传云存储

 //  结合'发布'的例子:
  send() {
    // 验证是否输入内容
    if (content.trim() === '') { // trim() 去掉字符串空格
      wx.showToast({
        title: '请输入内容',
        icon: 'none'
      })
      return
    }
    wx.showLoading({
      title: '发布中',
    })
    /**
     * 实现思路及步骤:
     * 1、图片 -> 上传 云存储  -> 生成 图片fineID(云文件ID) 
     * 2、数据 -> 录入 云数据库
     *    数据包括:文字内容、图片fineID、昵称、头像、发布时间、openId(用户唯一标识,在插入数据库是系统会自动添加_openId字段,不需要另外插入)
     */
    let promiseArr = []
    let fileIds = []
    // 图片上传云存储
    this.data.images.forEach((item) => {
      let p = new Promise((resolve, reject) => {
        let suffix = /\.\w+$/.exec(item)[0] // 文件扩展名(文件后缀)
        wx.cloud.uploadFile({ // 每次只能上传一个文件
          /**
           * cloudPath 云路径。如果路径相同,后上传的文件会覆盖原文件
           * 路径:blog/云存储中的文件夹 + Date.now()时间戳 + Math.random()*1000000随机数 + 文件后缀
           */
          cloudPath: 'blog/' + Date.now() + '-' + Math.random() * 1000000 + suffix,
          filePath: item, // 文件本地临时路径
          success: (res) => {
            fileIds.push(res.fileID)
            resolve()
          },
          fail: (err) => {
            console.error(err)
            reject()
          }
        })
      })
      promiseArr.push(p)
    })

    // 存入云数据库
    Promise.all(promiseArr).then((res) => {
      db.collection('blog').add({
        data: {
          ...userInfo, // 昵称、头像
          content, // 内容
          img: fileIds, // 图片fileID列表
          createTime: db.serverDate() // 创建时间,取服务端时间
        }
      }).then((res) => {
        wx.hideLoading()
        wx.showToast({
          title: '发布成功',
        })
        // 返回博客页面,并刷新
        wx.navigateBack()

      })
    }).catch((err) => {
      wx.hideLoading()
      wx.showToast({
        title: '抱歉,发布失败',
        icon: 'none'
      })
    })
  },

js模块化 (时间格式化)

在目录utils 中新建formatTime.js文件

// 时间格式化 模块封装
module.exports = (date) => { // date 数据格式为 date
  let fmt = 'yyyy-MM-dd hh:mm:ss' // 预定格式
  const o = {
    // + 正则中的1个或多个
    'M+': date.getMonth() + 1,
    'd+': date.getDate(),
    'h+': date.getHours(),
    'm+': date.getMinutes(),
    's+': date.getSeconds()
  }

  if (/(y+)/.test(fmt)) {
    // $1 表示正则中的第一个,即(y+)
    fmt = fmt.replace(RegExp.$1, date.getFullYear()) // replace 替换
  }

  for (let k in o) {
    if (new RegExp('('+ k +')').test(fmt)) {
      fmt = fmt.replace(RegExp.$1, o[k].toString().length === 1 ? '0' + o[k] : o[k])
    }
  }

  return fmt
}

在组件引入js模块

import formatTime from '../../utils/formatTime.js'

使用:
formatTime(new Date('Wed Aug 28 2019 16:23:06 GMT+0800 (中国标准时间)'))

阻止事件冒泡

bindcatch 都可以绑定事件,它们的区别是 bind 有事件冒泡,而 catch 没有

返回上一个页面并执行方法

API

 // 返回博客页面,并刷新
 wx.navigateBack()
 const pages = getCurrentPages() // 获取当前页面栈
 const prevPage = pages[pages.length - 2]  // 取到上一个页面
 prevPage.onPullDownRefresh() // 执行上一个页面的方法 onPullDownRefresh

图片懒加载

API

给image标签设置 lazy-load 为 true
<image class="img" src="{{item}}" lazy-load="true"></image>

.img {
  background: #eee;
}

懒加载占位图可以给image设置背景图或背景色

模糊查询

// 获取博客列表
  app.router('blogList', async (ctx, next) => {
    const keyword = event.keyword // 搜索关键字 调用接口时传递来的数据
    let w = {}
    if (keyword.trim() != '') {
      w = {
        content: db.RegExp({ // 正则
          regexp: keyword,
          options: 'i' // i表示忽略大小写
        })
      }
    }

    // where查询条件 skip 从第几条开始查,limit 查几条数据,orderBy(排序字段,排序方式) 排序,排序方式desc降序/asc升序
    ctx.body =  await blogCollection.where(w).skip(event.start).limit(event.count)
    .orderBy('createTime', 'desc').get().then((res) => {
      return res.data
    })

  })

提升模糊查询的效率 (添加索引,对数据量大的查询效果明显)

云开发控制台 > 数据库相应的集合 > 索引管理 > 添加索引 > 输入自定义索引名称、该字段的值是否唯一、被查询的字段名、升序/降序 > ok

小程序端调用云数据库

一般调用云数据库的操作都写在云函数内,其实小程序端也可以对数据库进行操作。

小程序端一次最多只能查询20条数据,云函数端最多可查询100条数据,可使用多次查询拼接的方式突破限制。

// 小程序端调用云数据库示例
    const db = wx.cloud.database() // 初始化数据库
    db.collection('blog').orderBy('createTime','deac').get().then((res) => {
      console.log(res)
    })

云数据库权限管理

注意:云控制台和服务端(云函数)始终有所有数据读写权限,

但权限的管理仅对小程序端发起的请求有效。

  • 仅创建者可写,所有人可读 (适合于文章)

  • 仅创建者可读写 (适用于私密内容)

  • 仅管理端可写,所有人可读(适用于商品信息)

  • 仅管理端可读写(适用于后台敏感数据)

数据库中1对N关系的三种设计方式

第一种:N的数量较少 几十个以内

1 条记录存储 N 个子数据

​ 如一条博客中,最多有9张图片,这9张图片可和其他数据放在一个记录中。

[
    {
        id:...
        img:[
        '...', '...', '...', '...', '...', '...', '...', '...', '...'
        ]
    }
]
第二种:N的数量较多 几十到几百个

1 存储 每个N的 id

可分两个数据库集合,

一个为 '目录' 集合,存放 '详情' 集合下的每条数据的 id 目录

一个为 '详情' 集合,每条数据对应一个单独的 id 和 详细数据

目录集合:
[
    {
        'id':"11",
        'name': '产品1',
        'xqs': ['111','222','333', ... ]  // 存放 详情集合 中的每条数据 id
    }
]



详情集合:
[
{'id':"111",name:'零件1',title:'...' ...},
{'id':"222",name:'零件2',title:'...' ...},
{'id':"333",name:'零件3',title:'...' ...},
...
]

如歌单列表,与歌曲详情的数据组合设计。

第三种:N的数量巨大 几百成千上万个

每个 N 都存储 1 的 id

如新浪博客中的一条博客下面有几千条评论

一条新浪博客:
[{
    'id':'11',
    'content':'博客内容'
    ...
}]


上千条评价:
[
{
    'id':'111111'
    'blogId':'11', // 这个id对应的是那一条博客的id
    'content': '评价内容1'
},
{
    'id':'222222'
    'blogId':'11', // 这个id对应的是那一条博客的id
    'content': '评价内容2'
},
{
    'id':'33333'
    'blogId':'11', // 这个id对应的是那一条博客的id
    'content': '评价内容3'
},
...
]

云调用

通过云函数调用服务端的开发接口

这些接口如:模板消息推送、生成小程序码...

模板消息推送

1、使用from表单才能触发消息推送,并设置report-submit="true"

<form slot="modal-content" report-submit="true" bind:submit="onSend">
    <textarea name="content" class="comment-content" placeholder="写评论" value="{{content}}" fixed="true"></textarea>
    <button class="send" form-type="submit">发送</button>
  </form>

2、需要到微信公众平台做相应的设置:

微信公众平台 > 功能 > 模板消息 > 添加模板 > 选择相应的模板> 添加成功后会有一个模板ID

3、新建一个云函数,用于云调用。在该云函数下新建配置文件:config.json ,用于配置权限

config.json :

{
  "permissions": {
    "openapi": [
      "templateMessage.send"
    ]
  }
}

云函数设置消息推送:

// 云函数入口函数
exports.main = async (event, context) => {
  // 获取openid
  const { OPENID } = cloud.getWXContext()

  // 模板推送消息
  const result = await cloud.openapi.templateMessage.send({
    touser: OPENID,
    page: `/pages/blog-comment/blog-comment?blogId=${event.blogId}`, // 用户点击推送消息打开的页面
    data: { // 模板的内容,keyword为在公众平台设置模板时对应的字段
      keyword1: { // 评价内容
        value: event.context 
      },
      keyword2: { // 评价时间
        value: event.time
      }
    },
    templateId: 'LNwKMcYwlz-0HabgBhmZi6CWZrlNSBiNJ2h0SMorcxQ', // 模板id,到公众平台模板消息上获取
    formId: event.formId // 触发消息推送的form表单的id
  })

  return result
}

4、在提交表单事件完成后调用消息推送云函数

 wx.cloud.callFunction({
          name: 'sendMessage',
          data: {
            content,
            formId,
            blogId: this.properties.blogId
          }
        }).then((res) => {
          console.log(res)
        })

云函数多集合查询数据库

// 博客详情(博客内容、评论)
  app.router('blogDetail', async(ctx, next) => {
    let blogId = event.blogId

    // 博客内容
    let detail = await blogCollection.where({
      _id: blogId
    }).get().then((res) => {
      return res.data
    })

    // 评论查询
    const countResult = await blogCollection.count()
    const total = countResult.total
    let commentList = {
      data: []
    }
    if (total > 0) {
      // 突破100条限制
      const batchTimes = Math.ceil(total / MAX_LIMIT)
      const tasks = []
      for (let i = 0; i < batchTimes; i++) {
        let promise = db.collection('blog-comment').skip(i * MAX_LIMIT)
          .limit(MAX_LIMIT).where({
            blogId
          }).orderBy('createTime', 'desc').get()
        tasks.push(promise)
      }
      if (tasks.length > 0) {
        commentList = (await Promise.all(tasks)).reduce((acc, cur) => {
          return {
            data: acc.data.concat(cur.data)
          }
        })
      }

    }
    ctx.body = {
      detail,
      commentList
    }
  })

分享功能

分享功能需要button标签,设置open-type="share"

<button open-type="share" data-blogid="{{blogId}}" data-blog="{{blog}}" class="share-btn" hover-class="share-hover">
      <i class="iconfont icon-fenxiang icon"></i>
      <text>分享</text>
    </button>

在js中有onShareAppMessage方法,点击button会自动执行此方法

onShareAppMessage: function (event) {
    console.log(event)

    // 对分享卡片的设置
    let blogObj = event.target.dataset.blog
    return {
      title: blogObj.content,
      path: `/pages/blog-comment/blog-comment?blogId=${blogObj._id}`,
      // imageUrl: '' // 自定义图片,不支持云存储的图片
    }
  }

不同场景获取用户信息的方式

场景一:只想在界面上显示自己的昵称和头像

以组件的方式:根据type类型获取不同用户数据

该方式不需要授权,只能用于在wxml显示自己的信息

open-data

<open-data type="userAvatarUrl"></open-data>
<open-data type="userNickName"></open-data>
...
场景二:在JS中获取用户信息

该方式要在用户授权以后才能获取用户信息

wx.getUserInfo

wx.getUserInfo({
      success: (res) => {
        console.log(res)
      }
    })

在未授权的情况下需要用户先授权:

// 判断用户是否授权
      wx.getSetting({
        success: (res) => { // 这里使用箭头函数可改变内部this指向为外部的this
          if (res.authSetting['scope.userInfo']) { // 已授权
            wx.getUserInfo({ // 获取用户信息
              success: (res) => { // 这里使用箭头函数可改变内部this指向为外部的this

                app.setGlobalData('userInfo', res.userInfo) // 设置app全局属性

                this.onLoginSuccess({
                  detail: res.userInfo
                })
              }
            })
          } else { // 未授权
            this.setData({ // 打开弹出层,显示获取用户信息按钮
              modalShow: true
            })
          }
        }
      })


  授权按钮
 <button class="login" open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">获取微信授权信息</button>


    onGetUserInfo(event) { // 获取用户信息
      const userInfo = event.detail.userInfo
      if (userInfo) { // 用户允许授权
        this.setData({
          modalShow: false
        })
        this.triggerEvent('loginSuccess', userInfo) // 给父组件传用户数据
      } else { // 用户拒绝授权
        this.triggerEvent('loginFail')
      }
    }

注意:上面这种方式没有获取到openId

场景三:获取openId

获取openId不需要用户授权

1、传统开发方式获取openId,后台服务器由自己开发,没使用云开发

小程序端 微信服务器 后端服务器

步骤:

小程序端 调用 wx.login 向微信服务器 获取code

小程序端 调用 wx.request 将 code 传递给 后端服务器

后端服务器 使用code 向微信服务器 换取openid和session_key

后端服务器 将openid 发送给 小程序端

2、云开发方式获取openId

云函数login中

// 获取 WX Context (微信调用上下文),包括 OPENID、APPID、及 UNIONID(需满足 UNIONID 获取条件)
  const wxContext = cloud.getWXContext()

  return {
    event,
    openid: wxContext.OPENID,
    appid: wxContext.APPID,
    unionid: wxContext.UNIONID,
  }
普通按钮
<button bindtap="getOpenid">获取openid</button>

getOpenid() {
    wx.cloud.callFunction({
        name: 'login'
    }).then((res) => {
        console.log(res)
    })
}

openid 在小程序和公众号下是不一样的

unionid 在小程序和公众号下都是一样的

《我的》页面

json文件

"navigationBarTitleText": "我的",
  "disableScroll": true  // 使页面无法滚动

导航页面链接跳转

navigator

背景图片

wxss背景图片不支持本地相对路径的图片,只支持网络图片和base64图片

建议使用base64图片,图片文件最好不要太大。

每个页面都有的page标签

page {
  background-color: #f1f1f1;
}

播放历史与本地存储

方案一:播放历史存储在数据库当中,这样在不同设备访问都可查看播放历史。读取速度相对较慢

方案二:播放历史存储在本地,仅当前设备可查看播放历史。读取速度较快

本项目采用本地存储:

使用openid作为本地存储的key,播放历史存入value

在app.js中获取openid,即打开小程序就获取openid。

// app.js
onLaunch: function () {
    this.getOpenid() // 获取openid并存储
},
getOpenid() { // 获取openid并存储
    wx.cloud.callFunction({
      name: 'login'
    }).then((res) => {
      const openid = res.result.openid
      this.globalData.openid = openid // 保存到全局变量
      if (wx.getStorageSync(openid) == '') { // 该用户从未打开过小程序,未存储过openid在本地
        wx.setStorageSync(openid, []) // 存储openid到本地
      }
    })
  }

歌曲播放时

// 保存播放历史到本地存储
  savePlayHistory() {
    const currentSong = musiclist[nowPlayingIndex] // 当前播放歌曲
    const openid = app.globalData.openid // 从全局属性获取openid
    const playHistory = wx.getStorageSync(openid) // 从本地存储获取播放历史数组

    for (let i = 0, len = playHistory.length; i < len; i++) {
      if (playHistory[i].id === currentSong.id) { // 当前播放歌曲已存在播放历史中
        playHistory.splice(i, 1) // 删除原纪录
        break
      }
    }

    playHistory.unshift(currentSong) // 在数组开头插入
    wx.setStorage({ // 存入本地
      key: openid,
      data: playHistory
    })

  },

播放历史页面获取

onLoad: function (options) {

    const openid = app.globalData.openid //从全局属性获取openid
    const playHistory = wx.getStorageSync(openid) // 读取本地播放历史数据

    if (playHistory.length !== 0) { // 有播放历史
      this.setData({
        playHistory
      })
      wx.setStorage({ // storage里把musiclist(播放列表)的内容换成播放历史的列表
        key: 'musiclist',
        data: playHistory,
      })
    }

  },

我的发现

代码分别演示了从云函数和小程序端获取数据,从小程序端获取数据享有权限管理的能力,不需要传openid。

小程序码

获取小程序码

本项目演示使用接口 B:适用于需要的码数量极多的业务场景 云调用 的方式。

步骤:

  • 创建云函数 gteQRCode

  • gteQRCode云函数下创建config.json配置权限,代码如下:

    {
     "permissions":{
       "openapi":[
         "wxacode.getUnlimited"
       ]
     }
    }
// 云函数入口函数
exports.main = async (event, context) => {
  const wxContext = cloud.getWXContext()

  const result = await cloud.openapi.wxacode.getUnlimited({
    scene: wxContext.OPENID, // 链接参数 不一定传openid,可传其他任意数据,然后通过此数据,在别人扫码进入时就可用于判断
    // page: "pages/blog/blog" // 默认进入主页
    // lineColor: { // 线条颜色
    //   'r': 211,
    //   'g': 60,
    //   'b': 57
    // },
    // isHyaline: true // 是否透明
  })

  // result为二进制数据, 先上传到云存储

  // 上传云存储
  const upload = await cloud.uploadFile({
    cloudPath: 'qrcode/qrcode' + Date.now() + Math.random() + '.png',
    fileContent: result.buffer
  })

  return upload.fileID
}
判断是从扫码小程序码进入,以及参数获取
// 在从小程序码进入的页面js,onLoad方法中,

onLoad: function (options) {
    console.log(options.scene) // 获取到小程序码进入的参数
}

版本更新检测

// app.js
onLaunch: function(options) {
    this.checkUpate()
},
checkUpate(){
    const updateManager = wx.getUpdateManager()
    // 检测版本更新
    updateManager.onCheckForUpdate((res)=>{
      if (res.hasUpdate){
        updateManager.onUpdateReady(()=>{
          wx.showModal({
            title: '更新提示',
            content: '新版本已经准备好,是否重启应用',
            success(res){
              if(res.confirm){
                updateManager.applyUpdate()
              }
            }
          })
        })
      }
    })
  },

性能优化

官网文档优化建议

使用开发者工具的调试器,Audits进行评分,然后根据提示针对项目进行优化。

场景值scene的作用与应用场景

场景值

场景值用来描述用户进入小程序的路径。完整场景值的含义请查看场景值列表

可根据不同场景进入实现不同业务处理,比如一个点餐小程序,店家内贴了小程序码,用户通过扫码进入,可立即进入点餐页面,等等

在app.js中的onLaunch(options) 、onShow(options),options包含scene场景值

开发者工具中,切后台,可模拟进入场景。

小程序的"SEO"---页面收录sitemap

在app.js的同级目录下有sitemap.json文件,用于配置收录规则

stiemap配置

作用:

使小程序搜索可根据小程序的内容进行搜索到

使用方法:

1、在微信公众平台,小程序信息 > 页面收录设置 > 打开 (默认是已开启)

2、打开sitemap.json文件,配置收录规则

{
  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
  "rules": [{ // 收录规则
  "action": "allow",// 是否被收录,allow允许被收录,disallow不允许
  "page": "*" // *星号表示所有页面都被收录
  }]
}
{
  "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
  "rules": [{ // 收录规则,可添加多条
  "action": "allow", // 是否被收录
  "page":"pages/player/player",  // 页面
  "params": ["musicId","index"], // 链接的动态参数
   "matching":'exact' // 表示params的参数是否要准确的匹配
  },{
  "action": "disallow", // 是否被收录
  "page":"*",  /
  }]
}

// 上面配置规则表示除了player页面被收录外,其他页面不被收录

小程序上线审核流程

微信公众平台,版本管理>把小程序上传为体验版》提交审核》上线

后台管理系统

架构示意图

​ 前端 后台 小程序云开发

vue-admin-template <---通过ajax--> 基于Koa2;HTTP API 或 tcb-admin-node ---->云函数、云数据库、云存储

vue-admin-template构建管理系统前端

vue-element-admin 基于element的后台管理系统模板

vue-admin-templatevue-element-admin的简化版

使用方法查看官方文档。

Koa2构建管理系统后端

官网: https://koa.bootcss.com/

新建空文件夹wx-music-admin-backend,打开终端:

# 生成package.json文件,-y 表示默认的配置
npm init -y

# 安装koa
npm install koa

# 新建app.js文件 (win10系统命令),作为项目入口文件
type nul > app.js

app.js:

const Koa = require('koa')
const chalk = require('chalk') // 使console.log打印文字有颜色的插件,需: npm i chalk
const app = new Koa()

app.use(async (ctx) => {
  ctx.body = 'Hello Wolrd'
})
const port = 3000
app.listen(port, () => { // 端口号,开启服务后的回调函数
  console.log(chalk.green(`> 服务已开启,访问:http://localhost:${port}`))
}) 

终端:

# node启动项目
node app.js

# 访问:http://localhost:3000

接口调用凭证 access_token 的缓存与更新

access_token,微信的接口调用凭证,详情:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html

回到项目wx-music-admin-backend,打开终端:

# HTTP 请求 插件
npm i request
npm i request-promise
/**
 * 获取微信接口调用凭证
 * 详情:https://developers.weixin.qq.com/miniprogram/dev/api-backend/open-api/access-token/auth.getAccessToken.html
 */


const rp = require('request-promise') // node发送http请求的插件
const fs = require('fs') // node文件模块
const path = require('path') // node 路径模块

//fileName = __dirname 当前文件所在目录的绝对路径, 加上 './access_token.json'
const fileName = path.resolve(__dirname, './access_token.json')

// 这两个参数的获取:微信公众平台>开发>开发设置
const APPID = 'wxc4e0b2d98063b103'
const APPSECRET = 'xxx' //小程序密钥,注意保密!

// 微信 access_token 请求地址
const URL = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${APPID}&secret=${APPSECRET}`

// 发送请求获取AccessToken
const updateAccessToken = async () => {
  const resStr = await rp(URL)
  const res = JSON.parse(resStr)

  if (res.access_token) {
    // node写文件,参数:1 文件路径,2 文件内容, 首次写文件为新建,往后为覆盖
    fs.writeFileSync(fileName, JSON.stringify({
      access_token: res.access_token,
      createTime: new Date()
    }))
  } else { // 如获取不到,再次获取
    await updateAccessToken()
  }
}

// 读取access_token
const getAccessToken = async () => {
  try {
     // node读取文件,参数:1 读取的文件,2 字符集
    const readRes = fs.readFileSync(fileName, 'utf8')
    const readObj = JSON.parse(readRes)

    // 如果服务器宕机导致setInterval无法定时更新,这里需要再次判断access_token的有效性
    const createTime = new Date(readObj.createTime).getTime()
    const nowTime = new Date().getTime()
    if((nowTime - createTime) / 1000 / 60 / 60 >= 2) {
      await updateAccessToken()
      await getAccessToken()
      return
    }
    return readObj.access_token

  } catch (error) { //捕获异常,在未创建文件时,先创建文件
    await updateAccessToken()
    await getAccessToken()
  }
}

// access_token有效期为2个小时,定时更新
setInterval(async () => {
  await updateAccessToken()
}, (7200 - 300) * 1000)

module.exports = getAccessToken

后端代码通过HTTP API 触发云函数获取数据

HTTP API 触发云函数

产生跨域和后端解决跨域问题

管理系统前端向管理系统后端请求数据,产生了跨域问题

  // 产生跨域的几种情况
  // http://www.a.com  https://www.a.com 协议不同
  // http://www.a.com  http://www.b.com 域名不同
  // http://www.a.com  http://news.a.com 主域与子域不同
  // http://www.a.com:8080  http://www.a.com:3000 端口不同

// 解决跨域的几种方法
// jsonp
// iframe
// postMessage跨域
// 跨域资源共享(CORS)

管理系统后端,安装

// 解决跨域问题的koa包
npm i koa2-cors

app.js

//处理跨域
app.use(cors({
  origin: ['http://localhost:9528'], // 允许访问本服务的域
  credentials: true
}))

云数据库的增删改查接口

数据库查询记录

后端获取前端post请求传来的数据

get请求可以直接通过ctx.request.query获取,但是post请求需要安装koa-body

npm i koa-body

app.js

const koaBody = require('koa-body') // 对post请求前端传来的数据的获取,需要此依赖

// 接收post参数解析
app.use(koaBody({
  multipart: true
}))

接口.js

router.post('/updatePlaylist', async (ctx, next) => {

  const params = ctx.request.body // post请求获取前端传来的数据,需安装和配置koa-body

})

后端获取云存储图片

云存储中上传图片,云数据库中新建图片的集合,并添加数据字段,字段包含云文件的fileid。

后端项目通过调用云数据库的方式获取数据

router.get('/list', async (ctx, next) => {
  // 接口中读取数据库默认最多10条数据
  const query = `db.collection('swiper').get()`
  const res = await callCloudDB(ctx, 'databasequery', query)
  console.log(res)

})

但获取到的数据为fileid,并不能用于显示图片,需要通过微信HTTP API获取云存储的接口来获取图片地址

获取云存储

后端上传图片到云存储

文件上传

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。