package models import ( "encoding/json" "github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils" _ "github.com/go-sql-driver/mysql" "github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/dbs" ) type BillType = string const ( BillTypeTraffic BillType = "traffic" // 按流量计费 ) type UserBillDAO dbs.DAO func NewUserBillDAO() *UserBillDAO { return dbs.NewDAO(&UserBillDAO{ DAOObject: dbs.DAOObject{ DB: Tea.Env, Table: "edgeUserBills", Model: new(UserBill), PkName: "id", }, }).(*UserBillDAO) } var SharedUserBillDAO *UserBillDAO func init() { dbs.OnReady(func() { SharedUserBillDAO = NewUserBillDAO() }) } // 计算账单数量 func (this *UserBillDAO) CountAllUserBills(tx *dbs.Tx, isPaid int32, userId int64, month string) (int64, error) { query := this.Query(tx) if isPaid == 0 { query.Attr("isPaid", 0) } else if isPaid > 0 { query.Attr("isPaid", 1) } if userId > 0 { query.Attr("userId", userId) } if len(month) > 0 { query.Attr("month", month) } return query.Count() } // 列出单页账单 func (this *UserBillDAO) ListUserBills(tx *dbs.Tx, isPaid int32, userId int64, month string, offset, size int64) (result []*UserBill, err error) { query := this.Query(tx) if isPaid == 0 { query.Attr("isPaid", 0) } else if isPaid > 0 { query.Attr("isPaid", 1) } if userId > 0 { query.Attr("userId", userId) } if len(month) > 0 { query.Attr("month", month) } _, err = query. Offset(offset). Limit(size). Slice(&result). DescPk(). FindAll() return } // 创建账单 func (this *UserBillDAO) CreateBill(tx *dbs.Tx, userId int64, billType BillType, description string, amount float32, month string) (int64, error) { op := NewUserBillOperator() op.UserId = userId op.Type = billType op.Description = description op.Amount = amount op.Month = month op.IsPaid = false return this.SaveInt64(tx, op) } // 检查是否有当月账单 func (this *UserBillDAO) ExistBill(tx *dbs.Tx, userId int64, billType BillType, month string) (bool, error) { return this.Query(tx). Attr("userId", userId). Attr("month", month). Attr("type", billType). Exist() } // 生成账单 // month 格式YYYYMM func (this *UserBillDAO) GenerateBills(tx *dbs.Tx, month string) error { // 用户 offset := int64(0) size := int64(100) // 每次只查询N次,防止由于执行时间过长而锁表 for { userIds, err := SharedUserDAO.ListEnabledUserIds(tx, offset, size) if err != nil { return err } offset += size if len(userIds) == 0 { break } for _, userId := range userIds { // CDN流量账单 err := this.generateTrafficBill(tx, userId, month) if err != nil { return err } } } return nil } // 生成CDN流量账单 // month 格式YYYYMM func (this *UserBillDAO) generateTrafficBill(tx *dbs.Tx, userId int64, month string) error { // 检查是否已经有账单了 b, err := this.ExistBill(tx, userId, BillTypeTraffic, month) if err != nil { return err } if b { return nil } // TODO 优化使用缓存 regions, err := SharedNodeRegionDAO.FindAllEnabledRegionPrices(tx) if err != nil { return err } if len(regions) == 0 { return nil } priceItems, err := SharedNodePriceItemDAO.FindAllEnabledRegionPrices(tx, NodePriceTypeTraffic) if err != nil { return err } if len(priceItems) == 0 { return nil } cost := float32(0) for _, region := range regions { if len(region.Prices) == 0 || region.Prices == "null" { continue } priceMap := map[string]float32{} err = json.Unmarshal([]byte(region.Prices), &priceMap) if err != nil { return err } trafficBytes, err := SharedServerDailyStatDAO.SumUserMonthly(tx, userId, int64(region.Id), month) if err != nil { return err } if trafficBytes == 0 { continue } itemId := SharedNodePriceItemDAO.SearchItemsWithBytes(priceItems, trafficBytes) if itemId == 0 { continue } price, ok := priceMap[numberutils.FormatInt64(itemId)] if !ok { continue } // 计算钱 // 这里采用1000进制 cost += (float32(trafficBytes*8) / 1_000_000_000) * price } if cost == 0 { return nil } // 创建账单 _, err = this.CreateBill(tx, userId, BillTypeTraffic, "按流量计费", cost, month) return err } // 获取账单类型名称 func (this *UserBillDAO) BillTypeName(billType BillType) string { switch billType { case BillTypeTraffic: return "流量" } return "" }