news 2026/9/16 13:41:16

Fizzy Identity API 实战指南:身份账户查询与时区更新

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Fizzy Identity API 实战指南:身份账户查询与时区更新

Fizzy Identity API 实战指南:身份账户查询与时区更新

【免费下载链接】fizzyKanban as it should be. Not as it has been.项目地址: https://gitcode.com/GitHub_Trending/fizzy2/fizzy

导读

Fizzy 是一个多租户看板(Kanban)应用,而Identity(身份)是它账号体系的基石——它代表的不是"某个工作区里的成员",而是"使用 Fizzy 的某个人",可以横跨多个账户(Account)存在。本文围绕官方 API 文档中 Identity 的两个核心端点展开:用GET /my/identity一次获取当前身份可访问的全部账户及其用户信息,用PATCH /:account_slug/my/timezone调整当前用户的时区偏好。读完本文,你将能够:在自己的应用或脚本中完成"启动时枚举账户"这一典型集成动作,并理解时区设置从 API 请求到通知邮件渲染的完整链路。文中所有接口细节均以官方文档为主干,并结合 Fizzy 仓库源码(模型、控制器、jbuilder 视图与路由)进行深度印证。

一、什么是 Identity:跨越账户的人

在深入接口之前,先建立一个准确的概念模型。官方文档对 Identity 的定义非常简洁:

An Identity represents a person using Fizzy.

在 Fizzy 的多租户设计中,人是全局的,账户是局部的。一个 Identity 可以加入多个账户,在每个账户中对应一条独立的 User 记录。源码 app/models/identity.rb 印证了这一关系:

class Identity < ApplicationRecord include Joinable, Transferable has_many :users, dependent: :nullify has_many :accounts, through: :users end

其中Joinable(app/models/identity/joinable.rb)提供了把身份加入账户的核心方法:

def join(account, **attributes) attributes[:name] ||= email_address transaction do account.users.find_or_create_by!(identity: self) do |user| user.assign_attributes(attributes) end.previously_new_record? end end

一个值得注意的细节:每个账户下的 User 记录默认以email_address作为名字——这正好解释了本文后面时区端点中 "current user" 的语义:时区是按账户内用户(User)存储的,而身份(Identity)本身不持有时区。

多租户上下文通过 app/models/current.rb 贯穿整个请求周期:Current.sessionCurrent.identityCurrent.user(按当前账户从 identity 的 users 中解析),构成了"一次登录、多账户切换"的基础。

二、GET /my/identity:获取身份与账户清单

2.1 端点概览

GET /my/identity

该端点不需要账户上下文(即不需要先访问某个/account_slug/...路径),返回当前登录身份可访问的所有账户,以及每个账户对应的用户信息。路由定义位于 config/routes.rb 的namespace :my块中:

namespace :my do resource :identity, only: :show resource :timezone # ... end

控制器 app/controllers/my/identities_controller.rb 极其精简,但有一处关键声明:

class My::IdentitiesController < ApplicationController disallow_account_scope def show @identity = Current.identity end end

disallow_account_scope(定义于 app/controllers/concerns/authentication.rb)会跳过require_account前置钩子并拦截带账户上下文的请求,从而保证该端点只依赖登录身份本身,与"当前处于哪个账户"无关。

2.2 身份认证方式

该端点既支持个人访问令牌(Personal Access Token),也支持会话 Cookie:

# 方式一:Bearer 令牌 curl -H "Authorization: Bearer put-your-access-token-here" \ -H "Accept: application/json" \ https://app.fizzy.do/my/identity # 方式二:会话 Cookie(magic link 登录后) curl -H "Cookie: session_token=eyJfcmFpbHMi..." \ -H "Accept: application/json" \ https://app.fizzy.do/my/identity

Bearer 令牌的校验逻辑在 app/controllers/concerns/authentication.rb 的authenticate_by_bearer_token中:仅对 JSON 请求生效,并通过Identity.find_by_permissable_access_token(token, method: request.method)(app/models/identity.rb)按 HTTP 方法校验令牌的读写权限。更完整的令牌申请、列举与删除流程见 docs/api/sections/authentication.md。

2.3 响应结构逐字段解读

官方文档给出的响应示例:

{ "accounts": [ { "id": "03f5v9zjskhcii2r45ih3u1rq", "name": "37signals", "slug": "/897362094", "created_at": "2025-12-05T19:36:35.377Z", "user": { "id": "03f5v9zjw7pz8717a4no1h8a7", "name": "David Heinemeier Hansson", "role": "owner", "active": true, "email_address": "david@example.com", "created_at": "2025-12-05T19:36:35.401Z", "url": "http://app.fizzy.localhost:3006/users/03f5v9zjw7pz8717a4no1h8a7" } } ] }

