第六节:添加响应中间件、redis链接、配置redis store中间件、跨域配置

添加响应中间件、redis链接、配置redis store中间件、跨域配置

一、响应中间件配置

1.1 HTTP 状态码标准化

文件位置: utils/httpStatus.js

javascript 复制代码
/**
 * HTTP状态码常量定义
 * 统一管理所有HTTP状态码,确保系统一致性
 */
const HTTP_STATUS = {
  // 2xx 成功系列
  SUCCESS: 200,               // 请求成功
  CREATED: 201,              // 资源创建成功
  ACCEPTED: 202,             // 请求已接受,处理中
  NO_CONTENT: 204,           // 请求成功,无返回内容
  
  // 3xx 重定向系列
  MOVED_PERMANENTLY: 301,    // 永久重定向
  SEE_OTHER: 303,            // 临时重定向
  NOT_MODIFIED: 304,         // 资源未修改
  
  // 4xx 客户端错误系列
  BAD_REQUEST: 400,          // 请求参数错误
  UNAUTHORIZED: 401,         // 未授权访问
  FORBIDDEN: 403,            // 禁止访问
  NOT_FOUND: 404,            // 资源未找到
  METHOD_NOT_ALLOWED: 405,   // 请求方法不允许
  CONFLICT: 409,             // 资源冲突
  UNSUPPORTED_TYPE: 415,     // 不支持的媒体类型
  
  // 5xx 服务器错误系列
  INTERNAL_SERVER_ERROR: 500, // 服务器内部错误
  NOT_IMPLEMENTED: 501,      // 功能未实现
  BAD_GATEWAY: 502,          // 网关错误
  SERVICE_UNAVAILABLE: 503,  // 服务不可用
  GATEWAY_TIMEOUT: 504,      // 网关超时
  
  // 自定义业务状态码(6xx系列)
  UNKNOWN_ERROR: 520,        // 未知错误
  DATABASE_ERROR: 521,       // 数据库操作错误
  WARNING: 601,              // 业务警告
  ERROR: 602                 // 业务错误
}

module.exports = HTTP_STATUS

1.2 响应包装中间件

文件位置: middlewares/responseWrapper.js

javascript 复制代码
/**
 * 统一响应包装中间件
 * 基于RuoYi框架响应格式标准,提供一致化的API响应结构
 */
const { API_ROOT } = require('../config')
const HTTP_STATUS = require('../utils/httpStatus')

function responseWrapper(req, res, next) {
  // 仅对API根路径下的请求进行包装
  if (!req.originalUrl?.startsWith(API_ROOT)) {
    return next()
  }

  // 保存原始的json方法
  const originalJson = res.json

  /**
   * 重写res.json方法,统一响应格式
   * 支持三种格式:
   * 1. 标准格式:{ code, data, msg }
   * 2. 分页格式:{ code, rows, total, msg }
   * 3. 已包装格式:直接返回
   */
  res.json = function(payload = null) {
    // 情况1:已经是完整响应格式,直接返回
    if (payload && typeof payload === 'object' && 'code' in payload) {
      return originalJson.call(this, payload)
    }
    
    // 情况2:分页响应格式
    if (payload && typeof payload === 'object' && 'rows' in payload && 'total' in payload) {
      const { rows, total, msg = '操作成功', ...rest } = payload
      return originalJson.call(this, {
        code: HTTP_STATUS.SUCCESS,
        rows,
        total,
        msg,
        ...rest
      })
    }

    // 情况3:普通数据响应,自动包装
    return originalJson.call(this, {
      code: HTTP_STATUS.SUCCESS,
      data: payload,
      msg: '操作成功'
    })
  }

  /**
   * 成功响应快捷方法
   * @param {any} data - 响应数据
   * @param {string} msg - 成功消息
   * @returns {Object} 标准响应格式
   */
  res.success = function(data = null, msg = '操作成功') {
    return originalJson.call(this, {
      code: HTTP_STATUS.SUCCESS,
      data,
      msg
    })
  }

  /**
   * 分页响应快捷方法
   * @param {Array} rows - 数据列表
   * @param {number} total - 总记录数
   * @param {Object} rest - 其他扩展字段
   * @param {string} msg - 成功消息
   * @returns {Object} 分页响应格式
   */
  res.page = function(rows, total, rest = {}, msg = '操作成功') {
    return originalJson.call(this, {
      code: HTTP_STATUS.SUCCESS,
      rows,
      total,
      msg,
      ...rest
    })
  }

  /**
   * 错误响应快捷方法
   * @param {string} msg - 错误消息
   * @param {number} code - 错误状态码,默认为业务错误码
   * @returns {Object} 错误响应格式
   */
  res.error = function(msg = '操作失败', code = HTTP_STATUS.ERROR) {
    // 设置HTTP状态码(映射到对应的HTTP状态码)
    const httpStatusCode = code >= 600 ? 200 : code
    this.status(httpStatusCode)
    
    return originalJson.call(this, {
      code,
      msg
    })
  }

  next()
}

