文档目录
glen
· 预计阅读 3 分钟

第 8 章 新模块接入权限与排查手册

本文导航
第 8 章 新模块接入权限与排查手册

第 8 章 新模块接入权限与排查手册

这一章把前面的知识落到「给 owl-admin 新增一个业务模块」上:需要写哪些代码、权限从哪来、怎么验证、出问题怎么查。

接入步骤

假设新增一个「公告」模块,路由前缀 /notices。

1. Handler 声明模块名

type NoticeHandle struct {
	svc *service.NoticeService
}

func (i *NoticeHandle) ModuleName() (string, string) {
	return "notice", "公告管理"
}

notice 会进入权限标识:admin:notice:Create。

2. 注册路由

在 app/route/api.go 的 InitApi 里:

{
	r := router.NewRouteInfoBuilder(appName, noticeHandle, gv1, router.MenuOption{
		ComponentName: "SystemNotice",
		Path:          "/system/notice/index",
		Icon:          "ep:bell",
	})

	r.Post("/notices", router.AccessAuthorized, noticeHandle.Create).Name("创建公告").Build()
	r.Get("/notices", router.AccessAuthorized, noticeHandle.Retrieve).Name("公告列表").Build()
	r.Put("/notices/:id", router.AccessAuthorized, noticeHandle.Update).Name("更新公告").Build()
	r.Delete("/notices/:id", router.AccessAuthorized, noticeHandle.Delete).Name("删除公告").Build()

	noticeMenu = r.GetMenu()
}

需要登录但不需要授权的接口用 AccessAuthenticated;公开接口用 AccessPublic。

3. 挂到菜单树

{
	Path: "/system",
	Name: "System",
	// ...
	Children: []*router.Menu{
		// ...
		noticeMenu,
	},
}

noticeMenu 的 Name 是 SystemNotice,前端页面组件必须同名:

<script setup lang="ts">
defineOptions({ name: "SystemNotice" });
</script>

否则多标签和 keep-alive 会异常。

4. 注册 Binds

func (i *SubAppAdmin) Binds() []any {
	return []any{
		// ...
		v1.NewNoticeHandle,
		service.NewNoticeService,
		repository.NewNoticeRepository,
	}
}

5. 迁移模型

把 &model.Notice{} 加进 database.Models(),确保 RegisterMigrate() 返回它。

6. 给角色授权

重启服务后:

  1. GET /menus/assignable 能看到「公告管理」页面和四个按钮;
  2. PUT /roles/:id/menus 勾选页面与按钮;
  3. 普通用户重新登录,验证:
    • 菜单出现;
    • me/permissions 包含 admin:notice:*;
    • 有权限的接口 200,没分配的接口 403。

权限标识一览

路由 权限标识
POST /notices admin:notice:Create
GET /notices admin:notice:Retrieve
PUT /notices/:id admin:notice:Update
DELETE /notices/:id admin:notice:Delete

如果某个按钮页面还需要调用公告详情,用 Deps 把详情权限带上:

r.Put("/notices/:id", router.AccessAuthorized, noticeHandle.Update).
	Deps(router.Dep{Handler: noticeHandle, Method: noticeHandle.Detail}).
	Name("更新公告").
	Build()

排查表

401 未授权

检查 说明
Authorization 头 必须是 Bearer <token>
令牌是否过期 conf/jwt.yaml 的 expire
signing-key 多实例必须一致
路由级别 AccessPublic 不会解析令牌,不会返回 401

403 未授权

检查 说明
路由级别 是否误设为 AccessSuperAdmin
权限标识 me/permissions 是否包含该接口
角色菜单 admin_role_menu 是否有对应按钮
Casbin p 行 casbin_rule 是否有 p, roleID, permission
Casbin g 行 是否有 g, userID, roleID
用户是否超管 超管应直接放行,若超管也 403,检查 JWT 里的 isSuperAdmin

菜单不显示

检查 说明
RegisterMenus() 是否返回了该菜单,且不是 nil
根菜单 SubApp 是否被 ?app= 过滤
角色 是否分配了页面菜单
Meta.ShowLink 是否为 true
前端组件 name 是否与菜单 Name 一致

按钮不显示

检查 说明
权限标识 前端写的字符串是否与后端完全一致
权限缓存 localStorage 的 user-permissions-cache 是否过期
数组语义 hasPerms([a,b]) 要求全部满足
超管通配 permissions 是否为 *:*:*

接口 404

检查 说明
RegisterRouters() 是否被调用(ConsoleShell 不会注册路由)
InitApi 是否真的执行
路由表 是否通过 Builder 注册
路径 是否少了 /api/v1 前缀或多了斜杠

权限改了不生效

检查 说明
是否走了接口 直接改表不会同步 Casbin
事件监听 listener.Init 是否执行
后端 每次请求 LoadPolicy,应即时生效
前端 权限缓存 24 小时,需要刷新或重新登录

上线前自检

  • [ ] 新模块的 Handler 实现了 ModuleName();
  • [ ] 所有路由通过 RouteInfoBuilder 注册,级别正确;
  • [ ] 需要多个接口的按钮用了 Deps;
  • [ ] 页面已加入 InitMenu,根菜单有 SubApp;
  • [ ] 前端组件 name 与菜单 Name 一致;
  • [ ] 按钮使用 hasPerms / Perms,标识与后端一致;
  • [ ] 用普通用户完成一次「有权限 200 / 无权限 403」验证;
  • [ ] 用超管验证没有误拦截;
  • [ ] 不需要授权的公开接口没有泄露敏感数据。

写在最后

权限系统的复杂度集中在「三处一致性」:

  1. 路由的权限标识与角色菜单里的按钮一致;
  2. Casbin 的 p / g 行与数据库关联一致;
  3. 前端 hasPerms 的字符串与后端权限标识一致。

任何一处对不上,表现都是「菜单/按钮/接口有一个不对」。按这三条逐一核对,绝大多数问题都能定位。

评论

加载评论中…

上一篇 第 7 章 前端权限控制:按钮该不该显示
暂无下一篇

BitLabs © 2026 · 论坛讨论 · 博客长文 · 文档手册