thread-master/apps/backend/internal/logic/proxy/quota.go

72 lines
2.3 KiB
Go
Raw Permalink Normal View History

2026-07-28 06:33:40 +00:00
package proxy
import (
"context"
"time"
"apps/backend/internal/svc"
"github.com/zeromicro/go-zero/core/logx"
)
// proxyCharge tracks one reserved quota unit for a proxied upstream call.
//
// The key mode is resolved once by the caller and reused for both the reservation and the audit
// event, because resolving separately reads member settings twice and a member editing their
// keys in between would reserve against one mode and bill the other.
type proxyCharge struct {
svcCtx *svc.ServiceContext
uid int64
meter string
mode string
label string
source string
settled bool
}
// reserveQuota holds quota for an upcoming call. Pair it with `defer charge.Settle(ctx, &err)`
// on a named error return so that failures refund instead of charging for nothing.
func reserveQuota(ctx context.Context, svcCtx *svc.ServiceContext, uid int64, meter, mode, label, source string) (*proxyCharge, error) {
if err := svcCtx.Usage.PrepareCallWithMode(ctx, uid, meter, mode); err != nil {
return nil, err
}
return &proxyCharge{
svcCtx: svcCtx, uid: uid, meter: meter, mode: mode, label: label, source: source,
}, nil
}
// Settle commits the charge on success and refunds it on failure.
func (c *proxyCharge) Settle(ctx context.Context, errp *error) {
if errp != nil && *errp != nil {
c.release(ctx)
return
}
c.commit(ctx)
}
// commit writes the audit event. The upstream call already succeeded, so a failure here is
// logged rather than returned: the member has their result and charging them is correct, but
// losing the event must not be silent either.
func (c *proxyCharge) commit(ctx context.Context) {
if c == nil || c.settled {
return
}
c.settled = true
if _, err := c.svcCtx.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 *proxyCharge) release(ctx context.Context) {
if c == nil || c.settled {
return
}
c.settled = true
// The request context is often already cancelled on the path being compensated for.
rctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), 5*time.Second)
defer cancel()
if err := c.svcCtx.Usage.ReleaseCall(rctx, c.uid, c.meter, c.mode); err != nil {
logx.Errorf("usage release uid=%d meter=%s source=%s: %v", c.uid, c.meter, c.source, err)
}
}