module.exports = responseWrapper

1.3 响应格式示例

响应类型 示例格式
成功响应 { "code": 200, "data": {...}, "msg": "操作成功" }
分页响应 { "code": 200, "rows": [...], "total": 100, ... }
错误响应 { "code": 602, "msg": "操作失败" }
HTTP错误 { "code": 404, "msg": "用户不存在" }

1.4 Express 应用集成

文件位置: app.js(简略版)

javascript 复制代码
const createError = require('http-errors')
const express = require('express')
const { API_ROOT } = require('./config')
const responseWrapper = require('./middlewares/responseWrapper')
const routes = require('./routes')

const app = express()

// 基础配置
app.set('view engine', 'ejs')
app.use(express.json())
app.use(express.urlencoded({ extended: false }))
app.use(express.static('public'))

// 响应包装中间件
app.use(responseWrapper)

// 路由注册
app.use(API_ROOT, routes)

// 错误处理
app.use((req, res, next) => {
  next(createError(404))
})

module.exports = app

二、Redis 连接与管理

2.1 配置文件

文件位置: config/index.js

javascript 复制代码
/**
 * 应用配置文件
 */
module.exports = {
  // 数据库配置
  DBHOST: '127.0.0.1',
  DBPORT: 27017,
  DBNAME: 'node-ruoyi',
  
  // 会话安全配置
  SESSION_SECRET: '31df9e38-03d3-4f97-b56c-a166a7420c8c',
  
  // Redis 配置
  REDIS_HOST: '127.0.0.1',
  REDIS_PORT: 6379,
  
  // API 配置
  API_ROOT: '/api',
  
  // 数据库自增配置
  COUNTERS_COLLECTION: 'auto_increment_counters'
}

2.2 Redis 客户端封装

文件位置: redis/index.js

javascript 复制代码
/**
 * Redis 客户端管理类
 * 提供连接池管理、自动重连、健康检查等功能
 */
const { createClient } = require('redis')
const { REDIS_HOST, REDIS_PORT, REDIS_PASSWORD } = require('../config')

class RedisClient {
  constructor() {
    this.client = null
    this.isConnected = false
  }

  /**
   * 建立 Redis 连接
   */
  async connect() {
    if (this.client && this.isConnected) {
      return this.client
    }

    try {
      // 创建 Redis 客户端
      this.client = createClient({
        url: `redis://${REDIS_HOST}:${REDIS_PORT}`,
        password: process.env.REDIS_PASSWORD || undefined,
        socket: {
          reconnectStrategy: retries => {
            if (retries > 10) {
              console.error('Redis 连接失败,已达到最大重试次数')
              return new Error('Redis 连接失败')
            }
            return Math.min(retries * 100, 3000) // 重试间隔
          }
        }
      })

      // 事件监听器
      this.client.on('error', err => {
        console.error('Redis 客户端错误:', err)
        this.isConnected = false
      })

      this.client.on('connect', () => {
        console.log('✅ Redis 连接成功')
        this.isConnected = true
      })

      this.client.on('end', () => {
        console.log('❌ Redis 连接关闭')
        this.isConnected = false
      })

      // 建立连接
      await this.client.connect()
      return this.client
    } catch (error) {
      console.error('Redis 连接失败:', error)
      throw error
    }
  }

