feat(server/devices): users.max_devices_override 单用户设备上限覆盖(NULL走套餐默认)+ 迁移000025
ci-pangolin / Redline Scan — 脱敏 (UI 文案) (pull_request) Successful in 23s
ci-pangolin / Cleartext Scan — Android 禁明文 (pull_request) Successful in 16s
ci-pangolin / OpenAPI Sync Check (pull_request) Successful in 39s
ci-pangolin / Portable SQL — 可移植性 (mysql/sqlite) (pull_request) Successful in 19s
ci-pangolin / Flutter — analyze + test (pull_request) Successful in 36s
ci-pangolin / Codegen Drift — token 生成物未漂移 (pull_request) Successful in 3s
ci-pangolin / DS-flow — 原型/跨端同源/代码色单源闸 (pull_request) Successful in 4s
ci-pangolin / Go — build + test (pull_request) Failing after 13s
ci-pangolin / E2E Smoke — L4 进程级端到端 (pull_request) Failing after 10s
ci-pangolin / Go — integration (mysql/redis testcontainers) (pull_request) Failing after 5m8s
ci-pangolin / Golden — 视觉回归 (全量:components/auth/desktop/tablet) (pull_request) Failing after 19s
ci-pangolin / Lint — shellcheck (pull_request) Failing after 13m37s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01P9G7E3wmAYL9KeYCVZVsqu
This commit is contained in:
wangjia
2026-07-13 10:15:07 +08:00
parent 3d24451835
commit 07c339c18e
9 changed files with 260 additions and 7 deletions
+15 -1
View File
@@ -450,7 +450,21 @@ func (svc *Service) ResolvePlan(ctx context.Context, userID int64) (Plan, *apier
if err != nil {
return Plan{}, apierr.ErrInternal
}
return resolveEffectivePlan(time.Now().UTC(), status, subs, free)
plan, apiErr := resolveEffectivePlan(time.Now().UTC(), status, subs, free)
if apiErr != nil {
return Plan{}, apiErr
}
// Per-user device-cap override (migration 000025, users.max_devices_override):
// NULL/0 = no override, keep the plan-derived cap resolved above. Applied
// once here — the single funnel both the subscription path and the free
// fallback flow through — so every consumer (subscription middleware,
// /v1/me, CheckDeviceLimit) sees it uniformly.
if override, ok, err := svc.store.GetMaxDevicesOverride(ctx, userID); err != nil {
return Plan{}, apierr.ErrInternal
} else if ok {
plan.MaxDevices = override
}
return plan, nil
}
// CheckDeviceLimit prunes stale devices (best-effort churn cleanup), resolves the