Files
creator-hub/internal/controlplane/api/account_environment.go
T
rogee bcc33fe497 feat(accounts): 账号即环境——创建即绑定、幂等补建、start 组合接口
- POST /api/phase-a/accounts 创建成功即绑定环境:alias=账号文本 ID、
  唯一网关自动取(缺失 404/多网关 409)、出口直连、seed 由账号 ID 派生
  (deriveSeed,数字主键落地后改 bigint id+1000);绑定失败 503 透传 account_id
- POST /api/phase-a/accounts/:id/environment 幂等补建:已绑定原样返回
- POST /api/phase-a/accounts/:id/start = resume + 启动环境(环境缺失自愈补建,
  审计对齐全);吊销账号 start → 409 readiness blocked;停止沿用 pause
- 测试:deriveSeed 纯单测(确定性/值域/固定向量)+ PG 集成覆盖
  绑定失败恢复、补建幂等、seed 断言、start 激活与冲突分支;
  RegisterAccountRoutes 参数升级为 HubStore,路由矩阵登记新端点
2026-09-28 20:10:50 +08:00

78 lines
2.9 KiB
Go
Raw 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.
package api
import (
"context"
"errors"
"hash/fnv"
hub "git.ipao.vip/rogee/creator-hub/internal/environment"
)
// 账号即环境:账号创建即绑定运行环境。
// 规则:alias = 账号文本 ID、唯一网关自动取、出口直连、seed 由账号 ID 派生。
// deriveSeed 由账号文本 ID 确定性派生 fingerprint seed(1001..2147483647)。
// 数字主键落地后改为 seed = 账号 bigint id + 1000,仅替换本函数。
func deriveSeed(accountID string) int64 {
hasher := fnv.New64a()
_, _ = hasher.Write([]byte(accountID))
return int64(hasher.Sum64()%(2147483647-1000)) + 1000
}
// soleGateway 返回平台唯一网关;零配置绑定要求恰有一个网关:
// 缺失返回 ErrNotFound,多个返回 ErrConflict(无法自动选择)。
func soleGateway(ctx context.Context, store HubStore) (hub.Gateway, error) {
gateways, err := store.ListGateways(ctx)
if err != nil {
return hub.Gateway{}, err
}
if len(gateways) == 0 {
return hub.Gateway{}, hub.ErrNotFound
}
if len(gateways) > 1 {
return hub.Gateway{}, hub.ErrConflict
}
return gateways[0], nil
}
// ensureAccountEnvironment 幂等补建账号环境:已绑定(任意出口)原样返回;
// 未绑定时以 alias=账号 ID、派生 seed、直连出口创建。
func ensureAccountEnvironment(ctx context.Context, store HubStore, accountID string) (hub.EnvironmentContext, bool, error) {
environment, err := store.GetEnvironmentContextForAccount(ctx, accountID)
if err == nil {
return environment, false, nil
}
if !errors.Is(err, hub.ErrNotFound) {
return hub.EnvironmentContext{}, false, err
}
gateway, err := soleGateway(ctx, store)
if err != nil {
return hub.EnvironmentContext{}, false, err
}
return store.CreateBoundEnv(ctx, hub.Env{
Alias: accountID, Name: accountID, Gateway: gateway.Name,
Fingerprint: hub.Fingerprint{Seed: deriveSeed(accountID)},
}, accountID, "")
}
// startAccountEnvironment 启动账号环境并记录审计对;环境缺失时幂等补建(创建即绑定的自愈路径)。
func startAccountEnvironment(ctx context.Context, store HubStore, accountID string) error {
environment, err := store.GetEnvironmentContextForAccount(ctx, accountID)
if errors.Is(err, hub.ErrNotFound) {
environment, _, err = ensureAccountEnvironment(ctx, store, accountID)
}
if err != nil {
return err
}
action := actionForEnvironment("start", environment)
if err := store.AppendEnvironmentAction(ctx, "environment_action_requested", action); err != nil {
return err
}
finish := func(outcome, reason string, current hub.EnvironmentContext) error {
action.Outcome, action.ReasonCode = outcome, reason
action.BindingVersion, action.NetworkExitID = current.BindingVersion, current.Exit.ID
return store.AppendEnvironmentAction(ctx, "environment_action_finished", action)
}
return startBrowserRuntime(ctx, store, defaultNetworkExitProbe(), environment, finish)
}