从零搭建通用管理后台:ShiyuAdmin 的 Go、React 与 RBAC 实践

从零搭建通用管理后台:ShiyuAdmin 的 Go、React 与 RBAC 实践

作者:王仕宇(JavaPub)

做后台系统时,很多需求总是在重复出现:用户登录、角色分配、菜单管理、接口鉴权、部门数据隔离、操作日志,以及部署上线。

如果每个项目都从头写一遍,业务还没开始,基础设施已经消耗了不少时间。

因此我开源了 ShiyuAdmin:一个基于 Go 和 React 的通用后台管理系统。它既可以作为新业务的基础脚手架,也适合用来学习一个管理后台的认证、权限与部署链路。

项目地址:https://github.com/Rodert/ShiyuAdmin

项目采用 Apache-2.0 协议。本文基于仓库 main 分支提交 6fe8886 的源码整理,重点分析具体实现,而不是简单罗列功能。项目仍在演进,实际使用请以最新代码为准。

一、项目整体架构

ShiyuAdmin 采用前后端分离的开发方式:

  • 后端使用 Go、Gin、GORM、Viper 和 JWT。
  • 前端使用 React、Umi Max、Ant Design Pro、TypeScript 和 ECharts。
  • 数据层支持 PostgreSQL、MySQL、SQLite 和 SQL Server 驱动。
  • Redis 用于项目中的缓存等能力。
  • 默认使用 Docker Compose 部署。

这里需要区分开发架构 和部署形态:代码是前后端分离的,但默认应用镜像并不是前端、后端各一个容器。项目采用多阶段构建,将前端静态文件和 Go 程序放入同一个应用镜像,通过 Nginx 和 Supervisor 管理。

默认 Compose 包含三个服务:应用、PostgreSQL 和 Redis。应用对外映射 18000 端口。

text 复制代码
浏览器
  │
  ▼
Nginx:静态资源与 API 入口
  │
  ▼
Gin HTTP 路由 → 中间件 → API Handler → Service → Repository
                                           │
                                           ├── 数据库
                                           └── Redis(可选初始化)

这套架构的目标不是追求复杂,而是让新增业务时能沿着清晰的分层继续扩展。

二、目录结构:接口、业务和数据访问分开

text 复制代码
ShiyuAdmin/
├── backend/shiyu-admin-backend/    # Go 后端
│   ├── cmd/server/                 # 进程入口
│   ├── internal/api/v1/system/     # HTTP 接口处理
│   ├── internal/middleware/        # 认证、权限等中间件
│   ├── internal/service/           # 业务逻辑
│   ├── internal/repository/        # 数据访问与接口定义
│   ├── internal/model/             # DTO、实体、VO
│   └── pkg/                        # 数据库、JWT、Redis、响应等公共能力
├── frontend/shiyu-admin-web/       # React 管理后台
├── site/                           # 独立官网与文档站
├── docs/                           # 部署和设计文档
├── Dockerfile
└── docker-compose.yml

一个典型请求会先进入 Gin 路由,经过认证和权限中间件,再由 Handler 解析参数、调用 Service,最终通过 Repository 访问数据库。

这样的分层有两个直接好处:一是 HTTP 协议处理和业务逻辑不必混在一起;二是业务代码可以围绕接口组织,替换实现或编写测试时更容易定位依赖。

后端入口为 cmd/server/main.go,具体装配在 internal/server/server.go。启动时会连接数据库、执行 GORM AutoMigrate、处理表结构注释、初始化管理员和 RBAC 基础数据,再初始化可选 Redis,最后装配服务并注册 Gin 路由。

需要注意,自动建表适合快速启动,但它不等于完整的数据库版本化迁移体系。正式环境中,涉及字段变更、历史数据迁移和回滚时,仍应建立独立的迁移与备份流程。

三、登录认证:从密码校验到 JWT

登录接口为:

text 复制代码
POST /api/v1/system/auth/login

认证逻辑位于 internal/service/auth/service.go,主要流程如下:

  1. 根据用户名查询用户。
  2. 使用 bcrypt 对比密码哈希。
  3. 检查用户状态。
  4. 签发 JWT,返回 Token、类型和过期时间。

JWT 的生成逻辑位于 pkg/jwtutil/token.go。当前签发使用 HS256,载荷包含用户编码、用户名、超级管理员标记及签发、过期等注册声明。

请求进入服务后,internal/middleware/auth.go 从 Authorization: Bearer <token> 读取并校验令牌,执行会话撤销检查,再将用户信息放入 Gin Context,供后续接口使用。

前端的 src/requestErrorConfig.ts 会从 localStorage 读取 shiyu_token,在请求中附加 Authorization 请求头。

这条链路解决了"当前请求是谁"的问题。至于"这个人能做什么",还要交给 RBAC 和接口权限判断。

安全边界:Token 放在 localStorage 需要关注 XSS 风险;生产环境还应检查 JWT 密钥、过期时间、算法与发行方校验、HTTPS 和会话撤销策略。不要把示例配置直接当作完整的生产安全方案。

