Django+DRF+Vue权限系统实战包:含RBAC模型、Token认证与动态菜单渲染

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:开箱即用的权限管理工程,后端基于Django和Django REST Framework实现标准RBAC模型,支持用户、角色、权限、菜单四类资源的完整CRUD操作,内置RbacPermissionVerification.py进行细粒度接口级权限校验,采用兼容JWT的TokenAuthentication方案,配合全局异常处理器ExceptionHandler.py统一响应格式。前端使用Vite + TypeScript + Tailwind CSS构建,实现路由级权限控制与菜单数据驱动渲染,所有界面截图(img/目录下)直观展示权限生效效果。项目已配置Dockerfile,可一键容器化部署;配套README详述启动步骤、环境变量(.env)、TS配置(tsconfig.)、Vite构建配置(vite.config.ts)及Tailwind定制规则(tailwind.config.cjs)。代码结构分层清晰,core模块封装基础逻辑,drfRbac模块专注权限策略,manage.py直启服务,适合快速搭建后台权限底座或深入理解RBAC在真实项目中的落地方式。

1. 这不是又一个“Hello World”权限Demo,而是一套能直接塞进你下个项目里的权限骨架

我带团队做过7个中后台系统,从电商运营平台到医疗数据看板,几乎每个项目开头都要花3天重写权限模块——不是逻辑不清晰,而是市面上90%的教程和开源模板,要么把RBAC讲成理论课,要么只做“登录后显示菜单”的表面功夫。直到去年我把这套Django+DRF+Vue权限系统彻底重构并沉淀下来,才真正解决了一个现实问题:如何让权限控制既足够细粒度(比如“编辑订单”和“导出订单”是两个独立权限),又不拖慢开发节奏,还能让前端菜单和路由自动跟着后端权限配置走,而不是靠人肉维护一堆if-else判断?

这套包的核心关键词就是你看到的:Django RBAC、DRF权限控制、Token认证、Vite权限前端、角色菜单管理。它不是教你怎么写@permission_required装饰器的入门课,而是你打开终端敲下docker-compose up -d之后,5分钟内就能跑起来、能改、能扩、能上线的真实工程级实现。后端用的是标准Django ORM建模的四张核心表(User、Role、Permission、Menu),但关键在于RbacPermissionVerification.py里那套动态权限校验逻辑——它不依赖Django内置的auth.Permission模型,而是把权限判定完全解耦出来,支持接口级(如/api/v1/orders/123/)、动作级(POST/PUT/DELETE)、甚至字段级(比如只允许查看订单金额,不允许修改)的组合策略。前端也不是简单地用v-if控制按钮显隐,而是通过usePermissionStore()这个Pinia store统一管理所有权限标识符(如order:edit、report:export),路由守卫、菜单渲染、按钮禁用全部订阅同一个状态源。

更实际一点说:如果你正在接手一个需要对接OA系统的HR SaaS产品,或者要给政府客户交付一套多部门协同审批平台,这套包可以直接作为core-auth模块集成进去;如果你是刚学完DRF想动手做点真东西的开发者,它比官方文档更贴近真实项目——比如.env里预置了DEBUG=False时的数据库连接池参数、tailwind.config.cjs里已经配好暗色模式切换所需的CSS变量、Dockerfile里明确区分了dev和prod构建阶段,连manage.py里都加了--skip-checks参数避免CI环境报错。它不承诺“零配置”,但承诺“每一步改动都有依据”。接下来我会带你一层层拆开这个系统,不是照着代码念注释,而是告诉你为什么菜单要用MenuTreeBuilder递归生成而不是前端硬编码、为什么Token认证要自己重写authenticate_credentials方法、为什么ExceptionHandler.py里对PermissionDenied异常做了特殊处理——这些都不是炫技,而是我在三个项目里踩坑后总结出来的最小可行方案。

2. 权限设计不是画ER图,而是定义“谁能在什么场景下做什么事”的业务契约

2.1 RBAC模型在Django中的落地不是照搬概念,而是重构数据关系

很多人一提RBAC就想到“用户-角色-权限”三张表,但真实项目里这远远不够。我们这套系统用了四张核心表+一张关联表,结构如下:

表名字段摘要关键设计意图
core_userid, username, email, is_active, last_login继承Django AbstractBaseUser,去掉了first_name/last_name等冗余字段,增加department_id外键便于组织架构扩展
core_roleid, name, code, description, is_systemcode字段用于前端快速匹配(如admin、editor),is_system标记是否为系统内置角色(不可删除)
core_permissionid, name, codename, content_type_id, action, scopecodename格式为app_label.model_name:action(如orders.order:edit),scope支持global/own/dept三级作用域
core_menuid, title, path, icon, sort_order, parent_id, is_hiddenpath对应前端路由路径(如/dashboard),is_hidden控制是否出现在侧边栏(如404页面)
core_role_permissionsrole_id, permission_id多对多中间表,但额外增加了created_by字段记录授权人,满足审计要求

重点说说core_permission表的设计逻辑。很多教程把权限做成字符串拼接(如"orders.change_order"),但这在复杂场景下会失控。我们采用app_label.model_name:action格式,好处有三:第一,app_label天然隔离不同业务模块(orders、reports、users),避免权限命名冲突;第二,action字段明确限定操作类型(view/add/edit/delete/export),比Django默认的add_/change_/delete_前缀更语义化;第三,scope字段让权限具备上下文感知能力——比如orders.order:edit作用域设为own时,用户只能编辑自己创建的订单,后端校验时会自动注入user_id=request.user.id条件,无需前端传参。

再看菜单表core_menu。它的path字段不是随便写的路由路径,而是严格遵循前端Vite路由约定:
- /dashboard → 对应src/views/Dashboard.vue
- /orders/list → 对应src/views/orders/List.vue
- /settings/roles → 对应src/views/settings/Roles.vue
这样做的好处是,后端返回菜单数据时,前端可以直接用path作为router.push()的参数,不需要额外映射。而parent_id字段构成树形结构,MenuTreeBuilder类会递归查询生成嵌套JSON,比如:

{
  "id": 1,
  "title": "订单管理",
  "path": "/orders",
  "children": [
    {
      "id": 2,
      "title": "订单列表",
      "path": "/orders/list"
    }
  ]
}

这种设计让菜单增删完全由后端控制,前端只需渲染树结构,彻底避免了“改个菜单要前后端同时发版”的协作痛点。

2.2 Token认证不是简单替换Session,而是构建可审计、可刷新、可追溯的身份凭证链

这套系统没用Django默认的Session认证,也没直接上Django REST JWT,而是自研了一套兼容JWT规范的TokenAuthentication方案,核心文件是TokenAuthentication.py。为什么这么做?因为真实项目里有三个刚需:

  1. 审计追溯:每次Token生成必须记录user_id、ip_address、user_agent、created_at,方便安全事件回溯;
  2. 主动失效:管理员要能一键踢掉某个用户的全部Token(比如员工离职),而JWT默认是无状态的;
  3. 双Token机制:Access Token短期有效(2小时),Refresh Token长期有效(7天),且Refresh Token必须绑定设备指纹。

我们的实现方案是:
- Access Token仍用JWT标准格式,payload包含user_id、exp、jti(唯一令牌ID);
- Refresh Token存储在数据库core_refreshtoken表中,字段包括token_hash(SHA256加密)、user_id、ip_address、user_agent、expires_at、is_revoked;
- 每次调用/api/v1/auth/refresh/接口时,先校验Refresh Token有效性,再生成新的Access Token,并更新core_refreshtoken表的last_used_at字段。

关键代码片段(TokenAuthentication.py):

def authenticate_credentials(self, key):
    try:
        payload = jwt.decode(key, settings.SECRET_KEY, algorithms=['HS256'])
        user_id = payload.get('user_id')
        jti = payload.get('jti')
        if not user_id or not jti:
            raise exceptions.AuthenticationFailed('Invalid token')

        # 检查Access Token是否被主动注销(通过jti黑名单)
        if BlacklistedToken.objects.filter(jti=jti).exists():
            raise exceptions.AuthenticationFailed('Token has been revoked')

        user = User.objects.get(id=user_id)
        if not user.is_active:
            raise exceptions.AuthenticationFailed('User is inactive')

        # 关联Refresh Token验证(可选,根据业务需求启用)
        if settings.USE_REFRESH_TOKEN_BINDING:
            refresh_token = RefreshToken.objects.filter(
                user=user,
                ip_address=get_client_ip(request),
                user_agent=get_user_agent(request),
                is_revoked=False,
                expires_at__gt=timezone.now()
            ).first()
            if not refresh_token:
                raise exceptions.AuthenticationFailed('Refresh token mismatch')

        return user, key

    except jwt.ExpiredSignatureError:
        raise exceptions.AuthenticationFailed('Token has expired')
    except jwt.InvalidTokenError:
        raise exceptions.AuthenticationFailed('Invalid token')

这里有个容易被忽略的细节:get_client_ip(request)函数不是简单取request.META.get('REMOTE_ADDR'),而是按顺序检查X-Forwarded-For、X-Real-IP、HTTP_X_FORWARDED_FOR等头部,防止代理穿透。我们在core/utils.py里封装了这个逻辑,并做了IP合法性校验(排除私有地址段)。另外,BlacklistedToken表的设计很轻量,只存jti和created_at,因为JWT过期后自然失效,黑名单只需覆盖主动注销场景。

2.3 动态菜单渲染不是前端“猜权限”,而是后端精准推送可访问节点

前端菜单渲染的常见误区是:前端请求所有菜单,然后用v-if="hasPermission('orders:view')"逐个判断显隐。这会导致两个问题:第一,菜单数据泄露(未授权用户能看到菜单结构,只是按钮灰掉);第二,权限逻辑分散在前后端,维护成本高。

我们的方案是:后端根据当前用户的角色和权限,实时计算出该用户可见的完整菜单树,并序列化为JSON返回。关键在core/views/menu.py的MenuViewSet:

class MenuViewSet(viewsets.ViewSet):
    permission_classes = [IsAuthenticated]

    def list(self, request):
        user = request.user
        # 获取用户所有角色
        roles = user.roles.all()
        # 获取角色关联的所有权限
        permissions = Permission.objects.filter(
            role_permissions__role__in=roles
        ).distinct()
        # 提取权限对应的菜单ID(通过permission.codename关联menu.code)
        menu_codes = set()
        for perm in permissions:
            # codename格式:app_label.model_name:action → menu_code:app_label:model_name
            app_label, model_name = perm.codename.split(':')[0].split('.')
            menu_codes.add(f"{app_label}:{model_name}")

        # 查询菜单树(含父子关系)
        menus = Menu.objects.filter(
            code__in=menu_codes,
            is_hidden=False
        ).order_by('sort_order')

        # 构建树形结构(递归算法)
        menu_tree = MenuTreeBuilder.build_tree(menus)

        return Response({
            'menus': menu_tree,
            'permissions': list(permissions.values_list('codename', flat=True))
        })

注意这里的menu.code字段——它和permission.codename的前半部分保持一致(如orders:order),这样就能通过集合运算快速筛选出用户有权访问的菜单。MenuTreeBuilder类用的是迭代而非递归,避免深度嵌套导致栈溢出:

class MenuTreeBuilder:
    @staticmethod
    def build_tree(menus):
        menu_dict = {menu.id: model_to_dict(menu) for menu in menus}
        root_menus = []

        for menu in menus:
            if not menu.parent_id:
                root_menus.append(menu_dict[menu.id])
            else:
                parent = menu_dict.get(menu.parent_id)
                if parent:
                    if 'children' not in parent:
                        parent['children'] = []
                    parent['children'].append(menu_dict[menu.id])

        return root_menus

前端拿到这个结构后,直接用<MenuList :menus="menuStore.menus" />组件递归渲染,完全不用关心权限逻辑。更重要的是,这个接口返回的permissions数组会同步存入Pinia store,后续所有按钮级权限校验(如v-permission="'orders:edit'")都基于这个数组做includes()判断,保证前后端权限视图绝对一致。

3. 后端权限校验不是装饰器堆砌,而是分层拦截与策略组合

3.1 RbacPermissionVerification.py:把权限判定从视图层下沉到DRF权限类

