Skip to content

基于nodejs,koa的server端框架,使用装饰器构建

License

Notifications You must be signed in to change notification settings

hfzhae/zyd-server-framework2

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

69 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

使用babel方案支持ES Module规范,因此创建项目后,在运行前请在项目中配置.babelrc文件。

Installation

$ npm install -s zyd-server-framework2

Quickstart

/.babelrc

{
  "presets": [
    "@babel/preset-env"
  ],
  "plugins": [
    [
      "@babel/plugin-proposal-decorators",
      {
        "legacy": true
      }
    ]
  ]
}

/package.json

{
  "name": "myApp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "serve": "nodemon --exec babel-node index.js",
    "prod": "babel-node index.js"
  },
  "author": "",
  "license": "ISC"
}

/index.js

import { Zsf } from "zyd-server-framework2"
const app = new Zsf() 
app.start()
$ npm run serve
┌───────────────────────┐
│ Powered by zydsoft®   │
│ zyd-server-framework2 │
└───────────────────────┘
version:1.0.x

configs: Index √
middlewares: Users √
controller: Users √
dbs: Mongo √
middleware: error √
schedule: handler √
services: Users √
router: post /api/Users/add √
router: get /api/Users/get √
models: Users √

start on port: 3000

┌───────────────┐
│ start success │
└───────────────┘

Options

/index.js

import { Zsf } from "zyd-server-framework2"
const app = new Zsf({ 
  baseUrl: "/open", // 基础路径设置
  ignoreDir: [ // 需要忽略的目录, 默认忽略:./.git、./node_modules
    "./homePage"
  ],
  ignoreFile: [ // 需要忽略的文件
    "./page/index.js",
    "./index.js"
  ],
  bodyParserOptions: { // koa-bodyparser配置对象
    jsonLimit: '200mb',
    formLimit: '200mb',
    textLimit: '200mb',
  },
  beforeInit(koaApp){ // 生命周期函数 - 初始化前
    koaApp.use(require("koa2-cors")()) // 跨域设置
    const session = require("koa-session") // session设置
    koaApp.keys = ["some secret hurr"]
    koaApp.use(session({
      key: "koa:sess",
      maxAge: 86400000,
      overwrite: true,
      httpOnly: true,
      signed: true,
      rolling: false,
      renew: false,
    }, koaApp))
  },
  afterInit(koaApp){ ... } // 生命周期函数 - 初始化后
})
app.start(3000, callBack(){
  console.log("start on port:3000")
})

Class decorators

name params desc
Controller prefix,options 定义控制器对象,prefix(String):前缀路径,options(Object)选项,支持middlewares(Array)定义类中间件
Config 定义配置对象
DataBase 定义数据库对象
Model 定义数据库模型对象
Plugin 定义插件模型对象
Service 定义服务对象
Middleware mids 定义全局中间件对象,mids(Array[Class]):中间件对象
Decorators 自定义装饰器

Function decorators

name decorators params function params desc
Get url,options ctx,next 定义Get方法路由,url(String)后置路径,options(Object)选项,支持middlewares(Array)定义中间件,ctx(koa.context),next(koa.next)
Put url,options ctx,next 定义Put方法路由,url(String)后置路径,options(Object)选项,支持middlewares(Array)定义中间件,ctx(koa.context),next(koa.next)
Del url,options ctx,next 定义Del方法路由,url(String)后置路径,options(Object)选项,支持middlewares(Array)定义中间件,ctx(koa.context),next(koa.next)
Post url,options ctx,next 定义Post方法路由,url(String)后置路径,options(Object)选项,支持middlewares(Array)定义中间件,ctx(koa.context),next(koa.next)
Patch url,options ctx,next 定义Patch方法路由,url(String)后置路径,options(Object)选项,支持middlewares(Array)定义中间件,ctx(koa.context),next(koa.next)
Schedule interval app 定义定时器对象,interval(String)定时器规则crontab格式,app全局模块,详见Global module

Global module

name desc usage
config 配置对象数组,对应Config装饰器对象 this.config
db 数据库连接对象数组,对应DataBase装饰器对象 this.db
model 模块对象数组,对应Model装饰器对象 this.model
plugin 插件对象数组,对应Plugin装饰器对象 this.plugin
service 服务对象数组,对应Service装饰器对象 this.service

Config

/config/conf.js

import { Config } from "zyd-server-framework2"
@Config()
class Index {
  constructor() {
    this.path = "/"
  }
}

/controller/users.js

this.config.Index.path

Controller

/controller/users.js

import { Post, Get, Controller } from "zyd-server-framework2"
import assert from "http-assert"
import authToken from "../middleware/authToken"
@Controller("api", {
  middlewares: [authToken]
}) // prefix
class Users {
  @Post("", {
    middlewares: [
      async function validation (ctx, next) {
        const name = ctx.request.body.name
        assert(name, 400, "Missing name")
        await next()
      },
    ]
  })
  add (ctx) {
    return { success: true, ...ctx.request.body }
  }
  @Get("getInfo")
  async get (ctx) {
    console.log(ctx.state.partnerId) // xxxxxx
    console.log(this.config.Index.path) // \
    console.log(ctx.request.query.name) // lucy
    return await this.service.Users.setUsers(ctx)
  }
}

