Rails + Inertia + React + Ant Design 的后台管理底座,默认主色为 #0094ff(并支持 blue/orange/red 品牌主题切换)。认证使用 Rails 原生 session + has_secure_password(支持用户名或邮箱登录),授权使用 Pundit,不依赖 Devise,并内置 Action Cable 实时通知能力。
- Rails 8.1、SQLite、Puma
- Inertia Rails、React、TypeScript、Vite
- Ant Design、@ant-design/icons
- Pundit、bcrypt
- 主题配置入口:
app/frontend/theme/antdTheme.ts - 当前支持:
blue(传智蓝)、orange(富卫橙)、red(汇丰红) - 后台右上角可直接切换主题,选择会持久化到浏览器本地存储。
bundle install
yarn install
bin/rails db:migrate
ADMIN_EMAIL=you@example.com ADMIN_USERNAME=admin ADMIN_PASSWORD='your-strong-password' bin/rails admin:bootstrap
bin/dev默认访问地址是 http://localhost:3000,如果环境中已有 PORT 会按该端口启动。Vite dev server 默认在 http://localhost:3036。
当前路由约定:
- 公开入口:
/(预留宣传页/商城/CMS 等业务入口) - 项目说明页:
/docs(README 对应的可视化使用文档) - 后台入口:
/admin - 后台登录:
/admin/login - API 文档入口:
/api/docs(需登录;生产环境默认关闭)
后台初始化逻辑集中在一个脚本里:lib/tasks/admin.rake。
你可以把它当成 Rails 版 manage.py 使用。
bin/rails admin:helpbin/rails admin:init_data会初始化这些内容(幂等):
- 权限:
users/roles/permissions/tags/api_endpoints和所有已注册后台资源的read/create/update/destroy - 角色:
super_admin、admin、readonly - API 配置:
/api/v1/health、/api/v1/users - 标签:
core/public/internal/monitoring
方式 A:环境变量(推荐,适合 CI/部署)
ADMIN_EMAIL=admin@example.com ADMIN_USERNAME=admin ADMIN_PASSWORD='StrongPassword123!' ADMIN_NAME='Admin' bin/rails admin:create_super_admin方式 B:参数(更像命令行参数)
bin/rails "admin:create_super_admin[admin@example.com,StrongPassword123!,Admin,admin]"方式 C:交互式(不传邮箱时会提示输入)
bin/rails admin:create_super_adminADMIN_EMAIL=admin@example.com ADMIN_USERNAME=admin ADMIN_PASSWORD='StrongPassword123!' bin/rails admin:bootstrap等价于先执行 admin:init_data 再执行 admin:create_super_admin。
ADMIN_EMAIL:管理员邮箱ADMIN_USERNAME:管理员用户名(不传时默认取邮箱前缀)ADMIN_PASSWORD:管理员密码ADMIN_NAME:管理员显示名,默认AdminADMIN_NON_INTERACTIVE=true:禁止交互输入,缺字段直接报错退出ADMIN_UPDATE_PASSWORD=true:即使管理员已存在,也强制更新密码
说明:
- 如果管理员已存在且没有设置
ADMIN_UPDATE_PASSWORD,命令会保留原密码,只更新姓名/状态/角色。 - 所有命令都设计为幂等,可重复执行。
项目仍保留 db/seeds.rb,但推荐优先用 admin:* 命令做管理员与基础数据初始化,流程更清晰,也更接近 Django 管理命令的使用方式。
直接追加注册到 app/admin/resources.rb:
bin/rails "admin:register[Product,商品,AppstoreOutlined]"也支持补充路由 path、优先级和是否显示在导航:
bin/rails "admin:register[ApiEndpoint,API,ApiOutlined,api-endpoints,60,true]"等价的环境变量方式(适合脚本化):
ADMIN_RESOURCE_MODEL=Product ADMIN_RESOURCE_LABEL='商品' ADMIN_RESOURCE_ICON='AppstoreOutlined' bin/rails admin:register说明:
- 同一模型重复注册会自动跳过(幂等)。
- 若自定义
path与已有资源冲突,命令会报错并阻止写入。 - 注册成功后会同步创建该资源的
read/create/update/destroy权限,并追加到内置角色。
如果你已经用 Rails scaffold 创建了模型,可以直接用生成器把模型接入后台或 API DSL。
注册后台资源:
bin/rails g admin:resource Product --label=商品 --icon=ShoppingOutlined --path=products --priority=80
bin/rails admin:init_data生成器会写入 app/admin/resources.rb,后台路由、通用 CRUD 页面和左侧导航会自动出现。随后执行 admin:init_data 会补齐该资源的 read/create/update/destroy 权限。
注册 API 资源:
bin/rails g api:resource Product \
--version=v1 \
--path=products \
--name=商品 \
--only index show create update destroy \
--permit name sku price enabled \
--search name sku \
--filter enabled:boolean created_at:date_range \
--sort id created_at \
--default-sort=created_at:desc生成器会写入 app/api/resources.rb。保存后会自动生成 /api/v1/products 相关 REST 路由,并进入 /api/docs 的 OpenAPI/Swagger 文档。
- 后台资源注册文件(类似 Django admin.py):
app/admin/resources.rb - API 资源注册文件:
app/api/resources.rb - 后台通用控制器:
app/controllers/admin_resources_controller.rb - API 控制器:
app/controllers/api/** - React 页面:
app/frontend/pages/** - 后台布局:
app/frontend/components/AdminLayout.tsx - 左侧导航配置:
app/frontend/config/adminNavigation.tsx - 左侧导航组件:
app/frontend/components/AdminSidebar.tsx - Pundit 策略:
app/policies/**
路径约定:
- 后台管理页面统一挂载在
/admin/** - API 目录固定为
app/controllers/api/**(例如/api/v1/...)
左侧导航支持两种方式:
- 自动模式:根据
app/admin/resources.rb的模型注册自动生成 - 可配置组件:
<AdminLayout title="..." navigationItems={customItems}>...</AdminLayout>
项目已内置通用 CRUD 底座:
- 后端通用控制器能力:
app/controllers/concerns/admin_crud_resource.rb - 模型可配置能力:
app/models/concerns/admin_crud_configurable.rb - 前端通用页面:
app/frontend/pages/Crud/Resource.tsx
当前通用页已支持:
- 分页:页码、每页条数、总数、切换每页条数
- 批量操作:可配置批量更新/批量删除,也支持自定义 Admin Action
- 审计日志:记录 create/update/destroy/Admin Action(含批量),支持字段 diff 和历史回滚
- 列表快速编辑:支持
list_editable(类似 Djangolist_editable) - 关联 inline:支持
*_ids多选关联,也支持真正的has_manyinline 子表单 - 关联字段远程搜索:
relation.remote_search: true时 Select 会按输入远程查询选项 - 字段布局:表单支持 sections/tabs、列数、字段 span
- 软删除/回收站:支持
deleted_at软删除、回收站列表、恢复和永久删除 - 导出:按当前搜索/过滤/排序导出 CSV
- 导入:资源开启
import.enabled后可上传 CSV 批量创建记录 - 只读字段:支持
read_only、read_only_on_create、disabled_on_edit - 字段提示:支持
help_text - 预填字段:支持
prepopulate_from(类似 Djangoprepopulated_fields) - 日期区间过滤:
filter_fields支持type: :date_range
你可以在 scaffold 后快速接入,不再手写每个资源的 Index.tsx 列表/弹窗页面。
P1(后台二开必备,已落地):
- 注册即上线:
app/admin/resources.rb注册模型即可生成路由、导航、CRUD 页面。 - Django 风格配置别名:支持
list_display/search_fields/list_filter/ordering/fieldsets/readonly_fields/prepopulated_fields/autocomplete_fields/list_editable/actions。 - 权限矩阵:角色表单可按资源和动作批量配置权限。
- 统一 API 错误响应、JWT、Swagger/OpenAPI、API DSL。
P2(后台效率能力,已部分落地):
- 分页、批量操作、审计日志、历史回滚、列表快速编辑。
- inline 子表单、关联字段远程搜索。
- CSV 导出与 CSV 导入。
- 软删除与回收站。
P3(后续增强方向):
- 更复杂的高级筛选 UI,例如多条件组合、保存视图。
- 更强的数据导入能力,例如更新模式、预校验报告、导入任务队列。
- 更完整的后台对象详情页和对象级自定义按钮。
- 多租户、字段级 UI 权限、操作审批流、后台插件化。
bin/rails g scaffold Product name:string sku:string status:string enabled:boolean stock:integer
bin/rails db:migrate# app/admin/resources.rb
Admin::ResourceRegistry.register(
Product,
label: "商品",
icon: "AppstoreOutlined",
priority: 80
)说明:
- 不需要为每个模型手写 route/controller。
- 注册后会自动生成:
/admin/products列表页- create/update/destroy
- bulk / audit_logs / export 路由
- 左侧导航入口
# app/models/product.rb
class Product < ApplicationRecord
include AdminCrudConfigurable
configure_admin_crud(
page: {
title: "商品管理",
subtitle: "支持搜索、过滤、排序和弹窗编辑。"
},
search: {
fields: %i[name sku],
placeholder: "搜索名称或 SKU"
},
filter_fields: [
{ key: :status, label: "状态", type: :select, options: %w[draft published archived] },
{ key: :enabled, label: "启用状态", type: :select, options: [{ label: "启用", value: true }, { label: "停用", value: false }] }
],
sort_fields: [
{ key: :name, label: "名称" },
{ key: :stock, label: "库存" },
{ key: :created_at, label: "创建时间" }
],
default_sort: { key: :created_at, direction: :desc },
pagination: {
default_per_page: 20,
per_page_options: [10, 20, 50, 100],
max_per_page: 200
},
bulk_actions: [
{ key: :enable, label: "批量启用", kind: :update, changes: { enabled: true } },
{ key: :disable, label: "批量停用", kind: :update, changes: { enabled: false } },
{ key: :destroy, label: "批量删除", kind: :destroy }
],
export: {
enabled: true,
filename: "products.csv",
columns: [
{ key: :id, label: "ID" },
{ key: :name, label: "名称" },
{ key: :status, label: "状态" },
{ key: :created_at, label: "创建时间" }
]
},
audit: {
enabled: true,
limit: 80
},
permit_fields: %i[name sku status enabled stock]
)
end如果你更习惯 Django admin 的配置方式,可以直接写:
class Product < ApplicationRecord
include AdminCrudConfigurable
configure_django_admin(
list_display: %i[name sku status enabled created_at],
list_editable: %i[enabled],
search_fields: %i[name sku],
list_filter: [
:status,
{ key: :created_at, type: :date_range }
],
ordering: [ "-created_at" ],
list_per_page: 20,
readonly_fields: %i[sku],
fields: %i[name sku status enabled description],
fieldsets: [
[ "基础信息", { fields: %i[name sku status enabled], columns: 2 } ],
[ "详情", { fields: %i[description], columns: 1 } ]
],
prepopulated_fields: {
sku: { fields: %i[name], separator: "-" }
},
autocomplete_fields: %i[category_id]
)
end这些别名会被转换为通用 CRUD 的标准配置,不影响原来的 configure_admin_crud 写法。
form: {
fields: [
{ key: :name, label: "角色名", required: true },
{
key: :permission_ids,
label: "关联权限",
type: :inline,
multiple: true,
relation: {
model: "Permission",
label_method: :key,
value_method: :id,
order: :key
}
}
]
},
permit_fields: [:name, { permission_ids: [] }]说明:
type: :inline+multiple: true会渲染为多选关联控件relation.model可写字符串或常量- 如果字段名是
*_ids,系统也会自动按关联字段处理数组参数
实际示例(已内置):
Tag资源:/admin/tagsRole表单中配置了permission_idsinline,可直接多选关联权限
父模型需要打开 Rails 原生 nested attributes:
class ApiEndpoint < ApplicationRecord
has_many :api_endpoint_tags, dependent: :destroy, inverse_of: :api_endpoint
accepts_nested_attributes_for :api_endpoint_tags, allow_destroy: true
end然后在 CRUD 配置中声明 form.inlines:
form: {
fields: [
{ key: :name, label: "名称", required: true }
],
inlines: [
{
association: :api_endpoint_tags,
label: "标签关联",
add_label: "添加标签",
fields: [
{
key: :tag_id,
label: "标签",
type: :select,
required: true,
relation: { model: "Tag", label_method: :name, value_method: :id, order: :name }
}
]
}
]
},
permit_fields: [
:name,
{ api_endpoint_tags_attributes: %i[id tag_id _destroy] }
]说明:
- 这是 Django admin inline formset 对应能力:可以在父表单里新增、修改、删除子记录。
- 页面保存时会自动同步缺失的子记录删除,适合 join model 和简单子表。
- 已内置示例:
ApiEndpoint通过api_endpoint_tagsinline 管理标签关联。
如果关联数据量较大,可以打开远程搜索:
relation: {
model: "Tag",
label_method: :name,
value_method: :id,
order: :name,
remote_search: true,
search_fields: %i[name key description],
limit: 20
}页面会调用当前资源下的 relation-options/:field JSON 接口,按输入关键词返回选项。
admin_actions 会显示在列表批量操作区域,处理器可以是模型类方法:
configure_admin_crud(
admin_actions: [
{
key: :mark_internal,
label: "标记内部接口",
kind: :custom,
handler: :admin_mark_internal!,
permission: :update,
success_message: "已标记为内部接口"
}
]
)
def self.admin_mark_internal!(records:, actor:, context:, action:)
records.each { |record| record.update!(enabled: true) }
{ count: records.size, message: "已处理 #{records.size} 条记录" }
end说明:
permission默认按update?校验,也可以改成read/create/destroy。- 自定义动作会写入审计日志,action 形如
admin_action.mark_internal。
资源显式开启后,页面右上角会显示“导入 CSV”:
configure_admin_crud(
import: {
enabled: true,
max_rows: 1_000,
columns: [
{ key: :name, label: "名称" },
{ key: :sku, label: "SKU" },
{ key: :enabled, label: "启用" }
]
}
)说明:
- 表头可以使用字段名(如
name)或配置的中文label(如名称)。 - 当前导入模式是批量创建记录,会走模型校验并写入审计日志。
- 只导入普通标量字段,不导入 inline、多选关联、权限矩阵等复杂字段。
- 已内置示例:
Tag支持导入名称/标识/颜色/描述。
form: {
fields: [
{ key: :name, label: "名称", required: true, help_text: "建议使用业务可读名称" },
{
key: :slug,
label: "Slug",
prepopulate_from: [:name],
prepopulate_separator: "-",
help_text: "会根据名称自动预填,可手动修改"
},
{
key: :key,
label: "标识",
disabled_on_edit: true,
help_text: "创建后保持稳定,避免权限代码失效"
},
{
key: :created_at,
label: "创建时间",
read_only: true
}
]
}以及列表过滤:
filter_fields: [
{ key: :status, label: "状态", type: :select, options: %w[draft published] },
{ key: :created_at, label: "创建时间", type: :date_range }
]table: {
list_editable: [:name, :enabled],
columns: [
{ key: :name, label: "名称", editable: true },
{ key: :status, label: "状态" },
{ key: :enabled, label: "启用", editable: true }
]
}说明:
list_editable或columns[*].editable: true都可以开启列表内快速编辑- 当前支持字段类型:
text、number、boolean、select - 标记为只读(
read_only/disabled_on_edit)的字段会自动排除
表单布局写在 form.layout,字段仍然写在 form.fields,避免布局和字段定义混在一起:
form: {
layout: {
type: :tabs, # 也可以是 :sections
sections: [
{
key: :base,
title: "基础信息",
columns: 2,
fields: %i[name sku status enabled]
},
{
key: :content,
title: "详情",
columns: 1,
fields: %i[description]
}
]
},
fields: [
{ key: :name, label: "名称", required: true },
{ key: :description, label: "详情", type: :textarea, span: 1 }
]
}说明:
type: :sections会按分组纵向展示;type: :tabs会用 Ant Design Tabs。columns支持 1-4 列。- 字段可配置
span,用于跨列。 - 未写入 layout 的字段或 inline 会自动追加到最后一个分组。
模型需要有 deleted_at 字段,并在 CRUD 配置中开启:
configure_admin_crud(
soft_delete: { enabled: true }
)开启后:
- 列表默认只显示
deleted_at IS NULL的记录。 - 删除会写入
deleted_at,记录进入回收站。 - 回收站支持恢复、永久删除、批量恢复、批量永久删除。
- 审计日志仍记录
destroy/restore/purge,软删除会带上softDelete: true元数据。
已内置示例:
TagApiEndpoint
你也可以在注册时追加/覆盖模型配置:
Admin::ResourceRegistry.register(
Product,
label: "商品后台",
crud: {
page: { title: "商品后台" },
sort_fields: [{ key: :updated_at, label: "更新时间" }]
}
)- 默认通用页面使用后台
AdminLayout。 - 你可以通过
admin_crud_for Product, component: "Your/CustomCrudPage"切换到自定义页面组件(用于不同 layout 或交互风格)。 - 如果只需要标题变化,直接改
page.title即可,页面头部会自动同步。
已切换为“注册即上线”模式的资源,可直接参考:
app/admin/resources.rbapp/controllers/admin_resources_controller.rbapp/models/api_endpoint.rbapp/models/role.rbapp/models/permission.rbapp/models/tag.rb
- 登录入口字段为
login,可输入「用户名」或「邮箱」。 - 登录地址为
/admin/login。 users表新增username(唯一索引)。- 引入
auth_identities表用于多登录方式扩展:provider:password/github/google/wechat/phoneuid:第三方平台唯一标识meta:平台扩展信息(JSON)
- 当前默认会为本地账号创建
provider=password的身份记录,后续接 GitHub/Google/微信/手机号时只需新增 OAuth/验证码流程并写入auth_identities,不需要重构主用户表。
- 页面内操作反馈:
- 仍使用 Inertia flash(
notice/alert)+ Ant Designmessage。
- 仍使用 Inertia flash(
- 实时通知:
- 使用 Rails Action Cable(
/cable)+NotificationsChannel。 - 服务端通过
UserNotifications::Publisher.deliver!创建并广播通知。 - 前端在后台布局中订阅通知,使用 Ant Design
notification弹出提示,并在右上角通知面板显示未读数量与历史消息。
- 使用 Rails Action Cable(
- 已提供接口:
PATCH /admin/notifications/mark-all-read:标记当前用户通知为已读。
权限模型对标 Django admin,但不引入 Group,直接使用 Role 承担分组职责:
User可拥有多个RoleRole可拥有多个PermissionPermission使用resource.action格式- Pundit 根据当前用户角色汇总出来的权限判断是否允许访问
权限键示例:
users.readusers.createroles.updatetags.readapi_endpoints.destroy
内置角色:
super_admin:拥有全部权限,策略层直接放行admin:默认拥有所有已初始化资源权限readonly:默认只拥有read权限
Pundit 默认会把模型类名转为复数资源名,例如 ApiEndpoint 对应 api_endpoints。admin:init_data 和 admin:register 都会自动为注册资源补齐标准权限。
所有 API controller 仍放在 app/controllers/api 下。通用资源 API 可通过 DSL 注册,不需要为每个模型手写 controller。
资源注册文件:app/api/resources.rb
Api::ResourceRegistry.register(
User,
version: :v1,
path: "users",
name: "用户",
description: "用户信息查询接口(支持搜索、过滤、排序、分页)",
only: %i[index show],
auth: :jwt,
permit: %i[name username email active],
scope: -> { User.active },
search_fields: %i[name username email],
filter_fields: [
{ key: :active, type: :boolean },
{ key: :created_at, type: :date_range }
],
sort_fields: %i[id name username created_at],
default_sort: { key: :name, direction: :asc },
pagination: { default_per_page: 20, max_per_page: 100 },
field_permissions: {
email: { read: "users.read_email", write: "users.update_email" },
active: { write: "users.update_active" }
},
audit: true,
serializer: {
only: %i[id name username email active created_at]
}
)注册后会自动生成:
GET /api/v1/usersGET /api/v1/users/:id
支持项:
version:API 版本,例如:v1path:路由路径only:开放动作,支持index/show/create/update/destroyauth:默认:jwt,也可设为false或:nonepolicy:默认开启 Pundit 授权permit:create/update 允许字段scope:列表查询范围search_fields:关键词搜索字段(q)filter_fields:过滤字段(filters),支持boolean/select/date_range/textsort_fields/default_sort:排序字段与默认排序(sort_by/sort_direction)pagination:分页参数(page/per_page)默认值与上限field_permissions:字段级读写权限,读权限不足会隐藏字段,写权限不足会忽略该字段audit:默认开启,记录 create/update/destroy 的 API 操作审计serializer:响应字段配置,也可以传 Proc 自定义序列化
列表接口会统一返回:
{
"data": [],
"meta": {
"pagination": {
"page": 1,
"perPage": 20,
"total": 100,
"totalPages": 5,
"hasPrev": false,
"hasNext": true
},
"query": {
"q": "",
"filters": {},
"sortBy": "name",
"sortDirection": "asc"
}
}
}GET /api/docs/openapi.json:OpenAPI 3.1 JSONGET /api/docs:Swagger UI 页面
文档会自动读取 app/api/resources.rb 中的注册项并生成,不需要额外手工维护。
访问控制说明:
- 需要先登录后台会话(
/admin/login)才能访问api/docs与api/docs/openapi.json - 生产环境默认隐藏(返回 404)
- 生产环境如需开放,设置
ENABLE_API_DOCS=true
Swagger 页面顶部会为当前登录用户自动生成可复制 JWT,并自动注入 bearerAuth,方便直接调试接口。
字段权限会通过 OpenAPI 扩展字段 x-fieldPermissions 暴露,方便前端或外部调用方识别。
登录换 token:
curl -X POST http://localhost:3000/api/v1/auth/login \
-d "login=admin" \
-d "password=StrongPassword123!"响应会包含:
{
"token": "...",
"tokenType": "Bearer",
"expiresAt": "2026-05-21T10:00:00+08:00",
"refreshToken": "...",
"refreshExpiresAt": "2026-06-20T10:00:00+08:00",
"user": {}
}后续请求:
curl http://localhost:3000/api/v1/users \
-H "Authorization: Bearer <token>"当前登录用户:
GET /api/v1/auth/me刷新 access token(refresh token 轮换):
curl -X POST http://localhost:3000/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{"refreshToken":"<refresh-token>"}'登出并吊销 refresh token:
curl -X DELETE http://localhost:3000/api/v1/auth/logout \
-H "Content-Type: application/json" \
-d '{"refreshToken":"<refresh-token>"}'JWT 使用 HS256,默认密钥来自 Rails.application.secret_key_base,生产环境也可以设置 JWT_SECRET。
refresh token 默认有效期 30 天,服务端只存储 token 摘要。每次刷新会轮换 token;如果旧 token 被再次使用,系统会判定为复用风险并吊销同一 token family 下的所有 refresh token。
通过 API DSL 生成的资源接口默认会记录 create/update/destroy:
action:api.create、api.update、api.destroyactor:当前 JWT 用户;使用服务 token 时为空audited_changes:创建/删除快照,或更新 diffmetadata.requestPayload:通过 DSLpermit和字段写权限过滤后的请求体白名单快照metadata.source/apiVersion/apiResource/requestId/ip/userAgent:调用来源与追踪信息
API 错误响应统一格式:
{
"error": "validation_failed",
"code": "validation_failed",
"message": "请求参数校验失败",
"requestId": "a1b2c3d4-...",
"timestamp": "2026-05-20T10:00:00+08:00",
"details": {
"messages": ["Name can't be blank"]
}
}默认限流策略:
POST /api/v1/auth/login:10 次/分钟/IPPOST /api/v1/auth/refresh:20 次/分钟/IP- 其他
/api/**:300 次/分钟/IP
响应头会返回:X-RateLimit-Limit、X-RateLimit-Remaining、X-RateLimit-Reset,触发限流时会返回 429 与 Retry-After。
需要完全自定义行为时,继续放在 app/controllers/api/**。当前已有:
GET /api/v1/health:自定义 controllerGET /api/v1/users:由app/api/resources.rbDSL 注册生成
兼容项:如果设置了 ADMIN_API_KEY,也可以用 Authorization: Bearer <ADMIN_API_KEY> 调用 DSL 资源接口;新功能优先推荐 JWT。
yarn run check
bin/rails zeitwerk:check
bin/rails test
bin/vite build