@@ -92,14 +92,38 @@ func (s *Service) List(ctx context.Context, filter ListFilter) (ListResult, erro
9292 }
9393
9494 ids := make ([]int , 0 , len (accounts ))
95+ openaiIDs := make ([]int , 0 , len (accounts ))
9596 for _ , item := range accounts {
9697 ids = append (ids , item .ID )
98+ // 生图统计仅 OpenAI 平台账号需要:其它平台没有 image endpoint,跑 SQL 也是 0 行白浪费。
99+ if item .Platform == "openai" {
100+ openaiIDs = append (openaiIDs , item .ID )
101+ }
97102 }
98103 counts := s .concurrency .GetCurrentCounts (ctx , ids )
99104 for index := range accounts {
100105 accounts [index ].CurrentConcurrency = counts [accounts [index ].ID ]
101106 }
102107
108+ // 生图请求计数:今日 + 累计。BatchImageStats 失败不阻断主响应(运维路径优先稳定)。
109+ if len (openaiIDs ) > 0 {
110+ todayStart := timezone .StartOfDay (s .now ().In (time .Local ))
111+ if imageStats , err := s .repo .BatchImageStats (ctx , openaiIDs , todayStart ); err == nil {
112+ for index := range accounts {
113+ if accounts [index ].Platform != "openai" {
114+ continue
115+ }
116+ if entry , ok := imageStats [accounts [index ].ID ]; ok {
117+ stats := entry
118+ accounts [index ].ImageStats = & stats
119+ } else {
120+ // 没记录:显式给个零值结构,让前端拿到 today=0/total=0 而不是 nil(区分"没数据"和"非 openai")
121+ accounts [index ].ImageStats = & AccountImageStats {}
122+ }
123+ }
124+ }
125+ }
126+
103127 return ListResult {
104128 List : accounts ,
105129 Total : total ,
@@ -110,10 +134,22 @@ func (s *Service) List(ctx context.Context, filter ListFilter) (ListResult, erro
110134
111135// Create 创建账号。
112136func (s * Service ) Create (ctx context.Context , input CreateInput ) (Account , error ) {
137+ logger := sdk .LoggerFromContext (ctx )
113138 account , err := s .repo .Create (ctx , input )
114- if err == nil {
115- s .InvalidateUsageCache ("" ) // 新账号创建后清除用量缓存
116- }
139+ if err != nil {
140+ logger .Error ("account_credential_persist_failed" ,
141+ sdk .LogFieldPlatform , input .Platform ,
142+ "type" , input .Type ,
143+ "name" , input .Name ,
144+ sdk .LogFieldError , err )
145+ return account , err
146+ }
147+ logger .Info ("account_created" ,
148+ sdk .LogFieldAccountID , account .ID ,
149+ sdk .LogFieldPlatform , account .Platform ,
150+ "type" , account .Type ,
151+ "name" , account .Name )
152+ s .InvalidateUsageCache ("" ) // 新账号创建后清除用量缓存
117153 return account , err
118154}
119155
@@ -144,15 +180,39 @@ func (s *Service) Import(ctx context.Context, items []CreateInput) ImportSummary
144180
145181// Update 更新账号。
146182func (s * Service ) Update (ctx context.Context , id int , input UpdateInput ) (Account , error ) {
147- return s .repo .Update (ctx , id , input )
183+ logger := sdk .LoggerFromContext (ctx )
184+ updated , err := s .repo .Update (ctx , id , input )
185+ if err != nil {
186+ logger .Error ("account_credential_persist_failed" ,
187+ sdk .LogFieldAccountID , id ,
188+ sdk .LogFieldError , err )
189+ return updated , err
190+ }
191+ switch {
192+ case input .State != nil :
193+ logger .Info ("account_status_changed" ,
194+ sdk .LogFieldAccountID , id ,
195+ "state" , * input .State )
196+ case input .MaxConcurrency != nil || input .RateMultiplier != nil :
197+ logger .Info ("account_quota_updated" ,
198+ sdk .LogFieldAccountID , id )
199+ }
200+ return updated , err
148201}
149202
150203// Delete 删除账号。
151204func (s * Service ) Delete (ctx context.Context , id int ) error {
205+ logger := sdk .LoggerFromContext (ctx )
152206 err := s .repo .Delete (ctx , id )
153- if err == nil {
154- s .InvalidateUsageCache ("" )
207+ if err != nil {
208+ logger .Error ("account_credential_persist_failed" ,
209+ sdk .LogFieldAccountID , id ,
210+ "op" , "delete" ,
211+ sdk .LogFieldError , err )
212+ return err
155213 }
214+ logger .Info ("account_deleted" , sdk .LogFieldAccountID , id )
215+ s .InvalidateUsageCache ("" )
156216 return err
157217}
158218
@@ -212,8 +272,12 @@ func (r *BulkResult) appendFailure(id int, err error) {
212272// ToggleScheduling 快速切换账号调度状态。active ↔ disabled。
213273// 其它中间态(rate_limited / degraded)一律视为"非 disabled",切换后目标 = disabled。
214274func (s * Service ) ToggleScheduling (ctx context.Context , id int ) (ToggleResult , error ) {
275+ logger := sdk .LoggerFromContext (ctx )
215276 item , err := s .repo .FindByID (ctx , id , LoadOptions {})
216277 if err != nil {
278+ logger .Error ("account_lookup_failed" ,
279+ sdk .LogFieldAccountID , id ,
280+ sdk .LogFieldError , err )
217281 return ToggleResult {}, err
218282 }
219283
@@ -224,20 +288,35 @@ func (s *Service) ToggleScheduling(ctx context.Context, id int) (ToggleResult, e
224288
225289 updated , err := s .repo .Update (ctx , id , UpdateInput {State : & newState })
226290 if err != nil {
291+ logger .Error ("account_credential_persist_failed" ,
292+ sdk .LogFieldAccountID , id ,
293+ "op" , "toggle_scheduling" ,
294+ sdk .LogFieldError , err )
227295 return ToggleResult {}, err
228296 }
297+ logger .Info ("account_status_changed" ,
298+ sdk .LogFieldAccountID , id ,
299+ "state" , updated .State )
229300 return ToggleResult {ID : updated .ID , State : updated .State }, nil
230301}
231302
232303// PrepareConnectivityTest 准备账号连通性测试。
233304func (s * Service ) PrepareConnectivityTest (ctx context.Context , id int , modelID string ) (* ConnectivityTest , error ) {
305+ logger := sdk .LoggerFromContext (ctx )
234306 item , err := s .repo .FindByID (ctx , id , LoadOptions {WithProxy : true })
235307 if err != nil {
308+ logger .Error ("account_lookup_failed" ,
309+ sdk .LogFieldAccountID , id ,
310+ sdk .LogFieldError , err )
236311 return nil , err
237312 }
238313
239314 inst := s .plugins .GetPluginByPlatform (item .Platform )
240315 if inst == nil || inst .Gateway == nil {
316+ logger .Warn ("account_credential_validation_failed" ,
317+ sdk .LogFieldAccountID , id ,
318+ sdk .LogFieldPlatform , item .Platform ,
319+ sdk .LogFieldReason , "plugin_not_found" )
241320 return nil , ErrPluginNotFound
242321 }
243322
@@ -808,13 +887,21 @@ func (s *Service) GetCredentialsSchema(platform string) CredentialSchema {
808887
809888// RefreshQuota 刷新账号额度。
810889func (s * Service ) RefreshQuota (ctx context.Context , id int ) (QuotaRefreshResult , error ) {
890+ logger := sdk .LoggerFromContext (ctx )
811891 item , err := s .repo .FindByID (ctx , id , LoadOptions {})
812892 if err != nil {
893+ logger .Error ("account_lookup_failed" ,
894+ sdk .LogFieldAccountID , id ,
895+ sdk .LogFieldError , err )
813896 return QuotaRefreshResult {}, err
814897 }
815898
816899 inst := s .plugins .GetPluginByPlatform (item .Platform )
817900 if inst == nil || inst .Gateway == nil {
901+ logger .Warn ("account_credential_validation_failed" ,
902+ sdk .LogFieldAccountID , id ,
903+ sdk .LogFieldPlatform , item .Platform ,
904+ sdk .LogFieldReason , "quota_refresh_unsupported" )
818905 return QuotaRefreshResult {}, ErrQuotaRefreshUnsupported
819906 }
820907
@@ -825,8 +912,16 @@ func (s *Service) RefreshQuota(ctx context.Context, id int) (QuotaRefreshResult,
825912 if err != nil {
826913 // 识别插件返回的 reauth_required 前缀(字符串识别,gRPC 不透传 sentinel error)。
827914 if strings .Contains (err .Error (), reauthRequiredPrefix ) {
915+ logger .Warn ("account_credential_validation_failed" ,
916+ sdk .LogFieldAccountID , id ,
917+ sdk .LogFieldPlatform , item .Platform ,
918+ sdk .LogFieldReason , "reauth_required" )
828919 return QuotaRefreshResult {}, ErrReauthRequired
829920 }
921+ logger .Error ("account_credential_validation_failed" ,
922+ sdk .LogFieldAccountID , id ,
923+ sdk .LogFieldPlatform , item .Platform ,
924+ sdk .LogFieldError , err )
830925 return QuotaRefreshResult {}, fmt .Errorf ("刷新额度失败: %w" , err )
831926 }
832927
@@ -853,6 +948,10 @@ func (s *Service) RefreshQuota(ctx context.Context, id int) (QuotaRefreshResult,
853948 }
854949 if updated {
855950 if err := s .repo .SaveCredentials (ctx , id , credentials ); err != nil {
951+ logger .Error ("account_credential_persist_failed" ,
952+ sdk .LogFieldAccountID , id ,
953+ "op" , "save_credentials" ,
954+ sdk .LogFieldError , err )
856955 return QuotaRefreshResult {}, err
857956 }
858957 }
@@ -885,17 +984,23 @@ func (s *Service) triggerUsageProbe(ctx context.Context, inst *plugin.PluginInst
885984 defer cancel ()
886985 status , _ , _ , err := inst .Gateway .HandleHTTPRequest (probeCtx , "POST" , "usage/probe" , "" , nil , reqBody )
887986 if err != nil || status != http .StatusOK {
888- slog .Debug ("usage/probe 探测失败(降级:等下一轮 5 分钟缓存过期重试)" ,
889- "account_id" , id , "status" , status , "error" , err )
987+ slog .Debug ("account_usage_probe_failed" ,
988+ sdk .LogFieldAccountID , id ,
989+ sdk .LogFieldStatus , status ,
990+ sdk .LogFieldError , err )
890991 }
891992 // 清掉本进程的 usage 5 分钟缓存,让下一次 GetAccountUsage 重新从插件拉窗口数据。
892993 s .InvalidateUsageCache ("" )
893994}
894995
895996// GetStats 获取单个账号统计。
896997func (s * Service ) GetStats (ctx context.Context , id int , query StatsQuery ) (StatsResult , error ) {
998+ logger := sdk .LoggerFromContext (ctx )
897999 item , err := s .repo .FindByID (ctx , id , LoadOptions {})
8981000 if err != nil {
1001+ logger .Error ("account_lookup_failed" ,
1002+ sdk .LogFieldAccountID , id ,
1003+ sdk .LogFieldError , err )
8991004 return StatsResult {}, err
9001005 }
9011006
@@ -908,6 +1013,10 @@ func (s *Service) GetStats(ctx context.Context, id int, query StatsQuery) (Stats
9081013
9091014 logs , err := s .repo .FindUsageLogs (ctx , id , startDate , endDate )
9101015 if err != nil {
1016+ logger .Error ("account_lookup_failed" ,
1017+ sdk .LogFieldAccountID , id ,
1018+ "op" , "find_usage_logs" ,
1019+ sdk .LogFieldError , err )
9111020 return StatsResult {}, err
9121021 }
9131022
0 commit comments