mirror of
				https://github.com/TeaOSLab/EdgeAPI.git
				synced 2025-11-04 16:00:24 +08:00 
			
		
		
		
	实现带宽计费套餐
This commit is contained in:
		@@ -1,6 +1,6 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
// 区域计费设置
 | 
			
		||||
// NodePriceItem 区域计费设置
 | 
			
		||||
type NodePriceItem struct {
 | 
			
		||||
	Id        uint32 `field:"id"`        // ID
 | 
			
		||||
	IsOn      uint8  `field:"isOn"`      // 是否启用
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
// 节点区域
 | 
			
		||||
// NodeRegion 节点区域
 | 
			
		||||
type NodeRegion struct {
 | 
			
		||||
	Id          uint32 `field:"id"`          // ID
 | 
			
		||||
	AdminId     uint32 `field:"adminId"`     // 管理员ID
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,18 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import "encoding/json"
 | 
			
		||||
 | 
			
		||||
func (this *NodeRegion) DecodePriceMap() map[int64]float64 {
 | 
			
		||||
	var m = map[int64]float64{}
 | 
			
		||||
	if len(this.Prices) == 0 {
 | 
			
		||||
		return m
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := json.Unmarshal([]byte(this.Prices), &m)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// 忽略错误
 | 
			
		||||
		return m
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return m
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ func TestNodeTaskDAO_CreateNodeTask(t *testing.T) {
 | 
			
		||||
	dbs.NotifyReady()
 | 
			
		||||
 | 
			
		||||
	var tx *dbs.Tx
 | 
			
		||||
	err := SharedNodeTaskDAO.CreateNodeTask(tx, nodeconfigs.NodeRoleNode, 1, 2, NodeTaskTypeConfigChanged, 0)
 | 
			
		||||
	err := SharedNodeTaskDAO.CreateNodeTask(tx, nodeconfigs.NodeRoleNode, 1, 2, 0, NodeTaskTypeConfigChanged, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -22,7 +22,7 @@ func TestNodeTaskDAO_CreateClusterTask(t *testing.T) {
 | 
			
		||||
	dbs.NotifyReady()
 | 
			
		||||
 | 
			
		||||
	var tx *dbs.Tx
 | 
			
		||||
	err := SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, 1, NodeTaskTypeConfigChanged)
 | 
			
		||||
	err := SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, 1, 0, NodeTaskTypeConfigChanged)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -33,7 +33,7 @@ func TestNodeTaskDAO_ExtractClusterTask(t *testing.T) {
 | 
			
		||||
	dbs.NotifyReady()
 | 
			
		||||
 | 
			
		||||
	var tx *dbs.Tx
 | 
			
		||||
	err := SharedNodeTaskDAO.ExtractNodeClusterTask(tx, 1, NodeTaskTypeConfigChanged)
 | 
			
		||||
	err := SharedNodeTaskDAO.ExtractNodeClusterTask(tx, 1, 0, NodeTaskTypeConfigChanged)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -80,7 +80,17 @@ func (this *PlanDAO) FindPlanName(tx *dbs.Tx, id int64) (string, error) {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreatePlan 创建套餐
 | 
			
		||||
func (this *PlanDAO) CreatePlan(tx *dbs.Tx, name string, clusterId int64, trafficLimitJSON []byte, featuresJSON []byte, priceType serverconfigs.PlanPriceType, trafficPriceJSON []byte, monthlyPrice float32, seasonallyPrice float32, yearlyPrice float32) (int64, error) {
 | 
			
		||||
func (this *PlanDAO) CreatePlan(tx *dbs.Tx,
 | 
			
		||||
	name string,
 | 
			
		||||
	clusterId int64,
 | 
			
		||||
	trafficLimitJSON []byte,
 | 
			
		||||
	featuresJSON []byte,
 | 
			
		||||
	priceType serverconfigs.PlanPriceType,
 | 
			
		||||
	trafficPriceJSON []byte,
 | 
			
		||||
	bandwidthPriceJSON []byte,
 | 
			
		||||
	monthlyPrice float32,
 | 
			
		||||
	seasonallyPrice float32,
 | 
			
		||||
	yearlyPrice float32) (int64, error) {
 | 
			
		||||
	var op = NewPlanOperator()
 | 
			
		||||
	op.Name = name
 | 
			
		||||
	op.ClusterId = clusterId
 | 
			
		||||
@@ -94,6 +104,9 @@ func (this *PlanDAO) CreatePlan(tx *dbs.Tx, name string, clusterId int64, traffi
 | 
			
		||||
	if len(trafficPriceJSON) > 0 {
 | 
			
		||||
		op.TrafficPrice = trafficPriceJSON
 | 
			
		||||
	}
 | 
			
		||||
	if len(bandwidthPriceJSON) > 0 {
 | 
			
		||||
		op.BandwidthPrice = bandwidthPriceJSON
 | 
			
		||||
	}
 | 
			
		||||
	if monthlyPrice >= 0 {
 | 
			
		||||
		op.MonthlyPrice = monthlyPrice
 | 
			
		||||
	}
 | 
			
		||||
@@ -109,7 +122,19 @@ func (this *PlanDAO) CreatePlan(tx *dbs.Tx, name string, clusterId int64, traffi
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdatePlan 修改套餐
 | 
			
		||||
func (this *PlanDAO) UpdatePlan(tx *dbs.Tx, planId int64, name string, isOn bool, clusterId int64, trafficLimitJSON []byte, featuresJSON []byte, priceType serverconfigs.PlanPriceType, trafficPriceJSON []byte, monthlyPrice float32, seasonallyPrice float32, yearlyPrice float32) error {
 | 
			
		||||
func (this *PlanDAO) UpdatePlan(tx *dbs.Tx,
 | 
			
		||||
	planId int64,
 | 
			
		||||
	name string,
 | 
			
		||||
	isOn bool,
 | 
			
		||||
	clusterId int64,
 | 
			
		||||
	trafficLimitJSON []byte,
 | 
			
		||||
	featuresJSON []byte,
 | 
			
		||||
	priceType serverconfigs.PlanPriceType,
 | 
			
		||||
	trafficPriceJSON []byte,
 | 
			
		||||
	bandwidthPriceJSON []byte,
 | 
			
		||||
	monthlyPrice float32,
 | 
			
		||||
	seasonallyPrice float32,
 | 
			
		||||
	yearlyPrice float32) error {
 | 
			
		||||
	if planId <= 0 {
 | 
			
		||||
		return errors.New("invalid planId")
 | 
			
		||||
	}
 | 
			
		||||
@@ -138,6 +163,9 @@ func (this *PlanDAO) UpdatePlan(tx *dbs.Tx, planId int64, name string, isOn bool
 | 
			
		||||
	if len(trafficPriceJSON) > 0 {
 | 
			
		||||
		op.TrafficPrice = trafficPriceJSON
 | 
			
		||||
	}
 | 
			
		||||
	if len(bandwidthPriceJSON) > 0 {
 | 
			
		||||
		op.BandwidthPrice = bandwidthPriceJSON
 | 
			
		||||
	}
 | 
			
		||||
	if monthlyPrice >= 0 {
 | 
			
		||||
		op.MonthlyPrice = monthlyPrice
 | 
			
		||||
	} else {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ type Plan struct {
 | 
			
		||||
	TrafficLimit    string  `field:"trafficLimit"`    // 流量限制
 | 
			
		||||
	Features        string  `field:"features"`        // 允许的功能
 | 
			
		||||
	TrafficPrice    string  `field:"trafficPrice"`    // 流量价格设定
 | 
			
		||||
	BandwidthPrice  string  `field:"bandwidthPrice"`  // 带宽价格
 | 
			
		||||
	MonthlyPrice    float64 `field:"monthlyPrice"`    // 月付
 | 
			
		||||
	SeasonallyPrice float64 `field:"seasonallyPrice"` // 季付
 | 
			
		||||
	YearlyPrice     float64 `field:"yearlyPrice"`     // 年付
 | 
			
		||||
@@ -25,6 +26,7 @@ type PlanOperator struct {
 | 
			
		||||
	TrafficLimit    interface{} // 流量限制
 | 
			
		||||
	Features        interface{} // 允许的功能
 | 
			
		||||
	TrafficPrice    interface{} // 流量价格设定
 | 
			
		||||
	BandwidthPrice  interface{} // 带宽价格
 | 
			
		||||
	MonthlyPrice    interface{} // 月付
 | 
			
		||||
	SeasonallyPrice interface{} // 季付
 | 
			
		||||
	YearlyPrice     interface{} // 年付
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1,38 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// DecodeTrafficPrice 流量价格配置
 | 
			
		||||
func (this *Plan) DecodeTrafficPrice() *serverconfigs.PlanTrafficPriceConfig {
 | 
			
		||||
	var config = &serverconfigs.PlanTrafficPriceConfig{}
 | 
			
		||||
 | 
			
		||||
	if len(this.TrafficPrice) == 0 {
 | 
			
		||||
		return config
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := json.Unmarshal([]byte(this.TrafficPrice), config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// 忽略错误
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return config
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DecodeBandwidthPrice 带宽价格配置
 | 
			
		||||
func (this *Plan) DecodeBandwidthPrice() *serverconfigs.PlanBandwidthPriceConfig {
 | 
			
		||||
	var config = &serverconfigs.PlanBandwidthPriceConfig{}
 | 
			
		||||
 | 
			
		||||
	if len(this.BandwidthPrice) == 0 {
 | 
			
		||||
		return config
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err := json.Unmarshal([]byte(this.BandwidthPrice), config)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		// 忽略错误
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return config
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										96
									
								
								internal/db/models/server_bill_dao.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								internal/db/models/server_bill_dao.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	_ "github.com/go-sql-driver/mysql"
 | 
			
		||||
	"github.com/iwind/TeaGo/Tea"
 | 
			
		||||
	"github.com/iwind/TeaGo/dbs"
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	"math"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ServerBillDAO dbs.DAO
 | 
			
		||||
 | 
			
		||||
func NewServerBillDAO() *ServerBillDAO {
 | 
			
		||||
	return dbs.NewDAO(&ServerBillDAO{
 | 
			
		||||
		DAOObject: dbs.DAOObject{
 | 
			
		||||
			DB:     Tea.Env,
 | 
			
		||||
			Table:  "edgeServerBills",
 | 
			
		||||
			Model:  new(ServerBill),
 | 
			
		||||
			PkName: "id",
 | 
			
		||||
		},
 | 
			
		||||
	}).(*ServerBillDAO)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var SharedServerBillDAO *ServerBillDAO
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	dbs.OnReady(func() {
 | 
			
		||||
		SharedServerBillDAO = NewServerBillDAO()
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateOrUpdateServerBill 创建账单
 | 
			
		||||
func (this *ServerBillDAO) CreateOrUpdateServerBill(tx *dbs.Tx, userId int64, serverId int64, month string, userPlanId int64, planId int64, totalTrafficBytes int64, bandwidthPercentileBytes int64, bandwidthPercentile int, fee float64) error {
 | 
			
		||||
	fee = math.Floor(fee*100) / 100
 | 
			
		||||
	return this.Query(tx).
 | 
			
		||||
		InsertOrUpdateQuickly(maps.Map{
 | 
			
		||||
			"userId":                   userId,
 | 
			
		||||
			"serverId":                 serverId,
 | 
			
		||||
			"month":                    month,
 | 
			
		||||
			"amount":                   fee,
 | 
			
		||||
			"userPlanId":               userPlanId,
 | 
			
		||||
			"planId":                   planId,
 | 
			
		||||
			"totalTrafficBytes":        totalTrafficBytes,
 | 
			
		||||
			"bandwidthPercentileBytes": bandwidthPercentileBytes,
 | 
			
		||||
			"bandwidthPercentile":      bandwidthPercentile,
 | 
			
		||||
			"createdAt":                time.Now().Unix(),
 | 
			
		||||
		}, maps.Map{
 | 
			
		||||
			"userId":                   userId,
 | 
			
		||||
			"amount":                   fee,
 | 
			
		||||
			"userPlanId":               userPlanId,
 | 
			
		||||
			"planId":                   planId,
 | 
			
		||||
			"totalTrafficBytes":        totalTrafficBytes,
 | 
			
		||||
			"bandwidthPercentileBytes": bandwidthPercentileBytes,
 | 
			
		||||
			"bandwidthPercentile":      bandwidthPercentile,
 | 
			
		||||
			"createdAt":                time.Now().Unix(),
 | 
			
		||||
		})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SumUserMonthlyAmount 计算总费用
 | 
			
		||||
func (this *ServerBillDAO) SumUserMonthlyAmount(tx *dbs.Tx, userId int64, month string) (float64, error) {
 | 
			
		||||
	return this.Query(tx).
 | 
			
		||||
		Attr("userId", userId).
 | 
			
		||||
		Attr("month", month).
 | 
			
		||||
		Sum("amount", 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CountServerBills 计算总账单数量
 | 
			
		||||
func (this *ServerBillDAO) CountServerBills(tx *dbs.Tx, userId int64, month string) (int64, error) {
 | 
			
		||||
	var query = this.Query(tx)
 | 
			
		||||
	if userId > 0 {
 | 
			
		||||
		query.Attr("userId", userId)
 | 
			
		||||
	}
 | 
			
		||||
	if len(month) > 0 {
 | 
			
		||||
		query.Attr("month", month)
 | 
			
		||||
	}
 | 
			
		||||
	return query.Count()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListServerBills 列出单页账单
 | 
			
		||||
func (this *ServerBillDAO) ListServerBills(tx *dbs.Tx, userId int64, month string, offset int64, size int64) (result []*ServerBill, err error) {
 | 
			
		||||
	var query = this.Query(tx)
 | 
			
		||||
	if userId > 0 {
 | 
			
		||||
		query.Attr("userId", userId)
 | 
			
		||||
	}
 | 
			
		||||
	if len(month) > 0 {
 | 
			
		||||
		query.Attr("month", month)
 | 
			
		||||
	}
 | 
			
		||||
	_, err = query.
 | 
			
		||||
		Desc("serverId").
 | 
			
		||||
		Offset(offset).
 | 
			
		||||
		Limit(size).
 | 
			
		||||
		Slice(&result).
 | 
			
		||||
		FindAll()
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								internal/db/models/server_bill_dao_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								internal/db/models/server_bill_dao_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	_ "github.com/go-sql-driver/mysql"
 | 
			
		||||
	_ "github.com/iwind/TeaGo/bootstrap"
 | 
			
		||||
	"github.com/iwind/TeaGo/dbs"
 | 
			
		||||
	timeutil "github.com/iwind/TeaGo/utils/time"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestServerBillDAO_CreateOrUpdateServerBill(t *testing.T) {
 | 
			
		||||
	var dao = NewServerBillDAO()
 | 
			
		||||
	var tx *dbs.Tx
 | 
			
		||||
	var month = timeutil.Format("Y02")
 | 
			
		||||
	err := dao.CreateOrUpdateServerBill(tx, 1, 2, month, 4, 5, 6, 7, 95, 100)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										34
									
								
								internal/db/models/server_bill_model.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								internal/db/models/server_bill_model.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
// ServerBill 服务账单
 | 
			
		||||
type ServerBill struct {
 | 
			
		||||
	Id                       uint64  `field:"id"`                       // ID
 | 
			
		||||
	UserId                   uint32  `field:"userId"`                   // 用户ID
 | 
			
		||||
	ServerId                 uint32  `field:"serverId"`                 // 服务ID
 | 
			
		||||
	Amount                   float64 `field:"amount"`                   // 金额
 | 
			
		||||
	Month                    string  `field:"month"`                    // 月份
 | 
			
		||||
	CreatedAt                uint64  `field:"createdAt"`                // 创建时间
 | 
			
		||||
	UserPlanId               uint32  `field:"userPlanId"`               // 用户套餐ID
 | 
			
		||||
	PlanId                   uint32  `field:"planId"`                   // 套餐ID
 | 
			
		||||
	TotalTrafficBytes        uint64  `field:"totalTrafficBytes"`        // 总流量
 | 
			
		||||
	BandwidthPercentileBytes uint64  `field:"bandwidthPercentileBytes"` // 带宽百分位字节
 | 
			
		||||
	BandwidthPercentile      uint8   `field:"bandwidthPercentile"`      // 带宽百分位
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ServerBillOperator struct {
 | 
			
		||||
	Id                       interface{} // ID
 | 
			
		||||
	UserId                   interface{} // 用户ID
 | 
			
		||||
	ServerId                 interface{} // 服务ID
 | 
			
		||||
	Amount                   interface{} // 金额
 | 
			
		||||
	Month                    interface{} // 月份
 | 
			
		||||
	CreatedAt                interface{} // 创建时间
 | 
			
		||||
	UserPlanId               interface{} // 用户套餐ID
 | 
			
		||||
	PlanId                   interface{} // 套餐ID
 | 
			
		||||
	TotalTrafficBytes        interface{} // 总流量
 | 
			
		||||
	BandwidthPercentileBytes interface{} // 带宽百分位字节
 | 
			
		||||
	BandwidthPercentile      interface{} // 带宽百分位
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewServerBillOperator() *ServerBillOperator {
 | 
			
		||||
	return &ServerBillOperator{}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								internal/db/models/server_bill_model_ext.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								internal/db/models/server_bill_model_ext.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
package models
 | 
			
		||||
@@ -12,7 +12,9 @@ import (
 | 
			
		||||
	"github.com/iwind/TeaGo/maps"
 | 
			
		||||
	"github.com/iwind/TeaGo/rands"
 | 
			
		||||
	timeutil "github.com/iwind/TeaGo/utils/time"
 | 
			
		||||
	"math"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -24,7 +26,7 @@ func init() {
 | 
			
		||||
		var ticker = time.NewTicker(time.Duration(rands.Int(24, 48)) * time.Hour)
 | 
			
		||||
		goman.New(func() {
 | 
			
		||||
			for range ticker.C {
 | 
			
		||||
				err := SharedServerDailyStatDAO.Clean(nil, 30) // 只保留N天
 | 
			
		||||
				err := SharedServerDailyStatDAO.Clean(nil, 60) // 只保留 N 天,时间需要长一些,因为需要用来生成账单
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logs.Println("ServerDailyStatDAO", "clean expired data failed: "+err.Error())
 | 
			
		||||
				}
 | 
			
		||||
@@ -130,15 +132,15 @@ func (this *ServerDailyStatDAO) SaveStats(tx *dbs.Tx, stats []*pb.ServerDailySta
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SumUserMonthly 根据用户计算某月合计
 | 
			
		||||
// SumServerMonthlyWithRegion 根据服务计算某月合计
 | 
			
		||||
// month 格式为YYYYMM
 | 
			
		||||
func (this *ServerDailyStatDAO) SumUserMonthly(tx *dbs.Tx, userId int64, regionId int64, month string) (int64, error) {
 | 
			
		||||
func (this *ServerDailyStatDAO) SumServerMonthlyWithRegion(tx *dbs.Tx, serverId int64, regionId int64, month string) (int64, error) {
 | 
			
		||||
	query := this.Query(tx)
 | 
			
		||||
	if regionId > 0 {
 | 
			
		||||
		query.Attr("regionId", regionId)
 | 
			
		||||
	}
 | 
			
		||||
	return query.Between("day", month+"01", month+"32").
 | 
			
		||||
		Attr("userId", userId).
 | 
			
		||||
		Attr("serverId", serverId).
 | 
			
		||||
		SumInt64("bytes", 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -156,16 +158,6 @@ func (this *ServerDailyStatDAO) SumUserMonthlyWithoutPlan(tx *dbs.Tx, userId int
 | 
			
		||||
		SumInt64("bytes", 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SumUserMonthlyFee 计算用户某个月费用
 | 
			
		||||
// month 格式为YYYYMM
 | 
			
		||||
func (this *ServerDailyStatDAO) SumUserMonthlyFee(tx *dbs.Tx, userId int64, month string) (float64, error) {
 | 
			
		||||
	return this.Query(tx).
 | 
			
		||||
		Attr("userId", userId).
 | 
			
		||||
		Between("day", month+"01", month+"32").
 | 
			
		||||
		Gt("fee", 0).
 | 
			
		||||
		Sum("fee", 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SumUserMonthlyPeek 获取某月带宽峰值
 | 
			
		||||
// month 格式为YYYYMM
 | 
			
		||||
func (this *ServerDailyStatDAO) SumUserMonthlyPeek(tx *dbs.Tx, userId int64, regionId int64, month string) (int64, error) {
 | 
			
		||||
@@ -195,6 +187,15 @@ func (this *ServerDailyStatDAO) SumUserDaily(tx *dbs.Tx, userId int64, regionId
 | 
			
		||||
		SumInt64("bytes", 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SumUserMonthly 获取某月流量总和
 | 
			
		||||
// month 格式为YYYYMM
 | 
			
		||||
func (this *ServerDailyStatDAO) SumUserMonthly(tx *dbs.Tx, userId int64, month string) (int64, error) {
 | 
			
		||||
	return this.Query(tx).
 | 
			
		||||
		Between("day", month+"01", month+"31").
 | 
			
		||||
		Attr("userId", userId).
 | 
			
		||||
		SumInt64("bytes", 0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SumUserDailyPeek 获取某天带宽峰值
 | 
			
		||||
// day 格式为YYYYMMDD
 | 
			
		||||
func (this *ServerDailyStatDAO) SumUserDailyPeek(tx *dbs.Tx, userId int64, regionId int64, day string) (int64, error) {
 | 
			
		||||
@@ -339,6 +340,59 @@ func (this *ServerDailyStatDAO) SumMonthlyStat(tx *dbs.Tx, serverId int64, month
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SumMonthlyBytes 获取某月内的流量
 | 
			
		||||
// month 格式为YYYYMM
 | 
			
		||||
func (this *ServerDailyStatDAO) SumMonthlyBytes(tx *dbs.Tx, serverId int64, month string) (result int64, err error) {
 | 
			
		||||
	if !regexp.MustCompile(`^\d{6}$`).MatchString(month) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return this.Query(tx).
 | 
			
		||||
		Result("SUM(bytes) AS bytes").
 | 
			
		||||
		Attr("serverId", serverId).
 | 
			
		||||
		Between("day", month+"01", month+"31").
 | 
			
		||||
		FindInt64Col(0)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindMonthlyPercentile 获取某月内百分位
 | 
			
		||||
func (this *ServerDailyStatDAO) FindMonthlyPercentile(tx *dbs.Tx, serverId int64, month string, percentile int) (result int64, err error) {
 | 
			
		||||
	if percentile <= 0 {
 | 
			
		||||
		percentile = 95
 | 
			
		||||
	}
 | 
			
		||||
	if percentile > 100 {
 | 
			
		||||
		percentile = 100
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	total, err := this.Query(tx).
 | 
			
		||||
		Attr("serverId", serverId).
 | 
			
		||||
		Between("day", month+"01", month+"31").
 | 
			
		||||
		Count()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return 0, err
 | 
			
		||||
	}
 | 
			
		||||
	if total == 0 {
 | 
			
		||||
		return 0, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var offset int64
 | 
			
		||||
 | 
			
		||||
	if total > 1 {
 | 
			
		||||
		offset = int64(math.Ceil(float64(total) * float64(100-percentile) / 100))
 | 
			
		||||
	}
 | 
			
		||||
	result, err = this.Query(tx).
 | 
			
		||||
		Result("bytes").
 | 
			
		||||
		Attr("serverId", serverId).
 | 
			
		||||
		Between("day", month+"01", month+"31").
 | 
			
		||||
		Desc("bytes").
 | 
			
		||||
		Offset(offset).
 | 
			
		||||
		Limit(1).
 | 
			
		||||
		FindInt64Col(0)
 | 
			
		||||
 | 
			
		||||
	// 因为是5分钟统计,所以需要除以300
 | 
			
		||||
	result = result / 300
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindDailyStats 按天统计
 | 
			
		||||
func (this *ServerDailyStatDAO) FindDailyStats(tx *dbs.Tx, serverId int64, dayFrom string, dayTo string) (result []*ServerDailyStat, err error) {
 | 
			
		||||
	ones, err := this.Query(tx).
 | 
			
		||||
@@ -428,6 +482,25 @@ func (this *ServerDailyStatDAO) FindTopUserStats(tx *dbs.Tx, hourFrom string, ho
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindDistinctServerIds 查找所有有流量的服务ID列表
 | 
			
		||||
// dayFrom YYYYMMDD
 | 
			
		||||
// dayTo YYYYMMDD
 | 
			
		||||
func (this *ServerDailyStatDAO) FindDistinctServerIds(tx *dbs.Tx, dayFrom string, dayTo string) (serverIds []int64, err error) {
 | 
			
		||||
	dayFrom = strings.ReplaceAll(dayFrom, "-", "")
 | 
			
		||||
	dayTo = strings.ReplaceAll(dayTo, "-", "")
 | 
			
		||||
	ones, _, err := this.Query(tx).
 | 
			
		||||
		Result("DISTINCT(serverId) AS serverId").
 | 
			
		||||
		Between("day", dayFrom, dayTo).
 | 
			
		||||
		FindOnes()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	for _, one := range ones {
 | 
			
		||||
		serverIds = append(serverIds, one.GetInt64("serverId"))
 | 
			
		||||
	}
 | 
			
		||||
	return serverIds, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateStatFee 设置费用
 | 
			
		||||
func (this *ServerDailyStatDAO) UpdateStatFee(tx *dbs.Tx, statId int64, fee float32) error {
 | 
			
		||||
	return this.Query(tx).
 | 
			
		||||
 
 | 
			
		||||
@@ -46,7 +46,7 @@ func TestServerDailyStatDAO_SaveStats2(t *testing.T) {
 | 
			
		||||
func TestServerDailyStatDAO_SumUserMonthly(t *testing.T) {
 | 
			
		||||
	dbs.NotifyReady()
 | 
			
		||||
	var tx *dbs.Tx
 | 
			
		||||
	bytes, err := NewServerDailyStatDAO().SumUserMonthly(tx, 1, 1, timeutil.Format("Ym"))
 | 
			
		||||
	bytes, err := NewServerDailyStatDAO().SumUserMonthly(tx, 1, timeutil.Format("Ym"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
@@ -68,9 +68,28 @@ func TestServerDailyStatDAO_SumMinutelyRequests(t *testing.T) {
 | 
			
		||||
	dbs.NotifyReady()
 | 
			
		||||
	var tx *dbs.Tx
 | 
			
		||||
 | 
			
		||||
	stat, err := NewServerDailyStatDAO().SumMinutelyStat(tx, 23, timeutil.Format("Ymd") + "1435")
 | 
			
		||||
	stat, err := NewServerDailyStatDAO().SumMinutelyStat(tx, 23, timeutil.Format("Ymd")+"1435")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	logs.PrintAsJSON(stat, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServerDailyStatDAO_FindDistinctPlanServerIdsBetweenDay(t *testing.T) {
 | 
			
		||||
	var tx *dbs.Tx
 | 
			
		||||
	serverIds, err := NewServerDailyStatDAO().FindDistinctServerIds(tx, timeutil.Format("Ym01"), timeutil.Format("Ymd"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log(serverIds)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestServerDailyStatDAO_FindMonthlyPercentile(t *testing.T) {
 | 
			
		||||
	var tx *dbs.Tx
 | 
			
		||||
	var dao = NewServerDailyStatDAO()
 | 
			
		||||
	result, err := dao.FindMonthlyPercentile(tx, 23, timeutil.Format("Ym"), 95)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("result:", result)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -221,6 +221,7 @@ func (this *ServerDAO) CreateServer(tx *dbs.Tx,
 | 
			
		||||
	op.DnsName = dnsName
 | 
			
		||||
 | 
			
		||||
	op.UserPlanId = userPlanId
 | 
			
		||||
	op.LastUserPlanId = userPlanId
 | 
			
		||||
 | 
			
		||||
	op.Version = 1
 | 
			
		||||
	op.IsOn = 1
 | 
			
		||||
@@ -2242,6 +2243,7 @@ func (this *ServerDAO) UpdateServerUserPlanId(tx *dbs.Tx, serverId int64, userPl
 | 
			
		||||
	err = this.Query(tx).
 | 
			
		||||
		Pk(serverId).
 | 
			
		||||
		Set("userPlanId", userPlanId).
 | 
			
		||||
		Set("lastUserPlanId", userPlanId).
 | 
			
		||||
		Set("clusterId", plan.ClusterId).
 | 
			
		||||
		UpdateQuickly()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -2250,6 +2252,19 @@ func (this *ServerDAO) UpdateServerUserPlanId(tx *dbs.Tx, serverId int64, userPl
 | 
			
		||||
	return this.NotifyUpdate(tx, serverId)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindServerLastUserPlanIdAndUserId 查找最后使用的套餐
 | 
			
		||||
func (this *ServerDAO) FindServerLastUserPlanIdAndUserId(tx *dbs.Tx, serverId int64) (userPlanId int64, userId int64, err error) {
 | 
			
		||||
	one, err := this.Query(tx).
 | 
			
		||||
		Pk(serverId).
 | 
			
		||||
		Result("lastUserPlanId", "userId").
 | 
			
		||||
		Find()
 | 
			
		||||
	if err != nil || one == nil {
 | 
			
		||||
		return 0, 0, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return int64(one.(*Server).LastUserPlanId), int64(one.(*Server).UserId), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NotifyUpdate 同步集群
 | 
			
		||||
func (this *ServerDAO) NotifyUpdate(tx *dbs.Tx, serverId int64) error {
 | 
			
		||||
	// 创建任务
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,7 @@ type Server struct {
 | 
			
		||||
	TrafficLimitStatus  string  `field:"trafficLimitStatus"`  // 流量限制状态
 | 
			
		||||
	TotalTraffic        float64 `field:"totalTraffic"`        // 总流量
 | 
			
		||||
	UserPlanId          uint32  `field:"userPlanId"`          // 所属套餐ID
 | 
			
		||||
	LastUserPlanId      uint32  `field:"lastUserPlanId"`      // 上一次使用的套餐
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ServerOperator struct {
 | 
			
		||||
@@ -87,6 +88,7 @@ type ServerOperator struct {
 | 
			
		||||
	TrafficLimitStatus  interface{} // 流量限制状态
 | 
			
		||||
	TotalTraffic        interface{} // 总流量
 | 
			
		||||
	UserPlanId          interface{} // 所属套餐ID
 | 
			
		||||
	LastUserPlanId      interface{} // 上一次使用的套餐
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewServerOperator() *ServerOperator {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,10 +1,9 @@
 | 
			
		||||
package models
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAPI/internal/goman"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAPI/internal/utils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
	_ "github.com/go-sql-driver/mysql"
 | 
			
		||||
	"github.com/iwind/TeaGo/Tea"
 | 
			
		||||
@@ -23,7 +22,7 @@ func init() {
 | 
			
		||||
 | 
			
		||||
		goman.New(func() {
 | 
			
		||||
			// 自动生成账单任务
 | 
			
		||||
			var ticker = time.NewTicker(1 * time.Minute)
 | 
			
		||||
			var ticker = time.NewTicker(1 * time.Hour)
 | 
			
		||||
			for range ticker.C {
 | 
			
		||||
				// 是否已经生成了,如果已经生成了就跳过
 | 
			
		||||
				var lastMonth = timeutil.Format("Ym", time.Now().AddDate(0, -1, 0))
 | 
			
		||||
@@ -136,7 +135,7 @@ func (this *UserBillDAO) FindUnpaidBills(tx *dbs.Tx, size int64) (result []*User
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CreateBill 创建账单
 | 
			
		||||
func (this *UserBillDAO) CreateBill(tx *dbs.Tx, userId int64, billType BillType, description string, amount float32, month string) error {
 | 
			
		||||
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
 | 
			
		||||
@@ -149,9 +148,11 @@ func (this *UserBillDAO) CreateBill(tx *dbs.Tx, userId int64, billType BillType,
 | 
			
		||||
			"amount":      amount,
 | 
			
		||||
			"month":       month,
 | 
			
		||||
			"code":        code,
 | 
			
		||||
			"isPaid":      false,
 | 
			
		||||
			"isPaid":      amount == 0,
 | 
			
		||||
			"canPay":      canPay,
 | 
			
		||||
		}, maps.Map{
 | 
			
		||||
			"amount": amount,
 | 
			
		||||
			"canPay": canPay,
 | 
			
		||||
		})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -172,19 +173,16 @@ func (this *UserBillDAO) GenerateBills(tx *dbs.Tx, month string) error {
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if len(regions) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
 | 
			
		||||
	var priceItems []*NodePriceItem
 | 
			
		||||
	if len(regions) > 0 {
 | 
			
		||||
		priceItems, err = SharedNodePriceItemDAO.FindAllEnabledRegionPrices(tx, NodePriceTypeTraffic)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	priceItems, err := SharedNodePriceItemDAO.FindAllEnabledRegionPrices(tx, NodePriceTypeTraffic)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if len(priceItems) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 计算套餐费用
 | 
			
		||||
	// 计算服务套餐费用
 | 
			
		||||
	plans, err := SharedPlanDAO.FindAllEnabledPlans(tx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
@@ -194,54 +192,169 @@ func (this *UserBillDAO) GenerateBills(tx *dbs.Tx, month string) error {
 | 
			
		||||
		planMap[int64(plan.Id)] = plan
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stats, err := SharedServerDailyStatDAO.FindMonthlyStatsWithPlan(tx, month)
 | 
			
		||||
	var dayFrom = month + "01"
 | 
			
		||||
	var dayTo = month + "32"
 | 
			
		||||
	serverIds, err := SharedServerDailyStatDAO.FindDistinctServerIds(tx, dayFrom, dayTo)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, stat := range stats {
 | 
			
		||||
		plan, ok := planMap[int64(stat.PlanId)]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if plan.PriceType != serverconfigs.PlanPriceTypeTraffic {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if len(plan.TrafficPrice) == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		var priceConfig = &serverconfigs.PlanTrafficPrice{}
 | 
			
		||||
		err = json.Unmarshal([]byte(plan.TrafficPrice), priceConfig)
 | 
			
		||||
	var cacheMap = utils.NewCacheMap()
 | 
			
		||||
	var userIds = []int64{}
 | 
			
		||||
	for _, serverId := range serverIds {
 | 
			
		||||
		// 套餐类型
 | 
			
		||||
		userPlanId, userId, err := SharedServerDAO.FindServerLastUserPlanIdAndUserId(tx, serverId)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if priceConfig.Base > 0 {
 | 
			
		||||
			var fee = priceConfig.Base * (float32(stat.Bytes) / 1024 / 1024 / 1024)
 | 
			
		||||
			err = SharedServerDailyStatDAO.UpdateStatFee(tx, int64(stat.Id), fee)
 | 
			
		||||
		if userId == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		userIds = append(userIds, userId)
 | 
			
		||||
		if userPlanId == 0 {
 | 
			
		||||
			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
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 总流量
 | 
			
		||||
			totalTrafficBytes, err := SharedServerDailyStatDAO.SumMonthlyBytes(tx, serverId, month)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			// 百分位
 | 
			
		||||
			var percentile = 95
 | 
			
		||||
			percentileBytes, err := SharedServerDailyStatDAO.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 := SharedServerDailyStatDAO.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, 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 := SharedServerDailyStatDAO.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, 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 := SharedServerDailyStatDAO.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, fee)
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					return err
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 用户
 | 
			
		||||
	offset := int64(0)
 | 
			
		||||
	size := int64(100) // 每次只查询N次,防止由于执行时间过长而锁表
 | 
			
		||||
	for {
 | 
			
		||||
		userIds, err := SharedUserDAO.ListEnabledUserIds(tx, offset, size)
 | 
			
		||||
	// 计算用户费用
 | 
			
		||||
	for _, userId := range userIds {
 | 
			
		||||
		if userId == 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		amount, err := SharedServerBillDAO.SumUserMonthlyAmount(tx, userId, month)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		offset += size
 | 
			
		||||
		if len(userIds) == 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, userId := range userIds {
 | 
			
		||||
			// CDN流量账单
 | 
			
		||||
			err := this.generateTrafficBill(tx, userId, month, regions, priceItems)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
		err = SharedUserBillDAO.CreateBill(tx, userId, BillTypeTraffic, "流量带宽费用", amount, month, month < timeutil.Format("Ym"))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@@ -256,74 +369,11 @@ func (this *UserBillDAO) UpdateUserBillIsPaid(tx *dbs.Tx, billId int64, isPaid b
 | 
			
		||||
		UpdateQuickly()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 生成CDN流量账单
 | 
			
		||||
// month 格式YYYYMM
 | 
			
		||||
func (this *UserBillDAO) generateTrafficBill(tx *dbs.Tx, userId int64, month string, regions []*NodeRegion, priceItems []*NodePriceItem) error {
 | 
			
		||||
	// 检查是否已经有账单了
 | 
			
		||||
	if month < timeutil.Format("Ym") {
 | 
			
		||||
		b, err := this.ExistBill(tx, userId, BillTypeTraffic, month)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if b {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var 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.SumUserMonthlyWithoutPlan(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
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 套餐费用
 | 
			
		||||
	planFee, err := SharedServerDailyStatDAO.SumUserMonthlyFee(tx, userId, month)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	cost += float32(planFee)
 | 
			
		||||
 | 
			
		||||
	if cost == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 创建账单
 | 
			
		||||
	return this.CreateBill(tx, userId, BillTypeTraffic, "按流量计费", cost, month)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// BillTypeName 获取账单类型名称
 | 
			
		||||
func (this *UserBillDAO) BillTypeName(billType BillType) string {
 | 
			
		||||
	switch billType {
 | 
			
		||||
	case BillTypeTraffic:
 | 
			
		||||
		return "流量"
 | 
			
		||||
		return "流量带宽"
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,10 @@ type UserBill struct {
 | 
			
		||||
	Type        string  `field:"type"`        // 消费类型
 | 
			
		||||
	Description string  `field:"description"` // 描述
 | 
			
		||||
	Amount      float64 `field:"amount"`      // 消费数额
 | 
			
		||||
	DayFrom     string  `field:"dayFrom"`     // YYYYMMDD
 | 
			
		||||
	DayTo       string  `field:"dayTo"`       // YYYYMMDD
 | 
			
		||||
	Month       string  `field:"month"`       // 帐期YYYYMM
 | 
			
		||||
	CanPay      uint8   `field:"canPay"`      // 是否可以支付
 | 
			
		||||
	IsPaid      uint8   `field:"isPaid"`      // 是否已支付
 | 
			
		||||
	PaidAt      uint64  `field:"paidAt"`      // 支付时间
 | 
			
		||||
	Code        string  `field:"code"`        // 账单编号
 | 
			
		||||
@@ -20,7 +23,10 @@ type UserBillOperator struct {
 | 
			
		||||
	Type        interface{} // 消费类型
 | 
			
		||||
	Description interface{} // 描述
 | 
			
		||||
	Amount      interface{} // 消费数额
 | 
			
		||||
	DayFrom     interface{} // YYYYMMDD
 | 
			
		||||
	DayTo       interface{} // YYYYMMDD
 | 
			
		||||
	Month       interface{} // 帐期YYYYMM
 | 
			
		||||
	CanPay      interface{} // 是否可以支付
 | 
			
		||||
	IsPaid      interface{} // 是否已支付
 | 
			
		||||
	PaidAt      interface{} // 支付时间
 | 
			
		||||
	Code        interface{} // 账单编号
 | 
			
		||||
 
 | 
			
		||||
@@ -95,6 +95,18 @@ func (this *UserDAO) FindEnabledBasicUser(tx *dbs.Tx, id int64) (*User, error) {
 | 
			
		||||
	return result.(*User), err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindBasicUserWithoutState 查找用户基本信息,并忽略状态
 | 
			
		||||
func (this *UserDAO) FindBasicUserWithoutState(tx *dbs.Tx, id int64) (*User, error) {
 | 
			
		||||
	result, err := this.Query(tx).
 | 
			
		||||
		Pk(id).
 | 
			
		||||
		Result("id", "fullname", "username").
 | 
			
		||||
		Find()
 | 
			
		||||
	if result == nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return result.(*User), err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindUserFullname 获取管理员名称
 | 
			
		||||
func (this *UserDAO) FindUserFullname(tx *dbs.Tx, userId int64) (string, error) {
 | 
			
		||||
	return this.Query(tx).
 | 
			
		||||
 
 | 
			
		||||
@@ -85,6 +85,31 @@ func (this *UserPlanDAO) FindEnabledUserPlan(tx *dbs.Tx, userPlanId int64, cache
 | 
			
		||||
	return result.(*UserPlan), err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindUserPlanWithoutState 查找套餐,并不检查状态
 | 
			
		||||
// 防止因为删除套餐而导致计费失败
 | 
			
		||||
func (this *UserPlanDAO) FindUserPlanWithoutState(tx *dbs.Tx, userPlanId int64, cacheMap *utils.CacheMap) (*UserPlan, error) {
 | 
			
		||||
	var cacheKey = this.Table + ":FindUserPlanWithoutState:" + types.String(userPlanId)
 | 
			
		||||
	if cacheMap != nil {
 | 
			
		||||
		cache, ok := cacheMap.Get(cacheKey)
 | 
			
		||||
		if ok {
 | 
			
		||||
			return cache.(*UserPlan), nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result, err := this.Query(tx).
 | 
			
		||||
		Pk(userPlanId).
 | 
			
		||||
		Find()
 | 
			
		||||
	if result == nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if cacheMap != nil {
 | 
			
		||||
		cacheMap.Put(cacheKey, result)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result.(*UserPlan), err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CountAllEnabledUserPlans 计算套餐数量
 | 
			
		||||
func (this *UserPlanDAO) CountAllEnabledUserPlans(tx *dbs.Tx, userId int64, isAvailable bool, isExpired bool, expiringDays int32) (int64, error) {
 | 
			
		||||
	var query = this.Query(tx).
 | 
			
		||||
 
 | 
			
		||||
@@ -348,6 +348,11 @@ func (this *APINode) registerServices(server *grpc.Server) {
 | 
			
		||||
		pb.RegisterUserBillServiceServer(server, instance)
 | 
			
		||||
		this.rest(instance)
 | 
			
		||||
	}
 | 
			
		||||
	{
 | 
			
		||||
		instance := this.serviceInstance(&services.ServerBillService{}).(*services.ServerBillService)
 | 
			
		||||
		pb.RegisterServerBillServiceServer(server, instance)
 | 
			
		||||
		this.rest(instance)
 | 
			
		||||
	}
 | 
			
		||||
	{
 | 
			
		||||
		instance := this.serviceInstance(&services.UserNodeService{}).(*services.UserNodeService)
 | 
			
		||||
		pb.RegisterUserNodeServiceServer(server, instance)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										131
									
								
								internal/rpc/services/service_server_bill.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										131
									
								
								internal/rpc/services/service_server_bill.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,131 @@
 | 
			
		||||
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
 | 
			
		||||
 | 
			
		||||
package services
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAPI/internal/db/models"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeAPI/internal/utils"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ServerBillService 服务账单相关服务
 | 
			
		||||
type ServerBillService struct {
 | 
			
		||||
	BaseService
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CountAllServerBills 查询服务账单数量
 | 
			
		||||
func (this *ServerBillService) CountAllServerBills(ctx context.Context, req *pb.CountAllServerBillsRequest) (*pb.RPCCountResponse, error) {
 | 
			
		||||
	_, userId, err := this.ValidateAdminAndUser(ctx, 0, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if userId > 0 {
 | 
			
		||||
		req.UserId = userId
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tx = this.NullTx()
 | 
			
		||||
	count, err := models.SharedServerBillDAO.CountServerBills(tx, req.UserId, req.Month)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return this.SuccessCount(count)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ListServerBills 查询服务账单列表
 | 
			
		||||
func (this *ServerBillService) ListServerBills(ctx context.Context, req *pb.ListServerBillsRequest) (*pb.ListServerBillsResponse, error) {
 | 
			
		||||
	_, userId, err := this.ValidateAdminAndUser(ctx, 0, 0)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if userId > 0 {
 | 
			
		||||
		req.UserId = userId
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var tx = this.NullTx()
 | 
			
		||||
	serverBills, err := models.SharedServerBillDAO.ListServerBills(tx, req.UserId, req.Month, req.Offset, req.Size)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var pbServerBills = []*pb.ServerBill{}
 | 
			
		||||
	var cacheMap = utils.NewCacheMap()
 | 
			
		||||
	for _, bill := range serverBills {
 | 
			
		||||
		// user
 | 
			
		||||
		user, err := models.SharedUserDAO.FindBasicUserWithoutState(tx, int64(bill.UserId))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		var pbUser = &pb.User{Id: int64(bill.UserId)}
 | 
			
		||||
		if user != nil {
 | 
			
		||||
			pbUser = &pb.User{
 | 
			
		||||
				Id:       int64(bill.UserId),
 | 
			
		||||
				Username: user.Username,
 | 
			
		||||
				Fullname: user.Fullname,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// plan
 | 
			
		||||
		var pbPlan *pb.Plan
 | 
			
		||||
		if bill.PlanId > 0 {
 | 
			
		||||
			plan, err := models.SharedPlanDAO.FindEnabledPlan(tx, int64(bill.PlanId))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			if plan != nil {
 | 
			
		||||
				pbPlan = &pb.Plan{
 | 
			
		||||
					Id:        int64(plan.Id),
 | 
			
		||||
					Name:      plan.Name,
 | 
			
		||||
					PriceType: plan.PriceType,
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// user plan
 | 
			
		||||
		var pbUserPlan *pb.UserPlan
 | 
			
		||||
		if bill.UserPlanId > 0 {
 | 
			
		||||
			userPlan, err := models.SharedUserPlanDAO.FindEnabledUserPlan(tx, int64(bill.UserPlanId), cacheMap)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			if userPlan != nil {
 | 
			
		||||
				pbUserPlan = &pb.UserPlan{
 | 
			
		||||
					Id: int64(userPlan.Id),
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// server
 | 
			
		||||
		var pbServer *pb.Server
 | 
			
		||||
		if bill.ServerId > 0 {
 | 
			
		||||
			server, err := models.SharedServerDAO.FindEnabledServerBasic(tx, int64(bill.ServerId))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return nil, err
 | 
			
		||||
			}
 | 
			
		||||
			if server != nil {
 | 
			
		||||
				pbServer = &pb.Server{Id: int64(bill.ServerId), Name: server.Name}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		pbServerBills = append(pbServerBills, &pb.ServerBill{
 | 
			
		||||
			Id:                       int64(bill.Id),
 | 
			
		||||
			UserId:                   int64(bill.UserId),
 | 
			
		||||
			ServerId:                 int64(bill.ServerId),
 | 
			
		||||
			Amount:                   float32(bill.Amount),
 | 
			
		||||
			CreatedAt:                int64(bill.CreatedAt),
 | 
			
		||||
			UserPlanId:               int64(bill.UserPlanId),
 | 
			
		||||
			PlanId:                   int64(bill.PlanId),
 | 
			
		||||
			TotalTrafficBytes:        int64(bill.TotalTrafficBytes),
 | 
			
		||||
			BandwidthPercentileBytes: int64(bill.BandwidthPercentileBytes),
 | 
			
		||||
			BandwidthPercentile:      int32(bill.BandwidthPercentile),
 | 
			
		||||
			User:                     pbUser,
 | 
			
		||||
			Plan:                     pbPlan,
 | 
			
		||||
			UserPlan:                 pbUserPlan,
 | 
			
		||||
			Server:                   pbServer,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &pb.ListServerBillsResponse{ServerBills: pbServerBills}, nil
 | 
			
		||||
}
 | 
			
		||||
@@ -406,7 +406,7 @@ func (this *UserService) ComposeUserDashboard(ctx context.Context, req *pb.Compo
 | 
			
		||||
 | 
			
		||||
	// 本月总流量
 | 
			
		||||
	month := timeutil.Format("Ym")
 | 
			
		||||
	monthlyTrafficBytes, err := models.SharedServerDailyStatDAO.SumUserMonthly(tx, req.UserId, 0, month)
 | 
			
		||||
	monthlyTrafficBytes, err := models.SharedServerDailyStatDAO.SumUserMonthly(tx, req.UserId, month)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ func (this *UserBillService) ListUserBills(ctx context.Context, req *pb.ListUser
 | 
			
		||||
	}
 | 
			
		||||
	result := []*pb.UserBill{}
 | 
			
		||||
	for _, bill := range bills {
 | 
			
		||||
		user, err := models.SharedUserDAO.FindEnabledBasicUser(tx, int64(bill.UserId))
 | 
			
		||||
		user, err := models.SharedUserDAO.FindBasicUserWithoutState(tx, int64(bill.UserId))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
@@ -85,15 +85,17 @@ func (this *UserBillService) ListUserBills(ctx context.Context, req *pb.ListUser
 | 
			
		||||
		result = append(result, &pb.UserBill{
 | 
			
		||||
			Id: int64(bill.Id),
 | 
			
		||||
			User: &pb.User{
 | 
			
		||||
				Id:       int64(bill.UserId),
 | 
			
		||||
				Fullname: user.Fullname,
 | 
			
		||||
				Username: user.Username,
 | 
			
		||||
				Id:        int64(bill.UserId),
 | 
			
		||||
				Fullname:  user.Fullname,
 | 
			
		||||
				Username:  user.Username,
 | 
			
		||||
				IsDeleted: user.State == models.UserStateDisabled,
 | 
			
		||||
			},
 | 
			
		||||
			Type:        bill.Type,
 | 
			
		||||
			TypeName:    models.SharedUserBillDAO.BillTypeName(bill.Type),
 | 
			
		||||
			Description: bill.Description,
 | 
			
		||||
			Amount:      float32(bill.Amount),
 | 
			
		||||
			Month:       bill.Month,
 | 
			
		||||
			CanPay:      bill.CanPay == 1,
 | 
			
		||||
			IsPaid:      bill.IsPaid == 1,
 | 
			
		||||
			PaidAt:      int64(bill.PaidAt),
 | 
			
		||||
			Code:        bill.Code,
 | 
			
		||||
@@ -151,6 +153,7 @@ func (this *UserBillService) FindUserBill(ctx context.Context, req *pb.FindUserB
 | 
			
		||||
			Description: bill.Description,
 | 
			
		||||
			Amount:      float32(bill.Amount),
 | 
			
		||||
			Month:       bill.Month,
 | 
			
		||||
			CanPay:      bill.CanPay == 1,
 | 
			
		||||
			IsPaid:      bill.IsPaid == 1,
 | 
			
		||||
			PaidAt:      int64(bill.PaidAt),
 | 
			
		||||
			Code:        bill.Code,
 | 
			
		||||
@@ -194,6 +197,10 @@ func (this *UserBillService) PayUserBill(ctx context.Context, req *pb.PayUserBil
 | 
			
		||||
			return models.SharedUserBillDAO.UpdateUserBillIsPaid(tx, req.UserBillId, true)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if bill.CanPay == 0 {
 | 
			
		||||
			return errors.New("can not pay now")
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 余额是否足够
 | 
			
		||||
		account, err := accounts.SharedUserAccountDAO.FindUserAccountWithUserId(tx, userId)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -65,6 +65,9 @@ var upgradeFuncs = []*upgradeVersion{
 | 
			
		||||
	{
 | 
			
		||||
		"0.4.0", upgradeV0_4_0,
 | 
			
		||||
	},
 | 
			
		||||
	{
 | 
			
		||||
		"0.4.1", upgradeV0_4_1,
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpgradeSQLData 升级SQL数据
 | 
			
		||||
@@ -569,3 +572,14 @@ func upgradeV0_4_0(db *dbs.DB) error {
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// v0.4.1
 | 
			
		||||
func upgradeV0_4_1(db *dbs.DB) error {
 | 
			
		||||
	// 升级 servers.lastUserPlanId
 | 
			
		||||
	_, err := db.Exec("UPDATE edgeServers SET lastUserPlanId=userPlanId WHERE userPlanId>0")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -85,7 +85,6 @@ func TestUpgradeSQLData_v0_3_7(t *testing.T) {
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
func TestUpgradeSQLData_v0_4_0(t *testing.T) {
 | 
			
		||||
	db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
 | 
			
		||||
		Driver: "mysql",
 | 
			
		||||
@@ -101,3 +100,19 @@ func TestUpgradeSQLData_v0_4_0(t *testing.T) {
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestUpgradeSQLData_v0_4_1(t *testing.T) {
 | 
			
		||||
	db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
 | 
			
		||||
		Driver: "mysql",
 | 
			
		||||
		Dsn:    "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
 | 
			
		||||
		Prefix: "edge",
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = upgradeV0_4_1(db)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user