基于 uniCloud 云函数项目兼容 koa2 插件或 uni-id 的 web 开发框架
uni-koa 借鉴 koa2 的思想,针对 uniCloud 云函数特点构建一个快速开发方便扩展的框架,充分使用 koa2 第三方插件和 uniClound 插件的优势最大程度来满足云函数项目开发的需求。
把 uni-koa公共模块 导入或拷贝到在项目中 uniCloud/cloudfunctions/common 下
= 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, 分为三个方面
一般由入口文件、配置文件、路由文件、控制器文件、数据库业务文件和中间件文件构成
app
+ config // 配置文件
+ controller // 解析输入, 返回结果
+ middleware // 存放中间件
+ node_modules // 安装的包
+ routes // 路由管理
- index.js // 自动挂载本目录下路由管理文件
+ service // 业务逻辑, 读写数据库
+ utils // 工具方法
- index.js // 云函数项目入口文件
- package.json
npm init -y
npm install koa-router koa-parameter bcryptjs jsonwebtoken koa-jwt
/*
koa-router 路由管理
koa-parameter 参数校验
bcryptjs 加密
jsonwebtoken token生成与校验
koa-jwt 路由校验
*/
// app-http/index.js
const Koa = require("uni-koa")
const app = new Koa()
const parameter = require('koa-parameter')
const koaJwt = require('koa-jwt')
// 自动注入路由方法
const routes = require("./routes")
// 自动刷新 token 中间件
const refresh = require("./middleware/refresh-token")
// 耗时统计
app.use(async (ctx, next) => {
const start = new Date()
await next()
const ms = new Date() - start
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
// uniCloud.logger.info(`${ctx.method} ${ctx.url} - ${ms}ms`)
})
// 自动刷新 token
app.use(refresh)
// 路由控制验证 token
const {TOKEN}=require("./config")
app.use(koaJwt({secret:TOKEN.key}).unless({
path:TOKEN.filter_path // 配置不验证 token 的路由
}))
// 校验参数
app.use(parameter(app))
// 挂载路由
routes(app)
// 启动监听请求并返回结果
exports.main = app.listen()
// app-http/config/index.js
module.exports = {
TOKEN: {
key: "设置你的 token 加密串",
filter_path: [ // 不验证的路径
"/api/users/login",
"/api/users/register"
],
exp: 7 * 24 * 60 * 60, // 过期时间 7 天
auto_refresh:true // 自动刷新 token
interval:7 * 24 * 60 * 60 / 2
}
}
// app-http/middleware/refresh-token.js
const jsonwebtoken = require("jsonwebtoken")
const { TOKEN } = require("../config")
module.exports = async (ctx, next) => {
if (TOKEN.auto_refresh) {
let { exp, id, username } = ctx.state.user || {}
if (exp) {
// 这里约定 TOKEN.exp在前后超期范围一半范围内重新生成 token,否则重新登录
if (Math.abs(new Date(exp * 1000) - new Date()) / 1000 > parseInt(TOKEN.interval)) {
const payload = { id, username }
const token = jsonwebtoken.sign(payload, TOKEN.key, { expiresIn: TOKEN.exp })
ctx.token = token
}
}
}
await next()
}
这里未考虑子目录,需要自行扩展
// app-http/routes/index.js
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())
})
}
// app-http/routes/users.js
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.get("/", users.list) // 用户列表
router.put("/:id", users.update) // 修改用户
router.delete("/:id", users.remove) // 删除用户
module.exports = router
// app-http/controller/users.js
const jsonwebtoken = require('jsonwebtoken')
const users = require("../service/users")
const { encrypt, compare, dateFormat } = require("../utils")
const { TOKEN } = require("../config")
class Users {
// 用户注册
async register(ctx) {
// 校验参数
ctx.verifyParams({
username: { type: "string", required: true },
password: { type: "string", required: true }
})
// 获取参数
const { username, password } = ctx.request.body
// 获取用户信息
const userinfo = await users.findOne({ username })
if (userinfo) ctx.throw(409, '用户名已经存在')
// 加密密码
const pwd = encrypt({ username, password })
// 添加用户
let res = await users.add({
username,
password: pwd
}) || {}
// 生成 token
const payload = {
id: res._id,
username
}
const token = jsonwebtoken.sign(payload, TOKEN.key, { expiresIn: TOKEN.exp })
// 返回 token 给用户绑定
ctx.body = { token }
}
// 用户登录
async login(ctx) {
// 校验参数
ctx.verifyParams({
username: { type: "string", required: true },
password: { type: "string", required: true }
})
const { username, password } = ctx.request.body
// 获取用户信息
const userinfo = await users.findOne({ username })
if (!userinfo) ctx.throw(401, '用户名或密码不正确')
// 验证密码
if (!compare({ username, password }, userinfo.password)) ctx.throw(401, '用户名或密码不正确')
// 生成token
const payload = {
id: userinfo._id,
username
}
const token = jsonwebtoken.sign(payload, TOKEN.key, { expiresIn: TOKEN.exp })
// 返回 token 给用户绑定
ctx.body = { token }
}
// 用户列表
async list(ctx) {
ctx.body = await users.find()
}
// 修改用户
async update(ctx) {
ctx.body = await users.update(ctx.params.id, ctx.request.body)
}
// 删除用户
async remove(ctx) {
ctx.body = await users.remove(ctx.params.id)
}
}
module.exports = new Users()
// app-http/service/users.js
const { dateFormat } = require("../utils")
const db = uniCloud.database()
class Users {
// 添加用户 {id: xxxx}
async add(data) {
const user = Object.assign({}, data, {
create_time: dateFormat(new Date()),
update_time: dateFormat(new Date())
})
return await db.collection("users").add(user)
}
// 删除用户 {affectedDoc:1,deleted:1}
async remove(id) {
return await db.collection("users").doc(id).remove()
}
// 修改用户 {affectedDoc:1,updated:1,upsertedId:xxxx}
async update(id, data) {
const user = Object.assign({}, data, {
update_time: dateFormat(new Date())
})
return await db.collection("users").doc(id).update(user)
}
// 用户列表 {affectedDoc:20,data:[]}
async find() {
const res = await db.collection("users").field({ password: false }).get()
return res.data
}
// 获取一个用户信息
async findOne(condition) {
const res = await db.collection("users").where(condition).get()
return res.data[0]
}
}
module.exports = new Users()
// app-http/utils/index.js
const bcrypt = require('bcryptjs')
// 加密密码
function encrypt(obj){
const salt = bcrypt.genSaltSync(10)
return bcrypt.hashSync(JSON.stringify(obj), salt)
}
// 验证密码
function compare(obj, hash){
return bcrypt.compareSync(JSON.stringify(obj), hash)
}
/*
时间格式化
let date = new Date()
dateFormat(date,"YYYY-MM-DD hh:mm")
*/
function dateFormat(date,fmt="YYYY-MM-DD hh:mm:ss") {
let ret;
const opt = {
"Y+": date.getFullYear().toString(), // 年
"M+": (date.getMonth() + 1).toString(), // 月
"D+": date.getDate().toString(), // 日
"h+": date.getHours().toString(), // 时
"m+": date.getMinutes().toString(), // 分
"s+": date.getSeconds().toString() // 秒
// 有其他格式化字符需求可以继续添加,必须转化成字符串
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(ret[1], (ret[1].length == 1) ? (opt[k]) : (opt[k].padStart(ret[1].length, "0")))
};
};
return fmt;
}
module.exports = {
dateFormat,encrypt,compare
}
npm init -y
npm install koa-router
// app-action/index.js
// 引入 uni-koa 创建 app 实例
const Koa = require("uni-koa")
const app = new Koa()
// 引入定义的路由集合
const routes = require("./routes")
// 引入校验路由中间件, 校验 token
const jwt=require("./middleware/jwt")
app.use(jwt([
"login","register","logout" // 不需验证列表
]))
// 挂载路由
routes(app)
// 启动监听请求并返回结果
exports.main = app.listen()
// ./middleware/jwt.js
const uniID = require("uni-id")
// 传入不需要校验的路由数组 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")
const payload = await uniID.checkToken(ctx.event.uniIdToken)
if (payload.code) ctx.throw(401, "校验未通过")
ctx.state.user = payload
if(payload.token) ctx.token = payload.token // 新 token 在config内配置了tokenExpiresThreshold的值
}
await next()
}
}
// app-action
const Router = require("koa-router")
const router = new Router()
// 用户管理控制器
const users = require("../controller/users")
// 这里约定均使用 post 方法标识请求
router.post("register",users.register) // 用户注册
router.post("login",users.login) // 用户登录
router.post("logout",users.logout) // 退出登录
router.post("getUserList", users.list) // 用户列表
router.post("updateUser", users.update) // 修改用户
router.post("removeUser", users.remove) // 删除用户
module.exports = router
// app-action/controller/user.js
const uniID = require('uni-id')
// 用户业务操作管理
const users = require("../service/users")
class Users {
// 用户注册
async register(ctx) {
const { username, password } = ctx.params
ctx.body = await uniID.register({username, password})
}
// 用户登录
async login(ctx) {
const { username, password } = ctx.params
ctx.body = await uniID.login({username, password})
}
// 退出登录
async logout(ctx) {
const { username, password } = ctx.params
ctx.body = await uniID.logout(ctx.uniIdToken)
}
// 用户列表 自定义方法
async list(ctx) {
let res = await users.find()
ctx.body = {code:0,list:res}
}
// 修改用户 一般只能自己修改自己
async update(ctx) {
const userinfo = ctx.state.user
ctx.body = await users.update(userinfo.id, ctx.params)
}
// 删除用户
async remove(ctx) {
ctx.body = await users.remove(ctx.params.id)
}
}
module.exports = new Users()
// app-action/service/users.js
const db = uniCloud.database()
class Users {
// 获取用户列表
async find() {
// 移除密码
const res = await db.collection("uni-id-users").field({ password: false }).get()
return res.data
}
// 删除用户
async remove(id){
return await await db.collection("uni-id-users").doc(id).remove()
}
// 修改用户
async update(id,data){
return await await db.collection("uni-id-users").doc(id).update(data)
}
}
module.exports = new Users()
// /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 即可调用
let res = await this.$request({
method:"post",
url:"/api/users/login",
data:{
username:"admin",
password:"123456"
}
})
返回信息
// 正确返回
{
code:0,
data:{
token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
}
// 失败返回
{
code:401,
msg:"用户名或密码不正确"
}
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 云开发项目更加健壮。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。