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,55 @@
package db
import (
"database/sql"
"database/sql/driver"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"testing"
"time"
)
func TestDB_Env(t *testing.T) {
Tea.Env = "prod"
t.Log(dbs.Default())
}
func TestDB_Instance(t *testing.T) {
for i := 0; i < 10; i++ {
db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s")
if err != nil {
t.Fatal(i, "open:", err)
}
db.SetConnMaxIdleTime(time.Minute * 3)
db.SetConnMaxLifetime(time.Minute * 3)
db.SetMaxIdleConns(0)
db.SetMaxOpenConns(1)
go func(db *sql.DB, i int) {
for j := 0; j < 100; j++ {
err := db.Ping()
if err != nil {
if err == driver.ErrBadConn {
return
}
t.Error(i, "exec:", err)
}
time.Sleep(1 * time.Second)
}
t.Log(i, "ok", db)
}(db, i)
}
time.Sleep(100 * time.Second)
}
func TestDB_Reuse(t *testing.T) {
var dao = models.NewVersionDAO()
for i := 0; i < 20_000; i++ {
_, _, err := dao.Query(nil).Attr("version", i).Reuse(true).FindOne()
if err != nil {
t.Fatal(err)
}
}
}

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)
}

View File

@@ -0,0 +1,54 @@
package acme
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
type ACMEAuthenticationDAO dbs.DAO
func NewACMEAuthenticationDAO() *ACMEAuthenticationDAO {
return dbs.NewDAO(&ACMEAuthenticationDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeACMEAuthentications",
Model: new(ACMEAuthentication),
PkName: "id",
},
}).(*ACMEAuthenticationDAO)
}
var SharedACMEAuthenticationDAO *ACMEAuthenticationDAO
func init() {
dbs.OnReady(func() {
SharedACMEAuthenticationDAO = NewACMEAuthenticationDAO()
})
}
// 创建认证信息
func (this *ACMEAuthenticationDAO) CreateAuth(tx *dbs.Tx, taskId int64, domain string, token string, key string) error {
var op = NewACMEAuthenticationOperator()
op.TaskId = taskId
op.Domain = domain
op.Token = token
op.Key = key
err := this.Save(tx, op)
return err
}
// 根据令牌查找认证信息
func (this *ACMEAuthenticationDAO) FindAuthWithToken(tx *dbs.Tx, token string) (*ACMEAuthentication, error) {
one, err := this.Query(tx).
Attr("token", token).
DescPk().
Find()
if err != nil {
return nil, err
}
if one == nil {
return nil, nil
}
return one.(*ACMEAuthentication), nil
}

View File

@@ -0,0 +1,5 @@
package acme
import (
_ "github.com/go-sql-driver/mysql"
)

View File

@@ -0,0 +1,24 @@
package acme
// ACME认证
type ACMEAuthentication struct {
Id uint64 `field:"id"` // ID
TaskId uint64 `field:"taskId"` // 任务ID
Domain string `field:"domain"` // 域名
Token string `field:"token"` // 令牌
Key string `field:"key"` // 密钥
CreatedAt uint64 `field:"createdAt"` // 创建时间
}
type ACMEAuthenticationOperator struct {
Id interface{} // ID
TaskId interface{} // 任务ID
Domain interface{} // 域名
Token interface{} // 令牌
Key interface{} // 密钥
CreatedAt interface{} // 创建时间
}
func NewACMEAuthenticationOperator() *ACMEAuthenticationOperator {
return &ACMEAuthenticationOperator{}
}

View File

@@ -0,0 +1 @@
package acme

View File

@@ -0,0 +1,154 @@
package acme
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
ACMEProviderAccountStateEnabled = 1 // 已启用
ACMEProviderAccountStateDisabled = 0 // 已禁用
)
type ACMEProviderAccountDAO dbs.DAO
func NewACMEProviderAccountDAO() *ACMEProviderAccountDAO {
return dbs.NewDAO(&ACMEProviderAccountDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeACMEProviderAccounts",
Model: new(ACMEProviderAccount),
PkName: "id",
},
}).(*ACMEProviderAccountDAO)
}
var SharedACMEProviderAccountDAO *ACMEProviderAccountDAO
func init() {
dbs.OnReady(func() {
SharedACMEProviderAccountDAO = NewACMEProviderAccountDAO()
})
}
// EnableACMEProviderAccount 启用条目
func (this *ACMEProviderAccountDAO) EnableACMEProviderAccount(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ACMEProviderAccountStateEnabled).
Update()
return err
}
// DisableACMEProviderAccount 禁用条目
func (this *ACMEProviderAccountDAO) DisableACMEProviderAccount(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ACMEProviderAccountStateDisabled).
Update()
return err
}
// FindEnabledACMEProviderAccount 查找启用中的条目
func (this *ACMEProviderAccountDAO) FindEnabledACMEProviderAccount(tx *dbs.Tx, id int64) (*ACMEProviderAccount, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", ACMEProviderAccountStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*ACMEProviderAccount), err
}
// FindACMEProviderAccountName 根据主键查找名称
func (this *ACMEProviderAccountDAO) FindACMEProviderAccountName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateAccount 创建账号
func (this *ACMEProviderAccountDAO) CreateAccount(tx *dbs.Tx, userId int64, name string, providerCode string, eabKid string, eabKey string) (int64, error) {
var op = NewACMEProviderAccountOperator()
op.UserId = userId
op.Name = name
op.ProviderCode = providerCode
op.EabKid = eabKid
op.EabKey = eabKey
op.IsOn = true
op.State = ACMEProviderAccountStateEnabled
return this.SaveInt64(tx, op)
}
// UpdateAccount 修改账号
func (this *ACMEProviderAccountDAO) UpdateAccount(tx *dbs.Tx, accountId int64, name string, eabKid string, eabKey string) error {
if accountId <= 0 {
return errors.New("invalid accountId")
}
var op = NewACMEProviderAccountOperator()
op.Id = accountId
op.Name = name
op.EabKid = eabKid
op.EabKey = eabKey
return this.Save(tx, op)
}
// CountAllEnabledAccounts 计算账号数量
func (this *ACMEProviderAccountDAO) CountAllEnabledAccounts(tx *dbs.Tx, userId int64) (int64, error) {
return this.Query(tx).
State(ACMEProviderAccountStateEnabled).
Attr("userId", userId).
Count()
}
// ListEnabledAccounts 查找单页账号
func (this *ACMEProviderAccountDAO) ListEnabledAccounts(tx *dbs.Tx, userId int64, offset int64, size int64) (result []*ACMEProviderAccount, err error) {
_, err = this.Query(tx).
State(ACMEProviderAccountStateEnabled).
Attr("userId", userId).
Offset(offset).
Limit(size).
DescPk().
Slice(&result).
FindAll()
return
}
// FindAllEnabledAccountsWithProviderCode 根据服务商代号查找账号
func (this *ACMEProviderAccountDAO) FindAllEnabledAccountsWithProviderCode(tx *dbs.Tx, userId int64, providerCode string) (result []*ACMEProviderAccount, err error) {
_, err = this.Query(tx).
State(ACMEProviderAccountStateEnabled).
Attr("providerCode", providerCode).
Attr("userId", userId).
DescPk().
Slice(&result).
FindAll()
return
}
// CheckUserAccount 检查是否为用户的服务商账号
func (this *ACMEProviderAccountDAO) CheckUserAccount(tx *dbs.Tx, userId int64, accountId int64) error {
if userId <= 0 || accountId <= 0 {
return models.ErrNotFound
}
b, err := this.Query(tx).
Pk(accountId).
State(ACMEProviderAccountStateEnabled).
Attr("userId", userId).
Exist()
if err != nil {
return err
}
if !b {
return models.ErrNotFound
}
return nil
}

View File

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

View File

@@ -0,0 +1,30 @@
package acme
// ACMEProviderAccount ACME提供商
type ACMEProviderAccount struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
IsOn bool `field:"isOn"` // 是否启用
Name string `field:"name"` // 名称
ProviderCode string `field:"providerCode"` // 代号
EabKid string `field:"eabKid"` // KID
EabKey string `field:"eabKey"` // Key
Error string `field:"error"` // 最后一条错误信息
State uint8 `field:"state"` // 状态
}
type ACMEProviderAccountOperator struct {
Id any // ID
UserId any // 用户ID
IsOn any // 是否启用
Name any // 名称
ProviderCode any // 代号
EabKid any // KID
EabKey any // Key
Error any // 最后一条错误信息
State any // 状态
}
func NewACMEProviderAccountOperator() *ACMEProviderAccountOperator {
return &ACMEProviderAccountOperator{}
}

View File

@@ -0,0 +1 @@
package acme

View File

