feat: 金额 decimal 精确计费、failover 按失败类型封顶、渠道小时桶预聚合、分层守护测试

- 计费:19 个金额字段 float64→decimal.Decimal,DB 列保持 numeric(20,8) 零迁移;
  API 金额仍为 JSON number(MarshalJSONWithoutQuotes),前端零改动
- failover:保留时间预算,新增 OT_PROXY_FAILOVER_CAPS_NET/SERVER/RATE/QUOTA
  按失败类型封顶换渠道次数(429/配额类瞬时失败时间预算形同虚设)
- 预聚合:新增 usage_channel_hourlies(schema v10),记账事务内同步 upsert,
  长窗口渠道统计走桶不再扫明细,spent_last_30d 改扫 usage_daily,v10 一次性回填幂等
- 守护:internal/arch_test.go 用 go/parser 强制分层依赖(fail-closed)
This commit is contained in:
Sakurasan committed 2026-11-09 00:41:48 +08:00
1 parent 5d6e5bdb04
commit b2e500ff51
25 files changed
+841 -253

No files matched your search

+5 -4
View File
@@ -11,6 +11,7 @@ import (
"github.com/gin-gonic/gin"
"github.com/openteam/server/internal/pkg/resp"
"github.com/openteam/server/internal/store"
"github.com/shopspring/decimal"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
@@ -177,14 +178,14 @@ func (h *Handler) AdminAdjustBalance(c *gin.Context) {
return
}
var req struct {
Amount float64 `json:"amount" binding:"required"`
Remark string `json:"remark"`
Amount decimal.Decimal `json:"amount" binding:"required"`
Remark string `json:"remark"`
}
if err := c.ShouldBindJSON(&req); err != nil {
resp.Fail(c, http.StatusBadRequest, "invalid input: amount required")
return
}
if req.Amount == 0 {
if req.Amount.IsZero() {
resp.Fail(c, http.StatusBadRequest, "amount must not be zero")
return
}
@@ -204,7 +205,7 @@ func (h *Handler) AdminAdjustBalance(c *gin.Context) {
return tx.Create(&store.BalanceLog{
UserID: u.ID,
Change: req.Amount,
BalanceAfter: u.Balance + req.Amount,
BalanceAfter: u.Balance.Add(req.Amount),
Type: store.BalanceTypeAdminAdjust,
RefID: ref,
Remark: req.Remark,