四、RBAC 权限:用户、角色、菜单与接口串起来

管理后台中的权限并不只是"有没有菜单"。至少要回答三个问题:

  • 用户是谁?
  • 用户属于哪些角色?
  • 角色能访问哪些菜单、按钮和接口?

ShiyuAdmin 的 RBAC 是项目内实现,并非依赖 Casbin。核心实体定义在 internal/model/entity/user.go,主要表包括:

  • sys_users:用户。
  • sys_roles:角色。
  • sys_menus:目录、菜单和按钮。
  • sys_depts:部门。
  • sys_user_roles:用户与角色的关联。
  • sys_role_menus:角色与菜单的关联。
  • sys_role_depts:角色与部门的关联。

菜单类型使用 M、C、F 分别表示目录、菜单和按钮。菜单上的 Perms 字段保存权限字符串,例如 system:user:list。

权限服务 internal/service/permission/service.go 根据用户关联的角色和角色菜单,收集启用状态下的权限标识、去重,再进行精确匹配。

接口注册时通过中间件明确声明所需权限。例如用户列表路由:

go 复制代码
rg.GET("/users",
    middleware.RequirePermission(permissionSvc, "system:user:list"),
    func(c *gin.Context) {
        listUsers(c, userSvc, dataScopeSvc)
    })

这个设计让权限标识与接口形成明确关联:即使有人绕过前端页面、直接请求接口,后端仍然会检查权限。没有有效身份时返回 401,权限不足时返回 403;超级管理员在中间件中有直接放行逻辑。

菜单权限和接口权限为什么都要有?

前端隐藏菜单主要解决体验问题:没有权限的用户不应该看到不能操作的入口。

但前端代码运行在用户浏览器中,不能作为最终安全边界。因此接口必须在服务端再次鉴权。实际项目中,新增功能时需要同时考虑菜单、按钮和接口权限,不能只添加一个页面。

动态菜单不等于动态加载任意页面

前端 src/app.tsx 会获取菜单树,并过滤禁用项和按钮类型,构造布局菜单;src/access.ts 根据权限和超级管理员状态控制路由访问。

但 config/routes.ts 仍然静态定义页面路径和组件。更准确的描述是:后端权限驱动菜单展示,配合前端路由访问控制和后端接口鉴权。这并不意味着数据库随便新增一个组件路径就能自动加载任意页面。

五、数据权限:不仅限制能做什么,还要限制能看哪些数据

接口权限解决"能否查看用户列表",数据权限则进一步解决"能看到哪些用户"。

项目的 internal/service/data_scope/service.go 包含以下范围:

  • all:全部数据。
  • dept:本部门。
  • deptAndChild:本部门及子部门。
  • custom:角色关联的指定部门。
  • self:仅本人,亦作为默认范围。

超级管理员可以查看全部数据;用户拥有多个角色时,部门范围会合并。

在已核对的用户列表链路中,服务先解析用户数据范围,再调用带范围条件的查询。这个思路适合进一步扩展到订单、工单或客户等业务:先获得可信的服务端数据范围,再把范围条件注入查询。

但数据权限不能只看列表:详情、导出、更新和删除也需要逐个检查。当前源码不能据此推断所有业务接口都已经覆盖对象级数据权限;二次开发时要对每条读写链路补齐校验和测试。

六、多数据库:驱动可用,不代表所有 SQL 自动兼容

pkg/database/database.go 中包含 PostgreSQL、MySQL、SQLite 和 SQL Server 的连接适配。仓库也分别提供 Compose 配置:

bash 复制代码
# PostgreSQL
 docker compose -f docker-compose.yml up -d

# MySQL
 docker compose -f docker-compose.mysql.yml up -d

# SQL Server(默认 Developer 版,仅供开发测试)
 docker compose -f docker-compose.sqlserver.yml up -d

# SQLite
 docker compose -f docker-compose.sqlite.yml up -d

实际切换前应先阅读对应配置,并停止当前模式,避免共用 shiyu-app 容器产生冲突。SQL Server 生产使用需要自行连接已授权实例并调整账号、密码和 TLS。

GORM 降低了基础数据库操作的差异,但业务中的原生 SQL、字段类型、分页和大小写匹配仍可能存在方言差异。例如当前文件模块的关键词查询使用了 PostgreSQL 风格的 ILIKE。因此,支持连接四种数据库,不等于每个业务模块都已经完成四种数据库的兼容性验证。

如果准备在新数据库上线,建议针对建表、初始化、分页、搜索、事务和备份恢复建立一套集成测试。仓库主题标签中出现数据库名称,也不能替代实际驱动和测试证据。

七、统一响应、日志和运维能力

后端通过 pkg/response/response.go 统一组织响应,成功响应格式示例:

json 复制代码
{
  "code": 200,
  "data": {},
  "message": "操作成功"
}

错误响应还包含稳定的 error_code,并使用对应的 HTTP 状态码,而不是所有情况都返回 HTTP 200。前端因此可以分别处理认证失败、权限不足和业务错误。