@@ -0,0 +1,517 @@
package acme
import (
"bytes"
"context"
"encoding/json"
acmeutils "github.com/TeaOSLab/EdgeAPI/internal/acme"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/dns"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/go-acme/lego/v4/registration"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"net/http"
"time"
)
const (
ACMETaskStateEnabled = 1 // 已启用
ACMETaskStateDisabled = 0 // 已禁用
)
type ACMETaskDAO dbs.DAO
func NewACMETaskDAO() *ACMETaskDAO {
return dbs.NewDAO(&ACMETaskDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeACMETasks",
Model: new(ACMETask),
PkName: "id",
},
}).(*ACMETaskDAO)
}
var SharedACMETaskDAO *ACMETaskDAO
func init() {
dbs.OnReady(func() {
SharedACMETaskDAO = NewACMETaskDAO()
})
}
// EnableACMETask 启用条目
func (this *ACMETaskDAO) EnableACMETask(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ACMETaskStateEnabled).
Update()
return err
}
// DisableACMETask 禁用条目
func (this *ACMETaskDAO) DisableACMETask(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ACMETaskStateDisabled).
Update()
return err
}
// FindEnabledACMETask 查找启用中的条目
func (this *ACMETaskDAO) FindEnabledACMETask(tx *dbs.Tx, id int64) (*ACMETask, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", ACMETaskStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*ACMETask), err
}
// CountACMETasksWithACMEUserId 计算某个ACME用户相关的任务数量
func (this *ACMETaskDAO) CountACMETasksWithACMEUserId(tx *dbs.Tx, acmeUserId int64) (int64, error) {
return this.Query(tx).
State(ACMETaskStateEnabled).
Attr("acmeUserId", acmeUserId).
Count()
}
// CountACMETasksWithDNSProviderId 计算某个DNS服务商相关的任务数量
func (this *ACMETaskDAO) CountACMETasksWithDNSProviderId(tx *dbs.Tx, dnsProviderId int64) (int64, error) {
return this.Query(tx).
State(ACMETaskStateEnabled).
Attr("dnsProviderId", dnsProviderId).
Count()
}
// DisableAllTasksWithCertId 停止某个证书相关任务
func (this *ACMETaskDAO) DisableAllTasksWithCertId(tx *dbs.Tx, certId int64) error {
_, err := this.Query(tx).
Attr("certId", certId).
Set("state", ACMETaskStateDisabled).
Update()
return err
}
// CountAllEnabledACMETasks 计算所有任务数量
func (this *ACMETaskDAO) CountAllEnabledACMETasks(tx *dbs.Tx, userId int64, isAvailable bool, isExpired bool, expiringDays int64, keyword string, userOnly bool) (int64, error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
} else {
if userOnly {
query.Gt("userId", 0)
} else {
query.Attr("userId", 0)
}
}
if isAvailable || isExpired || expiringDays > 0 {
query.Gt("certId", 0)
if isAvailable {
query.Where("certId IN (SELECT id FROM " + models.SharedSSLCertDAO.Table + " WHERE timeBeginAt<=UNIX_TIMESTAMP() AND timeEndAt>=UNIX_TIMESTAMP())")
}
if isExpired {
query.Where("certId IN (SELECT id FROM " + models.SharedSSLCertDAO.Table + " WHERE timeEndAt<UNIX_TIMESTAMP())")
}
if expiringDays > 0 {
query.Where("certId IN (SELECT id FROM "+models.SharedSSLCertDAO.Table+" WHERE timeEndAt>UNIX_TIMESTAMP() AND timeEndAt<:expiredAt)").
Param("expiredAt", time.Now().Unix()+expiringDays*86400)
}
}
if len(keyword) > 0 {
query.Where("(domains LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
if len(keyword) > 0 {
query.Where("domains LIKE :keyword").
Param("keyword", dbutils.QuoteLike(keyword))
}
return query.State(ACMETaskStateEnabled).
Count()
}
// ListEnabledACMETasks 列出单页任务
func (this *ACMETaskDAO) ListEnabledACMETasks(tx *dbs.Tx, userId int64, isAvailable bool, isExpired bool, expiringDays int64, keyword string, userOnly bool, offset int64, size int64) (result []*ACMETask, err error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
} else {
if userOnly {
query.Gt("userId", 0)
} else {
query.Attr("userId", 0)
}
}
if isAvailable || isExpired || expiringDays > 0 {
query.Gt("certId", 0)
if isAvailable {
query.Where("certId IN (SELECT id FROM " + models.SharedSSLCertDAO.Table + " WHERE timeBeginAt<=UNIX_TIMESTAMP() AND timeEndAt>=UNIX_TIMESTAMP())")
}
if isExpired {
query.Where("certId IN (SELECT id FROM " + models.SharedSSLCertDAO.Table + " WHERE timeEndAt<UNIX_TIMESTAMP())")
}
if expiringDays > 0 {
query.Where("certId IN (SELECT id FROM "+models.SharedSSLCertDAO.Table+" WHERE timeEndAt>UNIX_TIMESTAMP() AND timeEndAt<:expiredAt)").
Param("expiredAt", time.Now().Unix()+expiringDays*86400)
}
}
if len(keyword) > 0 {
query.Where("(domains LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
_, err = query.
State(ACMETaskStateEnabled).
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// CreateACMETask 创建任务
func (this *ACMETaskDAO) CreateACMETask(tx *dbs.Tx, adminId int64, userId int64, authType acmeutils.AuthType, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool, authURL string) (int64, error) {
var op = NewACMETaskOperator()
op.AdminId = adminId
op.UserId = userId
op.AuthType = authType
op.AcmeUserId = acmeUserId
op.DnsProviderId = dnsProviderId
op.DnsDomain = dnsDomain
if len(domains) > 0 {
domainsJSON, err := json.Marshal(domains)
if err != nil {
return 0, err
}
op.Domains = domainsJSON
} else {
op.Domains = "[]"
}
op.AutoRenew = autoRenew
op.AuthURL = authURL
op.IsOn = true
op.State = ACMETaskStateEnabled
err := this.Save(tx, op)
if err != nil {
return 0, err
}
return types.Int64(op.Id), nil
}
// UpdateACMETask 修改任务
func (this *ACMETaskDAO) UpdateACMETask(tx *dbs.Tx, acmeTaskId int64, acmeUserId int64, dnsProviderId int64, dnsDomain string, domains []string, autoRenew bool, authURL string) error {
if acmeTaskId <= 0 {
return errors.New("invalid acmeTaskId")
}
var op = NewACMETaskOperator()
op.Id = acmeTaskId
op.AcmeUserId = acmeUserId
op.DnsProviderId = dnsProviderId
op.DnsDomain = dnsDomain
if len(domains) > 0 {
domainsJSON, err := json.Marshal(domains)
if err != nil {
return err
}
op.Domains = domainsJSON
} else {
op.Domains = "[]"
}
op.AutoRenew = autoRenew
op.AuthURL = authURL
err := this.Save(tx, op)
return err
}
// CheckUserACMETask 检查用户权限
func (this *ACMETaskDAO) CheckUserACMETask(tx *dbs.Tx, userId int64, acmeTaskId int64) (bool, error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
}
return query.
State(ACMETaskStateEnabled).
Pk(acmeTaskId).
Exist()
}
// FindACMETaskUserId 查找任务所属用户ID
func (this *ACMETaskDAO) FindACMETaskUserId(tx *dbs.Tx, taskId int64) (userId int64, err error) {
return this.Query(tx).
Pk(taskId).
Result("userId").
FindInt64Col(0)
}
// UpdateACMETaskCert 设置任务关联的证书
func (this *ACMETaskDAO) UpdateACMETaskCert(tx *dbs.Tx, taskId int64, certId int64) error {
if taskId <= 0 {
return errors.New("invalid taskId")
}
var op = NewACMETaskOperator()
op.Id = taskId
op.CertId = certId
err := this.Save(tx, op)
return err
}
// RunTask 执行任务并记录日志
func (this *ACMETaskDAO) RunTask(tx *dbs.Tx, taskId int64) (isOk bool, errMsg string, resultCertId int64) {
isOk, errMsg, resultCertId = this.runTaskWithoutLog(tx, taskId)
// 记录日志
err := SharedACMETaskLogDAO.CreateACMETaskLog(tx, taskId, isOk, errMsg)
if err != nil {
logs.Error(err)
}
return
}
// 执行任务但并不记录日志
func (this *ACMETaskDAO) runTaskWithoutLog(tx *dbs.Tx, taskId int64) (isOk bool, errMsg string, resultCertId int64) {
task, err := this.FindEnabledACMETask(tx, taskId)
if err != nil {
errMsg = "查询任务信息时出错:" + err.Error()
return
}
if task == nil {
errMsg = "找不到要执行的任务"
return
}
if !task.IsOn {
errMsg = "任务没有启用"
return
}
// ACME用户
user, err := SharedACMEUserDAO.FindEnabledACMEUser(tx, int64(task.AcmeUserId))
if err != nil {
errMsg = "查询ACME用户时出错" + err.Error()
return
}
if user == nil {
errMsg = "找不到ACME用户"
return
}
// 服务商
if len(user.ProviderCode) == 0 {
user.ProviderCode = acmeutils.DefaultProviderCode
}
var acmeProvider = acmeutils.FindProviderWithCode(user.ProviderCode)
if acmeProvider == nil {
errMsg = "服务商已不可用"
return
}
// 账号
var acmeAccount *acmeutils.Account
if user.AccountId > 0 {
account, err := SharedACMEProviderAccountDAO.FindEnabledACMEProviderAccount(tx, int64(user.AccountId))
if err != nil {
errMsg = "查询ACME账号时出错" + err.Error()
return
}
if account != nil {
acmeAccount = &acmeutils.Account{
EABKid: account.EabKid,
EABKey: account.EabKey,
}
}
}
privateKey, err := acmeutils.ParsePrivateKeyFromBase64(user.PrivateKey)
if err != nil {
errMsg = "解析私钥时出错:" + err.Error()
return
}
var remoteUser = acmeutils.NewUser(user.Email, privateKey, func(resource *registration.Resource) error {
resourceJSON, err := json.Marshal(resource)
if err != nil {
return err
}
err = SharedACMEUserDAO.UpdateACMEUserRegistration(tx, int64(user.Id), resourceJSON)
return err
})
if len(user.Registration) > 0 {
err = remoteUser.SetRegistration(user.Registration)
if err != nil {
errMsg = "设置注册信息时出错:" + err.Error()
return
}
}
var acmeTask *acmeutils.Task = nil
if task.AuthType == acmeutils.AuthTypeDNS {
// DNS服务商
dnsProvider, err := dns.SharedDNSProviderDAO.FindEnabledDNSProvider(tx, int64(task.DnsProviderId))
if err != nil {
errMsg = "查找DNS服务商账号信息时出错" + err.Error()
return
}
if dnsProvider == nil {
errMsg = "找不到DNS服务商账号"
return
}
providerInterface := dnsclients.FindProvider(dnsProvider.Type, int64(dnsProvider.Id))
if providerInterface == nil {
errMsg = "暂不支持此类型的DNS服务商 '" + dnsProvider.Type + "'"
return
}
providerInterface.SetMinTTL(int32(dnsProvider.MinTTL))
apiParams, err := dnsProvider.DecodeAPIParams()
if err != nil {
errMsg = "解析DNS服务商API参数时出错" + err.Error()
return
}
err = providerInterface.Auth(apiParams)
if err != nil {
errMsg = "校验DNS服务商API参数时出错" + err.Error()
return
}
acmeTask = &acmeutils.Task{
User: remoteUser,
AuthType: acmeutils.AuthTypeDNS,
DNSProvider: providerInterface,
DNSDomain: task.DnsDomain,
Domains: task.DecodeDomains(),
}
} else if task.AuthType == acmeutils.AuthTypeHTTP {
acmeTask = &acmeutils.Task{
User: remoteUser,
AuthType: acmeutils.AuthTypeHTTP,
Domains: task.DecodeDomains(),
}
}
acmeTask.Provider = acmeProvider
acmeTask.Account = acmeAccount
var acmeRequest = acmeutils.NewRequest(acmeTask)
acmeRequest.OnAuth(func(domain, token, keyAuth string) {
err := SharedACMEAuthenticationDAO.CreateAuth(tx, taskId, domain, token, keyAuth)
if err != nil {
remotelogs.Error("ACME", "write authentication to database error: "+err.Error())
} else {
// 调用校验URL
if len(task.AuthURL) > 0 {
authJSON, err := json.Marshal(maps.Map{
"domain": domain,
"token": token,
"key": keyAuth,
})
if err != nil {
remotelogs.Error("ACME", "encode auth data failed: '"+task.AuthURL+"'")
} else {
var client = utils.SharedHttpClient(10 * time.Second)
req, err := http.NewRequest(http.MethodPost, task.AuthURL, bytes.NewReader(authJSON))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("User-Agent", teaconst.ProductName+"/"+teaconst.Version)
if err != nil {
remotelogs.Error("ACME", "parse auth url failed '"+task.AuthURL+"': "+err.Error())
} else {
resp, err := client.Do(req)
if err != nil {
remotelogs.Error("ACME", "call auth url failed '"+task.AuthURL+"': "+err.Error())
} else {
_ = resp.Body.Close()
}
}
}
}
}
})
certData, keyData, err := acmeRequest.Run()
if err != nil {
errMsg = "证书生成失败:" + err.Error()
return
}
// 分析证书
var sslConfig = &sslconfigs.SSLCertConfig{
CertData: certData,
KeyData: keyData,
}
err = sslConfig.Init(context.Background())
if err != nil {
errMsg = "证书生成成功,但是分析证书信息时发生错误:" + err.Error()
return
}
// 保存证书
resultCertId = int64(task.CertId)
if resultCertId > 0 {
cert, err := models.SharedSSLCertDAO.FindEnabledSSLCert(tx, resultCertId)
if err != nil {
errMsg = "证书生成成功,但查询已绑定的证书时出错:" + err.Error()
return
}
if cert == nil {
errMsg = "证书已被管理员或用户删除"
// 禁用
err = SharedACMETaskDAO.DisableACMETask(tx, taskId)
if err != nil {
errMsg = "禁用失效的ACME任务出错" + err.Error()
}
return
}
err = models.SharedSSLCertDAO.UpdateCert(tx, resultCertId, cert.IsOn, cert.Name, cert.Description, cert.ServerName, cert.IsCA, certData, keyData, sslConfig.TimeBeginAt, sslConfig.TimeEndAt, sslConfig.DNSNames, sslConfig.CommonNames)
if err != nil {
errMsg = "证书生成成功,但是修改数据库中的证书信息时出错:" + err.Error()
return
}
} else {
resultCertId, err = models.SharedSSLCertDAO.CreateCert(tx, int64(task.AdminId), int64(task.UserId), true, task.DnsDomain+"免费证书", "免费申请的证书", "", false, certData, keyData, sslConfig.TimeBeginAt, sslConfig.TimeEndAt, sslConfig.DNSNames, sslConfig.CommonNames)
if err != nil {
errMsg = "证书生成成功,但是保存到数据库失败:" + err.Error()
return
}
err = models.SharedSSLCertDAO.UpdateCertACME(tx, resultCertId, int64(task.Id))
if err != nil {
errMsg = "证书生成成功修改证书ACME信息时出错" + err.Error()
return
}
// 设置成功
err = SharedACMETaskDAO.UpdateACMETaskCert(tx, taskId, resultCertId)
if err != nil {
errMsg = "证书生成成功,设置任务关联的证书时出错:" + err.Error()
return
}
}
isOk = true
return
}

View File

@@ -0,0 +1,5 @@
package acme
import (
_ "github.com/go-sql-driver/mysql"
)

View File

@@ -0,0 +1,51 @@
package acme
import (
"github.com/TeaOSLab/EdgeAPI/internal/utils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
type ACMETaskLogDAO dbs.DAO
func NewACMETaskLogDAO() *ACMETaskLogDAO {
return dbs.NewDAO(&ACMETaskLogDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeACMETaskLogs",
Model: new(ACMETaskLog),
PkName: "id",
},
}).(*ACMETaskLogDAO)
}
var SharedACMETaskLogDAO *ACMETaskLogDAO
func init() {
dbs.OnReady(func() {
SharedACMETaskLogDAO = NewACMETaskLogDAO()
})
}
// CreateACMETaskLog 生成日志
func (this *ACMETaskLogDAO) CreateACMETaskLog(tx *dbs.Tx, taskId int64, isOk bool, errMsg string) error {
var op = NewACMETaskLogOperator()
op.TaskId = taskId
op.Error = utils.LimitString(errMsg, 1024)
op.IsOk = isOk
err := this.Save(tx, op)
return err
}
// FindLatestACMETasKLog 取得任务的最后一条执行日志
func (this *ACMETaskLogDAO) FindLatestACMETasKLog(tx *dbs.Tx, taskId int64) (*ACMETaskLog, error) {
one, err := this.Query(tx).
Attr("taskId", taskId).
DescPk().
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*ACMETaskLog), nil
}

View File

@@ -0,0 +1,5 @@
package acme
import (
_ "github.com/go-sql-driver/mysql"
)

View File

@@ -0,0 +1,22 @@
package acme
// ACMETaskLog ACME任务运行日志
type ACMETaskLog struct {
Id uint64 `field:"id"` // ID
TaskId uint64 `field:"taskId"` // 任务ID
IsOk bool `field:"isOk"` // 是否成功
Error string `field:"error"` // 错误信息
CreatedAt uint64 `field:"createdAt"` // 运行时间
}
type ACMETaskLogOperator struct {
Id interface{} // ID
TaskId interface{} // 任务ID
IsOk interface{} // 是否成功
Error interface{} // 错误信息
CreatedAt interface{} // 运行时间
}
func NewACMETaskLogOperator() *ACMETaskLogOperator {
return &ACMETaskLogOperator{}
}

View File

@@ -0,0 +1 @@
package acme

View File

@@ -0,0 +1,42 @@
package acme
import "github.com/iwind/TeaGo/dbs"
// ACMETask ACME任务
type ACMETask struct {
Id uint64 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
IsOn bool `field:"isOn"` // 是否启用
AcmeUserId uint32 `field:"acmeUserId"` // ACME用户ID
DnsDomain string `field:"dnsDomain"` // DNS主域名
DnsProviderId uint64 `field:"dnsProviderId"` // DNS服务商
Domains dbs.JSON `field:"domains"` // 证书域名
CreatedAt uint64 `field:"createdAt"` // 创建时间
State uint8 `field:"state"` // 状态
CertId uint64 `field:"certId"` // 生成的证书ID
AutoRenew uint8 `field:"autoRenew"` // 是否自动更新
AuthType string `field:"authType"` // 认证类型
AuthURL string `field:"authURL"` // 认证URL
}
type ACMETaskOperator struct {
Id interface{} // ID
AdminId interface{} // 管理员ID
UserId interface{} // 用户ID
IsOn interface{} // 是否启用
AcmeUserId interface{} // ACME用户ID
DnsDomain interface{} // DNS主域名
DnsProviderId interface{} // DNS服务商
Domains interface{} // 证书域名
CreatedAt interface{} // 创建时间
State interface{} // 状态
CertId interface{} // 生成的证书ID
AutoRenew interface{} // 是否自动更新
AuthType interface{} // 认证类型
AuthURL interface{} // 认证URL
}
func NewACMETaskOperator() *ACMETaskOperator {
return &ACMETaskOperator{}
}

View File

@@ -0,0 +1,20 @@
package acme
import (
"encoding/json"
"github.com/iwind/TeaGo/logs"
)
// DecodeDomains 将域名解析成字符串数组
func (this *ACMETask) DecodeDomains() []string {
if len(this.Domains) == 0 {
return nil
}
result := []string{}
err := json.Unmarshal(this.Domains, &result)
if err != nil {
logs.Error(err)
return nil
}
return result
}

View File

