更新记录

2.0.0.20210630(2021-06-29)

  • 修复 未带路径请求出错的问题
  • 修复 返回数据重复code的问题
  • 新增 创建uni-id实例, 以后使用ctx.uniID调用相应方法

2.0.0.20210629(2021-06-29)

支持配置自动刷新token

2.0.0.20210628(2021-06-28)

重构 uni-koa 框架

  • 兼容koa2第三方插件
  • 兼容uni-id请求方式
查看更多

平台兼容性

阿里云 腾讯云 支付宝云
×

云函数类插件通用教程

使用云函数类插件的前提是:使用HBuilderX 2.9+


uni-koa

基于 uniCloud 云函数项目兼容 koa2 插件或 uni-id 的 web 开发框架

uni-koa 借鉴 koa2 的思想,针对 uniCloud 云函数特点构建一个快速开发方便扩展的框架,充分使用 koa2 第三方插件和 uniClound 插件的优势最大程度来满足云函数项目开发的需求。

uni-koa 公共模块

把 uni-koa公共模块 导入或拷贝到在项目中 uniCloud/cloudfunctions/common 下

uni-koa 上下文 ctx

设置数据

  • ctx.body = 数据 设置请求成功信息,如 {code:0,data:数据}
  • ctx.throw(状态码,失败信息) 设置请求失败信息,如{code:状态码,msg:失败信息}
  • ctx.state.变量=数据 用来配置全局变量,如:登录成功后设置用户信息 ctx.state.user = 用户信息,这样全局就可以获取用户信息
  • ctx.token=新的 token 用于 token 自动刷新后设置的 newToken

获取数据

  • = ctx.method 获取请求方法名。http请求方式post/delete/put/get(默认); action请求方式均为 post
  • = ctx.url 获取请求 url
  • = ctx.path 获取路径名
  • = ctx.query 获取 get 请求的参数值
  • = ctx.request.body 获取 post/delete/put 请求参数值
  • = ctx.params 获取 http 请求方式中动态路参数 或 获取 action 请求中 params 值
  • = ctx.header.authorization 获取 http 请求方式绑定的 "Bearer "+token
  • = ctx.token 获取新的token
  • = ctx.body 获取设置请求成功的信息
  • = ctx.status 获取状态码
  • = ctx.event 获取客户端调用云函数时传入的参数
  • = ctx.context 获取客户端调用的调用信息和运行状态
  • = ctx.event.params 获取 action 请求方式的参数

开发记录

  • 用 uni-koa 框架开发云函数项目和开发 koa2 项目步骤一致。
  • 整个云函数项目由 index.js 入口文件启动和停止
  • 项目结构推荐
app 云函数项目名称
    + controller 控制器:用于解析用户的输入,处理后返回相应的结果       
    + middleware 中间件:用于存放编写的中间件     
    + routes 路由设置:用于配置 URL 路由规则     
    + service 业务逻辑:用于编写业务与数据库交互
    - index.js 入口文件

主程序

  • 当用户请求云函数项目时
    • 实例化 uni-koa框架
    • 把制定的相应规则挂载到项目上
    • 启动监听处理用户请求并返回处理结果
// 1. 创建云函数uni-koa应用
const Koa = require("uni-koa")
const app = new Koa()

// 2. 创建uniID实例,以后用ctx.uniID调用相应方法
const uniIDIns = require("./middleware/uni-id-ins")
app.use(uniIDIns)

// 3. 路由校验, 检查token
const jwt = require("./middleware/jwt")
app.use(jwt([
    "login", "register", "logout" // 不需验证列表
]))

// 4. 参数校验器挂载到应用
const parameter = require('koa-parameter')
app.use(parameter(app))

// 5. 挂载定义的路由到项目
const routes = require("./routes")
routes(app)

// 6. 监听用户请求返回处理结果
exports.main = app.listen()

路由

  • 根据用户的请求, 定义请求方式的规则
  • 当匹配到用户请求时,执行控制器对应的方法
  • 这里使用 koa-router 来对路由的管理

安装 koa-router

npm install koa-router

定义路由挂载函数

  • 为方便书写, 这里使用自动路由挂载方式
// app/routes/index.js
// 路由自动挂载到 app, 这里未考虑子目录

const fs = require("fs")

