mirror of
				https://github.com/TeaOSLab/EdgeAPI.git
				synced 2025-11-04 16:00:24 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			482 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			482 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
package models
 | 
						|
 | 
						|
import (
 | 
						|
	"github.com/TeaOSLab/EdgeAPI/internal/goman"
 | 
						|
	"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
 | 
						|
	"github.com/TeaOSLab/EdgeAPI/internal/utils"
 | 
						|
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
						|
	_ "github.com/go-sql-driver/mysql"
 | 
						|
	"github.com/iwind/TeaGo/Tea"
 | 
						|
	"github.com/iwind/TeaGo/dbs"
 | 
						|
	"github.com/iwind/TeaGo/lists"
 | 
						|
	"github.com/iwind/TeaGo/maps"
 | 
						|
	"github.com/iwind/TeaGo/rands"
 | 
						|
	"github.com/iwind/TeaGo/types"
 | 
						|
	timeutil "github.com/iwind/TeaGo/utils/time"
 | 
						|
	"time"
 | 
						|
)
 | 
						|
 | 
						|
func init() {
 | 
						|
	dbs.OnReadyDone(func() {
 | 
						|
		var generatedMonths = []string{}
 | 
						|
 | 
						|
		goman.New(func() {
 | 
						|
			// 自动生成账单任务
 | 
						|
			var ticker = time.NewTicker(1 * time.Hour)
 | 
						|
			for range ticker.C {
 | 
						|
				// 是否已经生成了,如果已经生成了就跳过
 | 
						|
				var lastMonth = timeutil.Format("Ym", time.Now().AddDate(0, -1, 0))
 | 
						|
				if lists.ContainsString(generatedMonths, lastMonth) {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
 | 
						|
				err := SharedUserBillDAO.GenerateBills(nil, lastMonth)
 | 
						|
				if err != nil {
 | 
						|
					remotelogs.Error("UserBillDAO", "generate bills failed: "+err.Error())
 | 
						|
				} else {
 | 
						|
					generatedMonths = append(generatedMonths, lastMonth)
 | 
						|
				}
 | 
						|
			}
 | 
						|
		})
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
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()
 | 
						|
	})
 | 
						|
}
 | 
						|
 | 
						|
// FindUserBill 查找单个账单
 | 
						|
func (this *UserBillDAO) FindUserBill(tx *dbs.Tx, billId int64) (*UserBill, error) {
 | 
						|
	one, err := this.Query(tx).
 | 
						|
		Pk(billId).
 | 
						|
		Find()
 | 
						|
	if err != nil || one == nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
	return one.(*UserBill), nil
 | 
						|
}
 | 
						|
 | 
						|
// CountAllUserBills 计算账单数量
 | 
						|
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()
 | 
						|
}
 | 
						|
 | 
						|
// ListUserBills 列出单页账单
 | 
						|
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
 | 
						|
}
 | 
						|
 | 
						|
// FindUnpaidBills 查找未支付订单
 | 
						|
func (this *UserBillDAO) FindUnpaidBills(tx *dbs.Tx, size int64) (result []*UserBill, err error) {
 | 
						|
	if size <= 0 {
 | 
						|
		size = 10000
 | 
						|
	}
 | 
						|
	_, err = this.Query(tx).
 | 
						|
		Attr("isPaid", false).
 | 
						|
		Lt("month", timeutil.Format("Ym")). //当月的不能支付,因为当月还没过完
 | 
						|
		Limit(size).
 | 
						|
		DescPk().
 | 
						|
		Slice(&result).
 | 
						|
		FindAll()
 | 
						|
	return
 | 
						|
}
 | 
						|
 | 
						|
// CreateBill 创建账单
 | 
						|
func (this *UserBillDAO) CreateBill(tx *dbs.Tx, userId int64, billType BillType, description string, amount float64, month string, canPay bool) error {
 | 
						|
	code, err := this.GenerateBillCode(tx)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	return this.Query(tx).
 | 
						|
		InsertOrUpdateQuickly(maps.Map{
 | 
						|
			"userId":      userId,
 | 
						|
			"type":        billType,
 | 
						|
			"description": description,
 | 
						|
			"amount":      amount,
 | 
						|
			"month":       month,
 | 
						|
			"code":        code,
 | 
						|
			"isPaid":      amount == 0,
 | 
						|
			"canPay":      canPay,
 | 
						|
		}, maps.Map{
 | 
						|
			"amount": amount,
 | 
						|
			"canPay": canPay,
 | 
						|
		})
 | 
						|
}
 | 
						|
 | 
						|