@@ -0,0 +1,211 @@
package acme
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/base64"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
)
const (
ACMEUserStateEnabled = 1 // 已启用
ACMEUserStateDisabled = 0 // 已禁用
)
type ACMEUserDAO dbs.DAO
func NewACMEUserDAO() *ACMEUserDAO {
return dbs.NewDAO(&ACMEUserDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeACMEUsers",
Model: new(ACMEUser),
PkName: "id",
},
}).(*ACMEUserDAO)
}
var SharedACMEUserDAO *ACMEUserDAO
func init() {
dbs.OnReady(func() {
SharedACMEUserDAO = NewACMEUserDAO()
})
}
// EnableACMEUser 启用条目
func (this *ACMEUserDAO) EnableACMEUser(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ACMEUserStateEnabled).
Update()
return err
}
// DisableACMEUser 禁用条目
func (this *ACMEUserDAO) DisableACMEUser(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ACMEUserStateDisabled).
Update()
return err
}
// 查找启用中的条目
func (this *ACMEUserDAO) FindEnabledACMEUser(tx *dbs.Tx, id int64) (*ACMEUser, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", ACMEUserStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*ACMEUser), err
}
// CreateACMEUser 创建用户
func (this *ACMEUserDAO) CreateACMEUser(tx *dbs.Tx, adminId int64, userId int64, providerCode string, accountId int64, email string, description string) (int64, error) {
// 生成私钥
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return 0, err
}
privateKeyData, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
return 0, err
}
privateKeyText := base64.StdEncoding.EncodeToString(privateKeyData)
var op = NewACMEUserOperator()
op.AdminId = adminId
op.UserId = userId
op.ProviderCode = providerCode
op.AccountId = accountId
op.Email = email
op.Description = description
op.PrivateKey = privateKeyText
op.State = ACMEUserStateEnabled
err = this.Save(tx, op)
if err != nil {
return 0, err
}
return types.Int64(op.Id), nil
}
// UpdateACMEUser 修改用户信息
func (this *ACMEUserDAO) UpdateACMEUser(tx *dbs.Tx, acmeUserId int64, description string) error {
if acmeUserId <= 0 {
return errors.New("invalid acmeUserId")
}
var op = NewACMEUserOperator()
op.Id = acmeUserId
op.Description = description
err := this.Save(tx, op)
return err
}
// UpdateACMEUserRegistration 修改用户ACME注册信息
func (this *ACMEUserDAO) UpdateACMEUserRegistration(tx *dbs.Tx, acmeUserId int64, registrationJSON []byte) error {
if acmeUserId <= 0 {
return errors.New("invalid acmeUserId")
}
var op = NewACMEUserOperator()
op.Id = acmeUserId
op.Registration = registrationJSON
err := this.Save(tx, op)
return err
}
// CountACMEUsersWithAdminId 计算用户数量
func (this *ACMEUserDAO) CountACMEUsersWithAdminId(tx *dbs.Tx, adminId int64, userId int64, accountId int64) (int64, error) {
query := this.Query(tx)
if adminId > 0 {
query.Attr("adminId", adminId)
}
if userId > 0 {
query.Attr("userId", userId)
} else {
query.Attr("userId", 0)
}
if accountId > 0 {
query.Attr("accountId", accountId)
}
return query.
State(ACMEUserStateEnabled).
Count()
}
// ListACMEUsers 列出当前管理员的用户
func (this *ACMEUserDAO) ListACMEUsers(tx *dbs.Tx, adminId int64, userId int64, offset int64, size int64) (result []*ACMEUser, err error) {
query := this.Query(tx)
if adminId > 0 {
query.Attr("adminId", adminId)
}
if userId > 0 {
query.Attr("userId", userId)
} else {
query.Attr("userId", 0)
}
_, err = query.
State(ACMEUserStateEnabled).
Offset(offset).
Limit(size).
Slice(&result).
DescPk().
FindAll()
return
}
// FindAllACMEUsers 查找所有用户
func (this *ACMEUserDAO) FindAllACMEUsers(tx *dbs.Tx, adminId int64, userId int64, providerCode string) (result []*ACMEUser, err error) {
// 防止没有传入条件导致返回的数据过多
if adminId <= 0 && userId <= 0 {
return nil, errors.New("'adminId' or 'userId' should not be empty")
}
query := this.Query(tx)
if adminId > 0 {
query.Attr("adminId", adminId)
}
if userId > 0 {
query.Attr("userId", userId)
}
if len(providerCode) > 0 {
query.Attr("providerCode", providerCode)
}
_, err = query.
State(ACMEUserStateEnabled).
Slice(&result).
DescPk().
FindAll()
return
}
// CheckACMEUser 检查用户权限
func (this *ACMEUserDAO) CheckACMEUser(tx *dbs.Tx, acmeUserId int64, adminId int64, userId int64) (bool, error) {
if acmeUserId <= 0 {
return false, nil
}
query := this.Query(tx)
if adminId > 0 {
query.Attr("adminId", adminId)
} else if userId > 0 {
query.Attr("userId", userId)
} else {
return false, nil
}
return query.
State(ACMEUserStateEnabled).
Exist()
}

View File

@@ -0,0 +1,5 @@
package acme
import (
_ "github.com/go-sql-driver/mysql"
)

View File

@@ -0,0 +1,36 @@
package acme
import "github.com/iwind/TeaGo/dbs"
// ACMEUser ACME用户
type ACMEUser struct {
Id uint64 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
PrivateKey string `field:"privateKey"` // 私钥
Email string `field:"email"` // E-mail
CreatedAt uint64 `field:"createdAt"` // 创建时间
State uint8 `field:"state"` // 状态
Description string `field:"description"` // 备注介绍
Registration dbs.JSON `field:"registration"` // 注册信息
ProviderCode string `field:"providerCode"` // 服务商代号
AccountId uint64 `field:"accountId"` // 提供商ID
}
type ACMEUserOperator struct {
Id interface{} // ID
AdminId interface{} // 管理员ID
UserId interface{} // 用户ID
PrivateKey interface{} // 私钥
Email interface{} // E-mail
CreatedAt interface{} // 创建时间
State interface{} // 状态
Description interface{} // 备注介绍
Registration interface{} // 注册信息
ProviderCode interface{} // 服务商代号
AccountId interface{} // 提供商ID
}
func NewACMEUserOperator() *ACMEUserOperator {
return &ACMEUserOperator{}
}

View File

@@ -0,0 +1 @@
package acme

View File

@@ -0,0 +1,71 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
ADNetworkStateEnabled = 1 // 已启用
ADNetworkStateDisabled = 0 // 已禁用
)
type ADNetworkDAO dbs.DAO
func NewADNetworkDAO() *ADNetworkDAO {
return dbs.NewDAO(&ADNetworkDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeADNetworks",
Model: new(ADNetwork),
PkName: "id",
},
}).(*ADNetworkDAO)
}
var SharedADNetworkDAO *ADNetworkDAO
func init() {
dbs.OnReady(func() {
SharedADNetworkDAO = NewADNetworkDAO()
})
}
// EnableADNetwork 启用条目
func (this *ADNetworkDAO) EnableADNetwork(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ADNetworkStateEnabled).
Update()
return err
}
// DisableADNetwork 禁用条目
func (this *ADNetworkDAO) DisableADNetwork(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ADNetworkStateDisabled).
Update()
return err
}
// FindEnabledADNetwork 查找启用中的条目
func (this *ADNetworkDAO) FindEnabledADNetwork(tx *dbs.Tx, id int64) (*ADNetwork, error) {
result, err := this.Query(tx).
Pk(id).
State(ADNetworkStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*ADNetwork), err
}
// FindADNetworkName 根据主键查找名称
func (this *ADNetworkDAO) FindADNetworkName(tx *dbs.Tx, id uint32) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}

View File

@@ -0,0 +1,56 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package models
import (
"errors"
"github.com/iwind/TeaGo/dbs"
)
// CreateNetwork 创建线路
func (this *ADNetworkDAO) CreateNetwork(tx *dbs.Tx, name string, description string) (int64, error) {
var op = NewADNetworkOperator()
op.Name = name
op.Description = description
op.IsOn = true
op.State = ADNetworkStateEnabled
return this.SaveInt64(tx, op)
}
// UpdateNetwork 修改线路
func (this *ADNetworkDAO) UpdateNetwork(tx *dbs.Tx, networkId int64, isOn bool, name string, description string) error {
if networkId <= 0 {
return errors.New("invalid networkId")
}
var op = NewADNetworkOperator()
op.Id = networkId
op.Name = name
op.Description = description
op.IsOn = isOn
return this.Save(tx, op)
}
// FindAllNetworks 列出所有线路
func (this *ADNetworkDAO) FindAllNetworks(tx *dbs.Tx) (result []*ADNetwork, err error) {
_, err = this.Query(tx).
State(ADNetworkStateEnabled).
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}
// FindAllAvailableNetworks 列出所有可用的线路
func (this *ADNetworkDAO) FindAllAvailableNetworks(tx *dbs.Tx) (result []*ADNetwork, err error) {
_, err = this.Query(tx).
State(ADNetworkStateEnabled).
Attr("isOn", true).
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}

View File

@@ -0,0 +1,22 @@
//go:build plus
package models_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"testing"
)
func TestADNetworkDAO_FindAllNetworks(t *testing.T) {
var dao = models.NewADNetworkDAO()
var tx *dbs.Tx
networks, err := dao.FindAllNetworks(tx)
if err != nil {
t.Fatal(err)
}
logs.PrintAsJSON(networks, t)
}

View File

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

View File

@@ -0,0 +1,24 @@
package models
// ADNetwork 高防线路
type ADNetwork struct {
Id uint32 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
Name string `field:"name"` // 名称
Description string `field:"description"` // 描述
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
}
type ADNetworkOperator struct {
Id any // ID
IsOn any // 是否启用
Name any // 名称
Description any // 描述
Order any // 排序
State any // 状态
}
func NewADNetworkOperator() *ADNetworkOperator {
return &ADNetworkOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -0,0 +1,71 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
ADPackageStateEnabled = 1 // 已启用
ADPackageStateDisabled = 0 // 已禁用
)
type ADPackageDAO dbs.DAO
func NewADPackageDAO() *ADPackageDAO {
return dbs.NewDAO(&ADPackageDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeADPackages",
Model: new(ADPackage),
PkName: "id",
},
}).(*ADPackageDAO)
}
var SharedADPackageDAO *ADPackageDAO
func init() {
dbs.OnReady(func() {
SharedADPackageDAO = NewADPackageDAO()
})
}
// EnableADPackage 启用条目
func (this *ADPackageDAO) EnableADPackage(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ADPackageStateEnabled).
Update()
return err
}
// DisableADPackage 禁用条目
func (this *ADPackageDAO) DisableADPackage(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ADPackageStateDisabled).
Update()
return err
}
// FindEnabledADPackage 查找启用中的条目
func (this *ADPackageDAO) FindEnabledADPackage(tx *dbs.Tx, id int64) (*ADPackage, error) {
result, err := this.Query(tx).
Pk(id).
State(ADPackageStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*ADPackage), err
}
// FindADPackageName 根据主键查找名称
func (this *ADPackageDAO) FindADPackageName(tx *dbs.Tx, id uint32) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}

View File

@@ -0,0 +1,131 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package models
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
)
// CreatePackage 创建高防产品
func (this *ADPackageDAO) CreatePackage(tx *dbs.Tx, networkId int64, protectionBandwidthSize int32, protectionBandwidthUnit userconfigs.ADPackageSizeUnit, serverBandwidthSize int32, serverBandwidthUnit userconfigs.ADPackageSizeUnit) (int64, error) {
var op = NewADPackageOperator()
op.NetworkId = networkId
op.ProtectionBandwidthSize = protectionBandwidthSize
op.ProtectionBandwidthUnit = protectionBandwidthUnit
op.ProtectionBandwidthBits = userconfigs.ADPackageSizeBits(protectionBandwidthSize, protectionBandwidthUnit)
op.ServerBandwidthSize = serverBandwidthSize
op.ServerBandwidthUnit = serverBandwidthUnit
op.ServerBandwidthBits = userconfigs.ADPackageSizeBits(serverBandwidthSize, serverBandwidthUnit)
op.IsOn = true
op.State = ADPackageStateEnabled
return this.SaveInt64(tx, op)
}
// UpdatePackage 修改高防产品
func (this *ADPackageDAO) UpdatePackage(tx *dbs.Tx, packageId int64, isOn bool, networkId int64, protectionBandwidthSize int32, protectionBandwidthUnit userconfigs.ADPackageSizeUnit, serverBandwidthSize int32, serverBandwidthUnit userconfigs.ADPackageSizeUnit) error {
if packageId <= 0 {
return errors.New("invalid packageId")
}
var op = NewADPackageOperator()
op.Id = packageId
op.NetworkId = networkId
op.ProtectionBandwidthSize = protectionBandwidthSize
op.ProtectionBandwidthUnit = protectionBandwidthUnit
op.ProtectionBandwidthBits = userconfigs.ADPackageSizeBits(protectionBandwidthSize, protectionBandwidthUnit)
op.ServerBandwidthSize = serverBandwidthSize
op.ServerBandwidthUnit = serverBandwidthUnit
op.ServerBandwidthBits = userconfigs.ADPackageSizeBits(serverBandwidthSize, serverBandwidthUnit)
op.IsOn = isOn
return this.Save(tx, op)
}
// CountAllPackages 查询高防产品数量
func (this *ADPackageDAO) CountAllPackages(tx *dbs.Tx, networkId int64) (int64, error) {
var query = this.Query(tx).
State(ADPackageStateEnabled)
if networkId > 0 {
query.Attr("networkId", networkId)
}
query.Where("networkId IN (SELECT id FROM " + SharedADNetworkDAO.Table + " WHERE state=1)")
return query.Count()
}
// ListPackages 列出单页高防产品
func (this *ADPackageDAO) ListPackages(tx *dbs.Tx, networkId int64, offset int64, size int64) (result []*ADPackage, err error) {
var query = this.Query(tx).
State(ADPackageStateEnabled)
if networkId > 0 {
query.Attr("networkId", networkId)
}
_, err = query.
Where("networkId IN (SELECT id FROM " + SharedADNetworkDAO.Table + " WHERE state=1)").
Asc("networkId").
Asc("protectionBandwidthSize").
Asc("serverBandwidthSize").
AscPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// CountAllIdlePackages 查询可用的产品数量
func (this *ADPackageDAO) CountAllIdlePackages(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
State(ADPackageStateEnabled).
Attr("isOn", true).
Where("networkId IN (SELECT id FROM "+SharedADNetworkDAO.Table+" WHERE state=1 AND isOn=1)").
Where("id IN (SELECT DISTINCT packageId FROM "+SharedADPackageInstanceDAO.Table+" WHERE state=1 AND isOn=1 AND (userId=0 OR userDayTo IS NULL OR userDayTo<:endDay))"). // 确保没有正在使用
Param("endDay", timeutil.Format("Ymd")).
Where("id IN (SELECT DISTINCT packageId FROM " + SharedADPackagePriceDAO.Table + " WHERE price>0 )"). // 已设定价格
Count()
}
// FindAllIdlePackages 列出所有可用的高防产品
func (this *ADPackageDAO) FindAllIdlePackages(tx *dbs.Tx) (result []*ADPackage, err error) {
_, err = this.Query(tx).
State(ADPackageStateEnabled).
Attr("isOn", true).
Where("networkId IN (SELECT id FROM "+SharedADNetworkDAO.Table+" WHERE state=1 AND isOn=1)").
Where("id IN (SELECT DISTINCT packageId FROM "+SharedADPackageInstanceDAO.Table+" WHERE state=1 AND isOn=1 AND (userId=0 OR userDayTo IS NULL OR userDayTo>:endDay))"). // 确保没有正在使用
Param("endDay", timeutil.Format("Ymd")).
Where("id IN (SELECT DISTINCT packageId FROM " + SharedADPackagePriceDAO.Table + " WHERE price>0 )"). // 已设定价格
Asc("protectionBandwidthSize").
Asc("serverBandwidthSize").
AscPk().
Slice(&result).
FindAll()
return
}
// FindIdlePackage 查找符合条件的高防产品
func (this *ADPackageDAO) FindIdlePackage(tx *dbs.Tx, networkId int64, protectionBandwidthSize int64, protectionBandwidthUnit string, serverBandwidthSize int64, serverBandwidthUnit string) (p *ADPackage, err error) {
one, err := this.Query(tx).
State(ADPackageStateEnabled).
Attr("isOn", true).
Attr("networkId", networkId).
Attr("protectionBandwidthSize", protectionBandwidthSize).
Attr("protectionBandwidthUnit", protectionBandwidthUnit).
Attr("serverBandwidthSize", serverBandwidthSize).
Attr("serverBandwidthUnit", serverBandwidthUnit).
Where("id IN (SELECT DISTINCT packageId FROM "+SharedADPackageInstanceDAO.Table+" WHERE state=1 AND isOn=1 AND (userId=0 OR userDayTo IS NULL OR userDayTo<:endDay))"). // 确保没有正在使用
Param("endDay", timeutil.Format("Ymd")).
Where("id IN (SELECT DISTINCT packageId FROM " + SharedADPackagePriceDAO.Table + " WHERE price>0 )"). // 已设定价格
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*ADPackage), nil
}

