Skip to content

Backend Architecture

gxxk-dev edited this page Mar 1, 2026 · 2 revisions

后端架构

本文档介绍 StudyWithMiku 后端的完整架构,包括 Cloudflare Workers 运行环境、Hono.js 路由结构、中间件栈、Durable Objects、D1 数据库以及 API 端点参考。


概述

后端运行在 Cloudflare Workers 上,使用 Hono.js 作为 Web 框架,提供认证、数据同步、在线计数等服务。核心设计思路是利用 Cloudflare 的边缘计算能力,将有状态逻辑(认证挑战、速率限制、在线计数)托管在 Durable Objects 中,将持久化数据存储在 D1 (SQLite) 数据库中。

flowchart TB
    subgraph Client["浏览器"]
        VueApp["Vue App"]
    end

    subgraph CFWorkers["Cloudflare Workers"]
        Hono["Hono.js App"]

        subgraph Middleware["中间件栈"]
            envDefaults["envDefaults"]
            securityHeaders["securityHeaders"]
            cors["CORS"]
            auth["Auth (JWT)"]
            rateLimit["Rate Limit"]
        end

        subgraph Routes["路由"]
            AuthRoutes["/auth/*"]
            OAuthRoutes["/oauth/*"]
            DataRoutes["/api/data/*"]
            CountRoutes["/count, /ws"]
            PushRoutes["/api/push/*"]
        end

        subgraph DurableObjects["Durable Objects"]
            OnlineCounter["OnlineCounter"]
            AuthChallenge["AuthChallenge"]
            RateLimiter["RateLimiter"]
            FocusNotifier["FocusNotifier"]
        end

        subgraph Database["D1 Database"]
            Users["users"]
            Credentials["credentials"]
            OAuthAccounts["oauth_accounts"]
            TokenBlacklist["token_blacklist"]
            UserData["user_data"]
            PushSubscriptions["push_subscriptions"]
        end
    end

    subgraph OAuthProviders["OAuth Providers"]
        GitHub["GitHub"]
        Google["Google"]
        Microsoft["Microsoft"]
        LinuxDo["LINUX DO"]
    end

    Client -->|"HTTPS / WebSocket"| Hono
    Hono --> Middleware
    Middleware --> Routes
    Routes --> DurableObjects
    Routes --> Database
    OAuthRoutes -->|"OAuth 2.0"| OAuthProviders
Loading

项目结构

workers/
├── index.js                    # Worker 入口,Hono 应用创建与路由挂载
├── constants.js                # 全局常量(WebAuthn、JWT、OAuth、速率限制、数据配置)
├── auth-challenge.js           # Durable Object: WebAuthn 挑战存储
├── rate-limiter.js             # Durable Object: 分布式速率限制
├── online-counter.js           # Durable Object: WebSocket 在线计数
├── focus-notifier.js           # Durable Object: 番茄钟推送通知
│
├── db/
│   ├── index.js                # Drizzle ORM 客户端工厂
│   └── schema.js               # 数据库表定义(6 张表)
│
├── middleware/
│   ├── envDefaults.js          # 环境变量自动检测
│   ├── securityHeaders.js      # 安全响应头
│   ├── cors.js                 # CORS 跨域配置
│   ├── auth.js                 # JWT 认证中间件
│   └── rateLimit.js            # 速率限制中间件
│
├── routes/
│   ├── auth/
│   │   ├── index.js            # 认证路由聚合
│   │   ├── register.js         # WebAuthn 注册端点
│   │   ├── login.js            # WebAuthn 登录端点
│   │   ├── token.js            # Token 刷新与注销
│   │   ├── profile.js          # 用户资料管理
│   │   ├── devices.js          # 设备管理与账号合并
│   │   └── methods.js          # 认证方法列表
│   ├── oauth.js                # OAuth 流程(4 个提供商)
│   ├── data.js                 # 用户数据同步端点
│   └── push.js                 # Web Push 推送路由
│
├── services/
│   ├── user.js                 # 用户 CRUD
│   ├── jwt.js                  # JWT 生成与验证
│   ├── webauthn.js             # WebAuthn 凭证验证
│   ├── credential.js           # WebAuthn 凭证 CRUD
│   ├── oauth.js                # OAuth 提供商集成
│   ├── oauthAccount.js         # OAuth 账号 CRUD
│   ├── userData.js             # 用户数据存储与同步
│   ├── merge.js                # 账号合并逻辑
│   ├── counter.js              # 在线计数 DO 访问器
│   └── push.js                 # Web Push 推送服务
│
├── schemas/
│   └── auth.js                 # Zod 验证模式
│
└── utils/
    ├── authHelpers.js          # 认证辅助函数
    ├── cookie.js               # Refresh Token Cookie 工具
    ├── avatar.js               # 头像 URL 构建
    └── protobufServer.js       # Protobuf 编解码封装