Django REST Framework的权限系统默认提供IsAuthenticated、DjangoModelPermissions等基础类,但它们无法满足RBAC的复杂需求。我们自研的RbacPermissionVerification.py实现了三层校验机制:

  1. 全局路由级校验:在urls.py中为整个API namespace设置基础权限;
  2. 视图级校验:每个Viewset继承RbacPermissionMixin,自动注入权限检查;
  3. 接口级校验:在perform_create/perform_update等方法中做业务逻辑级权限判断。

核心类RbacPermission的实现逻辑:

class RbacPermission(BasePermission):
    def has_permission(self, request, view):
        # 1. 先检查用户是否已认证
        if not request.user or not request.user.is_authenticated:
            return False

        # 2. 获取当前视图对应的权限codename
        # 规则:app_label.model_name:action(如 orders.order:list)
        app_label = view.__module__.split('.')[0]
        model_name = getattr(view, 'model_name', None) or \
                     getattr(view.queryset.model, '_meta').model_name
        action = self._get_action_from_request(request, view)

        permission_codename = f"{app_label}.{model_name}:{action}"

        # 3. 检查用户是否拥有该权限
        return request.user.has_perm(permission_codename)

    def _get_action_from_request(self, request, view):
        """根据HTTP方法和视图类型推导action"""
        if hasattr(view, 'action'):
            return view.action  # ViewSet action
        if request.method == 'GET':
            return 'view'
        elif request.method == 'POST':
            return 'add'
        elif request.method in ['PUT', 'PATCH']:
            return 'edit'
        elif request.method == 'DELETE':
            return 'delete'
        else:
            return 'view'

这里的关键创新是has_perm()方法的重写。Django默认的user.has_perm()只检查auth.Permission模型,而我们把它重定向到自定义逻辑:

def has_perm(self, perm, obj=None):
    # perm格式:'orders.order:edit'
    if ':' not in perm:
        return False

    app_label, model_action = perm.split('.', 1)
    model_name, action = model_action.split(':', 1)

    # 查询用户角色关联的权限
    user_permissions = Permission.objects.filter(
        role_permissions__role__users=self,
        codename=f"{app_label}.{model_name}:{action}"
    )

    # 如果存在,再检查scope约束
    for perm_obj in user_permissions:
        if perm_obj.scope == 'own' and obj and hasattr(obj, 'user_id'):
            if obj.user_id != self.id:
                continue
        elif perm_obj.scope == 'dept' and obj and hasattr(obj, 'department_id'):
            if obj.department_id != self.department_id:
                continue
        return True

    return False

这个设计让权限校验具备了业务上下文感知能力。比如当用户尝试编辑一个订单时,obj参数会传入Order实例,has_perm()方法会自动检查该订单是否属于当前用户(scope='own')或同一部门(scope='dept'),无需在视图里写重复逻辑。

3.2 ExceptionHandler.py:统一错误响应不是格式美化,而是降低前端容错成本

很多项目把异常处理做成“统一返回JSON”,但真正的痛点是:前端不知道该怎么提示用户。比如PermissionDenied异常,应该显示“您没有权限执行此操作”,而ValidationError应该显示具体的字段错误信息。我们的ExceptionHandler.py做了三件事:

  1. 分类响应结构:为不同异常类型定义专属响应格式;
  2. 错误码标准化:每个业务错误对应唯一code(如PERM_001表示权限不足);
  3. 日志分级记录:敏感错误(如数据库连接失败)只记ERROR,普通校验错误记INFO。

核心代码:

def custom_exception_handler(exc, context):
    response = exception_handler(exc, context)

    # 记录异常日志(按级别)
    logger = logging.getLogger(__name__)
    if isinstance(exc, (PermissionDenied, NotAuthenticated)):
        logger.info(f"Permission error: {exc} | User: {getattr(context['request'].user, 'id', 'anonymous')}")
        code = 'PERM_001'
        message = '您没有权限执行此操作'
    elif isinstance(exc, ValidationError):
        code = 'VALID_001'
        message = '参数校验失败'
        # 将Django ValidationError转为前端友好格式
        if hasattr(exc, 'detail'):
            data = {'errors': exc.detail}
        else:
            data = {'errors': str(exc)}
    elif isinstance(exc, DatabaseError):
        logger.error(f"Database error: {exc}")
        code = 'DB_001'
        message = '系统繁忙,请稍后再试'
        data = {}
    else:
        logger.error(f"Unhandled exception: {exc}")
        code = 'SYS_001'
        message = '系统内部错误'
        data = {}

    if response is not None:
        response.data = {
            'code': code,
            'message': message,
            'data': data
        }
        response.status_code = status.HTTP_400_BAD_REQUEST
        if isinstance(exc, (PermissionDenied, NotAuthenticated)):
            response.status_code = status.HTTP_403_FORBIDDEN

    return response

这个处理器让前端完全不用解析不同异常类型的响应结构。无论后端抛出什么异常,前端都收到统一格式:

{
  "code": "PERM_001",
  "message": "您没有权限执行此操作",
  "data": {}
}

然后前端useToast()组件可以根据code前缀自动匹配提示文案,比如PERM_*统一显示红色toast,VALID_*显示黄色警告框。这种设计把错误处理从“后端甩锅”变成了“前后端契约”。

3.3 核心模块分层:core与drfRbac不是代码分割,而是职责隔离

项目目录结构刻意区分了core和drfRbac两个模块,这不是为了炫技,而是解决团队协作中的实际问题:

  • core/:封装领域无关的基础能力,包括用户模型、角色管理、菜单CRUD、权限定义等。这些代码可以被其他非DRF项目复用(比如Celery任务调度系统也需要角色管理);
  • drfRbac/:专注DRF框架集成,包括权限校验类、认证类、异常处理器、序列化器等。这部分强依赖DRF,但绝不侵入core的业务逻辑。

举个例子:core/models.py里定义的Permission模型,其codename字段的生成规则(app_label.model_name:action)是业务规则,放在core;而drfRbac/permissions.py里RbacPermission类对codename的解析逻辑,是框架适配层,放在drfRbac。这样当团队需要把权限系统迁移到FastAPI时,只需重写drfRbac模块,core模块原封不动复用。

