管理端全部功能跑通

This commit is contained in:
robin
2026-02-27 10:35:22 +08:00
parent 4d275c921d
commit 150799f41d
263 changed files with 22664 additions and 4053 deletions

View File

@@ -94,6 +94,32 @@ function build() {
fi
fi
# build edge-httpdns
HTTPDNS_ROOT=$ROOT"/../../EdgeHttpDNS"
if [ -d "$HTTPDNS_ROOT" ]; then
HTTPDNSNodeVersion=$(lookup-version "$ROOT""/../../EdgeHttpDNS/internal/const/const.go")
echo "building edge-httpdns v${HTTPDNSNodeVersion} ..."
EDGE_HTTPDNS_NODE_BUILD_SCRIPT=$ROOT"/../../EdgeHttpDNS/build/build.sh"
if [ ! -f "$EDGE_HTTPDNS_NODE_BUILD_SCRIPT" ]; then
echo "unable to find edge-httpdns build script 'EdgeHttpDNS/build/build.sh'"
exit
fi
cd "$ROOT""/../../EdgeHttpDNS/build" || exit
echo "=============================="
architects=("amd64")
#architects=("amd64" "arm64")
for arch in "${architects[@]}"; do
# always rebuild to avoid reusing stale zip when version number is unchanged
./build.sh linux "$arch"
done
echo "=============================="
cd - || exit
for arch in "${architects[@]}"; do
cp "$ROOT""/../../EdgeHttpDNS/dist/edge-httpdns-linux-${arch}-v${HTTPDNSNodeVersion}.zip" "$ROOT"/deploy/edge-httpdns-linux-"${arch}"-v"${HTTPDNSNodeVersion}".zip
done
fi
# build sql
if [ $TAG = "plus" ]; then
echo "building sql ..."

View File

@@ -1,6 +1,7 @@
package clickhouse
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
@@ -12,13 +13,11 @@ import (
"time"
)
// Client 通过 HTTP 接口执行只读查询SELECT返回 JSONEachRow 解析为 map 或结构体
type Client struct {
cfg *Config
httpCli *http.Client
}
// NewClient 使用共享配置创建客户端
func NewClient() *Client {
cfg := SharedConfig()
transport := &http.Transport{}
@@ -28,6 +27,7 @@ func NewClient() *Client {
ServerName: cfg.TLSServerName,
}
}
return &Client{
cfg: cfg,
httpCli: &http.Client{
@@ -37,21 +37,20 @@ func NewClient() *Client {
}
}
// IsConfigured 是否已配置
func (c *Client) IsConfigured() bool {
return c.cfg != nil && c.cfg.IsConfigured()
}
// Query 执行 SELECT将每行 JSON 解析到 dest 切片dest 元素类型需为 *struct 或 map
func (c *Client) Query(ctx context.Context, query string, dest interface{}) error {
if !c.IsConfigured() {
return fmt.Errorf("clickhouse: not configured")
}
// 强制 JSONEachRow 便于解析
q := strings.TrimSpace(query)
if !strings.HasSuffix(strings.ToUpper(q), "FORMAT JSONEACHROW") {
query = q + " FORMAT JSONEachRow"
}
u := c.buildURL(query)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
@@ -60,28 +59,32 @@ func (c *Client) Query(ctx context.Context, query string, dest interface{}) erro
if c.cfg.User != "" || c.cfg.Password != "" {
req.SetBasicAuth(c.cfg.User, c.cfg.Password)
}
resp, err := c.httpCli.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("clickhouse HTTP %d: %s", resp.StatusCode, string(body))
}
dec := json.NewDecoder(resp.Body)
return decodeRows(dec, dest)
}
// QueryRow 执行仅返回一行的查询,将结果解析到 dest*struct 或 *map
func (c *Client) QueryRow(ctx context.Context, query string, dest interface{}) error {
if !c.IsConfigured() {
return fmt.Errorf("clickhouse: not configured")
}
q := strings.TrimSpace(query)
if !strings.HasSuffix(strings.ToUpper(q), "FORMAT JSONEACHROW") {
query = q + " FORMAT JSONEachRow"
}
u := c.buildURL(query)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
@@ -90,32 +93,109 @@ func (c *Client) QueryRow(ctx context.Context, query string, dest interface{}) e
if c.cfg.User != "" || c.cfg.Password != "" {
req.SetBasicAuth(c.cfg.User, c.cfg.Password)
}
resp, err := c.httpCli.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("clickhouse HTTP %d: %s", resp.StatusCode, string(body))
}
dec := json.NewDecoder(resp.Body)
return decodeOneRow(dec, dest)
}
func (c *Client) Execute(ctx context.Context, query string) error {
if !c.IsConfigured() {
return fmt.Errorf("clickhouse: not configured")
}
u := c.buildURL(strings.TrimSpace(query))
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, nil)
if err != nil {
return err
}
if c.cfg.User != "" || c.cfg.Password != "" {
req.SetBasicAuth(c.cfg.User, c.cfg.Password)
}
resp, err := c.httpCli.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("clickhouse HTTP %d: %s", resp.StatusCode, string(body))
}
return nil
}
func (c *Client) InsertJSONEachRow(ctx context.Context, insertSQL string, rows []map[string]interface{}) error {
if len(rows) == 0 {
return nil
}
if !c.IsConfigured() {
return fmt.Errorf("clickhouse: not configured")
}
query := strings.TrimSpace(insertSQL)
if !strings.HasSuffix(strings.ToUpper(query), "FORMAT JSONEACHROW") {
query += " FORMAT JSONEachRow"
}
var payload bytes.Buffer
for _, row := range rows {
if row == nil {
continue
}
data, err := json.Marshal(row)
if err != nil {
return err
}
payload.Write(data)
payload.WriteByte('\n')
}
u := c.buildURL(query)
req, err := http.NewRequestWithContext(ctx, http.MethodPost, u, &payload)
if err != nil {
return err
}
req.Header.Set("Content-Type", "application/json")
if c.cfg.User != "" || c.cfg.Password != "" {
req.SetBasicAuth(c.cfg.User, c.cfg.Password)
}
resp, err := c.httpCli.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("clickhouse HTTP %d: %s", resp.StatusCode, string(body))
}
return nil
}
func (c *Client) buildURL(query string) string {
scheme := "http"
if c.cfg != nil && strings.EqualFold(c.cfg.Scheme, "https") {
scheme = "https"
}
rawURL := fmt.Sprintf("%s://%s:%d/?query=%s&database=%s",
return fmt.Sprintf("%s://%s:%d/?query=%s&database=%s",
scheme, c.cfg.Host, c.cfg.Port, url.QueryEscape(query), url.QueryEscape(c.cfg.Database))
return rawURL
}
// decodeRows 将 JSONEachRow 流解析到 slice元素类型须为 *struct 或 *[]map[string]interface{}
func decodeRows(dec *json.Decoder, dest interface{}) error {
// dest 应为 *[]*SomeStruct 或 *[]map[string]interface{}
switch d := dest.(type) {
case *[]map[string]interface{}:
*d = (*d)[:0]
@@ -130,7 +210,7 @@ func decodeRows(dec *json.Decoder, dest interface{}) error {
*d = append(*d, row)
}
default:
return fmt.Errorf("clickhouse: unsupported dest type for Query (use *[]map[string]interface{} or implement decoder)")
return fmt.Errorf("clickhouse: unsupported dest type for Query (use *[]map[string]interface{})")
}
}

View File

@@ -12,15 +12,15 @@ import (
)
const (
envHost = "CLICKHOUSE_HOST"
envPort = "CLICKHOUSE_PORT"
envUser = "CLICKHOUSE_USER"
envPassword = "CLICKHOUSE_PASSWORD"
envDatabase = "CLICKHOUSE_DATABASE"
envScheme = "CLICKHOUSE_SCHEME"
defaultPort = 8443
defaultDB = "default"
defaultScheme = "https"
envHost = "CLICKHOUSE_HOST"
envPort = "CLICKHOUSE_PORT"
envUser = "CLICKHOUSE_USER"
envPassword = "CLICKHOUSE_PASSWORD"
envDatabase = "CLICKHOUSE_DATABASE"
envScheme = "CLICKHOUSE_SCHEME"
defaultPort = 8443
defaultDB = "default"
defaultScheme = "https"
)
var (

View File

@@ -0,0 +1,279 @@
package clickhouse
import (
"context"
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
const httpDNSAccessLogsTable = "httpdns_access_logs_ingest"
type HTTPDNSAccessLogRow struct {
RequestId string
ClusterId int64
NodeId int64
AppId string
AppName string
Domain string
QType string
ClientIP string
ClientRegion string
Carrier string
SDKVersion string
OS string
ResultIPs string
Status string
ErrorCode string
CostMs int32
CreatedAt int64
Day string
Summary string
}
type HTTPDNSAccessLogListFilter struct {
Day string
ClusterId int64
NodeId int64
AppId string
Domain string
Status string
Keyword string
Offset int64
Size int64
}
type HTTPDNSAccessLogsStore struct {
client *Client
}
func NewHTTPDNSAccessLogsStore() *HTTPDNSAccessLogsStore {
return &HTTPDNSAccessLogsStore{client: NewClient()}
}
func (s *HTTPDNSAccessLogsStore) Client() *Client {
return s.client
}
func (s *HTTPDNSAccessLogsStore) Insert(ctx context.Context, logs []*pb.HTTPDNSAccessLog) error {
if len(logs) == 0 {
return nil
}
if !s.client.IsConfigured() {
return fmt.Errorf("clickhouse: not configured")
}
rows := make([]map[string]interface{}, 0, len(logs))
for _, item := range logs {
if item == nil {
continue
}
rows = append(rows, map[string]interface{}{
"request_id": item.GetRequestId(),
"cluster_id": item.GetClusterId(),
"node_id": item.GetNodeId(),
"app_id": item.GetAppId(),
"app_name": item.GetAppName(),
"domain": item.GetDomain(),
"qtype": item.GetQtype(),
"client_ip": item.GetClientIP(),
"client_region": item.GetClientRegion(),
"carrier": item.GetCarrier(),
"sdk_version": item.GetSdkVersion(),
"os": item.GetOs(),
"result_ips": item.GetResultIPs(),
"status": item.GetStatus(),
"error_code": item.GetErrorCode(),
"cost_ms": item.GetCostMs(),
"created_at": item.GetCreatedAt(),
"day": item.GetDay(),
"summary": item.GetSummary(),
})
}
query := fmt.Sprintf("INSERT INTO %s (request_id, cluster_id, node_id, app_id, app_name, domain, qtype, client_ip, client_region, carrier, sdk_version, os, result_ips, status, error_code, cost_ms, created_at, day, summary)",
s.tableName())
return s.client.InsertJSONEachRow(ctx, query, rows)
}
func (s *HTTPDNSAccessLogsStore) Count(ctx context.Context, f HTTPDNSAccessLogListFilter) (int64, error) {
if !s.client.IsConfigured() {
return 0, fmt.Errorf("clickhouse: not configured")
}
conditions := s.buildConditions(f)
query := fmt.Sprintf("SELECT count() AS count FROM %s", s.tableName())
if len(conditions) > 0 {
query += " WHERE " + strings.Join(conditions, " AND ")
}
row := map[string]interface{}{}
if err := s.client.QueryRow(ctx, query, &row); err != nil {
return 0, err
}
return toInt64(row["count"]), nil
}
func (s *HTTPDNSAccessLogsStore) List(ctx context.Context, f HTTPDNSAccessLogListFilter) ([]*HTTPDNSAccessLogRow, error) {
if !s.client.IsConfigured() {
return nil, fmt.Errorf("clickhouse: not configured")
}
size := f.Size
if size <= 0 {
size = 20
}
if size > 1000 {
size = 1000
}
offset := f.Offset
if offset < 0 {
offset = 0
}
conditions := s.buildConditions(f)
query := fmt.Sprintf("SELECT request_id, cluster_id, node_id, app_id, app_name, domain, qtype, client_ip, client_region, carrier, sdk_version, os, result_ips, status, error_code, cost_ms, created_at, day, summary FROM %s",
s.tableName())
if len(conditions) > 0 {
query += " WHERE " + strings.Join(conditions, " AND ")
}
query += " ORDER BY created_at DESC, request_id DESC"
query += fmt.Sprintf(" LIMIT %d OFFSET %d", size, offset)
rawRows := []map[string]interface{}{}
if err := s.client.Query(ctx, query, &rawRows); err != nil {
return nil, err
}
result := make([]*HTTPDNSAccessLogRow, 0, len(rawRows))
for _, row := range rawRows {
result = append(result, &HTTPDNSAccessLogRow{
RequestId: toString(row["request_id"]),
ClusterId: toInt64(row["cluster_id"]),
NodeId: toInt64(row["node_id"]),
AppId: toString(row["app_id"]),
AppName: toString(row["app_name"]),
Domain: toString(row["domain"]),
QType: toString(row["qtype"]),
ClientIP: toString(row["client_ip"]),
ClientRegion: toString(row["client_region"]),
Carrier: toString(row["carrier"]),
SDKVersion: toString(row["sdk_version"]),
OS: toString(row["os"]),
ResultIPs: toString(row["result_ips"]),
Status: toString(row["status"]),
ErrorCode: toString(row["error_code"]),
CostMs: int32(toInt64(row["cost_ms"])),
CreatedAt: toInt64(row["created_at"]),
Day: toString(row["day"]),
Summary: toString(row["summary"]),
})
}
return result, nil
}
func HTTPDNSRowToPB(row *HTTPDNSAccessLogRow) *pb.HTTPDNSAccessLog {
if row == nil {
return nil
}
return &pb.HTTPDNSAccessLog{
RequestId: row.RequestId,
ClusterId: row.ClusterId,
NodeId: row.NodeId,
AppId: row.AppId,
AppName: row.AppName,
Domain: row.Domain,
Qtype: row.QType,
ClientIP: row.ClientIP,
ClientRegion: row.ClientRegion,
Carrier: row.Carrier,
SdkVersion: row.SDKVersion,
Os: row.OS,
ResultIPs: row.ResultIPs,
Status: row.Status,
ErrorCode: row.ErrorCode,
CostMs: row.CostMs,
CreatedAt: row.CreatedAt,
Day: row.Day,
Summary: row.Summary,
}
}
func (s *HTTPDNSAccessLogsStore) buildConditions(f HTTPDNSAccessLogListFilter) []string {
conditions := []string{}
if day := strings.TrimSpace(f.Day); day != "" {
conditions = append(conditions, "day = '"+escapeString(day)+"'")
}
if f.ClusterId > 0 {
conditions = append(conditions, "cluster_id = "+strconv.FormatInt(f.ClusterId, 10))
}
if f.NodeId > 0 {
conditions = append(conditions, "node_id = "+strconv.FormatInt(f.NodeId, 10))
}
if appID := strings.TrimSpace(f.AppId); appID != "" {
conditions = append(conditions, "app_id = '"+escapeString(appID)+"'")
}
if domain := strings.TrimSpace(f.Domain); domain != "" {
conditions = append(conditions, "domain = '"+escapeString(domain)+"'")
}
if status := strings.TrimSpace(f.Status); status != "" {
conditions = append(conditions, "status = '"+escapeString(status)+"'")
}
if keyword := strings.TrimSpace(f.Keyword); keyword != "" {
kw := escapeString(keyword)
conditions = append(conditions, "(summary LIKE '%"+kw+"%' OR app_name LIKE '%"+kw+"%' OR client_ip LIKE '%"+kw+"%' OR result_ips LIKE '%"+kw+"%')")
}
return conditions
}
func (s *HTTPDNSAccessLogsStore) tableName() string {
if s.client != nil && s.client.cfg != nil && s.client.cfg.Database != "" && s.client.cfg.Database != "default" {
return quoteIdent(s.client.cfg.Database) + "." + quoteIdent(httpDNSAccessLogsTable)
}
return quoteIdent(httpDNSAccessLogsTable)
}
func toString(value interface{}) string {
if value == nil {
return ""
}
switch v := value.(type) {
case string:
return v
case json.Number:
return v.String()
default:
return fmt.Sprintf("%v", v)
}
}
func toInt64(value interface{}) int64 {
if value == nil {
return 0
}
switch v := value.(type) {
case int:
return int64(v)
case int32:
return int64(v)
case int64:
return v
case uint32:
return int64(v)
case uint64:
return int64(v)
case float64:
return int64(v)
case json.Number:
n, _ := v.Int64()
return n
case string:
n, _ := strconv.ParseInt(v, 10, 64)
return n
default:
return 0
}
}

View File

@@ -351,8 +351,8 @@ func RowToPB(r *LogsIngestRow) *pb.HTTPAccessLog {
RemoteAddr: r.IP,
RequestMethod: r.Method,
RequestPath: r.Path,
RequestURI: r.Path, // 前端使用 requestURI 显示完整路径
Scheme: "http", // 默认 http日志中未存储实际值
RequestURI: r.Path, // 前端使用 requestURI 显示完整路径
Scheme: "http", // 默认 http日志中未存储实际值
Proto: "HTTP/1.1", // 默认值,日志中未存储实际值
Status: int32(r.Status),
RequestLength: int64(r.BytesIn),

View File

@@ -1,7 +1,7 @@
package teaconst
const (
Version = "1.4.7" //1.3.9
Version = "1.4.8" //1.3.9
ProductName = "Edge API"
ProcessName = "edge-api"
@@ -17,6 +17,6 @@ const (
// 其他节点版本号,用来检测是否有需要升级的节点
NodeVersion = "1.4.7" //1.3.8.2
NodeVersion = "1.4.8" //1.3.8.2
)

View File

@@ -4,8 +4,8 @@
package teaconst
const (
DNSNodeVersion = "1.4.7" //1.3.8.2
UserNodeVersion = "1.4.7" //1.3.8.2
DNSNodeVersion = "1.4.8" //1.3.8.2
UserNodeVersion = "1.4.8" //1.3.8.2
ReportNodeVersion = "0.1.5"
DefaultMaxNodes int32 = 50

View File

@@ -0,0 +1,101 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
type HTTPDNSAccessLogDAO dbs.DAO
func NewHTTPDNSAccessLogDAO() *HTTPDNSAccessLogDAO {
return dbs.NewDAO(&HTTPDNSAccessLogDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeHTTPDNSAccessLogs",
Model: new(HTTPDNSAccessLog),
PkName: "id",
},
}).(*HTTPDNSAccessLogDAO)
}
var SharedHTTPDNSAccessLogDAO *HTTPDNSAccessLogDAO
func init() {
dbs.OnReady(func() {
SharedHTTPDNSAccessLogDAO = NewHTTPDNSAccessLogDAO()
})
}
func (this *HTTPDNSAccessLogDAO) CreateLog(tx *dbs.Tx, log *HTTPDNSAccessLog) error {
var op = NewHTTPDNSAccessLogOperator()
op.RequestId = log.RequestId
op.ClusterId = log.ClusterId
op.NodeId = log.NodeId
op.AppId = log.AppId
op.AppName = log.AppName
op.Domain = log.Domain
op.QType = log.QType
op.ClientIP = log.ClientIP
op.ClientRegion = log.ClientRegion
op.Carrier = log.Carrier
op.SDKVersion = log.SDKVersion
op.OS = log.OS
op.ResultIPs = log.ResultIPs
op.Status = log.Status
op.ErrorCode = log.ErrorCode
op.CostMs = log.CostMs
op.CreatedAt = log.CreatedAt
op.Day = log.Day
op.Summary = log.Summary
return this.Save(tx, op)
}
func (this *HTTPDNSAccessLogDAO) BuildListQuery(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, domain string, status string, keyword string) *dbs.Query {
query := this.Query(tx).DescPk()
if len(day) > 0 {
query = query.Attr("day", day)
}
if clusterId > 0 {
query = query.Attr("clusterId", clusterId)
}
if nodeId > 0 {
query = query.Attr("nodeId", nodeId)
}
if len(appId) > 0 {
query = query.Attr("appId", appId)
}
if len(domain) > 0 {
query = query.Attr("domain", domain)
}
if len(status) > 0 {
query = query.Attr("status", status)
}
if len(keyword) > 0 {
query = query.Where("(summary LIKE :kw OR appName LIKE :kw OR clientIP LIKE :kw OR resultIPs LIKE :kw)").Param("kw", "%"+keyword+"%")
}
return query
}
func (this *HTTPDNSAccessLogDAO) CountLogs(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, domain string, status string, keyword string) (int64, error) {
return this.BuildListQuery(tx, day, clusterId, nodeId, appId, domain, status, keyword).Count()
}
func (this *HTTPDNSAccessLogDAO) ListLogs(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, domain string, status string, keyword string, offset int64, size int64) (result []*HTTPDNSAccessLog, err error) {
_, err = this.BuildListQuery(tx, day, clusterId, nodeId, appId, domain, status, keyword).
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}
func (this *HTTPDNSAccessLogDAO) DeleteLogsWithAppId(tx *dbs.Tx, appId string) error {
if len(appId) == 0 {
return nil
}
_, err := this.Query(tx).
Attr("appId", appId).
Delete()
return err
}

View File

@@ -0,0 +1,52 @@
package models
// HTTPDNSAccessLog 访问日志
type HTTPDNSAccessLog struct {
Id uint64 `field:"id"` // ID
RequestId string `field:"requestId"` // 请求ID
ClusterId uint32 `field:"clusterId"` // 集群ID
NodeId uint32 `field:"nodeId"` // 节点ID
AppId string `field:"appId"` // AppID
AppName string `field:"appName"` // 应用名
Domain string `field:"domain"` // 域名
QType string `field:"qtype"` // 查询类型
ClientIP string `field:"clientIP"` // 客户端IP
ClientRegion string `field:"clientRegion"` // 客户端区域
Carrier string `field:"carrier"` // 运营商
SDKVersion string `field:"sdkVersion"` // SDK版本
OS string `field:"os"` // 系统
ResultIPs string `field:"resultIPs"` // 结果IP
Status string `field:"status"` // 状态
ErrorCode string `field:"errorCode"` // 错误码
CostMs int32 `field:"costMs"` // 耗时
CreatedAt uint64 `field:"createdAt"` // 创建时间
Day string `field:"day"` // YYYYMMDD
Summary string `field:"summary"` // 概要
}
type HTTPDNSAccessLogOperator struct {
Id any // ID
RequestId any // 请求ID
ClusterId any // 集群ID
NodeId any // 节点ID
AppId any // AppID
AppName any // 应用名
Domain any // 域名
QType any // 查询类型
ClientIP any // 客户端IP
ClientRegion any // 客户端区域
Carrier any // 运营商
SDKVersion any // SDK版本
OS any // 系统
ResultIPs any // 结果IP
Status any // 状态
ErrorCode any // 错误码
CostMs any // 耗时
CreatedAt any // 创建时间
Day any // YYYYMMDD
Summary any // 概要
}
func NewHTTPDNSAccessLogOperator() *HTTPDNSAccessLogOperator {
return &HTTPDNSAccessLogOperator{}
}

View File

@@ -0,0 +1,128 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"time"
)
const (
HTTPDNSAppStateEnabled = 1
HTTPDNSAppStateDisabled = 0
HTTPDNSSNIModeFixedHide = "fixed_hide"
)
type HTTPDNSAppDAO dbs.DAO
func NewHTTPDNSAppDAO() *HTTPDNSAppDAO {
return dbs.NewDAO(&HTTPDNSAppDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeHTTPDNSApps",
Model: new(HTTPDNSApp),
PkName: "id",
},
}).(*HTTPDNSAppDAO)
}
var SharedHTTPDNSAppDAO *HTTPDNSAppDAO
func init() {
dbs.OnReady(func() {
SharedHTTPDNSAppDAO = NewHTTPDNSAppDAO()
})
}
func (this *HTTPDNSAppDAO) CreateApp(tx *dbs.Tx, name string, appId string, primaryClusterId int64, backupClusterId int64, isOn bool, userId int64) (int64, error) {
var op = NewHTTPDNSAppOperator()
op.Name = name
op.AppId = appId
op.PrimaryClusterId = primaryClusterId
op.BackupClusterId = backupClusterId
op.IsOn = isOn
op.UserId = userId
op.SNIMode = HTTPDNSSNIModeFixedHide
op.CreatedAt = time.Now().Unix()
op.UpdatedAt = time.Now().Unix()
op.State = HTTPDNSAppStateEnabled
err := this.Save(tx, op)
if err != nil {
return 0, err
}
return types.Int64(op.Id), nil
}
func (this *HTTPDNSAppDAO) UpdateApp(tx *dbs.Tx, appDbId int64, name string, primaryClusterId int64, backupClusterId int64, isOn bool, userId int64) error {
var op = NewHTTPDNSAppOperator()
op.Id = appDbId
op.Name = name
op.PrimaryClusterId = primaryClusterId
op.BackupClusterId = backupClusterId
op.IsOn = isOn
op.UserId = userId
op.UpdatedAt = time.Now().Unix()
return this.Save(tx, op)
}
func (this *HTTPDNSAppDAO) DisableApp(tx *dbs.Tx, appDbId int64) error {
_, err := this.Query(tx).
Pk(appDbId).
Set("state", HTTPDNSAppStateDisabled).
Update()
return err
}
func (this *HTTPDNSAppDAO) FindEnabledApp(tx *dbs.Tx, appDbId int64) (*HTTPDNSApp, error) {
one, err := this.Query(tx).
Pk(appDbId).
State(HTTPDNSAppStateEnabled).
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSApp), nil
}
func (this *HTTPDNSAppDAO) FindEnabledAppWithAppId(tx *dbs.Tx, appId string) (*HTTPDNSApp, error) {
one, err := this.Query(tx).
State(HTTPDNSAppStateEnabled).
Attr("appId", appId).
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSApp), nil
}
func (this *HTTPDNSAppDAO) ListEnabledApps(tx *dbs.Tx, offset int64, size int64, keyword string) (result []*HTTPDNSApp, err error) {
query := this.Query(tx).
State(HTTPDNSAppStateEnabled).
AscPk()
if len(keyword) > 0 {
query = query.Where("(name LIKE :kw OR appId LIKE :kw)").Param("kw", "%"+keyword+"%")
}
if size > 0 {
query = query.Offset(offset).Limit(size)
}
_, err = query.Slice(&result).FindAll()
return
}
func (this *HTTPDNSAppDAO) CountEnabledApps(tx *dbs.Tx, keyword string) (int64, error) {
query := this.Query(tx).State(HTTPDNSAppStateEnabled)
if len(keyword) > 0 {
query = query.Where("(name LIKE :kw OR appId LIKE :kw)").Param("kw", "%"+keyword+"%")
}
return query.Count()
}
func (this *HTTPDNSAppDAO) FindAllEnabledApps(tx *dbs.Tx) (result []*HTTPDNSApp, err error) {
_, err = this.Query(tx).
State(HTTPDNSAppStateEnabled).
AscPk().
Slice(&result).
FindAll()
return
}