View File

@@ -0,0 +1,24 @@
//go:build plus
package models_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"testing"
)
func TestADPackageInstanceDAO_FindIdlePackageInstances(t *testing.T) {
dbs.NotifyReady()
var dao = models.NewADPackageDAO()
var tx *dbs.Tx
result, err := dao.FindAllIdlePackages(tx)
if err != nil {
t.Fatal(err)
}
logs.PrintAsJSON(result, t)
}

View File

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

View File

@@ -0,0 +1,54 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
ADPackageInstanceStateEnabled = 1 // 已启用
ADPackageInstanceStateDisabled = 0 // 已禁用
)
type ADPackageInstanceDAO dbs.DAO
func NewADPackageInstanceDAO() *ADPackageInstanceDAO {
return dbs.NewDAO(&ADPackageInstanceDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeADPackageInstances",
Model: new(ADPackageInstance),
PkName: "id",
},
}).(*ADPackageInstanceDAO)
}
var SharedADPackageInstanceDAO *ADPackageInstanceDAO
func init() {
dbs.OnReady(func() {
SharedADPackageInstanceDAO = NewADPackageInstanceDAO()
})
}
// EnableADPackageInstance 启用条目
func (this *ADPackageInstanceDAO) EnableADPackageInstance(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ADPackageInstanceStateEnabled).
Update()
return err
}
// FindEnabledADPackageInstance 查找启用中的条目
func (this *ADPackageInstanceDAO) FindEnabledADPackageInstance(tx *dbs.Tx, id int64) (*ADPackageInstance, error) {
result, err := this.Query(tx).
Pk(id).
State(ADPackageInstanceStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*ADPackageInstance), err
}

View File

@@ -0,0 +1,352 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package models
import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
timeutil "github.com/iwind/TeaGo/utils/time"
"strconv"
)
// DisableADPackageInstance 禁用条目
func (this *ADPackageInstanceDAO) DisableADPackageInstance(tx *dbs.Tx, instanceId int64) error {
_, err := this.Query(tx).
Pk(instanceId).
Set("state", ADPackageInstanceStateDisabled).
Update()
if err != nil {
return err
}
// 通知更新
return this.NotifyUpdate(tx, instanceId)
}
// CreateInstance 创建实例
func (this *ADPackageInstanceDAO) CreateInstance(tx *dbs.Tx, packageId int64, clusterId int64, nodeIds []int64, ipAddresses []string) (int64, error) {
var op = NewADPackageInstanceOperator()
op.PackageId = packageId
// nodeIds
op.ClusterId = clusterId
if nodeIds == nil {
op.NodeIds = "[]"
} else {
nodeIdsJSON, err := json.Marshal(nodeIds)
if err != nil {
return 0, err
}
op.NodeIds = nodeIdsJSON
}
// ip addresses
if ipAddresses == nil {
op.IpAddresses = "[]"
} else {
ipAddressesJSON, err := json.Marshal(ipAddresses)
if err != nil {
return 0, err
}
op.IpAddresses = ipAddressesJSON
}
op.IsOn = true
op.State = ADPackageInstanceStateEnabled
return this.SaveInt64(tx, op)
}
// UpdateInstance 修改实例
func (this *ADPackageInstanceDAO) UpdateInstance(tx *dbs.Tx, instanceId int64, clusterId int64, nodeIds []int64, ipAddresses []string, isOn bool) error {
if instanceId <= 0 {
return errors.New("invalid 'instanceId'")
}
// 新老集群和节点变化
oldOne, err := this.Query(tx).
Pk(instanceId).
Result("clusterId").
Find()
if err != nil {
return err
}
var oldInstance = oldOne.(*ADPackageInstance)
if oldInstance == nil {
return nil
}
var oldClusterId = int64(oldInstance.ClusterId)
if oldClusterId != clusterId {
err = SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, oldClusterId, 0, 0, NodeTaskTypeConfigChanged)
if err != nil {
return err
}
}
// 保存新的
var op = NewADPackageInstanceOperator()
op.Id = instanceId
// nodeIds
op.ClusterId = clusterId
if nodeIds == nil {
op.NodeIds = "[]"
} else {
nodeIdsJSON, err := json.Marshal(nodeIds)
if err != nil {
return err
}
op.NodeIds = nodeIdsJSON
}
// ip addresses
if ipAddresses == nil {
op.IpAddresses = "[]"
} else {
ipAddressesJSON, err := json.Marshal(ipAddresses)
if err != nil {
return err
}
op.IpAddresses = ipAddressesJSON
}
op.IsOn = isOn
err = this.Save(tx, op)
if err != nil {
return err
}
// 通知更新
return this.NotifyUpdate(tx, instanceId)
}
// CountIdleInstances 计算可以购买的实例数量
func (this *ADPackageInstanceDAO) CountIdleInstances(tx *dbs.Tx, packageId int64) (int64, error) {
return this.Query(tx).
State(ADPackageInstanceStateEnabled).
Attr("isOn", true).
Attr("packageId", packageId).
Where("(userId=0 OR userDayTo IS NULL OR userDayTo<:today)").
Param("today", timeutil.Format("Ymd")).
Count()
}
// CountInstances 计算实例数量
func (this *ADPackageInstanceDAO) CountInstances(tx *dbs.Tx, userId int64, networkId int64, packageId int64, ip string) (int64, error) {
var query = this.Query(tx).
State(ADPackageInstanceStateEnabled)
if userId > 0 {
query.Attr("userId", userId)
query.Gte("dayTo", timeutil.Format("Ymd"))
}
if networkId > 0 {
query.Where("packageId IN (SELECT id FROM " + SharedADPackageDAO.Table + " WHERE networkId=:networkId AND state=1)")
query.Param("networkId", networkId)
} else {
query.Where("packageId IN (SELECT id FROM " + SharedADPackageDAO.Table + " WHERE state=1)")
}
if packageId > 0 {
query.Attr("packageId", packageId)
}
if len(ip) > 0 {
query.Where("JSON_CONTAINS(ipAddresses, :ip)")
query.Param("ip", strconv.Quote(ip))
}
return query.Count()
}
// ListInstances 列出单页实例
func (this *ADPackageInstanceDAO) ListInstances(tx *dbs.Tx, userId int64, networkId int64, packageId int64, ip string, offset int64, size int64) (result []*ADPackageInstance, err error) {
var query = this.Query(tx).
State(ADPackageInstanceStateEnabled)
if userId > 0 {
query.Attr("userId", userId)
query.Gte("dayTo", timeutil.Format("Ymd"))
}
if networkId > 0 {
query.Where("packageId IN (SELECT id FROM " + SharedADPackageDAO.Table + " WHERE networkId=:networkId AND state=1)")
query.Param("networkId", networkId)
} else {
query.Where("packageId IN (SELECT id FROM " + SharedADPackageDAO.Table + " WHERE state=1)")
}
if packageId > 0 {
query.Attr("packageId", packageId)
}
if len(ip) > 0 {
query.Where("JSON_CONTAINS(ipAddresses, :ip)")
query.Param("ip", strconv.Quote(ip))
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// FindAllPackageInstances 查找某个产品下的所有实例
func (this *ADPackageInstanceDAO) FindAllPackageInstances(tx *dbs.Tx, packageId int64) (result []*ADPackageInstance, err error) {
_, err = this.Query(tx).
State(ADPackageInstanceStateEnabled).
Attr("packageId", packageId).
DescPk().
Slice(&result).
FindAll()
return
}
// FindIdlePackageInstances 查找某个产品下的可用实例
func (this *ADPackageInstanceDAO) FindIdlePackageInstances(tx *dbs.Tx, packageId int64, count int32) (result []*ADPackageInstance, err error) {
_, err = this.Query(tx).
State(ADPackageInstanceStateEnabled).
Attr("packageId", packageId).
Where("(userId=0 OR userDayTo IS NULL OR userDayTo<:today)").
Param("today", timeutil.Format("Ymd")).
DescPk().
Limit(int64(count)).
Slice(&result).
FindAll()
return
}
// UpdateInstanceUser 修改实例所属用户
func (this *ADPackageInstanceDAO) UpdateInstanceUser(tx *dbs.Tx, instanceId int64, userId int64, dayTo string, userInstanceId int64) error {
if instanceId <= 0 {
return errors.New("invalid 'instanceId'")
}
var op = NewADPackageInstanceOperator()
op.Id = instanceId
op.UserId = userId
op.UserDayTo = dayTo
op.UserInstanceId = userInstanceId
err := this.Save(tx, op)
if err != nil {
return err
}
// 通知更新
return this.NotifyUpdate(tx, instanceId)
}
// ResetInstanceUser 重置实例所属用户
func (this *ADPackageInstanceDAO) ResetInstanceUser(tx *dbs.Tx, instanceId int64) error {
if instanceId <= 0 {
return errors.New("invalid 'instanceId'")
}
err := this.Query(tx).
Pk(instanceId).
Set("userId", 0).
Set("userDayTo", dbs.SQL("NULL")).
Set("userInstanceId", 0).
Set("objectCodes", "[]").
UpdateQuickly()
if err != nil {
return err
}
// 通知更新
return this.NotifyUpdate(tx, instanceId)
}
// UpdateInstanceObjects 修改实例防护对象
func (this *ADPackageInstanceDAO) UpdateInstanceObjects(tx *dbs.Tx, instanceId int64, objectCodes []string) error {
if instanceId <= 0 {
return errors.New("invalid 'instanceId'")
}
if len(objectCodes) == 0 {
objectCodes = []string{}
}
objectCodesJSON, err := json.Marshal(objectCodes)
if err != nil {
return err
}
err = this.Query(tx).
Pk(instanceId).
Set("objectCodes", objectCodesJSON).
UpdateQuickly()
if err != nil {
return err
}
// 通知更新
return this.NotifyUpdate(tx, instanceId)
}
// FindAvailableObjectCodesInCluster 查询部署在某个集群上的防护对象
func (this *ADPackageInstanceDAO) FindAvailableObjectCodesInCluster(tx *dbs.Tx, clusterId int64) (allObjectCodes []string, err error) {
// TODO 考虑是否加入network和package条件
ones, err := this.Query(tx).
Result("objectCodes").
Attr("isOn", true).
State(ADPackageInstanceStateEnabled).
Attr("clusterId", clusterId).
Gt("userId", 0).
Gte("userDayTo", timeutil.Format("Ymd")).
FindAll()
if err != nil {
return nil, err
}
for _, one := range ones {
var objectCodesJSON = one.(*ADPackageInstance).ObjectCodes
if IsNotNull(objectCodesJSON) {
var objectCodes = []string{}
err = json.Unmarshal(objectCodesJSON, &objectCodes)
if err != nil {
return nil, err
}
for _, objectCode := range objectCodes {
if !lists.ContainsString(allObjectCodes, objectCode) {
allObjectCodes = append(allObjectCodes, objectCode)
}
}
}
}
return allObjectCodes, nil
}
// FindInstanceDeploy 读取实例部署信息
func (this *ADPackageInstanceDAO) FindInstanceDeploy(tx *dbs.Tx, instanceId int64) (*ADPackageInstance, error) {
if instanceId <= 0 {
return nil, nil
}
// 不加入state和isOn条件
one, err := this.Query(tx).
Pk(instanceId).
Result("clusterId", "nodeIds").
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*ADPackageInstance), nil
}
// NotifyUpdate 通知更新
func (this *ADPackageInstanceDAO) NotifyUpdate(tx *dbs.Tx, instanceId int64) error {
instance, err := this.FindInstanceDeploy(tx, instanceId)
if err != nil {
return err
}
if instance == nil {
return nil
}
if instance.ClusterId > 0 {
err = SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, int64(instance.ClusterId), 0, 0, NodeTaskTypeConfigChanged)
if err != nil {
return err
}
}
return nil
}

View File

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

View File