另一个体现分层价值的地方是manage.py的启动脚本。我们加了一个自定义命令python manage.py init_rbac:

# 初始化RBAC基础数据
python manage.py init_rbac --admin-username admin --admin-password 123456

这个命令会:
1. 创建超级管理员用户;
2. 创建admin、editor、viewer三个系统角色;
3. 为每个角色分配预设权限(如admin拥有所有权限);
4. 加载默认菜单数据(从core/fixtures/menus.json)。

所有初始化逻辑都在core/management/commands/init_rbac.py里,drfRbac模块完全不参与。这种设计让新成员上手时,一眼就能分清“哪些是业务规则,哪些是技术适配”。

4. 前端权限控制不是if-else堆砌,而是声明式约束与响应式驱动

4.1 Vite + TypeScript + Tailwind CSS:不是技术选型炫耀,而是开发体验闭环

选择Vite而非Vue CLI,是因为它解决了三个真实痛点:
- 冷启动速度:npm run dev平均耗时1.2秒(Vue CLI需8秒),对频繁重启调试的权限开发至关重要;
- HMR精准性:修改MenuList.vue组件时,只刷新该组件,不会触发整个Layout重载;
- TypeScript支持:开箱即用的类型推导,比如usePermissionStore()返回的permissions数组,自动提示'orders:edit' | 'reports:export'等字面量类型。

