Initial commit (code only without large binaries)

This commit is contained in:
robin
2026-02-15 18:58:44 +08:00
commit 35df75498f
9442 changed files with 1495866 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
OrderMethodStateEnabled = 1 // 已启用
OrderMethodStateDisabled = 0 // 已禁用
)
type OrderMethodDAO dbs.DAO
func NewOrderMethodDAO() *OrderMethodDAO {
return dbs.NewDAO(&OrderMethodDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeOrderMethods",
Model: new(OrderMethod),
PkName: "id",
},
}).(*OrderMethodDAO)
}
var SharedOrderMethodDAO *OrderMethodDAO
func init() {
dbs.OnReady(func() {
SharedOrderMethodDAO = NewOrderMethodDAO()
})
}

View File

@@ -0,0 +1,180 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package accounts
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/dbs"
)
// EnableOrderMethod 启用条目
func (this *OrderMethodDAO) EnableOrderMethod(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", OrderMethodStateEnabled).
Update()
return err
}
// DisableOrderMethod 禁用条目
func (this *OrderMethodDAO) DisableOrderMethod(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", OrderMethodStateDisabled).
Update()
return err
}
// FindEnabledOrderMethod 查找支付方式
func (this *OrderMethodDAO) FindEnabledOrderMethod(tx *dbs.Tx, id int64) (*OrderMethod, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", OrderMethodStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*OrderMethod), err
}
// FindEnabledBasicOrderMethod 查找支付方式基本信息
func (this *OrderMethodDAO) FindEnabledBasicOrderMethod(tx *dbs.Tx, id int64) (*OrderMethod, error) {
result, err := this.Query(tx).
Pk(id).
Result("id", "code", "url", "isOn", "secret", "parentCode", "clientType").
Attr("state", OrderMethodStateEnabled).
Find()
if err != nil || result == nil {
return nil, err
}
return result.(*OrderMethod), err
}
// FindEnabledOrderMethodWithCode 根据代号查找支付方式
func (this *OrderMethodDAO) FindEnabledOrderMethodWithCode(tx *dbs.Tx, code string) (*OrderMethod, error) {
result, err := this.Query(tx).
Attr("code", code).
Attr("state", OrderMethodStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*OrderMethod), err
}
// ExistOrderMethodWithCode 根据代号检查支付方式是否存在
func (this *OrderMethodDAO) ExistOrderMethodWithCode(tx *dbs.Tx, code string, excludingMethodId int64) (bool, error) {
var query = this.Query(tx)
if excludingMethodId > 0 {
query.Neq("id", excludingMethodId)
}
return query.
Attr("code", code).
Attr("state", OrderMethodStateEnabled).
Exist()
}
// FindOrderMethodName 根据主键查找名称
func (this *OrderMethodDAO) FindOrderMethodName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateMethod 创建支付方式
func (this *OrderMethodDAO) CreateMethod(tx *dbs.Tx, name string, code string, url string, description string, parentCode string, params any, clientType userconfigs.PayClientType, qrcodeTitle string) (int64, error) {
var op = NewOrderMethodOperator()
op.Name = name
op.Code = code
op.Url = url
op.ParentCode = parentCode
if params != nil {
paramsJSON, err := json.Marshal(params)
if err != nil {
return 0, err
}
op.Params = paramsJSON
}
op.Description = description
op.Secret = utils.Sha1RandomString()
op.ClientType = clientType
op.QrcodeTitle = qrcodeTitle
op.IsOn = true
op.State = OrderMethodStateEnabled
return this.SaveInt64(tx, op)
}
// UpdateMethod 修改支付方式
// 切记不允许修改parentCode
func (this *OrderMethodDAO) UpdateMethod(tx *dbs.Tx, methodId int64, name string, code string, url string, description string, params any, clientType userconfigs.PayClientType, qrcodeTitle string, isOn bool) error {
if methodId <= 0 {
return errors.New("invalid methodId")
}
var op = NewOrderMethodOperator()
op.Id = methodId
op.Name = name
op.Code = code
op.Url = url
op.Description = description
if params != nil {
paramsJSON, err := json.Marshal(params)
if err != nil {
return err
}
op.Params = paramsJSON
}
op.ClientType = clientType
op.QrcodeTitle = qrcodeTitle
op.IsOn = isOn
return this.Save(tx, op)
}
// UpdateMethodOrders 修改排序
func (this *OrderMethodDAO) UpdateMethodOrders(tx *dbs.Tx, methodIds []int64) error {
var maxOrder = len(methodIds)
for index, methodId := range methodIds {
err := this.Query(tx).
Pk(methodId).
Set("order", maxOrder-index).
UpdateQuickly()
if err != nil {
return err
}
}
return nil
}
// FindAllEnabledMethodOrders 查找所有支付方式
func (this *OrderMethodDAO) FindAllEnabledMethodOrders(tx *dbs.Tx) (result []*OrderMethod, err error) {
_, err = this.Query(tx).
State(OrderMethodStateEnabled).
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}
// FindAllEnabledAndOnMethodOrders 查找所有已启用的支付方式
func (this *OrderMethodDAO) FindAllEnabledAndOnMethodOrders(tx *dbs.Tx) (result []*OrderMethod, err error) {
_, err = this.Query(tx).
State(OrderMethodStateEnabled).
Attr("isOn", true).
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}