View File

@@ -0,0 +1,36 @@
package models
// HTTPDNSApp maps to edgeHTTPDNSApps.
type HTTPDNSApp struct {
Id uint32 `field:"id"` // id
Name string `field:"name"` // app name
AppId string `field:"appId"` // external app id
IsOn bool `field:"isOn"` // enabled
PrimaryClusterId uint32 `field:"primaryClusterId"` // primary cluster id
BackupClusterId uint32 `field:"backupClusterId"` // backup cluster id
SNIMode string `field:"sniMode"` // sni mode
UserId int64 `field:"userId"` // owner user id
CreatedAt uint64 `field:"createdAt"` // created unix ts
UpdatedAt uint64 `field:"updatedAt"` // updated unix ts
State uint8 `field:"state"` // state
}
// HTTPDNSAppOperator is used by DAO save/update.
type HTTPDNSAppOperator struct {
Id any // id
Name any // app name
AppId any // external app id
IsOn any // enabled
PrimaryClusterId any // primary cluster id
BackupClusterId any // backup cluster id
SNIMode any // sni mode
UserId any // owner user id
CreatedAt any // created unix ts
UpdatedAt any // updated unix ts
State any // state
}
func NewHTTPDNSAppOperator() *HTTPDNSAppOperator {
return &HTTPDNSAppOperator{}
}

View File

@@ -0,0 +1,125 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
"time"
)
const (
HTTPDNSAppSecretStateEnabled = 1
HTTPDNSAppSecretStateDisabled = 0
)
type HTTPDNSAppSecretDAO dbs.DAO
func NewHTTPDNSAppSecretDAO() *HTTPDNSAppSecretDAO {
return dbs.NewDAO(&HTTPDNSAppSecretDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeHTTPDNSAppSecrets",
Model: new(HTTPDNSAppSecret),
PkName: "id",
},
}).(*HTTPDNSAppSecretDAO)
}
var SharedHTTPDNSAppSecretDAO *HTTPDNSAppSecretDAO
func init() {
dbs.OnReady(func() {
SharedHTTPDNSAppSecretDAO = NewHTTPDNSAppSecretDAO()
})
}
func (this *HTTPDNSAppSecretDAO) InitAppSecret(tx *dbs.Tx, appDbId int64, signEnabled bool) (string, uint64, error) {
signSecret := "ss_" + rands.HexString(12)
now := uint64(time.Now().Unix())
var op = NewHTTPDNSAppSecretOperator()
op.AppId = appDbId
op.SignEnabled = signEnabled
op.SignSecret = signSecret
op.SignUpdatedAt = now
op.UpdatedAt = now
op.State = HTTPDNSAppSecretStateEnabled
err := this.Save(tx, op)
return signSecret, now, err
}
func (this *HTTPDNSAppSecretDAO) FindEnabledAppSecret(tx *dbs.Tx, appDbId int64) (*HTTPDNSAppSecret, error) {
one, err := this.Query(tx).
Attr("appId", appDbId).
State(HTTPDNSAppSecretStateEnabled).
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSAppSecret), nil
}
func (this *HTTPDNSAppSecretDAO) UpdateSignEnabled(tx *dbs.Tx, appDbId int64, signEnabled bool) error {
_, err := this.Query(tx).
Attr("appId", appDbId).
State(HTTPDNSAppSecretStateEnabled).
Set("signEnabled", signEnabled).
Set("updatedAt", time.Now().Unix()).
Update()
return err
}
func (this *HTTPDNSAppSecretDAO) ResetSignSecret(tx *dbs.Tx, appDbId int64) (string, int64, error) {
signSecret := "ss_" + rands.HexString(12)
now := time.Now().Unix()
_, err := this.Query(tx).
Attr("appId", appDbId).
State(HTTPDNSAppSecretStateEnabled).
Set("signSecret", signSecret).
Set("signUpdatedAt", now).
Set("updatedAt", now).
Update()
if err != nil {
return "", 0, err
}
return signSecret, now, nil
}
func (this *HTTPDNSAppSecretDAO) FindSignEnabled(tx *dbs.Tx, appDbId int64) (bool, error) {
one, err := this.FindEnabledAppSecret(tx, appDbId)
if err != nil || one == nil {
return false, err
}
return one.SignEnabled, nil
}
func (this *HTTPDNSAppSecretDAO) FindSignSecretWithAppDbId(tx *dbs.Tx, appDbId int64) (string, error) {
return this.Query(tx).
Attr("appId", appDbId).
State(HTTPDNSAppSecretStateEnabled).
Result("signSecret").
FindStringCol("")
}
func (this *HTTPDNSAppSecretDAO) FindSignUpdatedAt(tx *dbs.Tx, appDbId int64) (int64, error) {
col, err := this.Query(tx).
Attr("appId", appDbId).
State(HTTPDNSAppSecretStateEnabled).
Result("signUpdatedAt").
FindCol(nil)
if err != nil {
return 0, err
}
return types.Int64(col), nil
}
func (this *HTTPDNSAppSecretDAO) DisableAppSecret(tx *dbs.Tx, appDbId int64) error {
_, err := this.Query(tx).
Attr("appId", appDbId).
State(HTTPDNSAppSecretStateEnabled).
Set("state", HTTPDNSAppSecretStateDisabled).
Set("updatedAt", time.Now().Unix()).
Update()
return err
}

View File

@@ -0,0 +1,26 @@
package models
// HTTPDNSAppSecret 应用验签密钥配置
type HTTPDNSAppSecret struct {
Id uint32 `field:"id"` // ID
AppId uint32 `field:"appId"` // 应用DB ID
SignEnabled bool `field:"signEnabled"` // 是否启用验签
SignSecret string `field:"signSecret"` // 验签密钥(当前先明文存储)
SignUpdatedAt uint64 `field:"signUpdatedAt"` // 验签密钥更新时间
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 记录状态
}
type HTTPDNSAppSecretOperator struct {
Id any // ID
AppId any // 应用DB ID
SignEnabled any // 是否启用验签
SignSecret any // 验签密钥
SignUpdatedAt any // 验签密钥更新时间
UpdatedAt any // 修改时间
State any // 记录状态
}
func NewHTTPDNSAppSecretOperator() *HTTPDNSAppSecretOperator {
return &HTTPDNSAppSecretOperator{}
}

View File

@@ -0,0 +1,169 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"time"
)
const (
HTTPDNSClusterStateEnabled = 1
HTTPDNSClusterStateDisabled = 0
)
type HTTPDNSClusterDAO dbs.DAO
func NewHTTPDNSClusterDAO() *HTTPDNSClusterDAO {
return dbs.NewDAO(&HTTPDNSClusterDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeHTTPDNSClusters",
Model: new(HTTPDNSCluster),
PkName: "id",
},
}).(*HTTPDNSClusterDAO)
}
var SharedHTTPDNSClusterDAO *HTTPDNSClusterDAO
func init() {
dbs.OnReady(func() {
SharedHTTPDNSClusterDAO = NewHTTPDNSClusterDAO()
})
}
func (this *HTTPDNSClusterDAO) CreateCluster(tx *dbs.Tx, name string, serviceDomain string, defaultTTL int32, fallbackTimeoutMs int32, installDir string, tlsPolicyJSON []byte, isOn bool, isDefault bool) (int64, error) {
if isDefault {
err := this.Query(tx).
State(HTTPDNSClusterStateEnabled).
Set("isDefault", false).
UpdateQuickly()
if err != nil {
return 0, err
}
}
var op = NewHTTPDNSClusterOperator()
op.Name = name
op.ServiceDomain = serviceDomain
op.DefaultTTL = defaultTTL
op.FallbackTimeoutMs = fallbackTimeoutMs
op.InstallDir = installDir
op.IsOn = isOn
op.IsDefault = isDefault
op.CreatedAt = time.Now().Unix()
op.UpdatedAt = time.Now().Unix()
op.State = HTTPDNSClusterStateEnabled
if len(tlsPolicyJSON) > 0 {
op.TLSPolicy = tlsPolicyJSON
}
err := this.Save(tx, op)
if err != nil {
return 0, err
}
return types.Int64(op.Id), nil
}
func (this *HTTPDNSClusterDAO) UpdateCluster(tx *dbs.Tx, clusterId int64, name string, serviceDomain string, defaultTTL int32, fallbackTimeoutMs int32, installDir string, tlsPolicyJSON []byte, isOn bool, isDefault bool) error {
if isDefault {
err := this.Query(tx).
State(HTTPDNSClusterStateEnabled).
Neq("id", clusterId).
Set("isDefault", false).
UpdateQuickly()
if err != nil {
return err
}
}
var op = NewHTTPDNSClusterOperator()
op.Id = clusterId
op.Name = name
op.ServiceDomain = serviceDomain
op.DefaultTTL = defaultTTL
op.FallbackTimeoutMs = fallbackTimeoutMs
op.InstallDir = installDir
op.IsOn = isOn
op.IsDefault = isDefault
op.UpdatedAt = time.Now().Unix()
if len(tlsPolicyJSON) > 0 {
op.TLSPolicy = tlsPolicyJSON
}
return this.Save(tx, op)
}
func (this *HTTPDNSClusterDAO) DisableCluster(tx *dbs.Tx, clusterId int64) error {
_, err := this.Query(tx).
Pk(clusterId).
Set("state", HTTPDNSClusterStateDisabled).
Update()
return err
}
func (this *HTTPDNSClusterDAO) FindEnabledCluster(tx *dbs.Tx, clusterId int64) (*HTTPDNSCluster, error) {
one, err := this.Query(tx).
Pk(clusterId).
State(HTTPDNSClusterStateEnabled).
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSCluster), nil
}
func (this *HTTPDNSClusterDAO) FindEnabledClusterName(tx *dbs.Tx, clusterId int64) (string, error) {
return this.Query(tx).
Pk(clusterId).
State(HTTPDNSClusterStateEnabled).
Result("name").
FindStringCol("")
}
func (this *HTTPDNSClusterDAO) ListEnabledClusters(tx *dbs.Tx, offset int64, size int64, keyword string) (result []*HTTPDNSCluster, err error) {
query := this.Query(tx).
State(HTTPDNSClusterStateEnabled).
AscPk()
if len(keyword) > 0 {
query = query.Where("(name LIKE :kw OR serviceDomain LIKE :kw)").Param("kw", "%"+keyword+"%")
}
if size > 0 {
query = query.Offset(offset).Limit(size)
}
_, err = query.Slice(&result).FindAll()
return
}
func (this *HTTPDNSClusterDAO) CountEnabledClusters(tx *dbs.Tx, keyword string) (int64, error) {
query := this.Query(tx).State(HTTPDNSClusterStateEnabled)
if len(keyword) > 0 {
query = query.Where("(name LIKE :kw OR serviceDomain LIKE :kw)").Param("kw", "%"+keyword+"%")
}
return query.Count()
}
func (this *HTTPDNSClusterDAO) FindAllEnabledClusters(tx *dbs.Tx) (result []*HTTPDNSCluster, err error) {
_, err = this.Query(tx).
State(HTTPDNSClusterStateEnabled).
AscPk().
Slice(&result).
FindAll()
return
}
func (this *HTTPDNSClusterDAO) UpdateDefaultCluster(tx *dbs.Tx, clusterId int64) error {
err := this.Query(tx).
State(HTTPDNSClusterStateEnabled).
Set("isDefault", false).
UpdateQuickly()
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(clusterId).
State(HTTPDNSClusterStateEnabled).
Set("isDefault", true).
Update()
return err
}

View File

@@ -0,0 +1,38 @@
package models
import "github.com/iwind/TeaGo/dbs"
// HTTPDNSCluster HTTPDNS集群
type HTTPDNSCluster struct {
Id uint32 `field:"id"` // ID
Name string `field:"name"` // 集群名称
IsOn bool `field:"isOn"` // 是否启用
IsDefault bool `field:"isDefault"` // 默认集群
ServiceDomain string `field:"serviceDomain"` // 服务域名
DefaultTTL int32 `field:"defaultTTL"` // 默认TTL
FallbackTimeoutMs int32 `field:"fallbackTimeoutMs"` // 降级超时
InstallDir string `field:"installDir"` // 安装目录
TLSPolicy dbs.JSON `field:"tlsPolicy"` // TLS策略
CreatedAt uint64 `field:"createdAt"` // 创建时间
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 记录状态
}
type HTTPDNSClusterOperator struct {
Id any // ID
Name any // 集群名称
IsOn any // 是否启用
IsDefault any // 默认集群
ServiceDomain any // 服务域名
DefaultTTL any // 默认TTL
FallbackTimeoutMs any // 降级超时
InstallDir any // 安装目录
TLSPolicy any // TLS策略
CreatedAt any // 创建时间
UpdatedAt any // 修改时间
State any // 记录状态
}
func NewHTTPDNSClusterOperator() *HTTPDNSClusterOperator {
return &HTTPDNSClusterOperator{}
}

View File

@@ -0,0 +1,143 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"time"
)
const (
HTTPDNSCustomRuleStateEnabled = 1
HTTPDNSCustomRuleStateDisabled = 0
)
type HTTPDNSCustomRuleDAO dbs.DAO
func NewHTTPDNSCustomRuleDAO() *HTTPDNSCustomRuleDAO {
return dbs.NewDAO(&HTTPDNSCustomRuleDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeHTTPDNSCustomRules",
Model: new(HTTPDNSCustomRule),
PkName: "id",
},
}).(*HTTPDNSCustomRuleDAO)
}
var SharedHTTPDNSCustomRuleDAO *HTTPDNSCustomRuleDAO
func init() {
dbs.OnReady(func() {
SharedHTTPDNSCustomRuleDAO = NewHTTPDNSCustomRuleDAO()
})
}
func (this *HTTPDNSCustomRuleDAO) CreateRule(tx *dbs.Tx, rule *HTTPDNSCustomRule) (int64, error) {
var op = NewHTTPDNSCustomRuleOperator()
op.AppId = rule.AppId
op.DomainId = rule.DomainId
op.RuleName = rule.RuleName
op.LineScope = rule.LineScope
op.LineCarrier = rule.LineCarrier
op.LineRegion = rule.LineRegion
op.LineProvince = rule.LineProvince
op.LineContinent = rule.LineContinent
op.LineCountry = rule.LineCountry
op.TTL = rule.TTL
op.IsOn = rule.IsOn
op.Priority = rule.Priority
op.UpdatedAt = time.Now().Unix()
op.State = HTTPDNSCustomRuleStateEnabled
err := this.Save(tx, op)
if err != nil {
return 0, err
}
return types.Int64(op.Id), nil
}
func (this *HTTPDNSCustomRuleDAO) UpdateRule(tx *dbs.Tx, rule *HTTPDNSCustomRule) error {
var op = NewHTTPDNSCustomRuleOperator()
op.Id = rule.Id
op.RuleName = rule.RuleName
op.LineScope = rule.LineScope
op.LineCarrier = rule.LineCarrier
op.LineRegion = rule.LineRegion
op.LineProvince = rule.LineProvince
op.LineContinent = rule.LineContinent
op.LineCountry = rule.LineCountry
op.TTL = rule.TTL
op.IsOn = rule.IsOn
op.Priority = rule.Priority
op.UpdatedAt = time.Now().Unix()
return this.Save(tx, op)
}
func (this *HTTPDNSCustomRuleDAO) DisableRule(tx *dbs.Tx, ruleId int64) error {
_, err := this.Query(tx).
Pk(ruleId).
Set("state", HTTPDNSCustomRuleStateDisabled).
Update()
return err
}
func (this *HTTPDNSCustomRuleDAO) DisableRulesWithAppId(tx *dbs.Tx, appDbId int64) error {
_, err := this.Query(tx).
Attr("appId", appDbId).
State(HTTPDNSCustomRuleStateEnabled).
Set("state", HTTPDNSCustomRuleStateDisabled).
Set("updatedAt", time.Now().Unix()).
Update()
return err
}
func (this *HTTPDNSCustomRuleDAO) UpdateRuleStatus(tx *dbs.Tx, ruleId int64, isOn bool) error {
_, err := this.Query(tx).
Pk(ruleId).
State(HTTPDNSCustomRuleStateEnabled).
Set("isOn", isOn).
Set("updatedAt", time.Now().Unix()).
Update()
return err
}
func (this *HTTPDNSCustomRuleDAO) FindEnabledRule(tx *dbs.Tx, ruleId int64) (*HTTPDNSCustomRule, error) {
one, err := this.Query(tx).
Pk(ruleId).
State(HTTPDNSCustomRuleStateEnabled).
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSCustomRule), nil
}
func (this *HTTPDNSCustomRuleDAO) ListEnabledRulesWithDomainId(tx *dbs.Tx, domainId int64) (result []*HTTPDNSCustomRule, err error) {
_, err = this.Query(tx).
State(HTTPDNSCustomRuleStateEnabled).
Attr("domainId", domainId).
Asc("priority").
AscPk().
Slice(&result).
FindAll()
return
}
func (this *HTTPDNSCustomRuleDAO) ListEnabledRulesWithAppId(tx *dbs.Tx, appDbId int64) (result []*HTTPDNSCustomRule, err error) {
_, err = this.Query(tx).
State(HTTPDNSCustomRuleStateEnabled).
Attr("appId", appDbId).
Asc("priority").
AscPk().
Slice(&result).
FindAll()
return
}
func (this *HTTPDNSCustomRuleDAO) CountEnabledRulesWithDomainId(tx *dbs.Tx, domainId int64) (int64, error) {
return this.Query(tx).
State(HTTPDNSCustomRuleStateEnabled).
Attr("domainId", domainId).
Count()
}

View File

@@ -0,0 +1,42 @@
package models
// HTTPDNSCustomRule 自定义解析规则
type HTTPDNSCustomRule struct {
Id uint32 `field:"id"` // ID
AppId uint32 `field:"appId"` // 应用DB ID
DomainId uint32 `field:"domainId"` // 域名ID
RuleName string `field:"ruleName"` // 规则名称
LineScope string `field:"lineScope"` // 线路范围
LineCarrier string `field:"lineCarrier"` // 运营商
LineRegion string `field:"lineRegion"` // 区域
LineProvince string `field:"lineProvince"` // 省份
LineContinent string `field:"lineContinent"` // 大洲
LineCountry string `field:"lineCountry"` // 国家
TTL int32 `field:"ttl"` // TTL
IsOn bool `field:"isOn"` // 启用状态
Priority int32 `field:"priority"` // 优先级
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 记录状态
}
type HTTPDNSCustomRuleOperator struct {
Id any // ID
AppId any // 应用DB ID
DomainId any // 域名ID
RuleName any // 规则名称
LineScope any // 线路范围
LineCarrier any // 运营商
LineRegion any // 区域
LineProvince any // 省份
LineContinent any // 大洲
LineCountry any // 国家
TTL any // TTL
IsOn any // 启用状态
Priority any // 优先级
UpdatedAt any // 修改时间
State any // 记录状态
}
func NewHTTPDNSCustomRuleOperator() *HTTPDNSCustomRuleOperator {
return &HTTPDNSCustomRuleOperator{}
}

View File

@@ -0,0 +1,69 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
)
const (
HTTPDNSCustomRuleRecordStateEnabled = 1
HTTPDNSCustomRuleRecordStateDisabled = 0
)
type HTTPDNSCustomRuleRecordDAO dbs.DAO
func NewHTTPDNSCustomRuleRecordDAO() *HTTPDNSCustomRuleRecordDAO {
return dbs.NewDAO(&HTTPDNSCustomRuleRecordDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeHTTPDNSCustomRuleRecords",
Model: new(HTTPDNSCustomRuleRecord),
PkName: "id",
},
}).(*HTTPDNSCustomRuleRecordDAO)
}
var SharedHTTPDNSCustomRuleRecordDAO *HTTPDNSCustomRuleRecordDAO
func init() {
dbs.OnReady(func() {
SharedHTTPDNSCustomRuleRecordDAO = NewHTTPDNSCustomRuleRecordDAO()
})
}
func (this *HTTPDNSCustomRuleRecordDAO) CreateRecord(tx *dbs.Tx, ruleId int64, recordType string, recordValue string, weight int32, sort int32) (int64, error) {
var op = NewHTTPDNSCustomRuleRecordOperator()
op.RuleId = ruleId
op.RecordType = recordType
op.RecordValue = recordValue
op.Weight = weight
op.Sort = sort
op.State = HTTPDNSCustomRuleRecordStateEnabled
err := this.Save(tx, op)
if err != nil {
return 0, err
}
return types.Int64(op.Id), nil
}
func (this *HTTPDNSCustomRuleRecordDAO) DisableRecordsWithRuleId(tx *dbs.Tx, ruleId int64) error {
_, err := this.Query(tx).
Attr("ruleId", ruleId).
State(HTTPDNSCustomRuleRecordStateEnabled).
Set("state", HTTPDNSCustomRuleRecordStateDisabled).
Update()
return err
}
func (this *HTTPDNSCustomRuleRecordDAO) ListEnabledRecordsWithRuleId(tx *dbs.Tx, ruleId int64) (result []*HTTPDNSCustomRuleRecord, err error) {
_, err = this.Query(tx).
State(HTTPDNSCustomRuleRecordStateEnabled).
Attr("ruleId", ruleId).
Asc("sort").
AscPk().
Slice(&result).
FindAll()
return
}

View File

@@ -0,0 +1,26 @@
package models
// HTTPDNSCustomRuleRecord 自定义规则记录值
type HTTPDNSCustomRuleRecord struct {
Id uint32 `field:"id"` // ID
RuleId uint32 `field:"ruleId"` // 规则ID
RecordType string `field:"recordType"` // 记录类型
RecordValue string `field:"recordValue"` // 记录值
Weight int32 `field:"weight"` // 权重
Sort int32 `field:"sort"` // 顺序
State uint8 `field:"state"` // 记录状态
}
type HTTPDNSCustomRuleRecordOperator struct {
Id any // ID
RuleId any // 规则ID
RecordType any // 记录类型
RecordValue any // 记录值
Weight any // 权重
Sort any // 顺序
State any // 记录状态
}
func NewHTTPDNSCustomRuleRecordOperator() *HTTPDNSCustomRuleRecordOperator {
return &HTTPDNSCustomRuleRecordOperator{}
}

View File

@@ -0,0 +1,115 @@
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/types"
"strings"
"time"
)
const (
HTTPDNSDomainStateEnabled = 1
HTTPDNSDomainStateDisabled = 0
)
type HTTPDNSDomainDAO dbs.DAO
func NewHTTPDNSDomainDAO() *HTTPDNSDomainDAO {
return dbs.NewDAO(&HTTPDNSDomainDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeHTTPDNSDomains",
Model: new(HTTPDNSDomain),
PkName: "id",
},
}).(*HTTPDNSDomainDAO)
}
var SharedHTTPDNSDomainDAO *HTTPDNSDomainDAO
func init() {
dbs.OnReady(func() {
SharedHTTPDNSDomainDAO = NewHTTPDNSDomainDAO()
})
}
func (this *HTTPDNSDomainDAO) CreateDomain(tx *dbs.Tx, appDbId int64, domain string, isOn bool) (int64, error) {
domain = strings.ToLower(strings.TrimSpace(domain))
var op = NewHTTPDNSDomainOperator()
op.AppId = appDbId
op.Domain = domain
op.IsOn = isOn
op.CreatedAt = time.Now().Unix()
op.UpdatedAt = time.Now().Unix()
op.State = HTTPDNSDomainStateEnabled
err := this.Save(tx, op)
if err != nil {
return 0, err
}
return types.Int64(op.Id), nil
}
func (this *HTTPDNSDomainDAO) DisableDomain(tx *dbs.Tx, domainId int64) error {
_, err := this.Query(tx).
Pk(domainId).
Set("state", HTTPDNSDomainStateDisabled).
Update()
return err
}
func (this *HTTPDNSDomainDAO) DisableDomainsWithAppId(tx *dbs.Tx, appDbId int64) error {
_, err := this.Query(tx).
Attr("appId", appDbId).
State(HTTPDNSDomainStateEnabled).
Set("state", HTTPDNSDomainStateDisabled).
Set("updatedAt", time.Now().Unix()).
Update()
return err
}
func (this *HTTPDNSDomainDAO) UpdateDomainStatus(tx *dbs.Tx, domainId int64, isOn bool) error {
_, err := this.Query(tx).
Pk(domainId).
State(HTTPDNSDomainStateEnabled).
Set("isOn", isOn).
Set("updatedAt", time.Now().Unix()).
Update()
return err
}
func (this *HTTPDNSDomainDAO) FindEnabledDomain(tx *dbs.Tx, domainId int64) (*HTTPDNSDomain, error) {
one, err := this.Query(tx).
Pk(domainId).
State(HTTPDNSDomainStateEnabled).
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSDomain), nil
}
func (this *HTTPDNSDomainDAO) FindEnabledDomainWithAppAndName(tx *dbs.Tx, appDbId int64, domain string) (*HTTPDNSDomain, error) {
one, err := this.Query(tx).
State(HTTPDNSDomainStateEnabled).
Attr("appId", appDbId).
Attr("domain", strings.ToLower(strings.TrimSpace(domain))).
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSDomain), nil
}
func (this *HTTPDNSDomainDAO) ListEnabledDomainsWithAppId(tx *dbs.Tx, appDbId int64, keyword string) (result []*HTTPDNSDomain, err error) {
query := this.Query(tx).
State(HTTPDNSDomainStateEnabled).
Attr("appId", appDbId).
AscPk()
if len(keyword) > 0 {
query = query.Where("domain LIKE :kw").Param("kw", "%"+keyword+"%")
}
_, err = query.Slice(&result).FindAll()
return
}

View File

@@ -0,0 +1,26 @@
package models
// HTTPDNSDomain 应用绑定域名
type HTTPDNSDomain struct {
Id uint32 `field:"id"` // ID
AppId uint32 `field:"appId"` // 应用DB ID
Domain string `field:"domain"` // 业务域名
IsOn bool `field:"isOn"` // 是否启用
CreatedAt uint64 `field:"createdAt"` // 创建时间
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 记录状态
}
type HTTPDNSDomainOperator struct {
Id any // ID
AppId any // 应用DB ID
Domain any // 业务域名
IsOn any // 是否启用
CreatedAt any // 创建时间
UpdatedAt any // 修改时间
State any // 记录状态
}
func NewHTTPDNSDomainOperator() *HTTPDNSDomainOperator {
return &HTTPDNSDomainOperator{}
}

View File

@@ -0,0 +1,273 @@
package models
import (
"encoding/json"
"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/rands"
"github.com/iwind/TeaGo/types"
"time"
)
const (
HTTPDNSNodeStateEnabled = 1 // 已启用
HTTPDNSNodeStateDisabled = 0 // 已禁用
)
type HTTPDNSNodeDAO dbs.DAO
func NewHTTPDNSNodeDAO() *HTTPDNSNodeDAO {
return dbs.NewDAO(&HTTPDNSNodeDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeHTTPDNSNodes",
Model: new(HTTPDNSNode),
PkName: "id",
},
}).(*HTTPDNSNodeDAO)
}
var SharedHTTPDNSNodeDAO *HTTPDNSNodeDAO
func init() {
dbs.OnReady(func() {
SharedHTTPDNSNodeDAO = NewHTTPDNSNodeDAO()
})
}
// FindEnabledNodeIdWithUniqueId 根据唯一ID获取启用中的HTTPDNS节点ID
func (this *HTTPDNSNodeDAO) FindEnabledNodeIdWithUniqueId(tx *dbs.Tx, uniqueId string) (int64, error) {
return this.Query(tx).
Attr("uniqueId", uniqueId).
Attr("state", HTTPDNSNodeStateEnabled).
ResultPk().
FindInt64Col(0)
}
// CreateNode 创建节点
func (this *HTTPDNSNodeDAO) CreateNode(tx *dbs.Tx, clusterId int64, name string, installDir string, isOn bool) (int64, error) {
uniqueId := rands.HexString(32)
secret := rands.String(32)
err := SharedApiTokenDAO.CreateAPIToken(tx, uniqueId, secret, nodeconfigs.NodeRoleHTTPDNS)
if err != nil {
return 0, err
}
var op = NewHTTPDNSNodeOperator()
op.ClusterId = clusterId
op.Name = name
op.IsOn = isOn
op.IsUp = false
op.IsInstalled = false
op.IsActive = false
op.UniqueId = uniqueId
op.Secret = secret
op.InstallDir = installDir
op.CreatedAt = time.Now().Unix()
op.UpdatedAt = time.Now().Unix()
op.State = HTTPDNSNodeStateEnabled
err = this.Save(tx, op)
if err != nil {
return 0, err
}
return types.Int64(op.Id), nil
}
// UpdateNode 更新节点
func (this *HTTPDNSNodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, installDir string, isOn bool) error {
var op = NewHTTPDNSNodeOperator()
op.Id = nodeId
op.Name = name
op.InstallDir = installDir
op.IsOn = isOn
op.UpdatedAt = time.Now().Unix()
return this.Save(tx, op)
}
// DisableNode 禁用节点
func (this *HTTPDNSNodeDAO) DisableNode(tx *dbs.Tx, nodeId int64) error {
node, err := this.FindEnabledNode(tx, nodeId)
if err != nil {
return err
}
if node == nil {
return nil
}
_, err = this.Query(tx).
Pk(nodeId).
Set("state", HTTPDNSNodeStateDisabled).
Update()
if err != nil {
return err
}
_, err = SharedApiTokenDAO.Query(tx).
Attr("nodeId", node.UniqueId).
Attr("role", nodeconfigs.NodeRoleHTTPDNS).
Set("state", ApiTokenStateDisabled).
Update()
return err
}
// FindEnabledNode 查找启用节点
func (this *HTTPDNSNodeDAO) FindEnabledNode(tx *dbs.Tx, nodeId int64) (*HTTPDNSNode, error) {
one, err := this.Query(tx).
Pk(nodeId).
Attr("state", HTTPDNSNodeStateEnabled).
Find()
if one == nil {
return nil, err
}
return one.(*HTTPDNSNode), nil
}
// FindNodeClusterId 查询节点所属集群ID
func (this *HTTPDNSNodeDAO) FindNodeClusterId(tx *dbs.Tx, nodeId int64) (int64, error) {
return this.Query(tx).
Pk(nodeId).
Attr("state", HTTPDNSNodeStateEnabled).
Result("clusterId").
FindInt64Col(0)
}
// ListEnabledNodes 列出节点
func (this *HTTPDNSNodeDAO) ListEnabledNodes(tx *dbs.Tx, clusterId int64) (result []*HTTPDNSNode, err error) {
query := this.Query(tx).
State(HTTPDNSNodeStateEnabled).
AscPk()
if clusterId > 0 {
query = query.Attr("clusterId", clusterId)
}
_, err = query.Slice(&result).FindAll()
return
}
// UpdateNodeStatus 更新节点状态
func (this *HTTPDNSNodeDAO) UpdateNodeStatus(tx *dbs.Tx, nodeId int64, isUp bool, isInstalled bool, isActive bool, statusJSON []byte, installStatusJSON []byte) error {
var op = NewHTTPDNSNodeOperator()
op.Id = nodeId
op.IsUp = isUp
op.IsInstalled = isInstalled
op.IsActive = isActive
op.UpdatedAt = time.Now().Unix()
if len(statusJSON) > 0 {
op.Status = statusJSON
}
if len(installStatusJSON) > 0 {
mergedStatusJSON, mergeErr := this.mergeInstallStatusJSON(tx, nodeId, installStatusJSON)
if mergeErr != nil {
return mergeErr
}
op.InstallStatus = mergedStatusJSON
}
return this.Save(tx, op)
}
// UpdateNodeInstallStatus 更新节点安装状态
func (this *HTTPDNSNodeDAO) UpdateNodeInstallStatus(tx *dbs.Tx, nodeId int64, installStatus *NodeInstallStatus) error {
if installStatus == nil {
return nil
}
// Read existing installStatus to preserve custom fields like 'ssh' and 'ipAddr'
raw, err := this.Query(tx).Pk(nodeId).Result("installStatus").FindBytesCol()
if err != nil {
return err
}
var m = map[string]interface{}{}
if len(raw) > 0 {
_ = json.Unmarshal(raw, &m)
}
// Overlay standard install status fields
statusData, err := json.Marshal(installStatus)
if err != nil {
return err
}
var newStatusMap = map[string]interface{}{}
_ = json.Unmarshal(statusData, &newStatusMap)
for k, v := range newStatusMap {
m[k] = v
}
// Re-marshal the merged map
mergedData, err := json.Marshal(m)
if err != nil {
return err
}
_, err = this.Query(tx).
Pk(nodeId).
Set("installStatus", mergedData).
Set("updatedAt", time.Now().Unix()).
Update()
return err
}
func (this *HTTPDNSNodeDAO) mergeInstallStatusJSON(tx *dbs.Tx, nodeId int64, patch []byte) ([]byte, error) {
if len(patch) == 0 {
return patch, nil
}
raw, err := this.Query(tx).Pk(nodeId).Result("installStatus").FindBytesCol()
if err != nil {
return nil, err
}
merged := map[string]interface{}{}
if len(raw) > 0 {
_ = json.Unmarshal(raw, &merged)
}
patchMap := map[string]interface{}{}
if len(patch) > 0 {
_ = json.Unmarshal(patch, &patchMap)
}
for k, v := range patchMap {
merged[k] = v
}
data, err := json.Marshal(merged)
if err != nil {
return nil, err
}
return data, nil
}
// FindNodeInstallStatus 读取节点安装状态
func (this *HTTPDNSNodeDAO) FindNodeInstallStatus(tx *dbs.Tx, nodeId int64) (*NodeInstallStatus, error) {
raw, err := this.Query(tx).
Pk(nodeId).
State(HTTPDNSNodeStateEnabled).
Result("installStatus").
FindBytesCol()
if err != nil {
return nil, err
}
if len(raw) == 0 {
return nil, nil
}
installStatus := &NodeInstallStatus{}
err = json.Unmarshal(raw, installStatus)
if err != nil {
return nil, err
}
return installStatus, nil
}
// UpdateNodeIsInstalled 更新节点安装状态位
func (this *HTTPDNSNodeDAO) UpdateNodeIsInstalled(tx *dbs.Tx, nodeId int64, isInstalled bool) error {
_, err := this.Query(tx).
Pk(nodeId).
State(HTTPDNSNodeStateEnabled).
Set("isInstalled", isInstalled).
Set("updatedAt", time.Now().Unix()).
Update()
return err
}

View File

@@ -0,0 +1,44 @@
package models
import "github.com/iwind/TeaGo/dbs"
// HTTPDNSNode HTTPDNS节点
type HTTPDNSNode struct {
Id uint32 `field:"id"` // ID
ClusterId uint32 `field:"clusterId"` // 集群ID
Name string `field:"name"` // 节点名称
IsOn bool `field:"isOn"` // 是否启用
IsUp bool `field:"isUp"` // 是否在线
IsInstalled bool `field:"isInstalled"` // 是否已安装
IsActive bool `field:"isActive"` // 是否活跃
UniqueId string `field:"uniqueId"` // 节点唯一ID
Secret string `field:"secret"` // 节点密钥
InstallDir string `field:"installDir"` // 安装目录
Status dbs.JSON `field:"status"` // 运行状态快照
InstallStatus dbs.JSON `field:"installStatus"` // 安装状态
CreatedAt uint64 `field:"createdAt"` // 创建时间
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 记录状态
}
type HTTPDNSNodeOperator struct {
Id any // ID
ClusterId any // 集群ID
Name any // 节点名称
IsOn any // 是否启用
IsUp any // 是否在线
IsInstalled any // 是否已安装
IsActive any // 是否活跃
UniqueId any // 节点唯一ID
Secret any // 节点密钥
InstallDir any // 安装目录
Status any // 运行状态快照
InstallStatus any // 安装状态
CreatedAt any // 创建时间
UpdatedAt any // 修改时间
State any // 记录状态
}
func NewHTTPDNSNodeOperator() *HTTPDNSNodeOperator {
return &HTTPDNSNodeOperator{}
}

View File

@@ -0,0 +1,108 @@
package models
import (
"strconv"
"time"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
)
type HTTPDNSRuntimeLogDAO dbs.DAO
func NewHTTPDNSRuntimeLogDAO() *HTTPDNSRuntimeLogDAO {
return dbs.NewDAO(&HTTPDNSRuntimeLogDAO{
DAOObject: dbs.DAOObject{
DB: Tea.Env,
Table: "edgeHTTPDNSRuntimeLogs",
Model: new(HTTPDNSRuntimeLog),
PkName: "id",
},
}).(*HTTPDNSRuntimeLogDAO)
}
var SharedHTTPDNSRuntimeLogDAO *HTTPDNSRuntimeLogDAO
func init() {
dbs.OnReady(func() {
SharedHTTPDNSRuntimeLogDAO = NewHTTPDNSRuntimeLogDAO()
})
}
func (this *HTTPDNSRuntimeLogDAO) CreateLog(tx *dbs.Tx, log *HTTPDNSRuntimeLog) error {
lastLog, err := this.Query(tx).
Result("id", "clusterId", "nodeId", "level", "type", "module", "description", "createdAt").
DescPk().
Find()
if err != nil {
return err
}
if lastLog != nil {
nodeLog := lastLog.(*HTTPDNSRuntimeLog)
if nodeLog.ClusterId == log.ClusterId &&
nodeLog.NodeId == log.NodeId &&
nodeLog.Level == log.Level &&
nodeLog.Type == log.Type &&
nodeLog.Module == log.Module &&
nodeLog.Description == log.Description &&
time.Now().Unix()-int64(nodeLog.CreatedAt) < 1800 {
count := log.Count
if count <= 0 {
count = 1
}
return this.Query(tx).
Pk(nodeLog.Id).
Set("count", dbs.SQL("count+"+strconv.FormatInt(count, 10))).
UpdateQuickly()
}
}
var op = NewHTTPDNSRuntimeLogOperator()
op.ClusterId = log.ClusterId
op.NodeId = log.NodeId
op.Level = log.Level
op.Type = log.Type
op.Module = log.Module
op.Description = log.Description
op.Count = log.Count
op.RequestId = log.RequestId
op.CreatedAt = log.CreatedAt
op.Day = log.Day
return this.Save(tx, op)
}
func (this *HTTPDNSRuntimeLogDAO) BuildListQuery(tx *dbs.Tx, day string, clusterId int64, nodeId int64, level string, keyword string) *dbs.Query {
query := this.Query(tx).DescPk()
if len(day) > 0 {
query = query.Attr("day", day)
}
if clusterId > 0 {
query = query.Attr("clusterId", clusterId)
}
if nodeId > 0 {
query = query.Attr("nodeId", nodeId)
}
if len(level) > 0 {
query = query.Attr("level", level)
}
if len(keyword) > 0 {
query = query.Where("(type LIKE :kw OR module LIKE :kw OR description LIKE :kw OR requestId LIKE :kw)").Param("kw", "%"+keyword+"%")
}
return query
}
func (this *HTTPDNSRuntimeLogDAO) CountLogs(tx *dbs.Tx, day string, clusterId int64, nodeId int64, level string, keyword string) (int64, error) {
return this.BuildListQuery(tx, day, clusterId, nodeId, level, keyword).Count()
}
func (this *HTTPDNSRuntimeLogDAO) ListLogs(tx *dbs.Tx, day string, clusterId int64, nodeId int64, level string, keyword string, offset int64, size int64) (result []*HTTPDNSRuntimeLog, err error) {
_, err = this.BuildListQuery(tx, day, clusterId, nodeId, level, keyword).
Offset(offset).
Limit(size).
Slice(&result).
FindAll()
return
}

View File

@@ -0,0 +1,34 @@
package models
// HTTPDNSRuntimeLog 运行日志
type HTTPDNSRuntimeLog struct {
Id uint64 `field:"id"` // ID
ClusterId uint32 `field:"clusterId"` // 集群ID
NodeId uint32 `field:"nodeId"` // 节点ID
Level string `field:"level"` // 级别
Type string `field:"type"` // 类型
Module string `field:"module"` // 模块
Description string `field:"description"` // 详情
Count int64 `field:"count"` // 次数
RequestId string `field:"requestId"` // 请求ID
CreatedAt uint64 `field:"createdAt"` // 创建时间
Day string `field:"day"` // YYYYMMDD
}
type HTTPDNSRuntimeLogOperator struct {
Id any // ID
ClusterId any // 集群ID
NodeId any // 节点ID
Level any // 级别
Type any // 类型
Module any // 模块
Description any // 详情
Count any // 次数
RequestId any // 请求ID
CreatedAt any // 创建时间
Day any // YYYYMMDD
}
func NewHTTPDNSRuntimeLogOperator() *HTTPDNSRuntimeLogOperator {
return &HTTPDNSRuntimeLogOperator{}
}

View File