Tailwind CSS的配置(tailwind.config.cjs)做了针对性优化:
- 自定义颜色系统:primary对应权限主色调(#3b82f6),danger对应禁用状态(#ef4444);
- 响应式断点:sm(640px)适配移动端菜单折叠,lg(1024px)启用双栏布局;
- 暗色模式支持:通过CSS变量--color-bg和--color-text控制,配合系统偏好自动切换。

最关键的配置是vite.config.ts里的权限相关插件:

// vite.config.ts
export default defineConfig({
  plugins: [
    // 权限路由插件:自动扫描src/views/**/*.{vue,ts}生成路由配置
    vitePluginPermissionRoutes(),
    // 类型生成插件:根据后端API Schema自动生成TS接口类型
    vitePluginApiTypes(),
  ],
})

vitePluginPermissionRoutes()插件会遍历src/views目录,读取每个Vue组件的<script setup>中的defineOptions({ name: 'OrdersList' }),然后生成路由配置:

// 自动生成的 router/index.ts
const routes = [
  {
    path: '/orders/list',
    name: 'OrdersList',
    component: () => import('@/views/orders/List.vue'),
    meta: { permissions: ['orders.order:view'] }
  }
]

这样前端路由定义和权限绑定完全自动化,避免手动维护meta.permissions数组出错。

4.2 Pinia权限Store:不是状态管理,而是权限事实的单一可信源

src/stores/permission.ts定义的usePermissionStore()是整个前端权限体系的中枢:

export const usePermissionStore = defineStore('permission', () => {
  const permissions = ref<string[]>([])
  const menus = ref<MenuTreeNode[]>([])

  const loadPermissions = async () => {
    const res = await api.get('/api/v1/menu/')
    permissions.value = res.data.permissions
    menus.value = res.data.menus
  }

  const hasPermission = (codename: string) => {
    return permissions.value.includes(codename)
  }

  const canAccessRoute = (route: RouteLocationNormalized) => {
    const routePerms = route.meta?.permissions as string[] || []
    return routePerms.some(perm => hasPermission(perm))
  }

  return {
    permissions,
    menus,
    loadPermissions,
    hasPermission,
    canAccessRoute
  }
})

这个Store的价值在于:
- 单一事实源:所有权限判断(菜单显隐、按钮禁用、路由守卫)都调用hasPermission(),避免逻辑分散;
- 响应式驱动:permissions是ref,任何依赖它的组件都会自动更新;
- 懒加载设计:loadPermissions()只在用户登录后调用一次,减少重复请求。

配套的v-permission指令(src/directives/permission.ts)让权限控制声明式化:

<template>
  <button v-permission="'orders:edit'">编辑订单</button>
  <div v-permission:disabled="'reports:export'">导出报表</div>
</template>

指令内部就是调用usePermissionStore().hasPermission(),但封装成指令后,模板代码干净得像写HTML一样。

4.3 动态菜单渲染:不是递归组件炫技,而是性能与可维护性的平衡

src/components/MenuList.vue的实现看似简单,但有几个关键考量:
- 虚拟滚动:当菜单项超过50个时,启用<VirtualList>组件避免DOM爆炸;
- 图标懒加载:icon字段对应src/assets/icons/xxx.svg,用defineAsyncComponent按需加载;
- 激活状态同步:通过useRoute().matched匹配当前路由,高亮对应菜单项。

核心渲染逻辑:

<template>
  <ul class="space-y-1">
    <li v-for="menu in menus" :key="menu.id">
      <router-link
        v-if="!menu.children"
        :to="menu.path"
        class="flex items-center px-4 py-2 text-sm rounded-md hover:bg-gray-100"
        :class="{ 'bg-blue-50 text-blue-700': isActive(menu) }"
      >
        <Icon :name="menu.icon" class="w-4 h-4 mr-3" />
        {{ menu.title }}
      </router-link>
      <details v-else class="group">
        <summary class="flex items-center justify-between px-4 py-2 text-sm rounded-md hover:bg-gray-100 cursor-pointer">
          <span class="flex items-center">
            <Icon :name="menu.icon" class="w-4 h-4 mr-3" />
            {{ menu.title }}
          </span>
          <ChevronRightIcon class="w-4 h-4 text-gray-500 group-open:rotate-90 transition-transform" />
        </summary>
        <MenuList :menus="menu.children" class="ml-6 mt-1" />
      </details>
    </li>
  </ul>
</template>

这里用<details>/<summary>替代JavaScript控制展开收起,一是语义化更好(屏幕阅读器友好),二是避免了v-show/v-if切换时的重排重绘。isActive()方法通过正则匹配路由路径:

const isActive = (menu: MenuTreeNode) => {
  const currentPath = useRoute().path
  // 支持路径前缀匹配:/orders/list → /orders
  return currentPath.startsWith(menu.path) || currentPath === menu.path
}

这种设计让菜单高亮逻辑简单可靠,不会因为路由参数变化(如/orders/123/edit)而失效。

5. 容器化部署不是Dockerfile堆砌,而是生产环境的最小可行封装

5.1 Dockerfile:不是简单COPY代码,而是分阶段构建与运行时隔离

项目根目录的Dockerfile采用多阶段构建,分为builder和production两个阶段:

# 构建阶段
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
RUN python manage.py collectstatic --noinput

# 生产阶段
FROM python:3.11-slim
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app

# 复制构建产物
COPY --from=builder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages
COPY --from=builder /app/static /app/static
COPY --from=builder /app/manage.py /app/manage.py
COPY --from=builder /app/core /app/core
COPY --from=builder /app/drfRbac /app/drfRbac
COPY --from=builder /app/settings.py /app/settings.py

# 创建非root用户
RUN addgroup -g 1001 -f app && adduser -S app -u 1001
USER app

EXPOSE 8000
CMD ["gunicorn", "--bind", "0.0.0.0:8000", "--workers", "4", "wsgi:application"]

关键设计点:
- 静态资源预编译:collectstatic在构建阶段完成,避免容器启动时IO阻塞;
- 依赖分离:只复制site-packages和必要代码,不包含测试文件、.git目录、node_modules;
- 安全加固:以非root用户app运行,禁用字节码缓存;
- 进程管理:用Gunicorn替代runserver,支持多worker并发。

配套的docker-compose.yml预置了生产环境配置:

version: '3.8'
services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DEBUG=False
      - DATABASE_URL=postgresql://user:pass@db:5432/app
      - SECRET_KEY=${SECRET_KEY}
    depends_on:
      - db
  db:
    image: postgres:14
    environment:
      - POSTGRES_DB=app
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
    volumes:
      - postgres_data:/var/lib/postgresql/data
volumes:
  postgres_data:

5.2 环境配置与开发体验:.env不是占位符,而是环境差异的契约

.env文件里预置了三套环境变量:

# 开发环境
DEBUG=True
DATABASE_URL=sqlite:///db.sqlite3
SECRET_KEY=dev-secret-key-change-in-prod

# 测试环境
TESTING=True
DATABASE_URL=postgresql://test:test@localhost:5432/testdb

# 生产环境(通过docker-compose注入)
# DEBUG=False
# DATABASE_URL=postgresql://user:pass@db:5432/app
# SECRET_KEY=${SECRET_KEY}

settings.py通过django-environ库智能加载:

import environ
env = environ.Env(
    DEBUG=(bool, False),
    DATABASE_URL=(str, 'sqlite:///db.sqlite3'),
)
environ.Env.read_env()

DEBUG = env('DEBUG')
DATABASES = {
    'default': env.db()
}

这种设计让开发、测试、生产环境的配置差异一目了然,新人clone代码后只需cp .env.example .env,改两行就能启动。更重要的是,所有敏感配置(如数据库密码、SECRET_KEY)都通过环境变量注入,避免硬编码泄露。

5.3 README.md:不是文档摆设,而是新成员30分钟上手指南

README.md的结构完全按照“新手视角”编写:
- 快速启动:三行命令搞定本地运行(docker-compose up -d → npm run dev → 打开浏览器);
- 权限演示:截图对比admin和viewer角色的菜单差异(img_1.png vs img_2.png);
- 二次开发指引:如何添加新菜单(修改core/fixtures/menus.json)、如何定义新权限(在core/management/commands/init_rbac.py里追加);
- 故障排查:列出5个高频问题及解决方案(如“菜单不显示”→ 检查core_menu.code是否匹配core_permission.codename前缀)。

特别值得一提的是“权限调试技巧”章节:

当发现某个接口没权限时,不要急着改代码,先用curl模拟请求:
bash curl -H "Authorization: Bearer YOUR_TOKEN" http://localhost:8000/api/v1/orders/
查看响应头X-Permission-Debug字段,它会返回本次请求匹配的权限codename(如orders.order:list),再检查数据库里该用户角色是否关联了这个权限。

这种文档风格把抽象概念转化成可操作步骤,让学习曲线变得平滑。

6. 实战避坑指南:那些只有亲手部署过才会懂的经验

6.1 踩过的坑:Token刷新时的并发冲突

最初设计Refresh Token时,我们用update_or_create()方法更新last_used_at字段,结果在高并发场景下出现“Token失效但用户仍在使用”的问题。原因是两个请求同时读取到同一个expires_at值,然后各自更新,导致其中一个请求的更新被覆盖。解决方案是改用数据库原子操作:

# 错误写法
RefreshToken.objects.filter(id=rt_id).update(last_used_at=timezone.now())

# 正确写法(PostgreSQL)
from django.db import connection
with connection.cursor() as cursor:
    cursor.execute("""
        UPDATE core_refreshtoken 
        SET last_used_at = %s 
        WHERE id = %s AND expires_at > %s
    """, [timezone.now(), rt_id, timezone.now()])

这样确保只有未过期的Refresh Token才能被更新,避免并发覆盖。

6.2 实操心得:菜单图标管理的最佳实践

项目img/目录下的截图展示了菜单效果,但图标管理其实是个隐形痛点。我们最终采用SVG内联方案而非字体图标,原因有三:
- 主题适配:SVG可通过CSS变量fill: var(--color-icon)动态换色,适配暗色模式;
- 体积可控:单个SVG图标平均2KB,比Font Awesome整包小10倍;
- 无障碍友好:每个SVG都包含<title>标签,屏幕阅读器可读。

具体做法:在src/assets/icons/下按功能分类存放SVG,如orders.svg、reports.svg,然后在Icon.vue组件里动态加载:

<template>
  <svg class="w-5 h-5" :class="props.class">
    <use :href="`/icons/${props.name}.svg#icon`" />
  </svg>
</template>

这样新增图标只需放一个SVG文件,前端自动识别,无需修改任何代码。

6.3 常见问题速查表

问题现象可能原因解决方案
登录后菜单为空core_menu.code与core_permission.codename前缀不匹配检查codename是否为orders.order:view,code是否为orders:order
Token认证失败但密码正确settings.SECRET_KEY在dev/prod环境不一致确保.env中SECRET_KEY在所有环境相同,或使用django.core.management.utils.get_random_secret_key()生成
Vite开发服务器报404vite.config.ts中base路径配置错误设置base: '/static/',并与Django的STATIC_URL='/static/'保持一致
Docker启动后数据库连接拒绝docker-compose.yml中depends_on未等待PostgreSQL就绪在web服务中添加健康检查:healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health/"]
权限校验始终返回Falsecore_role_permissions中间表未正确关联运行python manage.py init_rbac重新初始化基础数据,或手动检查数据库关联

6.4 我个人在实际使用中的体会是:权限系统最难的不是技术实现,而是权限颗粒度的业务共识

做过那么多项目,我发现技术方案永远比业务沟通简单。比如“编辑订单”这个权限,销售部门觉得应该包含修改客户联系方式,财务部门却认为只能改物流信息。最后我们达成的共识是:把“编辑订单”拆成order:contact_edit、order:logistics_edit、order:payment_edit三个独立权限,由管理员按角色组合分配。这套系统之所以能快速落地,正是因为它的权限模型天然支持这种细粒度拆分——core_permission.codename字段的设计,让业务方能用自然语言描述权限(如“订单-联系人-编辑”),技术方直接映射为orders.order:contact_edit。

所以如果你正在规划新项目的权限体系,我的建议是:先和业务方一起画一张“权限-操作-数据”三维矩阵表,再用这套包的技术能力去实现它。不要试图用一套通用权限模型去套所有业务,而是让技术服务于业务共识。这套包的价值,正在于它提供了足够灵活的底层能力,让你能把精力集中在真正的业务问题上,而不是反复造轮子。

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:开箱即用的权限管理工程,后端基于Django和Django REST Framework实现标准RBAC模型,支持用户、角色、权限、菜单四类资源的完整CRUD操作,内置RbacPermissionVerification.py进行细粒度接口级权限校验,采用兼容JWT的TokenAuthentication方案,配合全局异常处理器ExceptionHandler.py统一响应格式。前端使用Vite + TypeScript + Tailwind CSS构建,实现路由级权限控制与菜单数据驱动渲染,所有界面截图(img/目录下)直观展示权限生效效果。项目已配置Dockerfile,可一键容器化部署;配套README详述启动步骤、环境变量(.env)、TS配置(tsconfig.)、Vite构建配置(vite.config.ts)及Tailwind定制规则(tailwind.config.cjs)。代码结构分层清晰,core模块封装基础逻辑,drfRbac模块专注权限策略,manage.py直启服务,适合快速搭建后台权限底座或深入理解RBAC在真实项目中的落地方式。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

Django RBAC权限系统实战:从模型设计到前后端全链路实现 权限管理是Web开发,尤其是后台管理系统的核心基础。RBAC(基于角色的访问控制)作为经典模型,通过将权限赋予角色、再将角色赋予用户,实现了权限分配的抽象与简化,有效解决了硬编码权限带来的维护难题。其技术价值在于提升了系统的安全性与可维护性,是构建中大型应用的基石。在Django框架中,虽然自带基础权限系统,但面对复杂的菜单、按钮及数据行级权限控制时,往往需要深度定制。本文以Django RBAC实战为核心,详细阐述了如何设计用户、角色、菜单一体化的核心模型,并实现包括JWT认证、动态菜单生成、接口鉴权与数 阅读详情

相关推荐

Django+Flask+Vue全栈美食分享系统开发实战与部署复盘

前后端分离是当前Web应用开发的主流架构,它将数据与展示解耦,后端专注业务逻辑与API设计,前端专注交互体验。Django作为Python生态中功能完备的全栈框架,凭借ORM、Admin、DRF等组件,在复杂业务场景下展现出强大的工程化能力;而Flask以轻量灵活著称,适合承担独立的数据采集或辅助服务。借助Vue构建单页应用,可显著提升页面响应速度与用户交互流畅度。该组合广泛应用于内容社区、知识分享、电商后台等系统开发。本文基于一个真实的美食分享系统案例,从技术选型到前后端分离落地,再到Django与Fla

weixin_33178839的博客 87

Vue+Django REST framework 打造生鲜电商项目课程下载树大根深

随着国内互联网使用人数的增加,利用互联网进行网络购物并以银行卡付款的消费方式已渐趋流行,市场份额也在迅速增长,各种类型的电商网站也将层出不穷。它强调代码复用,多个组件可以很方便的以“插件”形式服务于整个框架,Django有许多功能强大的第三方插件,甚至可以很方便的开发出自己的工具包。这使得Django具有很强的可扩展性。微商等社交电商社交电商将成为未来零售的重要通道,社交电商所带来的巨大流量也将转变为电商的销量,人们在获取信息的同时还可以更好的享受购物带来的乐趣,社交电商将成为未来电商发展的新方向。

2301_80151034的博客 378

36--Django-项目实战-全栈开发-基于django+drf+vue+elementUI企业级项目开发流程-前台项目准备

使用axios,elementui,bootstrap,jquery。3.配置全局css,在assets/css/global.css。4.配置全局assets/js/settings.js。配置jquery:vue.config.js。配置bootstrap:main.js。4、bootstrap页面组件框架。安装:前端项目目录下的终端。安装:前端项目目录下的终端。安装:前端项目目录下的终端。安装:前端项目目录下的终端。,在main.js引入。main.js中使用。配置:main.js。

摘 月 1034

【2024最新】基于Python+Mysql+Django+Vue网上商城的设计与实现Lw+PPT

开发技术:SpringBoot、php、Python、小程序、SSM、Vue、MySQL、JSP、ElementUI等,。专栏推荐:SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码精品专栏:Java精选实战项目源码、Python精选实战项目源码、大数据精选实战项目源码。

man2017的博客 2346

基于Django与Vue.js的RBAC权限系统设计与实现

在Web应用开发中,权限管理是保障系统安全与数据隔离的核心模块。其基本原理是通过定义用户、角色与权限之间的映射关系,实现访问控制的逻辑抽象与集中管理。基于角色的访问控制(RBAC)模型因其灵活性与可管理性,成为中后台系统的技术标配,能有效应对组织架构变动与业务角色频繁调整的工程挑战。通过将权限控制逻辑从前端菜单渲染到后端API拦截进行一体化设计,可以构建出贴合现代前后端分离架构的权限管理闭环。本文以Django Rest Framework和Vue.js 3技术栈为例,深入剖析如何自研一个支持动态路由、菜单

weixin_30984001的博客 340

基于Django DRF与Vue 3的全栈企业管理系统实战指南

在当今企业级应用开发领域,前后端分离架构已成为主流范式,其核心在于通过清晰的职责划分提升开发效率与系统可维护性。其基本原理是后端专注于业务逻辑与数据API的提供,前端则负责用户交互与界面呈现,两者通过HTTP协议进行数据通信。这种架构的技术价值在于支持团队并行开发、技术栈灵活选型以及更好的用户体验。其典型应用场景包括各类后台管理系统、数据中台及需要快速迭代的Web应用。本文聚焦于如何运用Django REST Framework构建稳健的RESTful API,并结合Vue 3的Composition AP

weixin_33928137的博客 369

【亲测免费】 Vue-Django 开源项目教程

``` vue-django/ ├── backend/ │ ├── manage.py │ ├── backend/ │ │ ├── __init__.py │ │ ├── settings.py │ │ ├── urls.py │ │ └── wsgi.py ├── frontend/ │ ├── package.json │ ├── publi

gitblog_00834的博客 590

Django与DRF实现RBAC权限系统:从建表到缓存实践

在Web应用和后端服务中,权限管理是保障系统安全的基础能力。基于角色的访问控制(RBAC)通过引入角色层解耦用户与权限,成为企业级应用最常用的权限模型。Python生态中,Django提供强大的ORM和用户认证体系,Django REST Framework(DRF)则通过权限类钩子实现灵活接口鉴权。结合两者可快速构建一套可扩展的RBAC权限系统,覆盖用户建模、权限码规划、视图层判定、数据层隔离以及缓存优化等环节。内容涵盖从建表、自定义权限类到挂载ViewSet的完整工程实践,并提供权限验证与常见故障排查方

weixin_31240035的博客 304

使用Python+Django+drf+vue搭建测试平台---4搭建vue前端项目

使用Python+Django+drf+vue搭建测试平台—4搭建vue前端项目 1.搭建vue前准备工作 2.安装vue 3.安装Visual Studio Code 4.创建vue项目 搭建vue前准备工作 1、下载地址为:https://nodejs.org/en/ 2、检查node是否安装成功 3.安装 cnpm npm install -g cnpm –registry=https://registry.npm.taobao.org 检查node是否安装成功 安装vue 全局安装vue-c

ThinkInTesting的博客 1669

Django+Vue+Docker企业OA系统

视频课程下载——Django+Vue+Docker企业OA系统

Hy-MT2(Python)

「Hy-MT2(Python)」是开源项目(Python)。源码完整,下载解压即可查看使用,适合学习参考、课程设计与二次开发。

Django-Vue-Admin后端架构揭秘:Django+DRF构建RESTful API最佳实践指南

在当今快速发展的Web开发领域,前后端分离架构已成为主流选择。django-vue-admin作为一个基于RBAC模型权限控制的中小型应用开发平台,采用Django + Django REST Framework构建高效稳定的RESTful API后端,为开发者提供了一套完整的企业级解决方案。本文将深入解析这一优秀开源项目的后端架构设计,揭示其如何运用最佳实践构建高质量的API接口。🚀 ##

gitblog_00355的博客 362

SpringBoot+Vue+Mysql图书馆管理系统(源码+文档)

SpringBoot+Vue+Mysql图书馆管理系统 WEB端软件项目(源码+文档)--11 项目包含:①完整源码(后端+管理后台+用户前台);②数据库脚本(db.sql 建库 + 演示数据.sql,导入即有多条真实感演示数据);③论文文档;④开题报告;⑤答辩PPT;⑥任务书与项目部署运行说明文档(含演示账号与导入步骤)。 项目架构: B/S 架构、MVC 分层设计 技术栈: Java、MySQL、SpringBoot、MyBatis-Plus、Token 权限校验、管理后台 Vue+ElementUI、用户前台 HTML+Vue+Layui、后台首页数据看板(SVG 图表)、Maven 构建。 一、角色设计 读者(用户): 注册登录、图书检索与浏览、在线借阅申请、归还信息查看、罚金查询与缴纳、图书评论、图书收藏、留言反馈、个人中心等。 管理员: 图书信息管理、图书分类管理、借阅申请审核、归还登记、罚金管理、读者管理、评论管理、馆内资讯管理、留言板管理、轮播图管理、后台数据统计看板等。 二、前台功能模块(读者端) 首页展示:全屏轮播图、图书信息一览、馆内资讯速递、图书搜索入口 图书浏览:图书分类导航(世界名著 / 人文社科 / 少儿读物等六大分类)、图书列表检索、图书详情页(封面、作者、出版社、可借数量、简介) 借阅模块:在线提交借阅申请、选择借阅天数、管理员审核、借阅记录查询(借阅中 / 已归还)、应还日期提醒 归还模块:归还信息查看、归还日期登记核对 罚金模块:逾期罚金查询、缴纳状态管理(已缴 / 未缴) 图书评论:图书详情页留言评论互动 收藏模块:图书收藏与取消收藏、我的收藏列表 馆内资讯:开馆调整、新书通报、活动公告等资讯列表与详情 留言板:读者建议与咨询留言、管理员回复 个人中心:个人资料修改、修改密码 三、后台管理模块 .....

塔式熔盐光热朗肯循环电站建模!并且搭建了Simulink模型!!

内容概要:本文详细介绍了塔式熔盐光热朗肯循环电站的建模过程,并基于MATLAB/Simulink平台构建了完整的系统仿真模型。该模型深度融合了光热发电的核心组件与朗肯循环的能量转换机制,涵盖太阳能集热、熔盐传热储热、蒸汽发生及汽轮发电等关键环节,能够有效模拟系统的动态响应特性与能量转换效率。研究特别关注系统在变工况下的运行性能,通过仿真手段分析其电能输出稳定性、热力循环效率以及对电网调频的潜在支撑能力,凸显了Simulink在复杂能源系统多物理场耦合仿真中的强大功能与工程应用价值。; 适合人群:具备一定电力系统、热力学基础知识和Simulink软件操作技能的研究生、科研人员及能源领域的工程师。; 使用场景及目标:① 深入学习和掌握塔式熔盐光热电站的工作原理、系统组成及建模方法;② 利用Simulink进行多能耦合的综合能源系统仿真研究,探究系统优化运行策略与动态性能;③ 为从事新能源发电、储能技术及智能电网调度的专业人士提供先进的技术参考和创新灵感。; 阅读建议:读者在学习本文时,应结合具体的系统原理图与Simulink模型进行对照分析,深入理解各模块的数学建模与参数设置。强烈建议动手复现仿真模型并调整关键参数,通过改变光照强度、熔盐流量等输入条件来观察系统输出的变化,以加深对光热电站动态特性的理解,培养解决实际工程问题的实践与创新能力。

五子棋小游戏,可联机,单机

五子棋小游戏,可联机,单机

基于蜣螂优化算法的多无人机安全航迹规划方法研究(Matlab代码实现)

内容概要:本文研究了基于多策略改进蜣螂优化算法(MSDBO)的多无人机协同三维安全航迹规划方法,旨在解决城市空中交通等复杂环境中多无人机集群飞行的路径规划与动态避障问题。通过引入多种优化策略对蜣螂算法进行改进,增强了算法的全局搜索能力与收敛精度,构建了以路径长度、飞行高度、威胁规避和转弯角度等为约束的综合成本最小化目标函数,实现了多无人机在三维空间中的高效协同避障与安全路径生成。该方法有效提升了飞行任务的安全性与执行效率,并通过Matlab仿真验证了其相较于传统算法在路径质量、避障能力和收敛性能方面的优越性。; 适合人群:具备一定智能优化算法基础和Matlab编程能力,从事无人机路径规划、群体智能算法、空中交通管理或相关工程应用研究的科研人员、高校研究生及自动化、航空航天领域的技术人员。; 使用场景及目标:①应用于城市巡检、应急救援、物流配送等多无人机协同任务中的三维路径规划;②为解决复杂动态环境下多机冲突消解、威胁规避与航迹优化问题提供高效算法支持;③推动蜣螂优化算法在多智能体系统与复杂工程优化中的实际落地与拓展应用。; 阅读建议:建议结合文中提供的Matlab代码进行仿真实践,深入理解MSDBO算法的设计机制与航迹规划模型的构建逻辑,可尝试调整环境参数、威胁分布或优化目标权重,观察算法在不同场景下的适应性与鲁棒性,进一步掌握智能优化算法在无人机系统中的应用方法与调参技巧。

双Buck电路并联VDCM下垂控制(Simulink仿真实现)

内容概要:本文介绍了基于Simulink的双Buck电路并联VDCM下垂控制的仿真实现,属于电力电子与新能源系统控制领域的研究内容。通过构建双Buck电路的并联拓扑结构,结合VDCM(电压下降控制法)下垂控制策略,实现多个变换器模块间的均流与稳定输出电压控制。文中详细阐述了控制原理、系统建模方法、关键参数设计及Simulink仿真模型的搭建过程,展示了在负载变化或输入波动条件下系统的动态响应与稳定性表现,验证了所提控制策略在提高系统可靠性与模块化扩展性方面的有效性。; 适合人群:具备电力电子技术、自动控制理论基础,从事新能源变换器、直流微电网、模块化电源系统等方向研究的研究生、科研人员及工程技术人员。; 使用场景及目标:① 掌握双Buck电路的工作原理与并联运行特性;② 学习VDCM下垂控制机制及其在均流控制中的应用;③ 构建并调试多模块并联DC-DC变换器的Simulink仿真模型,用于科研验证或教学演示。; 阅读建议:建议结合Simulink仿真环境动手实践,重点关注控制器参数整定、均流效果评估及系统抗干扰能力分析,同时可延伸至其他并联均流控制策略(如主从控制、平均电流法)的对比研究。

有限长度直导线的三维磁场计算:应用毕奥-萨法尔定律(Matlab代码实现)

内容概要:本文系统阐述了基于毕奥-萨伐尔定律计算有限长度直导线在三维空间中产生磁场的理论方法与实现过程。通过建立直导线磁场的积分数学模型,结合数值离散化处理手段,利用Matlab编程实现磁场矢量的精确计算与三维可视化,详细展示了空间任意点处磁场强度的大小与方向分布特性。文中深入探讨了该方法相较于安培环路定律在处理非闭合、有限长导体时的适用优势,并提供了完整的Matlab代码实现方案,便于读者复现结果、理解物理规律并拓展至更复杂的电磁场建模问题。; 适合人群:具备电磁学基础理论知识和Matlab编程能力的高等院校电气、物理及相关专业的本科生、研究生、科研人员以及从事电磁兼容、电力工程、传感器设计等领域的工程技术开发者。; 使用场景及目标:①作为电磁场与电磁波课程的教学辅助材料,帮助学生直观理解毕奥-萨伐尔定律的物理内涵与实际应用;②为工程实践中直导线电流产生的磁场建模、电磁干扰(EMI)分析、磁传感器布局优化等提供可靠的仿真依据;③作为学习复杂电磁系统数值计算方法的入门级实践案例,为进一步研究奠定基础。; 阅读建议:建议读者结合经典电磁学教材,仔细推导文中的积分公式,透彻理解其物理意义,并亲自运行所提供的Matlab代码,通过调整导线长度、电流强度及观察点位置等参数,观察磁场分布的变化规律,从而深化对三维静磁场特性的直观认知与工程应用理解。

上一篇: 蜂窝网D2D通信MATLAB仿真包:含信道建模、功率响应与统一价格机制
下一篇: Matlab语音去噪实战工程:含可运行代码、实测音频样本与课程设计文档
linux6sysadmin
博客等级 码龄4年 59粉丝 1615原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值