- 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,路由矩阵登记新端点
78 lines
2.9 KiB
Go
78 lines
2.9 KiB
Go
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)
|
||
}
|