diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml index 2596a18c9..345c76904 100644 --- a/.github/workflows/backend-ci.yml +++ b/.github/workflows/backend-ci.yml @@ -17,9 +17,10 @@ jobs: go-version-file: backend/go.mod check-latest: false cache: true + cache-dependency-path: backend/go.sum - name: Verify Go version run: | - go version | grep -q 'go1.25.7' + go version | grep -q 'go1.26.0' - name: Unit tests working-directory: backend run: make test-unit @@ -36,12 +37,14 @@ jobs: go-version-file: backend/go.mod check-latest: false cache: true + cache-dependency-path: backend/go.sum - name: Verify Go version run: | - go version | grep -q 'go1.25.7' + go version | grep -q 'go1.26.0' - name: golangci-lint uses: golangci/golangci-lint-action@v9 with: - version: v2.7 + version: v2.7.2 + install-mode: goinstall args: --timeout=5m working-directory: backend diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50bb73e0c..5b92aa495 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -115,7 +115,7 @@ jobs: - name: Verify Go version run: | - go version | grep -q 'go1.25.7' + go version | grep -q 'go1.26.0' # Docker setup for GoReleaser - name: Set up QEMU diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 05dd1d1af..38da7b162 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -22,7 +22,7 @@ jobs: cache-dependency-path: backend/go.sum - name: Verify Go version run: | - go version | grep -q 'go1.25.7' + go version | grep -q 'go1.26.0' - name: Run govulncheck working-directory: backend run: | diff --git a/DEV_GUIDE.md b/DEV_GUIDE.md index 541bf1faa..a0e226b14 100644 --- a/DEV_GUIDE.md +++ b/DEV_GUIDE.md @@ -53,7 +53,7 @@ npm install -g pnpm ### CI 要求 -- Go 版本必须是 **1.25.7** +- Go 版本必须是 **1.26.0** - 前端使用 `pnpm install --frozen-lockfile`,必须提交 `pnpm-lock.yaml` ### 本地测试命令 diff --git a/Dockerfile b/Dockerfile index c9fcf3017..f91254b6e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ # ============================================================================= ARG NODE_IMAGE=node:24-alpine -ARG GOLANG_IMAGE=golang:1.25.7-alpine +ARG GOLANG_IMAGE=golang:1.26.0-alpine ARG ALPINE_IMAGE=alpine:3.20 ARG GOPROXY=https://goproxy.cn,direct ARG GOSUMDB=sum.golang.google.cn diff --git a/README.md b/README.md index 36949b0a4..f4433feaf 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@
-[![Go](https://img.shields.io/badge/Go-1.25.7-00ADD8.svg)](https://golang.org/) +[![Go](https://img.shields.io/badge/Go-1.26.0-00ADD8.svg)](https://golang.org/) [![Vue](https://img.shields.io/badge/Vue-3.4+-4FC08D.svg)](https://vuejs.org/) [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-15+-336791.svg)](https://www.postgresql.org/) [![Redis](https://img.shields.io/badge/Redis-7+-DC382D.svg)](https://redis.io/) @@ -44,7 +44,7 @@ Sub2API is an AI API gateway platform designed to distribute and manage API quot | Component | Technology | |-----------|------------| -| Backend | Go 1.25.7, Gin, Ent | +| Backend | Go 1.26.0, Gin, Ent | | Frontend | Vue 3.4+, Vite 5+, TailwindCSS | | Database | PostgreSQL 15+ | | Cache/Queue | Redis 7+ | @@ -298,7 +298,7 @@ Build and run from source code for development or customization. #### Prerequisites -- Go 1.21+ +- Go 1.26.0 - Node.js 18+ - PostgreSQL 15+ - Redis 7+ diff --git a/README_CN.md b/README_CN.md index 1e0d1d62a..69b6dad4c 100644 --- a/README_CN.md +++ b/README_CN.md @@ -2,7 +2,7 @@
-[![Go](https://img.shields.io/badge/Go-1.25.7-00ADD8.svg)](https://golang.org/) +[![Go](https://img.shields.io/badge/Go-1.26.0-00ADD8.svg)](https://golang.org/) [![Vue](https://img.shields.io/badge/Vue-3.4+-4FC08D.svg)](https://vuejs.org/) [![PostgreSQL](https://img.shields.io/badge/PostgreSQL-15+-336791.svg)](https://www.postgresql.org/) [![Redis](https://img.shields.io/badge/Redis-7+-DC382D.svg)](https://redis.io/) @@ -44,7 +44,7 @@ Sub2API 是一个 AI API 网关平台,用于分发和管理 AI 产品订阅( | 组件 | 技术 | |------|------| -| 后端 | Go 1.25.7, Gin, Ent | +| 后端 | Go 1.26.0, Gin, Ent | | 前端 | Vue 3.4+, Vite 5+, TailwindCSS | | 数据库 | PostgreSQL 15+ | | 缓存/队列 | Redis 7+ | @@ -305,7 +305,7 @@ rm -rf data/ postgres_data/ redis_data/ #### 前置条件 -- Go 1.21+ +- Go 1.26.0 - Node.js 18+ - PostgreSQL 15+ - Redis 7+ diff --git a/backend/.golangci.yml b/backend/.golangci.yml index 3ec692a84..ac929fa4e 100644 --- a/backend/.golangci.yml +++ b/backend/.golangci.yml @@ -75,10 +75,6 @@ linters: # Default: false check-escaping-errors: true staticcheck: - # https://staticcheck.dev/docs/configuration/options/#dot_import_whitelist - # Default: ["github.com/mmcloughlin/avo/build", "github.com/mmcloughlin/avo/operand", "github.com/mmcloughlin/avo/reg"] - dot-import-whitelist: - - fmt # https://staticcheck.dev/docs/configuration/options/#initialisms # Default: ["ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS"] initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS", "SIP", "RTP", "AMQP", "DB", "TS" ] @@ -94,6 +90,7 @@ linters: - all - -ST1000 # Package comment format - -ST1003 # Poorly chosen identifier (ApiKey vs APIKey) + - -ST1016 # Methods on the same type should have the same receiver name - -ST1020 # Comment on exported method format - -ST1021 # Comment on exported type format - -ST1022 # Comment on exported variable format @@ -225,7 +222,8 @@ linters: - SA4005 # A value assigned to a variable is never read before being overwritten. Forgotten error check or dead code?. # https://staticcheck.dev/docs/checks/#SA4006 - - SA4006 + # Go 1.26 的 new(value) 语法会触发 staticcheck 的误报(SA4006),先临时关闭。 + - -SA4006 # The variable in the loop condition never changes, are you incrementing the wrong variable?. # https://staticcheck.dev/docs/checks/#SA4008 - SA4008 @@ -255,7 +253,8 @@ linters: - SA4016 # Discarding the return values of a function without side effects, making the call pointless. # https://staticcheck.dev/docs/checks/#SA4017 - - SA4017 + # Go 1.26 的 new(value) 语法会触发 staticcheck 的误报(SA4017),先临时关闭。 + - -SA4017 # Self-assignment of variables. # https://staticcheck.dev/docs/checks/#SA4018 - SA4018 diff --git a/backend/Dockerfile b/backend/Dockerfile index aeb20fdb6..04db10c8f 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.25.7-alpine +FROM golang:1.26.0-alpine WORKDIR /app diff --git a/backend/go.mod b/backend/go.mod index 08d54b91a..238035fcf 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,6 +1,6 @@ module github.com/Wei-Shaw/sub2api -go 1.25.7 +go 1.26.0 require ( entgo.io/ent v0.14.5 diff --git a/backend/internal/handler/admin/account_data.go b/backend/internal/handler/admin/account_data.go index b5d1dd0a4..8f506ef17 100644 --- a/backend/internal/handler/admin/account_data.go +++ b/backend/internal/handler/admin/account_data.go @@ -140,8 +140,7 @@ func (h *AccountHandler) ExportData(c *gin.Context) { } var expiresAt *int64 if acc.ExpiresAt != nil { - v := acc.ExpiresAt.Unix() - expiresAt = &v + expiresAt = new(acc.ExpiresAt.Unix()) } dataAccounts = append(dataAccounts, DataAccount{ Name: acc.Name, diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go index 85400c6fa..c2fbc5350 100644 --- a/backend/internal/handler/admin/account_handler.go +++ b/backend/internal/handler/admin/account_handler.go @@ -312,8 +312,7 @@ func (h *AccountHandler) Create(c *gin.Context) { }) if err != nil { // 检查是否为混合渠道错误 - var mixedErr *service.MixedChannelError - if errors.As(err, &mixedErr) { + if mixedErr, ok := errors.AsType[*service.MixedChannelError](err); ok { // 返回特殊错误码要求确认 c.JSON(409, gin.H{ "error": "mixed_channel_warning", @@ -376,8 +375,7 @@ func (h *AccountHandler) Update(c *gin.Context) { }) if err != nil { // 检查是否为混合渠道错误 - var mixedErr *service.MixedChannelError - if errors.As(err, &mixedErr) { + if mixedErr, ok := errors.AsType[*service.MixedChannelError](err); ok { // 返回特殊错误码要求确认 c.JSON(409, gin.H{ "error": "mixed_channel_warning", diff --git a/backend/internal/handler/admin/announcement_handler.go b/backend/internal/handler/admin/announcement_handler.go index 0b5d0fbcc..db5e227ef 100644 --- a/backend/internal/handler/admin/announcement_handler.go +++ b/backend/internal/handler/admin/announcement_handler.go @@ -118,12 +118,10 @@ func (h *AnnouncementHandler) Create(c *gin.Context) { } if req.StartsAt != nil && *req.StartsAt > 0 { - t := time.Unix(*req.StartsAt, 0) - input.StartsAt = &t + input.StartsAt = new(time.Unix(*req.StartsAt, 0)) } if req.EndsAt != nil && *req.EndsAt > 0 { - t := time.Unix(*req.EndsAt, 0) - input.EndsAt = &t + input.EndsAt = new(time.Unix(*req.EndsAt, 0)) } created, err := h.announcementService.Create(c.Request.Context(), input) @@ -169,8 +167,7 @@ func (h *AnnouncementHandler) Update(c *gin.Context) { var cleared *time.Time = nil input.StartsAt = &cleared } else { - t := time.Unix(*req.StartsAt, 0) - ptr := &t + ptr := new(time.Unix(*req.StartsAt, 0)) input.StartsAt = &ptr } } @@ -180,8 +177,7 @@ func (h *AnnouncementHandler) Update(c *gin.Context) { var cleared *time.Time = nil input.EndsAt = &cleared } else { - t := time.Unix(*req.EndsAt, 0) - ptr := &t + ptr := new(time.Unix(*req.EndsAt, 0)) input.EndsAt = &ptr } } diff --git a/backend/internal/handler/admin/dashboard_handler.go b/backend/internal/handler/admin/dashboard_handler.go index 183651865..f635be93a 100644 --- a/backend/internal/handler/admin/dashboard_handler.go +++ b/backend/internal/handler/admin/dashboard_handler.go @@ -227,8 +227,7 @@ func (h *DashboardHandler) GetUsageTrend(c *gin.Context) { } if billingTypeStr := c.Query("billing_type"); billingTypeStr != "" { if v, err := strconv.ParseInt(billingTypeStr, 10, 8); err == nil { - bt := int8(v) - billingType = &bt + billingType = new(int8(v)) } else { response.BadRequest(c, "Invalid billing_type") return @@ -287,8 +286,7 @@ func (h *DashboardHandler) GetModelStats(c *gin.Context) { } if billingTypeStr := c.Query("billing_type"); billingTypeStr != "" { if v, err := strconv.ParseInt(billingTypeStr, 10, 8); err == nil { - bt := int8(v) - billingType = &bt + billingType = new(int8(v)) } else { response.BadRequest(c, "Invalid billing_type") return diff --git a/backend/internal/handler/admin/group_handler.go b/backend/internal/handler/admin/group_handler.go index 7daaf2811..0c3498cf0 100644 --- a/backend/internal/handler/admin/group_handler.go +++ b/backend/internal/handler/admin/group_handler.go @@ -96,8 +96,7 @@ func (h *GroupHandler) List(c *gin.Context) { var isExclusive *bool if isExclusiveStr != "" { - val := isExclusiveStr == "true" - isExclusive = &val + isExclusive = new(isExclusiveStr == "true") } groups, total, err := h.adminService.ListGroups(c.Request.Context(), page, pageSize, platform, status, search, isExclusive) diff --git a/backend/internal/handler/admin/ops_alerts_handler.go b/backend/internal/handler/admin/ops_alerts_handler.go index c9da19c75..2670d45a5 100644 --- a/backend/internal/handler/admin/ops_alerts_handler.go +++ b/backend/internal/handler/admin/ops_alerts_handler.go @@ -435,8 +435,7 @@ func (h *OpsHandler) UpdateAlertEventStatus(c *gin.Context) { var resolvedAt *time.Time if payload.Status == service.OpsAlertStatusResolved || payload.Status == service.OpsAlertStatusManualResolved { - now := time.Now().UTC() - resolvedAt = &now + resolvedAt = new(time.Now().UTC()) } if err := h.opsService.UpdateAlertEventStatus(c.Request.Context(), id, payload.Status, resolvedAt); err != nil { response.ErrorFrom(c, err) @@ -479,8 +478,7 @@ func (h *OpsHandler) CreateAlertSilence(c *gin.Context) { createdBy := (*int64)(nil) if subject, ok := middleware.GetAuthSubjectFromContext(c); ok { - uid := subject.UserID - createdBy = &uid + createdBy = new(subject.UserID) } silence := &service.OpsAlertSilence{ @@ -531,11 +529,9 @@ func (h *OpsHandler) ListAlertEvents(c *gin.Context) { vv := strings.ToLower(v) switch vv { case "true", "1": - b := true - filter.EmailSent = &b + filter.EmailSent = new(true) case "false", "0": - b := false - filter.EmailSent = &b + filter.EmailSent = new(false) default: response.BadRequest(c, "Invalid email_sent") return diff --git a/backend/internal/handler/admin/ops_handler.go b/backend/internal/handler/admin/ops_handler.go index 44accc8f7..0d1546127 100644 --- a/backend/internal/handler/admin/ops_handler.go +++ b/backend/internal/handler/admin/ops_handler.go @@ -142,11 +142,9 @@ func (h *OpsHandler) GetErrorLogs(c *gin.Context) { if v := strings.TrimSpace(c.Query("resolved")); v != "" { switch strings.ToLower(v) { case "1", "true", "yes": - b := true - filter.Resolved = &b + filter.Resolved = new(true) case "0", "false", "no": - b := false - filter.Resolved = &b + filter.Resolved = new(false) default: response.BadRequest(c, "Invalid resolved") return @@ -243,11 +241,9 @@ func (h *OpsHandler) ListRequestErrors(c *gin.Context) { if v := strings.TrimSpace(c.Query("resolved")); v != "" { switch strings.ToLower(v) { case "1", "true", "yes": - b := true - filter.Resolved = &b + filter.Resolved = new(true) case "0", "false", "no": - b := false - filter.Resolved = &b + filter.Resolved = new(false) default: response.BadRequest(c, "Invalid resolved") return @@ -522,11 +518,9 @@ func (h *OpsHandler) ListUpstreamErrors(c *gin.Context) { if v := strings.TrimSpace(c.Query("resolved")); v != "" { switch strings.ToLower(v) { case "1", "true", "yes": - b := true - filter.Resolved = &b + filter.Resolved = new(true) case "0", "false", "no": - b := false - filter.Resolved = &b + filter.Resolved = new(false) default: response.BadRequest(c, "Invalid resolved") return @@ -836,8 +830,7 @@ func (h *OpsHandler) UpdateErrorResolution(c *gin.Context) { response.BadRequest(c, "Invalid request: "+err.Error()) return } - uid := subject.UserID - if err := h.opsService.UpdateErrorResolution(c.Request.Context(), id, req.Resolved, &uid, nil); err != nil { + if err := h.opsService.UpdateErrorResolution(c.Request.Context(), id, req.Resolved, new(subject.UserID), nil); err != nil { response.ErrorFrom(c, err) return } diff --git a/backend/internal/handler/admin/promo_handler.go b/backend/internal/handler/admin/promo_handler.go index 3eafa3801..efb1ee43c 100644 --- a/backend/internal/handler/admin/promo_handler.go +++ b/backend/internal/handler/admin/promo_handler.go @@ -107,8 +107,7 @@ func (h *PromoHandler) Create(c *gin.Context) { } if req.ExpiresAt != nil { - t := time.Unix(*req.ExpiresAt, 0) - input.ExpiresAt = &t + input.ExpiresAt = new(time.Unix(*req.ExpiresAt, 0)) } code, err := h.promoService.Create(c.Request.Context(), input) @@ -148,8 +147,7 @@ func (h *PromoHandler) Update(c *gin.Context) { // 0 表示清除过期时间 input.ExpiresAt = nil } else { - t := time.Unix(*req.ExpiresAt, 0) - input.ExpiresAt = &t + input.ExpiresAt = new(time.Unix(*req.ExpiresAt, 0)) } } diff --git a/backend/internal/handler/admin/usage_cleanup_handler_test.go b/backend/internal/handler/admin/usage_cleanup_handler_test.go index ed1c7cc22..7f158a01f 100644 --- a/backend/internal/handler/admin/usage_cleanup_handler_test.go +++ b/backend/internal/handler/admin/usage_cleanup_handler_test.go @@ -44,8 +44,7 @@ func (s *cleanupRepoStub) CreateTask(ctx context.Context, task *service.UsageCle task.CreatedAt = time.Now().UTC() } task.UpdatedAt = task.CreatedAt - clone := *task - s.created = append(s.created, &clone) + s.created = append(s.created, new(*task)) return nil } diff --git a/backend/internal/handler/admin/usage_handler.go b/backend/internal/handler/admin/usage_handler.go index 3f3238ddc..83f3910e9 100644 --- a/backend/internal/handler/admin/usage_handler.go +++ b/backend/internal/handler/admin/usage_handler.go @@ -117,8 +117,7 @@ func (h *UsageHandler) List(c *gin.Context) { response.BadRequest(c, "Invalid billing_type") return } - bt := int8(val) - billingType = &bt + billingType = new(int8(val)) } // Parse date range @@ -230,8 +229,7 @@ func (h *UsageHandler) Stats(c *gin.Context) { response.BadRequest(c, "Invalid billing_type") return } - bt := int8(val) - billingType = &bt + billingType = new(int8(val)) } // Parse date range diff --git a/backend/internal/handler/admin/user_attribute_handler.go b/backend/internal/handler/admin/user_attribute_handler.go index 2f326279d..005fc0653 100644 --- a/backend/internal/handler/admin/user_attribute_handler.go +++ b/backend/internal/handler/admin/user_attribute_handler.go @@ -199,8 +199,7 @@ func (h *UserAttributeHandler) UpdateDefinition(c *gin.Context) { Enabled: req.Enabled, } if req.Type != nil { - t := service.UserAttributeType(*req.Type) - input.Type = &t + input.Type = new(service.UserAttributeType(*req.Type)) } def, err := h.attrService.UpdateDefinition(c.Request.Context(), id, input) diff --git a/backend/internal/handler/auth_linuxdo_oauth.go b/backend/internal/handler/auth_linuxdo_oauth.go index 0ccf47e4a..000ecb2bb 100644 --- a/backend/internal/handler/auth_linuxdo_oauth.go +++ b/backend/internal/handler/auth_linuxdo_oauth.go @@ -180,8 +180,7 @@ func (h *AuthHandler) LinuxDoOAuthCallback(c *gin.Context) { tokenResp, err := linuxDoExchangeCode(c.Request.Context(), cfg, code, redirectURI, codeVerifier) if err != nil { description := "" - var exchangeErr *linuxDoTokenExchangeError - if errors.As(err, &exchangeErr) && exchangeErr != nil { + if exchangeErr, ok := errors.AsType[*linuxDoTokenExchangeError](err); ok && exchangeErr != nil { log.Printf( "[LinuxDo OAuth] token exchange failed: status=%d provider_error=%q provider_description=%q body=%s", exchangeErr.StatusCode, diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go index 2caf6847b..6a0b5040e 100644 --- a/backend/internal/handler/dto/mappers.go +++ b/backend/internal/handler/dto/mappers.go @@ -33,15 +33,13 @@ func UserFromService(u *service.User) *User { if len(u.APIKeys) > 0 { out.APIKeys = make([]APIKey, 0, len(u.APIKeys)) for i := range u.APIKeys { - k := u.APIKeys[i] - out.APIKeys = append(out.APIKeys, *APIKeyFromService(&k)) + out.APIKeys = append(out.APIKeys, *APIKeyFromService(new(u.APIKeys[i]))) } } if len(u.Subscriptions) > 0 { out.Subscriptions = make([]UserSubscription, 0, len(u.Subscriptions)) for i := range u.Subscriptions { - s := u.Subscriptions[i] - out.Subscriptions = append(out.Subscriptions, *UserSubscriptionFromService(&s)) + out.Subscriptions = append(out.Subscriptions, *UserSubscriptionFromService(new(u.Subscriptions[i]))) } } return out @@ -91,8 +89,7 @@ func GroupFromServiceShallow(g *service.Group) *Group { if g == nil { return nil } - out := groupFromServiceBase(g) - return &out + return new(groupFromServiceBase(g)) } func GroupFromService(g *service.Group) *Group { @@ -120,8 +117,7 @@ func GroupFromServiceAdmin(g *service.Group) *AdminGroup { if len(g.AccountGroups) > 0 { out.AccountGroups = make([]AccountGroup, 0, len(g.AccountGroups)) for i := range g.AccountGroups { - ag := g.AccountGroups[i] - out.AccountGroups = append(out.AccountGroups, *AccountGroupFromService(&ag)) + out.AccountGroups = append(out.AccountGroups, *AccountGroupFromService(new(g.AccountGroups[i]))) } } return out @@ -203,13 +199,11 @@ func AccountFromServiceShallow(a *service.Account) *Account { } // TLS指纹伪装开关 if a.IsTLSFingerprintEnabled() { - enabled := true - out.EnableTLSFingerprint = &enabled + out.EnableTLSFingerprint = new(true) } // 会话ID伪装开关 if a.IsSessionIDMaskingEnabled() { - enabled := true - out.EnableSessionIDMasking = &enabled + out.EnableSessionIDMasking = new(true) } } @@ -225,8 +219,7 @@ func AccountFromService(a *service.Account) *Account { if len(a.AccountGroups) > 0 { out.AccountGroups = make([]AccountGroup, 0, len(a.AccountGroups)) for i := range a.AccountGroups { - ag := a.AccountGroups[i] - out.AccountGroups = append(out.AccountGroups, *AccountGroupFromService(&ag)) + out.AccountGroups = append(out.AccountGroups, *AccountGroupFromService(new(a.AccountGroups[i]))) } } if len(a.Groups) > 0 { @@ -242,8 +235,7 @@ func timeToUnixSeconds(value *time.Time) *int64 { if value == nil { return nil } - ts := value.Unix() - return &ts + return new(value.Unix()) } func AccountGroupFromService(ag *service.AccountGroup) *AccountGroup { @@ -313,8 +305,7 @@ func RedeemCodeFromService(rc *service.RedeemCode) *RedeemCode { if rc == nil { return nil } - out := redeemCodeFromServiceBase(rc) - return &out + return new(redeemCodeFromServiceBase(rc)) } // RedeemCodeFromServiceAdmin converts a service RedeemCode to DTO for admin users. @@ -412,8 +403,7 @@ func UsageLogFromService(l *service.UsageLog) *UsageLog { if l == nil { return nil } - u := usageLogFromServiceUser(l) - return &u + return new(usageLogFromServiceUser(l)) } // UsageLogFromServiceAdmin converts a service UsageLog to DTO for admin users. @@ -476,8 +466,7 @@ func UserSubscriptionFromService(sub *service.UserSubscription) *UserSubscriptio if sub == nil { return nil } - out := userSubscriptionFromServiceBase(sub) - return &out + return new(userSubscriptionFromServiceBase(sub)) } // UserSubscriptionFromServiceAdmin converts a service UserSubscription to DTO for admin users. diff --git a/backend/internal/handler/gateway_handler.go b/backend/internal/handler/gateway_handler.go index c2b6bf096..e31365d24 100644 --- a/backend/internal/handler/gateway_handler.go +++ b/backend/internal/handler/gateway_handler.go @@ -358,8 +358,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) { accountReleaseFunc() } if err != nil { - var failoverErr *service.UpstreamFailoverError - if errors.As(err, &failoverErr) { + if failoverErr, ok2 := errors.AsType[*service.UpstreamFailoverError](err); ok2 { lastFailoverErr = failoverErr if needForceCacheBilling(hasBoundSession, failoverErr) { forceCacheBilling = true @@ -561,8 +560,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) { accountReleaseFunc() } if err != nil { - var promptTooLongErr *service.PromptTooLongError - if errors.As(err, &promptTooLongErr) { + if promptTooLongErr, ok2 := errors.AsType[*service.PromptTooLongError](err); ok2 { log.Printf("Prompt too long from antigravity: group=%d fallback_group_id=%v fallback_used=%v", currentAPIKey.GroupID, fallbackGroupID, fallbackUsed) if !fallbackUsed && fallbackGroupID != nil && *fallbackGroupID > 0 { fallbackGroup, err := h.gatewayService.ResolveGroupByID(c.Request.Context(), *fallbackGroupID) @@ -596,8 +594,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) { _ = h.antigravityGatewayService.WriteMappedClaudeError(c, account, promptTooLongErr.StatusCode, promptTooLongErr.RequestID, promptTooLongErr.Body) return } - var failoverErr *service.UpstreamFailoverError - if errors.As(err, &failoverErr) { + if failoverErr, ok2 := errors.AsType[*service.UpstreamFailoverError](err); ok2 { lastFailoverErr = failoverErr if needForceCacheBilling(hasBoundSession, failoverErr) { forceCacheBilling = true @@ -733,8 +730,7 @@ func cloneAPIKeyWithGroup(apiKey *service.APIKey, group *service.Group) *service return apiKey } cloned := *apiKey - groupID := group.ID - cloned.GroupID = &groupID + cloned.GroupID = new(group.ID) cloned.Group = group return &cloned } diff --git a/backend/internal/handler/gemini_v1beta_handler.go b/backend/internal/handler/gemini_v1beta_handler.go index 3d25505ba..b1fa8c848 100644 --- a/backend/internal/handler/gemini_v1beta_handler.go +++ b/backend/internal/handler/gemini_v1beta_handler.go @@ -441,8 +441,7 @@ func (h *GatewayHandler) GeminiV1BetaModels(c *gin.Context) { accountReleaseFunc() } if err != nil { - var failoverErr *service.UpstreamFailoverError - if errors.As(err, &failoverErr) { + if failoverErr, ok2 := errors.AsType[*service.UpstreamFailoverError](err); ok2 { failedAccountIDs[account.ID] = struct{}{} if needForceCacheBilling(hasBoundSession, failoverErr) { forceCacheBilling = true diff --git a/backend/internal/handler/openai_gateway_handler.go b/backend/internal/handler/openai_gateway_handler.go index c08a8b0ec..ebb677502 100644 --- a/backend/internal/handler/openai_gateway_handler.go +++ b/backend/internal/handler/openai_gateway_handler.go @@ -287,8 +287,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) { accountReleaseFunc() } if err != nil { - var failoverErr *service.UpstreamFailoverError - if errors.As(err, &failoverErr) { + if failoverErr, ok2 := errors.AsType[*service.UpstreamFailoverError](err); ok2 { failedAccountIDs[account.ID] = struct{}{} lastFailoverErr = failoverErr if switchCount >= maxAccountSwitches { diff --git a/backend/internal/handler/ops_error_logger.go b/backend/internal/handler/ops_error_logger.go index cb62ceaeb..90c9a1682 100644 --- a/backend/internal/handler/ops_error_logger.go +++ b/backend/internal/handler/ops_error_logger.go @@ -378,8 +378,7 @@ func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc { var accountID *int64 if len(events) > 0 { if last := events[len(events)-1]; last != nil && last.AccountID > 0 { - v := last.AccountID - accountID = &v + accountID = new(last.AccountID) } } if accountID == nil { @@ -404,8 +403,7 @@ func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc { last := events[len(events)-1] if last != nil { if last.UpstreamStatusCode > 0 { - code := last.UpstreamStatusCode - upstreamStatusCode = &code + upstreamStatusCode = new(last.UpstreamStatusCode) } if msg := strings.TrimSpace(last.Message); msg != "" { upstreamErrorMessage = &msg @@ -421,13 +419,11 @@ func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc { switch t := v.(type) { case int: if t > 0 { - code := t - upstreamStatusCode = &code + upstreamStatusCode = new(t) } case int64: if t > 0 { - code := int(t) - upstreamStatusCode = &code + upstreamStatusCode = new(int(t)) } } } @@ -435,16 +431,14 @@ func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc { if upstreamErrorMessage == nil { if v, ok := c.Get(service.OpsUpstreamErrorMessageKey); ok { if s, ok := v.(string); ok && strings.TrimSpace(s) != "" { - msg := strings.TrimSpace(s) - upstreamErrorMessage = &msg + upstreamErrorMessage = new(strings.TrimSpace(s)) } } } if upstreamErrorDetail == nil { if v, ok := c.Get(service.OpsUpstreamErrorDetailKey); ok { if s, ok := v.(string); ok && strings.TrimSpace(s) != "" { - detail := strings.TrimSpace(s) - upstreamErrorDetail = &detail + upstreamErrorDetail = new(strings.TrimSpace(s)) } } } @@ -640,13 +634,11 @@ func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc { switch t := v.(type) { case int: if t > 0 { - code := t - entry.UpstreamStatusCode = &code + entry.UpstreamStatusCode = new(t) } case int64: if t > 0 { - code := int(t) - entry.UpstreamStatusCode = &code + entry.UpstreamStatusCode = new(int(t)) } } } @@ -671,16 +663,13 @@ func OpsErrorLoggerMiddleware(ops *service.OpsService) gin.HandlerFunc { last := events[len(events)-1] if last != nil { if entry.UpstreamStatusCode == nil && last.UpstreamStatusCode > 0 { - code := last.UpstreamStatusCode - entry.UpstreamStatusCode = &code + entry.UpstreamStatusCode = new(last.UpstreamStatusCode) } if entry.UpstreamErrorMessage == nil && strings.TrimSpace(last.Message) != "" { - msg := strings.TrimSpace(last.Message) - entry.UpstreamErrorMessage = &msg + entry.UpstreamErrorMessage = new(strings.TrimSpace(last.Message)) } if entry.UpstreamErrorDetail == nil && strings.TrimSpace(last.Detail) != "" { - detail := strings.TrimSpace(last.Detail) - entry.UpstreamErrorDetail = &detail + entry.UpstreamErrorDetail = new(strings.TrimSpace(last.Detail)) } } } @@ -756,8 +745,7 @@ func extractOpsRetryRequestHeaders(c *gin.Context) *string { if err != nil { return nil } - s := string(raw) - return &s + return new(string(raw)) } type parsedOpsError struct { diff --git a/backend/internal/handler/request_body_limit.go b/backend/internal/handler/request_body_limit.go index d746673b3..a0c2075a5 100644 --- a/backend/internal/handler/request_body_limit.go +++ b/backend/internal/handler/request_body_limit.go @@ -7,8 +7,7 @@ import ( ) func extractMaxBytesError(err error) (*http.MaxBytesError, bool) { - var maxErr *http.MaxBytesError - if errors.As(err, &maxErr) { + if maxErr, ok := errors.AsType[*http.MaxBytesError](err); ok { return maxErr, true } return nil, false diff --git a/backend/internal/handler/subscription_handler.go b/backend/internal/handler/subscription_handler.go index b40df8333..27d994b16 100644 --- a/backend/internal/handler/subscription_handler.go +++ b/backend/internal/handler/subscription_handler.go @@ -164,8 +164,7 @@ func (h *SubscriptionHandler) GetSummary(c *gin.Context) { // Format expiration time if !sub.ExpiresAt.IsZero() { - formatted := sub.ExpiresAt.Format("2006-01-02T15:04:05Z07:00") - item.ExpiresAt = &formatted + item.ExpiresAt = new(sub.ExpiresAt.Format("2006-01-02T15:04:05Z07:00")) } // Track total usage (use monthly as the most comprehensive) diff --git a/backend/internal/handler/totp_handler.go b/backend/internal/handler/totp_handler.go index 5c5eb567a..c1c4e6fe2 100644 --- a/backend/internal/handler/totp_handler.go +++ b/backend/internal/handler/totp_handler.go @@ -48,8 +48,7 @@ func (h *TotpHandler) GetStatus(c *gin.Context) { } if status.EnabledAt != nil { - ts := status.EnabledAt.Unix() - resp.EnabledAt = &ts + resp.EnabledAt = new(status.EnabledAt.Unix()) } response.Success(c, resp) diff --git a/backend/internal/handler/usage_handler.go b/backend/internal/handler/usage_handler.go index 129dbfa65..7a57b4737 100644 --- a/backend/internal/handler/usage_handler.go +++ b/backend/internal/handler/usage_handler.go @@ -82,8 +82,7 @@ func (h *UsageHandler) List(c *gin.Context) { response.BadRequest(c, "Invalid billing_type") return } - bt := int8(val) - billingType = &bt + billingType = new(int8(val)) } // Parse date range diff --git a/backend/internal/pkg/antigravity/client.go b/backend/internal/pkg/antigravity/client.go index ac32fae59..fb0374501 100644 --- a/backend/internal/pkg/antigravity/client.go +++ b/backend/internal/pkg/antigravity/client.go @@ -174,14 +174,12 @@ func isConnectionError(err error) bool { } // 检查超时错误 - var netErr net.Error - if errors.As(err, &netErr) && netErr.Timeout() { + if netErr, ok := errors.AsType[net.Error](err); ok && netErr.Timeout() { return true } // 检查连接错误(DNS 失败、连接拒绝) - var opErr *net.OpError - if errors.As(err, &opErr) { + if _, ok := errors.AsType[*net.OpError](err); ok { return true } diff --git a/backend/internal/repository/account_repo.go b/backend/internal/repository/account_repo.go index d28ae0425..4e27f59ae 100644 --- a/backend/internal/repository/account_repo.go +++ b/backend/internal/repository/account_repo.go @@ -1370,8 +1370,7 @@ func (r *accountRepository) loadTempUnschedStates(ctx context.Context, accountID } var untilPtr *time.Time if until.Valid { - tmp := until.Time - untilPtr = &tmp + untilPtr = new(until.Time) } if reason.Valid { out[id] = tempUnschedSnapshot{until: untilPtr, reason: reason.String} @@ -1494,8 +1493,6 @@ func accountEntityToService(m *dbent.Account) *service.Account { return nil } - rateMultiplier := m.RateMultiplier - return &service.Account{ ID: m.ID, Name: m.Name, @@ -1507,7 +1504,7 @@ func accountEntityToService(m *dbent.Account) *service.Account { ProxyID: m.ProxyID, Concurrency: m.Concurrency, Priority: m.Priority, - RateMultiplier: &rateMultiplier, + RateMultiplier: new(m.RateMultiplier), Status: m.Status, ErrorMessage: derefString(m.ErrorMessage), LastUsedAt: m.LastUsedAt, diff --git a/backend/internal/repository/error_translate.go b/backend/internal/repository/error_translate.go index b8065ffe2..be2a79b0f 100644 --- a/backend/internal/repository/error_translate.go +++ b/backend/internal/repository/error_translate.go @@ -83,8 +83,7 @@ func isUniqueConstraintViolation(err error) bool { // 优先检测 PostgreSQL 特定错误码(最精确)。 // 错误码 23505 对应 unique_violation。 // 参考:https://www.postgresql.org/docs/current/errcodes-appendix.html - var pgErr *pq.Error - if errors.As(err, &pgErr) { + if pgErr, ok := errors.AsType[*pq.Error](err); ok { return pgErr.Code == "23505" } diff --git a/backend/internal/repository/gateway_cache_integration_test.go b/backend/internal/repository/gateway_cache_integration_test.go index 2fdaa3d1e..0eebc33f6 100644 --- a/backend/internal/repository/gateway_cache_integration_test.go +++ b/backend/internal/repository/gateway_cache_integration_test.go @@ -104,7 +104,6 @@ func (s *GatewayCacheSuite) TestGetSessionAccountID_CorruptedValue() { require.False(s.T(), errors.Is(err, redis.Nil), "expected parsing error, not redis.Nil") } - func TestGatewayCacheSuite(t *testing.T) { suite.Run(t, new(GatewayCacheSuite)) } diff --git a/backend/internal/repository/ops_repo.go b/backend/internal/repository/ops_repo.go index b04154b76..888df9f02 100644 --- a/backend/internal/repository/ops_repo.go +++ b/backend/internal/repository/ops_repo.go @@ -240,40 +240,32 @@ LIMIT $` + itoa(len(args)+1) + ` OFFSET $` + itoa(len(args)+2) return nil, err } if resolvedAt.Valid { - t := resolvedAt.Time - item.ResolvedAt = &t + item.ResolvedAt = new(resolvedAt.Time) } if resolvedBy.Valid { - v := resolvedBy.Int64 - item.ResolvedByUserID = &v + item.ResolvedByUserID = new(resolvedBy.Int64) } item.ResolvedByUserName = resolvedByName if resolvedRetryID.Valid { - v := resolvedRetryID.Int64 - item.ResolvedRetryID = &v + item.ResolvedRetryID = new(resolvedRetryID.Int64) } item.StatusCode = int(statusCode.Int64) if clientIP.Valid { - s := clientIP.String - item.ClientIP = &s + item.ClientIP = new(clientIP.String) } if userID.Valid { - v := userID.Int64 - item.UserID = &v + item.UserID = new(userID.Int64) } item.UserEmail = userEmail if apiKeyID.Valid { - v := apiKeyID.Int64 - item.APIKeyID = &v + item.APIKeyID = new(apiKeyID.Int64) } if accountID.Valid { - v := accountID.Int64 - item.AccountID = &v + item.AccountID = new(accountID.Int64) } item.AccountName = accountName if groupID.Valid { - v := groupID.Int64 - item.GroupID = &v + item.GroupID = new(groupID.Int64) } item.GroupName = groupName out = append(out, &item) @@ -423,64 +415,49 @@ LIMIT 1` out.StatusCode = int(statusCode.Int64) if resolvedAt.Valid { - t := resolvedAt.Time - out.ResolvedAt = &t + out.ResolvedAt = new(resolvedAt.Time) } if resolvedBy.Valid { - v := resolvedBy.Int64 - out.ResolvedByUserID = &v + out.ResolvedByUserID = new(resolvedBy.Int64) } if resolvedRetryID.Valid { - v := resolvedRetryID.Int64 - out.ResolvedRetryID = &v + out.ResolvedRetryID = new(resolvedRetryID.Int64) } if clientIP.Valid { - s := clientIP.String - out.ClientIP = &s + out.ClientIP = new(clientIP.String) } if upstreamStatusCode.Valid && upstreamStatusCode.Int64 > 0 { - v := int(upstreamStatusCode.Int64) - out.UpstreamStatusCode = &v + out.UpstreamStatusCode = new(int(upstreamStatusCode.Int64)) } if userID.Valid { - v := userID.Int64 - out.UserID = &v + out.UserID = new(userID.Int64) } if apiKeyID.Valid { - v := apiKeyID.Int64 - out.APIKeyID = &v + out.APIKeyID = new(apiKeyID.Int64) } if accountID.Valid { - v := accountID.Int64 - out.AccountID = &v + out.AccountID = new(accountID.Int64) } if groupID.Valid { - v := groupID.Int64 - out.GroupID = &v + out.GroupID = new(groupID.Int64) } if authLatency.Valid { - v := authLatency.Int64 - out.AuthLatencyMs = &v + out.AuthLatencyMs = new(authLatency.Int64) } if routingLatency.Valid { - v := routingLatency.Int64 - out.RoutingLatencyMs = &v + out.RoutingLatencyMs = new(routingLatency.Int64) } if upstreamLatency.Valid { - v := upstreamLatency.Int64 - out.UpstreamLatencyMs = &v + out.UpstreamLatencyMs = new(upstreamLatency.Int64) } if responseLatency.Valid { - v := responseLatency.Int64 - out.ResponseLatencyMs = &v + out.ResponseLatencyMs = new(responseLatency.Int64) } if ttft.Valid { - v := ttft.Int64 - out.TimeToFirstTokenMs = &v + out.TimeToFirstTokenMs = new(ttft.Int64) } if requestBodyBytes.Valid { - v := int(requestBodyBytes.Int64) - out.RequestBodyBytes = &v + out.RequestBodyBytes = new(int(requestBodyBytes.Int64)) } // Normalize request_body to empty string when stored as JSON null. @@ -669,56 +646,43 @@ LIMIT 1` } out.RequestedByUserID = requestedBy.Int64 if pinnedAccountID.Valid { - v := pinnedAccountID.Int64 - out.PinnedAccountID = &v + out.PinnedAccountID = new(pinnedAccountID.Int64) } if startedAt.Valid { - t := startedAt.Time - out.StartedAt = &t + out.StartedAt = new(startedAt.Time) } if finishedAt.Valid { - t := finishedAt.Time - out.FinishedAt = &t + out.FinishedAt = new(finishedAt.Time) } if durationMs.Valid { - v := durationMs.Int64 - out.DurationMs = &v + out.DurationMs = new(durationMs.Int64) } if success.Valid { - v := success.Bool - out.Success = &v + out.Success = new(success.Bool) } if httpStatusCode.Valid { - v := int(httpStatusCode.Int64) - out.HTTPStatusCode = &v + out.HTTPStatusCode = new(int(httpStatusCode.Int64)) } if upstreamRequestID.Valid { - s := upstreamRequestID.String - out.UpstreamRequestID = &s + out.UpstreamRequestID = new(upstreamRequestID.String) } if usedAccountID.Valid { - v := usedAccountID.Int64 - out.UsedAccountID = &v + out.UsedAccountID = new(usedAccountID.Int64) } if responsePreview.Valid { - s := responsePreview.String - out.ResponsePreview = &s + out.ResponsePreview = new(responsePreview.String) } if responseTruncated.Valid { - v := responseTruncated.Bool - out.ResponseTruncated = &v + out.ResponseTruncated = new(responseTruncated.Bool) } if resultRequestID.Valid { - s := resultRequestID.String - out.ResultRequestID = &s + out.ResultRequestID = new(resultRequestID.String) } if resultErrorID.Valid { - v := resultErrorID.Int64 - out.ResultErrorID = &v + out.ResultErrorID = new(resultErrorID.Int64) } if errorMessage.Valid { - s := errorMessage.String - out.ErrorMessage = &s + out.ErrorMessage = new(errorMessage.String) } return &out, nil @@ -836,51 +800,42 @@ LIMIT $2` item.RequestedByUserID = requestedBy.Int64 if pinnedAccountID.Valid { - v := pinnedAccountID.Int64 - item.PinnedAccountID = &v + item.PinnedAccountID = new(pinnedAccountID.Int64) } item.PinnedAccountName = pinnedAccountName if startedAt.Valid { - t := startedAt.Time - item.StartedAt = &t + item.StartedAt = new(startedAt.Time) } if finishedAt.Valid { - t := finishedAt.Time - item.FinishedAt = &t + item.FinishedAt = new(finishedAt.Time) } if durationMs.Valid { - v := durationMs.Int64 - item.DurationMs = &v + item.DurationMs = new(durationMs.Int64) } if success.Valid { - v := success.Bool - item.Success = &v + item.Success = new(success.Bool) } if httpStatusCode.Valid { - v := int(httpStatusCode.Int64) - item.HTTPStatusCode = &v + item.HTTPStatusCode = new(int(httpStatusCode.Int64)) } if upstreamRequestID.Valid { item.UpstreamRequestID = &upstreamRequestID.String } if usedAccountID.Valid { - v := usedAccountID.Int64 - item.UsedAccountID = &v + item.UsedAccountID = new(usedAccountID.Int64) } item.UsedAccountName = usedAccountName if responsePreview.Valid { item.ResponsePreview = &responsePreview.String } if responseTruncated.Valid { - v := responseTruncated.Bool - item.ResponseTruncated = &v + item.ResponseTruncated = new(responseTruncated.Bool) } if resultRequestID.Valid { item.ResultRequestID = &resultRequestID.String } if resultErrorID.Valid { - v := resultErrorID.Int64 - item.ResultErrorID = &v + item.ResultErrorID = new(resultErrorID.Int64) } if errorMessage.Valid { item.ErrorMessage = &errorMessage.String diff --git a/backend/internal/repository/ops_repo_alerts.go b/backend/internal/repository/ops_repo_alerts.go index bd98b7e4c..cd430ed0d 100644 --- a/backend/internal/repository/ops_repo_alerts.go +++ b/backend/internal/repository/ops_repo_alerts.go @@ -69,8 +69,7 @@ ORDER BY id DESC` return nil, err } if lastTriggeredAt.Valid { - v := lastTriggeredAt.Time - rule.LastTriggeredAt = &v + rule.LastTriggeredAt = new(lastTriggeredAt.Time) } if len(filtersRaw) > 0 && string(filtersRaw) != "null" { var decoded map[string]any @@ -176,8 +175,7 @@ RETURNING return nil, err } if lastTriggeredAt.Valid { - v := lastTriggeredAt.Time - out.LastTriggeredAt = &v + out.LastTriggeredAt = new(lastTriggeredAt.Time) } if len(filtersRaw) > 0 && string(filtersRaw) != "null" { var decoded map[string]any @@ -282,8 +280,7 @@ RETURNING } if lastTriggeredAt.Valid { - v := lastTriggeredAt.Time - out.LastTriggeredAt = &v + out.LastTriggeredAt = new(lastTriggeredAt.Time) } if len(filtersRaw) > 0 && string(filtersRaw) != "null" { var decoded map[string]any @@ -388,16 +385,13 @@ LIMIT ` + limitArg return nil, err } if metricValue.Valid { - v := metricValue.Float64 - ev.MetricValue = &v + ev.MetricValue = new(metricValue.Float64) } if thresholdValue.Valid { - v := thresholdValue.Float64 - ev.ThresholdValue = &v + ev.ThresholdValue = new(thresholdValue.Float64) } if resolvedAt.Valid { - v := resolvedAt.Time - ev.ResolvedAt = &v + ev.ResolvedAt = new(resolvedAt.Time) } if len(dimensionsRaw) > 0 && string(dimensionsRaw) != "null" { var decoded map[string]any @@ -691,8 +685,7 @@ RETURNING id, rule_id, platform, group_id, region, until, COALESCE(reason,''), c return nil, err } if groupID.Valid { - v := groupID.Int64 - out.GroupID = &v + out.GroupID = new(groupID.Int64) } if region.Valid { v := strings.TrimSpace(region.String) @@ -701,8 +694,7 @@ RETURNING id, rule_id, platform, group_id, region, until, COALESCE(reason,''), c } } if createdBy.Valid { - v := createdBy.Int64 - out.CreatedBy = &v + out.CreatedBy = new(createdBy.Int64) } return &out, nil } @@ -768,16 +760,13 @@ func scanOpsAlertEvent(row opsAlertEventRow) (*service.OpsAlertEvent, error) { return nil, err } if metricValue.Valid { - v := metricValue.Float64 - ev.MetricValue = &v + ev.MetricValue = new(metricValue.Float64) } if thresholdValue.Valid { - v := thresholdValue.Float64 - ev.ThresholdValue = &v + ev.ThresholdValue = new(thresholdValue.Float64) } if resolvedAt.Valid { - v := resolvedAt.Time - ev.ResolvedAt = &v + ev.ResolvedAt = new(resolvedAt.Time) } if len(dimensionsRaw) > 0 && string(dimensionsRaw) != "null" { var decoded map[string]any diff --git a/backend/internal/repository/ops_repo_dashboard.go b/backend/internal/repository/ops_repo_dashboard.go index 85791a9a6..0d4e06939 100644 --- a/backend/internal/repository/ops_repo_dashboard.go +++ b/backend/internal/repository/ops_repo_dashboard.go @@ -536,35 +536,29 @@ func aggregateHourlyRows(rows []opsHourlyMetricsRow) opsDashboardPartial { // duration if p50W > 0 { - v := int(math.Round(p50Sum / float64(p50W))) - out.duration.P50 = &v + out.duration.P50 = new(int(math.Round(p50Sum / float64(p50W)))) } if p90W > 0 { - v := int(math.Round(p90Sum / float64(p90W))) - out.duration.P90 = &v + out.duration.P90 = new(int(math.Round(p90Sum / float64(p90W)))) } out.duration.P95 = p95Max out.duration.P99 = p99Max if avgW > 0 { - v := int(math.Round(avgSum / float64(avgW))) - out.duration.Avg = &v + out.duration.Avg = new(int(math.Round(avgSum / float64(avgW)))) } out.duration.Max = maxMax // ttft if ttftP50W > 0 { - v := int(math.Round(ttftP50Sum / float64(ttftP50W))) - out.ttft.P50 = &v + out.ttft.P50 = new(int(math.Round(ttftP50Sum / float64(ttftP50W)))) } if ttftP90W > 0 { - v := int(math.Round(ttftP90Sum / float64(ttftP90W))) - out.ttft.P90 = &v + out.ttft.P90 = new(int(math.Round(ttftP90Sum / float64(ttftP90W)))) } out.ttft.P95 = ttftP95Max out.ttft.P99 = ttftP99Max if ttftAvgW > 0 { - v := int(math.Round(ttftAvgSum / float64(ttftAvgW))) - out.ttft.Avg = &v + out.ttft.Avg = new(int(math.Round(ttftAvgSum / float64(ttftAvgW)))) } out.ttft.Max = ttftMaxMax @@ -648,8 +642,7 @@ func combineApproxPercentiles(segments []opsPercentileSegment) service.OpsPercen if w <= 0 { return nil } - out := int(math.Round(sum / float64(w))) - return &out + return new(int(math.Round(sum / float64(w)))) } maxInt := func(get func(service.OpsPercentiles) *int) *int { @@ -660,8 +653,7 @@ func combineApproxPercentiles(segments []opsPercentileSegment) service.OpsPercen continue } if max == nil || *v > *max { - c := *v - max = &c + max = new(*v) } } return max @@ -762,8 +754,7 @@ AND duration_ms IS NOT NULL` duration.P99 = floatToIntPtr(p99) duration.Avg = floatToIntPtr(avg) if max.Valid { - v := int(max.Int64) - duration.Max = &v + duration.Max = new(int(max.Int64)) } } @@ -794,8 +785,7 @@ AND first_token_ms IS NOT NULL` ttft.P99 = floatToIntPtr(p99) ttft.Avg = floatToIntPtr(avg) if max.Valid { - v := int(max.Int64) - ttft.Max = &v + ttft.Max = new(int(max.Int64)) } } @@ -995,8 +985,7 @@ func floatToIntPtr(v sql.NullFloat64) *int { if !v.Valid { return nil } - n := int(math.Round(v.Float64)) - return &n + return new(int(math.Round(v.Float64))) } func safeDivideFloat64(numerator float64, denominator float64) float64 { diff --git a/backend/internal/repository/ops_repo_metrics.go b/backend/internal/repository/ops_repo_metrics.go index f1e57c386..ee73ad102 100644 --- a/backend/internal/repository/ops_repo_metrics.go +++ b/backend/internal/repository/ops_repo_metrics.go @@ -227,60 +227,46 @@ LIMIT 1` } if cpu.Valid { - v := cpu.Float64 - out.CPUUsagePercent = &v + out.CPUUsagePercent = new(cpu.Float64) } if memUsed.Valid { - v := memUsed.Int64 - out.MemoryUsedMB = &v + out.MemoryUsedMB = new(memUsed.Int64) } if memTotal.Valid { - v := memTotal.Int64 - out.MemoryTotalMB = &v + out.MemoryTotalMB = new(memTotal.Int64) } if memPct.Valid { - v := memPct.Float64 - out.MemoryUsagePercent = &v + out.MemoryUsagePercent = new(memPct.Float64) } if dbOK.Valid { - v := dbOK.Bool - out.DBOK = &v + out.DBOK = new(dbOK.Bool) } if redisOK.Valid { - v := redisOK.Bool - out.RedisOK = &v + out.RedisOK = new(redisOK.Bool) } if redisTotal.Valid { - v := int(redisTotal.Int64) - out.RedisConnTotal = &v + out.RedisConnTotal = new(int(redisTotal.Int64)) } if redisIdle.Valid { - v := int(redisIdle.Int64) - out.RedisConnIdle = &v + out.RedisConnIdle = new(int(redisIdle.Int64)) } if dbActive.Valid { - v := int(dbActive.Int64) - out.DBConnActive = &v + out.DBConnActive = new(int(dbActive.Int64)) } if dbIdle.Valid { - v := int(dbIdle.Int64) - out.DBConnIdle = &v + out.DBConnIdle = new(int(dbIdle.Int64)) } if dbWaiting.Valid { - v := int(dbWaiting.Int64) - out.DBConnWaiting = &v + out.DBConnWaiting = new(int(dbWaiting.Int64)) } if goroutines.Valid { - v := int(goroutines.Int64) - out.GoroutineCount = &v + out.GoroutineCount = new(int(goroutines.Int64)) } if queueDepth.Valid { - v := int(queueDepth.Int64) - out.ConcurrencyQueueDepth = &v + out.ConcurrencyQueueDepth = new(int(queueDepth.Int64)) } if accountSwitchCount.Valid { - v := accountSwitchCount.Int64 - out.AccountSwitchCount = &v + out.AccountSwitchCount = new(accountSwitchCount.Int64) } return &out, nil @@ -391,28 +377,22 @@ ORDER BY job_name ASC` } if lastRun.Valid { - v := lastRun.Time - item.LastRunAt = &v + item.LastRunAt = new(lastRun.Time) } if lastSuccess.Valid { - v := lastSuccess.Time - item.LastSuccessAt = &v + item.LastSuccessAt = new(lastSuccess.Time) } if lastErrorAt.Valid { - v := lastErrorAt.Time - item.LastErrorAt = &v + item.LastErrorAt = new(lastErrorAt.Time) } if lastError.Valid { - v := lastError.String - item.LastError = &v + item.LastError = new(lastError.String) } if lastDuration.Valid { - v := lastDuration.Int64 - item.LastDurationMs = &v + item.LastDurationMs = new(lastDuration.Int64) } if lastResult.Valid { - v := lastResult.String - item.LastResult = &v + item.LastResult = new(lastResult.String) } out = append(out, &item) diff --git a/backend/internal/repository/ops_repo_request_details.go b/backend/internal/repository/ops_repo_request_details.go index d8d5d111b..5e6ced3cd 100644 --- a/backend/internal/repository/ops_repo_request_details.go +++ b/backend/internal/repository/ops_repo_request_details.go @@ -193,15 +193,13 @@ LIMIT $%d OFFSET $%d if !v.Valid { return nil } - i := int(v.Int64) - return &i + return new(int(v.Int64)) } toInt64Ptr := func(v sql.NullInt64) *int64 { if !v.Valid { return nil } - i := v.Int64 - return &i + return new(v.Int64) } out := make([]*service.OpsRequestDetail, 0, pageSize) diff --git a/backend/internal/repository/scheduler_outbox_repo.go b/backend/internal/repository/scheduler_outbox_repo.go index d7bc97dad..edd1e8a6a 100644 --- a/backend/internal/repository/scheduler_outbox_repo.go +++ b/backend/internal/repository/scheduler_outbox_repo.go @@ -46,12 +46,10 @@ func (r *schedulerOutboxRepository) ListAfter(ctx context.Context, afterID int64 return nil, err } if accountID.Valid { - v := accountID.Int64 - event.AccountID = &v + event.AccountID = new(accountID.Int64) } if groupID.Valid { - v := groupID.Int64 - event.GroupID = &v + event.GroupID = new(groupID.Int64) } if len(payloadRaw) > 0 { var payload map[string]any diff --git a/backend/internal/repository/usage_cleanup_repo.go b/backend/internal/repository/usage_cleanup_repo.go index 9c0213573..435449f28 100644 --- a/backend/internal/repository/usage_cleanup_repo.go +++ b/backend/internal/repository/usage_cleanup_repo.go @@ -96,8 +96,7 @@ func (r *usageCleanupRepository) ListTasks(ctx context.Context, params paginatio task.ErrorMsg = &errMsg.String } if canceledBy.Valid { - v := canceledBy.Int64 - task.CanceledBy = &v + task.CanceledBy = new(canceledBy.Int64) } if canceledAt.Valid { task.CanceledAt = &canceledAt.Time diff --git a/backend/internal/repository/usage_cleanup_repo_ent_test.go b/backend/internal/repository/usage_cleanup_repo_ent_test.go index 6c20b2b9a..d5238328a 100644 --- a/backend/internal/repository/usage_cleanup_repo_ent_test.go +++ b/backend/internal/repository/usage_cleanup_repo_ent_test.go @@ -209,11 +209,6 @@ func TestUsageCleanupRepositoryEntListInvalidFilters(t *testing.T) { func TestUsageCleanupTaskFromEntFull(t *testing.T) { start := time.Date(2024, 1, 2, 0, 0, 0, 0, time.UTC) end := start.Add(24 * time.Hour) - errMsg := "failed" - canceledBy := int64(2) - canceledAt := start.Add(time.Minute) - startedAt := start.Add(2 * time.Minute) - finishedAt := start.Add(3 * time.Minute) filters := service.UsageCleanupFilters{StartTime: start, EndTime: end} filtersJSON, err := json.Marshal(filters) require.NoError(t, err) @@ -224,11 +219,11 @@ func TestUsageCleanupTaskFromEntFull(t *testing.T) { Filters: filtersJSON, CreatedBy: 11, DeletedRows: 7, - ErrorMessage: &errMsg, - CanceledBy: &canceledBy, - CanceledAt: &canceledAt, - StartedAt: &startedAt, - FinishedAt: &finishedAt, + ErrorMessage: new("failed"), + CanceledBy: new(int64(2)), + CanceledAt: new(start.Add(time.Minute)), + StartedAt: new(start.Add(2 * time.Minute)), + FinishedAt: new(start.Add(3 * time.Minute)), CreatedAt: start, UpdatedAt: end, }) diff --git a/backend/internal/repository/usage_cleanup_repo_test.go b/backend/internal/repository/usage_cleanup_repo_test.go index 0ca30ec7d..d2199e81c 100644 --- a/backend/internal/repository/usage_cleanup_repo_test.go +++ b/backend/internal/repository/usage_cleanup_repo_test.go @@ -404,12 +404,11 @@ func TestUsageCleanupRepositoryDeleteUsageLogsBatch(t *testing.T) { start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) end := start.Add(24 * time.Hour) userID := int64(3) - model := " gpt-4 " filters := service.UsageCleanupFilters{ StartTime: start, EndTime: end, UserID: &userID, - Model: &model, + Model: new(" gpt-4 "), } mock.ExpectQuery("DELETE FROM usage_logs"). @@ -446,7 +445,6 @@ func TestBuildUsageCleanupWhere(t *testing.T) { apiKeyID := int64(2) accountID := int64(3) groupID := int64(4) - model := " gpt-4 " stream := true billingType := int8(2) @@ -457,7 +455,7 @@ func TestBuildUsageCleanupWhere(t *testing.T) { APIKeyID: &apiKeyID, AccountID: &accountID, GroupID: &groupID, - Model: &model, + Model: new(" gpt-4 "), Stream: &stream, BillingType: &billingType, }) @@ -469,12 +467,10 @@ func TestBuildUsageCleanupWhere(t *testing.T) { func TestBuildUsageCleanupWhereModelEmpty(t *testing.T) { start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) end := start.Add(24 * time.Hour) - model := " " - where, args := buildUsageCleanupWhere(service.UsageCleanupFilters{ StartTime: start, EndTime: end, - Model: &model, + Model: new(" "), }) require.Equal(t, "created_at >= $1 AND created_at <= $2", where) diff --git a/backend/internal/repository/usage_log_repo.go b/backend/internal/repository/usage_log_repo.go index 2db1764fa..98a26028b 100644 --- a/backend/internal/repository/usage_log_repo.go +++ b/backend/internal/repository/usage_log_repo.go @@ -2265,20 +2265,16 @@ func scanUsageLog(scanner interface{ Scan(...any) error }) (*service.UsageLog, e log.RequestID = requestID.String } if groupID.Valid { - value := groupID.Int64 - log.GroupID = &value + log.GroupID = new(groupID.Int64) } if subscriptionID.Valid { - value := subscriptionID.Int64 - log.SubscriptionID = &value + log.SubscriptionID = new(subscriptionID.Int64) } if durationMs.Valid { - value := int(durationMs.Int64) - log.DurationMs = &value + log.DurationMs = new(int(durationMs.Int64)) } if firstTokenMs.Valid { - value := int(firstTokenMs.Int64) - log.FirstTokenMs = &value + log.FirstTokenMs = new(int(firstTokenMs.Int64)) } if userAgent.Valid { log.UserAgent = &userAgent.String @@ -2368,8 +2364,7 @@ func nullFloat64Ptr(v sql.NullFloat64) *float64 { if !v.Valid { return nil } - out := v.Float64 - return &out + return new(v.Float64) } func nullString(v *string) sql.NullString { diff --git a/backend/internal/repository/user_attribute_repo.go b/backend/internal/repository/user_attribute_repo.go index 0b616caf7..f47157d7a 100644 --- a/backend/internal/repository/user_attribute_repo.go +++ b/backend/internal/repository/user_attribute_repo.go @@ -374,11 +374,9 @@ func getInt(m map[string]any, key string) *int { case int: return &n case int64: - i := int(n) - return &i + return new(int(n)) case float64: - i := int(n) - return &i + return new(int(n)) } } return nil diff --git a/backend/internal/server/middleware/api_key_auth_google_test.go b/backend/internal/server/middleware/api_key_auth_google_test.go index 38b93cb25..fe5f4dd33 100644 --- a/backend/internal/server/middleware/api_key_auth_google_test.go +++ b/backend/internal/server/middleware/api_key_auth_google_test.go @@ -180,8 +180,7 @@ func TestApiKeyAuthWithSubscriptionGoogleSetsGroupContext(t *testing.T) { if key != apiKey.Key { return nil, service.ErrAPIKeyNotFound } - clone := *apiKey - return &clone, nil + return new(*apiKey), nil }, }, nil, diff --git a/backend/internal/service/account.go b/backend/internal/service/account.go index 138d5bcb0..d9c4514d4 100644 --- a/backend/internal/service/account.go +++ b/backend/internal/service/account.go @@ -194,8 +194,7 @@ func (a *Account) GetCredentialAsTime(key string) *time.Time { } // 尝试 Unix 时间戳(纯数字字符串) if ts, err := strconv.ParseInt(s, 10, 64); err == nil { - t := time.Unix(ts, 0) - return &t + return new(time.Unix(ts, 0)) } return nil } diff --git a/backend/internal/service/account_billing_rate_multiplier_test.go b/backend/internal/service/account_billing_rate_multiplier_test.go index 731cfa7a2..7c245be45 100644 --- a/backend/internal/service/account_billing_rate_multiplier_test.go +++ b/backend/internal/service/account_billing_rate_multiplier_test.go @@ -15,13 +15,11 @@ func TestAccount_BillingRateMultiplier_DefaultsToOneWhenNil(t *testing.T) { } func TestAccount_BillingRateMultiplier_AllowsZero(t *testing.T) { - v := 0.0 - a := Account{RateMultiplier: &v} + a := Account{RateMultiplier: new(0.0)} require.Equal(t, 0.0, a.BillingRateMultiplier()) } func TestAccount_BillingRateMultiplier_NegativeFallsBackToOne(t *testing.T) { - v := -1.0 - a := Account{RateMultiplier: &v} + a := Account{RateMultiplier: new(-1.0)} require.Equal(t, 1.0, a.BillingRateMultiplier()) } diff --git a/backend/internal/service/account_usage_service.go b/backend/internal/service/account_usage_service.go index 304c57811..000be549c 100644 --- a/backend/internal/service/account_usage_service.go +++ b/backend/internal/service/account_usage_service.go @@ -250,8 +250,7 @@ func (s *AccountUsageService) GetUsage(ctx context.Context, accountID int64) (*U } // 3. 构建 UsageInfo(每次都重新计算 RemainingSeconds) - now := time.Now() - usage := s.buildUsageInfo(apiResp, &now) + usage := s.buildUsageInfo(apiResp, new(time.Now())) // 4. 添加窗口统计(有独立缓存,1 分钟) s.addWindowStats(ctx, account, usage) @@ -331,8 +330,7 @@ func (s *AccountUsageService) getGeminiUsage(ctx context.Context, account *Accou // getAntigravityUsage 获取 Antigravity 账户额度 func (s *AccountUsageService) getAntigravityUsage(ctx context.Context, account *Account) (*UsageInfo, error) { if s.antigravityQuotaFetcher == nil || !s.antigravityQuotaFetcher.CanFetch(account) { - now := time.Now() - return &UsageInfo{UpdatedAt: &now}, nil + return &UsageInfo{UpdatedAt: new(time.Now())}, nil } // 1. 检查缓存(10 分钟) @@ -589,10 +587,9 @@ func buildGeminiUsageProgress(used, limit int64, resetAt time.Time, tokens int64 if remainingSeconds < 0 { remainingSeconds = 0 } - resetCopy := resetAt return &UsageProgress{ Utilization: utilization, - ResetsAt: &resetCopy, + ResetsAt: new(resetAt), RemainingSeconds: remainingSeconds, UsedRequests: used, LimitRequests: limit, diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go index 06354e1e0..2a5f8946b 100644 --- a/backend/internal/service/admin_service.go +++ b/backend/internal/service/admin_service.go @@ -468,8 +468,7 @@ func (s *adminServiceImpl) UpdateUser(ctx context.Context, id int64, input *Upda Status: StatusUsed, UsedBy: &user.ID, } - now := time.Now() - adjustmentRecord.UsedAt = &now + adjustmentRecord.UsedAt = new(time.Now()) if err := s.redeemCodeRepo.Create(ctx, adjustmentRecord); err != nil { log.Printf("failed to create concurrency adjustment redeem code: %v", err) } @@ -551,8 +550,7 @@ func (s *adminServiceImpl) UpdateUserBalance(ctx context.Context, userID int64, UsedBy: &user.ID, Notes: notes, } - now := time.Now() - adjustmentRecord.UsedAt = &now + adjustmentRecord.UsedAt = new(time.Now()) if err := s.redeemCodeRepo.Create(ctx, adjustmentRecord); err != nil { log.Printf("failed to create balance adjustment redeem code: %v", err) @@ -1085,8 +1083,7 @@ func (s *adminServiceImpl) CreateAccount(ctx context.Context, input *CreateAccou Schedulable: true, } if input.ExpiresAt != nil && *input.ExpiresAt > 0 { - expiresAt := time.Unix(*input.ExpiresAt, 0) - account.ExpiresAt = &expiresAt + account.ExpiresAt = new(time.Unix(*input.ExpiresAt, 0)) } if input.AutoPauseOnExpired != nil { account.AutoPauseOnExpired = *input.AutoPauseOnExpired @@ -1164,8 +1161,7 @@ func (s *adminServiceImpl) UpdateAccount(ctx context.Context, id int64, input *U if *input.ExpiresAt <= 0 { account.ExpiresAt = nil } else { - expiresAt := time.Unix(*input.ExpiresAt, 0) - account.ExpiresAt = &expiresAt + account.ExpiresAt = new(time.Unix(*input.ExpiresAt, 0)) } } if input.AutoPauseOnExpired != nil { @@ -1605,10 +1601,9 @@ func (s *adminServiceImpl) TestProxy(ctx context.Context, id int64) (*ProxyTestR }, nil } - latency := latencyMs s.saveProxyLatency(ctx, id, &ProxyLatencyInfo{ Success: true, - LatencyMs: &latency, + LatencyMs: new(latencyMs), Message: "Proxy is accessible", IPAddress: exitInfo.IP, Country: exitInfo.Country, @@ -1643,10 +1638,9 @@ func (s *adminServiceImpl) probeProxyLatency(ctx context.Context, proxy *Proxy) return } - latency := latencyMs s.saveProxyLatency(ctx, proxy.ID, &ProxyLatencyInfo{ Success: true, - LatencyMs: &latency, + LatencyMs: new(latencyMs), Message: "Proxy is accessible", IPAddress: exitInfo.IP, Country: exitInfo.Country, diff --git a/backend/internal/service/announcement_service.go b/backend/internal/service/announcement_service.go index c2588e6ca..341f0004a 100644 --- a/backend/internal/service/announcement_service.go +++ b/backend/internal/service/announcement_service.go @@ -249,8 +249,7 @@ func (s *AnnouncementService) ListForUser(ctx context.Context, userID int64, unr } var ptr *time.Time if ok { - t := readAt - ptr = &t + ptr = new(readAt) } out = append(out, UserAnnouncement{ Announcement: a, @@ -351,8 +350,7 @@ func (s *AnnouncementService) ListUserReadStatus( readAt, ok := readMap[u.ID] var ptr *time.Time if ok { - t := readAt - ptr = &t + ptr = new(readAt) } out = append(out, AnnouncementUserReadStatus{ diff --git a/backend/internal/service/antigravity_gateway_service.go b/backend/internal/service/antigravity_gateway_service.go index 9e7ed7d5a..7385af999 100644 --- a/backend/internal/service/antigravity_gateway_service.go +++ b/backend/internal/service/antigravity_gateway_service.go @@ -105,8 +105,7 @@ func (e *AntigravityAccountSwitchError) Error() string { // IsAntigravityAccountSwitchError 检查错误是否为账号切换信号 func IsAntigravityAccountSwitchError(err error) (*AntigravityAccountSwitchError, bool) { - var switchErr *AntigravityAccountSwitchError - if errors.As(err, &switchErr) { + if switchErr, ok := errors.AsType[*AntigravityAccountSwitchError](err); ok { return switchErr, true } return nil, false @@ -769,8 +768,7 @@ func isAntigravityConnectionError(err error) bool { } // 检查超时错误 - var netErr net.Error - if errors.As(err, &netErr) && netErr.Timeout() { + if netErr, ok := errors.AsType[net.Error](err); ok && netErr.Timeout() { return true } @@ -2905,8 +2903,7 @@ func (s *AntigravityGatewayService) handleGeminiStreamingResponse(c *gin.Context } if firstTokenMs == nil { - ms := int(time.Since(startTime).Milliseconds()) - firstTokenMs = &ms + firstTokenMs = new(int(time.Since(startTime).Milliseconds())) } cw.Fprintf("data: %s\n\n", payload) @@ -3035,8 +3032,7 @@ func (s *AntigravityGatewayService) handleGeminiStreamToNonStreaming(c *gin.Cont // 记录首 token 时间 if firstTokenMs == nil { - ms := int(time.Since(startTime).Milliseconds()) - firstTokenMs = &ms + firstTokenMs = new(int(time.Since(startTime).Milliseconds())) } last = parsed @@ -3496,8 +3492,7 @@ func (s *AntigravityGatewayService) handleClaudeStreamToNonStreaming(c *gin.Cont // 记录首 token 时间 if firstTokenMs == nil { - ms := int(time.Since(startTime).Milliseconds()) - firstTokenMs = &ms + firstTokenMs = new(int(time.Since(startTime).Milliseconds())) } last = parsed @@ -3693,8 +3688,7 @@ func (s *AntigravityGatewayService) handleClaudeStreamingResponse(c *gin.Context claudeEvents := processor.ProcessLine(strings.TrimRight(ev.line, "\r\n")) if len(claudeEvents) > 0 { if firstTokenMs == nil { - ms := int(time.Since(startTime).Milliseconds()) - firstTokenMs = &ms + firstTokenMs = new(int(time.Since(startTime).Milliseconds())) } cw.Write(claudeEvents) } @@ -4060,8 +4054,7 @@ func (s *AntigravityGatewayService) streamUpstreamResponse(c *gin.Context, resp // 记录首 token 时间 if firstTokenMs == nil && len(line) > 0 { - ms := int(time.Since(startTime).Milliseconds()) - firstTokenMs = &ms + firstTokenMs = new(int(time.Since(startTime).Milliseconds())) } // 尝试从 message_delta 或 message_stop 事件提取 usage diff --git a/backend/internal/service/antigravity_quota_fetcher.go b/backend/internal/service/antigravity_quota_fetcher.go index 07eb563d0..63dfc50e3 100644 --- a/backend/internal/service/antigravity_quota_fetcher.go +++ b/backend/internal/service/antigravity_quota_fetcher.go @@ -50,9 +50,8 @@ func (f *AntigravityQuotaFetcher) FetchQuota(ctx context.Context, account *Accou // buildUsageInfo 将 API 响应转换为 UsageInfo func (f *AntigravityQuotaFetcher) buildUsageInfo(modelsResp *antigravity.FetchAvailableModelsResponse) *UsageInfo { - now := time.Now() info := &UsageInfo{ - UpdatedAt: &now, + UpdatedAt: new(time.Now()), AntigravityQuota: make(map[string]*AntigravityModelQuota), } diff --git a/backend/internal/service/api_key_service.go b/backend/internal/service/api_key_service.go index cb1dd60a0..4a339dda3 100644 --- a/backend/internal/service/api_key_service.go +++ b/backend/internal/service/api_key_service.go @@ -315,8 +315,7 @@ func (s *APIKeyService) Create(ctx context.Context, userID int64, req CreateAPIK // Set expiration time if specified if req.ExpiresInDays != nil && *req.ExpiresInDays > 0 { - expiresAt := time.Now().AddDate(0, 0, *req.ExpiresInDays) - apiKey.ExpiresAt = &expiresAt + apiKey.ExpiresAt = new(time.Now().AddDate(0, 0, *req.ExpiresInDays)) } if err := s.apiKeyRepo.Create(ctx, apiKey); err != nil { diff --git a/backend/internal/service/crs_sync_service.go b/backend/internal/service/crs_sync_service.go index 040b2357b..68e0f2386 100644 --- a/backend/internal/service/crs_sync_service.go +++ b/backend/internal/service/crs_sync_service.go @@ -1055,8 +1055,7 @@ func (s *CRSSyncService) mapOrCreateProxy(ctx context.Context, enabled bool, cac p.Port == port && p.Username == username && p.Password == password { - id := p.ID - return &id, nil + return new(p.ID), nil } } @@ -1075,8 +1074,7 @@ func (s *CRSSyncService) mapOrCreateProxy(ctx context.Context, enabled bool, cac } *cached = append(*cached, *proxy) - id := proxy.ID - return &id, nil + return new(proxy.ID), nil } func defaultProxyName(base, protocol, host string, port int) string { diff --git a/backend/internal/service/gateway_service.go b/backend/internal/service/gateway_service.go index 56af4610b..ea9469832 100644 --- a/backend/internal/service/gateway_service.go +++ b/backend/internal/service/gateway_service.go @@ -4356,8 +4356,7 @@ func (s *GatewayService) handleStreamingResponse(ctx context.Context, resp *http } if data != "" { if firstTokenMs == nil && data != "[DONE]" { - ms := int(time.Since(startTime).Milliseconds()) - firstTokenMs = &ms + firstTokenMs = new(int(time.Since(startTime).Milliseconds())) } s.parseSSEUsage(data, usage) } @@ -4587,12 +4586,10 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu } // 创建使用日志 - durationMs := int(result.Duration.Milliseconds()) var imageSize *string if result.ImageSize != "" { imageSize = &result.ImageSize } - accountRateMultiplier := account.BillingRateMultiplier() usageLog := &UsageLog{ UserID: user.ID, APIKeyID: apiKey.ID, @@ -4610,10 +4607,10 @@ func (s *GatewayService) RecordUsage(ctx context.Context, input *RecordUsageInpu TotalCost: cost.TotalCost, ActualCost: cost.ActualCost, RateMultiplier: multiplier, - AccountRateMultiplier: &accountRateMultiplier, + AccountRateMultiplier: new(account.BillingRateMultiplier()), BillingType: billingType, Stream: result.Stream, - DurationMs: &durationMs, + DurationMs: new(int(result.Duration.Milliseconds())), FirstTokenMs: result.FirstTokenMs, ImageCount: result.ImageCount, ImageSize: imageSize, @@ -4768,12 +4765,10 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input * } // 创建使用日志 - durationMs := int(result.Duration.Milliseconds()) var imageSize *string if result.ImageSize != "" { imageSize = &result.ImageSize } - accountRateMultiplier := account.BillingRateMultiplier() usageLog := &UsageLog{ UserID: user.ID, APIKeyID: apiKey.ID, @@ -4791,10 +4786,10 @@ func (s *GatewayService) RecordUsageWithLongContext(ctx context.Context, input * TotalCost: cost.TotalCost, ActualCost: cost.ActualCost, RateMultiplier: multiplier, - AccountRateMultiplier: &accountRateMultiplier, + AccountRateMultiplier: new(account.BillingRateMultiplier()), BillingType: billingType, Stream: result.Stream, - DurationMs: &durationMs, + DurationMs: new(int(result.Duration.Milliseconds())), FirstTokenMs: result.FirstTokenMs, ImageCount: result.ImageCount, ImageSize: imageSize, diff --git a/backend/internal/service/gemini_messages_compat_service.go b/backend/internal/service/gemini_messages_compat_service.go index f3abd1dc4..fd6d25beb 100644 --- a/backend/internal/service/gemini_messages_compat_service.go +++ b/backend/internal/service/gemini_messages_compat_service.go @@ -1938,8 +1938,7 @@ func (s *GeminiMessagesCompatService) handleStreamingResponse(c *gin.Context, re } if firstTokenMs == nil { - ms := int(time.Since(startTime).Milliseconds()) - firstTokenMs = &ms + firstTokenMs = new(int(time.Since(startTime).Milliseconds())) } writeSSE(c.Writer, "content_block_delta", map[string]any{ "type": "content_block_delta", @@ -2480,8 +2479,7 @@ func (s *GeminiMessagesCompatService) handleNativeStreamingResponse(c *gin.Conte } if firstTokenMs == nil { - ms := int(time.Since(startTime).Milliseconds()) - firstTokenMs = &ms + firstTokenMs = new(int(time.Since(startTime).Milliseconds())) } if isOAuth { @@ -2768,8 +2766,7 @@ func ParseGeminiRateLimitResetTime(body []byte) *int64 { if dur, err := time.ParseDuration(v); err == nil { // Use ceil to avoid undercounting fractional seconds (e.g. 10.1s should not become 10s), // which can affect scheduling decisions around thresholds (like 10s). - ts := time.Now().Unix() + int64(math.Ceil(dur.Seconds())) - return &ts + return new(time.Now().Unix() + int64(math.Ceil(dur.Seconds()))) } } } @@ -2782,8 +2779,7 @@ func ParseGeminiRateLimitResetTime(body []byte) *int64 { matches := retryInRegex.FindStringSubmatch(string(body)) if len(matches) == 2 { if dur, err := time.ParseDuration(matches[1] + "s"); err == nil { - ts := time.Now().Unix() + int64(math.Ceil(dur.Seconds())) - return &ts + return new(time.Now().Unix() + int64(math.Ceil(dur.Seconds()))) } } @@ -2800,8 +2796,7 @@ func looksLikeGeminiDailyQuota(message string) bool { func nextGeminiDailyResetUnix() *int64 { reset := geminiDailyResetTime(time.Now()) - ts := reset.Unix() - return &ts + return new(reset.Unix()) } func ensureGeminiFunctionCallThoughtSignatures(body []byte) []byte { diff --git a/backend/internal/service/openai_gateway_service.go b/backend/internal/service/openai_gateway_service.go index 6c4fe256c..dbe6bd9be 100644 --- a/backend/internal/service/openai_gateway_service.go +++ b/backend/internal/service/openai_gateway_service.go @@ -1379,8 +1379,7 @@ func (s *OpenAIGatewayService) handleStreamingResponse(ctx context.Context, resp // Record first token time if firstTokenMs == nil && data != "" && data != "[DONE]" { - ms := int(time.Since(startTime).Milliseconds()) - firstTokenMs = &ms + firstTokenMs = new(int(time.Since(startTime).Milliseconds())) } s.parseSSEUsage(data, usage) } else { @@ -1756,8 +1755,6 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec } // Create usage log - durationMs := int(result.Duration.Milliseconds()) - accountRateMultiplier := account.BillingRateMultiplier() usageLog := &UsageLog{ UserID: user.ID, APIKeyID: apiKey.ID, @@ -1776,10 +1773,10 @@ func (s *OpenAIGatewayService) RecordUsage(ctx context.Context, input *OpenAIRec TotalCost: cost.TotalCost, ActualCost: cost.ActualCost, RateMultiplier: multiplier, - AccountRateMultiplier: &accountRateMultiplier, + AccountRateMultiplier: new(account.BillingRateMultiplier()), BillingType: billingType, Stream: result.Stream, - DurationMs: &durationMs, + DurationMs: new(int(result.Duration.Milliseconds())), FirstTokenMs: result.FirstTokenMs, CreatedAt: time.Now(), } diff --git a/backend/internal/service/openai_gateway_service_test.go b/backend/internal/service/openai_gateway_service_test.go index ae69a9867..574faff56 100644 --- a/backend/internal/service/openai_gateway_service_test.go +++ b/backend/internal/service/openai_gateway_service_test.go @@ -206,9 +206,6 @@ func (c *stubGatewayCache) DeleteSessionAccountID(ctx context.Context, groupID i func TestOpenAISelectAccountWithLoadAwareness_FiltersUnschedulable(t *testing.T) { now := time.Now() - resetAt := now.Add(10 * time.Minute) - groupID := int64(1) - rateLimited := Account{ ID: 1, Platform: PlatformOpenAI, @@ -217,7 +214,7 @@ func TestOpenAISelectAccountWithLoadAwareness_FiltersUnschedulable(t *testing.T) Schedulable: true, Concurrency: 1, Priority: 0, - RateLimitResetAt: &resetAt, + RateLimitResetAt: new(now.Add(10 * time.Minute)), } available := Account{ ID: 2, @@ -234,7 +231,7 @@ func TestOpenAISelectAccountWithLoadAwareness_FiltersUnschedulable(t *testing.T) concurrencyService: NewConcurrencyService(stubConcurrencyCache{}), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "", "gpt-5.2", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), "", "gpt-5.2", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } @@ -251,9 +248,6 @@ func TestOpenAISelectAccountWithLoadAwareness_FiltersUnschedulable(t *testing.T) func TestOpenAISelectAccountWithLoadAwareness_FiltersUnschedulableWhenNoConcurrencyService(t *testing.T) { now := time.Now() - resetAt := now.Add(10 * time.Minute) - groupID := int64(1) - rateLimited := Account{ ID: 1, Platform: PlatformOpenAI, @@ -262,7 +256,7 @@ func TestOpenAISelectAccountWithLoadAwareness_FiltersUnschedulableWhenNoConcurre Schedulable: true, Concurrency: 1, Priority: 0, - RateLimitResetAt: &resetAt, + RateLimitResetAt: new(now.Add(10 * time.Minute)), } available := Account{ ID: 2, @@ -279,7 +273,7 @@ func TestOpenAISelectAccountWithLoadAwareness_FiltersUnschedulableWhenNoConcurre // concurrencyService is nil, forcing the non-load-batch selection path. } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "", "gpt-5.2", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), "", "gpt-5.2", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } @@ -328,7 +322,6 @@ func TestOpenAISelectAccountForModelWithExclusions_StickyUnschedulableClearsSess func TestOpenAISelectAccountWithLoadAwareness_StickyUnschedulableClearsSession(t *testing.T) { sessionHash := "session-2" - groupID := int64(1) repo := stubOpenAIAccountRepo{ accounts: []Account{ {ID: 1, Platform: PlatformOpenAI, Status: StatusDisabled, Schedulable: true, Concurrency: 1}, @@ -345,7 +338,7 @@ func TestOpenAISelectAccountWithLoadAwareness_StickyUnschedulableClearsSession(t concurrencyService: NewConcurrencyService(stubConcurrencyCache{}), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, sessionHash, "gpt-4", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), sessionHash, "gpt-4", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } @@ -395,7 +388,6 @@ func TestOpenAISelectAccountForModelWithExclusions_NoModelSupport(t *testing.T) } func TestOpenAISelectAccountWithLoadAwareness_LoadBatchErrorFallback(t *testing.T) { - groupID := int64(1) repo := stubOpenAIAccountRepo{ accounts: []Account{ {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 2}, @@ -413,7 +405,7 @@ func TestOpenAISelectAccountWithLoadAwareness_LoadBatchErrorFallback(t *testing. concurrencyService: NewConcurrencyService(concurrencyCache), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "fallback", "gpt-4", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), "fallback", "gpt-4", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } @@ -432,7 +424,6 @@ func TestOpenAISelectAccountWithLoadAwareness_LoadBatchErrorFallback(t *testing. } func TestOpenAISelectAccountWithLoadAwareness_NoSlotFallbackWait(t *testing.T) { - groupID := int64(1) repo := stubOpenAIAccountRepo{ accounts: []Account{ {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1}, @@ -452,7 +443,7 @@ func TestOpenAISelectAccountWithLoadAwareness_NoSlotFallbackWait(t *testing.T) { concurrencyService: NewConcurrencyService(concurrencyCache), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "", "gpt-4", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), "", "gpt-4", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } @@ -492,7 +483,6 @@ func TestOpenAISelectAccountForModelWithExclusions_SetsStickyBinding(t *testing. func TestOpenAISelectAccountWithLoadAwareness_StickyWaitPlan(t *testing.T) { sessionHash := "sticky-wait" - groupID := int64(1) repo := stubOpenAIAccountRepo{ accounts: []Account{ {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1}, @@ -512,7 +502,7 @@ func TestOpenAISelectAccountWithLoadAwareness_StickyWaitPlan(t *testing.T) { concurrencyService: NewConcurrencyService(concurrencyCache), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, sessionHash, "gpt-4", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), sessionHash, "gpt-4", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } @@ -525,7 +515,6 @@ func TestOpenAISelectAccountWithLoadAwareness_StickyWaitPlan(t *testing.T) { } func TestOpenAISelectAccountWithLoadAwareness_PrefersLowerLoad(t *testing.T) { - groupID := int64(1) repo := stubOpenAIAccountRepo{ accounts: []Account{ {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1}, @@ -546,7 +535,7 @@ func TestOpenAISelectAccountWithLoadAwareness_PrefersLowerLoad(t *testing.T) { concurrencyService: NewConcurrencyService(concurrencyCache), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "load", "gpt-4", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), "load", "gpt-4", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } @@ -633,11 +622,9 @@ func TestOpenAISelectAccountForModelWithExclusions_NoAccounts(t *testing.T) { } func TestOpenAISelectAccountWithLoadAwareness_NoCandidates(t *testing.T) { - groupID := int64(1) - resetAt := time.Now().Add(1 * time.Hour) repo := stubOpenAIAccountRepo{ accounts: []Account{ - {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1, RateLimitResetAt: &resetAt}, + {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1, RateLimitResetAt: new(time.Now().Add(1 * time.Hour))}, }, } cache := &stubGatewayCache{} @@ -649,7 +636,7 @@ func TestOpenAISelectAccountWithLoadAwareness_NoCandidates(t *testing.T) { concurrencyService: NewConcurrencyService(concurrencyCache), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "", "gpt-4", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), "", "gpt-4", nil) if err == nil { t.Fatalf("expected error for no candidates") } @@ -659,7 +646,6 @@ func TestOpenAISelectAccountWithLoadAwareness_NoCandidates(t *testing.T) { } func TestOpenAISelectAccountWithLoadAwareness_AllFullWaitPlan(t *testing.T) { - groupID := int64(1) repo := stubOpenAIAccountRepo{ accounts: []Account{ {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1}, @@ -678,7 +664,7 @@ func TestOpenAISelectAccountWithLoadAwareness_AllFullWaitPlan(t *testing.T) { concurrencyService: NewConcurrencyService(concurrencyCache), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "", "gpt-4", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), "", "gpt-4", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } @@ -688,7 +674,6 @@ func TestOpenAISelectAccountWithLoadAwareness_AllFullWaitPlan(t *testing.T) { } func TestOpenAISelectAccountWithLoadAwareness_LoadBatchErrorNoAcquire(t *testing.T) { - groupID := int64(1) repo := stubOpenAIAccountRepo{ accounts: []Account{ {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1}, @@ -706,7 +691,7 @@ func TestOpenAISelectAccountWithLoadAwareness_LoadBatchErrorNoAcquire(t *testing concurrencyService: NewConcurrencyService(concurrencyCache), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "", "gpt-4", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), "", "gpt-4", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } @@ -716,7 +701,6 @@ func TestOpenAISelectAccountWithLoadAwareness_LoadBatchErrorNoAcquire(t *testing } func TestOpenAISelectAccountWithLoadAwareness_MissingLoadInfo(t *testing.T) { - groupID := int64(1) repo := stubOpenAIAccountRepo{ accounts: []Account{ {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1}, @@ -737,7 +721,7 @@ func TestOpenAISelectAccountWithLoadAwareness_MissingLoadInfo(t *testing.T) { concurrencyService: NewConcurrencyService(concurrencyCache), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "", "gpt-4", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), "", "gpt-4", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } @@ -747,12 +731,10 @@ func TestOpenAISelectAccountWithLoadAwareness_MissingLoadInfo(t *testing.T) { } func TestOpenAISelectAccountForModelWithExclusions_LeastRecentlyUsed(t *testing.T) { - oldTime := time.Now().Add(-2 * time.Hour) - newTime := time.Now().Add(-1 * time.Hour) repo := stubOpenAIAccountRepo{ accounts: []Account{ - {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Priority: 1, LastUsedAt: &newTime}, - {ID: 2, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Priority: 1, LastUsedAt: &oldTime}, + {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Priority: 1, LastUsedAt: new(time.Now().Add(-1 * time.Hour))}, + {ID: 2, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Priority: 1, LastUsedAt: new(time.Now().Add(-2 * time.Hour))}, }, } cache := &stubGatewayCache{} @@ -772,11 +754,9 @@ func TestOpenAISelectAccountForModelWithExclusions_LeastRecentlyUsed(t *testing. } func TestOpenAISelectAccountWithLoadAwareness_PreferNeverUsed(t *testing.T) { - groupID := int64(1) - lastUsed := time.Now().Add(-1 * time.Hour) repo := stubOpenAIAccountRepo{ accounts: []Account{ - {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1, LastUsedAt: &lastUsed}, + {ID: 1, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1, LastUsedAt: new(time.Now().Add(-1 * time.Hour))}, {ID: 2, Platform: PlatformOpenAI, Status: StatusActive, Schedulable: true, Concurrency: 1, Priority: 1}, }, } @@ -794,7 +774,7 @@ func TestOpenAISelectAccountWithLoadAwareness_PreferNeverUsed(t *testing.T) { concurrencyService: NewConcurrencyService(concurrencyCache), } - selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), &groupID, "", "gpt-4", nil) + selection, err := svc.SelectAccountWithLoadAwareness(context.Background(), new(int64(1)), "", "gpt-4", nil) if err != nil { t.Fatalf("SelectAccountWithLoadAwareness error: %v", err) } diff --git a/backend/internal/service/ops_account_availability.go b/backend/internal/service/ops_account_availability.go index da66ec4dd..2559c84e3 100644 --- a/backend/internal/service/ops_account_availability.go +++ b/backend/internal/service/ops_account_availability.go @@ -39,8 +39,6 @@ func (s *OpsService) GetAccountAvailabilityStats(ctx context.Context, platformFi } now := time.Now() - collectedAt := now - platform := make(map[string]*PlatformAvailability) group := make(map[int64]*GroupAvailability) account := make(map[int64]*AccountAvailability) @@ -154,7 +152,7 @@ func (s *OpsService) GetAccountAvailabilityStats(ctx context.Context, platformFi account[acc.ID] = item } - return platform, group, account, &collectedAt, nil + return platform, group, account, new(now), nil } type OpsAccountAvailability struct { diff --git a/backend/internal/service/ops_aggregation_service.go b/backend/internal/service/ops_aggregation_service.go index 972462ec8..2736f9845 100644 --- a/backend/internal/service/ops_aggregation_service.go +++ b/backend/internal/service/ops_aggregation_service.go @@ -219,30 +219,26 @@ func (s *OpsAggregationService) aggregateHourly() { dur := durationMs if aggErr != nil { - msg := truncateString(aggErr.Error(), 2048) - errAt := finishedAt hbCtx, hbCancel := context.WithTimeout(context.Background(), 2*time.Second) defer hbCancel() _ = s.opsRepo.UpsertJobHeartbeat(hbCtx, &OpsUpsertJobHeartbeatInput{ JobName: opsAggHourlyJobName, LastRunAt: &runAt, - LastErrorAt: &errAt, - LastError: &msg, + LastErrorAt: new(finishedAt), + LastError: new(truncateString(aggErr.Error(), 2048)), LastDurationMs: &dur, }) return } - successAt := finishedAt hbCtx, hbCancel := context.WithTimeout(context.Background(), 2*time.Second) defer hbCancel() - result := truncateString(fmt.Sprintf("window=%s..%s", start.Format(time.RFC3339), end.Format(time.RFC3339)), 2048) _ = s.opsRepo.UpsertJobHeartbeat(hbCtx, &OpsUpsertJobHeartbeatInput{ JobName: opsAggHourlyJobName, LastRunAt: &runAt, - LastSuccessAt: &successAt, + LastSuccessAt: new(finishedAt), LastDurationMs: &dur, - LastResult: &result, + LastResult: new(truncateString(fmt.Sprintf("window=%s..%s", start.Format(time.RFC3339), end.Format(time.RFC3339)), 2048)), }) } @@ -317,30 +313,26 @@ func (s *OpsAggregationService) aggregateDaily() { dur := durationMs if aggErr != nil { - msg := truncateString(aggErr.Error(), 2048) - errAt := finishedAt hbCtx, hbCancel := context.WithTimeout(context.Background(), 2*time.Second) defer hbCancel() _ = s.opsRepo.UpsertJobHeartbeat(hbCtx, &OpsUpsertJobHeartbeatInput{ JobName: opsAggDailyJobName, LastRunAt: &runAt, - LastErrorAt: &errAt, - LastError: &msg, + LastErrorAt: new(finishedAt), + LastError: new(truncateString(aggErr.Error(), 2048)), LastDurationMs: &dur, }) return } - successAt := finishedAt hbCtx, hbCancel := context.WithTimeout(context.Background(), 2*time.Second) defer hbCancel() - result := truncateString(fmt.Sprintf("window=%s..%s", start.Format(time.RFC3339), end.Format(time.RFC3339)), 2048) _ = s.opsRepo.UpsertJobHeartbeat(hbCtx, &OpsUpsertJobHeartbeatInput{ JobName: opsAggDailyJobName, LastRunAt: &runAt, - LastSuccessAt: &successAt, + LastSuccessAt: new(finishedAt), LastDurationMs: &dur, - LastResult: &result, + LastResult: new(truncateString(fmt.Sprintf("window=%s..%s", start.Format(time.RFC3339), end.Format(time.RFC3339)), 2048)), }) } diff --git a/backend/internal/service/ops_alert_evaluator_service.go b/backend/internal/service/ops_alert_evaluator_service.go index 7c62e2479..b5f31e586 100644 --- a/backend/internal/service/ops_alert_evaluator_service.go +++ b/backend/internal/service/ops_alert_evaluator_service.go @@ -298,8 +298,7 @@ func (s *OpsAlertEvaluatorService) evaluateOnce(interval time.Duration) { // Not breached: resolve active event if present. if activeEvent != nil { - resolvedAt := now - if err := s.opsRepo.UpdateAlertEventStatus(ctx, activeEvent.ID, OpsAlertStatusResolved, &resolvedAt); err != nil { + if err := s.opsRepo.UpdateAlertEventStatus(ctx, activeEvent.ID, OpsAlertStatusResolved, new(now)); err != nil { log.Printf("[OpsAlertEvaluator] resolve event failed (event=%d): %v", activeEvent.ID, err) } else { eventsResolved++ @@ -398,18 +397,15 @@ func parseOpsAlertRuleScope(filters map[string]any) (platform string, groupID *i switch t := v.(type) { case float64: if t > 0 { - id := int64(t) - groupID = &id + groupID = new(int64(t)) } case int64: if t > 0 { - id := t - groupID = &id + groupID = new(t) } case int: if t > 0 { - id := int64(t) - groupID = &id + groupID = new(int64(t)) } case string: n, err := strconv.ParseInt(strings.TrimSpace(t), 10, 64) @@ -826,8 +822,6 @@ func (s *OpsAlertEvaluatorService) recordHeartbeatSuccess(runAt time.Time, durat if s == nil || s.opsRepo == nil { return } - now := time.Now().UTC() - durMs := duration.Milliseconds() ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() msg := strings.TrimSpace(result) @@ -838,8 +832,8 @@ func (s *OpsAlertEvaluatorService) recordHeartbeatSuccess(runAt time.Time, durat _ = s.opsRepo.UpsertJobHeartbeat(ctx, &OpsUpsertJobHeartbeatInput{ JobName: opsAlertEvaluatorJobName, LastRunAt: &runAt, - LastSuccessAt: &now, - LastDurationMs: &durMs, + LastSuccessAt: new(time.Now().UTC()), + LastDurationMs: new(duration.Milliseconds()), LastResult: &msg, }) } @@ -848,17 +842,14 @@ func (s *OpsAlertEvaluatorService) recordHeartbeatError(runAt time.Time, duratio if s == nil || s.opsRepo == nil || err == nil { return } - now := time.Now().UTC() - durMs := duration.Milliseconds() - msg := truncateString(err.Error(), 2048) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _ = s.opsRepo.UpsertJobHeartbeat(ctx, &OpsUpsertJobHeartbeatInput{ JobName: opsAlertEvaluatorJobName, LastRunAt: &runAt, - LastErrorAt: &now, - LastError: &msg, - LastDurationMs: &durMs, + LastErrorAt: new(time.Now().UTC()), + LastError: new(truncateString(err.Error(), 2048)), + LastDurationMs: new(duration.Milliseconds()), }) } diff --git a/backend/internal/service/ops_cleanup_service.go b/backend/internal/service/ops_cleanup_service.go index 1ade7176d..768405ec6 100644 --- a/backend/internal/service/ops_cleanup_service.go +++ b/backend/internal/service/ops_cleanup_service.go @@ -334,17 +334,14 @@ func (s *OpsCleanupService) recordHeartbeatSuccess(runAt time.Time, duration tim if s == nil || s.opsRepo == nil { return } - now := time.Now().UTC() - durMs := duration.Milliseconds() - result := truncateString(counts.String(), 2048) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _ = s.opsRepo.UpsertJobHeartbeat(ctx, &OpsUpsertJobHeartbeatInput{ JobName: opsCleanupJobName, LastRunAt: &runAt, - LastSuccessAt: &now, - LastDurationMs: &durMs, - LastResult: &result, + LastSuccessAt: new(time.Now().UTC()), + LastDurationMs: new(duration.Milliseconds()), + LastResult: new(truncateString(counts.String(), 2048)), }) } @@ -352,16 +349,13 @@ func (s *OpsCleanupService) recordHeartbeatError(runAt time.Time, duration time. if s == nil || s.opsRepo == nil || err == nil { return } - now := time.Now().UTC() - durMs := duration.Milliseconds() - msg := truncateString(err.Error(), 2048) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _ = s.opsRepo.UpsertJobHeartbeat(ctx, &OpsUpsertJobHeartbeatInput{ JobName: opsCleanupJobName, LastRunAt: &runAt, - LastErrorAt: &now, - LastError: &msg, - LastDurationMs: &durMs, + LastErrorAt: new(time.Now().UTC()), + LastError: new(truncateString(err.Error(), 2048)), + LastDurationMs: new(duration.Milliseconds()), }) } diff --git a/backend/internal/service/ops_concurrency.go b/backend/internal/service/ops_concurrency.go index f6541d088..99788c671 100644 --- a/backend/internal/service/ops_concurrency.go +++ b/backend/internal/service/ops_concurrency.go @@ -116,7 +116,6 @@ func (s *OpsService) GetConcurrencyStats( return nil, nil, nil, nil, err } - collectedAt := time.Now() loadMap := s.getAccountsLoadMapBestEffort(ctx, accounts) platform := make(map[string]*PlatformConcurrencyInfo) @@ -253,7 +252,7 @@ func (s *OpsService) GetConcurrencyStats( } } - return platform, group, account, &collectedAt, nil + return platform, group, account, new(time.Now()), nil } // listAllActiveUsersForOps returns all active users with their concurrency settings. @@ -355,7 +354,6 @@ func (s *OpsService) GetUserConcurrencyStats(ctx context.Context) (map[int64]*Us return nil, nil, err } - collectedAt := time.Now() loadMap := s.getUsersLoadMapBestEffort(ctx, users) result := make(map[int64]*UserConcurrencyInfo) @@ -392,5 +390,5 @@ func (s *OpsService) GetUserConcurrencyStats(ctx context.Context) (map[int64]*Us result[u.ID] = info } - return result, &collectedAt, nil + return result, new(time.Now()), nil } diff --git a/backend/internal/service/ops_metrics_collector.go b/backend/internal/service/ops_metrics_collector.go index 30adaae06..db92d9d6b 100644 --- a/backend/internal/service/ops_metrics_collector.go +++ b/backend/internal/service/ops_metrics_collector.go @@ -193,28 +193,25 @@ func (c *OpsMetricsCollector) collectOnce() { runAt := startedAt if err != nil { - msg := truncateString(err.Error(), 2048) - errAt := finishedAt hbCtx, hbCancel := context.WithTimeout(context.Background(), opsMetricsCollectorHeartbeatTimeout) defer hbCancel() _ = c.opsRepo.UpsertJobHeartbeat(hbCtx, &OpsUpsertJobHeartbeatInput{ JobName: opsMetricsCollectorJobName, LastRunAt: &runAt, - LastErrorAt: &errAt, - LastError: &msg, + LastErrorAt: new(finishedAt), + LastError: new(truncateString(err.Error(), 2048)), LastDurationMs: &dur, }) log.Printf("[OpsMetricsCollector] collect failed: %v", err) return } - successAt := finishedAt hbCtx, hbCancel := context.WithTimeout(context.Background(), opsMetricsCollectorHeartbeatTimeout) defer hbCancel() _ = c.opsRepo.UpsertJobHeartbeat(hbCtx, &OpsUpsertJobHeartbeatInput{ JobName: opsMetricsCollectorJobName, LastRunAt: &runAt, - LastSuccessAt: &successAt, + LastSuccessAt: new(finishedAt), LastDurationMs: &dur, }) } @@ -380,8 +377,7 @@ func (c *OpsMetricsCollector) collectConcurrencyQueueDepth(parentCtx context.Con return nil } if len(accounts) == 0 { - zero := 0 - return &zero + return new(0) } batch := make([]AccountWithConcurrency, 0, len(accounts)) @@ -399,8 +395,7 @@ func (c *OpsMetricsCollector) collectConcurrencyQueueDepth(parentCtx context.Con }) } if len(batch) == 0 { - zero := 0 - return &zero + return new(0) } loadMap, err := c.concurrencyService.GetAccountsLoadBatch(ctx, batch) @@ -423,8 +418,7 @@ func (c *OpsMetricsCollector) collectConcurrencyQueueDepth(parentCtx context.Con if total > maxInt { total = maxInt } - v := int(total) - return &v + return new(int(total)) } type opsCollectedPercentiles struct { @@ -479,12 +473,10 @@ WHERE created_at >= $1 AND created_at < $2 duration.p95 = floatToIntPtr(p95) duration.p99 = floatToIntPtr(p99) if avg.Valid { - v := roundTo1DP(avg.Float64) - duration.avg = &v + duration.avg = new(roundTo1DP(avg.Float64)) } if max.Valid { - v := int(max.Int64) - duration.max = &v + duration.max = new(int(max.Int64)) } } @@ -512,12 +504,10 @@ WHERE created_at >= $1 AND created_at < $2 ttft.p95 = floatToIntPtr(p95) ttft.p99 = floatToIntPtr(p99) if avg.Valid { - v := roundTo1DP(avg.Float64) - ttft.avg = &v + ttft.avg = new(roundTo1DP(avg.Float64)) } if max.Valid { - v := int(max.Int64) - ttft.max = &v + ttft.max = new(int(max.Int64)) } } @@ -600,21 +590,17 @@ func (c *OpsMetricsCollector) collectSystemStats(ctx context.Context) (*opsColle cgroupUsed, cgroupTotal, cgroupOK := readCgroupMemoryBytes() if cgroupOK { - usedMB := int64(cgroupUsed / bytesPerMB) - out.memoryUsedMB = &usedMB + out.memoryUsedMB = new(int64(cgroupUsed / bytesPerMB)) if cgroupTotal > 0 { - totalMB := int64(cgroupTotal / bytesPerMB) - out.memoryTotalMB = &totalMB - pct := roundTo1DP(float64(cgroupUsed) / float64(cgroupTotal) * 100) - out.memoryUsagePercent = &pct + out.memoryTotalMB = new(int64(cgroupTotal / bytesPerMB)) + out.memoryUsagePercent = new(roundTo1DP(float64(cgroupUsed) / float64(cgroupTotal) * 100)) } } // Fallback to host metrics if cgroup metrics are unavailable (or incomplete). if out.cpuUsagePercent == nil { if cpuPercents, err := cpu.PercentWithContext(ctx, 0, false); err == nil && len(cpuPercents) > 0 { - v := roundTo1DP(cpuPercents[0]) - out.cpuUsagePercent = &v + out.cpuUsagePercent = new(roundTo1DP(cpuPercents[0])) } } @@ -622,20 +608,16 @@ func (c *OpsMetricsCollector) collectSystemStats(ctx context.Context) (*opsColle if out.memoryUsedMB == nil || out.memoryTotalMB == nil || out.memoryUsagePercent == nil { if vm, err := mem.VirtualMemoryWithContext(ctx); err == nil && vm != nil { if out.memoryUsedMB == nil { - usedMB := int64(vm.Used / bytesPerMB) - out.memoryUsedMB = &usedMB + out.memoryUsedMB = new(int64(vm.Used / bytesPerMB)) } if out.memoryTotalMB == nil { - totalMB := int64(vm.Total / bytesPerMB) - out.memoryTotalMB = &totalMB + out.memoryTotalMB = new(int64(vm.Total / bytesPerMB)) } if out.memoryUsagePercent == nil { if out.memoryUsedMB != nil && out.memoryTotalMB != nil && *out.memoryTotalMB > 0 { - pct := roundTo1DP(float64(*out.memoryUsedMB) / float64(*out.memoryTotalMB) * 100) - out.memoryUsagePercent = &pct + out.memoryUsagePercent = new(roundTo1DP(float64(*out.memoryUsedMB) / float64(*out.memoryTotalMB) * 100)) } else { - pct := roundTo1DP(vm.UsedPercent) - out.memoryUsagePercent = &pct + out.memoryUsagePercent = new(roundTo1DP(vm.UsedPercent)) } } } @@ -693,8 +675,7 @@ func (c *OpsMetricsCollector) tryCgroupCPUPercent(now time.Time) *float64 { if pct > 100 { pct = 100 } - v := roundTo1DP(pct) - return &v + return new(roundTo1DP(pct)) } func readCgroupMemoryBytes() (usedBytes uint64, totalBytes uint64, ok bool) { @@ -909,8 +890,7 @@ func floatToIntPtr(v sql.NullFloat64) *int { if !v.Valid { return nil } - n := int(math.Round(v.Float64)) - return &n + return new(int(math.Round(v.Float64))) } func roundTo1DP(v float64) float64 { @@ -932,16 +912,13 @@ func truncateString(s string, max int) string { } func boolPtr(v bool) *bool { - out := v - return &out + return new(v) } func intPtr(v int) *int { - out := v - return &out + return new(v) } func float64Ptr(v float64) *float64 { - out := v - return &out + return new(v) } diff --git a/backend/internal/service/ops_retry.go b/backend/internal/service/ops_retry.go index 23a524ad2..dc831277e 100644 --- a/backend/internal/service/ops_retry.go +++ b/backend/internal/service/ops_retry.go @@ -196,11 +196,8 @@ func (s *OpsService) RetryUpstreamEvent(ctx context.Context, requestedByUserID i } override := *errorLog - override.RequestBody = upstreamBody - pinned := ev.AccountID - - // Persist as upstream_event, execute as upstream pinned retry. - return s.retryWithErrorLog(ctx, requestedByUserID, errorID, OpsRetryModeUpstreamEvent, OpsRetryModeUpstream, &pinned, &override) + override.RequestBody = upstreamBody // Persist as upstream_event, execute as upstream pinned retry. + return s.retryWithErrorLog(ctx, requestedByUserID, errorID, OpsRetryModeUpstreamEvent, OpsRetryModeUpstream, new(ev.AccountID), &override) } func (s *OpsService) retryWithErrorLog(ctx context.Context, requestedByUserID int64, errorID int64, mode string, execMode string, pinnedAccountID *int64, errorLog *OpsErrorLogDetail) (*OpsRetryResult, error) { @@ -250,8 +247,7 @@ func (s *OpsService) retryWithErrorLog(ctx context.Context, requestedByUserID in StartedAt: startedAt, }) if err != nil { - var pqErr *pq.Error - if errors.As(err, &pqErr) && string(pqErr.Code) == "23505" { + if pqErr, ok := errors.AsType[*pq.Error](err); ok && string(pqErr.Code) == "23505" { return nil, infraerrors.Conflict("OPS_RETRY_IN_PROGRESS", "A retry is already in progress for this error") } return nil, infraerrors.InternalServer("OPS_RETRY_CREATE_ATTEMPT_FAILED", "Failed to create retry attempt").WithCause(err) @@ -294,8 +290,7 @@ func (s *OpsService) retryWithErrorLog(ctx context.Context, requestedByUserID in var updateErrMsg *string if strings.TrimSpace(result.ErrorMessage) != "" { - msg := result.ErrorMessage - updateErrMsg = &msg + updateErrMsg = new(result.ErrorMessage) } // Keep legacy result_request_id empty; use upstream_request_id instead. var resultRequestID *string @@ -306,23 +301,18 @@ func (s *OpsService) retryWithErrorLog(ctx context.Context, requestedByUserID in } success := strings.EqualFold(finalStatus, opsRetryStatusSucceeded) - httpStatus := result.HTTPStatusCode - upstreamReqID := result.UpstreamRequestID usedAccountID := result.UsedAccountID - preview := result.ResponsePreview - truncated := result.ResponseTruncated - if err := s.opsRepo.UpdateRetryAttempt(updateCtx, &OpsUpdateRetryAttemptInput{ ID: attemptID, Status: finalStatus, FinishedAt: finishedAt, DurationMs: result.DurationMs, Success: &success, - HTTPStatusCode: &httpStatus, - UpstreamRequestID: &upstreamReqID, + HTTPStatusCode: new(result.HTTPStatusCode), + UpstreamRequestID: new(result.UpstreamRequestID), UsedAccountID: usedAccountID, - ResponsePreview: &preview, - ResponseTruncated: &truncated, + ResponsePreview: new(result.ResponsePreview), + ResponseTruncated: new(result.ResponseTruncated), ResultRequestID: resultRequestID, ErrorMessage: updateErrMsg, }); err != nil { @@ -434,9 +424,8 @@ func (s *OpsService) executePinnedRetry(ctx context.Context, reqType opsRetryReq defer release() } - usedID := account.ID exec := s.executeWithAccount(ctx, reqType, errorLog, body, account) - exec.usedAccountID = &usedID + exec.usedAccountID = new(account.ID) if exec.status == "" { exec.status = opsRetryStatusFailed } @@ -489,8 +478,7 @@ func (s *OpsService) executeClientRetry(ctx context.Context, reqType opsRetryReq if exec != nil { if exec.status == opsRetryStatusSucceeded { - usedID := account.ID - exec.usedAccountID = &usedID + exec.usedAccountID = new(account.ID) return exec } // If the gateway services ask for failover, try another account. @@ -499,8 +487,7 @@ func (s *OpsService) executeClientRetry(ctx context.Context, reqType opsRetryReq switches++ continue } - usedID := account.ID - exec.usedAccountID = &usedID + exec.usedAccountID = new(account.ID) return exec } diff --git a/backend/internal/service/ops_scheduled_report_service.go b/backend/internal/service/ops_scheduled_report_service.go index 98b2045de..3d584b011 100644 --- a/backend/internal/service/ops_scheduled_report_service.go +++ b/backend/internal/service/ops_scheduled_report_service.go @@ -280,8 +280,7 @@ func (s *OpsScheduledReportService) listScheduledReports(ctx context.Context, no var lastRunPtr *time.Time if !lastRun.IsZero() { - lastCopy := lastRun - lastRunPtr = &lastCopy + lastRunPtr = new(lastRun) } out = append(out, &opsScheduledReport{ @@ -391,11 +390,9 @@ func (s *OpsScheduledReportService) generateReportHTML(ctx context.Context, repo return buildOpsSummaryEmailHTML(report.Name, start, end, overview), nil case "error_digest": // Lightweight digest: list recent errors (status>=400) and breakdown by type. - startTime := start - endTime := end filter := &OpsErrorLogFilter{ - StartTime: &startTime, - EndTime: &endTime, + StartTime: new(start), + EndTime: new(end), Page: 1, PageSize: 100, } @@ -664,8 +661,6 @@ func (s *OpsScheduledReportService) recordHeartbeatSuccess(runAt time.Time, dura if s == nil || s.opsService == nil || s.opsService.opsRepo == nil { return } - now := time.Now().UTC() - durMs := duration.Milliseconds() ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() msg := strings.TrimSpace(result) @@ -676,8 +671,8 @@ func (s *OpsScheduledReportService) recordHeartbeatSuccess(runAt time.Time, dura _ = s.opsService.opsRepo.UpsertJobHeartbeat(ctx, &OpsUpsertJobHeartbeatInput{ JobName: opsScheduledReportJobName, LastRunAt: &runAt, - LastSuccessAt: &now, - LastDurationMs: &durMs, + LastSuccessAt: new(time.Now().UTC()), + LastDurationMs: new(duration.Milliseconds()), LastResult: &msg, }) } @@ -686,17 +681,14 @@ func (s *OpsScheduledReportService) recordHeartbeatError(runAt time.Time, durati if s == nil || s.opsService == nil || s.opsService.opsRepo == nil || err == nil { return } - now := time.Now().UTC() - durMs := duration.Milliseconds() - msg := truncateString(err.Error(), 2048) ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() _ = s.opsService.opsRepo.UpsertJobHeartbeat(ctx, &OpsUpsertJobHeartbeatInput{ JobName: opsScheduledReportJobName, LastRunAt: &runAt, - LastErrorAt: &now, - LastError: &msg, - LastDurationMs: &durMs, + LastErrorAt: new(time.Now().UTC()), + LastError: new(truncateString(err.Error(), 2048)), + LastDurationMs: new(duration.Milliseconds()), }) } diff --git a/backend/internal/service/ops_service.go b/backend/internal/service/ops_service.go index 9c121b8b8..41a118177 100644 --- a/backend/internal/service/ops_service.go +++ b/backend/internal/service/ops_service.go @@ -235,8 +235,7 @@ func (s *OpsService) RecordError(ctx context.Context, entry *OpsInsertErrorLogIn continue } - evCopy := out - sanitized = append(sanitized, &evCopy) + sanitized = append(sanitized, new(out)) } entry.UpstreamErrorsJSON = marshalOpsUpstreamErrors(sanitized) diff --git a/backend/internal/service/ops_settings.go b/backend/internal/service/ops_settings.go index a6a4a0d71..1ad5480fa 100644 --- a/backend/internal/service/ops_settings.go +++ b/backend/internal/service/ops_settings.go @@ -483,15 +483,11 @@ func (s *OpsService) UpdateOpsAdvancedSettings(ctx context.Context, cfg *OpsAdva const SettingKeyOpsMetricThresholds = "ops_metric_thresholds" func defaultOpsMetricThresholds() *OpsMetricThresholds { - slaMin := 99.5 - ttftMax := 500.0 - reqErrMax := 5.0 - upstreamErrMax := 5.0 return &OpsMetricThresholds{ - SLAPercentMin: &slaMin, - TTFTp99MsMax: &ttftMax, - RequestErrorRatePercentMax: &reqErrMax, - UpstreamErrorRatePercentMax: &upstreamErrMax, + SLAPercentMin: new(99.5), + TTFTp99MsMax: new(500.0), + RequestErrorRatePercentMax: new(5.0), + UpstreamErrorRatePercentMax: new(5.0), } } diff --git a/backend/internal/service/ops_upstream_context.go b/backend/internal/service/ops_upstream_context.go index 3514df791..714601bd7 100644 --- a/backend/internal/service/ops_upstream_context.go +++ b/backend/internal/service/ops_upstream_context.go @@ -149,8 +149,7 @@ func marshalOpsUpstreamErrors(events []*OpsUpstreamErrorEvent) *string { if err != nil || len(raw) == 0 { return nil } - s := string(raw) - return &s + return new(string(raw)) } func ParseOpsUpstreamErrors(raw string) ([]*OpsUpstreamErrorEvent, error) { diff --git a/backend/internal/service/ratelimit_service.go b/backend/internal/service/ratelimit_service.go index 12c48ab83..bc84c0ea3 100644 --- a/backend/internal/service/ratelimit_service.go +++ b/backend/internal/service/ratelimit_service.go @@ -440,9 +440,7 @@ func (s *RateLimitService) handle429(ctx context.Context, account *Account, head } // 根据重置时间反推5h窗口 - windowEnd := resetAt - windowStart := resetAt.Add(-5 * time.Hour) - if err := s.accountRepo.UpdateSessionWindow(ctx, account.ID, &windowStart, &windowEnd, "rejected"); err != nil { + if err := s.accountRepo.UpdateSessionWindow(ctx, account.ID, new(resetAt.Add(-5*time.Hour)), new(resetAt), "rejected"); err != nil { slog.Warn("rate_limit_update_session_window_failed", "account_id", account.ID, "error", err) } @@ -527,8 +525,7 @@ func parseOpenAIRateLimitResetTime(body []byte) *int64 { // 优先使用 resets_at(Unix 时间戳) if resetsAt, ok := errObj["resets_at"].(float64); ok { - ts := int64(resetsAt) - return &ts + return new(int64(resetsAt)) } if resetsAt, ok := errObj["resets_at"].(string); ok { if ts, err := strconv.ParseInt(resetsAt, 10, 64); err == nil { @@ -538,13 +535,11 @@ func parseOpenAIRateLimitResetTime(body []byte) *int64 { // 如果没有 resets_at,尝试使用 resets_in_seconds if resetsInSeconds, ok := errObj["resets_in_seconds"].(float64); ok { - ts := time.Now().Unix() + int64(resetsInSeconds) - return &ts + return new(time.Now().Unix() + int64(resetsInSeconds)) } if resetsInSeconds, ok := errObj["resets_in_seconds"].(string); ok { if sec, err := strconv.ParseInt(resetsInSeconds, 10, 64); err == nil { - ts := time.Now().Unix() + sec - return &ts + return new(time.Now().Unix() + sec) } } diff --git a/backend/internal/service/ratelimit_service_openai_test.go b/backend/internal/service/ratelimit_service_openai_test.go index 009020686..a09374cb3 100644 --- a/backend/internal/service/ratelimit_service_openai_test.go +++ b/backend/internal/service/ratelimit_service_openai_test.go @@ -143,20 +143,13 @@ func TestCalculateOpenAI429ResetTime_ReversedWindowOrder(t *testing.T) { func TestNormalizedCodexLimits(t *testing.T) { // Test the Normalize() method directly - pUsed := 100.0 - pReset := 384607 - pWindow := 10080 - sUsed := 3.0 - sReset := 17369 - sWindow := 300 - snapshot := &OpenAICodexUsageSnapshot{ - PrimaryUsedPercent: &pUsed, - PrimaryResetAfterSeconds: &pReset, - PrimaryWindowMinutes: &pWindow, - SecondaryUsedPercent: &sUsed, - SecondaryResetAfterSeconds: &sReset, - SecondaryWindowMinutes: &sWindow, + PrimaryUsedPercent: new(100.0), + PrimaryResetAfterSeconds: new(384607), + PrimaryWindowMinutes: new(10080), + SecondaryUsedPercent: new(3.0), + SecondaryResetAfterSeconds: new(17369), + SecondaryWindowMinutes: new(300), } normalized := snapshot.Normalize() @@ -181,12 +174,9 @@ func TestNormalizedCodexLimits(t *testing.T) { func TestNormalizedCodexLimits_OnlyPrimaryData(t *testing.T) { // Test when only primary has data, no window_minutes - pUsed := 80.0 - pReset := 50000 - snapshot := &OpenAICodexUsageSnapshot{ - PrimaryUsedPercent: &pUsed, - PrimaryResetAfterSeconds: &pReset, + PrimaryUsedPercent: new(80.0), + PrimaryResetAfterSeconds: new(50000), // No window_minutes, no secondary data } @@ -213,12 +203,9 @@ func TestNormalizedCodexLimits_OnlyPrimaryData(t *testing.T) { func TestNormalizedCodexLimits_OnlySecondaryData(t *testing.T) { // Test when only secondary has data, no window_minutes - sUsed := 60.0 - sReset := 3000 - snapshot := &OpenAICodexUsageSnapshot{ - SecondaryUsedPercent: &sUsed, - SecondaryResetAfterSeconds: &sReset, + SecondaryUsedPercent: new(60.0), + SecondaryResetAfterSeconds: new(3000), // No window_minutes, no primary data } @@ -243,16 +230,11 @@ func TestNormalizedCodexLimits_OnlySecondaryData(t *testing.T) { func TestNormalizedCodexLimits_BothDataNoWindowMinutes(t *testing.T) { // Test when both have data but no window_minutes - pUsed := 100.0 - pReset := 400000 - sUsed := 50.0 - sReset := 10000 - snapshot := &OpenAICodexUsageSnapshot{ - PrimaryUsedPercent: &pUsed, - PrimaryResetAfterSeconds: &pReset, - SecondaryUsedPercent: &sUsed, - SecondaryResetAfterSeconds: &sReset, + PrimaryUsedPercent: new(100.0), + PrimaryResetAfterSeconds: new(400000), + SecondaryUsedPercent: new(50.0), + SecondaryResetAfterSeconds: new(10000), // No window_minutes } diff --git a/backend/internal/service/usage_cleanup_service_test.go b/backend/internal/service/usage_cleanup_service_test.go index c6c309b60..740c5976b 100644 --- a/backend/internal/service/usage_cleanup_service_test.go +++ b/backend/internal/service/usage_cleanup_service_test.go @@ -105,8 +105,7 @@ func (s *cleanupRepoStub) CreateTask(ctx context.Context, task *UsageCleanupTask if task.UpdatedAt.IsZero() { task.UpdatedAt = task.CreatedAt } - clone := *task - s.created = append(s.created, &clone) + s.created = append(s.created, new(*task)) return nil } @@ -232,17 +231,14 @@ func TestUsageCleanupServiceCreateTaskSanitizeFilters(t *testing.T) { start := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC) end := start.Add(24 * time.Hour) - userID := int64(-1) apiKeyID := int64(10) - model := " gpt-4 " - billingType := int8(-2) filters := UsageCleanupFilters{ StartTime: start, EndTime: end, - UserID: &userID, + UserID: new(int64(-1)), APIKeyID: &apiKeyID, - Model: &model, - BillingType: &billingType, + Model: new(" gpt-4 "), + BillingType: new(int8(-2)), } task, err := svc.CreateTask(context.Background(), filters, 9) @@ -645,12 +641,11 @@ func TestUsageCleanupServiceCancelTaskConflict(t *testing.T) { } func TestUsageCleanupServiceCancelTaskRepoConflict(t *testing.T) { - shouldCancel := false repo := &cleanupRepoStub{ statusByID: map[int64]string{ 7: UsageCleanupStatusPending, }, - cancelResult: &shouldCancel, + cancelResult: new(false), } cfg := &config.Config{UsageCleanup: config.UsageCleanupConfig{Enabled: true}} svc := NewUsageCleanupService(repo, nil, nil, cfg) @@ -753,16 +748,13 @@ func TestUsageCleanupServiceDefaultsAndLifecycle(t *testing.T) { } func TestSanitizeUsageCleanupFiltersModelEmpty(t *testing.T) { - model := " " apiKeyID := int64(-5) - accountID := int64(-1) - groupID := int64(-2) filters := UsageCleanupFilters{ UserID: &apiKeyID, APIKeyID: &apiKeyID, - AccountID: &accountID, - GroupID: &groupID, - Model: &model, + AccountID: new(int64(-1)), + GroupID: new(int64(-2)), + Model: new(" "), } sanitizeUsageCleanupFilters(&filters) @@ -776,23 +768,16 @@ func TestSanitizeUsageCleanupFiltersModelEmpty(t *testing.T) { func TestDescribeUsageCleanupFiltersAllFields(t *testing.T) { start := time.Date(2024, 2, 1, 10, 0, 0, 0, time.UTC) end := start.Add(2 * time.Hour) - userID := int64(1) - apiKeyID := int64(2) - accountID := int64(3) - groupID := int64(4) - model := " gpt-4 " - stream := true - billingType := int8(2) filters := UsageCleanupFilters{ StartTime: start, EndTime: end, - UserID: &userID, - APIKeyID: &apiKeyID, - AccountID: &accountID, - GroupID: &groupID, - Model: &model, - Stream: &stream, - BillingType: &billingType, + UserID: new(int64(1)), + APIKeyID: new(int64(2)), + AccountID: new(int64(3)), + GroupID: new(int64(4)), + Model: new(" gpt-4 "), + Stream: new(true), + BillingType: new(int8(2)), } desc := describeUsageCleanupFilters(filters) diff --git a/backend/internal/service/user_subscription.go b/backend/internal/service/user_subscription.go index ec547d81a..e137cbab5 100644 --- a/backend/internal/service/user_subscription.go +++ b/backend/internal/service/user_subscription.go @@ -75,24 +75,21 @@ func (s *UserSubscription) DailyResetTime() *time.Time { if s.DailyWindowStart == nil { return nil } - t := s.DailyWindowStart.Add(24 * time.Hour) - return &t + return new(s.DailyWindowStart.Add(24 * time.Hour)) } func (s *UserSubscription) WeeklyResetTime() *time.Time { if s.WeeklyWindowStart == nil { return nil } - t := s.WeeklyWindowStart.Add(7 * 24 * time.Hour) - return &t + return new(s.WeeklyWindowStart.Add(7 * 24 * time.Hour)) } func (s *UserSubscription) MonthlyResetTime() *time.Time { if s.MonthlyWindowStart == nil { return nil } - t := s.MonthlyWindowStart.Add(30 * 24 * time.Hour) - return &t + return new(s.MonthlyWindowStart.Add(30 * 24 * time.Hour)) } func (s *UserSubscription) CheckDailyLimit(group *Group, additionalCost float64) bool {