  /**
   * 获取 Redis 客户端实例
   */
  async getClient() {
    if (!this.client || !this.isConnected) {
      await this.connect()
    }
    return this.client
  }

  /**
   * 断开连接
   */
  async disconnect() {
    if (this.client) {
      await this.client.quit()
      this.isConnected = false
    }
  }

  /**
   * 健康检查
   */
  async ping() {
    try {
      const client = await this.getClient()
      const result = await client.ping()
      return result === 'PONG'
    } catch (error) {
      console.error('Redis ping 失败:', error)
      return false
    }
  }
}

// 导出单例实例
module.exports = new RedisClient()

三、Session 管理与跨域配置

3.1 依赖安装

bash 复制代码
# 安装必要的中间件
npm install connect-redis express-session cors --save

3.2 Express 应用完整配置

文件位置: app.js(完整版)

javascript 复制代码
const createError = require('http-errors')
const express = require('express')
const path = require('path')
const cookieParser = require('cookie-parser')
const logger = require('morgan')
const session = require('express-session')
const cors = require('cors')
const { RedisStore } = require('connect-redis')

// 自定义模块
const { SESSION_SECRET, API_ROOT } = require('./config')
const responseWrapper = require('./middlewares/responseWrapper')
const redisClient = require('./redis')
const routes = require('./routes')

// 创建 Express 应用
const app = express()

// ==================== 中间件配置 ====================

// 1. 视图引擎配置
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'ejs')

// 2. 开发日志
app.use(logger('dev'))

// 3. 请求体解析
app.use(express.json({ strict: false }))
app.use(express.urlencoded({ extended: true }))

// 4. Cookie 解析
app.use(cookieParser())

// 5. 静态文件服务
app.use(express.static(path.join(__dirname, 'public')))

// 6. 跨域配置
app.use(cors({
  origin: true,                        // 允许所有来源(生产环境应限制)
  credentials: true,                   // 允许携带凭证(Cookie)
  exposedHeaders: ['set-cookie', 'download-filename']
}))

// ==================== Session 配置 ====================

/**
 * 初始化 Redis Session 存储
 */
const initRedisSession = async () => {
  const client = await redisClient.getClient()
  
  const redisStore = new RedisStore({
    client,
    prefix: 'session:',               // Redis 键前缀
    ttl: 60 * 10,                     // 过期时间(秒)
    disableTouch: false              // 允许更新过期时间
  })
  
  return session({
    store: redisStore,
    secret: SESSION_SECRET,
    name: 'sid',                      // Cookie 名称
    resave: false,                    // 避免重复保存
    saveUninitialized: false,         // 不保存空 session
    cookie: {
      maxAge: 24 * 60 * 60 * 1000,    // 24小时
      httpOnly: true,                 // 防止 XSS 攻击
      secure: process.env.NODE_ENV === 'production'
    },
    rolling: true,                    // 每次请求刷新过期时间
    unset: 'destroy'                  // 删除时从存储移除
  })
}

// ==================== 应用初始化 ====================

