一、框架选型与基础搭建

  1. 核心框架选择
    • 优先选成熟框架:Express(轻量灵活,适合中小项目)、NestJS(企业级,模块化 / TypeScript 友好)、Koa2(更现代的中间件机制),避免从零手写 HTTP 服务。
    • 配套生态:选框架适配的中间件(如 Express 用 express.json() 解析 JSON 请求体,而非自己写解析逻辑)。
  2. 项目结构规范
    • 避免 “面条代码”,按功能 / 分层拆分目录,示例(Express):

      plaintext

      project/
      ├── config/       // 配置文件(数据库、JWT、端口等)
      ├── controllers/  // 控制器(处理请求、返回响应)
      ├── models/       // 数据模型(数据库交互)
      ├── routes/       // 路由定义(接口路径映射)
      ├── middleware/   // 自定义中间件(鉴权、日志、异常处理)
      ├── utils/        // 工具函数(加密、格式化、通用方法)
      ├── app.js        // 应用入口(挂载中间件、路由)
      └── server.js     // 启动服务器
      
    • 配置文件抽离:敏感配置(数据库密码、JWT 密钥)不要硬编码,用 dotenv 加载 .env 文件,示例:

      javascript

      运行

      // .env 文件(添加到 .gitignore,不提交到仓库)
      PORT=3000
      DB_PASSWORD=123456
      JWT_SECRET=your-secret-key
      
      // config/index.js
      require('dotenv').config();
      module.exports = {
        port: process.env.PORT,
        db: { password: process.env.DB_PASSWORD },
        jwt: { secret: process.env.JWT_SECRET }
      };
      

二、安全防护(重中之重)

  1. 密码与身份认证
    • 密码不可逆存储:必须用 bcryptjs/bcrypt 哈希加密,禁止明文 / MD5(易破解)存储。
    • JWT 规范:
      • 设置合理过期时间(如 2h),避免永久有效;
      • 密钥复杂度足够(至少 16 位随机字符串),定期更换;
      • 避免在 JWT Payload 中存敏感信息(如密码),仅存用户 ID、角色等非敏感数据。
  2. 防常见攻击
    • 跨域:用 cors 中间件,指定允许的 origin(生产环境禁止 *),示例:

      javascript

      运行

      app.use(cors({
        origin: ['https://your-frontend.com'], // 仅允许指定前端域名
        credentials: true // 如需跨域携带 Cookie
      }));
      
    • XSS 攻击:返回响应时过滤 HTML 特殊字符(用 xss 模块),禁止前端输入的内容直接渲染。
    • SQL 注入:用 ORM 框架(Sequelize、Prisma)或参数化查询,禁止拼接 SQL 字符串,示例:

      javascript

      运行

      // 错误(易注入):
      db.query(`SELECT * FROM users WHERE name = '${req.query.name}'`);
      // 正确(参数化):
      db.query('SELECT * FROM users WHERE name = ?', [req.query.name]);
      
    • CSRF 攻击:接口鉴权优先用 JWT(放在请求头 Authorization: Bearer <token>),而非 Cookie;如需 Cookie,加 csurf 中间件。
    • 接口限流:用 express-rate-limit 限制单 IP 请求频率(如 1 分钟最多 100 次),防止暴力攻击,示例:

      javascript

      运行

      const rateLimit = require('express-rate-limit');
      const limiter = rateLimit({
        windowMs: 60 * 1000, // 1分钟
        max: 100, // 单IP最多100次请求
        message: '请求过于频繁,请稍后再试'
      });
      app.use('/api/', limiter); // 对所有/api接口限流
      

三、异常处理与日志

  1. 全局异常捕获
    • 避免未捕获的异常导致服务器崩溃,示例(Express):

      javascript

      运行

      // 自定义异常处理中间件(放在所有路由之后)
      app.use((err, req, res, next) => {
        console.error('异常信息:', err.stack);
        res.status(err.statusCode || 500).json({
          code: err.code || 500,
          msg: process.env.NODE_ENV === 'production' ? '服务器内部错误' : err.message
        });
      });
      
      // 异步函数异常捕获(Express 需手动传递给 next)
      app.get('/api/user', async (req, res, next) => {
        try {
          const data = await getUserData();
          res.json(data);
        } catch (err) {
          next(err); // 交给全局异常中间件处理
        }
      });
      
  2. 日志记录
    • 不要只用 console.log,用 winston/morgan 记录日志:
      • morgan:记录 HTTP 请求日志(方法、路径、状态码、响应时间);
      • winston:分级记录日志(info/error/warn),输出到文件 + 控制台,示例:

        javascript

        运行

        const winston = require('winston');
        const logger = winston.createLogger({
          level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
          format: winston.format.json(),
          transports: [
            new winston.transports.Console(), // 控制台输出
            new winston.transports.File({ filename: 'logs/error.log', level: 'error' }), // 错误日志
            new winston.transports.File({ filename: 'logs/combined.log' }) // 所有日志
          ]
        });
        // 使用:logger.error('数据库连接失败');
        

