Bytebase 统一实例许可(Unified Instance License)实现指南:从后端判定到前端呈现的全链路落地
2026/9/15 13:02:31 网站建设 项目流程

Bytebase 统一实例许可(Unified Instance License)实现指南:从后端判定到前端呈现的全链路落地

【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase

本文以 docs/superpowers/plans/2026-04-28-unified-instance-license.md 为骨架,讲解 Bytebase 如何把"注册实例数 ≤ 激活实例数"的许可(License)统一表现为"一个数字"的实例许可模式:后端只需一个集中式判定助手即可计算有效激活、不必改动存储中的实例元数据,前端订阅 Store 同步镜像同一规则,让功能开关与设置页在统一模式下隐藏"分配(assignment)"类 UI,同时保留旧式"拆分额度(split-cap)"许可的既有行为。读完本文,你将掌握这套方案的统一判定规则、Go 后端落地步骤、Connect v1 API 输出层改造、Pinia 订阅 Store 派生逻辑,以及前后端回归测试的完整验证命令。


1. 方案背景:为什么要引入统一实例许可

Bytebase 的实例许可(Instance License)历史上采用"双额度"模型:一张许可同时携带**注册实例数(Instances)激活实例数(ActiveInstances)**两个上限。注册额度决定工作区能接入多少实例,激活额度决定其中有多少实例能启用数据脱敏(FEATURE_DATA_MASKING)、只读连接(FEATURE_INSTANCE_READ_ONLY_CONNECTION)、外部密钥管理(FEATURE_EXTERNAL_SECRET_MANAGER)等实例级(instance-gated)功能。用户需要手动把激活额度"分配"给具体实例,设置页也因此存在"实例许可分配表(InstanceAssignmentSheet)"这类面向分配操作的 UI。

新发行的许可则是"单一数字"模型:注册上限与激活上限相等(即Instances == ActiveInstances),每个可注册的实例天然就是已激活的,分配操作不再有意义。

本计划的Goal一句话概括为:

让"有效注册上限 ≤ 有效激活上限"的许可,在行为与呈现上都表现得像一个单数字实例许可。

Architecture则明确了两条原则:

  1. 后端只添加一个集中式许可模式判定助手(license-mode helper),用它在不修改(mutate)已存储实例元数据的前提下计算"有效激活";
  2. 前端订阅 Store 镜像同一"有效上限比较"规则,使功能守卫(feature guards)与设置页在统一模式下隐藏面向分配的 UI,而旧式拆分额度许可保持现状。

Tech Stack覆盖 Go 后端服务与测试、Connect v1 API、Pinia/Vue 订阅 Store、React 设置页与组件、Vitest 前端测试。


2. 统一判定规则:核心逻辑与表驱动测试

统一模式的判定只需一行比较:

func isUnifiedInstanceLimit(instanceLimit, activatedInstanceLimit int) bool { return instanceLimit <= activatedInstanceLimit }

即:当"可注册的实例上限"不超过"可激活的实例上限"时,许可整体表现为统一模式。该纯函数位于 backend/enterprise/license.go(当前仓库中已落地为isUnifiedInstanceLimit)。

计划要求以表驱动测试覆盖所有边界组合(backend/enterprise/license_test.go,包名enterprise):

package enterprise import ( "math" "testing" ) func TestIsUnifiedInstanceLimit(t *testing.T) { tests := []struct { name string instanceLimit int activatedLimit int want bool }{ {name: "equal finite caps", instanceLimit: 10, activatedLimit: 10, want: true}, {name: "activated cap larger than registration cap", instanceLimit: 10, activatedLimit: 20, want: true}, {name: "split cap", instanceLimit: 50, activatedLimit: 20, want: false}, {name: "unlimited both sides", instanceLimit: math.MaxInt, activatedLimit: math.MaxInt, want: true}, {name: "unlimited registration finite activation", instanceLimit: math.MaxInt, activatedLimit: 20, want: false}, {name: "finite registration unlimited activation", instanceLimit: 20, activatedLimit: math.MaxInt, want: true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := isUnifiedInstanceLimit(tt.instanceLimit, tt.activatedLimit); got != tt.want { t.Fatalf("isUnifiedInstanceLimit(%d, %d) = %v, want %v", tt.instanceLimit, tt.activatedLimit, got, tt.want) } }) } }

