2024-04-16 | 项目构建 | UNLOCK | 更新时间:2024-4-23 11:19

NodeJS项目的开始和扩展问题

从零开始搭建一个Node的项目,使用Express框架,使用Sqlite作为数据库,废话不多说,直接开始

初始化项目

  1. 创建空文件,然后init项目

mkdir node-project
cd node-project
npm init

创建项目目录

|—— common/       //公共模块 
  |—— authenticate.js //认证管理
  |—— database.js     //数据库连接池
  |—— webSocket.js    //websocket服务
|—— Interface/    //接口
  |—— user.js         //用户管理接口
  |—— ...
|—— Logs/         //日志
  |—— info.log      //详细日志文件
  |—— error.log     //错误日志文件
  |—— ...
|—— Routes/       //路由
|—— SQLiteStudio/ //数据库管理工具(忽略该文件夹)
|—— .env          //环境变量
|—— .gitignore    //忽略文件
|—— index.js      //入口文件
|—— package.json  //项目配置文件
|—— README.md     //项目说明
|—— SQLite.db     //数据库文件
|—— SQLite.sql    //数据库脚本
|—— swaggerConfig.js //swagger配置文件

安装相关模块

npm install nodemon –save
npm install dotenv –save
npm install express –save
npm install sqlite3 –save
npm install generic-pool –save
npm install log4js –save
npm install jsonwebtoken –save
npm install express-jsdoc-swagger –save
npm install swagger-jsdoc –save
npm install express-ws –save
npm install cors –save
npm install body-parser –save

热更新启动

安装 nodemon 模块,在package.json中添加启动命令。

npm install nodemon –save

// package.json
"scripts": {
  "dev": "nodemon index.js"
}

环境变量

安装 dotenv 模块,创建.env 文件,并设置环境变量。

npm install dotenv –save

// .env
PORT = 8888
SECRET_KEY = "123456"

Express

Express 是一个用于 Node.js 的快速构建的 Web 框架。

npm install express –save

// index.js
require('dotenv').config();
const express = require('express');
const app = express();

const PORT = process.env.PORT || 8888;
// 启动 Express 服务 
const c  = require('child_process')
const server=app.listen(PORT,()=>{
  console.log('  Express 服务启动,正在监听'+ PORT +'端口');
  c.exec('start http://localhost:8888/docs')
})

数据库支持

创建数据库文件,设置连接池,创建连接池管理文件。

npm install sqlite3 –save
npm install generic-pool –save

// 跳过预编译,直接源码编译

npm install –build-from-source

// common/database.js
const sqlite3 = require('sqlite3').verbose(); // 引入sqlite3模块
const genericPool = require('generic-pool');  // 引入generic-pool模块
const factory = {
  create: function() { // 创建一个数据库实例
    return new Promise(function(resolve, reject) {
      let db = new sqlite3.Database('./SQLite.db', sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => { // 创建数据库实例
        if (err) {
          reject(err);
        } else {
          resolve(db);
        }
      });
    });
  },
  destroy: function(db) { // 销毁一个数据库实例
    return new Promise(function(resolve) {
      db.close();
      resolve();
    });
  }
};

const opts = {
  max: 10, // 最大连接数
  min: 2  // 最小连接数
};

const myPool = genericPool.createPool(factory, opts); // 创建连接池
module.exports = myPool; // 导出连接池

日志管理

你如果要使用日志管理文件,可以安装以下两大模块之一:

  • Log4js 提供了模块化的日志记录功能,可以根据需要为不同的模块或功能分配独立的日志级别,从而实现更精细的信息输出控制。

    npm install log4js –save

// common/log.js
const log4js = require('log4js');
log4js.configure({
  appenders: {
    errorFile: { type: 'file', filename: '../Logs/errors.log' },
    debugFile: { type: 'file', filename: '../Logs/debugs.log' },
    infoFile: { type: 'file', filename: '../Logs/info.log' },
    error: { type: 'logLevelFilter', level: 'error', appender: 'errorFile' },
    debug: { type: 'logLevelFilter', level: 'debug', appender: 'debugFile', maxLevel: 'debug' },
    info: { type: 'logLevelFilter', level: 'info', appender: 'infoFile', maxLevel: 'info' }
  },
  categories: {
    default: { appenders: ['info', 'debug', 'error'], level: 'trace' }
  }
});
const logger = log4js.getLogger();
module.exports = logger;
  • winston是一个日志记录器,它提供了灵活的配置和灵活的输出功能。winston-daily-rotate-file 是一个基于winston的日志模块,它允许你根据日期自动生成日志文件。

npm install winston –save
npm install winston-daily-rotate-file –save

