Initial commit (code only without large binaries)

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

View File

@@ -0,0 +1,33 @@
package nameservers
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSDomainStateEnabled = 1 // 已启用
NSDomainStateDisabled = 0 // 已禁用
)
type NSDomainDAO dbs.DAO
func NewNSDomainDAO() *NSDomainDAO {
return dbs.NewDAO(&NSDomainDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSDomains",
Model: new(NSDomain),
PkName: "id",
},
}).(*NSDomainDAO)
}
var SharedNSDomainDAO *NSDomainDAO
func init() {
dbs.OnReady(func() {
SharedNSDomainDAO = NewNSDomainDAO()
})
}

View File

@@ -0,0 +1,613 @@
//go:build plus
package nameservers
import (
"encoding/json"
"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/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"sort"
"strings"
"time"
)
// EnableNSDomain 启用条目
func (this *NSDomainDAO) EnableNSDomain(tx *dbs.Tx, domainId int64) error {
_, err := this.Query(tx).
Pk(domainId).
Set("state", NSDomainStateEnabled).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, domainId)
}
// DisableNSDomain 禁用条目
func (this *NSDomainDAO) DisableNSDomain(tx *dbs.Tx, domainId int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(domainId).
Set("state", NSDomainStateDisabled).
Set("version", version).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, domainId)
}
// FindEnabledNSDomain 查找启用中的条目
func (this *NSDomainDAO) FindEnabledNSDomain(tx *dbs.Tx, id int64) (*NSDomain, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSDomainStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSDomain), err
}
// FindNSDomainName 根据主键查找名称
func (this *NSDomainDAO) FindNSDomainName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateDomain 创建域名
func (this *NSDomainDAO) CreateDomain(tx *dbs.Tx, clusterId int64, userId int64, groupIds []int64, name string, status dnsconfigs.NSDomainStatus) (int64, error) {
version, err := this.IncreaseVersion(tx)
if err != nil {
return 0, err
}
var op = NewNSDomainOperator()
op.ClusterId = clusterId
op.UserId = userId
// group ids
if groupIds == nil {
groupIds = []int64{}
}
groupIdsJSON, err := json.Marshal(groupIds)
if err != nil {
return 0, err
}
op.GroupIds = groupIdsJSON
op.Name = strings.ToLower(name)
op.Version = version
op.IsOn = true
if len(status) == 0 {
op.Status = dnsconfigs.NSDomainStatusNone
} else {
op.Status = status
}
op.State = NSDomainStateEnabled
domainId, err := this.SaveInt64(tx, op)
if err != nil {
return 0, err
}
err = this.NotifyUpdate(tx, domainId)
if err != nil {
return domainId, err
}
return domainId, nil
}
// UpdateDomain 修改域名
// 不能允许用户修改域名名称,因为要重新验证
func (this *NSDomainDAO) UpdateDomain(tx *dbs.Tx, domainId int64, clusterId int64, userId int64, groupIds []int64, isOn bool) error {
if domainId <= 0 {
return errors.New("invalid domainId")
}
oldClusterId, err := this.Query(tx).
Pk(domainId).
Result("clusterId").
FindInt64Col(0)
if err != nil {
return err
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
var op = NewNSDomainOperator()
op.Id = domainId
// 如果集群为0表示不修改
if clusterId > 0 {
op.ClusterId = clusterId
}
op.UserId = userId
// group ids
if groupIds == nil {
groupIds = []int64{}
}
groupIdsJSON, err := json.Marshal(groupIds)
if err != nil {
return err
}
op.GroupIds = groupIdsJSON
op.IsOn = isOn
op.Version = version
err = this.Save(tx, op)
if err != nil {
return err
}
// 通知更新
if clusterId > 0 && oldClusterId > 0 && oldClusterId != clusterId {
err = models.SharedNSClusterDAO.NotifyUpdate(tx, oldClusterId)
if err != nil {
return err
}
}
return this.NotifyUpdate(tx, domainId)
}
// UpdateDomainStatus 修改域名状态
func (this *NSDomainDAO) UpdateDomainStatus(tx *dbs.Tx, domainId int64, status dnsconfigs.NSDomainStatus) error {
if !dnsconfigs.NSDomainStatusIsValid(status) {
return errors.New("invalid status '" + status + "'")
}
if domainId <= 0 {
return errors.New("invalid 'domainId'")
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
err = this.Query(tx).
Pk(domainId).
Set("version", version).
Set("status", status).
UpdateQuickly()
if err != nil {
return err
}
return this.NotifyUpdate(tx, domainId)
}
// CountAllEnabledDomains 计算域名数量
func (this *NSDomainDAO) CountAllEnabledDomains(tx *dbs.Tx, clusterId int64, userId int64, groupId int64, status dnsconfigs.NSDomainStatus, keyword string) (int64, error) {
query := this.Query(tx)
if clusterId > 0 {
query.Attr("clusterId", clusterId)
} else {
query.Where("clusterId IN (SELECT id FROM " + models.SharedNSClusterDAO.Table + " WHERE state=1)")
}
if userId > 0 {
query.Attr("userId", userId)
} else {
query.Where("(userId=0 OR userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1))")
}
if groupId > 0 {
query.JSONContains("groupIds", types.String(groupId))
}
if len(keyword) > 0 {
query.Where("(name LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
if len(status) > 0 {
query.Attr("status", status)
}
return query.
State(NSDomainStateEnabled).
Count()
}
// ListEnabledDomains 列出单页域名
func (this *NSDomainDAO) ListEnabledDomains(tx *dbs.Tx, clusterId int64, userId int64, groupId int64, keyword string, offset int64, size int64) (result []*NSDomain, err error) {
query := this.Query(tx)
if clusterId > 0 {
query.Attr("clusterId", clusterId)
} else {
query.Where("clusterId IN (SELECT id FROM " + models.SharedNSClusterDAO.Table + " WHERE state=1)")
}
if userId > 0 {
query.Attr("userId", userId)
} else {
query.Where("(userId=0 OR userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1))")
}
if groupId > 0 {
query.JSONContains("groupIds", types.String(groupId))
}
if len(keyword) > 0 {
query.Where("(name LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
_, err = query.
State(NSDomainStateEnabled).
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// IncreaseVersion 增加版本
func (this *NSDomainDAO) IncreaseVersion(tx *dbs.Tx) (int64, error) {
return models.SharedSysLockerDAO.Increase(tx, "NS_DOMAIN_VERSION", 1)
}
// ListDomainsAfterVersion 列出某个版本后的域名
func (this *NSDomainDAO) ListDomainsAfterVersion(tx *dbs.Tx, version int64, size int64) (result []*NSDomain, err error) {
if size <= 0 {
size = 10000
}
_, err = this.Query(tx).
Gte("version", version).
Limit(size).
Asc("version").
Slice(&result).
FindAll()
return
}
// FindDomainIdWithName 根据名称查找域名ID
func (this *NSDomainDAO) FindDomainIdWithName(tx *dbs.Tx, clusterId int64, userId int64, name string, ignoreUser bool) (int64, error) {
if len(name) == 0 {
return 0, nil
}
name = strings.ToLower(name)
var query = this.Query(tx)
if clusterId > 0 {
query.Attr("clusterId", clusterId)
}
if userId > 0 {
query.Attr("userId", userId)
} else {
if !ignoreUser {
// 很重要,防止影响其他用户
query.Attr("userId", 0)
}
}
return query.
Attr("name", name).
State(NSDomainStateEnabled).
ResultPk().
FindInt64Col(0)
}
// FindDomainWithName 根据名称查找域名
func (this *NSDomainDAO) FindDomainWithName(tx *dbs.Tx, userId int64, name string) (*NSDomain, error) {
if len(name) == 0 {
return nil, nil
}
var query = this.Query(tx)
one, err := query.
Attr("userId", userId).
Attr("name", name).
State(NSDomainStateEnabled).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*NSDomain), nil
}
// FindEnabledDomainTSIG 获取TSIG配置
func (this *NSDomainDAO) FindEnabledDomainTSIG(tx *dbs.Tx, domainId int64) ([]byte, error) {
tsig, err := this.Query(tx).
Pk(domainId).
Result("tsig").
FindStringCol("")
if err != nil {
return nil, err
}
return []byte(tsig), nil
}
// UpdateDomainTSIG 修改TSIG配置
func (this *NSDomainDAO) UpdateDomainTSIG(tx *dbs.Tx, domainId int64, tsigJSON []byte) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
err = this.Query(tx).
Pk(domainId).
Set("tsig", tsigJSON).
Set("version", version).
UpdateQuickly()
if err != nil {
return err
}
return this.NotifyUpdate(tx, domainId)
}
// FindEnabledDomainClusterId 获取域名的集群ID
func (this *NSDomainDAO) FindEnabledDomainClusterId(tx *dbs.Tx, domainId int64) (int64, error) {
return this.Query(tx).
Pk(domainId).
State(NSDomainStateEnabled).
Result("clusterId").
FindInt64Col(0)
}
// ExistUserDomain 检查域名是否存在
func (this *NSDomainDAO) ExistUserDomain(tx *dbs.Tx, userId int64, name string) (bool, error) {
name = strings.ToLower(name)
return this.Query(tx).
Attr("userId", userId).
Attr("name", name).
State(NSDomainStateEnabled).
Exist()
}
// ExistVerifiedDomain 检查是否有验证通过的域名存在
func (this *NSDomainDAO) ExistVerifiedDomain(tx *dbs.Tx, name string) (bool, error) {
name = strings.ToLower(name)
return this.Query(tx).
Attr("name", name).
State(NSDomainStateEnabled).
Attr("status", dnsconfigs.NSDomainStatusVerified).
Exist()
}
// CheckUserDomain 检查用户域名
func (this *NSDomainDAO) CheckUserDomain(tx *dbs.Tx, userId int64, domainId int64) error {
if userId <= 0 || domainId <= 0 {
return models.ErrNotFound
}
b, err := this.Query(tx).
Pk(domainId).
Attr("userId", userId).
State(NSDomainStateEnabled).
Exist()
if err != nil {
return err
}
if !b {
return models.ErrNotFound
}
return nil
}
// DisableDomainWithName 使用名称删除域名
func (this *NSDomainDAO) DisableDomainWithName(tx *dbs.Tx, name string) error {
if len(name) == 0 {
return nil
}
domainId, err := this.Query(tx).
ResultPk().
State(NSDomainStateEnabled).
Attr("userId", 0). // 不要删除用户的域名
Attr("name", name).
FindInt64Col(0)
if err != nil {
return err
}
if domainId == 0 {
return nil
}
return this.DisableNSDomain(tx, domainId)
}
// DisableUserDomainWithName 使用名称删除某个用户域名
func (this *NSDomainDAO) DisableUserDomainWithName(tx *dbs.Tx, userId int64, name string) error {
if userId <= 0 || len(name) == 0 {
return nil
}
domainId, err := this.Query(tx).
ResultPk().
State(NSDomainStateEnabled).
Attr("userId", userId).
Attr("name", name).
FindInt64Col(0)
if err != nil {
return err
}
if domainId == 0 {
return nil
}
return this.DisableNSDomain(tx, domainId)
}
// FindDomainVerifyingInfo 查找域名验证信息
func (this *NSDomainDAO) FindDomainVerifyingInfo(tx *dbs.Tx, domainId int64, autoCreate bool) (*NSDomain, error) {
one, err := this.Query(tx).
Pk(domainId).
State(NSDomainStateEnabled).
Result("verifyTXT", "verifyExpiresAt", "status").
Find()
if err != nil || one == nil {
return nil, err
}
var domain = one.(*NSDomain)
if autoCreate {
if domain.Status == dnsconfigs.NSDomainStatusNone && (len(domain.VerifyTXT) == 0 || int64(domain.VerifyExpiresAt) < time.Now().Unix()) {
// 生成一个
var txt = rands.HexString(32)
var expiresAt = time.Now().Unix() + 7200
err = this.Query(tx).
Pk(domainId).
Set("verifyTXT", txt).
Set("verifyExpiresAt", expiresAt).
UpdateQuickly()
if err != nil {
return nil, err
}
domain.VerifyTXT = txt
domain.VerifyExpiresAt = uint64(expiresAt)
}
}
return domain, nil
}
// FindVerifiedDomainWithName 查询验证过的域名
func (this *NSDomainDAO) FindVerifiedDomainWithName(tx *dbs.Tx, name string) (*NSDomain, error) {
one, err := this.Query(tx).
State(NSDomainStateEnabled).
Attr("name", name).
Attr("status", dnsconfigs.NSDomainStatusVerified).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*NSDomain), nil
}
// FindVerifiedDomainWithNameOnCluster 查询某个集群上验证过的域名
func (this *NSDomainDAO) FindVerifiedDomainWithNameOnCluster(tx *dbs.Tx, clusterId int64, name string) (*NSDomain, error) {
one, err := this.Query(tx).
State(NSDomainStateEnabled).
Attr("clusterId", clusterId).
Attr("name", name).
Attr("status", dnsconfigs.NSDomainStatusVerified).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*NSDomain), nil
}
// FindRecordsHealthCheckConfig 查找健康检查配置
func (this *NSDomainDAO) FindRecordsHealthCheckConfig(tx *dbs.Tx, domainId int64) (*dnsconfigs.NSRecordsHealthCheckConfig, error) {
if domainId <= 0 {
return nil, errors.New("not valid 'domainId'")
}
recordsHealthCheckConfigJSON, err := this.Query(tx).
Pk(domainId).
State(NSDomainStateEnabled).
Result("recordsHealthCheck").
FindJSONCol()
if err != nil {
return nil, err
}
var config = dnsconfigs.NewNSRecordsHealthCheckConfig()
if models.IsNull(recordsHealthCheckConfigJSON) {
return config, nil
}
err = json.Unmarshal(recordsHealthCheckConfigJSON, config)
if err != nil {
return nil, err
}
return config, nil
}
// UpdateRecordsHealthCheckConfig 修改域名健康检查配置
func (this *NSDomainDAO) UpdateRecordsHealthCheckConfig(tx *dbs.Tx, domainId int64, healthCheckConfig *dnsconfigs.NSRecordsHealthCheckConfig) error {
if domainId <= 0 {
return errors.New("not valid 'domainId'")
}
if healthCheckConfig == nil {
return errors.New("'healthCheckConfig' should not be nil")
}
healthCheckConfigJSON, err := json.Marshal(healthCheckConfig)
if err != nil {
return err
}
err = this.Query(tx).
Pk(domainId).
Set("recordsHealthCheck", healthCheckConfigJSON).
UpdateQuickly()
if err != nil {
return nil
}
// 恢复离线记录
if !healthCheckConfig.IsOn {
err = SharedNSRecordDAO.UpdateDomainRecordsUp(tx, domainId)
if err != nil {
return err
}
}
return nil
}
// FindDomainsWithHealthCheckOn 查找开启健康检查的域名
// TODO 如果域名非常多的情况下,需要优化性能
func (this *NSDomainDAO) FindDomainsWithHealthCheckOn(tx *dbs.Tx, queryPlanIds []int64) (result []*NSDomain, err error) {
var query = this.Query(tx).
Result("id", "userId", "recordsHealthCheck", "name").
State(NSDomainStateEnabled).
Attr("isOn", true).
Where(`JSON_CONTAINS(recordsHealthCheck, '{"isOn": true}')`)
if len(queryPlanIds) > 0 {
// 排序以便于让SQL尽可能得到重用
sort.Slice(queryPlanIds, func(i, j int) bool {
return queryPlanIds[i] < queryPlanIds[j]
})
var queryPlanStringIds = []string{}
for _, planId := range queryPlanIds {
queryPlanStringIds = append(queryPlanStringIds, types.String(planId))
}
query.Where("userId IN (SELECT userId FROM " + SharedNSUserPlanDAO.Table + " WHERE state=1 AND dayTo>=:dayTo AND planId IN (" + strings.Join(queryPlanStringIds, ",") + "))")
query.Param("dayTo", timeutil.Format("Ymd"))
}
_, err = query.
Slice(&result).
FindAll()
return
}
// NotifyUpdate 通知更改
func (this *NSDomainDAO) NotifyUpdate(tx *dbs.Tx, domainId int64) error {
clusterId, err := this.Query(tx).
Result("clusterId").
Pk(domainId).
FindInt64Col(0)
if err != nil {
return err
}
if clusterId > 0 {
return models.SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleDNS, clusterId, 0, 0, models.NSNodeTaskTypeDomainChanged)
}
return nil
}

View File

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

View File

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

View File

@@ -0,0 +1,149 @@
//go:build plus
package nameservers
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 (
NSDomainGroupStateEnabled = 1 // 已启用
NSDomainGroupStateDisabled = 0 // 已禁用
)
type NSDomainGroupDAO dbs.DAO
func NewNSDomainGroupDAO() *NSDomainGroupDAO {
return dbs.NewDAO(&NSDomainGroupDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSDomainGroups",
Model: new(NSDomainGroup),
PkName: "id",
},
}).(*NSDomainGroupDAO)
}
var SharedNSDomainGroupDAO *NSDomainGroupDAO
func init() {
dbs.OnReady(func() {
SharedNSDomainGroupDAO = NewNSDomainGroupDAO()
})
}
// EnableNSDomainGroup 启用条目
func (this *NSDomainGroupDAO) EnableNSDomainGroup(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSDomainGroupStateEnabled).
Update()
return err
}
// DisableNSDomainGroup 禁用条目
func (this *NSDomainGroupDAO) DisableNSDomainGroup(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSDomainGroupStateDisabled).
Update()
return err
}
// FindEnabledNSDomainGroup 查找启用中的条目
func (this *NSDomainGroupDAO) FindEnabledNSDomainGroup(tx *dbs.Tx, id int64) (*NSDomainGroup, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSDomainGroupStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSDomainGroup), err
}
// FindNSDomainGroupName 根据主键查找名称
func (this *NSDomainGroupDAO) FindNSDomainGroupName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateGroup 创建分组
func (this *NSDomainGroupDAO) CreateGroup(tx *dbs.Tx, userId int64, name string) (int64, error) {
var op = NewNSDomainGroupOperator()
op.UserId = userId
op.Name = name
op.State = NSDomainGroupStateEnabled
return this.SaveInt64(tx, op)
}
// UpdateGroup 修改分组
func (this *NSDomainGroupDAO) UpdateGroup(tx *dbs.Tx, groupId int64, name string, isOn bool) error {
if groupId <= 0 {
return errors.New("invalid groupId")
}
var op = NewNSDomainGroupOperator()
op.Id = groupId
op.Name = name
op.IsOn = isOn
return this.Save(tx, op)
}
// FindAllGroups 查找所有分组
func (this *NSDomainGroupDAO) FindAllGroups(tx *dbs.Tx, userId int64) (result []*NSDomainGroup, err error) {
_, err = this.Query(tx).
Attr("userId", userId).
State(NSDomainGroupStateEnabled).
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}
// CountAllAvailableGroups 计算可用分组数量
func (this *NSDomainGroupDAO) CountAllAvailableGroups(tx *dbs.Tx, userId int64) (int64, error) {
return this.Query(tx).
Attr("userId", userId).
Attr("isOn", true).
State(NSDomainGroupStateEnabled).
Count()
}
// FindAllAvailableGroups 查找所有分组
func (this *NSDomainGroupDAO) FindAllAvailableGroups(tx *dbs.Tx, userId int64) (result []*NSDomainGroup, err error) {
_, err = this.Query(tx).
Attr("userId", userId).
Attr("isOn", true).
State(NSDomainGroupStateEnabled).
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}
// CheckUserGroup 检查用户分组
func (this *NSDomainGroupDAO) CheckUserGroup(tx *dbs.Tx, userId int64, groupId int64) error {
if groupId <= 0 || userId <= 0 {
return models.ErrNotFound
}
b, err := this.Query(tx).
Pk(groupId).
State(NSDomainGroupStateEnabled).
Attr("userId", userId).
Exist()
if err != nil {
return err
}
if !b {
return models.ErrNotFound
}
return nil
}