;(async () => {
  try {
    // 1. 初始化 Redis Session
    const sessionMiddleware = await initRedisSession()
    app.use(sessionMiddleware)

    // 2. 响应包装中间件
    app.use(responseWrapper)

    // 3. 注册路由
    app.use(API_ROOT, routes)

    // 4. API 专用错误处理器
    app.use(API_ROOT, (err, req, res, next) => {
      console.log('API 错误日志:', err)
      res.status(err.status || 500).json({
        code: err.status || 500,
        msg: err.message || '服务器内部错误'
      })
    })

    // 5. 404 处理器
    app.use((req, res, next) => {
      next(createError(404))
    })

    // 6. 通用错误处理器
    app.use((err, req, res, next) => {
      res.locals.message = err.message
      res.locals.error = req.app.get('env') === 'development' ? err : {}
      res.status(err.status || 500)
      res.render('error')
    })

    console.log('✅ 应用配置完成')
  } catch (error) {
    console.error('❌ 应用初始化失败:', error)
    process.exit(1)
  }
})()

module.exports = app

4.2 路由配置示例

javascript 复制代码
// routes/user.js
const express = require('express')
const router = express.Router()
const userController = require('../controllers/userController')
const { verifyToken } = require('../jwt')

// 公开接口
router.get('/public', userController.getPublicData)

// 需要认证的接口
router.get('/profile', verifyToken(), userController.getProfile)
router.put('/profile', verifyToken(), userController.updateProfile)

// 分页查询
router.get('/list', verifyToken(), userController.getUsersPaginated)

module.exports = router

4.3 目录结构参考

bash 复制代码
node-ruoyi/
├── config/
│   └── index.js              # 配置文件
├── redis/
│   ├── index.js              # Redis 客户端
│   ├── token.js              # Token 管理
│   └── captcha.js            # 验证码存储
├── middlewares/
│   ├── responseWrapper.js    # 响应包装
│   ├── index.js              # 中间件入口
│   └── validator/            # 验证中间件
├── utils/
│   └── httpStatus.js         # HTTP 状态码
├── routes/                   # 路由目录
├── controllers/              # 控制器目录
├── models/                   # 数据模型
├── app.js                    # 应用主文件
└── package.json

📊 配置总结表

组件 配置文件 主要功能 依赖
HTTP 响应 responseWrapper.js 统一响应格式、快捷方法 -
状态码 httpStatus.js 标准化状态码定义 -
Redis redis/index.js 连接池、自动重连、健康检查 redis
Session app.js Redis 存储、安全配置 express-session, connect-redis
跨域 app.js CORS 配置、凭证支持 cors
应用配置 config/index.js 集中配置管理 -

🛡️ 安全建议

  1. 生产环境配置

    • 使用环境变量存储敏感信息
    • 启用 HTTPS
    • 配置合适的 CORS 白名单
  2. Redis 安全

    • 设置密码认证
    • 限制网络访问
    • 定期备份数据
  3. Session 安全

    • 定期更换 SESSION_SECRET
    • 设置合适的过期时间
    • 启用 securehttpOnly
相关推荐
Merlyn102 小时前
NVM介绍及Windows下的安装
windows·node.js·nvm
TDengine (老段)2 小时前
Node.js 语言连接器进阶指南
大数据·物联网·node.js·编辑器·vim·时序数据库·tdengine
哈哈哈hhhhhh5 小时前
使用 Node.js 从零开始构建你自己的 Web 服务器
服务器·node.js
搞全栈小苏5 小时前
使用 nvm(不破坏系统)Linux 上把 Node.js / npm 升级到你指定版本(Node v23.x、npm 10.x)
linux·npm·node.js
放逐者-保持本心,方可放逐5 小时前
Node.js 多线程与高并发+实例+思考(简要版)
node.js·编辑器·vim·高并发·多线程·场景应用实例
萌萌哒草头将军18 小时前
Node.js 存在多个严重安全漏洞!官方建议尽快升级🚀🚀🚀
vue.js·react.js·node.js
这个图像胖嘟嘟18 小时前
前端开发的基本运行环境配置
开发语言·javascript·vue.js·react.js·typescript·npm·node.js
前端付豪1 天前
必知Node应用性能提升及API test 接口测试
前端·react.js·node.js
王同学 学出来1 天前
vue+nodejs项目在服务器实现docker部署
服务器·前端·vue.js·docker·node.js