Skip to content

neekin/zen_admin_base

Repository files navigation

Zen Admin Base

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(需登录;生产环境默认关闭)

管理命令(类似 Django)

后台初始化逻辑集中在一个脚本里:lib/tasks/admin.rake
你可以把它当成 Rails 版 manage.py 使用。

1) 查看帮助

bin/rails admin:help

2) 初始化基础数据(不创建管理员)

bin/rails admin:init_data

会初始化这些内容(幂等):

  • 权限:users/roles/permissions/tags/api_endpoints 和所有已注册后台资源的 read/create/update/destroy
  • 角色:super_adminadminreadonly
  • API 配置:/api/v1/health/api/v1/users
  • 标签:core/public/internal/monitoring

3) 创建或更新超级管理员

方式 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_admin

4) 一键初始化 + 创建管理员(首次部署推荐)

ADMIN_EMAIL=admin@example.com ADMIN_USERNAME=admin ADMIN_PASSWORD='StrongPassword123!' bin/rails admin:bootstrap

等价于先执行 admin:init_data 再执行 admin:create_super_admin

5) 常用环境变量说明

  • ADMIN_EMAIL:管理员邮箱
  • ADMIN_USERNAME:管理员用户名(不传时默认取邮箱前缀)
  • ADMIN_PASSWORD:管理员密码
  • ADMIN_NAME:管理员显示名,默认 Admin
  • ADMIN_NON_INTERACTIVE=true:禁止交互输入,缺字段直接报错退出
  • ADMIN_UPDATE_PASSWORD=true:即使管理员已存在,也强制更新密码

说明:

  • 如果管理员已存在且没有设置 ADMIN_UPDATE_PASSWORD,命令会保留原密码,只更新姓名/状态/角色。
  • 所有命令都设计为幂等,可重复执行。

6) 与 db:seed 的关系

项目仍保留 db/seeds.rb,但推荐优先用 admin:* 命令做管理员与基础数据初始化,流程更清晰,也更接近 Django 管理命令的使用方式。

7) 一条命令注册后台资源(类似 Django admin.py 注册)

直接追加注册到 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 权限,并追加到内置角色。

8) Rails 生成器(推荐用于二次开发)

如果你已经用 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 模板(Django Admin 风格)

项目已内置通用 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(类似 Django list_editable
  • 关联 inline:支持 *_ids 多选关联,也支持真正的 has_many inline 子表单
  • 关联字段远程搜索:relation.remote_search: true 时 Select 会按输入远程查询选项
  • 字段布局:表单支持 sections/tabs、列数、字段 span
  • 软删除/回收站:支持 deleted_at 软删除、回收站列表、恢复和永久删除
  • 导出:按当前搜索/过滤/排序导出 CSV
  • 导入:资源开启 import.enabled 后可上传 CSV 批量创建记录
  • 只读字段:支持 read_onlyread_only_on_createdisabled_on_edit
  • 字段提示:支持 help_text
  • 预填字段:支持 prepopulate_from(类似 Django prepopulated_fields
  • 日期区间过滤:filter_fields 支持 type: :date_range

你可以在 scaffold 后快速接入,不再手写每个资源的 Index.tsx 列表/弹窗页面。

P1/P2/P3 对标计划

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 权限、操作审批流、后台插件化。

1) scaffold 生成资源

bin/rails g scaffold Product name:string sku:string status:string enabled:boolean stock:integer
bin/rails db:migrate

2) 类 Django 的注册方式(推荐)

# app/admin/resources.rb
Admin::ResourceRegistry.register(
  Product,
  label: "商品",
  icon: "AppstoreOutlined",
  priority: 80
)

说明:

  • 不需要为每个模型手写 route/controller。
  • 注册后会自动生成:
    • /admin/products 列表页
    • create/update/destroy
    • bulk / audit_logs / export 路由
    • 左侧导航入口

3) model 配置搜索/过滤/排序/表单(推荐)

# 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

3.0) Django 风格别名 DSL

如果你更习惯 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 写法。

3.1) 关联 inline 配置示例(*_ids

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/tags
  • Role 表单中配置了 permission_ids inline,可直接多选关联权限

3.2) 真正的 inline 子表单(has_many

父模型需要打开 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_tags inline 管理标签关联。

如果关联数据量较大,可以打开远程搜索:

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 接口,按输入关键词返回选项。

3.3) 自定义 Admin Action

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

3.3.1) CSV 导入

资源显式开启后,页面右上角会显示“导入 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 支持导入 名称/标识/颜色/描述

3.4) Django 风格字段增强示例

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 }
]

3.5) 列表快速编辑(list_editable

table: {
  list_editable: [:name, :enabled],
  columns: [
    { key: :name, label: "名称", editable: true },
    { key: :status, label: "状态" },
    { key: :enabled, label: "启用", editable: true }
  ]
}

说明:

  • list_editablecolumns[*].editable: true 都可以开启列表内快速编辑
  • 当前支持字段类型:textnumberbooleanselect
  • 标记为只读(read_only / disabled_on_edit)的字段会自动排除

3.6) 字段布局(sections / tabs)

表单布局写在 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 会自动追加到最后一个分组。

3.7) 软删除与回收站

模型需要有 deleted_at 字段,并在 CRUD 配置中开启:

configure_admin_crud(
  soft_delete: { enabled: true }
)

开启后:

  • 列表默认只显示 deleted_at IS NULL 的记录。
  • 删除会写入 deleted_at,记录进入回收站。
  • 回收站支持恢复、永久删除、批量恢复、批量永久删除。
  • 审计日志仍记录 destroy/restore/purge,软删除会带上 softDelete: true 元数据。