这个 JSON 并非手写示例,而是由 jbuilder 模板逐字段渲染出来的,源码清晰可查。

外层响应由 app/views/my/identities/show.json.jbuilder 生成:

json.id @identity.id json.accounts @identity.users_with_active_accounts do |user| json.partial! "my/identities/account", account: user.account json.user user, partial: "users/user", as: :user end

两个值得注意的实现细节:

  1. 只返回"活跃账户"users_with_active_accounts(app/models/identity.rb)通过users.joins(:account).merge(Account.active)过滤,已关闭(deactivated)的账户不会出现在列表中。
  2. 响应带顶层id:文档示例省略了顶层id(即 Identity 自身的 ULID),但实际渲染会输出json.id @identity.id

账户片段由 app/views/my/identities/_account.json.jbuilder 渲染:

json.cache! account do json.(account, :id, :name, :slug) json.created_at account.created_at.utc end

用户片段由 app/views/users/_user.json.jbuilder 渲染:

json.cache! user do json.(user, :id, :name, :role, :active) json.email_address user.identity&.email_address json.created_at user.created_at.utc json.url user_url(user) json.avatar_url user_avatar_url(user) end

各字段含义与数据来源汇总如下:

字段类型含义来源
idstring账户/用户/身份的 ULID 主键模型主键
namestring账户名 / 用户名Account / User
slugstring账户标识(形如/897362094,用于构造账户级 API 路径)Account
created_atdatetime创建时间(UTC)模型时间戳
rolestring用户在账户内的角色:owner/admin/member/systemapp/models/user/role.rb 枚举
activeboolean用户是否处于激活状态User#active
email_addressstring身份邮箱(跨账户共享)user.identity&.email_address
urlstring用户页面对外 URLuser_url(user)
avatar_urlstring用户头像 URL(响应中实际存在,文档示例未列出)user_avatar_url(user)

角色枚举定义在 app/models/user/role.rb:

enum :role, %i[ owner admin member system ].index_by(&:itself), scopes: false

同时提供admin?super || owner?)等权限判断,owner是账户内的最高角色,本文开头示例中 DHH 在 "37signals" 账户中的角色正是owner

2.4 典型使用场景

该端点是客户端集成的"入口端点",最常见的用途是应用/脚本启动时枚举用户可用的全部账户,随后再针对具体账户发起业务请求(如/1234567/boards/1234567/cards)。配合slug字段(如/897362094)即可拼出账户作用域路径。由于响应中account片段启用了json.cache!,并且控制器层通过etag { Current.identity.id }提供 ETag(app/controllers/concerns/authentication.rb),客户端可配合If-None-Match做增量缓存,账户列表未变化时直接命中304 Not Modified,大幅减少重复拉取开销(ETag 用法详见 docs/api/README.md)。

三、PATCH /:account_slug/my/timezone:更新当前用户时区

3.1 端点概览

PATCH /:account_slug/my/timezone

该端点更新当前账户上下文内当前用户的时区。官方文档明确说明其作用范围:影响通知邮件(notification emails)中时间的显示方式

参数定义如下(完全继承自官方文档):