@@ -0,0 +1,36 @@
package models
import "github.com/iwind/TeaGo/dbs"
// ADPackageInstance 高防实例
type ADPackageInstance struct {
Id uint32 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
PackageId uint32 `field:"packageId"` // 规格ID
ClusterId uint32 `field:"clusterId"` // 集群ID
NodeIds dbs.JSON `field:"nodeIds"` // 节点ID
IpAddresses dbs.JSON `field:"ipAddresses"` // IP地址
UserId uint64 `field:"userId"` // 用户ID
UserDayTo string `field:"userDayTo"` // 用户有效期YYYYMMDD
UserInstanceId uint64 `field:"userInstanceId"` // 用户实例ID
State uint8 `field:"state"` // 状态
ObjectCodes dbs.JSON `field:"objectCodes"` // 防护对象
}
type ADPackageInstanceOperator struct {
Id any // ID
IsOn any // 是否启用
PackageId any // 规格ID
ClusterId any // 集群ID
NodeIds any // 节点ID
IpAddresses any // IP地址
UserId any // 用户ID
UserDayTo any // 用户有效期YYYYMMDD
UserInstanceId any // 用户实例ID
State any // 状态
ObjectCodes any // 防护对象
}
func NewADPackageInstanceOperator() *ADPackageInstanceOperator {
return &ADPackageInstanceOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -0,0 +1,43 @@
//go:build plus
package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
timeutil "github.com/iwind/TeaGo/utils/time"
)
func (this *ADPackageInstance) DecodeNodeIds() []int64 {
if IsNull(this.NodeIds) {
return []int64{}
}
var result = []int64{}
err := json.Unmarshal(this.NodeIds, &result)
if err != nil {
remotelogs.Error("ADPackageInstance.DecodeNodeIds", err.Error())
}
return result
}
func (this *ADPackageInstance) DecodeIPAddresses() []string {
if IsNull(this.IpAddresses) {
return []string{}
}
var result = []string{}
err := json.Unmarshal(this.IpAddresses, &result)
if err != nil {
remotelogs.Error("ADPackageInstance.DecodeIPAddresses", err.Error())
}
return result
}
func (this *ADPackageInstance) CurrentUser() (*User, error) {
if this.UserId > 0 && len(this.UserDayTo) > 0 && this.UserDayTo >= timeutil.Format("Ymd") {
return SharedUserDAO.FindEnabledBasicUser(nil, int64(this.UserId))
}
return nil, nil
}

View File

@@ -0,0 +1,32 @@
package models
// ADPackage 高防产品规格
type ADPackage struct {
Id uint32 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
NetworkId uint32 `field:"networkId"` // 线路ID
ProtectionBandwidthSize uint32 `field:"protectionBandwidthSize"` // 防护带宽尺寸
ProtectionBandwidthUnit string `field:"protectionBandwidthUnit"` // 防护带宽单位
ProtectionBandwidthBits uint64 `field:"protectionBandwidthBits"` // 防护带宽比特
ServerBandwidthSize uint32 `field:"serverBandwidthSize"` // 业务带宽尺寸
ServerBandwidthUnit string `field:"serverBandwidthUnit"` // 业务带宽单位
ServerBandwidthBits uint64 `field:"serverBandwidthBits"` // 业务带宽比特
State uint8 `field:"state"` // 状态
}
type ADPackageOperator struct {
Id any // ID
IsOn any // 是否启用
NetworkId any // 线路ID
ProtectionBandwidthSize any // 防护带宽尺寸
ProtectionBandwidthUnit any // 防护带宽单位
ProtectionBandwidthBits any // 防护带宽比特
ServerBandwidthSize any // 业务带宽尺寸
ServerBandwidthUnit any // 业务带宽单位
ServerBandwidthBits any // 业务带宽比特
State any // 状态
}
func NewADPackageOperator() *ADPackageOperator {
return &ADPackageOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -0,0 +1,30 @@
//go:build plus
package models
import (
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/types"
)
// Summary 当前高防产品概述
func (this *ADPackage) Summary(adNetwork *ADNetwork) string {
if adNetwork == nil {
adNetworkOne, err := SharedADNetworkDAO.Find(nil, this.NetworkId) // 这里不需要设置state条件
if err != nil {
remotelogs.Error("ADPackage", "Summary(): "+err.Error())
return ""
}
if adNetworkOne != nil {
adNetwork = adNetworkOne.(*ADNetwork)
}
}
var summary = ""
if adNetwork != nil {
summary += adNetwork.Name + "/"
}
summary += "防护" + types.String(this.ProtectionBandwidthSize) + userconfigs.ADPackageSizeFullUnit(this.ProtectionBandwidthUnit) + "/业务" + types.String(this.ServerBandwidthSize) + userconfigs.ADPackageSizeFullUnit(this.ServerBandwidthUnit)
return summary
}

View File

@@ -0,0 +1,63 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
ADPackagePeriodStateEnabled = 1 // 已启用
ADPackagePeriodStateDisabled = 0 // 已禁用
)
type ADPackagePeriodDAO dbs.DAO
func NewADPackagePeriodDAO() *ADPackagePeriodDAO {
return dbs.NewDAO(&ADPackagePeriodDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeADPackagePeriods",
Model: new(ADPackagePeriod),
PkName: "id",
},
}).(*ADPackagePeriodDAO)
}
var SharedADPackagePeriodDAO *ADPackagePeriodDAO
func init() {
dbs.OnReady(func() {
SharedADPackagePeriodDAO = NewADPackagePeriodDAO()
})
}
// EnableADPackagePeriod 启用条目
func (this *ADPackagePeriodDAO) EnableADPackagePeriod(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ADPackagePeriodStateEnabled).
Update()
return err
}
// DisableADPackagePeriod 禁用条目
func (this *ADPackagePeriodDAO) DisableADPackagePeriod(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", ADPackagePeriodStateDisabled).
Update()
return err
}
// FindEnabledADPackagePeriod 查找启用中的条目
func (this *ADPackagePeriodDAO) FindEnabledADPackagePeriod(tx *dbs.Tx, id int64) (*ADPackagePeriod, error) {
result, err := this.Query(tx).
Pk(id).
State(ADPackagePeriodStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*ADPackagePeriod), err
}

View File

@@ -0,0 +1,94 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package models
import (
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
"github.com/iwind/TeaGo/dbs"
)
// CreatePeriod 创建有效期
func (this *ADPackagePeriodDAO) CreatePeriod(tx *dbs.Tx, count int32, unit userconfigs.ADPackagePeriod) (int64, error) {
if !userconfigs.IsValidADPackagePeriod(unit) {
return 0, errors.New("invalid period unit '" + unit + "'")
}
var op = NewADPackagePeriodOperator()
op.Count = count
op.Unit = unit
op.Months = userconfigs.ADPackagePeriodToMonths(count, unit)
op.IsOn = true
op.State = ADPackageStateEnabled
return this.SaveInt64(tx, op)
}
// UpdatePeriod 修改有效期
func (this *ADPackagePeriodDAO) UpdatePeriod(tx *dbs.Tx, periodId int64, isOn bool) error {
if periodId <= 0 {
return errors.New("invalid periodId")
}
var op = NewADPackagePeriodOperator()
op.Id = periodId
op.IsOn = isOn
return this.Save(tx, op)
}
// FindAllPeriods 列出所有有效期
func (this *ADPackagePeriodDAO) FindAllPeriods(tx *dbs.Tx) (result []*ADPackagePeriod, err error) {
_, err = this.Query(tx).
State(ADPackagePeriodStateEnabled).
Asc("months").
Slice(&result).
FindAll()
if err != nil {
return nil, err
}
// 如果尚未有记录,就初始化若干个
if len(result) == 0 {
var count int64
count, err = this.Query(tx).Count()
if err != nil {
return nil, err
}
if count == 0 {
{
_, err = this.CreatePeriod(tx, 1, userconfigs.ADPackagePeriodMonth)
if err != nil {
return nil, err
}
}
{
_, err = this.CreatePeriod(tx, 1, userconfigs.ADPackagePeriodYear)
if err != nil {
return nil, err
}
}
}
// find again
_, err = this.Query(tx).
State(ADPackagePeriodStateEnabled).
Asc("months").
Slice(&result).
FindAll()
}
return
}
// FindAllAvailablePeriods 列出所有可用的有效期
func (this *ADPackagePeriodDAO) FindAllAvailablePeriods(tx *dbs.Tx) (result []*ADPackagePeriod, err error) {
_, err = this.Query(tx).
State(ADPackagePeriodStateEnabled).
Attr("isOn", true).
Asc("months").
Slice(&result).
FindAll()
return
}

View File

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

View File

@@ -0,0 +1,24 @@
package models
// ADPackagePeriod 高防产品有效期
type ADPackagePeriod struct {
Id uint32 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
Count uint32 `field:"count"` // 数量
Unit string `field:"unit"` // 单位month, year
Months uint32 `field:"months"` // 月数
State uint8 `field:"state"` // 状态
}
type ADPackagePeriodOperator struct {
Id any // ID
IsOn any // 是否启用
Count any // 数量
Unit any // 单位month, year
Months any // 月数
State any // 状态
}
func NewADPackagePeriodOperator() *ADPackagePeriodOperator {
return &ADPackagePeriodOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -0,0 +1,12 @@
//go:build plus
package models
import (
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
func (this *ADPackagePeriod) DayPeriod() (dayFrom string, dayTo string) {
return timeutil.Format("Ymd"), timeutil.Format("Ymd", time.Now().AddDate(0, int(this.Months), 0))
}

View File

@@ -0,0 +1,28 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
type ADPackagePriceDAO dbs.DAO
func NewADPackagePriceDAO() *ADPackagePriceDAO {
return dbs.NewDAO(&ADPackagePriceDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeADPackagePrices",
Model: new(ADPackagePrice),
PkName: "id",
},
}).(*ADPackagePriceDAO)
}
var SharedADPackagePriceDAO *ADPackagePriceDAO
func init() {
dbs.OnReady(func() {
SharedADPackagePriceDAO = NewADPackagePriceDAO()
})
}

View File

@@ -0,0 +1,78 @@
// Copyright 2023 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build plus
package models
import (
"errors"
"github.com/iwind/TeaGo/dbs"
)
// UpdatePackagePrice 设置高防产品价格
func (this *ADPackagePriceDAO) UpdatePackagePrice(tx *dbs.Tx, packageId int64, periodId int64, price float64) error {
if packageId <= 0 {
return errors.New("invalid packageId")
}
if periodId <= 0 {
return errors.New("invalid periodId")
}
oldPriceId, err := this.Query(tx).
ResultPk().
Attr("packageId", packageId).
Attr("periodId", periodId).
FindInt64Col(0)
if err != nil {
return err
}
var op = NewADPackagePriceOperator()
if oldPriceId > 0 {
op.Id = oldPriceId
}
op.PackageId = packageId
op.PeriodId = periodId
op.Price = price
return this.Save(tx, op)
}
// FindPackagePrice 获取单个高防产品具体价格
func (this *ADPackagePriceDAO) FindPackagePrice(tx *dbs.Tx, packageId int64, periodId int64) (float64, error) {
return this.Query(tx).
Result("price").
Attr("packageId", packageId).
Attr("periodId", periodId).
FindFloat64Col(0)
}
// CountPackagePrices 计算高防产品价格项数量
func (this *ADPackagePriceDAO) CountPackagePrices(tx *dbs.Tx, packageId int64) (count int64, err error) {
return this.Query(tx).
Attr("packageId", packageId).
Where("periodId IN (SELECT id FROM "+SharedADPackagePeriodDAO.Table+" WHERE state=1 AND isOn=1)").
Gt("price", 0).
Count()
}
// FindPackagePrices 查找高防产品价格
func (this *ADPackagePriceDAO) FindPackagePrices(tx *dbs.Tx, packageId int64) (result []*ADPackagePrice, err error) {
_, err = this.Query(tx).
Attr("packageId", packageId).
Where("periodId IN (SELECT id FROM "+SharedADPackagePeriodDAO.Table+" WHERE state=1 AND isOn=1)").
Gt("price", 0).
Slice(&result).
FindAll()
return
}
// FindAllPackagePrices 查找所有高防产品价格
// TODO 考虑是否加入 edgeADNetworks.state=1 AND edgeADNetworks.isOn=1 的条件
func (this *ADPackagePriceDAO) FindAllPackagePrices(tx *dbs.Tx) (result []*ADPackagePrice, err error) {
_, err = this.Query(tx).
Where("packageId IN (SELECT id FROM "+SharedADPackageDAO.Table+" WHERE state=1 AND isOn=1)").
Where("periodId IN (SELECT id FROM "+SharedADPackagePeriodDAO.Table+" WHERE state=1 AND isOn=1)").
Gt("price", 0).
Slice(&result).
FindAll()
return
}

View File

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

View File

