Omi Admin Dashboard 深度指南:基于 Next.js 的内部运营后台架构、鉴权与本地开发全解
【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend
导读
web/admin是 Omi 项目(AI 眼镜助手)的内部运营管理后台,运行在admin.omi.me,采用 Next.js 构建。它以浏览器端 Firebase 登录认证管理员身份,并在同源app/api/下暴露受保护的 Next.js Route Handler,由服务端统一读取 Firestore、调用 Omi 后端 API 与第三方厂商 API,浏览器代码永远不会拿到服务端密钥。本文基于仓库内 web/admin/README.md 展开,结合.env.example、lib/auth.ts、lib/dev-auth.ts、Dockerfile与 GitHub Actions 部署工作流等源码,系统讲解其本地启动流程、开发鉴权绕过机制、环境变量全量说明、权限模型以及 CI/部署链路,读者学完后即可独立完成本地开发、联调、自检与生产部署全流程。
一、整体架构:浏览器、Route Handler 与服务端的信任边界
1.1 三层职责划分
从 README 与目录结构可以提炼出后台的经典三层架构:
- 客户端(
app/(protected)/dashboard/**):负责渲染内部运营界面(analytics、reviews、announcements、fair-use、subscriptions 等数十个页面),通过 Firebase 登录获得身份,但不在浏览器里直接读取 Firestore,也不自行构造鉴权头。根据 web/admin/AGENTS.md 的硬性约定,所有网络请求必须经由hooks/useAuthToken.ts统一携带 token。 - 服务端 Route Handler(
app/api/omi/**、app/api/stats/**等):每个受保护路由入口都调用verifyAdmin(request)(见 lib/auth.ts)完成鉴权,之后才代表管理员读取 Firestore 或调用 Omi API / 厂商 API。第三方凭证(Stripe、PostHog、Typesense、GoAffPro 等)只存在于服务端环境变量中,浏览器永远接触不到。 - 上游服务:Omi 后端 API(
backend/)、Firestore、LLM 网关(OMI_LLM_GATEWAY_URL)以及各类厂商系统。
1.2 为什么浏览器拿不到服务端凭证
README 明确指出:"Browser code never receives the server credentials"。实现上,服务端密钥只在 Route Handler 进程内使用,例如 LLM 网关路由只依赖OMI_LLM_GATEWAY_SERVICE_TOKEN(服务端令牌),而不会持有任何 LLM 提供商的 API Key。这层设计让后台可以安全地聚合管理 Stripe、Shopify、ShipBob、Mixpanel 等敏感系统,而不必担心把密钥泄露进 JS bundle。
二、本地开发环境搭建(UI 快速起步)
2.1 最小安装步骤
README 给出了标准的三步安装:
cd web/admin cp .env.example .env.local npm ci注意使用npm ci(而非npm install),它会严格按package-lock.json锁定依赖版本,保证与 CI 环境一致。项目核心依赖见 package.json:Next.js 16、React 18、Firebase 客户端 SDKfirebase11 与 Admin SDKfirebase-admin13、SWR 数据请求、Radix UI 组件库、recharts 图表与 zod 校验。
2.2 跳过登录的开发鉴权绕过(DEV BYPASS)
纯 UI 开发不需要真实 Firebase 登录,只需在.env.local中设置:
NEXT_PUBLIC_DEV_BYPASS_AUTH=1然后启动:
npm run dev打开 http://localhost:3000/dashboard 即可。绕过的固定本地身份为dev-admin,它会同时提供给客户端和受保护路由处理器;对未配置对应服务依赖的 API 卡片,页面会显示错误(README 已提示:"API cards whose backing service is not configured will show errors"),这属于预期行为。
实现细节:绕过逻辑在 lib/dev-auth.ts 中非常明确:
export const DEV_BYPASS_ENABLED = process.env.NODE_ENV !== "production" && (process.env.NEXT_PUBLIC_DEV_BYPASS_AUTH === "1" || process.env.DEV_BYPASS_AUTH === "1"); export const DEV_BYPASS_UID = "dev-admin"; export const DEV_BYPASS_TOKEN = "dev-bypass-token";两个关键事实值得注意:
- 绕过同时受
NODE_ENV !== "production"和显式开启标志的双重约束,在生产构建中被硬性禁用,无法通过误设环境变量绕开; - lib/auth.ts 中,服务端只接受
Authorization: Bearer dev-bypass-token这一特定令牌并映射到dev-admin,其余情况一律走 Firebase ID Token 校验流程。
⚠️ README 特别警告:不要在.env.local中放入生产凭证。
三、对接本地 Omi 后端进行真实联调
当需要验证真实的 Omi API 集成时,需要把backend/也跑起来(按 backend/AGENTS.md 开发者指南操作),并使用独立的开发或离线数据环境。在web/admin/.env.local中设置:
NEXT_PUBLIC_DEV_BYPASS_AUTH=1 NEXT_PUBLIC_OMI_API_URL=http://localhost:8080 OMI_API_SECRET_KEY=<the same value as backend ADMIN_KEY>3.1 双重凭证的拼接逻辑
README 说明了后台的鉴权方式:向 Omi 后端发送基础密钥(base key)与由该密钥 + 已认证 UID 拼接而成的 token两个值。绕过模式下 UID 恒为dev-admin,因此只要本地后端的ADMIN_KEY与OMI_API_SECRET_KEY一致,即可通过认证。可对照 lib/services/omi-api/client.ts 中的客户端实现验证实际请求构造方式。
⚠️ 危险边界:管理员操作会真实修改后端及其数据存储,因此该模式严禁指向生产环境。
3.2 数据类与外部系统类页面的额外配置
不同功能对凭证的需求分档如下:
| 功能类别 | 所需配置 | 说明 |
|---|---|---|
| 读写 Firestore 的路由 | FIREBASE_PROJECT_ID、FIREBASE_CLIENT_EMAIL、FIREBASE_PRIVATE_KEY | 必须是非生产 Firebase 项目 |
| 使用外部系统的页面 | 对应系统在.env.example中的凭证 | 如 Stripe、PostHog、Typesense、GoAffPro |
| LLM 网关路由 | OMI_LLM_GATEWAY_URL+ 服务端专用OMI_LLM_GATEWAY_SERVICE_TOKEN | 服务端只持有网关服务令牌,从不接触 LLM 提供商凭证 |
| 真实登录流程测试 | NEXT_PUBLIC_FIREBASE_*客户端变量 | 仅绕过模式关闭时才需要 |
四、环境变量全量参考(.env.example 逐项解析)
完整的模板见 web/admin/.env.example,按用途可划分为九组,这里给出每组的关键变量与使用场景:
4.1 开发与鉴权
NEXT_PUBLIC_DEV_BYPASS_AUTH:本地开发入口开关,置1免登录进入后台;NODE_ENV=production时被硬禁用。
4.2 Firebase 客户端 SDK(可公开暴露)
NEXT_PUBLIC_FIREBASE_API_KEY、NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN、NEXT_PUBLIC_FIREBASE_PROJECT_ID、NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET、NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID、NEXT_PUBLIC_FIREBASE_APP_ID、NEXT_PUBLIC_FIREBASE_VAPID_KEY——这些属于浏览器端安全公开的配置,用于真实登录与消息推送。
4.3 Firebase Admin SDK(服务端专用,绝不暴露)
FIREBASE_PROJECT_ID/FIREBASE_CLIENT_EMAIL/FIREBASE_PRIVATE_KEY:服务端校验 Firebase ID Token、读取 Firestore 的adminData/{uid}集合时使用。
4.4 Omi 后端 API
NEXT_PUBLIC_OMI_API_URL:Omi 后端地址(本地联调指向http://localhost:8080)。OMI_API_SECRET_KEY:服务端专用,须与后端ADMIN_KEY一致。NEXT_PUBLIC_PLUGINS_APP_ID:插件应用标识。
4.5 外部系统凭证(各页面按需使用)
- Stripe:
STRIPE_SECRET_KEY(对应 lib/stripe.ts 的订阅指标读取)。 - Typesense:
TYPESENSE_HOST、TYPESENSE_API_KEY、TYPESENSE_CONVERSATION_COLLECTION(对话检索集合名)。 - Redis:
REDIS_HOST、REDIS_PORT、REDIS_PASSWORD(缓存预热使用,见 lib/redis.ts)。 - PostgreSQL:
POSTGRES_URL。 - ShipBob / Shopify:
SHIPBOB_API_KEY、SHOPIFY_STORE、SHOPIFY_ACCESS_TOKEN(分销与订阅订单)。 - Mixpanel:
MIXPANEL_SECRET、MIXPANEL_API_BASE。 - PostHog:
POSTHOG_PERSONAL_API_KEY、POSTHOG_PROJECT_ID、POSTHOG_HOST(增长指标统计)。 - LLM 网关:
OMI_LLM_GATEWAY_URL、OMI_LLM_GATEWAY_SERVICE_TOKEN。 - 其他:
ENCRYPTION_SECRET、GITHUB_TOKEN、GOAFFPRO_ACCESS_TOKEN。
4.6 成本核算与定时任务(Stats/Infra 页)
CRON_SECRET:受保护的定时缓存预计算任务(对应app/api/internal/precompute/route.ts)。ADMIN_INFRA_OVERHEAD_MONTHLY、ADMIN_SERVICE_COSTS_JSON:估算模式基础设施开销与服务成本表。GCP_BILLING_SA_JSON:计费模式服务账号 JSON(BigQueryjobUser+dataViewer权限,留空则走 ambient ADC),对应 lib/services/gcp-billing.ts。GCP_BILLING_TABLE:计费导出表覆盖项(project.dataset.table)。ADMIN_ANTHROPIC_COST_API_KEY、ADMIN_OPENAI_COST_API_KEY:成本 API 专用密钥(仅限 ORG-ADMIN,推理密钥会收到 401/403)。模板注释特别说明:故意不命名为ANTHROPIC_API_KEY/OPENAI_API_KEY,因为部署契约会从 admin 服务中剥离这两个名字。ADMIN_PLATFORM_COST_SHARES_JSON:桌面/移动端成本分摊比例(来自omi-cost-analysis用量加权报告,结构为{"llm":{"desktop":..,"mobile":..},"core":{...},"asOf":"YYYY-MM-DD","method":".."})。
五、鉴权与权限模型
5.1 生产环境鉴权链路
正常运营中,浏览器使用 Firebase Google 或邮箱登录。鉴权遵循"双重校验"原则:
- 客户端:登录后,要求当前用户 UID 存在于 Firestore 的
adminData/{uid}文档中,否则视为非管理员; - 服务端(强校验):每个受保护 Route Handler 通过 Firebase Admin SDK 验证 ID Token,不完全信任客户端检查结果。核心逻辑在 lib/auth.ts:
const authorization = request.headers.get('Authorization'); if (DEV_BYPASS_ENABLED && authorization === `Bearer ${DEV_BYPASS_TOKEN}`) { return { uid: DEV_BYPASS_UID }; } // 正常路径:验证 Firebase ID Token + adminData/{uid} 存在性 const decodedToken = await verifyFirebaseToken(token); const adminDoc = await db.collection('adminData').doc(decodedToken.uid).get(); if (!adminDoc.exists) { return NextResponse.json({ error: 'Forbidden: Not an admin' }, { status: 403 }); }失败时路由返回标准的 401(令牌缺失/无效)或 403(非管理员)响应。
5.2 本地开发的权限矩阵
README 按工作类型给出清晰的权限分档:
| 工作类型 | 需要的前提条件 |
|---|---|
| 纯 UI 工作 | 启用开发绕过即可,无需云端、Firebase 或厂商访问权限 |
| 真实登录测试 | 非生产项目中的 Firebase Authentication 用户 + 匹配的adminData/{uid}文档 |
| 数据驱动页面 | 能读取非生产 Firestore 项目的服务账号 + 被测页面对应的服务凭证 |
| 本地 Omi API 联调 | 本地后端ADMIN_KEY,禁止申请或使用生产密钥 |
| 通过 GitHub Actions 部署 | 目标部署分支的合并/推送权限 + 目标 GitHub Environment 的审批;需要权限的是工作流的部署身份(而非开发者工作站),需具备 Artifact Registry、Cloud Run 与 Cloud Run 运行时密钥的访问权;若直接走 GCP 部署,还需等价的 Cloud Run、Artifact Registry、服务账号模拟与 Secret Manager 权限 |
六、提交前自检与 CI 校验
6.1 本地检查命令
README 要求开 PR 前执行:
npm run check npm run build从 package.json 的 scripts 可以看到check实际是三个检查的串联:npm run lint(ESLint)+npm run typecheck(tsc --noEmit)+npm test(Vitest 单元测试)。编写或修改测试时用:
npm run test:watch6.2 测试资产分布
仓库为后台沉淀了较完整的测试体系,可作为编写新测试的参照:
- 路由测试:app/api/tests/route-param-encoding.test.ts、app/api/omi/team-members/tests/route.test.ts;
- 页面测试:app/(protected)/dashboard/team/tests/page.test.tsx/dashboard/team/tests/page.test.tsx);
- 核心逻辑测试:
web/admin/lib/__tests__/下覆盖鉴权、Stripe 订阅、统计指标诚实性(stats-honesty-routes、stats-staleness-honesty、profitability-honesty)、PostHog 行数限制、平台范围路由(platform-scope)、TV 模式等数十个用例。其中多个*-honesty测试说明该项目对后台统计数据的真实性有自动化约束,很值得阅读学习。
6.3 GitHub 侧检查
GitHub 的 web check 会在web/admin/有改动时运行与本地相同的三项检查(ESLint、TypeScript、Vitest),随后执行生产构建。
七、交付路径:GitHub Actions → Docker → Cloud Run
7.1 部署工作流
.github/workflows/gcp_admin.yml 负责部署:推送至main或development分支且包含web/admin/改动时触发。流程要点:
- 从本目录构建 Docker 镜像;
- 将公开的
NEXT_PUBLIC_*值烘焙进镜像; - 服务端密钥在 Cloud Run运行时注入(不写入镜像);
- 将 100% 流量切换到新版本 revision。
7.2 Dockerfile 中的密钥治理佐证
Dockerfile 从构建层面印证了 README 的密钥边界设计:
- 多阶段构建:
deps(安装依赖)→builder(构建)→runner(运行),最终阶段使用非 root 的nextjs用户; - 构建阶段通过 ARG 接收 9 个
NEXT_PUBLIC_*变量(OMI_REQUIRED_PUBLIC_BUILD_INPUTS),并且构建前逐一校验这些变量非空,缺失即失败; - 服务端密钥明确不通过 build-arg 传递,注释写着 "Server-side secrets are injected at runtime via Cloud Run Secret Manager — no need to pass them as build-args",运行时阶段同样强调密钥不烘焙进镜像("NOT baked into the Docker image (more secure)");
- 采用 Next.js standalone 输出(
output: 'standalone')配合.next/standalone与.next/static拷贝,缩小镜像体积。
7.3 已知缺口与后续方向
README 明确列出当前尚未具备的能力:没有 PR 预览部署、本地 Firebase emulator、预置的种子管理数据,也没有浏览器端到端测试。这些是本地 UI + 后端联调循环进入常规使用后值得优先投入的方向。对于想要贡献的开发者,这是一个明确的切入点。
八、开发注意事项速查
- 客户端一律使用
hooks/useAuthToken.ts发起网络请求,禁止直接调用getIdToken()、自行构造鉴权头或读取 Firestore; - 订阅类指标一律使用 lib/stripe-subscriptions.ts,禁止按 price ID 遍历订阅;
- 数据获取契约、SWR 部分失败与订阅范围等更细的约定见 docs/data-contracts.md;
/dashboard通过grafana/嵌入 UID 为omi-tv的看板,详见 grafana/README.md;- TV 轮播分享链接走
/dashboard/tv-links(管理员)→/tv/view/<token>(免登录),约定见 docs/tv-mode.md。
结语
Omi Admin Dashboard 是一个"客户端轻、服务端重"的典型 Next.js 内部后台:浏览器只负责展示与登录,一切敏感读取与写操作都收敛在受verifyAdmin保护的 Route Handler 中。从NEXT_PUBLIC_DEV_BYPASS_AUTH的本地开发捷径,到 FirebaseadminData/{uid}双重校验,再到 Cloud Run 运行时注入密钥与构建期强制校验NEXT_PUBLIC_*非空的部署契约,整条链路的设计始终围绕"服务端密钥不落浏览器、生产凭证不落本地"这一安全基线。理解了这份 README 与配套源码,你就能在本地快速迭代 UI、安全地对联后端,并为后台补齐 PR 预览、Firebase emulator 与 E2E 测试等下一个里程碑。
【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考