身份校验

JSON Web Token(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。这种信息可以被验证和信任,因为它是数字签名的。JWT可以使用密钥(例如,秘密或公钥/私钥对)进行签名,也可以选择性地加密其包含的信息

npm install jsonwebtoken –save

// common/authenticate.js
const jwt = require('jsonwebtoken');
const SECRET_KEY = process.env.SECRET_KEY; // 密钥
exports.authenticateToken=(req, res, next)=>{
    const authHeader = req.headers['authorization']; // 获取请求头中的token
    const token = authHeader && authHeader.split(' ')[1];
    if (token == null){
        return res.status(401).send({status:"401",message:'无权限访问'})
    } 
    jwt.verify(token, SECRET_KEY, (err, user) => {
        if (err){
            return res.status(403).send({status:"403",message:'token无效或已过期'})
        }
/* //临近过期时(前10分钟),刷新token
        let Expiration = process.env.Expiration
        let exp = user.exp * 1000
        let now = new Date().getTime()
        console.log(new Date(exp).toLocaleString(),now - exp)
        if (exp - now <= 60 * 1000) {
            const newToken = jwt.sign({ id: user.UserID, name:user.UserName }, SECRET_KEY, { expiresIn: Expiration });
            res.setHeader('Refresh-Authorization',newToken );
        }
 */
        req.user = user; // 将用户信息存储在请求中
        next(); // 继续执行后续操作
    });
}
// ../Interface/user
const myPool = require('../common/database.js');

//创建用户,以下的业务代码省略
exports.userCreatePost = async(req,res)=>{
  //...
}
// ./Routes/user.js
const express = require('express');
const router = express.Router();
const User = require('../Interface/user')
const {authenticateToken} = require('../common/authenticate')

// 创建用户
router.post('/api/user/create',authenticateToken,(req,res)=>{
  User.userCreatePost(req,res)
})

接口文档支持

如果需要使用接口文档,可以使用以下两个方案中的一个:

  • express-jsdoc-swagger 能够根据JSDoc注释自动生成接口文档,并且支持 Swagger UI。介绍更详细,对前端更友好,但是需要自己编写注释。

npm install express-jsdoc-swagger –save
npm install swagger-jsdoc –save

// swaggerConfig.js
const options = {
  info: {
    version: '1.0.0',
    title: "node-template",
    description:"node-template's interface documentation",
    license: {
      name: 'MIT',
    },
  },
  security: {
    BasicAuth: {
      type: 'http',
      scheme: 'basic',
    },
  },
  baseDir: __dirname,
  filesPattern: './**/*.js',
  swaggerUIPath: '/docs',
  exposeSwaggerUI: true,
  exposeApiDocs: false,
  apiDocsPath: '/v3/api-docs',
  notRequiredAsNullable: false,
  swaggerUiOptions: {},
  multiple: true,
};
module.exports = options;
// index.js
const expressJSDocSwagger = require('express-jsdoc-swagger');
const swaggerConfig = require('./swaggerConfig.js')
expressJSDocSwagger(app)(swaggerConfig);
// ./Routes/user.js

/**
 * 创建用户的请求对象
 * @typedef {object} Register
 * @property {string} UserName.required - 用户名称
 * @property {string} Email.required - 用户邮箱
 */
/**
 * POST /api/user/create
 * @summary 用户的创建接口
 * @tags user
 * @param {Register} request.body - 用户信息 - application/json
 * @return {object} 200 - 注册成功 - application/json
 * @return 409 - 用户名已存在
 * @return 500 - 错误的查询
 * @example response - 200 - 注册成功示例 - application/json
 * {
 *     "id": "XXXXXXXXX",
 *     "UserName": "XXXXXXXXXX",
 *     "status": "200",
 *     "message": "用户注册成功"
 * }
 */

router.post('/api/user/create',authenticateToken,(req,res)=>{
  User.userCreatePost(req,res)
})
  • swagger-ui-express 是一个基于express的WebSocket中间件,它允许你轻松地使用WebSocket协议。能根据json文件自动生成接口文档,并且支持 Swagger UI。对后端更轻松,但是文档不好看(我不喜欢这种,不仔细介绍了)。

npm install swagger-ui-express –save
npm install swagger-jsdoc –save

// swagger.json
{  
  "openapi": "3.0.3",  
  "info": {  
    "title": "My API",  
    "version": "1.0.0"  
  },  
  "paths": { //各个接口及相关的内容
    "/users": {  
      "get": { /*...*/ },  
      "post": { /*...*/ }  
    }  
  },  
  "components": { //请求对象和响应对象
    "schemas": {  
      "User": { /*...*/ }  
    }  
  }  
}
// index.js
const swaggerUi = require('swagger-ui-express');
const swaggerDocument = require('./swagger.json');
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

WebSocket

你如果要使用WebSocket,可以安装以下两个模块之一:

  • ws是一个Node.js模块,主要用于在服务器端实现WebSocket协议。它允许客户端(通常是浏览器)与服务端建立持久化的连接.

npm install ws –save

// ./common/webSocket.js
const WebSocket = require('ws');
const jwt = require('jsonwebtoken');
const SECRET_KEY = process.env.SECRET_KEY; // 密钥

function verifyToken(token) { //验证token  
    var isValid = false
    var user = null;
    jwt.verify(token, SECRET_KEY, (err, _user) => {
        if (!err){
            isValid = true
            user = _user
        }
    });
    return {isValid,user};  
}

class WebSocketServer {  
    constructor(port) {  
        this.wss = new WebSocket.Server({ 
            port,
            verifyClient: (info, done) => {
                const token = info.req.headers.authorization || info.req.url.split('?')[1].split('=')[1];
                const { isValid, user } = verifyToken(token);
                if (isValid) {  
                    info.req.user = user; //将用户信息附加到请求对象上
                    done(true); //验证通过,允许连接  
                  } else {  
                    done(false, 401, 'Unauthorized'); //验证失败,拒绝连接并返回 401 状态码  
                  }  
            }
         }); 
        this.clients = new Map(); //使用Map来存储客户端连接  
        this.wss.on('connection', this.onConnection.bind(this));  
    }
    //监听客户端连接
    onConnection(ws,req) {
        const user = req.user
        this.clients.set(user.id, ws); 
        console.log("WebSocket :"+user.id+"用户已上线");
        logger.info("WebSocket :"+user.id+"用户已上线")
        ws.on('message', (messageBuffer) => {  
            //将Buffer解码为UTF-8字符串  
            const messageString = messageBuffer.toString('utf8');
            console.log('接受消息:', messageString);  
            ws.send('Message received: ' + messageString); 
            
        });
        //断开连接
        ws.on('close', () => {
            this.removeClient(ws)
        });
    }
    //广播消息给所有连接的客户端,除了指定的ws  
    broadcast(message, exceptWs){
        this.clients.forEach((client) => {  
            if (client !== exceptWs && client.readyState === WebSocket.OPEN) {  
                client.send(message);  
            }
        });  
    }
    //推送给特定人员
    sendMessageToUser(userId, message) {
        const ws = this.clients.get(userId);
        if (ws && ws.readyState === WebSocket.OPEN) {
            ws.send(message);
        }else{
            console.log("WebSocket :"+userId+"用户不在线");
            logger.info("WebSocket :"+userId+"用户不在线")
        }
    }
    //从连接列表中移除指定的客户端  
    removeClient(userId) {  
        const ws = this.clients.get(userId);
        if (ws) {
            ws.close();
            this.clients.delete(userId);
            console.log("WebSocket :"+userId+"用户已下线");
            logger.info("WebSocket :"+userId+"用户已下线")
        }
    }  
    //启动服务
    start() { 
        console.log('WebSocket 服务启动,正在监听'+ this.wss.options.port +'端口');
        logger.info('./common/webSocket.js WebSocket服务启动,正在监听'+ this.wss.options.port +'端口');
    }  
}  

const WSPORT = process.env.WSPORT || 8889;
const WSdev = new WebSocketServer(WSPORT);

module.exports = WSdev;
// ./index.js
const WSdev = require('./common/webSocket');
WSdev.start(); //启动 WebSocket 服务
  • express-ws是一个针对Node.js开发者的库,它扩展了流行的Express框架,通过添加对WebSocket协议的支持,让开发者能够利用熟悉的Express API轻松地创建和管理WebSocket服务器。(推荐使用)

npm install express-ws –save

中间件数据处理

npm install cors –save
npm install body-parser –save

// index.js
const app = express()

const cors = require('cors')
app.use(cors()) //允许跨域:启动所有Cors请求

const bodyParser = require('body-parser')
app.use(bodyParser.json());  //解析json数据
app.use(bodyParser.urlencoded({extended: false}));  //解析form数据

后续扩展

npm install express-session –save
npm install express-jwt –save
npm install express-validator –save
npm install express-winston –save
npm install express-rate-limit –save
npm install express-ipfilter –save
npm install express-ipware –save
npm install express-fileupload –save
npm install express-flash –save
npm install express-handlebars –save
npm install express-graphql –save
npm install express-graphql-auth –save
npm install express-graphql-auth-jwt –save