View File

@@ -0,0 +1,6 @@
package accounts_test
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,40 @@
package accounts
import "github.com/iwind/TeaGo/dbs"
// OrderMethod 订单支付方式
type OrderMethod struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 名称
IsOn bool `field:"isOn"` // 是否启用
Description string `field:"description"` // 描述
ParentCode string `field:"parentCode"` // 内置的父级代号
Code string `field:"code"` // 代号
Url string `field:"url"` // URL
Secret string `field:"secret"` // 密钥
Params dbs.JSON `field:"params"` // 参数
ClientType string `field:"clientType"` // 客户端类型
QrcodeTitle string `field:"qrcodeTitle"` // 二维码标题
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
}
type OrderMethodOperator struct {
Id any // ID
Name any // 名称
IsOn any // 是否启用
Description any // 描述
ParentCode any // 内置的父级代号
Code any // 代号
Url any // URL
Secret any // 密钥
Params any // 参数
ClientType any // 客户端类型
QrcodeTitle any // 二维码标题
Order any // 排序
State any // 状态
}
func NewOrderMethodOperator() *OrderMethodOperator {
return &OrderMethodOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -0,0 +1,82 @@
//go:build plus
package accounts
import (
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type UserAccountDailyStatDAO dbs.DAO
func NewUserAccountDailyStatDAO() *UserAccountDailyStatDAO {
return dbs.NewDAO(&UserAccountDailyStatDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserAccountDailyStats",
Model: new(UserAccountDailyStat),
PkName: "id",
},
}).(*UserAccountDailyStatDAO)
}
var SharedUserAccountDailyStatDAO *UserAccountDailyStatDAO
func init() {
dbs.OnReady(func() {
SharedUserAccountDailyStatDAO = NewUserAccountDailyStatDAO()
})
}
// UpdateDailyStat 更新当天统计数据
func (this *UserAccountDailyStatDAO) UpdateDailyStat(tx *dbs.Tx) error {
var day = timeutil.Format("Ymd")
var month = timeutil.Format("Ym")
income, err := SharedUserAccountLogDAO.SumDailyEventTypes(tx, day, userconfigs.AccountIncomeEventTypes)
if err != nil {
return err
}
expense, err := SharedUserAccountLogDAO.SumDailyEventTypes(tx, day, userconfigs.AccountExpenseEventTypes)
if err != nil {
return err
}
if expense < 0 {
expense = -expense
}
return this.Query(tx).
InsertOrUpdateQuickly(maps.Map{
"day": day,
"month": month,
"income": income,
"expense": expense,
}, maps.Map{
"income": income,
"expense": expense,
})
}
// FindDailyStats 查看按天统计
func (this *UserAccountDailyStatDAO) FindDailyStats(tx *dbs.Tx, dayFrom string, dayTo string) (result []*UserAccountDailyStat, err error) {
_, err = this.Query(tx).
Between("day", dayFrom, dayTo).
Slice(&result).
FindAll()
return
}
// FindMonthlyStats 查看某月统计
func (this *UserAccountDailyStatDAO) FindMonthlyStats(tx *dbs.Tx, dayFrom string, dayTo string) (result []*UserAccountDailyStat, err error) {
_, err = this.Query(tx).
Result("SUM(income) AS income", "SUM(expense) AS expense", "month").
Between("day", dayFrom, dayTo).
Group("month").
Slice(&result).
FindAll()
return
}

View File

@@ -0,0 +1,6 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,22 @@
package accounts
// UserAccountDailyStat 账户每日统计
type UserAccountDailyStat struct {
Id uint32 `field:"id"` // ID
Day string `field:"day"` // YYYYMMDD
Month string `field:"month"` // YYYYMM
Income float64 `field:"income"` // 收入
Expense float64 `field:"expense"` // 支出
}
type UserAccountDailyStatOperator struct {
Id interface{} // ID
Day interface{} // YYYYMMDD
Month interface{} // YYYYMM
Income interface{} // 收入
Expense interface{} // 支出
}
func NewUserAccountDailyStatOperator() *UserAccountDailyStatOperator {
return &UserAccountDailyStatOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -0,0 +1,264 @@
//go:build plus
package accounts
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"time"
)
func init() {
dbs.OnReadyDone(func() {
goman.New(func() {
// 自动支付账单任务
var ticker = time.NewTicker(6 * time.Hour)
for range ticker.C {
if SharedUserAccountDAO.Instance != nil {
err := SharedUserAccountDAO.Instance.RunTx(func(tx *dbs.Tx) error {
return SharedUserAccountDAO.PayBillsAutomatically(tx)
})
if err != nil {
remotelogs.Error("USER_ACCOUNT_DAO", "pay bills task failed: "+err.Error())
}
}
}
})
})
}
type UserAccountDAO dbs.DAO
func NewUserAccountDAO() *UserAccountDAO {
return dbs.NewDAO(&UserAccountDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserAccounts",
Model: new(UserAccount),
PkName: "id",
},
}).(*UserAccountDAO)
}
var SharedUserAccountDAO *UserAccountDAO
func init() {
dbs.OnReady(func() {
SharedUserAccountDAO = NewUserAccountDAO()
})
}
// FindUserAccountWithUserId 根据用户ID查找用户账户
func (this *UserAccountDAO) FindUserAccountWithUserId(tx *dbs.Tx, userId int64) (*UserAccount, error) {
if userId <= 0 {
return nil, errors.New("invalid userId '" + types.String(userId) + "'")
}
// 用户是否存在
user, err := models.SharedUserDAO.FindEnabledUser(tx, userId, nil)
if err != nil {
return nil, err
}
if user == nil {
return nil, nil
}
account, err := this.Query(tx).
Attr("userId", userId).
Find()
if err != nil {
return nil, err
}
if account != nil {
return account.(*UserAccount), nil
}
var op = NewUserAccountOperator()
op.UserId = userId
_, err = this.SaveInt64(tx, op)
if err != nil {
return nil, err
}
return this.FindUserAccountWithUserId(tx, userId)
}
// FindUserAccountWithAccountId 根据ID查找用户账户
func (this *UserAccountDAO) FindUserAccountWithAccountId(tx *dbs.Tx, accountId int64) (*UserAccount, error) {
one, err := this.Query(tx).
Pk(accountId).
Find()
if one != nil {
return one.(*UserAccount), nil
}
return nil, err
}
// UpdateUserAccount 操作用户账户
func (this *UserAccountDAO) UpdateUserAccount(tx *dbs.Tx, accountId int64, delta float64, eventType userconfigs.AccountEventType, description string, params maps.Map) error {
// 截取两位两位小数
var realDelta = delta
if realDelta < 0 {
realDelta = numberutils.FloorFloat64(realDelta, 2)
}
account, err := this.FindUserAccountWithAccountId(tx, accountId)
if err != nil {
return err
}
if account == nil {
return errors.New("invalid account id '" + types.String(accountId) + "'")
}
var userId = int64(account.UserId)
if delta < 0 && account.Total < -delta {
return errors.New("not enough account quota to decrease")
}
// 操作账户
err = this.Query(tx).
Pk(account.Id).
Set("total", dbs.SQL("total+:delta")).
Param("delta", realDelta).
UpdateQuickly()
if err != nil {
return err
}
// 生成日志
err = SharedUserAccountLogDAO.CreateAccountLog(tx, userId, accountId, realDelta, 0, eventType, description, params)
if err != nil {
return err
}
return nil
}
// UpdateUserAccountFrozen 操作用户账户冻结余额
func (this *UserAccountDAO) UpdateUserAccountFrozen(tx *dbs.Tx, userId int64, delta float64, eventType userconfigs.AccountEventType, description string, params maps.Map) error {
account, err := this.FindUserAccountWithUserId(tx, userId)
if err != nil {
return err
}
if account == nil {
return errors.New("invalid user account")
}
var deltaFloat64 = delta
if deltaFloat64 < 0 && account.TotalFrozen < -deltaFloat64 {
return errors.New("not enough account frozen quota to decrease")
}
// 操作账户
err = this.Query(tx).
Pk(account.Id).
Set("totalFrozen", dbs.SQL("total+:delta")).
Param("delta", delta).
UpdateQuickly()
if err != nil {
return err
}
// 生成日志
err = SharedUserAccountLogDAO.CreateAccountLog(tx, userId, int64(account.Id), 0, delta, eventType, description, params)
if err != nil {
return err
}
return nil
}
// CountAllAccounts 计算所有账户数量
func (this *UserAccountDAO) CountAllAccounts(tx *dbs.Tx, keyword string) (int64, error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword))")
query.Param("keyword", keyword)
} else {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1)")
}
return query.Count()
}
// ListAccounts 列出单页账户
func (this *UserAccountDAO) ListAccounts(tx *dbs.Tx, keyword string, offset int64, size int64) (result []*UserAccount, err error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword))")
query.Param("keyword", keyword)
} else {
query.Where("userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1)")
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// PayBills 尝试自动支付账单
func (this *UserAccountDAO) PayBillsAutomatically(tx *dbs.Tx) error {
bills, err := models.SharedUserBillDAO.FindUnpaidBills(tx, 10000)
if err != nil {
return err
}
// 先支付久远的
lists.Reverse(bills)
for _, bill := range bills {
if bill.Amount <= 0 {
err = models.SharedUserBillDAO.UpdateUserBillIsPaid(tx, int64(bill.Id), true)
if err != nil {
return err
}
continue
}
account, err := SharedUserAccountDAO.FindUserAccountWithUserId(tx, int64(bill.UserId))
if err != nil {
return err
}
if account == nil || account.Total < bill.Amount {
continue
}
// 扣款
err = SharedUserAccountDAO.UpdateUserAccount(tx, int64(account.Id), -bill.Amount, userconfigs.AccountEventTypePayBill, "支付账单"+bill.Code, maps.Map{"billId": bill.Id})
if err != nil {
return err
}
// 改为已支付
err = models.SharedUserBillDAO.UpdateUserBillIsPaid(tx, int64(bill.Id), true)
if err != nil {
return err
}
}
return nil
}
// CheckUserAccount 检查用户账户
func (this *UserAccountDAO) CheckUserAccount(tx *dbs.Tx, userId int64, accountId int64) error {
exists, err := this.Query(tx).
Pk(accountId).
Attr("userId", userId).
Exist()
if err != nil {
return err
}
if !exists {
return models.ErrNotFound
}
return nil
}

View File

@@ -0,0 +1,47 @@
//go:build plus
package accounts_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models/accounts"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"testing"
)
func TestUserAccountDAO_PayBills(t *testing.T) {
dbs.NotifyReady()
err := accounts.NewUserAccountDAO().PayBillsAutomatically(nil)
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}
func TestUserAccountDAO_UpdateUserAccount(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
var dao = accounts.NewUserAccountDAO()
err := dao.UpdateUserAccount(tx, 3, -17.88, userconfigs.AccountEventTypePayBill, "test", nil)
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}
// 221745.12
func TestUserAccountDAO_UpdateUserAccount2(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
var dao = accounts.NewUserAccountDAO()
err := dao.UpdateUserAccount(tx, 3, -221745.12, userconfigs.AccountEventTypePayBill, "test", nil)
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}

View File

@@ -0,0 +1,131 @@
//go:build plus
package accounts
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type UserAccountLogDAO dbs.DAO
func NewUserAccountLogDAO() *UserAccountLogDAO {
return dbs.NewDAO(&UserAccountLogDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserAccountLogs",
Model: new(UserAccountLog),
PkName: "id",
},
}).(*UserAccountLogDAO)
}
var SharedUserAccountLogDAO *UserAccountLogDAO
func init() {
dbs.OnReady(func() {
SharedUserAccountLogDAO = NewUserAccountLogDAO()
})
}
// CreateAccountLog 生成用户账户日志
func (this *UserAccountLogDAO) CreateAccountLog(tx *dbs.Tx, userId int64, accountId int64, delta float64, deltaFrozen float64, eventType userconfigs.AccountEventType, description string, params maps.Map) error {
var op = NewUserAccountLogOperator()
op.UserId = userId
op.AccountId = accountId
op.Delta = delta
op.DeltaFrozen = deltaFrozen
account, err := SharedUserAccountDAO.FindUserAccountWithAccountId(tx, accountId)
if err != nil {
return err
}
if account == nil {
return errors.New("invalid account id '" + types.String(accountId) + "'")
}
op.Total = account.Total
op.TotalFrozen = account.TotalFrozen
op.EventType = eventType
op.Description = description
if params == nil {
params = maps.Map{}
}
op.Params = params.AsJSON()
op.Day = timeutil.Format("Ymd")
err = this.Save(tx, op)
if err != nil {
return err
}
return SharedUserAccountDailyStatDAO.UpdateDailyStat(tx)
}
// CountAccountLogs 计算日志数量
func (this *UserAccountLogDAO) CountAccountLogs(tx *dbs.Tx, userId int64, accountId int64, keyword string, eventType string) (int64, error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
}
if accountId > 0 {
query.Attr("accountId", accountId)
}
if len(keyword) > 0 {
query.Where("(userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword)) OR description LIKE :keyword)")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
if len(eventType) > 0 {
query.Attr("eventType", eventType)
}
return query.Count()
}
// ListAccountLogs 列出单页日志
func (this *UserAccountLogDAO) ListAccountLogs(tx *dbs.Tx, userId int64, accountId int64, keyword string, eventType string, offset int64, size int64) (result []*UserAccountLog, err error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
}
if accountId > 0 {
query.Attr("accountId", accountId)
}
if len(keyword) > 0 {
query.Where("(userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword)) OR description LIKE :keyword)")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
if len(eventType) > 0 {
query.Attr("eventType", eventType)
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// SumDailyEventTypes 统计某天数据总和
func (this *UserAccountLogDAO) SumDailyEventTypes(tx *dbs.Tx, day string, eventTypes []userconfigs.AccountEventType) (float32, error) {
if len(eventTypes) == 0 {
return 0, nil
}
result, err := this.Query(tx).
Attr("day", day).
Attr("eventType", eventTypes).
Sum("delta", 0)
if err != nil {
return 0, err
}
return types.Float32(result), nil
}

View File

@@ -0,0 +1,6 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,38 @@
package accounts
import "github.com/iwind/TeaGo/dbs"
// UserAccountLog 用户账户日志
type UserAccountLog struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
AccountId uint64 `field:"accountId"` // 账户ID
Delta float64 `field:"delta"` // 操作余额的数量(可为负)
DeltaFrozen float64 `field:"deltaFrozen"` // 操作冻结的数量(可为负)
Total float64 `field:"total"` // 操作后余额
TotalFrozen float64 `field:"totalFrozen"` // 操作后冻结余额
EventType string `field:"eventType"` // 类型
Description string `field:"description"` // 描述文字
Day string `field:"day"` // YYYYMMDD
CreatedAt uint64 `field:"createdAt"` // 时间
Params dbs.JSON `field:"params"` // 参数
}
type UserAccountLogOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
AccountId interface{} // 账户ID
Delta interface{} // 操作余额的数量(可为负)
DeltaFrozen interface{} // 操作冻结的数量(可为负)
Total interface{} // 操作后余额
TotalFrozen interface{} // 操作后冻结余额
EventType interface{} // 类型
Description interface{} // 描述文字
Day interface{} // YYYYMMDD
CreatedAt interface{} // 时间
Params interface{} // 参数
}
func NewUserAccountLogOperator() *UserAccountLogOperator {
return &UserAccountLogOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -0,0 +1,20 @@
package accounts
// UserAccount 用户账号
type UserAccount struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
Total float64 `field:"total"` // 可用总余额
TotalFrozen float64 `field:"totalFrozen"` // 冻结余额
}
type UserAccountOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
Total interface{} // 可用总余额
TotalFrozen interface{} // 冻结余额
}
func NewUserAccountOperator() *UserAccountOperator {
return &UserAccountOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -0,0 +1,33 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
UserOrderStateEnabled = 1 // 已启用
UserOrderStateDisabled = 0 // 已禁用
)
type UserOrderDAO dbs.DAO
func NewUserOrderDAO() *UserOrderDAO {
return dbs.NewDAO(&UserOrderDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserOrders",
Model: new(UserOrder),
PkName: "id",
},
}).(*UserOrderDAO)
}
var SharedUserOrderDAO *UserOrderDAO
func init() {
dbs.OnReady(func() {
SharedUserOrderDAO = NewUserOrderDAO()
})
}