http:https://localhost:3000/api/Users/getInfo?name=lucy

/controller/product.js

import { Get, Controller } from "zyd-server-framework2"
@Controller("api") // prefix
class Product {
  @Get("/") // 路由后缀为:/ 时,直接使用对象定义路由
  async query (ctx) {
    return await this.service.Product.query(ctx)
  }
}

http:https://localhost:3000/api/Product

DataBase

/dataBase/mongo.js

import { DataBase } from "zyd-server-framework2"
import mongoose from "mongoose"
@DataBase()
class Mongo {
  constructor() {
    this.prod = mongoose.createConnection(`mongodb:https://127.0.0.1:27017?replicaSet=rs0`, {
      // useCreateIndex: true,
      // useFindAndModify: false,
      useNewUrlParser: true,
      useUnifiedTopology: true,
      dbName: "prodDb"
    })
    this.prod.on("connected", () => {
      console.log('mongodb connect prod success')
    })
    this.prod.on("error", () => {
      console.log('mongodb connect prod error')
    })
    this.prod.on("disconnected", () => {
      console.log('mongodb connect prod disconnected')
    })
    this.test = mongoose.createConnection(`mongodb:https://127.0.0.1:27017?replicaSet=rs0`, {
      // useCreateIndex: true,
      // useFindAndModify: false,
      useNewUrlParser: true,
      useUnifiedTopology: true,
      dbName: "testDb"
    })
    this.test.on("connected", () => {
      console.log('mongodb connect test success')
    })
    this.test.on("error", () => {
      console.log('mongodb connect test error')
    })
    this.test.on("disconnected", () => {
      console.log('mongodb connect test disconnected')
    })
  }
  async mongoSession (dataBase) {
    const session = await dataBase.startSession({
      readPreference: { mode: 'primary' }, //只从主节点读取,默认值。
    })
    await session.startTransaction({
      readConcern: { level: 'majority' }, //读取在大多数节点上提交完成的数据。level:"snapshot"读取最近快照中的数据。
      writeConcern: { w: 'majority' }, //大多数节点成功原则,例如一个复制集 3 个节点,2 个节点成功就认为本次写入成功。 w:"all"所以节点都成功,才认为写入成功,效率较低。
    })
    return session
  }
}
this.db.Mongo.prod
this.db.Mongo.test

/dataBase/mssql.js

import Sequelize from "sequelize"
import { DataBase } from "zyd-server-framework2"
@DataBase()
class Mssql {
  constructor() {
    this.prod = new Sequelize("eb3000", "sa", "", {
      host: "localhost",
      dialect: "mssql",
      dialectOptions: {
        options: {
          encrypt: false,
        },
      }
    })
    this.conncet()
  }
  async conncet () {
    try {
      await this.prod.authenticate()
      console.log("mssql connect success")
     } catch (err) {
      console.log('mssql connect error', err)
    }
  }
}
this.db.Mssql.prod

Middleware

/middleware/middleware.js

import { Middleware } from "zyd-server-framework2"
import koaStatic from "koa-static"
import mount from "koa-mount"
@Middleware([
  "error",
  "favicon",
  "homePage",
])
class Middlewares {
  constructor() {
    this.homePage = mount('/homePage', koaStatic('./homePage')) // 静态页面配置在构造器中
  }
  async error (ctx, next) => {
    try {
      await next()
    } catch (err) {
      console.log(err)
      const code = err.status || 500
      const message = err.response && err.response.data || err.message
      ctx.body = {
        code,
        message
      }
      ctx.status = code // 200
    }
  }
  async favicon (ctx, next) => {
    if (ctx.path === "/favicon.ico") {
      ctx.body = ""
      return
    }
    await next()
  } 
}

/middleware/authToken.js

import assert from "http-assert"
export default async function (ctx, next) { // 此处不能使用尖头函数,否则无法通过this获取全局模块数组
  assert(ctx.header.token, 408, "invalid token")
  ctx.state.partnerId = "xxxxxx"
  await next()
}

Model

/model/users.js

import mongoose from "mongoose"
import { Model } from "zyd-server-framework2"
@Model()
class Users {
  constructor() {
    const schema = new mongoose.Schema({
      name: { type: String },
      age: { type: Number }
    }, {
      versionKey: false,
      timestamps: {
        createdAt: "createdAt",
        updatedAt: "updatedAt"
      }
    })
    this.prod = this.db.Mongo.prod.model("users", schema, "users")
    this.test = this.db.Mongo.test.model("users", schema, "users")
  }
}
this.model.Users.prod.find()
>/model/product.js
import { Model } from "zyd-server-framework2"
import Sequelize from "sequelize"
@Model()
class Product {
  constructor() {
    this.prod = this.db.Mssql.prod.define("biProduct", {
      id: {
        type: Sequelize.NUMBER,
        primaryKey: true
      },
      code: Sequelize.STRING,
      title: Sequelize.STRING,
    }, {
      timestamps: false,
      freezeTableName: true
    })
  }
}
this.model.Product.prod.findAll({
  where: {
    id: 1
  }
})