六种组合的语义对照:

注册上限激活上限判定结果含义
1010统一模式单数字许可(新旧等额许可的典型形态)
1020统一模式激活额度充足,所有可注册实例都可被激活
5020拆分模式经典 split-cap,需要分配激活额度
无限无限统一模式两端均无上限
无限20拆分模式可注册无限但可激活有限,仍受分配约束
20无限统一模式注册有限而激活无限,实质也是单数字

运行该测试(按计划的 TDD 顺序,先红后绿):

go test -v -count=1 ./backend/enterprise -run ^TestIsUnifiedInstanceLimit$

2.1 上限取值细节:从订阅对象到整型上限

判定使用的两个上限来自LicenseService的两个方法(backend/enterprise/license.go):

  • GetInstanceLimit:优先取订阅的Instances(大于 0 时直接返回);否则回落到plan.yaml中各计划的maximumInstanceCount,其中 ENTERPRISE 计划配置为-1,代码里会把-1归一化为math.MaxInt("无限")。可对照 backend/enterprise/plan.yaml 中 FREE/TEAM 为maximumInstanceCount: 10、ENTERPRISE 为-1的配置。
  • GetActivatedInstanceLimit:取订阅的ActiveInstances,若小于 0 同样归一化为math.MaxInt

两个上限最终都归一化到int域做比较,因此math.MaxInt作为"无限"的哨兵值贯穿始终。所有上限读取都经由LoadEffectiveSubscription,该函数带有基于expirable.LRU的订阅缓存与singleflight防击穿,缺失/无效/不可读的许可回落到 Free 计划。


3. 后端 LicenseService:统一模式助手与有效激活

在纯函数之上,计划新增两个面向业务的方法:

// IsUnifiedInstanceLicense returns whether every registrable instance is effectively activated. func (s *LicenseService) IsUnifiedInstanceLicense(ctx context.Context, workspaceID string) bool { return isUnifiedInstanceLimit( s.GetInstanceLimit(ctx, workspaceID), s.GetActivatedInstanceLimit(ctx, workspaceID), ) }

当前仓库中还进一步封装了"实例是否有效激活"的判定(IsInstanceEffectivelyActivated),它把存储中的激活标志统一许可模式做了或运算:

func (s *LicenseService) IsInstanceEffectivelyActivated(ctx context.Context, workspaceID string, instance *store.InstanceMessage) bool { if instance == nil { return false } return instance.Metadata.GetActivation() || s.IsUnifiedInstanceLicense(ctx, workspaceID) }

这意味着:在统一模式下,即使实例的存储元数据Activation: false,也被视为已激活;而在拆分模式下,仍必须依赖实例自身的激活标志。

3.1 功能门控:IsFeatureEnabledForInstance 使用有效激活

计划要求把IsFeatureEnabledForInstance的最终激活检查改为:

if s.IsUnifiedInstanceLicense(ctx, workspaceID) { return nil } if !instance.Metadata.GetActivation() { return errors.Errorf(`feature "%s" is not available for instance %s, please assign license to the instance to enable it`, f.String(), instance.ResourceID) } return nil

即统一模式下不再检查存储激活标志、直接放行;拆分模式下仍保留"请为该实例分配许可"的错误提示。当前仓库的实现通过IsInstanceEffectivelyActivated收敛了这一逻辑:FREE 计划只做计划级检查(不检查实例许可),付费计划先做计划级功能检查,再检查有效激活。

计划配套的测试通过直接向LicenseService.cache注入订阅来构造场景(利用同包测试可直取私有字段),并模拟一个Activation: false的实例:

func newTestLicenseService(sub *v1pb.Subscription) *LicenseService { s := &LicenseService{ cache: expirable.NewLRUstring, *v1pb.Subscription, } s.cache.Add(licenseCacheKey("test-workspace"), sub) return s } func TestIsFeatureEnabledForInstanceUnifiedLicense(t *testing.T) { ctx := context.Background() instance := &store.InstanceMessage{ ResourceID: "prod", Workspace: "test-workspace", Metadata: &storepb.Instance{ Activation: false, }, } service := newTestLicenseService(&v1pb.Subscription{ Plan: v1pb.PlanType_ENTERPRISE, Instances: 10, ActiveInstances: 10, }) if err := service.IsFeatureEnabledForInstance(ctx, "test-workspace", v1pb.PlanFeature_FEATURE_DATA_MASKING, instance); err != nil { t.Fatalf("unified license should enable feature for inactive stored instance: %v", err) } } func TestIsFeatureEnabledForInstanceSplitLicense(t *testing.T) { ctx := context.Background() instance := &store.InstanceMessage{ ResourceID: "prod", Workspace: "test-workspace", Metadata: &storepb.Instance{ Activation: false, }, } service := newTestLicenseService(&v1pb.Subscription{ Plan: v1pb.PlanType_ENTERPRISE, Instances: 50, ActiveInstances: 20, }) if err := service.IsFeatureEnabledForInstance(ctx, "test-workspace", v1pb.PlanFeature_FEATURE_DATA_MASKING, instance); err == nil { t.Fatal("split license should still require stored activation") } }

两个用例形成对照:Instances=10 / ActiveInstances=10时未激活实例也可用脱敏功能;Instances=50 / ActiveInstances=20时未激活实例被拒绝。

3.2 发证侧回归:CreateLicense 等额 Claims

统一许可的发行端也需要回归保护:签发许可时必须保证ActiveInstancesInstances相等。计划把CreateLicense中字面量的Claims构造抽取为独立函数,并在发证时调用它:

func newLicenseClaims(params *LicenseParams) *Claims { return &Claims{ Plan: params.Plan, Seats: params.Seats, ActiveInstances: params.Instances, Instances: params.Instances, WorkspaceID: params.WorkspaceID, } }
c := newLicenseClaims(params)

注意ActiveInstances直接取自params.Instances——这正是"等额"的保证点。配套回归测试:

func TestCreateLicenseUsesEqualInstanceClaims(t *testing.T) { claims := newLicenseClaims(&LicenseParams{ Plan: v1pb.PlanType_ENTERPRISE.String(), Seats: 5, Instances: 10, WorkspaceID: "test-workspace", }) if claims.Instances != 10 { t.Fatalf("Instances = %d, want 10", claims.Instances) } if claims.ActiveInstances != 10 { t.Fatalf("ActiveInstances = %d, want 10", claims.ActiveInstances) } }

该函数与测试同样已落地于 backend/enterprise/license.go 与 backend/enterprise/license_test.go。许可证 JWT 的解析校验逻辑(parseLicenseUncheckedExpiry)会校验签名方法为 RSA、kid版本、iss/aud、计划类型与 workspaceId 归属,最终把Claims映射为v1pb.SubscriptionActiveInstances/Instances/Seats等字段,供上层统一读取。


4. 实例与 Actuator API:只算不存的"有效激活"

后端 API 层的原则是:响应中呈现有效激活,但绝不改写存储。计划拆成三步。

4.1 转换助手:覆盖响应中的 Activation 字段

在 backend/api/v1/instance_service_converter.go 增加一个轻量助手,它先走既有转换、再覆盖激活字段:

func convertToV1InstanceWithEffectiveActivation(instance *store.InstanceMessage, effectiveActivation bool) *v1pb.Instance { result := convertToV1Instance(instance) result.Activation = effectiveActivation return result }

当前仓库中该文件的实际形态是convertToV1Instance(instance *store.InstanceMessage, activation bool),激活作为显式入参传入并写入响应对象的Activation字段;而反向的convertToStoreInstance(请求 → 存储)仍原样保留请求带来的激活值——create/update 请求语义不受影响,这正是"不改存储"的落点。

