Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
17e182b413 | ||
|
|
afbaaa869c | ||
|
|
a25a474d6a | ||
|
|
fb8348ef47 | ||
|
|
491ade1bc3 | ||
|
|
a10f3f3740 | ||
|
|
49776c3d0a | ||
|
|
49021c7415 | ||
|
|
c040d7c90d | ||
|
|
30a1b4fa5e | ||
|
|
54e808a85d | ||
|
|
c6cbf3c505 | ||
|
|
532891fad0 | ||
|
|
853897a6f8 | ||
|
|
2a76d1773d | ||
|
|
5d0b7c7e91 | ||
|
|
150799f41d | ||
|
|
4d275c921d | ||
|
|
2eb32b9f1f | ||
|
|
60dc87e0f2 | ||
|
|
f3af234308 | ||
|
|
9d941f8bb9 | ||
|
|
9378cdc8f0 | ||
|
|
6d6462c65a | ||
|
|
7d83a9f7f4 | ||
|
|
92ffa1ab77 | ||
|
|
2a811183c4 | ||
|
|
afbc0cde7e | ||
|
|
468d0eeffc | ||
|
|
dd396d31b5 | ||
|
|
eafac7a204 | ||
|
|
4259026c6e | ||
|
|
f38505e210 | ||
|
|
e9093baffb | ||
|
|
c6da67db79 | ||
|
|
c28317ee07 | ||
|
|
1bb8140a41 | ||
|
|
4812ad5aaf | ||
|
|
b7388d83b0 | ||
|
|
bc223fd1aa | ||
|
|
3b042d1dad |
3
.gitattributes
vendored
Normal file
3
.gitattributes
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
*.sh text eol=lf
|
||||
*.bash text eol=lf
|
||||
*.zsh text eol=lf
|
||||
17
.gitignore
vendored
17
.gitignore
vendored
@@ -5,3 +5,20 @@ deploy/fluent-bit/logs.db
|
||||
deploy/fluent-bit/logs.db-shm
|
||||
deploy/fluent-bit/logs.db-wal
|
||||
deploy/fluent-bit/storage/
|
||||
/pkg/
|
||||
/.claude/
|
||||
CLAUDE.md
|
||||
CHANGELOG-1.5.1.md
|
||||
docs/*
|
||||
|
||||
# Test artifacts
|
||||
EdgeDNS/agents.test.exe
|
||||
EdgeAdmin/test_db2.go
|
||||
|
||||
# Runtime data (generated at runtime, not source)
|
||||
EdgeNode/build/data/
|
||||
EdgeDNS/build/data/
|
||||
|
||||
# Local large build artifacts
|
||||
EdgeAdmin/edge-admin.exe
|
||||
EdgeAPI/deploy/edge-node-linux-amd64-v*.zip
|
||||
@@ -41,17 +41,7 @@ function build() {
|
||||
cd "$ROOT""/../../EdgeNode/build" || exit
|
||||
echo "=============================="
|
||||
for arch in "${NODE_ARCHITECTS[@]}"; do
|
||||
# 查找 zip 文件时不包含 plus 标记
|
||||
if [ "${TAG}" = "plus" ]; then
|
||||
NODE_ZIP_FILE="$ROOT""/../../EdgeNode/dist/edge-node-linux-${arch}-v${NodeVersion}.zip"
|
||||
else
|
||||
NODE_ZIP_FILE="$ROOT""/../../EdgeNode/dist/edge-node-linux-${arch}-${TAG}-v${NodeVersion}.zip"
|
||||
fi
|
||||
if [ ! -f "$NODE_ZIP_FILE" ]; then
|
||||
./build.sh linux "$arch" $TAG
|
||||
else
|
||||
echo "use built node linux/$arch/v${NodeVersion}"
|
||||
fi
|
||||
./build.sh linux "$arch" $TAG
|
||||
done
|
||||
echo "=============================="
|
||||
cd - || exit
|
||||
@@ -94,6 +84,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 ..."
|
||||
@@ -102,13 +118,11 @@ function build() {
|
||||
|
||||
# copy files
|
||||
echo "copying ..."
|
||||
if [ ! -d "$DIST" ]; then
|
||||
mkdir "$DIST"
|
||||
mkdir "$DIST"/bin
|
||||
mkdir "$DIST"/configs
|
||||
mkdir "$DIST"/logs
|
||||
mkdir "$DIST"/data
|
||||
fi
|
||||
rm -rf "$DIST"
|
||||
mkdir -p "$DIST"/bin
|
||||
mkdir -p "$DIST"/configs
|
||||
mkdir -p "$DIST"/logs
|
||||
mkdir -p "$DIST"/data
|
||||
cp "$ROOT"/configs/api.template.yaml "$DIST"/configs/
|
||||
cp "$ROOT"/configs/db.template.yaml "$DIST"/configs/
|
||||
# 复制 EdgeCommon 的配置文件(如果存在)
|
||||
@@ -122,42 +136,6 @@ function build() {
|
||||
rm -f "$DIST"/deploy/.gitignore
|
||||
cp -R "$ROOT"/installers "$DIST"/
|
||||
|
||||
# copy fluent-bit templates and local packages from repo root
|
||||
FLUENT_ROOT="$ROOT/../../deploy/fluent-bit"
|
||||
FLUENT_DIST="$DIST/deploy/fluent-bit"
|
||||
if [ -d "$FLUENT_ROOT" ]; then
|
||||
rm -rf "$FLUENT_DIST"
|
||||
mkdir -p "$FLUENT_DIST"
|
||||
|
||||
FLUENT_FILES=(
|
||||
"fluent-bit.conf"
|
||||
"fluent-bit-dns.conf"
|
||||
"fluent-bit-https.conf"
|
||||
"fluent-bit-dns-https.conf"
|
||||
"fluent-bit-windows.conf"
|
||||
"fluent-bit-windows-https.conf"
|
||||
"parsers.conf"
|
||||
"clickhouse-upstream.conf"
|
||||
"clickhouse-upstream-windows.conf"
|
||||
"logrotate.conf"
|
||||
"README.md"
|
||||
)
|
||||
for file in "${FLUENT_FILES[@]}"; do
|
||||
if [ -f "$FLUENT_ROOT/$file" ]; then
|
||||
cp "$FLUENT_ROOT/$file" "$FLUENT_DIST/"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "$FLUENT_ROOT/packages" ]; then
|
||||
cp -R "$FLUENT_ROOT/packages" "$FLUENT_DIST/"
|
||||
fi
|
||||
|
||||
# remove local runtime artifacts if present
|
||||
rm -f "$FLUENT_DIST/.gitignore"
|
||||
rm -f "$FLUENT_DIST"/logs.db*
|
||||
rm -rf "$FLUENT_DIST/storage"
|
||||
fi
|
||||
|
||||
# building edge installer
|
||||
echo "building node installer ..."
|
||||
architects=("amd64")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
user: root
|
||||
password: 123456
|
||||
host: 127.0.0.1:3306
|
||||
host: 127.0.0.1:3308
|
||||
database: db_edge
|
||||
boolFields: [ "uamIsOn", "followPort", "requestHostExcludingPort", "autoRemoteStart", "autoInstallNftables", "enableIPLists", "detectAgents", "checkingPorts", "enableRecordHealthCheck", "offlineIsNotified", "http2Enabled", "http3Enabled", "enableHTTP2", "retry50X", "retry40X", "autoSystemTuning", "disableDefaultDB", "autoTrimDisks", "enableGlobalPages", "ignoreLocal", "ignoreSearchEngine" ]
|
||||
@@ -3,5 +3,11 @@
|
||||
# generate 'internal/setup/sql.json' file
|
||||
|
||||
CWD="$(dirname "$0")"
|
||||
SQL_JSON="${CWD}/../internal/setup/sql.json"
|
||||
|
||||
go run "${CWD}"/../cmd/sql-dump/main.go -dir="${CWD}"
|
||||
if [ "$1" = "--force" ] || [ ! -f "$SQL_JSON" ]; then
|
||||
rm -f "$SQL_JSON"
|
||||
go run "${CWD}"/../cmd/sql-dump/main.go -dir="${CWD}"
|
||||
else
|
||||
echo "sql.json exists, skip (use --force to regenerate)"
|
||||
fi
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
)
|
||||
|
||||
func main() {
|
||||
Tea.Env = "prod"
|
||||
|
||||
db, err := dbs.Default()
|
||||
if err != nil {
|
||||
fmt.Println("[ERROR]" + err.Error())
|
||||
|
||||
@@ -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{})")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
|
||||
470
EdgeAPI/internal/clickhouse/httpdns_access_logs_store.go
Normal file
470
EdgeAPI/internal/clickhouse/httpdns_access_logs_store.go
Normal file
@@ -0,0 +1,470 @@
|
||||
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
|
||||
AppIds []string
|
||||
Domain string
|
||||
Status string
|
||||
Keyword string
|
||||
Offset int64
|
||||
Size int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogHourlyStat struct {
|
||||
Hour string
|
||||
CountRequests int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogDailyStat struct {
|
||||
Day string
|
||||
CountRequests int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopAppStat struct {
|
||||
AppId string
|
||||
AppName string
|
||||
CountRequests int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopDomainStat struct {
|
||||
Domain string
|
||||
CountRequests int64
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopNodeStat struct {
|
||||
ClusterId uint32
|
||||
NodeId uint32
|
||||
CountRequests 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 (s *HTTPDNSAccessLogsStore) FindHourlyStats(ctx context.Context, hourFrom string, hourTo string) ([]*HTTPDNSAccessLogHourlyStat, error) {
|
||||
if !s.client.IsConfigured() {
|
||||
return nil, fmt.Errorf("clickhouse: not configured")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT formatDateTime(toDateTime(created_at), '%%Y%%m%%d%%H') AS hour, count() AS count_requests FROM %s WHERE formatDateTime(toDateTime(created_at), '%%Y%%m%%d%%H') BETWEEN '%s' AND '%s' GROUP BY hour ORDER BY hour ASC",
|
||||
s.tableName(),
|
||||
escapeString(hourFrom),
|
||||
escapeString(hourTo),
|
||||
)
|
||||
|
||||
rows := []map[string]interface{}{}
|
||||
if err := s.client.Query(ctx, query, &rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*HTTPDNSAccessLogHourlyStat, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogHourlyStat{
|
||||
Hour: toString(row["hour"]),
|
||||
CountRequests: toInt64(row["count_requests"]),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *HTTPDNSAccessLogsStore) FindDailyStats(ctx context.Context, dayFrom string, dayTo string) ([]*HTTPDNSAccessLogDailyStat, error) {
|
||||
if !s.client.IsConfigured() {
|
||||
return nil, fmt.Errorf("clickhouse: not configured")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT day, count() AS count_requests FROM %s WHERE day BETWEEN '%s' AND '%s' GROUP BY day ORDER BY day ASC",
|
||||
s.tableName(),
|
||||
escapeString(dayFrom),
|
||||
escapeString(dayTo),
|
||||
)
|
||||
|
||||
rows := []map[string]interface{}{}
|
||||
if err := s.client.Query(ctx, query, &rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*HTTPDNSAccessLogDailyStat, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogDailyStat{
|
||||
Day: toString(row["day"]),
|
||||
CountRequests: toInt64(row["count_requests"]),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *HTTPDNSAccessLogsStore) ListTopApps(ctx context.Context, dayFrom string, dayTo string, limit int64) ([]*HTTPDNSAccessLogTopAppStat, error) {
|
||||
if !s.client.IsConfigured() {
|
||||
return nil, fmt.Errorf("clickhouse: not configured")
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT app_id, max(app_name) AS app_name, count() AS count_requests FROM %s WHERE day BETWEEN '%s' AND '%s' GROUP BY app_id ORDER BY count_requests DESC LIMIT %d",
|
||||
s.tableName(),
|
||||
escapeString(dayFrom),
|
||||
escapeString(dayTo),
|
||||
limit,
|
||||
)
|
||||
|
||||
rows := []map[string]interface{}{}
|
||||
if err := s.client.Query(ctx, query, &rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*HTTPDNSAccessLogTopAppStat, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopAppStat{
|
||||
AppId: toString(row["app_id"]),
|
||||
AppName: toString(row["app_name"]),
|
||||
CountRequests: toInt64(row["count_requests"]),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *HTTPDNSAccessLogsStore) ListTopDomains(ctx context.Context, dayFrom string, dayTo string, limit int64) ([]*HTTPDNSAccessLogTopDomainStat, error) {
|
||||
if !s.client.IsConfigured() {
|
||||
return nil, fmt.Errorf("clickhouse: not configured")
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT domain, count() AS count_requests FROM %s WHERE day BETWEEN '%s' AND '%s' AND domain != '' GROUP BY domain ORDER BY count_requests DESC LIMIT %d",
|
||||
s.tableName(),
|
||||
escapeString(dayFrom),
|
||||
escapeString(dayTo),
|
||||
limit,
|
||||
)
|
||||
|
||||
rows := []map[string]interface{}{}
|
||||
if err := s.client.Query(ctx, query, &rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*HTTPDNSAccessLogTopDomainStat, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopDomainStat{
|
||||
Domain: toString(row["domain"]),
|
||||
CountRequests: toInt64(row["count_requests"]),
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (s *HTTPDNSAccessLogsStore) ListTopNodes(ctx context.Context, dayFrom string, dayTo string, limit int64) ([]*HTTPDNSAccessLogTopNodeStat, error) {
|
||||
if !s.client.IsConfigured() {
|
||||
return nil, fmt.Errorf("clickhouse: not configured")
|
||||
}
|
||||
if limit <= 0 {
|
||||
limit = 10
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(
|
||||
"SELECT min(cluster_id) AS cluster_id, node_id, count() AS count_requests FROM %s WHERE day BETWEEN '%s' AND '%s' GROUP BY node_id ORDER BY count_requests DESC LIMIT %d",
|
||||
s.tableName(),
|
||||
escapeString(dayFrom),
|
||||
escapeString(dayTo),
|
||||
limit,
|
||||
)
|
||||
|
||||
rows := []map[string]interface{}{}
|
||||
if err := s.client.Query(ctx, query, &rows); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result := make([]*HTTPDNSAccessLogTopNodeStat, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopNodeStat{
|
||||
ClusterId: uint32(toInt64(row["cluster_id"])),
|
||||
NodeId: uint32(toInt64(row["node_id"])),
|
||||
CountRequests: toInt64(row["count_requests"]),
|
||||
})
|
||||
}
|
||||
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)+"'")
|
||||
} else if len(f.AppIds) > 0 {
|
||||
validAppIds := make([]string, 0, len(f.AppIds))
|
||||
for _, appID := range f.AppIds {
|
||||
appID = strings.TrimSpace(appID)
|
||||
if len(appID) == 0 {
|
||||
continue
|
||||
}
|
||||
validAppIds = append(validAppIds, "'"+escapeString(appID)+"'")
|
||||
}
|
||||
if len(validAppIds) == 0 {
|
||||
conditions = append(conditions, "1 = 0")
|
||||
} else {
|
||||
conditions = append(conditions, "app_id IN ("+strings.Join(validAppIds, ",")+")")
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ type LogsIngestRow struct {
|
||||
RequestBody string
|
||||
ResponseHeaders string
|
||||
ResponseBody string
|
||||
Attrs string
|
||||
}
|
||||
|
||||
// ListFilter 列表查询条件(与 ListHTTPAccessLogsRequest 对齐)
|
||||
@@ -178,7 +179,7 @@ func (s *LogsIngestStore) List(ctx context.Context, f ListFilter) (rows []*LogsI
|
||||
|
||||
// 列表查询不 SELECT 大字段(request_headers / request_body / response_headers / response_body),
|
||||
// 避免每次翻页读取 GB 级数据。详情查看时通过 FindByTraceId 单独获取。
|
||||
query := fmt.Sprintf("SELECT timestamp, node_id, cluster_id, server_id, host, ip, method, path, status, bytes_in, bytes_out, cost_ms, ua, referer, log_type, trace_id, firewall_policy_id, firewall_rule_group_id, firewall_rule_set_id, firewall_rule_id FROM %s WHERE %s ORDER BY %s LIMIT %d",
|
||||
query := fmt.Sprintf("SELECT timestamp, node_id, cluster_id, server_id, host, ip, method, path, status, bytes_in, bytes_out, cost_ms, ua, referer, log_type, trace_id, firewall_policy_id, firewall_rule_group_id, firewall_rule_set_id, firewall_rule_id, attrs FROM %s WHERE %s ORDER BY %s LIMIT %d",
|
||||
table, where, orderBy, limit+1)
|
||||
|
||||
var rawRows []map[string]interface{}
|
||||
@@ -225,7 +226,7 @@ func (s *LogsIngestStore) FindByTraceId(ctx context.Context, traceId string) (*L
|
||||
}
|
||||
|
||||
table := quoteIdent("logs_ingest")
|
||||
query := fmt.Sprintf("SELECT timestamp, node_id, cluster_id, server_id, host, ip, method, path, status, bytes_in, bytes_out, cost_ms, ua, referer, log_type, trace_id, firewall_policy_id, firewall_rule_group_id, firewall_rule_set_id, firewall_rule_id, request_headers, request_body, response_headers, response_body FROM %s WHERE trace_id = '%s' LIMIT 1",
|
||||
query := fmt.Sprintf("SELECT timestamp, node_id, cluster_id, server_id, host, ip, method, path, status, bytes_in, bytes_out, cost_ms, ua, referer, log_type, trace_id, firewall_policy_id, firewall_rule_group_id, firewall_rule_set_id, firewall_rule_id, request_headers, request_body, response_headers, response_body, attrs FROM %s WHERE trace_id = '%s' LIMIT 1",
|
||||
table, escapeString(traceId))
|
||||
|
||||
var rawRows []map[string]interface{}
|
||||
@@ -333,6 +334,7 @@ func mapToLogsIngestRow(m map[string]interface{}) *LogsIngestRow {
|
||||
r.RequestBody = str("request_body")
|
||||
r.ResponseHeaders = str("response_headers")
|
||||
r.ResponseBody = str("response_body")
|
||||
r.Attrs = str("attrs")
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -351,8 +353,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),
|
||||
@@ -401,6 +403,14 @@ func RowToPB(r *LogsIngestRow) *pb.HTTPAccessLog {
|
||||
a.RequestBody = []byte(r.RequestBody)
|
||||
}
|
||||
|
||||
// 扩展属性(cache.status 等)
|
||||
if r.Attrs != "" {
|
||||
var attrs map[string]string
|
||||
if err := json.Unmarshal([]byte(r.Attrs), &attrs); err == nil && len(attrs) > 0 {
|
||||
a.Attrs = attrs
|
||||
}
|
||||
}
|
||||
|
||||
return a
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.7" //1.3.9
|
||||
Version = "1.5.1" //1.3.9
|
||||
|
||||
ProductName = "Edge API"
|
||||
ProcessName = "edge-api"
|
||||
@@ -17,6 +17,6 @@ const (
|
||||
|
||||
// 其他节点版本号,用来检测是否有需要升级的节点
|
||||
|
||||
NodeVersion = "1.4.7" //1.3.8.2
|
||||
NodeVersion = "1.5.1" //1.3.8.2
|
||||
)
|
||||
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
DNSNodeVersion = "1.4.7" //1.3.8.2
|
||||
UserNodeVersion = "1.4.7" //1.3.8.2
|
||||
ReportNodeVersion = "0.1.5"
|
||||
DNSNodeVersion = "1.5.1" //1.3.8.2
|
||||
UserNodeVersion = "1.5.1" //1.3.8.2
|
||||
ReportNodeVersion = "1.5.1"
|
||||
|
||||
DefaultMaxNodes int32 = 50
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestDB_Env(t *testing.T) {
|
||||
|
||||
func TestDB_Instance(t *testing.T) {
|
||||
for i := 0; i < 10; i++ {
|
||||
db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s")
|
||||
db, err := sql.Open("mysql", "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s")
|
||||
if err != nil {
|
||||
t.Fatal(i, "open:", err)
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
func TestNewHTTPAccessLogManager(t *testing.T) {
|
||||
var config = &dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_log?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_log?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
Connections: struct {
|
||||
Pool int `yaml:"pool"`
|
||||
@@ -45,7 +45,7 @@ func TestNewHTTPAccessLogManager(t *testing.T) {
|
||||
func TestHTTPAccessLogManager_FindTableNames(t *testing.T) {
|
||||
var config = &dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_log?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_log?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
Connections: struct {
|
||||
Pool int `yaml:"pool"`
|
||||
@@ -84,7 +84,7 @@ func TestHTTPAccessLogManager_FindTableNames(t *testing.T) {
|
||||
func TestHTTPAccessLogManager_FindTables(t *testing.T) {
|
||||
var config = &dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_log?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_log?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
Connections: struct {
|
||||
Pool int `yaml:"pool"`
|
||||
@@ -123,7 +123,7 @@ func TestHTTPAccessLogManager_FindTables(t *testing.T) {
|
||||
func TestHTTPAccessLogManager_FindLastTable(t *testing.T) {
|
||||
var config = &dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_log?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_log?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
Connections: struct {
|
||||
Pool int `yaml:"pool"`
|
||||
@@ -162,7 +162,7 @@ func TestHTTPAccessLogManager_FindLastTable(t *testing.T) {
|
||||
func TestHTTPAccessLogManager_FindPartitionTable(t *testing.T) {
|
||||
var config = &dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_log?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_log?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
Connections: struct {
|
||||
Pool int `yaml:"pool"`
|
||||
|
||||
134
EdgeAPI/internal/db/models/httpdns_access_log_dao.go
Normal file
134
EdgeAPI/internal/db/models/httpdns_access_log_dao.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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 {
|
||||
return this.BuildListQueryWithAppIds(tx, day, clusterId, nodeId, appId, nil, domain, status, keyword)
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) BuildListQueryWithAppIds(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, appIds []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(appIds) > 0 {
|
||||
validAppIds := make([]string, 0, len(appIds))
|
||||
for _, value := range appIds {
|
||||
value = strings.TrimSpace(value)
|
||||
if len(value) == 0 {
|
||||
continue
|
||||
}
|
||||
validAppIds = append(validAppIds, value)
|
||||
}
|
||||
if len(validAppIds) == 0 {
|
||||
query = query.Where("1 = 0")
|
||||
} else {
|
||||
query = query.Attr("appId", validAppIds)
|
||||
}
|
||||
}
|
||||
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.BuildListQueryWithAppIds(tx, day, clusterId, nodeId, appId, nil, 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.BuildListQueryWithAppIds(tx, day, clusterId, nodeId, appId, nil, domain, status, keyword).
|
||||
Offset(offset).
|
||||
Limit(size).
|
||||
Slice(&result).
|
||||
FindAll()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) CountLogsWithAppIds(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, appIds []string, domain string, status string, keyword string) (int64, error) {
|
||||
return this.BuildListQueryWithAppIds(tx, day, clusterId, nodeId, appId, appIds, domain, status, keyword).Count()
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) ListLogsWithAppIds(tx *dbs.Tx, day string, clusterId int64, nodeId int64, appId string, appIds []string, domain string, status string, keyword string, offset int64, size int64) (result []*HTTPDNSAccessLog, err error) {
|
||||
_, err = this.BuildListQueryWithAppIds(tx, day, clusterId, nodeId, appId, appIds, 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
|
||||
}
|
||||
138
EdgeAPI/internal/db/models/httpdns_access_log_dao_stat.go
Normal file
138
EdgeAPI/internal/db/models/httpdns_access_log_dao_stat.go
Normal file
@@ -0,0 +1,138 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
)
|
||||
|
||||
type HTTPDNSAccessLogHourlyStat struct {
|
||||
Hour string `field:"hour"`
|
||||
CountRequests int64 `field:"countRequests"`
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogDailyStat struct {
|
||||
Day string `field:"day"`
|
||||
CountRequests int64 `field:"countRequests"`
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopAppStat struct {
|
||||
AppId string `field:"appId"`
|
||||
AppName string `field:"appName"`
|
||||
CountRequests int64 `field:"countRequests"`
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopDomainStat struct {
|
||||
Domain string `field:"domain"`
|
||||
CountRequests int64 `field:"countRequests"`
|
||||
}
|
||||
|
||||
type HTTPDNSAccessLogTopNodeStat struct {
|
||||
ClusterId uint32 `field:"clusterId"`
|
||||
NodeId uint32 `field:"nodeId"`
|
||||
CountRequests int64 `field:"countRequests"`
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) FindHourlyStats(tx *dbs.Tx, hourFrom string, hourTo string) (result []*HTTPDNSAccessLogHourlyStat, err error) {
|
||||
rows, _, err := this.Query(tx).
|
||||
Result("CONCAT(day, LPAD(HOUR(FROM_UNIXTIME(createdAt)),2,'0')) AS hour", "COUNT(*) AS countRequests").
|
||||
Where("CONCAT(day, LPAD(HOUR(FROM_UNIXTIME(createdAt)),2,'0')) BETWEEN :hourFrom AND :hourTo").
|
||||
Param("hourFrom", hourFrom).
|
||||
Param("hourTo", hourTo).
|
||||
Group("hour").
|
||||
FindOnes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogHourlyStat{
|
||||
Hour: row.GetString("hour"),
|
||||
CountRequests: row.GetInt64("countRequests"),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) FindDailyStats(tx *dbs.Tx, dayFrom string, dayTo string) (result []*HTTPDNSAccessLogDailyStat, err error) {
|
||||
rows, _, err := this.Query(tx).
|
||||
Result("day", "COUNT(*) AS countRequests").
|
||||
Between("day", dayFrom, dayTo).
|
||||
Group("day").
|
||||
FindOnes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogDailyStat{
|
||||
Day: row.GetString("day"),
|
||||
CountRequests: row.GetInt64("countRequests"),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) ListTopApps(tx *dbs.Tx, dayFrom string, dayTo string, limit int64) (result []*HTTPDNSAccessLogTopAppStat, err error) {
|
||||
rows, _, err := this.Query(tx).
|
||||
Result("appId", "MAX(appName) AS appName", "COUNT(*) AS countRequests").
|
||||
Between("day", dayFrom, dayTo).
|
||||
Group("appId").
|
||||
Desc("countRequests").
|
||||
Limit(limit).
|
||||
FindOnes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopAppStat{
|
||||
AppId: row.GetString("appId"),
|
||||
AppName: row.GetString("appName"),
|
||||
CountRequests: row.GetInt64("countRequests"),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) ListTopDomains(tx *dbs.Tx, dayFrom string, dayTo string, limit int64) (result []*HTTPDNSAccessLogTopDomainStat, err error) {
|
||||
rows, _, err := this.Query(tx).
|
||||
Result("domain", "COUNT(*) AS countRequests").
|
||||
Between("day", dayFrom, dayTo).
|
||||
Where("domain != ''").
|
||||
Group("domain").
|
||||
Desc("countRequests").
|
||||
Limit(limit).
|
||||
FindOnes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopDomainStat{
|
||||
Domain: row.GetString("domain"),
|
||||
CountRequests: row.GetInt64("countRequests"),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAccessLogDAO) ListTopNodes(tx *dbs.Tx, dayFrom string, dayTo string, limit int64) (result []*HTTPDNSAccessLogTopNodeStat, err error) {
|
||||
rows, _, err := this.Query(tx).
|
||||
Result("MIN(clusterId) AS clusterId", "nodeId", "COUNT(*) AS countRequests").
|
||||
Between("day", dayFrom, dayTo).
|
||||
Group("nodeId").
|
||||
Desc("countRequests").
|
||||
Limit(limit).
|
||||
FindOnes()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, row := range rows {
|
||||
result = append(result, &HTTPDNSAccessLogTopNodeStat{
|
||||
ClusterId: row.GetUint32("clusterId"),
|
||||
NodeId: row.GetUint32("nodeId"),
|
||||
CountRequests: row.GetInt64("countRequests"),
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
52
EdgeAPI/internal/db/models/httpdns_access_log_model.go
Normal file
52
EdgeAPI/internal/db/models/httpdns_access_log_model.go
Normal 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{}
|
||||
}
|
||||
239
EdgeAPI/internal/db/models/httpdns_app_dao.go
Normal file
239
EdgeAPI/internal/db/models/httpdns_app_dao.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
_ "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, clusterIdsJSON []byte, isOn bool, userId int64) (int64, error) {
|
||||
var op = NewHTTPDNSAppOperator()
|
||||
op.Name = name
|
||||
op.AppId = appId
|
||||
|
||||
if len(clusterIdsJSON) > 0 {
|
||||
op.ClusterIdsJSON = string(clusterIdsJSON)
|
||||
} else {
|
||||
op.ClusterIdsJSON = "[]"
|
||||
}
|
||||
|
||||
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, clusterIdsJSON []byte, isOn bool, userId int64) error {
|
||||
var op = NewHTTPDNSAppOperator()
|
||||
op.Id = appDbId
|
||||
op.Name = name
|
||||
|
||||
if len(clusterIdsJSON) > 0 {
|
||||
op.ClusterIdsJSON = string(clusterIdsJSON)
|
||||
} else {
|
||||
op.ClusterIdsJSON = "[]"
|
||||
}
|
||||
|
||||
op.IsOn = isOn
|
||||
op.UserId = userId
|
||||
op.UpdatedAt = time.Now().Unix()
|
||||
return this.Save(tx, op)
|
||||
}
|
||||
|
||||
// ReadAppClusterIds reads cluster IDs from ClusterIdsJSON.
|
||||
func (this *HTTPDNSAppDAO) ReadAppClusterIds(app *HTTPDNSApp) []int64 {
|
||||
if app == nil {
|
||||
return nil
|
||||
}
|
||||
if len(app.ClusterIdsJSON) > 0 {
|
||||
var ids []int64
|
||||
if err := json.Unmarshal([]byte(app.ClusterIdsJSON), &ids); err == nil && len(ids) > 0 {
|
||||
return ids
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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) FindEnabledAppWithUser(tx *dbs.Tx, appDbId int64, userId int64) (*HTTPDNSApp, error) {
|
||||
one, err := this.Query(tx).
|
||||
Pk(appDbId).
|
||||
State(HTTPDNSAppStateEnabled).
|
||||
Attr("userId", userId).
|
||||
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) FindEnabledAppWithAppIdAndUser(tx *dbs.Tx, appId string, userId int64) (*HTTPDNSApp, error) {
|
||||
one, err := this.Query(tx).
|
||||
State(HTTPDNSAppStateEnabled).
|
||||
Attr("appId", appId).
|
||||
Attr("userId", userId).
|
||||
Find()
|
||||
if one == nil {
|
||||
return nil, err
|
||||
}
|
||||
return one.(*HTTPDNSApp), nil
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAppDAO) FindLatestEnabledAppWithNameAndUser(tx *dbs.Tx, name string, userId int64) (*HTTPDNSApp, error) {
|
||||
one, err := this.Query(tx).
|
||||
State(HTTPDNSAppStateEnabled).
|
||||
Attr("name", name).
|
||||
Attr("userId", userId).
|
||||
DescPk().
|
||||
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) ListEnabledAppsWithUser(tx *dbs.Tx, userId int64, offset int64, size int64, keyword string) (result []*HTTPDNSApp, err error) {
|
||||
query := this.Query(tx).
|
||||
State(HTTPDNSAppStateEnabled).
|
||||
Attr("userId", userId).
|
||||
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) CountEnabledAppsWithUser(tx *dbs.Tx, userId int64, keyword string) (int64, error) {
|
||||
query := this.Query(tx).State(HTTPDNSAppStateEnabled).Attr("userId", userId)
|
||||
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
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAppDAO) FindAllEnabledAppsWithUser(tx *dbs.Tx, userId int64) (result []*HTTPDNSApp, err error) {
|
||||
_, err = this.Query(tx).
|
||||
State(HTTPDNSAppStateEnabled).
|
||||
Attr("userId", userId).
|
||||
AscPk().
|
||||
Slice(&result).
|
||||
FindAll()
|
||||
return
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAppDAO) ListEnabledAppIdsWithUser(tx *dbs.Tx, userId int64) (result []string, err error) {
|
||||
apps, err := this.FindAllEnabledAppsWithUser(tx, userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = make([]string, 0, len(apps))
|
||||
for _, app := range apps {
|
||||
if app == nil || len(app.AppId) == 0 {
|
||||
continue
|
||||
}
|
||||
result = append(result, app.AppId)
|
||||
}
|
||||
return
|
||||
}
|
||||
34
EdgeAPI/internal/db/models/httpdns_app_model.go
Normal file
34
EdgeAPI/internal/db/models/httpdns_app_model.go
Normal file
@@ -0,0 +1,34 @@
|
||||
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
|
||||
ClusterIdsJSON string `field:"clusterIdsJSON"` // cluster ids json
|
||||
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
|
||||
ClusterIdsJSON any // cluster ids json
|
||||
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{}
|
||||
}
|
||||
|
||||
146
EdgeAPI/internal/db/models/httpdns_app_secret_dao.go
Normal file
146
EdgeAPI/internal/db/models/httpdns_app_secret_dao.go
Normal file
@@ -0,0 +1,146 @@
|
||||
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())
|
||||
|
||||
// 兼容历史数据:如果已存在(可能是停用状态)则直接恢复并更新,避免 UNIQUE(appId) 冲突
|
||||
old, err := this.Query(tx).
|
||||
Attr("appId", appDbId).
|
||||
Find()
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
if old != nil {
|
||||
oldSecret := old.(*HTTPDNSAppSecret)
|
||||
_, err = this.Query(tx).
|
||||
Pk(oldSecret.Id).
|
||||
Set("signEnabled", signEnabled).
|
||||
Set("signSecret", signSecret).
|
||||
Set("signUpdatedAt", now).
|
||||
Set("updatedAt", now).
|
||||
Set("state", HTTPDNSAppSecretStateEnabled).
|
||||
Update()
|
||||
return signSecret, now, err
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
26
EdgeAPI/internal/db/models/httpdns_app_secret_model.go
Normal file
26
EdgeAPI/internal/db/models/httpdns_app_secret_model.go
Normal 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{}
|
||||
}
|
||||
191
EdgeAPI/internal/db/models/httpdns_cluster_dao.go
Normal file
191
EdgeAPI/internal/db/models/httpdns_cluster_dao.go
Normal file
@@ -0,0 +1,191 @@
|
||||
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, autoRemoteStart bool, accessLogIsOn bool, timeZone string) (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.AutoRemoteStart = autoRemoteStart
|
||||
op.AccessLogIsOn = accessLogIsOn
|
||||
op.TimeZone = timeZone
|
||||
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, autoRemoteStart bool, accessLogIsOn bool, timeZone string) 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.AutoRemoteStart = autoRemoteStart
|
||||
op.AccessLogIsOn = accessLogIsOn
|
||||
op.TimeZone = timeZone
|
||||
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) FindDefaultPrimaryClusterId(tx *dbs.Tx) (int64, error) {
|
||||
col, err := this.Query(tx).
|
||||
State(HTTPDNSClusterStateEnabled).
|
||||
Attr("isDefault", true).
|
||||
Result("id").
|
||||
AscPk().
|
||||
FindCol(nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if col == nil {
|
||||
return 0, nil
|
||||
}
|
||||
return types.Int64(col), nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
44
EdgeAPI/internal/db/models/httpdns_cluster_model.go
Normal file
44
EdgeAPI/internal/db/models/httpdns_cluster_model.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package models
|
||||
|
||||
import "github.com/iwind/TeaGo/dbs"
|
||||
|
||||
// HTTPDNSCluster HTTPDNS集群
|
||||
type HTTPDNSCluster struct {
|
||||
Id uint32 `field:"id"` // ID
|
||||
Name string `field:"name"` // 集群名称
|
||||
IsOn uint8 `field:"isOn"` // 是否启用(tinyint unsigned)
|
||||
IsDefault uint8 `field:"isDefault"` // 默认集群(tinyint unsigned)
|
||||
ServiceDomain string `field:"serviceDomain"` // 服务域名
|
||||
DefaultTTL int32 `field:"defaultTTL"` // 默认TTL
|
||||
FallbackTimeoutMs int32 `field:"fallbackTimeoutMs"` // 降级超时
|
||||
InstallDir string `field:"installDir"` // 安装目录
|
||||
TLSPolicy dbs.JSON `field:"tlsPolicy"` // TLS策略
|
||||
AutoRemoteStart uint8 `field:"autoRemoteStart"` // 自动远程启动(tinyint unsigned)
|
||||
AccessLogIsOn uint8 `field:"accessLogIsOn"` // 访问日志是否开启(tinyint unsigned)
|
||||
TimeZone string `field:"timeZone"` // 时区
|
||||
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策略
|
||||
AutoRemoteStart any // 自动远程启动
|
||||
AccessLogIsOn any // 访问日志是否开启
|
||||
TimeZone any // 时区
|
||||
CreatedAt any // 创建时间
|
||||
UpdatedAt any // 修改时间
|
||||
State any // 记录状态
|
||||
}
|
||||
|
||||
func NewHTTPDNSClusterOperator() *HTTPDNSClusterOperator {
|
||||
return &HTTPDNSClusterOperator{}
|
||||
}
|
||||
143
EdgeAPI/internal/db/models/httpdns_custom_rule_dao.go
Normal file
143
EdgeAPI/internal/db/models/httpdns_custom_rule_dao.go
Normal 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()
|
||||
}
|
||||
42
EdgeAPI/internal/db/models/httpdns_custom_rule_model.go
Normal file
42
EdgeAPI/internal/db/models/httpdns_custom_rule_model.go
Normal 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{}
|
||||
}
|
||||
69
EdgeAPI/internal/db/models/httpdns_custom_rule_record_dao.go
Normal file
69
EdgeAPI/internal/db/models/httpdns_custom_rule_record_dao.go
Normal 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
|
||||
}
|
||||
@@ -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{}
|
||||
}
|
||||
123
EdgeAPI/internal/db/models/httpdns_domain_dao.go
Normal file
123
EdgeAPI/internal/db/models/httpdns_domain_dao.go
Normal file
@@ -0,0 +1,123 @@
|
||||
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
|
||||
}
|
||||
|
||||
func (this *HTTPDNSDomainDAO) CountEnabledDomains(tx *dbs.Tx, appDbId int64) (int64, error) {
|
||||
query := this.Query(tx).State(HTTPDNSDomainStateEnabled)
|
||||
if appDbId > 0 {
|
||||
query = query.Attr("appId", appDbId)
|
||||
}
|
||||
return query.Count()
|
||||
}
|
||||
26
EdgeAPI/internal/db/models/httpdns_domain_model.go
Normal file
26
EdgeAPI/internal/db/models/httpdns_domain_model.go
Normal 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{}
|
||||
}
|
||||
327
EdgeAPI/internal/db/models/httpdns_node_dao.go
Normal file
327
EdgeAPI/internal/db/models/httpdns_node_dao.go
Normal file
@@ -0,0 +1,327 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"github.com/iwind/TeaGo/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 鏌ヨ鑺傜偣鎵€灞為泦缇D
|
||||
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
|
||||
}
|
||||
|
||||
// FindAllInactiveNodesWithClusterId 取得一个集群离线的HTTPDNS节点
|
||||
func (this *HTTPDNSNodeDAO) FindAllInactiveNodesWithClusterId(tx *dbs.Tx, clusterId int64) (result []*HTTPDNSNode, err error) {
|
||||
_, err = this.Query(tx).
|
||||
State(HTTPDNSNodeStateEnabled).
|
||||
Attr("clusterId", clusterId).
|
||||
Attr("isOn", true). // 只监控启用的节点
|
||||
Attr("isInstalled", true). // 只监控已经安装的节点
|
||||
Attr("isActive", false). // 当前处于离线状态
|
||||
Result("id", "name").
|
||||
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
|
||||
}
|
||||
|
||||
// CountAllLowerVersionNodesWithClusterId 璁$畻鍗曚釜闆嗙兢涓墍鏈変綆浜庢煇涓増鏈殑鑺傜偣鏁伴噺
|
||||
func (this *HTTPDNSNodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterId int64, os string, arch string, version string) (int64, error) {
|
||||
return this.Query(tx).
|
||||
State(HTTPDNSNodeStateEnabled).
|
||||
Attr("clusterId", clusterId).
|
||||
Attr("isOn", true).
|
||||
Attr("isUp", true).
|
||||
Attr("isActive", true).
|
||||
Where("status IS NOT NULL").
|
||||
Where("JSON_EXTRACT(status, '$.os')=:os").
|
||||
Where("JSON_EXTRACT(status, '$.arch')=:arch").
|
||||
Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)").
|
||||
Param("os", os).
|
||||
Param("arch", arch).
|
||||
Param("version", utils.VersionToLong(version)).
|
||||
Count()
|
||||
}
|
||||
|
||||
// FindAllLowerVersionNodesWithClusterId 鏌ユ壘鍗曚釜闆嗙兢涓墍鏈変綆浜庢煇涓増鏈殑鑺傜偣
|
||||
func (this *HTTPDNSNodeDAO) FindAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterId int64, os string, arch string, version string) (result []*HTTPDNSNode, err error) {
|
||||
_, err = this.Query(tx).
|
||||
State(HTTPDNSNodeStateEnabled).
|
||||
Attr("clusterId", clusterId).
|
||||
Attr("isOn", true).
|
||||
Attr("isUp", true).
|
||||
Attr("isActive", true).
|
||||
Where("status IS NOT NULL").
|
||||
Where("JSON_EXTRACT(status, '$.os')=:os").
|
||||
Where("JSON_EXTRACT(status, '$.arch')=:arch").
|
||||
Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)").
|
||||
Param("os", os).
|
||||
Param("arch", arch).
|
||||
Param("version", utils.VersionToLong(version)).
|
||||
DescPk().
|
||||
Slice(&result).
|
||||
FindAll()
|
||||
return
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
44
EdgeAPI/internal/db/models/httpdns_node_model.go
Normal file
44
EdgeAPI/internal/db/models/httpdns_node_model.go
Normal 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{}
|
||||
}
|
||||
108
EdgeAPI/internal/db/models/httpdns_runtime_log_dao.go
Normal file
108
EdgeAPI/internal/db/models/httpdns_runtime_log_dao.go
Normal 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
|
||||
}
|
||||
34
EdgeAPI/internal/db/models/httpdns_runtime_log_model.go
Normal file
34
EdgeAPI/internal/db/models/httpdns_runtime_log_model.go
Normal 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{}
|
||||
}
|
||||
@@ -1521,6 +1521,8 @@ func (this *NodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterI
|
||||
return this.Query(tx).
|
||||
State(NodeStateEnabled).
|
||||
Attr("isOn", true).
|
||||
Attr("isUp", true).
|
||||
Attr("isActive", true).
|
||||
Attr("clusterId", clusterId).
|
||||
Where("status IS NOT NULL").
|
||||
Where("JSON_EXTRACT(status, '$.os')=:os").
|
||||
@@ -1536,6 +1538,9 @@ func (this *NodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterI
|
||||
func (this *NodeDAO) FindAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterId int64, os string, arch string, version string) (result []*Node, err error) {
|
||||
_, err = this.Query(tx).
|
||||
State(NodeStateEnabled).
|
||||
Attr("isOn", true).
|
||||
Attr("isUp", true).
|
||||
Attr("isActive", true).
|
||||
Attr("clusterId", clusterId).
|
||||
Where("status IS NOT NULL").
|
||||
Where("JSON_EXTRACT(status, '$.os')=:os").
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
47
EdgeAPI/internal/db/models/node_task_dao_httpdns.go
Normal file
47
EdgeAPI/internal/db/models/node_task_dao_httpdns.go
Normal 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
|
||||
}
|
||||
@@ -221,6 +221,41 @@ func (this *NodeValueDAO) ListValuesForNSNodes(tx *dbs.Tx, item string, key stri
|
||||
return
|
||||
}
|
||||
|
||||
// ListValuesForHTTPDNSNodes 列出HTTPDNS节点相关的平均数据
|
||||
func (this *NodeValueDAO) ListValuesForHTTPDNSNodes(tx *dbs.Tx, item string, key string, timeRange nodeconfigs.NodeValueRange) (result []*NodeValue, err error) {
|
||||
query := this.Query(tx).
|
||||
Attr("role", "httpdns").
|
||||
Attr("item", item).
|
||||
Result("AVG(JSON_EXTRACT(value, '$." + key + "')) AS value, MIN(createdAt) AS createdAt")
|
||||
|
||||
switch timeRange {
|
||||
// TODO 支持更多的时间范围
|
||||
case nodeconfigs.NodeValueRangeMinute:
|
||||
fromMinute := timeutil.FormatTime("YmdHi", time.Now().Unix()-3600) // 一个小时之前的
|
||||
query.Gte("minute", fromMinute)
|
||||
query.Result("minute")
|
||||
query.Group("minute")
|
||||
default:
|
||||
err = errors.New("invalid 'range' value: '" + timeRange + "'")
|
||||
return
|
||||
}
|
||||
|
||||
_, err = query.Slice(&result).
|
||||
FindAll()
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, nodeValue := range result {
|
||||
nodeValue.Value, _ = json.Marshal(maps.Map{
|
||||
key: types.Float32(string(nodeValue.Value)),
|
||||
})
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// SumAllNodeValues 计算所有节点的某项参数值
|
||||
func (this *NodeValueDAO) SumAllNodeValues(tx *dbs.Tx, role string, item nodeconfigs.NodeValueItem, param string, duration int32, durationUnit nodeconfigs.NodeValueDurationUnit) (total float64, avg float64, max float64, err error) {
|
||||
if duration <= 0 {
|
||||
|
||||
@@ -94,6 +94,8 @@ func (this *NSNodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, cluste
|
||||
State(NSNodeStateEnabled).
|
||||
Attr("clusterId", clusterId).
|
||||
Attr("isOn", true).
|
||||
Attr("isUp", true).
|
||||
Attr("isActive", true).
|
||||
Where("status IS NOT NULL").
|
||||
Where("JSON_EXTRACT(status, '$.os')=:os").
|
||||
Where("JSON_EXTRACT(status, '$.arch')=:arch").
|
||||
@@ -104,6 +106,27 @@ func (this *NSNodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, cluste
|
||||
Count()
|
||||
}
|
||||
|
||||
// FindAllLowerVersionNodesWithClusterId 查找单个集群中所有低于某个版本的节点
|
||||
func (this *NSNodeDAO) FindAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterId int64, os string, arch string, version string) (result []*NSNode, err error) {
|
||||
_, err = this.Query(tx).
|
||||
State(NSNodeStateEnabled).
|
||||
Attr("clusterId", clusterId).
|
||||
Attr("isOn", true).
|
||||
Attr("isUp", true).
|
||||
Attr("isActive", true).
|
||||
Where("status IS NOT NULL").
|
||||
Where("JSON_EXTRACT(status, '$.os')=:os").
|
||||
Where("JSON_EXTRACT(status, '$.arch')=:arch").
|
||||
Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)").
|
||||
Param("os", os).
|
||||
Param("arch", arch).
|
||||
Param("version", utils.VersionToLong(version)).
|
||||
DescPk().
|
||||
Slice(&result).
|
||||
FindAll()
|
||||
return
|
||||
}
|
||||
|
||||
// FindEnabledNodeIdWithUniqueId 根据唯一ID获取节点ID
|
||||
func (this *NSNodeDAO) FindEnabledNodeIdWithUniqueId(tx *dbs.Tx, uniqueId string) (int64, error) {
|
||||
return this.Query(tx).
|
||||
|
||||
@@ -209,6 +209,8 @@ func (this *NSNodeDAO) CountAllLowerVersionNodesWithClusterId(tx *dbs.Tx, cluste
|
||||
return this.Query(tx).
|
||||
State(NSNodeStateEnabled).
|
||||
Attr("isOn", true).
|
||||
Attr("isUp", true).
|
||||
Attr("isActive", true).
|
||||
Attr("clusterId", clusterId).
|
||||
Where("status IS NOT NULL").
|
||||
Where("JSON_EXTRACT(status, '$.os')=:os").
|
||||
@@ -412,6 +414,27 @@ func (this *NSNodeDAO) CountAllLowerVersionNodes(tx *dbs.Tx, version string) (in
|
||||
Count()
|
||||
}
|
||||
|
||||
// FindAllLowerVersionNodesWithClusterId 查找单个集群中所有低于某个版本的节点
|
||||
func (this *NSNodeDAO) FindAllLowerVersionNodesWithClusterId(tx *dbs.Tx, clusterId int64, os string, arch string, version string) (result []*NSNode, err error) {
|
||||
_, err = this.Query(tx).
|
||||
State(NSNodeStateEnabled).
|
||||
Attr("clusterId", clusterId).
|
||||
Attr("isOn", true).
|
||||
Attr("isUp", true).
|
||||
Attr("isActive", true).
|
||||
Where("status IS NOT NULL").
|
||||
Where("JSON_EXTRACT(status, '$.os')=:os").
|
||||
Where("JSON_EXTRACT(status, '$.arch')=:arch").
|
||||
Where("(JSON_EXTRACT(status, '$.buildVersionCode') IS NULL OR JSON_EXTRACT(status, '$.buildVersionCode')<:version)").
|
||||
Param("os", os).
|
||||
Param("arch", arch).
|
||||
Param("version", utils.VersionToLong(version)).
|
||||
DescPk().
|
||||
Slice(&result).
|
||||
FindAll()
|
||||
return
|
||||
}
|
||||
|
||||
// ComposeNodeConfig 组合节点配置
|
||||
func (this *NSNodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64) (*dnsconfigs.NSNodeConfig, error) {
|
||||
if nodeId <= 0 {
|
||||
|
||||
@@ -246,7 +246,7 @@ func (this *UserDAO) CreateUser(tx *dbs.Tx, username string,
|
||||
}
|
||||
|
||||
// UpdateUser 修改用户
|
||||
func (this *UserDAO) UpdateUser(tx *dbs.Tx, userId int64, username string, password string, fullname string, mobile string, tel string, email string, remark string, isOn bool, nodeClusterId int64, bandwidthAlgo systemconfigs.BandwidthAlgo) error {
|
||||
func (this *UserDAO) UpdateUser(tx *dbs.Tx, userId int64, username string, password string, fullname string, mobile string, tel string, email string, remark string, isOn bool, nodeClusterId int64, bandwidthAlgo systemconfigs.BandwidthAlgo, httpdnsClusterIdsJSON []byte) error {
|
||||
if userId <= 0 {
|
||||
return errors.New("invalid userId")
|
||||
}
|
||||
@@ -265,6 +265,11 @@ func (this *UserDAO) UpdateUser(tx *dbs.Tx, userId int64, username string, passw
|
||||
op.ClusterId = nodeClusterId
|
||||
op.BandwidthAlgo = bandwidthAlgo
|
||||
op.IsOn = isOn
|
||||
if len(httpdnsClusterIdsJSON) > 0 {
|
||||
op.HttpdnsClusterIds = string(httpdnsClusterIdsJSON)
|
||||
} else {
|
||||
op.HttpdnsClusterIds = "[]"
|
||||
}
|
||||
err := this.Save(tx, op)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -466,6 +471,21 @@ func (this *UserDAO) FindUserClusterId(tx *dbs.Tx, userId int64) (int64, error)
|
||||
FindInt64Col(0)
|
||||
}
|
||||
|
||||
// UpdateUserHttpdnsClusterIds 更新用户的HTTPDNS关联集群ID列表
|
||||
func (this *UserDAO) UpdateUserHttpdnsClusterIds(tx *dbs.Tx, userId int64, httpdnsClusterIdsJSON []byte) error {
|
||||
if userId <= 0 {
|
||||
return errors.New("invalid userId")
|
||||
}
|
||||
if len(httpdnsClusterIdsJSON) == 0 {
|
||||
httpdnsClusterIdsJSON = []byte("[]")
|
||||
}
|
||||
_, err := this.Query(tx).
|
||||
Pk(userId).
|
||||
Set("httpdnsClusterIds", httpdnsClusterIdsJSON).
|
||||
Update()
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateUserFeatures 更新单个用户Features
|
||||
func (this *UserDAO) UpdateUserFeatures(tx *dbs.Tx, userId int64, featuresJSON []byte) error {
|
||||
if userId <= 0 {
|
||||
|
||||
@@ -37,6 +37,7 @@ const (
|
||||
UserField_BandwidthAlgo dbs.FieldName = "bandwidthAlgo" // 带宽算法
|
||||
UserField_BandwidthModifier dbs.FieldName = "bandwidthModifier" // 带宽修正值
|
||||
UserField_Lang dbs.FieldName = "lang" // 语言代号
|
||||
UserField_HttpdnsClusterIds dbs.FieldName = "httpdnsClusterIds" // HTTPDNS关联集群ID列表
|
||||
)
|
||||
|
||||
// User 用户
|
||||
@@ -75,6 +76,7 @@ type User struct {
|
||||
BandwidthAlgo string `field:"bandwidthAlgo"` // 带宽算法
|
||||
BandwidthModifier float64 `field:"bandwidthModifier"` // 带宽修正值
|
||||
Lang string `field:"lang"` // 语言代号
|
||||
HttpdnsClusterIds dbs.JSON `field:"httpdnsClusterIds"` // HTTPDNS关联集群ID列表
|
||||
}
|
||||
|
||||
type UserOperator struct {
|
||||
@@ -112,6 +114,7 @@ type UserOperator struct {
|
||||
BandwidthAlgo any // 带宽算法
|
||||
BandwidthModifier any // 带宽修正值
|
||||
Lang any // 语言代号
|
||||
HttpdnsClusterIds any // HTTPDNS关联集群ID列表
|
||||
}
|
||||
|
||||
func NewUserOperator() *UserOperator {
|
||||
|
||||
@@ -19,7 +19,7 @@ func TestIsLocalAddr(t *testing.T) {
|
||||
a.IsTrue(dbutils.IsLocalAddr("127.0.0.1"))
|
||||
a.IsTrue(dbutils.IsLocalAddr("localhost"))
|
||||
a.IsTrue(dbutils.IsLocalAddr("::1"))
|
||||
a.IsTrue(dbutils.IsLocalAddr("127.0.0.1:3306"))
|
||||
a.IsTrue(dbutils.IsLocalAddr("127.0.0.1:3308"))
|
||||
a.IsFalse(dbutils.IsLocalAddr("192.168.2.200"))
|
||||
a.IsFalse(dbutils.IsLocalAddr("192.168.2.200:3306"))
|
||||
}
|
||||
|
||||
@@ -15,8 +15,9 @@ var SharedDeployManager = NewDeployManager()
|
||||
type DeployManager struct {
|
||||
dir string
|
||||
|
||||
nodeFiles []*DeployFile
|
||||
nsNodeFiles []*DeployFile
|
||||
nodeFiles []*DeployFile
|
||||
nsNodeFiles []*DeployFile
|
||||
httpdnsNodeFiles []*DeployFile
|
||||
|
||||
locker sync.Mutex
|
||||
}
|
||||
@@ -28,6 +29,7 @@ func NewDeployManager() *DeployManager {
|
||||
}
|
||||
manager.LoadNodeFiles()
|
||||
manager.LoadNSNodeFiles()
|
||||
manager.LoadHTTPDNSNodeFiles()
|
||||
return manager
|
||||
}
|
||||
|
||||
@@ -141,6 +143,76 @@ func (this *DeployManager) FindNSNodeFile(os string, arch string) *DeployFile {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadHTTPDNSNodeFiles 加载所有HTTPDNS节点安装文件
|
||||
func (this *DeployManager) LoadHTTPDNSNodeFiles() []*DeployFile {
|
||||
this.locker.Lock()
|
||||
defer this.locker.Unlock()
|
||||
|
||||
if len(this.httpdnsNodeFiles) > 0 {
|
||||
return this.httpdnsNodeFiles
|
||||
}
|
||||
|
||||
var keyMap = map[string]*DeployFile{} // key => File
|
||||
|
||||
var reg = regexp.MustCompile(`^edge-httpdns-(\w+)-(\w+)-v([0-9.]+)\.zip$`)
|
||||
for _, file := range files.NewFile(this.dir).List() {
|
||||
var name = file.Name()
|
||||
if !reg.MatchString(name) {
|
||||
continue
|
||||
}
|
||||
var matches = reg.FindStringSubmatch(name)
|
||||
var osName = matches[1]
|
||||
var arch = matches[2]
|
||||
var version = matches[3]
|
||||
|
||||
var key = osName + "_" + arch
|
||||
oldFile, ok := keyMap[key]
|
||||
if ok && stringutil.VersionCompare(oldFile.Version, version) > 0 {
|
||||
continue
|
||||
}
|
||||
keyMap[key] = &DeployFile{
|
||||
OS: osName,
|
||||
Arch: arch,
|
||||
Version: version,
|
||||
Path: file.Path(),
|
||||
}
|
||||
}
|
||||
|
||||
var result = []*DeployFile{}
|
||||
for _, v := range keyMap {
|
||||
result = append(result, v)
|
||||
}
|
||||
|
||||
this.httpdnsNodeFiles = result
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// FindHTTPDNSNodeFile 查找特定平台的HTTPDNS节点安装文件
|
||||
func (this *DeployManager) FindHTTPDNSNodeFile(os string, arch string) *DeployFile {
|
||||
for _, file := range this.LoadHTTPDNSNodeFiles() {
|
||||
if file.OS == os && file.Arch == arch {
|
||||
return file
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// LatestNodeVersion 获取部署目录中边缘节点的最新版本号
|
||||
func (this *DeployManager) LatestNodeVersion() string {
|
||||
return latestVersion(this.LoadNodeFiles())
|
||||
}
|
||||
|
||||
// LatestNSNodeVersion 获取部署目录中DNS节点的最新版本号
|
||||
func (this *DeployManager) LatestNSNodeVersion() string {
|
||||
return latestVersion(this.LoadNSNodeFiles())
|
||||
}
|
||||
|
||||
// LatestHTTPDNSNodeVersion 获取部署目录中HTTPDNS节点的最新版本号
|
||||
func (this *DeployManager) LatestHTTPDNSNodeVersion() string {
|
||||
return latestVersion(this.LoadHTTPDNSNodeFiles())
|
||||
}
|
||||
|
||||
// Reload 重置缓存
|
||||
func (this *DeployManager) Reload() {
|
||||
this.locker.Lock()
|
||||
@@ -148,4 +220,15 @@ func (this *DeployManager) Reload() {
|
||||
|
||||
this.nodeFiles = nil
|
||||
this.nsNodeFiles = nil
|
||||
this.httpdnsNodeFiles = nil
|
||||
}
|
||||
|
||||
func latestVersion(files []*DeployFile) string {
|
||||
var latest string
|
||||
for _, f := range files {
|
||||
if len(latest) == 0 || stringutil.VersionCompare(f.Version, latest) > 0 {
|
||||
latest = f.Version
|
||||
}
|
||||
}
|
||||
return latest
|
||||
}
|
||||
|
||||
@@ -1,832 +0,0 @@
|
||||
package installers
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
slashpath "path"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/iwind/TeaGo/Tea"
|
||||
)
|
||||
|
||||
const (
|
||||
fluentBitConfigDir = "/etc/fluent-bit"
|
||||
fluentBitStorageDir = "/var/lib/fluent-bit/storage"
|
||||
fluentBitMainConfigFile = "/etc/fluent-bit/fluent-bit.conf"
|
||||
fluentBitParsersFile = "/etc/fluent-bit/parsers.conf"
|
||||
fluentBitManagedMetaFile = "/etc/fluent-bit/.edge-managed.json"
|
||||
fluentBitManagedEnvFile = "/etc/fluent-bit/.edge-managed.env"
|
||||
fluentBitDropInDir = "/etc/systemd/system/fluent-bit.service.d"
|
||||
fluentBitDropInFile = "/etc/systemd/system/fluent-bit.service.d/edge-managed.conf"
|
||||
fluentBitServiceName = "fluent-bit"
|
||||
fluentBitDefaultBinPath = "/opt/fluent-bit/bin/fluent-bit"
|
||||
fluentBitLocalPackagesRoot = "packages"
|
||||
fluentBitHTTPPathPattern = "/var/log/edge/edge-node/*.log"
|
||||
fluentBitDNSPathPattern = "/var/log/edge/edge-dns/*.log"
|
||||
fluentBitManagedMarker = "managed-by-edgeapi"
|
||||
fluentBitRoleNode = "node"
|
||||
fluentBitRoleDNS = "dns"
|
||||
)
|
||||
|
||||
var errFluentBitLocalPackageNotFound = errors.New("fluent-bit local package not found")
|
||||
|
||||
var fluentBitPackageFileMapping = map[string]string{
|
||||
"ubuntu22.04-amd64": "fluent-bit_4.2.2_amd64.deb",
|
||||
"ubuntu22.04-arm64": "fluent-bit_4.2.2_arm64.deb",
|
||||
"amzn2023-amd64": "fluent-bit-4.2.2-1.x86_64.rpm",
|
||||
"amzn2023-arm64": "fluent-bit-4.2.2-1.aarch64.rpm",
|
||||
}
|
||||
|
||||
type fluentBitManagedMeta struct {
|
||||
Roles []string `json:"roles"`
|
||||
Hash string `json:"hash"`
|
||||
UpdatedAt int64 `json:"updatedAt"`
|
||||
SourceVersion string `json:"sourceVersion"`
|
||||
}
|
||||
|
||||
type fluentBitDesiredConfig struct {
|
||||
Roles []string
|
||||
ClickHouse *systemconfigs.ClickHouseSetting
|
||||
HTTPPathPattern string
|
||||
DNSPathPattern string
|
||||
}
|
||||
|
||||
// SetupFluentBit 安装并托管 Fluent Bit 配置(离线包 + 平台渲染配置)。
|
||||
func (this *BaseInstaller) SetupFluentBit(role nodeconfigs.NodeRole) error {
|
||||
if this.client == nil {
|
||||
return errors.New("ssh client is nil")
|
||||
}
|
||||
|
||||
uname := this.uname()
|
||||
if !strings.Contains(uname, "Linux") {
|
||||
return nil
|
||||
}
|
||||
|
||||
tempDir := strings.TrimRight(this.client.UserHome(), "/") + "/.edge-fluent-bit"
|
||||
_, _, _ = this.client.Exec("mkdir -p " + shQuote(tempDir))
|
||||
defer func() {
|
||||
_, _, _ = this.client.Exec("rm -rf " + shQuote(tempDir))
|
||||
}()
|
||||
|
||||
if err := this.ensureFluentBitInstalled(tempDir); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, stderr, err := this.client.Exec("mkdir -p " + shQuote(fluentBitConfigDir) + " " + shQuote(fluentBitStorageDir))
|
||||
if err != nil {
|
||||
return fmt.Errorf("prepare fluent-bit directories failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
|
||||
parserContent, err := this.readLocalParsersContent()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
existingMeta, err := this.readManagedMeta()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mergedRoles, err := mergeManagedRoles(existingMeta, role)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
desiredConfig, err := this.buildDesiredFluentBitConfig(mergedRoles)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configChanged, err := this.applyManagedConfig(tempDir, desiredConfig, parserContent, existingMeta)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
binPath, err := this.lookupFluentBitBinPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := this.ensureFluentBitService(tempDir, binPath, configChanged); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) ensureFluentBitInstalled(tempDir string) error {
|
||||
binPath, _ := this.lookupFluentBitBinPath()
|
||||
if binPath != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
platformKey, packageName, arch, err := this.detectRemotePlatformAndPackage()
|
||||
if err != nil {
|
||||
return fmt.Errorf("detect fluent-bit platform failed: %w", err)
|
||||
}
|
||||
|
||||
if err := this.installFluentBitFromLocalPackage(tempDir, arch, packageName); err != nil {
|
||||
if errors.Is(err, errFluentBitLocalPackageNotFound) {
|
||||
expectedPath := filepath.Join("deploy", "fluent-bit", fluentBitLocalPackagesRoot, "linux-"+arch, packageName)
|
||||
return fmt.Errorf("install fluent-bit failed: local package missing for platform '%s', expected '%s'", platformKey, expectedPath)
|
||||
}
|
||||
return fmt.Errorf("install fluent-bit from local package failed: %w", err)
|
||||
}
|
||||
|
||||
binPath, err = this.lookupFluentBitBinPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if binPath == "" {
|
||||
return errors.New("fluent-bit binary not found after local package install")
|
||||
}
|
||||
|
||||
_, stderr, err := this.client.Exec(binPath + " --version")
|
||||
if err != nil {
|
||||
return fmt.Errorf("verify fluent-bit version failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) installFluentBitFromLocalPackage(tempDir string, arch string, packageName string) error {
|
||||
packageDir := filepath.Join(Tea.Root, "deploy", "fluent-bit", fluentBitLocalPackagesRoot, "linux-"+arch)
|
||||
localPackagePath := filepath.Join(packageDir, packageName)
|
||||
if _, err := os.Stat(localPackagePath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return errFluentBitLocalPackageNotFound
|
||||
}
|
||||
return fmt.Errorf("check local package failed: %w", err)
|
||||
}
|
||||
|
||||
remotePackagePath := tempDir + "/" + filepath.Base(localPackagePath)
|
||||
if err := this.client.Copy(localPackagePath, remotePackagePath, 0644); err != nil {
|
||||
return fmt.Errorf("upload local package failed: %w", err)
|
||||
}
|
||||
|
||||
var installCmd string
|
||||
lowerName := strings.ToLower(localPackagePath)
|
||||
switch {
|
||||
case strings.HasSuffix(lowerName, ".deb"):
|
||||
installCmd = "dpkg -i " + shQuote(remotePackagePath)
|
||||
case strings.HasSuffix(lowerName, ".rpm"):
|
||||
installCmd = "rpm -Uvh --force " + shQuote(remotePackagePath) + " || rpm -ivh --force " + shQuote(remotePackagePath)
|
||||
case strings.HasSuffix(lowerName, ".tar.gz") || strings.HasSuffix(lowerName, ".tgz"):
|
||||
extractDir := tempDir + "/extract"
|
||||
installCmd = "rm -rf " + shQuote(extractDir) + "; mkdir -p " + shQuote(extractDir) + "; tar -xzf " + shQuote(remotePackagePath) + " -C " + shQuote(extractDir) + "; " +
|
||||
"bin=$(find " + shQuote(extractDir) + " -type f -name fluent-bit | head -n 1); " +
|
||||
"if [ -z \"$bin\" ]; then exit 3; fi; " +
|
||||
"mkdir -p /opt/fluent-bit/bin /usr/local/bin; " +
|
||||
"install -m 0755 \"$bin\" /opt/fluent-bit/bin/fluent-bit; " +
|
||||
"ln -sf /opt/fluent-bit/bin/fluent-bit /usr/local/bin/fluent-bit"
|
||||
default:
|
||||
return fmt.Errorf("unsupported local package format: %s", packageName)
|
||||
}
|
||||
|
||||
_, stderr, err := this.client.Exec(installCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("install fluent-bit local package '%s' failed: %w, stderr: %s", filepath.Base(localPackagePath), err, stderr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) detectRemotePlatformAndPackage() (platformKey string, packageName string, arch string, err error) {
|
||||
arch, err = this.detectRemoteLinuxArch()
|
||||
if err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
releaseData, stderr, err := this.client.Exec("cat /etc/os-release")
|
||||
if err != nil {
|
||||
return "", "", "", fmt.Errorf("read /etc/os-release failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
if strings.TrimSpace(releaseData) == "" {
|
||||
return "", "", "", errors.New("/etc/os-release is empty")
|
||||
}
|
||||
|
||||
releaseMap := parseOSRelease(releaseData)
|
||||
id := strings.ToLower(strings.TrimSpace(releaseMap["ID"]))
|
||||
versionID := strings.TrimSpace(releaseMap["VERSION_ID"])
|
||||
|
||||
var distro string
|
||||
switch {
|
||||
case id == "ubuntu" && strings.HasPrefix(versionID, "22.04"):
|
||||
distro = "ubuntu22.04"
|
||||
case id == "amzn" && strings.HasPrefix(versionID, "2023"):
|
||||
distro = "amzn2023"
|
||||
default:
|
||||
return "", "", "", fmt.Errorf("unsupported linux platform id='%s' version='%s'", id, versionID)
|
||||
}
|
||||
|
||||
platformKey = distro + "-" + arch
|
||||
packageName, ok := fluentBitPackageFileMapping[platformKey]
|
||||
if !ok {
|
||||
return "", "", "", fmt.Errorf("no local package mapping for platform '%s'", platformKey)
|
||||
}
|
||||
return platformKey, packageName, arch, nil
|
||||
}
|
||||
|
||||
func parseOSRelease(content string) map[string]string {
|
||||
result := map[string]string{}
|
||||
lines := strings.Split(content, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
value = strings.Trim(value, "\"")
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) detectRemoteLinuxArch() (string, error) {
|
||||
stdout, stderr, err := this.client.Exec("uname -m")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("detect remote arch failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
|
||||
arch := strings.ToLower(strings.TrimSpace(stdout))
|
||||
switch arch {
|
||||
case "x86_64", "amd64":
|
||||
return "amd64", nil
|
||||
case "aarch64", "arm64":
|
||||
return "arm64", nil
|
||||
default:
|
||||
return arch, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) lookupFluentBitBinPath() (string, error) {
|
||||
stdout, stderr, err := this.client.Exec("if command -v fluent-bit >/dev/null 2>&1; then command -v fluent-bit; elif [ -x " + fluentBitDefaultBinPath + " ]; then echo " + fluentBitDefaultBinPath + "; fi")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("lookup fluent-bit binary failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
return strings.TrimSpace(stdout), nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) readLocalParsersContent() (string, error) {
|
||||
parsersPath := filepath.Join(Tea.Root, "deploy", "fluent-bit", "parsers.conf")
|
||||
data, err := os.ReadFile(parsersPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read local parsers config failed: %w", err)
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) readManagedMeta() (*fluentBitManagedMeta, error) {
|
||||
exists, err := this.remoteFileExists(fluentBitManagedMetaFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
content, stderr, err := this.client.Exec("cat " + shQuote(fluentBitManagedMetaFile))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read fluent-bit managed metadata failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
if strings.TrimSpace(content) == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
meta := &fluentBitManagedMeta{}
|
||||
if err := json.Unmarshal([]byte(content), meta); err != nil {
|
||||
return nil, fmt.Errorf("decode fluent-bit managed metadata failed: %w", err)
|
||||
}
|
||||
meta.Roles = normalizeRoles(meta.Roles)
|
||||
return meta, nil
|
||||
}
|
||||
|
||||
func mergeManagedRoles(meta *fluentBitManagedMeta, role nodeconfigs.NodeRole) ([]string, error) {
|
||||
roleName, err := mapNodeRole(role)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
roleSet := map[string]struct{}{}
|
||||
if meta != nil {
|
||||
for _, r := range normalizeRoles(meta.Roles) {
|
||||
roleSet[r] = struct{}{}
|
||||
}
|
||||
}
|
||||
roleSet[roleName] = struct{}{}
|
||||
|
||||
roles := make([]string, 0, len(roleSet))
|
||||
for r := range roleSet {
|
||||
roles = append(roles, r)
|
||||
}
|
||||
sort.Strings(roles)
|
||||
return roles, nil
|
||||
}
|
||||
|
||||
func mapNodeRole(role nodeconfigs.NodeRole) (string, error) {
|
||||
switch role {
|
||||
case nodeconfigs.NodeRoleNode:
|
||||
return fluentBitRoleNode, nil
|
||||
case nodeconfigs.NodeRoleDNS:
|
||||
return fluentBitRoleDNS, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported fluent-bit role '%s'", role)
|
||||
}
|
||||
}
|
||||
|
||||
func normalizeRoles(rawRoles []string) []string {
|
||||
roleSet := map[string]struct{}{}
|
||||
for _, role := range rawRoles {
|
||||
role = strings.ToLower(strings.TrimSpace(role))
|
||||
if role != fluentBitRoleNode && role != fluentBitRoleDNS {
|
||||
continue
|
||||
}
|
||||
roleSet[role] = struct{}{}
|
||||
}
|
||||
|
||||
roles := make([]string, 0, len(roleSet))
|
||||
for role := range roleSet {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
sort.Strings(roles)
|
||||
return roles
|
||||
}
|
||||
|
||||
func hasRole(roles []string, role string) bool {
|
||||
for _, one := range roles {
|
||||
if one == role {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) buildDesiredFluentBitConfig(roles []string) (*fluentBitDesiredConfig, error) {
|
||||
if len(roles) == 0 {
|
||||
return nil, errors.New("fluent-bit roles should not be empty")
|
||||
}
|
||||
|
||||
ch, err := models.SharedSysSettingDAO.ReadClickHouseConfig(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read clickhouse setting failed: %w", err)
|
||||
}
|
||||
if ch == nil {
|
||||
ch = &systemconfigs.ClickHouseSetting{}
|
||||
}
|
||||
if strings.TrimSpace(ch.Host) == "" {
|
||||
ch.Host = "127.0.0.1"
|
||||
}
|
||||
|
||||
ch.Scheme = strings.ToLower(strings.TrimSpace(ch.Scheme))
|
||||
if ch.Scheme == "" {
|
||||
ch.Scheme = "https"
|
||||
}
|
||||
if ch.Scheme != "http" && ch.Scheme != "https" {
|
||||
return nil, fmt.Errorf("unsupported clickhouse scheme '%s'", ch.Scheme)
|
||||
}
|
||||
|
||||
if ch.Port <= 0 {
|
||||
if ch.Scheme == "https" {
|
||||
ch.Port = 8443
|
||||
} else {
|
||||
ch.Port = 8443
|
||||
}
|
||||
}
|
||||
if strings.TrimSpace(ch.Database) == "" {
|
||||
ch.Database = "default"
|
||||
}
|
||||
if strings.TrimSpace(ch.User) == "" {
|
||||
ch.User = "default"
|
||||
}
|
||||
// 当前平台策略:后台固定跳过 ClickHouse TLS 证书校验,不暴露 ServerName 配置。
|
||||
ch.TLSSkipVerify = true
|
||||
ch.TLSServerName = ""
|
||||
|
||||
httpPathPattern := fluentBitHTTPPathPattern
|
||||
dnsPathPattern := fluentBitDNSPathPattern
|
||||
publicPolicyPath, err := this.readPublicAccessLogPolicyPath()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policyDir := dirFromPolicyPath(publicPolicyPath)
|
||||
if policyDir != "" {
|
||||
pattern := strings.TrimRight(policyDir, "/") + "/*.log"
|
||||
httpPathPattern = pattern
|
||||
dnsPathPattern = pattern
|
||||
}
|
||||
|
||||
return &fluentBitDesiredConfig{
|
||||
Roles: normalizeRoles(roles),
|
||||
ClickHouse: ch,
|
||||
HTTPPathPattern: httpPathPattern,
|
||||
DNSPathPattern: dnsPathPattern,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) readPublicAccessLogPolicyPath() (string, error) {
|
||||
policyId, err := models.SharedHTTPAccessLogPolicyDAO.FindCurrentPublicPolicyId(nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("find current public access log policy failed: %w", err)
|
||||
}
|
||||
if policyId <= 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
policy, err := models.SharedHTTPAccessLogPolicyDAO.FindEnabledHTTPAccessLogPolicy(nil, policyId)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("read public access log policy failed: %w", err)
|
||||
}
|
||||
if policy == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return strings.TrimSpace(models.ParseHTTPAccessLogPolicyFilePath(policy)), nil
|
||||
}
|
||||
|
||||
func dirFromPolicyPath(policyPath string) string {
|
||||
pathValue := strings.TrimSpace(policyPath)
|
||||
if pathValue == "" {
|
||||
return ""
|
||||
}
|
||||
pathValue = strings.ReplaceAll(pathValue, "\\", "/")
|
||||
dir := slashpath.Dir(pathValue)
|
||||
if dir == "." {
|
||||
return ""
|
||||
}
|
||||
return strings.TrimRight(dir, "/")
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) applyManagedConfig(tempDir string, desired *fluentBitDesiredConfig, parserContent string, existingMeta *fluentBitManagedMeta) (bool, error) {
|
||||
mainExists, err := this.remoteFileExists(fluentBitMainConfigFile)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if mainExists && existingMeta == nil {
|
||||
containsMarker, err := this.remoteFileContains(fluentBitMainConfigFile, fluentBitManagedMarker)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !containsMarker {
|
||||
// Adopt unmanaged config by backing it up and replacing with managed config below.
|
||||
}
|
||||
}
|
||||
|
||||
configContent, err := renderManagedConfig(desired)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
envContent := renderManagedEnv(desired.ClickHouse)
|
||||
metaContent, newMeta, err := renderManagedMeta(desired, configContent, parserContent, envContent)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
requiredFiles := []string{fluentBitMainConfigFile, fluentBitParsersFile, fluentBitManagedEnvFile, fluentBitManagedMetaFile}
|
||||
if existingMeta != nil && existingMeta.Hash == newMeta.Hash {
|
||||
allExists := true
|
||||
for _, file := range requiredFiles {
|
||||
exists, err := this.remoteFileExists(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if !exists {
|
||||
allExists = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allExists {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
if mainExists {
|
||||
backup := fluentBitMainConfigFile + ".bak." + strconv.FormatInt(time.Now().Unix(), 10)
|
||||
_, stderr, err := this.client.Exec("cp -f " + shQuote(fluentBitMainConfigFile) + " " + shQuote(backup))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("backup existing fluent-bit config failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
if err := this.writeRemoteFileByTemp(tempDir, fluentBitMainConfigFile, configContent, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := this.writeRemoteFileByTemp(tempDir, fluentBitParsersFile, parserContent, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := this.writeRemoteFileByTemp(tempDir, fluentBitManagedEnvFile, envContent, 0600); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := this.writeRemoteFileByTemp(tempDir, fluentBitManagedMetaFile, metaContent, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func renderManagedConfig(desired *fluentBitDesiredConfig) (string, error) {
|
||||
if desired == nil || desired.ClickHouse == nil {
|
||||
return "", errors.New("invalid fluent-bit desired config")
|
||||
}
|
||||
|
||||
scheme := strings.ToLower(strings.TrimSpace(desired.ClickHouse.Scheme))
|
||||
if scheme == "" {
|
||||
scheme = "http"
|
||||
}
|
||||
if scheme != "http" && scheme != "https" {
|
||||
return "", fmt.Errorf("unsupported clickhouse scheme '%s'", desired.ClickHouse.Scheme)
|
||||
}
|
||||
useTLS := scheme == "https"
|
||||
|
||||
insertHTTP := url.QueryEscape(fmt.Sprintf("INSERT INTO %s.logs_ingest FORMAT JSONEachRow", desired.ClickHouse.Database))
|
||||
insertDNS := url.QueryEscape(fmt.Sprintf("INSERT INTO %s.dns_logs_ingest FORMAT JSONEachRow", desired.ClickHouse.Database))
|
||||
|
||||
lines := []string{
|
||||
"# " + fluentBitManagedMarker,
|
||||
"[SERVICE]",
|
||||
" Flush 1",
|
||||
" Log_Level info",
|
||||
" Parsers_File " + fluentBitParsersFile,
|
||||
" storage.path " + fluentBitStorageDir,
|
||||
" storage.sync normal",
|
||||
" storage.checksum off",
|
||||
" storage.backlog.mem_limit 512MB",
|
||||
"",
|
||||
}
|
||||
|
||||
if hasRole(desired.Roles, fluentBitRoleNode) {
|
||||
lines = append(lines,
|
||||
"[INPUT]",
|
||||
" Name tail",
|
||||
" Path "+desired.HTTPPathPattern,
|
||||
" Tag app.http.logs",
|
||||
" Parser json",
|
||||
" Refresh_Interval 2",
|
||||
" Read_from_Head false",
|
||||
" DB /var/lib/fluent-bit/http-logs.db",
|
||||
" storage.type filesystem",
|
||||
" Mem_Buf_Limit 256MB",
|
||||
" Skip_Long_Lines On",
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if hasRole(desired.Roles, fluentBitRoleDNS) {
|
||||
lines = append(lines,
|
||||
"[INPUT]",
|
||||
" Name tail",
|
||||
" Path "+desired.DNSPathPattern,
|
||||
" Tag app.dns.logs",
|
||||
" Parser json",
|
||||
" Refresh_Interval 2",
|
||||
" Read_from_Head false",
|
||||
" DB /var/lib/fluent-bit/dns-logs.db",
|
||||
" storage.type filesystem",
|
||||
" Mem_Buf_Limit 256MB",
|
||||
" Skip_Long_Lines On",
|
||||
"",
|
||||
)
|
||||
}
|
||||
|
||||
if hasRole(desired.Roles, fluentBitRoleNode) {
|
||||
lines = append(lines,
|
||||
"[OUTPUT]",
|
||||
" Name http",
|
||||
" Match app.http.logs",
|
||||
" Host "+desired.ClickHouse.Host,
|
||||
" Port "+strconv.Itoa(desired.ClickHouse.Port),
|
||||
" URI /?query="+insertHTTP,
|
||||
" Format json_lines",
|
||||
" http_user ${CH_USER}",
|
||||
" http_passwd ${CH_PASSWORD}",
|
||||
" json_date_key timestamp",
|
||||
" json_date_format epoch",
|
||||
" workers 2",
|
||||
" net.keepalive On",
|
||||
" Retry_Limit False",
|
||||
)
|
||||
if useTLS {
|
||||
lines = append(lines, " tls On")
|
||||
if desired.ClickHouse.TLSSkipVerify {
|
||||
lines = append(lines, " tls.verify Off")
|
||||
} else {
|
||||
lines = append(lines, " tls.verify On")
|
||||
}
|
||||
if strings.TrimSpace(desired.ClickHouse.TLSServerName) != "" {
|
||||
lines = append(lines, " tls.vhost "+strings.TrimSpace(desired.ClickHouse.TLSServerName))
|
||||
}
|
||||
}
|
||||
lines = append(lines, "")
|
||||
}
|
||||
|
||||
if hasRole(desired.Roles, fluentBitRoleDNS) {
|
||||
lines = append(lines,
|
||||
"[OUTPUT]",
|
||||
" Name http",
|
||||
" Match app.dns.logs",
|
||||
" Host "+desired.ClickHouse.Host,
|
||||
" Port "+strconv.Itoa(desired.ClickHouse.Port),
|
||||
" URI /?query="+insertDNS,
|
||||
" Format json_lines",
|
||||
" http_user ${CH_USER}",
|
||||
" http_passwd ${CH_PASSWORD}",
|
||||
" json_date_key timestamp",
|
||||
" json_date_format epoch",
|
||||
" workers 2",
|
||||
" net.keepalive On",
|
||||
" Retry_Limit False",
|
||||
)
|
||||
if useTLS {
|
||||
lines = append(lines, " tls On")
|
||||
if desired.ClickHouse.TLSSkipVerify {
|
||||
lines = append(lines, " tls.verify Off")
|
||||
} else {
|
||||
lines = append(lines, " tls.verify On")
|
||||
}
|
||||
if strings.TrimSpace(desired.ClickHouse.TLSServerName) != "" {
|
||||
lines = append(lines, " tls.vhost "+strings.TrimSpace(desired.ClickHouse.TLSServerName))
|
||||
}
|
||||
}
|
||||
lines = append(lines, "")
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n"), nil
|
||||
}
|
||||
|
||||
func renderManagedEnv(ch *systemconfigs.ClickHouseSetting) string {
|
||||
user := "default"
|
||||
password := ""
|
||||
if ch != nil {
|
||||
if strings.TrimSpace(ch.User) != "" {
|
||||
user = strings.TrimSpace(ch.User)
|
||||
}
|
||||
password = ch.Password
|
||||
}
|
||||
return "CH_USER=" + strconv.Quote(user) + "\n" +
|
||||
"CH_PASSWORD=" + strconv.Quote(password) + "\n"
|
||||
}
|
||||
|
||||
func renderManagedMeta(desired *fluentBitDesiredConfig, configContent string, parserContent string, envContent string) (string, *fluentBitManagedMeta, error) {
|
||||
hashInput := configContent + "\n---\n" + parserContent + "\n---\n" + envContent + "\n---\n" + strings.Join(desired.Roles, ",")
|
||||
hashBytes := sha256.Sum256([]byte(hashInput))
|
||||
hash := fmt.Sprintf("%x", hashBytes[:])
|
||||
|
||||
meta := &fluentBitManagedMeta{
|
||||
Roles: desired.Roles,
|
||||
Hash: hash,
|
||||
UpdatedAt: time.Now().Unix(),
|
||||
SourceVersion: teaconst.Version,
|
||||
}
|
||||
data, err := json.MarshalIndent(meta, "", " ")
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("encode fluent-bit managed metadata failed: %w", err)
|
||||
}
|
||||
return string(data) + "\n", meta, nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) copyLocalFileToRemote(tempDir string, localPath string, remotePath string, mode os.FileMode) error {
|
||||
tempFile := tempDir + "/" + filepath.Base(remotePath)
|
||||
if err := this.client.Copy(localPath, tempFile, mode); err != nil {
|
||||
return fmt.Errorf("upload fluent-bit file '%s' failed: %w", localPath, err)
|
||||
}
|
||||
_, stderr, err := this.client.Exec("cp -f " + shQuote(tempFile) + " " + shQuote(remotePath) + " && chmod " + fmt.Sprintf("%04o", mode) + " " + shQuote(remotePath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("install remote fluent-bit file '%s' failed: %w, stderr: %s", remotePath, err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) writeRemoteFileByTemp(tempDir string, remotePath string, content string, mode os.FileMode) error {
|
||||
tempFile := tempDir + "/" + filepath.Base(remotePath) + ".tmp"
|
||||
if _, err := this.client.WriteFile(tempFile, []byte(content)); err != nil {
|
||||
return fmt.Errorf("write temp fluent-bit file '%s' failed: %w", tempFile, err)
|
||||
}
|
||||
|
||||
_, stderr, err := this.client.Exec("cp -f " + shQuote(tempFile) + " " + shQuote(remotePath) + " && chmod " + fmt.Sprintf("%04o", mode) + " " + shQuote(remotePath))
|
||||
if err != nil {
|
||||
return fmt.Errorf("write managed fluent-bit file '%s' failed: %w, stderr: %s", remotePath, err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) ensureFluentBitService(tempDir string, binPath string, configChanged bool) error {
|
||||
_, _, _ = this.client.Exec("if command -v systemctl >/dev/null 2>&1 && [ ! -f /etc/systemd/system/" + fluentBitServiceName + ".service ] && [ ! -f /lib/systemd/system/" + fluentBitServiceName + ".service ]; then " +
|
||||
"cat > /etc/systemd/system/" + fluentBitServiceName + ".service <<'EOF'\n" +
|
||||
"[Unit]\n" +
|
||||
"Description=Fluent Bit\n" +
|
||||
"After=network.target\n" +
|
||||
"\n" +
|
||||
"[Service]\n" +
|
||||
"ExecStart=" + binPath + " -c " + fluentBitMainConfigFile + "\n" +
|
||||
"Restart=always\n" +
|
||||
"RestartSec=5\n" +
|
||||
"\n" +
|
||||
"[Install]\n" +
|
||||
"WantedBy=multi-user.target\n" +
|
||||
"EOF\n" +
|
||||
"fi")
|
||||
|
||||
stdout, _, err := this.client.Exec("if command -v systemctl >/dev/null 2>&1; then echo 1; else echo 0; fi")
|
||||
if err != nil {
|
||||
return fmt.Errorf("check systemctl failed: %w", err)
|
||||
}
|
||||
|
||||
if strings.TrimSpace(stdout) == "1" {
|
||||
dropInChanged, err := this.ensureServiceDropIn(tempDir, binPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
restartRequired := configChanged || dropInChanged
|
||||
_, stderr, err := this.client.Exec("systemctl daemon-reload; systemctl enable " + fluentBitServiceName + " >/dev/null 2>&1 || true; " +
|
||||
"if systemctl is-active " + fluentBitServiceName + " >/dev/null 2>&1; then " +
|
||||
"if [ \"" + boolToString(restartRequired) + "\" = \"1\" ]; then systemctl restart " + fluentBitServiceName + "; fi; " +
|
||||
"else systemctl start " + fluentBitServiceName + "; fi")
|
||||
if err != nil {
|
||||
return fmt.Errorf("ensure fluent-bit service failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if configChanged {
|
||||
_, _, _ = this.client.Exec("pkill -f \"fluent-bit.*fluent-bit.conf\" >/dev/null 2>&1 || true")
|
||||
}
|
||||
|
||||
_, _, runningErr := this.client.Exec("pgrep -f \"fluent-bit.*fluent-bit.conf\" >/dev/null 2>&1")
|
||||
if runningErr != nil {
|
||||
startCmd := "set -a; [ -f " + shQuote(fluentBitManagedEnvFile) + " ] && . " + shQuote(fluentBitManagedEnvFile) + "; set +a; " +
|
||||
shQuote(binPath) + " -c " + shQuote(fluentBitMainConfigFile) + " >/dev/null 2>&1 &"
|
||||
_, stderr, err := this.client.Exec(startCmd)
|
||||
if err != nil {
|
||||
return fmt.Errorf("start fluent-bit without systemd failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) ensureServiceDropIn(tempDir string, binPath string) (bool, error) {
|
||||
_, stderr, err := this.client.Exec("mkdir -p " + shQuote(fluentBitDropInDir))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("prepare fluent-bit drop-in dir failed: %w, stderr: %s", err, stderr)
|
||||
}
|
||||
|
||||
content := "[Service]\n" +
|
||||
"EnvironmentFile=-" + fluentBitManagedEnvFile + "\n" +
|
||||
"ExecStart=\n" +
|
||||
"ExecStart=" + binPath + " -c " + fluentBitMainConfigFile + "\n"
|
||||
|
||||
existing, _, _ := this.client.Exec("if [ -f " + shQuote(fluentBitDropInFile) + " ]; then cat " + shQuote(fluentBitDropInFile) + "; fi")
|
||||
if existing == content {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if err := this.writeRemoteFileByTemp(tempDir, fluentBitDropInFile, content, 0644); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) remoteFileExists(path string) (bool, error) {
|
||||
stdout, stderr, err := this.client.Exec("if [ -f " + shQuote(path) + " ]; then echo 1; else echo 0; fi")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("check remote file '%s' failed: %w, stderr: %s", path, err, stderr)
|
||||
}
|
||||
return strings.TrimSpace(stdout) == "1", nil
|
||||
}
|
||||
|
||||
func (this *BaseInstaller) remoteFileContains(path string, pattern string) (bool, error) {
|
||||
stdout, stderr, err := this.client.Exec("if grep -F " + shQuote(pattern) + " " + shQuote(path) + " >/dev/null 2>&1; then echo 1; else echo 0; fi")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("check remote file content '%s' failed: %w, stderr: %s", path, err, stderr)
|
||||
}
|
||||
return strings.TrimSpace(stdout) == "1", nil
|
||||
}
|
||||
|
||||
func shQuote(value string) string {
|
||||
if value == "" {
|
||||
return "''"
|
||||
}
|
||||
return "'" + strings.ReplaceAll(value, "'", "'\"'\"'") + "'"
|
||||
}
|
||||
|
||||
func boolToString(v bool) string {
|
||||
if v {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
@@ -279,3 +279,27 @@ func (this *BaseInstaller) uname() (uname string) {
|
||||
|
||||
return "x86_64 GNU/Linux"
|
||||
}
|
||||
|
||||
func parseOSRelease(content string) map[string]string {
|
||||
result := map[string]string{}
|
||||
lines := strings.Split(content, "\n")
|
||||
for _, line := range lines {
|
||||
line = strings.TrimSpace(line)
|
||||
if line == "" || strings.HasPrefix(line, "#") || !strings.Contains(line, "=") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
key := strings.TrimSpace(parts[0])
|
||||
value := strings.TrimSpace(parts[1])
|
||||
value = strings.Trim(value, "\"")
|
||||
result[key] = value
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func shQuote(s string) string {
|
||||
if len(s) == 0 {
|
||||
return "''"
|
||||
}
|
||||
return "'" + strings.ReplaceAll(s, "'", "'\"'\"'") + "'"
|
||||
}
|
||||
|
||||
231
EdgeAPI/internal/installers/installer_httpdns_node.go
Normal file
231
EdgeAPI/internal/installers/installer_httpdns_node.go
Normal file
@@ -0,0 +1,231 @@
|
||||
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))
|
||||
}
|
||||
|
||||
listenAddr := strings.TrimSpace(nodeParams.HTTPDNSListenAddr)
|
||||
if len(listenAddr) == 0 {
|
||||
listenAddr = ":443"
|
||||
}
|
||||
|
||||
configData := []byte(`rpc.endpoints: [ ${endpoints} ]
|
||||
nodeId: "${nodeId}"
|
||||
secret: "${nodeSecret}"
|
||||
|
||||
https.listenAddr: "${listenAddr}"
|
||||
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("${listenAddr}"), []byte(listenAddr))
|
||||
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)
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
}
|
||||
@@ -137,12 +137,6 @@ secret: "${nodeSecret}"`)
|
||||
}
|
||||
}
|
||||
|
||||
// 在线安装/更新 Fluent Bit(与边缘节点安装流程联动)
|
||||
err = this.SetupFluentBit(nodeconfigs.NodeRoleNode)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "SETUP_FLUENT_BIT_FAILED"
|
||||
return fmt.Errorf("setup fluent-bit failed: %w", err)
|
||||
}
|
||||
|
||||
// 测试
|
||||
_, stderr, err = this.client.Exec(dir + "/edge-node/bin/edge-node test")
|
||||
|
||||
@@ -139,12 +139,6 @@ secret: "${nodeSecret}"`)
|
||||
}
|
||||
}
|
||||
|
||||
// 在线安装/更新 Fluent Bit(与 DNS 节点安装流程联动)
|
||||
err = this.SetupFluentBit(nodeconfigs.NodeRoleDNS)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "SETUP_FLUENT_BIT_FAILED"
|
||||
return fmt.Errorf("setup fluent-bit failed: %w", err)
|
||||
}
|
||||
|
||||
// 测试
|
||||
_, stderr, err = this.client.Exec(dir + "/edge-dns/bin/edge-dns test")
|
||||
|
||||
@@ -6,10 +6,13 @@ import (
|
||||
)
|
||||
|
||||
type NodeParams struct {
|
||||
Endpoints []string
|
||||
NodeId string
|
||||
Secret string
|
||||
IsUpgrading bool // 是否为升级
|
||||
Endpoints []string
|
||||
NodeId string
|
||||
Secret string
|
||||
TLSCertData []byte
|
||||
TLSKeyData []byte
|
||||
HTTPDNSListenAddr string
|
||||
IsUpgrading bool // 是否为升级
|
||||
}
|
||||
|
||||
func (this *NodeParams) Validate() error {
|
||||
|
||||
416
EdgeAPI/internal/installers/queue_httpdns_node.go
Normal file
416
EdgeAPI/internal/installers/queue_httpdns_node.go
Normal file
@@ -0,0 +1,416 @@
|
||||
package installers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
)
|
||||
|
||||
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, installStatus)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
httpdnsListenAddr, err := q.resolveClusterTLSListenAddr(cluster)
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "INVALID_TLS_LISTEN"
|
||||
return err
|
||||
}
|
||||
|
||||
params := &NodeParams{
|
||||
Endpoints: apiEndpoints,
|
||||
NodeId: node.UniqueId,
|
||||
Secret: node.Secret,
|
||||
TLSCertData: tlsCertData,
|
||||
TLSKeyData: tlsKeyData,
|
||||
HTTPDNSListenAddr: httpdnsListenAddr,
|
||||
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)
|
||||
}
|
||||
|
||||
// StartNode 启动HTTPDNS节点
|
||||
func (q *HTTPDNSNodeQueue) StartNode(nodeId int64) 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) + "'")
|
||||
}
|
||||
|
||||
// 登录信息
|
||||
login, err := models.SharedNodeLoginDAO.FindEnabledNodeLoginWithNodeId(nil, nodeconfigs.NodeRoleHTTPDNS, nodeId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if login == nil {
|
||||
return newGrantError("can not find node login information")
|
||||
}
|
||||
loginParams, err := login.DecodeSSHParams()
|
||||
if err != nil {
|
||||
return newGrantError(err.Error())
|
||||
}
|
||||
if len(strings.TrimSpace(loginParams.Host)) == 0 {
|
||||
return newGrantError("ssh host should not be empty")
|
||||
}
|
||||
if loginParams.Port <= 0 {
|
||||
loginParams.Port = 22
|
||||
}
|
||||
if loginParams.GrantId <= 0 {
|
||||
return newGrantError("can not find node grant")
|
||||
}
|
||||
|
||||
grant, err := models.SharedNodeGrantDAO.FindEnabledNodeGrant(nil, loginParams.GrantId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if grant == nil {
|
||||
return newGrantError("can not find user grant with id '" + numberutils.FormatInt64(loginParams.GrantId) + "'")
|
||||
}
|
||||
|
||||
installer := &HTTPDNSNodeInstaller{}
|
||||
err = installer.Login(&Credentials{
|
||||
Host: strings.TrimSpace(loginParams.Host),
|
||||
Port: loginParams.Port,
|
||||
Username: grant.Username,
|
||||
Password: grant.Password,
|
||||
PrivateKey: grant.PrivateKey,
|
||||
Passphrase: grant.Passphrase,
|
||||
Method: grant.Method,
|
||||
Sudo: grant.Su == 1,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = installer.Close()
|
||||
}()
|
||||
|
||||
installDir := strings.TrimSpace(node.InstallDir)
|
||||
if len(installDir) == 0 {
|
||||
cluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(nil, int64(node.ClusterId))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if cluster == nil {
|
||||
return errors.New("can not find cluster, ID '" + numberutils.FormatInt64(int64(node.ClusterId)) + "'")
|
||||
}
|
||||
installDir = strings.TrimSpace(cluster.InstallDir)
|
||||
if len(installDir) == 0 {
|
||||
installDir = installer.client.UserHome() + "/edge-httpdns"
|
||||
}
|
||||
}
|
||||
_, appDir := resolveHTTPDNSInstallPaths(installDir)
|
||||
exeFile := appDir + "/bin/edge-httpdns"
|
||||
|
||||
_, err = installer.client.Stat(exeFile)
|
||||
if err != nil {
|
||||
return errors.New("httpdns node is not installed correctly, can not find executable file: " + exeFile)
|
||||
}
|
||||
|
||||
// 先尝试 systemd 拉起
|
||||
_, _, _ = installer.client.Exec("/usr/bin/systemctl start edge-httpdns")
|
||||
|
||||
_, stderr, err := installer.client.Exec(exeFile + " start")
|
||||
if err != nil {
|
||||
return fmt.Errorf("start failed: %w", err)
|
||||
}
|
||||
if len(strings.TrimSpace(stderr)) > 0 {
|
||||
return errors.New("start failed: " + strings.TrimSpace(stderr))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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) resolveClusterTLSListenAddr(cluster *models.HTTPDNSCluster) (string, error) {
|
||||
const defaultListenAddr = ":443"
|
||||
|
||||
if cluster == nil || len(cluster.TLSPolicy) == 0 {
|
||||
return defaultListenAddr, nil
|
||||
}
|
||||
|
||||
tlsConfig, err := serverconfigs.NewTLSProtocolConfigFromJSON(cluster.TLSPolicy)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("decode cluster tls listen failed: %w", err)
|
||||
}
|
||||
|
||||
for _, listen := range tlsConfig.Listen {
|
||||
if listen == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := listen.Init(); err != nil {
|
||||
return "", fmt.Errorf("invalid cluster tls listen address '%s': %w", listen.PortRange, err)
|
||||
}
|
||||
if listen.MinPort <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
host := strings.TrimSpace(listen.Host)
|
||||
return net.JoinHostPort(host, strconv.Itoa(listen.MinPort)), nil
|
||||
}
|
||||
|
||||
return defaultListenAddr, nil
|
||||
}
|
||||
|
||||
func (q *HTTPDNSNodeQueue) parseSSHInfo(node *models.HTTPDNSNode, installStatus *models.NodeInstallStatus) (string, int, int64, error) {
|
||||
if node == nil {
|
||||
return "", 0, 0, errors.New("node should not be nil")
|
||||
}
|
||||
|
||||
login, err := models.SharedNodeLoginDAO.FindEnabledNodeLoginWithNodeId(nil, nodeconfigs.NodeRoleHTTPDNS, int64(node.Id))
|
||||
if err != nil {
|
||||
return "", 0, 0, err
|
||||
}
|
||||
if login == nil {
|
||||
installStatus.ErrorCode = "EMPTY_SSH"
|
||||
return "", 0, 0, errors.New("ssh login not found for node '" + numberutils.FormatInt64(int64(node.Id)) + "'")
|
||||
}
|
||||
|
||||
sshParams, err := login.DecodeSSHParams()
|
||||
if err != nil {
|
||||
installStatus.ErrorCode = "EMPTY_SSH"
|
||||
return "", 0, 0, err
|
||||
}
|
||||
|
||||
if len(sshParams.Host) == 0 {
|
||||
installStatus.ErrorCode = "EMPTY_SSH_HOST"
|
||||
return "", 0, 0, errors.New("ssh host should not be empty")
|
||||
}
|
||||
if sshParams.Port <= 0 {
|
||||
sshParams.Port = 22
|
||||
}
|
||||
if sshParams.GrantId <= 0 {
|
||||
installStatus.ErrorCode = "EMPTY_GRANT"
|
||||
return "", 0, 0, errors.New("grant id should not be empty")
|
||||
}
|
||||
|
||||
return sshParams.Host, sshParams.Port, sshParams.GrantId, nil
|
||||
}
|
||||
25
EdgeAPI/internal/installers/upgrade_queue.go
Normal file
25
EdgeAPI/internal/installers/upgrade_queue.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package installers
|
||||
|
||||
// UpgradeQueue 升级队列,控制并发数
|
||||
type UpgradeQueue struct {
|
||||
sem chan struct{}
|
||||
}
|
||||
|
||||
// SharedUpgradeQueue 全局升级队列,最多5个并发
|
||||
var SharedUpgradeQueue = NewUpgradeQueue(5)
|
||||
|
||||
// NewUpgradeQueue 创建升级队列
|
||||
func NewUpgradeQueue(maxConcurrent int) *UpgradeQueue {
|
||||
return &UpgradeQueue{
|
||||
sem: make(chan struct{}, maxConcurrent),
|
||||
}
|
||||
}
|
||||
|
||||
// SubmitNodeUpgrade 提交节点升级任务(异步执行,超过并发限制自动排队)
|
||||
func (q *UpgradeQueue) SubmitNodeUpgrade(nodeId int64, upgradeFunc func(int64) error) {
|
||||
go func() {
|
||||
q.sem <- struct{}{}
|
||||
defer func() { <-q.sem }()
|
||||
_ = upgradeFunc(nodeId)
|
||||
}()
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/accounts"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/anti-ddos"
|
||||
httpdnsServices "github.com/TeaOSLab/EdgeAPI/internal/rpc/services/httpdns"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/nameservers"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/posts"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services/reporters"
|
||||
@@ -313,4 +314,9 @@ func APINodeServicesRegister(node *APINode, server *grpc.Server) {
|
||||
pb.RegisterPostCategoryServiceServer(server, instance)
|
||||
node.rest(instance)
|
||||
}
|
||||
{
|
||||
var instance = node.serviceInstance(&httpdnsServices.HTTPDNSBoardService{}).(*httpdnsServices.HTTPDNSBoardService)
|
||||
pb.RegisterHTTPDNSBoardServiceServer(server, instance)
|
||||
node.rest(instance)
|
||||
}
|
||||
}
|
||||
|
||||
220
EdgeAPI/internal/rpc/services/httpdns/converters.go
Normal file
220
EdgeAPI/internal/rpc/services/httpdns/converters.go
Normal file
@@ -0,0 +1,220 @@
|
||||
package httpdns
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
)
|
||||
|
||||
func toPBCluster(cluster *models.HTTPDNSCluster) *pb.HTTPDNSCluster {
|
||||
if cluster == nil {
|
||||
return nil
|
||||
}
|
||||
return &pb.HTTPDNSCluster{
|
||||
Id: int64(cluster.Id),
|
||||
IsOn: cluster.IsOn > 0,
|
||||
IsDefault: cluster.IsDefault > 0,
|
||||
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),
|
||||
AutoRemoteStart: cluster.AutoRemoteStart > 0,
|
||||
AccessLogIsOn: cluster.AccessLogIsOn > 0,
|
||||
TimeZone: cluster.TimeZone,
|
||||
}
|
||||
}
|
||||
|
||||
// toPBClusterWithResolvedCerts 转换集群并解析证书引用为实际 PEM 数据
|
||||
// 供节点调用的 RPC 使用,确保节点能拿到完整的证书内容
|
||||
func toPBClusterWithResolvedCerts(tx *dbs.Tx, cluster *models.HTTPDNSCluster) *pb.HTTPDNSCluster {
|
||||
pbCluster := toPBCluster(cluster)
|
||||
if pbCluster == nil {
|
||||
return nil
|
||||
}
|
||||
resolved := resolveTLSPolicyCerts(tx, cluster.TLSPolicy)
|
||||
if resolved != nil {
|
||||
pbCluster.TlsPolicyJSON = resolved
|
||||
}
|
||||
return pbCluster
|
||||
}
|
||||
|
||||
// resolveTLSPolicyCerts 将 tlsPolicyJSON 中的 certRefs 解析为带实际 PEM 数据的 certs
|
||||
func resolveTLSPolicyCerts(tx *dbs.Tx, tlsPolicyJSON []byte) []byte {
|
||||
if len(tlsPolicyJSON) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 解析外层结构: {"listen": [...], "sslPolicy": {...}}
|
||||
var tlsConfig map[string]json.RawMessage
|
||||
if err := json.Unmarshal(tlsPolicyJSON, &tlsConfig); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
sslPolicyData, ok := tlsConfig["sslPolicy"]
|
||||
if !ok || len(sslPolicyData) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var sslPolicy sslconfigs.SSLPolicy
|
||||
if err := json.Unmarshal(sslPolicyData, &sslPolicy); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 检查 certs 是否已经有实际数据
|
||||
for _, cert := range sslPolicy.Certs {
|
||||
if cert != nil && len(cert.CertData) > 128 && len(cert.KeyData) > 128 {
|
||||
return nil // 已有完整 PEM 数据,无需处理
|
||||
}
|
||||
}
|
||||
|
||||
// 从 certRefs 解析实际证书数据
|
||||
if len(sslPolicy.CertRefs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var resolvedCerts []*sslconfigs.SSLCertConfig
|
||||
for _, ref := range sslPolicy.CertRefs {
|
||||
if ref == nil || ref.CertId <= 0 {
|
||||
continue
|
||||
}
|
||||
certConfig, err := models.SharedSSLCertDAO.ComposeCertConfig(tx, ref.CertId, false, nil, nil)
|
||||
if err != nil {
|
||||
log.Println("[HTTPDNS]resolve cert", ref.CertId, "failed:", err.Error())
|
||||
continue
|
||||
}
|
||||
if certConfig == nil || len(certConfig.CertData) == 0 || len(certConfig.KeyData) == 0 {
|
||||
continue
|
||||
}
|
||||
resolvedCerts = append(resolvedCerts, certConfig)
|
||||
}
|
||||
|
||||
if len(resolvedCerts) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 把解析后的证书写回 sslPolicy.Certs
|
||||
sslPolicy.Certs = resolvedCerts
|
||||
|
||||
newPolicyData, err := json.Marshal(&sslPolicy)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
tlsConfig["sslPolicy"] = newPolicyData
|
||||
|
||||
result, err := json.Marshal(tlsConfig)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
// 构建 clusterIdsJSON
|
||||
clusterIds := models.SharedHTTPDNSAppDAO.ReadAppClusterIds(app)
|
||||
clusterIdsJSON, _ := json.Marshal(clusterIds)
|
||||
|
||||
return &pb.HTTPDNSApp{
|
||||
Id: int64(app.Id),
|
||||
Name: app.Name,
|
||||
AppId: app.AppId,
|
||||
IsOn: app.IsOn,
|
||||
SniMode: app.SNIMode,
|
||||
SignEnabled: signEnabled,
|
||||
SignSecret: signSecret,
|
||||
SignUpdatedAt: signUpdatedAt,
|
||||
CreatedAt: int64(app.CreatedAt),
|
||||
UpdatedAt: int64(app.UpdatedAt),
|
||||
UserId: int64(app.UserId),
|
||||
ClusterIdsJSON: clusterIdsJSON,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
package httpdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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) {
|
||||
_, userId, err := s.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
allowedAppIds := []string(nil)
|
||||
if userId > 0 {
|
||||
if len(strings.TrimSpace(req.GetAppId())) > 0 {
|
||||
app, err := ensureAppAccessByAppId(s.NullTx(), req.GetAppId(), userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if app == nil {
|
||||
return &pb.ListHTTPDNSAccessLogsResponse{
|
||||
Logs: []*pb.HTTPDNSAccessLog{},
|
||||
Total: 0,
|
||||
}, nil
|
||||
}
|
||||
} else {
|
||||
allowedAppIds, err = models.SharedHTTPDNSAppDAO.ListEnabledAppIdsWithUser(s.NullTx(), userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(allowedAppIds) == 0 {
|
||||
return &pb.ListHTTPDNSAccessLogsResponse{
|
||||
Logs: []*pb.HTTPDNSAccessLog{},
|
||||
Total: 0,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
store := clickhouse.NewHTTPDNSAccessLogsStore()
|
||||
canReadFromClickHouse := s.shouldReadHTTPDNSAccessLogsFromClickHouse() && store.Client().IsConfigured()
|
||||
canReadFromMySQL := s.shouldReadHTTPDNSAccessLogsFromMySQL()
|
||||
if canReadFromClickHouse {
|
||||
resp, listErr := s.listFromClickHouse(ctx, store, req, allowedAppIds)
|
||||
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.CountLogsWithAppIds(s.NullTx(), req.GetDay(), req.GetClusterId(), req.GetNodeId(), req.GetAppId(), allowedAppIds, req.GetDomain(), req.GetStatus(), req.GetKeyword())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logs, err := models.SharedHTTPDNSAccessLogDAO.ListLogsWithAppIds(s.NullTx(), req.GetDay(), req.GetClusterId(), req.GetNodeId(), req.GetAppId(), allowedAppIds, 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, allowedAppIds []string) (*pb.ListHTTPDNSAccessLogsResponse, error) {
|
||||
filter := clickhouse.HTTPDNSAccessLogListFilter{
|
||||
Day: req.GetDay(),
|
||||
ClusterId: req.GetClusterId(),
|
||||
NodeId: req.GetNodeId(),
|
||||
AppId: req.GetAppId(),
|
||||
AppIds: allowedAppIds,
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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 ©Targets
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
370
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_app.go
Normal file
370
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_app.go
Normal file
@@ -0,0 +1,370 @@
|
||||
package httpdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if userId > 0 {
|
||||
req.UserId = userId
|
||||
}
|
||||
appName := strings.TrimSpace(req.Name)
|
||||
appId := strings.TrimSpace(req.AppId)
|
||||
if len(appName) == 0 || len(appId) == 0 {
|
||||
return nil, errors.New("required 'name' and 'appId'")
|
||||
}
|
||||
var appDbId int64
|
||||
now := time.Now().Unix()
|
||||
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||
// 用户端防重复提交:短时间内同用户同应用名仅创建一次。
|
||||
if req.UserId > 0 {
|
||||
latest, err := models.SharedHTTPDNSAppDAO.FindLatestEnabledAppWithNameAndUser(tx, appName, req.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if latest != nil && int64(latest.CreatedAt) >= now-5 {
|
||||
appDbId = int64(latest.Id)
|
||||
secret, err := models.SharedHTTPDNSAppSecretDAO.FindEnabledAppSecret(tx, appDbId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if secret == nil {
|
||||
_, _, err = models.SharedHTTPDNSAppSecretDAO.InitAppSecret(tx, appDbId, req.SignEnabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
exists, err := models.SharedHTTPDNSAppDAO.FindEnabledAppWithAppId(tx, appId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists != nil {
|
||||
return errors.New("appId already exists")
|
||||
}
|
||||
|
||||
// 使用 clusterIdsJSON;若为空则从用户关联集群获取
|
||||
clusterIdsJSON := req.ClusterIdsJSON
|
||||
if len(clusterIdsJSON) == 0 || string(clusterIdsJSON) == "[]" || string(clusterIdsJSON) == "null" {
|
||||
// 读取用户关联的 HTTPDNS 集群
|
||||
if req.UserId > 0 {
|
||||
user, userErr := models.SharedUserDAO.FindEnabledUser(tx, req.UserId, nil)
|
||||
if userErr != nil {
|
||||
return userErr
|
||||
}
|
||||
if user != nil && len(user.HttpdnsClusterIds) > 0 {
|
||||
var userClusterIds []int64
|
||||
if json.Unmarshal([]byte(user.HttpdnsClusterIds), &userClusterIds) == nil && len(userClusterIds) > 0 {
|
||||
clusterIdsJSON, _ = json.Marshal(userClusterIds)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果仍然没有集群,则不允许创建
|
||||
if len(clusterIdsJSON) == 0 || string(clusterIdsJSON) == "[]" || string(clusterIdsJSON) == "null" {
|
||||
return errors.New("用户尚未分配 HTTPDNS 集群,无法创建应用")
|
||||
}
|
||||
|
||||
appDbId, err = models.SharedHTTPDNSAppDAO.CreateApp(tx, appName, appId, clusterIdsJSON, 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
|
||||
}
|
||||
|
||||
// readHTTPDNSDefaultClusterIdList reads default cluster IDs from UserRegisterConfig.
|
||||
func readHTTPDNSDefaultClusterIdList(tx *dbs.Tx) ([]int64, error) {
|
||||
// 优先从 UserRegisterConfig 中读取
|
||||
configJSON, err := models.SharedSysSettingDAO.ReadSetting(tx, systemconfigs.SettingCodeUserRegisterConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(configJSON) > 0 {
|
||||
var config userconfigs.UserRegisterConfig
|
||||
if err := json.Unmarshal(configJSON, &config); err == nil {
|
||||
if len(config.HTTPDNSDefaultClusterIds) > 0 {
|
||||
// 验证集群有效性
|
||||
var validIds []int64
|
||||
for _, id := range config.HTTPDNSDefaultClusterIds {
|
||||
if id <= 0 {
|
||||
continue
|
||||
}
|
||||
cluster, err := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(tx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if cluster != nil && cluster.IsOn > 0 {
|
||||
validIds = append(validIds, id)
|
||||
}
|
||||
}
|
||||
if len(validIds) > 0 {
|
||||
return validIds, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (this *HTTPDNSAppService) UpdateHTTPDNSApp(ctx context.Context, req *pb.UpdateHTTPDNSAppRequest) (*pb.RPCSuccess, error) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||
oldApp, err := ensureAppAccess(tx, req.AppDbId, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if oldApp == nil {
|
||||
return errors.New("app not found")
|
||||
}
|
||||
|
||||
targetUserId := req.UserId
|
||||
if targetUserId <= 0 {
|
||||
targetUserId = oldApp.UserId
|
||||
}
|
||||
if userId > 0 {
|
||||
targetUserId = userId
|
||||
}
|
||||
|
||||
err = models.SharedHTTPDNSAppDAO.UpdateApp(tx, req.AppDbId, req.Name, req.ClusterIdsJSON, req.IsOn, targetUserId)
|
||||
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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||
app, err := ensureAppAccess(tx, req.AppDbId, userId)
|
||||
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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
app, err := ensureAppAccess(this.NullTx(), req.AppDbId, userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if app == nil {
|
||||
return &pb.FindHTTPDNSAppResponse{}, nil
|
||||
}
|
||||
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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var apps []*models.HTTPDNSApp
|
||||
if userId > 0 {
|
||||
apps, err = models.SharedHTTPDNSAppDAO.ListEnabledAppsWithUser(this.NullTx(), userId, req.Offset, req.Size, req.Keyword)
|
||||
} else {
|
||||
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) {
|
||||
_, userId, validateErr := this.ValidateAdminAndUser(ctx, true)
|
||||
if validateErr != nil {
|
||||
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
|
||||
return nil, validateErr
|
||||
}
|
||||
}
|
||||
var apps []*models.HTTPDNSApp
|
||||
var err error
|
||||
if validateErr == nil && userId > 0 {
|
||||
apps, err = models.SharedHTTPDNSAppDAO.FindAllEnabledAppsWithUser(this.NullTx(), userId)
|
||||
} else {
|
||||
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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||
app, err := ensureAppAccess(tx, req.AppDbId, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app == nil {
|
||||
return errors.New("app not found")
|
||||
}
|
||||
|
||||
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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var signSecret string
|
||||
var updatedAt int64
|
||||
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||
app, err := ensureAppAccess(tx, req.AppDbId, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app == nil {
|
||||
return errors.New("app not found")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
324
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_board.go
Normal file
324
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_board.go
Normal file
@@ -0,0 +1,324 @@
|
||||
package httpdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/clickhouse"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
timeutil "github.com/iwind/TeaGo/utils/time"
|
||||
)
|
||||
|
||||
// HTTPDNSBoardService HTTPDNS 仪表盘服务
|
||||
type HTTPDNSBoardService struct {
|
||||
services.BaseService
|
||||
}
|
||||
|
||||
// ComposeHTTPDNSBoard 组合看板数据
|
||||
func (this *HTTPDNSBoardService) ComposeHTTPDNSBoard(ctx context.Context, req *pb.ComposeHTTPDNSBoardRequest) (*pb.ComposeHTTPDNSBoardResponse, error) {
|
||||
_, err := this.ValidateAdmin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := this.NullTx()
|
||||
result := &pb.ComposeHTTPDNSBoardResponse{}
|
||||
|
||||
countApps, err := models.SharedHTTPDNSAppDAO.CountEnabledApps(tx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.CountApps = countApps
|
||||
|
||||
countDomains, err := models.SharedHTTPDNSDomainDAO.CountEnabledDomains(tx, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.CountDomains = countDomains
|
||||
|
||||
countClusters, err := models.SharedHTTPDNSClusterDAO.CountEnabledClusters(tx, "")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.CountClusters = countClusters
|
||||
|
||||
allNodes, err := models.SharedHTTPDNSNodeDAO.ListEnabledNodes(tx, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result.CountNodes = int64(len(allNodes))
|
||||
|
||||
var countOffline int64
|
||||
for _, node := range allNodes {
|
||||
if !node.IsActive {
|
||||
countOffline++
|
||||
}
|
||||
}
|
||||
result.CountOfflineNodes = countOffline
|
||||
|
||||
hourFrom := timeutil.Format("YmdH", time.Now().Add(-23*time.Hour))
|
||||
hourTo := timeutil.Format("YmdH")
|
||||
dayFrom := timeutil.Format("Ymd", time.Now().AddDate(0, 0, -14))
|
||||
dayTo := timeutil.Format("Ymd")
|
||||
todayFrom := timeutil.Format("Ymd", time.Now().Add(-24*time.Hour))
|
||||
|
||||
store := clickhouse.NewHTTPDNSAccessLogsStore()
|
||||
if store.Client().IsConfigured() {
|
||||
err = this.composeTrafficAndRanksFromClickHouse(ctx, tx, store, result, hourFrom, hourTo, dayFrom, dayTo, todayFrom)
|
||||
}
|
||||
if err != nil || !store.Client().IsConfigured() {
|
||||
err = this.composeTrafficAndRanksFromMySQL(tx, result, hourFrom, hourTo, dayFrom, dayTo, todayFrom)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
err = this.fillNodeValues(tx, result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (this *HTTPDNSBoardService) composeTrafficAndRanksFromClickHouse(ctx context.Context, tx *dbs.Tx, store *clickhouse.HTTPDNSAccessLogsStore, result *pb.ComposeHTTPDNSBoardResponse, hourFrom string, hourTo string, dayFrom string, dayTo string, todayFrom string) error {
|
||||
hourlyStats, err := store.FindHourlyStats(ctx, hourFrom, hourTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hourlyMap := map[string]*clickhouse.HTTPDNSAccessLogHourlyStat{}
|
||||
for _, stat := range hourlyStats {
|
||||
hourlyMap[stat.Hour] = stat
|
||||
}
|
||||
hours, err := utils.RangeHours(hourFrom, hourTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, hour := range hours {
|
||||
stat, ok := hourlyMap[hour]
|
||||
if ok {
|
||||
result.HourlyTrafficStats = append(result.HourlyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_HourlyTrafficStat{
|
||||
Hour: hour,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
} else {
|
||||
result.HourlyTrafficStats = append(result.HourlyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_HourlyTrafficStat{Hour: hour})
|
||||
}
|
||||
}
|
||||
|
||||
dailyStats, err := store.FindDailyStats(ctx, dayFrom, dayTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dailyMap := map[string]*clickhouse.HTTPDNSAccessLogDailyStat{}
|
||||
for _, stat := range dailyStats {
|
||||
dailyMap[stat.Day] = stat
|
||||
}
|
||||
days, err := utils.RangeDays(dayFrom, dayTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, day := range days {
|
||||
stat, ok := dailyMap[day]
|
||||
if ok {
|
||||
result.DailyTrafficStats = append(result.DailyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_DailyTrafficStat{
|
||||
Day: day,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
} else {
|
||||
result.DailyTrafficStats = append(result.DailyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_DailyTrafficStat{Day: day})
|
||||
}
|
||||
}
|
||||
|
||||
topAppStats, err := store.ListTopApps(ctx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topAppStats {
|
||||
appName := stat.AppName
|
||||
if len(appName) == 0 {
|
||||
appName = stat.AppId
|
||||
}
|
||||
result.TopAppStats = append(result.TopAppStats, &pb.ComposeHTTPDNSBoardResponse_TopAppStat{
|
||||
AppId: 0,
|
||||
AppName: appName,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
topDomainStats, err := store.ListTopDomains(ctx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topDomainStats {
|
||||
result.TopDomainStats = append(result.TopDomainStats, &pb.ComposeHTTPDNSBoardResponse_TopDomainStat{
|
||||
DomainName: stat.Domain,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
topNodeStats, err := store.ListTopNodes(ctx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topNodeStats {
|
||||
nodeName := ""
|
||||
node, nodeErr := models.SharedHTTPDNSNodeDAO.FindEnabledNode(tx, int64(stat.NodeId))
|
||||
if nodeErr == nil && node != nil {
|
||||
nodeName = node.Name
|
||||
}
|
||||
if len(nodeName) == 0 {
|
||||
continue
|
||||
}
|
||||
result.TopNodeStats = append(result.TopNodeStats, &pb.ComposeHTTPDNSBoardResponse_TopNodeStat{
|
||||
ClusterId: int64(stat.ClusterId),
|
||||
NodeId: int64(stat.NodeId),
|
||||
NodeName: nodeName,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *HTTPDNSBoardService) composeTrafficAndRanksFromMySQL(tx *dbs.Tx, result *pb.ComposeHTTPDNSBoardResponse, hourFrom string, hourTo string, dayFrom string, dayTo string, todayFrom string) error {
|
||||
hourlyStats, err := models.SharedHTTPDNSAccessLogDAO.FindHourlyStats(tx, hourFrom, hourTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hourlyMap := map[string]*models.HTTPDNSAccessLogHourlyStat{}
|
||||
for _, stat := range hourlyStats {
|
||||
hourlyMap[stat.Hour] = stat
|
||||
}
|
||||
hours, err := utils.RangeHours(hourFrom, hourTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, hour := range hours {
|
||||
stat, ok := hourlyMap[hour]
|
||||
if ok {
|
||||
result.HourlyTrafficStats = append(result.HourlyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_HourlyTrafficStat{
|
||||
Hour: hour,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
} else {
|
||||
result.HourlyTrafficStats = append(result.HourlyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_HourlyTrafficStat{Hour: hour})
|
||||
}
|
||||
}
|
||||
|
||||
dailyStats, err := models.SharedHTTPDNSAccessLogDAO.FindDailyStats(tx, dayFrom, dayTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dailyMap := map[string]*models.HTTPDNSAccessLogDailyStat{}
|
||||
for _, stat := range dailyStats {
|
||||
dailyMap[stat.Day] = stat
|
||||
}
|
||||
days, err := utils.RangeDays(dayFrom, dayTo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, day := range days {
|
||||
stat, ok := dailyMap[day]
|
||||
if ok {
|
||||
result.DailyTrafficStats = append(result.DailyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_DailyTrafficStat{
|
||||
Day: day,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
} else {
|
||||
result.DailyTrafficStats = append(result.DailyTrafficStats, &pb.ComposeHTTPDNSBoardResponse_DailyTrafficStat{Day: day})
|
||||
}
|
||||
}
|
||||
|
||||
topAppStats, err := models.SharedHTTPDNSAccessLogDAO.ListTopApps(tx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topAppStats {
|
||||
appName := stat.AppName
|
||||
if len(appName) == 0 {
|
||||
appName = stat.AppId
|
||||
}
|
||||
result.TopAppStats = append(result.TopAppStats, &pb.ComposeHTTPDNSBoardResponse_TopAppStat{
|
||||
AppId: 0,
|
||||
AppName: appName,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
topDomainStats, err := models.SharedHTTPDNSAccessLogDAO.ListTopDomains(tx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topDomainStats {
|
||||
result.TopDomainStats = append(result.TopDomainStats, &pb.ComposeHTTPDNSBoardResponse_TopDomainStat{
|
||||
DomainName: stat.Domain,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
topNodeStats, err := models.SharedHTTPDNSAccessLogDAO.ListTopNodes(tx, todayFrom, dayTo, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, stat := range topNodeStats {
|
||||
nodeName := ""
|
||||
node, nodeErr := models.SharedHTTPDNSNodeDAO.FindEnabledNode(tx, int64(stat.NodeId))
|
||||
if nodeErr == nil && node != nil {
|
||||
nodeName = node.Name
|
||||
}
|
||||
if len(nodeName) == 0 {
|
||||
continue
|
||||
}
|
||||
result.TopNodeStats = append(result.TopNodeStats, &pb.ComposeHTTPDNSBoardResponse_TopNodeStat{
|
||||
ClusterId: int64(stat.ClusterId),
|
||||
NodeId: int64(stat.NodeId),
|
||||
NodeName: nodeName,
|
||||
CountRequests: stat.CountRequests,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (this *HTTPDNSBoardService) fillNodeValues(tx *dbs.Tx, result *pb.ComposeHTTPDNSBoardResponse) error {
|
||||
cpuValues, err := models.SharedNodeValueDAO.ListValuesForHTTPDNSNodes(tx, nodeconfigs.NodeValueItemCPU, "usage", nodeconfigs.NodeValueRangeMinute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range cpuValues {
|
||||
result.CpuNodeValues = append(result.CpuNodeValues, &pb.NodeValue{
|
||||
ValueJSON: v.Value,
|
||||
CreatedAt: int64(v.CreatedAt),
|
||||
})
|
||||
}
|
||||
|
||||
memoryValues, err := models.SharedNodeValueDAO.ListValuesForHTTPDNSNodes(tx, nodeconfigs.NodeValueItemMemory, "usage", nodeconfigs.NodeValueRangeMinute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range memoryValues {
|
||||
result.MemoryNodeValues = append(result.MemoryNodeValues, &pb.NodeValue{
|
||||
ValueJSON: v.Value,
|
||||
CreatedAt: int64(v.CreatedAt),
|
||||
})
|
||||
}
|
||||
|
||||
loadValues, err := models.SharedNodeValueDAO.ListValuesForHTTPDNSNodes(tx, nodeconfigs.NodeValueItemLoad, "load1m", nodeconfigs.NodeValueRangeMinute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, v := range loadValues {
|
||||
result.LoadNodeValues = append(result.LoadNodeValues, &pb.NodeValue{
|
||||
ValueJSON: v.Value,
|
||||
CreatedAt: int64(v.CreatedAt),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
216
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_cluster.go
Normal file
216
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_cluster.go
Normal file
@@ -0,0 +1,216 @@
|
||||
package httpdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"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"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
)
|
||||
|
||||
// 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, req.AutoRemoteStart, req.AccessLogIsOn, req.TimeZone)
|
||||
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
|
||||
}
|
||||
|
||||
// Compatibility fallback:
|
||||
// If protobuf schemas between edge-admin and edge-api are inconsistent,
|
||||
// these newly-added fields may be lost on the wire. Read gRPC metadata as fallback.
|
||||
if md, ok := metadata.FromIncomingContext(ctx); ok {
|
||||
if values := md.Get("x-httpdns-auto-remote-start"); len(values) > 0 {
|
||||
raw := strings.ToLower(strings.TrimSpace(values[0]))
|
||||
req.AutoRemoteStart = raw == "1" || raw == "true" || raw == "on" || raw == "yes" || raw == "enabled"
|
||||
}
|
||||
if values := md.Get("x-httpdns-access-log-is-on"); len(values) > 0 {
|
||||
raw := strings.ToLower(strings.TrimSpace(values[0]))
|
||||
req.AccessLogIsOn = raw == "1" || raw == "true" || raw == "on" || raw == "yes" || raw == "enabled"
|
||||
}
|
||||
if values := md.Get("x-httpdns-time-zone"); len(values) > 0 {
|
||||
raw := strings.TrimSpace(values[0])
|
||||
if len(raw) > 0 {
|
||||
req.TimeZone = raw
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||
// 先读取旧的 TLS 配置,用于判断是否真正发生了变化
|
||||
var oldTLSJSON string
|
||||
oldCluster, findErr := models.SharedHTTPDNSClusterDAO.FindEnabledCluster(tx, req.ClusterId)
|
||||
if findErr == nil && oldCluster != nil {
|
||||
oldTLSJSON = string(oldCluster.TLSPolicy)
|
||||
}
|
||||
|
||||
err = models.SharedHTTPDNSClusterDAO.UpdateCluster(tx, req.ClusterId, req.Name, req.ServiceDomain, req.DefaultTTL, req.FallbackTimeoutMs, req.InstallDir, req.TlsPolicyJSON, req.IsOn, req.IsDefault, req.AutoRemoteStart, req.AccessLogIsOn, req.TimeZone)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskType := models.HTTPDNSNodeTaskTypeConfigChanged
|
||||
if len(req.TlsPolicyJSON) > 0 && string(req.TlsPolicyJSON) != oldTLSJSON {
|
||||
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
|
||||
}
|
||||
if cluster != nil {
|
||||
_ = grpc.SetHeader(ctx, metadata.Pairs(
|
||||
"x-httpdns-auto-remote-start", fmt.Sprintf("%t", cluster.AutoRemoteStart > 0),
|
||||
"x-httpdns-access-log-is-on", fmt.Sprintf("%t", cluster.AccessLogIsOn > 0),
|
||||
"x-httpdns-time-zone", cluster.TimeZone,
|
||||
))
|
||||
}
|
||||
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)
|
||||
isNode := false
|
||||
if validateErr != nil {
|
||||
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
|
||||
return nil, validateErr
|
||||
}
|
||||
isNode = true
|
||||
}
|
||||
clusters, err := models.SharedHTTPDNSClusterDAO.FindAllEnabledClusters(this.NullTx())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var pbClusters []*pb.HTTPDNSCluster
|
||||
for _, cluster := range clusters {
|
||||
if isNode {
|
||||
// 节点调用时解析证书引用,嵌入实际 PEM 数据
|
||||
pbClusters = append(pbClusters, toPBClusterWithResolvedCerts(this.NullTx(), cluster))
|
||||
} else {
|
||||
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
|
||||
}
|
||||
128
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_domain.go
Normal file
128
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_domain.go
Normal file
@@ -0,0 +1,128 @@
|
||||
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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
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 {
|
||||
app, err := ensureAppAccess(tx, req.AppDbId, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if app == nil {
|
||||
return errors.New("app not found")
|
||||
}
|
||||
|
||||
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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||
domain, app, err := ensureDomainAccess(tx, req.DomainId, userId)
|
||||
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(app.Id), models.HTTPDNSNodeTaskTypeDomainChanged)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return this.Success()
|
||||
}
|
||||
|
||||
func (this *HTTPDNSDomainService) UpdateHTTPDNSDomainStatus(ctx context.Context, req *pb.UpdateHTTPDNSDomainStatusRequest) (*pb.RPCSuccess, error) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||
domain, app, err := ensureDomainAccess(tx, req.DomainId, userId)
|
||||
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(app.Id), models.HTTPDNSNodeTaskTypeDomainChanged)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return this.Success()
|
||||
}
|
||||
|
||||
func (this *HTTPDNSDomainService) ListHTTPDNSDomainsWithAppId(ctx context.Context, req *pb.ListHTTPDNSDomainsWithAppIdRequest) (*pb.ListHTTPDNSDomainsWithAppIdResponse, error) {
|
||||
_, userId, validateErr := this.ValidateAdminAndUser(ctx, true)
|
||||
if validateErr != nil {
|
||||
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
|
||||
return nil, validateErr
|
||||
}
|
||||
} else if userId > 0 {
|
||||
app, err := ensureAppAccess(this.NullTx(), req.AppDbId, userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if app == nil {
|
||||
return &pb.ListHTTPDNSDomainsWithAppIdResponse{}, nil
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
409
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_node.go
Normal file
409
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_node.go
Normal file
@@ -0,0 +1,409 @@
|
||||
package httpdns
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"path/filepath"
|
||||
|
||||
"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/EdgeAPI/internal/setup"
|
||||
rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
"github.com/iwind/TeaGo/logs"
|
||||
stringutil "github.com/iwind/TeaGo/utils/string"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
pbNode := toPBNode(node)
|
||||
|
||||
// 认证信息
|
||||
if pbNode != nil {
|
||||
login, loginErr := models.SharedNodeLoginDAO.FindEnabledNodeLoginWithNodeId(this.NullTx(), nodeconfigs.NodeRoleHTTPDNS, nodeId)
|
||||
if loginErr != nil {
|
||||
return nil, loginErr
|
||||
}
|
||||
if login != nil {
|
||||
pbNode.NodeLogin = &pb.NodeLogin{
|
||||
Id: int64(login.Id),
|
||||
Name: login.Name,
|
||||
Type: login.Type,
|
||||
Params: login.Params,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.FindHTTPDNSNodeResponse{Node: pbNode}, 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()
|
||||
}
|
||||
|
||||
// UpdateHTTPDNSNodeLogin 修改HTTPDNS节点登录信息
|
||||
func (this *HTTPDNSNodeService) UpdateHTTPDNSNodeLogin(ctx context.Context, req *pb.UpdateHTTPDNSNodeLoginRequest) (*pb.RPCSuccess, error) {
|
||||
_, err := this.ValidateAdmin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tx = this.NullTx()
|
||||
|
||||
if req.NodeLogin.Id <= 0 {
|
||||
loginId, createErr := models.SharedNodeLoginDAO.CreateNodeLogin(tx, nodeconfigs.NodeRoleHTTPDNS, req.NodeId, req.NodeLogin.Name, req.NodeLogin.Type, req.NodeLogin.Params)
|
||||
if createErr != nil {
|
||||
return nil, createErr
|
||||
}
|
||||
req.NodeLogin.Id = loginId
|
||||
}
|
||||
|
||||
err = models.SharedNodeLoginDAO.UpdateNodeLogin(tx, req.NodeLogin.Id, req.NodeLogin.Name, req.NodeLogin.Type, req.NodeLogin.Params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return this.Success()
|
||||
}
|
||||
|
||||
// CheckHTTPDNSNodeLatestVersion 检查HTTPDNS节点新版本
|
||||
func (this *HTTPDNSNodeService) CheckHTTPDNSNodeLatestVersion(ctx context.Context, req *pb.CheckHTTPDNSNodeLatestVersionRequest) (*pb.CheckHTTPDNSNodeLatestVersionResponse, error) {
|
||||
_, _, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeAdmin, rpcutils.UserTypeHTTPDNS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deployFiles := installers.SharedDeployManager.LoadHTTPDNSNodeFiles()
|
||||
for _, file := range deployFiles {
|
||||
if file.OS == req.Os && file.Arch == req.Arch && stringutil.VersionCompare(file.Version, req.CurrentVersion) > 0 {
|
||||
return &pb.CheckHTTPDNSNodeLatestVersionResponse{
|
||||
HasNewVersion: true,
|
||||
NewVersion: file.Version,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
return &pb.CheckHTTPDNSNodeLatestVersionResponse{HasNewVersion: false}, nil
|
||||
}
|
||||
|
||||
// DownloadHTTPDNSNodeInstallationFile 下载最新HTTPDNS节点安装文件
|
||||
func (this *HTTPDNSNodeService) DownloadHTTPDNSNodeInstallationFile(ctx context.Context, req *pb.DownloadHTTPDNSNodeInstallationFileRequest) (*pb.DownloadHTTPDNSNodeInstallationFileResponse, error) {
|
||||
nodeId, err := this.ValidateHTTPDNSNode(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查自动升级开关
|
||||
upgradeConfig, _ := setup.LoadUpgradeConfig()
|
||||
if upgradeConfig != nil && !upgradeConfig.AutoUpgrade {
|
||||
return &pb.DownloadHTTPDNSNodeInstallationFileResponse{}, nil
|
||||
}
|
||||
|
||||
var file = installers.SharedDeployManager.FindHTTPDNSNodeFile(req.Os, req.Arch)
|
||||
if file == nil {
|
||||
return &pb.DownloadHTTPDNSNodeInstallationFileResponse{}, nil
|
||||
}
|
||||
|
||||
sum, err := file.Sum()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
data, offset, err := file.Read(req.ChunkOffset)
|
||||
if err != nil && err != io.EOF {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 增加下载速度监控
|
||||
installers.SharedUpgradeLimiter.UpdateNodeBytes(nodeconfigs.NodeRoleHTTPDNS, nodeId, int64(len(data)))
|
||||
|
||||
return &pb.DownloadHTTPDNSNodeInstallationFileResponse{
|
||||
Sum: sum,
|
||||
Offset: offset,
|
||||
ChunkData: data,
|
||||
Version: file.Version,
|
||||
Filename: filepath.Base(file.Path),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CountAllUpgradeHTTPDNSNodesWithClusterId 计算需要升级的HTTPDNS节点数量
|
||||
func (this *HTTPDNSNodeService) CountAllUpgradeHTTPDNSNodesWithClusterId(ctx context.Context, req *pb.CountAllUpgradeHTTPDNSNodesWithClusterIdRequest) (*pb.RPCCountResponse, error) {
|
||||
_, err := this.ValidateAdmin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tx = this.NullTx()
|
||||
deployFiles := installers.SharedDeployManager.LoadHTTPDNSNodeFiles()
|
||||
total := int64(0)
|
||||
for _, deployFile := range deployFiles {
|
||||
count, err := models.SharedHTTPDNSNodeDAO.CountAllLowerVersionNodesWithClusterId(tx, req.ClusterId, deployFile.OS, deployFile.Arch, deployFile.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total += count
|
||||
}
|
||||
return this.SuccessCount(total)
|
||||
}
|
||||
|
||||
// FindAllUpgradeHTTPDNSNodesWithClusterId 列出所有需要升级的HTTPDNS节点
|
||||
func (this *HTTPDNSNodeService) FindAllUpgradeHTTPDNSNodesWithClusterId(ctx context.Context, req *pb.FindAllUpgradeHTTPDNSNodesWithClusterIdRequest) (*pb.FindAllUpgradeHTTPDNSNodesWithClusterIdResponse, error) {
|
||||
_, err := this.ValidateAdmin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tx = this.NullTx()
|
||||
deployFiles := installers.SharedDeployManager.LoadHTTPDNSNodeFiles()
|
||||
var result []*pb.FindAllUpgradeHTTPDNSNodesWithClusterIdResponse_HTTPDNSNodeUpgrade
|
||||
for _, deployFile := range deployFiles {
|
||||
nodes, err := models.SharedHTTPDNSNodeDAO.FindAllLowerVersionNodesWithClusterId(tx, req.ClusterId, deployFile.OS, deployFile.Arch, deployFile.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, node := range nodes {
|
||||
// 解析状态获取当前版本
|
||||
var oldVersion string
|
||||
if len(node.Status) > 0 {
|
||||
var statusMap map[string]interface{}
|
||||
if json.Unmarshal(node.Status, &statusMap) == nil {
|
||||
if v, ok := statusMap["buildVersion"]; ok {
|
||||
oldVersion, _ = v.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pbNode := toPBNode(node)
|
||||
|
||||
// 认证信息
|
||||
login, loginErr := models.SharedNodeLoginDAO.FindEnabledNodeLoginWithNodeId(tx, nodeconfigs.NodeRoleHTTPDNS, int64(node.Id))
|
||||
if loginErr != nil {
|
||||
return nil, loginErr
|
||||
}
|
||||
if login != nil && pbNode != nil {
|
||||
pbNode.NodeLogin = &pb.NodeLogin{
|
||||
Id: int64(login.Id),
|
||||
Name: login.Name,
|
||||
Type: login.Type,
|
||||
Params: login.Params,
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdResponse_HTTPDNSNodeUpgrade{
|
||||
Node: pbNode,
|
||||
Os: deployFile.OS,
|
||||
Arch: deployFile.Arch,
|
||||
OldVersion: oldVersion,
|
||||
NewVersion: deployFile.Version,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &pb.FindAllUpgradeHTTPDNSNodesWithClusterIdResponse{Nodes: result}, nil
|
||||
}
|
||||
|
||||
// UpgradeHTTPDNSNode 升级单个HTTPDNS节点
|
||||
func (this *HTTPDNSNodeService) UpgradeHTTPDNSNode(ctx context.Context, req *pb.UpgradeHTTPDNSNodeRequest) (*pb.RPCSuccess, error) {
|
||||
_, err := this.ValidateAdmin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tx = this.NullTx()
|
||||
|
||||
err = models.SharedHTTPDNSNodeDAO.UpdateNodeIsInstalled(tx, req.NodeId, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 重置安装状态
|
||||
installStatus, err := models.SharedHTTPDNSNodeDAO.FindNodeInstallStatus(tx, req.NodeId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if installStatus == nil {
|
||||
installStatus = &models.NodeInstallStatus{}
|
||||
}
|
||||
installStatus.IsOk = false
|
||||
installStatus.IsFinished = false
|
||||
err = models.SharedHTTPDNSNodeDAO.UpdateNodeInstallStatus(tx, req.NodeId, installStatus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goman.New(func() {
|
||||
installErr := installers.SharedHTTPDNSNodeQueue().InstallNodeProcess(req.NodeId, true)
|
||||
if installErr != nil {
|
||||
logs.Println("[RPC][HTTPDNS]upgrade 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
|
||||
}
|
||||
206
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_rule.go
Normal file
206
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_rule.go
Normal file
@@ -0,0 +1,206 @@
|
||||
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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
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 {
|
||||
domain, app, err := ensureDomainAccess(tx, req.Rule.DomainId, userId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if domain == nil || app == nil {
|
||||
return errors.New("domain not found")
|
||||
}
|
||||
|
||||
rule := &models.HTTPDNSCustomRule{
|
||||
AppId: domain.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, int64(app.Id), 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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
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, app, err := ensureRuleAccess(tx, req.Rule.Id, userId)
|
||||
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(app.Id), models.HTTPDNSNodeTaskTypeRuleChanged)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetAppDbId := int64(app.Id)
|
||||
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) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||
rule, app, err := ensureRuleAccess(tx, req.RuleId, userId)
|
||||
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(app.Id), models.HTTPDNSNodeTaskTypeRuleChanged)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return this.Success()
|
||||
}
|
||||
|
||||
func (this *HTTPDNSRuleService) UpdateHTTPDNSCustomRuleStatus(ctx context.Context, req *pb.UpdateHTTPDNSCustomRuleStatusRequest) (*pb.RPCSuccess, error) {
|
||||
_, userId, err := this.ValidateAdminAndUser(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = this.RunTx(func(tx *dbs.Tx) error {
|
||||
rule, app, err := ensureRuleAccess(tx, req.RuleId, userId)
|
||||
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(app.Id), models.HTTPDNSNodeTaskTypeRuleChanged)
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return this.Success()
|
||||
}
|
||||
|
||||
func (this *HTTPDNSRuleService) ListHTTPDNSCustomRulesWithDomainId(ctx context.Context, req *pb.ListHTTPDNSCustomRulesWithDomainIdRequest) (*pb.ListHTTPDNSCustomRulesWithDomainIdResponse, error) {
|
||||
_, userId, validateErr := this.ValidateAdminAndUser(ctx, true)
|
||||
if validateErr != nil {
|
||||
if _, nodeErr := this.ValidateHTTPDNSNode(ctx); nodeErr != nil {
|
||||
return nil, validateErr
|
||||
}
|
||||
} else if userId > 0 {
|
||||
domain, _, err := ensureDomainAccess(this.NullTx(), req.DomainId, userId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if domain == nil {
|
||||
return &pb.ListHTTPDNSCustomRulesWithDomainIdResponse{}, nil
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
285
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_sandbox.go
Normal file
285
EdgeAPI/internal/rpc/services/httpdns/service_httpdns_sandbox.go
Normal file
@@ -0,0 +1,285 @@
|
||||
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) {
|
||||
_, userId, 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 userId > 0 && app != nil && app.UserId != userId {
|
||||
return nil, errors.New("access denied")
|
||||
}
|
||||
if app == nil || !app.IsOn {
|
||||
return &pb.TestHTTPDNSResolveResponse{
|
||||
Code: "APP_NOT_FOUND_OR_DISABLED",
|
||||
Message: "找不到指定的应用,或该应用已下线",
|
||||
RequestId: "rid-" + rands.HexString(12),
|
||||
}, nil
|
||||
}
|
||||
// 检查集群是否绑定
|
||||
appClusterIds := models.SharedHTTPDNSAppDAO.ReadAppClusterIds(app)
|
||||
if req.ClusterId > 0 {
|
||||
var found bool
|
||||
for _, cid := range appClusterIds {
|
||||
if cid == req.ClusterId {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
return &pb.TestHTTPDNSResolveResponse{
|
||||
Code: "APP_CLUSTER_MISMATCH",
|
||||
Message: "当前应用未绑定到该集群",
|
||||
RequestId: "rid-" + rands.HexString(12),
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
qtype := strings.ToUpper(strings.TrimSpace(req.Qtype))
|
||||
if qtype == "" {
|
||||
qtype = "A"
|
||||
}
|
||||
|
||||
// 获取集群服务域名
|
||||
clusterId := req.ClusterId
|
||||
if clusterId <= 0 && len(appClusterIds) > 0 {
|
||||
clusterId = appClusterIds[0]
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
port := "443"
|
||||
if len(cluster.TLSPolicy) > 0 {
|
||||
var tlsConfig map[string]interface{}
|
||||
if err := json.Unmarshal(cluster.TLSPolicy, &tlsConfig); err == nil {
|
||||
if listenRaw, ok := tlsConfig["listen"]; ok && listenRaw != nil {
|
||||
if data, err := json.Marshal(listenRaw); err == nil {
|
||||
var listenAddresses []map[string]interface{}
|
||||
if err := json.Unmarshal(data, &listenAddresses); err == nil {
|
||||
if len(listenAddresses) > 0 {
|
||||
if portRange, ok := listenAddresses[0]["portRange"].(string); ok && len(portRange) > 0 {
|
||||
port = portRange
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 + ":" + port + "/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))
|
||||
}
|
||||
47
EdgeAPI/internal/rpc/services/httpdns/task_notify.go
Normal file
47
EdgeAPI/internal/rpc/services/httpdns/task_notify.go
Normal file
@@ -0,0 +1,47 @@
|
||||
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
|
||||
}
|
||||
|
||||
clusterIds := models.SharedHTTPDNSAppDAO.ReadAppClusterIds(app)
|
||||
notified := map[int64]bool{}
|
||||
for _, clusterId := range clusterIds {
|
||||
if clusterId <= 0 || notified[clusterId] {
|
||||
continue
|
||||
}
|
||||
notified[clusterId] = true
|
||||
err := notifyHTTPDNSClusterTask(tx, clusterId, 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)
|
||||
}
|
||||
81
EdgeAPI/internal/rpc/services/httpdns/user_auth_helpers.go
Normal file
81
EdgeAPI/internal/rpc/services/httpdns/user_auth_helpers.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package httpdns
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
)
|
||||
|
||||
func ensureAppAccess(tx *dbs.Tx, appDbId int64, userId int64) (*models.HTTPDNSApp, error) {
|
||||
app, err := models.SharedHTTPDNSAppDAO.FindEnabledApp(tx, appDbId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if app == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if userId > 0 && app.UserId != userId {
|
||||
return nil, errors.New("access denied")
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func ensureAppAccessByAppId(tx *dbs.Tx, appId string, userId int64) (*models.HTTPDNSApp, error) {
|
||||
appId = strings.TrimSpace(appId)
|
||||
if len(appId) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
app, err := models.SharedHTTPDNSAppDAO.FindEnabledAppWithAppId(tx, appId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if app == nil {
|
||||
return nil, nil
|
||||
}
|
||||
if userId > 0 && app.UserId != userId {
|
||||
return nil, errors.New("access denied")
|
||||
}
|
||||
return app, nil
|
||||
}
|
||||
|
||||
func ensureDomainAccess(tx *dbs.Tx, domainId int64, userId int64) (*models.HTTPDNSDomain, *models.HTTPDNSApp, error) {
|
||||
domain, err := models.SharedHTTPDNSDomainDAO.FindEnabledDomain(tx, domainId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if domain == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
app, err := ensureAppAccess(tx, int64(domain.AppId), userId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if app == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
return domain, app, nil
|
||||
}
|
||||
|
||||
func ensureRuleAccess(tx *dbs.Tx, ruleId int64, userId int64) (*models.HTTPDNSCustomRule, *models.HTTPDNSApp, error) {
|
||||
rule, err := models.SharedHTTPDNSCustomRuleDAO.FindEnabledRule(tx, ruleId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if rule == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
app, err := ensureAppAccess(tx, int64(rule.AppId), userId)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if app == nil {
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
return rule, app, nil
|
||||
}
|
||||
@@ -6,12 +6,12 @@ package nameservers
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/errors"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/installers"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/setup"
|
||||
rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
@@ -474,7 +474,7 @@ func (this *NSNodeService) FindLatestNSNodeVersion(ctx context.Context, req *pb.
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pb.FindLatestNSNodeVersionResponse{Version: teaconst.DNSNodeVersion}, nil
|
||||
return &pb.FindLatestNSNodeVersionResponse{Version: installers.SharedDeployManager.LatestNSNodeVersion()}, nil
|
||||
}
|
||||
|
||||
// DownloadNSNodeInstallationFile 下载最新DNS节点安装文件
|
||||
@@ -484,6 +484,12 @@ func (this *NSNodeService) DownloadNSNodeInstallationFile(ctx context.Context, r
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查自动升级开关
|
||||
upgradeConfig, _ := setup.LoadUpgradeConfig()
|
||||
if upgradeConfig != nil && !upgradeConfig.AutoUpgrade {
|
||||
return &pb.DownloadNSNodeInstallationFileResponse{}, nil
|
||||
}
|
||||
|
||||
var file = installers.SharedDeployManager.FindNSNodeFile(req.Os, req.Arch)
|
||||
if file == nil {
|
||||
return &pb.DownloadNSNodeInstallationFileResponse{}, nil
|
||||
@@ -738,3 +744,109 @@ func (this *NSNodeService) UpdateNSNodeAPIConfig(ctx context.Context, req *pb.Up
|
||||
|
||||
return this.Success()
|
||||
}
|
||||
|
||||
// FindAllUpgradeNSNodesWithNSClusterId 列出所有需要升级的NS节点
|
||||
func (this *NSNodeService) FindAllUpgradeNSNodesWithNSClusterId(ctx context.Context, req *pb.FindAllUpgradeNSNodesWithNSClusterIdRequest) (*pb.FindAllUpgradeNSNodesWithNSClusterIdResponse, error) {
|
||||
_, err := this.ValidateAdmin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tx = this.NullTx()
|
||||
deployFiles := installers.SharedDeployManager.LoadNSNodeFiles()
|
||||
var result []*pb.FindAllUpgradeNSNodesWithNSClusterIdResponse_NSNodeUpgrade
|
||||
for _, deployFile := range deployFiles {
|
||||
nodes, err := models.SharedNSNodeDAO.FindAllLowerVersionNodesWithClusterId(tx, req.NsClusterId, deployFile.OS, deployFile.Arch, deployFile.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, node := range nodes {
|
||||
// 解析状态获取当前版本
|
||||
var oldVersion string
|
||||
if len(node.Status) > 0 {
|
||||
var statusMap map[string]interface{}
|
||||
if json.Unmarshal(node.Status, &statusMap) == nil {
|
||||
if v, ok := statusMap["buildVersion"]; ok {
|
||||
oldVersion, _ = v.(string)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 安装信息
|
||||
installStatus, installErr := node.DecodeInstallStatus()
|
||||
if installErr != nil {
|
||||
return nil, installErr
|
||||
}
|
||||
pbInstallStatus := &pb.NodeInstallStatus{}
|
||||
if installStatus != nil {
|
||||
pbInstallStatus = &pb.NodeInstallStatus{
|
||||
IsRunning: installStatus.IsRunning,
|
||||
IsFinished: installStatus.IsFinished,
|
||||
IsOk: installStatus.IsOk,
|
||||
Error: installStatus.Error,
|
||||
ErrorCode: installStatus.ErrorCode,
|
||||
UpdatedAt: installStatus.UpdatedAt,
|
||||
}
|
||||
}
|
||||
|
||||
// 认证信息
|
||||
login, loginErr := models.SharedNodeLoginDAO.FindEnabledNodeLoginWithNodeId(tx, nodeconfigs.NodeRoleDNS, int64(node.Id))
|
||||
if loginErr != nil {
|
||||
return nil, loginErr
|
||||
}
|
||||
var pbLogin *pb.NodeLogin
|
||||
if login != nil {
|
||||
pbLogin = &pb.NodeLogin{
|
||||
Id: int64(login.Id),
|
||||
Name: login.Name,
|
||||
Type: login.Type,
|
||||
Params: login.Params,
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, &pb.FindAllUpgradeNSNodesWithNSClusterIdResponse_NSNodeUpgrade{
|
||||
NsNode: &pb.NSNode{
|
||||
Id: int64(node.Id),
|
||||
Name: node.Name,
|
||||
IsOn: node.IsOn,
|
||||
UniqueId: node.UniqueId,
|
||||
IsInstalled: node.IsInstalled,
|
||||
IsUp: node.IsUp,
|
||||
IsActive: node.IsActive,
|
||||
StatusJSON: node.Status,
|
||||
InstallStatus: pbInstallStatus,
|
||||
NodeLogin: pbLogin,
|
||||
},
|
||||
Os: deployFile.OS,
|
||||
Arch: deployFile.Arch,
|
||||
OldVersion: oldVersion,
|
||||
NewVersion: deployFile.Version,
|
||||
})
|
||||
}
|
||||
}
|
||||
return &pb.FindAllUpgradeNSNodesWithNSClusterIdResponse{Nodes: result}, nil
|
||||
}
|
||||
|
||||
// UpgradeNSNode 升级单个NS节点
|
||||
func (this *NSNodeService) UpgradeNSNode(ctx context.Context, req *pb.UpgradeNSNodeRequest) (*pb.RPCSuccess, error) {
|
||||
_, err := this.ValidateAdmin(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var tx = this.NullTx()
|
||||
|
||||
err = models.SharedNSNodeDAO.UpdateNodeIsInstalled(tx, req.NsNodeId, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
goman.New(func() {
|
||||
installErr := installers.SharedNSNodeQueue().InstallNodeProcess(req.NsNodeId, true)
|
||||
if installErr != nil {
|
||||
logs.Println("[RPC]upgrade dns node:" + installErr.Error())
|
||||
}
|
||||
})
|
||||
|
||||
return this.Success()
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models/stats"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/errors"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/installers"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/rpc/tasks"
|
||||
rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
||||
@@ -689,17 +690,20 @@ func (this *AdminService) ComposeAdminDashboard(ctx context.Context, req *pb.Com
|
||||
|
||||
// 边缘节点升级信息
|
||||
{
|
||||
upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{
|
||||
NewVersion: teaconst.NodeVersion,
|
||||
var nodeVersion = installers.SharedDeployManager.LatestNodeVersion()
|
||||
if len(nodeVersion) > 0 {
|
||||
upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{
|
||||
NewVersion: nodeVersion,
|
||||
}
|
||||
this.BeginTag(ctx, "SharedNodeDAO.CountAllLowerVersionNodes")
|
||||
countNodes, err := models.SharedNodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion)
|
||||
this.EndTag(ctx, "SharedNodeDAO.CountAllLowerVersionNodes")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
upgradeInfo.CountNodes = countNodes
|
||||
result.NodeUpgradeInfo = upgradeInfo
|
||||
}
|
||||
this.BeginTag(ctx, "SharedNodeDAO.CountAllLowerVersionNodes")
|
||||
countNodes, err := models.SharedNodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion)
|
||||
this.EndTag(ctx, "SharedNodeDAO.CountAllLowerVersionNodes")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
upgradeInfo.CountNodes = countNodes
|
||||
result.NodeUpgradeInfo = upgradeInfo
|
||||
}
|
||||
|
||||
// API节点升级信息
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/installers"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models/regions"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models/stats"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
@@ -36,17 +37,20 @@ func (this *AdminService) composeAdminDashboardExt(tx *dbs.Tx, ctx context.Conte
|
||||
|
||||
// DNS节点升级信息
|
||||
if isPlus {
|
||||
upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{
|
||||
NewVersion: teaconst.DNSNodeVersion,
|
||||
var dnsNodeVersion = installers.SharedDeployManager.LatestNSNodeVersion()
|
||||
if len(dnsNodeVersion) > 0 {
|
||||
upgradeInfo := &pb.ComposeAdminDashboardResponse_UpgradeInfo{
|
||||
NewVersion: dnsNodeVersion,
|
||||
}
|
||||
this.BeginTag(ctx, "SharedNSNodeDAO.CountAllLowerVersionNodes")
|
||||
countNodes, err := models.SharedNSNodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion)
|
||||
this.EndTag(ctx, "SharedNSNodeDAO.CountAllLowerVersionNodes")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
upgradeInfo.CountNodes = countNodes
|
||||
result.NsNodeUpgradeInfo = upgradeInfo
|
||||
}
|
||||
this.BeginTag(ctx, "SharedNSNodeDAO.CountAllLowerVersionNodes")
|
||||
countNodes, err := models.SharedNSNodeDAO.CountAllLowerVersionNodes(tx, upgradeInfo.NewVersion)
|
||||
this.EndTag(ctx, "SharedNSNodeDAO.CountAllLowerVersionNodes")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
upgradeInfo.CountNodes = countNodes
|
||||
result.NsNodeUpgradeInfo = upgradeInfo
|
||||
}
|
||||
|
||||
// Report节点升级信息
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/installers"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
|
||||
rpcutils "github.com/TeaOSLab/EdgeAPI/internal/rpc/utils"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/setup"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/utils"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
|
||||
@@ -1716,6 +1717,12 @@ func (this *NodeService) DownloadNodeInstallationFile(ctx context.Context, req *
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 检查自动升级开关
|
||||
upgradeConfig, _ := setup.LoadUpgradeConfig()
|
||||
if upgradeConfig != nil && !upgradeConfig.AutoUpgrade {
|
||||
return &pb.DownloadNodeInstallationFileResponse{}, nil
|
||||
}
|
||||
|
||||
var file = installers.SharedDeployManager.FindNodeFile(req.Os, req.Arch)
|
||||
if file == nil {
|
||||
return &pb.DownloadNodeInstallationFileResponse{}, nil
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ type NodeValueService struct {
|
||||
|
||||
// CreateNodeValue 记录数据
|
||||
func (this *NodeValueService) CreateNodeValue(ctx context.Context, req *pb.CreateNodeValueRequest) (*pb.RPCSuccess, error) {
|
||||
role, nodeId, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS, rpcutils.UserTypeUser)
|
||||
role, nodeId, _, err := rpcutils.ValidateRequest(ctx, rpcutils.UserTypeNode, rpcutils.UserTypeDNS, rpcutils.UserTypeUser, rpcutils.UserTypeHTTPDNS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -30,6 +30,8 @@ func (this *NodeValueService) CreateNodeValue(ctx context.Context, req *pb.Creat
|
||||
clusterId, err = models.SharedNodeDAO.FindNodeClusterId(tx, nodeId)
|
||||
case rpcutils.UserTypeDNS:
|
||||
clusterId, err = models.SharedNSNodeDAO.FindNodeClusterId(tx, nodeId)
|
||||
case rpcutils.UserTypeHTTPDNS:
|
||||
clusterId, err = models.SharedHTTPDNSNodeDAO.FindNodeClusterId(tx, nodeId)
|
||||
case rpcutils.UserTypeUser:
|
||||
}
|
||||
if err != nil {
|
||||
|
||||
@@ -71,7 +71,7 @@ func (this *UserService) UpdateUser(ctx context.Context, req *pb.UpdateUserReque
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = models.SharedUserDAO.UpdateUser(tx, req.UserId, req.Username, req.Password, req.Fullname, req.Mobile, req.Tel, req.Email, req.Remark, req.IsOn, req.NodeClusterId, req.BandwidthAlgo)
|
||||
err = models.SharedUserDAO.UpdateUser(tx, req.UserId, req.Username, req.Password, req.Fullname, req.Mobile, req.Tel, req.Email, req.Remark, req.IsOn, req.NodeClusterId, req.BandwidthAlgo, req.HttpdnsClusterIdsJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -242,6 +242,20 @@ func (this *UserService) FindEnabledUser(ctx context.Context, req *pb.FindEnable
|
||||
}
|
||||
}
|
||||
|
||||
// 用户功能列表
|
||||
var pbFeatures []*pb.UserFeature
|
||||
userFeatures, err := models.SharedUserDAO.FindUserFeatures(tx, req.UserId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, f := range userFeatures {
|
||||
pbFeatures = append(pbFeatures, &pb.UserFeature{
|
||||
Name: f.Name,
|
||||
Code: f.Code,
|
||||
Description: f.Description,
|
||||
})
|
||||
}
|
||||
|
||||
return &pb.FindEnabledUserResponse{
|
||||
User: &pb.User{
|
||||
Id: int64(user.Id),
|
||||
@@ -265,6 +279,8 @@ func (this *UserService) FindEnabledUser(ctx context.Context, req *pb.FindEnable
|
||||
BandwidthAlgo: user.BandwidthAlgo,
|
||||
OtpLogin: pbOtpAuth,
|
||||
Lang: user.Lang,
|
||||
Features: pbFeatures,
|
||||
HttpdnsClusterIdsJSON: user.HttpdnsClusterIds,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ package users
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
@@ -142,13 +143,24 @@ func (this *UserService) RegisterUser(ctx context.Context, req *pb.RegisterUserR
|
||||
return errors.New("the username exists already")
|
||||
}
|
||||
|
||||
features := registerConfig.Features
|
||||
|
||||
// 创建用户
|
||||
userId, err := models.SharedUserDAO.CreateUser(tx, req.Username, req.Password, req.Fullname, req.Mobile, "", req.Email, "", req.Source, registerConfig.ClusterId, registerConfig.Features, req.Ip, !registerConfig.RequireVerification)
|
||||
userId, err := models.SharedUserDAO.CreateUser(tx, req.Username, req.Password, req.Fullname, req.Mobile, "", req.Email, "", req.Source, registerConfig.ClusterId, features, req.Ip, !registerConfig.RequireVerification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
createdUserId = userId
|
||||
|
||||
// 自动关联默认 HTTPDNS 集群
|
||||
if registerConfig.HTTPDNSIsOn && len(registerConfig.HTTPDNSDefaultClusterIds) > 0 {
|
||||
httpdnsJSON, _ := json.Marshal(registerConfig.HTTPDNSDefaultClusterIds)
|
||||
err = models.SharedUserDAO.UpdateUserHttpdnsClusterIds(tx, userId, httpdnsJSON)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 发送激活邮件
|
||||
if len(req.Email) > 0 && registerConfig.EmailVerification.IsOn {
|
||||
_, err := models.SharedUserEmailVerificationDAO.CreateVerification(tx, userId, req.Email)
|
||||
|
||||
@@ -16,6 +16,7 @@ const (
|
||||
UserTypeCluster = "cluster"
|
||||
UserTypeStat = "stat"
|
||||
UserTypeDNS = "dns"
|
||||
UserTypeHTTPDNS = "httpdns"
|
||||
UserTypeLog = "log"
|
||||
UserTypeAPI = "api"
|
||||
UserTypeAuthority = "authority"
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
147
EdgeAPI/internal/setup/clickhouse_upgrade.go
Normal file
147
EdgeAPI/internal/setup/clickhouse_upgrade.go
Normal file
@@ -0,0 +1,147 @@
|
||||
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 DEFAULT '' CODEC(ZSTD(3)),
|
||||
request_body String DEFAULT '' CODEC(ZSTD(3)),
|
||||
response_headers String DEFAULT '' CODEC(ZSTD(3)),
|
||||
response_body String DEFAULT '' CODEC(ZSTD(3)),
|
||||
attrs String DEFAULT '' CODEC(ZSTD(3)),
|
||||
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 DEFAULT '' CODEC(ZSTD(3)),
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// v1.5.1: 为 logs_ingest 添加 attrs 列(存储 cache.status 等扩展属性)
|
||||
upgradeSQLs := []string{
|
||||
`ALTER TABLE logs_ingest ADD COLUMN IF NOT EXISTS attrs String DEFAULT '' CODEC(ZSTD(3)) AFTER response_body`,
|
||||
}
|
||||
for _, sql := range upgradeSQLs {
|
||||
stmt := strings.TrimSpace(sql)
|
||||
if len(stmt) == 0 {
|
||||
continue
|
||||
}
|
||||
if err := client.Execute(ctx, stmt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ import (
|
||||
func TestSQLDump_Dump(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -53,7 +53,7 @@ func TestSQLDump_Dump(t *testing.T) {
|
||||
func TestSQLDump_Apply(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -9,7 +9,7 @@ func TestSQLExecutor_Run(t *testing.T) {
|
||||
var executor = NewSQLExecutor(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Prefix: "edge",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_new?charset=utf8mb4&multiStatements=true",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_new?charset=utf8mb4&multiStatements=true",
|
||||
})
|
||||
err := executor.Run(false)
|
||||
if err != nil {
|
||||
@@ -22,7 +22,7 @@ func TestSQLExecutor_checkCluster(t *testing.T) {
|
||||
var executor = NewSQLExecutor(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Prefix: "edge",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_new?charset=utf8mb4&multiStatements=true",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_new?charset=utf8mb4&multiStatements=true",
|
||||
})
|
||||
db, err := dbs.NewInstanceFromConfig(executor.dbConfig)
|
||||
if err != nil {
|
||||
@@ -43,7 +43,7 @@ func TestSQLExecutor_checkMetricItems(t *testing.T) {
|
||||
var executor = NewSQLExecutor(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Prefix: "edge",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_new?charset=utf8mb4&multiStatements=true",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_new?charset=utf8mb4&multiStatements=true",
|
||||
})
|
||||
db, err := dbs.NewInstanceFromConfig(executor.dbConfig)
|
||||
if err != nil {
|
||||
@@ -64,7 +64,7 @@ func TestSQLExecutor_checkNS(t *testing.T) {
|
||||
var executor = NewSQLExecutor(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Prefix: "edge",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_new?charset=utf8mb4&multiStatements=true",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_new?charset=utf8mb4&multiStatements=true",
|
||||
})
|
||||
db, err := dbs.NewInstanceFromConfig(executor.dbConfig)
|
||||
if err != nil {
|
||||
@@ -85,7 +85,7 @@ func TestSQLExecutor_checkClientAgents(t *testing.T) {
|
||||
var executor = NewSQLExecutor(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Prefix: "edge",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&multiStatements=true",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&multiStatements=true",
|
||||
})
|
||||
db, err := dbs.NewInstanceFromConfig(executor.dbConfig)
|
||||
if err != nil {
|
||||
|
||||
@@ -110,6 +110,15 @@ var upgradeFuncs = []*upgradeVersion{
|
||||
{
|
||||
"1.4.4", upgradeV1_4_4,
|
||||
},
|
||||
{
|
||||
"1.4.8", upgradeV1_4_8,
|
||||
},
|
||||
{
|
||||
"1.4.9", upgradeV1_4_9,
|
||||
},
|
||||
{
|
||||
"1.5.1", upgradeV1_5_1,
|
||||
},
|
||||
}
|
||||
|
||||
// UpgradeSQLData 升级SQL数据
|
||||
@@ -269,14 +278,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 +318,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 +351,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 +382,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 +466,7 @@ func upgradeV0_3_2(db *dbs.DB) error {
|
||||
}
|
||||
}
|
||||
|
||||
// 更新服务端口
|
||||
// 鏇存柊鏈嶅姟绔彛
|
||||
var serverDAO = models.NewServerDAO()
|
||||
ones, err := serverDAO.Query(nil).
|
||||
ResultPk().
|
||||
@@ -479,14 +487,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 +505,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 +522,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 +536,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 +553,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 +577,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 +597,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 +629,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 +681,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 +759,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 +1031,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 +1139,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 +1241,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 +1261,79 @@ func upgradeV1_4_4(db *dbs.DB) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1.4.8
|
||||
func upgradeV1_4_8(db *dbs.DB) error {
|
||||
err := createHTTPDNSTables(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// edgeUsers: 增加 httpdnsClusterIds 字段
|
||||
_, alterErr := db.Exec("ALTER TABLE `edgeUsers` ADD COLUMN `httpdnsClusterIds` text DEFAULT NULL")
|
||||
if alterErr != nil {
|
||||
if strings.Contains(alterErr.Error(), "Duplicate column") {
|
||||
return nil
|
||||
}
|
||||
return alterErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 1.4.9
|
||||
func upgradeV1_4_9(db *dbs.DB) error {
|
||||
_, err := db.Exec("ALTER TABLE `edgeHTTPDNSClusters` ALTER COLUMN `installDir` SET DEFAULT '/root/edge-httpdns'")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = db.Exec("ALTER TABLE `edgeHTTPDNSNodes` ALTER COLUMN `installDir` SET DEFAULT '/root/edge-httpdns'")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// v1.5.1
|
||||
func upgradeV1_5_1(db *dbs.DB) error {
|
||||
// 补充 httpdnsClusterIds 字段(1.4.8 升级可能因版本号跳过未执行)
|
||||
_, err := db.Exec("ALTER TABLE `edgeUsers` ADD COLUMN `httpdnsClusterIds` text DEFAULT NULL COMMENT 'HTTPDNS关联集群ID列表'")
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "Duplicate column") {
|
||||
// 字段已存在,忽略
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 确保 HTTPDNS 相关表存在
|
||||
err = createHTTPDNSTables(db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
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 '/root/edge-httpdns',`tlsPolicy` json DEFAULT NULL,`autoRemoteStart` tinyint unsigned DEFAULT '0',`accessLogIsOn` tinyint unsigned DEFAULT '0',`timeZone` varchar(128) NOT NULL DEFAULT '',`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 '/root/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',`clusterIdsJSON` text DEFAULT NULL,`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 `userId` (`userId`),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
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
func TestUpgradeSQLData_v0_5_6(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -31,7 +31,7 @@ func TestUpgradeSQLData_v0_5_6(t *testing.T) {
|
||||
func TestUpgradeSQLData_v1_3_4(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
func TestUpgradeSQLData_v0_5_6(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -29,7 +29,7 @@ func TestUpgradeSQLData_v0_5_6(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_5_8(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -48,7 +48,7 @@ func TestUpgradeSQLData_v0_5_8(t *testing.T) {
|
||||
func TestUpgradeSQLData_v1_2_9(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
func TestUpgradeSQLData(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_new?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_new?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -27,7 +27,7 @@ func TestUpgradeSQLData(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_3_1(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge_new?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge_new?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -46,7 +46,7 @@ func TestUpgradeSQLData_v0_3_1(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_3_2(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -65,7 +65,7 @@ func TestUpgradeSQLData_v0_3_2(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_3_3(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -84,7 +84,7 @@ func TestUpgradeSQLData_v0_3_3(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_3_7(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -103,7 +103,7 @@ func TestUpgradeSQLData_v0_3_7(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_4_0(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -122,7 +122,7 @@ func TestUpgradeSQLData_v0_4_0(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_4_1(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -141,7 +141,7 @@ func TestUpgradeSQLData_v0_4_1(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_4_5(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -160,7 +160,7 @@ func TestUpgradeSQLData_v0_4_5(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_4_7(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -179,7 +179,7 @@ func TestUpgradeSQLData_v0_4_7(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_4_8(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -198,7 +198,7 @@ func TestUpgradeSQLData_v0_4_8(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_4_9(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -217,7 +217,7 @@ func TestUpgradeSQLData_v0_4_9(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_4_11(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -236,7 +236,7 @@ func TestUpgradeSQLData_v0_4_11(t *testing.T) {
|
||||
func TestUpgradeSQLData_v0_5_3(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -255,7 +255,7 @@ func TestUpgradeSQLData_v0_5_3(t *testing.T) {
|
||||
func TestUpgradeSQLData_v1_2_1(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -274,7 +274,7 @@ func TestUpgradeSQLData_v1_2_1(t *testing.T) {
|
||||
func TestUpgradeSQLData_v1_2_10(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
@@ -293,7 +293,7 @@ func TestUpgradeSQLData_v1_2_10(t *testing.T) {
|
||||
func TestUpgradeSQLData_v1_3_2(t *testing.T) {
|
||||
db, err := dbs.NewInstanceFromConfig(&dbs.DBConfig{
|
||||
Driver: "mysql",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s",
|
||||
Prefix: "edge",
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
45
EdgeAPI/internal/setup/upgrade_config.go
Normal file
45
EdgeAPI/internal/setup/upgrade_config.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
)
|
||||
|
||||
var (
|
||||
sharedUpgradeConfig *systemconfigs.UpgradeConfig
|
||||
sharedUpgradeConfigTime time.Time
|
||||
sharedUpgradeConfigMu sync.Mutex
|
||||
)
|
||||
|
||||
const upgradeConfigTTL = 5 * time.Minute
|
||||
|
||||
// LoadUpgradeConfig 读取升级配置(带5分钟内存缓存)
|
||||
func LoadUpgradeConfig() (*systemconfigs.UpgradeConfig, error) {
|
||||
sharedUpgradeConfigMu.Lock()
|
||||
defer sharedUpgradeConfigMu.Unlock()
|
||||
|
||||
if sharedUpgradeConfig != nil && time.Since(sharedUpgradeConfigTime) < upgradeConfigTTL {
|
||||
return sharedUpgradeConfig, nil
|
||||
}
|
||||
|
||||
valueJSON, err := models.SharedSysSettingDAO.ReadSetting(nil, systemconfigs.SettingCodeUpgradeConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
config := systemconfigs.NewUpgradeConfig()
|
||||
if len(valueJSON) > 0 {
|
||||
err = json.Unmarshal(valueJSON, config)
|
||||
if err != nil {
|
||||
return config, nil
|
||||
}
|
||||
}
|
||||
|
||||
sharedUpgradeConfig = config
|
||||
sharedUpgradeConfigTime = time.Now()
|
||||
return config, nil
|
||||
}
|
||||
107
EdgeAPI/internal/tasks/httpdns_node_monitor_task.go
Normal file
107
EdgeAPI/internal/tasks/httpdns_node_monitor_task.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package tasks
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/goman"
|
||||
"github.com/TeaOSLab/EdgeAPI/internal/installers"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
|
||||
"github.com/iwind/TeaGo/dbs"
|
||||
)
|
||||
|
||||
func init() {
|
||||
dbs.OnReadyDone(func() {
|
||||
goman.New(func() {
|
||||
NewHTTPDNSNodeMonitorTask(1 * time.Minute).Start()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type httpdnsNodeStartingTry struct {
|
||||
count int
|
||||
timestamp int64
|
||||
}
|
||||
|
||||
// HTTPDNSNodeMonitorTask monitors HTTPDNS node activity and optionally tries to start offline nodes.
|
||||
type HTTPDNSNodeMonitorTask struct {
|
||||
BaseTask
|
||||
|
||||
ticker *time.Ticker
|
||||
|
||||
recoverMap map[int64]*httpdnsNodeStartingTry // nodeId => retry info
|
||||
}
|
||||
|
||||
func NewHTTPDNSNodeMonitorTask(duration time.Duration) *HTTPDNSNodeMonitorTask {
|
||||
return &HTTPDNSNodeMonitorTask{
|
||||
ticker: time.NewTicker(duration),
|
||||
recoverMap: map[int64]*httpdnsNodeStartingTry{},
|
||||
}
|
||||
}
|
||||
|
||||
func (t *HTTPDNSNodeMonitorTask) Start() {
|
||||
for range t.ticker.C {
|
||||
if err := t.Loop(); err != nil {
|
||||
t.logErr("HTTPDNS_NODE_MONITOR", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *HTTPDNSNodeMonitorTask) Loop() error {
|
||||
// only run on primary api node
|
||||
if !t.IsPrimaryNode() {
|
||||
return nil
|
||||
}
|
||||
|
||||
clusters, err := models.SharedHTTPDNSClusterDAO.FindAllEnabledClusters(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, cluster := range clusters {
|
||||
if cluster == nil || cluster.IsOn == 0 || cluster.AutoRemoteStart == 0 {
|
||||
continue
|
||||
}
|
||||
clusterID := int64(cluster.Id)
|
||||
inactiveNodes, err := models.SharedHTTPDNSNodeDAO.FindAllInactiveNodesWithClusterId(nil, clusterID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(inactiveNodes) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
nodeQueue := installers.NewHTTPDNSNodeQueue()
|
||||
for _, node := range inactiveNodes {
|
||||
nodeID := int64(node.Id)
|
||||
tryInfo, ok := t.recoverMap[nodeID]
|
||||
if !ok {
|
||||
tryInfo = &httpdnsNodeStartingTry{
|
||||
count: 1,
|
||||
timestamp: time.Now().Unix(),
|
||||
}
|
||||
t.recoverMap[nodeID] = tryInfo
|
||||
} else {
|
||||
if tryInfo.count >= 3 {
|
||||
if tryInfo.timestamp+10*60 > time.Now().Unix() {
|
||||
continue
|
||||
}
|
||||
tryInfo.timestamp = time.Now().Unix()
|
||||
tryInfo.count = 0
|
||||
}
|
||||
tryInfo.count++
|
||||
}
|
||||
|
||||
err = nodeQueue.StartNode(nodeID)
|
||||
if err != nil {
|
||||
if !installers.IsGrantError(err) {
|
||||
_ = models.SharedNodeLogDAO.CreateLog(nil, nodeconfigs.NodeRoleHTTPDNS, nodeID, 0, 0, models.LevelError, "NODE", "start node from remote API failed: "+err.Error(), time.Now().Unix(), "", nil)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_ = models.SharedNodeLogDAO.CreateLog(nil, nodeconfigs.NodeRoleHTTPDNS, nodeID, 0, 0, models.LevelSuccess, "NODE", "start node from remote API successfully", time.Now().Unix(), "", nil)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
function verify_components_bundle() {
|
||||
local file_path="$1"
|
||||
if [ ! -f "$file_path" ]; then
|
||||
echo "[error] components.js not found: $file_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local file_size
|
||||
file_size=$(wc -c < "$file_path")
|
||||
if [ "$file_size" -lt 100000 ]; then
|
||||
echo "[error] components.js looks too small ($file_size bytes), generate likely failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! grep -q 'Vue.component("csrf-token"' "$file_path"; then
|
||||
echo "[error] components.js missing csrf-token component, generate likely failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "verify components.js: ok ($file_size bytes)"
|
||||
return 0
|
||||
}
|
||||
|
||||
function build() {
|
||||
ROOT=$(dirname "$0")
|
||||
@@ -23,7 +47,7 @@ function build() {
|
||||
|
||||
# checking environment
|
||||
echo "checking required commands ..."
|
||||
commands=("zip" "unzip" "go" "find" "sed")
|
||||
commands=("zip" "unzip" "go" "find" "sed" "protoc")
|
||||
for cmd in "${commands[@]}"; do
|
||||
if [ "$(which "${cmd}")" ]; then
|
||||
echo "checking ${cmd}: ok"
|
||||
@@ -41,6 +65,19 @@ function build() {
|
||||
ZIP="${NAME}-${OS}-${ARCH}-${TAG}-v${VERSION}.zip"
|
||||
fi
|
||||
|
||||
# regenerate protobuf files to keep rawDesc in sync with .proto
|
||||
echo "regenerating protobuf files ..."
|
||||
EDGE_COMMON_BUILD="$ROOT/../../EdgeCommon/build"
|
||||
if [ -f "$EDGE_COMMON_BUILD/build.sh" ]; then
|
||||
(cd "$EDGE_COMMON_BUILD" && ./build.sh)
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "protobuf generation failed! Fix errors before building."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "EdgeCommon/build/build.sh not found, skipping protobuf generation"
|
||||
fi
|
||||
|
||||
# build edge-api
|
||||
APINodeVersion=$(lookup-version "$ROOT""/../../EdgeAPI/internal/const/const.go")
|
||||
echo "building edge-api v${APINodeVersion} ..."
|
||||
@@ -58,7 +95,7 @@ function build() {
|
||||
|
||||
# generate files
|
||||
echo "generating files ..."
|
||||
env CGO_ENABLED=0 go run -tags $TAG "$ROOT"/../cmd/edge-admin/main.go generate
|
||||
env TEAROOT="$ROOT" CGO_ENABLED=0 go run -tags "$TAG" "$ROOT"/../cmd/edge-admin/main.go generate
|
||||
if [ "$(which uglifyjs)" ]; then
|
||||
echo "compress to component.js ..."
|
||||
uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js
|
||||
@@ -69,14 +106,14 @@ function build() {
|
||||
cp "${JS_ROOT}"/utils.js "${JS_ROOT}"/utils.min.js
|
||||
fi
|
||||
|
||||
verify_components_bundle "${JS_ROOT}/components.js"
|
||||
|
||||
# create dir & copy files
|
||||
echo "copying ..."
|
||||
if [ ! -d "$DIST" ]; then
|
||||
mkdir "$DIST"
|
||||
mkdir "$DIST"/bin
|
||||
mkdir "$DIST"/configs
|
||||
mkdir "$DIST"/logs
|
||||
fi
|
||||
rm -rf "$DIST"
|
||||
mkdir -p "$DIST"/bin
|
||||
mkdir -p "$DIST"/configs
|
||||
mkdir -p "$DIST"/logs
|
||||
|
||||
cp -R "$ROOT"/../web "$DIST"/
|
||||
rm -f "$DIST"/web/tmp/*
|
||||
@@ -109,30 +146,6 @@ function build() {
|
||||
rm -f "$(basename "$EDGE_API_ZIP_FILE")"
|
||||
|
||||
# ensure edge-api package always contains fluent-bit runtime assets/packages
|
||||
FLUENT_ROOT="$ROOT/../../deploy/fluent-bit"
|
||||
FLUENT_DIST="$DIST/edge-api/deploy/fluent-bit"
|
||||
if [ -d "$FLUENT_ROOT" ]; then
|
||||
verify_fluent_bit_package_matrix "$FLUENT_ROOT" "$ARCH" || exit 1
|
||||
rm -rf "$FLUENT_DIST"
|
||||
mkdir -p "$FLUENT_DIST"
|
||||
|
||||
FLUENT_FILES=(
|
||||
"parsers.conf"
|
||||
)
|
||||
for file in "${FLUENT_FILES[@]}"; do
|
||||
if [ -f "$FLUENT_ROOT/$file" ]; then
|
||||
cp "$FLUENT_ROOT/$file" "$FLUENT_DIST/"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "$FLUENT_ROOT/packages" ]; then
|
||||
cp -R "$FLUENT_ROOT/packages" "$FLUENT_DIST/"
|
||||
fi
|
||||
|
||||
rm -f "$FLUENT_DIST/.gitignore"
|
||||
rm -f "$FLUENT_DIST"/logs.db*
|
||||
rm -rf "$FLUENT_DIST/storage"
|
||||
fi
|
||||
|
||||
# 鍒犻櫎 MaxMind 鏁版嵁搴撴枃浠讹紙浣跨敤宓屽叆鐨勬暟鎹簱锛屼笉闇€瑕佸閮ㄦ枃浠讹級
|
||||
find . -name "*.mmdb" -type f -delete
|
||||
@@ -194,39 +207,6 @@ function build() {
|
||||
echo "[done]"
|
||||
}
|
||||
|
||||
function verify_fluent_bit_package_matrix() {
|
||||
FLUENT_ROOT=$1
|
||||
ARCH=$2
|
||||
REQUIRED_FILES=()
|
||||
if [ "$ARCH" = "amd64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-amd64/fluent-bit_4.2.2_amd64.deb"
|
||||
"packages/linux-amd64/fluent-bit-4.2.2-1.x86_64.rpm"
|
||||
)
|
||||
elif [ "$ARCH" = "arm64" ]; then
|
||||
REQUIRED_FILES=(
|
||||
"packages/linux-arm64/fluent-bit_4.2.2_arm64.deb"
|
||||
"packages/linux-arm64/fluent-bit-4.2.2-1.aarch64.rpm"
|
||||
)
|
||||
else
|
||||
echo "[error] unsupported arch for fluent-bit package validation: $ARCH"
|
||||
return 1
|
||||
fi
|
||||
|
||||
MISSING=0
|
||||
for FILE in "${REQUIRED_FILES[@]}"; do
|
||||
if [ ! -f "$FLUENT_ROOT/$FILE" ]; then
|
||||
echo "[error] fluent-bit matrix package missing: $FLUENT_ROOT/$FILE"
|
||||
MISSING=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$MISSING" -ne 0 ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
function lookup-version() {
|
||||
FILE=$1
|
||||
VERSION_DATA=$(cat "$FILE")
|
||||
|
||||
@@ -5,7 +5,7 @@ default:
|
||||
dbs:
|
||||
prod:
|
||||
driver: "mysql"
|
||||
dsn: "root:123456@tcp(127.0.0.1:3306)/db_edge?charset=utf8mb4&timeout=30s"
|
||||
dsn: "root:123456@tcp(127.0.0.1:3308)/db_edge?charset=utf8mb4&timeout=30s"
|
||||
prefix: "edge"
|
||||
models:
|
||||
package: internal/web/models
|
||||
|
||||
@@ -1,22 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
|
||||
JS_ROOT=../web/public/js
|
||||
ROOT=$(cd "$(dirname "$0")" && pwd)
|
||||
JS_ROOT="$ROOT"/../web/public/js
|
||||
|
||||
function verify_components_bundle() {
|
||||
local file_path="$1"
|
||||
if [ ! -f "$file_path" ]; then
|
||||
echo "[error] components.js not found: $file_path"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local file_size
|
||||
file_size=$(wc -c < "$file_path")
|
||||
if [ "$file_size" -lt 100000 ]; then
|
||||
echo "[error] components.js looks too small ($file_size bytes), generate likely failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! grep -q 'Vue.component("csrf-token"' "$file_path"; then
|
||||
echo "[error] components.js missing csrf-token component, generate likely failed"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "verify components.js: ok ($file_size bytes)"
|
||||
return 0
|
||||
}
|
||||
|
||||
echo "generating component.src.js ..."
|
||||
env CGO_ENABLED=0 go run -tags=community ../cmd/edge-admin/main.go generate
|
||||
env TEAROOT="$ROOT" CGO_ENABLED=0 go run -tags=community "$ROOT"/../cmd/edge-admin/main.go generate
|
||||
|
||||
if [ "$(which uglifyjs)" ]; then
|
||||
echo "compress to component.js ..."
|
||||
uglifyjs --compress --mangle -- ${JS_ROOT}/components.src.js > ${JS_ROOT}/components.js
|
||||
uglifyjs --compress --mangle -- "${JS_ROOT}"/components.src.js > "${JS_ROOT}"/components.js
|
||||
|
||||
echo "compress to utils.min.js ..."
|
||||
uglifyjs --compress --mangle -- ${JS_ROOT}/utils.js > ${JS_ROOT}/utils.min.js
|
||||
uglifyjs --compress --mangle -- "${JS_ROOT}"/utils.js > "${JS_ROOT}"/utils.min.js
|
||||
else
|
||||
echo "copy to component.js ..."
|
||||
cp ${JS_ROOT}/components.src.js ${JS_ROOT}/components.js
|
||||
cp "${JS_ROOT}"/components.src.js "${JS_ROOT}"/components.js
|
||||
|
||||
echo "copy to utils.min.js ..."
|
||||
cp ${JS_ROOT}/utils.js ${JS_ROOT}/utils.min.js
|
||||
cp "${JS_ROOT}"/utils.js "${JS_ROOT}"/utils.min.js
|
||||
fi
|
||||
|
||||
echo "ok"
|
||||
verify_components_bundle "${JS_ROOT}/components.js"
|
||||
|
||||
echo "ok"
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -112,10 +113,12 @@ func main() {
|
||||
}
|
||||
})
|
||||
app.On("generate", func() {
|
||||
prepareGenerateRoot()
|
||||
|
||||
err := gen.Generate()
|
||||
if err != nil {
|
||||
fmt.Println("generate failed: " + err.Error())
|
||||
return
|
||||
os.Exit(1)
|
||||
}
|
||||
})
|
||||
app.On("dev", func() {
|
||||
@@ -214,3 +217,32 @@ func main() {
|
||||
adminNode.Run()
|
||||
})
|
||||
}
|
||||
|
||||
func prepareGenerateRoot() {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
candidates := []string{
|
||||
wd,
|
||||
filepath.Clean(filepath.Join(wd, "..")),
|
||||
}
|
||||
|
||||
for _, root := range candidates {
|
||||
componentsDir := filepath.Join(root, "web", "public", "js", "components")
|
||||
stat, statErr := os.Stat(componentsDir)
|
||||
if statErr != nil || !stat.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
// In testing mode, generator reads from Tea.Root + "/../web/...",
|
||||
// so keep Root under build dir to make relative path stable.
|
||||
buildRoot := filepath.Join(root, "build")
|
||||
Tea.UpdateRoot(buildRoot)
|
||||
Tea.SetPublicDir(filepath.Join(root, "web", "public"))
|
||||
Tea.SetViewsDir(filepath.Join(root, "web", "views"))
|
||||
Tea.SetTmpDir(filepath.Join(root, "web", "tmp"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ require (
|
||||
github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000
|
||||
github.com/TeaOSLab/EdgePlus v0.0.0-00010101000000-000000000000
|
||||
github.com/cespare/xxhash/v2 v2.3.0
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/go-sql-driver/mysql v1.7.0
|
||||
github.com/iwind/TeaGo v0.0.0-20240429060313-31a7bc8e9cc9
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4
|
||||
github.com/miekg/dns v1.1.43
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
github.com/OneOfOne/xxhash v1.2.2 h1:KMrpdQIwFcEqXDklaen+P1axHaj9BSKzvpUUfnHldSE=
|
||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||
github.com/TeaOSLab/EdgeAPI v1.3.9 h1:qUSQJsDmxb6XBatu2tKMZ24+6hmJHhBBGx5WXBTbfN4=
|
||||
github.com/TeaOSLab/EdgeAPI v1.3.9/go.mod h1:/XK/wF+DcEeQ0v42VpWC/co5sRUuExbs7wvTNv6b8EE=
|
||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
@@ -7,14 +13,16 @@ github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
|
||||
github.com/frankban/quicktest v1.11.3 h1:8sXhOn0uLys67V8EsXLc6eszDs8VXWxL3iRvebPhedY=
|
||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
@@ -34,8 +42,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/iwind/TeaGo v0.0.0-20240429060313-31a7bc8e9cc9 h1:lqSd+OeAMzPlejoVtsmHJEeQ6/MWXCr+Ws2eX9/Rewg=
|
||||
github.com/iwind/TeaGo v0.0.0-20240429060313-31a7bc8e9cc9/go.mod h1:SfqVbWyIPdVflyA6lMgicZzsoGS8pyeLiTRe8/CIpGI=
|
||||
github.com/iwind/TeaGo v0.0.0-20240508072741-7647e70b7070 h1:0YHZBcuXYbvtQ0XfEdtzr/XybiMrwD8vV1lvgAwzUW4=
|
||||
github.com/iwind/TeaGo v0.0.0-20240508072741-7647e70b7070/go.mod h1:SfqVbWyIPdVflyA6lMgicZzsoGS8pyeLiTRe8/CIpGI=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4 h1:VWGsCqTzObdlbf7UUE3oceIpcEKi4C/YBUszQXk118A=
|
||||
github.com/iwind/gosock v0.0.0-20211103081026-ee4652210ca4/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/iwind/gosock v0.0.0-20220505115348-f88412125a62 h1:HJH6RDheAY156DnIfJSD/bEvqyXzsZuE2gzs8PuUjoo=
|
||||
github.com/iwind/gosock v0.0.0-20220505115348-f88412125a62/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
@@ -43,11 +55,16 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
|
||||
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
|
||||
github.com/miekg/dns v1.1.58 h1:ca2Hdkz+cDg/7eNF6V56jjzuZ4aCAE+DbVkILdQWG/4=
|
||||
github.com/miekg/dns v1.1.58/go.mod h1:Ypv+3b/KadlvW9vJfXOTf300O4UqaHFzFCuHz+rPkBY=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
|
||||
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
|
||||
github.com/mozillazg/go-pinyin v0.18.0 h1:hQompXO23/0ohH8YNjvfsAITnCQImCiR/Fny8EhIeW0=
|
||||
github.com/mozillazg/go-pinyin v0.18.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1 h1:V++EzdbhI4ZV4ev0UTIj0PzhzOcReJFyJaLjtSF55M8=
|
||||
github.com/onsi/ginkgo/v2 v2.17.1/go.mod h1:llBI3WDLL9Z6taip6f33H76YcWtJv+7R3HigUjbIBOs=
|
||||
github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8=
|
||||
@@ -81,6 +98,8 @@ github.com/shirou/gopsutil/v3 v3.22.5 h1:atX36I/IXgFiB81687vSiBI5zrMsxcIBkP9cQMJ
|
||||
github.com/shirou/gopsutil/v3 v3.22.5/go.mod h1:so9G9VzeHt/hsd0YwqprnjHnfARAUktauykSbr+y2gA=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
|
||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -122,24 +141,18 @@ golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ=
|
||||
golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E=
|
||||
golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA=
|
||||
golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY=
|
||||
golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU=
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I=
|
||||
golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
|
||||
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
|
||||
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
|
||||
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
|
||||
@@ -18,6 +18,7 @@ const (
|
||||
AdminModuleCodeServer AdminModuleCode = "server" // 网站
|
||||
AdminModuleCodeNode AdminModuleCode = "node" // 节点
|
||||
AdminModuleCodeDNS AdminModuleCode = "dns" // DNS
|
||||
AdminModuleCodeHttpDNS AdminModuleCode = "httpdns" // HTTPDNS
|
||||
AdminModuleCodeNS AdminModuleCode = "ns" // 域名服务
|
||||
AdminModuleCodeAdmin AdminModuleCode = "admin" // 系统用户
|
||||
AdminModuleCodeUser AdminModuleCode = "user" // 平台用户
|
||||
@@ -106,7 +107,19 @@ func AllowModule(adminId int64, module string) bool {
|
||||
|
||||
list, ok := sharedAdminModuleMapping[adminId]
|
||||
if ok {
|
||||
return list.Allow(module)
|
||||
if list.Allow(module) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Backward compatibility: old admin module sets may not contain "httpdns".
|
||||
// In that case, reuse related CDN module permissions to keep HTTPDNS visible/accessible.
|
||||
if module == AdminModuleCodeHttpDNS {
|
||||
return list.Allow(AdminModuleCodeDNS) ||
|
||||
list.Allow(AdminModuleCodeNode) ||
|
||||
list.Allow(AdminModuleCodeServer)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
return false
|
||||
@@ -226,6 +239,11 @@ func AllModuleMaps(langCode string) []maps.Map {
|
||||
"code": AdminModuleCodeDNS,
|
||||
"url": "/dns",
|
||||
},
|
||||
{
|
||||
"name": "HTTPDNS",
|
||||
"code": AdminModuleCodeHttpDNS,
|
||||
"url": "/httpdns/clusters",
|
||||
},
|
||||
}
|
||||
if teaconst.IsPlus {
|
||||
m = append(m, maps.Map{
|
||||
|
||||
69
EdgeAdmin/internal/configloaders/upgrade_config.go
Normal file
69
EdgeAdmin/internal/configloaders/upgrade_config.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package configloaders
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/TeaOSLab/EdgeAdmin/internal/rpc"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
|
||||
"github.com/TeaOSLab/EdgeCommon/pkg/systemconfigs"
|
||||
)
|
||||
|
||||
const UpgradeSettingName = "upgradeConfig"
|
||||
|
||||
var sharedUpgradeConfig *systemconfigs.UpgradeConfig
|
||||
|
||||
func LoadUpgradeConfig() (*systemconfigs.UpgradeConfig, error) {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
if sharedUpgradeConfig != nil {
|
||||
return sharedUpgradeConfig, nil
|
||||
}
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := rpcClient.SysSettingRPC().ReadSysSetting(rpcClient.Context(0), &pb.ReadSysSettingRequest{
|
||||
Code: UpgradeSettingName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(resp.ValueJSON) == 0 {
|
||||
sharedUpgradeConfig = systemconfigs.NewUpgradeConfig()
|
||||
return sharedUpgradeConfig, nil
|
||||
}
|
||||
|
||||
config := systemconfigs.NewUpgradeConfig()
|
||||
err = json.Unmarshal(resp.ValueJSON, config)
|
||||
if err != nil {
|
||||
sharedUpgradeConfig = systemconfigs.NewUpgradeConfig()
|
||||
return sharedUpgradeConfig, nil
|
||||
}
|
||||
sharedUpgradeConfig = config
|
||||
return sharedUpgradeConfig, nil
|
||||
}
|
||||
|
||||
func UpdateUpgradeConfig(config *systemconfigs.UpgradeConfig) error {
|
||||
locker.Lock()
|
||||
defer locker.Unlock()
|
||||
|
||||
rpcClient, err := rpc.SharedRPC()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
valueJSON, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = rpcClient.SysSettingRPC().UpdateSysSetting(rpcClient.Context(0), &pb.UpdateSysSettingRequest{
|
||||
Code: UpgradeSettingName,
|
||||
ValueJSON: valueJSON,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sharedUpgradeConfig = config
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package teaconst
|
||||
|
||||
const (
|
||||
Version = "1.4.7" //1.3.9
|
||||
Version = "1.5.1" //1.3.9
|
||||
|
||||
APINodeVersion = "1.4.7" //1.3.9
|
||||
APINodeVersion = "1.5.1" //1.3.9
|
||||
|
||||
ProductName = "Edge Admin"
|
||||
ProcessName = "edge-admin"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user