accounts/docs/development/code-structure.md
2026-04-14 16:32:15 +08:00

566 lines
32 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Code Structure / 代码结构
## 中文
### 阅读顺序
建议按下面顺序阅读当前仓库主路径:
1. `cmd/accountsvc/main.go`
2. `api/api.go`
3. `internal/store/store.go`
4. `internal/auth/*`
5. `internal/service/*`
6. `internal/xrayconfig/*`
7. `internal/agentserver/registry.go`
8. `internal/agentmode/*`
9. `internal/agentproto/types.go`
### `cmd/accountsvc`(运行时装配)
**核心职责**
- 读取配置并决定运行模式:`server`、`agent`、`server-agent`。
- 装配主 store、GORM admin DB、mailer、token service、OAuth provider、agent registry、xray syncer。
- 确保 root / review / sandbox 账户与 RBAC schema 满足当前契约。
**关键非导出 owner**
| 符号 | 说明 | 主要输入 | 主要输出 |
| --- | --- | --- | --- |
| `rootCmd.RunE` | CLI 主入口,负责 mode dispatch | `configPath`、`logLevel` | 调用 `runServer` / `runAgent` / `runServerAndAgent` |
| `runServer(ctx, cfg, logger)` | server 模式总装配函数 | `context.Context`、`config.Config`、`*slog.Logger` | Gin server、后台循环、依赖注入 |
| `runAgent(ctx, cfg, logger)` | agent 模式装配函数 | 同上 | `agentmode.Run(...)` |
| `openAdminSettingsDB(cfg.Store)` | 打开并迁移 GORM DB | `config.Store` | `*gorm.DB`、cleanup 闭包 |
| `applyRBACSchema(ctx, db, driver)` | 写入 RBAC / users / agents / sessions 等 schema 与 seed | `context.Context`、`*gorm.DB`、driver 名 | error |
### `api`HTTP 控制层)
**文件**
- `api/api.go`主路由注册、auth/session/MFA/subscription/OAuth。
- `api/admin_*.go`admin metrics、agent status、sandbox assume/bind、用户管理。
- `api/accounting.go`account / traffic / policy / heartbeat 读写面。
- `api/xworkmate*.go`tenant/profile/secret/Vault 集成。
- `api/stripe.go`checkout / portal / webhook。
- `api/homepage_video.go`、`api/config_sync.go`、`api/internal_*.go`:辅助控制面接口。
**核心导出符号**
| 符号 | kind | 签名 / 字段 | 参数 | 返回 | 说明 |
| --- | --- | --- | --- | --- | --- |
| `Option` | func type | `type Option func(*handler)` | `*handler` | 无 | API 层依赖注入闭包。 |
| `WithStore` | func | `WithStore(st store.Store) Option` | `st`: 主业务 store | `Option` | 覆盖默认内存 store。 |
| `WithSessionTTL` | func | `WithSessionTTL(ttl time.Duration) Option` | `ttl`: session 生命周期 | `Option` | 控制会话签发 TTL。 |
| `WithEmailSender` | func | `WithEmailSender(sender EmailSender) Option` | `sender`: 邮件发送器 | `Option` | 注入真实或 mock mailer。 |
| `WithEmailVerification` | func | `WithEmailVerification(enabled bool) Option` | `enabled`: 是否要求邮件验证 | `Option` | 控制注册前邮箱验证逻辑。 |
| `WithEmailVerificationTTL` | func | `WithEmailVerificationTTL(ttl time.Duration) Option` | `ttl`: 验证码 TTL | `Option` | 覆盖默认邮箱验证码 TTL。 |
| `WithUserMetricsProvider` | func | `WithUserMetricsProvider(provider service.UserMetricsProvider) Option` | metrics provider | `Option` | 注入用户指标聚合器。 |
| `WithAgentStatusReader` | func | `WithAgentStatusReader(reader agentStatusReader) Option` | registry / status reader | `Option` | 注入 admin agent status 读面。 |
| `WithPasswordResetTTL` | func | `WithPasswordResetTTL(ttl time.Duration) Option` | reset TTL | `Option` | 控制密码重置 token 生命周期。 |
| `WithTokenService` | func | `WithTokenService(tokenService *auth.TokenService) Option` | token service | `Option` | 启用可选 JWT + session middleware。 |
| `WithOAuthProviders` | func | `WithOAuthProviders(providers map[string]auth.OAuthProvider) Option` | provider map | `Option` | 注册 GitHub / Google OAuth provider。 |
| `WithServerPublicURL` | func | `WithServerPublicURL(url string) Option` | public URL | `Option` | 供 sync config / VLESS URL 生成使用。 |
| `WithXrayConfigRenderer` | func | `WithXrayConfigRenderer(renderer func(*store.User) (string, string, []string, error)) Option` | 自定义 renderer | `Option` | 主要用于测试或替换 sync render。 |
| `WithOAuthFrontendURL` | func | `WithOAuthFrontendURL(url string) Option` | frontend URL | `Option` | 决定 OAuth callback 的前端跳转目标。 |
| `WithXWorkmateVaultService` | func | `WithXWorkmateVaultService(vaultService xworkmateVaultService) Option` | Vault backend | `Option` | 启用 XWorkmate secret 读写。 |
| `WithAgentRegistry` | func | `WithAgentRegistry(registry agentRegistry) Option` | agent registry | `Option` | 注入 agent auth 与 sandbox agent 读面。 |
| `WithGormDB` | func | `WithGormDB(db *gorm.DB) Option` | GORM DB | `Option` | admin / tenant / homepage video 使用。 |
| `WithStripeConfig` | func | `WithStripeConfig(cfg StripeConfig) Option` | Stripe 配置 | `Option` | 启用 Stripe 集成。 |
| `RegisterRoutes` | func | `RegisterRoutes(r *gin.Engine, opts ...Option)` | Gin engine、Option 列表 | 无 | 一次性挂载全部 HTTP 路由。 |
| `EmailMessage` | struct | `To []string`, `Subject string`, `PlainBody string`, `HTMLBody string` | N/A | N/A | API 层统一邮件消息体。 |
| `EmailSender` | interface | `Send(ctx context.Context, msg EmailMessage) error` | context、message | `error` | 邮件发送接口。 |
| `EmailSenderFunc` | func type | `func(ctx context.Context, msg EmailMessage) error` | 同上 | `error` | 函数适配器。 |
| `StripeConfig` | struct | `SecretKey`, `WebhookSecret`, `AllowedPriceIDs`, `FrontendURL` | N/A | N/A | Stripe 客户端构造参数。 |
| `XWorkmateVaultConfig` | struct | `Address`, `Token`, `Namespace`, `Mount`, `Timeout`, `HTTPClient` | N/A | N/A | XWorkmate Vault HTTP 客户端配置。 |
| `NewXWorkmateVaultService` | func | `NewXWorkmateVaultService(cfg XWorkmateVaultConfig) (xworkmateVaultService, error)` | Vault config | service、`error` | 根据配置决定返回 HTTP backend 或 `nil`。 |
**关键非导出 owner**
| 符号 | 说明 |
| --- | --- |
| `handler` | HTTP 层真正的状态拥有者,持有 store、session TTL、MFA challenge map、verification map、password reset map、OAuth exchange code map、metrics provider、token service、Vault service、Stripe client、agent registry 等。 |
| `respondError` | 标准错误信封输出函数,形如 `{"error":"code","message":"message"}`。 |
| `sanitizeUser` / `sanitizeSubscription` | 主 API 的稳定返回 shape 组装器。 |
| `buildXWorkmateProfileResponse` | XWorkmate profile / secret API 的统一响应组装器。 |
### `internal/store`(领域模型与持久化抽象)
**核心导出类型**
| 符号 | kind | 关键字段 / 方法 | 说明 |
| --- | --- | --- | --- |
| `User` | struct | `ID`, `Name`, `Email`, `Level`, `Role`, `Groups`, `Permissions`, `EmailVerified`, `PasswordHash`, `MFATOTPSecret`, `MFAEnabled`, `ProxyUUID`, `ProxyUUIDExpiresAt` | 主账号实体。 |
| `Subscription` | struct | `ID`, `UserID`, `Provider`, `PaymentMethod`, `Kind`, `PlanID`, `ExternalID`, `Status`, `Meta` | 订阅或账单关系。 |
| `Identity` | struct | `ID`, `UserID`, `Provider`, `ExternalID` | OAuth 身份映射。 |
| `Agent` | struct | `ID`, `Name`, `Groups`, `Healthy`, `LastHeartbeat`, `ClientsCount`, `SyncRevision` | controller 持久化的 agent 状态行。 |
| `TrafficStatCheckpoint` / `TrafficMinuteBucket` / `BillingLedgerEntry` / `AccountQuotaState` / `AccountBillingProfile` / `AccountPolicySnapshot` / `NodeHealthSnapshot` / `SchedulerDecision` | struct | 计费、流量、调度读模型 | 为 `/api/account/*``/api/admin/traffic/*` 提供事实层。 |
| `Tenant` / `TenantDomain` / `TenantMembership` / `XWorkmateProfile` / `XWorkmateSecretLocator` | struct | tenant / 域名 / 成员关系 / integration profile / secret locator 元数据 | 支撑 XWorkmate 多租户与 Vault locator 设计。 |
| `Store` | interface | 见下表 | 主业务持久化抽象。 |
**`Store` 接口方法分组**
- 用户与身份:
- `CreateUser(ctx context.Context, user *User) error`
- `GetUserByEmail(ctx context.Context, email string) (*User, error)`
- `GetUserByID(ctx context.Context, id string) (*User, error)`
- `GetUserByName(ctx context.Context, name string) (*User, error)`
- `UpdateUser(ctx context.Context, user *User) error`
- `CreateIdentity(ctx context.Context, identity *Identity) error`
- `ListUsers(ctx context.Context) ([]User, error)`
- `DeleteUser(ctx context.Context, id string) error`
- 订阅:
- `UpsertSubscription(ctx context.Context, subscription *Subscription) error`
- `ListSubscriptionsByUser(ctx context.Context, userID string) ([]Subscription, error)`
- `CancelSubscription(ctx context.Context, userID, externalID string, cancelledAt time.Time) (*Subscription, error)`
- 黑名单:
- `AddToBlacklist(ctx context.Context, email string) error`
- `RemoveFromBlacklist(ctx context.Context, email string) error`
- `IsBlacklisted(ctx context.Context, email string) (bool, error)`
- `ListBlacklist(ctx context.Context) ([]string, error)`
- 会话:
- `CreateSession(ctx context.Context, token, userID string, expiresAt time.Time) error`
- `GetSession(ctx context.Context, token string) (string, time.Time, error)`
- `DeleteSession(ctx context.Context, token string) error`
- Agent
- `UpsertAgent(ctx context.Context, agent *Agent) error`
- `GetAgent(ctx context.Context, id string) (*Agent, error)`
- `ListAgents(ctx context.Context) ([]*Agent, error)`
- `DeleteAgent(ctx context.Context, id string) error`
- `DeleteStaleAgents(ctx context.Context, staleThreshold time.Duration) (int, error)`
- 流量 / 计费 / 调度:
- `UpsertTrafficStatCheckpoint(ctx context.Context, checkpoint *TrafficStatCheckpoint) error`
- `GetTrafficStatCheckpoint(ctx context.Context, nodeID, accountUUID string) (*TrafficStatCheckpoint, error)`
- `ListTrafficStatCheckpoints(ctx context.Context) ([]TrafficStatCheckpoint, error)`
- `UpsertTrafficMinuteBucket(ctx context.Context, bucket *TrafficMinuteBucket) error`
- `ListTrafficMinuteBucketsByAccount(ctx context.Context, accountUUID string, start, end time.Time) ([]TrafficMinuteBucket, error)`
- `ListTrafficMinuteBuckets(ctx context.Context) ([]TrafficMinuteBucket, error)`
- `InsertBillingLedgerEntry(ctx context.Context, entry *BillingLedgerEntry) error`
- `ListBillingLedgerByAccount(ctx context.Context, accountUUID string, limit int) ([]BillingLedgerEntry, error)`
- `UpsertAccountQuotaState(ctx context.Context, state *AccountQuotaState) error`
- `GetAccountQuotaState(ctx context.Context, accountUUID string) (*AccountQuotaState, error)`
- `UpsertAccountBillingProfile(ctx context.Context, profile *AccountBillingProfile) error`
- `GetAccountBillingProfile(ctx context.Context, accountUUID string) (*AccountBillingProfile, error)`
- `UpsertAccountPolicySnapshot(ctx context.Context, snapshot *AccountPolicySnapshot) error`
- `GetLatestAccountPolicySnapshot(ctx context.Context, accountUUID string) (*AccountPolicySnapshot, error)`
- `UpsertNodeHealthSnapshot(ctx context.Context, snapshot *NodeHealthSnapshot) error`
- `ListLatestNodeHealthSnapshots(ctx context.Context) ([]NodeHealthSnapshot, error)`
- `InsertSchedulerDecision(ctx context.Context, decision *SchedulerDecision) error`
- `ListRecentSchedulerDecisions(ctx context.Context, limit int) ([]SchedulerDecision, error)`
- Tenant / XWorkmate
- `EnsureTenant(ctx context.Context, tenant *Tenant) error`
- `EnsureTenantDomain(ctx context.Context, domain *TenantDomain) error`
- `UpsertTenantMembership(ctx context.Context, membership *TenantMembership) error`
- `ResolveTenantByHost(ctx context.Context, host string) (*Tenant, *TenantDomain, error)`
- `ListTenantMembershipsByUser(ctx context.Context, userID string) ([]TenantMembership, error)`
- `GetTenantMembership(ctx context.Context, tenantID, userID string) (*TenantMembership, error)`
- `GetXWorkmateProfile(ctx context.Context, tenantID, userID, scope string) (*XWorkmateProfile, error)`
- `UpsertXWorkmateProfile(ctx context.Context, profile *XWorkmateProfile) error`
**规范化与角色辅助函数**
- `NormalizeTenantEdition(value string) string`
- `NormalizeTenantMembershipRole(value string) string`
- `NormalizeTenantDomainKind(value string) string`
- `NormalizeTenantDomainStatus(value string) string`
- `NormalizeXWorkmateProfileScope(value string) string`
- `NormalizeXWorkmateSecretLocator(locator *XWorkmateSecretLocator)`
- `NormalizeHostname(value string) string`
- `IsSharedTenantHost(host string) bool`
- `GenerateRandomTenantDomain() (string, error)`
- `IsRootRole(role string) bool`
- `IsAdminRole(role string) bool`
- `IsOperatorRole(role string) bool`
- `NewMemoryStore() Store`
- `NewMemoryStoreWithSuperAdminCounting() Store`
### `internal/auth`认证、OAuth、中间件
**核心导出符号**
| 符号 | 签名 / 字段 | 参数 | 返回 | 说明 |
| --- | --- | --- | --- | --- |
| `TokenPair` | `PublicToken`, `AccessToken`, `RefreshToken`, `TokenType`, `ExpiresIn` | N/A | N/A | JWT 相关返回体。 |
| `Claims` | `UserID`, `Email`, `Roles`, `MFA`, `jwt.RegisteredClaims` | N/A | N/A | access token claims。 |
| `TokenConfig` | `PublicToken`, `RefreshSecret`, `AccessSecret`, `AccessExpiry`, `RefreshExpiry`, `Store` | N/A | N/A | token service 配置。 |
| `TokenService` | token / secret / expiry / store 成员 | N/A | N/A | JWT 与 session fallback 中心。 |
| `NewTokenService` | `NewTokenService(config TokenConfig) *TokenService` | config | `*TokenService` | 构造 token service。 |
| `SetStore` | `SetStore(st store.Store)` | store | 无 | 为 AuthMiddleware 注入 session store。 |
| `ValidatePublicToken` | `ValidatePublicToken(publicToken string) bool` | public token | `bool` | 校验 public token。 |
| `GeneratePublicToken` | `GeneratePublicToken(userID, email string, roles []string) string` | user identity | `string` | 返回配置中的 public token。 |
| `GenerateTokenPair` | `GenerateTokenPair(userID, email string, roles []string) (*TokenPair, error)` | user identity | token pair、error | 生成 access + refresh token。 |
| `ValidateAccessToken` | `ValidateAccessToken(accessToken string) (*Claims, error)` | JWT string | claims、error | 解析 access token。 |
| `RefreshAccessToken` | `RefreshAccessToken(refreshToken string) (string, error)` | refresh token | new access token、error | 刷新 access token。 |
| `GetAccessTokenExpiry` | `GetAccessTokenExpiry() time.Duration` | 无 | duration | 返回 access token TTL。 |
| `OAuthUserProfile` | `ID`, `Email`, `Name`, `Verified` | N/A | N/A | 统一 OAuth 用户 profile。 |
| `OAuthProvider` | `AuthCodeURL(state string) string`, `Exchange(ctx, code) (*oauth2.Token, error)`, `FetchProfile(ctx, token) (*OAuthUserProfile, error)`, `Name() string` | N/A | N/A | OAuth provider 抽象。 |
| `GitHubProvider` / `GoogleProvider` | provider struct | N/A | N/A | 具体 OAuth provider。 |
| `NewGitHubProvider` | `NewGitHubProvider(clientID, clientSecret, redirectURL string) *GitHubProvider` | OAuth config | provider | GitHub OAuth 构造。 |
| `NewGoogleProvider` | `NewGoogleProvider(clientID, clientSecret, redirectURL string) *GoogleProvider` | OAuth config | provider | Google OAuth 构造。 |
| `AuthMiddleware` | `func (s *TokenService) AuthMiddleware() gin.HandlerFunc` | token service receiver | Gin middleware | JWT 失败后 fallback 到 session store。 |
| `RequireActiveUser` | `RequireActiveUser(s store.Store) gin.HandlerFunc` | store | middleware | 拒绝 suspended user。 |
| `InternalAuthMiddleware` | `InternalAuthMiddleware() gin.HandlerFunc` | 无 | middleware | 校验 `X-Service-Token`。 |
| `RequireMFA` | `RequireMFA() gin.HandlerFunc` | 无 | middleware | 要求 MFA verified。 |
| `RequireRole` | `RequireRole(role string) gin.HandlerFunc` | role | middleware | 角色检查。 |
| `GetUserID` / `GetEmail` / `GetRoles` / `IsMFAVerified` | context getter | `*gin.Context` | 基础值 | 从 Gin context 中读取 auth state。 |
| `HTTPHandler` / `Wrap` | 轻量 handler adapter | handler | `gin.HandlerFunc` | 包内辅助适配器。 |
### `internal/service`(管理面服务)
**admin settings**
- `type AdminSettings struct`
- 字段:`Version uint64`、`Matrix map[string]map[string]bool`
- `SetDB(d *gorm.DB)`
- 参数GORM DB
- 返回:无
- 作用:设置 service 层共享 DB 并清空缓存
- `GetAdminSettings(ctx context.Context) (AdminSettings, error)`
- 返回:当前 permission matrix 与 version
- `SaveAdminSettings(ctx context.Context, payload AdminSettings) (AdminSettings, error)`
- 返回:新版本 settings 或版本冲突错误
**homepage video**
- `type HomepageVideoEntry struct`
- 字段:`DomainKey`, `VideoURL`, `PosterURL`
- `type HomepageVideoSettings struct`
- 字段:`DefaultEntry HomepageVideoEntry`, `Overrides []HomepageVideoEntry`
- `GetHomepageVideoSettings(ctx context.Context) (HomepageVideoSettings, error)`
- `SaveHomepageVideoSettings(ctx context.Context, settings HomepageVideoSettings, updatedBy string) (HomepageVideoSettings, error)`
- `ResolveHomepageVideoEntry(ctx context.Context, host string) (HomepageVideoEntry, error)`
**user metrics**
- `type UserRepository interface`
- `ListUsers(ctx context.Context) ([]UserRecord, error)`
- `type SubscriptionProvider interface`
- `FetchSubscriptionStates(ctx context.Context, userIDs []string) (map[string]SubscriptionState, error)`
- `type UserRecord struct`
- `ID`, `CreatedAt`, `Active`
- `type SubscriptionState struct`
- `Active`, `ExpiresAt`
- `type UserMetricsService struct`
- 依赖:`Users UserRepository`, `Subscriptions SubscriptionProvider`, `DailyPeriods`, `WeeklyPeriods`
- `Compute(ctx context.Context) (UserMetrics, error)`
- 返回:`MetricsOverview` + `MetricsSeries`
### `internal/xrayconfig`(配置生成与同步)
| 符号 | 签名 / 字段 | 说明 |
| --- | --- | --- |
| `Definition` | `Base() (map[string]interface{}, error)` | Xray 基础模板抽象。 |
| `JSONDefinition` | `Raw []byte` | 以 JSON 文档实现 `Definition`。 |
| `Client` | `ID`, `Email`, `Flow` | Xray client 条目。 |
| `Generator` | `Definition`, `OutputPath`, `FileMode`, `Domain` | 根据 clients 生成配置文件。 |
| `Generate` | `Generate(clients []Client) error` | 渲染并原子写入文件。 |
| `Render` | `Render(clients []Client) ([]byte, error)` | 只返回 JSON不写盘。 |
| `GormClientSource` | `DB *gorm.DB`, `Logger *slog.Logger` | 从 users 表读取 client。 |
| `NewGormClientSource` | `NewGormClientSource(db *gorm.DB) (*GormClientSource, error)` | 构造 GORM source。 |
| `ClientSource` | `ListClients(ctx context.Context) ([]Client, error)` | `PeriodicSyncer` 依赖的源接口。 |
| `PeriodicOptions` | `Logger`, `Interval`, `Source`, `Generators`, `ValidateCommand`, `RestartCommand`, `Runner`, `OnSync` | syncer 配置。 |
| `PeriodicSyncer` | 周期构造体 | 周期性收敛配置文件。 |
| `SyncResult` | `Clients`, `Error`, `CompletedAt` | 单次同步结果。 |
| `NewPeriodicSyncer` | `NewPeriodicSyncer(opts PeriodicOptions) (*PeriodicSyncer, error)` | 构造 syncer。 |
| `Start` | `Start(ctx context.Context) (func(context.Context) error, error)` | 启动后台循环并返回 stop 闭包。 |
| `DefaultDefinition` / `TCPDefinition` / `XHTTPDefinition` | 模板函数 | 返回预置 Xray 模板。 |
| `VLESSTCPScheme` / `VLESSXHTTPScheme` | URI 模板函数 | 供 sync config / node URL 使用。 |
### `internal/agentmode`agent 运行时)
| 符号 | 签名 / 字段 | 参数 | 返回 | 说明 |
| --- | --- | --- | --- | --- |
| `ClientOptions` | `Timeout`, `InsecureSkipVerify`, `UserAgent`, `AgentID` | N/A | N/A | controller HTTP client 配置。 |
| `Client` | `baseURL`, `token`, `http`, `userAgent`, `agentID` | N/A | N/A | 认证 HTTP client。 |
| `NewClient` | `NewClient(baseURL, token string, opts ClientOptions) (*Client, error)` | controller URL、token、client options | client、error | 构造 controller 客户端。 |
| `ListClients` | `ListClients(ctx context.Context) (agentproto.ClientListResponse, error)` | context | response、error | 调用 `/api/agent-server/v1/users`。 |
| `ReportStatus` | `ReportStatus(ctx context.Context, report agentproto.StatusReport) error` | context、status report | error | 调用 `/api/agent-server/v1/status`。 |
| `Options` | `Logger`, `Agent config.Agent`, `Xray config.Xray` | N/A | N/A | agent mode 总配置。 |
| `Run` | `Run(ctx context.Context, opts Options) error` | context、options | error | 启动 agent 全链路:拉取 clients、生成 config、周期上报状态。 |
| `HTTPClientSource` | `client`, `tracker` | N/A | N/A | 把 controller API 适配成 `xrayconfig.ClientSource`。 |
| `NewHTTPClientSource` | `NewHTTPClientSource(client *Client, tracker *syncTracker) *HTTPClientSource` | client、tracker | source | 构造 HTTP source。 |
| `ListClients`source | `ListClients(ctx context.Context) ([]xrayconfig.Client, error)` | context | clients、error | 把 controller payload 转成 syncer 输入。 |
### `internal/agentserver`controller 侧 agent registry
| 符号 | 签名 / 字段 | 说明 |
| --- | --- | --- |
| `Credential` | `ID`, `Name`, `Token`, `Groups` | 配置层 agent 凭据。 |
| `Config` | `Credentials []Credential` | registry 构造参数。 |
| `Identity` | `ID`, `Name`, `Groups` | 认证成功后的 agent 身份。 |
| `StatusSnapshot` | `Agent Identity`, `Report agentproto.StatusReport`, `UpdatedAt time.Time` | agent 最近一次状态快照。 |
| `Registry` | credential digest、byID、statuses、sandboxAgents、store、logger | controller 内存注册表。 |
| `NewRegistry` | `NewRegistry(cfg Config) (*Registry, error)` | 构造并校验 credential 集。 |
| `SetStore` / `SetLogger` | 注入 store 与 logger | 持久化与日志配置。 |
| `Authenticate` | `Authenticate(token string) (*Identity, bool)` | 通过 token digest 查找 agent identity。 |
| `ReportStatus` | `ReportStatus(agent Identity, report agentproto.StatusReport)` | 更新内存快照并异步持久化到 store。 |
| `RegisterAgent` | `RegisterAgent(agentID string, groups []string) Identity` | 动态注册共享 token 下的 agent。 |
| `Load` | `Load(ctx context.Context) error` | 从 store 回填 agents / statuses。 |
| `Statuses` / `Agents` | 只读导出方法 | 返回当前 registry read model。 |
| `IsSandboxAgent` / `SetSandboxAgent` / `ClearSandboxAgents` | sandbox 标记管理 | 供 sandbox node binding 使用。 |
### `internal/agentproto`controller-agent DTO
- `type ClientListResponse struct`
- 字段:`Clients []xrayconfig.Client`, `Total int`, `GeneratedAt time.Time`, `Revision string`
- `type StatusReport struct`
- 字段:`AgentID`, `Healthy`, `Message`, `Users`, `SyncRevision`, `Xray XrayStatus`
- `type XrayStatus struct`
- 字段:`Running`, `Clients`, `LastSync`, `ConfigHash`, `NodeID`, `Region`, `LineCode`, `PricingGroup`, `StatsEnabled`, `XrayRevision`
## English
### Recommended Reading Order
Read the current core path in this order:
1. `cmd/accountsvc/main.go`
2. `api/api.go`
3. `internal/store/store.go`
4. `internal/auth/*`
5. `internal/service/*`
6. `internal/xrayconfig/*`
7. `internal/agentserver/registry.go`
8. `internal/agentmode/*`
9. `internal/agentproto/types.go`
### `cmd/accountsvc` (runtime composition)
**Core responsibility**
- Loads config and chooses runtime mode: `server`, `agent`, or `server-agent`.
- Composes the primary store, GORM admin DB, mailer, token service, OAuth providers, agent registry, and xray syncer.
- Normalizes root / review / sandbox accounts plus RBAC schema.
**Key non-export owners**
| Symbol | Purpose | Main inputs | Main outputs |
| --- | --- | --- | --- |
| `rootCmd.RunE` | CLI entry point and mode dispatch | `configPath`, `logLevel` | Calls `runServer`, `runAgent`, or `runServerAndAgent` |
| `runServer(ctx, cfg, logger)` | Main server composition path | `context.Context`, `config.Config`, `*slog.Logger` | Gin server, background loops, injected dependencies |
| `runAgent(ctx, cfg, logger)` | Agent-mode composition path | same | `agentmode.Run(...)` |
| `openAdminSettingsDB(cfg.Store)` | Opens and migrates the GORM-backed admin DB | `config.Store` | `*gorm.DB`, cleanup function |
| `applyRBACSchema(ctx, db, driver)` | Creates and seeds RBAC / users / agents / sessions schema | `context.Context`, `*gorm.DB`, driver name | `error` |
### `api` (HTTP control layer)
**Files**
- `api/api.go`: main route registration plus auth / session / MFA / subscription / OAuth.
- `api/admin_*.go`: admin metrics, agent status, sandbox assume / bind, user operations.
- `api/accounting.go`: account usage / billing / policy / heartbeat reads.
- `api/xworkmate*.go`: tenant, profile, secret, and Vault-backed integration paths.
- `api/stripe.go`: checkout, portal, webhook.
- `api/homepage_video.go`, `api/config_sync.go`, `api/internal_*.go`: auxiliary control-plane endpoints.
**Core exported symbols**
- `type Option func(*handler)`
- `WithStore(st store.Store) Option`
- `WithSessionTTL(ttl time.Duration) Option`
- `WithEmailSender(sender EmailSender) Option`
- `WithEmailVerification(enabled bool) Option`
- `WithEmailVerificationTTL(ttl time.Duration) Option`
- `WithUserMetricsProvider(provider service.UserMetricsProvider) Option`
- `WithAgentStatusReader(reader agentStatusReader) Option`
- `WithPasswordResetTTL(ttl time.Duration) Option`
- `WithTokenService(tokenService *auth.TokenService) Option`
- `WithOAuthProviders(providers map[string]auth.OAuthProvider) Option`
- `WithServerPublicURL(url string) Option`
- `WithXrayConfigRenderer(renderer func(*store.User) (string, string, []string, error)) Option`
- `WithOAuthFrontendURL(url string) Option`
- `WithXWorkmateVaultService(vaultService xworkmateVaultService) Option`
- `WithAgentRegistry(registry agentRegistry) Option`
- `WithGormDB(db *gorm.DB) Option`
- `WithStripeConfig(cfg StripeConfig) Option`
- `RegisterRoutes(r *gin.Engine, opts ...Option)`
- `type EmailMessage struct`
- `type EmailSender interface`
- `type EmailSenderFunc`
- `type StripeConfig struct`
- `type XWorkmateVaultConfig struct`
- `NewXWorkmateVaultService(cfg XWorkmateVaultConfig) (xworkmateVaultService, error)`
**Key non-export owners**
- `handler`: the HTTP-layer state owner for store, session state, MFA challenges, email verification, password reset, OAuth exchange codes, metrics provider, token service, Vault service, Stripe client, and agent registry.
- `respondError`: emits the standard `{"error":"code","message":"message"}` envelope.
- `sanitizeUser` / `sanitizeSubscription`: stable user and subscription response shapers.
- `buildXWorkmateProfileResponse`: shared response builder for XWorkmate profile / secret APIs.
### `internal/store` (domain model and persistence abstraction)
**Key exported types**
- `User`
- `Subscription`
- `Identity`
- `Agent`
- `TrafficStatCheckpoint`
- `TrafficMinuteBucket`
- `BillingLedgerEntry`
- `AccountQuotaState`
- `AccountBillingProfile`
- `AccountPolicySnapshot`
- `NodeHealthSnapshot`
- `SchedulerDecision`
- `Tenant`
- `TenantDomain`
- `TenantMembership`
- `XWorkmateProfile`
- `XWorkmateSecretLocator`
- `Store`
**`Store` method groups**
- User / identity: `CreateUser`, `GetUserByEmail`, `GetUserByID`, `GetUserByName`, `UpdateUser`, `CreateIdentity`, `ListUsers`, `DeleteUser`
- Subscription: `UpsertSubscription`, `ListSubscriptionsByUser`, `CancelSubscription`
- Blacklist: `AddToBlacklist`, `RemoveFromBlacklist`, `IsBlacklisted`, `ListBlacklist`
- Session: `CreateSession`, `GetSession`, `DeleteSession`
- Agent: `UpsertAgent`, `GetAgent`, `ListAgents`, `DeleteAgent`, `DeleteStaleAgents`
- Traffic / billing / scheduler: `UpsertTrafficStatCheckpoint`, `GetTrafficStatCheckpoint`, `ListTrafficStatCheckpoints`, `UpsertTrafficMinuteBucket`, `ListTrafficMinuteBucketsByAccount`, `ListTrafficMinuteBuckets`, `InsertBillingLedgerEntry`, `ListBillingLedgerByAccount`, `UpsertAccountQuotaState`, `GetAccountQuotaState`, `UpsertAccountBillingProfile`, `GetAccountBillingProfile`, `UpsertAccountPolicySnapshot`, `GetLatestAccountPolicySnapshot`, `UpsertNodeHealthSnapshot`, `ListLatestNodeHealthSnapshots`, `InsertSchedulerDecision`, `ListRecentSchedulerDecisions`
- Tenant / XWorkmate: `EnsureTenant`, `EnsureTenantDomain`, `UpsertTenantMembership`, `ResolveTenantByHost`, `ListTenantMembershipsByUser`, `GetTenantMembership`, `GetXWorkmateProfile`, `UpsertXWorkmateProfile`
**Normalization and role helpers**
- `NormalizeTenantEdition`
- `NormalizeTenantMembershipRole`
- `NormalizeTenantDomainKind`
- `NormalizeTenantDomainStatus`
- `NormalizeXWorkmateProfileScope`
- `NormalizeXWorkmateSecretLocator`
- `NormalizeHostname`
- `IsSharedTenantHost`
- `GenerateRandomTenantDomain`
- `IsRootRole`
- `IsAdminRole`
- `IsOperatorRole`
- `NewMemoryStore`
- `NewMemoryStoreWithSuperAdminCounting`
### `internal/auth` (authentication, OAuth, middleware)
Core exported symbols:
- `TokenPair`
- `Claims`
- `TokenConfig`
- `TokenService`
- `NewTokenService(config TokenConfig) *TokenService`
- `SetStore(st store.Store)`
- `ValidatePublicToken(publicToken string) bool`
- `GeneratePublicToken(userID, email string, roles []string) string`
- `GenerateTokenPair(userID, email string, roles []string) (*TokenPair, error)`
- `ValidateAccessToken(accessToken string) (*Claims, error)`
- `RefreshAccessToken(refreshToken string) (string, error)`
- `GetAccessTokenExpiry() time.Duration`
- `OAuthUserProfile`
- `OAuthProvider`
- `GitHubProvider`
- `GoogleProvider`
- `NewGitHubProvider(clientID, clientSecret, redirectURL string) *GitHubProvider`
- `NewGoogleProvider(clientID, clientSecret, redirectURL string) *GoogleProvider`
- `AuthMiddleware() gin.HandlerFunc`
- `RequireActiveUser(s store.Store) gin.HandlerFunc`
- `InternalAuthMiddleware() gin.HandlerFunc`
- `RequireMFA() gin.HandlerFunc`
- `RequireRole(role string) gin.HandlerFunc`
- `GetUserID`, `GetEmail`, `GetRoles`, `IsMFAVerified`
- `HTTPHandler`
- `Wrap(handler HTTPHandler) gin.HandlerFunc`
### `internal/service` (admin-side services)
Admin settings:
- `AdminSettings`
- `SetDB`
- `GetAdminSettings`
- `SaveAdminSettings`
Homepage video:
- `HomepageVideoEntry`
- `HomepageVideoSettings`
- `GetHomepageVideoSettings`
- `SaveHomepageVideoSettings`
- `ResolveHomepageVideoEntry`
User metrics:
- `UserRepository`
- `SubscriptionProvider`
- `UserRecord`
- `SubscriptionState`
- `UserMetricsService`
- `UserMetricsProvider`
- `UserMetrics`
- `MetricsOverview`
- `MetricsSeries`
- `MetricsPoint`
- `Compute(ctx context.Context) (UserMetrics, error)`
### `internal/xrayconfig` (config generation and sync)
- `Definition`
- `JSONDefinition`
- `Client`
- `Generator`
- `Generate(clients []Client) error`
- `Render(clients []Client) ([]byte, error)`
- `GormClientSource`
- `NewGormClientSource(db *gorm.DB) (*GormClientSource, error)`
- `ClientSource`
- `PeriodicOptions`
- `PeriodicSyncer`
- `SyncResult`
- `NewPeriodicSyncer(opts PeriodicOptions) (*PeriodicSyncer, error)`
- `Start(ctx context.Context) (func(context.Context) error, error)`
- `DefaultDefinition`, `TCPDefinition`, `XHTTPDefinition`
- `VLESSTCPScheme`, `VLESSXHTTPScheme`
### `internal/agentmode` (agent runtime)
- `ClientOptions`
- `Client`
- `NewClient(baseURL, token string, opts ClientOptions) (*Client, error)`
- `ListClients(ctx context.Context) (agentproto.ClientListResponse, error)`
- `ReportStatus(ctx context.Context, report agentproto.StatusReport) error`
- `Options`
- `Run(ctx context.Context, opts Options) error`
- `HTTPClientSource`
- `NewHTTPClientSource(client *Client, tracker *syncTracker) *HTTPClientSource`
- `ListClients(ctx context.Context) ([]xrayconfig.Client, error)` on `HTTPClientSource`
### `internal/agentserver` (controller-side registry)
- `Credential`
- `Config`
- `Identity`
- `StatusSnapshot`
- `Registry`
- `NewRegistry(cfg Config) (*Registry, error)`
- `SetStore`
- `SetLogger`
- `Authenticate`
- `ReportStatus`
- `RegisterAgent`
- `Load`
- `Statuses`
- `Agents`
- `IsSandboxAgent`
- `SetSandboxAgent`
- `ClearSandboxAgents`
### `internal/agentproto` (controller-agent DTOs)
- `ClientListResponse`
- `StatusReport`
- `XrayStatus`