四、数据库操作

  1. 连接与复用
    • 数据库连接池:MySQL/MongoDB 等都要配置连接池,避免每次请求新建连接(性能损耗),示例(MySQL2):

      javascript

      运行

      const mysql = require('mysql2/promise');
      const pool = mysql.createPool({
        host: 'localhost',
        user: 'root',
        password: '123456',
        database: 'test',
        waitForConnections: true,
        connectionLimit: 10, // 最大连接数
        queueLimit: 0
      });
      // 复用连接池,而非每次创建连接
      module.exports = pool;
      
  2. 数据校验
    • 前端传参必须校验:用 joi/express-validator 验证参数类型、长度、格式,避免无效数据入库,示例:

      javascript

      运行

      const { body, validationResult } = require('express-validator');
      // 注册接口参数校验
      app.post('/api/register', [
        body('name').notEmpty().withMessage('用户名不能为空'),
        body('password').isLength({ min: 6 }).withMessage('密码至少6位')
      ], (req, res) => {
        // 校验结果
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
          return res.status(400).json({ errors: errors.array() });
        }
        // 校验通过,处理业务
      });
      
  3. 事务处理
    • 涉及多表操作(如转账、订单创建)必须用事务,避免数据不一致,示例(MySQL2):

      javascript

      运行

      const pool = require('./config/db');
      async function createOrder() {
        const connection = await pool.getConnection();
        try {
          await connection.beginTransaction(); // 开启事务
          // 执行多个SQL操作
          await connection.execute('INSERT INTO orders (...) VALUES (...)');
          await connection.execute('UPDATE goods SET stock = stock - 1 WHERE id = ?', [1]);
          await connection.commit(); // 提交事务
        } catch (err) {
          await connection.rollback(); // 回滚事务
          throw err;
        } finally {
          connection.release(); // 释放连接
        }
      }
      

五、性能与部署

  1. 性能优化
    • 接口缓存:高频读取接口(如首页数据、商品列表)用 redis 缓存,减少数据库查询,示例:

      javascript

      运行

      const redis = require('redis');
      const client = redis.createClient({ url: 'redis://localhost:6379' });
      client.connect();
      
      // 获取商品列表(先查缓存,再查数据库)
      app.get('/api/goods', async (req, res) => {
        const cacheKey = 'goods:list';
        const cacheData = await client.get(cacheKey);
        if (cacheData) {
          return res.json(JSON.parse(cacheData));
        }
        // 缓存未命中,查数据库
        const goods = await db.query('SELECT * FROM goods');
        // 存入缓存(设置过期时间,避免数据不一致)
        await client.setEx(cacheKey, 3600, JSON.stringify(goods));
        res.json(goods);
      });
      
    • 避免同步阻塞:Node.js 是单线程,禁止用 fs.readFileSync/bcryptjs.hashSync 等同步方法处理高并发请求,改用异步版本(fs.readFile/bcryptjs.hash)。
  2. 部署注意事项
    • 环境区分:开发 / 测试 / 生产环境配置分离,生产环境关闭 debug 日志、禁用 cors: *
    • 进程守护:用 pm2 启动服务,防止进程崩溃,示例:

      bash

      运行

      # 安装 pm2
      npm install pm2 -g
      # 启动服务(配置文件 ecosystem.config.js 更佳)
      pm2 start server.js --name my-api
      # 查看进程状态
      pm2 status
      # 日志查看
      pm2 logs
      
    • 端口与权限:生产环境避免用 80/443 等低端口(需 root 权限),用 Nginx 反向代理到 Node.js 服务(如 Nginx 监听 80,转发到 3000 端口)。

六、代码质量与维护

  1. 代码规范
    • 用 ESLint + Prettier 统一代码风格,避免团队协作时格式混乱;
    • 优先用 TypeScript:强类型检查能提前发现类型错误,尤其适合中大型项目。
  2. 接口文档
    • 自动生成接口文档:用 Swagger/OpenAPI(如 swagger-jsdoc + swagger-ui-express),避免手动维护文档,示例:

      javascript

      运行

      const swaggerJsdoc = require('swagger-jsdoc');
      const swaggerUi = require('swagger-ui-express');
      const options = {
        definition: {
          openapi: '3.0.0',
          info: { title: 'API 文档', version: '1.0.0' },
        },
        apis: ['./routes/*.js'], // 从路由文件读取注释生成文档
      };
      const specs = swaggerJsdoc(options);
      app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(specs));
      
  3. 单元测试
    • 核心逻辑写单元测试:用 Jest/Mocha 测试控制器、工具函数,示例(Jest):

      javascript

      运行

      // utils/encrypt.test.js
      const { encryptPwd } = require('./encrypt');
      test('密码加密后能验证通过', async () => {
        const pwd = '123456';
        const hash = await encryptPwd(pwd);
        const isMatch = await bcryptjs.compare(pwd, hash);
        expect(isMatch).toBe(true);
      });
      

总结

  1. 基础层:规范项目结构、抽离配置、选成熟框架,避免重复造轮子;
  2. 安全层:密码哈希、JWT 规范、防注入 / XSS / 限流,敏感配置不硬编码;
  3. 稳定性层:全局异常捕获、日志记录、数据库事务、连接池复用;
  4. 性能与维护:缓存高频接口、用 PM2 守护进程、自动生成接口文档、写单元测试。
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