@@ -0,0 +1,22 @@
package models
// ADPackagePrice 流量包价格
type ADPackagePrice struct {
Id uint32 `field:"id"` // ID
PackageId uint32 `field:"packageId"` // 高防产品ID
PeriodId uint32 `field:"periodId"` // 有效期ID
Price float64 `field:"price"` // 价格
DiscountPrice float64 `field:"discountPrice"` // 折后价格
}
type ADPackagePriceOperator struct {
Id any // ID
PackageId any // 高防产品ID
PeriodId any // 有效期ID
Price any // 价格
DiscountPrice any // 折后价格
}
func NewADPackagePriceOperator() *ADPackagePriceOperator {
return &ADPackagePriceOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -0,0 +1,347 @@
package models
import (
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
stringutil "github.com/iwind/TeaGo/utils/string"
)
const (
AdminStateEnabled = 1 // 已启用
AdminStateDisabled = 0 // 已禁用
)
type AdminDAO dbs.DAO
func NewAdminDAO() *AdminDAO {
return dbs.NewDAO(&AdminDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeAdmins",
Model: new(Admin),
PkName: "id",
},
}).(*AdminDAO)
}
var SharedAdminDAO *AdminDAO
func init() {
dbs.OnReady(func() {
SharedAdminDAO = NewAdminDAO()
})
}
// EnableAdmin 启用条目
func (this *AdminDAO) EnableAdmin(tx *dbs.Tx, id int64) (rowsAffected int64, err error) {
return this.Query(tx).
Pk(id).
Set("state", AdminStateEnabled).
Update()
}
// DisableAdmin 禁用条目
func (this *AdminDAO) DisableAdmin(tx *dbs.Tx, adminId int64) error {
err := this.Query(tx).
Pk(adminId).
Set("state", AdminStateDisabled).
UpdateQuickly()
if err != nil {
return err
}
// 删除AccessTokens
return SharedAPIAccessTokenDAO.DeleteAccessTokens(tx, adminId, 0)
}
// FindEnabledAdmin 查找启用中的条目
func (this *AdminDAO) FindEnabledAdmin(tx *dbs.Tx, id int64) (*Admin, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", AdminStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*Admin), err
}
// FindBasicAdmin 查找管理员基本信息
func (this *AdminDAO) FindBasicAdmin(tx *dbs.Tx, id int64) (*Admin, error) {
result, err := this.Query(tx).
Result("id", "username", "fullname").
Pk(id).
Attr("state", AdminStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*Admin), err
}
// ExistEnabledAdmin 检查管理员是否存在
func (this *AdminDAO) ExistEnabledAdmin(tx *dbs.Tx, adminId int64) (bool, error) {
return this.Query(tx).
Pk(adminId).
State(AdminStateEnabled).
Exist()
}
// FindAdminFullname 获取管理员名称
func (this *AdminDAO) FindAdminFullname(tx *dbs.Tx, adminId int64) (string, error) {
return this.Query(tx).
Pk(adminId).
Result("fullname").
FindStringCol("")
}
// CheckAdminPassword 检查用户名、密码
func (this *AdminDAO) CheckAdminPassword(tx *dbs.Tx, username string, encryptedPassword string) (int64, error) {
if len(username) == 0 || len(encryptedPassword) == 0 {
return 0, nil
}
return this.Query(tx).
Attr("username", username).
Attr("password", encryptedPassword).
Attr("state", AdminStateEnabled).
Attr("isOn", true).
Attr("canLogin", 1).
ResultPk().
FindInt64Col(0)
}
// FindAdminIdWithUsername 根据用户名查询管理员ID
func (this *AdminDAO) FindAdminIdWithUsername(tx *dbs.Tx, username string) (int64, error) {
one, err := this.Query(tx).
Attr("username", username).
State(AdminStateEnabled).
ResultPk().
Find()
if err != nil {
return 0, err
}
if one == nil {
return 0, nil
}
return int64(one.(*Admin).Id), nil
}
// FindAdminWithUsername 根据用户名查询管理员信息
func (this *AdminDAO) FindAdminWithUsername(tx *dbs.Tx, username string) (*Admin, error) {
one, err := this.Query(tx).
Attr("username", username).
State(AdminStateEnabled).
ResultPk().
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*Admin), nil
}
// UpdateAdminPassword 更改管理员密码
func (this *AdminDAO) UpdateAdminPassword(tx *dbs.Tx, adminId int64, password string) error {
if adminId <= 0 {
return errors.New("invalid adminId")
}
var op = NewAdminOperator()
op.Id = adminId
op.Password = stringutil.Md5(password)
err := this.Save(tx, op)
return err
}
// CreateAdmin 创建管理员
func (this *AdminDAO) CreateAdmin(tx *dbs.Tx, username string, canLogin bool, password string, fullname string, isSuper bool, modulesJSON []byte) (int64, error) {
var op = NewAdminOperator()
op.IsOn = true
op.State = AdminStateEnabled
op.Username = username
op.CanLogin = canLogin
op.Password = stringutil.Md5(password)
op.Fullname = fullname
op.IsSuper = isSuper
if len(modulesJSON) > 0 {
op.Modules = modulesJSON
} else {
op.Modules = "[]"
}
err := this.Save(tx, op)
if err != nil {
return 0, err
}
return types.Int64(op.Id), nil
}
// UpdateAdminInfo 修改管理员个人资料
func (this *AdminDAO) UpdateAdminInfo(tx *dbs.Tx, adminId int64, fullname string) error {
if adminId <= 0 {
return errors.New("invalid adminId")
}
var op = NewAdminOperator()
op.Id = adminId
op.Fullname = fullname
err := this.Save(tx, op)
return err
}
// UpdateAdmin 修改管理员详细信息
func (this *AdminDAO) UpdateAdmin(tx *dbs.Tx, adminId int64, username string, canLogin bool, password string, fullname string, isSuper bool, modulesJSON []byte, isOn bool) error {
if adminId <= 0 {
return errors.New("invalid adminId")
}
var op = NewAdminOperator()
op.Id = adminId
op.Fullname = fullname
op.Username = username
op.CanLogin = canLogin
if len(password) > 0 {
op.Password = stringutil.Md5(password)
}
op.IsSuper = isSuper
if len(modulesJSON) > 0 {
op.Modules = modulesJSON
} else {
op.Modules = "[]"
}
op.IsOn = isOn
err := this.Save(tx, op)
if err != nil {
return err
}
if !isOn {
// 删除AccessTokens
err = SharedAPIAccessTokenDAO.DeleteAccessTokens(tx, adminId, 0)
if err != nil {
return err
}
}
return nil
}
// CheckAdminUsername 检查管理员用户名是否存在
func (this *AdminDAO) CheckAdminUsername(tx *dbs.Tx, adminId int64, username string) (bool, error) {
query := this.Query(tx).
State(AdminStateEnabled).
Attr("username", username)
if adminId > 0 {
query.
Where("id!=:id").
Param("id", adminId)
}
return query.Exist()
}
// UpdateAdminLogin 修改管理员登录信息
func (this *AdminDAO) UpdateAdminLogin(tx *dbs.Tx, adminId int64, username string, password string) error {
if adminId <= 0 {
return errors.New("invalid adminId")
}
var op = NewAdminOperator()
op.Id = adminId
op.Username = username
if len(password) > 0 {
op.Password = stringutil.Md5(password)
}
err := this.Save(tx, op)
return err
}
// UpdateAdminModules 修改管理员可以管理的模块
func (this *AdminDAO) UpdateAdminModules(tx *dbs.Tx, adminId int64, allowModulesJSON []byte) error {
if adminId <= 0 {
return errors.New("invalid adminId")
}
var op = NewAdminOperator()
op.Id = adminId
op.Modules = allowModulesJSON
err := this.Save(tx, op)
if err != nil {
return err
}
return nil
}
// FindAllAdminModules 查询所有管理的权限
func (this *AdminDAO) FindAllAdminModules(tx *dbs.Tx) (result []*Admin, err error) {
_, err = this.Query(tx).
State(AdminStateEnabled).
Attr("isOn", true).
Result("id", "modules", "isSuper", "fullname", "theme", "lang").
Slice(&result).
FindAll()
return
}
// CountAllEnabledAdmins 计算所有管理员数量
func (this *AdminDAO) CountAllEnabledAdmins(tx *dbs.Tx, keyword string, hasWeakPasswords bool) (int64, error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Where("(username LIKE :keyword OR fullname LIKE :keyword)")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
if hasWeakPasswords {
query.Attr("password", weakPasswords)
query.Attr("isOn", true)
}
return query.
State(AdminStateEnabled).
Count()
}
// ListEnabledAdmins 列出单页的管理员
func (this *AdminDAO) ListEnabledAdmins(tx *dbs.Tx, keyword string, hasWeakPasswords bool, offset int64, size int64) (result []*Admin, err error) {
var query = this.Query(tx)
if len(keyword) > 0 {
query.Where("(username LIKE :keyword OR fullname LIKE :keyword)")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
if hasWeakPasswords {
query.Attr("password", weakPasswords)
query.Attr("isOn", true)
}
_, err = query.
State(AdminStateEnabled).
Result("id", "isOn", "username", "fullname", "isSuper", "createdAt", "canLogin", "password").
Offset(offset).
Limit(size).
DescPk().
Slice(&result).
FindAll()
return
}
// UpdateAdminTheme 设置管理员Theme
func (this *AdminDAO) UpdateAdminTheme(tx *dbs.Tx, adminId int64, theme string) error {
return this.Query(tx).
Pk(adminId).
Set("theme", theme).
UpdateQuickly()
}
// UpdateAdminLang 设置管理员语言
func (this *AdminDAO) UpdateAdminLang(tx *dbs.Tx, adminId int64, langCode string) error {
return this.Query(tx).
Pk(adminId).
Set("lang", langCode).
UpdateQuickly()
}
// CheckSuperAdmin 检查管理员是否为超级管理员
func (this *AdminDAO) CheckSuperAdmin(tx *dbs.Tx, adminId int64) (bool, error) {
if adminId <= 0 {
return false, nil
}
return this.Query(tx).
Pk(adminId).
State(AdminStateEnabled).
Attr("isSuper", true).
Exist()
}

View File

@@ -0,0 +1,5 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
)

View File

@@ -0,0 +1,56 @@
package models
import "github.com/iwind/TeaGo/dbs"
const (
AdminField_Id dbs.FieldName = "id" // ID
AdminField_IsOn dbs.FieldName = "isOn" // 是否启用
AdminField_Username dbs.FieldName = "username" // 用户名
AdminField_Password dbs.FieldName = "password" // 密码
AdminField_Fullname dbs.FieldName = "fullname" // 全名
AdminField_IsSuper dbs.FieldName = "isSuper" // 是否为超级管理员
AdminField_CreatedAt dbs.FieldName = "createdAt" // 创建时间
AdminField_UpdatedAt dbs.FieldName = "updatedAt" // 修改时间
AdminField_State dbs.FieldName = "state" // 状态
AdminField_Modules dbs.FieldName = "modules" // 允许的模块
AdminField_CanLogin dbs.FieldName = "canLogin" // 是否可以登录
AdminField_Theme dbs.FieldName = "theme" // 模板设置
AdminField_Lang dbs.FieldName = "lang" // 语言代号
)
// Admin 管理员
type Admin struct {
Id uint32 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
Username string `field:"username"` // 用户名
Password string `field:"password"` // 密码
Fullname string `field:"fullname"` // 全名
IsSuper bool `field:"isSuper"` // 是否为超级管理员
CreatedAt uint64 `field:"createdAt"` // 创建时间
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 状态
Modules dbs.JSON `field:"modules"` // 允许的模块
CanLogin bool `field:"canLogin"` // 是否可以登录
Theme string `field:"theme"` // 模板设置
Lang string `field:"lang"` // 语言代号
}
type AdminOperator struct {
Id any // ID
IsOn any // 是否启用
Username any // 用户名
Password any // 密码
Fullname any // 全名
IsSuper any // 是否为超级管理员
CreatedAt any // 创建时间
UpdatedAt any // 修改时间
State any // 状态
Modules any // 允许的模块
CanLogin any // 是否可以登录
Theme any // 模板设置
Lang any // 语言代号
}
func NewAdminOperator() *AdminOperator {
return &AdminOperator{}
}

View File

@@ -0,0 +1,42 @@
package models
import stringutil "github.com/iwind/TeaGo/utils/string"
// 弱密码集合
var weakPasswords = []string{}
func init() {
// 初始化弱密码集合
for _, password := range []string{
"123",
"1234",
"12345",
"123456",
"12345678",
"123456789",
"000000",
"111111",
"666666",
"888888",
"654321",
"123456789",
"password",
"qwerty",
"admin",
} {
weakPasswords = append(weakPasswords, stringutil.Md5(password))
}
}
func (this *Admin) HasWeakPassword() bool {
if len(this.Password) == 0 {
return false
}
for _, weakPassword := range weakPasswords {
if weakPassword == this.Password {
return true
}
}
return false
}

View File

@@ -0,0 +1,96 @@
package models
import (
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/rands"
"time"
)
type APIAccessTokenDAO dbs.DAO
func NewAPIAccessTokenDAO() *APIAccessTokenDAO {
return dbs.NewDAO(&APIAccessTokenDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeAPIAccessTokens",
Model: new(APIAccessToken),
PkName: "id",
},
}).(*APIAccessTokenDAO)
}
var SharedAPIAccessTokenDAO *APIAccessTokenDAO
func init() {
dbs.OnReady(func() {
SharedAPIAccessTokenDAO = NewAPIAccessTokenDAO()
})
}
// GenerateAccessToken 生成AccessToken
func (this *APIAccessTokenDAO) GenerateAccessToken(tx *dbs.Tx, adminId int64, userId int64) (token string, expiresAt int64, err error) {
if adminId <= 0 && userId <= 0 {
err = errors.New("either 'adminId' or 'userId' should not be zero")
return
}
if adminId > 0 {
userId = 0
}
if userId > 0 {
adminId = 0
}
// 查询以前的
accessToken, err := this.Query(tx).
Attr("adminId", adminId).
Attr("userId", userId).
Find()
if err != nil {
return "", 0, err
}
token = rands.String(128) // TODO 增强安全性,将来使用 base64_encode(encrypt(salt+random)) 算法来代替
expiresAt = time.Now().Unix() + 7200
var op = NewAPIAccessTokenOperator()
if accessToken != nil {
op.Id = accessToken.(*APIAccessToken).Id
}
op.AdminId = adminId
op.UserId = userId
op.Token = token
op.CreatedAt = time.Now().Unix()
op.ExpiredAt = expiresAt
err = this.Save(tx, op)
return
}
// FindAccessToken 查找AccessToken
func (this *APIAccessTokenDAO) FindAccessToken(tx *dbs.Tx, token string) (*APIAccessToken, error) {
one, err := this.Query(tx).
Attr("token", token).
Find()
if one == nil || err != nil {
return nil, err
}
return one.(*APIAccessToken), nil
}
// DeleteAccessTokens 删除用户的令牌
func (this *APIAccessTokenDAO) DeleteAccessTokens(tx *dbs.Tx, adminId int64, userId int64) error {
var query = this.Query(tx)
if adminId > 0 {
query.Attr("adminId", adminId)
} else if userId > 0 {
query.Attr("userId", userId)
} else {
return nil
}
return query.DeleteQuickly()
}

View File

@@ -0,0 +1,5 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
)

View File

