node.js --- (2)框架express/koa2/egg

253 阅读16分钟

github.com/hejiyun/Nod…

express

  1. Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架。
npm install -g express-generator
  1. 使用express创建node项目
express NodeExpress

image.png

  1. 启动项目
npm i npm start / nodo ./bin/www

winston

  1. 安装 nodemon 来监控 node.js 源代码的任何变化和自动重启你的服务器
npm install -g nodemon // 然后启动命令变为 nodemon bin/www
  1. 添加打印日志文件, 安装 winston
npm i winston
  1. winston使用

image.png

  1. 日志
const levels = {error:0,warn:1,info:2,http:3,verbose:4,debug:5,silly:6};
  1. 创建日志
const logger = winston.createLogger({ 
    transports:[ 
        new winston.transports.Console(), 
        new winston.transports.File({filename:'combined.log'})
    ]
});
  1. 自定义格式 format, format.combine设置打印日志的格式
format: format.combine( 
    format.label({ 
    label: path.basename(process.mainModule.filename)
    }), 
    format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' })
),
  1. 修改app.js, 修改error处理逻辑
// 具体操作 将 const logger = require('morgan'); 改成: const morgan = require('morgan') 
//相应的也要把 app.use(logger('dev')); 改成: app.use(morgan('dev')); 
//引入 logger.js 文件 const logger = require('./logger') 
// 修改error
// 处理非404的错误(throw 出来的错误) 

// 最终 app.js 
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var morgan = require('morgan');
var logger = require('./logger')

var indexRouter = require('./routes/index');
var usersRouter = require('./routes/users');


var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');