module.exports = (app) => {
    fs.readdirSync(__dirname).forEach(file => {
        if (file === "index.js") return
        const route = require(`./${file}`)
        app.use(route.routes()).use(route.allowedMethods())
    })
}
  • 每一类尽可用一个文件来管理路由

action 请求方式

由于云函数的请求本身就没有 GET/POST 等这类的请求,这里把习惯直接 方法名称+参数 来定义的路由的 action 请求方式

  • 注:为方便使用 koa-router 约定定义路由时全部采用 POST 方式来定义
// action 用户路由管理

const Router = require("koa-router")
const router = new Router({
    // prefix : "/api/users" // 路由前缀用不用看自己的喜好
})

// 用户管理控制器
const users = require("../controller/users")

router.post("register", users.register) // 用户注册
router.post("login", users.login)       // 用户登录
router.post("getUserList",users.list)   // 用户列表
router.post("updateUser",users.update)  // 修改用户
router.post("deleteUser",users.remove)  // 删除用户
router.post("setAvatar",users.setAvatar) // 设置头像

module.exports = router

如 "用户登录" 配置运行测试参数应写为:

{
    "action":"login",
    "params":{
        "username":"admin",
        "password":"123456"
    }
}

http 请求方式

习惯用 koa 开发的喜欢 RESTFul API 方式(方法类型+路径+参数)来定义路由, 这里称为 http 请求方式

// app/routes/users.js
// 用户路由管理
// 一般采用 RESTFul 风格

const Router = require("koa-router")

const router = new Router({
    prefix : "/api/users" // 路由前缀
})

// 用户管理控制器
const users = require("../controller/users")

router.post("/", users.register)    // 用户注册 
router.post("/login", users.login)  // 用户登录
router.get("/",users.list)          // 用户列表
router.put("/:id",users.update)     // 修改用户
router.delete("/:id",users.remove)  // 删除用户
router.put("/:id/avatar",users.setAvatar)  // 设置头像

module.exports = router

如 "用户登录" 配置运行测试参数应写为:

{
    "method":"post",
    "url":"/api/users/login",
    "data":{
        "username":"admin",
        "password":"123456"
    }
}

上述两种请求方式项目开发中尽可能选用一种。

控制器

  • 校验请求参数
  • 根据请求参数从业务数据中获取数据
  • 对获取的数据进行加工后返回给用户
// app/controller/users.js
// 用户管理控制器

// 用户业务数据
const users = require("../service/users")

class Users {
    // 用户登录
    async login(ctx) {
        const { username, password } = ctx.request.body
        ctx.body = await ctx.uniID.login({ username, password })
    }

    // 用户注册
    async register(ctx) {
        // 参数校验
        ctx.verifyParams({
            username: { type: "string", required: true },
            password: { type: "string", required: true, min: 6 }
        })
        const { username, password } = ctx.request.body

        ctx.body = await ctx.uniID.register({
            username,
            password
        })
    }

    // 用户列表
    async list(ctx) {
        ctx.body = await users.find()
    }

    // 修改用户
    async update(ctx) {
        if(!ctx.params.id) ctx.throw(422,"未提供修改用户的id")
        ctx.body = await users.update(ctx.params.id, ctx.request.body)
    }

    // 删除用户
    async remove(ctx) {
        if(!ctx.params.id) ctx.throw(422,"未提供删除用户的id")
        ctx.body = await users.remove(ctx.params.id)
    }

    // 设置头像只能自己给自己设置
    async setAvatar(ctx) {
        if(ctx.state.user.uid !== ctx.params.id) ctx.throw(403, "没有权限操作")
        ctx.body = await ctx.uniID.update(ctx.params.id,{           
            avatar:ctx.request.body.avatar
        })
    }

}

module.exports = new Users()

参数校验

  • 用来校验请求传过来的参数是否是自己所需要的
  • 校验数据为
    • ctx.request.query
    • ctx.request.body

安装

npm i koa-parameter

引入挂载

在index.js中引入koa-paramete

const parameter = require('koa-parameter')
app.use(parameter(app))

使用

如用户管理控制器中 “用户注册”

ctx.verifyParams({
    username: { type: "string", required: true },
    password: { type: "string", required: true, min: 6 }
})

不满足规则时, 自动返回错误信息给用户, 状态码为 422