@@ -0,0 +1,24 @@
package models
// APIAccessToken API访问令牌
type APIAccessToken struct {
Id uint64 `field:"id"` // ID
UserId uint32 `field:"userId"` // 用户ID
AdminId uint32 `field:"adminId"` // 管理员ID
Token string `field:"token"` // 令牌
CreatedAt uint64 `field:"createdAt"` // 创建时间
ExpiredAt uint64 `field:"expiredAt"` // 过期时间
}
type APIAccessTokenOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
AdminId interface{} // 管理员ID
Token interface{} // 令牌
CreatedAt interface{} // 创建时间
ExpiredAt interface{} // 过期时间
}
func NewAPIAccessTokenOperator() *APIAccessTokenOperator {
return &APIAccessTokenOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -0,0 +1,76 @@
package models
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type APIMethodStatDAO dbs.DAO
func NewAPIMethodStatDAO() *APIMethodStatDAO {
return dbs.NewDAO(&APIMethodStatDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeAPIMethodStats",
Model: new(APIMethodStat),
PkName: "id",
},
}).(*APIMethodStatDAO)
}
var SharedAPIMethodStatDAO *APIMethodStatDAO
func init() {
dbs.OnReady(func() {
SharedAPIMethodStatDAO = NewAPIMethodStatDAO()
})
}
// CreateStat 记录统计数据
func (this *APIMethodStatDAO) CreateStat(tx *dbs.Tx, method string, tag string, costMs float64) error {
var day = timeutil.Format("Ymd")
return this.Query(tx).
Param("costMs", costMs).
InsertOrUpdateQuickly(map[string]interface{}{
"apiNodeId": teaconst.NodeId,
"method": method,
"tag": tag,
"costMs": costMs,
"peekMs": costMs,
"countCalls": 1,
"day": day,
}, map[string]interface{}{
"costMs": dbs.SQL("(costMs*countCalls+:costMs)/(countCalls+1)"),
"peekMs": dbs.SQL("IF(peekMs>:costMs, peekMs, :costMs)"),
"countCalls": dbs.SQL("countCalls+1"),
})
}
// FindAllStatsWithDay 查询当前统计
func (this *APIMethodStatDAO) FindAllStatsWithDay(tx *dbs.Tx, day string) (result []*APIMethodStat, err error) {
_, err = this.Query(tx).
Attr("day", day).
Slice(&result).
FindAll()
return
}
// CountAllStatsWithDay 统计当天数量
func (this *APIMethodStatDAO) CountAllStatsWithDay(tx *dbs.Tx, day string) (int64, error) {
return this.Query(tx).
Attr("day", day).
Count()
}
// Clean 清理数据
func (this *APIMethodStatDAO) Clean(tx *dbs.Tx) error {
var day = timeutil.Format("Ymd")
_, err := this.Query(tx).
Param("day", day).
Where("day<:day").
Delete()
return err
}

View File

@@ -0,0 +1,19 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"testing"
)
func TestAPIMethodStatDAO_CreateStat(t *testing.T) {
var dao = NewAPIMethodStatDAO()
var tx *dbs.Tx
err := dao.CreateStat(tx, "/pb.Hello/World", "tag", 1.123)
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}

View File

@@ -0,0 +1,28 @@
package models
// APIMethodStat API方法统计
type APIMethodStat struct {
Id uint64 `field:"id"` // ID
ApiNodeId uint32 `field:"apiNodeId"` // API节点ID
Method string `field:"method"` // 方法
Tag string `field:"tag"` // 标签方法
CostMs float64 `field:"costMs"` // 耗时Ms
PeekMs float64 `field:"peekMs"` // 峰值耗时
CountCalls uint64 `field:"countCalls"` // 调用次数
Day string `field:"day"` // 日期
}
type APIMethodStatOperator struct {
Id interface{} // ID
ApiNodeId interface{} // API节点ID
Method interface{} // 方法
Tag interface{} // 标签方法
CostMs interface{} // 耗时Ms
PeekMs interface{} // 峰值耗时
CountCalls interface{} // 调用次数
Day interface{} // 日期
}
func NewAPIMethodStatOperator() *APIMethodStatOperator {
return &APIMethodStatOperator{}
}

View File

@@ -0,0 +1 @@
package models

View File

@@ -0,0 +1,518 @@
package models
import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/configs"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
"net"
"strconv"
"strings"
)
const (
APINodeStateEnabled = 1 // 已启用
APINodeStateDisabled = 0 // 已禁用
)
type APINodeDAO dbs.DAO
func NewAPINodeDAO() *APINodeDAO {
return dbs.NewDAO(&APINodeDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeAPINodes",
Model: new(APINode),
PkName: "id",
},
}).(*APINodeDAO)
}
var SharedAPINodeDAO *APINodeDAO
func init() {
dbs.OnReady(func() {
SharedAPINodeDAO = NewAPINodeDAO()
})
}
// EnableAPINode 启用条目
func (this *APINodeDAO) EnableAPINode(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", APINodeStateEnabled).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, id)
}
// DisableAPINode 禁用条目
func (this *APINodeDAO) DisableAPINode(tx *dbs.Tx, nodeId int64) error {
_, err := this.Query(tx).
Pk(nodeId).
Set("state", APINodeStateDisabled).
Update()
if err != nil {
return err
}
err = this.NotifyUpdate(tx, nodeId)
if err != nil {
return err
}
// 删除运行日志
return SharedNodeLogDAO.DeleteNodeLogs(tx, nodeconfigs.NodeRoleAPI, nodeId)
}
// FindEnabledAPINode 查找启用中的条目
func (this *APINodeDAO) FindEnabledAPINode(tx *dbs.Tx, id int64, cacheMap *utils.CacheMap) (*APINode, error) {
var cacheKey = this.Table + ":FindEnabledAPINode:" + types.String(id)
if cacheMap != nil {
cache, ok := cacheMap.Get(cacheKey)
if ok {
return cache.(*APINode), nil
}
}
result, err := this.Query(tx).
Pk(id).
Attr("state", APINodeStateEnabled).
Find()
if result == nil {
return nil, err
}
if cacheMap != nil {
cacheMap.Put(cacheKey, result)
}
return result.(*APINode), err
}
// FindEnabledAPINodeWithUniqueIdAndSecret 根据ID和Secret查找节点
func (this *APINodeDAO) FindEnabledAPINodeWithUniqueIdAndSecret(tx *dbs.Tx, uniqueId string, secret string) (*APINode, error) {
one, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("uniqueId", uniqueId).
Attr("secret", secret).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*APINode), nil
}
// FindAPINodeName 根据主键查找名称
func (this *APINodeDAO) FindAPINodeName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateAPINode 创建API节点
func (this *APINodeDAO) CreateAPINode(tx *dbs.Tx, name string, description string, httpJSON []byte, httpsJSON []byte, restIsOn bool, restHTTPJSON []byte, restHTTPSJSON []byte, accessAddrsJSON []byte, isOn bool) (nodeId int64, err error) {
uniqueId, err := this.genUniqueId(tx)
if err != nil {
return 0, err
}
secret := rands.String(32)
err = NewApiTokenDAO().CreateAPIToken(tx, uniqueId, secret, nodeconfigs.NodeRoleAPI)
if err != nil {
return
}
var op = NewAPINodeOperator()
op.IsOn = isOn
op.UniqueId = uniqueId
op.Secret = secret
op.Name = name
op.Description = description
if len(httpJSON) > 0 {
op.Http = httpJSON
}
if len(httpsJSON) > 0 {
op.Https = httpsJSON
}
op.RestIsOn = restIsOn
if len(restHTTPJSON) > 0 {
op.RestHTTP = restHTTPJSON
}
if len(restHTTPSJSON) > 0 {
op.RestHTTPS = restHTTPSJSON
}
if len(accessAddrsJSON) > 0 {
op.AccessAddrs = accessAddrsJSON
}
op.State = NodeStateEnabled
err = this.Save(tx, op)
if err != nil {
return
}
err = this.NotifyUpdate(tx, types.Int64(op.Id))
if err != nil {
remotelogs.Error("API_NODE_DAO", err.Error())
}
return types.Int64(op.Id), nil
}
// UpdateAPINode 修改API节点
func (this *APINodeDAO) UpdateAPINode(tx *dbs.Tx, nodeId int64, name string, description string, httpJSON []byte, httpsJSON []byte, restIsOn bool, restHTTPJSON []byte, restHTTPSJSON []byte, accessAddrsJSON []byte, isOn bool, isPrimary bool) error {
if nodeId <= 0 {
return errors.New("invalid nodeId")
}
// 取消别的Primary
if isPrimary {
err := this.Query(tx).
Neq("id", nodeId).
Attr("isPrimary", true).
Set("isPrimary", false).
UpdateQuickly()
if err != nil {
return err
}
}
var op = NewAPINodeOperator()
op.Id = nodeId
op.Name = name
op.Description = description
op.IsOn = isOn
if len(httpJSON) > 0 {
op.Http = httpJSON
} else {
op.Http = "{}"
}
if len(httpsJSON) > 0 {
op.Https = httpsJSON
} else {
op.Https = "{}"
}
op.RestIsOn = restIsOn
if len(restHTTPJSON) > 0 {
op.RestHTTP = restHTTPJSON
} else {
op.RestHTTP = "{}"
}
if len(restHTTPSJSON) > 0 {
op.RestHTTPS = restHTTPSJSON
} else {
op.RestHTTPS = "{}"
}
if len(accessAddrsJSON) > 0 {
op.AccessAddrs = accessAddrsJSON
} else {
op.AccessAddrs = "[]"
}
op.IsPrimary = isPrimary
err := this.Save(tx, op)
if err != nil {
return err
}
return this.NotifyUpdate(tx, nodeId)
}
// FindAllEnabledAPINodes 列出所有可用API节点
func (this *APINodeDAO) FindAllEnabledAPINodes(tx *dbs.Tx) (result []*APINode, err error) {
_, err = this.Query(tx).
Attr("clusterId", 0). // 非集群专用
State(APINodeStateEnabled).
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}
// FindAllEnabledAndOnAPINodes 列出所有可用而且启用的API节点
func (this *APINodeDAO) FindAllEnabledAndOnAPINodes(tx *dbs.Tx) (result []*APINode, err error) {
_, err = this.Query(tx).
Attr("clusterId", 0). // 非集群专用
Attr("isOn", true).
State(APINodeStateEnabled).
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}
// CountAllEnabledAPINodes 计算API节点数量
func (this *APINodeDAO) CountAllEnabledAPINodes(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
State(APINodeStateEnabled).
Count()
}
// CountAllEnabledAndOnAPINodes 计算启用中的API节点数量
func (this *APINodeDAO) CountAllEnabledAndOnAPINodes(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Count()
}
// CountAllEnabledAndOnOfflineAPINodes 计算API节点数量
func (this *APINodeDAO) CountAllEnabledAndOnOfflineAPINodes(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Where("(status IS NULL OR NOT JSON_EXTRACT(status, '$.isActive') OR UNIX_TIMESTAMP()-JSON_EXTRACT(status, '$.updatedAt')>60)").
Count()
}
// ListEnabledAPINodes 列出单页的API节点
func (this *APINodeDAO) ListEnabledAPINodes(tx *dbs.Tx, offset int64, size int64) (result []*APINode, err error) {
_, err = this.Query(tx).
Attr("clusterId", 0). // 非集群专用
State(APINodeStateEnabled).
Offset(offset).
Limit(size).
Desc("order").
DescPk().
Slice(&result).
FindAll()
return
}
// FindEnabledAPINodeIdWithAddr 根据主机名和端口获取ID
func (this *APINodeDAO) FindEnabledAPINodeIdWithAddr(tx *dbs.Tx, protocol string, host string, port int) (int64, error) {
addr := maps.Map{
"protocol": protocol,
"host": host,
"portRange": strconv.Itoa(port),
}
addrJSON, err := json.Marshal(addr)
if err != nil {
return 0, err
}
one, err := this.Query(tx).
State(APINodeStateEnabled).
Where("JSON_CONTAINS(accessAddrs, :addr)").
Param("addr", string(addrJSON)).
ResultPk().
Find()
if err != nil {
return 0, err
}
if one == nil {
return 0, nil
}
return int64(one.(*APINode).Id), nil
}
// UpdateAPINodeStatus 设置API节点状态
func (this *APINodeDAO) UpdateAPINodeStatus(tx *dbs.Tx, apiNodeId int64, statusJSON []byte) error {
_, err := this.Query(tx).
Pk(apiNodeId).
Set("status", statusJSON).
Update()
return err
}
// CountAllLowerVersionNodes 计算所有节点中低于某个版本的节点数量
func (this *APINodeDAO) CountAllLowerVersionNodes(tx *dbs.Tx, version string) (int64, error) {
return this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Where("status IS NOT NULL").
Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)").
Param("version", utils.VersionToLong(version)).
Count()
}
// CountAllEnabledAPINodesWithSSLPolicyIds 计算使用SSL策略的所有API节点数量
func (this *APINodeDAO) CountAllEnabledAPINodesWithSSLPolicyIds(tx *dbs.Tx, sslPolicyIds []int64) (count int64, err error) {
if len(sslPolicyIds) == 0 {
return
}
policyStringIds := []string{}
for _, policyId := range sslPolicyIds {
policyStringIds = append(policyStringIds, strconv.FormatInt(policyId, 10))
}
return this.Query(tx).
State(APINodeStateEnabled).
Where("(FIND_IN_SET(JSON_EXTRACT(https, '$.sslPolicyRef.sslPolicyId'), :policyIds) OR FIND_IN_SET(JSON_EXTRACT(restHTTPS, '$.sslPolicyRef.sslPolicyId'), :policyIds))").
Param("policyIds", strings.Join(policyStringIds, ",")).
Count()
}
// FindAllEnabledAPIAccessIPs 获取所有的API可访问IP地址
func (this *APINodeDAO) FindAllEnabledAPIAccessIPs(tx *dbs.Tx, cacheMap *utils.CacheMap) ([]string, error) {
var cacheKey = this.Table + ":FindAllEnabledAPIAccessIPs"
if cacheMap != nil {
cache, ok := cacheMap.Get(cacheKey)
if ok {
return cache.([]string), nil
}
}
ones, _, err := this.Query(tx).
State(APINodeStateEnabled).
Result("JSON_EXTRACT(accessAddrs, '$[*].host') AS host").
FindOnes()
if err != nil {
return nil, err
}
var result = []string{}
for _, one := range ones {
var host = one.GetString("host")
if len(host) == 0 {
continue
}
var ips = []string{}
err = json.Unmarshal([]byte(host), &ips)
if err != nil {
continue
}
for _, ip := range ips {
if !lists.ContainsString(result, ip) {
if net.ParseIP(ip) == nil {
continue
}
result = append(result, ip)
}
}
}
if cacheMap != nil {
cacheMap.Put(cacheKey, result)
}
return result, nil
}
// CheckAPINodeIsPrimary 检查当前节点是否为Primary节点
func (this *APINodeDAO) CheckAPINodeIsPrimary(tx *dbs.Tx) (bool, error) {
config, err := configs.SharedAPIConfig()
if err != nil {
return false, err
}
isPrimary, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("uniqueId", config.NodeId).
Attr("isPrimary", true).
Exist()
if err != nil {
return false, err
}
if isPrimary {
return true, nil
}
// 检查是否有别的Primary节点
count, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Attr("isPrimary", true).
Count()
if err != nil {
return false, err
}
if count == 0 {
err = this.ResetPrimaryAPINode(tx)
if err != nil {
return false, err
}
return true, nil
}
return false, nil
}
// CheckAPINodeIsPrimaryWithoutErr 检查当前节点是否为Primary节点并忽略错误
func (this *APINodeDAO) CheckAPINodeIsPrimaryWithoutErr() bool {
b, err := this.CheckAPINodeIsPrimary(nil)
return b && err == nil
}
// ResetPrimaryAPINode 重置Primary节点
func (this *APINodeDAO) ResetPrimaryAPINode(tx *dbs.Tx) error {
// 当前是否有Primary节点
apiNode, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Attr("isPrimary", true).
Find()
if err != nil {
return err
}
if apiNode == nil {
// 选择一个作为Primary
// TODO 将来需要考虑API节点离线的情况
apiNodeId, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
ResultPk().
FindInt64Col(0)
if err != nil {
return err
}
if apiNodeId > 0 {
err = this.Query(tx).
Pk(apiNodeId).
Set("isPrimary", true).
UpdateQuickly()
if err != nil {
return err
}
}
}
return nil
}
// NotifyUpdate 通知变更
func (this *APINodeDAO) NotifyUpdate(tx *dbs.Tx, apiNodeId int64) error {
// suppress IDE warning
_ = apiNodeId
err := this.ResetPrimaryAPINode(tx)
if err != nil {
return err
}
return nil
}
// 生成唯一ID
func (this *APINodeDAO) genUniqueId(tx *dbs.Tx) (string, error) {
for {
uniqueId := rands.HexString(32)
ok, err := this.Query(tx).
Attr("uniqueId", uniqueId).
Exist()
if err != nil {
return "", err
}
if ok {
continue
}
return uniqueId, nil
}
}

