zyd-server-framework2
v1.0.53
Published
基于nodejs,koa的server端框架,使用装饰器构建
Downloads
60
Readme
zyd-server-framework2
使用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://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://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://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://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 })
}
}