View File

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

View File

@@ -0,0 +1,24 @@
package nameservers
// NSDomainGroup 域名分组
type NSDomainGroup struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
Name string `field:"name"` // 分组名称
IsOn bool `field:"isOn"` // 是否启用
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
}
type NSDomainGroupOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
Name interface{} // 分组名称
IsOn interface{} // 是否启用
Order interface{} // 排序
State interface{} // 状态
}
func NewNSDomainGroupOperator() *NSDomainGroupOperator {
return &NSDomainGroupOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -0,0 +1,42 @@
package nameservers
import "github.com/iwind/TeaGo/dbs"
// NSDomain DNS域名
type NSDomain struct {
Id uint64 `field:"id"` // ID
ClusterId uint32 `field:"clusterId"` // 集群ID
UserId uint32 `field:"userId"` // 用户ID
IsOn bool `field:"isOn"` // 是否启用
Name string `field:"name"` // 域名
GroupIds dbs.JSON `field:"groupIds"` // 分组ID
Tsig dbs.JSON `field:"tsig"` // TSIG配置
VerifyTXT string `field:"verifyTXT"` // 验证用的TXT
VerifyExpiresAt uint64 `field:"verifyExpiresAt"` // 验证TXT过期时间
RecordsHealthCheck dbs.JSON `field:"recordsHealthCheck"` // 记录健康检查设置
CreatedAt uint64 `field:"createdAt"` // 创建时间
Version uint64 `field:"version"` // 版本号
Status string `field:"status"` // 状态none|verified
State uint8 `field:"state"` // 状态
}
type NSDomainOperator struct {
Id any // ID
ClusterId any // 集群ID
UserId any // 用户ID
IsOn any // 是否启用
Name any // 域名
GroupIds any // 分组ID
Tsig any // TSIG配置
VerifyTXT any // 验证用的TXT
VerifyExpiresAt any // 验证TXT过期时间
RecordsHealthCheck any // 记录健康检查设置
CreatedAt any // 创建时间
Version any // 版本号
Status any // 状态none|verified
State any // 状态
}
func NewNSDomainOperator() *NSDomainOperator {
return &NSDomainOperator{}
}

View File

@@ -0,0 +1,20 @@
package nameservers
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
)
func (this *NSDomain) DecodeGroupIds() []int64 {
if models.IsNull(this.GroupIds) {
return nil
}
var result = []int64{}
err := json.Unmarshal(this.GroupIds, &result)
if err != nil {
remotelogs.Error("NSDomain", "DecodeGroupIds:"+err.Error())
}
return result
}

View File

