feat(api): 账号环境指纹更新端点——PUT /creator/accounts/:id/fingerprint 单独保存并按需重启

- Store.UpdateAccountFingerprint:更新账号绑定环境指纹,seed 保持账号派生值,携带代理直接拒绝(门禁对齐 CreateBoundEnv)
- 运行中的环境先停后启以应用新指纹,未运行仅落库下次启动生效;账号资源锁防并发冲突
- accountEnvironmentView 内联 fingerprint 回显,编辑页预填数据源
- 覆盖:store 层 seed 保留/校验拒绝/JSON 往返、路由畸形入参、PG 端到端(保存/回显/持久化/400/404)
This commit is contained in:
2026-09-30 17:12:56 +08:00
parent 50aada884f
commit 2c4fe307f7
8 changed files with 321 additions and 1 deletions
+60
View File
@@ -194,6 +194,21 @@ func registerCreatorWithServices(app *fiber.App, store *creator.Store, phaseASto
}
return c.JSON(profile)
})
// 编辑页指纹浏览器环境:单独保存;运行中的环境停后以新指纹重启,未运行仅落库下次启动生效。
app.Put("/api/creator/accounts/:id/fingerprint", func(c fiber.Ctx) error {
var input hub.Fingerprint
if err := decodeCreator(c, &input); err != nil {
return creatorError(c, err)
}
if hubStore == nil {
return c.Status(fiber.StatusServiceUnavailable).JSON(map[string]string{"error": "environment store unavailable"})
}
view, err := updateAccountEnvironmentFingerprint(c.Context(), hubStore, c.Params("id"), input)
if err != nil {
return creatorError(c, err)
}
return c.JSON(view)
})
app.Put("/api/creator/accounts/:id/tags", func(c fiber.Ctx) error {
var input struct {
Tags []string `json:"tags"`
@@ -739,6 +754,51 @@ func startCreatorEnvironment(ctx context.Context, store HubStore, environment hu
return startBrowserRuntime(ctx, store, defaultNetworkExitProbe(), environment, finish)
}
// updateAccountEnvironmentFingerprint 编辑页指纹浏览器环境单独保存:
// 运行中的环境先停后启以应用新指纹;未运行仅落库,下次启动自然生效。
// seed 属账号派生、代理属出口管理,Store 层拒绝透传入参携带。
func updateAccountEnvironmentFingerprint(ctx context.Context, hubStore HubStore, accountID string, input hub.Fingerprint) (map[string]any, error) {
unlock, err := lockAccountResources(ctx, hubStore, accountID)
if err != nil {
return nil, fmt.Errorf("%w: lock account environment: %v", creator.ErrUnavailable, err)
}
defer unlock()
environment, err := hubStore.GetEnvironmentContextForAccount(ctx, accountID)
if errors.Is(err, hub.ErrNotFound) {
return nil, creator.ErrNotFound
}
if err != nil {
return nil, fmt.Errorf("%w: load account environment: %v", creator.ErrUnavailable, err)
}
restart := environment.RuntimeID != ""
if restart {
if err := stopEnvironmentRuntime(ctx, hubStore, environment); err != nil {
return nil, fmt.Errorf("%w: stop environment before fingerprint update: %v", creator.ErrUnavailable, err)
}
}
environment, err = hubStore.UpdateAccountFingerprint(ctx, accountID, input)
if errors.Is(err, hub.ErrInvalid) {
return nil, creator.ErrInvalid
}
if errors.Is(err, hub.ErrNotFound) {
return nil, creator.ErrNotFound
}
if err != nil {
return nil, fmt.Errorf("%w: persist fingerprint: %v", creator.ErrUnavailable, err)
}
if restart {
if err := startCreatorEnvironment(ctx, hubStore, environment); err != nil {
return nil, fmt.Errorf("%w: restart environment with updated fingerprint: %v", creator.ErrUnavailable, err)
}
environment, err = hubStore.GetEnvironmentContextForAccount(ctx, accountID)
if err != nil {
return nil, fmt.Errorf("%w: reload environment after fingerprint restart: %v", creator.ErrUnavailable, err)
}
}
return accountEnvironmentView(environment), nil
}
func creatorLoginQRCode(ctx context.Context, store *creator.Store, phaseAStore *accountdomain.Store, hubStore HubStore, accountID string) (map[string]any, error) {
if store == nil || phaseAStore == nil || hubStore == nil || strings.TrimSpace(accountID) == "" {
return nil, creator.ErrUnavailable