基于用户权限为同一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; }
关键注意事项
- 确保你的认证中间件已将用户角色信息存入
req.user.role,这是所有方案的基础; - 权限配置文件集中管理,后续修改角色权限无需修改业务代码;
- 如果需要更复杂的权限判断(比如用户是否是资源所有者),可在过滤逻辑中加入额外条件,比如判断
req.user.id是否等于证书的created_by字段,再决定使用哪个字段集。
内容的提问来源于stack exchange,提问作者Ahmet Yazıcı
相关产品推荐
相关产品推荐