已内置示例:

  • Tag
  • ApiEndpoint

4) 注册时覆盖配置(可选)

你也可以在注册时追加/覆盖模型配置:

Admin::ResourceRegistry.register(
  Product,
  label: "商品后台",
  crud: {
    page: { title: "商品后台" },
    sort_fields: [{ key: :updated_at, label: "更新时间" }]
  }
)

5) layout 指定方式

  • 默认通用页面使用后台 AdminLayout
  • 你可以通过 admin_crud_for Product, component: "Your/CustomCrudPage" 切换到自定义页面组件(用于不同 layout 或交互风格)。
  • 如果只需要标题变化,直接改 page.title 即可,页面头部会自动同步。

6) 已落地示例

已切换为“注册即上线”模式的资源,可直接参考:

  • app/admin/resources.rb
  • app/controllers/admin_resources_controller.rb
  • app/models/api_endpoint.rb
  • app/models/role.rb
  • app/models/permission.rb
  • app/models/tag.rb

登录与身份扩展

  • 登录入口字段为 login,可输入「用户名」或「邮箱」。
  • 登录地址为 /admin/login
  • users 表新增 username(唯一索引)。
  • 引入 auth_identities 表用于多登录方式扩展:
    • providerpassword/github/google/wechat/phone
    • uid:第三方平台唯一标识
    • meta:平台扩展信息(JSON)
  • 当前默认会为本地账号创建 provider=password 的身份记录,后续接 GitHub/Google/微信/手机号时只需新增 OAuth/验证码流程并写入 auth_identities,不需要重构主用户表。

统一消息提示与实时通知

  • 页面内操作反馈:
    • 仍使用 Inertia flash(notice/alert)+ Ant Design message
  • 实时通知:
    • 使用 Rails Action Cable(/cable)+ NotificationsChannel
    • 服务端通过 UserNotifications::Publisher.deliver! 创建并广播通知。
    • 前端在后台布局中订阅通知,使用 Ant Design notification 弹出提示,并在右上角通知面板显示未读数量与历史消息。
  • 已提供接口:
    • PATCH /admin/notifications/mark-all-read:标记当前用户通知为已读。

权限约定

权限模型对标 Django admin,但不引入 Group,直接使用 Role 承担分组职责:

  • User 可拥有多个 Role
  • Role 可拥有多个 Permission
  • Permission 使用 resource.action 格式
  • Pundit 根据当前用户角色汇总出来的权限判断是否允许访问

权限键示例:

  • users.read
  • users.create
  • roles.update
  • tags.read
  • api_endpoints.destroy

内置角色:

  • super_admin:拥有全部权限,策略层直接放行
  • admin:默认拥有所有已初始化资源权限
  • readonly:默认只拥有 read 权限

Pundit 默认会把模型类名转为复数资源名,例如 ApiEndpoint 对应 api_endpointsadmin:init_dataadmin:register 都会自动为注册资源补齐标准权限。

API

所有 API controller 仍放在 app/controllers/api 下。通用资源 API 可通过 DSL 注册,不需要为每个模型手写 controller。

API DSL

资源注册文件: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/users
  • GET /api/v1/users/:id

支持项:

  • version:API 版本,例如 :v1
  • path:路由路径
  • only:开放动作,支持 index/show/create/update/destroy
  • auth:默认 :jwt,也可设为 false:none
  • policy:默认开启 Pundit 授权
  • permit:create/update 允许字段
  • scope:列表查询范围
  • search_fields:关键词搜索字段(q
  • filter_fields:过滤字段(filters),支持 boolean/select/date_range/text
  • sort_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"
    }
  }
}

API 文档(OpenAPI + Swagger UI)

  • GET /api/docs/openapi.json:OpenAPI 3.1 JSON
  • GET /api/docs:Swagger UI 页面

文档会自动读取 app/api/resources.rb 中的注册项并生成,不需要额外手工维护。
访问控制说明:

  • 需要先登录后台会话(/admin/login)才能访问 api/docsapi/docs/openapi.json
  • 生产环境默认隐藏(返回 404)
  • 生产环境如需开放,设置 ENABLE_API_DOCS=true

Swagger 页面顶部会为当前登录用户自动生成可复制 JWT,并自动注入 bearerAuth,方便直接调试接口。 字段权限会通过 OpenAPI 扩展字段 x-fieldPermissions 暴露,方便前端或外部调用方识别。

JWT

登录换 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 审计

通过 API DSL 生成的资源接口默认会记录 create/update/destroy:

  • actionapi.createapi.updateapi.destroy
  • actor:当前 JWT 用户;使用服务 token 时为空
  • audited_changes:创建/删除快照,或更新 diff
  • metadata.requestPayload:通过 DSL permit 和字段写权限过滤后的请求体白名单快照
  • metadata.source/apiVersion/apiResource/requestId/ip/userAgent:调用来源与追踪信息

API 错误与限流

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 次/分钟/IP
  • POST /api/v1/auth/refresh:20 次/分钟/IP
  • 其他 /api/**:300 次/分钟/IP

响应头会返回:X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset,触发限流时会返回 429Retry-After

自定义 API Controller

需要完全自定义行为时,继续放在 app/controllers/api/**。当前已有:

  • GET /api/v1/health:自定义 controller
  • GET /api/v1/users:由 app/api/resources.rb DSL 注册生成

兼容项:如果设置了 ADMIN_API_KEY,也可以用 Authorization: Bearer <ADMIN_API_KEY> 调用 DSL 资源接口;新功能优先推荐 JWT。

检查

yarn run check
bin/rails zeitwerk:check
bin/rails test
bin/vite build

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors