Compare commits

...

23 Commits

Author SHA1 Message Date
刘祥超
2888634fb0 将版本号修改为1.3.1 2023-11-23 17:24:09 +08:00
刘祥超
94defc3e0c 优化SSH认证sudo设置 2023-11-23 16:12:52 +08:00
刘祥超
9089ed2657 DNSPod改名为腾讯云DNSPod/DNSPod 支持腾讯云API密钥 2023-11-23 15:15:11 +08:00
刘祥超
b60bb5f6da 提交SQL 2023-11-19 09:11:07 +08:00
刘祥超
ff4ea41963 节点配置中增加节点IP信息 2023-11-18 12:09:47 +08:00
刘祥超
b7dccad449 实现用户系统手机号码绑定和登录(商业版) 2023-11-17 11:51:29 +08:00
刘祥超
7fead214d4 更新SQL 2023-11-15 19:10:18 +08:00
刘祥超
d9590ec605 创建反向代理时默认不自动重试50X/源站支持404内容自动重试其他源站 2023-11-15 19:05:43 +08:00
刘祥超
20b936580f 版本号修改为1.3.0 2023-11-14 14:47:32 +08:00
刘祥超
b7b43bc31f 限制访问日志中域名能写入的最大长度 2023-11-13 17:12:11 +08:00
刘祥超
6fd4f26755 自定义页面增加例外URL和限制URL设置 2023-11-13 10:46:12 +08:00
刘祥超
f15d114708 自定义页面增加“跳转URL”功能 2023-11-10 16:36:09 +08:00
刘祥超
fc24195b55 增加访问日志中域名长度 2023-11-10 09:56:17 +08:00
刘祥超
ed5de57244 去除一处多余的日志 2023-11-07 17:34:09 +08:00
刘祥超
4ce347738f 修复无法将OSS源站修改为http/https源站的问题 2023-11-04 08:28:08 +08:00
刘祥超
f6e725781c 优化节点阈值设置 2023-11-03 11:20:47 +08:00
刘祥超
55d70418cc 节点健康检查失败时增加节点名称和节点IP提示 2023-11-03 09:54:42 +08:00
刘祥超
7f5b070e36 优化商业版验证 2023-11-02 17:20:12 +08:00
刘祥超
993c7ee822 上传域名统计数据时限制域名长度不能超过64位 2023-11-02 17:19:56 +08:00
刘祥超
b5bb4e0df9 更新数据库 2023-10-30 19:04:23 +08:00
刘祥超
9f120fd0e0 访问日志存储策略增加“停止默认数据库存储”选项 2023-10-30 19:03:39 +08:00
刘祥超
77d614c9ea 实现网络数据包相关统计(商业版本) 2023-10-26 17:17:43 +08:00
刘祥超
531ec3c55d 优化域名解析文字提示 2023-10-17 15:54:08 +08:00
52 changed files with 1901 additions and 317 deletions

View File

@@ -12,5 +12,5 @@ dbs:
fields:
bool: [ "uamIsOn", "followPort", "requestHostExcludingPort", "autoRemoteStart", "autoInstallNftables", "enableIPLists", "detectAgents", "checkingPorts", "enableRecordHealthCheck", "offlineIsNotified", "http2Enabled", "http3Enabled", "enableHTTP2", "retry50X", "autoSystemTuning" ]
bool: [ "uamIsOn", "followPort", "requestHostExcludingPort", "autoRemoteStart", "autoInstallNftables", "enableIPLists", "detectAgents", "checkingPorts", "enableRecordHealthCheck", "offlineIsNotified", "http2Enabled", "http3Enabled", "enableHTTP2", "retry50X", "retry40X", "autoSystemTuning", "disableDefaultDB" ]

View File