项目还提供用户、角色、菜单、部门管理,以及系统监控、数据管理、Redis 缓存管理和操作日志。README 描述操作日志覆盖新增、修改、删除等写操作以及登录成功与失败记录。

需要特别注意:数据管理接口当前使用超级管理员限制,不能仅根据前端菜单权限判断普通角色一定能访问。暴露表结构和基础数据属于敏感管理能力,部署时也应控制访问范围。

八、Docker Compose 快速体验

如果本地已经安装 Docker 和 Docker Compose,可以按仓库 README 从源码构建:

bash 复制代码
git clone https://github.com/Rodert/ShiyuAdmin.git
cd ShiyuAdmin

docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --build

启动后访问:

  • 后台页面:http://localhost:18000
  • API 基础路径:http://localhost:18000/api/v1
  • 健康检查:http://localhost:18000/api/v1/system/health

项目 README 提供了本地体验账号:管理员 admin / Admin@123,普通用户 user / User@123。这些是公开的演示凭据,只适合本地体验,生产环境必须修改。

默认 Compose 使用 PostgreSQL 15 和 Redis 7,并配置数据持久化。默认配置中的数据库和 Redis 端口也映射到宿主机,实际部署应按网络环境限制访问,不要默认暴露到公网。

生产配置默认拉取 GHCR 应用镜像。上线时建议固定经过验证的镜像标签或摘要,而不是依赖可变化的 latest,并单独管理数据库密码、JWT 密钥、备份和升级回滚。

本地开发也可以分别启动前后端,但后端配置默认依赖 PostgreSQL 和 Redis,先准备好对应服务并核对连接配置:

bash 复制代码
# 后端
cd backend/shiyu-admin-backend
go run ./cmd/server
bash 复制代码
# 前端:在另一个终端执行
cd frontend/shiyu-admin-web
npm install
npm run start:dev

前端依赖应安装在前端目录,不需要在仓库根目录执行前端安装命令。项目官网是独立的 Docusaurus 站点;当前仓库说明未提供可登录的在线后台演示。

九、二次开发前,先了解这些边界

读源码时,我认为有几个地方值得明确说明:

  1. 权限缓存尚未接入当前装配链路。 仓库中存在 Redis 权限缓存相关实现,但当前服务装配使用的是普通权限服务,不能据此宣传角色变更后缓存自动失效。
  2. 对象存储不是完整实现。 配置中有 S3、OSS 等驱动入口,但当前非本地上传返回 501,不能理解成已经完成对象存储上传下载。
  3. 数据权限需要逐接口审查。 已看到列表范围控制,不代表所有详情、修改、删除与导出都具备相同保护。
  4. 数据库适配需要业务验证。 四种连接驱动与四种 Compose 配置,不代表每段业务 SQL 在所有数据库均通过测试。
  5. 默认账号和配置只适合体验。 上线前必须处理密码、密钥、网络访问、日志脱敏、备份和升级策略。

这些边界并不妨碍它作为脚手架使用,但能帮助使用者在接入业务之前做出更准确的技术判断。

十、适合哪些场景?

如果你正在做内部管理系统、运营平台、工具后台,或者想学习 Go 项目如何组织认证和权限,ShiyuAdmin 可以作为一个阅读和二次开发的起点。

建议的阅读顺序是:

  1. 从 internal/server/server.go 看服务如何装配。
  2. 沿登录接口阅读认证服务、JWT 和认证中间件。
  3. 从用户列表路由出发,跟踪接口权限和数据范围。
  4. 阅读前端菜单、路由和请求拦截的衔接。
  5. 最后结合 Dockerfile 和 Compose 理解构建与部署。

项目还在持续完善,欢迎提交 Issue、PR,分享使用中遇到的问题,也欢迎一起补充数据库兼容性、权限测试和部署文档。

GitHub:https://github.com/Rodert/ShiyuAdmin

项目主页:https://rodert.github.io/ShiyuAdmin/


本文基于源码静态阅读整理,未在本次写作中启动服务或运行完整测试。实际运行结果、配置和功能以所使用版本为准。

相关推荐
海的透彻2 小时前
dockerfile如何快速构建容器(java服务)
java·开发语言
code2cat2 小时前
Java工具篇之Gradle:用任务图组织构建与交付
java·开发语言·单元测试·gradle
北京中科新远科技2 小时前
高速交换机端口预算怎么做?下行、上联、拆分和冗余计算框架
开发语言·php
xiaoye-duck3 小时前
《C++ 项目:手写大模型接入 SDK》DeepSeek接口设计(下):流式响应实现和测试
开发语言·网络·c++·语言模型
AI源码3 小时前
基于 Python 的短视频用户行为分析系统
开发语言·python
旖旎夜光4 小时前
【C++ 基础入门】类和对象(上):封装、对象模型与 this 指针
开发语言·c++·笔记·学习
揽星河..4 小时前
【C语言】结构体
c语言·开发语言
2601_966949654 小时前
如何封装 qd.klines.batch(),让批量 K 线查询函数在多个策略中复用?
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
神仙别闹6 小时前
基于Python实现英词语自动对齐系统
开发语言·python