@@ -15,35 +15,41 @@ import (
type NodeTaskType = string
const (
// CDN相关
// 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策略变化
NodeTaskTypeNetworkSecurityPolicyChanged NodeTaskType = "networkSecurityPolicyChanged" // 网络安全策略变化
NodeTaskTypeWebPPolicyChanged NodeTaskType = "webPPolicyChanged" // WebP策略变化
NodeTaskTypeUpdatingServers NodeTaskType = "updatingServers" // 更新一组服务
NodeTaskTypeTOAChanged NodeTaskType = "toaChanged" // TOA配置变化
NodeTaskTypePlanChanged NodeTaskType = "planChanged" // 套餐变化
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" // 缃戠粶瀹夊叏绛栫暐鍙樺寲
NodeTaskTypeWebPPolicyChanged NodeTaskType = "webPPolicyChanged" // WebP绛栫暐鍙樺寲
NodeTaskTypeUpdatingServers NodeTaskType = "updatingServers" // 鏇存柊涓€缁勬湇鍔?
NodeTaskTypeTOAChanged NodeTaskType = "toaChanged" // TOA閰嶇疆鍙樺寲
NodeTaskTypePlanChanged NodeTaskType = "planChanged" // 濂楅鍙樺寲
// NS相关
// NS鐩稿叧
NSNodeTaskTypeConfigChanged NodeTaskType = "nsConfigChanged"
NSNodeTaskTypeDomainChanged NodeTaskType = "nsDomainChanged"
NSNodeTaskTypeRecordChanged NodeTaskType = "nsRecordChanged"
NSNodeTaskTypeRouteChanged NodeTaskType = "nsRouteChanged"
NSNodeTaskTypeKeyChanged NodeTaskType = "nsKeyChanged"
NSNodeTaskTypeDDosProtectionChanged NodeTaskType = "nsDDoSProtectionChanged" // 节点DDoS配置变更
NSNodeTaskTypeDDosProtectionChanged NodeTaskType = "nsDDoSProtectionChanged" // 鑺傜偣DDoS閰嶇疆鍙樻洿
// HTTPDNS相关
HTTPDNSNodeTaskTypeConfigChanged NodeTaskType = "httpdnsConfigChanged"
HTTPDNSNodeTaskTypeAppChanged NodeTaskType = "httpdnsAppChanged"
HTTPDNSNodeTaskTypeDomainChanged NodeTaskType = "httpdnsDomainChanged"
HTTPDNSNodeTaskTypeRuleChanged NodeTaskType = "httpdnsRuleChanged"
HTTPDNSNodeTaskTypeTLSChanged NodeTaskType = "httpdnsTLSChanged"
)
type NodeTaskDAO dbs.DAO
@@ -67,15 +73,15 @@ func init() {
})
}
// CreateNodeTask 创建单个节点任务
// CreateNodeTask 鍒涘缓鍗曚釜鑺傜偣浠诲姟
func (this *NodeTaskDAO) CreateNodeTask(tx *dbs.Tx, role string, clusterId int64, nodeId int64, userId int64, serverId int64, taskType NodeTaskType) error {
if clusterId <= 0 || nodeId <= 0 {
return nil
}
var uniqueId = role + "@" + types.String(nodeId) + "@node@" + types.String(serverId) + "@" + taskType
// 用户信息
// 没有直接加入到 uniqueId 中,是为了兼容以前的字段值
// 鐢ㄦ埛淇℃伅
// 娌℃湁鐩存帴鍔犲叆鍒?uniqueId 涓紝鏄负浜嗗吋瀹逛互鍓嶇殑瀛楁鍊?
if userId > 0 {
uniqueId += "@" + types.String(userId)
}
@@ -113,7 +119,7 @@ func (this *NodeTaskDAO) CreateNodeTask(tx *dbs.Tx, role string, clusterId int64
return err
}
// CreateClusterTask 创建集群任务
// CreateClusterTask 鍒涘缓闆嗙兢浠诲姟
func (this *NodeTaskDAO) CreateClusterTask(tx *dbs.Tx, role string, clusterId int64, userId int64, serverId int64, taskType NodeTaskType) error {
if clusterId <= 0 {
return nil
@@ -121,8 +127,8 @@ func (this *NodeTaskDAO) CreateClusterTask(tx *dbs.Tx, role string, clusterId in
var uniqueId = role + "@" + types.String(clusterId) + "@" + types.String(serverId) + "@cluster@" + taskType
// 用户信息
// 没有直接加入到 uniqueId 中,是为了兼容以前的字段值
// 鐢ㄦ埛淇℃伅
// 娌℃湁鐩存帴鍔犲叆鍒?uniqueId 涓紝鏄负浜嗗吋瀹逛互鍓嶇殑瀛楁鍊?
if userId > 0 {
uniqueId += "@" + types.String(userId)
}
@@ -155,7 +161,7 @@ func (this *NodeTaskDAO) CreateClusterTask(tx *dbs.Tx, role string, clusterId in
return err
}
// ExtractNodeClusterTask 分解边缘节点集群任务
// ExtractNodeClusterTask 鍒嗚В杈圭紭鑺傜偣闆嗙兢浠诲姟
func (this *NodeTaskDAO) ExtractNodeClusterTask(tx *dbs.Tx, clusterId int64, userId int64, serverId int64, taskType NodeTaskType) error {
nodeIds, err := SharedNodeDAO.FindAllNodeIdsMatch(tx, clusterId, true, configutils.BoolStateYes)
if err != nil {
@@ -193,7 +199,7 @@ func (this *NodeTaskDAO) ExtractNodeClusterTask(tx *dbs.Tx, clusterId int64, use
return nil
}
// ExtractAllClusterTasks 分解所有集群任务
// ExtractAllClusterTasks 鍒嗚В鎵€鏈夐泦缇や换鍔?
func (this *NodeTaskDAO) ExtractAllClusterTasks(tx *dbs.Tx, role string) error {
ones, err := this.Query(tx).
Attr("role", role).
@@ -216,12 +222,17 @@ func (this *NodeTaskDAO) ExtractAllClusterTasks(tx *dbs.Tx, role string) error {
if err != nil {
return err
}
case nodeconfigs.NodeRoleHTTPDNS:
err = this.ExtractHTTPDNSClusterTask(tx, clusterId, one.(*NodeTask).Type)
if err != nil {
return err
}
}
}
return nil
}
// DeleteAllClusterTasks 删除集群所有相关任务
// DeleteAllClusterTasks 鍒犻櫎闆嗙兢鎵€鏈夌浉鍏充换鍔?
func (this *NodeTaskDAO) DeleteAllClusterTasks(tx *dbs.Tx, role string, clusterId int64) error {
_, err := this.Query(tx).
Attr("role", role).
@@ -230,7 +241,7 @@ func (this *NodeTaskDAO) DeleteAllClusterTasks(tx *dbs.Tx, role string, clusterI
return err
}
// DeleteNodeTasks 删除节点相关任务
// DeleteNodeTasks 鍒犻櫎鑺傜偣鐩稿叧浠诲姟
func (this *NodeTaskDAO) DeleteNodeTasks(tx *dbs.Tx, role string, nodeId int64) error {
_, err := this.Query(tx).
Attr("role", role).
@@ -239,13 +250,13 @@ func (this *NodeTaskDAO) DeleteNodeTasks(tx *dbs.Tx, role string, nodeId int64)
return err
}
// DeleteAllNodeTasks 删除所有节点相关任务
// DeleteAllNodeTasks 鍒犻櫎鎵€鏈夎妭鐐圭浉鍏充换鍔?
func (this *NodeTaskDAO) DeleteAllNodeTasks(tx *dbs.Tx) error {
return this.Query(tx).
DeleteQuickly()
}
// FindDoingNodeTasks 查询一个节点的所有任务
// FindDoingNodeTasks 鏌ヨ涓€涓妭鐐圭殑鎵€鏈変换鍔?
func (this *NodeTaskDAO) FindDoingNodeTasks(tx *dbs.Tx, role string, nodeId int64, version int64) (result []*NodeTask, err error) {
if nodeId <= 0 {
return
@@ -256,10 +267,10 @@ func (this *NodeTaskDAO) FindDoingNodeTasks(tx *dbs.Tx, role string, nodeId int6
UseIndex("nodeId").
Asc("version")
if version > 0 {
query.Lt("LENGTH(version)", 19) // 兼容以往版本
query.Lt("LENGTH(version)", 19) // 鍏煎浠ュ線鐗堟湰
query.Gt("version", version)
} else {
// 第一次访问时只取当前正在执行的或者执行失败的
// 绗竴娆¤闂椂鍙彇褰撳墠姝e湪鎵ц鐨勬垨鑰呮墽琛屽け璐ョ殑
query.Where("(isDone=0 OR (isDone=1 AND isOk=0))")
}
_, err = query.
@@ -268,10 +279,10 @@ func (this *NodeTaskDAO) FindDoingNodeTasks(tx *dbs.Tx, role string, nodeId int6
return
}
// UpdateNodeTaskDone 修改节点任务的完成状态
// UpdateNodeTaskDone 淇敼鑺傜偣浠诲姟鐨勫畬鎴愮姸鎬?
func (this *NodeTaskDAO) UpdateNodeTaskDone(tx *dbs.Tx, taskId int64, isOk bool, errorMessage string) error {
if isOk {
// 特殊任务删除
// 鐗规畩浠诲姟鍒犻櫎
taskType, err := this.Query(tx).
Pk(taskId).
Result("type").
@@ -286,7 +297,7 @@ func (this *NodeTaskDAO) UpdateNodeTaskDone(tx *dbs.Tx, taskId int64, isOk bool,
}
}
// 其他任务标记为完成
// 鍏朵粬浠诲姟鏍囪涓哄畬鎴?
var query = this.Query(tx).
Pk(taskId)
if !isOk {
@@ -305,7 +316,7 @@ func (this *NodeTaskDAO) UpdateNodeTaskDone(tx *dbs.Tx, taskId int64, isOk bool,
return err
}
// FindAllDoingTaskClusterIds 查找正在更新的集群IDs
// FindAllDoingTaskClusterIds 鏌ユ壘姝e湪鏇存柊鐨勯泦缇Ds
func (this *NodeTaskDAO) FindAllDoingTaskClusterIds(tx *dbs.Tx, role string) ([]int64, error) {
ones, _, err := this.Query(tx).
Result("DISTINCT(clusterId) AS clusterId").
@@ -322,7 +333,7 @@ func (this *NodeTaskDAO) FindAllDoingTaskClusterIds(tx *dbs.Tx, role string) ([]
return result, nil
}
// FindAllDoingNodeTasksWithClusterId 查询某个集群下所有的任务
// FindAllDoingNodeTasksWithClusterId 鏌ヨ鏌愪釜闆嗙兢涓嬫墍鏈夌殑浠诲姟
func (this *NodeTaskDAO) FindAllDoingNodeTasksWithClusterId(tx *dbs.Tx, role string, clusterId int64) (result []*NodeTask, err error) {
_, err = this.Query(tx).
Attr("role", role).
@@ -337,7 +348,7 @@ func (this *NodeTaskDAO) FindAllDoingNodeTasksWithClusterId(tx *dbs.Tx, role str
return
}
// FindAllDoingNodeIds 查询有任务的节点IDs
// FindAllDoingNodeIds 鏌ヨ鏈変换鍔$殑鑺傜偣IDs
func (this *NodeTaskDAO) FindAllDoingNodeIds(tx *dbs.Tx, role string) ([]int64, error) {
ones, err := this.Query(tx).
Result("DISTINCT(nodeId) AS nodeId").
@@ -356,7 +367,7 @@ func (this *NodeTaskDAO) FindAllDoingNodeIds(tx *dbs.Tx, role string) ([]int64,
return result, nil
}
// ExistsDoingNodeTasks 检查是否有正在执行的任务
// ExistsDoingNodeTasks 妫€鏌ユ槸鍚︽湁姝e湪鎵ц鐨勪换鍔?
func (this *NodeTaskDAO) ExistsDoingNodeTasks(tx *dbs.Tx, role string, excludeTypes []NodeTaskType) (bool, error) {
var query = this.Query(tx).
Attr("role", role).
@@ -370,7 +381,7 @@ func (this *NodeTaskDAO) ExistsDoingNodeTasks(tx *dbs.Tx, role string, excludeTy
return query.Exist()
}
// ExistsErrorNodeTasks 是否有错误的任务
// ExistsErrorNodeTasks 鏄惁鏈夐敊璇殑浠诲姟
func (this *NodeTaskDAO) ExistsErrorNodeTasks(tx *dbs.Tx, role string, excludeTypes []NodeTaskType) (bool, error) {
var query = this.Query(tx).
Attr("role", role).
@@ -383,7 +394,7 @@ func (this *NodeTaskDAO) ExistsErrorNodeTasks(tx *dbs.Tx, role string, excludeTy
return query.Exist()
}
// DeleteNodeTask 删除任务
// DeleteNodeTask 鍒犻櫎浠诲姟
func (this *NodeTaskDAO) DeleteNodeTask(tx *dbs.Tx, taskId int64) error {
_, err := this.Query(tx).
Pk(taskId).
@@ -391,7 +402,7 @@ func (this *NodeTaskDAO) DeleteNodeTask(tx *dbs.Tx, taskId int64) error {
return err
}
// CountDoingNodeTasks 计算正在执行的任务
// CountDoingNodeTasks 璁$畻姝e湪鎵ц鐨勪换鍔?
func (this *NodeTaskDAO) CountDoingNodeTasks(tx *dbs.Tx, role string) (int64, error) {
return this.Query(tx).
Attr("isDone", 0).
@@ -400,7 +411,7 @@ func (this *NodeTaskDAO) CountDoingNodeTasks(tx *dbs.Tx, role string) (int64, er
Count()
}
// FindNotifyingNodeTasks 查找需要通知的任务
// FindNotifyingNodeTasks 鏌ユ壘闇€瑕侀€氱煡鐨勪换鍔?
func (this *NodeTaskDAO) FindNotifyingNodeTasks(tx *dbs.Tx, role string, size int64) (result []*NodeTask, err error) {
_, err = this.Query(tx).
Attr("role", role).
@@ -413,7 +424,7 @@ func (this *NodeTaskDAO) FindNotifyingNodeTasks(tx *dbs.Tx, role string, size in
return
}
// UpdateTasksNotified 设置任务已通知
// UpdateTasksNotified 璁剧疆浠诲姟宸查€氱煡
func (this *NodeTaskDAO) UpdateTasksNotified(tx *dbs.Tx, taskIds []int64) error {
if len(taskIds) == 0 {
return nil
@@ -430,7 +441,7 @@ func (this *NodeTaskDAO) UpdateTasksNotified(tx *dbs.Tx, taskIds []int64) error
return nil
}
// 生成一个版本号
// 鐢熸垚涓€涓増鏈彿
func (this *NodeTaskDAO) increaseVersion(tx *dbs.Tx) (version int64, err error) {
return SharedSysLockerDAO.Increase(tx, "NODE_TASK_VERSION", 0)
}

View File

@@ -0,0 +1,47 @@
package models
import (
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/iwind/TeaGo/dbs"
)
// ExtractHTTPDNSClusterTask 分解HTTPDNS节点集群任务
func (this *NodeTaskDAO) ExtractHTTPDNSClusterTask(tx *dbs.Tx, clusterId int64, taskType NodeTaskType) error {
nodes, err := SharedHTTPDNSNodeDAO.ListEnabledNodes(tx, clusterId)
if err != nil {
return err
}
_, err = this.Query(tx).
Attr("role", nodeconfigs.NodeRoleHTTPDNS).
Attr("clusterId", clusterId).
Gt("nodeId", 0).
Attr("type", taskType).
Delete()
if err != nil {
return err
}
for _, node := range nodes {
if !node.IsOn {
continue
}
err = this.CreateNodeTask(tx, nodeconfigs.NodeRoleHTTPDNS, clusterId, int64(node.Id), 0, 0, taskType)
if err != nil {
return err
}
}
_, err = this.Query(tx).
Attr("role", nodeconfigs.NodeRoleHTTPDNS).
Attr("clusterId", clusterId).
Attr("nodeId", 0).
Attr("type", taskType).
Delete()
if err != nil {
return err
}
return nil
}

View File

@@ -343,6 +343,8 @@ func mapNodeRole(role nodeconfigs.NodeRole) (string, error) {
return fluentBitRoleNode, nil
case nodeconfigs.NodeRoleDNS:
return fluentBitRoleDNS, nil
case nodeconfigs.NodeRoleHTTPDNS:
return fluentBitRoleDNS, nil
default:
return "", fmt.Errorf("unsupported fluent-bit role '%s'", role)
}

View File

@@ -0,0 +1,230 @@
package installers
import (
"bytes"
"errors"
"fmt"
"os"
"path/filepath"
"regexp"
"strings"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
)
type HTTPDNSNodeInstaller struct {
BaseInstaller
}
func (i *HTTPDNSNodeInstaller) Install(dir string, params interface{}, installStatus *models.NodeInstallStatus) error {
if params == nil {
return errors.New("'params' required for node installation")
}
nodeParams, ok := params.(*NodeParams)
if !ok {
return errors.New("'params' should be *NodeParams")
}
err := nodeParams.Validate()
if err != nil {
return fmt.Errorf("params validation: %w", err)
}
installRootDir, appDir := resolveHTTPDNSInstallPaths(dir)
_, err = i.client.Stat(installRootDir)
if err != nil {
err = i.client.MkdirAll(installRootDir)
if err != nil {
installStatus.ErrorCode = "CREATE_ROOT_DIRECTORY_FAILED"
return fmt.Errorf("create directory '%s' failed: %w", installRootDir, err)
}
}
env, err := i.InstallHelper(installRootDir, nodeconfigs.NodeRoleHTTPDNS)
if err != nil {
installStatus.ErrorCode = "INSTALL_HELPER_FAILED"
return err
}
filePrefix := "edge-httpdns-" + env.OS + "-" + env.Arch
zipFile, err := i.LookupLatestInstallerForTarget(filePrefix, env)
if err != nil {
return err
}
if len(zipFile) == 0 {
return errors.New("can not find installer file for " + env.OS + "/" + env.Arch + ", expected '" + filePrefix + "-v*.zip' or distro-specific '" + filePrefix + "-{ubuntu22.04|amzn2023}-v*.zip'")
}
targetZip, err := i.copyZipToRemote(installRootDir, zipFile)
if err != nil {
return err
}
if !nodeParams.IsUpgrading {
_, stderr, testErr := i.client.Exec(env.HelperPath + " -cmd=test")
if testErr != nil {
return fmt.Errorf("test failed: %w", testErr)
}
if len(stderr) > 0 {
return errors.New("test failed: " + stderr)
}
}
exePath := appDir + "/bin/edge-httpdns"
if nodeParams.IsUpgrading {
_, err = i.client.Stat(exePath)
if err == nil {
_, _, _ = i.client.Exec(exePath + " stop")
removeErr := i.client.Remove(exePath)
if removeErr != nil && removeErr != os.ErrNotExist {
return fmt.Errorf("remove old file failed: %w", removeErr)
}
}
}
_, stderr, err := i.client.Exec(env.HelperPath + " -cmd=unzip -zip=\"" + targetZip + "\" -target=\"" + installRootDir + "\"")
if err != nil {
return err
}
if len(stderr) > 0 {
return errors.New("unzip installer failed: " + stderr)
}
certFile := appDir + "/configs/tls/server.crt"
keyFile := appDir + "/configs/tls/server.key"
err = i.writeTLSCertificate(certFile, keyFile, nodeParams.TLSCertData, nodeParams.TLSKeyData)
if err != nil {
installStatus.ErrorCode = "WRITE_TLS_CERT_FAILED"
return err
}
configFile := appDir + "/configs/api_httpdns.yaml"
if i.client.sudo {
_, _, _ = i.client.Exec("chown " + i.client.User() + " " + filepath.Dir(configFile))
}
configData := []byte(`rpc.endpoints: [ ${endpoints} ]
nodeId: "${nodeId}"
secret: "${nodeSecret}"
https.listenAddr: ":443"
https.cert: "${certFile}"
https.key: "${keyFile}"`)
certFileClean := strings.ReplaceAll(certFile, "\\", "/")
keyFileClean := strings.ReplaceAll(keyFile, "\\", "/")
configData = bytes.ReplaceAll(configData, []byte("${endpoints}"), []byte(nodeParams.QuoteEndpoints()))
configData = bytes.ReplaceAll(configData, []byte("${nodeId}"), []byte(nodeParams.NodeId))
configData = bytes.ReplaceAll(configData, []byte("${nodeSecret}"), []byte(nodeParams.Secret))
configData = bytes.ReplaceAll(configData, []byte("${certFile}"), []byte(certFileClean))
configData = bytes.ReplaceAll(configData, []byte("${keyFile}"), []byte(keyFileClean))
_, err = i.client.WriteFile(configFile, configData)
if err != nil {
return fmt.Errorf("write '%s': %w", configFile, err)
}
err = i.SetupFluentBit(nodeconfigs.NodeRoleHTTPDNS)
if err != nil {
installStatus.ErrorCode = "SETUP_FLUENT_BIT_FAILED"
return fmt.Errorf("setup fluent-bit failed: %w", err)
}
startCmdPrefix := "cd " + shQuote(appDir+"/configs") + " && ../bin/edge-httpdns "
stdout, stderr, err := i.client.Exec(startCmdPrefix + "test")
if err != nil {
installStatus.ErrorCode = "TEST_FAILED"
return fmt.Errorf("test edge-httpdns failed: %w, stdout: %s, stderr: %s", err, stdout, stderr)
}
if len(stderr) > 0 {
if regexp.MustCompile(`(?i)rpc`).MatchString(stderr) || regexp.MustCompile(`(?i)rpc`).MatchString(stdout) {
installStatus.ErrorCode = "RPC_TEST_FAILED"
}
return errors.New("test edge-httpdns failed, stdout: " + stdout + ", stderr: " + stderr)
}
stdout, stderr, err = i.client.Exec(startCmdPrefix + "start")
if err != nil {
return fmt.Errorf("start edge-httpdns failed: %w, stdout: %s, stderr: %s", err, stdout, stderr)
}
if len(stderr) > 0 {
return errors.New("start edge-httpdns failed, stdout: " + stdout + ", stderr: " + stderr)
}
return nil
}
func resolveHTTPDNSInstallPaths(rawDir string) (installRootDir string, appDir string) {
dir := strings.TrimSpace(rawDir)
dir = strings.TrimRight(dir, "/")
if len(dir) == 0 {
return rawDir, rawDir + "/edge-httpdns"
}
if strings.HasSuffix(dir, "/edge-httpdns") {
root := strings.TrimSuffix(dir, "/edge-httpdns")
if len(root) == 0 {
root = "/"
}
return root, dir
}
return dir, dir + "/edge-httpdns"
}
func (i *HTTPDNSNodeInstaller) copyZipToRemote(dir string, zipFile string) (string, error) {
targetZip := ""
var firstCopyErr error
zipName := filepath.Base(zipFile)
for _, candidate := range []string{
dir + "/" + zipName,
i.client.UserHome() + "/" + zipName,
"/tmp/" + zipName,
} {
err := i.client.Copy(zipFile, candidate, 0777)
if err != nil {
if firstCopyErr == nil {
firstCopyErr = err
}
continue
}
targetZip = candidate
firstCopyErr = nil
break
}
if firstCopyErr != nil {
return "", fmt.Errorf("upload httpdns file failed: %w", firstCopyErr)
}
return targetZip, nil
}
func (i *HTTPDNSNodeInstaller) writeTLSCertificate(certFile string, keyFile string, certData []byte, keyData []byte) error {
if len(certData) == 0 || len(keyData) == 0 {
return errors.New("cluster tls certificate is empty")
}
certDir := filepath.Dir(certFile)
_, stderr, err := i.client.Exec("mkdir -p " + shQuote(certDir))
if err != nil {
return fmt.Errorf("create tls directory failed: %w, stderr: %s", err, stderr)
}
if i.client.sudo {
_, _, _ = i.client.Exec("chown " + i.client.User() + " " + shQuote(certDir))
}
_, err = i.client.WriteFile(certFile, certData)
if err != nil {
return fmt.Errorf("write cert file failed: %w", err)
}
_, err = i.client.WriteFile(keyFile, keyData)
if err != nil {
return fmt.Errorf("write key file failed: %w", err)
}
_, stderr, err = i.client.Exec("chmod 0644 " + shQuote(certFile) + " && chmod 0600 " + shQuote(keyFile))
if err != nil {
return fmt.Errorf("chmod tls files failed: %w, stderr: %s", err, stderr)
}
return nil
}

View File

@@ -9,6 +9,8 @@ type NodeParams struct {
Endpoints []string
NodeId string
Secret string
TLSCertData []byte
TLSKeyData []byte
IsUpgrading bool // 是否为升级
}

View File

@@ -0,0 +1,280 @@
package installers
import (
"encoding/json"
"errors"
"fmt"
"time"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
)
var sharedHTTPDNSNodeQueue = NewHTTPDNSNodeQueue()
type HTTPDNSNodeQueue struct{}
func NewHTTPDNSNodeQueue() *HTTPDNSNodeQueue {
return &HTTPDNSNodeQueue{}
}
func SharedHTTPDNSNodeQueue() *HTTPDNSNodeQueue {
return sharedHTTPDNSNodeQueue
}
// InstallNodeProcess 鍦ㄧ嚎瀹夎 HTTPDNS 鑺傜偣娴佺▼鎺у埗
func (q *HTTPDNSNodeQueue) InstallNodeProcess(nodeId int64, isUpgrading bool) error {
installStatus := models.NewNodeInstallStatus()
installStatus.IsRunning = true
installStatus.IsFinished = false
installStatus.IsOk = false
installStatus.Error = ""
installStatus.ErrorCode = ""
installStatus.UpdatedAt = time.Now().Unix()
err := models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(nil, nodeId, installStatus)
if err != nil {
return err
}
ticker := utils.NewTicker(3 * time.Second)
goman.New(func() {
for ticker.Wait() {
installStatus.UpdatedAt = time.Now().Unix()
updateErr := models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(nil, nodeId, installStatus)
if updateErr != nil {
logs.Println("[HTTPDNS_INSTALL]" + updateErr.Error())
}
}
})
defer ticker.Stop()
err = q.InstallNode(nodeId, installStatus, isUpgrading)
installStatus.IsRunning = false
installStatus.IsFinished = true
if err != nil {
installStatus.IsOk = false
installStatus.Error = err.Error()
} else {
installStatus.IsOk = true
}
installStatus.UpdatedAt = time.Now().Unix()
updateErr := models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(nil, nodeId, installStatus)
if updateErr != nil {
return updateErr
}
if installStatus.IsOk {
return models.SharedHTTPDNSNodeDAO.UpdateNodeIsInstalled(nil, nodeId, true)
}
return nil
}
// InstallNode 鍦ㄧ嚎瀹夎 HTTPDNS 鑺傜偣
func (q *HTTPDNSNodeQueue) InstallNode(nodeId int64, installStatus *models.NodeInstallStatus, isUpgrading bool) error {
node, err := models.SharedHTTPDNSNodeDAO.FindEnabledNode(nil, nodeId)
if err != nil {
return err
}
if node == nil {
return errors.New("can not find node, ID '" + numberutils.FormatInt64(nodeId) + "'")
}
cluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(nil, int64(node.ClusterId))
if err != nil {
return err
}
if cluster == nil {
return errors.New("can not find cluster")
}
sshHost, sshPort, grantId, err := q.parseSSHInfo(node)
if err != nil {
installStatus.ErrorCode = "EMPTY_SSH"
return err
}
grant, err := models.SharedNodeGrantDAO.FindEnabledNodeGrant(nil, grantId)
if err != nil {
return err
}
if grant == nil {
installStatus.ErrorCode = "EMPTY_GRANT"
return errors.New("can not find user grant with id '" + numberutils.FormatInt64(grantId) + "'")
}
apiNodes, err := models.SharedAPINodeDAO.FindAllEnabledAndOnAPINodes(nil)
if err != nil {
return err
}
if len(apiNodes) == 0 {
return errors.New("no available api nodes")
}
apiEndpoints := make([]string, 0, 8)
for _, apiNode := range apiNodes {
addrConfigs, decodeErr := apiNode.DecodeAccessAddrs()
if decodeErr != nil {
return fmt.Errorf("decode api node access addresses failed: %w", decodeErr)
}
for _, addrConfig := range addrConfigs {
apiEndpoints = append(apiEndpoints, addrConfig.FullAddresses()...)
}
}
if len(apiEndpoints) == 0 {
return errors.New("no available api endpoints")
}
tlsCertData, tlsKeyData, err := q.resolveClusterTLSCertPair(cluster)
if err != nil {
installStatus.ErrorCode = "EMPTY_TLS_CERT"
return err
}
params := &NodeParams{
Endpoints: apiEndpoints,
NodeId: node.UniqueId,
Secret: node.Secret,
TLSCertData: tlsCertData,
TLSKeyData: tlsKeyData,
IsUpgrading: isUpgrading,
}
installer := &HTTPDNSNodeInstaller{}
err = installer.Login(&Credentials{
Host: sshHost,
Port: sshPort,
Username: grant.Username,
Password: grant.Password,
PrivateKey: grant.PrivateKey,
Passphrase: grant.Passphrase,
Method: grant.Method,
Sudo: grant.Su == 1,
})
if err != nil {
installStatus.ErrorCode = "SSH_LOGIN_FAILED"
return err
}
defer func() {
_ = installer.Close()
}()
installDir := node.InstallDir
if len(installDir) == 0 {
if cluster != nil && len(cluster.InstallDir) > 0 {
installDir = cluster.InstallDir
}
if len(installDir) == 0 {
installDir = installer.client.UserHome() + "/edge-httpdns"
}
}
return installer.Install(installDir, params, installStatus)
}
func (q *HTTPDNSNodeQueue) resolveClusterTLSCertPair(cluster *models.HTTPDNSCluster) ([]byte, []byte, error) {
if cluster == nil {
return nil, nil, errors.New("cluster not found")
}
if len(cluster.TLSPolicy) == 0 {
return nil, nil, errors.New("cluster tls policy is empty")
}
tlsConfig := map[string]json.RawMessage{}
if err := json.Unmarshal(cluster.TLSPolicy, &tlsConfig); err != nil {
return nil, nil, fmt.Errorf("decode cluster tls policy failed: %w", err)
}
sslPolicyData := tlsConfig["sslPolicy"]
if len(sslPolicyData) == 0 {
// Compatible with old data where TLSPolicy stores SSLPolicy directly.
sslPolicyData = json.RawMessage(cluster.TLSPolicy)
}
sslPolicy := &sslconfigs.SSLPolicy{}
if err := json.Unmarshal(sslPolicyData, sslPolicy); err != nil {
return nil, nil, fmt.Errorf("decode ssl policy failed: %w", err)
}
for _, cert := range sslPolicy.Certs {
if cert == nil {
continue
}
if len(cert.CertData) > 0 && len(cert.KeyData) > 0 {
return cert.CertData, cert.KeyData, nil
}
}
for _, certRef := range sslPolicy.CertRefs {
if certRef == nil || certRef.CertId <= 0 {
continue
}
certConfig, err := models.SharedSSLCertDAO.ComposeCertConfig(nil, certRef.CertId, false, nil, nil)
if err != nil {
return nil, nil, fmt.Errorf("load ssl cert %d failed: %w", certRef.CertId, err)
}
if certConfig == nil {
continue
}
if len(certConfig.CertData) > 0 && len(certConfig.KeyData) > 0 {
return certConfig.CertData, certConfig.KeyData, nil
}
}
if sslPolicy.Id > 0 {
policyConfig, err := models.SharedSSLPolicyDAO.ComposePolicyConfig(nil, sslPolicy.Id, false, nil, nil)
if err != nil {
return nil, nil, fmt.Errorf("load ssl policy %d failed: %w", sslPolicy.Id, err)
}
if policyConfig != nil {
for _, cert := range policyConfig.Certs {
if cert == nil {
continue
}
if len(cert.CertData) > 0 && len(cert.KeyData) > 0 {
return cert.CertData, cert.KeyData, nil
}
}
}
}
return nil, nil, errors.New("cluster tls certificate is not configured")
}
func (q *HTTPDNSNodeQueue) parseSSHInfo(node *models.HTTPDNSNode) (string, int, int64, error) {
if node == nil {
return "", 0, 0, errors.New("node should not be nil")
}
if len(node.InstallStatus) == 0 {
return "", 0, 0, errors.New("ssh config should not be empty")
}
statusMap := maps.Map{}
err := json.Unmarshal(node.InstallStatus, &statusMap)
if err != nil {
return "", 0, 0, errors.New("invalid install status data")
}
sshMap := statusMap.GetMap("ssh")
if sshMap == nil {
return "", 0, 0, errors.New("ssh config should not be empty")
}
host := sshMap.GetString("host")
port := sshMap.GetInt("port")
grantId := sshMap.GetInt64("grantId")
if len(host) == 0 {
return "", 0, 0, errors.New("ssh host should not be empty")
}
if port <= 0 {
port = 22
}
if grantId <= 0 {
return "", 0, 0, errors.New("grant id should not be empty")
}
return host, port, grantId, nil
}

View File

@@ -144,6 +144,17 @@ func (this *APINode) Start() {
this.processTableNames()
dbs.NotifyReady()
// 自动确保 ClickHouse 日志表存在(不阻断主流程)
this.setProgress("CLICKHOUSE", "正在检查 ClickHouse 日志表")
logs.Println("[API_NODE]ensuring clickhouse tables ...")
err = setup.EnsureClickHouseTables()
if err != nil {
logs.Println("[API_NODE]WARNING: ensure clickhouse tables failed: " + err.Error())
remotelogs.Error("API_NODE", "ensure clickhouse tables failed: "+err.Error())
} else {
logs.Println("[API_NODE]ensure clickhouse tables done")
}
// 设置时区
this.setProgress("TIMEZONE", "正在设置时区")
this.setupTimeZone()

View File

@@ -5,6 +5,7 @@ package nodes
import (
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/clients"
httpdnsservices "github.com/TeaOSLab/EdgeAPI/internal/rpc/services/httpdns"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/users"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"google.golang.org/grpc"
@@ -425,6 +426,46 @@ func (this *APINode) registerServices(server *grpc.Server) {
pb.RegisterDNSTaskServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSClusterService{}).(*httpdnsservices.HTTPDNSClusterService)
pb.RegisterHTTPDNSClusterServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSNodeService{}).(*httpdnsservices.HTTPDNSNodeService)
pb.RegisterHTTPDNSNodeServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSAppService{}).(*httpdnsservices.HTTPDNSAppService)
pb.RegisterHTTPDNSAppServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSDomainService{}).(*httpdnsservices.HTTPDNSDomainService)
pb.RegisterHTTPDNSDomainServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSRuleService{}).(*httpdnsservices.HTTPDNSRuleService)
pb.RegisterHTTPDNSRuleServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSAccessLogService{}).(*httpdnsservices.HTTPDNSAccessLogService)
pb.RegisterHTTPDNSAccessLogServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSRuntimeLogService{}).(*httpdnsservices.HTTPDNSRuntimeLogService)
pb.RegisterHTTPDNSRuntimeLogServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&httpdnsservices.HTTPDNSSandboxService{}).(*httpdnsservices.HTTPDNSSandboxService)
pb.RegisterHTTPDNSSandboxServiceServer(server, instance)
this.rest(instance)
}
{
var instance = this.serviceInstance(&services.NodeClusterFirewallActionService{}).(*services.NodeClusterFirewallActionService)
pb.RegisterNodeClusterFirewallActionServiceServer(server, instance)

View File

@@ -0,0 +1,125 @@
package httpdns
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
)
func toPBCluster(cluster *models.HTTPDNSCluster) *pb.HTTPDNSCluster {
if cluster == nil {
return nil
}
return &pb.HTTPDNSCluster{
Id: int64(cluster.Id),
IsOn: cluster.IsOn,
IsDefault: cluster.IsDefault,
Name: cluster.Name,
ServiceDomain: cluster.ServiceDomain,
DefaultTTL: cluster.DefaultTTL,
FallbackTimeoutMs: cluster.FallbackTimeoutMs,
InstallDir: cluster.InstallDir,
TlsPolicyJSON: cluster.TLSPolicy,
CreatedAt: int64(cluster.CreatedAt),
UpdatedAt: int64(cluster.UpdatedAt),
}
}
func toPBNode(node *models.HTTPDNSNode) *pb.HTTPDNSNode {
if node == nil {
return nil
}
return &pb.HTTPDNSNode{
Id: int64(node.Id),
ClusterId: int64(node.ClusterId),
Name: node.Name,
IsOn: node.IsOn,
IsUp: node.IsUp,
IsInstalled: node.IsInstalled,
IsActive: node.IsActive,
UniqueId: node.UniqueId,
Secret: node.Secret,
InstallDir: node.InstallDir,
StatusJSON: node.Status,
InstallStatusJSON: node.InstallStatus,
CreatedAt: int64(node.CreatedAt),
UpdatedAt: int64(node.UpdatedAt),
}
}
func toPBApp(app *models.HTTPDNSApp, secret *models.HTTPDNSAppSecret) *pb.HTTPDNSApp {
if app == nil {
return nil
}
var signEnabled bool
var signSecret string
var signUpdatedAt int64
if secret != nil {
signEnabled = secret.SignEnabled
signSecret = secret.SignSecret
signUpdatedAt = int64(secret.SignUpdatedAt)
}
return &pb.HTTPDNSApp{
Id: int64(app.Id),
Name: app.Name,
AppId: app.AppId,
IsOn: app.IsOn,
PrimaryClusterId: int64(app.PrimaryClusterId),
BackupClusterId: int64(app.BackupClusterId),
SniMode: app.SNIMode,
SignEnabled: signEnabled,
SignSecret: signSecret,
SignUpdatedAt: signUpdatedAt,
CreatedAt: int64(app.CreatedAt),
UpdatedAt: int64(app.UpdatedAt),
UserId: int64(app.UserId),
}
}
func toPBDomain(domain *models.HTTPDNSDomain, ruleCount int64) *pb.HTTPDNSDomain {
if domain == nil {
return nil
}
return &pb.HTTPDNSDomain{
Id: int64(domain.Id),
AppId: int64(domain.AppId),
Domain: domain.Domain,
IsOn: domain.IsOn,
CreatedAt: int64(domain.CreatedAt),
UpdatedAt: int64(domain.UpdatedAt),
RuleCount: ruleCount,
}
}
func toPBRule(rule *models.HTTPDNSCustomRule, records []*models.HTTPDNSCustomRuleRecord) *pb.HTTPDNSCustomRule {
if rule == nil {
return nil
}
var pbRecords []*pb.HTTPDNSRuleRecord
for _, record := range records {
pbRecords = append(pbRecords, &pb.HTTPDNSRuleRecord{
Id: int64(record.Id),
RuleId: int64(record.RuleId),
RecordType: record.RecordType,
RecordValue: record.RecordValue,
Weight: record.Weight,
Sort: record.Sort,
})
}
return &pb.HTTPDNSCustomRule{
Id: int64(rule.Id),
AppId: int64(rule.AppId),
DomainId: int64(rule.DomainId),
RuleName: rule.RuleName,
LineScope: rule.LineScope,
LineCarrier: rule.LineCarrier,
LineRegion: rule.LineRegion,
LineProvince: rule.LineProvince,
LineContinent: rule.LineContinent,
LineCountry: rule.LineCountry,
Ttl: rule.TTL,
IsOn: rule.IsOn,
Priority: rule.Priority,
UpdatedAt: int64(rule.UpdatedAt),
Records: pbRecords,
}
}

View File

@@ -0,0 +1,258 @@
package httpdns
import (
"context"
"log"
"strconv"
"time"
"github.com/TeaOSLab/EdgeAPI/internal/clickhouse"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
timeutil "github.com/iwind/TeaGo/utils/time"
)
type HTTPDNSAccessLogService struct {
services.BaseService
pb.UnimplementedHTTPDNSAccessLogServiceServer
}
func (s *HTTPDNSAccessLogService) CreateHTTPDNSAccessLogs(ctx context.Context, req *pb.CreateHTTPDNSAccessLogsRequest) (*pb.CreateHTTPDNSAccessLogsResponse, error) {
nodeIdInContext, err := s.ValidateHTTPDNSNode(ctx)
if err != nil {
_, err = s.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
}
if len(req.GetLogs()) == 0 {
return &pb.CreateHTTPDNSAccessLogsResponse{}, nil
}
mysqlLogs := make([]*models.HTTPDNSAccessLog, 0, len(req.GetLogs()))
chLogs := make([]*pb.HTTPDNSAccessLog, 0, len(req.GetLogs()))
seen := map[string]struct{}{}
for _, item := range req.GetLogs() {
if item == nil {
continue
}
nodeId := item.GetNodeId()
// When called by HTTPDNS node, trust node id parsed from RPC token.
if nodeIdInContext > 0 {
nodeId = nodeIdInContext
}
clusterId := item.GetClusterId()
if clusterId <= 0 && nodeId > 0 {
clusterId, _ = models.SharedHTTPDNSNodeDAO.FindNodeClusterId(s.NullTx(), nodeId)
}
key := item.GetRequestId() + "#" + strconv.FormatInt(nodeId, 10)
if _, ok := seen[key]; ok {
continue
}
seen[key] = struct{}{}
createdAt := item.GetCreatedAt()
if createdAt <= 0 {
createdAt = time.Now().Unix()
}
day := item.GetDay()
if len(day) == 0 {
day = timeutil.Format("Ymd")
}
mysqlLogs = append(mysqlLogs, &models.HTTPDNSAccessLog{
RequestId: item.GetRequestId(),
ClusterId: uint32(clusterId),
NodeId: uint32(nodeId),
AppId: item.GetAppId(),
AppName: item.GetAppName(),
Domain: item.GetDomain(),
QType: item.GetQtype(),
ClientIP: item.GetClientIP(),
ClientRegion: item.GetClientRegion(),
Carrier: item.GetCarrier(),
SDKVersion: item.GetSdkVersion(),
OS: item.GetOs(),
ResultIPs: item.GetResultIPs(),
Status: item.GetStatus(),
ErrorCode: item.GetErrorCode(),
CostMs: item.GetCostMs(),
CreatedAt: uint64(createdAt),
Day: day,
Summary: item.GetSummary(),
})
chLogs = append(chLogs, &pb.HTTPDNSAccessLog{
RequestId: item.GetRequestId(),
ClusterId: clusterId,
NodeId: nodeId,
AppId: item.GetAppId(),
AppName: item.GetAppName(),
Domain: item.GetDomain(),
Qtype: item.GetQtype(),
ClientIP: item.GetClientIP(),
ClientRegion: item.GetClientRegion(),
Carrier: item.GetCarrier(),
SdkVersion: item.GetSdkVersion(),
Os: item.GetOs(),
ResultIPs: item.GetResultIPs(),
Status: item.GetStatus(),
ErrorCode: item.GetErrorCode(),
CostMs: item.GetCostMs(),
CreatedAt: createdAt,
Day: day,
Summary: item.GetSummary(),
})
}
if s.canWriteHTTPDNSAccessLogsToMySQL() {
for _, item := range mysqlLogs {
err = models.SharedHTTPDNSAccessLogDAO.CreateLog(s.NullTx(), item)
if err != nil {
if models.CheckSQLDuplicateErr(err) {
continue
}
return nil, err
}
}
}
store := clickhouse.NewHTTPDNSAccessLogsStore()
if s.canWriteHTTPDNSAccessLogsToClickHouse() && store.Client().IsConfigured() && len(chLogs) > 0 {
err = store.Insert(ctx, chLogs)
if err != nil {
log.Println("[HTTPDNS_ACCESS_LOG]write clickhouse failed, keep mysql success:", err.Error())
}
}
return &pb.CreateHTTPDNSAccessLogsResponse{}, nil
}
func (s *HTTPDNSAccessLogService) ListHTTPDNSAccessLogs(ctx context.Context, req *pb.ListHTTPDNSAccessLogsRequest) (*pb.ListHTTPDNSAccessLogsResponse, error) {
_, _, err := s.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
store := clickhouse.NewHTTPDNSAccessLogsStore()
canReadFromClickHouse := s.shouldReadHTTPDNSAccessLogsFromClickHouse() && store.Client().IsConfigured()
canReadFromMySQL := s.shouldReadHTTPDNSAccessLogsFromMySQL()
if canReadFromClickHouse {
resp, listErr := s.listFromClickHouse(ctx, store, req)
if listErr == nil {
return resp, nil
}
log.Println("[HTTPDNS_ACCESS_LOG]read clickhouse failed, fallback mysql:", listErr.Error())
if !canReadFromMySQL {
return nil, listErr
}
}
if !canReadFromMySQL {
return &pb.ListHTTPDNSAccessLogsResponse{
Logs: []*pb.HTTPDNSAccessLog{},
Total: 0,
}, nil
}
total, err := models.SharedHTTPDNSAccessLogDAO.CountLogs(s.NullTx(), req.GetDay(), req.GetClusterId(), req.GetNodeId(), req.GetAppId(), req.GetDomain(), req.GetStatus(), req.GetKeyword())
if err != nil {
return nil, err
}
logs, err := models.SharedHTTPDNSAccessLogDAO.ListLogs(s.NullTx(), req.GetDay(), req.GetClusterId(), req.GetNodeId(), req.GetAppId(), req.GetDomain(), req.GetStatus(), req.GetKeyword(), req.GetOffset(), req.GetSize())
if err != nil {
return nil, err
}
result := make([]*pb.HTTPDNSAccessLog, 0, len(logs))
for _, item := range logs {
if item == nil {
continue
}
clusterName, _ := models.SharedHTTPDNSClusterDAO.FindEnabledClusterName(s.NullTx(), int64(item.ClusterId))
nodeName := ""
node, _ := models.SharedHTTPDNSNodeDAO.FindEnabledNode(s.NullTx(), int64(item.NodeId))
if node != nil {
nodeName = node.Name
}
result = append(result, &pb.HTTPDNSAccessLog{
Id: int64(item.Id),
RequestId: item.RequestId,
ClusterId: int64(item.ClusterId),
NodeId: int64(item.NodeId),
AppId: item.AppId,
AppName: item.AppName,
Domain: item.Domain,
Qtype: item.QType,
ClientIP: item.ClientIP,
ClientRegion: item.ClientRegion,
Carrier: item.Carrier,
SdkVersion: item.SDKVersion,
Os: item.OS,
ResultIPs: item.ResultIPs,
Status: item.Status,
ErrorCode: item.ErrorCode,
CostMs: item.CostMs,
CreatedAt: int64(item.CreatedAt),
Day: item.Day,
Summary: item.Summary,
NodeName: nodeName,
ClusterName: clusterName,
})
}
return &pb.ListHTTPDNSAccessLogsResponse{
Logs: result,
Total: total,
}, nil
}
func (s *HTTPDNSAccessLogService) listFromClickHouse(ctx context.Context, store *clickhouse.HTTPDNSAccessLogsStore, req *pb.ListHTTPDNSAccessLogsRequest) (*pb.ListHTTPDNSAccessLogsResponse, error) {
filter := clickhouse.HTTPDNSAccessLogListFilter{
Day: req.GetDay(),
ClusterId: req.GetClusterId(),
NodeId: req.GetNodeId(),
AppId: req.GetAppId(),
Domain: req.GetDomain(),
Status: req.GetStatus(),
Keyword: req.GetKeyword(),
Offset: req.GetOffset(),
Size: req.GetSize(),
}
total, err := store.Count(ctx, filter)
if err != nil {
return nil, err
}
rows, err := store.List(ctx, filter)
if err != nil {
return nil, err
}
result := make([]*pb.HTTPDNSAccessLog, 0, len(rows))
for _, row := range rows {
item := clickhouse.HTTPDNSRowToPB(row)
if item == nil {
continue
}
clusterName, _ := models.SharedHTTPDNSClusterDAO.FindEnabledClusterName(s.NullTx(), item.GetClusterId())
nodeName := ""
node, _ := models.SharedHTTPDNSNodeDAO.FindEnabledNode(s.NullTx(), item.GetNodeId())
if node != nil {
nodeName = node.Name
}
item.ClusterName = clusterName
item.NodeName = nodeName
result = append(result, item)
}
return &pb.ListHTTPDNSAccessLogsResponse{
Logs: result,
Total: total,
}, nil
}

View File

@@ -0,0 +1,19 @@
//go:build !plus
package httpdns
func (s *HTTPDNSAccessLogService) canWriteHTTPDNSAccessLogsToMySQL() bool {
return true
}
func (s *HTTPDNSAccessLogService) canWriteHTTPDNSAccessLogsToClickHouse() bool {
return true
}
func (s *HTTPDNSAccessLogService) shouldReadHTTPDNSAccessLogsFromClickHouse() bool {
return true
}
func (s *HTTPDNSAccessLogService) shouldReadHTTPDNSAccessLogsFromMySQL() bool {
return true
}

View File

@@ -0,0 +1,124 @@
//go:build plus
package httpdns
import (
"log"
"sync"
"time"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
)
var (
httpDNSAccessLogWriteTargetsLocker sync.RWMutex
httpDNSAccessLogWriteTargetsCache = &serverconfigs.AccessLogWriteTargets{
File: true,
MySQL: true,
ClickHouse: false,
}
httpDNSAccessLogWriteTargetsExpireAt int64
)
const httpDNSAccessLogWriteTargetsCacheTTL = 10 * time.Second
func (s *HTTPDNSAccessLogService) canWriteHTTPDNSAccessLogsToMySQL() bool {
targets := s.readHTTPDNSAccessLogWriteTargets()
if targets == nil {
return true
}
return targets.MySQL
}
func (s *HTTPDNSAccessLogService) canWriteHTTPDNSAccessLogsToClickHouse() bool {
targets := s.readHTTPDNSAccessLogWriteTargets()
if targets == nil {
return false
}
return targets.ClickHouse
}
func (s *HTTPDNSAccessLogService) shouldReadHTTPDNSAccessLogsFromClickHouse() bool {
targets := s.readHTTPDNSAccessLogWriteTargets()
if targets == nil {
return false
}
return targets.ClickHouse
}
func (s *HTTPDNSAccessLogService) shouldReadHTTPDNSAccessLogsFromMySQL() bool {
targets := s.readHTTPDNSAccessLogWriteTargets()
if targets == nil {
return true
}
return targets.MySQL
}
func (s *HTTPDNSAccessLogService) readHTTPDNSAccessLogWriteTargets() *serverconfigs.AccessLogWriteTargets {
now := time.Now().Unix()
httpDNSAccessLogWriteTargetsLocker.RLock()
if now < httpDNSAccessLogWriteTargetsExpireAt && httpDNSAccessLogWriteTargetsCache != nil {
targets := *httpDNSAccessLogWriteTargetsCache
httpDNSAccessLogWriteTargetsLocker.RUnlock()
return &targets
}
httpDNSAccessLogWriteTargetsLocker.RUnlock()
httpDNSAccessLogWriteTargetsLocker.Lock()
defer httpDNSAccessLogWriteTargetsLocker.Unlock()
// double-check
now = time.Now().Unix()
if now < httpDNSAccessLogWriteTargetsExpireAt && httpDNSAccessLogWriteTargetsCache != nil {
targets := *httpDNSAccessLogWriteTargetsCache
return &targets
}
targets := s.loadHTTPDNSAccessLogWriteTargetsFromPolicy()
if targets == nil {
targets = &serverconfigs.AccessLogWriteTargets{
File: true,
MySQL: true,
ClickHouse: false,
}
}
httpDNSAccessLogWriteTargetsCache = targets
httpDNSAccessLogWriteTargetsExpireAt = time.Now().Add(httpDNSAccessLogWriteTargetsCacheTTL).Unix()
copyTargets := *targets
return &copyTargets
}
func (s *HTTPDNSAccessLogService) loadHTTPDNSAccessLogWriteTargetsFromPolicy() *serverconfigs.AccessLogWriteTargets {
tx := s.NullTx()
publicPolicyId, err := models.SharedHTTPAccessLogPolicyDAO.FindCurrentPublicPolicyId(tx)
if err != nil {
log.Println("[HTTPDNS_ACCESS_LOG]load public access log policy failed:", err.Error())
return nil
}
if publicPolicyId <= 0 {
return &serverconfigs.AccessLogWriteTargets{
File: true,
MySQL: true,
ClickHouse: false,
}
}
policy, err := models.SharedHTTPAccessLogPolicyDAO.FindEnabledHTTPAccessLogPolicy(tx, publicPolicyId)
if err != nil {
log.Println("[HTTPDNS_ACCESS_LOG]load access log policy detail failed:", err.Error())
return nil
}
if policy == nil {
return &serverconfigs.AccessLogWriteTargets{
File: true,
MySQL: true,
ClickHouse: false,
}
}
return serverconfigs.ParseWriteTargetsFromPolicy(policy.WriteTargets, policy.Type, policy.DisableDefaultDB)
}

View File

@@ -0,0 +1,246 @@
package httpdns
import (
"context"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/dbs"
"strings"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
)
// HTTPDNSAppService HTTPDNS应用服务
type HTTPDNSAppService struct {
services.BaseService
pb.UnimplementedHTTPDNSAppServiceServer
}
func (this *HTTPDNSAppService) CreateHTTPDNSApp(ctx context.Context, req *pb.CreateHTTPDNSAppRequest) (*pb.CreateHTTPDNSAppResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
if len(req.Name) == 0 || len(req.AppId) == 0 {
return nil, errors.New("required 'name' and 'appId'")
}
if req.PrimaryClusterId <= 0 {
return nil, errors.New("required 'primaryClusterId'")
}
var appDbId int64
err = this.RunTx(func(tx *dbs.Tx) error {
exists, err := models.SharedHTTPDNSAppDAO.FindEnabledAppWithAppId(tx, strings.TrimSpace(req.AppId))
if err != nil {
return err
}
if exists != nil {
return errors.New("appId already exists")
}
appDbId, err = models.SharedHTTPDNSAppDAO.CreateApp(tx, req.Name, strings.TrimSpace(req.AppId), req.PrimaryClusterId, req.BackupClusterId, req.IsOn, req.UserId)
if err != nil {
return err
}
_, _, err = models.SharedHTTPDNSAppSecretDAO.InitAppSecret(tx, appDbId, req.SignEnabled)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, appDbId, models.HTTPDNSNodeTaskTypeAppChanged)
})
if err != nil {
return nil, err
}
return &pb.CreateHTTPDNSAppResponse{AppDbId: appDbId}, nil
}
func (this *HTTPDNSAppService) UpdateHTTPDNSApp(ctx context.Context, req *pb.UpdateHTTPDNSAppRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
oldApp, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(tx, req.AppDbId)
if err != nil {
return err
}
if oldApp == nil {
return errors.New("app not found")
}
err = models.SharedHTTPDNSAppDAO.UpdateApp(tx, req.AppDbId, req.Name, req.PrimaryClusterId, req.BackupClusterId, req.IsOn, req.UserId)
if err != nil {
return err
}
err = notifyHTTPDNSAppTasksByApp(tx, oldApp, models.HTTPDNSNodeTaskTypeAppChanged)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, req.AppDbId, models.HTTPDNSNodeTaskTypeAppChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSAppService) DeleteHTTPDNSApp(ctx context.Context, req *pb.DeleteHTTPDNSAppRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
app, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(tx, req.AppDbId)
if err != nil {
return err
}
if app == nil {
return nil
}
// 1) 先停用规则记录
rules, err := models.SharedHTTPDNSCustomRuleDAO.ListEnabledRulesWithAppId(tx, req.AppDbId)
if err != nil {
return err
}
for _, rule := range rules {
err = models.SharedHTTPDNSCustomRuleRecordDAO.DisableRecordsWithRuleId(tx, int64(rule.Id))
if err != nil {
return err
}
}
// 2) 停用规则、域名、密钥
err = models.SharedHTTPDNSCustomRuleDAO.DisableRulesWithAppId(tx, req.AppDbId)
if err != nil {
return err
}
err = models.SharedHTTPDNSDomainDAO.DisableDomainsWithAppId(tx, req.AppDbId)
if err != nil {
return err
}
err = models.SharedHTTPDNSAppSecretDAO.DisableAppSecret(tx, req.AppDbId)
if err != nil {
return err
}
// 3) 删除该应用的 MySQL 访问日志,避免残留
err = models.SharedHTTPDNSAccessLogDAO.DeleteLogsWithAppId(tx, app.AppId)
if err != nil {
return err
}
// 4) 最后停用应用
err = models.SharedHTTPDNSAppDAO.DisableApp(tx, req.AppDbId)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByApp(tx, app, models.HTTPDNSNodeTaskTypeAppChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSAppService) FindHTTPDNSApp(ctx context.Context, req *pb.FindHTTPDNSAppRequest) (*pb.FindHTTPDNSAppResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
app, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(this.NullTx(), req.AppDbId)
if err != nil {
return nil, err
}
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(this.NullTx(), req.AppDbId)
if err != nil {
return nil, err
}
return &pb.FindHTTPDNSAppResponse{App: toPBApp(app, secret)}, nil
}
func (this *HTTPDNSAppService) ListHTTPDNSApps(ctx context.Context, req *pb.ListHTTPDNSAppsRequest) (*pb.ListHTTPDNSAppsResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
apps, err := models.SharedHTTPDNSAppDAO.ListEnabledApps(this.NullTx(), req.Offset, req.Size, req.Keyword)
if err != nil {
return nil, err
}
var pbApps []*pb.HTTPDNSApp
for _, app := range apps {
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(this.NullTx(), int64(app.Id))
if err != nil {
return nil, err
}
pbApps = append(pbApps, toPBApp(app, secret))
}
return &pb.ListHTTPDNSAppsResponse{Apps: pbApps}, nil
}
func (this *HTTPDNSAppService) FindAllHTTPDNSApps(ctx context.Context, req *pb.FindAllHTTPDNSAppsRequest) (*pb.FindAllHTTPDNSAppsResponse, error) {
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
if validateErr != nil {
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
return nil, validateErr
}
}
apps, err := models.SharedHTTPDNSAppDAO.FindAllEnabledApps(this.NullTx())
if err != nil {
return nil, err
}
var pbApps []*pb.HTTPDNSApp
for _, app := range apps {
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(this.NullTx(), int64(app.Id))
if err != nil {
return nil, err
}
pbApps = append(pbApps, toPBApp(app, secret))
}
return &pb.FindAllHTTPDNSAppsResponse{Apps: pbApps}, nil
}
func (this *HTTPDNSAppService) UpdateHTTPDNSAppSignEnabled(ctx context.Context, req *pb.UpdateHTTPDNSAppSignEnabledRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
err := models.SharedHTTPDNSAppSecretDAO.UpdateSignEnabled(tx, req.AppDbId, req.SignEnabled)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, req.AppDbId, models.HTTPDNSNodeTaskTypeAppChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSAppService) ResetHTTPDNSAppSignSecret(ctx context.Context, req *pb.ResetHTTPDNSAppSignSecretRequest) (*pb.ResetHTTPDNSAppSignSecretResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
var signSecret string
var updatedAt int64
err = this.RunTx(func(tx *dbs.Tx) error {
var err error
signSecret, updatedAt, err = models.SharedHTTPDNSAppSecretDAO.ResetSignSecret(tx, req.AppDbId)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, req.AppDbId, models.HTTPDNSNodeTaskTypeAppChanged)
})
if err != nil {
return nil, err
}
return &pb.ResetHTTPDNSAppSignSecretResponse{
SignSecret: signSecret,
UpdatedAt: updatedAt,
}, nil
}

View File

@@ -0,0 +1,170 @@
package httpdns
import (
"context"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/dbs"
)
// HTTPDNSClusterService HTTPDNS集群服务
type HTTPDNSClusterService struct {
services.BaseService
pb.UnimplementedHTTPDNSClusterServiceServer
}
func (this *HTTPDNSClusterService) CreateHTTPDNSCluster(ctx context.Context, req *pb.CreateHTTPDNSClusterRequest) (*pb.CreateHTTPDNSClusterResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
if len(req.Name) == 0 {
return nil, errors.New("required 'name'")
}
var clusterId int64
err = this.RunTx(func(tx *dbs.Tx) error {
clusterId, err = models.SharedHTTPDNSClusterDAO.CreateCluster(tx, req.Name, req.ServiceDomain, req.DefaultTTL, req.FallbackTimeoutMs, req.InstallDir, req.TlsPolicyJSON, req.IsOn, req.IsDefault)
if err != nil {
return err
}
return notifyHTTPDNSClusterTask(tx, clusterId, models.HTTPDNSNodeTaskTypeConfigChanged)
})
if err != nil {
return nil, err
}
return &pb.CreateHTTPDNSClusterResponse{ClusterId: clusterId}, nil
}
func (this *HTTPDNSClusterService) UpdateHTTPDNSCluster(ctx context.Context, req *pb.UpdateHTTPDNSClusterRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
err = models.SharedHTTPDNSClusterDAO.UpdateCluster(tx, req.ClusterId, req.Name, req.ServiceDomain, req.DefaultTTL, req.FallbackTimeoutMs, req.InstallDir, req.TlsPolicyJSON, req.IsOn, req.IsDefault)
if err != nil {
return err
}
taskType := models.HTTPDNSNodeTaskTypeConfigChanged
if len(req.TlsPolicyJSON) > 0 {
taskType = models.HTTPDNSNodeTaskTypeTLSChanged
}
return notifyHTTPDNSClusterTask(tx, req.ClusterId, taskType)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSClusterService) DeleteHTTPDNSCluster(ctx context.Context, req *pb.DeleteHTTPDNSClusterRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
err = models.SharedHTTPDNSClusterDAO.DisableCluster(tx, req.ClusterId)
if err != nil {
return err
}
return notifyHTTPDNSClusterTask(tx, req.ClusterId, models.HTTPDNSNodeTaskTypeConfigChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSClusterService) FindHTTPDNSCluster(ctx context.Context, req *pb.FindHTTPDNSClusterRequest) (*pb.FindHTTPDNSClusterResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
cluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(this.NullTx(), req.ClusterId)
if err != nil {
return nil, err
}
return &pb.FindHTTPDNSClusterResponse{Cluster: toPBCluster(cluster)}, nil
}
func (this *HTTPDNSClusterService) ListHTTPDNSClusters(ctx context.Context, req *pb.ListHTTPDNSClustersRequest) (*pb.ListHTTPDNSClustersResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
clusters, err := models.SharedHTTPDNSClusterDAO.ListEnabledClusters(this.NullTx(), req.Offset, req.Size, req.Keyword)
if err != nil {
return nil, err
}
var pbClusters []*pb.HTTPDNSCluster
for _, cluster := range clusters {
pbClusters = append(pbClusters, toPBCluster(cluster))
}
return &pb.ListHTTPDNSClustersResponse{Clusters: pbClusters}, nil
}
func (this *HTTPDNSClusterService) FindAllHTTPDNSClusters(ctx context.Context, req *pb.FindAllHTTPDNSClustersRequest) (*pb.FindAllHTTPDNSClustersResponse, error) {
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
if validateErr != nil {
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
return nil, validateErr
}
}
clusters, err := models.SharedHTTPDNSClusterDAO.FindAllEnabledClusters(this.NullTx())
if err != nil {
return nil, err
}
var pbClusters []*pb.HTTPDNSCluster
for _, cluster := range clusters {
pbClusters = append(pbClusters, toPBCluster(cluster))
}
return &pb.FindAllHTTPDNSClustersResponse{Clusters: pbClusters}, nil
}
func (this *HTTPDNSClusterService) UpdateHTTPDNSClusterDefault(ctx context.Context, req *pb.UpdateHTTPDNSClusterDefaultRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
err = models.SharedHTTPDNSClusterDAO.UpdateDefaultCluster(tx, req.ClusterId)
if err != nil {
return err
}
clusters, err := models.SharedHTTPDNSClusterDAO.FindAllEnabledClusters(tx)
if err != nil {
return err
}
for _, cluster := range clusters {
err = notifyHTTPDNSClusterTask(tx, int64(cluster.Id), models.HTTPDNSNodeTaskTypeConfigChanged)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSClusterService) ListHTTPDNSNodesWithClusterId(ctx context.Context, req *pb.ListHTTPDNSNodesWithClusterIdRequest) (*pb.ListHTTPDNSNodesWithClusterIdResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
nodes, err := models.SharedHTTPDNSNodeDAO.ListEnabledNodes(this.NullTx(), req.ClusterId)
if err != nil {
return nil, err
}
var pbNodes []*pb.HTTPDNSNode
for _, node := range nodes {
pbNodes = append(pbNodes, toPBNode(node))
}
return &pb.ListHTTPDNSNodesWithClusterIdResponse{Nodes: pbNodes}, nil
}

View File

@@ -0,0 +1,112 @@
package httpdns
import (
"context"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/dbs"
)
// HTTPDNSDomainService HTTPDNS域名服务
type HTTPDNSDomainService struct {
services.BaseService
pb.UnimplementedHTTPDNSDomainServiceServer
}
func (this *HTTPDNSDomainService) CreateHTTPDNSDomain(ctx context.Context, req *pb.CreateHTTPDNSDomainRequest) (*pb.CreateHTTPDNSDomainResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
if req.AppDbId <= 0 || len(req.Domain) == 0 {
return nil, errors.New("required 'appDbId' and 'domain'")
}
var domainId int64
err = this.RunTx(func(tx *dbs.Tx) error {
domainId, err = models.SharedHTTPDNSDomainDAO.CreateDomain(tx, req.AppDbId, req.Domain, req.IsOn)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, req.AppDbId, models.HTTPDNSNodeTaskTypeDomainChanged)
})
if err != nil {
return nil, err
}
return &pb.CreateHTTPDNSDomainResponse{DomainId: domainId}, nil
}
func (this *HTTPDNSDomainService) DeleteHTTPDNSDomain(ctx context.Context, req *pb.DeleteHTTPDNSDomainRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
domain, err := models.SharedHTTPDNSDomainDAO.FindEnabledDomain(tx, req.DomainId)
if err != nil {
return err
}
if domain == nil {
return nil
}
err = models.SharedHTTPDNSDomainDAO.DisableDomain(tx, req.DomainId)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(domain.AppId), models.HTTPDNSNodeTaskTypeDomainChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSDomainService) UpdateHTTPDNSDomainStatus(ctx context.Context, req *pb.UpdateHTTPDNSDomainStatusRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
domain, err := models.SharedHTTPDNSDomainDAO.FindEnabledDomain(tx, req.DomainId)
if err != nil {
return err
}
if domain == nil {
return nil
}
err = models.SharedHTTPDNSDomainDAO.UpdateDomainStatus(tx, req.DomainId, req.IsOn)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(domain.AppId), models.HTTPDNSNodeTaskTypeDomainChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSDomainService) ListHTTPDNSDomainsWithAppId(ctx context.Context, req *pb.ListHTTPDNSDomainsWithAppIdRequest) (*pb.ListHTTPDNSDomainsWithAppIdResponse, error) {
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
if validateErr != nil {
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
return nil, validateErr
}
}
domains, err := models.SharedHTTPDNSDomainDAO.ListEnabledDomainsWithAppId(this.NullTx(), req.AppDbId, req.Keyword)
if err != nil {
return nil, err
}
var pbDomains []*pb.HTTPDNSDomain
for _, domain := range domains {
ruleCount, err := models.SharedHTTPDNSCustomRuleDAO.CountEnabledRulesWithDomainId(this.NullTx(), int64(domain.Id))
if err != nil {
return nil, err
}
pbDomains = append(pbDomains, toPBDomain(domain, ruleCount))
}
return &pb.ListHTTPDNSDomainsWithAppIdResponse{Domains: pbDomains}, nil
}

View File

@@ -0,0 +1,185 @@
package httpdns
import (
"context"
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/installers"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
)
// HTTPDNSNodeService HTTPDNS节点服务
type HTTPDNSNodeService struct {
services.BaseService
pb.UnimplementedHTTPDNSNodeServiceServer
}
func (this *HTTPDNSNodeService) CreateHTTPDNSNode(ctx context.Context, req *pb.CreateHTTPDNSNodeRequest) (*pb.CreateHTTPDNSNodeResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
if req.ClusterId <= 0 {
return nil, errors.New("required 'clusterId'")
}
var nodeId int64
err = this.RunTx(func(tx *dbs.Tx) error {
nodeId, err = models.SharedHTTPDNSNodeDAO.CreateNode(tx, req.ClusterId, req.Name, req.InstallDir, req.IsOn)
if err != nil {
return err
}
return notifyHTTPDNSClusterTask(tx, req.ClusterId, models.HTTPDNSNodeTaskTypeConfigChanged)
})
if err != nil {
return nil, err
}
return &pb.CreateHTTPDNSNodeResponse{NodeId: nodeId}, nil
}
func (this *HTTPDNSNodeService) UpdateHTTPDNSNode(ctx context.Context, req *pb.UpdateHTTPDNSNodeRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
node, err := models.SharedHTTPDNSNodeDAO.FindEnabledNode(tx, req.NodeId)
if err != nil {
return err
}
if node == nil {
return errors.New("node not found")
}
err = models.SharedHTTPDNSNodeDAO.UpdateNode(tx, req.NodeId, req.Name, req.InstallDir, req.IsOn)
if err != nil {
return err
}
return notifyHTTPDNSClusterTask(tx, int64(node.ClusterId), models.HTTPDNSNodeTaskTypeConfigChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSNodeService) DeleteHTTPDNSNode(ctx context.Context, req *pb.DeleteHTTPDNSNodeRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
node, err := models.SharedHTTPDNSNodeDAO.FindEnabledNode(tx, req.NodeId)
if err != nil {
return err
}
if node == nil {
return nil
}
err = models.SharedHTTPDNSNodeDAO.DisableNode(tx, req.NodeId)
if err != nil {
return err
}
return notifyHTTPDNSClusterTask(tx, int64(node.ClusterId), models.HTTPDNSNodeTaskTypeConfigChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSNodeService) FindHTTPDNSNode(ctx context.Context, req *pb.FindHTTPDNSNodeRequest) (*pb.FindHTTPDNSNodeResponse, error) {
nodeId := req.NodeId
if nodeId <= 0 {
parsedNodeId, nodeErr := this.ValidateHTTPDNSNode(ctx)
if nodeErr != nil {
return nil, errors.New("invalid 'nodeId'")
}
nodeId = parsedNodeId
} else {
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
if validateErr != nil {
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
return nil, validateErr
}
}
}
node, err := models.SharedHTTPDNSNodeDAO.FindEnabledNode(this.NullTx(), nodeId)
if err != nil {
return nil, err
}
return &pb.FindHTTPDNSNodeResponse{Node: toPBNode(node)}, nil
}
func (this *HTTPDNSNodeService) ListHTTPDNSNodes(ctx context.Context, req *pb.ListHTTPDNSNodesRequest) (*pb.ListHTTPDNSNodesResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
nodes, err := models.SharedHTTPDNSNodeDAO.ListEnabledNodes(this.NullTx(), req.ClusterId)
if err != nil {
return nil, err
}
var pbNodes []*pb.HTTPDNSNode
for _, node := range nodes {
pbNodes = append(pbNodes, toPBNode(node))
}
return &pb.ListHTTPDNSNodesResponse{Nodes: pbNodes}, nil
}
func (this *HTTPDNSNodeService) UpdateHTTPDNSNodeStatus(ctx context.Context, req *pb.UpdateHTTPDNSNodeStatusRequest) (*pb.RPCSuccess, error) {
nodeId := req.GetNodeId()
isAdminCaller := false
if nodeId > 0 {
if _, adminErr := this.ValidateAdmin(ctx); adminErr == nil {
isAdminCaller = true
}
}
if !isAdminCaller {
if nodeId <= 0 {
parsedNodeId, err := this.ValidateHTTPDNSNode(ctx)
if err != nil {
return nil, err
}
nodeId = parsedNodeId
}
}
if nodeId <= 0 {
return nil, errors.New("invalid 'nodeId'")
}
err := models.SharedHTTPDNSNodeDAO.UpdateNodeStatus(this.NullTx(), nodeId, req.GetIsUp(), req.GetIsInstalled(), req.GetIsActive(), req.GetStatusJSON(), req.GetInstallStatusJSON())
if err != nil {
return nil, err
}
if isAdminCaller && shouldTriggerHTTPDNSInstall(req.GetInstallStatusJSON()) {
goman.New(func() {
installErr := installers.SharedHTTPDNSNodeQueue().InstallNodeProcess(nodeId, false)
if installErr != nil {
logs.Println("[RPC][HTTPDNS]install node failed:", installErr.Error())
}
})
}
return this.Success()
}
func shouldTriggerHTTPDNSInstall(installStatusJSON []byte) bool {
if len(installStatusJSON) == 0 {
return false
}
installStatus := &models.NodeInstallStatus{}
err := json.Unmarshal(installStatusJSON, installStatus)
if err != nil {
return false
}
return installStatus.IsRunning && !installStatus.IsFinished
}

View File

@@ -0,0 +1,193 @@
package httpdns
import (
"context"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/dbs"
)
// HTTPDNSRuleService HTTPDNS规则服务
type HTTPDNSRuleService struct {
services.BaseService
pb.UnimplementedHTTPDNSRuleServiceServer
}
func (this *HTTPDNSRuleService) CreateHTTPDNSCustomRule(ctx context.Context, req *pb.CreateHTTPDNSCustomRuleRequest) (*pb.CreateHTTPDNSCustomRuleResponse, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
if req.Rule == nil {
return nil, errors.New("required 'rule'")
}
var ruleId int64
err = this.RunTx(func(tx *dbs.Tx) error {
rule := &models.HTTPDNSCustomRule{
AppId: uint32(req.Rule.AppId),
DomainId: uint32(req.Rule.DomainId),
RuleName: req.Rule.RuleName,
LineScope: req.Rule.LineScope,
LineCarrier: req.Rule.LineCarrier,
LineRegion: req.Rule.LineRegion,
LineProvince: req.Rule.LineProvince,
LineContinent: req.Rule.LineContinent,
LineCountry: req.Rule.LineCountry,
TTL: req.Rule.Ttl,
IsOn: req.Rule.IsOn,
Priority: req.Rule.Priority,
}
ruleId, err = models.SharedHTTPDNSCustomRuleDAO.CreateRule(tx, rule)
if err != nil {
return err
}
for _, record := range req.Rule.Records {
_, err := models.SharedHTTPDNSCustomRuleRecordDAO.CreateRecord(tx, ruleId, record.RecordType, record.RecordValue, record.Weight, record.Sort)
if err != nil {
return err
}
}
return notifyHTTPDNSAppTasksByAppDbId(tx, req.Rule.AppId, models.HTTPDNSNodeTaskTypeRuleChanged)
})
if err != nil {
return nil, err
}
return &pb.CreateHTTPDNSCustomRuleResponse{RuleId: ruleId}, nil
}
func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRule(ctx context.Context, req *pb.UpdateHTTPDNSCustomRuleRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
if req.Rule == nil || req.Rule.Id <= 0 {
return nil, errors.New("invalid 'rule.id'")
}
err = this.RunTx(func(tx *dbs.Tx) error {
oldRule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, req.Rule.Id)
if err != nil {
return err
}
if oldRule == nil {
return errors.New("rule not found")
}
rule := &models.HTTPDNSCustomRule{
Id: uint32(req.Rule.Id),
RuleName: req.Rule.RuleName,
LineScope: req.Rule.LineScope,
LineCarrier: req.Rule.LineCarrier,
LineRegion: req.Rule.LineRegion,
LineProvince: req.Rule.LineProvince,
LineContinent: req.Rule.LineContinent,
LineCountry: req.Rule.LineCountry,
TTL: req.Rule.Ttl,
IsOn: req.Rule.IsOn,
Priority: req.Rule.Priority,
}
err = models.SharedHTTPDNSCustomRuleDAO.UpdateRule(tx, rule)
if err != nil {
return err
}
err = models.SharedHTTPDNSCustomRuleRecordDAO.DisableRecordsWithRuleId(tx, req.Rule.Id)
if err != nil {
return err
}
for _, record := range req.Rule.Records {
_, err := models.SharedHTTPDNSCustomRuleRecordDAO.CreateRecord(tx, req.Rule.Id, record.RecordType, record.RecordValue, record.Weight, record.Sort)
if err != nil {
return err
}
}
err = notifyHTTPDNSAppTasksByAppDbId(tx, int64(oldRule.AppId), models.HTTPDNSNodeTaskTypeRuleChanged)
if err != nil {
return err
}
targetAppDbId := req.Rule.AppId
if targetAppDbId <= 0 {
targetAppDbId = int64(oldRule.AppId)
}
return notifyHTTPDNSAppTasksByAppDbId(tx, targetAppDbId, models.HTTPDNSNodeTaskTypeRuleChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSRuleService) DeleteHTTPDNSCustomRule(ctx context.Context, req *pb.DeleteHTTPDNSCustomRuleRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
rule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, req.RuleId)
if err != nil {
return err
}
if rule == nil {
return nil
}
err = models.SharedHTTPDNSCustomRuleDAO.DisableRule(tx, req.RuleId)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(rule.AppId), models.HTTPDNSNodeTaskTypeRuleChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRuleStatus(ctx context.Context, req *pb.UpdateHTTPDNSCustomRuleStatusRequest) (*pb.RPCSuccess, error) {
_, err := this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
err = this.RunTx(func(tx *dbs.Tx) error {
rule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, req.RuleId)
if err != nil {
return err
}
if rule == nil {
return nil
}
err = models.SharedHTTPDNSCustomRuleDAO.UpdateRuleStatus(tx, req.RuleId, req.IsOn)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByAppDbId(tx, int64(rule.AppId), models.HTTPDNSNodeTaskTypeRuleChanged)
})
if err != nil {
return nil, err
}
return this.Success()
}
func (this *HTTPDNSRuleService) ListHTTPDNSCustomRulesWithDomainId(ctx context.Context, req *pb.ListHTTPDNSCustomRulesWithDomainIdRequest) (*pb.ListHTTPDNSCustomRulesWithDomainIdResponse, error) {
_, _, validateErr := this.ValidateAdminAndUser(ctx, true)
if validateErr != nil {
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
return nil, validateErr
}
}
rules, err := models.SharedHTTPDNSCustomRuleDAO.ListEnabledRulesWithDomainId(this.NullTx(), req.DomainId)
if err != nil {
return nil, err
}
var pbRules []*pb.HTTPDNSCustomRule
for _, rule := range rules {
records, err := models.SharedHTTPDNSCustomRuleRecordDAO.ListEnabledRecordsWithRuleId(this.NullTx(), int64(rule.Id))
if err != nil {
return nil, err
}
pbRules = append(pbRules, toPBRule(rule, records))
}
return &pb.ListHTTPDNSCustomRulesWithDomainIdResponse{Rules: pbRules}, nil
}

View File

@@ -0,0 +1,107 @@
package httpdns
import (
"context"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
// HTTPDNSRuntimeLogService HTTPDNS运行日志服务
type HTTPDNSRuntimeLogService struct {
services.BaseService
pb.UnimplementedHTTPDNSRuntimeLogServiceServer
}
func (this *HTTPDNSRuntimeLogService) CreateHTTPDNSRuntimeLogs(ctx context.Context, req *pb.CreateHTTPDNSRuntimeLogsRequest) (*pb.CreateHTTPDNSRuntimeLogsResponse, error) {
nodeIdInContext, err := this.ValidateHTTPDNSNode(ctx)
if err != nil {
_, err = this.ValidateAdmin(ctx)
if err != nil {
return nil, err
}
}
for _, item := range req.Logs {
createdAt := item.CreatedAt
if createdAt <= 0 {
createdAt = time.Now().Unix()
}
day := item.Day
if len(day) == 0 {
day = timeutil.Format("Ymd")
}
nodeId := item.NodeId
// When called by HTTPDNS node, trust node id parsed from RPC token.
if nodeIdInContext > 0 {
nodeId = nodeIdInContext
}
clusterId := item.ClusterId
if clusterId <= 0 && nodeId > 0 {
clusterId, _ = models.SharedHTTPDNSNodeDAO.FindNodeClusterId(this.NullTx(), nodeId)
}
log := &models.HTTPDNSRuntimeLog{
ClusterId: uint32(clusterId),
NodeId: uint32(nodeId),
Level: item.Level,
Type: item.Type,
Module: item.Module,
Description: item.Description,
Count: item.Count,
RequestId: item.RequestId,
CreatedAt: uint64(createdAt),
Day: day,
}
err := models.SharedHTTPDNSRuntimeLogDAO.CreateLog(this.NullTx(), log)
if err != nil {
return nil, err
}
}
return &pb.CreateHTTPDNSRuntimeLogsResponse{}, nil
}
func (this *HTTPDNSRuntimeLogService) ListHTTPDNSRuntimeLogs(ctx context.Context, req *pb.ListHTTPDNSRuntimeLogsRequest) (*pb.ListHTTPDNSRuntimeLogsResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
total, err := models.SharedHTTPDNSRuntimeLogDAO.CountLogs(this.NullTx(), req.Day, req.ClusterId, req.NodeId, req.Level, req.Keyword)
if err != nil {
return nil, err
}
logs, err := models.SharedHTTPDNSRuntimeLogDAO.ListLogs(this.NullTx(), req.Day, req.ClusterId, req.NodeId, req.Level, req.Keyword, req.Offset, req.Size)
if err != nil {
return nil, err
}
var pbLogs []*pb.HTTPDNSRuntimeLog
for _, item := range logs {
clusterName, _ := models.SharedHTTPDNSClusterDAO.FindEnabledClusterName(this.NullTx(), int64(item.ClusterId))
nodeName := ""
node, _ := models.SharedHTTPDNSNodeDAO.FindEnabledNode(this.NullTx(), int64(item.NodeId))
if node != nil {
nodeName = node.Name
}
pbLogs = append(pbLogs, &pb.HTTPDNSRuntimeLog{
Id: int64(item.Id),
ClusterId: int64(item.ClusterId),
NodeId: int64(item.NodeId),
Level: item.Level,
Type: item.Type,
Module: item.Module,
Description: item.Description,
Count: item.Count,
RequestId: item.RequestId,
CreatedAt: int64(item.CreatedAt),
Day: item.Day,
ClusterName: clusterName,
NodeName: nodeName,
})
}
return &pb.ListHTTPDNSRuntimeLogsResponse{
Logs: pbLogs,
Total: total,
}, nil
}

View File

@@ -0,0 +1,252 @@
package httpdns
import (
"context"
"crypto/hmac"
"crypto/sha256"
"crypto/tls"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/rands"
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
// HTTPDNSSandboxService HTTPDNS解析测试服务
type HTTPDNSSandboxService struct {
services.BaseService
pb.UnimplementedHTTPDNSSandboxServiceServer
}
// nodeResolveResponse 节点返回的 JSON 结构(对齐 EdgeHttpDNS resolve_server.go
type nodeResolveResponse struct {
Code string `json:"code"`
Message string `json:"message"`
RequestID string `json:"requestId"`
Data *nodeResolveData `json:"data,omitempty"`
}
type nodeResolveData struct {
Domain string `json:"domain"`
QType string `json:"qtype"`
TTL int32 `json:"ttl"`
Records []*nodeResolveRecord `json:"records"`
Client *nodeClientInfo `json:"client"`
Summary string `json:"summary"`
}
type nodeResolveRecord struct {
Type string `json:"type"`
IP string `json:"ip"`
Weight int32 `json:"weight"`
Line string `json:"line"`
Region string `json:"region"`
}
type nodeClientInfo struct {
IP string `json:"ip"`
Region string `json:"region"`
Carrier string `json:"carrier"`
Country string `json:"country"`
}
func (this *HTTPDNSSandboxService) TestHTTPDNSResolve(ctx context.Context, req *pb.TestHTTPDNSResolveRequest) (*pb.TestHTTPDNSResolveResponse, error) {
_, _, err := this.ValidateAdminAndUser(ctx, true)
if err != nil {
return nil, err
}
if len(req.AppId) == 0 || len(req.Domain) == 0 {
return nil, errors.New("appId 和 domain 不能为空")
}
app, err := models.SharedHTTPDNSAppDAO.FindEnabledAppWithAppId(this.NullTx(), req.AppId)
if err != nil {
return nil, err
}
if app == nil || !app.IsOn {
return &pb.TestHTTPDNSResolveResponse{
Code: "APP_NOT_FOUND_OR_DISABLED",
Message: "找不到指定的应用,或该应用已下线",
RequestId: "rid-" + rands.HexString(12),
}, nil
}
if req.ClusterId > 0 && req.ClusterId != int64(app.PrimaryClusterId) && req.ClusterId != int64(app.BackupClusterId) {
return &pb.TestHTTPDNSResolveResponse{
Code: "APP_CLUSTER_MISMATCH",
Message: "当前应用未绑定到该集群 (主集群: " + strconv.FormatInt(int64(app.PrimaryClusterId), 10) + ", 备用集群: " + strconv.FormatInt(int64(app.BackupClusterId), 10) + ")",
RequestId: "rid-" + rands.HexString(12),
}, nil
}
qtype := strings.ToUpper(strings.TrimSpace(req.Qtype))
if qtype == "" {
qtype = "A"
}
// 获取集群服务域名
clusterId := req.ClusterId
if clusterId <= 0 {
clusterId = int64(app.PrimaryClusterId)
}
cluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(this.NullTx(), clusterId)
if err != nil {
return nil, err
}
if cluster == nil {
return &pb.TestHTTPDNSResolveResponse{
Code: "CLUSTER_NOT_FOUND",
Message: "找不到指定的集群",
RequestId: "rid-" + rands.HexString(12),
}, nil
}
serviceDomain := strings.TrimSpace(cluster.ServiceDomain)
if len(serviceDomain) == 0 {
return &pb.TestHTTPDNSResolveResponse{
Code: "NO_SERVICE_DOMAIN",
Message: "该集群未配置服务域名",
RequestId: "rid-" + rands.HexString(12),
}, nil
}
// 构造请求转发到 EdgeHttpDNS 节点
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(this.NullTx(), int64(app.Id))
if err != nil {
return nil, err
}
query := url.Values{}
query.Set("appId", req.AppId)
query.Set("dn", req.Domain)
query.Set("qtype", qtype)
if len(req.ClientIP) > 0 {
query.Set("cip", req.ClientIP)
}
if len(req.Sid) > 0 {
query.Set("sid", req.Sid)
}
if len(req.SdkVersion) > 0 {
query.Set("sdk_version", req.SdkVersion)
}
if len(req.Os) > 0 {
query.Set("os", req.Os)
}
// 应用开启验签时,沙盒自动生成签名参数,避免测试请求被拒绝
if secret != nil && secret.SignEnabled {
signSecret := strings.TrimSpace(secret.SignSecret)
if len(signSecret) == 0 {
return &pb.TestHTTPDNSResolveResponse{
Code: "SIGN_INVALID",
Message: "应用开启了请求验签,但未配置有效加签 Secret",
RequestId: "rid-" + rands.HexString(12),
Domain: req.Domain,
Qtype: qtype,
}, nil
}
exp := strconv.FormatInt(time.Now().Unix()+300, 10)
nonce := "sandbox-" + rands.HexString(16)
sign := buildSandboxResolveSign(signSecret, req.AppId, req.Domain, qtype, exp, nonce)
query.Set("exp", exp)
query.Set("nonce", nonce)
query.Set("sign", sign)
}
resolveURL := "https://" + serviceDomain + "/resolve?" + query.Encode()
httpClient := &http.Client{
Timeout: 5 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true, // 沙盒测试环境允许自签名证书
},
},
}
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, resolveURL, nil)
if err != nil {
return nil, fmt.Errorf("构建请求失败: %w", err)
}
resp, err := httpClient.Do(httpReq)
if err != nil {
return &pb.TestHTTPDNSResolveResponse{
Code: "NODE_UNREACHABLE",
Message: "无法连接到 HTTPDNS 节点: " + err.Error(),
RequestId: "rid-" + rands.HexString(12),
Domain: req.Domain,
Qtype: qtype,
}, nil
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 64*1024))
if err != nil {
return nil, fmt.Errorf("读取节点响应失败: %w", err)
}
// 解析节点返回的 JSON
var nodeResp nodeResolveResponse
if err := json.Unmarshal(body, &nodeResp); err != nil {
return &pb.TestHTTPDNSResolveResponse{
Code: "PARSE_ERROR",
Message: "解析节点返回数据失败: " + err.Error(),
RequestId: "rid-" + rands.HexString(12),
Domain: req.Domain,
Qtype: qtype,
}, nil
}
// 映射节点响应到 protobuf 响应
pbResp := &pb.TestHTTPDNSResolveResponse{
Code: nodeResp.Code,
Message: nodeResp.Message,
RequestId: nodeResp.RequestID,
Domain: req.Domain,
Qtype: qtype,
}
if nodeResp.Data != nil {
pbResp.Ttl = nodeResp.Data.TTL
pbResp.Summary = nodeResp.Data.Summary
if nodeResp.Data.Client != nil {
pbResp.ClientIP = nodeResp.Data.Client.IP
pbResp.ClientRegion = nodeResp.Data.Client.Region
pbResp.ClientCarrier = nodeResp.Data.Client.Carrier
pbResp.ClientCountry = nodeResp.Data.Client.Country
}
for _, rec := range nodeResp.Data.Records {
pbResp.Records = append(pbResp.Records, &pb.HTTPDNSResolveRecord{
Type: rec.Type,
Ip: rec.IP,
Ttl: nodeResp.Data.TTL,
Weight: rec.Weight,
Line: rec.Line,
Region: rec.Region,
})
}
}
return pbResp, nil
}
func buildSandboxResolveSign(signSecret string, appID string, domain string, qtype string, exp string, nonce string) string {
raw := strings.TrimSpace(appID) + "|" + strings.ToLower(strings.TrimSpace(domain)) + "|" + strings.ToUpper(strings.TrimSpace(qtype)) + "|" + strings.TrimSpace(exp) + "|" + strings.TrimSpace(nonce)
mac := hmac.New(sha256.New, []byte(strings.TrimSpace(signSecret)))
_, _ = mac.Write([]byte(raw))
return hex.EncodeToString(mac.Sum(nil))
}

View File

@@ -0,0 +1,49 @@
package httpdns
import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/iwind/TeaGo/dbs"
)
func notifyHTTPDNSClusterTask(tx *dbs.Tx, clusterId int64, taskType models.NodeTaskType) error {
if clusterId <= 0 {
return nil
}
return models.SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleHTTPDNS, clusterId, 0, 0, taskType)
}
func notifyHTTPDNSAppTasksByApp(tx *dbs.Tx, app *models.HTTPDNSApp, taskType models.NodeTaskType) error {
if app == nil {
return nil
}
primaryClusterId := int64(app.PrimaryClusterId)
backupClusterId := int64(app.BackupClusterId)
err := notifyHTTPDNSClusterTask(tx, primaryClusterId, taskType)
if err != nil {
return err
}
if backupClusterId > 0 && backupClusterId != primaryClusterId {
err = notifyHTTPDNSClusterTask(tx, backupClusterId, taskType)
if err != nil {
return err
}
}
return nil
}
func notifyHTTPDNSAppTasksByAppDbId(tx *dbs.Tx, appDbId int64, taskType models.NodeTaskType) error {
if appDbId <= 0 {
return nil
}
app, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(tx, appDbId)
if err != nil {
return err
}
return notifyHTTPDNSAppTasksByApp(tx, app, taskType)
}

View File

@@ -83,6 +83,12 @@ func (this *BaseService) ValidateNSNode(ctx context.Context) (nodeId int64, err
return
}
// ValidateHTTPDNSNode 校验HTTPDNS节点
func (this *BaseService) ValidateHTTPDNSNode(ctx context.Context) (nodeId int64, err error) {
_, _, nodeId, err = rpcutils.ValidateRequest(ctx, rpcutils.UserTypeHTTPDNS)
return
}
// ValidateUserNode 校验用户节点
func (this *BaseService) ValidateUserNode(ctx context.Context, canRest bool) (userId int64, err error) {
// 不允许REST调用
@@ -105,7 +111,7 @@ func (this *BaseService) ValidateAuthorityNode(ctx context.Context) (nodeId int6
func (this *BaseService) ValidateNodeId(ctx context.Context, roles ...rpcutils.UserType) (role rpcutils.UserType, nodeIntId int64, err error) {
// 默认包含大部分节点
if len(roles) == 0 {
roles = []rpcutils.UserType{rpcutils.UserTypeNode, rpcutils.UserTypeCluster, rpcutils.UserTypeAdmin, rpcutils.UserTypeUser, rpcutils.UserTypeDNS, rpcutils.UserTypeReport, rpcutils.UserTypeLog, rpcutils.UserTypeAPI}
roles = []rpcutils.UserType{rpcutils.UserTypeNode, rpcutils.UserTypeCluster, rpcutils.UserTypeAdmin, rpcutils.UserTypeUser, rpcutils.UserTypeDNS, rpcutils.UserTypeHTTPDNS, rpcutils.UserTypeReport, rpcutils.UserTypeLog, rpcutils.UserTypeAPI}
}
if ctx == nil {
@@ -191,6 +197,8 @@ func (this *BaseService) ValidateNodeId(ctx context.Context, roles ...rpcutils.U
nodeIntId = 0
case rpcutils.UserTypeDNS:
nodeIntId, err = models.SharedNSNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
case rpcutils.UserTypeHTTPDNS:
nodeIntId, err = models.SharedHTTPDNSNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
case rpcutils.UserTypeReport:
nodeIntId, err = models.SharedReportNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
case rpcutils.UserTypeAuthority:

View File

@@ -19,7 +19,7 @@ type NodeTaskService struct {
// FindNodeTasks 获取单节点同步任务
func (this *NodeTaskService) FindNodeTasks(ctx context.Context, req *pb.FindNodeTasksRequest) (*pb.FindNodeTasksResponse, error) {
nodeType, nodeId, err := this.ValidateNodeId(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS)
nodeType, nodeId, err := this.ValidateNodeId(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS, rpcutils.UserTypeHTTPDNS)
if err != nil {
return nil, err
}
@@ -65,7 +65,7 @@ func (this *NodeTaskService) FindNodeTasks(ctx context.Context, req *pb.FindNode
// ReportNodeTaskDone 报告同步任务结果
func (this *NodeTaskService) ReportNodeTaskDone(ctx context.Context, req *pb.ReportNodeTaskDoneRequest) (*pb.RPCSuccess, error) {
_, _, err := this.ValidateNodeId(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS)
_, _, err := this.ValidateNodeId(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS, rpcutils.UserTypeHTTPDNS)
if err != nil {
return nil, err
}

View File

@@ -16,6 +16,7 @@ const (
UserTypeCluster = "cluster"
UserTypeStat = "stat"
UserTypeDNS = "dns"
UserTypeHTTPDNS = "httpdns"
UserTypeLog = "log"
UserTypeAPI = "api"
UserTypeAuthority = "authority"

View File

@@ -142,6 +142,16 @@ func ValidateRequest(ctx context.Context, userTypes ...UserType) (userType UserT
return UserTypeUser, 0, 0, errors.New("context: not found node with id '" + nodeId + "'")
}
resultNodeId = nodeIntId
case UserTypeHTTPDNS:
nodeIntId, err := models.SharedHTTPDNSNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
if err != nil {
return UserTypeHTTPDNS, nodeIntId, 0, errors.New("context: " + err.Error())
}
if nodeIntId <= 0 {
return UserTypeHTTPDNS, nodeIntId, 0, errors.New("context: not found node with id '" + nodeId + "'")
}
nodeUserId = nodeIntId
resultNodeId = nodeIntId
}
if nodeUserId > 0 {

View File

@@ -171,6 +171,16 @@ func ValidateRequest(ctx context.Context, userTypes ...UserType) (userType UserT
}
nodeUserId = nodeIntId
resultNodeId = nodeIntId
case UserTypeHTTPDNS:
nodeIntId, err := models.SharedHTTPDNSNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
if err != nil {
return UserTypeHTTPDNS, nodeIntId, 0, errors.New("context: " + err.Error())
}
if nodeIntId <= 0 {
return UserTypeHTTPDNS, nodeIntId, 0, errors.New("context: not found node with id '" + nodeId + "'")
}
nodeUserId = nodeIntId
resultNodeId = nodeIntId
case UserTypeReport:
nodeIntId, err := models.SharedReportNodeDAO.FindEnabledNodeIdWithUniqueId(nil, nodeId)
if err != nil {

View File

@@ -0,0 +1,132 @@
package setup
import (
"context"
"strings"
"time"
"github.com/TeaOSLab/EdgeAPI/internal/clickhouse"
)
// EnsureClickHouseTables 自动确保日志相关 ClickHouse 表存在。
// 仅做 CREATE TABLE IF NOT EXISTS不会覆盖已有表结构。
func EnsureClickHouseTables() error {
client := clickhouse.NewClient()
if !client.IsConfigured() {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
sqls := []string{
`CREATE TABLE IF NOT EXISTS logs_ingest
(
timestamp DateTime CODEC(DoubleDelta, ZSTD(1)),
node_id UInt64,
cluster_id UInt64,
server_id UInt64,
host LowCardinality(String),
ip String,
method LowCardinality(String),
path String CODEC(ZSTD(1)),
status UInt16,
bytes_in UInt64 CODEC(Delta, ZSTD(1)),
bytes_out UInt64 CODEC(Delta, ZSTD(1)),
cost_ms UInt32 CODEC(Delta, ZSTD(1)),
ua String CODEC(ZSTD(1)),
referer String CODEC(ZSTD(1)),
log_type LowCardinality(String),
trace_id String,
firewall_policy_id UInt64 DEFAULT 0,
firewall_rule_group_id UInt64 DEFAULT 0,
firewall_rule_set_id UInt64 DEFAULT 0,
firewall_rule_id UInt64 DEFAULT 0,
request_headers String CODEC(ZSTD(3)) DEFAULT '',
request_body String CODEC(ZSTD(3)) DEFAULT '',
response_headers String CODEC(ZSTD(3)) DEFAULT '',
response_body String CODEC(ZSTD(3)) DEFAULT '',
INDEX idx_trace_id trace_id TYPE bloom_filter(0.01) GRANULARITY 4,
INDEX idx_ip ip TYPE bloom_filter(0.01) GRANULARITY 4,
INDEX idx_host host TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,
INDEX idx_fw_policy firewall_policy_id TYPE minmax GRANULARITY 4,
INDEX idx_status status TYPE minmax GRANULARITY 4
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (timestamp, node_id, server_id, trace_id)
SETTINGS index_granularity = 8192`,
`CREATE TABLE IF NOT EXISTS dns_logs_ingest
(
timestamp DateTime CODEC(DoubleDelta, ZSTD(1)),
request_id String,
node_id UInt64,
cluster_id UInt64,
domain_id UInt64,
record_id UInt64,
remote_addr String,
question_name String,
question_type LowCardinality(String),
record_name String,
record_type LowCardinality(String),
record_value String,
networking LowCardinality(String),
is_recursive UInt8,
error String CODEC(ZSTD(1)),
ns_route_codes Array(String),
content_json String CODEC(ZSTD(3)) DEFAULT '',
INDEX idx_request_id request_id TYPE bloom_filter(0.01) GRANULARITY 4,
INDEX idx_remote_addr remote_addr TYPE bloom_filter(0.01) GRANULARITY 4,
INDEX idx_question_name question_name TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,
INDEX idx_domain_id domain_id TYPE minmax GRANULARITY 4
)
ENGINE = MergeTree
PARTITION BY toYYYYMMDD(timestamp)
ORDER BY (timestamp, request_id, node_id)
SETTINGS index_granularity = 8192`,
`CREATE TABLE IF NOT EXISTS httpdns_access_logs_ingest
(
request_id String,
cluster_id UInt64,
node_id UInt64,
app_id String,
app_name String,
domain String,
qtype LowCardinality(String),
client_ip String,
client_region String,
carrier String,
sdk_version String,
os LowCardinality(String),
result_ips String,
status LowCardinality(String),
error_code String,
cost_ms UInt32,
created_at UInt64,
day String,
summary String CODEC(ZSTD(1)),
INDEX idx_request_id request_id TYPE bloom_filter(0.01) GRANULARITY 4,
INDEX idx_cluster_id cluster_id TYPE minmax GRANULARITY 4,
INDEX idx_node_id node_id TYPE minmax GRANULARITY 4,
INDEX idx_app_id app_id TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,
INDEX idx_domain domain TYPE tokenbf_v1(10240, 3, 0) GRANULARITY 4,
INDEX idx_status status TYPE minmax GRANULARITY 4
)
ENGINE = MergeTree
PARTITION BY day
ORDER BY (day, created_at, request_id, node_id)
SETTINGS index_granularity = 8192`,
}
for _, sql := range sqls {
stmt := strings.TrimSpace(sql)
if len(stmt) == 0 {
continue
}
if err := client.Execute(ctx, stmt); err != nil {
return err
}
}
return nil
}

View File

@@ -110,6 +110,9 @@ var upgradeFuncs = []*upgradeVersion{
{
"1.4.4", upgradeV1_4_4,
},
{
"1.4.8", upgradeV1_4_8,
},
}
// UpgradeSQLData 升级SQL数据
@@ -269,14 +272,14 @@ func upgradeV0_0_10(db *dbs.DB) error {
// v0.2.5
func upgradeV0_2_5(db *dbs.DB) error {
// 更新用户
// 鏇存柊鐢ㄦ埛
_, err := db.Exec("UPDATE edgeUsers SET day=FROM_UNIXTIME(createdAt,'%Y%m%d') WHERE day IS NULL OR LENGTH(day)=0")
if err != nil {
return err
}
// 更新防火墙规则
ones, _, err := db.FindOnes("SELECT id, actions, action, actionOptions FROM edgeHTTPFirewallRuleSets WHERE actions IS NULL OR LENGTH(actions)=0")
// 更新防火墙规则
ones, _, err := db.FindOnes("SELECT id, actions, action, actionOptions FROM edgeHTTPFirewallRuleSets WHERE actions IS NULL OR LENGTH(actions)=0")
if err != nil {
return err
}
@@ -309,8 +312,8 @@ func upgradeV0_2_5(db *dbs.DB) error {
// v0.3.0
func upgradeV0_3_0(db *dbs.DB) error {
// 升级健康检查
ones, _, err := db.FindOnes("SELECT id,healthCheck FROM edgeNodeClusters WHERE state=1")
// 升级健康检查
ones, _, err := db.FindOnes("SELECT id,healthCheck FROM edgeNodeClusters WHERE state=1")
if err != nil {
return err
}
@@ -342,11 +345,10 @@ func upgradeV0_3_0(db *dbs.DB) error {
// v0.3.1
func upgradeV0_3_1(db *dbs.DB) error {
// 清空域名统计,已使用分表代替
// 因为可能有权限问题,所以我们忽略错误
_, _ = db.Exec("TRUNCATE table edgeServerDomainHourlyStats")
// 娓呯┖鍩熷悕缁熻锛屽凡浣跨敤鍒嗚〃浠f浛
// 鍥犱负鍙兘鏈夋潈闄愰棶棰橈紝鎵€浠ユ垜浠拷鐣ラ敊璇? _, _ = db.Exec("TRUNCATE table edgeServerDomainHourlyStats")
// 升级APIToken
// 鍗囩骇APIToken
ones, _, err := db.FindOnes("SELECT uniqueId,secret FROM edgeNodeClusters")
if err != nil {
return err
@@ -374,9 +376,9 @@ func upgradeV0_3_2(db *dbs.DB) error {
// gzip => compression
type HTTPGzipRef struct {
IsPrior bool `yaml:"isPrior" json:"isPrior"` // 是否覆盖
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
GzipId int64 `yaml:"gzipId" json:"gzipId"` // 使用的配置ID
IsPrior bool `yaml:"isPrior" json:"isPrior"` // 鏄惁瑕嗙洊
IsOn bool `yaml:"isOn" json:"isOn"` // 是否开启
GzipId int64 `yaml:"gzipId" json:"gzipId"` // 使用的配置ID
}
webOnes, _, err := db.FindOnes("SELECT id, gzip FROM edgeHTTPWebs WHERE gzip IS NOT NULL AND compression IS NULL")
@@ -458,7 +460,7 @@ func upgradeV0_3_2(db *dbs.DB) error {
}
}
// 更新服务端口
// 鏇存柊鏈嶅姟绔彛
var serverDAO = models.NewServerDAO()
ones, err := serverDAO.Query(nil).
ResultPk().
@@ -479,14 +481,14 @@ func upgradeV0_3_2(db *dbs.DB) error {
// v0.3.3
func upgradeV0_3_3(db *dbs.DB) error {
// 升级CC请求数Code
_, err := db.Exec("UPDATE edgeHTTPFirewallRuleSets SET code='8002' WHERE name='CC请求数' AND code='8001'")
// 鍗囩骇CC璇锋眰鏁癈ode
_, err := db.Exec("UPDATE edgeHTTPFirewallRuleSets SET code='8002' WHERE name='CC璇锋眰鏁? AND code='8001'")
if err != nil {
return err
}
// 清除节点
// 删除7天以前的info日志
// 娓呴櫎鑺傜偣
// 鍒犻櫎7澶╀互鍓嶇殑info鏃ュ織
err = models.NewNodeLogDAO().DeleteExpiredLogsWithLevel(nil, "info", 7)
if err != nil {
return err
@@ -497,13 +499,13 @@ func upgradeV0_3_3(db *dbs.DB) error {
// v0.3.7
func upgradeV0_3_7(db *dbs.DB) error {
// 修改所有edgeNodeGrants中的su为0
// 淇敼鎵€鏈塭dgeNodeGrants涓殑su涓?
_, err := db.Exec("UPDATE edgeNodeGrants SET su=0 WHERE su=1")
if err != nil {
return err
}
// WAF预置分组
// WAF棰勭疆鍒嗙粍
_, err = db.Exec("UPDATE edgeHTTPFirewallRuleGroups SET isTemplate=1 WHERE LENGTH(code)>0")
if err != nil {
return err
@@ -514,7 +516,7 @@ func upgradeV0_3_7(db *dbs.DB) error {
// v0.4.0
func upgradeV0_4_0(db *dbs.DB) error {
// 升级SYN Flood配置
// 鍗囩骇SYN Flood閰嶇疆
synFloodJSON, err := json.Marshal(firewallconfigs.NewSYNFloodConfig())
if err == nil {
_, err := db.Exec("UPDATE edgeHTTPFirewallPolicies SET synFlood=? WHERE synFlood IS NULL AND state=1", string(synFloodJSON))
@@ -528,13 +530,13 @@ func upgradeV0_4_0(db *dbs.DB) error {
// v0.4.1
func upgradeV0_4_1(db *dbs.DB) error {
// 升级 servers.lastUserPlanId
// 鍗囩骇 servers.lastUserPlanId
_, err := db.Exec("UPDATE edgeServers SET lastUserPlanId=userPlanId WHERE userPlanId>0")
if err != nil {
return err
}
// 执行域名统计清理
// 鎵ц鍩熷悕缁熻娓呯悊
err = stats.NewServerDomainHourlyStatDAO().CleanDays(nil, 7)
if err != nil {
return err
@@ -545,7 +547,7 @@ func upgradeV0_4_1(db *dbs.DB) error {
// v0.4.5
func upgradeV0_4_5(db *dbs.DB) error {
// 升级访问日志自动分表
// 鍗囩骇璁块棶鏃ュ織鑷姩鍒嗚〃
{
var dao = models.NewSysSettingDAO()
valueJSON, err := dao.ReadSetting(nil, systemconfigs.SettingCodeAccessLogQueue)
@@ -569,7 +571,7 @@ func upgradeV0_4_5(db *dbs.DB) error {
}
}
// 升级一个防SQL注入规则
// 鍗囩骇涓€涓槻SQL娉ㄥ叆瑙勫垯
{
ones, _, err := db.FindOnes(`SELECT id FROM edgeHTTPFirewallRules WHERE value=?`, "(updatexml|extractvalue|ascii|ord|char|chr|count|concat|rand|floor|substr|length|len|user|database|benchmark|analyse)\\s*\\(")
if err != nil {
@@ -589,7 +591,7 @@ func upgradeV0_4_5(db *dbs.DB) error {
// v0.4.7
func upgradeV0_4_7(db *dbs.DB) error {
// 升级 edgeServers 中的 plainServerNames
// 鍗囩骇 edgeServers 涓殑 plainServerNames
{
ones, _, err := db.FindOnes("SELECT id,serverNames FROM edgeServers WHERE state=1")
if err != nil {
@@ -621,7 +623,7 @@ func upgradeV0_4_7(db *dbs.DB) error {
// v0.4.8
func upgradeV0_4_8(db *dbs.DB) error {
// 设置edgeIPLists中的serverId
// 璁剧疆edgeIPLists涓殑serverId
{
firewallPolicyOnes, _, err := db.FindOnes("SELECT inbound,serverId FROM edgeHTTPFirewallPolicies WHERE serverId>0")
if err != nil {
@@ -673,7 +675,7 @@ func upgradeV0_4_8(db *dbs.DB) error {
// v0.4.11
func upgradeV0_4_11(db *dbs.DB) error {
// 升级ns端口
// 鍗囩骇ns绔彛
{
// TCP
{
@@ -751,19 +753,19 @@ func upgradeV1_2_1(db *dbs.DB) error {
// 1.2.10
func upgradeV1_2_10(db *dbs.DB) error {
{
type OldGlobalConfig struct {
// HTTP & HTTPS相关配置
HTTPAll struct {
DomainAuditingIsOn bool `yaml:"domainAuditingIsOn" json:"domainAuditingIsOn"` // 域名是否需要审核
DomainAuditingPrompt string `yaml:"domainAuditingPrompt" json:"domainAuditingPrompt"` // 域名审核提示
} `yaml:"httpAll" json:"httpAll"`
type OldGlobalConfig struct {
// HTTP & HTTPS鐩稿叧閰嶇疆
HTTPAll struct {
DomainAuditingIsOn bool `yaml:"domainAuditingIsOn" json:"domainAuditingIsOn"` // 域名是否需要审核
DomainAuditingPrompt string `yaml:"domainAuditingPrompt" json:"domainAuditingPrompt"` // 域名审核提示
} `yaml:"httpAll" json:"httpAll"`
TCPAll struct {
PortRangeMin int `yaml:"portRangeMin" json:"portRangeMin"` // 最小端口
PortRangeMax int `yaml:"portRangeMax" json:"portRangeMax"` // 最大端口
DenyPorts []int `yaml:"denyPorts" json:"denyPorts"` // 禁止使用的端口
} `yaml:"tcpAll" json:"tcpAll"`
}
TCPAll struct {
PortRangeMin int `yaml:"portRangeMin" json:"portRangeMin"` // 最小端口
PortRangeMax int `yaml:"portRangeMax" json:"portRangeMax"` // 最大端口
DenyPorts []int `yaml:"denyPorts" json:"denyPorts"` // 禁止端口
} `yaml:"tcpAll" json:"tcpAll"`
}
globalConfigValue, err := db.FindCol(0, "SELECT value FROM edgeSysSettings WHERE code='serverGlobalConfig'")
if err != nil {
@@ -1023,7 +1025,7 @@ func upgradeV1_3_2(db *dbs.DB) error {
}
}
err = addRuleToGroup(ruleGroup.GetInt64("id"), "7010", "SQL注入检测", []*firewallconfigs.HTTPFirewallActionConfig{
err = addRuleToGroup(ruleGroup.GetInt64("id"), "7010", "SQL注入检测", []*firewallconfigs.HTTPFirewallActionConfig{
{
Code: firewallconfigs.HTTPFirewallActionPage,
Options: maps.Map{"status": 403, "body": ""},
@@ -1131,7 +1133,7 @@ func upgradeV1_3_2(db *dbs.DB) error {
}
}
err = addRuleToGroup(ruleGroup.GetInt64("id"), "1010", "XSS攻击检测", []*firewallconfigs.HTTPFirewallActionConfig{
err = addRuleToGroup(ruleGroup.GetInt64("id"), "1010", "XSS攻击检测", []*firewallconfigs.HTTPFirewallActionConfig{
{
Code: firewallconfigs.HTTPFirewallActionPage,
Options: maps.Map{"status": 403, "body": ""},
@@ -1233,8 +1235,8 @@ func upgradeV1_3_4(db *dbs.DB) error {
// 1.4.4
func upgradeV1_4_4(db *dbs.DB) error {
// 检查 encryption 字段是否已存在
col, err := db.FindCol(0, "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='edgeHTTPWebs' AND COLUMN_NAME='encryption'")
// 检查 encryption 字段是否已存在
col, err := db.FindCol(0, "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA=DATABASE() AND TABLE_NAME='edgeHTTPWebs' AND COLUMN_NAME='encryption'")
if err != nil {
return err
}
@@ -1253,3 +1255,30 @@ func upgradeV1_4_4(db *dbs.DB) error {
return nil
}
// 1.4.8
func upgradeV1_4_8(db *dbs.DB) error {
return createHTTPDNSTables(db)
}
func createHTTPDNSTables(db *dbs.DB) error {
sqls := []string{
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSClusters` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`isDefault` tinyint unsigned DEFAULT '0',`serviceDomain` varchar(255) DEFAULT NULL,`defaultTTL` int unsigned DEFAULT '30',`fallbackTimeoutMs` int unsigned DEFAULT '300',`installDir` varchar(255) DEFAULT '/opt/edge-httpdns',`tlsPolicy` json DEFAULT NULL,`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),KEY `name` (`name`),KEY `isDefault` (`isDefault`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS集群配置表默认TTL、回退超时、服务域名等'",
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSNodes` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`clusterId` bigint unsigned DEFAULT '0',`name` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`isUp` tinyint unsigned DEFAULT '0',`isInstalled` tinyint unsigned DEFAULT '0',`isActive` tinyint unsigned DEFAULT '0',`uniqueId` varchar(64) DEFAULT NULL,`secret` varchar(64) DEFAULT NULL,`installDir` varchar(255) DEFAULT '/opt/edge-httpdns',`status` json DEFAULT NULL,`installStatus` json DEFAULT NULL,`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `uniqueId` (`uniqueId`),KEY `clusterId` (`clusterId`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS节点表节点基础信息与运行状态'",
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSApps` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`appId` varchar(64) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`primaryClusterId` bigint unsigned DEFAULT '0',`backupClusterId` bigint unsigned DEFAULT '0',`sniMode` varchar(64) DEFAULT 'fixed_hide',`userId` bigint unsigned DEFAULT '0',`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `appId` (`appId`),KEY `name` (`name`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS应用表应用与主备集群绑定关系'",
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSAppSecrets` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`appId` bigint unsigned DEFAULT '0',`signEnabled` tinyint unsigned DEFAULT '0',`signSecret` varchar(255) DEFAULT NULL,`signUpdatedAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `appId` (`appId`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS应用密钥表请求验签开关与加签Secret'",
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSDomains` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`appId` bigint unsigned DEFAULT '0',`domain` varchar(255) DEFAULT NULL,`isOn` tinyint unsigned DEFAULT '1',`createdAt` bigint unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),UNIQUE KEY `appId_domain` (`appId`,`domain`),KEY `domain` (`domain`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS应用域名表应用绑定的业务域名'",
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSCustomRules` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`appId` bigint unsigned DEFAULT '0',`domainId` bigint unsigned DEFAULT '0',`ruleName` varchar(255) DEFAULT NULL,`lineScope` varchar(64) DEFAULT NULL,`lineCarrier` varchar(64) DEFAULT NULL,`lineRegion` varchar(64) DEFAULT NULL,`lineProvince` varchar(64) DEFAULT NULL,`lineContinent` varchar(64) DEFAULT NULL,`lineCountry` varchar(128) DEFAULT NULL,`ttl` int unsigned DEFAULT '30',`isOn` tinyint unsigned DEFAULT '1',`priority` int unsigned DEFAULT '0',`updatedAt` bigint unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),KEY `domainId_isOn_priority` (`domainId`,`isOn`,`priority`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS自定义解析规则表按线路/地域匹配)'",
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSCustomRuleRecords` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`ruleId` bigint unsigned DEFAULT '0',`recordType` varchar(32) DEFAULT NULL,`recordValue` varchar(255) DEFAULT NULL,`weight` int unsigned DEFAULT '0',`sort` int unsigned DEFAULT '0',`state` tinyint unsigned DEFAULT '1',PRIMARY KEY (`id`),KEY `ruleId` (`ruleId`),KEY `state` (`state`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS自定义规则记录值表A/AAAA及权重'",
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSAccessLogs` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`requestId` varchar(128) DEFAULT NULL,`clusterId` bigint unsigned DEFAULT '0',`nodeId` bigint unsigned DEFAULT '0',`appId` varchar(64) DEFAULT NULL,`appName` varchar(255) DEFAULT NULL,`domain` varchar(255) DEFAULT NULL,`qtype` varchar(16) DEFAULT NULL,`clientIP` varchar(64) DEFAULT NULL,`clientRegion` varchar(255) DEFAULT NULL,`carrier` varchar(128) DEFAULT NULL,`sdkVersion` varchar(64) DEFAULT NULL,`os` varchar(64) DEFAULT NULL,`resultIPs` text,`status` varchar(32) DEFAULT NULL,`errorCode` varchar(64) DEFAULT NULL,`costMs` int unsigned DEFAULT '0',`createdAt` bigint unsigned DEFAULT '0',`day` varchar(8) DEFAULT NULL,`summary` text,PRIMARY KEY (`id`),UNIQUE KEY `requestId_nodeId` (`requestId`,`nodeId`),KEY `day_cluster_node_domain_status_createdAt` (`day`,`clusterId`,`nodeId`,`domain`,`status`,`createdAt`),KEY `appId` (`appId`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS访问日志表解析请求与结果'",
"CREATE TABLE IF NOT EXISTS `edgeHTTPDNSRuntimeLogs` (`id` bigint unsigned NOT NULL AUTO_INCREMENT,`clusterId` bigint unsigned DEFAULT '0',`nodeId` bigint unsigned DEFAULT '0',`level` varchar(32) DEFAULT NULL,`type` varchar(64) DEFAULT NULL,`module` varchar(64) DEFAULT NULL,`description` text,`count` bigint unsigned DEFAULT '1',`requestId` varchar(128) DEFAULT NULL,`createdAt` bigint unsigned DEFAULT '0',`day` varchar(8) DEFAULT NULL,PRIMARY KEY (`id`),KEY `day_cluster_node_level_createdAt` (`day`,`clusterId`,`nodeId`,`level`,`createdAt`),KEY `requestId` (`requestId`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HTTPDNS运行日志表节点运行与异常日志'",
}
for _, sql := range sqls {
if _, err := db.Exec(sql); err != nil {
return err
}
}
return nil
}

View File

@@ -46,7 +46,7 @@ func (this *NodeTaskExtractor) Loop() error {
// 这里不解锁是为了让任务N秒钟之内只运行一次
for _, role := range []string{nodeconfigs.NodeRoleNode, nodeconfigs.NodeRoleDNS} {
for _, role := range []string{nodeconfigs.NodeRoleNode, nodeconfigs.NodeRoleDNS, nodeconfigs.NodeRoleHTTPDNS} {
err := models.SharedNodeTaskDAO.ExtractAllClusterTasks(nil, role)
if err != nil {
return err