规则示例

 {
    param1: 'string', // 必填的字符串入参
    param2: 'string?', // 可选的字符串入参
    param3: {
        type: 'int', // 整形入参
        required: false, // 该入参可选
        min: 0, // 该入参的最小值
        max: 10, // 该入参的最大值
    }
}

业务数据

  • 根据控制需求与数据库进行交互
  • 把交互的结果返回给控制器
// app/service/users.js
const db = uniCloud.database()

class Users {
    // 查询数据表中记录
    async find() {
        let res = await db.collection("uni-id-users").field({
            password: false
        }).get()

        return res.data
    }

    // 更新数据表中记录
    async update(id, data) {
        return await db.collection("uni-id-users").doc(id).update(data)
    }

    // 删除数据表中记录
    async remove(id) {
        return await db.collection("uni-id-users").doc(id).remove()
    }
}

module.exports = new Users()

路由验证

  • 这里相当于是对用户鉴权的判断, 是否是登录用户,即 token 是否正确和是否过期
  • 如何是否有权限进行操作应根据权限管理来进行判断
// 传入不需要校验的路由数组 noNeedToken=[]
module.exports = function(noNeedToken){
    return async (ctx, next) => {       
        if (!noNeedToken || noNeedToken.indexOf(ctx.event.action) === -1){
            if (!ctx.event.uniIdToken) ctx.throw(403, "未携带token")

            // 用uniID.checkToken校验
            const payload = await ctx.uniID.checkToken(ctx.event.uniIdToken)
            if (payload.code) ctx.throw(401, "校验未通过")

            // 将用户数据挂载到 ctx
            ctx.state.user = payload

            // 新 token 在config内配置了tokenExpiresThreshold的值
            if(payload.token) ctx.token = payload.token 
        }

        await next()
    }
}

前端数据请求

封装请求方法

// /api/request.js
const BASE_URL = "app-action" // "你的云函数名称"

function request(opts = {}) {
    if (!(opts.action ? opts.action : opts.url)) throw "请求方法必须有 " + opts.action ? "action." : "url."
    let data = opts.action ? {
        action: opts.action,
        params: opts.params || {}
    } : {
        method: opts.method || "get",
        url: opts.url,
        data: opts.data || {},
        header: {
            // 自动绑定 token
            authorization: `Bearer ${uni.getStorageSync("uni_id_token") || ""}`
        }
    }

    return new Promise((reslove, reject) => {
        uniCloud.callFunction({
            name: BASE_URL,
            data: data,
            success(res) {
                // 保存token,适用于自动更新
                if (res.result && res.result.code === 0 && (res.result.token || (res.result.data && res
                        .result.data.token))) {
                    uni.setStorageSync('uni_id_token', res.result.token)
                }
                reslove(res.result)
            },
            fail(err) {
                uni.showToast({
                    title: "请求失败" + err.message,
                    icon: "none"
                })
                reject()
            }
        })
    })
}

export default request

使用

一般可以将封装好的请求挂载到 vue 上

// main.js
import request from "./api/request.js"
Vue.prototype.$request = request

使用时 this.$request 即可调用

http请求 开发使用

let res = await this.$request({
    method:"post",
    url:"/api/users/login",
    data:{
        username:"admin",
        password:"123456"
    }
})

返回信息

// 正确返回
{
    code:0,
    data:{
        token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
    }
}

// 失败返回
{
    code:401,
    msg:"用户名或密码不正确"
}

action请求 开发使用

let res = await this.$request({
    action:"login",
    params:{
        username:"admin",
        password:"123456"
    }
})

返回信息

// 成功返回
{
    code: 0
    message: "登录成功"
    msg: "登录成功"
    token: "eyJhbGciOiJ...."
    tokenExpired: 1624919764836
    type: "login"
    uid: "60d7cafc3b7d35000175246c"
    userInfo: {_id: "60d7cafc3b7d35000175246c", username: "admin",…}
    username: "admin"
}

// 失败返回
{
    code: 10102,
    message: "密码错误",
    msg: "密码错误"
}

结语

通过使用,使用 uni-koa 框架,能更有效进行项目的组装,对于习惯 koa 开发者来说基本拿来就能上手,对于刚接触的 koa 开发的用户能更有效将 uniCloud 云开发项目更加健壮。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件不采集任何数据

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

许可协议

MIT协议

使用中有什么不明白的地方,就向插件作者提问吧~ 我要提问