02、express + mysql搭建nodejs服务端项目流程(二)——添加打印日志winston和mysql相关依赖

84 阅读4分钟

express + mysql搭建nodejs服务端项目流程系列文章链接:
01、express + mysql搭建nodejs服务端项目流程(一)——使用express-generator创建项目
02、express + mysql搭建nodejs服务端项目流程(二)——添加打印日志winston和mysql相关依赖
03、express + mysql搭建nodejs服务端项目流程(三)——使用joi、crypto-js和jwt等技术完善注册登录接口
本文将记录安装各个依赖包来完善项目架构

1、添加打印日志文件

这里采用的是 winston 日志
具体用法参考:
如何在Express中使用Winston进行日志记录,并配置按日分割文件存储

1.1、安装winston、winston-daily-rotate-file

npm install winston winston-daily-rotate-file

1.2、配置打印日志格式

在项目的根目录添加一个配置打印日志格式的文件logger.js

const winston = require('winston');
require('winston-daily-rotate-file');
const { combine, timestamp, printf, colorize,label } = winston.format;

const path = require('path');
const env = process.env.NODE_ENV || 'development';

// 自定义日志格式
const logFormat = printf( (info) => {
  return `${info.timestamp} ${info.level} [${info.label}]: ${info.message}`;
});

// 创建 Logger 实例
const logger = winston.createLogger({
  level: env === 'production' ? 'info' : 'debug', // 设置日志级别(error, warn, info, verbose, debug, silly)
  format: combine(
    label({ label: path.basename(process.mainModule.filename) }),
    timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), // 添加时间戳
    logFormat // 应用自定义格式
  ),
  transports: [
    // 控制台输出(带颜色)
    new winston.transports.Console({
      format: combine(colorize(), logFormat)
    }),
    // 按日分割文件
    new winston.transports.DailyRotateFile({
      dirname: 'logs', // 日志目录
      filename: 'app-%DATE%.log', // 文件名(%DATE% 自动替换为日期)
      datePattern: 'YYYY-MM-DD', // 日期格式
      zippedArchive: true, // 压缩旧日志
      maxSize: '20m', // 单个文件最大大小
      maxFiles: '7d' // 保留最近7天的日志
    })
  ],
  // 异常处理(可选)
  exceptionHandlers: [
    new winston.transports.File({ filename: 'logs/exceptions.log' })
  ],
  // Promise拒绝处理(可选)
  rejectionHandlers: [
    new winston.transports.File({ filename: 'logs/rejections.log' })
  ]
});

module.exports = logger;

1.3、修改 app.js 的配置

1.3.1、修改项目默认的变量名

将原来自动生成的代码中,引入的morgan 的变量名改成morgan
var logger = require('morgan');
改成:
const morgan = require('morgan')

相应的也要把
app.use(logger('dev'));
改成:
app.use(morgan('dev'));

1.3.2、引入 logger.js 文件

const logger = require('./logger')

image.png

1.3.3、修改错误处理

将错误打印到日志上

image.png

// 处理非404的错误(throw 出来的错误)
const _errorHandler = (err, req, res, next) => {
  const errorMsg = err instanceof Error ? err.message : err
  logger.error(`${req.method} ${req.originalUrl} ` + errorMsg)
  res.status(err.status || 500).json({
    code: -1,
    success: false,
    message: errorMsg,
    data: null
  })
}
app.use(_errorHandler)
1.3.4、测试日志是否打印成功

请求一个不存在的路由,如: http://localhost:3000/hello

image.png
打开日志文件,看到这条错误被打印出来就成功了。

image.png

2、操作MySql数据库

2.1、安装依赖

npm install mysql2 knex dotenv

2.2、配置 MySQL 连接

2.2.1、 创建 .env 文件

在项目根目录新建 .env,配置数据库信息:

DB_HOST=localhost
DB_USER=root
DB_PASSWORD=your_password
DB_NAME=your_database
DB_PORT=3306
2.2.2、 创建 Knex 配置文件

在项目根目录新建 knexfile.js

require('dotenv').config();

module.exports = {
  development: {
    client: 'mysql2',
    connection: {
      host: process.env.DB_HOST,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
      database: process.env.DB_NAME,
      port: process.env.DB_PORT
    },
    migrations: {
      directory: './db/migrations' // 迁移文件目录
    },
    seeds: {
      directory: './db/seeds' // 种子数据目录
    }
  }
};
2.2.3、 初始化 Knex 实例

在项目根目录新建db目录,在db目录下新建 db.js 文件:

const knex = require('knex');
const config = require('../knexfile');
const db = knex(config.development);

module.exports = db;

在更多关于Knex的使用方式在下面的文章中有记录:
NodeJS 的Express 中集成 MySQL 和 Knex 的完整指南(一)
NodeJS 的Express 中集成 MySQL 和 Knex 的完整指南(二)


2.3、编写简单注册接口,往mysql数据库中添加数据

实现步骤:

  1. 建表、创建用户models和业务处理类
  2. 检测表单数据是否合法
  3. 检测用户名是否被占用
  4. 插入新用户
2.3.1、 在本地数据库中新建users表,如下:

image.png

2.3.2、新建用户模型和控制器,编写用户简单注册接口

在根目录下创建controllers目录,在controllers目录下再创建models目录,在models目录下创建base.js文件

const db = require('../../db/db');

class Base {
    constructor(tableName) {
        this.tableName = tableName;
    }
    // 新增
    insert(params) {
        return db(this.tableName).insert(params);
    }

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

    // 更改
    update(id, params) {
        return db(this.tableName).where('id', '=', id).update(params);
    }
    // 查找
    all() {
        return db(this.tableName).select();
    }
    // 根据主键id查询指定记录
    findRecordById(id) {
        return db(this.tableName).where('id',id).select();
    }
}

module.exports = Base;

在models目录下创建user.js文件

const Base = require('./base');
const db = require('../../db/db');

class User extends Base {
    constructor(tableName = 'users'){
        super(tableName);
    }
    /**
     * 根据账号查询该用户信息
     * @param {*} account 
     */
    findUserByAccount(account){
        return db(this.tableName).select().where({account: account}).first();
    }
}
module.exports = new User();

在controllers目录下创建user.js文件

const userInfo = require('./models/user');
const logger = require('../logger')

const userController = {
    /**
     * 处理post请求过来的注册业务
     * @param {*} req 
     * @param {*} res 
     * @param {*} next 
     */
    regUser: async (req, res, next) => {
        try {
            const { account, pwd, nickName } = req.body;
            // 判断数据是否合法
            if (!account) {
                throw '账号不能为空!'
            }
            if (!pwd) {
                throw '密码不能为空!'
            }
            if (!nickName) {
                throw '用户名不能为空!'
            }
            let dbUser = await userInfo.findUserByAccount(account)
            if (dbUser && Object.keys(dbUser).length > 0) {
                throw '当前账号已存在,不能重复注册!'
            }
            let insertData = {
                account: account,
                pwd: pwd,
                name: nickName
            }
            let insertResult = await userInfo.insert(insertData);
            logger.info('用户注册时,插入数据库的结果,result = 【' + insertResult + '】')
            res.json({
                code: 200,
                message: `账号${account}注册成功!`,
                data: null
            })
        } catch (error) {
            // 通过next,传给应用级别的错误处理方法进行统一处理
            next(error)
        }
    }
}

module.exports = userController

在routes目录下的users.js的文件中添加用户注册接口:

/** 用户注册接口 */
router.post('/regUser',userController.regUser);

image.png

2.3.3、使用apifox或者postman测试接口

image.png