View File

@@ -0,0 +1,52 @@
package models
import (
"github.com/TeaOSLab/EdgeAPI/internal/utils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/dbs"
"runtime"
"testing"
)
func TestAPINodeDAO_FindEnabledAPINodeIdWithAddr(t *testing.T) {
dao := NewAPINodeDAO()
var tx *dbs.Tx
{
apiNodeId, err := dao.FindEnabledAPINodeIdWithAddr(tx, "http", "127.0.0.1", 123)
if err != nil {
t.Fatal(err)
}
t.Log("apiNodeId:", apiNodeId)
}
{
apiNodeId, err := dao.FindEnabledAPINodeIdWithAddr(tx, "http", "127.0.0.1", 8003)
if err != nil {
t.Fatal(err)
}
t.Log("apiNodeId:", apiNodeId)
}
}
func TestAPINodeDAO_FindAllEnabledAPIAccessIPs(t *testing.T) {
var cacheMap = utils.NewCacheMap()
t.Log(NewAPINodeDAO().FindAllEnabledAPIAccessIPs(nil, cacheMap))
t.Log(NewAPINodeDAO().FindAllEnabledAPIAccessIPs(nil, cacheMap))
}
func TestAPINodeDAO_CheckAPINodeIsPrimary(t *testing.T) {
var dao = NewAPINodeDAO()
t.Log(dao.CheckAPINodeIsPrimary(nil))
}
func TestAPINodeDAO_ResetPrimaryAPINode(t *testing.T) {
var dao = NewAPINodeDAO()
t.Log(dao.ResetPrimaryAPINode(nil))
}
func BenchmarkAPINodeDAO_New(b *testing.B) {
runtime.GOMAXPROCS(1)
for i := 0; i < b.N; i++ {
_ = NewAPINodeDAO()
}
}

View File

@@ -0,0 +1,54 @@
package models
import "github.com/iwind/TeaGo/dbs"
// APINode API节点
type APINode struct {
Id uint32 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
ClusterId uint32 `field:"clusterId"` // 专用集群ID
UniqueId string `field:"uniqueId"` // 唯一ID
Secret string `field:"secret"` // 密钥
Name string `field:"name"` // 名称
Description string `field:"description"` // 描述
Http dbs.JSON `field:"http"` // 监听的HTTP配置
Https dbs.JSON `field:"https"` // 监听的HTTPS配置
RestIsOn uint8 `field:"restIsOn"` // 是否开放REST
RestHTTP dbs.JSON `field:"restHTTP"` // REST HTTP配置
RestHTTPS dbs.JSON `field:"restHTTPS"` // REST HTTPS配置
AccessAddrs dbs.JSON `field:"accessAddrs"` // 外部访问地址
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
AdminId uint32 `field:"adminId"` // 管理员ID
Weight uint32 `field:"weight"` // 权重
Status dbs.JSON `field:"status"` // 运行状态
IsPrimary bool `field:"isPrimary"` // 是否为主API节点
}
type APINodeOperator struct {
Id interface{} // ID
IsOn interface{} // 是否启用
ClusterId interface{} // 专用集群ID
UniqueId interface{} // 唯一ID
Secret interface{} // 密钥
Name interface{} // 名称
Description interface{} // 描述
Http interface{} // 监听的HTTP配置
Https interface{} // 监听的HTTPS配置
RestIsOn interface{} // 是否开放REST
RestHTTP interface{} // REST HTTP配置
RestHTTPS interface{} // REST HTTPS配置
AccessAddrs interface{} // 外部访问地址
Order interface{} // 排序
State interface{} // 状态
CreatedAt interface{} // 创建时间
AdminId interface{} // 管理员ID
Weight interface{} // 权重
Status interface{} // 运行状态
IsPrimary interface{} // 是否为主API节点
}
func NewAPINodeOperator() *APINodeOperator {
return &APINodeOperator{}
}

View File

@@ -0,0 +1,163 @@
package models
import (
"context"
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/dbs"
)
// DecodeHTTP 解析HTTP配置
func (this *APINode) DecodeHTTP() (*serverconfigs.HTTPProtocolConfig, error) {
if !IsNotNull(this.Http) {
return nil, nil
}
config := &serverconfigs.HTTPProtocolConfig{}
err := json.Unmarshal(this.Http, config)
if err != nil {
return nil, err
}
err = config.Init()
if err != nil {
return nil, err
}
return config, nil
}
// DecodeHTTPS 解析HTTPS配置
func (this *APINode) DecodeHTTPS(tx *dbs.Tx, cacheMap *utils.CacheMap) (*serverconfigs.HTTPSProtocolConfig, error) {
if !IsNotNull(this.Https) {
return nil, nil
}
config := &serverconfigs.HTTPSProtocolConfig{}
err := json.Unmarshal(this.Https, config)
if err != nil {
return nil, err
}
err = config.Init(context.TODO())
if err != nil {
return nil, err
}
if config.SSLPolicyRef != nil {
var policyId = config.SSLPolicyRef.SSLPolicyId
if policyId > 0 {
sslPolicy, err := SharedSSLPolicyDAO.ComposePolicyConfig(tx, policyId, false, nil, cacheMap)
if err != nil {
return nil, err
}
if sslPolicy != nil {
config.SSLPolicy = sslPolicy
}
}
}
err = config.Init(context.TODO())
if err != nil {
return nil, err
}
return config, nil
}
// DecodeAccessAddrs 解析访问地址
func (this *APINode) DecodeAccessAddrs() ([]*serverconfigs.NetworkAddressConfig, error) {
if !IsNotNull(this.AccessAddrs) {
return nil, nil
}
addrConfigs := []*serverconfigs.NetworkAddressConfig{}
err := json.Unmarshal(this.AccessAddrs, &addrConfigs)
if err != nil {
return nil, err
}
for _, addrConfig := range addrConfigs {
err = addrConfig.Init()
if err != nil {
return nil, err
}
}
return addrConfigs, nil
}
// DecodeAccessAddrStrings 解析访问地址,并返回字符串形式
func (this *APINode) DecodeAccessAddrStrings() ([]string, error) {
addrs, err := this.DecodeAccessAddrs()
if err != nil {
return nil, err
}
result := []string{}
for _, addr := range addrs {
result = append(result, addr.FullAddresses()...)
}
return result, nil
}
// DecodeRestHTTP 解析Rest HTTP配置
func (this *APINode) DecodeRestHTTP() (*serverconfigs.HTTPProtocolConfig, error) {
if this.RestIsOn != 1 {
return nil, nil
}
if !IsNotNull(this.RestHTTP) {
return nil, nil
}
config := &serverconfigs.HTTPProtocolConfig{}
err := json.Unmarshal(this.RestHTTP, config)
if err != nil {
return nil, err
}
err = config.Init()
if err != nil {
return nil, err
}
return config, nil
}
// DecodeRestHTTPS 解析HTTPS配置
func (this *APINode) DecodeRestHTTPS(tx *dbs.Tx, cacheMap *utils.CacheMap) (*serverconfigs.HTTPSProtocolConfig, error) {
if cacheMap == nil {
cacheMap = utils.NewCacheMap()
}
if this.RestIsOn != 1 {
return nil, nil
}
if !IsNotNull(this.RestHTTPS) {
return nil, nil
}
var config = &serverconfigs.HTTPSProtocolConfig{}
err := json.Unmarshal(this.RestHTTPS, config)
if err != nil {
return nil, err
}
err = config.Init(context.TODO())
if err != nil {
return nil, err
}
if config.SSLPolicyRef != nil {
policyId := config.SSLPolicyRef.SSLPolicyId
if policyId > 0 {
sslPolicy, err := SharedSSLPolicyDAO.ComposePolicyConfig(tx, policyId, false, nil, cacheMap)
if err != nil {
return nil, err
}
if sslPolicy != nil {
config.SSLPolicy = sslPolicy
}
}
}
err = config.Init(context.TODO())
if err != nil {
return nil, err
}
return config, nil
}

View File

@@ -0,0 +1,132 @@
package models
import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
ApiTokenStateEnabled = 1 // 已启用
ApiTokenStateDisabled = 0 // 已禁用
)
var apiTokenCacheMap = map[string]*ApiToken{} // uniqueId => ApiToken
type ApiTokenDAO dbs.DAO
func NewApiTokenDAO() *ApiTokenDAO {
return dbs.NewDAO(&ApiTokenDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeAPITokens",
Model: new(ApiToken),
PkName: "id",
},
}).(*ApiTokenDAO)
}
var SharedApiTokenDAO *ApiTokenDAO
func init() {
dbs.OnReady(func() {
SharedApiTokenDAO = NewApiTokenDAO()
})
}
// EnableApiToken 启用条目
func (this *ApiTokenDAO) EnableApiToken(tx *dbs.Tx, id uint32) (rowsAffected int64, err error) {
return this.Query(tx).
Pk(id).
Set("state", ApiTokenStateEnabled).
Update()
}
// DisableApiToken 禁用条目
func (this *ApiTokenDAO) DisableApiToken(tx *dbs.Tx, id uint32) (rowsAffected int64, err error) {
return this.Query(tx).
Pk(id).
Set("state", ApiTokenStateDisabled).
Update()
}
// FindEnabledApiToken 查找启用中的条目
func (this *ApiTokenDAO) FindEnabledApiToken(tx *dbs.Tx, id uint32) (*ApiToken, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", ApiTokenStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*ApiToken), err
}
// FindEnabledTokenWithNodeCacheable 获取可缓存的节点Token信息
func (this *ApiTokenDAO) FindEnabledTokenWithNodeCacheable(tx *dbs.Tx, nodeId string) (*ApiToken, error) {
SharedCacheLocker.RLock()
token, ok := apiTokenCacheMap[nodeId]
if ok {
SharedCacheLocker.RUnlock()
return token, nil
}
SharedCacheLocker.RUnlock()
one, err := this.Query(tx).
Attr("nodeId", nodeId).
State(ApiTokenStateEnabled).
Find()
if one != nil {
token = one.(*ApiToken)
SharedCacheLocker.Lock()
apiTokenCacheMap[nodeId] = token
SharedCacheLocker.Unlock()
return token, nil
}
return nil, err
}
// FindEnabledTokenWithNode 获取节点Token信息并可以缓存
func (this *ApiTokenDAO) FindEnabledTokenWithNode(tx *dbs.Tx, nodeId string) (*ApiToken, error) {
one, err := this.Query(tx).
Attr("nodeId", nodeId).
State(ApiTokenStateEnabled).
Find()
if one != nil {
return one.(*ApiToken), nil
}
return nil, err
}
// FindEnabledTokenWithRole 根据角色获取节点
func (this *ApiTokenDAO) FindEnabledTokenWithRole(tx *dbs.Tx, role string) (*ApiToken, error) {
one, err := this.Query(tx).
Attr("role", role).
State(ApiTokenStateEnabled).
Find()
if one != nil {
return one.(*ApiToken), nil
}
return nil, err
}
// CreateAPIToken 保存API Token
func (this *ApiTokenDAO) CreateAPIToken(tx *dbs.Tx, nodeId string, secret string, role nodeconfigs.NodeRole) error {
var op = NewApiTokenOperator()
op.NodeId = nodeId
op.Secret = secret
op.Role = role
op.State = ApiTokenStateEnabled
err := this.Save(tx, op)
return err
}
// FindAllEnabledAPITokens 读取API令牌
func (this *ApiTokenDAO) FindAllEnabledAPITokens(tx *dbs.Tx, role string) (result []*ApiToken, err error) {
_, err = this.Query(tx).
Attr("role", role).
State(ApiTokenStateEnabled).
Slice(&result).
FindAll()
return
}

View File

@@ -0,0 +1,5 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
)

View File

@@ -0,0 +1,22 @@
package models
// API令牌管理
type ApiToken struct {
Id uint32 `field:"id"` // ID
NodeId string `field:"nodeId"` // 节点ID
Secret string `field:"secret"` // 节点密钥
Role string `field:"role"` // 节点角色
State uint8 `field:"state"` // 状态
}
type ApiTokenOperator struct {
Id interface{} // ID
NodeId interface{} // 节点ID
Secret interface{} // 节点密钥
Role interface{} // 节点角色
State interface{} // 状态
}
func NewApiTokenOperator() *ApiTokenOperator {
return &ApiTokenOperator{}
}

View File

@@ -0,0 +1,31 @@
package authority
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
type AuthorityKeyDAO dbs.DAO
func NewAuthorityKeyDAO() *AuthorityKeyDAO {
return dbs.NewDAO(&AuthorityKeyDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeAuthorityKeys",
Model: new(AuthorityKey),
PkName: "id",
},
}).(*AuthorityKeyDAO)
}
var SharedAuthorityKeyDAO *AuthorityKeyDAO
func init() {
dbs.OnReady(func() {
SharedAuthorityKeyDAO = NewAuthorityKeyDAO()
// 初始化IsPlus值
_, _ = SharedAuthorityKeyDAO.IsPlus(nil)
})
}

View File

@@ -0,0 +1,14 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
// +build !plus
package authority
import (
"github.com/iwind/TeaGo/dbs"
)
// IsPlus 判断是否为企业版
func (this *AuthorityKeyDAO) IsPlus(tx *dbs.Tx) (bool, error) {
return false, nil
}

Some files were not shown because too many files have changed in this diff Show More