@@ -0,0 +1,233 @@
//go:build plus
package nameservers
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSKeyStateEnabled = 1 // 已启用
NSKeyStateDisabled = 0 // 已禁用
)
type NSKeyDAO dbs.DAO
func NewNSKeyDAO() *NSKeyDAO {
return dbs.NewDAO(&NSKeyDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSKeys",
Model: new(NSKey),
PkName: "id",
},
}).(*NSKeyDAO)
}
var SharedNSKeyDAO *NSKeyDAO
func init() {
dbs.OnReady(func() {
SharedNSKeyDAO = NewNSKeyDAO()
})
}
// EnableNSKey 启用条目
func (this *NSKeyDAO) EnableNSKey(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSKeyStateEnabled).
Update()
return err
}
// DisableNSKey 禁用条目
func (this *NSKeyDAO) DisableNSKey(tx *dbs.Tx, keyId int64) error {
_, err := this.Query(tx).
Pk(keyId).
Set("state", NSKeyStateDisabled).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, keyId)
}
// FindEnabledNSKey 查找启用中的条目
func (this *NSKeyDAO) FindEnabledNSKey(tx *dbs.Tx, id int64) (*NSKey, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSKeyStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSKey), err
}
// FindNSKeyName 根据主键查找名称
func (this *NSKeyDAO) FindNSKeyName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateKey 创建Key
func (this *NSKeyDAO) CreateKey(tx *dbs.Tx, domainId int64, zoneId int64, name string, algo dnsconfigs.KeyAlgorithmType, secret string, secretType string) (int64, error) {
var op = NewNSKeyOperator()
op.DomainId = domainId
op.ZoneId = zoneId
op.Name = name
op.Algo = algo
op.Secret = secret
op.SecretType = secretType
op.State = NSKeyStateEnabled
keyId, err := this.SaveInt64(tx, op)
if err != nil {
return 0, err
}
err = this.NotifyUpdate(tx, keyId)
if err != nil {
return keyId, err
}
return keyId, nil
}
// UpdateKey 修改Key
func (this *NSKeyDAO) UpdateKey(tx *dbs.Tx, keyId int64, name string, algo dnsconfigs.KeyAlgorithmType, secret string, secretType string, isOn bool) error {
if keyId <= 0 {
return errors.New("invalid keyId")
}
var op = NewNSKeyOperator()
op.Id = keyId
op.Name = name
op.Algo = algo
op.Secret = secret
op.SecretType = secretType
op.IsOn = isOn
err := this.Save(tx, op)
if err != nil {
return err
}
return this.NotifyUpdate(tx, keyId)
}
// CountEnabledKeys 计算Key的数量
func (this *NSKeyDAO) CountEnabledKeys(tx *dbs.Tx, domainId int64, zoneId int64) (int64, error) {
var query = this.Query(tx).
State(NSKeyStateEnabled)
if domainId > 0 {
query.Attr("domainId", domainId)
}
if zoneId > 0 {
query.Attr("zoneId", zoneId)
}
return query.Count()
}
// ListEnabledKeys 列出单页Key
func (this *NSKeyDAO) ListEnabledKeys(tx *dbs.Tx, domainId int64, zoneId int64, offset int64, size int64) (result []*NSKey, err error) {
var query = this.Query(tx).
State(NSKeyStateEnabled)
if domainId > 0 {
query.Attr("domainId", domainId)
}
if zoneId > 0 {
query.Attr("zoneId", zoneId)
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// IncreaseVersion 增加版本
func (this *NSKeyDAO) IncreaseVersion(tx *dbs.Tx) (int64, error) {
return models.SharedSysLockerDAO.Increase(tx, "NS_KEY_VERSION", 1)
}
// ListKeysAfterVersion 列出某个版本后的密钥
func (this *NSKeyDAO) ListKeysAfterVersion(tx *dbs.Tx, version int64, size int64) (result []*NSKey, err error) {
if size <= 0 {
size = 10000
}
_, err = this.Query(tx).
Gte("version", version).
Limit(size).
Asc("version").
Slice(&result).
FindAll()
return
}
// CheckUserKey 检查用户权限
func (this *NSKeyDAO) CheckUserKey(tx *dbs.Tx, userId int64, keyId int64) error {
if userId <= 0 || keyId <= 0 {
return models.ErrNotFound
}
domainId, err := this.Query(tx).
Result("domainId").
Pk(keyId).
State(NSKeyStateEnabled).
FindInt64Col(0)
if err != nil {
return err
}
if domainId <= 0 {
return models.ErrNotFound
}
return SharedNSDomainDAO.CheckUserDomain(tx, userId, domainId)
}
// NotifyUpdate 通知更新
func (this *NSKeyDAO) NotifyUpdate(tx *dbs.Tx, keyId int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
err = this.Query(tx).
Pk(keyId).
Set("version", version).
UpdateQuickly()
if err != nil {
return err
}
// 通知集群
domainId, err := this.Query(tx).
Pk(keyId).
Result("domainId").
FindInt64Col(0)
if err != nil {
return err
}
if domainId > 0 {
clusterId, err := SharedNSDomainDAO.FindEnabledDomainClusterId(tx, domainId)
if err != nil {
return err
}
if clusterId > 0 {
err = models.SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleDNS, clusterId, 0, 0, models.NSNodeTaskTypeKeyChanged)
if err != nil {
return err
}
}
}
return nil
}

View File

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

View File

@@ -0,0 +1,32 @@
package nameservers
// NSKey 密钥管理
type NSKey struct {
Id uint64 `field:"id"` // ID
IsOn bool `field:"isOn"` // 状态
Name string `field:"name"` // 名称
DomainId uint64 `field:"domainId"` // 域名ID
ZoneId uint64 `field:"zoneId"` // 子域ID
Algo string `field:"algo"` // 算法
Secret string `field:"secret"` // 密码
SecretType string `field:"secretType"` // 密码类型
Version uint64 `field:"version"` // 版本号
State uint8 `field:"state"` // 状态
}
type NSKeyOperator struct {
Id interface{} // ID
IsOn interface{} // 状态
Name interface{} // 名称
DomainId interface{} // 域名ID
ZoneId interface{} // 子域ID
Algo interface{} // 算法
Secret interface{} // 密码
SecretType interface{} // 密码类型
Version interface{} // 版本号
State interface{} // 状态
}
func NewNSKeyOperator() *NSKeyOperator {
return &NSKeyOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -0,0 +1,168 @@
//go:build plus
package nameservers
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSPlanStateEnabled = 1 // 已启用
NSPlanStateDisabled = 0 // 已禁用
)
type NSPlanDAO dbs.DAO
func NewNSPlanDAO() *NSPlanDAO {
return dbs.NewDAO(&NSPlanDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSPlans",
Model: new(NSPlan),
PkName: "id",
},
}).(*NSPlanDAO)
}
var SharedNSPlanDAO *NSPlanDAO
func init() {
dbs.OnReady(func() {
SharedNSPlanDAO = NewNSPlanDAO()
})
}
// EnableNSPlan 启用条目
func (this *NSPlanDAO) EnableNSPlan(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSPlanStateEnabled).
Update()
return err
}
// DisableNSPlan 禁用条目
func (this *NSPlanDAO) DisableNSPlan(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSPlanStateDisabled).
Update()
return err
}
// FindEnabledNSPlan 查找启用中的条目
func (this *NSPlanDAO) FindEnabledNSPlan(tx *dbs.Tx, id int64) (*NSPlan, error) {
result, err := this.Query(tx).
Pk(id).
State(NSPlanStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSPlan), err
}
// FindNSPlanName 根据主键查找名称
func (this *NSPlanDAO) FindNSPlanName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreatePlan 创建套餐
func (this *NSPlanDAO) CreatePlan(tx *dbs.Tx, name string, monthlyPrice float32, yearlyPrice float32, config *dnsconfigs.NSPlanConfig) (int64, error) {
var op = NewNSPlanOperator()
op.Name = name
op.MonthlyPrice = monthlyPrice
op.YearlyPrice = yearlyPrice
configJSON, err := json.Marshal(config)
if err != nil {
return 0, err
}
op.Config = configJSON
op.IsOn = true
op.State = NSPlanStateEnabled
return this.SaveInt64(tx, op)
}
// UpdatePlan 修改套餐
func (this *NSPlanDAO) UpdatePlan(tx *dbs.Tx, planId int64, name string, isOn bool, monthlyPrice float32, yearlyPrice float32, config *dnsconfigs.NSPlanConfig) error {
if planId <= 0 {
return errors.New("invalid planId")
}
var op = NewNSPlanOperator()
op.Id = planId
op.Name = name
op.MonthlyPrice = monthlyPrice
op.YearlyPrice = yearlyPrice
configJSON, err := json.Marshal(config)
if err != nil {
return err
}
op.Config = configJSON
op.IsOn = isOn
return this.Save(tx, op)
}
// UpdatePlanOrders 修改套餐排序
func (this *NSPlanDAO) UpdatePlanOrders(tx *dbs.Tx, planIds []int64) error {
var total = len(planIds)
if total == 0 {
return nil
}
total++
for index, planId := range planIds {
err := this.Query(tx).
Pk(planId).
Set("order", total-index).
UpdateQuickly()
if err != nil {
return err
}
}
return nil
}
// FindAllPlans 查找所有套餐
func (this *NSPlanDAO) FindAllPlans(tx *dbs.Tx) (result []*NSPlan, err error) {
_, err = this.Query(tx).
State(NSPlanStateEnabled).
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}
// FindAllEnabledPlans 查找所有可用套餐
func (this *NSPlanDAO) FindAllEnabledPlans(tx *dbs.Tx) (result []*NSPlan, err error) {
_, err = this.Query(tx).
State(NSPlanStateEnabled).
Attr("isOn", true).
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}
// ExistPlan 检查套餐是否存在
func (this *NSPlanDAO) ExistPlan(tx *dbs.Tx, planId int64) (bool, error) {
if planId <= 0 {
return false, nil
}
return this.Query(tx).
Pk(planId).
Exist()
}

View File

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

View File

@@ -0,0 +1,30 @@
package nameservers
import "github.com/iwind/TeaGo/dbs"
// NSPlan NS套餐
type NSPlan struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 套餐名称
IsOn bool `field:"isOn"` // 是否启用
MonthlyPrice float64 `field:"monthlyPrice"` // 月价格
YearlyPrice float64 `field:"yearlyPrice"` // 年价格
Order uint32 `field:"order"` // 排序
Config dbs.JSON `field:"config"` // 配置
State uint8 `field:"state"` // 状态
}
type NSPlanOperator struct {
Id any // ID
Name any // 套餐名称
IsOn any // 是否启用
MonthlyPrice any // 月价格
YearlyPrice any // 年价格
Order any // 排序
Config any // 配置
State any // 状态
}
func NewNSPlanOperator() *NSPlanOperator {
return &NSPlanOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -0,0 +1,69 @@
//go:build plus
package nameservers
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
)
type NSQuestionOptionDAO dbs.DAO
func NewNSQuestionOptionDAO() *NSQuestionOptionDAO {
return dbs.NewDAO(&NSQuestionOptionDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSQuestionOptions",
Model: new(NSQuestionOption),
PkName: "id",
},
}).(*NSQuestionOptionDAO)
}
var SharedNSQuestionOptionDAO *NSQuestionOptionDAO
func init() {
dbs.OnReady(func() {
SharedNSQuestionOptionDAO = NewNSQuestionOptionDAO()
})
}
// FindNSQuestionOptionName 根据主键查找名称
func (this *NSQuestionOptionDAO) FindNSQuestionOptionName(tx *dbs.Tx, id uint64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateOption 创建选项
func (this *NSQuestionOptionDAO) CreateOption(tx *dbs.Tx, name string, values maps.Map) (int64, error) {
if values == nil {
values = maps.Map{}
}
var op = NewNSQuestionOptionOperator()
op.Name = name
op.Values = values.AsJSON()
return this.SaveInt64(tx, op)
}
// FindOption 读取选项
func (this *NSQuestionOptionDAO) FindOption(tx *dbs.Tx, optionId int64) (*NSQuestionOption, error) {
one, err := this.Query(tx).
Pk(optionId).
Find()
if one == nil {
return nil, err
}
return one.(*NSQuestionOption), nil
}
// DeleteOption 删除选项
func (this *NSQuestionOptionDAO) DeleteOption(tx *dbs.Tx, optionId int64) error {
_, err := this.Query(tx).
Pk(optionId).
Delete()
return err
}

View File

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

View File

@@ -0,0 +1,22 @@
package nameservers
import "github.com/iwind/TeaGo/dbs"
// NSQuestionOption DNS请求选项
type NSQuestionOption struct {
Id uint64 `field:"id"` // ID
Name string `field:"name"` // 选项名
Values dbs.JSON `field:"values"` // 选项值
CreatedAt uint64 `field:"createdAt"` // 创建时间
}
type NSQuestionOptionOperator struct {
Id interface{} // ID
Name interface{} // 选项名
Values interface{} // 选项值
CreatedAt interface{} // 创建时间
}
func NewNSQuestionOptionOperator() *NSQuestionOptionOperator {
return &NSQuestionOptionOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -0,0 +1,73 @@
//go:build !plus
package nameservers
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSRecordStateEnabled = 1 // 已启用
NSRecordStateDisabled = 0 // 已禁用
)
type NSRecordDAO dbs.DAO
func NewNSRecordDAO() *NSRecordDAO {
return dbs.NewDAO(&NSRecordDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSRecords",
Model: new(NSRecord),
PkName: "id",
},
}).(*NSRecordDAO)
}
var SharedNSRecordDAO *NSRecordDAO
func init() {
dbs.OnReady(func() {
SharedNSRecordDAO = NewNSRecordDAO()
})
}
// EnableNSRecord 启用条目
func (this *NSRecordDAO) EnableNSRecord(tx *dbs.Tx, id uint64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSRecordStateEnabled).
Update()
return err
}
// DisableNSRecord 禁用条目
func (this *NSRecordDAO) DisableNSRecord(tx *dbs.Tx, id uint64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSRecordStateDisabled).
Update()
return err
}
// FindEnabledNSRecord 查找启用中的条目
func (this *NSRecordDAO) FindEnabledNSRecord(tx *dbs.Tx, id uint64) (*NSRecord, error) {
result, err := this.Query(tx).
Pk(id).
State(NSRecordStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSRecord), err
}
// FindNSRecordName 根据主键查找名称
func (this *NSRecordDAO) FindNSRecordName(tx *dbs.Tx, id uint64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}

View File