View File

@@ -0,0 +1,292 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package accounts
import (
"encoding/json"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/nameservers"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
// CreateOrder 创建订单
func (this *UserOrderDAO) CreateOrder(tx *dbs.Tx, adminId int64, userId int64, orderType userconfigs.OrderType, methodId int64, amount float64, paramsJSON []byte) (orderId int64, code string, err error) {
// 查询过期时间
configJSON, err := models.SharedSysSettingDAO.ReadSetting(tx, systemconfigs.SettingCodeUserOrderConfig)
if err != nil {
return 0, "", err
}
var config = userconfigs.DefaultUserOrderConfig()
if len(configJSON) == 0 {
err = json.Unmarshal(configJSON, config)
if err != nil {
return 0, "", fmt.Errorf("decode order config failed: %w", err)
}
}
// 保存订单
var op = NewUserOrderOperator()
op.UserId = userId
op.Type = orderType
op.MethodId = methodId
op.Amount = amount
if len(paramsJSON) > 0 {
op.Params = paramsJSON
}
op.Status = userconfigs.OrderStatusNone
if config.OrderLife != nil && config.OrderLife.Count > 0 {
op.ExpiredAt = time.Now().Unix() + int64(config.OrderLife.Duration().Seconds())
} else {
op.ExpiredAt = time.Now().Unix() + 3600 /** 默认一个小时 **/
}
op.State = UserOrderStateEnabled
orderId, err = this.SaveInt64(tx, op)
if err != nil {
return 0, "", err
}
var orderCode = timeutil.Format("Ymd") + fmt.Sprintf("%08d", orderId) // 16 bytes
err = this.Query(tx).
Pk(orderId).
Set("code", orderCode).
UpdateQuickly()
if err != nil {
return 0, "", err
}
// 生成订单日志
err = SharedUserOrderLogDAO.CreateOrderLog(tx, adminId, userId, orderId, userconfigs.OrderStatusNone)
if err != nil {
return 0, "", err
}
return orderId, orderCode, nil
}
// FindEnabledOrder 查找某个订单
func (this *UserOrderDAO) FindEnabledOrder(tx *dbs.Tx, orderId int64) (*UserOrder, error) {
one, err := this.Query(tx).
Pk(orderId).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*UserOrder), nil
}
// CancelOrder 取消订单
func (this *UserOrderDAO) CancelOrder(tx *dbs.Tx, adminId int64, userId int64, orderId int64) error {
status, err := this.Query(tx).
Pk(orderId).
Result("status").
FindStringCol("")
if err != nil {
return err
}
if status != userconfigs.OrderStatusNone {
return errors.New("can not cancel the order with status '" + status + "'")
}
err = this.Query(tx).
Pk(orderId).
Set("status", userconfigs.OrderStatusCancelled).
Set("cancelledAt", time.Now().Unix()).
UpdateQuickly()
if err != nil {
return err
}
return SharedUserOrderLogDAO.CreateOrderLog(tx, adminId, userId, orderId, userconfigs.OrderStatusCancelled)
}
// FinishOrder 完成订单
// 不需要检查过期时间,因为用户可能在支付页面停留非常久后才完成支付
func (this *UserOrderDAO) FinishOrder(tx *dbs.Tx, adminId int64, userId int64, orderId int64) error {
// 检查订单状态
order, err := SharedUserOrderDAO.FindEnabledOrder(tx, orderId)
if err != nil {
return err
}
if order == nil {
return errors.New("can not find order")
}
if order.Status != userconfigs.OrderStatusNone {
return errors.New("you can not finish the order, cause order status is '" + order.Status + "'")
}
// 用户账户
account, err := SharedUserAccountDAO.FindUserAccountWithUserId(tx, int64(order.UserId))
if err != nil {
return err
}
if account == nil {
return errors.New("user account not found")
}
switch order.Type {
case userconfigs.OrderTypeCharge: // 充值
err = SharedUserAccountDAO.UpdateUserAccount(tx, int64(account.Id), order.Amount, userconfigs.AccountEventTypeCharge, "充值,订单号:"+order.Code, maps.Map{
"orderCode": order.Code,
})
if err != nil {
return err
}
case userconfigs.OrderTypeBuyNSPlan: // 购买DNS套餐
var params = &userconfigs.OrderTypeBuyNSPlanParams{}
err = json.Unmarshal(order.Params, params)
if err != nil {
return err
}
err = nameservers.SharedNSUserPlanDAO.RenewUserPlan(tx, int64(order.UserId), params.PlanId, params.DayFrom, params.DayTo, params.Period)
if err != nil {
return err
}
case userconfigs.OrderTypeBuyTrafficPackage: // 购买流量包
var params = &userconfigs.OrderTypeBuyTrafficPackageParams{}
err = json.Unmarshal(order.Params, params)
if err != nil {
return err
}
if params.Count > 0 {
for i := 0; i < int(params.Count); i++ {
_, err = models.SharedUserTrafficPackageDAO.CreateUserPackage(tx, int64(order.UserId), 0, params.PackageId, params.RegionId, params.PeriodId)
if err != nil {
return err
}
}
}
case userconfigs.OrderTypeBuyAntiDDoSInstance: // 购买高防实例
var params = &userconfigs.OrderTypeBuyAntiDDoSInstanceParams{}
err = json.Unmarshal(order.Params, params)
if err != nil {
return err
}
err = models.SharedUserADInstanceDAO.CreateUserADInstances(tx, int64(order.UserId), params.PackageId, params.PeriodId, params.Count)
if err != nil {
return err
}
case userconfigs.OrderTypeRenewAntiDDoSInstance: // 续费高防实例
var params = &userconfigs.OrderTypeRenewAntiDDoSInstanceParams{}
err = json.Unmarshal(order.Params, params)
if err != nil {
return err
}
userInstance, err := models.SharedUserADInstanceDAO.FindEnabledUserADInstance(tx, params.UserInstanceId)
if err != nil {
return err
}
if userInstance == nil {
return errors.New("could not find user instance '" + types.String(params.UserInstanceId) + "'")
}
period, err := models.SharedADPackagePeriodDAO.FindEnabledADPackagePeriod(tx, params.PeriodId)
if err != nil {
return err
}
if period == nil {
return errors.New("could not find period '" + types.String(params.PeriodId) + "'")
}
_, err = models.SharedUserADInstanceDAO.RenewUserInstance(tx, userInstance, period)
if err != nil {
return err
}
}
err = this.Query(tx).
Pk(orderId).
Set("status", userconfigs.OrderStatusFinished).
Set("finishedAt", time.Now().Unix()).
UpdateQuickly()
if err != nil {
return err
}
return SharedUserOrderLogDAO.CreateOrderLog(tx, adminId, userId, orderId, userconfigs.OrderStatusFinished)
}
// CountEnabledUserOrders 计算订单数量
func (this *UserOrderDAO) CountEnabledUserOrders(tx *dbs.Tx, userId int64, status userconfigs.OrderStatus, keyword string) (int64, error) {
var query = this.Query(tx).
State(UserOrderStateEnabled)
if userId > 0 {
query.Attr("userId", userId)
}
if len(status) > 0 {
query.Attr("status", status)
}
if len(keyword) > 0 {
query.Where("(code LIKE :keyword)")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
return query.Count()
}
// ListEnabledUserOrders 列出单页订单
func (this *UserOrderDAO) ListEnabledUserOrders(tx *dbs.Tx, userId int64, status userconfigs.OrderStatus, keyword string, offset int64, size int64) (result []*UserOrder, err error) {
var query = this.Query(tx).
State(UserOrderStateEnabled)
if userId > 0 {
query.Attr("userId", userId)
}
if len(status) > 0 {
query.Attr("status", status)
}
if len(keyword) > 0 {
query.Where("(code LIKE :keyword)")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// FindUserOrderIdWithCode 根据订单号查找订单ID
func (this *UserOrderDAO) FindUserOrderIdWithCode(tx *dbs.Tx, code string) (int64, error) {
if len(code) == 0 {
return 0, nil
}
return this.Query(tx).
ResultPk().
State(UserOrderStateEnabled).
Attr("code", code).
FindInt64Col(0)
}
// FindUserOrderWithCode 根据订单号查找订单
func (this *UserOrderDAO) FindUserOrderWithCode(tx *dbs.Tx, code string) (*UserOrder, error) {
if len(code) == 0 {
return nil, nil
}
one, err := this.Query(tx).
State(UserOrderStateEnabled).
Attr("code", code).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*UserOrder), nil
}

View File

@@ -0,0 +1,6 @@
package accounts_test
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,28 @@
package accounts
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
type UserOrderLogDAO dbs.DAO
func NewUserOrderLogDAO() *UserOrderLogDAO {
return dbs.NewDAO(&UserOrderLogDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserOrderLogs",
Model: new(UserOrderLog),
PkName: "id",
},
}).(*UserOrderLogDAO)
}
var SharedUserOrderLogDAO *UserOrderLogDAO
func init() {
dbs.OnReady(func() {
SharedUserOrderLogDAO = NewUserOrderLogDAO()
})
}