4.2 InstanceService 响应转换:统一模式下恒为已激活

在 backend/api/v1/instance_service.go 增加服务方法,把分散的响应转换调用点统一收敛:

func (s *InstanceService) convertToV1Instance(ctx context.Context, instance *store.InstanceMessage) *v1pb.Instance { if s.licenseService.IsUnifiedInstanceLicense(ctx, common.GetWorkspaceIDFromContext(ctx)) { return convertToV1InstanceWithEffectiveActivation(instance, true) } return convertToV1Instance(instance) }

然后替换所有响应转换调用点,例如把:

result := convertToV1Instance(instance)

替换为:

result := s.convertToV1Instance(ctx, instance)

列表场景同样处理:

ins := convertToV1Instance(instance)

替换为:

ins := s.convertToV1Instance(ctx, instance)

从当前仓库的 instance_service.go 可以看到,GetInstanceCreateInstanceUpdateInstance、样本项目实例接口等都已统一走s.convertToV1Instance(ctx, ...);列表接口则直接调用convertToV1Instance(instance, s.licenseService.IsInstanceEffectivelyActivated(ctx, workspaceID, instance))。两种写法在统一模式下都等价地返回Activation = true

4.3 配额检查:统一模式跳过激活配额

创建/更新实例时若触发了激活(Metadata.Activation == true),原本会做激活配额校验;统一模式下该校验应整体跳过。计划给出的守卫写法:

if instanceMessage.Metadata.GetActivation() && !s.licenseService.IsUnifiedInstanceLicense(ctx, workspaceID) { activatedInstanceLimit := s.licenseService.GetActivatedInstanceLimit(ctx, workspaceID) count, err := s.store.GetActivatedInstanceCount(ctx, workspaceID) if err != nil { return nil, connect.NewError(connect.CodeInternal, err) } if count >= activatedInstanceLimit { return nil, connect.NewError(connect.CodeResourceExhausted, errors.Errorf(instanceExceededError, activatedInstanceLimit)) } }

更新路径同理,以updateActivation && !...IsUnifiedInstanceLicense(...)作为守卫。需要把原先无条件计算的activatedInstanceLimit := ...移入守卫分支内,避免统一模式下无谓地读取额度。

当前仓库把这段逻辑收敛为checkActivationLimit(ctx, workspaceID, activating bool)助手:!activating || IsUnifiedInstanceLicense(...)时直接返回 nil,否则计算GetActivatedInstanceCount并与GetActivatedInstanceLimit比较,超限时返回connect.CodeResourceExhaustedinstanceExceededError"activation instance count has reached the limit (%v)")。

4.4 Actuator 统计:统一模式下"全部注册即全部激活"

Actuator 服务上报的ActivatedInstanceCount在统一模式下应等于全部实例数。计划给出:

activeInstanceCount, err := s.store.CountActiveInstances(ctx, workspaceID) if err != nil { return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to count total instance")) } serverInfo.TotalInstanceCount = int32(activeInstanceCount) if s.licenseService.IsUnifiedInstanceLicense(ctx, workspaceID) { serverInfo.ActivatedInstanceCount = int32(activeInstanceCount) } else { activatedInstanceCount, err := s.store.GetActivatedInstanceCount(ctx, workspaceID) if err != nil { return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to count activated instance")) } serverInfo.ActivatedInstanceCount = int32(activatedInstanceCount) }

这段逻辑在 backend/api/v1/actuator_service.go 已落地,且保留了附近无关的 actuator 字段不受影响。这也解释了前端为何能通过totalInstanceCount == activatedInstanceCount快速判断"是否存在未分配许可的实例"。

4.5 编译级验证

对 API 包做编译验证(该包可能依赖外部服务,无法直接跑单测时至少保证编译通过):

