目錄
Ant Design Pro 后端接口權限按鈕系統?
系統架構圖
前端實現
權限按鈕組件 (AuthButton.tsx)
權限鉤子 (useAccess.ts)
權限服務 (permission.ts)
產品列表頁面 (ProductList.tsx)
后端接口設計 (Node.js + Express 示例)
權限接口控制器 (permissionController.js)
權限接口路由 (permissionRoutes.js)
數據庫模型 (models.js)
權限系統特點
1. 權限等級體系
2. 權限分類
3. 緩存策略
權限管理最佳實踐
1. 權限按鈕使用示例
2. 權限控制策略
3. 高級權限場景
權限審計與監控
Ant Design Pro 后端接口權限按鈕系統?
下面是一個完整的基于后端接口的權限按鈕實現方案,包含前端頁面、后端接口設計和權限控制系統。
系統架構圖
前端實現
權限按鈕組件 (AuthButton.tsx)
import React from 'react';
import { Button, ButtonProps, Tooltip } from 'antd';
import { useAccess, Access } from 'umi';
import { hasPermission } from '@/services/permission';
import type { PermissionAction } from '@/services/permission';interface AuthButtonProps extends ButtonProps {/** 權限標識 */permission: string;/** 無權限時提示信息 */noPermissionText?: string;/** 是否隱藏無權限按鈕 */hideWhenDisabled?: boolean;/** 是否禁用而非隱藏 */disableWhenNoPermission?: boolean;/** 需要的數據權限上下文 */context?: any;
}const AuthButton: React.FC<AuthButtonProps> = ({permission,noPermissionText = '無操作權限',hideWhenDisabled = true,disableWhenNoPermission = false,context,children,...buttonProps
}) => {const { hasPermission } = useAccess();const hasAuth = hasPermission(permission, context);// 無權限時處理邏輯if (!hasAuth) {// 無權限時需要隱藏按鈕if (hideWhenDisabled) return null;// 無權限但不禁用,添加提示const disabledButton = (<Button {...buttonProps} disabled={disableWhenNoPermission}>{children}</Button>);// 根據配置返回禁用按鈕或帶提示的按鈕return disableWhenNoPermission ? (disabledButton) : (<Tooltip title={noPermissionText}>{disabledButton}</Tooltip>);}// 有權限,返回正常按鈕return (<Button {...buttonProps}>{children}</Button>);
};export default AuthButton;
權限鉤子 (useAccess.ts)
// src/hooks/useAccess.ts
import { useState, useEffect, useCallback } from 'react';
import { getPermissions } from '@/services/permission';type PermissionResult = {hasPermission: (permission: string, context?: any) => boolean;loading: boolean;permissions: string[];refreshPermissions: () => void;
};export default function useAccess(): PermissionResult {const [permissions, setPermissions] = useState<string[]>([]);const [loading, setLoading] = useState(true);// 獲取權限數據const fetchPermissions = useCallback(async () => {setLoading(true);try {const res = await getPermissions();setPermissions(res?.permissions || []);} catch (error) {console.error('獲取權限失敗', error);setPermissions([]);} finally {setLoading(false);}}, []);// 初始化時獲取權限useEffect(() => {fetchPermissions();}, [fetchPermissions]);// 檢查是否有權限const hasPermission = useCallback((permission: string, context?: any): boolean => {// 如果權限列表包含通配符權限if (permissions.includes('*')) return true;// 精確匹配權限if (permissions.includes(permission)) return true;// 基于數據的權限檢查(示例)if (context?.owner === currentUser.id && permissions.includes(`${permission}:own`)) {return true;}return false;}, [permissions]);return {hasPermission,loading,permissions,refreshPermissions: fetchPermissions};
}
權限服務 (permission.ts)
// src/services/permission.ts
import request from 'umi-request';
import type { RequestOptionsInit } from 'umi-request';// 權限類型定義
export type PermissionAction = | 'create' | 'read' | 'update' | 'delete' | 'export' | 'approve' | 'manage' | string;// 權限接口響應格式
export interface PermissionResponse {success: boolean;permissions: string[];
}// 獲取當前用戶權限
export async function getPermissions(options?: RequestOptionsInit
): Promise<PermissionResponse> {return request('/api/permissions', {method: 'GET',...(options || {}),});
}// 檢查特定權限
export async function checkPermission(permission: string, context?: any,options?: RequestOptionsInit
): Promise<{ hasPermission: boolean }> {return request('/api/permissions/check', {method: 'POST',data: { permission, context },...(options || {}),});
}// 權限控制裝飾器(用于頁面組件)
export function withPermissions(WrappedComponent: React.ComponentType) {return (props: any) => {const { loading, permissions } = useAccess();if (loading) {return (<div style={{ textAlign: 'center', padding: 40 }}><Spin tip="權限驗證中..." size="large" /></div>);}if (!permissions.length) {return (<Resultstatus="403"title="403"subTitle="抱歉,您沒有訪問權限,請聯系管理員"/>);}return <WrappedComponent {...props} />;};
}
產品列表頁面 (ProductList.tsx)
import React, { useState } from 'react';
import { PageContainer, ProTable } from '@ant-design/pro-components';
import AuthButton from '@/components/AuthButton';
import { PlusOutlined, EditOutlined, DeleteOutlined, ExportOutlined } from '@ant-design/icons';
import type { ProColumns } from '@ant-design/pro-components';
import { Modal, message } from 'antd';
import ProductForm from './ProductForm';interface Product {id: string;name: string;category: string;price: number;stock: number;owner: string;
}const ProductList: React.FC = () => {const [selectedRowKeys, setSelectedRowKeys] = useState<string[]>([]);const [createModalVisible, setCreateModalVisible] = useState(false);const [editRecord, setEditRecord] = useState<Product | null>(null);const columns: ProColumns<Product>[] = [{title: '產品名稱',dataIndex: 'name',key: 'name',},{title: '類別',dataIndex: 'category',key: 'category',},{title: '價格',dataIndex: 'price',key: 'price',valueType: 'money',},{title: '庫存',dataIndex: 'stock',key: 'stock',renderText: (val: number) => `${val}件`,},{title: '操作',key: 'action',render: (text, record) => (<span><AuthButton type="link" permission="product:update"context={{ owner: record.owner }}onClick={() => handleEdit(record)}><EditOutlined /> 編輯</AuthButton><AuthButton type="link" dangerpermission="product:delete"context={{ owner: record.owner }}noPermissionText="無法刪除非本人創建的產品"onClick={() => handleDelete(record)}><DeleteOutlined /> 刪除</AuthButton></span>),},];const handleCreate = () => {setCreateModalVisible(true);};const handleEdit = (record: Product) => {setEditRecord(record);};const handleDelete = (record: Product) => {Modal.confirm({title: '確認刪除',content: `確定要刪除產品【${record.name}】嗎?`,onOk: () => {// 調用刪除APImessage.success('刪除成功');},});};const handleExport = () => {// 導出操作message.success('導出成功');};const handleBatchDelete = () => {Modal.confirm({title: '批量刪除',content: `確定要刪除選中的${selectedRowKeys.length}個產品嗎?`,onOk: () => {// 調用批量刪除APImessage.success('批量刪除成功');setSelectedRowKeys([]);},});};return (<PageContainer><ProTable<Product>columns={columns}rowKey="id"headerTitle="產品列表"rowSelection={{selectedRowKeys,onChange: setSelectedRowKeys,}}toolBarRender={() => [<AuthButton key="create"type="primary" icon={<PlusOutlined />}permission="product:create"onClick={handleCreate}>新建產品</AuthButton>,<AuthButton key="export"icon={<ExportOutlined />}permission="product:export"onClick={handleExport}>導出數據</AuthButton>,selectedRowKeys.length > 0 && (<AuthButton key="batchDelete"dangerpermission="product:batch-delete"onClick={handleBatchDelete}noPermissionText="無批量刪除權限"disableWhenNoPermission>批量刪除</AuthButton>)]}request={async (params) => {// 模擬從后端獲取產品數據return {data: mockProducts,success: true,total: mockProducts.length,};}}/>{/* 新建/編輯模態框 */}<Modaltitle={editRecord ? "編輯產品" : "新建產品"}open={createModalVisible || !!editRecord}onCancel={() => {setCreateModalVisible(false);setEditRecord(null);}}destroyOnClosefooter={null}><ProductForm initialValues={editRecord}onSuccess={() => {setCreateModalVisible(false);setEditRecord(null);// 刷新表格}}/></Modal></PageContainer>);
};// 模擬產品數據
const mockProducts: Product[] = [{ id: '1', name: 'MacBook Pro', category: 'Laptop', price: 14999, stock: 15, owner: 'user-001' },{ id: '2', name: 'iPhone 14', category: 'Phone', price: 7999, stock: 30, owner: 'user-002' },{ id: '3', name: 'iPad Air', category: 'Tablet', price: 4799, stock: 8, owner: 'user-003' },
];export default ProductList;
后端接口設計 (Node.js + Express 示例)
權限接口控制器 (permissionController.js)
// 后端控制層 - permissionController.js
const db = require('../models');
const { Op } = require('sequelize');class PermissionController {// 獲取用戶所有權限async getPermissions(req, res) {try {const userId = req.user.id; // 從認證信息中獲取用戶ID// 1. 獲取用戶角色const user = await db.User.findByPk(userId, {include: [{model: db.Role,as: 'roles',attributes: ['id', 'name'],through: { attributes: [] }}]});// 2. 獲取角色關聯的權限const roleIds = user.roles.map(role => role.id);const rolesWithPermissions = await db.Role.findAll({where: { id: { [Op.in]: roleIds } },include: [{model: db.Permission,as: 'permissions',attributes: ['code'],through: { attributes: [] }}]});// 3. 獲取用戶自定義權限const customPermissions = await db.UserPermission.findAll({where: { userId },include: [{model: db.Permission,attributes: ['code']}]});// 4. 合并權限并去重const permissions = new Set();// 角色權限rolesWithPermissions.forEach(role => {role.permissions.forEach(permission => {permissions.add(permission.code);});});// 自定義權限customPermissions.forEach(up => {permissions.add(up.Permission.code);});res.json({success: true,permissions: Array.from(permissions)});} catch (error) {console.error('獲取權限失敗', error);res.status(500).json({success: false,message: '獲取權限失敗'});}}// 檢查特定權限async checkPermission(req, res) {try {const userId = req.user.id;const { permission, context } = req.body;// 1. 獲取用戶所有權限const { permissions } = await this._getAllPermissions(userId);// 2. 檢查是否存在完全匹配的權限if (permissions.has(permission) {return res.json({ hasPermission: true });}// 3. 數據權限檢查(示例:檢查產品所有權)if (permission.startsWith('product:') && context?.productId) {const product = await db.Product.findByPk(context.productId);if (product && product.ownerId === userId) {// 檢查擁有者的特殊權限(如 product:update:own)const ownPermission = `${permission}:own`;if (permissions.has(ownPermission)) {return res.json({ hasPermission: true });}}}res.json({ hasPermission: false });} catch (error) {console.error('權限檢查失敗', error);res.status(500).json({success: false,message: '權限檢查失敗'});}}// 內部方法:獲取用戶所有權限async _getAllPermissions(userId) {// ...與getPermissions方法類似// 返回Set對象包含所有權限}
}module.exports = new PermissionController();
權限接口路由 (permissionRoutes.js)
const express = require('express');
const router = express.Router();
const permissionController = require('../controllers/permissionController');
const authMiddleware = require('../middlewares/auth');// 權限認證中間件
router.use(authMiddleware);// 獲取用戶權限列表
router.get('/permissions', permissionController.getPermissions);// 檢查特定權限
router.post('/permissions/check', permissionController.checkPermission);module.exports = router;
數據庫模型 (models.js)
module.exports = (sequelize, DataTypes) => {const User = sequelize.define('User', {id: { type: DataTypes.UUID, primaryKey: true },username: DataTypes.STRING,email: DataTypes.STRING,// ...});const Role = sequelize.define('Role', {id: { type: DataTypes.UUID, primaryKey: true },name: DataTypes.STRING,description: DataTypes.STRING});const Permission = sequelize.define('Permission', {id: { type: DataTypes.UUID, primaryKey: true },code: { type: DataTypes.STRING, unique: true },name: DataTypes.STRING,category: DataTypes.STRING,description: DataTypes.TEXT});// 關聯關系User.belongsToMany(Role, { through: 'UserRole' });Role.belongsToMany(User, { through: 'UserRole' });Role.belongsToMany(Permission, { through: 'RolePermission' });Permission.belongsToMany(Role, { through: 'RolePermission' });User.belongsToMany(Permission, { through: 'UserPermission' });Permission.belongsToMany(User, { through: 'UserPermission' });return { User, Role, Permission };
};
權限系統特點
1. 權限等級體系
2. 權限分類
權限類型 | 描述 | 示例 |
---|---|---|
?功能權限? | 控制操作入口 | product:create |
?數據權限? | 控制數據訪問范圍 | product:read:department |
?操作權限? | 控制特定操作 | order:approve |
?狀態權限? | 控制狀態變更 | order:status:change |
3. 緩存策略
// 前端權限緩存機制
const PERMISSION_CACHE_KEY = 'user_permissions';
const CACHE_TTL = 30 * 60 * 1000; // 30分鐘async function getPermissionsWithCache() {const cached = localStorage.getItem(PERMISSION_CACHE_KEY);if (cached) {const { permissions, timestamp } = JSON.parse(cached);if (Date.now() - timestamp < CACHE_TTL) {return permissions;}}// 后端獲取新權限const res = await getPermissions();const dataToCache = {permissions: res.permissions,timestamp: Date.now()};localStorage.setItem(PERMISSION_CACHE_KEY, JSON.stringify(dataToCache));return res.permissions;
}// 強制刷新
function refreshPermissions() {localStorage.removeItem(PERMISSION_CACHE_KEY);getPermissionsWithCache();
}
權限管理最佳實踐
1. 權限按鈕使用示例
// 基本按鈕
<AuthButton permission="product:create">創建產品</AuthButton>// 帶圖標和提示的按鈕
<AuthButton permission="product:delete"icon={<DeleteOutlined />}noPermissionText="無刪除權限"
>刪除
</AuthButton>// 數據上下文權限
<AuthButton permission="product:update"context={{ owner: currentUser.id }}
>編輯我的產品
</AuthButton>
2. 權限控制策略
// 頁面級別權限控制
const AdminPage = () => {return (<Access permission="system:admin"><div>管理員面板</div>{/* 管理員功能區域 */}</Access>);
};// 條件渲染多個元素
<div><Access permission="reports:view"><DashboardSection /><ReportChart /></Access><Access permission="user:manage"><UserManagementSection /></Access>
</div>
3. 高級權限場景
?動態權限加載?
const [dynamicPermissions, setDynamicPermissions] = useState(false);useEffect(() => {checkPermission('advanced:feature').then(res => {setDynamicPermissions(res.hasPermission);});
}, []);return (<div>{dynamicPermissions && (<Button>高級功能</Button>)}</div>
);
?批量權限檢查?
const [buttonsEnabled, setButtonsEnabled] = useState({});useEffect(() => {const permissionsToCheck = ['order:create','order:approve','order:export'];checkMultiplePermissions(permissionsToCheck).then(res => {setButtonsEnabled(res);});
}, []);// ...
<Button disabled={!buttonsEnabled['order:create']}>新建訂單</Button>
權限審計與監控
// 前端操作日志記錄
function logPermissionAction(action: string, hasPermission: boolean, context?: any
) {analytics.track('permission_action', {action,hasPermission,userId: currentUser.id,context,timestamp: new Date().toISOString(),pathname: window.location.pathname});
}// 在權限鉤子中調用
const hasPermission = (perm: string, context?: any) => {const hasPerm = /* 權限檢查邏輯 */;if (perm.startsWith('sensitive:')) {logPermissionAction(perm, hasPerm, context);}return hasPerm;
};
這個基于 Ant Design Pro 和 TypeScript 的權限按鈕系統提供了完整的解決方案:
- ?前后端分離?:前端基于組件化設計,后端提供靈活的權限服務
- ?細粒度控制?:支持功能權限和數據權限
- ?靈活配置?:多種權限控制策略(隱藏、禁用、提示)
- ?權限緩存?:優化性能減少后端請求
- ?權限繼承?:支持角色權限和直接用戶權限
- ?上下文權限?:支持基于數據所有權的權限檢查
- ?完整審計?:記錄敏感操作便于追蹤
在企業級項目中,這樣的權限系統可以輕松集成到現有架構中,提供安全可靠的權限控制能力。