更新记录
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 云开发项目更加健壮。