Hono.js 路由结构

请求处理管道

flowchart LR
    Request["HTTP Request"]
    envDefaults["envDefaults()"]
    securityHeaders["securityHeaders()"]
    cors["cors()"]
    rateLimit["rateLimit()"]
    auth["requireAuth()"]
    Handler["Route Handler"]
    Response["HTTP Response"]

    Request --> envDefaults --> securityHeaders --> cors
    cors --> rateLimit --> auth --> Handler --> Response
Loading

不是所有路由都经过全部中间件。全局中间件只有 envDefaultssecurityHeaders,CORS 仅应用于特定路径前缀,rateLimitauth 按路由组配置。

路由挂载

// workers/index.js
const app = new Hono()

// 全局中间件
app.use('*', envDefaults())
app.use('*', securityHeaders())

// 按路径前缀启用 CORS
app.use('/ws', cors(corsConfig))
app.use('/count', cors(corsConfig))
app.use('/auth/*', cors(corsConfig))
app.use('/oauth/*', cors(corsConfig))
app.use('/api/*', cors(corsConfig))

// 路由挂载
app.get('/count', handleCount)         // 在线人数
app.get('/ws', handleWebSocket)        // WebSocket 连接
app.route('/auth', authRoutes)         // 认证路由组
app.route('/oauth', oauthRoutes)       // OAuth 路由组
app.route('/api/data', dataRoutes)     // 数据同步路由组

// 全局错误处理
app.onError((err, c) => {
  return c.json({ error: 'Internal Server Error', message: err.message }, 500)
})

export default app
export { OnlineCounter, AuthChallenge, RateLimiter, FocusNotifier }

中间件栈

1. envDefaults — 环境变量自动检测

从请求 URL 中自动推断环境变量,减少手动配置:

变量 推断方式
WEBAUTHN_RP_ID request.url 的 hostname 提取
WEBAUTHN_RP_NAME 默认 'Study with Miku'
OAUTH_CALLBACK_BASE request.url 的 origin 提取

2. securityHeaders — 安全响应头

为所有响应添加安全头:

X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: geolocation=(), microphone=(), camera=()

3. cors — CORS 跨域

  • 解析 CORS_ORIGINS 环境变量
  • 开发环境自动允许 localhost
  • 自动允许同源请求
  • 处理 OPTIONS 预检请求
  • 允许方法:GET, POST, PUT, DELETE, OPTIONS
  • 允许头:Content-Type, Authorization

4. auth — JWT 认证

提供两种认证模式:

中间件 行为
requireAuth() 必须提供有效 JWT,否则 401
optionalAuth() 有 JWT 则解析,没有也放行

认证流程:

  1. Authorization: Bearer <token> 提取 token
  2. 验证签名和过期时间
  3. 检查 token 是否在黑名单中
  4. 设置 c.set('user', { id, username, jti, exp })

5. rateLimit — 速率限制

通过 RateLimiter Durable Object 实现分布式速率限制:

类型 窗口 上限 适用路由
AUTH 60s 10 次/分 /auth/*
DATA 60s 30 次/分 /api/data/*
GENERAL 60s 100 次/分 其他 API

超过限制返回 429 Too Many Requests,响应包含 X-RateLimit-* 头和 Retry-After


Durable Objects

OnlineCounter

文件: workers/online-counter.js

职责: 通过 WebSocket 追踪实时在线用户数并广播给所有连接的客户端。

工作机制:

  • 客户端通过 /ws 建立 WebSocket 连接
  • 使用 DO 内置的 WebSocket 管理 (this.state.getWebSockets())
  • 每次连接/断开时向所有客户端广播当前连接数
  • 响应 ping 消息保持连接活跃
  • GET /count 返回当前在线人数(不需要 WebSocket)

AuthChallenge

文件: workers/auth-challenge.js

职责: 临时存储 WebAuthn 注册/登录挑战,以及账号合并 Token。

工作机制:

  • PUT — 存储挑战(5 分钟 TTL)或合并 Token(10 分钟 TTL)
  • GET — 读取挑战/Token
  • DELETE — 验证后立即删除(防止重放攻击)
  • alarm() — 定时清理过期条目

每个挑战在验证后立即删除,确保一次性使用。

RateLimiter

文件: workers/rate-limiter.js

职责: 按 IP + 端点组合进行分布式速率限制。

工作机制:

  • 每个 keyPrefix:ip 组合对应一个隔离的 DO 实例
  • 在滑动窗口内追踪请求计数
  • 返回速率限制状态:
{
  allowed: boolean,     // 是否放行
  count: number,        // 当前窗口请求数
  remaining: number,    // 剩余配额
  resetTime: number,    // 窗口重置时间
  retryAfter: number    // 超限后等待秒数
}

FocusNotifier

文件: workers/focus-notifier.js

职责: 服务端番茄钟状态机,当客户端关闭后通过 Durable Object Alarm 定时触发推送通知。

工作机制:

  • 客户端通过 Push 路由 schedule 请求
  • DO 内部维护 running/paused/idle 状态和 alarm
  • alarm 触发时发送 Web Push 通知
  • 支持自动切换到下一阶段(autoStartBreaks/autoStartFocus)

API: /schedule, /pause, /resume, /cancel, /update-settings, /state


D1 数据库

Schema 概览

使用 Drizzle ORM 定义 6 张表,通过 drizzle-kit 管理迁移。

erDiagram
    users ||--o{ credentials : "1:N"
    users ||--o{ oauth_accounts : "1:N"
    users ||--o{ user_data : "1:N"
    users ||--o{ push_subscriptions : "1:N"

    users {
        text id PK
        text username UK
        text displayName
        text avatarUrl
        text email
        text qqNumber
    }

    credentials {
        text id PK
        text userId FK
        blob publicKey
        integer counter
        text transports
        text deviceType
        text deviceName
        integer backedUp
        integer lastUsedAt
    }

    oauth_accounts {
        text id PK
        text userId FK
        text provider
        text providerId
        text displayName
        text avatarUrl
        text email
        integer linkedAt
    }

    token_blacklist {
        text jti PK
        integer expiresAt
    }

    user_data {
        text userId PK
        text dataType PK
        blob data
        text dataFormat
        integer version
    }

    push_subscriptions {
        text id PK
        text userId FK
        text endpoint
        text p256dh
        text auth
        integer createdAt
        text userAgent
    }
Loading

表详情

users — 用户表

类型 说明
id text PK 随机生成的 hex ID
username text UNIQUE 用户名(3-20 位字母数字下划线)
displayName text 显示名称
avatarUrl text 头像 URL
email text 电子邮件
qqNumber text QQ 号(用于 QQ 头像)

索引: idx_users_username (username)

credentials — WebAuthn 凭证表

类型 说明
id text PK 凭证 ID (Base64URL)
userId text FK 关联用户,级联删除
publicKey blob 公钥
counter integer 签名计数器(克隆检测)
transports text 传输方式 JSON (internal/usb/ble/nfc)
deviceType text 设备类型
deviceName text 设备名称(自动生成)
backedUp integer 是否已备份 (0/1)
lastUsedAt integer 最后使用时间戳

索引: idx_credentials_user_id (userId)

oauth_accounts — OAuth 关联表

类型 说明
id text PK 随机 hex ID
userId text FK 关联用户,级联删除
provider text 提供商 (github/google/microsoft/linuxdo)
providerId text 提供商用户 ID
displayName text 提供商显示名
avatarUrl text 提供商头像
email text 提供商邮箱
linkedAt integer 关联时间戳

唯一索引: idx_oauth_accounts_provider (provider, providerId)

token_blacklist — Token 黑名单

类型 说明
jti text PK JWT ID
expiresAt integer 过期时间戳(用于自动清理)

索引: idx_token_blacklist_expires (expiresAt)

Token 黑名单通过概率性清理保持精简:每次登录有 10% 概率触发过期条目清理。

user_data — 用户同步数据

类型 说明
userId text PK 关联用户
dataType text PK 数据类型
data blob Protobuf 编码的二进制数据
dataFormat text 格式标识 ('protobuf')
version integer 乐观并发版本号

复合主键: (userId, dataType)

push_subscriptions — 推送订阅表

类型 说明
id text PK 订阅 ID
userId text FK 关联用户,级联删除
endpoint text 推送服务端点 URL
p256dh text 客户端公钥
auth text 认证密钥
createdAt integer 创建时间戳
userAgent text 用户代理字符串

索引: idx_push_subs_user_id (userId) 唯一索引: idx_push_subs_endpoint (endpoint)

Drizzle ORM 用法

// workers/db/index.js
import { drizzle } from 'drizzle-orm/d1'
import * as schema from './schema.js'

export const createDb = (d1) => drizzle(d1, { schema })

在路由中使用:

// workers/routes/auth/profile.js
const db = createDb(c.env.DB)
const user = await db.select().from(users).where(eq(users.id, userId))

索引策略

索引 用途
idx_users_username users username 登录时按用户名查找
idx_credentials_user_id credentials userId 查询用户的所有凭证
idx_oauth_accounts_user_id oauth_accounts userId 查询用户的 OAuth 关联
idx_oauth_accounts_provider oauth_accounts provider, providerId OAuth 登录时查找关联(唯一)
idx_token_blacklist_expires token_blacklist expiresAt 清理过期黑名单条目
idx_push_subs_user_id push_subscriptions userId 查询用户的推送订阅
idx_push_subs_endpoint push_subscriptions endpoint 按端点查找订阅(唯一)

部署配置

wrangler.toml 核心配置

name = "study-with-miku"
main = "workers/index.js"
compatibility_date = "2024-09-23"
compatibility_flags = ["nodejs_compat"]

# 静态资源
assets = { directory = "./dist", binding = "ASSETS" }

# 自定义域名
[routes]
pattern = "swm.frez79.io"
zone_name = "frez79.io"
custom_domain = true

Bindings

Binding 类型 用途
ASSETS Static Assets 前端构建产物
DB D1 Database 用户数据持久化
ONLINE_COUNTER Durable Object 在线计数服务
AUTH_CHALLENGE Durable Object WebAuthn 挑战存储
RATE_LIMITER Durable Object 速率限制
FOCUS_NOTIFIER Durable Object 番茄钟推送通知

环境变量

变量 说明 自动推断
JWT_SECRET JWT 签名密钥 否(Secret)
CORS_ORIGINS 允许的跨域来源 部分(自动检测 localhost)
WEBAUTHN_RP_ID WebAuthn 信赖方 ID 是(从请求 hostname)
WEBAUTHN_RP_NAME WebAuthn 信赖方名称 是(默认 'Study with Miku')
OAUTH_CALLBACK_BASE OAuth 回调基础 URL 是(从请求 origin)
GITHUB_CLIENT_ID GitHub OAuth 否(Secret)
GITHUB_CLIENT_SECRET GitHub OAuth 否(Secret)
GOOGLE_CLIENT_ID Google OAuth 否(Secret)
GOOGLE_CLIENT_SECRET Google OAuth 否(Secret)
MICROSOFT_CLIENT_ID Microsoft OAuth 否(Secret)
MICROSOFT_CLIENT_SECRET Microsoft OAuth 否(Secret)
LINUXDO_CLIENT_ID LINUX DO OAuth 否(Secret)
LINUXDO_CLIENT_SECRET LINUX DO OAuth 否(Secret)
VAPID_PUBLIC_KEY VAPID 推送公钥 否(Secret)
VAPID_PRIVATE_JWK VAPID 推送私钥 (JWK) 否(Secret)
VAPID_SUBJECT VAPID 主体 (mailto:)

API 端点参考

在线计数

方法 路径 认证 说明
GET /count 获取当前在线人数
GET /ws WebSocket 连接(实时在线计数)

认证 — WebAuthn

方法 路径 认证 说明
GET /auth/config 获取可用认证方式配置
POST /auth/register/options 请求注册挑战
POST /auth/register/verify 验证注册响应
POST /auth/login/options 请求登录挑战
POST /auth/login/verify 验证登录响应

认证 — Token 管理

方法 路径 认证 说明
POST /auth/refresh Cookie 刷新 Access Token
POST /auth/logout Bearer 注销并吊销 Token

认证 — 用户资料

方法 路径 认证 说明
GET /auth/me Bearer 获取当前用户信息
PATCH /auth/me Bearer 更新用户资料

认证 — 设备管理

方法 路径 认证 说明
GET /auth/devices Bearer 列出已注册设备
POST /auth/devices/add/options Bearer 请求添加设备挑战
POST /auth/devices/add/verify Bearer 验证添加设备
DELETE /auth/devices/:id Bearer 删除设备
POST /auth/devices/merge Bearer 合并设备关联的账号

认证 — 认证方法

方法 路径 认证 说明
GET /auth/methods Bearer 列出所有认证方法(WebAuthn + OAuth)
DELETE /auth/methods/oauth/:id Bearer 解除 OAuth 关联

OAuth

方法 路径 认证 说明
GET /oauth/:provider 跳转到 OAuth 授权页
GET /oauth/:provider/callback OAuth 回调处理
POST /oauth/link/:provider Bearer 关联 OAuth 账号
POST /oauth/merge Bearer 合并 OAuth 关联的账号

数据同步

方法 路径 认证 说明
GET /api/data/:type Bearer 下载指定类型数据
GET /api/data/:type/version Bearer 获取数据版本号
PUT /api/data/:type Bearer 上传/更新数据(支持冲突检测)
DELETE /api/data/:type Bearer 删除指定类型数据

其中 :type 为以下值之一:focus_records, focus_settings, playlists, user_settings, share_config, hook_settings

Push 推送

方法 路径 认证 说明
GET /api/push/vapid-key 获取 VAPID 公钥
POST /api/push/subscribe Bearer 注册推送订阅
DELETE /api/push/subscribe Bearer 删除推送订阅
POST /api/push/session/schedule Bearer 注册定时推送
POST /api/push/session/pause Bearer 暂停推送定时器
POST /api/push/session/resume Bearer 恢复推送定时器
POST /api/push/session/cancel Bearer 取消推送定时器
GET /api/push/session/state Bearer 获取推送定时器状态
POST /api/push/session/update-settings Bearer 更新推送定时器设置

详细的认证流程参见 Authentication-System,数据同步协议参见 Cloud-Sync,Hook 系统参见 Hook-System

Clone this wiki locally