app.use(morgan('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.use('/', indexRouter);
app.use('/users', usersRouter);


// catch 404 and forward to error handler
app.use(function(req, res, next) {
  next(createError(404));
});

/**
* error handler
* @private
*/
// 处理非404的错误(throw 出来的错误)
const _errorHandler = (err, req, res, next) => {
  logger.error(`${req.method} ${req.originalUrl} ` + err.message)
  const errorMsg = err.message
  res.status(err.status || 500).json({
    code: -1,
    success: false,
    message: errorMsg,
    data: {}
  })
}
app.use(_errorHandler)

module.exports = app;

  1. 在项目的根目录添加一个配置打印日志格式的文件 logger.js,
const { createLogger, format, transports } = require('winston');
const fs = require('fs');
const path = require('path');

const env = process.env.NODE_ENV || 'development';
const logDir = 'log';

// Create the log directory if it does not exist
if (!fs.existsSync(logDir)) {
  fs.mkdirSync(logDir);
}

const filename = path.join(logDir, 'results.log');

const logger = createLogger({
  // change level if in dev environment versus production
  level: env === 'production' ? 'info' : 'debug',
  format: format.combine(
    format.label({ label: path.basename(process.mainModule.filename) }),
    format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' })
  ),
  transports: [
     // 终端打印日志信息
    new transports.Console({
      format: format.combine(
        format.colorize(),
        format.printf(
          info =>
            `${info.timestamp} ${info.level} [${info.label}]: ${info.message}`
        )
      )
    }),
    // log文件写入的信息格式
    new transports.File({
      filename,
      format: format.combine(
        format.printf(
          info =>
            `${info.timestamp} ${info.level} [${info.label}]: ${info.message}`
        )
      )
    })
  ]
});

module.exports = logger;

  1. 测试一下日志是否成功, 随便增加一个域名后缀

image.png

连接mysql

  1. 在根目录下添加config.js。 记录连接数据库的信息, 并且在上传时忽略
const configs = {
    mysql: {
      host: '127.0.0.1',
      port: '3306',
      user: 'root',
      password: '00000000',  // 自己设置的密码
      database: 'NodeExpress' // 数据库的名字
    },
    // 打印错误
    log: {
      error (message) {
        console.log('[knex error]', message)
      }
    }
  }
  
  module.exports = configs

数据库添加,表添加

  1. 使用navicat数据库管理工具新建数据库 database 名称为: NodeExpress, 并且在库下新建user表
  2. 右键mysql数据库, 选择新建数据库,填入NodeExpress, utf8, 确认创建。 然后右键NodeExpress, 选择新建表, 创建user, user新增三个字段, id, name, phone.

image.png

  1. 在user中随意添加几条数据

image.png

使用 Knex 增删改查数据库

  1. Knex.js是为 Postgres,MSSQL,MySQL,MariaDB,SQLite3,Oracle 和 Amazon Redshift 设计的 SQL 查询构建器,其设计灵活,便于携带并且使用起来非常有趣。Knex 的主要目标环境是 Node.js,
  2. 安装knex
npm install -save knex mysql
  1. 先在根目录增加 .gitignore文件忽略上传, config.js需要忽略保证安全
.DS_Store
.idea
npm-debug.log
yarn-error.log
node_modules
log
config.js 
  1. 添加数据库配置。 在根目录下创建models文件夹, 然后创建 knex.js文件进行数据库配置
// knex.js 
// 引用配置文件
const configs = require('../config');
// 把配置文件中的信息,设置在初始化配置中
module.exports = require('knex')({
  client: 'mysql',
  connection: {
    host: configs.mysql.host,
    port: configs.mysql.port,
    user: configs.mysql.user,
    password: configs.mysql.password,
    database: configs.mysql.database
  },
  // 打印错误
  log: {
    error (message) {
      console.log('[knex error]', message)
    }
  }
})
  1. 配置好连接数据库信息的文件后, 还需要添加一个base.js文件, 这个文件里 配置增删改查的逻辑
// models ---> base.js 
const knex = require('../models/knex');

class Base{
  constructor(props){
    this.table = props;
  }

  // 查找
  all (){
    return knex(this.table).select();
  }

  // 新增
  insert (params){
    return knex(this.table).insert(params);
  }

  // 更改
  update (id, params){
    return knex(this.table).where('id', '=', id).update(params);
  }

  // 删除
  delete (id){
    return knex(this.table).where('id', '=', id).del();
  }

}

module.exports = Base;
  1. 配置好了增删改查后, 还需要设计一下用户模型,在models下新增user.js
// models ---> user.js
const Base = require('./base');

class User extends Base {
  // 定义参数默认值为 user 表
  constructor(props = 'user'){
    super(props);
  }
}

module.exports = new User();
  1. 在根目录新建控制器文件夹 controllers,在 controllers 新建 user.js,并设置 showUser 方法
// 引用用户模版数据
const User = require('../models/user.js');

const userController = {
  // showUser 获取用户数据并返回到页面
  showUser: async function(req,res,next){
    try{
      let userData = await User.all()
      res.json({
        code: 200,
        message: "操作成功",
        data: userData
      })
    }catch(e){
      res.json({ code: 0, message: "操作失败", data: e })
    }
  },
  addUser: async function(req,res,next){
    try{
      await User.insert(req.body)
      res.json({
        code: 200,
        message: "操作成功",
        data: {}
      })
    }catch(e){
      res.json({ code: 0, message: "操作失败", data: e })
    }       
  },
  updateUser: async function(req,res,next){
    try{
      await User.update(req.body.id, req.body)
      res.json({
        code: 200,
        message: "操作成功",
        data: {}
      })
    }catch(e){
      res.json({ code: 0, message: "操作失败", data: e })
    }
  },
  deleteUser: async function(req,res,next){
    console.log(req.body, 'zheki')
    try{
      await User.delete(req.body.id)
      res.json({
        code: 200,
        message: "操作成功",
        data: {}
      })
    }catch(e){
      res.json({ code: 0, message: "操作失败", data: e })
    }
  }
}

module.exports = userController;
  1. 然后添加请求用户信息的接口,修改路由 routes/index.js,添加获取用户信息的接口
// routes--->index.js
var express = require('express');
var router = express.Router();
const userController = require('../controllers/user');
// 1. 引入包
const multer = require('multer');
// 2. 配置
const upload = multer({dest:'uploads/'}) // 上传的文件会保存在这个目录下,名称可以任意取


/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

// 获取用户信息
router.get('/get_user', userController.showUser);

// 新增用户信息
router.post('/add_user', userController.addUser);

// 更新用户信息
router.post('/update_user', userController.updateUser);

// 删除用户信息
router.post('/delete_user', userController.deleteUser);

module.exports = router;

  1. 阶段文件展示

image.png

  1. 测试接口是否成功, 有返回表数据即成功
访问一下 http://localhost:3000/get_user 页面反馈

image.png

express中get 与post(三种参数类型:简单键值对/json/form-data)请求参数获取, express.urlencoded && bodypaser.urlencoded

  1. 在低于4.16.0版本的express中, 通常使用body-parser 来设置请求参数类型,以达到在req中添加body的作用
  2. 在4.16.0+版本的express中,提供了 express.urlencoded,来代替body-parser的作用。 两者的使用方式是一样的
// app.js 
// bodypaser.urlencoded 
var bodyParser = require('body-parser');
// 解析 application/json 
app.use(bodyParser.json()); 
// 解析 url编码 
app.use(bodyParser.urlencoded({ extended: true }));
// express.urlencoded 
var express = require('express'); 
app.use(express.json()); 
app.use(express.urlencoded({ extended: false }));
  1. 配置后,post使用raw-->json格式类型数据 或者简单的键值对格式,即可正常请求操作

image.png

image.png

  1. 如果是form-data格式的数据,一般也用于文件上传, 那么需要进一步修改, 先安装multer
npm i multer
  1. 然后在router.js中, 引入配置multer
// 1. 引入包 
const multer = require('multer'); 
// 2. 配置 
const upload = multer({dest:'uploads/'}) 
// 上传的文件会保存在这个目录下,名称可以任意取
  1. 再在router.js中添加一个新增用户信息带文件的接口
// 新增用户信息带文件
router.post(
    '/add_file',
    upload.single('file'), 
    (req,res,next) => {userController.addFile(req,res,next)}
);

  1. 添加好后, 需要去controlles, user.js中添加addfile的控制器,并且注意, 需要在存的时候把file改成url形式存到表中,以便于契合实际服务器逻辑(一般文件都会存储在第三方中,表中存的是url)
 addFile: async function(req,res,next){
    console.log(req.body, req.file)
     // 处理file字段, 简单处理一下
    req.body.file = `http://www.xxxx.com?` + req.file.originalname
    try{
      await User.insert(req.body)
      res.json({
        code: 200,
        message: "操作成功",
        data: {}
      })
    }catch(e){
      res.json({ code: 0, message: "操作失败", data: e })
    }       
  },
  1. 测试一下是否可用,

image.png

  1. 终端打印结果

image.png

  1. 当然, 在发送请求,需要往user表中新增一个file字段,

image.png

查询数据库数据

  1. 查询使用get请求, 可以直接在地址栏拼接接口地址以查看数据
http://localhost:3000/get_user

向数据库新增一条数据 ,post方法则不能使用地址栏, 通过使用postman进行请求

  1. 为router.js添加新增add数据方法, 然后在控制器controller.js-》user.js中增加addUser方法
// router.js 
....
// 新增用户信息 
router.post(
'/add_user', 
userController.addUser); 


// controllerjs ----> user.js 

.... 
addUser: async function(req,res,next){ 
try{ await User.insert(req.body) 
res.json({ code: 200, message: "操作成功", data: {} }) 
}catch(e){ res.json({ code: 0, message: "操作失败", data: e }) } },

image.png

image.png

向数据库更新一条数据

  1. 为router.js添加新增update数据方法, 然后在控制器controller.js-》user.js中增加updateUser方法
// router.js .... // 更新用户信息 router.post('/update_user', userController.updateUser); // controllerjs ----> user.js .... updateUser: async function(req,res,next){ try{ await User.update(req.body.id, req.body) res.json({ code: 200, message: "操作成功", data: {} }) }catch(e){ res.json({ code: 0, message: "操作失败", data: e }) } },

image.png

image.png

删除数据库一条数据

  1. 为router.js添加新增delete数据方法, 然后在控制器controller.js-》user.js中增加deleteUser方法
// router.js 
....
// 删除用户信息 
router.post('/delete_user', userController.deleteUser);

// controllerjs ----> user.js 
.... 
deleteUser: async function(req,res,next){ console.log(req.body, 'zheki')
try{ 
await User.delete(req.body.id) 
res.json({ code: 200, message: "操作成功", data: {} }) 
}catch(e){ res.json({ code: 0, message: "操作失败", data: e }) } },

image.png

image.png

koa2

express 是目前最流行的node.jsweb应用级框架,它是基于es5设计的语法,与新的被称作下一代框架的koa相比,
它实现异步的过程会显得比较臃肿冗余, 而koa则是原团队基于es6的 Generator 语法所设计的,
在node。js支持async await后, koa又基于Promiseasyncawait 之上设计了koa2,
当然, 这也意味着需要node 7以上的版本才能支持, 
在使用上,发送请求信息方面 ,express与koa不同点在于express通过的是函数创建的方式, 而koa则是通过 new 来创建。
Koa 提供了一个 Context 对象, 它包含了req与res封装对象, 通过它来操作http的响应与请求。
路由方面, express自带了 router中间件系统, 而koa则需要使用koa-route/koa-router路由模块。
静态资源方面, 也同样如此,koa需要额外使用koa-static模块 在中间件合成方面,
express通过app.use()可以传入中间件的数据, 而koa需要使用koa-compose模块进行合成 
在表单处理方面,也就是post请求的键值对获取,express自带了表单解析功能, 而koa需要使用koa-body来获取, 
在文件上传方面, express通过multer中间件来处理表单数据, 而koa通过使用 koa-body进行操作 
由此可以看出, koa更像一个轻量级的express,它没有捆绑中间件, 所以保持了一个很小的体积,
至于具体的使用, 还是看个人需求, koa2的优势在于代码简洁。
  1. 安装koa, 这里直接使用脚手架搭建项目
npm install -g koa-generator / sudo(mac权限) npm install -g koa-generator
  1. 创建koa2项目, node 7+以上
koa2 -e NodeKoa2(项目名称)
  1. 安装依赖, 执行项目, 同样安装nodemon来动态监测node,
npm i 
npm start 
sudo npm install -g nodemon 
// 修改package.json中start
"scripts": { 
    "start": "nodemon bin/www", 
    ...
},
  1. 对于数据库操作, 就依然沿用mysql和knex,省略下步骤。 log也沿用
npm install -save knex mysql
npm install -g nodemon
  1. 那么还是根目录下创建 controllers/models/config.js/loggerjs,如果上面的已经创建了, 可以直接复制,基本可以复用
  2. 然后再修改app.js
const Koa = require('koa')
const app = new Koa()
const views = require('koa-views')
const json = require('koa-json')
const onerror = require('koa-onerror')
const bodyparser = require('koa-bodyparser')
const koaLogger = require('koa-logger')
var logger = require('./logger')

const index = require('./routes/index')
const users = require('./routes/users')

// error handler
onerror(app)

// middlewares
app.use(bodyparser({
  enableTypes:['json', 'form', 'text']
}))
app.use(json())
app.use(koaLogger())
app.use(require('koa-static')(__dirname + '/public'))

app.use(views(__dirname + '/views', {
  extension: 'ejs'
}))

// logger
app.use(async (ctx, next) => {
  const start = new Date()
  await next()
  const ms = new Date() - start
  console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
})

// routes
app.use(index.routes(), index.allowedMethods())
app.use(users.routes(), users.allowedMethods())

// error-handling
app.on('error', (err, ctx) => {
  logger.error(`${ctx.method} ${ctx.url} ` + err.message)
});


module.exports = app

  1. 再修改router.js, express和koa的区别在于 koa将req和res进行了一个封装对象ctx
const router = require('koa-router')()
const userController = require('../controllers/user');
// 1. 引入包
const multer = require('koa-multer');
// 2. 配置
const upload = multer({dest:'uploads/'}) // 上传的文件会保存在这个目录下,名称可以任意取


router.get('/', async (ctx, next) => {
  await ctx.render('index', {
    title: 'Hello Koa 2!'
  })
})

router.get('/string', async (ctx, next) => {
  ctx.body = 'koa2 string'
})

router.get('/json', async (ctx, next) => {
  ctx.body = {
    title: 'koa2 json'
  }
})

// 获取用户信息
router.get('/get_user', userController.showUser);

// 新增用户信息
router.post('/add_user', userController.addUser);

// 更新用户信息
router.post('/update_user', userController.updateUser);

// 删除用户信息
router.post('/delete_user', userController.deleteUser);


// 新增用户信息带文件
// 新增用户信息带文件
router.post('/add_file',upload.single('file'), (ctx) => {userController.addFile(ctx)});

module.exports = router

  1. 既然req.res变成了ctx。 那么就需要修改一下controllers里的方法参数
//controllers --> user.js
// 引用用户模版数据
const User = require('../models/user.js');

const userController = {
  // showUser 获取用户数据并返回到页面
  showUser:async function(ctx, name){
    try{
      let userData = await User.all()
      ctx.body = {
        code: 200,
        message: "操作成功",
        data: userData
      }
    }catch(e){
      ctx.app.emit('error', e, ctx);
      ctx.body = {
          code: ctx.response.status,
          msg: e.message
      };
    }
  },
  addUser: async function(ctx, name){
    // console.log(ctx.request.body)
    try{
      await User.insert(ctx.request.body)
      ctx.body = {
        code: 200,
        message: "操作成功",
        data: {}
      }
    }catch(e){
      ctx.app.emit('error', e, ctx);
      ctx.body = {
        code: ctx.response.status,
        msg: e.message
      };
    }       
  },
  updateUser: async function(ctx,name){
    console.log(ctx.request.body)
    try{
      await User.update(ctx.request.body.id, ctx.request.body)
      ctx.body = {
        code: 200,
        message: "操作成功",
        data: {}
      }
    }catch(e){
      ctx.app.emit('error', e, ctx);
      ctx.body = {
        code: ctx.response.status,
        msg: e.message
      };
    }
  },
  deleteUser:async function(ctx,name){
    console.log(ctx.request.body)
    try{
      await User.delete(ctx.request.body.id)
      ctx.body = {
        code: 200,
        message: "操作成功",
        data: {}
      }
    }catch(e){
      ctx.app.emit('error', e, ctx);
      ctx.body = {
        code: ctx.response.status,
        msg: e.message
      };
    }
  },
  addFile: async function(ctx,next){
    console.log(ctx.request.body, ctx.req.body)
    // console.log(ctx.req.file, '这里是', ctx.req.body)
     // 处理file字段, 简单处理一下
     ctx.req.body.file = `http://www.xxxx2.com?` + ctx.req.file.originalname
     ctx.body = {
        filename: ctx.req.file.filename,//返回文件名
        body:ctx.req.body
      }
    try{
      ctx.body = {
        code: 200,
        message: "操作成功",
        data: ctx.req.body
      }
      await User.insert(ctx.req.body)
    }catch(e){
      ctx.app.emit('error', e, ctx);
      ctx.body = {
        code: ctx.response.status,
        msg: e.message
      };
    }       
  },
}

module.exports = userController;
  1. 用法和理解上基本不存在区别, 唯一值得注意的地方是, ctx里, req,/request 与 res/response 含义的区别
ctx.app 为应用程序实例引用。 
ctx.req 为 Node 的 request 对象。
ctx.res 为 Node 的 response 对象。
ctx.request 为 koa 的 Request 对象。
ctx.response 为 koa 的 Response 对象。
  1. 在普通非文件上传的接口中, 通过ctx.request.body获取参数, 而在文件上传接口formdata中, 通过ctx.req.body获取参数。

egg

创建一个egg项目

npm init egg --type=simple --registry=china // 输入路径名称NodeEgg

egg中多路由管理

// 在app 文件夹下创建router文件夹, 作为存储各路由模块的仓库 
// app ---> router 示例 model1模块路由的管理与导入 
// 在app --> router文件夹下创建model1.js文件, 并且写入model1模块所有路由 
// app ----> router ---> model1.js 
module.exports = app => { 
app.router.get('/', app.controller.home.index); app.router.get('/model1/list',
app.controller.model1.list.index) 
app.router.get('/model1/detail', app.controller.model1.detail.index) }; 

// 同时, 需要在controller文件夹下,创建相应的控制器 
// controller --> model1 ---> detail.js && list.js 
// detail.js
'use strict'; 
const { Controller } = require('egg');
class DetailController extends Controller { 
async index() { 
const { ctx } = this; 
ctx.body = 'this is the detail page return';
} 
} 
module.exports = DetailController; 

// list.js 
'use strict';
const { Controller } = require('egg'); 
class ListController extends Controller { 
async index() { 
const { ctx } = this; 
ctx.body = 'this is the list page return'; 
}
}
module.exports = ListController;

// 创建完模块路由后, 需要将模块路由导入到router中,
// app --->router.js
'use strict'; 
/** * @param {Egg.Application} app - egg application */
module.exports = app => { require('./router/model1')(app) };
  1. 测试路由管理是否可用, 在地址栏后,添加/model1/list测试是否成功返回写入文案。

image.png

image.png

中间件middleware

  1. 在app目录下创建middleware文件夹
egg 约定一个中间件是一个放置在 app/middleware 目录下的单独文件,
它需要导出一个普通的函数,该函数接受两个参数: 
options: 中间件的配置项,框架会将 app.config[${middlewareName}] 传递进来。
app: 当前应用 Application 的实例。
  1. 全局启用中间件 示例: slow.js
// 在middleware文件夹下创建 slow.js 
// middleware ---> slow.js 
module.exports = (options, app) => { 
return async function (ctx, next) { 
const startTime = Date.now() 
await next() 
console.log(options, '这里是参数') 
const consume = Date.now() - startTime
const { threshold = 0 } = options || {} 
console.log(`${ctx.url}请求耗时${consume}毫秒`) } }
  1. 然后在config.default。js中注入使用, config.middleware = ['slow']填入中间件的集合数组, config.middleName对应各中间件参数传递的具体配置
... 
config.middleware = ['slow'];
// slow传递参数threshold ,值为1
config.slow = { threshold: 1, };
  1. 对局部路由添加特定中间件, 依然使用slow示例
方式一 // 在config.default中的中间件参数配置中, 添加match属性, 值为特定路由前缀
... 
config.middleware = ['slow'];
// slow传递参数threshold ,值为1, 并且只在model1为前缀路由时启动
config.slow = { threshold: 1, match: "/model1/list", };

方式二 // config.default中中间件不加入特定中间件的配置, 直接在 router模块中引用
// config.default.js 
...
// 此处不需要添加特定中间件
slow.js config.middleware = []; 

// router ---> model1.js
// 使用app.middleware.slow({ threshold: 1 }) ,为/model1/list 加入特定的中间件slow。 
module.exports = app => { 
app.router.get('/', app.controller.home.index); app.router.get('/model1/list', 
app.middleware.slow({ threshold: 1 }), app.controller.model1.list.index) 
app.router.get('/model1/detail', app.controller.model1.detail.index) };
// 通过切换地址, 可以在控制台查看中间价添加结果
  1. egg 把中间件分成应用层定义的中间件(app.config.appMiddleware):config.default.js 中添加在middleware中的中间件 和框架默认内置的中间件(app.config.coreMiddleware),
  2. 内置中间件的取消方法, 示例
// 取消内置中间件 i18n 
// config.default.js 
... 
config.i18n = { enable: false }

控制器

  1. 控制器方面, 跟上面类似, 主要的作用就是充当一个桥梁 ,通过将调用 service中写好的查表方法(获取相应的数据,然后对于这些数据, 做一些业务性的逻辑处理, 这些当然也可以在控制器中做)的结果进行包装处理返回给渲染。
// 例如 
const { Controller } = require('egg'); 
class PostController extends Controller { 
async create() { 
const { ctx, service } = this;
const createRule = { title: { type: 'string' },
content: { type: 'string' }, }; 
// 校验和组装参数
ctx.validate(createRule); 
const data = Object.assign(ctx.request.body, 
{ author: ctx.session.userId }); 
// 调用 Service 进行业务处理 
const res = await service.post.create(data); 
// 响应客户端数据 
ctx.body = { id: res.id }; ctx.status = 201; } }
module.exports = PostController;
  1. 设置通用controller. 在app目录下新建core来存放通用controller, 并创建base_Controller.js
// app ---> core ---> base_Controller.js 
// app/core/base_controller.js 
const { Controller } = require('egg'); 
class BaseController extends Controller { 
// 这里可以配置一些希望拥有 的通用功能性函数和属性 
get user() { return this.ctx.session.user; } 
success(data) { this.ctx.body = { success: true, data }; }
notFound(msg) { this.ctx.throw(404, msg || 'not found'); } } 
module.exports = BaseController;
  1. 使用通用controller, 只需要在需要继承通用控制器的控制器文件里,将 require('egg') 替换成require('../core/base_controller')即可
const { Controller } = require('egg'); 
=========》 替换成下面即可在文件中访问通用controller中设定的方法和属性 
const Controller = require('../core/base_controller'); 
// 示例 
// app/controller/post.js
const Controller = require('../core/base_controller'); 
class PostController extends Controller { 
async list() { 
const posts = await this.service.listByUser(this.user); this.success(posts); } }
  1. 和koa类似, 在controller中获取ctx参数使用 this.ctx.参数名。 具体配置如下
ctx.query:URL 中的请求参数(忽略重复 key)
ctx.quries:URL 中的请求参数(重复的 key 被放入数组中)
ctx.params:Router 上的命名参数 
ctx.request.body:HTTP 请求体中的内容
ctx.request.files:前端上传的文件对象 
ctx.getFileStream():获取上传的文件流 
ctx.multipart():获取 multipart/form-data 数据
ctx.cookies:读取和设置 
cookie ctx.session:读取和设置 session 
ctx.service.xxx:获取指定 service 对象的实例(懒加载)
ctx.status:设置状态码 
ctx.body:设置响应体 
ctx.set:设置响应头 
ctx.redirect(url):重定向 
ctx.render(template):渲染模板 
this.ctx 上下文对象是 egg 框架和 koa 框架中最重要的一个对象,不过需要注意的是,
有些属性并非直接挂在 app.ctx 对象上,而是代理了 request 或 response 对象的属性,
可以用 Object.keys(ctx) 打印查看具体属性

数据库操作, egg-knex /egg-sequelize

  1. 安装egg-knex/ egg-sequelize
npm install -save egg-knex mysql / npm install -save egg-knex mysql
  1. 然后在app目录config文件夹中的plugin.js中添加导入相应的插件, 二选一即可, 这里还是用knex
'use strict'; 
/** @type Egg.EggPlugin */ 
module.exports = { 
// had enabled by egg // 
static: { 
// enable: true, //
} 
// 关闭csrf安全策略 
// 这个不设置, 当发送请求时会被csrf拒绝。
config.security = { csrf: { enable: false, } } 
// 设置允许文件上传 
config.multipart = { mode: 'file', // 对应文件类型 }
// 导入 knex
knex : { enable: true, package: 'egg-knex', }
// 导入 sequelize 
sequelize : { enable: true, package: 'egg-sequelize', } };
  1. 然后在config.default.js中添加knex相关连接配置
// config --> config.default.js
.... 
exports.knex = {
    // database configuration
    client: {
      // database dialect
      dialect: 'mysql',
      connection: {
        // host
        host: '127.0.0.1',
        // port
        port: '3306',
        // username
        user: 'root',
        // password
        password: '00000000',
        // database
        database: 'NodeExpress',
      },
      // connection pool
      pool: { min: 0, max: 5 },
      // acquire connection timeout, millisecond
      acquireConnectionTimeout: 30000,
    },
    // load into app, default is open
    app: true,
    // load into agent, default is close
    agent: false,
  };
  1. 在app目录下创建service文件夹, 存放数据库操作文件 user.js
'use strict';

const { Service } = require('egg');


class User extends Service {

    // 查找
    async all() {
      const all = await this.app.knex('user').select()
      return all;
    }
  
    // 新增
    async insert(params) {
      const insert = await this.app.knex('user').insert(params)
      return insert
    }
  
    // 更改
    async update(id, params) {
      const update = await this.app.knex('user')
        .where('id', '=', id).update(params)
      return update
    }

    // 删除
    async delete(id) {
        const update = await this.app.knex('user')
          .where('id', '=', id)
          .del()
        return update
    }
  }
  
module.exports = User;
  1. 然后在controller中直接调用操作表数据即可, 特别注意的是, 调用方法以文件名为准,即使内部导出为User, ctx.service.user 获取的是对应的文件。
'use strict'; 
const { Controller } = require('egg'); 
class DetailController extends Controller { 
async index() { 
const { ctx } = this; 
const res = await ctx.service.user.all()
ctx.body = { code: 200, message: "操作成功", data: res }; } } 
module.exports = DetailController;

image.png

  1. 其他方法操作都类似, knex的用法在egg中, 大致上只有配置方法不同, 其他没有什么区别
  2. 文件上传类型也简单, egg内置了文件上传, 所以只需要在config.default.js设置里添加config.multipart的mode类型即可
// config ---> config.default.js 
... 
config.multipart = { mode: 'file', // 对应文件类型 }
  1. 然后获取参数的方法参数也与koa有点区别
'use strict'; 
const { Controller } = require('egg'); 
class ListController extends Controller { 
async index() { 
const { ctx } = this;
console.log( ctx.request.files[0],ctx.request.body ) // 处理file字段, 简单处理一下
ctx.request.body.file = `http://www.xxxx2.com?` + ctx.request.files[0].filename 
ctx.body = { filename: ctx.request.files[0].filename,//返回文件名 body:ctx.request.body } 
const res = await ctx.service.user.insert(ctx.request.body)
ctx.body = { code: 200, message: "操作成功", data: res }; } } 
module.exports = ListController;
  1. 当然, 需要注意的是, multipart属于全局中间件, 如果单独添加, 可以使用上面说的 ignore/match 进行自定义修改, 然后使用 app.middleware.multipart()单独设置文件大小和类型
// 注意, 单独设置文件上传的时候, 需要在config.default.js中忽略掉这个路由, 否则会报重复调用的问题。
module.exports = app => { 
app.router.get('/', app.controller.home.index); 
app.router.post('/model1/list', 
app.middleware.multipart({ fileSize: '1mb'mode: 'strema' }), app.controller.model1.list.index) 
app.router.get('/model1/detail', app.controller.model1.detail.index) };

日志打印

  1. 首先了解下egg打印的内容
Egg.js中自带了三种logger,分别是 Context Logger App Logger Agent Logger Context Logger
主要是用来记录请求相关的日志。每行日志都会在开头自动的记录当前请求的一些信息,
比如时间、ip、请求url等等。 App Logger用于记录应用级别的日志,比如程序启动日志。
Agent Logger用于记录多进程模式运行下的日志。
  1. 如何自定义请求日志
我们想自定义请求级别的日志,那重点就要从Context Logger去研究怎么做。 通常期望的效果即是,
将每个请求结果分类, 即根据请求状态level记录成不同的log文件夹, 
方便需要时直接通过level类型文件快速查看请求及结果 然后每个level文件夹下, 又进行天数的分割形成对应的log. 
DEBUG、INFO、WARN、ERROR以及NONE 而Context Logger是基于egg-logger的FileTransport类去进行文件落地的,
同时FileTransport也默认配置了egg-logrotator的日志拆分。
所以,在自定义日志的时候, 只需要继承FileTransport类,实现接口就可以了,
  1. 在app目录下创建extend扩展文件夹, 然后创建content.js基础文件。
  2. 然后在extend目录下创建一个基础实现类接口文件CoustomTransport.js来继承FileTransport
// extend ---> CoustomTransport.js 
// 这个文件主要处理日志信息,编辑日志格式 
const FileTransport = require('egg-logger').FileTransport;
const moment = require('moment'); 
class CoustomTransport extends FileTransport { 
constructor(options, ctx) { 
super(options); 
this.ctx = ctx;
} 
log(level, args, meta) { 
const prefixStr = this.buildFormat(level);
for (let i in args) { 
if (args.hasOwnProperty(i)) { 
if (parseInt(i, 10) === 0) { args[i] = `${prefixStr}${args[i]}`; } 
if (parseInt(i, 10) === args.length - 1) { args[i] += '\n'; } } } 
super.log(level, args, meta); }
buildFormat(level) { 
const timeStr = `[${moment().format('YYYY-MM-DD HH:mm:ss.SSS')}]`;
const threadNameStr = `[${process.pid}]`;
const urlStr = `[${this.ctx.request.url}]` 
console.log(`${timeStr}${threadNameStr}${urlStr}`) 
return `${timeStr}${threadNameStr}${urlStr}`; 
} 
setUserId(userId) { this.userId = userId; } }
module.exports = CoustomTransport;
  1. 写好实现类后, 再在extend下创建一个CustomLogger.js, 用来初始化logger
// extend ---> CustomLogger.js 
const Logger = require('egg-logger').Logger; 
const CoustomTransport = require('./CoustomTransport.js');
module.exports = function(ctx){ 
const logger = new Logger(); 
logger.set('file', 
new CoustomTransport({ 
level: 'INFO', file: `./logs/NodeEgg/NodeEgg.log`, 
// 如果需要单独设置某一放置位置, 可以自定义路径 
}, ctx)); 
return logger; 
};
  1. 然后在扩展中添加自定义请求配置, 修改context.js
// extend ---> context.js 
const CustomLogger = require('./CustomLogger');
module.exports = { get swLog() { return CustomLogger(this); } };
  1. 因为需要记录的是每次请求的结果日志, 所以针对于所有请求都需要去配合调用写好的swlog方法, 所以修改basecontroller.js, 并把controller中的继承对象由egg改为basecontroller
// core ---> base_Controller.js 
const { Controller } = require('egg');
class BaseController extends Controller { 
get user() { return this.ctx.session.user; } 
success(data) { 
this.ctx.swLog.info('Hello World'); 
this.ctx.body = { success: true, data }; 
}
notFound(msg) { 
ctx.swLog.error('Hello World'); 
this.ctx.throw(404, msg || 'not found'); 
} 
} 
module.exports = BaseController;

// controller --> model1 ---> detail.js 
'use strict';

const BaseController = require('../../core/base_Controller');
class DetailController extends BaseController {
  async index() {
    const { ctx } = this;
    const res = await ctx.service.user.all()
    this.success(res)
  }
}

module.exports = DetailController;


// controller ---> model1 ---> list.js 
'use strict';

const BaseController = require('../../core/base_Controller');

class ListController extends BaseController {
  async index() {
    const { ctx } = this;
    // console.log(ctx.req.file, '这里是', ctx.req.body)
    console.log(ctx.request.files[0])
     // 处理file字段, 简单处理一下
     ctx.request.body.file = `http://www.xxxx2.com?` + ctx.request.files[0].filename
     ctx.body = {
        filename: ctx.request.files[0].filename,//返回文件名
        body:ctx.request.body
      }
    const res = await ctx.service.user.insert(ctx.request.body)
    this.success(res)
  }
}

module.exports = ListController;

image.png

  1. 简单测试一下,可以在logs/NodeEgg/NodeEgg.js中查看到相应记录, 说明自定义监听日志已经成功挂载

image.png

  1. 日志监听及打印功能都设置完成后, 就可以进一步修改日志的格式,
// extend ---> CoustomTransport.js 
const FileTransport = require('egg-logger').FileTransport;
const moment = require('moment');

class CoustomTransport extends FileTransport {
    constructor(options, ctx) {
        super(options);
        this.ctx = ctx;
    }
    log(level, args, meta) {
        const prefixStr = this.buildFormat(level);
        for (let i in args) {
            if (args.hasOwnProperty(i)) {
                if (parseInt(i, 10) === 0) {
                    args[i] = `${prefixStr}${args[i]}`;
                }
                if (parseInt(i, 10) === args.length - 1) {
                    args[i] += '\n';
                }
            }
        }
        super.log(level, args, meta);
    }

    buildFormat(level) {
        // 时间
        const timeStr = `[${moment().format('YYYY-MM-DD HH:mm:ss.SSS')}]`;
        // 用户
        const threadNameStr = `[userID:${process.pid}]`;
        // 请求类型
        const methodtype = `[${this.ctx.request.method}]` 
        // 请求地址
        const urlStr = `[${this.ctx.request.url}]`
        
        return `${timeStr}${threadNameStr}${methodtype}${urlStr}`;
    }

    setUserId(userId) {
        this.userId = userId;
    }
}

module.exports = CoustomTransport;
  1. 查看输出结果
[2023-02-10 16:03:04.161][userID:10177][POST][/model1/list]success
[2023-02-10 16:03:08.423][userID:10177][POST][/model1/list]success
[2023-02-13 10:04:57.613][userID:968][GET][/model1/detail]success 
[2023-02-13 10:05:35.103][userID:968][GET][/model1/detail]success 
[2023-02-13 10:06:04.823][userID:1025][GET][/model1/detail]success