@@ -0,0 +1,849 @@
//go:build plus
package nameservers
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"strconv"
"strings"
)
const (
NSRecordStateEnabled = 1 // 已启用
NSRecordStateDisabled = 0 // 已禁用
)
type NSRecordDAO dbs.DAO
func NewNSRecordDAO() *NSRecordDAO {
return dbs.NewDAO(&NSRecordDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSRecords",
Model: new(NSRecord),
PkName: "id",
},
}).(*NSRecordDAO)
}
var SharedNSRecordDAO *NSRecordDAO
func init() {
dbs.OnReady(func() {
SharedNSRecordDAO = NewNSRecordDAO()
})
}
// EnableNSRecord 启用条目
func (this *NSRecordDAO) EnableNSRecord(tx *dbs.Tx, recordId int64) error {
_, err := this.Query(tx).
Pk(recordId).
Set("state", NSRecordStateEnabled).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, recordId)
}
// DisableNSRecord 禁用条目
func (this *NSRecordDAO) DisableNSRecord(tx *dbs.Tx, recordId int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(recordId).
Set("state", NSRecordStateDisabled).
Set("version", version).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx, recordId)
}
// FindEnabledNSRecord 查找启用中的条目
func (this *NSRecordDAO) FindEnabledNSRecord(tx *dbs.Tx, id int64) (*NSRecord, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSRecordStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSRecord), err
}
// FindNSRecordName 根据主键查找名称
func (this *NSRecordDAO) FindNSRecordName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateRecord 创建记录
func (this *NSRecordDAO) CreateRecord(tx *dbs.Tx, domainId int64, description string, name string, recordType dnsconfigs.RecordType, value string, mxPriority int32, srvPriority int32, srvWeight int32, srvPort int32, caaFlag int32, caaTag string, ttl int32, routeIds []string, weight int32) (int64, error) {
// TTL默认值
// TODO 根据用户级别设置不同的TTL
if ttl <= 0 {
ttl = 600
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return 0, err
}
// 处理各个类型的值
recordType = strings.ToUpper(recordType)
switch recordType {
case dnsconfigs.RecordTypeCNAME:
if !strings.HasSuffix(value, ".") {
value += "."
}
}
var op = NewNSRecordOperator()
op.DomainId = domainId
op.Description = description
op.Name = name
op.Type = recordType
op.Value = value
// mx priority
if recordType == dnsconfigs.RecordTypeMX {
if mxPriority > 0 {
op.MxPriority = mxPriority
} else {
op.MxPriority = 0
}
}
// srv
if recordType == dnsconfigs.RecordTypeSRV {
op.SrvPriority = srvPriority
op.SrvWeight = srvWeight
op.SrvPort = srvPort
}
// caa
if recordType == dnsconfigs.RecordTypeCAA {
op.CaaFlag = caaFlag
op.CaaTag = caaTag
}
op.Ttl = ttl
if len(routeIds) == 0 {
op.RouteIds = `["default"]`
} else {
routeIds, err := json.Marshal(routeIds)
if err != nil {
return 0, err
}
op.RouteIds = routeIds
}
op.Weight = weight
op.IsOn = true
op.IsUp = true
op.State = NSRecordStateEnabled
op.Version = version
recordId, err := this.SaveInt64(tx, op)
if err != nil {
return 0, err
}
err = this.NotifyUpdate(tx, recordId)
if err != nil {
return 0, err
}
return recordId, nil
}
// UpdateRecord 修改记录
func (this *NSRecordDAO) UpdateRecord(tx *dbs.Tx, recordId int64, description string, name string, recordType dnsconfigs.RecordType, value string, mxPriority int32, srvPriority int32, srvWeight int32, srvPort int32, caaFlag int32, caaTag string, ttl int32, routeIds []string, weight int32, isOn bool) error {
// 以前信息
// 不需要增加状态条件
oldValue, err := this.Query(tx).
Pk(recordId).
Result("value").
FindStringCol("")
if err != nil {
return err
}
// TTL默认值
// TODO 根据用户级别设置不同的TTL
if ttl <= 0 {
ttl = 600
}
if recordId <= 0 {
return errors.New("invalid recordId")
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
// 处理各个类型的值
recordType = strings.ToUpper(recordType)
switch recordType {
case dnsconfigs.RecordTypeCNAME:
if !strings.HasSuffix(value, ".") {
value += "."
}
}
var op = NewNSRecordOperator()
op.Id = recordId
op.Description = description
op.Name = name
op.Type = recordType
op.Value = value
// mx priority
if recordType == dnsconfigs.RecordTypeMX {
if mxPriority > 0 {
op.MxPriority = mxPriority
} else {
op.MxPriority = 0
}
}
// srv
if recordType == dnsconfigs.RecordTypeSRV {
op.SrvPriority = srvPriority
op.SrvWeight = srvWeight
op.SrvPort = srvPort
}
// caa
if recordType == dnsconfigs.RecordTypeCAA {
op.CaaFlag = caaFlag
op.CaaTag = caaTag
}
op.Ttl = ttl
op.Weight = weight
op.IsOn = isOn
if oldValue != value {
op.IsUp = true
}
if len(routeIds) == 0 {
op.RouteIds = `["default"]`
} else {
routeIdsJSON, err := json.Marshal(routeIds)
if err != nil {
return err
}
op.RouteIds = routeIdsJSON
}
op.Version = version
err = this.Save(tx, op)
if err != nil {
return err
}
return this.NotifyUpdate(tx, recordId)
}
// CountAllEnabledDomainRecords 计算域名中记录数量
func (this *NSRecordDAO) CountAllEnabledDomainRecords(tx *dbs.Tx, domainId int64, dnsType dnsconfigs.RecordType, keyword string, routeCode string) (int64, error) {
query := this.Query(tx).
Attr("domainId", domainId).
State(NSRecordStateEnabled)
if len(dnsType) > 0 {
query.Attr("type", dnsType)
}
if len(keyword) > 0 {
var args = utils.SplitKeywordArgs(keyword)
if len(args) > 0 {
var realKeyword = ""
for _, arg := range args {
switch arg.Key {
case "name":
query.Attr("name", arg.Value)
case "value":
query.Attr("value", arg.Value)
case "type":
query.Attr("type", arg.Value)
default:
if len(realKeyword) > 0 {
realKeyword += " " + arg.String()
} else {
realKeyword += arg.String()
}
}
}
keyword = realKeyword
}
if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR value LIKE :keyword OR description LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
}
if len(routeCode) > 0 {
routeCodeJSON, err := json.Marshal(routeCode)
if err != nil {
return 0, err
}
query.JSONContains("routeIds", string(routeCodeJSON))
}
return query.Count()
}
// CountAllRecordsWithName 查询域名中相同记录名的数量
func (this *NSRecordDAO) CountAllRecordsWithName(tx *dbs.Tx, domainId int64, dnsType dnsconfigs.RecordType, name string) (int64, error) {
return this.Query(tx).
State(NSRecordStateEnabled).
Attr("domainId", domainId).
Attr("type", dnsType).
Attr("name", name).
Count()
}
// CountAllEnabledRecords 计算所有记录数量
func (this *NSRecordDAO) CountAllEnabledRecords(tx *dbs.Tx) (int64, error) {
return this.Query(tx).
Where("domainId IN (SELECT id FROM " + SharedNSDomainDAO.Table + " WHERE state=1)").
State(NSRecordStateEnabled).
Count()
}
// ListEnabledRecords 列出单页记录
func (this *NSRecordDAO) ListEnabledRecords(tx *dbs.Tx,
domainId int64,
dnsType dnsconfigs.RecordType,
keyword string,
routeCode string,
nameAsc bool,
nameDesc bool,
typeAsc bool,
typeDesc bool,
ttlAsc bool,
ttlDesc bool,
upAsc bool,
upDesc bool,
offset int64,
size int64) (result []*NSRecord, err error) {
var query = this.Query(tx).
Attr("domainId", domainId).
State(NSRecordStateEnabled)
if len(dnsType) > 0 {
query.Attr("type", dnsType)
}
if len(keyword) > 0 {
var args = utils.SplitKeywordArgs(keyword)
if len(args) > 0 {
var realKeyword = ""
for _, arg := range args {
switch arg.Key {
case "name":
query.Attr("name", arg.Value)
case "value":
query.Attr("value", arg.Value)
case "type":
query.Attr("type", arg.Value)
default:
if len(realKeyword) > 0 {
realKeyword += " " + arg.String()
} else {
realKeyword += arg.String()
}
}
}
keyword = realKeyword
}
if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR value LIKE :keyword OR description LIKE :keyword)").
Param("keyword", dbutils.QuoteLike(keyword))
}
}
if len(routeCode) > 0 {
routeCodeJSON, err := json.Marshal(routeCode)
if err != nil {
return nil, err
}
query.JSONContains("routeIds", string(routeCodeJSON))
}
// 排序
if nameAsc {
query.Asc("name")
} else if nameDesc {
query.Desc("name")
}
if typeAsc {
query.Asc("type")
} else if typeDesc {
query.Desc("type")
}
if ttlAsc {
query.Asc("ttl")
} else if ttlDesc {
query.Desc("ttl")
}
if upAsc {
query.Asc("IF(JSON_CONTAINS(healthCheck, '{\"isOn\": true}'),isUp,2) ")
} else if upDesc {
query.Desc("isUp + JSON_CONTAINS(healthCheck, '{\"isOn\": true}')")
}
_, err = query.
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// IncreaseVersion 增加版本
func (this *NSRecordDAO) IncreaseVersion(tx *dbs.Tx) (int64, error) {
return models.SharedSysLockerDAO.Increase(tx, "NS_RECORD_VERSION", 1)
}
// ListRecordsAfterVersion 列出某个版本后的记录
func (this *NSRecordDAO) ListRecordsAfterVersion(tx *dbs.Tx, version int64, size int64) (result []*NSRecord, err error) {
if size <= 0 {
size = 10000
}
_, err = this.Query(tx).
Gte("version", version).
Limit(size).
Asc("version").
Slice(&result).
FindAll()
return
}
// FindEnabledRecordWithName 查询单条记录
func (this *NSRecordDAO) FindEnabledRecordWithName(tx *dbs.Tx, domainId int64, recordName string, recordType dnsconfigs.RecordType) (*NSRecord, error) {
record, err := this.Query(tx).
State(NSRecordStateEnabled).
Attr("domainId", domainId).
Attr("name", recordName).
Attr("type", recordType).
Find()
if record == nil {
return nil, err
}
return record.(*NSRecord), nil
}
// FindEnabledRecordsWithName 查询多条记录
func (this *NSRecordDAO) FindEnabledRecordsWithName(tx *dbs.Tx, domainId int64, recordName string, recordType dnsconfigs.RecordType) (result []*NSRecord, err error) {
_, err = this.Query(tx).
State(NSRecordStateEnabled).
Attr("domainId", domainId).
Attr("name", recordName).
Attr("type", recordType).
Slice(&result).
FindAll()
return
}
// DisableRecordsInDomain 禁用某个域名中的所有记录
func (this *NSRecordDAO) DisableRecordsInDomain(tx *dbs.Tx, domainId int64) error {
if domainId <= 0 {
return nil
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
err = this.Query(tx).
Attr("domainId", domainId).
Set("state", NSRecordStateDisabled).
Set("version", version).
UpdateQuickly()
if err != nil {
return err
}
return SharedNSDomainDAO.NotifyUpdate(tx, domainId)
}
// DisableRecordsInDomainWithNameAndType 禁用某个域名中的某个名称的记录
func (this *NSRecordDAO) DisableRecordsInDomainWithNameAndType(tx *dbs.Tx, domainId int64, name string, recordType string) error {
if domainId <= 0 {
return nil
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
err = this.Query(tx).
Attr("domainId", domainId).
Attr("name", name).
Attr("type", recordType).
Set("version", version).
Set("state", NSRecordStateDisabled).
UpdateQuickly()
if err != nil {
return err
}
return SharedNSDomainDAO.NotifyUpdate(tx, domainId)
}
// UpdateRecordsWithDomainId 批量修改记录
func (this *NSRecordDAO) UpdateRecordsWithDomainId(tx *dbs.Tx, domainId int64, searchName string, searchType string, searchValue string, searchRouteCodes []string, newName string, newType string, newValue string, newRouteCodes []string) error {
searchType = strings.ToUpper(searchType)
newType = strings.ToUpper(newType)
var query = this.Query(tx)
query.Attr("domainId", domainId)
if len(searchName) > 0 {
query.Attr("name", searchName)
}
if len(searchType) > 0 {
query.Attr("type", searchType)
}
if len(searchValue) > 0 {
if searchType == "CNAME" && !strings.HasSuffix(searchValue, ".") {
searchValue += "."
}
query.Attr("value", searchValue)
}
if len(searchRouteCodes) > 0 {
for _, routeCode := range searchRouteCodes {
query.JSONContains("routeIds", strconv.Quote(routeCode))
}
}
var shouldChange = false
if len(newName) > 0 {
query.Set("name", newName)
shouldChange = true
}
if len(newType) > 0 {
query.Set("type", newType)
shouldChange = true
}
if len(newValue) > 0 {
if newType == "CNAME" && !strings.HasSuffix(newValue, ".") {
newValue += "."
}
query.Set("value", newValue)
shouldChange = true
}
if len(newRouteCodes) > 0 {
routeCodesJSON, err := json.Marshal(newRouteCodes)
if err != nil {
return err
}
query.Set("routeIds", routeCodesJSON)
shouldChange = true
}
if !shouldChange {
return nil
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
query.Set("version", version)
err = query.UpdateQuickly()
if err != nil {
return err
}
return SharedNSDomainDAO.NotifyUpdate(tx, domainId)
}
// DisableRecordsWithDomainId 批量删除记录
func (this *NSRecordDAO) DisableRecordsWithDomainId(tx *dbs.Tx, domainId int64, searchName string, searchType string, searchValue string, searchRouteCodes []string) error {
searchType = strings.ToUpper(searchType)
var query = this.Query(tx)
query.Attr("domainId", domainId)
if len(searchName) > 0 {
query.Attr("name", searchName)
}
if len(searchType) > 0 {
query.Attr("type", searchType)
}
if len(searchValue) > 0 {
if searchType == "CNAME" && !strings.HasSuffix(searchValue, ".") {
searchValue += "."
}
query.Attr("value", searchValue)
}
if len(searchRouteCodes) > 0 {
for _, routeCode := range searchRouteCodes {
query.JSONContains("routeIds", strconv.Quote(routeCode))
}
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
query.Set("version", version)
query.Set("state", NSRecordStateDisabled)
err = query.UpdateQuickly()
if err != nil {
return err
}
return SharedNSDomainDAO.NotifyUpdate(tx, domainId)
}
// UpdateRecordsIsOnWithDomainId 批量修改域名中的IsOn状态
func (this *NSRecordDAO) UpdateRecordsIsOnWithDomainId(tx *dbs.Tx, domainId int64, searchName string, searchType string, searchValue string, searchRouteCodes []string, isOn bool) error {
searchType = strings.ToUpper(searchType)
var query = this.Query(tx)
query.Attr("domainId", domainId)
if len(searchName) > 0 {
query.Attr("name", searchName)
}
if len(searchType) > 0 {
query.Attr("type", searchType)
}
if len(searchValue) > 0 {
if searchType == "CNAME" && !strings.HasSuffix(searchValue, ".") {
searchValue += "."
}
query.Attr("value", searchValue)
}
if len(searchRouteCodes) > 0 {
for _, routeCode := range searchRouteCodes {
query.JSONContains("routeIds", strconv.Quote(routeCode))
}
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
query.Set("version", version)
query.Set("isOn", isOn)
err = query.UpdateQuickly()
if err != nil {
return err
}
return SharedNSDomainDAO.NotifyUpdate(tx, domainId)
}
// CheckUserRecord 检查用户记录
// 注意这里的userId可能为0
func (this *NSRecordDAO) CheckUserRecord(tx *dbs.Tx, userId int64, recordId int64) error {
if recordId <= 0 {
return nil
}
domainId, err := this.Query(tx).
Result("domainId").
Pk(recordId).
FindInt64Col(0)
if err != nil {
return err
}
if domainId == 0 {
return models.ErrNotFound
}
return SharedNSDomainDAO.CheckUserDomain(tx, userId, domainId)
}
// CountAllUserRecords 计算用户的记录数
func (this *NSRecordDAO) CountAllUserRecords(tx *dbs.Tx, userId int64) (int64, error) {
if userId <= 0 {
return 0, nil
}
return this.Query(tx).
Where("domainId IN (SELECT id FROM "+SharedNSDomainDAO.Table+" WHERE state=1 AND userId=:userId)").
Param("userId", userId).
Count()
}
// FindRecordHealthCheckConfig 查找健康检查配置
func (this *NSRecordDAO) FindRecordHealthCheckConfig(tx *dbs.Tx, recordId int64) (*dnsconfigs.NSRecordHealthCheckConfig, error) {
if recordId <= 0 {
return nil, errors.New("not valid 'recordId'")
}
recordHealthCheckConfigJSON, err := this.Query(tx).
Pk(recordId).
State(NSRecordStateEnabled).
Result("healthCheck").
FindJSONCol()
if err != nil {
return nil, err
}
var config = dnsconfigs.NewNSRecordHealthCheckConfig()
if models.IsNull(recordHealthCheckConfigJSON) {
return config, nil
}
err = json.Unmarshal(recordHealthCheckConfigJSON, config)
if err != nil {
return nil, err
}
return config, nil
}
// UpdateRecordHealthCheckConfig 修改健康检查配置
func (this *NSRecordDAO) UpdateRecordHealthCheckConfig(tx *dbs.Tx, recordId int64, healthCheckConfig *dnsconfigs.NSRecordHealthCheckConfig) error {
if recordId <= 0 {
return errors.New("not valid 'recordId'")
}
if healthCheckConfig == nil {
return errors.New("'healthCheckConfig' should not be nil")
}
healthCheckConfigJSON, err := json.Marshal(healthCheckConfig)
if err != nil {
return err
}
var query = this.Query(tx).
Pk(recordId).
Set("healthCheck", healthCheckConfigJSON)
// 恢复上线状态
if !healthCheckConfig.IsOn {
query.Set("isUp", true)
}
return query.UpdateQuickly()
}
// UpdateRecordIsUp 设置记录在线
func (this *NSRecordDAO) UpdateRecordIsUp(tx *dbs.Tx, recordId int64, isUp bool, countUp int, countDown int) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
rowsAffected, err := this.Query(tx).
Pk(recordId).
Set("isUp", isUp).
Set("countUp", countUp).
Set("countDown", countDown).
Set("version", version).
Update()
if err != nil {
return err
}
if rowsAffected > 0 {
return this.NotifyUpdate(tx, recordId)
}
return nil
}
// UpdateDomainRecordsUp 将域名下所有记录设置为在线
func (this *NSRecordDAO) UpdateDomainRecordsUp(tx *dbs.Tx, domainId int64) error {
records, err := this.Query(tx).
Result("id").
Attr("domainId", domainId).
Attr("isUp", 0).
State(NSRecordStateEnabled).
FindAll()
if err != nil {
return err
}
for _, record := range records {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
err = this.Query(tx).
Pk(record.(*NSRecord).Id).
Set("isUp", 1).
Set("countDown", 0).
Set("countUp", 0).
Set("version", version).
UpdateQuickly()
if err != nil {
return err
}
}
return nil
}
// FindAllDomainRecordsWithHealthCheckOn 查找开启健康检查的记录
func (this *NSRecordDAO) FindAllDomainRecordsWithHealthCheckOn(tx *dbs.Tx, domainId int64) (result []*NSRecord, err error) {
_, err = this.Query(tx).
Result("id", "name", "type", "value", "healthCheck", "countUp", "countDown", "isUp").
State(NSRecordStateEnabled).
Attr("isOn", true).
Attr("domainId", domainId).
Attr("type", []string{dnsconfigs.RecordTypeA, dnsconfigs.RecordTypeAAAA}).
Where(`JSON_CONTAINS(healthCheck, '{"isOn": true}')`).
Slice(&result).
FindAll()
return
}
// NotifyUpdate 通知更新
func (this *NSRecordDAO) NotifyUpdate(tx *dbs.Tx, recordId int64) error {
domainId, err := this.Query(tx).
Pk(recordId).
Result("domainId").
FindInt64Col(0)
if err != nil {
return err
}
if domainId == 0 {
return nil
}
clusterId, err := SharedNSDomainDAO.FindEnabledDomainClusterId(tx, domainId)
if err != nil {
return err
}
if clusterId > 0 {
err = models.SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleDNS, clusterId, 0, 0, models.NSNodeTaskTypeRecordChanged)
if err != nil {
return err
}
}
return nil
}

View File

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

View File

@@ -0,0 +1,262 @@
//go:build plus
package nameservers
import (
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/regexputils"
_ "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/rands"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
type NSRecordHourlyStatDAO dbs.DAO
func init() {
dbs.OnReadyDone(func() {
// 清理数据任务
var ticker = time.NewTicker(time.Duration(rands.Int(24, 48)) * time.Hour)
goman.New(func() {
for range ticker.C {
err := SharedNSRecordHourlyStatDAO.Clean(nil, 30) // 只保留N天
if err != nil {
remotelogs.Error("NodeClusterTrafficDailyStatDAO", "clean expired data failed: "+err.Error())
}
}
})
})
}
func NewNSRecordHourlyStatDAO() *NSRecordHourlyStatDAO {
return dbs.NewDAO(&NSRecordHourlyStatDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSRecordHourlyStats",
Model: new(NSRecordHourlyStat),
PkName: "id",
},
}).(*NSRecordHourlyStatDAO)
}
var SharedNSRecordHourlyStatDAO *NSRecordHourlyStatDAO
func init() {
dbs.OnReady(func() {
SharedNSRecordHourlyStatDAO = NewNSRecordHourlyStatDAO()
})
}
// IncreaseHourlyStat 增加统计数据
func (this *NSRecordHourlyStatDAO) IncreaseHourlyStat(tx *dbs.Tx, clusterId int64, nodeId int64, hour string, domainId int64, recordId int64, countRequests int64, bytes int64) error {
if len(hour) != 10 {
return errors.New("invalid hour '" + hour + "'")
}
return this.Query(tx).
Param("countRequests", countRequests).
Param("bytes", bytes).
InsertOrUpdateQuickly(maps.Map{
"clusterId": clusterId,
"nodeId": nodeId,
"domainId": domainId,
"recordId": recordId,
"day": hour[:8],
"hour": hour,
"countRequests": countRequests,
"bytes": bytes,
}, maps.Map{
"countRequests": dbs.SQL("countRequests+:countRequests"),
"bytes": dbs.SQL("bytes+:bytes"),
})
}
// FindHourlyStats 按小时统计
func (this *NSRecordHourlyStatDAO) FindHourlyStats(tx *dbs.Tx, hourFrom string, hourTo string) (result []*NSRecordHourlyStat, err error) {
ones, err := this.Query(tx).
Result("hour", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes").
Between("hour", hourFrom, hourTo).
Group("hour").
FindAll()
if err != nil {
return nil, err
}
var m = map[string]*NSRecordHourlyStat{} // hour => *NSRecordHourlyStat
for _, one := range ones {
m[one.(*NSRecordHourlyStat).Hour] = one.(*NSRecordHourlyStat)
}
hours, err := utils.RangeHours(hourFrom, hourTo)
if err != nil {
return nil, err
}
for _, hour := range hours {
stat, ok := m[hour]
if ok {
result = append(result, stat)
} else {
result = append(result, &NSRecordHourlyStat{
Hour: hour,
})
}
}
return
}
// FindHourlyStatsWithRecordId 读取某个记录的按小时统计
func (this *NSRecordHourlyStatDAO) FindHourlyStatsWithRecordId(tx *dbs.Tx, recordId int64, hourFrom string, hourTo string) (result []*NSRecordHourlyStat, err error) {
ones, err := this.Query(tx).
Attr("recordId", recordId).
Result("hour", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes").
Between("hour", hourFrom, hourTo).
Group("hour").
FindAll()
if err != nil {
return nil, err
}
var m = map[string]*NSRecordHourlyStat{} // hour => *NSRecordHourlyStat
for _, one := range ones {
m[one.(*NSRecordHourlyStat).Hour] = one.(*NSRecordHourlyStat)
}
hours, err := utils.RangeHours(hourFrom, hourTo)
if err != nil {
return nil, err
}
for _, hour := range hours {
stat, ok := m[hour]
if ok {
result = append(result, stat)
} else {
result = append(result, &NSRecordHourlyStat{
Hour: hour,
})
}
}
return
}
// FindHourlyStatsWithRecordIdInDay 读取某个记录某天的按小时统计
// day YYYYMMDD
func (this *NSRecordHourlyStatDAO) FindHourlyStatsWithRecordIdInDay(tx *dbs.Tx, recordId int64, day string) (result []*NSRecordHourlyStat, err error) {
if !regexputils.YYYYMMDD.MatchString(day) {
return
}
ones, err := this.Query(tx).
Attr("recordId", recordId).
Result("hour", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes").
Attr("day", day).
Group("hour").
FindAll()
if err != nil {
return nil, err
}
var m = map[string]*NSRecordHourlyStat{} // hour => *NSRecordHourlyStat
for _, one := range ones {
m[one.(*NSRecordHourlyStat).Hour] = one.(*NSRecordHourlyStat)
}
hours, err := utils.RangeHours(day+"00", day+"23")
if err != nil {
return nil, err
}
for _, hour := range hours {
stat, ok := m[hour]
if ok {
result = append(result, stat)
} else {
result = append(result, &NSRecordHourlyStat{
Hour: hour,
})
}
}
return
}
// FindHourlyStatWithRecordId 获取某个记录的某个小时的统计
func (this *NSRecordHourlyStatDAO) FindHourlyStatWithRecordId(tx *dbs.Tx, recordId int64, hour string) (*NSRecordHourlyStat, error) {
one, err := this.Query(tx).
Result("hour", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes").
Attr("recordId", recordId).
Attr("hour", hour).
Group("hour").
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*NSRecordHourlyStat), nil
}
// FindDailyStats 按天统计
func (this *NSRecordHourlyStatDAO) FindDailyStats(tx *dbs.Tx, dayFrom string, dayTo string) (result []*NSRecordHourlyStat, err error) {
ones, err := this.Query(tx).
Result("day", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes").
Between("day", dayFrom, dayTo).
Group("day").
FindAll()
if err != nil {
return nil, err
}
var m = map[string]*NSRecordHourlyStat{} // day => *NSRecordHourlyStat
for _, one := range ones {
m[one.(*NSRecordHourlyStat).Day] = one.(*NSRecordHourlyStat)
}
days, err := utils.RangeDays(dayFrom, dayTo)
if err != nil {
return nil, err
}
for _, day := range days {
stat, ok := m[day]
if ok {
result = append(result, stat)
} else {
result = append(result, &NSRecordHourlyStat{
Day: day,
})
}
}
return
}
// ListTopNodes 节点排行
func (this *NSRecordHourlyStatDAO) ListTopNodes(tx *dbs.Tx, hourFrom string, hourTo string, size int64) (result []*NSRecordHourlyStat, err error) {
_, err = this.Query(tx).
Result("MIN(clusterId) AS clusterId", "nodeId", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes").
Between("hour", hourFrom, hourTo).
Group("nodeId").
Limit(size).
Slice(&result).
FindAll()
return
}
// ListTopDomains 域名排行
func (this *NSRecordHourlyStatDAO) ListTopDomains(tx *dbs.Tx, userId int64, hourFrom string, hourTo string, size int64) (result []*NSRecordHourlyStat, err error) {
var query = this.Query(tx).
Result("domainId", "SUM(countRequests) AS countRequests", "SUM(bytes) AS bytes")
if userId > 0 {
query.Where("domainId IN (SELECT id FROM " + SharedNSDomainDAO.Table + " WHERE state=1 AND userId=:userId)")
query.Param("userId", userId)
}
_, err = query.
Between("hour", hourFrom, hourTo).
Group("domainId").
Limit(size).
Slice(&result).
FindAll()
return
}
// Clean 清理历史数据
func (this *NSRecordHourlyStatDAO) Clean(tx *dbs.Tx, days int) error {
var hour = timeutil.Format("Ymd00", time.Now().AddDate(0, 0, -days))
_, err := this.Query(tx).
Lt("hour", hour).
Delete()
return err
}

View File

@@ -0,0 +1,8 @@
//go:build plus
package nameservers
import (
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
)

View File

@@ -0,0 +1,30 @@
package nameservers
// NSRecordHourlyStat NS记录统计
type NSRecordHourlyStat struct {
Id uint64 `field:"id"` // ID
ClusterId uint32 `field:"clusterId"` // 集群ID
NodeId uint32 `field:"nodeId"` // 节点ID
DomainId uint64 `field:"domainId"` // 域名ID
RecordId uint64 `field:"recordId"` // 记录ID
Day string `field:"day"` // YYYYMMDD
Hour string `field:"hour"` // YYYYMMDDHH
CountRequests uint64 `field:"countRequests"` // 请求数
Bytes uint64 `field:"bytes"` // 流量
}
type NSRecordHourlyStatOperator struct {
Id interface{} // ID
ClusterId interface{} // 集群ID
NodeId interface{} // 节点ID
DomainId interface{} // 域名ID
RecordId interface{} // 记录ID
Day interface{} // YYYYMMDD
Hour interface{} // YYYYMMDDHH
CountRequests interface{} // 请求数
Bytes interface{} // 流量
}
func NewNSRecordHourlyStatOperator() *NSRecordHourlyStatOperator {
return &NSRecordHourlyStatOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -0,0 +1,60 @@
package nameservers
import "github.com/iwind/TeaGo/dbs"
// NSRecord DNS记录
type NSRecord struct {
Id uint64 `field:"id"` // ID
DomainId uint32 `field:"domainId"` // 域名ID
IsOn bool `field:"isOn"` // 是否启用
Description string `field:"description"` // 备注
Name string `field:"name"` // 记录名
Type string `field:"type"` // 类型
Value string `field:"value"` // 值
MxPriority uint32 `field:"mxPriority"` // MX优先级
SrvPriority uint32 `field:"srvPriority"` // SRV优先级
SrvWeight uint32 `field:"srvWeight"` // SRV权重
SrvPort uint32 `field:"srvPort"` // SRV端口
CaaFlag uint8 `field:"caaFlag"` // CAA Flag
CaaTag string `field:"caaTag"` // CAA TAG
Ttl uint32 `field:"ttl"` // TTL
Weight uint32 `field:"weight"` // 权重
RouteIds dbs.JSON `field:"routeIds"` // 线路
HealthCheck dbs.JSON `field:"healthCheck"` // 健康检查配置
CountUp uint32 `field:"countUp"` // 连续上线次数
CountDown uint32 `field:"countDown"` // 连续离线次数
IsUp bool `field:"isUp"` // 是否在线
CreatedAt uint64 `field:"createdAt"` // 创建时间
Version uint64 `field:"version"` // 版本号
State uint8 `field:"state"` // 状态
}
type NSRecordOperator struct {
Id any // ID
DomainId any // 域名ID
IsOn any // 是否启用
Description any // 备注
Name any // 记录名
Type any // 类型
Value any // 值
MxPriority any // MX优先级
SrvPriority any // SRV优先级
SrvWeight any // SRV权重
SrvPort any // SRV端口
CaaFlag any // CAA Flag
CaaTag any // CAA TAG
Ttl any // TTL
Weight any // 权重
RouteIds any // 线路
HealthCheck any // 健康检查配置
CountUp any // 连续上线次数
CountDown any // 连续离线次数
IsUp any // 是否在线
CreatedAt any // 创建时间
Version any // 版本号
State any // 状态
}
func NewNSRecordOperator() *NSRecordOperator {
return &NSRecordOperator{}
}

View File

@@ -0,0 +1,26 @@
package nameservers
import (
"encoding/json"
"github.com/iwind/TeaGo/types"
)
func (this *NSRecord) DecodeRouteIds() []string {
var routeIds = []string{}
if len(this.RouteIds) > 0 {
err := json.Unmarshal(this.RouteIds, &routeIds)
if err != nil {
// 检查是否有旧的数据
var oldRouteIds = []int64{}
err = json.Unmarshal(this.RouteIds, &oldRouteIds)
if err != nil {
return []string{}
}
routeIds = []string{}
for _, routeId := range oldRouteIds {
routeIds = append(routeIds, "id:"+types.String(routeId))
}
}
}
return routeIds
}

View File

@@ -0,0 +1,33 @@
package nameservers
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSRouteCategoryStateEnabled = 1 // 已启用
NSRouteCategoryStateDisabled = 0 // 已禁用
)
type NSRouteCategoryDAO dbs.DAO
func NewNSRouteCategoryDAO() *NSRouteCategoryDAO {
return dbs.NewDAO(&NSRouteCategoryDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSRouteCategories",
Model: new(NSRouteCategory),
PkName: "id",
},
}).(*NSRouteCategoryDAO)
}
var SharedNSRouteCategoryDAO *NSRouteCategoryDAO
func init() {
dbs.OnReady(func() {
SharedNSRouteCategoryDAO = NewNSRouteCategoryDAO()
})
}

View File

@@ -0,0 +1,121 @@
//go:build plus
package nameservers
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/dbs"
)
// EnableNSRouteCategory 启用条目
func (this *NSRouteCategoryDAO) EnableNSRouteCategory(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSRouteCategoryStateEnabled).
Update()
return err
}
// DisableNSRouteCategory 禁用条目
func (this *NSRouteCategoryDAO) DisableNSRouteCategory(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSRouteCategoryStateDisabled).
Update()
return err
}
// FindCategory 查找启用中的条目
func (this *NSRouteCategoryDAO) FindCategory(tx *dbs.Tx, id int64) (*NSRouteCategory, error) {
result, err := this.Query(tx).
Pk(id).
State(NSRouteCategoryStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSRouteCategory), err
}
// FindNSRouteCategoryName 根据主键查找名称
func (this *NSRouteCategoryDAO) FindNSRouteCategoryName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateCategory 创建分类
func (this *NSRouteCategoryDAO) CreateCategory(tx *dbs.Tx, adminId int64, userId int64, name string) (int64, error) {
var op = NewNSRouteCategoryOperator()
op.AdminId = adminId
op.UserId = userId
op.Name = name
op.IsOn = true
op.State = NSRouteCategoryStateEnabled
return this.SaveInt64(tx, op)
}
// UpdateCategory 修改分类
func (this *NSRouteCategoryDAO) UpdateCategory(tx *dbs.Tx, categoryId int64, name string, isOn bool) error {
if categoryId <= 0 {
return errors.New("invalid categoryId")
}
var op = NewNSRouteCategoryOperator()
op.Id = categoryId
op.Name = name
op.IsOn = isOn
return this.Save(tx, op)
}
// FindAllCategories 查找所有分类
func (this *NSRouteCategoryDAO) FindAllCategories(tx *dbs.Tx, userId int64) (result []*NSRouteCategory, err error) {
_, err = this.Query(tx).
State(NSRouteCategoryStateEnabled).
Attr("userId", userId). // 必要条件即使userId为0
Desc("order").
AscPk().
Slice(&result).
FindAll()
return
}
// UpdateCategoryOrders 修改分类排序
func (this *NSRouteCategoryDAO) UpdateCategoryOrders(tx *dbs.Tx, userId int64, categoryIds []int64) error {
var order = len(categoryIds)
for _, categoryId := range categoryIds {
_, err := this.Query(tx).
Pk(categoryId).
Attr("userId", userId). // 防止用户突破自己的权限操作
Set("order", order).
Update()
if err != nil {
return err
}
order--
}
return nil
}
// CheckUserCategory 检查用户分类
func (this *NSRouteCategoryDAO) CheckUserCategory(tx *dbs.Tx, userId int64, categoryId int64) error {
if userId <= 0 || categoryId <= 0 {
return models.ErrNotFound
}
b, err := this.Query(tx).
Pk(categoryId).
State(NSRouteCategoryStateEnabled).
Attr("userId", userId).
Exist()
if err != nil {
return err
}
if !b {
return models.ErrNotFound
}
return nil
}

View File

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

View File

@@ -0,0 +1,26 @@
package nameservers
// NSRouteCategory 线路分类
type NSRouteCategory struct {
Id uint64 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
Name string `field:"name"` // 分类名
AdminId uint64 `field:"adminId"` // 管理员ID
UserId uint64 `field:"userId"` // 用户ID
Order uint32 `field:"order"` // 排序
State uint8 `field:"state"` // 状态
}
type NSRouteCategoryOperator struct {
Id any // ID
IsOn any // 是否启用
Name any // 分类名
AdminId any // 管理员ID
UserId any // 用户ID
Order any // 排序
State any // 状态
}
func NewNSRouteCategoryOperator() *NSRouteCategoryOperator {
return &NSRouteCategoryOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -0,0 +1,73 @@
//go:build !plus
package nameservers
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSRouteStateEnabled = 1 // 已启用
NSRouteStateDisabled = 0 // 已禁用
)
type NSRouteDAO dbs.DAO
func NewNSRouteDAO() *NSRouteDAO {
return dbs.NewDAO(&NSRouteDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSRoutes",
Model: new(NSRoute),
PkName: "id",
},
}).(*NSRouteDAO)
}
var SharedNSRouteDAO *NSRouteDAO
func init() {
dbs.OnReady(func() {
SharedNSRouteDAO = NewNSRouteDAO()
})
}
// EnableNSRoute 启用条目
func (this *NSRouteDAO) EnableNSRoute(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSRouteStateEnabled).
Update()
return err
}
// DisableNSRoute 禁用条目
func (this *NSRouteDAO) DisableNSRoute(tx *dbs.Tx, id uint32) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSRouteStateDisabled).
Update()
return err
}
// FindEnabledNSRoute 查找启用中的条目
func (this *NSRouteDAO) FindEnabledNSRoute(tx *dbs.Tx, id uint32) (*NSRoute, error) {
result, err := this.Query(tx).
Pk(id).
State(NSRouteStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSRoute), err
}
// FindNSRouteName 根据主键查找名称
func (this *NSRouteDAO) FindNSRouteName(tx *dbs.Tx, id uint32) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}

