2026-08-03 05:52:02 +00:00
|
|
|
|
package usecase
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
2026-08-19 07:37:44 +00:00
|
|
|
|
"time"
|
2026-08-03 05:52:02 +00:00
|
|
|
|
|
|
|
|
|
|
"github.com/zeromicro/go-zero/core/logx"
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
|
charge 是一次計點呼叫的預留與結算,沿用既有四個 meter,不新增第五個(RG-04)。
|
|
|
|
|
|
|
|
|
|
|
|
用法:`defer charge.Settle(ctx, &err)` 綁在具名 error 回傳上,這樣任何失敗路徑都退點,
|
|
|
|
|
|
不會出現「AI 失敗了但點數扣掉」。
|
|
|
|
|
|
*/
|
|
|
|
|
|
type charge struct {
|
|
|
|
|
|
svc *Service
|
|
|
|
|
|
uid int64
|
|
|
|
|
|
meter string
|
|
|
|
|
|
mode string
|
|
|
|
|
|
label string
|
|
|
|
|
|
source string
|
|
|
|
|
|
settled bool
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (s *Service) bill(ctx context.Context, uid int64, meter, label, source string) (*charge, error) {
|
|
|
|
|
|
if s == nil || s.Usage == nil {
|
|
|
|
|
|
return &charge{settled: true}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
mode, err := s.Usage.PrepareCall(ctx, uid, meter)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return nil, err
|
|
|
|
|
|
}
|
|
|
|
|
|
return &charge{svc: s, uid: uid, meter: meter, mode: mode, label: label, source: source}, nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (c *charge) Settle(ctx context.Context, errp *error) {
|
|
|
|
|
|
if errp != nil && *errp != nil {
|
|
|
|
|
|
c.Release(ctx)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
c.Commit(ctx)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Commit 寫入用量事件。使用者已經拿到結果,所以這裡失敗只記錄不轉成錯誤。
|
|
|
|
|
|
func (c *charge) Commit(ctx context.Context) {
|
|
|
|
|
|
if c == nil || c.settled {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
c.settled = true
|
|
|
|
|
|
if _, err := c.svc.Usage.RecordCall(ctx, c.uid, c.meter, c.mode, c.label, c.source); err != nil {
|
|
|
|
|
|
logx.Errorf("usage record uid=%d meter=%s source=%s: %v", c.uid, c.meter, c.source, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
func (c *charge) Release(ctx context.Context) {
|
|
|
|
|
|
if c == nil || c.settled {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
c.settled = true
|
2026-08-19 07:37:44 +00:00
|
|
|
|
// 要補償的失敗常常就是「請求被取消」,退點必須用活得比它久的 context,否則使用者被扣點。
|
|
|
|
|
|
rctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
if err := c.svc.Usage.ReleaseCall(rctx, c.uid, c.meter, c.mode); err != nil {
|
2026-08-03 05:52:02 +00:00
|
|
|
|
logx.Errorf("usage release uid=%d meter=%s source=%s: %v", c.uid, c.meter, c.source, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|