Plugin

/plugin/utils.js

import { Plugin } from "zyd-server-framework2"
@Plugin()
class Utils {
  constructor() {
    /**
     * 日期格式化方法
     * @param {*} fmt 
     * @returns 
     * @example new Date().format("yyyy-MM-dd")
     */
    Date.prototype.format = function (fmt) {
      // 将当前
      var o = {
        "M+": this.getMonth() + 1, //月份 
        "d+": this.getDate(), //日 
        "h+": this.getHours(), //小时 
        "m+": this.getMinutes(), //分 
        "s+": this.getSeconds(), //秒 
        "q+": Math.floor((this.getMonth() + 3) / 3), //季度 
        "S": this.getMilliseconds() //毫秒 
      };
      // 先替换年份
      if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
      // 再依次替换其他时间日期内容
      for (var k in o)
        if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
      return fmt;
    }
  }
  /**
   * 阻塞函数
   * @param {Number} milliSeconds 毫秒数 
   */
  sleep (milliSeconds) {
    const startTime = new Date().getTime()
    while (new Date().getTime() < startTime + milliSeconds) { }
  }
  getClientIp (req) {//获取客户端ip地址
    let ip = req.headers['x-forwarded-for'] ||
      req.ip ||
      req.connection.remoteAddress ||
      req.socket.remoteAddress ||
      req.connection.socket.remoteAddress || '';
    if (ip.split(',').length > 0) {
      ip = ip.split(',')[0];
    }
    if (ip === "::1") ip = "127.0.0.1"
    return ip.match(/\d+\.\d+\.\d+\.\d+/)[0];
  }
}
this.plugin.Utils.sleep(5000)

Schedule

/schedule/index.js

import { Schedule } from "zyd-server-framework2"
class Index {
  @Schedule("0 0 1 * * *") //crontab格式
  handler () {
    console.log("这是一个定时任务 " + new Date().toLocaleString())
  }
}

Service

/service/users.js

import { Service } from "zyd-server-framework2"
import assert from "http-assert"
@Service()
class Users {
  async setUsers (ctx) {
    // mongo数据库执行事物方式
    const session = await this.db.Mongo.mongoSession(this.db.Mongo.prod)
    let result = []
    try {
      result.push(await this.model.Users.prod.create(
        [{ name: "张三", age: 25 }],
        { session }
      ))
      result.push(await this.model.Users.prod.findByIdAndUpdate(
        result._id,
        { $set: { name: "李四" }},
        { session }
      ))
      assert(result[1], 401, "写库失败,数据回滚")
      await session.commitTransaction()
      return result
    } catch (err) {
      await session.abortTransaction()
      assert(false, err.status, err.message)
    } finally {
      await session.endSession()
    }
  }
}
this.service.Users.setUsers(ctx)

/service/product.js

import { Service } from "zyd-server-framework2"
@Service()
class Product {
  async query (ctx) {
    return await this.model.Product.prod.findAll({
      where: {
        id: 1
      }
    })
  }
}
this.service.Product.query(ctx)

Decorators

/decorators/authToken.js

import assert from "http-assert"
import jwt from "jsonwebtoken"
/**
 * 鉴权装饰器 2023-6-25 zz
 */
export default () => {
  return (target, property, descriptor) => {
    const oldValue = descriptor.value
    descriptor.value = function () {
      const ctx = arguments[0]
      const token = String(ctx.req.headers.authorization || "").split(" ").pop()
      const { jwtKey } = target.config.Global
      assert(jwtKey, 401, "缺少jwtKey")
      assert(token, 401, "缺少token")
      const { partnerId } = jwt.verify(token, jwtKey + ctx.state.experiment, (err, decode) => {
        err && assert(false, 401, err.message)
        return decode
      })
      assert(partnerId, 401, "无效的token")
      ctx.state.partnerId = partnerId
      return oldValue.apply(null, arguments)
    }
    return descriptor
  }
}
import { Post, Controller } from "zyd-server-framework2"
import authToken from "../../decorators/authToken"
@Controller()
class Doctor {
  @Post("/")
  @authToken() // token鉴权
  add (ctx) {
    const data = ctx.request.body
    assert(data, 400, "缺少data")
    assert(data.phone || data.wxopenid || data.email, 400, '缺少phone 或 wxopenid 或 email')
    const { database, partnerId } = ctx.state
    return this.service.Doctor.add({ partnerId, database, data })
  }
}

License

MIT

About

基于nodejs,koa的server端框架,使用装饰器构建

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published