@@ -1,3 +1,7 @@
#!/usr/bin/env bash
go run `dirname $0`/../cmd/sql-dump/main.go -dir=`dirname $0`
# generate 'internal/setup/sql.json' file
CWD="$(dirname "$0")"
go run "${CWD}"/../cmd/sql-dump/main.go -dir="${CWD}"

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "1.2.11"
Version = "1.3.1"
ProductName = "Edge API"
ProcessName = "edge-api"
@@ -20,7 +20,7 @@ const (
// 其他节点版本号,用来检测是否有需要升级的节点
NodeVersion = "1.2.10"
NodeVersion = "1.3.1"
// SQLVersion SQL版本号
SQLVersion = "11"

View File

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

View File

@@ -2,6 +2,18 @@ package authority
import "github.com/iwind/TeaGo/dbs"
const (
AuthorityKeyField_Id dbs.FieldName = "id" // ID
AuthorityKeyField_Value dbs.FieldName = "value" // Key值
AuthorityKeyField_DayFrom dbs.FieldName = "dayFrom" // 开始日期
AuthorityKeyField_DayTo dbs.FieldName = "dayTo" // 结束日期
AuthorityKeyField_Hostname dbs.FieldName = "hostname" // Hostname
AuthorityKeyField_MacAddresses dbs.FieldName = "macAddresses" // MAC地址
AuthorityKeyField_UpdatedAt dbs.FieldName = "updatedAt" // 创建/修改时间
AuthorityKeyField_Company dbs.FieldName = "company" // 公司组织
AuthorityKeyField_RequestCode dbs.FieldName = "requestCode" // 申请码
)
// AuthorityKey 企业版认证信息
type AuthorityKey struct {
Id uint32 `field:"id"` // ID
@@ -12,17 +24,19 @@ type AuthorityKey struct {
MacAddresses dbs.JSON `field:"macAddresses"` // MAC地址
UpdatedAt uint64 `field:"updatedAt"` // 创建/修改时间
Company string `field:"company"` // 公司组织
RequestCode string `field:"requestCode"` // 申请码
}
type AuthorityKeyOperator struct {
Id interface{} // ID
Value interface{} // Key值
DayFrom interface{} // 开始日期
DayTo interface{} // 结束日期
Hostname interface{} // Hostname
MacAddresses interface{} // MAC地址
UpdatedAt interface{} // 创建/修改时间
Company interface{} // 公司组织
Id any // ID
Value any // Key值
DayFrom any // 开始日期
DayTo any // 结束日期
Hostname any // Hostname
MacAddresses any // MAC地址
UpdatedAt any // 创建/修改时间
Company any // 公司组织
RequestCode any // 申请码
}
func NewAuthorityKeyOperator() *AuthorityKeyOperator {

View File

@@ -245,7 +245,7 @@ func (this *HTTPAccessLogDAO) CreateHTTPAccessLog(tx *dbs.Tx, dao *HTTPAccessLog
return err
}
fields := map[string]interface{}{}
var fields = map[string]any{}
fields["serverId"] = accessLog.ServerId
fields["nodeId"] = accessLog.NodeId
fields["status"] = accessLog.Status
@@ -265,7 +265,11 @@ func (this *HTTPAccessLogDAO) CreateHTTPAccessLog(tx *dbs.Tx, dao *HTTPAccessLog
fields["remoteAddr"] = accessLog.RemoteAddr
}
if tableDef.HasDomain {
fields["domain"] = accessLog.Host
if len(accessLog.Host) > 128 {
fields["domain"] = accessLog.Host[:128]
} else {
fields["domain"] = accessLog.Host
}
}
content, err := json.Marshal(accessLog)
@@ -863,8 +867,4 @@ func (this *HTTPAccessLogDAO) SetupQueue() {
oldAccessLogQueue = accessLogQueue
accessLogQueue = make(chan *pb.HTTPAccessLog, config.MaxLength)
}
if Tea.IsTesting() {
remotelogs.Println("HTTP_ACCESS_LOG_QUEUE", "change queue "+string(configJSON))
}
}

View File

@@ -239,7 +239,7 @@ func (this *HTTPAccessLogManager) FindLastTable(db *dbs.DB, day string, force bo
// CreateTable 创建访问日志表格
func (this *HTTPAccessLogManager) CreateTable(db *dbs.DB, tableName string) error {
_, err := db.Exec("CREATE TABLE `" + tableName + "` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',\n `serverId` int(11) unsigned DEFAULT '0' COMMENT '服务ID',\n `nodeId` int(11) unsigned DEFAULT '0' COMMENT '节点ID',\n `status` int(3) unsigned DEFAULT '0' COMMENT '状态码',\n `createdAt` bigint(11) unsigned DEFAULT '0' COMMENT '创建时间',\n `content` json DEFAULT NULL COMMENT '日志内容',\n `requestId` varchar(128) DEFAULT NULL COMMENT '请求ID',\n `firewallPolicyId` int(11) unsigned DEFAULT '0' COMMENT 'WAF策略ID',\n `firewallRuleGroupId` int(11) unsigned DEFAULT '0' COMMENT 'WAF分组ID',\n `firewallRuleSetId` int(11) unsigned DEFAULT '0' COMMENT 'WAF集ID',\n `firewallRuleId` int(11) unsigned DEFAULT '0' COMMENT 'WAF规则ID',\n `remoteAddr` varchar(64) DEFAULT NULL COMMENT 'IP地址',\n `domain` varchar(128) DEFAULT NULL COMMENT '域名',\n `requestBody` mediumblob COMMENT '请求内容',\n `responseBody` mediumblob COMMENT '响应内容',\n PRIMARY KEY (`id`),\n KEY `serverId` (`serverId`),\n KEY `nodeId` (`nodeId`),\n KEY `serverId_status` (`serverId`,`status`),\n KEY `requestId` (`requestId`),\n KEY `firewallPolicyId` (`firewallPolicyId`),\n KEY `firewallRuleGroupId` (`firewallRuleGroupId`),\n KEY `firewallRuleSetId` (`firewallRuleSetId`),\n KEY `firewallRuleId` (`firewallRuleId`),\n KEY `remoteAddr` (`remoteAddr`),\n KEY `domain` (`domain`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='访问日志';")
_, err := db.Exec("CREATE TABLE `" + tableName + "` (\n `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'ID',\n `serverId` int(11) unsigned DEFAULT '0' COMMENT '服务ID',\n `nodeId` int(11) unsigned DEFAULT '0' COMMENT '节点ID',\n `status` int(3) unsigned DEFAULT '0' COMMENT '状态码',\n `createdAt` bigint(11) unsigned DEFAULT '0' COMMENT '创建时间',\n `content` json DEFAULT NULL COMMENT '日志内容',\n `requestId` varchar(128) DEFAULT NULL COMMENT '请求ID',\n `firewallPolicyId` int(11) unsigned DEFAULT '0' COMMENT 'WAF策略ID',\n `firewallRuleGroupId` int(11) unsigned DEFAULT '0' COMMENT 'WAF分组ID',\n `firewallRuleSetId` int(11) unsigned DEFAULT '0' COMMENT 'WAF集ID',\n `firewallRuleId` int(11) unsigned DEFAULT '0' COMMENT 'WAF规则ID',\n `remoteAddr` varchar(64) DEFAULT NULL COMMENT 'IP地址',\n `domain` varchar(255) DEFAULT NULL COMMENT '域名',\n `requestBody` mediumblob COMMENT '请求内容',\n `responseBody` mediumblob COMMENT '响应内容',\n PRIMARY KEY (`id`),\n KEY `serverId` (`serverId`),\n KEY `nodeId` (`nodeId`),\n KEY `serverId_status` (`serverId`,`status`),\n KEY `requestId` (`requestId`),\n KEY `firewallPolicyId` (`firewallPolicyId`),\n KEY `firewallRuleGroupId` (`firewallRuleGroupId`),\n KEY `firewallRuleSetId` (`firewallRuleSetId`),\n KEY `firewallRuleId` (`firewallRuleId`),\n KEY `remoteAddr` (`remoteAddr`),\n KEY `domain` (`domain`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='访问日志';")
if err != nil {
if CheckSQLErrCode(err, 1050) { // Error 1050: Table 'xxx' already exists
return nil

View File

@@ -107,7 +107,7 @@ func (this *HTTPAccessLogPolicyDAO) FindAllEnabledAndOnPolicies(tx *dbs.Tx) (res
}
// CreatePolicy 创建策略
func (this *HTTPAccessLogPolicyDAO) CreatePolicy(tx *dbs.Tx, name string, policyType string, optionsJSON []byte, condsJSON []byte, isPublic bool, firewallOnly bool) (policyId int64, err error) {
func (this *HTTPAccessLogPolicyDAO) CreatePolicy(tx *dbs.Tx, name string, policyType string, optionsJSON []byte, condsJSON []byte, isPublic bool, firewallOnly bool, disableDefaultDB bool) (policyId int64, err error) {
var op = NewHTTPAccessLogPolicyOperator()
op.Name = name
op.Type = policyType
@@ -120,12 +120,13 @@ func (this *HTTPAccessLogPolicyDAO) CreatePolicy(tx *dbs.Tx, name string, policy
op.IsPublic = isPublic
op.IsOn = true
op.FirewallOnly = firewallOnly
op.DisableDefaultDB = disableDefaultDB
op.State = HTTPAccessLogPolicyStateEnabled
return this.SaveInt64(tx, op)
}
// UpdatePolicy 修改策略
func (this *HTTPAccessLogPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, name string, optionsJSON []byte, condsJSON []byte, isPublic bool, firewallOnly bool, isOn bool) error {
func (this *HTTPAccessLogPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, name string, optionsJSON []byte, condsJSON []byte, isPublic bool, firewallOnly bool, disableDefaultDB bool, isOn bool) error {
if policyId <= 0 {
return errors.New("invalid policyId")
}
@@ -159,6 +160,7 @@ func (this *HTTPAccessLogPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, nam
op.IsPublic = isPublic
op.FirewallOnly = firewallOnly
op.DisableDefaultDB = disableDefaultDB
op.IsOn = isOn
return this.Save(tx, op)
}

View File

@@ -2,39 +2,59 @@ package models
import "github.com/iwind/TeaGo/dbs"
const (
HTTPAccessLogPolicyField_Id dbs.FieldName = "id" // ID
HTTPAccessLogPolicyField_TemplateId dbs.FieldName = "templateId" // 模版ID
HTTPAccessLogPolicyField_AdminId dbs.FieldName = "adminId" // 管理员ID
HTTPAccessLogPolicyField_UserId dbs.FieldName = "userId" // 用户ID
HTTPAccessLogPolicyField_State dbs.FieldName = "state" // 状态
HTTPAccessLogPolicyField_CreatedAt dbs.FieldName = "createdAt" // 创建时间
HTTPAccessLogPolicyField_Name dbs.FieldName = "name" // 名称
HTTPAccessLogPolicyField_IsOn dbs.FieldName = "isOn" // 是否启用
HTTPAccessLogPolicyField_Type dbs.FieldName = "type" // 存储类型
HTTPAccessLogPolicyField_Options dbs.FieldName = "options" // 存储选项
HTTPAccessLogPolicyField_Conds dbs.FieldName = "conds" // 请求条件
HTTPAccessLogPolicyField_IsPublic dbs.FieldName = "isPublic" // 是否为公用
HTTPAccessLogPolicyField_FirewallOnly dbs.FieldName = "firewallOnly" // 是否只记录防火墙相关
HTTPAccessLogPolicyField_Version dbs.FieldName = "version" // 版本号
HTTPAccessLogPolicyField_DisableDefaultDB dbs.FieldName = "disableDefaultDB" // 是否停止默认数据库存储
)
// HTTPAccessLogPolicy 访问日志策略
type HTTPAccessLogPolicy struct {
Id uint32 `field:"id"` // ID
TemplateId uint32 `field:"templateId"` // 模版ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
Name string `field:"name"` // 名称
IsOn bool `field:"isOn"` // 是否启用
Type string `field:"type"` // 存储类型
Options dbs.JSON `field:"options"` // 存储选项
Conds dbs.JSON `field:"conds"` // 请求条件
IsPublic bool `field:"isPublic"` // 是否为公用
FirewallOnly uint8 `field:"firewallOnly"` // 是否只记录防火墙相关
Version uint32 `field:"version"` // 版本号
Id uint32 `field:"id"` // ID
TemplateId uint32 `field:"templateId"` // 模版ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
Name string `field:"name"` // 名称
IsOn bool `field:"isOn"` // 是否启用
Type string `field:"type"` // 存储类型
Options dbs.JSON `field:"options"` // 存储选项
Conds dbs.JSON `field:"conds"` // 请求条件
IsPublic bool `field:"isPublic"` // 是否为公用
FirewallOnly uint8 `field:"firewallOnly"` // 是否只记录防火墙相关
Version uint32 `field:"version"` // 版本号
DisableDefaultDB bool `field:"disableDefaultDB"` // 是否停止默认数据库存储
}
type HTTPAccessLogPolicyOperator struct {
Id interface{} // ID
TemplateId interface{} // 模版ID
AdminId interface{} // 管理员ID
UserId interface{} // 用户ID
State interface{} // 状态
CreatedAt interface{} // 创建时间
Name interface{} // 名称
IsOn interface{} // 是否启用
Type interface{} // 存储类型
Options interface{} // 存储选项
Conds interface{} // 请求条件
IsPublic interface{} // 是否为公用
FirewallOnly interface{} // 是否只记录防火墙相关
Version interface{} // 版本号
Id any // ID
TemplateId any // 模版ID
AdminId any // 管理员ID
UserId any // 用户ID
State any // 状态
CreatedAt any // 创建时间
Name any // 名称
IsOn any // 是否启用
Type any // 存储类型
Options any // 存储选项
Conds any // 请求条件
IsPublic any // 是否为公用
FirewallOnly any // 是否只记录防火墙相关
Version any // 版本号
DisableDefaultDB any // 是否停止默认数据库存储
}
func NewHTTPAccessLogPolicyOperator() *HTTPAccessLogPolicyOperator {

View File

@@ -77,7 +77,7 @@ func (this *HTTPPageDAO) FindEnabledHTTPPage(tx *dbs.Tx, id int64) (*HTTPPage, e
}
// CreatePage 创建Page
func (this *HTTPPageDAO) CreatePage(tx *dbs.Tx, userId int64, statusList []string, bodyType shared.BodyType, url string, body string, newStatus int) (pageId int64, err error) {
func (this *HTTPPageDAO) CreatePage(tx *dbs.Tx, userId int64, statusList []string, bodyType serverconfigs.HTTPPageBodyType, url string, body string, newStatus int, exceptURLPatterns []*shared.URLPattern, onlyURLPatterns []*shared.URLPattern) (pageId int64, err error) {
var op = NewHTTPPageOperator()
op.UserId = userId
op.IsOn = true
@@ -94,6 +94,29 @@ func (this *HTTPPageDAO) CreatePage(tx *dbs.Tx, userId int64, statusList []strin
op.Url = url
op.Body = body
op.NewStatus = newStatus
{
if exceptURLPatterns == nil {
exceptURLPatterns = []*shared.URLPattern{}
}
exceptURLPatternsJSON, err := json.Marshal(exceptURLPatterns)
if err != nil {
return 0, err
}
op.ExceptURLPatterns = exceptURLPatternsJSON
}
{
if onlyURLPatterns == nil {
onlyURLPatterns = []*shared.URLPattern{}
}
onlyURLPatternsJSON, err := json.Marshal(onlyURLPatterns)
if err != nil {
return 0, err
}
op.OnlyURLPatterns = onlyURLPatternsJSON
}
err = this.Save(tx, op)
if err != nil {
return 0, err
@@ -103,7 +126,7 @@ func (this *HTTPPageDAO) CreatePage(tx *dbs.Tx, userId int64, statusList []strin
}
// UpdatePage 修改Page
func (this *HTTPPageDAO) UpdatePage(tx *dbs.Tx, pageId int64, statusList []string, bodyType shared.BodyType, url string, body string, newStatus int) error {
func (this *HTTPPageDAO) UpdatePage(tx *dbs.Tx, pageId int64, statusList []string, bodyType serverconfigs.HTTPPageBodyType, url string, body string, newStatus int, exceptURLPatterns []*shared.URLPattern, onlyURLPatterns []*shared.URLPattern) error {
if pageId <= 0 {
return errors.New("invalid pageId")
}
@@ -126,6 +149,29 @@ func (this *HTTPPageDAO) UpdatePage(tx *dbs.Tx, pageId int64, statusList []strin
op.Url = url
op.Body = body
op.NewStatus = newStatus
{
if exceptURLPatterns == nil {
exceptURLPatterns = []*shared.URLPattern{}
}
exceptURLPatternsJSON, err := json.Marshal(exceptURLPatterns)
if err != nil {
return err
}
op.ExceptURLPatterns = exceptURLPatternsJSON
}
{
if onlyURLPatterns == nil {
onlyURLPatterns = []*shared.URLPattern{}
}
onlyURLPatternsJSON, err := json.Marshal(onlyURLPatterns)
if err != nil {
return err
}
op.OnlyURLPatterns = onlyURLPatternsJSON
}
err = this.Save(tx, op)
if err != nil {
return err
@@ -156,6 +202,14 @@ func (this *HTTPPageDAO) ClonePage(tx *dbs.Tx, fromPageId int64) (newPageId int6
op.Body = page.Body
op.BodyType = page.BodyType
op.State = page.State
if len(page.ExceptURLPatterns) > 0 {
op.ExceptURLPatterns = page.ExceptURLPatterns
}
if len(page.OnlyURLPatterns) > 0 {
op.OnlyURLPatterns = page.OnlyURLPatterns
}
return this.SaveInt64(tx, op)
}
@@ -179,7 +233,7 @@ func (this *HTTPPageDAO) ComposePageConfig(tx *dbs.Tx, pageId int64, cacheMap *u
return nil, nil
}
config := &serverconfigs.HTTPPageConfig{}
var config = &serverconfigs.HTTPPageConfig{}
config.Id = int64(page.Id)
config.IsOn = page.IsOn
config.NewStatus = int(page.NewStatus)
@@ -188,7 +242,7 @@ func (this *HTTPPageDAO) ComposePageConfig(tx *dbs.Tx, pageId int64, cacheMap *u
config.BodyType = page.BodyType
if len(page.BodyType) == 0 {
page.BodyType = shared.BodyTypeURL
page.BodyType = serverconfigs.HTTPPageBodyTypeURL
}
if len(page.StatusList) > 0 {
@@ -202,6 +256,28 @@ func (this *HTTPPageDAO) ComposePageConfig(tx *dbs.Tx, pageId int64, cacheMap *u
}
}
if len(page.ExceptURLPatterns) > 0 {
var exceptURLPatterns = []*shared.URLPattern{}
err = json.Unmarshal(page.ExceptURLPatterns, &exceptURLPatterns)
if err != nil {
return nil, err
}
if len(exceptURLPatterns) > 0 {
config.ExceptURLPatterns = exceptURLPatterns
}
}
if len(page.OnlyURLPatterns) > 0 {
var onlyURLPatterns = []*shared.URLPattern{}
err = json.Unmarshal(page.OnlyURLPatterns, &onlyURLPatterns)
if err != nil {
return nil, err
}
if len(onlyURLPatterns) > 0 {
config.OnlyURLPatterns = onlyURLPatterns
}
}
if cacheMap != nil {
cacheMap.Put(cacheKey, config)
}

View File

@@ -2,33 +2,53 @@ package models
import "github.com/iwind/TeaGo/dbs"
const (
HTTPPageField_Id dbs.FieldName = "id" // ID
HTTPPageField_AdminId dbs.FieldName = "adminId" // 管理员ID
HTTPPageField_UserId dbs.FieldName = "userId" // 用户ID
HTTPPageField_IsOn dbs.FieldName = "isOn" // 是否启用
HTTPPageField_StatusList dbs.FieldName = "statusList" // 状态列表
HTTPPageField_Url dbs.FieldName = "url" // 页面URL
HTTPPageField_NewStatus dbs.FieldName = "newStatus" // 新状态码
HTTPPageField_State dbs.FieldName = "state" // 状态
HTTPPageField_CreatedAt dbs.FieldName = "createdAt" // 创建时间
HTTPPageField_Body dbs.FieldName = "body" // 页面内容
HTTPPageField_BodyType dbs.FieldName = "bodyType" // 内容类型
HTTPPageField_ExceptURLPatterns dbs.FieldName = "exceptURLPatterns" // 例外URL
HTTPPageField_OnlyURLPatterns dbs.FieldName = "onlyURLPatterns" // 限制URL
)
// HTTPPage 特殊页面
type HTTPPage struct {
Id uint32 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
IsOn bool `field:"isOn"` // 是否启用
StatusList dbs.JSON `field:"statusList"` // 状态列表
Url string `field:"url"` // 页面URL
NewStatus int32 `field:"newStatus"` // 新状态码
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
Body string `field:"body"` // 页面内容
BodyType string `field:"bodyType"` // 内容类型
Id uint32 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID
IsOn bool `field:"isOn"` // 是否启用
StatusList dbs.JSON `field:"statusList"` // 状态列表
Url string `field:"url"` // 页面URL
NewStatus int32 `field:"newStatus"` // 新状态码
State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
Body string `field:"body"` // 页面内容
BodyType string `field:"bodyType"` // 内容类型
ExceptURLPatterns dbs.JSON `field:"exceptURLPatterns"` // 例外URL
OnlyURLPatterns dbs.JSON `field:"onlyURLPatterns"` // 限制URL
}
type HTTPPageOperator struct {
Id interface{} // ID
AdminId interface{} // 管理员ID
UserId interface{} // 用户ID
IsOn interface{} // 是否启用
StatusList interface{} // 状态列表
Url interface{} // 页面URL
NewStatus interface{} // 新状态码
State interface{} // 状态
CreatedAt interface{} // 创建时间
Body interface{} // 页面内容
BodyType interface{} // 内容类型
Id any // ID
AdminId any // 管理员ID
UserId any // 用户ID
IsOn any // 是否启用
StatusList any // 状态列表
Url any // 页面URL
NewStatus any // 新状态码
State any // 状态
CreatedAt any // 创建时间
Body any // 页面内容
BodyType any // 内容类型
ExceptURLPatterns any // 例外URL
OnlyURLPatterns any // 限制URL
}
func NewHTTPPageOperator() *HTTPPageOperator {

View File

@@ -111,8 +111,11 @@ func (this *MessageDAO) FindEnabledMessage(tx *dbs.Tx, id int64) (*Message, erro
}
// CreateClusterMessage 创建集群消息
func (this *MessageDAO) CreateClusterMessage(tx *dbs.Tx, role string, clusterId int64, messageType MessageType, level string, subject string, body string, paramsJSON []byte) error {
_, err := this.createMessage(tx, role, clusterId, 0, messageType, level, subject, body, paramsJSON)
func (this *MessageDAO) CreateClusterMessage(tx *dbs.Tx, role string, clusterId int64, messageType MessageType, level string, subject string, shortBody string, body string, paramsJSON []byte) error {
if len(shortBody) == 0 {
shortBody = body
}
_, err := this.createMessage(tx, role, clusterId, 0, messageType, level, subject, shortBody, paramsJSON)
if err != nil {
return err
}

View File

@@ -12,7 +12,7 @@ func TestMessageDAO_CreateClusterMessage(t *testing.T) {
var tx *dbs.Tx
dao := NewMessageDAO()
err := dao.CreateClusterMessage(tx, nodeconfigs.NodeRoleNode, 1, "test", "error", "123", "123", []byte("456"))
err := dao.CreateClusterMessage(tx, nodeconfigs.NodeRoleNode, 1, "test", "error", "123", "123", "123", []byte("456"))
if err != nil {
t.Fatal(err)
}

View File

@@ -8,20 +8,6 @@ import (
"testing"
)
func TestMessageTaskDAO_FindSendingMessageTasks(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
tasks, err := models.NewMessageTaskDAO().FindSendingMessageTasks(tx, 100)
if err != nil {
t.Fatal(err)
}
t.Log(len(tasks), "tasks")
for _, task := range tasks {
t.Log("task:", task.Id, "recipient:", task.RecipientId)
}
}
func TestMessageTaskDAO_CleanExpiredMessageTasks(t *testing.T) {
var dao = models.NewMessageTaskDAO()
var tx *dbs.Tx

View File

@@ -1021,7 +1021,7 @@ func (this *NodeClusterDAO) FindClusterBasicInfo(tx *dbs.Tx, clusterId int64, ca
cluster, err := this.Query(tx).
Pk(clusterId).
State(NodeClusterStateEnabled).
Result("id", "name", "timeZone", "nodeMaxThreads", "cachePolicyId", "httpFirewallPolicyId", "autoOpenPorts", "webp", "uam", "cc", "httpPages", "http3", "isOn", "ddosProtection", "clock", "globalServerConfig", "autoInstallNftables", "autoSystemTuning").
Result("id", "name", "timeZone", "nodeMaxThreads", "cachePolicyId", "httpFirewallPolicyId", "autoOpenPorts", "webp", "uam", "cc", "httpPages", "http3", "isOn", "ddosProtection", "clock", "globalServerConfig", "autoInstallNftables", "autoSystemTuning", "networkSecurity").
Find()
if err != nil || cluster == nil {
return nil, err
@@ -1269,6 +1269,57 @@ func (this *NodeClusterDAO) FindClusterHTTP3Policy(tx *dbs.Tx, clusterId int64,
return policy, nil
}
// UpdateClusterNetworkSecurityPolicy 修改网络安全策略设置
func (this *NodeClusterDAO) UpdateClusterNetworkSecurityPolicy(tx *dbs.Tx, clusterId int64, networkSecurityPolicy *nodeconfigs.NetworkSecurityPolicy) error {
if networkSecurityPolicy == nil {
networkSecurityPolicy = nodeconfigs.NewNetworkSecurityPolicy()
}
networkSecurityPolicyJSON, err := json.Marshal(networkSecurityPolicy)
if err != nil {
return err
}
err = this.Query(tx).
Pk(clusterId).
Set("networkSecurity", networkSecurityPolicyJSON).
UpdateQuickly()
if err != nil {
return err
}
return this.NotifyNetworkSecurityUpdate(tx, clusterId)
}
// FindClusterNetworkSecurityPolicy 查询网络安全策略设置
func (this *NodeClusterDAO) FindClusterNetworkSecurityPolicy(tx *dbs.Tx, clusterId int64, cacheMap *utils.CacheMap) (*nodeconfigs.NetworkSecurityPolicy, error) {
var cacheKey = this.Table + ":FindClusterNetworkSecurityPolicy:" + types.String(clusterId)
if cacheMap != nil {
cache, ok := cacheMap.Get(cacheKey)
if ok {
return cache.(*nodeconfigs.NetworkSecurityPolicy), nil
}
}
networkSecurityPolicyJSON, err := this.Query(tx).
Pk(clusterId).
Result("networkSecurity").
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(networkSecurityPolicyJSON) {
return nodeconfigs.NewNetworkSecurityPolicy(), nil
}
var policy = nodeconfigs.NewNetworkSecurityPolicy()
err = json.Unmarshal(networkSecurityPolicyJSON, policy)
if err != nil {
return nil, err
}
return policy, nil
}
// UpdateClusterHTTPPagesPolicy 修改自定义页面设置
func (this *NodeClusterDAO) UpdateClusterHTTPPagesPolicy(tx *dbs.Tx, clusterId int64, httpPagesPolicy *nodeconfigs.HTTPPagesPolicy) error {
if httpPagesPolicy == nil {
@@ -1452,6 +1503,11 @@ func (this *NodeClusterDAO) NotifyHTTP3Update(tx *dbs.Tx, clusterId int64) error
return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, 0, 0, NodeTaskTypeHTTP3PolicyChanged)
}
// NotifyNetworkSecurityUpdate 通知网络安全策略更新
func (this *NodeClusterDAO) NotifyNetworkSecurityUpdate(tx *dbs.Tx, clusterId int64) error {
return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, 0, 0, NodeTaskTypeNetworkSecurityPolicyChanged)
}
// NotifyHTTPPagesPolicyUpdate 通知HTTP Pages更新
func (this *NodeClusterDAO) NotifyHTTPPagesPolicyUpdate(tx *dbs.Tx, clusterId int64) error {
return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, 0, 0, NodeTaskTypeHTTPPagesPolicyChanged)

View File

@@ -44,6 +44,7 @@ const (
NodeClusterField_Cc dbs.FieldName = "cc" // CC设置
NodeClusterField_Http3 dbs.FieldName = "http3" // HTTP3设置
NodeClusterField_AutoSystemTuning dbs.FieldName = "autoSystemTuning" // 是否自动调整系统参数
NodeClusterField_NetworkSecurity dbs.FieldName = "networkSecurity" // 网络安全策略
)
// NodeCluster 节点集群
@@ -89,6 +90,7 @@ type NodeCluster struct {
Cc dbs.JSON `field:"cc"` // CC设置
Http3 dbs.JSON `field:"http3"` // HTTP3设置
AutoSystemTuning bool `field:"autoSystemTuning"` // 是否自动调整系统参数
NetworkSecurity dbs.JSON `field:"networkSecurity"` // 网络安全策略
}
type NodeClusterOperator struct {
@@ -133,6 +135,7 @@ type NodeClusterOperator struct {
Cc any // CC设置
Http3 any // HTTP3设置
AutoSystemTuning any // 是否自动调整系统参数
NetworkSecurity any // 网络安全策略
}
func NewNodeClusterOperator() *NodeClusterOperator {

View File

@@ -37,7 +37,7 @@ func (this *NodeCluster) DecodeDDoSProtection() *ddosconfigs.ProtectionConfig {
return result
}
// HasDDoSProtection 检查是否有DDOS设置
// HasDDoSProtection 检查是否有DDoS设置
func (this *NodeCluster) HasDDoSProtection() bool {
var config = this.DecodeDDoSProtection()
if config != nil {
@@ -46,6 +46,27 @@ func (this *NodeCluster) HasDDoSProtection() bool {
return false
}
// HasNetworkSecurityPolicy 检查是否有安全策略设置
func (this *NodeCluster) HasNetworkSecurityPolicy() bool {
var policy = this.DecodeNetworkSecurityPolicy()
if policy != nil {
return policy.IsOn()
}
return false
}
// DecodeNetworkSecurityPolicy 解析安全策略设置
func (this *NodeCluster) DecodeNetworkSecurityPolicy() *nodeconfigs.NetworkSecurityPolicy {
var policy = nodeconfigs.NewNetworkSecurityPolicy()
if IsNotNull(this.NetworkSecurity) {
err := json.Unmarshal(this.NetworkSecurity, policy)
if err != nil {
remotelogs.Error("NodeCluster.DecodeNetworkSecurityPolicy()", err.Error())
}
}
return policy
}
// DecodeClock 解析时钟配置
func (this *NodeCluster) DecodeClock() *nodeconfigs.ClockConfig {
var clock = nodeconfigs.DefaultClockConfig()

View File

@@ -1018,6 +1018,13 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, dataMap *shared
}
config.AllowedIPs = append(config.AllowedIPs, apiNodeIPs...)
// 当前的节点IP地址
nodeNodeIPs, err := SharedNodeIPAddressDAO.FindAllEnabledAddressStringsWithNode(tx, nodeId, nodeconfigs.NodeRoleNode)
if err != nil {
return nil, err
}
config.IPAddresses = nodeNodeIPs
// 所属集群
var primaryClusterId = int64(node.ClusterId)
var clusterIds = []int64{primaryClusterId}
@@ -1076,7 +1083,7 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, dataMap *shared
continue
}
// 节点IP地址
// 所有节点IP地址
nodeIPAddresses, err := SharedNodeIPAddressDAO.FindAllAccessibleIPAddressesWithClusterId(tx, nodeconfigs.NodeRoleNode, clusterId, cacheMap)
if err != nil {
return nil, err
@@ -1221,6 +1228,11 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, dataMap *shared
config.AutoSystemTuning = nodeCluster.AutoSystemTuning
}
// 安全设置
if clusterIndex == 0 {
config.NetworkSecurityPolicy = nodeCluster.DecodeNetworkSecurityPolicy()
}
clusterIndex++
}

View File

@@ -89,7 +89,9 @@ func (this *NodeGrantDAO) CreateGrant(tx *dbs.Tx, adminId int64, name string, me
op.PrivateKey = privateKey
op.Passphrase = passphrase
}
op.Su = su
if username != "root" { // only for non-root user
op.Su = su
}
op.Description = description
op.NodeId = nodeId
op.State = NodeGrantStateEnabled
@@ -117,7 +119,11 @@ func (this *NodeGrantDAO) UpdateGrant(tx *dbs.Tx, grantId int64, name string, me
op.PrivateKey = privateKey
op.Passphrase = passphrase
}
op.Su = su
if username != "root" { // only for non-root user
op.Su = su
} else {
op.Su = false
}
op.Description = description
op.NodeId = nodeId
err := this.Save(tx, op)

View File

@@ -256,6 +256,32 @@ func (this *NodeIPAddressDAO) FindAllEnabledAddressesWithNode(tx *dbs.Tx, nodeId
return
}
// FindAllEnabledAddressStringsWithNode 查找节点的所有的IP地址地府传
func (this *NodeIPAddressDAO) FindAllEnabledAddressStringsWithNode(tx *dbs.Tx, nodeId int64, role nodeconfigs.NodeRole) (result []string, err error) {
if len(role) == 0 {
role = nodeconfigs.NodeRoleNode
}
ones, err := this.Query(tx).
Attr("nodeId", nodeId).
Attr("role", role).
State(NodeIPAddressStateEnabled).
Result("ip", "backupIP").
FindAll()
if err != nil {
return nil, err
}
for _, one := range ones {
var addr = one.(*NodeIPAddress)
result = append(result, addr.Ip)
if len(addr.BackupIP) > 0 {
result = append(result, addr.BackupIP)
}
}
return
}
// FindFirstNodeAccessIPAddress 查找节点的第一个可访问的IP地址
func (this *NodeIPAddressDAO) FindFirstNodeAccessIPAddress(tx *dbs.Tx, nodeId int64, mustUp bool, role nodeconfigs.NodeRole) (ip string, addrId int64, err error) {
if len(role) == 0 {

View File

@@ -17,21 +17,22 @@ type NodeTaskType = string
const (
// CDN相关
NodeTaskTypeConfigChanged NodeTaskType = "configChanged" // 节点整体配置变化
NodeTaskTypeDDosProtectionChanged NodeTaskType = "ddosProtectionChanged" // 节点DDoS配置变更
NodeTaskTypeGlobalServerConfigChanged NodeTaskType = "globalServerConfigChanged" // 全局服务设置变化
NodeTaskTypeIPListDeleted NodeTaskType = "ipListDeleted" // IPList被删除
NodeTaskTypeIPItemChanged NodeTaskType = "ipItemChanged" // IP条目变更
NodeTaskTypeNodeVersionChanged NodeTaskType = "nodeVersionChanged" // 节点版本变化
NodeTaskTypeScriptsChanged NodeTaskType = "scriptsChanged" // 脚本配置变化
NodeTaskTypeNodeLevelChanged NodeTaskType = "nodeLevelChanged" // 节点级别变化
NodeTaskTypeUserServersStateChanged NodeTaskType = "userServersStateChanged" // 用户服务状态变化
NodeTaskTypeUAMPolicyChanged NodeTaskType = "uamPolicyChanged" // UAM策略变化
NodeTaskTypeHTTPPagesPolicyChanged NodeTaskType = "httpPagesPolicyChanged" // 自定义页面变化
NodeTaskTypeHTTPCCPolicyChanged NodeTaskType = "httpCCPolicyChanged" // CC策略变化
NodeTaskTypeHTTP3PolicyChanged NodeTaskType = "http3PolicyChanged" // HTTP3策略变化
NodeTaskTypeUpdatingServers NodeTaskType = "updatingServers" // 更新一组服务
NodeTaskTypeTOAChanged NodeTaskType = "toaChanged" // TOA配置变化
NodeTaskTypeConfigChanged NodeTaskType = "configChanged" // 节点整体配置变化
NodeTaskTypeDDosProtectionChanged NodeTaskType = "ddosProtectionChanged" // 节点DDoS配置变更
NodeTaskTypeGlobalServerConfigChanged NodeTaskType = "globalServerConfigChanged" // 全局服务设置变化
NodeTaskTypeIPListDeleted NodeTaskType = "ipListDeleted" // IPList被删除
NodeTaskTypeIPItemChanged NodeTaskType = "ipItemChanged" // IP条目变更
NodeTaskTypeNodeVersionChanged NodeTaskType = "nodeVersionChanged" // 节点版本变化
NodeTaskTypeScriptsChanged NodeTaskType = "scriptsChanged" // 脚本配置变化
NodeTaskTypeNodeLevelChanged NodeTaskType = "nodeLevelChanged" // 节点级别变化
NodeTaskTypeUserServersStateChanged NodeTaskType = "userServersStateChanged" // 用户服务状态变化
NodeTaskTypeUAMPolicyChanged NodeTaskType = "uamPolicyChanged" // UAM策略变化
NodeTaskTypeHTTPPagesPolicyChanged NodeTaskType = "httpPagesPolicyChanged" // 自定义页面变化
NodeTaskTypeHTTPCCPolicyChanged NodeTaskType = "httpCCPolicyChanged" // CC策略变化
NodeTaskTypeHTTP3PolicyChanged NodeTaskType = "http3PolicyChanged" // HTTP3策略变化
NodeTaskTypeNetworkSecurityPolicyChanged NodeTaskType = "networkSecurityPolicyChanged" // 网络安全策略变化
NodeTaskTypeUpdatingServers NodeTaskType = "updatingServers" // 更新一组服务
NodeTaskTypeTOAChanged NodeTaskType = "toaChanged" // TOA配置变化
// NS相关

View File

@@ -1,17 +1,11 @@
package models
import (
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"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/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
"time"
)
const (
@@ -153,12 +147,13 @@ func (this *NodeThresholdDAO) FindAllEnabledAndOnClusterThresholds(tx *dbs.Tx, r
}
// FindAllEnabledAndOnNodeThresholds 查询节点专属的阈值设置
func (this *NodeThresholdDAO) FindAllEnabledAndOnNodeThresholds(tx *dbs.Tx, role string, nodeId int64, item string) (result []*NodeThreshold, err error) {
if nodeId <= 0 {
func (this *NodeThresholdDAO) FindAllEnabledAndOnNodeThresholds(tx *dbs.Tx, role string, clusterId int64, nodeId int64, item string) (result []*NodeThreshold, err error) {
if clusterId <= 0 || nodeId <= 0 {
return
}
_, err = this.Query(tx).
Attr("role", role).
Attr("clusterId", clusterId).
Attr("nodeId", nodeId).
Attr("item", item).
Attr("isOn", true).
@@ -186,87 +181,3 @@ func (this *NodeThresholdDAO) CountAllEnabledThresholds(tx *dbs.Tx, role string,
query.State(NodeThresholdStateEnabled)
return query.Count()
}
// FireNodeThreshold 触发相关阈值设置
func (this *NodeThresholdDAO) FireNodeThreshold(tx *dbs.Tx, role string, nodeId int64, item string) error {
clusterId, err := SharedNodeDAO.FindNodeClusterId(tx, nodeId)
if err != nil {
return err
}
if clusterId == 0 {
return nil
}
// 集群相关阈值
var thresholds []*NodeThreshold
{
clusterThresholds, err := this.FindAllEnabledAndOnClusterThresholds(tx, role, clusterId, item)
if err != nil {
return err
}
thresholds = append(thresholds, clusterThresholds...)
}
// 节点相关阈值
{
nodeThresholds, err := this.FindAllEnabledAndOnNodeThresholds(tx, role, nodeId, item)
if err != nil {
return err
}
thresholds = append(thresholds, nodeThresholds...)
}
if len(thresholds) > 0 {
for _, threshold := range thresholds {
if len(threshold.Param) == 0 || threshold.Duration <= 0 {
continue
}
paramValue, err := SharedNodeValueDAO.SumNodeValues(tx, role, nodeId, item, threshold.Param, threshold.SumMethod, types.Int32(threshold.Duration), threshold.DurationUnit)
if err != nil {
return err
}
originValue := nodeconfigs.UnmarshalNodeValue(threshold.Value)
thresholdValue := types.Float64(originValue)
isMatched := nodeconfigs.CompareNodeValue(threshold.Operator, paramValue, thresholdValue)
if isMatched {
// TODO 执行其他动作
// 是否已经通知过
if threshold.NotifyDuration > 0 && threshold.NotifiedAt > 0 && time.Now().Unix()-int64(threshold.NotifiedAt) < int64(threshold.NotifyDuration*60) {
continue
}
// 创建消息
nodeName, err := SharedNodeDAO.FindNodeName(tx, nodeId)
if err != nil {
return err
}
itemName := nodeconfigs.FindNodeValueItemName(threshold.Item)
paramName := nodeconfigs.FindNodeValueItemParamName(threshold.Item, threshold.Param)
operatorName := nodeconfigs.FindNodeValueOperatorName(threshold.Operator)
subject := "节点 \"" + nodeName + "\" " + itemName + " 达到阈值"
body := "节点 \"" + nodeName + "\" " + itemName + " 达到阈值\n阈值设置" + paramName + " " + operatorName + " " + originValue + "\n当前值" + fmt.Sprintf("%.2f", paramValue) + "\n触发时间" + timeutil.Format("Y-m-d H:i:s")
if len(threshold.Message) > 0 {
body = threshold.Message
body = strings.Replace(body, "${item.name}", itemName, -1)
body = strings.Replace(body, "${value}", fmt.Sprintf("%.2f", paramValue), -1)
}
err = SharedMessageDAO.CreateNodeMessage(tx, role, clusterId, nodeId, MessageTypeThresholdSatisfied, MessageLevelWarning, subject, body, maps.Map{}.AsJSON(), true)
if err != nil {
return err
}
// 设置通知时间
_, err = this.Query(tx).
Pk(threshold.Id).
Set("notifiedAt", time.Now().Unix()).
Update()
if err != nil {
return err
}
}
}
}
return nil
}

View File

@@ -0,0 +1,12 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
//go:build !plus
package models
import "github.com/iwind/TeaGo/dbs"
// FireNodeThreshold 触发相关阈值设置
func (this *NodeThresholdDAO) FireNodeThreshold(tx *dbs.Tx, role string, nodeId int64, item string) error {
// stub
return nil
}

View File

@@ -227,6 +227,8 @@ func (this *OriginDAO) UpdateOrigin(tx *dbs.Tx,
return err
}
op.Oss = ossConfigJSON
} else {
op.Oss = dbs.SQL("NULL")
}
op.Description = description
@@ -400,6 +402,7 @@ func (this *OriginDAO) ComposeOriginConfig(tx *dbs.Tx, originId int64, dataMap *
}
// addr
var isOSS = false
if IsNotNull(origin.Addr) {
var addr = &serverconfigs.NetworkAddressConfig{}
err = json.Unmarshal(origin.Addr, addr)
@@ -407,10 +410,11 @@ func (this *OriginDAO) ComposeOriginConfig(tx *dbs.Tx, originId int64, dataMap *
return nil, err
}
config.Addr = addr
isOSS = ossconfigs.IsOSSProtocol(string(addr.Protocol))
}
// oss
if IsNotNull(origin.Oss) {
if isOSS && IsNotNull(origin.Oss) {
var ossConfig = ossconfigs.NewOSSConfig()
err = json.Unmarshal(origin.Oss, ossConfig)
if err != nil {

View File

@@ -110,6 +110,7 @@ func (this *ReverseProxyDAO) ComposeReverseProxyConfig(tx *dbs.Tx, reverseProxyI
config.AutoFlush = reverseProxy.AutoFlush == 1
config.FollowRedirects = reverseProxy.FollowRedirects == 1
config.Retry50X = reverseProxy.Retry50X
config.Retry40X = reverseProxy.Retry40X
var schedulingConfig = &serverconfigs.SchedulingConfig{}
if IsNotNull(reverseProxy.Scheduling) {
@@ -219,7 +220,8 @@ func (this *ReverseProxyDAO) CreateReverseProxy(tx *dbs.Tx, adminId int64, userI
op.AdminId = adminId
op.UserId = userId
op.RequestHostType = serverconfigs.RequestHostTypeProxyServer
op.Retry50X = true
op.Retry50X = false
op.Retry40X = false
defaultHeaders := []string{"X-Real-IP", "X-Forwarded-For", "X-Forwarded-By", "X-Forwarded-Host", "X-Forwarded-Proto"}
defaultHeadersJSON, err := json.Marshal(defaultHeaders)
@@ -428,7 +430,8 @@ func (this *ReverseProxyDAO) UpdateReverseProxy(tx *dbs.Tx,
maxIdleConns int32,
proxyProtocolJSON []byte,
followRedirects bool,
retry50X bool) error {
retry50X bool,
retry40X bool) error {
if reverseProxyId <= 0 {
return errors.New("invalid reverseProxyId")
}
@@ -494,6 +497,7 @@ func (this *ReverseProxyDAO) UpdateReverseProxy(tx *dbs.Tx,
}
op.Retry50X = retry50X
op.Retry40X = retry40X
err = this.Save(tx, op)
if err != nil {

View File

@@ -28,6 +28,7 @@ const (
ReverseProxyField_ProxyProtocol dbs.FieldName = "proxyProtocol" // Proxy Protocol配置
ReverseProxyField_FollowRedirects dbs.FieldName = "followRedirects" // 回源跟随
ReverseProxyField_Retry50X dbs.FieldName = "retry50X" // 启用50X重试
ReverseProxyField_Retry40X dbs.FieldName = "retry40X" // 启用40X重试
)
// ReverseProxy 反向代理配置
@@ -57,6 +58,7 @@ type ReverseProxy struct {
ProxyProtocol dbs.JSON `field:"proxyProtocol"` // Proxy Protocol配置
FollowRedirects uint8 `field:"followRedirects"` // 回源跟随
Retry50X bool `field:"retry50X"` // 启用50X重试
Retry40X bool `field:"retry40X"` // 启用40X重试
}
type ReverseProxyOperator struct {
@@ -85,6 +87,7 @@ type ReverseProxyOperator struct {
ProxyProtocol any // Proxy Protocol配置
FollowRedirects any // 回源跟随
Retry50X any // 启用50X重试
Retry40X any // 启用40X重试
}
func NewReverseProxyOperator() *ReverseProxyOperator {

View File

@@ -83,7 +83,7 @@ func (this *ServerDomainHourlyStatDAO) IncreaseHourlyStat(tx *dbs.Tx, clusterId
if len(hour) != 10 {
return errors.New("invalid hour '" + hour + "'")
}
if len(domain) == 0 {
if len(domain) == 0 || len(domain) > 64 {
return nil
}
err := this.Query(tx).

View File

@@ -428,6 +428,20 @@ func (this *UserDAO) CheckUserEmailPassword(tx *dbs.Tx, verifiedEmail string, en
FindInt64Col(0)
}
// CheckUserMobilePassword 检查邮箱+密码
func (this *UserDAO) CheckUserMobilePassword(tx *dbs.Tx, verifiedEmail string, encryptedPassword string) (int64, error) {
if len(verifiedEmail) == 0 || len(encryptedPassword) == 0 {
return 0, nil
}
return this.Query(tx).
Attr("verifiedMobile", verifiedEmail).
Attr("password", encryptedPassword).
Attr("state", UserStateEnabled).
Attr("isOn", true).
ResultPk().
FindInt64Col(0)
}
// FindUserClusterId 查找用户所在集群
func (this *UserDAO) FindUserClusterId(tx *dbs.Tx, userId int64) (int64, error) {
return this.Query(tx).
@@ -663,7 +677,7 @@ func (this *UserDAO) RenewUserServersState(tx *dbs.Tx, userId int64) (bool, erro
// FindUserIdWithVerifiedEmail 使用验证后Email查找用户ID
func (this *UserDAO) FindUserIdWithVerifiedEmail(tx *dbs.Tx, verifiedEmail string) (int64, error) {
if len(verifiedEmail) == 0 {
return 0, nil
}
return this.Query(tx).
ResultPk().
@@ -672,6 +686,18 @@ func (this *UserDAO) FindUserIdWithVerifiedEmail(tx *dbs.Tx, verifiedEmail strin
FindInt64Col(0)
}
// FindUserIdWithVerifiedMobile 使用验证后手机号码查找用户ID
func (this *UserDAO) FindUserIdWithVerifiedMobile(tx *dbs.Tx, verifiedMobile string) (int64, error) {
if len(verifiedMobile) == 0 {
return 0, nil
}
return this.Query(tx).
ResultPk().
State(UserStateEnabled).
Attr("verifiedMobile", verifiedMobile).
FindInt64Col(0)
}
// UpdateUserVerifiedEmail 修改已激活邮箱
func (this *UserDAO) UpdateUserVerifiedEmail(tx *dbs.Tx, userId int64, verifiedEmail string) error {
if userId <= 0 {
@@ -684,6 +710,18 @@ func (this *UserDAO) UpdateUserVerifiedEmail(tx *dbs.Tx, userId int64, verifiedE
UpdateQuickly()
}
// UpdateUserVerifiedMobile 修改已激活手机号码
func (this *UserDAO) UpdateUserVerifiedMobile(tx *dbs.Tx, userId int64, verifiedMobile string) error {
if userId <= 0 {
return nil
}
return this.Query(tx).
Pk(userId).
Set("verifiedMobile", verifiedMobile).
Set("mobileIsVerified", true).
UpdateQuickly()
}
// FindUserBandwidthAlgoForView 获取用户浏览用的带宽算法
func (this *UserDAO) FindUserBandwidthAlgoForView(tx *dbs.Tx, userId int64, uiConfig *systemconfigs.UserUIConfig) (bandwidthAlgo string, err error) {
bandwidthAlgo, err = this.Query(tx).

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 UserMobileVerificationDAO dbs.DAO
func NewUserMobileVerificationDAO() *UserMobileVerificationDAO {
return dbs.NewDAO(&UserMobileVerificationDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeUserMobileVerifications",
Model: new(UserMobileVerification),
PkName: "id",
},
}).(*UserMobileVerificationDAO)
}
var SharedUserMobileVerificationDAO *UserMobileVerificationDAO
func init() {
dbs.OnReady(func() {
SharedUserMobileVerificationDAO = NewUserMobileVerificationDAO()
})
}

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,41 @@
package models
import "github.com/iwind/TeaGo/dbs"
const (
UserMobileVerificationField_Id dbs.FieldName = "id" // ID
UserMobileVerificationField_Mobile dbs.FieldName = "mobile" // 手机号码
UserMobileVerificationField_UserId dbs.FieldName = "userId" // 用户ID
UserMobileVerificationField_Code dbs.FieldName = "code" // 激活码
UserMobileVerificationField_CreatedAt dbs.FieldName = "createdAt" // 创建时间
UserMobileVerificationField_IsSent dbs.FieldName = "isSent" // 是否已发送
UserMobileVerificationField_IsVerified dbs.FieldName = "isVerified" // 是否已激活
UserMobileVerificationField_Day dbs.FieldName = "day" // YYYYMMDD
)
// UserMobileVerification 邮箱激活邮件队列
type UserMobileVerification struct {
Id uint64 `field:"id"` // ID
Mobile string `field:"mobile"` // 手机号码
UserId uint64 `field:"userId"` // 用户ID
Code string `field:"code"` // 激活码
CreatedAt uint64 `field:"createdAt"` // 创建时间
IsSent bool `field:"isSent"` // 是否已发送
IsVerified bool `field:"isVerified"` // 是否已激活
Day string `field:"day"` // YYYYMMDD
}
type UserMobileVerificationOperator struct {
Id any // ID
Mobile any // 手机号码
UserId any // 用户ID
Code any // 激活码
CreatedAt any // 创建时间
IsSent any // 是否已发送
IsVerified any // 是否已激活
Day any // YYYYMMDD
}
func NewUserMobileVerificationOperator() *UserMobileVerificationOperator {
return &UserMobileVerificationOperator{}
}

View File

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

View File

@@ -10,6 +10,7 @@ const (
UserField_Fullname dbs.FieldName = "fullname" // 真实姓名
UserField_Mobile dbs.FieldName = "mobile" // 手机号
UserField_VerifiedMobile dbs.FieldName = "verifiedMobile" // 已验证手机号
UserField_MobileIsVerified dbs.FieldName = "mobileIsVerified" // 手机号是否已验证
UserField_Tel dbs.FieldName = "tel" // 联系电话
UserField_Remark dbs.FieldName = "remark" // 备注
UserField_Email dbs.FieldName = "email" // 邮箱地址
@@ -47,6 +48,7 @@ type User struct {
Fullname string `field:"fullname"` // 真实姓名
Mobile string `field:"mobile"` // 手机号
VerifiedMobile string `field:"verifiedMobile"` // 已验证手机号
MobileIsVerified uint8 `field:"mobileIsVerified"` // 手机号是否已验证
Tel string `field:"tel"` // 联系电话
Remark string `field:"remark"` // 备注
Email string `field:"email"` // 邮箱地址
@@ -83,6 +85,7 @@ type UserOperator struct {
Fullname any // 真实姓名
Mobile any // 手机号
VerifiedMobile any // 已验证手机号
MobileIsVerified any // 手机号是否已验证
Tel any // 联系电话
Remark any // 备注
Email any // 邮箱地址

View File

@@ -32,8 +32,6 @@ var dnsPodHTTPClient = &http.Client{
}
// DNSPodProvider DNSPod服务商
// TODO 考虑支持线路ID
// TODO 支持自定义线路
type DNSPodProvider struct {
BaseProvider
@@ -42,19 +40,30 @@ type DNSPodProvider struct {
region string
apiId string
apiToken string
tencentDNSProvider *TencentDNSProvider
}
// Auth 认证
func (this *DNSPodProvider) Auth(params maps.Map) error {
this.apiId = params.GetString("id")
this.apiToken = params.GetString("token")
this.region = params.GetString("region")
// 兼容腾讯云API
var apiType = params.GetString("apiType")
if len(this.apiId) == 0 {
return errors.New("'id' should be not empty")
}
if len(this.apiToken) == 0 {
return errors.New("'token' should not be empty")
switch apiType {
case "tencentDNS":
this.tencentDNSProvider = NewTencentDNSProvider()
return this.tencentDNSProvider.Auth(params)
default:
this.apiId = params.GetString("id")
this.apiToken = params.GetString("token")
this.region = params.GetString("region")
if len(this.apiId) == 0 {
return errors.New("'id' should be not empty")
}
if len(this.apiToken) == 0 {
return errors.New("'token' should not be empty")
}
}
return nil
@@ -62,6 +71,10 @@ func (this *DNSPodProvider) Auth(params maps.Map) error {
// GetDomains 获取所有域名列表
func (this *DNSPodProvider) GetDomains() (domains []string, err error) {
if this.tencentDNSProvider != nil {
return this.tencentDNSProvider.GetDomains()
}
var offset = 0
var size = 3000
@@ -92,6 +105,10 @@ func (this *DNSPodProvider) GetDomains() (domains []string, err error) {
// GetRecords 获取域名列表
func (this *DNSPodProvider) GetRecords(domain string) (records []*dnstypes.Record, err error) {
if this.tencentDNSProvider != nil {
return this.tencentDNSProvider.GetRecords(domain)
}
var offset = 0
var size = 3000
for {
@@ -135,6 +152,10 @@ func (this *DNSPodProvider) GetRecords(domain string) (records []*dnstypes.Recor
// GetRoutes 读取线路数据
func (this *DNSPodProvider) GetRoutes(domain string) (routes []*dnstypes.Route, err error) {
if this.tencentDNSProvider != nil {
return this.tencentDNSProvider.GetRoutes(domain)
}
var domainInfoResp = new(dnspod.DomainInfoResponse)
err = this.doAPI("/Domain.Info", map[string]string{
"domain": domain,
@@ -217,6 +238,10 @@ func (this *DNSPodProvider) GetRoutes(domain string) (routes []*dnstypes.Route,
// QueryRecord 查询单个记录
func (this *DNSPodProvider) QueryRecord(domain string, name string, recordType dnstypes.RecordType) (*dnstypes.Record, error) {
if this.tencentDNSProvider != nil {
return this.tencentDNSProvider.QueryRecord(domain, name, recordType)
}
// 从缓存中读取
if this.ProviderId > 0 {
record, hasRecords, _ := sharedDomainRecordsCache.QueryDomainRecord(this.ProviderId, domain, name, recordType)
@@ -239,6 +264,10 @@ func (this *DNSPodProvider) QueryRecord(domain string, name string, recordType d
// QueryRecords 查询多个记录
func (this *DNSPodProvider) QueryRecords(domain string, name string, recordType dnstypes.RecordType) ([]*dnstypes.Record, error) {
if this.tencentDNSProvider != nil {
return this.tencentDNSProvider.QueryRecords(domain, name, recordType)
}
// 从缓存中读取
if this.ProviderId > 0 {
records, hasRecords, _ := sharedDomainRecordsCache.QueryDomainRecords(this.ProviderId, domain, name, recordType)
@@ -262,6 +291,10 @@ func (this *DNSPodProvider) QueryRecords(domain string, name string, recordType
// AddRecord 设置记录
func (this *DNSPodProvider) AddRecord(domain string, newRecord *dnstypes.Record) error {
if this.tencentDNSProvider != nil {
return this.tencentDNSProvider.AddRecord(domain, newRecord)
}
if newRecord == nil {
return errors.New("invalid new record")
}
@@ -298,6 +331,10 @@ func (this *DNSPodProvider) AddRecord(domain string, newRecord *dnstypes.Record)
// UpdateRecord 修改记录
func (this *DNSPodProvider) UpdateRecord(domain string, record *dnstypes.Record, newRecord *dnstypes.Record) error {
if this.tencentDNSProvider != nil {
return this.tencentDNSProvider.UpdateRecord(domain, record, newRecord)
}
if record == nil {
return errors.New("invalid record")
}
@@ -339,6 +376,10 @@ func (this *DNSPodProvider) UpdateRecord(domain string, record *dnstypes.Record,
// DeleteRecord 删除记录
func (this *DNSPodProvider) DeleteRecord(domain string, record *dnstypes.Record) error {
if this.tencentDNSProvider != nil {
return this.tencentDNSProvider.DeleteRecord(domain, record)
}
if record == nil {
return errors.New("invalid record to delete")
}
@@ -412,6 +453,10 @@ func (this *DNSPodProvider) doAPI(path string, params map[string]string, respPtr
// DefaultRoute 默认线路
func (this *DNSPodProvider) DefaultRoute() string {
if this.tencentDNSProvider != nil {
return this.tencentDNSProvider.DefaultRoute()
}
if this.isInternational() {
return "Default"
}

View File

@@ -0,0 +1,409 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package dnsclients
import (
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
tencenterrors "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
dnspod "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod/v20210323"
"strings"
)
// TencentDNSProvider 腾讯云DNS云解析
type TencentDNSProvider struct {
BaseProvider
ProviderId int64
client *dnspod.Client
}
func NewTencentDNSProvider() *TencentDNSProvider {
return &TencentDNSProvider{}
}
// Auth 认证
func (this *TencentDNSProvider) Auth(params maps.Map) error {
var accessKeyId = params.GetString("accessKeyId")
var accessKeySecret = params.GetString("accessKeySecret")
if len(accessKeyId) == 0 {
return errors.New("'accessKeyId' required")
}
if len(accessKeySecret) == 0 {
return errors.New("'accessKeySecret' required")
}
client, err := dnspod.NewClient(common.NewCredential(accessKeyId, accessKeySecret), "", profile.NewClientProfile())
if err != nil {
return err
}
this.client = client
return nil
}
// GetDomains 获取所有域名列表
func (this *TencentDNSProvider) GetDomains() (domains []string, err error) {
var offset int64 = 0
var limit int64 = 1000
for {
var req = dnspod.NewDescribeDomainListRequest()
req.Offset = this.int64Val(offset)
req.Limit = this.int64Val(limit)
resp, respErr := this.client.DescribeDomainList(req)
if respErr != nil {
if this.isNotFoundErr(respErr) {
break
}
return nil, respErr
}
var countDomains = len(resp.Response.DomainList)
if countDomains == 0 {
break
}
for _, domainObj := range resp.Response.DomainList {
domains = append(domains, *domainObj.Name)
}
offset += int64(countDomains)
}
return
}
// GetRecords 获取域名列表
func (this *TencentDNSProvider) GetRecords(domain string) (records []*dnstypes.Record, err error) {
var offset uint64 = 0
var limit uint64 = 1000
for {
var req = dnspod.NewDescribeRecordListRequest()
req.Domain = this.stringVal(domain)
req.Offset = this.uint64Val(offset)
req.Limit = this.uint64Val(limit)
resp, respErr := this.client.DescribeRecordList(req)
if respErr != nil {
if this.isNotFoundErr(respErr) {
break
}
return nil, respErr
}
var countRecords = len(resp.Response.RecordList)
if countRecords == 0 {
break
}
for _, recordObj := range resp.Response.RecordList {
records = append(records, &dnstypes.Record{
Id: types.String(*recordObj.RecordId),
Name: *recordObj.Name,
Type: *recordObj.Type,
Value: this.fixCNAME(*recordObj.Type, *recordObj.Value),
Route: *recordObj.LineId,
TTL: types.Int32(*recordObj.TTL),
})
}
offset += uint64(countRecords)
}
// 写入缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.WriteDomainRecords(this.ProviderId, domain, records)
}
return
}
// GetRoutes 读取线路数据
func (this *TencentDNSProvider) GetRoutes(domain string) (routes []*dnstypes.Route, err error) {
// 等级信息
var domainGrade string
{
var req = dnspod.NewDescribeDomainRequest()
req.Domain = this.stringVal(domain)
resp, respErr := this.client.DescribeDomain(req)
if respErr != nil {
if this.isNotFoundErr(respErr) {
return
}
return nil, respErr
}
if resp.Response.DomainInfo == nil {
return
}
domainGrade = *resp.Response.DomainInfo.Grade
}
// 等级允许的线路
{
var req = dnspod.NewDescribeRecordLineListRequest()
req.Domain = this.stringVal(domain)
req.DomainGrade = this.stringVal(domainGrade)
resp, respErr := this.client.DescribeRecordLineList(req)
if respErr != nil {
return nil, respErr
}
for _, lineGroupObj := range resp.Response.LineGroupList {
routes = append(routes, &dnstypes.Route{
Name: "Group:" + *lineGroupObj.Name,
Code: *lineGroupObj.LineId,
})
}
for _, lineObj := range resp.Response.LineList {
routes = append(routes, &dnstypes.Route{
Name: *lineObj.Name,
Code: *lineObj.LineId,
})
}
}
return
}
// QueryRecord 查询单个记录
func (this *TencentDNSProvider) QueryRecord(domain string, name string, recordType dnstypes.RecordType) (*dnstypes.Record, error) {
// 从缓存中读取
if this.ProviderId > 0 {
record, hasRecords, _ := sharedDomainRecordsCache.QueryDomainRecord(this.ProviderId, domain, name, recordType)
if hasRecords { // 有效的搜索
return record, nil
}
}
var offset uint64 = 0
var limit uint64 = 1000
var req = dnspod.NewDescribeRecordFilterListRequest()
req.Domain = this.stringVal(domain)
req.Offset = this.uint64Val(offset)
req.Limit = this.uint64Val(limit)
req.SubDomain = this.stringVal(name)
req.RecordType = []*string{this.stringVal(recordType)}
resp, respErr := this.client.DescribeRecordFilterList(req)
if respErr != nil {
if this.isNotFoundErr(respErr) {
return nil, nil
}
return nil, respErr
}
var countRecords = len(resp.Response.RecordList)
if countRecords == 0 {
return nil, nil
}
for _, recordObj := range resp.Response.RecordList {
if *recordObj.Name == name && *recordObj.Type == recordType {
return &dnstypes.Record{
Id: types.String(*recordObj.RecordId),
Name: *recordObj.Name,
Type: *recordObj.Type,
Value: this.fixCNAME(*recordObj.Type, *recordObj.Value),
Route: *recordObj.LineId,
TTL: types.Int32(*recordObj.TTL),
}, nil
}
}
return nil, nil
}
// QueryRecords 查询多个记录
func (this *TencentDNSProvider) QueryRecords(domain string, name string, recordType dnstypes.RecordType) ([]*dnstypes.Record, error) {
// 从缓存中读取
if this.ProviderId > 0 {
records, hasRecords, _ := sharedDomainRecordsCache.QueryDomainRecords(this.ProviderId, domain, name, recordType)
if hasRecords { // 有效的搜索
return records, nil
}
}
var offset uint64 = 0
var limit uint64 = 1000
var records []*dnstypes.Record
for {
var req = dnspod.NewDescribeRecordFilterListRequest()
req.Domain = this.stringVal(domain)
req.Offset = this.uint64Val(offset)
req.Limit = this.uint64Val(limit)
req.SubDomain = this.stringVal(name)
req.RecordType = []*string{this.stringVal(recordType)}
resp, respErr := this.client.DescribeRecordFilterList(req)
if respErr != nil {
if this.isNotFoundErr(respErr) {
break
}
return nil, respErr
}
var countRecords = len(resp.Response.RecordList)
if countRecords == 0 {
break
}
for _, recordObj := range resp.Response.RecordList {
records = append(records, &dnstypes.Record{
Id: types.String(*recordObj.RecordId),
Name: *recordObj.Name,
Type: *recordObj.Type,
Value: this.fixCNAME(*recordObj.Type, *recordObj.Value),
Route: *recordObj.LineId,
TTL: types.Int32(*recordObj.TTL),
})
}
offset += uint64(countRecords)
}
return records, nil
}
// AddRecord 设置记录
func (this *TencentDNSProvider) AddRecord(domain string, newRecord *dnstypes.Record) error {
if newRecord == nil {
return errors.New("invalid new record")
}
// 在CHANGE记录后面加入点
if newRecord.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(newRecord.Value, ".") {
newRecord.Value += "."
}
var ttl = newRecord.TTL
if ttl <= 0 {
ttl = 600
}
var req = dnspod.NewCreateRecordRequest()
req.Domain = this.stringVal(domain)
req.SubDomain = this.stringVal(newRecord.Name)
req.RecordType = this.stringVal(newRecord.Type)
req.TTL = this.uint64Val(uint64(ttl))
req.RecordLine = this.stringVal(this.DefaultRouteName()) // 默认必填项但以RecordLineId优先
req.RecordLineId = this.stringVal(newRecord.Route)
req.Value = this.stringVal(newRecord.Value)
resp, respErr := this.client.CreateRecord(req)
if respErr != nil {
return respErr
}
newRecord.Id = types.String(*resp.Response.RecordId)
// 加入缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.AddDomainRecord(this.ProviderId, domain, newRecord)
}
return nil
}
// UpdateRecord 修改记录
func (this *TencentDNSProvider) UpdateRecord(domain string, record *dnstypes.Record, newRecord *dnstypes.Record) error {
if record == nil {
return errors.New("invalid record")
}
if newRecord == nil {
return errors.New("invalid new record")
}
// 在CHANGE记录后面加入点
if newRecord.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(newRecord.Value, ".") {
newRecord.Value += "."
}
var newRoute = newRecord.Route
if len(newRoute) == 0 {
newRoute = this.DefaultRoute()
}
var ttl = newRecord.TTL
if ttl <= 0 {
ttl = 600
}
var req = dnspod.NewModifyRecordRequest()
req.Domain = this.stringVal(domain)
req.RecordId = this.uint64Val(types.Uint64(record.Id))
req.SubDomain = this.stringVal(newRecord.Name)
req.RecordType = this.stringVal(newRecord.Type)
req.TTL = this.uint64Val(uint64(ttl))
req.RecordLine = this.stringVal(this.DefaultRouteName()) // 默认必填项但以RecordLineId优先
req.RecordLineId = this.stringVal(newRecord.Route)
req.Value = this.stringVal(newRecord.Value)
_, respErr := this.client.ModifyRecord(req)
if respErr != nil {
return respErr
}
// 修改缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.UpdateDomainRecord(this.ProviderId, domain, newRecord)
}
return nil
}
// DeleteRecord 删除记录
func (this *TencentDNSProvider) DeleteRecord(domain string, record *dnstypes.Record) error {
if record == nil {
return errors.New("invalid record to delete")
}
var req = dnspod.NewDeleteRecordRequest()
req.Domain = this.stringVal(domain)
req.RecordId = this.uint64Val(types.Uint64(record.Id))
_, respErr := this.client.DeleteRecord(req)
if respErr != nil {
if len(record.Id) > 0 && this.isRecordInvalidErr(respErr) {
return nil
}
return respErr
}
// 删除缓存
if this.ProviderId > 0 {
sharedDomainRecordsCache.DeleteDomainRecord(this.ProviderId, domain, record.Id)
}
return nil
}
// DefaultRoute 默认线路
func (this *TencentDNSProvider) DefaultRoute() string {
return "0"
}
func (this *TencentDNSProvider) DefaultRouteName() string {
return "默认"
}
func (this *TencentDNSProvider) fixCNAME(recordType string, recordValue string) string {
// 修正Record
if strings.ToUpper(recordType) == dnstypes.RecordTypeCNAME && !strings.HasSuffix(recordValue, ".") {
recordValue += "."
}
return recordValue
}
func (this *TencentDNSProvider) int64Val(v int64) *int64 {
return &v
}
func (this *TencentDNSProvider) uint64Val(v uint64) *uint64 {
return &v
}
func (this *TencentDNSProvider) stringVal(s string) *string {
return &s
}
func (this *TencentDNSProvider) isNotFoundErr(err error) bool {
if err == nil {
return false
}
var sdkErr *tencenterrors.TencentCloudSDKError
return errors.As(err, &sdkErr) && strings.HasPrefix(sdkErr.Code, "ResourceNotFound.")
}
func (this *TencentDNSProvider) isRecordInvalidErr(err error) bool {
if err == nil {
return false
}
var sdkErr *tencenterrors.TencentCloudSDKError
return errors.As(err, &sdkErr) && sdkErr.Code == "InvalidParameter.RecordIdInvalid"
}

View File

@@ -26,7 +26,7 @@ func FindAllProviderTypes() []maps.Map {
"description": "阿里云提供的DNS服务。",
},
{
"name": "DNSPod",
"name": "腾讯云DNSPod",
"code": ProviderTypeDNSPod,
"description": "DNSPod提供的DNS服务。",
},
@@ -52,7 +52,7 @@ func FindAllProviderTypes() []maps.Map {
typeMaps = append(typeMaps, maps.Map{
"name": "自定义HTTP DNS",
"code": ProviderTypeCustomHTTP,
"description": "通过自定义的HTTP接口提供DNS服务",
"description": "通过自定义的HTTP接口提供DNS服务具体使用方法请参考官网文档https://goedge.cn/docs/DNS/CustomHTTP.md ",
})
return typeMaps
}

View File

@@ -10,6 +10,10 @@ import (
// FindProvider 查找服务商实例
func FindProvider(providerType ProviderType, providerId int64) ProviderInterface {
switch providerType {
case ProviderTypeTencentDNS:
return &TencentDNSProvider{
ProviderId: providerId,
}
case ProviderTypeDNSPod:
return &DNSPodProvider{
ProviderId: providerId,

View File

@@ -31,9 +31,11 @@ func (this *HTTPAccessLogService) CreateHTTPAccessLogs(ctx context.Context, req
var tx = this.NullTx()
err = models.SharedHTTPAccessLogDAO.CreateHTTPAccessLogs(tx, req.HttpAccessLogs)
if err != nil {
return nil, err
if this.canWriteAccessLogsToDB() {
err = models.SharedHTTPAccessLogDAO.CreateHTTPAccessLogs(tx, req.HttpAccessLogs)
if err != nil {
return nil, err
}
}
err = this.writeAccessLogsToPolicy(req.HttpAccessLogs)

View File

@@ -5,6 +5,10 @@ package services
import "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
func (this *HTTPAccessLogService) canWriteAccessLogsToDB() bool {
return true
}
func (this *HTTPAccessLogService) writeAccessLogsToPolicy(pbAccessLogs []*pb.HTTPAccessLog) error {
return nil
}

View File

@@ -4,9 +4,11 @@ import (
"context"
"encoding/json"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/utils/regexputils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/types"
)
@@ -30,7 +32,7 @@ func (this *HTTPPageService) CreateHTTPPage(ctx context.Context, req *pb.CreateH
const maxBodyLength = 32 * 1024
switch req.BodyType {
case shared.BodyTypeURL:
case serverconfigs.HTTPPageBodyTypeURL:
if len(req.Url) > maxURLLength {
return nil, errors.New("'url' too long")
}
@@ -41,7 +43,18 @@ func (this *HTTPPageService) CreateHTTPPage(ctx context.Context, req *pb.CreateH
if len(req.Body) > maxBodyLength { // we keep short body for user experience
req.Body = ""
}
case shared.BodyTypeHTML:
case serverconfigs.HTTPPageBodyTypeRedirectURL:
if len(req.Url) > maxURLLength {
return nil, errors.New("'url' too long")
}
if !regexputils.HTTPProtocol.MatchString(req.Url) {
return nil, errors.New("invalid 'url' format")
}
if len(req.Body) > maxBodyLength { // we keep short body for user experience
req.Body = ""
}
case serverconfigs.HTTPPageBodyTypeHTML:
if len(req.Body) > maxBodyLength {
return nil, errors.New("'body' too long")
}
@@ -53,7 +66,35 @@ func (this *HTTPPageService) CreateHTTPPage(ctx context.Context, req *pb.CreateH
return nil, errors.New("invalid 'bodyType': " + req.BodyType)
}
pageId, err := models.SharedHTTPPageDAO.CreatePage(tx, userId, req.StatusList, req.BodyType, req.Url, req.Body, types.Int(req.NewStatus))
var exceptURLPatterns = []*shared.URLPattern{}
if len(req.ExceptURLPatternsJSON) > 0 {
err = json.Unmarshal(req.ExceptURLPatternsJSON, &exceptURLPatterns)
if err != nil {
return nil, err
}
for _, pattern := range exceptURLPatterns {
err = pattern.Init()
if err != nil {
return nil, fmt.Errorf("validate url pattern '"+pattern.Pattern+"' failed: %w", err)
}
}
}
var onlyURLPatterns = []*shared.URLPattern{}
if len(req.OnlyURLPatternsJSON) > 0 {
err = json.Unmarshal(req.OnlyURLPatternsJSON, &onlyURLPatterns)
if err != nil {
return nil, err
}
for _, pattern := range onlyURLPatterns {
err = pattern.Init()
if err != nil {
return nil, fmt.Errorf("validate url pattern '"+pattern.Pattern+"' failed: %w", err)
}
}
}
pageId, err := models.SharedHTTPPageDAO.CreatePage(tx, userId, req.StatusList, req.BodyType, req.Url, req.Body, types.Int(req.NewStatus), exceptURLPatterns, onlyURLPatterns)
if err != nil {
return nil, err
}
@@ -82,7 +123,7 @@ func (this *HTTPPageService) UpdateHTTPPage(ctx context.Context, req *pb.UpdateH
const maxBodyLength = 32 * 1024
switch req.BodyType {
case shared.BodyTypeURL:
case serverconfigs.HTTPPageBodyTypeURL:
if len(req.Url) > maxURLLength {
return nil, errors.New("'url' too long")
}
@@ -93,7 +134,18 @@ func (this *HTTPPageService) UpdateHTTPPage(ctx context.Context, req *pb.UpdateH
if len(req.Body) > maxBodyLength { // we keep short body for user experience
req.Body = ""
}
case shared.BodyTypeHTML:
case serverconfigs.HTTPPageBodyTypeRedirectURL:
if len(req.Url) > maxURLLength {
return nil, errors.New("'url' too long")
}
if !regexputils.HTTPProtocol.MatchString(req.Url) {
return nil, errors.New("invalid 'url' format")
}
if len(req.Body) > maxBodyLength { // we keep short body for user experience
req.Body = ""
}
case serverconfigs.HTTPPageBodyTypeHTML:
if len(req.Body) > maxBodyLength {
return nil, errors.New("'body' too long")
}
@@ -105,7 +157,35 @@ func (this *HTTPPageService) UpdateHTTPPage(ctx context.Context, req *pb.UpdateH
return nil, errors.New("invalid 'bodyType': " + req.BodyType)
}
err = models.SharedHTTPPageDAO.UpdatePage(tx, req.HttpPageId, req.StatusList, req.BodyType, req.Url, req.Body, types.Int(req.NewStatus))
var exceptURLPatterns = []*shared.URLPattern{}
if len(req.ExceptURLPatternsJSON) > 0 {
err = json.Unmarshal(req.ExceptURLPatternsJSON, &exceptURLPatterns)
if err != nil {
return nil, err
}
for _, pattern := range exceptURLPatterns {
err = pattern.Init()
if err != nil {
return nil, fmt.Errorf("validate url pattern '"+pattern.Pattern+"' failed: %w", err)
}
}
}
var onlyURLPatterns = []*shared.URLPattern{}
if len(req.OnlyURLPatternsJSON) > 0 {
err = json.Unmarshal(req.OnlyURLPatternsJSON, &onlyURLPatterns)
if err != nil {
return nil, err
}
for _, pattern := range onlyURLPatterns {
err = pattern.Init()
if err != nil {
return nil, fmt.Errorf("validate url pattern '"+pattern.Pattern+"' failed: %w", err)
}
}
}
err = models.SharedHTTPPageDAO.UpdatePage(tx, req.HttpPageId, req.StatusList, req.BodyType, req.Url, req.Body, types.Int(req.NewStatus), exceptURLPatterns, onlyURLPatterns)
if err != nil {
return nil, err
}

View File

@@ -8,7 +8,6 @@ import (
"github.com/TeaOSLab/EdgeAPI/internal/utils/regexputils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared"
"github.com/iwind/TeaGo/dbs"
)
@@ -365,7 +364,7 @@ func (this *HTTPWebService) UpdateHTTPWebShutdown(ctx context.Context, req *pb.U
}
switch shutdownConfig.BodyType {
case shared.BodyTypeURL:
case serverconfigs.HTTPPageBodyTypeURL:
if len(shutdownConfig.URL) > maxURLLength {
return nil, errors.New("'url' too long")
}
@@ -376,7 +375,18 @@ func (this *HTTPWebService) UpdateHTTPWebShutdown(ctx context.Context, req *pb.U
if len(shutdownConfig.Body) > maxBodyLength { // we keep short body for user experience
shutdownConfig.Body = ""
}
case shared.BodyTypeHTML:
case serverconfigs.HTTPPageBodyTypeRedirectURL:
if len(shutdownConfig.URL) > maxURLLength {
return nil, errors.New("'url' too long")
}
if shutdownConfig.IsOn /** validate when it's on **/ && !regexputils.HTTPProtocol.MatchString(shutdownConfig.URL) {
return nil, errors.New("invalid 'url' format")
}
if len(shutdownConfig.Body) > maxBodyLength { // we keep short body for user experience
shutdownConfig.Body = ""
}
case serverconfigs.HTTPPageBodyTypeHTML:
if len(shutdownConfig.Body) > maxBodyLength {
return nil, errors.New("'body' too long")
}

View File

@@ -2066,7 +2066,7 @@ func (this *NodeService) FindEnabledNodeConfigInfo(ctx context.Context, req *pb.
}
// thresholds
countThresholds, err := models.SharedNodeThresholdDAO.CountAllEnabledThresholds(tx, nodeconfigs.NodeRoleNode, 0, req.NodeId)
countThresholds, err := models.SharedNodeThresholdDAO.CountAllEnabledThresholds(tx, nodeconfigs.NodeRoleNode, int64(node.ClusterId), req.NodeId)
if err != nil {
return nil, err
}

View File

@@ -1197,6 +1197,9 @@ func (this *NodeClusterService) FindEnabledNodeClusterConfigInfo(ctx context.Con
result.Http3IsOn = http3Policy.IsOn
}
// 网络安全策略
result.HasNetworkSecurityPolicy = cluster.HasNetworkSecurityPolicy()
return result, nil
}

View File

@@ -11,3 +11,13 @@ import (
func (this *NodeClusterService) FindNodeClusterHTTP3Policy(ctx context.Context, req *pb.FindNodeClusterHTTP3PolicyRequest) (*pb.FindNodeClusterHTTP3PolicyResponse, error) {
return nil, this.NotImplementedYet()
}
// FindNodeClusterNetworkSecurityPolicy 获取集群的网络安全策略
func (this *NodeClusterService) FindNodeClusterNetworkSecurityPolicy(ctx context.Context, req *pb.FindNodeClusterNetworkSecurityPolicyRequest) (*pb.FindNodeClusterNetworkSecurityPolicyResponse, error) {
return nil, this.NotImplementedYet()
}
// UpdateNodeClusterNetworkSecurityPolicy 修改集群的网络安全策略
func (this *NodeClusterService) UpdateNodeClusterNetworkSecurityPolicy(ctx context.Context, req *pb.UpdateNodeClusterNetworkSecurityPolicyRequest) (*pb.RPCSuccess, error) {
return nil, this.NotImplementedYet()
}

View File

@@ -52,3 +52,8 @@ func (this *NodeService) CopyNodeActionsToNodeCluster(ctx context.Context, req *
func (this *NodeService) FindNodeTOAConfig(ctx context.Context, req *pb.FindNodeTOAConfigRequest) (*pb.FindNodeTOAConfigResponse, error) {
return nil, this.NotImplementedYet()
}
// FindNodeNetworkSecurityPolicy 查找节点的网络安全策略
func (this *NodeService) FindNodeNetworkSecurityPolicy(ctx context.Context, req *pb.FindNodeNetworkSecurityPolicyRequest) (*pb.FindNodeNetworkSecurityPolicyResponse, error) {
return nil, this.NotImplementedYet()
}

View File

@@ -216,7 +216,7 @@ func (this *ReverseProxyService) UpdateReverseProxy(ctx context.Context, req *pb
}
}
err = models.SharedReverseProxyDAO.UpdateReverseProxy(tx, req.ReverseProxyId, types.Int8(req.RequestHostType), req.RequestHost, req.RequestHostExcludingPort, req.RequestURI, req.StripPrefix, req.AutoFlush, req.AddHeaders, connTimeout, readTimeout, idleTimeout, req.MaxConns, req.MaxIdleConns, req.ProxyProtocolJSON, req.FollowRedirects, req.Retry50X)
err = models.SharedReverseProxyDAO.UpdateReverseProxy(tx, req.ReverseProxyId, types.Int8(req.RequestHostType), req.RequestHost, req.RequestHostExcludingPort, req.RequestURI, req.StripPrefix, req.AutoFlush, req.AddHeaders, connTimeout, readTimeout, idleTimeout, req.MaxConns, req.MaxIdleConns, req.ProxyProtocolJSON, req.FollowRedirects, req.Retry50X, req.Retry40X)
if err != nil {
return nil, err
}

View File

@@ -414,6 +414,17 @@ func (this *ServerStatBoardService) ComposeServerStatNodeBoard(ctx context.Conte
})
}
networkPacketsValues, err := models.SharedNodeValueDAO.ListValues(tx, "node", req.NodeId, nodeconfigs.NodeValueItemNetworkPackets, nodeconfigs.NodeValueRangeMinute)
if err != nil {
return nil, err
}
for _, v := range networkPacketsValues {
result.NetworkPacketsValues = append(result.NetworkPacketsValues, &pb.NodeValue{
ValueJSON: v.Value,
CreatedAt: int64(v.CreatedAt),
})
}
cacheDirValues, err := models.SharedNodeValueDAO.ListValues(tx, "node", req.NodeId, nodeconfigs.NodeValueItemCacheDir, nodeconfigs.NodeValueRangeMinute)
if err != nil {
return nil, err

View File

@@ -241,28 +241,31 @@ func (this *UserService) FindEnabledUser(ctx context.Context, req *pb.FindEnable
}
}
return &pb.FindEnabledUserResponse{User: &pb.User{
Id: int64(user.Id),
Username: user.Username,
Fullname: user.Fullname,
Mobile: user.Mobile,
Tel: user.Tel,
Email: user.Email,
VerifiedEmail: user.VerifiedEmail,
Remark: user.Remark,
IsOn: user.IsOn,
CreatedAt: int64(user.CreatedAt),
RegisteredIP: user.RegisteredIP,
IsVerified: user.IsVerified,
IsRejected: user.IsRejected,
RejectReason: user.RejectReason,
NodeCluster: pbCluster,
IsIndividualIdentified: isIndividualIdentified,
IsEnterpriseIdentified: isEnterpriseIdentified,
BandwidthAlgo: user.BandwidthAlgo,
OtpLogin: pbOtpAuth,
Lang: user.Lang,
}}, nil
return &pb.FindEnabledUserResponse{
User: &pb.User{
Id: int64(user.Id),
Username: user.Username,
Fullname: user.Fullname,
Mobile: user.Mobile,
Tel: user.Tel,
Email: user.Email,
VerifiedEmail: user.VerifiedEmail,
VerifiedMobile: user.VerifiedMobile,
Remark: user.Remark,
IsOn: user.IsOn,
CreatedAt: int64(user.CreatedAt),
RegisteredIP: user.RegisteredIP,
IsVerified: user.IsVerified,
IsRejected: user.IsRejected,
RejectReason: user.RejectReason,
NodeCluster: pbCluster,
IsIndividualIdentified: isIndividualIdentified,
IsEnterpriseIdentified: isEnterpriseIdentified,
BandwidthAlgo: user.BandwidthAlgo,
OtpLogin: pbOtpAuth,
Lang: user.Lang,
},
}, nil
}
// CheckUserUsername 检查用户名是否存在
@@ -312,11 +315,14 @@ func (this *UserService) LoginUser(ctx context.Context, req *pb.LoginUserRequest
var tx = this.NullTx()
// 邮箱登录
var registerConfig *userconfigs.UserRegisterConfig
if strings.Contains(req.Username, "@") {
// 是否允许
registerConfig, err := models.SharedSysSettingDAO.ReadUserRegisterConfig(tx)
if err != nil {
return nil, err
if registerConfig == nil {
registerConfig, err = models.SharedSysSettingDAO.ReadUserRegisterConfig(tx)
if err != nil {
return nil, err
}
}
if registerConfig != nil && registerConfig.EmailVerification.CanLogin {
userId, err := models.SharedUserDAO.CheckUserEmailPassword(tx, req.Username, req.Password)
@@ -332,6 +338,29 @@ func (this *UserService) LoginUser(ctx context.Context, req *pb.LoginUserRequest
}
}
// 手机号登录
if utils.IsValidMobile(req.Username) {
// 是否允许
if registerConfig == nil {
registerConfig, err = models.SharedSysSettingDAO.ReadUserRegisterConfig(tx)
if err != nil {
return nil, err
}
}
if registerConfig != nil && registerConfig.MobileVerification.CanLogin {
userId, err := models.SharedUserDAO.CheckUserMobilePassword(tx, req.Username, req.Password)
if err != nil {
return nil, err
}
if userId > 0 {
return &pb.LoginUserResponse{
UserId: userId,
IsOk: true,
}, nil
}
}
}
// 用户名登录
userId, err := models.SharedUserDAO.CheckUserPassword(tx, req.Username, req.Password)
if err != nil {
@@ -840,6 +869,28 @@ func (this *UserService) CheckUserEmail(ctx context.Context, req *pb.CheckUserEm
return &pb.CheckUserEmailResponse{Exists: false}, nil
}
// CheckUserMobile 检查手机号码是否被使用
func (this *UserService) CheckUserMobile(ctx context.Context, req *pb.CheckUserMobileRequest) (*pb.CheckUserMobileResponse, error) {
userId, err := this.ValidateUserNode(ctx, false)
if err != nil {
return nil, err
}
if len(req.Mobile) == 0 {
return nil, errors.New("'mobile' required")
}
var tx = this.NullTx()
mobileOwnerUserId, err := models.SharedUserDAO.FindUserIdWithVerifiedMobile(tx, req.Mobile)
if err != nil {
return nil, err
}
if mobileOwnerUserId > 0 && userId != mobileOwnerUserId {
return &pb.CheckUserMobileResponse{Exists: true}, nil
}
return &pb.CheckUserMobileResponse{Exists: false}, nil
}
// FindUserVerifiedEmailWithUsername 根据用户名查询用户绑定的邮箱
func (this *UserService) FindUserVerifiedEmailWithUsername(ctx context.Context, req *pb.FindUserVerifiedEmailWithUsernameRequest) (*pb.FindUserVerifiedEmailWithUsernameResponse, error) {
_, err := this.ValidateUserNode(ctx, false)

File diff suppressed because it is too large Load Diff

View File

@@ -10,6 +10,8 @@ import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"strings"
"time"
)
@@ -127,8 +129,32 @@ func (this *HealthCheckClusterTask) Loop() error {
if err != nil {
return err
}
var message = "有" + numberutils.FormatInt(len(failedResults)) + "个节点IP在健康检查中出现问题"
err = models.NewMessageDAO().CreateClusterMessage(nil, nodeconfigs.NodeRoleNode, this.clusterId, models.MessageTypeHealthCheckFailed, models.MessageLevelError, message, message, failedResultsJSON)
var subject = "有" + numberutils.FormatInt(len(failedResults)) + "个节点IP在健康检查中出现问题"
var message = "有" + numberutils.FormatInt(len(failedResults)) + "个节点IP在健康检查中出现问题"
var failedDescriptions = []string{}
var failedIndex int
for _, result := range results {
if result.IsOk || result.Node == nil {
continue
}
failedIndex++
failedDescriptions = append(failedDescriptions, "节点"+types.String(failedIndex)+""+result.Node.Name+"IP"+result.NodeAddr)
}
const maxNodeDescriptions = 10
var isOverMax = false
if len(failedDescriptions) > maxNodeDescriptions {
failedDescriptions = failedDescriptions[:maxNodeDescriptions]
isOverMax = true
}
message += strings.Join(failedDescriptions, "")
if isOverMax {
message += " ..."
} else {
message += "。"
}
err = models.NewMessageDAO().CreateClusterMessage(nil, nodeconfigs.NodeRoleNode, this.clusterId, models.MessageTypeHealthCheckFailed, models.MessageLevelError, subject, subject, message, failedResultsJSON)
if err != nil {
return err
}

12
internal/utils/mobile.go Normal file
View File

@@ -0,0 +1,12 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils
import "regexp"
var mobileRegex = regexp.MustCompile(`^1\d{10}$`)
// IsValidMobile validate mobile number
func IsValidMobile(mobile string) bool {
return mobileRegex.MatchString(mobile)
}

View File

@@ -0,0 +1,17 @@
// Copyright 2023 GoEdge CDN goedge.cdn@gmail.com. All rights reserved. Official site: https://goedge.cn .
package utils_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/iwind/TeaGo/assert"
"testing"
)
func TestIsValidMobile(t *testing.T) {
var a = assert.NewAssertion(t)
a.IsFalse(utils.IsValidMobile("138"))
a.IsFalse(utils.IsValidMobile("1382222"))
a.IsFalse(utils.IsValidMobile("1381234567890"))
a.IsTrue(utils.IsValidMobile("13812345678"))
}