go test -v -count=1 ./backend/api/v1 -run '^(TestNonExistent)$' # 若依赖外部服务无法干净运行,退化为纯编译检查: go test -run '^$' ./backend/api/v1

5. 前端订阅 Store:镜像同一"有效上限"规则

前端核心是让订阅 Store 用与后端完全相同的规则派生出统一模式。

计划给出的版本位于frontend/src/store/modules/v1/subscription.ts注意:从当前仓库结构看,该实现实际落在 frontend/src/stores/app/workspace.ts,属于 Pinia store 体系,其instanceCountLimit/instanceLicenseCount/hasUnifiedInstanceLicense的语义与计划一致):

instanceLicenseCount之后新增派生值:

const hasUnifiedInstanceLicense = computed(() => { return instanceCountLimit.value <= instanceLicenseCount.value; });

其中instanceCountLimit对应后端的GetInstanceLimit(优先取订阅instances,否则回落计划默认值),instanceLicenseCount对应ActiveInstances(小于 0 视为无限,即Number.MAX_VALUE)。比较式instanceCountLimit <= instanceLicenseCount与后端isUnifiedInstanceLimit完全一致。

然后让功能守卫使用它。hasInstanceFeature更新为:

return checkInstanceFeature( currentPlan.value, feature, hasUnifiedInstanceLicense.value || instance.activation );

instanceMissingLicense更新为:

if (hasUnifiedInstanceLicense.value) { return false; } return hasFeature(feature) && !instance.activation;

并把hasUnifiedInstanceLicense加入 Store 的返回 getters:

hasUnifiedInstanceLicense,

对照当前仓库的 workspace.ts,hasUnifiedInstanceLicense为普通 getter,instanceMissingLicense已包含统一模式短路,hasInstanceFeature使用hasUnifiedInstanceLicense() || instance.activation,同时还派生了一个hasSplitInstanceLicense!isFreePlan() && !hasUnifiedInstanceLicense()),进一步印证"拆分模式"与"统一模式"互斥的模型。类型检查:

pnpm --dir frontend type-check

6. 前端呈现层:统一模式下隐藏分配型 UI

呈现层的三条改动共同保证:统一模式下用户不再看到任何"分配许可"的操作入口。

6.1 订阅设置页:单数字配额,无分配表

在 frontend/src/react/pages/settings/SubscriptionPage.tsx(当前仓库对应 frontend/src/routes/workspace/SubscriptionPage.tsx)读取 Store 模式:

const hasUnifiedInstanceLicense = useVueState( () => subscriptionStore.hasUnifiedInstanceLicense );

传入InstanceLicenseStats,并在 FREE 或统一模式下渲染单数字:

function InstanceLicenseStats({ planType, hasUnifiedInstanceLicense, instanceCountLimit, activatedCount, totalLicenseCount, onManageInstanceLicenses, }: { planType: string; hasUnifiedInstanceLicense: boolean; instanceCountLimit: number; activatedCount: number; totalLicenseCount: string; onManageInstanceLicenses: () => void; }) { const { t } = useTranslation(); if (planType === "FREE" || hasUnifiedInstanceLicense) { return ( <div className="flex flex-col text-left"> <dt className="text-main">{t("subscription.max-instance-count")}</dt> <div className="mt-1 text-4xl">{instanceCountLimit}</div> </div> ); }

同时,仅非统一模式渲染分配面板:

{!hasUnifiedInstanceLicense && ( <InstanceAssignmentSheet open={showInstanceAssignmentSheet} onOpenChange={setShowInstanceAssignmentSheet} /> )}

6.2 FeatureAttention:无"分配许可"提示与动作

在 frontend/src/react/components/FeatureAttention.tsx(当前仓库对应 frontend/src/components/FeatureAttention.tsx)读取模式:

const hasUnifiedInstanceLicense = useVueState( () => subscriptionStore.hasUnifiedInstanceLicense );

"是否存在未分配许可的实例"的条件中加入统一模式否定:

const existInstanceWithoutLicense = useVueState( () => !subscriptionStore.hasUnifiedInstanceLicense && actuatorStore.totalInstanceCount > actuatorStore.activatedInstanceCount && instanceLimitFeature.has(feature) );

分配面板仅在非统一模式下渲染:

{!hasUnifiedInstanceLicense && ( <InstanceAssignmentSheet open={showInstanceAssignment} selectedInstanceList={instance ? [instance.name] : []} onOpenChange={setShowInstanceAssignment} /> )}

当前仓库实现与计划一致:existInstanceWithoutLicense!hasUnifiedInstanceLicense && totalInstanceCount > activatedInstanceCount && instanceLimitFeature.has(feature)构成,且actionText分支中"分配许可"文案同样以!hasUnifiedInstanceLicense为前提。

6.3 实例表单:隐藏激活开关

在 frontend/src/react/components/instance/InstanceFormBody.tsx(当前仓库对应 frontend/src/components/instance/InstanceFormBody.tsx)读取模式:

const hasUnifiedInstanceLicense = subscriptionStore.hasUnifiedInstanceLicense;

激活开关的渲染条件收紧为:

{subscriptionStore.currentPlan !== PlanType.FREE && !hasUnifiedInstanceLicense && allowEdit && (

即:FREE 之外、非统一模式、且有编辑权限时才展示激活开关。统一模式与 FREE 计划一样不再出现该开关。

6.4 前端校验

pnpm --dir frontend fix pnpm --dir frontend type-check pnpm --dir frontend test -- FeatureAttention

7. 聚焦回归测试:呈现层与 Store 双保险

7.1 FeatureAttention 统一模式测试

扩展 frontend/src/react/components/FeatureAttention.test.tsx,在 mock 的订阅 Store 中增加hasUnifiedInstanceLicense字段(beforeEach重置为false),并新增用例:

test("does not show assignment attention in unified instance license mode", () => { mocks.hasFeature.mockReturnValue(true); mocks.instanceMissingLicense.mockReturnValue(false); mocks.hasUnifiedInstanceLicense = true; mocks.totalInstanceCount = 2; mocks.activatedInstanceCount = 2; render(<FeatureAttention feature={PlanFeature.FEATURE_DATA_MASKING} />); expect(screen.queryByText("subscription.instance-assignment.assign-license")).not.toBeInTheDocument(); });

当前仓库的 FeatureAttention.test.tsx 已包含hasUnifiedInstanceLicense相关的 mock 状态。

7.2 Store 助手测试

新增frontend/src/store/modules/v1/subscription.test.ts(当前仓库对应为 app store 测试,语义一致):

import { create } from "@bufbuild/protobuf"; import { createPinia, setActivePinia } from "pinia"; import { beforeEach, describe, expect, test } from "vitest"; import { useSubscriptionV1Store } from "./subscription"; import { InstanceSchema } from "@/types/proto-es/v1/instance_service_pb"; import { PlanFeature, PlanType, SubscriptionSchema, } from "@/types/proto-es/v1/subscription_service_pb"; describe("useSubscriptionV1Store unified instance license", () => { beforeEach(() => { setActivePinia(createPinia()); }); test("computes unified mode from effective limits", () => { const store = useSubscriptionV1Store(); store.setSubscription( create(SubscriptionSchema, { plan: PlanType.ENTERPRISE, instances: 10, activeInstances: 10, }) ); expect(store.hasUnifiedInstanceLicense).toBe(true); store.setSubscription( create(SubscriptionSchema, { plan: PlanType.ENTERPRISE, instances: 50, activeInstances: 20, }) ); expect(store.hasUnifiedInstanceLicense).toBe(false); }); test("does not report missing instance license in unified mode", () => { const store = useSubscriptionV1Store(); store.setSubscription( create(SubscriptionSchema, { plan: PlanType.ENTERPRISE, instances: 10, activeInstances: 10, }) ); const inactiveInstance = create(InstanceSchema, { name: "instances/prod", title: "prod", activation: false, }); expect( store.instanceMissingLicense( PlanFeature.FEATURE_DATA_MASKING, inactiveInstance ) ).toBe(false); }); });

用例覆盖两点:10/10判定为统一模式、50/20判定为非统一模式;统一模式下未激活实例不再报告"缺许可"。

运行:

pnpm --dir frontend test -- FeatureAttention pnpm --dir frontend test -- subscription.test

8. 任务清单与最终验证

整份计划按 7 个任务推进,可直接作为实施与评审的追踪清单:

  • Task 1 后端统一许可助手:新增isUnifiedInstanceLimit纯函数与IsUnifiedInstanceLicense方法,配表驱动测试(backend/enterprise/license.go、backend/enterprise/license_test.go)。
  • Task 2 后端有效功能激活IsFeatureEnabledForInstance改用有效激活判定;抽取newLicenseClaims并保证CreateLicense等额签发;补两个门控测试与等额 claims 回归测试。
  • Task 3 实例与 Actuator API 输出convertToV1InstanceWithEffectiveActivation覆盖响应激活;统一模式下跳过激活配额检查;Actuator 的ActivatedInstanceCount在统一模式下等于总实例数(backend/api/v1/instance_service_converter.go、backend/api/v1/instance_service.go、backend/api/v1/actuator_service.go)。
  • Task 4 前端 Store 统一模式hasUnifiedInstanceLicense = instanceCountLimit <= instanceLicenseCount,并接入hasInstanceFeature/instanceMissingLicense
  • Task 5 前端呈现更新:订阅页单数字配额且不渲染分配面板;FeatureAttention 隐藏分配提示与动作;实例表单隐藏激活开关;不动无引用的WorkspaceInstanceLicenseStats.vue
  • Task 6 聚焦回归测试:FeatureAttention 统一模式用例 + Store 助手用例。
  • Task 7 最终验证:格式化、后端测试、lint、前端全量校验、后端构建、Git 状态检查。

最终验证命令集(按计划原文):

# Go 格式化 gofmt -w backend/enterprise/license.go backend/enterprise/license_test.go backend/api/v1/instance_service_converter.go backend/api/v1/instance_service.go backend/api/v1/actuator_service.go # 后端测试 go test -v -count=1 ./backend/enterprise go test -run '^$' ./backend/api/v1 # Lint(必要时先 --fix 再复跑) golangci-lint run --allow-parallel-runners # 前端全量校验 pnpm --dir frontend fix pnpm --dir frontend check pnpm --dir frontend type-check pnpm --dir frontend test # 后端构建 go build -ldflags "-w -s" -p=16 -o ./bytebase-build/bytebase ./backend/bin/server/main.go # 状态确认 git status --short git log --oneline -5

9. 设计要点回顾

  1. 单一判定规则贯穿前后端:后端instanceLimit <= activatedInstanceLimit与前端instanceCountLimit <= instanceLicenseCount是同一规则的镜像实现,任何一边改动都必须在另一边保持同步。
  2. 只读计算、绝不落库:有效激活只在 API 响应与功能门控层计算,实例存储元数据中的Activation保持原样,create/update 请求的激活语义不被覆盖。
  3. 呈现层三处收敛:设置页统计、功能提醒组件、实例表单的分配/激活入口统一以"非统一模式"为前提,避免用户面对无意义的操作。
  4. 测试覆盖双端:后端以表驱动 + 注入缓存覆盖边界与门控,前端以 Pinia + mock 覆盖派生值与呈现行为,最终以 lint、type-check、全量测试与构建收口。

该方案对"旧许可"保持完全兼容:只有满足"注册上限 ≤ 激活上限"的许可才进入统一模式,历史上50/20这类拆分额度许可的分配行为、配额检查与 UI 均不受影响——这正是计划中"legacy split-cap licenses keep current behavior"的落点。

【免费下载链接】bytebaseDatabase governance built for humans and agents — controlling changes and access across every major database.项目地址: https://gitcode.com/GitHub_Trending/by/bytebase

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询