第六节:添加响应中间件、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
相关推荐
没事别瞎琢磨2 小时前
十一、审计与 Run Session——每一步操作都被记录
人工智能·node.js
没事别瞎琢磨2 小时前
十六、AgentSandbox——把所有模块串起来的编排类
人工智能·node.js
没事别瞎琢磨2 小时前
十二、网络代理与白名单规则引擎
人工智能·node.js
没事别瞎琢磨2 小时前
十四、Git Worktree 隔离执行
人工智能·node.js
没事别瞎琢磨3 小时前
十、统一 Runner 入口——能力检测与模式回退
人工智能·node.js
没事别瞎琢磨3 小时前
八、环境隔离——构建安全的子进程环境
人工智能·node.js
没事别瞎琢磨4 小时前
六、输出捕获与截断
人工智能·node.js
没事别瞎琢磨4 小时前
七、敏感路径预检——Protected Paths
人工智能·node.js
没事别瞎琢磨4 小时前
五、进程执行——spawn、超时与进程树清理
人工智能·node.js
没事别瞎琢磨4 小时前
四、命令风险分级与审批策略
人工智能·node.js