You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于用户权限为同一Express端点返回不同响应的实现方案

基于用户角色的Express接口差异化响应实现方案

针对你提出的同一接口根据角色返回不同内容的需求,以下是几种低冗余、可复用的实现方式,核心思路是把角色判断和字段过滤逻辑抽离,避免重复代码:

方案1:集中式字段配置 + 过滤中间件

这种方式通过统一的权限配置文件定义各接口的角色字段权限,再用中间件完成数据过滤,业务控制器只负责查询全量数据。

步骤1:定义角色字段权限配置

先把每个接口允许不同角色返回的字段集中管理,方便后续维护:

// config/fieldPermissions.js
module.exports = {
  getCreatedCertificates: {
    // 证书创建者能看到的字段
    issuer: ['id', 'title', 'description', 'issuedAt', 'expiresAt', 'recipientDetails', 'internalNotes'],
    // 普通用户能看到的字段
    user: ['id', 'title', 'description', 'issuedAt', 'expiresAt']
  }
  // 其他接口的权限配置可在此扩展
};

步骤2:编写字段过滤中间件

这个中间件会根据当前接口标识和用户角色,对res.locals中的全量数据进行字段过滤:

// middleware/fieldFilter.js
const fieldPermissions = require('../config/fieldPermissions');

function filterFields(routeKey) {
  return (req, res, next) => {
    const userRole = req.user.role; // 假设认证中间件已将用户信息存入req.user
    const allowedFields = fieldPermissions[routeKey][userRole] || fieldPermissions[routeKey].user;

    // 过滤单条数据的工具函数
    const filterItem = (item) => {
      return Object.fromEntries(
        Object.entries(item).filter(([key]) => allowedFields.includes(key))
      );
    };

    // 处理数组或单条数据的情况
    let filteredData;
    if (Array.isArray(res.locals.data)) {
      filteredData = res.locals.data.map(filterItem);
    } else {
      filteredData = filterItem(res.locals.data);
    }

    res.locals.data = filteredData;
    next();
  };
}

module.exports = filterFields;

步骤3:接口路由与控制器配置

控制器只负责查询全量数据并放入res.locals,后续交给中间件过滤后统一返回:

// controllers/certificateController.js
const pool = require('../db/postgres');

async function getCreatedCertificates(req, res, next) {
  try {
    const issuerId = req.params.issuerId;
    // 查询全量字段,不考虑角色
    const result = await pool.query(
      `SELECT 
        id, title, description, 
        issued_at AS issuedAt, expires_at AS expiresAt,
        recipient_details AS recipientDetails, internal_notes AS internalNotes 
      FROM certificates WHERE created_by = $1`,
      [issuerId]
    );
    res.locals.data = result.rows;
    next(); // 交给过滤中间件处理
  } catch (err) {
    next(err);
  }
}

module.exports = { getCreatedCertificates };
// routes/certificates.js
const express = require('express');
const router = express.Router();
const { getCreatedCertificates } = require('../controllers/certificateController');
const authMiddleware = require('../middleware/auth'); // 你的认证中间件
const filterFields = require('../middleware/fieldFilter');

router.get('/issuers/:issuerId/certificates',
  authMiddleware, // 先校验用户身份,存入req.user
  getCreatedCertificates,
  filterFields('getCreatedCertificates'), // 指定当前接口的权限配置key
  (req, res) => {
    res.json({ data: res.locals.data }); // 统一返回过滤后的数据
  }
);

module.exports = router;

方案2:高阶函数包装控制器(更紧凑的写法)

如果觉得中间件的流程拆分太散,可以用高阶函数直接包装控制器,在控制器执行查询后立刻完成字段过滤:

// utils/withRoleFilter.js
const fieldPermissions = require('../config/fieldPermissions');

function withRoleFilter(routeKey, controller) {
  return async (req, res, next) => {
    try {
      const userRole = req.user.role;
      const allowedFields = fieldPermissions[routeKey][userRole] || fieldPermissions[routeKey].user;
      
      // 执行原控制器,获取全量数据
      const rawData = await controller(req, res);
      
      // 过滤字段
      const filterItem = (item) => Object.fromEntries(
        Object.entries(item).filter(([key]) => allowedFields.includes(key))
      );
      const filteredData = Array.isArray(rawData) ? rawData.map(filterItem) : filterItem(rawData);
      
      res.json({ data: filteredData });
    } catch (err) {
      next(err);
    }
  };
}

module.exports = withRoleFilter;

路由配置简化

router.get('/issuers/:issuerId/certificates',
  authMiddleware,
  withRoleFilter('getCreatedCertificates', getCreatedCertificates)
);

可选优化:数据库层面过滤字段

如果数据量较大,查询全量字段再过滤会浪费性能,可以把字段选择逻辑提前到SQL查询阶段:

// config/queryFields.js
module.exports = {
  getCreatedCertificates: {
    issuer: 'id, title, description, issued_at AS issuedAt, expires_at AS expiresAt, recipient_details AS recipientDetails, internal_notes AS internalNotes',
    user: 'id, title, description, issued_at AS issuedAt, expires_at AS expiresAt'
  }
};

修改高阶函数,把要查询的字段传给控制器:

function withRoleQuery(routeKey, controller) {
  return async (req, res, next) => {
    try {
      const userRole = req.user.role;
      req.queryFields = queryFields[routeKey][userRole] || queryFields[routeKey].user;
      const data = await controller(req, res);
      res.json({ data });
    } catch (err) {
      next(err);
    }
  };
}

控制器使用req.queryFields构建SQL:

async function getCreatedCertificates(req, res) {
  const issuerId = req.params.issuerId;
  const result = await pool.query(
    `SELECT ${req.queryFields} FROM certificates WHERE created_by = $1`,
    [issuerId]
  );
  return result.rows;
}

关键注意事项

  1. 确保你的认证中间件已将用户角色信息存入req.user.role,这是所有方案的基础;
  2. 权限配置文件集中管理,后续修改角色权限无需修改业务代码;
  3. 如果需要更复杂的权限判断(比如用户是否是资源所有者),可在过滤逻辑中加入额外条件,比如判断req.user.id是否等于证书的created_by字段,再决定使用哪个字段集。

内容的提问来源于stack exchange,提问作者Ahmet Yazıcı

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 09:40:32