View File

@@ -0,0 +1,535 @@
//go:build plus
package nameservers
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/clients"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/regions"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"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/types"
"regexp"
"strings"
)
const (
NSRouteStateEnabled = 1 // 已启用
NSRouteStateDisabled = 0 // 已禁用
)
// 线路的几种表达式
var routeCodeReg = regexp.MustCompile(`^id:\d+$`) // 自定义线路
var routeAgentReg = regexp.MustCompile(`^agent:\w+$`) // 搜索引擎线路
var routeProvinceReg = regexp.MustCompile(`^region:province:\d+$`) // 省份线路
type NSRouteDAO dbs.DAO
func NewNSRouteDAO() *NSRouteDAO {
return dbs.NewDAO(&NSRouteDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSRoutes",
Model: new(NSRoute),
PkName: "id",
},
}).(*NSRouteDAO)
}
var SharedNSRouteDAO *NSRouteDAO
func init() {
dbs.OnReady(func() {
SharedNSRouteDAO = NewNSRouteDAO()
})
}
// EnableNSRoute 启用条目
func (this *NSRouteDAO) EnableNSRoute(tx *dbs.Tx, routeId int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(routeId).
Set("state", NSRouteStateEnabled).
Set("version", version).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx)
}
// DisableNSRoute 禁用条目
func (this *NSRouteDAO) DisableNSRoute(tx *dbs.Tx, routeId int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(routeId).
Set("state", NSRouteStateDisabled).
Set("version", version).
Update()
if err != nil {
return err
}
return this.NotifyUpdate(tx)
}
// FindEnabledNSRoute 查找启用中的条目
func (this *NSRouteDAO) FindEnabledNSRoute(tx *dbs.Tx, id int64) (*NSRoute, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSRouteStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSRoute), err
}
// FindEnabledRouteWithCode 根据代号获取线路信息
func (this *NSRouteDAO) FindEnabledRouteWithCode(tx *dbs.Tx, code string) (*NSRoute, error) {
// id:xxx
if routeCodeReg.MatchString(code) {
var routeId = types.Int64(code[strings.Index(code, ":")+1:])
route, err := this.FindEnabledNSRoute(tx, routeId)
if route == nil || err != nil {
return nil, err
}
route.Code = "id:" + types.String(routeId)
return route, nil
}
// agent:xxx
if code == "agent" {
return &NSRoute{
Id: 0,
IsOn: true,
Name: "搜索引擎",
Code: code,
State: NSRouteStateEnabled,
}, nil
}
if routeAgentReg.MatchString(code) {
var agentCode = code[strings.Index(code, ":")+1:]
agentName, err := clients.SharedClientAgentDAO.FindAgentNameWithCode(tx, agentCode)
if err != nil {
return nil, err
}
if len(agentName) == 0 {
return nil, nil
}
return &NSRoute{
Id: 0,
IsOn: true,
Name: agentName,
Code: code,
State: NSRouteStateEnabled,
}, nil
}
// province
if routeProvinceReg.MatchString(code) {
var provinceId = types.Int64(code[strings.LastIndex(code, ":")+1:])
provinceName, err := regions.SharedRegionProvinceDAO.FindRegionProvinceName(tx, provinceId)
if err != nil {
return nil, err
}
if len(provinceName) == 0 {
return nil, nil
}
// country
countryId, err := regions.SharedRegionProvinceDAO.FindRegionCountryId(tx, provinceId)
if err != nil {
return nil, err
}
if countryId > 0 {
countryName, err := regions.SharedRegionCountryDAO.FindRegionCountryName(tx, countryId)
if err != nil {
return nil, err
}
if len(countryName) > 0 {
return &NSRoute{
Id: 0,
IsOn: true,
Name: countryName + "-" + provinceName,
Code: code,
State: NSRouteStateEnabled,
}, nil
}
}
}
var route = dnsconfigs.FindDefaultRoute(code)
if route == nil {
return nil, nil
}
return &NSRoute{
Id: 0,
IsOn: true,
Name: route.Name,
Code: route.Code,
State: NSRouteStateEnabled,
}, nil
}
// FindNSRouteName 根据主键查找名称
func (this *NSRouteDAO) FindNSRouteName(tx *dbs.Tx, id int64) (string, error) {
return this.Query(tx).
Pk(id).
Result("name").
FindStringCol("")
}
// CreateRoute 创建线路
func (this *NSRouteDAO) CreateRoute(tx *dbs.Tx, clusterId int64, domainId int64, userId int64, name string, rangesJSON []byte, categoryId int64, priority int32, isPublic bool) (int64, error) {
version, err := this.IncreaseVersion(tx)
if err != nil {
return 0, err
}
var op = NewNSRouteOperator()
op.ClusterId = clusterId
op.DomainId = domainId
op.UserId = userId
op.Name = name
if len(rangesJSON) > 0 {
op.Ranges = rangesJSON
} else {
op.Ranges = "[]"
}
op.IsPublic = isPublic
op.CategoryId = categoryId
if priority < 0 {
priority = 0
}
op.Priority = priority
op.IsOn = true
op.State = NSRouteStateEnabled
op.Version = version
routeId, err := this.SaveInt64(tx, op)
if err != nil {
return 0, err
}
err = this.NotifyUpdate(tx)
if err != nil {
return 0, err
}
return routeId, nil
}
// UpdateRoute 修改线路
func (this *NSRouteDAO) UpdateRoute(tx *dbs.Tx, routeId int64, name string, rangesJSON []byte, categoryId int64, priority int32, isPublic bool, isOn bool) error {
if routeId <= 0 {
return errors.New("invalid routeId")
}
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
var op = NewNSRouteOperator()
op.Id = routeId
op.Name = name
if len(rangesJSON) > 0 {
op.Ranges = rangesJSON
} else {
op.Ranges = "[]"
}
op.IsPublic = isPublic
op.CategoryId = categoryId
op.Version = version
op.IsOn = isOn
if priority < 0 {
priority = 0
}
op.Priority = priority
err = this.Save(tx, op)
if err != nil {
return err
}
return this.NotifyUpdate(tx)
}
// UpdateRouteOrders 修改线路排序
func (this *NSRouteDAO) UpdateRouteOrders(tx *dbs.Tx, routeIds []int64) error {
version, err := this.IncreaseVersion(tx)
if err != nil {
return err
}
order := len(routeIds)
for _, routeId := range routeIds {
_, err = this.Query(tx).
Pk(routeId).
Set("order", order).
Set("version", version).
Update()
if err != nil {
return err
}
order--
}
return this.NotifyUpdate(tx)
}
// FindAllEnabledRoutes 列出所有自定义线路
func (this *NSRouteDAO) FindAllEnabledRoutes(tx *dbs.Tx, clusterId int64, domainId int64, userId int64, categoryId int64) (result []*NSRoute, err error) {
var query = this.Query(tx).
State(NSRouteStateEnabled).
Slice(&result).
Asc("categoryId").
Desc("order").
AscPk()
if clusterId < 0 {
clusterId = 0
}
if domainId < 0 {
domainId = 0
}
if userId < 0 {
userId = 0
}
if categoryId < 0 {
categoryId = 0
}
// 必要条件即使值为0
query.Attr("clusterId", clusterId)
query.Attr("userId", userId)
query.Attr("domainId", domainId)
// 非必要条件
if categoryId > 0 {
query.Attr("categoryId", categoryId)
}
_, err = query.FindAll()
return
}
// FindAllPublicRoutes 查找公用的自定义线路
func (this *NSRouteDAO) FindAllPublicRoutes(tx *dbs.Tx) (result []*NSRoute, err error) {
_, err = this.Query(tx).
State(NSRouteStateEnabled).
Attr("userId", 0). // 只能查询管理员的
Attr("isOn", true).
Attr("clusterId", 0).
Attr("domainId", 0).
Attr("isPublic", true).
Slice(&result).
Desc("order").
AscPk().
FindAll()
return
}
// CountAllEnabledRoutes 计算所有线路数量
func (this *NSRouteDAO) CountAllEnabledRoutes(tx *dbs.Tx, clusterId int64, domainId int64, userId int64) (int64, error) {
var query = this.Query(tx).
State(NSRouteStateEnabled)
if clusterId > 0 {
query.Attr("clusterId", clusterId)
} else {
// 不查询所有集群的线路
query.Attr("clusterId", 0)
}
if domainId > 0 {
query.Attr("domainId", domainId)
}
if userId > 0 {
query.Attr("userId", userId)
}
return query.Count()
}
// IncreaseVersion 增加版本
func (this *NSRouteDAO) IncreaseVersion(tx *dbs.Tx) (int64, error) {
return models.SharedSysLockerDAO.Increase(tx, "NS_ROUTE_VERSION", 1)
}
// ListRoutesAfterVersion 列出某个版本后的域名
func (this *NSRouteDAO) ListRoutesAfterVersion(tx *dbs.Tx, version int64, size int64) (result []*NSRoute, err error) {
if size <= 0 {
size = 10000
}
_, err = this.Query(tx).
Gte("version", version).
Limit(size).
Asc("version").
Slice(&result).
FindAll()
return
}
// CheckUserRoute 检查用户权限
func (this *NSRouteDAO) CheckUserRoute(tx *dbs.Tx, userId int64, routeId int64) error {
if userId <= 0 || routeId <= 0 {
return models.ErrNotFound
}
b, err := this.Query(tx).
Pk(routeId).
State(NSRouteStateEnabled).
Attr("userId", userId).
Exist()
if err != nil {
return err
}
if !b {
return models.ErrNotFound
}
return nil
}
// CheckRouteCode 检查用户是否可以使用某个线路代号
func (this *NSRouteDAO) CheckRouteCode(tx *dbs.Tx, routeCode string, userId int64) error {
if len(routeCode) == 0 {
return nil
}
// 是否为内置线路
var routeInfo = dnsconfigs.FindDefaultRoute(routeCode)
if routeInfo != nil {
return nil
}
// 如果是管理员的话直接可以使用
if userId == 0 {
return nil
}
// id:xxx
if routeCodeReg.MatchString(routeCode) {
var colonIndex = strings.LastIndex(routeCode, ":")
if colonIndex > 0 {
var routeId = types.Int64(routeCode[colonIndex+1:])
if routeId > 0 {
routeOne, err := this.Query(tx).
Pk(routeId).
State(NSRouteStateEnabled).
Result("userId", "isPublic").
Find()
if err != nil {
return err
}
if routeOne != nil {
var route = routeOne.(*NSRoute)
// 检查是否为公用线路 或者 自己的线路
if (route.UserId == 0 && route.IsPublic) || (int64(route.UserId) == userId) {
return nil
}
}
}
}
}
// agent:xxx
if routeCode == "agent" {
return nil
}
if routeAgentReg.MatchString(routeCode) {
var colonIndex = strings.Index(routeCode, ":")
if colonIndex > 0 {
var agentCode = routeCode[colonIndex+1:]
if len(agentCode) > 0 {
agentId, err := clients.SharedClientAgentDAO.FindAgentIdWithCode(tx, agentCode)
if err != nil {
return err
}
if agentId > 0 {
return nil
}
}
}
}
// province:xxx
if routeProvinceReg.MatchString(routeCode) {
var provinceId = types.Int64(routeCode[strings.LastIndex(routeCode, ":")+1:])
if provinceId <= 0 {
return errors.New("invalid province id in '" + routeCode + "'")
}
b, err := regions.SharedRegionProvinceDAO.Exist(tx, provinceId)
if err != nil {
return err
}
if !b {
return errors.New("province id in '" + routeCode + "' not found")
}
return nil
}
return errors.New("no route with code '" + routeCode + "' found")
}
// CheckRouteCodes 检查用户是否可以使用一组线路代号
func (this *NSRouteDAO) CheckRouteCodes(tx *dbs.Tx, routeCodes []string, userId int64) error {
if len(routeCodes) == 0 {
return nil
}
for _, routeCode := range routeCodes {
if len(routeCode) == 0 {
continue
}
err := this.CheckRouteCode(tx, routeCode, userId)
if err != nil {
return err
}
}
return nil
}
// ResetRoutesCategory 重置线路分类
func (this *NSRouteDAO) ResetRoutesCategory(tx *dbs.Tx, categoryId int64) error {
if categoryId <= 0 {
return nil
}
return this.Query(tx).
Attr("categoryId", categoryId).
Set("categoryId", 0).
UpdateQuickly()
}
// NotifyUpdate 通知更新
func (this *NSRouteDAO) NotifyUpdate(tx *dbs.Tx) error {
// 线路变更时所有集群都要更新
clusterIds, err := models.SharedNSClusterDAO.FindAllEnabledClusterIds(tx)
if err != nil {
return err
}
for _, clusterId := range clusterIds {
err = models.SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleDNS, clusterId, 0, 0, models.NSNodeTaskTypeRouteChanged)
if err != nil {
return err
}
}
return nil
}