// ExistBill 检查是否有当月账单
 | 
						|
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()
 | 
						|
}
 | 
						|
 | 
						|
// GenerateBills 生成账单
 | 
						|
// month 格式YYYYMM
 | 
						|
func (this *UserBillDAO) GenerateBills(tx *dbs.Tx, month string) error {
 | 
						|
	// 区域价格
 | 
						|
	regions, err := SharedNodeRegionDAO.FindAllEnabledRegionPrices(tx)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	var priceItems []*NodePriceItem
 | 
						|
	if len(regions) > 0 {
 | 
						|
		priceItems, err = SharedNodePriceItemDAO.FindAllEnabledRegionPrices(tx, NodePriceTypeTraffic)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 默认计费方式
 | 
						|
	userFinanceConfig, err := SharedSysSettingDAO.ReadUserFinanceConfig(tx)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
 | 
						|
	// 计算服务套餐费用
 | 
						|
	plans, err := SharedPlanDAO.FindAllEnabledPlans(tx)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	var planMap = map[int64]*Plan{}
 | 
						|
	for _, plan := range plans {
 | 
						|
		planMap[int64(plan.Id)] = plan
 | 
						|
	}
 | 
						|
 | 
						|
	var dayFrom = month + "01"
 | 
						|
	var dayTo = month + "32"
 | 
						|
	serverIds, err := SharedServerDailyStatDAO.FindDistinctServerIds(tx, dayFrom, dayTo)
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	var cacheMap = utils.NewCacheMap()
 | 
						|
	var userIds = []int64{}
 | 
						|
	for _, serverId := range serverIds {
 | 
						|
		// 套餐类型
 | 
						|
		userPlanId, userId, err := SharedServerDAO.FindServerLastUserPlanIdAndUserId(tx, serverId)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		if userId == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		userIds = append(userIds, userId)
 | 
						|
		if userPlanId == 0 {
 | 
						|
			// 总流量
 | 
						|
			totalTrafficBytes, err := SharedServerDailyStatDAO.SumMonthlyBytes(tx, serverId, month)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			// 默认计费方式
 | 
						|
			if userFinanceConfig != nil && userFinanceConfig.IsOn { // 默认计费方式
 | 
						|
				switch userFinanceConfig.PriceType {
 | 
						|
				case serverconfigs.PlanPriceTypeTraffic:
 | 
						|
					var config = userFinanceConfig.TrafficPriceConfig
 | 
						|
					var fee float64 = 0
 | 
						|
					if config != nil && config.Base > 0 {
 | 
						|
						fee = float64(totalTrafficBytes) / 1024 / 1024 / 1024 * float64(config.Base)
 | 
						|
					}
 | 
						|
 | 
						|
					// 百分位
 | 
						|
					var percentile = 95
 | 
						|
					percentileBytes, err := SharedServerBandwidthStatDAO.FindMonthlyPercentile(tx, serverId, month, percentile)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
 | 
						|
					err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, userId, serverId, month, userPlanId, 0, totalTrafficBytes, percentileBytes, percentile, userFinanceConfig.PriceType, fee)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
				case serverconfigs.PlanPriceTypeBandwidth:
 | 
						|
					// 百分位
 | 
						|
					var percentile = 95
 | 
						|
					var config = userFinanceConfig.BandwidthPriceConfig
 | 
						|
					if config != nil {
 | 
						|
						percentile = config.Percentile
 | 
						|
						if percentile <= 0 {
 | 
						|
							percentile = 95
 | 
						|
						} else if percentile > 100 {
 | 
						|
							percentile = 100
 | 
						|
						}
 | 
						|
					}
 | 
						|
					percentileBytes, err := SharedServerBandwidthStatDAO.FindMonthlyPercentile(tx, serverId, month, percentile)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
					var mb = float32(percentileBytes) / 1024 / 1024
 | 
						|
					var price float32
 | 
						|
					if config != nil {
 | 
						|
						price = config.LookupPrice(mb)
 | 
						|
					}
 | 
						|
					var fee = float64(price)
 | 
						|
					err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, userId, serverId, month, userPlanId, 0, totalTrafficBytes, percentileBytes, percentile, userFinanceConfig.PriceType, fee)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
				}
 | 
						|
			} else { // 区域流量计费
 | 
						|
				var fee float64
 | 
						|
 | 
						|
				for _, region := range regions {
 | 
						|
					var regionId = int64(region.Id)
 | 
						|
					var pricesMap = region.DecodePriceMap()
 | 
						|
					if len(pricesMap) == 0 {
 | 
						|
						continue
 | 
						|
					}
 | 
						|
 | 
						|
					trafficBytes, err := SharedServerDailyStatDAO.SumServerMonthlyWithRegion(tx, serverId, regionId, month)
 | 
						|
					if err != nil {
 | 
						|
						return err
 | 
						|
					}
 | 
						|
					if trafficBytes == 0 {
 | 
						|
						continue
 | 
						|
					}
 | 
						|
					var itemId = SharedNodePriceItemDAO.SearchItemsWithBytes(priceItems, trafficBytes)
 | 
						|
					if itemId == 0 {
 | 
						|
						continue
 | 
						|
					}
 | 
						|
					price, ok := pricesMap[itemId]
 | 
						|
					if !ok {
 | 
						|
						continue
 | 
						|
					}
 | 
						|
					if price <= 0 {
 | 
						|
						continue
 | 
						|
					}
 | 
						|
					var regionFee = float64(trafficBytes) / 1000 / 1000 / 1000 * 8 * price
 | 
						|
					fee += regionFee
 | 
						|
				}
 | 
						|
 | 
						|
				// 百分位
 | 
						|
				var percentile = 95
 | 
						|
				percentileBytes, err := SharedServerBandwidthStatDAO.FindMonthlyPercentile(tx, serverId, month, percentile)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
 | 
						|
				err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, userId, serverId, month, userPlanId, 0, totalTrafficBytes, percentileBytes, percentile, "", fee)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			userPlan, err := SharedUserPlanDAO.FindUserPlanWithoutState(tx, userPlanId, cacheMap)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
			if userPlan == nil {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			plan, ok := planMap[int64(userPlan.PlanId)]
 | 
						|
			if !ok {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
 | 
						|
			// 总流量
 | 
						|
			totalTrafficBytes, err := SharedServerDailyStatDAO.SumMonthlyBytes(tx, serverId, month)
 | 
						|
			if err != nil {
 | 
						|
				return err
 | 
						|
			}
 | 
						|
 | 
						|
			switch plan.PriceType {
 | 
						|
			case serverconfigs.PlanPriceTypePeriod:
 | 
						|
				// 已经在购买套餐的时候付过费,这里不再重复计费
 | 
						|
				var fee float64 = 0
 | 
						|
 | 
						|
				// 百分位
 | 
						|
				var percentile = 95
 | 
						|
				percentileBytes, err := SharedServerBandwidthStatDAO.FindMonthlyPercentile(tx, serverId, month, percentile)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
 | 
						|
				err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, int64(userPlan.UserId), serverId, month, userPlanId, int64(userPlan.PlanId), totalTrafficBytes, percentileBytes, percentile, plan.PriceType, fee)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			case serverconfigs.PlanPriceTypeTraffic:
 | 
						|
				var config = plan.DecodeTrafficPrice()
 | 
						|
				var fee float64 = 0
 | 
						|
				if config != nil && config.Base > 0 {
 | 
						|
					fee = float64(totalTrafficBytes) / 1024 / 1024 / 1024 * float64(config.Base)
 | 
						|
				}
 | 
						|
 | 
						|
				// 百分位
 | 
						|
				var percentile = 95
 | 
						|
				percentileBytes, err := SharedServerBandwidthStatDAO.FindMonthlyPercentile(tx, serverId, month, percentile)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
 | 
						|
				err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, int64(userPlan.UserId), serverId, month, userPlanId, int64(userPlan.PlanId), totalTrafficBytes, percentileBytes, percentile, plan.PriceType, fee)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			case serverconfigs.PlanPriceTypeBandwidth:
 | 
						|
				// 百分位
 | 
						|
				var percentile = 95
 | 
						|
				var config = plan.DecodeBandwidthPrice()
 | 
						|
				if config != nil {
 | 
						|
					percentile = config.Percentile
 | 
						|
					if percentile <= 0 {
 | 
						|
						percentile = 95
 | 
						|
					} else if percentile > 100 {
 | 
						|
						percentile = 100
 | 
						|
					}
 | 
						|
				}
 | 
						|
				percentileBytes, err := SharedServerBandwidthStatDAO.FindMonthlyPercentile(tx, serverId, month, percentile)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
				var mb = float32(percentileBytes) / 1024 / 1024
 | 
						|
				var price float32
 | 
						|
				if config != nil {
 | 
						|
					price = config.LookupPrice(mb)
 | 
						|
				}
 | 
						|
				var fee = float64(price)
 | 
						|
				err = SharedServerBillDAO.CreateOrUpdateServerBill(tx, int64(userPlan.UserId), serverId, month, userPlanId, int64(userPlan.PlanId), totalTrafficBytes, percentileBytes, percentile, plan.PriceType, fee)
 | 
						|
				if err != nil {
 | 
						|
					return err
 | 
						|
				}
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	// 计算用户费用
 | 
						|
	for _, userId := range userIds {
 | 
						|
		if userId == 0 {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
		amount, err := SharedServerBillDAO.SumUserMonthlyAmount(tx, userId, month)
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
		err = SharedUserBillDAO.CreateBill(tx, userId, BillTypeTraffic, "流量带宽费用", amount, month, month < timeutil.Format("Ym"))
 | 
						|
		if err != nil {
 | 
						|
			return err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// UpdateUserBillIsPaid 设置账单已支付
 | 
						|
func (this *UserBillDAO) UpdateUserBillIsPaid(tx *dbs.Tx, billId int64, isPaid bool) error {
 | 
						|
	return this.Query(tx).
 | 
						|
		Pk(billId).
 | 
						|
		Set("isPaid", isPaid).
 | 
						|
		UpdateQuickly()
 | 
						|
}
 | 
						|
 | 
						|
// BillTypeName 获取账单类型名称
 | 
						|
func (this *UserBillDAO) BillTypeName(billType BillType) string {
 | 
						|
	switch billType {
 | 
						|
	case BillTypeTraffic:
 | 
						|
		return "流量带宽"
 | 
						|
	}
 | 
						|
	return ""
 | 
						|
}
 | 
						|
 | 
						|
// GenerateBillCode 生成账单编号
 | 
						|
func (this *UserBillDAO) GenerateBillCode(tx *dbs.Tx) (string, error) {
 | 
						|
	var code = timeutil.Format("YmdHis") + types.String(rands.Int(100000, 999999))
 | 
						|
	exists, err := this.Query(tx).
 | 
						|
		Attr("code", code).
 | 
						|
		Exist()
 | 
						|
	if err != nil {
 | 
						|
		return "", err
 | 
						|
	}
 | 
						|
	if !exists {
 | 
						|
		return code, nil
 | 
						|
	}
 | 
						|
	return this.GenerateBillCode(tx)
 | 
						|
}
 | 
						|
 | 
						|
// CheckUserBill 检查用户账单
 | 
						|
func (this *UserBillDAO) CheckUserBill(tx *dbs.Tx, userId int64, billId int64) error {
 | 
						|
	if userId <= 0 || billId <= 0 {
 | 
						|
		return ErrNotFound
 | 
						|
	}
 | 
						|
	exists, err := this.Query(tx).
 | 
						|
		Pk(billId).
 | 
						|
		Attr("userId", userId).
 | 
						|
		Exist()
 | 
						|
	if err != nil {
 | 
						|
		return err
 | 
						|
	}
 | 
						|
	if !exists {
 | 
						|
		return ErrNotFound
 | 
						|
	}
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// SumUnpaidUserBill 计算某个用户未支付总额
 | 
						|
func (this *UserBillDAO) SumUnpaidUserBill(tx *dbs.Tx, userId int64) (float32, error) {
 | 
						|
	sum, err := this.Query(tx).
 | 
						|
		Attr("userId", userId).
 | 
						|
		Attr("isPaid", 0).
 | 
						|
		Sum("amount", 0)
 | 
						|
	if err != nil {
 | 
						|
		return 0, err
 | 
						|
	}
 | 
						|
	return float32(sum), nil
 | 
						|
}
 |