View File

@@ -0,0 +1,21 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package accounts
import (
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/dbs"
"time"
)
// CreateOrderLog 创建订单日志
func (this *UserOrderLogDAO) CreateOrderLog(tx *dbs.Tx, adminId int64, userId int64, orderId int64, status userconfigs.OrderStatus) error {
var op = NewUserOrderLogOperator()
op.AdminId = adminId
op.UserId = userId
op.OrderId = orderId
op.Status = status
op.CreatedAt = time.Now().Unix()
return this.Save(tx, op)
}

View File

@@ -0,0 +1,6 @@
package accounts_test
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,28 @@
package accounts
import "github.com/iwind/TeaGo/dbs"
// UserOrderLog 订单日志
type UserOrderLog struct {
Id uint64 `field:"id"` // ID
AdminId uint64 `field:"adminId"` // 管理员ID
UserId uint64 `field:"userId"` // 用户ID
OrderId uint64 `field:"orderId"` // 订单ID
Status string `field:"status"` // 状态
Snapshot dbs.JSON `field:"snapshot"` // 状态快照
CreatedAt uint64 `field:"createdAt"` // 创建时间
}
type UserOrderLogOperator struct {
Id interface{} // ID
AdminId interface{} // 管理员ID
UserId interface{} // 用户ID
OrderId interface{} // 订单ID
Status interface{} // 状态
Snapshot interface{} // 状态快照
CreatedAt interface{} // 创建时间
}
func NewUserOrderLogOperator() *UserOrderLogOperator {
return &UserOrderLogOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -0,0 +1,40 @@
package accounts
import "github.com/iwind/TeaGo/dbs"
// UserOrder 用户订单
type UserOrder struct {
Id uint64 `field:"id"` // 用户订单
UserId uint64 `field:"userId"` // 用户ID
Code string `field:"code"` // 订单号
Type string `field:"type"` // 订单类型
MethodId uint32 `field:"methodId"` // 支付方式
Status string `field:"status"` // 订单状态
Amount float64 `field:"amount"` // 金额
Params dbs.JSON `field:"params"` // 附加参数
ExpiredAt uint64 `field:"expiredAt"` // 过期时间
CreatedAt uint64 `field:"createdAt"` // 创建时间
CancelledAt uint64 `field:"cancelledAt"` // 取消时间
FinishedAt uint64 `field:"finishedAt"` // 结束时间
State uint8 `field:"state"` // 状态
}
type UserOrderOperator struct {
Id interface{} // 用户订单
UserId interface{} // 用户ID
Code interface{} // 订单号
Type interface{} // 订单类型
MethodId interface{} // 支付方式
Status interface{} // 订单状态
Amount interface{} // 金额
Params interface{} // 附加参数
ExpiredAt interface{} // 过期时间
CreatedAt interface{} // 创建时间
CancelledAt interface{} // 取消时间
FinishedAt interface{} // 结束时间
State interface{} // 状态
}
func NewUserOrderOperator() *UserOrderOperator {
return &UserOrderOperator{}
}

View File

@@ -0,0 +1 @@
package accounts

View File

@@ -0,0 +1,14 @@
//go:build plus
package accounts
import (
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"time"
)
// IsExpired 检查当前订单是否已经过期
func (this *UserOrder) IsExpired() bool {
return this.Status == userconfigs.OrderStatusNone &&
time.Now().Unix() > int64(this.ExpiredAt)
}