View File

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

View File

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

View File

@@ -0,0 +1,44 @@
package nameservers
import "github.com/iwind/TeaGo/dbs"
// NSRoute DNS线路
type NSRoute struct {
Id uint32 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用
ClusterId uint32 `field:"clusterId"` // 集群ID
CategoryId uint32 `field:"categoryId"` // 分类ID
DomainId uint64 `field:"domainId"` // 域名ID
AdminId uint64 `field:"adminId"` // 管理员ID
UserId uint64 `field:"userId"` // 用户ID
IsPublic bool `field:"isPublic"` // 是否公用(管理员创建的线路)
Name string `field:"name"` // 名称
Ranges dbs.JSON `field:"ranges"` // 范围
Order uint32 `field:"order"` // 排序
Version uint64 `field:"version"` // 版本号
Priority uint32 `field:"priority"` // 优先级,越高越优先
Code string `field:"code"` // 代号
State uint8 `field:"state"` // 状态
}
type NSRouteOperator struct {
Id any // ID
IsOn any // 是否启用
ClusterId any // 集群ID
CategoryId any // 分类ID
DomainId any // 域名ID
AdminId any // 管理员ID
UserId any // 用户ID
IsPublic any // 是否公用(管理员创建的线路)
Name any // 名称
Ranges any // 范围
Order any // 排序
Version any // 版本号
Priority any // 优先级,越高越优先
Code any // 代号
State any // 状态
}
func NewNSRouteOperator() *NSRouteOperator {
return &NSRouteOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -0,0 +1,245 @@
//go:build plus
package nameservers
import (
"encoding/json"
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
const (
NSUserPlanStateEnabled = 1 // 已启用
NSUserPlanStateDisabled = 0 // 已禁用
)
type NSUserPlanPeriodUnit = string
const (
NSUserPlanPeriodUnitMonthly NSUserPlanPeriodUnit = "monthly"
NSUserPlanPeriodUnitYearly NSUserPlanPeriodUnit = "yearly"
)
type NSUserPlanDAO dbs.DAO
func NewNSUserPlanDAO() *NSUserPlanDAO {
return dbs.NewDAO(&NSUserPlanDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSUserPlans",
Model: new(NSUserPlan),
PkName: "id",
},
}).(*NSUserPlanDAO)
}
var SharedNSUserPlanDAO *NSUserPlanDAO
func init() {
dbs.OnReady(func() {
SharedNSUserPlanDAO = NewNSUserPlanDAO()
})
}
// EnableNSUserPlan 启用条目
func (this *NSUserPlanDAO) EnableNSUserPlan(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSUserPlanStateEnabled).
Update()
return err
}
// DisableNSUserPlan 禁用条目
func (this *NSUserPlanDAO) DisableNSUserPlan(tx *dbs.Tx, id int64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSUserPlanStateDisabled).
Update()
return err
}
// FindEnabledNSUserPlan 查找启用中的条目
func (this *NSUserPlanDAO) FindEnabledNSUserPlan(tx *dbs.Tx, id int64) (*NSUserPlan, error) {
result, err := this.Query(tx).
Pk(id).
State(NSUserPlanStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSUserPlan), err
}
// CreateUserPlan 创建用户套餐
func (this *NSUserPlanDAO) CreateUserPlan(tx *dbs.Tx, userId int64, planId int64, dayFrom string, dayTo string, periodUnit NSUserPlanPeriodUnit) (int64, error) {
var op = NewNSUserPlanOperator()
op.UserId = userId
op.PlanId = planId
op.DayFrom = dayFrom
op.DayTo = dayTo
op.PeriodUnit = periodUnit
op.State = NSUserPlanStateEnabled
return this.SaveInt64(tx, op)
}
// UpdateUserPlan 修改用户套餐
func (this *NSUserPlanDAO) UpdateUserPlan(tx *dbs.Tx, userPlanId int64, planId int64, dayFrom string, dayTo string, periodUnit NSUserPlanPeriodUnit) error {
if userPlanId <= 0 {
return errors.New("invalid userPlanId")
}
var op = NewNSUserPlanOperator()
op.Id = userPlanId
op.PlanId = planId
op.DayFrom = dayFrom
op.DayTo = dayTo
op.PeriodUnit = periodUnit
return this.Save(tx, op)
}
// CountUserPlans 计算用户套餐数量
func (this *NSUserPlanDAO) CountUserPlans(tx *dbs.Tx, userId int64, planId int64, periodUnit string, isExpired bool, expireDays int32) (int64, error) {
var query = this.Query(tx).
State(NSUserPlanStateEnabled)
if userId > 0 {
query.Attr("userId", userId)
}
if planId > 0 {
query.Attr("planId", planId)
}
if len(periodUnit) > 0 {
query.Attr("periodUnit", periodUnit)
}
if isExpired {
query.Lt("dayTo", timeutil.Format("Ymd"))
} else if expireDays == 0 {
query.Gte("dayTo", timeutil.Format("Ymd"))
} else if expireDays > 0 {
query.Gte("dayTo", timeutil.Format("Ymd"))
query.Lte("dayTo", timeutil.Format("Ymd", time.Now().AddDate(0, 0, int(expireDays))))
}
query.Where("planId IN (SELECT id FROM " + SharedNSPlanDAO.Table + " WHERE state=1)")
return query.Count()
}
// ListUserPlans 列出单页用户套餐
func (this *NSUserPlanDAO) ListUserPlans(tx *dbs.Tx, userId int64, planId int64, periodUnit string, isExpired bool, expireDays int32, offset int64, size int64) (result []*NSUserPlan, err error) {
var query = this.Query(tx)
if userId > 0 {
query.Attr("userId", userId)
}
if planId > 0 {
query.Attr("planId", planId)
}
if len(periodUnit) > 0 {
query.Attr("periodUnit", periodUnit)
}
if isExpired {
query.Lt("dayTo", timeutil.Format("Ymd"))
} else if expireDays == 0 {
query.Gte("dayTo", timeutil.Format("Ymd"))
} else if expireDays > 0 {
query.Gte("dayTo", timeutil.Format("Ymd"))
query.Lte("dayTo", timeutil.Format("Ymd", time.Now().AddDate(0, 0, int(expireDays))))
}
query.Where("planId IN (SELECT id FROM " + SharedNSPlanDAO.Table + " WHERE state=1)")
_, err = query.
State(NSUserPlanStateEnabled).
Desc("dayFrom").
DescPk().
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
// FindUserPlan 查找用户对应的套餐
func (this *NSUserPlanDAO) FindUserPlan(tx *dbs.Tx, userId int64) (*NSUserPlan, error) {
if userId <= 0 {
return nil, nil
}
one, err := this.Query(tx).
State(NSUserPlanStateEnabled).
Attr("userId", userId).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*NSUserPlan), nil
}
// RenewUserPlan 更新用户套餐
func (this *NSUserPlanDAO) RenewUserPlan(tx *dbs.Tx, userId int64, planId int64, dayFrom string, dayTo string, period NSUserPlanPeriodUnit) error {
if userId <= 0 {
return errors.New("invalid userId")
}
userPlan, err := this.FindUserPlan(tx, userId)
if err != nil {
return err
}
if userPlan == nil {
_, err = this.CreateUserPlan(tx, userId, planId, dayFrom, dayTo, period)
return err
}
return this.UpdateUserPlan(tx, int64(userPlan.Id), planId, dayFrom, dayTo, period)
}
// FindUserPlanConfig 用户配置
// 需要保证非 error 的情况下,一定会返回一个不为空的 NSPlanConfig
func (this *NSUserPlanDAO) FindUserPlanConfig(tx *dbs.Tx, userId int64) (*dnsconfigs.NSPlanConfig, error) {
// 查找用户套餐
userPlan, err := this.FindUserPlan(tx, userId)
if err != nil {
return nil, err
}
if userPlan != nil &&
len(userPlan.DayTo) > 0 &&
userPlan.DayTo >= timeutil.Format("Ymd") /** 在有效期内 **/ {
var planId = int64(userPlan.PlanId)
if planId > 0 {
plan, err := SharedNSPlanDAO.FindEnabledNSPlan(tx, planId)
if err != nil {
return nil, err
}
if plan != nil &&
len(plan.Config) > 0 &&
plan.IsOn {
var config = dnsconfigs.DefaultNSUserPlanConfig()
err = json.Unmarshal(plan.Config, config)
if err != nil {
return nil, fmt.Errorf("decode plan config failed '%w'", err)
}
return config, nil
}
}
}
// 从用户全局设置中读取
userConfig, err := models.SharedSysSettingDAO.ReadNSUserConfig(tx)
if err != nil {
return nil, err
}
if userConfig != nil && userConfig.DefaultPlanConfig != nil {
return userConfig.DefaultPlanConfig, nil
}
return dnsconfigs.DefaultNSUserPlanConfig(), nil
}

