mirror of
https://github.com/TeaOSLab/EdgeAPI.git
synced 2025-11-06 18:10:25 +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)
|
||||
}
|
||||
@@ -74,3 +74,22 @@ func TestServerDailyStatDAO_SumMinutelyRequests(t *testing.T) {
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
priceItems, err := SharedNodePriceItemDAO.FindAllEnabledRegionPrices(tx, NodePriceTypeTraffic)
|
||||
var priceItems []*NodePriceItem
|
||||
if len(regions) > 0 {
|
||||
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)]
|
||||
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 {
|
||||
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 plan.PriceType != serverconfigs.PlanPriceTypeTraffic {
|
||||
if price <= 0 {
|
||||
continue
|
||||
}
|
||||
if len(plan.TrafficPrice) == 0 {
|
||||
continue
|
||||
var regionFee = float64(trafficBytes) / 1000 / 1000 / 1000 * 8 * price
|
||||
fee += regionFee
|
||||
}
|
||||
var priceConfig = &serverconfigs.PlanTrafficPrice{}
|
||||
err = json.Unmarshal([]byte(plan.TrafficPrice), priceConfig)
|
||||
|
||||
// 总流量
|
||||
totalTrafficBytes, err := SharedServerDailyStatDAO.SumMonthlyBytes(tx, serverId, month)
|
||||
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)
|
||||
|
||||
// 百分位
|
||||
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)
|
||||
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 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -88,12 +88,14 @@ func (this *UserBillService) ListUserBills(ctx context.Context, req *pb.ListUser
|
||||
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