Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
|
||||
"github.com/Wei-Shaw/sub2api/internal/pkg/ctxkey"
|
||||
)
|
||||
|
||||
// withGatewayProfitControlGate installs the gate only for explicitly marked
|
||||
// token requests. This keeps media, metadata, and models-list paths outside
|
||||
// the profit-control surface by construction.
|
||||
func (s *GatewayService) withGatewayProfitControlGate(ctx context.Context, groupID *int64) context.Context {
|
||||
if _, ok := gatewayTokenRequestPricingAtFromContext(ctx); !ok || groupID == nil || *groupID <= 0 {
|
||||
return ctx
|
||||
}
|
||||
if existing, ok := ctx.Value(openAIProfitControlGateCtxKey{}).(*openAIProfitControlGate); ok && existing != nil && existing.groupID == *groupID {
|
||||
return ctx
|
||||
}
|
||||
|
||||
group, err := s.resolveProfitControlGroup(ctx, *groupID)
|
||||
if err != nil {
|
||||
slog.Warn("profit_control_group_load_failed", "group_id", *groupID, "error", err)
|
||||
return s.clearForeignProfitControlGate(ctx, groupID)
|
||||
}
|
||||
if group == nil || !group.ProfitControlEnabled || !profitControlPlatformSupported(group.Platform) {
|
||||
return s.clearForeignProfitControlGate(ctx, groupID)
|
||||
}
|
||||
|
||||
pricingAt, _ := gatewayTokenRequestPricingAtFromContext(ctx)
|
||||
billingGroup := gatewayTokenRequestBillingGroupFromContext(ctx)
|
||||
if billingGroup == nil {
|
||||
if ctxGroup, ok := ctx.Value(ctxkey.Group).(*Group); ok && IsGroupContextValid(ctxGroup) {
|
||||
billingGroup = ctxGroup
|
||||
} else {
|
||||
billingGroup = group
|
||||
}
|
||||
}
|
||||
|
||||
downstream := billingGroup.RateMultiplier
|
||||
if userID, _ := ctx.Value(ctxkey.UserID).(int64); userID > 0 {
|
||||
downstream = s.ResolveUserGroupRateMultiplier(ctx, userID, billingGroup.ID, billingGroup.RateMultiplier)
|
||||
}
|
||||
downstream *= billingGroup.PeakMultiplierAt(pricingAt)
|
||||
threshold := clampProfitControlThreshold(downstream * (1 - group.ProfitMinMargin - group.ProfitSafetyBuffer))
|
||||
|
||||
gate := &openAIProfitControlGate{
|
||||
groupID: group.ID,
|
||||
platform: group.Platform,
|
||||
threshold: threshold,
|
||||
pricingAt: pricingAt,
|
||||
}
|
||||
openAIProfitControlObserverInstance.recordInstall(gate.groupID, gate.platform, gate.threshold)
|
||||
return context.WithValue(ctx, openAIProfitControlGateCtxKey{}, gate)
|
||||
}
|
||||
|
||||
func (s *GatewayService) clearForeignProfitControlGate(ctx context.Context, groupID *int64) context.Context {
|
||||
existing, ok := ctx.Value(openAIProfitControlGateCtxKey{}).(*openAIProfitControlGate)
|
||||
if !ok || existing == nil || groupID == nil || existing.groupID == *groupID {
|
||||
return ctx
|
||||
}
|
||||
return context.WithValue(ctx, openAIProfitControlGateCtxKey{}, (*openAIProfitControlGate)(nil))
|
||||
}
|
||||
|
||||
func (s *GatewayService) resolveProfitControlGroup(ctx context.Context, groupID int64) (*Group, error) {
|
||||
if group, ok := ctx.Value(ctxkey.Group).(*Group); ok && IsGroupContextValid(group) && group.ID == groupID {
|
||||
return group, nil
|
||||
}
|
||||
if s.schedulerSnapshot != nil {
|
||||
// Lite 读取:门只用平台/倍率/利润/高峰字段,不需要账号计数聚合。
|
||||
return s.schedulerSnapshot.GetGroupByIDLite(ctx, groupID)
|
||||
}
|
||||
return s.resolveGroupByID(ctx, groupID)
|
||||
}
|
||||
|
||||
// GatewayProfitControlVetoLatest performs the terminal post-slot check against
|
||||
// the latest scheduler snapshot. Snapshot read failures are deliberately
|
||||
// fail-open to preserve availability, but are observable.
|
||||
func (s *GatewayService) GatewayProfitControlVetoLatest(ctx context.Context, selected *Account) (*Account, bool, string) {
|
||||
return profitControlVetoLatest(ctx, selected, s.schedulerSnapshot)
|
||||
}
|
||||
|
||||
func profitControlVetoLatest(ctx context.Context, selected *Account, snapshot *SchedulerSnapshotService) (*Account, bool, string) {
|
||||
gate, _ := ctx.Value(openAIProfitControlGateCtxKey{}).(*openAIProfitControlGate)
|
||||
if gate == nil || selected == nil {
|
||||
return selected, false, ""
|
||||
}
|
||||
latest := selected
|
||||
if snapshot != nil {
|
||||
refreshed, err := snapshot.GetAccount(ctx, selected.ID)
|
||||
if err != nil || refreshed == nil {
|
||||
slog.Warn("profit_control_account_refresh_failed", "group_id", gate.groupID, "platform", gate.platform, "account_id", selected.ID, "error", err)
|
||||
openAIProfitControlObserverInstance.recordRefreshFailure(gate.groupID, gate.platform, gate.threshold)
|
||||
} else if !refreshed.UpdatedAt.Before(selected.UpdatedAt) {
|
||||
// 选号路径可能已做过 DB recheck,selected 比缓存快照更新鲜;只有
|
||||
// 快照不落后时才替换,避免终检把新鲜账号换回较旧的缓存对象。
|
||||
latest = refreshed
|
||||
}
|
||||
}
|
||||
vetoed, reason := openAIProfitControlVetoReason(ctx, latest)
|
||||
return latest, vetoed, reason
|
||||
}
|
||||
|
||||
func (s *GatewayService) isGatewayAccountProfitEligible(ctx context.Context, account *Account) bool {
|
||||
vetoed, _ := openAIProfitControlVetoReason(ctx, account)
|
||||
return !vetoed
|
||||
}
|
||||
|
||||
func gatewayProfitControlGateActive(ctx context.Context) bool {
|
||||
gate, _ := ctx.Value(openAIProfitControlGateCtxKey{}).(*openAIProfitControlGate)
|
||||
return gate != nil
|
||||
}
|
||||
Reference in New Issue
Block a user