View File

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

View File

@@ -0,0 +1,28 @@
package nameservers
// NSUserPlan 用户套餐
type NSUserPlan struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
PlanId uint32 `field:"planId"` // 套餐ID
DayFrom string `field:"dayFrom"` // YYYYMMDD
DayTo string `field:"dayTo"` // YYYYMMDD
PeriodUnit string `field:"periodUnit"` // monthly|yearly
CreatedAt uint64 `field:"createdAt"` // 创建时间
State uint8 `field:"state"` // 状态
}
type NSUserPlanOperator struct {
Id any // ID
UserId any // 用户ID
PlanId any // 套餐ID
DayFrom any // YYYYMMDD
DayTo any // YYYYMMDD
PeriodUnit any // monthly|yearly
CreatedAt any // 创建时间
State any // 状态
}
func NewNSUserPlanOperator() *NSUserPlanOperator {
return &NSUserPlanOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers

View File

@@ -0,0 +1,13 @@
//go:build plus
package nameservers
import timeutil "github.com/iwind/TeaGo/utils/time"
// IsAvailable 判断当前套餐是否有效
func (this *NSUserPlan) IsAvailable() bool {
if len(this.DayTo) == 0 {
return false
}
return this.DayTo >= timeutil.Format("Ymd")
}

View File

@@ -0,0 +1,65 @@
//go:build plus
package nameservers
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
const (
NSZoneStateEnabled = 1 // 已启用
NSZoneStateDisabled = 0 // 已禁用
)
type NSZoneDAO dbs.DAO
func NewNSZoneDAO() *NSZoneDAO {
return dbs.NewDAO(&NSZoneDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeNSZones",
Model: new(NSZone),
PkName: "id",
},
}).(*NSZoneDAO)
}
var SharedNSZoneDAO *NSZoneDAO
func init() {
dbs.OnReady(func() {
SharedNSZoneDAO = NewNSZoneDAO()
})
}
// EnableNSZone 启用条目
func (this *NSZoneDAO) EnableNSZone(tx *dbs.Tx, id uint64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSZoneStateEnabled).
Update()
return err
}
// DisableNSZone 禁用条目
func (this *NSZoneDAO) DisableNSZone(tx *dbs.Tx, id uint64) error {
_, err := this.Query(tx).
Pk(id).
Set("state", NSZoneStateDisabled).
Update()
return err
}
// FindEnabledNSZone 查找启用中的条目
func (this *NSZoneDAO) FindEnabledNSZone(tx *dbs.Tx, id uint64) (*NSZone, error) {
result, err := this.Query(tx).
Pk(id).
Attr("state", NSZoneStateEnabled).
Find()
if result == nil {
return nil, err
}
return result.(*NSZone), err
}

View File

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

View File

@@ -0,0 +1,28 @@
package nameservers
import "github.com/iwind/TeaGo/dbs"
// NSZone 域名子域
type NSZone struct {
Id uint64 `field:"id"` // ID
DomainId uint64 `field:"domainId"` // 域名ID
IsOn bool `field:"isOn"` // 是否启用
Order uint32 `field:"order"` // 排序
Version uint64 `field:"version"` // 版本
Tsig dbs.JSON `field:"tsig"` // TSIG配置
State uint8 `field:"state"` // 状态
}
type NSZoneOperator struct {
Id interface{} // ID
DomainId interface{} // 域名ID
IsOn interface{} // 是否启用
Order interface{} // 排序
Version interface{} // 版本
Tsig interface{} // TSIG配置
State interface{} // 状态
}
func NewNSZoneOperator() *NSZoneOperator {
return &NSZoneOperator{}
}

View File

@@ -0,0 +1 @@
package nameservers