参数类型必填说明
timezone_namestringIANA 时区标识符(如America/New_YorkEurope/LondonAsia/Tokyo

请求示例:

{ "timezone_name": "America/New_York" }

成功时返回204 No Content

3.2 源码实现链路

控制器 app/controllers/my/timezones_controller.rb 是整条链路的入口:

class My::TimezonesController < ApplicationController def update Current.user.settings.update!(timezone_name: timezone_param) head :no_content end private def timezone_param params[:timezone_name] end end

可以看到,时区并非存储在 Identity 上,而是落在账户内用户(User)的 Settings 记录上。User通过Configurable模块(app/models/user/configurable.rb)维护这份设置:

has_one :settings, class_name: "User::Settings", dependent: :destroy after_create :create_settings, unless: :system? delegate :timezone, to: :settings, allow_nil: true def time_zone(&block) Time.use_zone(timezone, &block) end

也就是说,每个用户创建时会自动生成一份User::Settings,而timezone是这份设置的动态计算结果。

3.3 时区解析与默认值

设置的实际存储与解析逻辑位于 app/models/user/settings.rb:

def timezone if timezone_name.present? ActiveSupport::TimeZone[timezone_name] || default_timezone else default_timezone end end def default_timezone ActiveSupport::TimeZone["UTC"] end

两个关键行为:

  1. IANA 标识符解析timezone_name存的是America/New_York这类 IANA 名称,通过ActiveSupport::TimeZone[...]解析为 Ruby 的时区对象(TimeZone内部即映射 IANA 名称)。
  2. 容错回退:若传入的名称无法解析(拼写错误或不存在),不会抛异常,而是回退到默认时区UTC。未设置时同样默认 UTC。

3.4 时区如何影响通知邮件

timezone_name的语义在上游被Time.use_zone消费:凡是需要在当前用户时区下渲染时间的代码,都会通过user.time_zone { ... }包裹执行(见 app/models/user/configurable.rb)。官方文档所述的"影响通知邮件中的时间显示"正是通过这条委托链实现的——邮件模板在渲染时间戳时进入用户时区,从而显示为当地时间而非服务器 UTC 时间。

需要注意的是,该设置按账户内用户生效:同一个 Identity 在不同账户下有各自的 User 与 Settings,因此时区偏好天然隔离,不会跨账户串扰。

3.5 路由与作用域

路由同样定义在namespace :my中(config/routes.rb):

resource :timezone

resource单数形式意味着这是一条单例资源路由——用户没有"多个时区",只有一个偏好设置。完整路径为PATCH /:account_slug/my/timezone,其中:account_slug是账户级作用域前缀(如/897362094),与GET /my/identity这类全局端点在作用域上形成互补:一个是"账户无关的全局视图",一个是"账户内的用户偏好"。

四、实践建议与注意事项

  1. 启动流程推荐组合:先GET /my/identity拿到账户清单(含 slug),再逐账户调用业务 API;Identity 顶层id可用于客户端本地关联身份缓存。
  2. 时区名称必须使用 IANA 标识符:不要传ESTGMT+8这类缩写或偏移量,ActiveSupport::TimeZone按 IANA 名称解析,非法值会静默回退为 UTC——排查"时区没生效"时优先检查拼写。
  3. 区分全局端点与账户端点GET /my/identity无账户前缀且禁用了账户作用域(disallow_account_scope);而时区端点必须携带:account_slug,二者不可混用。
  4. 响应缓存GET /my/identity的账户片段启用了片段缓存且响应带 ETag,高频轮询应使用If-None-Match以节省带宽。
  5. 权限提示:该端点的语义对象是"当前用户",即认证身份在当前账户下的 User 记录;如果请求未携带有效账户上下文,会因require_account失败而无法命中,因此调用前请确认已先进入目标账户作用域(如通过/1234567前缀的请求建立账户上下文)。

五、小结

Identity 是 Fizzy 多租户账号体系的核心抽象:一个人(Identity)跨多个账户(Account),每个账户内体现为一条 User 记录。GET /my/identity是这个体系面向 API 的窗口——一次请求即可枚举身份的全部活跃账户及其用户信息,适合作为集成客户端的启动入口;PATCH /:account_slug/my/timezone则把 IANA 时区偏好写入账户内用户的 Settings,经ActiveSupport::TimeZone解析、Time.use_zone消费,最终体现在通知邮件的本地化时间渲染上。两个端点一"全局"一"账户内",共同构成了 Fizzy 身份层对外部应用最基本的两个操作。想要进一步探索身份机制,推荐继续阅读 app/models/identity.rb、app/models/current.rb 与 docs/api/sections/authentication.md。

【免费下载链接】fizzyKanban as it should be. Not as it has been.项目地址: https://gitcode.com/GitHub_Trending/fizzy2/fizzy

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/16 13:40:15

camofox-browser访问控制:CAMOFOX_ACCESS_KEY全局Bearer认证实战

camofox-browser访问控制&#xff1a;CAMOFOX_ACCESS_KEY全局Bearer认证实战 【免费下载链接】camofox-browser Stealth headless browser for AI agents — bypass Cloudflare, bot detection, and anti-scraping. Drop-in Puppeteer/Playwright replacement. 项目地址: htt…

作者头像 李华
网站建设 2026/9/16 13:38:16

抖音批量下载教程:Douyin Downloader 从安装到保存整个作者主页

抖音批量下载教程&#xff1a;Douyin Downloader 从安装到保存整个作者主页 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallb…

作者头像 李华
网站建设 2026/9/16 13:34:46

C#上位机开发:数据绑定与线程安全实践

1. 为什么数据绑定是C#上位机的命门&#xff1f;刚入行时我做过一个工业温控项目&#xff0c;界面上要实时显示20个传感器的数据。最初用最土的办法&#xff1a;在每个TextBox的TextChanged事件里手动更新变量&#xff0c;结果代码写成了一团乱麻&#xff0c;数据延迟高达500ms…

作者头像 李华