Compare commits

..

66 Commits

Author SHA1 Message Date
刘祥超
b212e124c2 统计看板中合并相同Key的指标数据 2022-04-24 15:24:40 +08:00
刘祥超
ee2781fe65 优化代码 2022-04-23 13:26:15 +08:00
刘祥超
89c1edc9ee 多个API节点时选择一个作为主节点/优化任务相关代码 2022-04-23 12:32:30 +08:00
刘祥超
773f3e1a7e 修复无法使用倒序分表查询日志的问题 2022-04-22 22:23:07 +08:00
刘祥超
e6f6f4dcc2 集群概要信息中增加系统服务状态 2022-04-22 22:04:29 +08:00
刘祥超
174946aa4d 修复集群健康检查无法自动上下线IP地址的Bug 2022-04-22 21:53:38 +08:00
刘祥超
93c594a785 更新SQL 2022-04-22 17:16:19 +08:00
刘祥超
2aea527dff 访问日志策略增加只记录WAF相关访问日志选项 2022-04-22 17:13:59 +08:00
刘祥超
fa30015ca5 增加WAF策略日志设置 2022-04-21 20:00:56 +08:00
刘祥超
d06c8cebf5 IP列表增加名单类型筛选 2022-04-21 15:09:18 +08:00
刘祥超
e4ef2e8253 WAF策略增加日志配置(暂未开放) /修复通过IP可能无法查询到访问日志的Bug 2022-04-21 09:41:04 +08:00
刘祥超
665aa06cc7 优化代码 2022-04-19 19:48:37 +08:00
刘祥超
88dfda82d6 Linux下自动添加端口到Firewalld 2022-04-19 19:35:50 +08:00
刘祥超
243463df83 优化代码 2022-04-19 12:58:00 +08:00
刘祥超
2dc1bfcb55 创建和修改证书的时候检查时间 2022-04-19 11:09:42 +08:00
刘祥超
e6f7cafe7e 当修改集群主域名和DNS子域名时,自动删除旧的相关域名 2022-04-18 21:00:40 +08:00
刘祥超
26fe3558f4 如果服务变更集群前后域名ID一致,则不执行删除操作 2022-04-18 18:35:57 +08:00
刘祥超
ea09ef3d91 服务切换集群后,直接删除老的域名记录 2022-04-18 18:21:29 +08:00
刘祥超
c4ca2521ee 单个服务切换集群时可以选择是否保留配置 2022-04-18 17:18:00 +08:00
刘祥超
db5cdd2957 修复没有日志数据库时无法进行分区查询的Bug 2022-04-17 16:50:38 +08:00
刘祥超
0f483b98ec 删除一处调试信息 2022-04-17 16:24:27 +08:00
刘祥超
0fe51abeb1 访问日志可以使用分表查询 2022-04-17 16:18:53 +08:00
刘祥超
db6b7f57bb 修复看板中统计数据可能不显示的问题 2022-04-16 22:22:05 +08:00
刘祥超
663ead19e4 优化节点排序 2022-04-16 14:45:54 +08:00
刘祥超
bc8adb663a 服务列表增加下行带宽 2022-04-15 12:14:59 +08:00
刘祥超
08b5dd7531 判断节点数量时增加集群状态检查 2022-04-14 22:00:47 +08:00
刘祥超
8177f3d7e4 可以通过groupId=-1查询到未分组的服务 2022-04-14 16:58:21 +08:00
刘祥超
bb5252caf6 edgeServers增加plainServerNames字段 2022-04-14 16:44:00 +08:00
刘祥超
bd4a47d2a1 服务列表中分组信息中增加UserId字段 2022-04-13 15:01:45 +08:00
刘祥超
300be4e936 预估同时间流量的时候刻度改为10分钟 2022-04-10 21:57:26 +08:00
刘祥超
c9dbfb79a7 按天统计流量接口可以预估某日同时间流量 2022-04-10 21:25:24 +08:00
刘祥超
589ae124f1 修复访问日志可能显示重复的问题 2022-04-10 18:21:52 +08:00
刘祥超
d72b440406 修改DNS版本为0.2.2 2022-04-10 16:02:21 +08:00
刘祥超
63e4e7cf9f 优化本地日志 2022-04-10 15:57:36 +08:00
刘祥超
ad416dddec 增加两个数据库相关调试命令 2022-04-08 15:09:33 +08:00
刘祥超
4e18129c6c 更新TeaGo 2022-04-08 14:57:20 +08:00
刘祥超
c03d9f1880 优化数据库相关代码 2022-04-08 14:15:45 +08:00
刘祥超
fe448e6556 增加当日统计接口 2022-04-07 19:46:50 +08:00
刘祥超
adcb33fce4 优化节点列表 2022-04-07 18:31:38 +08:00
刘祥超
e58c3774b6 优化代码 2022-04-05 19:32:35 +08:00
刘祥超
cd7e01c2f0 商业版支持L2节点 2022-04-04 12:08:08 +08:00
刘祥超
d884777a55 增加单元测试 2022-04-02 11:52:42 +08:00
刘祥超
c48aba1c99 更新TeaGo 2022-04-02 11:52:11 +08:00
刘祥超
8cc0faf1d7 集群可以单独设置WebP策略 2022-04-01 16:42:23 +08:00
刘祥超
9851b1a146 集群可以设置WebP策略 2022-04-01 16:18:54 +08:00
刘祥超
36162c603d 看板:只有指标数据不为空时才缓存 2022-04-01 10:00:10 +08:00
刘祥超
cc2782584e 自定义页面支持用户操作 2022-03-31 15:30:04 +08:00
刘祥超
1c1e82ee38 指标统计数据分表 2022-03-30 15:35:42 +08:00
刘祥超
4b3a9cedfa 可以用域名搜索DNS账号 2022-03-30 11:15:38 +08:00
刘祥超
e9497ee65d DNSPod支持国际站 2022-03-30 10:56:22 +08:00
刘祥超
29fae55dc6 IP列表可以使用级别筛选 2022-03-30 09:39:43 +08:00
刘祥超
54fdf3b762 支持DNSPod国际版(需要测试) 2022-03-30 09:12:42 +08:00
刘祥超
f609008984 商业版增加UAM功能 2022-03-29 21:24:31 +08:00
刘祥超
f9b6838dc6 写入指标统计数据时忽略MySQL 1213错误 2022-03-29 20:01:49 +08:00
刘祥超
6d2ecb9af3 对统计指标进行分表 2022-03-28 16:25:16 +08:00
刘祥超
7c4a01137b 数据库管理中列出更多可手动清理的数据表 2022-03-28 11:19:50 +08:00
刘祥超
418c15f79f 数据库管理中列出更多可手动清理的数据表 2022-03-28 11:12:49 +08:00
刘祥超
d13176b8a5 可以自行设定指标数据保留时间 2022-03-28 09:37:28 +08:00
刘祥超
b2752ddd5a 优化看板打开速度 2022-03-27 16:39:54 +08:00
刘祥超
7aea2fd48c 访问日志关键词支持完整的URL/优化Like语句 2022-03-27 12:22:47 +08:00
刘祥超
803f11659c 优化代码 2022-03-26 22:04:16 +08:00
刘祥超
073926ff67 修改用户平台版本为0.3.3 2022-03-26 22:04:10 +08:00
刘祥超
65b4832c94 增加脚本相关表 2022-03-25 14:11:17 +08:00
刘祥超
5f793f1f76 IP找不到不再提示错误 2022-03-24 21:41:14 +08:00
刘祥超
0ce1df25bc 可以修复单页或者全部服务日志 2022-03-23 17:31:26 +08:00
刘祥超
06ad9cc63b 版本号改为0.4.7 2022-03-23 14:45:37 +08:00
158 changed files with 4496 additions and 1474 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock" "github.com/iwind/gosock/pkg/gosock"
"log" "log"
"os" "os"
@@ -97,6 +98,31 @@ func main() {
} }
} }
}) })
app.On("db.stmt.prepare", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{Code: "db.stmt.prepare"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
var isOn = maps.NewMap(reply.Params).GetBool("isOn")
if isOn {
fmt.Println("show statements: on")
} else {
fmt.Println("show statements: off")
}
}
})
app.On("db.stmt.count", func() {
var sock = gosock.NewTmpSock(teaconst.ProcessName)
reply, err := sock.Send(&gosock.Command{Code: "db.stmt.count"})
if err != nil {
fmt.Println("[ERROR]" + err.Error())
} else {
var count = maps.NewMap(reply.Params).GetInt("count")
fmt.Println("prepared statements count: " + types.String(count))
}
})
app.Run(func() { app.Run(func() {
nodes.NewAPINode().Start() nodes.NewAPINode().Start()
}) })

2
go.mod
View File

@@ -12,7 +12,7 @@ require (
github.com/go-acme/lego/v4 v4.5.2 github.com/go-acme/lego/v4 v4.5.2
github.com/go-sql-driver/mysql v1.5.0 github.com/go-sql-driver/mysql v1.5.0
github.com/golang/protobuf v1.5.2 github.com/golang/protobuf v1.5.2
github.com/iwind/TeaGo v0.0.0-20220321132348-7da816422f25 // indirect github.com/iwind/TeaGo v0.0.0-20220408064305-92be81dc2f7c
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3 github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/mozillazg/go-pinyin v0.18.0 github.com/mozillazg/go-pinyin v0.18.0

10
go.sum
View File

@@ -238,12 +238,10 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt
github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI= github.com/infobloxopen/infoblox-go-client v1.1.1/go.mod h1:BXiw7S2b9qJoM8MS40vfgCNB2NLHGusk1DtO16BD9zI=
github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= github.com/iwind/TeaGo v0.0.0-20210411134150-ddf57e240c2f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc=
github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc= github.com/iwind/TeaGo v0.0.0-20220304043459-0dd944a5b475/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/TeaGo v0.0.0-20220321112016-5a2cd71d3151 h1:jksmjwlGC8QMpyHZmzxr7J+3NeMOr9Zy2+yNJxVSIjI= github.com/iwind/TeaGo v0.0.0-20220322141208-22f88d04004d h1:e8fkTKras/RXQWECApM9fKlFWujjYjEClpshkmZmtYg=
github.com/iwind/TeaGo v0.0.0-20220321112016-5a2cd71d3151/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc= github.com/iwind/TeaGo v0.0.0-20220322141208-22f88d04004d/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/TeaGo v0.0.0-20220321131553-fd7b112ba7e7 h1:gdMQZQk/aXfNuKuWCBQhP3byy5Dr8XHMe5+GXdGHcPQ= github.com/iwind/TeaGo v0.0.0-20220408064305-92be81dc2f7c h1:ugjYZ74FJGWlfDKKraNgMyDTeS4vbXHe89JGUVQIJMo=
github.com/iwind/TeaGo v0.0.0-20220321131553-fd7b112ba7e7/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc= github.com/iwind/TeaGo v0.0.0-20220408064305-92be81dc2f7c/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/TeaGo v0.0.0-20220321132348-7da816422f25 h1:UpJ52iq8FEz2OeaXFhW1kuYeqVRUQ/5N+NVHvVuTnvw=
github.com/iwind/TeaGo v0.0.0-20220321132348-7da816422f25/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3 h1:aBSonas7vFcgTj9u96/bWGILGv1ZbUSTLiOzcI1ZT6c= github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3 h1:aBSonas7vFcgTj9u96/bWGILGv1ZbUSTLiOzcI1ZT6c=
github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA= github.com/iwind/gosock v0.0.0-20210722083328-12b2d66abec3/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=

View File

@@ -12,8 +12,9 @@ import (
) )
type BaseStorage struct { type BaseStorage struct {
isOk bool isOk bool
version int version int
firewallOnly bool
} }
func (this *BaseStorage) SetVersion(version int) { func (this *BaseStorage) SetVersion(version int) {
@@ -32,6 +33,10 @@ func (this *BaseStorage) SetOk(isOk bool) {
this.isOk = isOk this.isOk = isOk
} }
func (this *BaseStorage) SetFirewallOnly(firewallOnly bool) {
this.firewallOnly = firewallOnly
}
// Marshal 对日志进行编码 // Marshal 对日志进行编码
func (this *BaseStorage) Marshal(accessLog *pb.HTTPAccessLog) ([]byte, error) { func (this *BaseStorage) Marshal(accessLog *pb.HTTPAccessLog) ([]byte, error) {
return json.Marshal(accessLog) return json.Marshal(accessLog)

View File

@@ -61,6 +61,10 @@ func (this *CommandStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
return err return err
} }
for _, accessLog := range accessLogs { for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog) data, err := this.Marshal(accessLog)
if err != nil { if err != nil {
logs.Error(err) logs.Error(err)

View File

@@ -59,6 +59,10 @@ func (this *ESStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
indexName := this.FormatVariables(this.config.Index) indexName := this.FormatVariables(this.config.Index)
typeName := this.FormatVariables(this.config.MappingType) typeName := this.FormatVariables(this.config.MappingType)
for _, accessLog := range accessLogs { for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
if len(accessLog.RequestId) == 0 { if len(accessLog.RequestId) == 0 {
continue continue
} }

View File

@@ -57,6 +57,9 @@ func (this *FileStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
defer this.writeLocker.Unlock() defer this.writeLocker.Unlock()
for _, accessLog := range accessLogs { for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog) data, err := this.Marshal(accessLog)
if err != nil { if err != nil {
logs.Error(err) logs.Error(err)

View File

@@ -12,6 +12,9 @@ type StorageInterface interface {
// SetVersion 设置版本 // SetVersion 设置版本
SetVersion(version int) SetVersion(version int)
// SetFirewallOnly 设置是否只处理防火墙相关的访问日志
SetFirewallOnly(firewallOnly bool)
IsOk() bool IsOk() bool
SetOk(ok bool) SetOk(ok bool)

View File

@@ -101,6 +101,7 @@ func (this *StorageManager) Loop() error {
} }
storage.SetVersion(types.Int(policy.Version)) storage.SetVersion(types.Int(policy.Version))
storage.SetFirewallOnly(policy.FirewallOnly == 1)
err := storage.Start() err := storage.Start()
if err != nil { if err != nil {
remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "start policy '"+types.String(policyId)+"' failed: "+err.Error()) remotelogs.Error("ACCESS_LOG_STORAGE_MANAGER", "start policy '"+types.String(policyId)+"' failed: "+err.Error())
@@ -116,6 +117,7 @@ func (this *StorageManager) Loop() error {
continue continue
} }
storage.SetVersion(types.Int(policy.Version)) storage.SetVersion(types.Int(policy.Version))
storage.SetFirewallOnly(policy.FirewallOnly == 1)
this.storageMap[policyId] = storage this.storageMap[policyId] = storage
err = storage.Start() err = storage.Start()
if err != nil { if err != nil {

View File

@@ -106,6 +106,9 @@ func (this *SyslogStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
} }
for _, accessLog := range accessLogs { for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog) data, err := this.Marshal(accessLog)
if err != nil { if err != nil {
logs.Error(err) logs.Error(err)

View File

@@ -60,6 +60,9 @@ func (this *TCPStorage) Write(accessLogs []*pb.HTTPAccessLog) error {
defer this.writeLocker.Unlock() defer this.writeLocker.Unlock()
for _, accessLog := range accessLogs { for _, accessLog := range accessLogs {
if this.firewallOnly && accessLog.FirewallPolicyId == 0 {
continue
}
data, err := this.Marshal(accessLog) data, err := this.Marshal(accessLog)
if err != nil { if err != nil {
logs.Error(err) logs.Error(err)

View File

@@ -1,51 +1,108 @@
package apps package apps
import ( import (
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/utils/sizes"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files" "github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs" timeutil "github.com/iwind/TeaGo/utils/time"
"github.com/iwind/TeaGo/utils/time"
"log" "log"
"os"
"runtime"
"strconv"
"strings"
) )
type LogWriter struct { type LogWriter struct {
fileAppender *files.Appender fp *os.File
c chan string
} }
func (this *LogWriter) Init() { func (this *LogWriter) Init() {
// 创建目录 // 创建目录
dir := files.NewFile(Tea.LogDir()) var dir = files.NewFile(Tea.LogDir())
if !dir.Exists() { if !dir.Exists() {
err := dir.Mkdir() err := dir.Mkdir()
if err != nil { if err != nil {
log.Println("[error]" + err.Error()) log.Println("[LOG]create log dir failed: " + err.Error())
} }
} }
logFile := files.NewFile(Tea.LogFile("run.log"))
// 打开要写入的日志文件 // 打开要写入的日志文件
appender, err := logFile.Appender() var logPath = Tea.LogFile("run.log")
fp, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err != nil { if err != nil {
logs.Error(err) log.Println("[LOG]open log file failed: " + err.Error())
} else { } else {
this.fileAppender = appender this.fp = fp
}
this.c = make(chan string, 1024)
// 异步写入文件
var maxFileSize = 2 * sizes.G // 文件最大尺寸,超出此尺寸则清空
if fp != nil {
goman.New(func() {
var totalSize int64 = 0
stat, err := fp.Stat()
if err == nil {
totalSize = stat.Size()
}
for message := range this.c {
totalSize += int64(len(message))
_, err := fp.WriteString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
if err != nil {
log.Println("[LOG]write log failed: " + err.Error())
} else {
// 如果太大则Truncate
if totalSize > maxFileSize {
_ = fp.Truncate(0)
totalSize = 0
}
}
}
})
} }
} }
func (this *LogWriter) Write(message string) { func (this *LogWriter) Write(message string) {
log.Println(message) backgroundEnv, _ := os.LookupEnv("EdgeBackground")
if backgroundEnv != "on" {
// 文件和行号
var file string
var line int
if Tea.IsTesting() {
var callDepth = 3
var ok bool
_, file, line, ok = runtime.Caller(callDepth)
if ok {
file = this.packagePath(file)
}
}
if this.fileAppender != nil { if len(file) > 0 {
_, err := this.fileAppender.AppendString(timeutil.Format("Y/m/d H:i:s ") + message + "\n") log.Println(message + " (" + file + ":" + strconv.Itoa(line) + ")")
if err != nil { } else {
log.Println("[error]" + err.Error()) log.Println(message)
} }
} }
this.c <- message
} }
func (this *LogWriter) Close() { func (this *LogWriter) Close() {
if this.fileAppender != nil { if this.fp != nil {
_ = this.fileAppender.Close() _ = this.fp.Close()
} }
close(this.c)
}
func (this *LogWriter) packagePath(path string) string {
var pieces = strings.Split(path, "/")
if len(pieces) >= 2 {
return strings.Join(pieces[len(pieces)-2:], "/")
}
return path
} }

View File

@@ -1,7 +1,7 @@
package teaconst package teaconst
const ( const (
Version = "0.4.6" Version = "0.4.7"
ProductName = "Edge API" ProductName = "Edge API"
ProcessName = "edge-api" ProcessName = "edge-api"
@@ -18,13 +18,13 @@ const (
// 其他节点版本号,用来检测是否有需要升级的节点 // 其他节点版本号,用来检测是否有需要升级的节点
NodeVersion = "0.4.5" NodeVersion = "0.4.7"
UserNodeVersion = "0.3.2" UserNodeVersion = "0.3.3"
AuthorityNodeVersion = "0.0.2" AuthorityNodeVersion = "0.0.2"
MonitorNodeVersion = "0.0.3" MonitorNodeVersion = "0.0.3"
DNSNodeVersion = "0.2.1" DNSNodeVersion = "0.2.2"
ReportNodeVersion = "0.1.0" ReportNodeVersion = "0.1.0"
// SQLVersion SQL版本号 // SQLVersion SQL版本号
SQLVersion = "5" SQLVersion = "8"
) )

View File

@@ -3,6 +3,7 @@ package db
import ( import (
"database/sql" "database/sql"
"database/sql/driver" "database/sql/driver"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
@@ -42,3 +43,13 @@ func TestDB_Instance(t *testing.T) {
} }
time.Sleep(100 * time.Second) time.Sleep(100 * time.Second)
} }
func TestDB_Reuse(t *testing.T) {
var dao = models.NewVersionDAO()
for i := 0; i < 20_000; i++ {
_, _, err := dao.Query(nil).Attr("version", i).Reuse(true).FindOne()
if err != nil {
t.Fatal(err)
}
}
}

View File

@@ -2,6 +2,7 @@ package accounts
import ( import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/db/models"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
@@ -79,7 +80,7 @@ func (this *UserAccountLogDAO) CountAccountLogs(tx *dbs.Tx, userId int64, accoun
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword)) OR description LIKE :keyword)") query.Where("(userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword)) OR description LIKE :keyword)")
query.Param("keyword", "%"+keyword+"%") query.Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(eventType) > 0 { if len(eventType) > 0 {
query.Attr("eventType", eventType) query.Attr("eventType", eventType)
@@ -98,7 +99,7 @@ func (this *UserAccountLogDAO) ListAccountLogs(tx *dbs.Tx, userId int64, account
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword)) OR description LIKE :keyword)") query.Where("(userId IN (SELECT id FROM " + models.SharedUserDAO.Table + " WHERE state=1 AND (username LIKE :keyword OR fullname LIKE :keyword)) OR description LIKE :keyword)")
query.Param("keyword", "%"+keyword+"%") query.Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(eventType) > 0 { if len(eventType) > 0 {
query.Attr("eventType", eventType) query.Attr("eventType", eventType)

View File

@@ -125,11 +125,11 @@ func (this *ACMETaskDAO) CountAllEnabledACMETasks(tx *dbs.Tx, adminId int64, use
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(domains LIKE :keyword)"). query.Where("(domains LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("domains LIKE :keyword"). query.Where("domains LIKE :keyword").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query.State(ACMETaskStateEnabled). return query.State(ACMETaskStateEnabled).
@@ -155,7 +155,7 @@ func (this *ACMETaskDAO) ListEnabledACMETasks(tx *dbs.Tx, adminId int64, userId
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(domains LIKE :keyword)"). query.Where("(domains LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query. _, err = query.
State(ACMETaskStateEnabled). State(ACMETaskStateEnabled).

View File

@@ -3,6 +3,8 @@ package models
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/TeaOSLab/EdgeAPI/internal/configs"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
@@ -49,7 +51,10 @@ func (this *APINodeDAO) EnableAPINode(tx *dbs.Tx, id int64) error {
Pk(id). Pk(id).
Set("state", APINodeStateEnabled). Set("state", APINodeStateEnabled).
Update() Update()
return err if err != nil {
return err
}
return this.NotifyUpdate(tx, id)
} }
// DisableAPINode 禁用条目 // DisableAPINode 禁用条目
@@ -58,7 +63,10 @@ func (this *APINodeDAO) DisableAPINode(tx *dbs.Tx, id int64) error {
Pk(id). Pk(id).
Set("state", APINodeStateDisabled). Set("state", APINodeStateDisabled).
Update() Update()
return err if err != nil {
return err
}
return this.NotifyUpdate(tx, id)
} }
// FindEnabledAPINode 查找启用中的条目 // FindEnabledAPINode 查找启用中的条目
@@ -149,16 +157,33 @@ func (this *APINodeDAO) CreateAPINode(tx *dbs.Tx, name string, description strin
return return
} }
err = this.NotifyUpdate(tx, types.Int64(op.Id))
if err != nil {
remotelogs.Error("API_NODE_DAO", err.Error())
}
return types.Int64(op.Id), nil return types.Int64(op.Id), nil
} }
// UpdateAPINode 修改API节点 // UpdateAPINode 修改API节点
func (this *APINodeDAO) UpdateAPINode(tx *dbs.Tx, nodeId int64, name string, description string, httpJSON []byte, httpsJSON []byte, restIsOn bool, restHTTPJSON []byte, restHTTPSJSON []byte, accessAddrsJSON []byte, isOn bool) error { func (this *APINodeDAO) UpdateAPINode(tx *dbs.Tx, nodeId int64, name string, description string, httpJSON []byte, httpsJSON []byte, restIsOn bool, restHTTPJSON []byte, restHTTPSJSON []byte, accessAddrsJSON []byte, isOn bool, isPrimary bool) error {
if nodeId <= 0 { if nodeId <= 0 {
return errors.New("invalid nodeId") return errors.New("invalid nodeId")
} }
op := NewAPINodeOperator() // 取消别的Primary
if isPrimary {
err := this.Query(tx).
Neq("id", nodeId).
Attr("isPrimary", true).
Set("isPrimary", false).
UpdateQuickly()
if err != nil {
return err
}
}
var op = NewAPINodeOperator()
op.Id = nodeId op.Id = nodeId
op.Name = name op.Name = name
op.Description = description op.Description = description
@@ -191,8 +216,13 @@ func (this *APINodeDAO) UpdateAPINode(tx *dbs.Tx, nodeId int64, name string, des
op.AccessAddrs = "[]" op.AccessAddrs = "[]"
} }
op.IsPrimary = isPrimary
err := this.Save(tx, op) err := this.Save(tx, op)
return err if err != nil {
return err
}
return this.NotifyUpdate(tx, nodeId)
} }
// FindAllEnabledAPINodes 列出所有可用API节点 // FindAllEnabledAPINodes 列出所有可用API节点
@@ -294,23 +324,6 @@ func (this *APINodeDAO) UpdateAPINodeStatus(tx *dbs.Tx, apiNodeId int64, statusJ
return err return err
} }
// 生成唯一ID
func (this *APINodeDAO) genUniqueId(tx *dbs.Tx) (string, error) {
for {
uniqueId := rands.HexString(32)
ok, err := this.Query(tx).
Attr("uniqueId", uniqueId).
Exist()
if err != nil {
return "", err
}
if ok {
continue
}
return uniqueId, nil
}
}
// CountAllLowerVersionNodes 计算所有节点中低于某个版本的节点数量 // CountAllLowerVersionNodes 计算所有节点中低于某个版本的节点数量
func (this *APINodeDAO) CountAllLowerVersionNodes(tx *dbs.Tx, version string) (int64, error) { func (this *APINodeDAO) CountAllLowerVersionNodes(tx *dbs.Tx, version string) (int64, error) {
return this.Query(tx). return this.Query(tx).
@@ -384,3 +397,114 @@ func (this *APINodeDAO) FindAllEnabledAPIAccessIPs(tx *dbs.Tx, cacheMap *utils.C
return result, nil return result, nil
} }
// CheckAPINodeIsPrimary 检查当前节点是否为Primary节点
func (this *APINodeDAO) CheckAPINodeIsPrimary(tx *dbs.Tx) (bool, error) {
config, err := configs.SharedAPIConfig()
if err != nil {
return false, err
}
isPrimary, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("uniqueId", config.NodeId).
Attr("isPrimary", true).
Exist()
if err != nil {
return false, err
}
if isPrimary {
return true, nil
}
// 检查是否有别的Primary节点
count, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Attr("isPrimary", true).
Count()
if err != nil {
return false, err
}
if count == 0 {
err = this.ResetPrimaryAPINode(tx)
if err != nil {
return false, err
}
return true, nil
}
return false, nil
}
// CheckAPINodeIsPrimaryWithoutErr 检查当前节点是否为Primary节点并忽略错误
func (this *APINodeDAO) CheckAPINodeIsPrimaryWithoutErr() bool {
b, err := this.CheckAPINodeIsPrimary(nil)
return b && err == nil
}
// ResetPrimaryAPINode 重置Primary节点
func (this *APINodeDAO) ResetPrimaryAPINode(tx *dbs.Tx) error {
// 当前是否有Primary节点
apiNode, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
Attr("isPrimary", true).
Find()
if err != nil {
return err
}
if apiNode == nil {
// 选择一个作为Primary
// TODO 将来需要考虑API节点离线的情况
apiNodeId, err := this.Query(tx).
State(APINodeStateEnabled).
Attr("isOn", true).
ResultPk().
FindInt64Col(0)
if err != nil {
return err
}
if apiNodeId > 0 {
err = this.Query(tx).
Pk(apiNodeId).
Set("isPrimary", true).
UpdateQuickly()
if err != nil {
return err
}
}
}
return nil
}
// NotifyUpdate 通知变更
func (this *APINodeDAO) NotifyUpdate(tx *dbs.Tx, apiNodeId int64) error {
// suppress IDE warning
_ = apiNodeId
err := this.ResetPrimaryAPINode(tx)
if err != nil {
return err
}
return nil
}
// 生成唯一ID
func (this *APINodeDAO) genUniqueId(tx *dbs.Tx) (string, error) {
for {
uniqueId := rands.HexString(32)
ok, err := this.Query(tx).
Attr("uniqueId", uniqueId).
Exist()
if err != nil {
return "", err
}
if ok {
continue
}
return uniqueId, nil
}
}

View File

@@ -34,6 +34,16 @@ func TestAPINodeDAO_FindAllEnabledAPIAccessIPs(t *testing.T) {
t.Log(NewAPINodeDAO().FindAllEnabledAPIAccessIPs(nil, cacheMap)) t.Log(NewAPINodeDAO().FindAllEnabledAPIAccessIPs(nil, cacheMap))
} }
func TestAPINodeDAO_CheckAPINodeIsPrimary(t *testing.T) {
var dao = NewAPINodeDAO()
t.Log(dao.CheckAPINodeIsPrimary(nil))
}
func TestAPINodeDAO_ResetPrimaryAPINode(t *testing.T) {
var dao = NewAPINodeDAO()
t.Log(dao.ResetPrimaryAPINode(nil))
}
func BenchmarkAPINodeDAO_New(b *testing.B) { func BenchmarkAPINodeDAO_New(b *testing.B) {
runtime.GOMAXPROCS(1) runtime.GOMAXPROCS(1)
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {

View File

@@ -23,6 +23,7 @@ type APINode struct {
AdminId uint32 `field:"adminId"` // 管理员ID AdminId uint32 `field:"adminId"` // 管理员ID
Weight uint32 `field:"weight"` // 权重 Weight uint32 `field:"weight"` // 权重
Status dbs.JSON `field:"status"` // 运行状态 Status dbs.JSON `field:"status"` // 运行状态
IsPrimary bool `field:"isPrimary"` // 是否为主API节点
} }
type APINodeOperator struct { type APINodeOperator struct {
@@ -45,6 +46,7 @@ type APINodeOperator struct {
AdminId interface{} // 管理员ID AdminId interface{} // 管理员ID
Weight interface{} // 权重 Weight interface{} // 权重
Status interface{} // 运行状态 Status interface{} // 运行状态
IsPrimary interface{} // 是否为主API节点
} }
func NewAPINodeOperator() *APINodeOperator { func NewAPINodeOperator() *APINodeOperator {

View File

@@ -56,6 +56,25 @@ func init() {
}) })
} }
func AllAccessLogDBs() []*dbs.DB {
accessLogLocker.Lock()
defer accessLogLocker.Unlock()
var result = []*dbs.DB{}
for _, db := range accessLogDBMapping {
result = append(result, db)
}
if len(result) == 0 {
db, _ := dbs.Default()
if db != nil {
result = append(result, db)
}
}
return result
}
// 获取获取DAO // 获取获取DAO
func randomHTTPAccessLogDAO() (dao *HTTPAccessLogDAOWrapper) { func randomHTTPAccessLogDAO() (dao *HTTPAccessLogDAOWrapper) {
accessLogLocker.RLock() accessLogLocker.RLock()
@@ -237,7 +256,7 @@ func (this *DBNodeInitializer) loop() error {
} }
if db == nil { if db == nil {
config := &dbs.DBConfig{ var config = &dbs.DBConfig{
Driver: "mysql", Driver: "mysql",
Dsn: dsn, Dsn: dsn,
Prefix: "edge", Prefix: "edge",
@@ -251,7 +270,7 @@ func (this *DBNodeInitializer) loop() error {
// 检查表是否存在 // 检查表是否存在
// httpAccessLog // httpAccessLog
{ {
tableDef, err := SharedHTTPAccessLogManager.FindTable(db, timeutil.Format("Ymd"), true) tableDef, err := SharedHTTPAccessLogManager.FindLastTable(db, timeutil.Format("Ymd"), true)
if err != nil { if err != nil {
remotelogs.Error("DB_NODE", "create first table in database node failed: "+err.Error()) remotelogs.Error("DB_NODE", "create first table in database node failed: "+err.Error())

View File

@@ -107,22 +107,32 @@ func (this *DNSProviderDAO) UpdateDNSProvider(tx *dbs.Tx, dnsProviderId int64, n
} }
// CountAllEnabledDNSProviders 计算服务商数量 // CountAllEnabledDNSProviders 计算服务商数量
func (this *DNSProviderDAO) CountAllEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, keyword string) (int64, error) { func (this *DNSProviderDAO) CountAllEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, keyword string, domain string) (int64, error) {
var query = dbutils.NewQuery(tx, this, adminId, userId) var query = dbutils.NewQuery(tx, this, adminId, userId)
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword)"). query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(domain) > 0 {
query.Where("id IN (SELECT providerId FROM " + SharedDNSDomainDAO.Table + " WHERE state=1 AND name=:domain)")
query.Param("domain", domain)
}
return query.State(DNSProviderStateEnabled). return query.State(DNSProviderStateEnabled).
Count() Count()
} }
// ListEnabledDNSProviders 列出单页服务商 // ListEnabledDNSProviders 列出单页服务商
func (this *DNSProviderDAO) ListEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, keyword string, offset int64, size int64) (result []*DNSProvider, err error) { func (this *DNSProviderDAO) ListEnabledDNSProviders(tx *dbs.Tx, adminId int64, userId int64, keyword string, domain string, offset int64, size int64) (result []*DNSProvider, err error) {
var query = dbutils.NewQuery(tx, this, adminId, userId) var query = dbutils.NewQuery(tx, this, adminId, userId)
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword)"). query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
}
if len(domain) > 0 {
query.Where("id IN (SELECT providerId FROM " + SharedDNSDomainDAO.Table + " WHERE state=1 AND name=:domain)")
query.Param("domain", domain)
} }
_, err = query. _, err = query.
State(DNSProviderStateEnabled). State(DNSProviderStateEnabled).

View File

@@ -12,10 +12,11 @@ import (
type DNSTaskType = string type DNSTaskType = string
const ( const (
DNSTaskTypeClusterChange DNSTaskType = "clusterChange" DNSTaskTypeClusterChange DNSTaskType = "clusterChange"
DNSTaskTypeNodeChange DNSTaskType = "nodeChange" DNSTaskTypeClusterRemoveDomain DNSTaskType = "clusterRemoveDomain" // 从集群中移除域名
DNSTaskTypeServerChange DNSTaskType = "serverChange" DNSTaskTypeNodeChange DNSTaskType = "nodeChange"
DNSTaskTypeDomainChange DNSTaskType = "domainChange" DNSTaskTypeServerChange DNSTaskType = "serverChange"
DNSTaskTypeDomainChange DNSTaskType = "domainChange"
) )
type DNSTaskDAO dbs.DAO type DNSTaskDAO dbs.DAO
@@ -40,20 +41,21 @@ func init() {
} }
// CreateDNSTask 生成任务 // CreateDNSTask 生成任务
func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int64, nodeId int64, domainId int64, taskType string) error { func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int64, nodeId int64, domainId int64, recordName string, taskType string) error {
if clusterId <= 0 && serverId <= 0 && nodeId <= 0 && domainId <= 0 { if clusterId <= 0 && serverId <= 0 && nodeId <= 0 && domainId <= 0 {
return nil return nil
} }
err := this.Query(tx).InsertOrUpdateQuickly(maps.Map{ err := this.Query(tx).InsertOrUpdateQuickly(maps.Map{
"clusterId": clusterId, "clusterId": clusterId,
"serverId": serverId, "serverId": serverId,
"nodeId": nodeId, "nodeId": nodeId,
"domainId": domainId, "domainId": domainId,
"updatedAt": time.Now().Unix(), "recordName": recordName,
"type": taskType, "updatedAt": time.Now().Unix(),
"isDone": false, "type": taskType,
"isOk": false, "isDone": false,
"error": "", "isOk": false,
"error": "",
}, maps.Map{ }, maps.Map{
"updatedAt": time.Now().Unix(), "updatedAt": time.Now().Unix(),
"isDone": false, "isDone": false,
@@ -63,24 +65,29 @@ func (this *DNSTaskDAO) CreateDNSTask(tx *dbs.Tx, clusterId int64, serverId int6
return err return err
} }
// CreateClusterTask 生成集群任务 // CreateClusterTask 生成集群变更任务
func (this *DNSTaskDAO) CreateClusterTask(tx *dbs.Tx, clusterId int64, taskType DNSTaskType) error { func (this *DNSTaskDAO) CreateClusterTask(tx *dbs.Tx, clusterId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, clusterId, 0, 0, 0, taskType) return this.CreateDNSTask(tx, clusterId, 0, 0, 0, "", taskType)
}
// CreateClusterRemoveTask 生成集群删除域名任务
func (this *DNSTaskDAO) CreateClusterRemoveTask(tx *dbs.Tx, clusterId int64, domainId int64, recordName string) error {
return this.CreateDNSTask(tx, clusterId, 0, 0, domainId, recordName, DNSTaskTypeClusterRemoveDomain)
} }
// CreateNodeTask 生成节点任务 // CreateNodeTask 生成节点任务
func (this *DNSTaskDAO) CreateNodeTask(tx *dbs.Tx, nodeId int64, taskType DNSTaskType) error { func (this *DNSTaskDAO) CreateNodeTask(tx *dbs.Tx, nodeId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, 0, 0, nodeId, 0, taskType) return this.CreateDNSTask(tx, 0, 0, nodeId, 0, "", taskType)
} }
// CreateServerTask 生成服务任务 // CreateServerTask 生成服务任务
func (this *DNSTaskDAO) CreateServerTask(tx *dbs.Tx, serverId int64, taskType DNSTaskType) error { func (this *DNSTaskDAO) CreateServerTask(tx *dbs.Tx, clusterId int64, serverId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, 0, serverId, 0, 0, taskType) return this.CreateDNSTask(tx, clusterId, serverId, 0, 0, "", taskType)
} }
// CreateDomainTask 生成域名更新任务 // CreateDomainTask 生成域名更新任务
func (this *DNSTaskDAO) CreateDomainTask(tx *dbs.Tx, domainId int64, taskType DNSTaskType) error { func (this *DNSTaskDAO) CreateDomainTask(tx *dbs.Tx, domainId int64, taskType DNSTaskType) error {
return this.CreateDNSTask(tx, 0, 0, 0, domainId, taskType) return this.CreateDNSTask(tx, 0, 0, 0, domainId, "", taskType)
} }
// FindAllDoingTasks 查找所有正在执行的任务 // FindAllDoingTasks 查找所有正在执行的任务
@@ -101,6 +108,7 @@ func (this *DNSTaskDAO) FindAllDoingOrErrorTasks(tx *dbs.Tx, nodeClusterId int64
} }
_, err = query. _, err = query.
Where("(isDone=0 OR (isDone=1 AND isOk=0))"). Where("(isDone=0 OR (isDone=1 AND isOk=0))").
Asc("updatedAt").
AscPk(). AscPk().
Slice(&result). Slice(&result).
FindAll() FindAll()

View File

@@ -1,30 +1,32 @@
package dns package dns
// DNS更新任务 // DNSTask DNS更新任务
type DNSTask struct { type DNSTask struct {
Id uint64 `field:"id"` // ID Id uint64 `field:"id"` // ID
ClusterId uint32 `field:"clusterId"` // 集群ID ClusterId uint32 `field:"clusterId"` // 集群ID
ServerId uint32 `field:"serverId"` // 服务ID ServerId uint32 `field:"serverId"` // 服务ID
NodeId uint32 `field:"nodeId"` // 节点ID NodeId uint32 `field:"nodeId"` // 节点ID
DomainId uint32 `field:"domainId"` // 域名ID DomainId uint32 `field:"domainId"` // 域名ID
Type string `field:"type"` // 任务类型 RecordName string `field:"recordName"` // 记录名
UpdatedAt uint64 `field:"updatedAt"` // 更新时间 Type string `field:"type"` // 任务类型
IsDone bool `field:"isDone"` // 是否已完成 UpdatedAt uint64 `field:"updatedAt"` // 更新时间
IsOk bool `field:"isOk"` // 是否成 IsDone bool `field:"isDone"` // 是否已完
Error string `field:"error"` // 错误信息 IsOk bool `field:"isOk"` // 是否成功
Error string `field:"error"` // 错误信息
} }
type DNSTaskOperator struct { type DNSTaskOperator struct {
Id interface{} // ID Id interface{} // ID
ClusterId interface{} // 集群ID ClusterId interface{} // 集群ID
ServerId interface{} // 服务ID ServerId interface{} // 服务ID
NodeId interface{} // 节点ID NodeId interface{} // 节点ID
DomainId interface{} // 域名ID DomainId interface{} // 域名ID
Type interface{} // 任务类型 RecordName interface{} // 记录名
UpdatedAt interface{} // 更新时间 Type interface{} // 任务类型
IsDone interface{} // 是否已完成 UpdatedAt interface{} // 更新时间
IsOk interface{} // 是否成 IsDone interface{} // 是否已完
Error interface{} // 错误信息 IsOk interface{} // 是否成功
Error interface{} // 错误信息
} }
func NewDNSTaskOperator() *DNSTaskOperator { func NewDNSTaskOperator() *DNSTaskOperator {

View File

@@ -153,7 +153,7 @@ func CheckClusterDNS(tx *dbs.Tx, cluster *models.NodeCluster) (issues []*pb.DNSI
} }
// 检查IP地址 // 检查IP地址
ipAddr, _, err := models.SharedNodeIPAddressDAO.FindFirstNodeAccessIPAddress(tx, nodeId, nodeconfigs.NodeRoleNode) ipAddr, _, err := models.SharedNodeIPAddressDAO.FindFirstNodeAccessIPAddress(tx, nodeId, true, nodeconfigs.NodeRoleNode)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -3,6 +3,7 @@ package models
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman" "github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs" "github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
@@ -22,6 +23,7 @@ import (
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"net" "net"
"net/http" "net/http"
"net/url"
"regexp" "regexp"
"sort" "sort"
"strings" "strings"
@@ -181,7 +183,7 @@ Loop:
// CreateHTTPAccessLog 写入单条访问日志 // CreateHTTPAccessLog 写入单条访问日志
func (this *HTTPAccessLogDAO) CreateHTTPAccessLog(tx *dbs.Tx, dao *HTTPAccessLogDAO, accessLog *pb.HTTPAccessLog) error { func (this *HTTPAccessLogDAO) CreateHTTPAccessLog(tx *dbs.Tx, dao *HTTPAccessLogDAO, accessLog *pb.HTTPAccessLog) error {
var day = timeutil.FormatTime("Ymd", accessLog.Timestamp) var day = timeutil.FormatTime("Ymd", accessLog.Timestamp)
tableDef, err := SharedHTTPAccessLogManager.FindTable(dao.Instance, day, true) tableDef, err := SharedHTTPAccessLogManager.FindLastTable(dao.Instance, day, true)
if err != nil { if err != nil {
return err return err
} }
@@ -248,7 +250,9 @@ func (this *HTTPAccessLogDAO) CreateHTTPAccessLog(tx *dbs.Tx, dao *HTTPAccessLog
} }
// ListAccessLogs 读取往前的 单页访问日志 // ListAccessLogs 读取往前的 单页访问日志
func (this *HTTPAccessLogDAO) ListAccessLogs(tx *dbs.Tx, lastRequestId string, func (this *HTTPAccessLogDAO) ListAccessLogs(tx *dbs.Tx,
partition int32,
lastRequestId string,
size int64, size int64,
day string, day string,
hourFrom string, hourFrom string,
@@ -275,18 +279,19 @@ func (this *HTTPAccessLogDAO) ListAccessLogs(tx *dbs.Tx, lastRequestId string,
size = 1000 size = 1000
} }
result, nextLastRequestId, err = this.listAccessLogs(tx, lastRequestId, size, day, hourFrom, hourTo, clusterId, nodeId, serverId, reverse, hasError, firewallPolicyId, firewallRuleGroupId, firewallRuleSetId, hasFirewallPolicy, userId, keyword, ip, domain) result, nextLastRequestId, err = this.listAccessLogs(tx, partition, lastRequestId, size, day, hourFrom, hourTo, clusterId, nodeId, serverId, reverse, hasError, firewallPolicyId, firewallRuleGroupId, firewallRuleSetId, hasFirewallPolicy, userId, keyword, ip, domain)
if err != nil || int64(len(result)) < size { if err != nil || int64(len(result)) < size {
return return
} }
moreResult, _, _ := this.listAccessLogs(tx, nextLastRequestId, 1, day, hourFrom, hourTo, clusterId, nodeId, serverId, reverse, hasError, firewallPolicyId, firewallRuleGroupId, firewallRuleSetId, hasFirewallPolicy, userId, keyword, ip, domain) moreResult, _, _ := this.listAccessLogs(tx, partition, nextLastRequestId, 1, day, hourFrom, hourTo, clusterId, nodeId, serverId, reverse, hasError, firewallPolicyId, firewallRuleGroupId, firewallRuleSetId, hasFirewallPolicy, userId, keyword, ip, domain)
hasMore = len(moreResult) > 0 hasMore = len(moreResult) > 0
return return
} }
// 读取往前的单页访问日志 // 读取往前的单页访问日志
func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx, func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
partition int32,
lastRequestId string, lastRequestId string,
size int64, size int64,
day string, day string,
@@ -309,7 +314,7 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
return nil, lastRequestId, nil return nil, lastRequestId, nil
} }
serverIds := []int64{} var serverIds = []int64{}
if userId > 0 { if userId > 0 {
serverIds, err = SharedServerDAO.FindAllEnabledServerIdsWithUserId(tx, userId) serverIds, err = SharedServerDAO.FindAllEnabledServerIdsWithUserId(tx, userId)
if err != nil { if err != nil {
@@ -321,7 +326,7 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
} }
accessLogLocker.RLock() accessLogLocker.RLock()
daoList := []*HTTPAccessLogDAOWrapper{} var daoList = []*HTTPAccessLogDAOWrapper{}
for _, daoWrapper := range httpAccessLogDAOMapping { for _, daoWrapper := range httpAccessLogDAOMapping {
daoList = append(daoList, daoWrapper) daoList = append(daoList, daoWrapper)
} }
@@ -339,7 +344,7 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
if clusterId > 0 { if clusterId > 0 {
nodeIds, err = SharedNodeDAO.FindAllEnabledNodeIdsWithClusterId(tx, clusterId) nodeIds, err = SharedNodeDAO.FindAllEnabledNodeIdsWithClusterId(tx, clusterId)
if err != nil { if err != nil {
remotelogs.Error("DBNODE", err.Error()) remotelogs.Error("DB_NODE", err.Error())
return return
} }
sort.Slice(nodeIds, func(i, j int) bool { sort.Slice(nodeIds, func(i, j int) bool {
@@ -349,32 +354,56 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
// 准备查询 // 准备查询
var tableQueries = []*accessLogTableQuery{} var tableQueries = []*accessLogTableQuery{}
var maxTableName = ""
for _, daoWrapper := range daoList { for _, daoWrapper := range daoList {
var instance = daoWrapper.DAO.Instance var instance = daoWrapper.DAO.Instance
tableDefs, err := SharedHTTPAccessLogManager.FindTables(instance, day) def, err := SharedHTTPAccessLogManager.FindPartitionTable(instance, day, partition)
if err != nil { if err != nil {
return nil, "", err return nil, "", err
} }
for _, def := range tableDefs { if !def.Exists {
tableQueries = append(tableQueries, &accessLogTableQuery{ continue
daoWrapper: daoWrapper,
name: def.Name,
hasRemoteAddrField: def.HasRemoteAddr,
hasDomainField: def.HasDomain,
})
} }
if len(maxTableName) == 0 || def.Name > maxTableName {
maxTableName = def.Name
}
tableQueries = append(tableQueries, &accessLogTableQuery{
daoWrapper: daoWrapper,
name: def.Name,
hasRemoteAddrField: def.HasRemoteAddr,
hasDomainField: def.HasDomain,
})
}
// 检查各个分表是否一致
if partition < 0 {
var newTableQueries = []*accessLogTableQuery{}
for _, tableQuery := range tableQueries {
if tableQuery.name != maxTableName {
continue
}
newTableQueries = append(newTableQueries, tableQuery)
}
tableQueries = newTableQueries
}
if len(tableQueries) == 0 {
return nil, "", nil
} }
var locker = sync.Mutex{} var locker = sync.Mutex{}
var statusPrefixReg = regexp.MustCompile(`status:\s*(\d{3})\b`) var statusPrefixReg = regexp.MustCompile(`status:\s*(\d{3})\b`)
var statusRangeReg = regexp.MustCompile(`status:\s*(\d{3})-(\d{3})\b`) var statusRangeReg = regexp.MustCompile(`status:\s*(\d{3})-(\d{3})\b`)
var urlReg = regexp.MustCompile(`^(http|https)://`)
var count = len(tableQueries) var count = len(tableQueries)
var wg = &sync.WaitGroup{} var wg = &sync.WaitGroup{}
wg.Add(count) wg.Add(count)
for _, tableQuery := range tableQueries { for _, tableQuery := range tableQueries {
go func(tableQuery *accessLogTableQuery) { go func(tableQuery *accessLogTableQuery, keyword string) {
defer wg.Done() defer wg.Done()
var dao = tableQuery.daoWrapper.DAO var dao = tableQuery.daoWrapper.DAO
@@ -462,27 +491,41 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
} }
if len(keyword) > 0 { if len(keyword) > 0 {
// remoteAddr var isSpecialKeyword = false
if tableQuery.hasRemoteAddrField && net.ParseIP(keyword) != nil {
if tableQuery.hasRemoteAddrField && net.ParseIP(keyword) != nil { // ip
isSpecialKeyword = true
query.Attr("remoteAddr", keyword) query.Attr("remoteAddr", keyword)
} else if tableQuery.hasRemoteAddrField && regexp.MustCompile(`^ip:.+`).MatchString(keyword) { } else if tableQuery.hasRemoteAddrField && regexp.MustCompile(`^ip:.+`).MatchString(keyword) { // ip:x.x.x.x
isSpecialKeyword = true
keyword = keyword[3:] keyword = keyword[3:]
pieces := strings.SplitN(keyword, ",", 2) pieces := strings.SplitN(keyword, ",", 2)
if len(pieces) == 1 || len(pieces[1]) == 0 { if len(pieces) == 1 || len(pieces[1]) == 0 || pieces[0] == pieces[1] {
query.Attr("remoteAddr", pieces[0]) query.Attr("remoteAddr", pieces[0])
} else { } else {
query.Between("INET_ATON(remoteAddr)", utils.IP2Long(pieces[0]), utils.IP2Long(pieces[1])) query.Between("INET_ATON(remoteAddr)", utils.IP2Long(pieces[0]), utils.IP2Long(pieces[1]))
} }
} else if statusRangeReg.MatchString(keyword) { } else if statusRangeReg.MatchString(keyword) { // status:200-400
isSpecialKeyword = true
var matches = statusRangeReg.FindStringSubmatch(keyword) var matches = statusRangeReg.FindStringSubmatch(keyword)
query.Between("status", types.Int(matches[1]), types.Int(matches[2])) query.Between("status", types.Int(matches[1]), types.Int(matches[2]))
// TODO 处理剩余的关键词 // TODO 处理剩余的关键词
} else if statusPrefixReg.MatchString(keyword) { } else if statusPrefixReg.MatchString(keyword) { // status:200
isSpecialKeyword = true
var matches = statusPrefixReg.FindStringSubmatch(keyword) var matches = statusPrefixReg.FindStringSubmatch(keyword)
query.Attr("status", matches[1]) query.Attr("status", matches[1])
// TODO 处理剩余的关键词 // TODO 处理剩余的关键词
} else { } else if urlReg.MatchString(keyword) { // https://xxx/yyy
u, err := url.Parse(keyword)
if err == nil {
isSpecialKeyword = true
query.Attr("domain", u.Host)
query.Where("JSON_EXTRACT(content, '$.requestURI') LIKE :keyword").
Param("keyword", dbutils.QuoteLikePrefix("\""+u.RequestURI()))
}
}
if !isSpecialKeyword {
if regexp.MustCompile(`^ip:.+`).MatchString(keyword) { if regexp.MustCompile(`^ip:.+`).MatchString(keyword) {
keyword = keyword[3:] keyword = keyword[3:]
} }
@@ -530,7 +573,7 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
} }
query.Where("("+where+")"). query.Where("("+where+")").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
if useOriginKeyword { if useOriginKeyword {
query.Param("originKeyword", keyword) query.Param("originKeyword", keyword)
} }
@@ -574,17 +617,17 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
Limit(size). Limit(size).
FindAll() FindAll()
if err != nil { if err != nil {
logs.Println("[DB_NODE]" + err.Error()) remotelogs.Println("DB_NODE", err.Error())
return return
} }
locker.Lock() locker.Lock()
for _, one := range ones { for _, one := range ones {
accessLog := one.(*HTTPAccessLog) var accessLog = one.(*HTTPAccessLog)
result = append(result, accessLog) result = append(result, accessLog)
} }
locker.Unlock() locker.Unlock()
}(tableQuery) }(tableQuery, keyword)
} }
wg.Wait() wg.Wait()

View File

@@ -1,6 +1,7 @@
package models package models
import ( import (
"encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
@@ -53,7 +54,7 @@ func TestHTTPAccessLogDAO_ListAccessLogs(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
accessLogs, requestId, hasMore, err := SharedHTTPAccessLogDAO.ListAccessLogs(tx, "", 10, timeutil.Format("Ymd"), "", "", 0, 0, 0, false, false, 0, 0, 0, false, 0, "", "", "") accessLogs, requestId, hasMore, err := SharedHTTPAccessLogDAO.ListAccessLogs(tx, -1, "", 10, timeutil.Format("Ymd"), "", "", 0, 0, 0, false, false, 0, 0, 0, false, 0, "", "", "")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -80,7 +81,7 @@ func TestHTTPAccessLogDAO_ListAccessLogs_Page(t *testing.T) {
times := 0 // 防止循环次数太多 times := 0 // 防止循环次数太多
for { for {
before := time.Now() before := time.Now()
accessLogs, requestId, hasMore, err := SharedHTTPAccessLogDAO.ListAccessLogs(tx, lastRequestId, 2, timeutil.Format("Ymd"), "", "", 0, 0, 0, false, false, 0, 0, 0, false, 0, "", "", "") accessLogs, requestId, hasMore, err := SharedHTTPAccessLogDAO.ListAccessLogs(tx, -1, lastRequestId, 2, timeutil.Format("Ymd"), "", "", 0, 0, 0, false, false, 0, 0, 0, false, 0, "", "", "")
cost := time.Since(before).Seconds() cost := time.Since(before).Seconds()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -111,7 +112,7 @@ func TestHTTPAccessLogDAO_ListAccessLogs_Reverse(t *testing.T) {
} }
before := time.Now() before := time.Now()
accessLogs, requestId, hasMore, err := SharedHTTPAccessLogDAO.ListAccessLogs(tx, "16023261176446590001000000000000003500000004", 2, timeutil.Format("Ymd"), "", "", 0, 0, 0, true, false, 0, 0, 0, false, 0, "", "", "") accessLogs, requestId, hasMore, err := SharedHTTPAccessLogDAO.ListAccessLogs(tx, -1, "16023261176446590001000000000000003500000004", 2, timeutil.Format("Ymd"), "", "", 0, 0, 0, true, false, 0, 0, 0, false, 0, "", "", "")
cost := time.Since(before).Seconds() cost := time.Since(before).Seconds()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -136,7 +137,7 @@ func TestHTTPAccessLogDAO_ListAccessLogs_Page_NotExists(t *testing.T) {
times := 0 // 防止循环次数太多 times := 0 // 防止循环次数太多
for { for {
before := time.Now() before := time.Now()
accessLogs, requestId, hasMore, err := SharedHTTPAccessLogDAO.ListAccessLogs(tx, lastRequestId, 2, timeutil.Format("Ymd", time.Now().AddDate(0, 0, 1)), "", "", 0, 0, 0, false, false, 0, 0, 0, false, 0, "", "", "") accessLogs, requestId, hasMore, err := SharedHTTPAccessLogDAO.ListAccessLogs(tx, -1, lastRequestId, 2, timeutil.Format("Ymd", time.Now().AddDate(0, 0, 1)), "", "", 0, 0, 0, false, false, 0, 0, 0, false, 0, "", "", "")
cost := time.Since(before).Seconds() cost := time.Since(before).Seconds()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -157,3 +158,13 @@ func TestHTTPAccessLogDAO_ListAccessLogs_Page_NotExists(t *testing.T) {
} }
} }
} }
func BenchmarkHTTPAccessLogDAO_JSONEncode(b *testing.B) {
var accessLog = &pb.HTTPAccessLog{
RequestPath: "/hello/world",
}
for i := 0; i < b.N; i++ {
_, _ = json.Marshal(accessLog)
}
}

View File

@@ -15,6 +15,7 @@ import (
) )
// 访问日志的两个表格形式 // 访问日志的两个表格形式
// 括号位置需要固定,会用来读取日期和分区
var accessLogTableMainReg = regexp.MustCompile(`_(\d{8})$`) var accessLogTableMainReg = regexp.MustCompile(`_(\d{8})$`)
var accessLogTablePartialReg = regexp.MustCompile(`_(\d{8})_(\d{4})$`) var accessLogTablePartialReg = regexp.MustCompile(`_(\d{8})_(\d{4})$`)
@@ -38,7 +39,7 @@ func (this *HTTPAccessLogManager) FindTableNames(db *dbs.DB, day string) ([]stri
// 需要防止用户设置了表名自动小写 // 需要防止用户设置了表名自动小写
for _, prefix := range []string{"edgeHTTPAccessLogs_" + day + "%", "edgehttpaccesslogs_" + day + "%"} { for _, prefix := range []string{"edgeHTTPAccessLogs_" + day + "%", "edgehttpaccesslogs_" + day + "%"} {
ones, columnNames, err := db.FindOnes(`SHOW TABLES LIKE '` + prefix + `'`) ones, columnNames, err := db.FindPreparedOnes(`SHOW TABLES LIKE '` + prefix + `'`)
if err != nil { if err != nil {
return nil, errors.New("query table names error: " + err.Error()) return nil, errors.New("query table names error: " + err.Error())
} }
@@ -77,9 +78,15 @@ func (this *HTTPAccessLogManager) FindTables(db *dbs.DB, day string) ([]*httpAcc
var results = []*httpAccessLogDefinition{} var results = []*httpAccessLogDefinition{}
var tableNames = []string{} var tableNames = []string{}
config, err := db.Config()
if err != nil {
return nil, err
}
var cachePrefix = config.Dsn
// 需要防止用户设置了表名自动小写 // 需要防止用户设置了表名自动小写
for _, prefix := range []string{"edgeHTTPAccessLogs_" + day + "%", "edgehttpaccesslogs_" + day + "%"} { for _, prefix := range []string{"edgeHTTPAccessLogs_" + day + "%", "edgehttpaccesslogs_" + day + "%"} {
ones, columnNames, err := db.FindOnes(`SHOW TABLES LIKE '` + prefix + `'`) ones, columnNames, err := db.FindPreparedOnes(`SHOW TABLES LIKE '` + prefix + `'`)
if err != nil { if err != nil {
return nil, errors.New("query table names error: " + err.Error()) return nil, errors.New("query table names error: " + err.Error())
} }
@@ -96,17 +103,32 @@ func (this *HTTPAccessLogManager) FindTables(db *dbs.DB, day string) ([]*httpAcc
if accessLogTableMainReg.MatchString(tableName) { if accessLogTableMainReg.MatchString(tableName) {
tableNames = append(tableNames, tableName) tableNames = append(tableNames, tableName)
hasRemoteAddrField, hasDomainField, err := this.checkTableFields(db, tableName) // 查找已有的表格信息避免SHOW FIELDS
if err != nil { var tableDay = tableName[strings.LastIndex(tableName, "_")+1:]
return nil, err var cacheKey = this.composeTableCacheKey(cachePrefix, tableDay)
} this.locker.Lock()
currentTableDef, ok := this.currentTableMapping[cacheKey]
this.locker.Unlock()
if ok {
results = append(results, &httpAccessLogDefinition{
Name: tableName,
HasRemoteAddr: currentTableDef.HasRemoteAddr,
HasDomain: currentTableDef.HasDomain,
Exists: true,
})
} else {
hasRemoteAddrField, hasDomainField, err := this.checkTableFields(db, tableName)
if err != nil {
return nil, err
}
results = append(results, &httpAccessLogDefinition{ results = append(results, &httpAccessLogDefinition{
Name: tableName, Name: tableName,
HasRemoteAddr: hasRemoteAddrField, HasRemoteAddr: hasRemoteAddrField,
HasDomain: hasDomainField, HasDomain: hasDomainField,
Exists: true, Exists: true,
}) })
}
} else if accessLogTablePartialReg.MatchString(tableName) { } else if accessLogTablePartialReg.MatchString(tableName) {
tableNames = append(tableNames, tableName) tableNames = append(tableNames, tableName)
@@ -128,11 +150,55 @@ func (this *HTTPAccessLogManager) FindTables(db *dbs.DB, day string) ([]*httpAcc
return results, nil return results, nil
} }
// FindTable 根据日期获取表名 func (this *HTTPAccessLogManager) FindPartitionTable(db *dbs.DB, day string, partition int32) (*httpAccessLogDefinition, error) {
var tableNames []string
if partition < 0 {
tableList, err := this.FindTables(db, day)
if err != nil {
return nil, err
}
if len(tableList) > 0 {
return tableList[len(tableList)-1], nil
}
return &httpAccessLogDefinition{
Name: "",
HasRemoteAddr: false,
HasDomain: false,
Exists: false,
}, nil
} else if partition == 0 {
tableNames = []string{"edgeHTTPAccessLogs_" + day, "edgehttpaccesslogs_" + day}
} else {
tableNames = []string{"edgeHTTPAccessLogs_" + day + "_" + fmt.Sprintf("%04d", partition), "edgehttpaccesslogs_" + day + "_" + fmt.Sprintf("%04d", partition)}
}
for _, tableName := range tableNames {
hasRemoteField, hasDomainField, err := this.checkTableFields(db, tableName)
if err != nil {
continue
}
return &httpAccessLogDefinition{
Name: tableName,
HasRemoteAddr: hasRemoteField,
HasDomain: hasDomainField,
Exists: true,
}, nil
}
return &httpAccessLogDefinition{
Name: "",
HasRemoteAddr: false,
HasDomain: false,
Exists: false,
}, nil
}
// FindLastTable 根据日期获取上一个可以使用的表名
// 表名组成 // 表名组成
// - PREFIX_DAY // - PREFIX_DAY
// - PREFIX_DAY_0001 // - PREFIX_DAY_0001
func (this *HTTPAccessLogManager) FindTable(db *dbs.DB, day string, force bool) (*httpAccessLogDefinition, error) { func (this *HTTPAccessLogManager) FindLastTable(db *dbs.DB, day string, force bool) (*httpAccessLogDefinition, error) {
this.locker.Lock() this.locker.Lock()
defer this.locker.Unlock() defer this.locker.Unlock()
@@ -197,6 +263,60 @@ func (this *HTTPAccessLogManager) ResetTable(db *dbs.DB, day string) {
delete(this.currentTableMapping, this.composeTableCacheKey(config.Dsn, day)) delete(this.currentTableMapping, this.composeTableCacheKey(config.Dsn, day))
} }
// TablePartition 从表名中获取分区
func (this *HTTPAccessLogManager) TablePartition(tableName string) (partition int32) {
if accessLogTablePartialReg.MatchString(tableName) {
return types.Int32(accessLogTablePartialReg.FindStringSubmatch(tableName)[2])
}
return 0
}
// FindLatestPartition 读取最后一个分区
func (this *HTTPAccessLogManager) FindLatestPartition(day string) (int32, error) {
var dbList = AllAccessLogDBs()
if len(dbList) == 0 {
return 0, errors.New("no valid database")
}
var partitions = []int32{}
var locker sync.Mutex
var wg = sync.WaitGroup{}
wg.Add(len(dbList))
var lastErr error
for _, db := range dbList {
go func(db *dbs.DB) {
defer wg.Done()
names, err := this.FindTableNames(db, day)
if err != nil {
lastErr = err
}
for _, name := range names {
var partition = this.TablePartition(name)
locker.Lock()
if !lists.Contains(partitions, partition) {
partitions = append(partitions, partition)
}
locker.Unlock()
}
}(db)
}
wg.Wait()
if lastErr != nil {
return 0, lastErr
}
if len(partitions) == 0 {
return 0, nil
}
return partitions[len(partitions)-1], nil
}
// 查找某个表格 // 查找某个表格
func (this *HTTPAccessLogManager) findTableWithoutCache(db *dbs.DB, day string, force bool) (*httpAccessLogDefinition, error) { func (this *HTTPAccessLogManager) findTableWithoutCache(db *dbs.DB, day string, force bool) (*httpAccessLogDefinition, error) {
tableNames, err := this.FindTableNames(db, day) tableNames, err := this.FindTableNames(db, day)
@@ -296,7 +416,7 @@ func (this *HTTPAccessLogManager) findTableWithoutCache(db *dbs.DB, day string,
// TODO 考虑缓存检查结果 // TODO 考虑缓存检查结果
func (this *HTTPAccessLogManager) checkTableFields(db *dbs.DB, tableName string) (hasRemoteAddrField bool, hasDomainField bool, err error) { func (this *HTTPAccessLogManager) checkTableFields(db *dbs.DB, tableName string) (hasRemoteAddrField bool, hasDomainField bool, err error) {
fields, _, err := db.FindOnes("SHOW FIELDS FROM " + tableName) fields, _, err := db.FindPreparedOnes("SHOW FIELDS FROM " + tableName)
if err != nil { if err != nil {
return false, false, err return false, false, err
} }

View File

@@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
"testing" "testing"
"time" "time"
) )
@@ -30,6 +31,9 @@ func TestNewHTTPAccessLogManager(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() {
_ = db.Close()
}()
var manager = models.SharedHTTPAccessLogManager var manager = models.SharedHTTPAccessLogManager
err = manager.CreateTable(db, "accessLog_1") err = manager.CreateTable(db, "accessLog_1")
@@ -58,6 +62,9 @@ func TestHTTPAccessLogManager_FindTableNames(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() {
_ = db.Close()
}()
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
var before = time.Now() var before = time.Now()
@@ -74,7 +81,6 @@ func TestHTTPAccessLogManager_FindTableNames(t *testing.T) {
} }
} }
func TestHTTPAccessLogManager_FindTables(t *testing.T) { func TestHTTPAccessLogManager_FindTables(t *testing.T) {
var config = &dbs.DBConfig{ var config = &dbs.DBConfig{
Driver: "mysql", Driver: "mysql",
@@ -95,6 +101,9 @@ func TestHTTPAccessLogManager_FindTables(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() {
_ = db.Close()
}()
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
var before = time.Now() var before = time.Now()
@@ -111,7 +120,7 @@ func TestHTTPAccessLogManager_FindTables(t *testing.T) {
} }
} }
func TestHTTPAccessLogManager_FindTable(t *testing.T) { func TestHTTPAccessLogManager_FindLastTable(t *testing.T) {
var config = &dbs.DBConfig{ var config = &dbs.DBConfig{
Driver: "mysql", 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:3306)/db_edge_log?charset=utf8mb4&timeout=30s",
@@ -131,10 +140,13 @@ func TestHTTPAccessLogManager_FindTable(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
defer func() {
_ = db.Close()
}()
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
var before = time.Now() var before = time.Now()
tableDef, err := models.SharedHTTPAccessLogManager.FindTable(db, "20220306", false) tableDef, err := models.SharedHTTPAccessLogManager.FindLastTable(db, "20220306", false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -146,3 +158,32 @@ func TestHTTPAccessLogManager_FindTable(t *testing.T) {
t.Log(time.Since(before).Seconds()*1000, "ms") t.Log(time.Since(before).Seconds()*1000, "ms")
} }
} }
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",
Prefix: "edge",
Connections: struct {
Pool int `yaml:"pool"`
Max int `yaml:"max"`
Life string `yaml:"life"`
LifeDuration time.Duration `yaml:",omitempty"`
}{},
Models: struct {
Package string `yaml:"package"`
}{},
}
db, err := dbs.NewInstanceFromConfig(config)
if err != nil {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
t.Log(models.SharedHTTPAccessLogManager.FindPartitionTable(db, timeutil.Format("Ymd", time.Now().AddDate(0, 0, -1)), -1))
t.Log(models.SharedHTTPAccessLogManager.FindPartitionTable(db, timeutil.Format("Ymd", time.Now().AddDate(0, 0, -1)), 0))
t.Log(models.SharedHTTPAccessLogManager.FindPartitionTable(db, timeutil.Format("Ymd", time.Now().AddDate(0, 0, -1)), 1))
}

View File

@@ -1,13 +1,10 @@
package models package models
import ( import (
"bytes"
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
) )
const ( const (
@@ -109,7 +106,7 @@ func (this *HTTPAccessLogPolicyDAO) FindAllEnabledAndOnPolicies(tx *dbs.Tx) (res
} }
// CreatePolicy 创建策略 // CreatePolicy 创建策略
func (this *HTTPAccessLogPolicyDAO) CreatePolicy(tx *dbs.Tx, name string, policyType string, optionsJSON []byte, condsJSON []byte, isPublic bool) (policyId int64, err error) { func (this *HTTPAccessLogPolicyDAO) CreatePolicy(tx *dbs.Tx, name string, policyType string, optionsJSON []byte, condsJSON []byte, isPublic bool, firewallOnly bool) (policyId int64, err error) {
var op = NewHTTPAccessLogPolicyOperator() var op = NewHTTPAccessLogPolicyOperator()
op.Name = name op.Name = name
op.Type = policyType op.Type = policyType
@@ -121,12 +118,13 @@ func (this *HTTPAccessLogPolicyDAO) CreatePolicy(tx *dbs.Tx, name string, policy
} }
op.IsPublic = isPublic op.IsPublic = isPublic
op.IsOn = true op.IsOn = true
op.FirewallOnly = firewallOnly
op.State = HTTPAccessLogPolicyStateEnabled op.State = HTTPAccessLogPolicyStateEnabled
return this.SaveInt64(tx, op) return this.SaveInt64(tx, op)
} }
// UpdatePolicy 修改策略 // UpdatePolicy 修改策略
func (this *HTTPAccessLogPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, name string, optionsJSON []byte, condsJSON []byte, isPublic bool, isOn bool) error { func (this *HTTPAccessLogPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, name string, optionsJSON []byte, condsJSON []byte, isPublic bool, firewallOnly bool, isOn bool) error {
if policyId <= 0 { if policyId <= 0 {
return errors.New("invalid policyId") return errors.New("invalid policyId")
} }
@@ -140,7 +138,6 @@ func (this *HTTPAccessLogPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, nam
if oldOne == nil { if oldOne == nil {
return nil return nil
} }
var oldPolicy = oldOne.(*HTTPAccessLogPolicy)
var op = NewHTTPAccessLogPolicyOperator() var op = NewHTTPAccessLogPolicyOperator()
op.Id = policyId op.Id = policyId
@@ -156,22 +153,11 @@ func (this *HTTPAccessLogPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, nam
op.Conds = "{}" op.Conds = "{}"
} }
// 版本号 // 版本号总是加1
if len(oldPolicy.Options) == 0 || len(optionsJSON) == 0 { op.Version = dbs.SQL("version+1")
op.Version = dbs.SQL("version+1")
} else {
var m1 = maps.Map{}
_ = json.Unmarshal(oldPolicy.Options, &m1)
var m2 = maps.Map{}
_ = json.Unmarshal(optionsJSON, &m2)
if bytes.Compare(m1.AsJSON(), m2.AsJSON()) != 0 {
op.Version = dbs.SQL("version+1")
}
}
op.IsPublic = isPublic op.IsPublic = isPublic
op.FirewallOnly = firewallOnly
op.IsOn = isOn op.IsOn = isOn
return this.Save(tx, op) return this.Save(tx, op)
} }

View File

@@ -4,35 +4,37 @@ import "github.com/iwind/TeaGo/dbs"
// HTTPAccessLogPolicy 访问日志策略 // HTTPAccessLogPolicy 访问日志策略
type HTTPAccessLogPolicy struct { type HTTPAccessLogPolicy struct {
Id uint32 `field:"id"` // ID Id uint32 `field:"id"` // ID
TemplateId uint32 `field:"templateId"` // 模版ID TemplateId uint32 `field:"templateId"` // 模版ID
AdminId uint32 `field:"adminId"` // 管理员ID AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID UserId uint32 `field:"userId"` // 用户ID
State uint8 `field:"state"` // 状态 State uint8 `field:"state"` // 状态
CreatedAt uint64 `field:"createdAt"` // 创建时间 CreatedAt uint64 `field:"createdAt"` // 创建时间
Name string `field:"name"` // 名称 Name string `field:"name"` // 名称
IsOn bool `field:"isOn"` // 是否启用 IsOn bool `field:"isOn"` // 是否启用
Type string `field:"type"` // 存储类型 Type string `field:"type"` // 存储类型
Options dbs.JSON `field:"options"` // 存储选项 Options dbs.JSON `field:"options"` // 存储选项
Conds dbs.JSON `field:"conds"` // 请求条件 Conds dbs.JSON `field:"conds"` // 请求条件
IsPublic bool `field:"isPublic"` // 是否为公用 IsPublic bool `field:"isPublic"` // 是否为公用
Version uint32 `field:"version"` // 版本号 FirewallOnly uint8 `field:"firewallOnly"` // 是否只记录防火墙相关
Version uint32 `field:"version"` // 版本号
} }
type HTTPAccessLogPolicyOperator struct { type HTTPAccessLogPolicyOperator struct {
Id interface{} // ID Id interface{} // ID
TemplateId interface{} // 模版ID TemplateId interface{} // 模版ID
AdminId interface{} // 管理员ID AdminId interface{} // 管理员ID
UserId interface{} // 用户ID UserId interface{} // 用户ID
State interface{} // 状态 State interface{} // 状态
CreatedAt interface{} // 创建时间 CreatedAt interface{} // 创建时间
Name interface{} // 名称 Name interface{} // 名称
IsOn interface{} // 是否启用 IsOn interface{} // 是否启用
Type interface{} // 存储类型 Type interface{} // 存储类型
Options interface{} // 存储选项 Options interface{} // 存储选项
Conds interface{} // 请求条件 Conds interface{} // 请求条件
IsPublic interface{} // 是否为公用 IsPublic interface{} // 是否为公用
Version interface{} // 版本号 FirewallOnly interface{} // 是否只记录防火墙相关
Version interface{} // 版本号
} }
func NewHTTPAccessLogPolicyOperator() *HTTPAccessLogPolicyOperator { func NewHTTPAccessLogPolicyOperator() *HTTPAccessLogPolicyOperator {

View File

@@ -2,6 +2,7 @@ package models
import ( import (
"encoding/json" "encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
@@ -318,7 +319,7 @@ func (this *HTTPCachePolicyDAO) CountAllEnabledHTTPCachePolicies(tx *dbs.Tx, clu
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword)"). query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(storageType) > 0 { if len(storageType) > 0 {
query.Attr("type", storageType) query.Attr("type", storageType)
@@ -336,7 +337,7 @@ func (this *HTTPCachePolicyDAO) ListEnabledHTTPCachePolicies(tx *dbs.Tx, cluster
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword)"). query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(storageType) > 0 { if len(storageType) > 0 {
query.Attr("type", storageType) query.Attr("type", storageType)

View File

@@ -2,6 +2,7 @@ package models
import ( import (
"encoding/json" "encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
@@ -259,7 +260,18 @@ func (this *HTTPFirewallPolicyDAO) UpdateFirewallPolicyInbound(tx *dbs.Tx, polic
} }
// UpdateFirewallPolicy 修改策略 // UpdateFirewallPolicy 修改策略
func (this *HTTPFirewallPolicyDAO) UpdateFirewallPolicy(tx *dbs.Tx, policyId int64, isOn bool, name string, description string, inboundJSON []byte, outboundJSON []byte, blockOptionsJSON []byte, mode firewallconfigs.FirewallMode, useLocalFirewall bool, synFloodConfig *firewallconfigs.SYNFloodConfig) error { func (this *HTTPFirewallPolicyDAO) UpdateFirewallPolicy(tx *dbs.Tx,
policyId int64,
isOn bool,
name string,
description string,
inboundJSON []byte,
outboundJSON []byte,
blockOptionsJSON []byte,
mode firewallconfigs.FirewallMode,
useLocalFirewall bool,
synFloodConfig *firewallconfigs.SYNFloodConfig,
logConfig *firewallconfigs.HTTPFirewallPolicyLogConfig) error {
if policyId <= 0 { if policyId <= 0 {
return errors.New("invalid policyId") return errors.New("invalid policyId")
} }
@@ -293,6 +305,16 @@ func (this *HTTPFirewallPolicyDAO) UpdateFirewallPolicy(tx *dbs.Tx, policyId int
op.SynFlood = "null" op.SynFlood = "null"
} }
if logConfig != nil {
logJSON, err := json.Marshal(logConfig)
if err != nil {
return err
}
op.Log = logJSON
} else {
op.Log = "null"
}
op.UseLocalFirewall = useLocalFirewall op.UseLocalFirewall = useLocalFirewall
err := this.Save(tx, op) err := this.Save(tx, op)
if err != nil { if err != nil {
@@ -311,7 +333,7 @@ func (this *HTTPFirewallPolicyDAO) CountAllEnabledFirewallPolicies(tx *dbs.Tx, c
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword)"). query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query. return query.
State(HTTPFirewallPolicyStateEnabled). State(HTTPFirewallPolicyStateEnabled).
@@ -330,7 +352,7 @@ func (this *HTTPFirewallPolicyDAO) ListEnabledFirewallPolicies(tx *dbs.Tx, clust
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword)"). query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query. _, err = query.
State(HTTPFirewallPolicyStateEnabled). State(HTTPFirewallPolicyStateEnabled).
@@ -364,7 +386,7 @@ func (this *HTTPFirewallPolicyDAO) ComposeFirewallPolicy(tx *dbs.Tx, policyId in
return nil, nil return nil, nil
} }
config := &firewallconfigs.HTTPFirewallPolicy{} var config = &firewallconfigs.HTTPFirewallPolicy{}
config.Id = int64(policy.Id) config.Id = int64(policy.Id)
config.IsOn = policy.IsOn config.IsOn = policy.IsOn
config.Name = policy.Name config.Name = policy.Name
@@ -452,6 +474,18 @@ func (this *HTTPFirewallPolicyDAO) ComposeFirewallPolicy(tx *dbs.Tx, policyId in
config.SYNFlood = synFloodConfig config.SYNFlood = synFloodConfig
} }
// log
if IsNotNull(policy.Log) {
var logConfig = &firewallconfigs.HTTPFirewallPolicyLogConfig{}
err = json.Unmarshal(policy.Log, logConfig)
if err != nil {
return nil, err
}
config.Log = logConfig
} else {
config.Log = firewallconfigs.DefaultHTTPFirewallPolicyLogConfig
}
if cacheMap != nil { if cacheMap != nil {
cacheMap.Put(cacheKey, config) cacheMap.Put(cacheKey, config)
} }

View File

@@ -21,6 +21,7 @@ type HTTPFirewallPolicy struct {
Mode string `field:"mode"` // 模式 Mode string `field:"mode"` // 模式
UseLocalFirewall uint8 `field:"useLocalFirewall"` // 是否自动使用本地防火墙 UseLocalFirewall uint8 `field:"useLocalFirewall"` // 是否自动使用本地防火墙
SynFlood dbs.JSON `field:"synFlood"` // SynFlood防御设置 SynFlood dbs.JSON `field:"synFlood"` // SynFlood防御设置
Log dbs.JSON `field:"log"` // 日志配置
} }
type HTTPFirewallPolicyOperator struct { type HTTPFirewallPolicyOperator struct {
@@ -41,6 +42,7 @@ type HTTPFirewallPolicyOperator struct {
Mode interface{} // 模式 Mode interface{} // 模式
UseLocalFirewall interface{} // 是否自动使用本地防火墙 UseLocalFirewall interface{} // 是否自动使用本地防火墙
SynFlood interface{} // SynFlood防御设置 SynFlood interface{} // SynFlood防御设置
Log interface{} // 日志配置
} }
func NewHTTPFirewallPolicyOperator() *HTTPFirewallPolicyOperator { func NewHTTPFirewallPolicyOperator() *HTTPFirewallPolicyOperator {

View File

@@ -77,8 +77,9 @@ func (this *HTTPPageDAO) FindEnabledHTTPPage(tx *dbs.Tx, id int64) (*HTTPPage, e
} }
// CreatePage 创建Page // CreatePage 创建Page
func (this *HTTPPageDAO) CreatePage(tx *dbs.Tx, statusList []string, bodyType shared.BodyType, url string, body string, newStatus int) (pageId int64, err error) { func (this *HTTPPageDAO) CreatePage(tx *dbs.Tx, userId int64, statusList []string, bodyType shared.BodyType, url string, body string, newStatus int) (pageId int64, err error) {
op := NewHTTPPageOperator() op := NewHTTPPageOperator()
op.UserId = userId
op.IsOn = true op.IsOn = true
op.State = HTTPPageStateEnabled op.State = HTTPPageStateEnabled
@@ -182,6 +183,26 @@ func (this *HTTPPageDAO) ComposePageConfig(tx *dbs.Tx, pageId int64, cacheMap *u
return config, nil return config, nil
} }
// CheckUserPage 检查用户页面
func (this *HTTPPageDAO) CheckUserPage(tx *dbs.Tx, userId int64, pageId int64) error {
if userId <= 0 || pageId <= 0 {
return ErrNotFound
}
b, err := this.Query(tx).
Pk(pageId).
Attr("userId", userId).
State(HTTPPageStateEnabled).
Exist()
if err != nil {
return err
}
if !b {
return ErrNotFound
}
return nil
}
// NotifyUpdate 通知更新 // NotifyUpdate 通知更新
func (this *HTTPPageDAO) NotifyUpdate(tx *dbs.Tx, pageId int64) error { func (this *HTTPPageDAO) NotifyUpdate(tx *dbs.Tx, pageId int64) error {
webId, err := SharedHTTPWebDAO.FindEnabledWebIdWithPageId(tx, pageId) webId, err := SharedHTTPWebDAO.FindEnabledWebIdWithPageId(tx, pageId)

View File

@@ -94,7 +94,7 @@ func (this *HTTPWebDAO) ComposeWebConfig(tx *dbs.Tx, webId int64, cacheMap *util
return nil, nil return nil, nil
} }
config := &serverconfigs.HTTPWebConfig{} var config = &serverconfigs.HTTPWebConfig{}
config.Id = webId config.Id = webId
config.IsOn = web.IsOn config.IsOn = web.IsOn

View File

@@ -1,6 +1,7 @@
package models package models
import ( import (
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman" "github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs" "github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
@@ -264,13 +265,13 @@ func (this *IPItemDAO) UpdateIPItem(tx *dbs.Tx, itemId int64, ipFrom string, ipT
} }
// CountIPItemsWithListId 计算IP数量 // CountIPItemsWithListId 计算IP数量
func (this *IPItemDAO) CountIPItemsWithListId(tx *dbs.Tx, listId int64, ipFrom string, ipTo string, keyword string) (int64, error) { func (this *IPItemDAO) CountIPItemsWithListId(tx *dbs.Tx, listId int64, ipFrom string, ipTo string, keyword string, eventLevel string) (int64, error) {
var query = this.Query(tx). var query = this.Query(tx).
State(IPItemStateEnabled). State(IPItemStateEnabled).
Attr("listId", listId) Attr("listId", listId)
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(ipFrom LIKE :keyword OR ipTo LIKE :keyword)"). query.Where("(ipFrom LIKE :keyword OR ipTo LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(ipFrom) > 0 { if len(ipFrom) > 0 {
query.Attr("ipFrom", ipFrom) query.Attr("ipFrom", ipFrom)
@@ -278,17 +279,20 @@ func (this *IPItemDAO) CountIPItemsWithListId(tx *dbs.Tx, listId int64, ipFrom s
if len(ipTo) > 0 { if len(ipTo) > 0 {
query.Attr("ipTo", ipTo) query.Attr("ipTo", ipTo)
} }
if len(eventLevel) > 0 {
query.Attr("eventLevel", eventLevel)
}
return query.Count() return query.Count()
} }
// ListIPItemsWithListId 查找IP列表 // ListIPItemsWithListId 查找IP列表
func (this *IPItemDAO) ListIPItemsWithListId(tx *dbs.Tx, listId int64, keyword string, ipFrom string, ipTo string, offset int64, size int64) (result []*IPItem, err error) { func (this *IPItemDAO) ListIPItemsWithListId(tx *dbs.Tx, listId int64, keyword string, ipFrom string, ipTo string, eventLevel string, offset int64, size int64) (result []*IPItem, err error) {
var query = this.Query(tx). var query = this.Query(tx).
State(IPItemStateEnabled). State(IPItemStateEnabled).
Attr("listId", listId) Attr("listId", listId)
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(ipFrom LIKE :keyword OR ipTo LIKE :keyword)"). query.Where("(ipFrom LIKE :keyword OR ipTo LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(ipFrom) > 0 { if len(ipFrom) > 0 {
query.Attr("ipFrom", ipFrom) query.Attr("ipFrom", ipFrom)
@@ -296,6 +300,9 @@ func (this *IPItemDAO) ListIPItemsWithListId(tx *dbs.Tx, listId int64, keyword s
if len(ipTo) > 0 { if len(ipTo) > 0 {
query.Attr("ipTo", ipTo) query.Attr("ipTo", ipTo)
} }
if len(eventLevel) > 0 {
query.Attr("eventLevel", eventLevel)
}
_, err = query. _, err = query.
DescPk(). DescPk().
Slice(&result). Slice(&result).
@@ -371,7 +378,7 @@ func (this *IPItemDAO) ExistsEnabledItem(tx *dbs.Tx, itemId int64) (bool, error)
} }
// CountAllEnabledIPItems 计算数量 // CountAllEnabledIPItems 计算数量
func (this *IPItemDAO) CountAllEnabledIPItems(tx *dbs.Tx, ip string, listId int64, unread bool) (int64, error) { func (this *IPItemDAO) CountAllEnabledIPItems(tx *dbs.Tx, ip string, listId int64, unread bool, eventLevel string, listType string) (int64, error) {
var query = this.Query(tx) var query = this.Query(tx)
if len(ip) > 0 { if len(ip) > 0 {
query.Attr("ipFrom", ip) query.Attr("ipFrom", ip)
@@ -379,11 +386,20 @@ func (this *IPItemDAO) CountAllEnabledIPItems(tx *dbs.Tx, ip string, listId int6
if listId > 0 { if listId > 0 {
query.Attr("listId", listId) query.Attr("listId", listId)
} else { } else {
query.Where("(listId=" + types.String(firewallconfigs.GlobalListId) + " OR listId IN (SELECT id FROM " + SharedIPListDAO.Table + " WHERE state=1))") if len(listType) > 0 {
query.Where("(listId=" + types.String(firewallconfigs.GlobalListId) + " OR listId IN (SELECT id FROM " + SharedIPListDAO.Table + " WHERE state=1 AND type=:listType))")
query.Param("listType", listType)
} else {
query.Where("(listId=" + types.String(firewallconfigs.GlobalListId) + " OR listId IN (SELECT id FROM " + SharedIPListDAO.Table + " WHERE state=1))")
}
} }
if unread { if unread {
query.Attr("isRead", 0) query.Attr("isRead", 0)
} }
if len(eventLevel) > 0 {
query.Attr("eventLevel", eventLevel)
}
return query. return query.
State(IPItemStateEnabled). State(IPItemStateEnabled).
Where("(expiredAt=0 OR expiredAt>:expiredAt)"). Where("(expiredAt=0 OR expiredAt>:expiredAt)").
@@ -392,7 +408,7 @@ func (this *IPItemDAO) CountAllEnabledIPItems(tx *dbs.Tx, ip string, listId int6
} }
// ListAllEnabledIPItems 搜索所有IP // ListAllEnabledIPItems 搜索所有IP
func (this *IPItemDAO) ListAllEnabledIPItems(tx *dbs.Tx, ip string, listId int64, unread bool, offset int64, size int64) (result []*IPItem, err error) { func (this *IPItemDAO) ListAllEnabledIPItems(tx *dbs.Tx, ip string, listId int64, unread bool, eventLevel string, listType string, offset int64, size int64) (result []*IPItem, err error) {
var query = this.Query(tx) var query = this.Query(tx)
if len(ip) > 0 { if len(ip) > 0 {
query.Attr("ipFrom", ip) query.Attr("ipFrom", ip)
@@ -400,11 +416,19 @@ func (this *IPItemDAO) ListAllEnabledIPItems(tx *dbs.Tx, ip string, listId int64
if listId > 0 { if listId > 0 {
query.Attr("listId", listId) query.Attr("listId", listId)
} else { } else {
query.Where("(listId=" + types.String(firewallconfigs.GlobalListId) + " OR listId IN (SELECT id FROM " + SharedIPListDAO.Table + " WHERE state=1))") if len(listType) > 0 {
query.Where("(listId=" + types.String(firewallconfigs.GlobalListId) + " OR listId IN (SELECT id FROM " + SharedIPListDAO.Table + " WHERE state=1 AND type=:listType))")
query.Param("listType", listType)
} else {
query.Where("(listId=" + types.String(firewallconfigs.GlobalListId) + " OR listId IN (SELECT id FROM " + SharedIPListDAO.Table + " WHERE state=1))")
}
} }
if unread { if unread {
query.Attr("isRead", 0) query.Attr("isRead", 0)
} }
if len(eventLevel) > 0 {
query.Attr("eventLevel", eventLevel)
}
_, err = query. _, err = query.
State(IPItemStateEnabled). State(IPItemStateEnabled).
Where("(expiredAt=0 OR expiredAt>:expiredAt)"). Where("(expiredAt=0 OR expiredAt>:expiredAt)").

View File

@@ -1,6 +1,7 @@
package models package models
import ( import (
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
@@ -221,7 +222,7 @@ func (this *IPListDAO) CountAllEnabledIPLists(tx *dbs.Tx, listType string, isPub
Attr("isPublic", isPublic) Attr("isPublic", isPublic)
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR description LIKE :keyword)"). query.Where("(name LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query.Count() return query.Count()
} }
@@ -234,7 +235,7 @@ func (this *IPListDAO) ListEnabledIPLists(tx *dbs.Tx, listType string, isPublic
Attr("isPublic", isPublic) Attr("isPublic", isPublic)
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR description LIKE :keyword)"). query.Where("(name LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query.Offset(offset). _, err = query.Offset(offset).
Limit(size). Limit(size).

View File

@@ -1,6 +1,7 @@
package models package models
import ( import (
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
@@ -72,7 +73,7 @@ func (this *LogDAO) CountLogs(tx *dbs.Tx, dayFrom string, dayTo string, keyword
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(description LIKE :keyword OR ip LIKE :keyword OR action LIKE :keyword)"). query.Where("(description LIKE :keyword OR ip LIKE :keyword OR action LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
// 用户类型 // 用户类型
@@ -100,7 +101,7 @@ func (this *LogDAO) ListLogs(tx *dbs.Tx, offset int64, size int64, dayFrom strin
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(description LIKE :keyword OR ip LIKE :keyword OR action LIKE :keyword)"). query.Where("(description LIKE :keyword OR ip LIKE :keyword OR action LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
// 用户类型 // 用户类型

View File

@@ -2,6 +2,7 @@ package models
import ( import (
"encoding/json" "encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
@@ -149,7 +150,7 @@ func (this *MessageMediaInstanceDAO) CountAllEnabledMediaInstances(tx *dbs.Tx, m
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR description LIKE :keyword)"). query.Where("(name LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query. return query.
State(MessageMediaInstanceStateEnabled). State(MessageMediaInstanceStateEnabled).
@@ -165,7 +166,7 @@ func (this *MessageMediaInstanceDAO) ListAllEnabledMediaInstances(tx *dbs.Tx, me
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR description LIKE :keyword)"). query.Where("(name LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query. _, err = query.
State(MessageMediaInstanceStateEnabled). State(MessageMediaInstanceStateEnabled).

View File

@@ -2,6 +2,7 @@ package models
import ( import (
"encoding/json" "encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils" "github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
@@ -172,7 +173,7 @@ func (this *MessageRecipientDAO) CountAllEnabledRecipients(tx *dbs.Tx, adminId i
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(`user` LIKE :keyword OR description LIKE :keyword)"). query.Where("(`user` LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query. return query.
State(MessageRecipientStateEnabled). State(MessageRecipientStateEnabled).
@@ -197,7 +198,7 @@ func (this *MessageRecipientDAO) ListAllEnabledRecipients(tx *dbs.Tx, adminId in
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(`user` LIKE :keyword OR description LIKE :keyword)"). query.Where("(`user` LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query. _, err = query.
State(MessageRecipientStateEnabled). State(MessageRecipientStateEnabled).

View File

@@ -12,6 +12,7 @@ import (
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
"sort" "sort"
"strings" "strings"
"sync"
) )
const ( const (
@@ -34,6 +35,9 @@ func NewMetricItemDAO() *MetricItemDAO {
var SharedMetricItemDAO *MetricItemDAO var SharedMetricItemDAO *MetricItemDAO
var metricItemLastTimeCacheMap = map[int64]string{} // itemId => time
var metricItemLastTimeCacheLocker = &sync.Mutex{}
func init() { func init() {
dbs.OnReady(func() { dbs.OnReady(func() {
SharedMetricItemDAO = NewMetricItemDAO() SharedMetricItemDAO = NewMetricItemDAO()
@@ -108,10 +112,10 @@ func (this *MetricItemDAO) FindMetricItemName(tx *dbs.Tx, id int64) (string, err
} }
// CreateItem 创建指标 // CreateItem 创建指标
func (this *MetricItemDAO) CreateItem(tx *dbs.Tx, code string, category string, name string, keys []string, period int32, periodUnit string, value string, isPublic bool) (int64, error) { func (this *MetricItemDAO) CreateItem(tx *dbs.Tx, code string, category string, name string, keys []string, period int32, periodUnit string, expiresPeriod int32, value string, isPublic bool) (int64, error) {
sort.Strings(keys) sort.Strings(keys)
op := NewMetricItemOperator() var op = NewMetricItemOperator()
op.Code = code op.Code = code
op.Category = category op.Category = category
op.Name = name op.Name = name
@@ -126,6 +130,7 @@ func (this *MetricItemDAO) CreateItem(tx *dbs.Tx, code string, category string,
} }
op.Period = period op.Period = period
op.PeriodUnit = periodUnit op.PeriodUnit = periodUnit
op.ExpiresPeriod = expiresPeriod
op.Value = value op.Value = value
op.IsPublic = isPublic op.IsPublic = isPublic
op.IsOn = true op.IsOn = true
@@ -146,7 +151,7 @@ func (this *MetricItemDAO) CreateItem(tx *dbs.Tx, code string, category string,
} }
// UpdateItem 修改\指标 // UpdateItem 修改\指标
func (this *MetricItemDAO) UpdateItem(tx *dbs.Tx, itemId int64, name string, keys []string, period int32, periodUnit string, value string, isOn bool, isPublic bool) error { func (this *MetricItemDAO) UpdateItem(tx *dbs.Tx, itemId int64, name string, keys []string, period int32, periodUnit string, expiresPeriod int32, value string, isOn bool, isPublic bool) error {
if itemId <= 0 { if itemId <= 0 {
return errors.New("invalid itemId") return errors.New("invalid itemId")
} }
@@ -168,7 +173,7 @@ func (this *MetricItemDAO) UpdateItem(tx *dbs.Tx, itemId int64, name string, key
} }
// 保存 // 保存
op := NewMetricItemOperator() var op = NewMetricItemOperator()
op.Id = itemId op.Id = itemId
op.Name = name op.Name = name
if len(keys) > 0 { if len(keys) > 0 {
@@ -182,6 +187,7 @@ func (this *MetricItemDAO) UpdateItem(tx *dbs.Tx, itemId int64, name string, key
} }
op.Period = period op.Period = period
op.PeriodUnit = periodUnit op.PeriodUnit = periodUnit
op.ExpiresPeriod = expiresPeriod
op.Value = value op.Value = value
op.IsOn = isOn op.IsOn = isOn
if versionChanged { if versionChanged {
@@ -282,14 +288,15 @@ func (this *MetricItemDAO) ComposeItemConfig(tx *dbs.Tx, itemId int64) (*serverc
} }
var item = one.(*MetricItem) var item = one.(*MetricItem)
var config = &serverconfigs.MetricItemConfig{ var config = &serverconfigs.MetricItemConfig{
Id: int64(item.Id), Id: int64(item.Id),
IsOn: item.IsOn, IsOn: item.IsOn,
Period: types.Int(item.Period), Period: types.Int(item.Period),
PeriodUnit: item.PeriodUnit, PeriodUnit: item.PeriodUnit,
Category: item.Category, ExpiresPeriod: types.Int(item.ExpiresPeriod),
Value: item.Value, Category: item.Category,
Keys: item.DecodeKeys(), Value: item.Value,
Version: types.Int32(item.Version), Keys: item.DecodeKeys(),
Version: types.Int32(item.Version),
} }
return config, nil return config, nil
@@ -301,14 +308,15 @@ func (this *MetricItemDAO) ComposeItemConfigWithItem(item *MetricItem) *serverco
return nil return nil
} }
var config = &serverconfigs.MetricItemConfig{ var config = &serverconfigs.MetricItemConfig{
Id: int64(item.Id), Id: int64(item.Id),
IsOn: item.IsOn, IsOn: item.IsOn,
Period: types.Int(item.Period), Period: types.Int(item.Period),
PeriodUnit: item.PeriodUnit, PeriodUnit: item.PeriodUnit,
Category: item.Category, ExpiresPeriod: types.Int(item.ExpiresPeriod),
Value: item.Value, Category: item.Category,
Keys: item.DecodeKeys(), Value: item.Value,
Version: types.Int32(item.Version), Keys: item.DecodeKeys(),
Version: types.Int32(item.Version),
} }
return config return config
@@ -326,6 +334,32 @@ func (this *MetricItemDAO) FindItemVersion(tx *dbs.Tx, itemId int64) (int32, err
return types.Int32(version), nil return types.Int32(version), nil
} }
// UpdateMetricLastTime 更新指标最新数据的时间
func (this *MetricItemDAO) UpdateMetricLastTime(tx *dbs.Tx, itemId int64, lastTime string) error {
metricItemLastTimeCacheLocker.Lock()
cachedTime, ok := metricItemLastTimeCacheMap[itemId]
if ok && cachedTime == lastTime {
metricItemLastTimeCacheLocker.Unlock()
return nil
}
metricItemLastTimeCacheMap[itemId] = lastTime
metricItemLastTimeCacheLocker.Unlock()
return this.Query(tx).
Pk(itemId).
Set("lastTime", lastTime).
UpdateQuickly()
}
// FindMetricLastTime 读取指标最新数据的时间
func (this *MetricItemDAO) FindMetricLastTime(tx *dbs.Tx, itemId int64) (string, error) {
return this.Query(tx).
Result("lastTime").
Pk(itemId).
FindStringCol("")
}
// NotifyUpdate 通知更新 // NotifyUpdate 通知更新
func (this *MetricItemDAO) NotifyUpdate(tx *dbs.Tx, itemId int64, isPublic bool) error { func (this *MetricItemDAO) NotifyUpdate(tx *dbs.Tx, itemId int64, isPublic bool) error {
if isPublic { if isPublic {

View File

@@ -1,6 +1,13 @@
package models package models_test
import ( import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"testing"
) )
func TestMetricStatDAO_Clean(t *testing.T) {
var dao = models.NewMetricStatDAO()
t.Log(dao.Clean(nil))
}

View File

@@ -4,37 +4,41 @@ import "github.com/iwind/TeaGo/dbs"
// MetricItem 指标定义 // MetricItem 指标定义
type MetricItem struct { type MetricItem struct {
Id uint64 `field:"id"` // ID Id uint64 `field:"id"` // ID
IsOn bool `field:"isOn"` // 是否启用 IsOn bool `field:"isOn"` // 是否启用
Code string `field:"code"` // 代号(用来区分是否内置) Code string `field:"code"` // 代号(用来区分是否内置)
Category string `field:"category"` // 类型比如http, tcp等 Category string `field:"category"` // 类型比如http, tcp等
AdminId uint32 `field:"adminId"` // 管理员ID AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID UserId uint32 `field:"userId"` // 用户ID
Name string `field:"name"` // 指标名称 Name string `field:"name"` // 指标名称
Keys dbs.JSON `field:"keys"` // 统计的Key Keys dbs.JSON `field:"keys"` // 统计的Key
Period uint32 `field:"period"` // 周期 Period uint32 `field:"period"` // 周期
PeriodUnit string `field:"periodUnit"` // 周期单位 PeriodUnit string `field:"periodUnit"` // 周期单位
Value string `field:"value"` // 值运算 ExpiresPeriod uint32 `field:"expiresPeriod"` // 过期周期
State uint8 `field:"state"` // 状态 Value string `field:"value"` // 值运算
Version uint32 `field:"version"` // 版本号 State uint8 `field:"state"` // 状态
IsPublic bool `field:"isPublic"` // 是否为公用 Version uint32 `field:"version"` // 版本号
IsPublic bool `field:"isPublic"` // 是否为公用
LastTime string `field:"lastTime"` // 最新时间
} }
type MetricItemOperator struct { type MetricItemOperator struct {
Id interface{} // ID Id interface{} // ID
IsOn interface{} // 是否启用 IsOn interface{} // 是否启用
Code interface{} // 代号(用来区分是否内置) Code interface{} // 代号(用来区分是否内置)
Category interface{} // 类型比如http, tcp等 Category interface{} // 类型比如http, tcp等
AdminId interface{} // 管理员ID AdminId interface{} // 管理员ID
UserId interface{} // 用户ID UserId interface{} // 用户ID
Name interface{} // 指标名称 Name interface{} // 指标名称
Keys interface{} // 统计的Key Keys interface{} // 统计的Key
Period interface{} // 周期 Period interface{} // 周期
PeriodUnit interface{} // 周期单位 PeriodUnit interface{} // 周期单位
Value interface{} // 值运算 ExpiresPeriod interface{} // 过期周期
State interface{} // 状态 Value interface{} // 值运算
Version interface{} // 版本号 State interface{} // 状态
IsPublic interface{} // 是否为公用 Version interface{} // 版本号
IsPublic interface{} // 是否为公用
LastTime interface{} // 最新时间
} }
func NewMetricItemOperator() *MetricItemOperator { func NewMetricItemOperator() *MetricItemOperator {

View File

@@ -3,16 +3,21 @@ package models
import ( import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/goman" "github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/go-sql-driver/mysql"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"sort"
"strconv" "strconv"
"sync"
"sync/atomic"
"time" "time"
) )
@@ -26,13 +31,15 @@ func init() {
for range ticker.C { for range ticker.C {
err := SharedMetricStatDAO.Clean(nil) err := SharedMetricStatDAO.Clean(nil)
if err != nil { if err != nil {
logs.Println("SharedMetricStatDAO: clean expired data failed: " + err.Error()) remotelogs.Error("SharedMetricStatDAO", "clean expired data failed: "+err.Error())
} }
} }
}) })
}) })
} }
const MetricStatTablePartials = 20 // 表格Partial数量
func NewMetricStatDAO() *MetricStatDAO { func NewMetricStatDAO() *MetricStatDAO {
return dbs.NewDAO(&MetricStatDAO{ return dbs.NewDAO(&MetricStatDAO{
DAOObject: dbs.DAOObject{ DAOObject: dbs.DAOObject{
@@ -65,7 +72,8 @@ func (this *MetricStatDAO) CreateStat(tx *dbs.Tx, hash string, clusterId int64,
} else { } else {
keysString = "[]" keysString = "[]"
} }
return this.Query(tx). err := this.Query(tx).
Table(this.partialTable(serverId)).
Param("value", value). Param("value", value).
InsertOrUpdateQuickly(maps.Map{ InsertOrUpdateQuickly(maps.Map{
"hash": hash, "hash": hash,
@@ -81,57 +89,134 @@ func (this *MetricStatDAO) CreateStat(tx *dbs.Tx, hash string, clusterId int64,
}, maps.Map{ }, maps.Map{
"value": value, "value": value,
}) })
if err != nil {
// 忽略 Error 1213: Deadlock found 错误
mysqlErr, ok := err.(*mysql.MySQLError)
if ok && mysqlErr.Number == 1213 {
return nil
}
return err
}
return SharedMetricItemDAO.UpdateMetricLastTime(tx, itemId, time)
} }
// DeleteOldVersionItemStats 删除以前版本的统计数据 // DeleteOldVersionItemStats 删除以前版本的统计数据
func (this *MetricStatDAO) DeleteOldVersionItemStats(tx *dbs.Tx, itemId int64, version int32) error { func (this *MetricStatDAO) DeleteOldVersionItemStats(tx *dbs.Tx, itemId int64, version int32) error {
_, err := this.Query(tx). return this.runBatch(func(table string, locker *sync.Mutex) error {
Attr("itemId", itemId). _, err := this.Query(tx).
Where("version<:version"). Table(table).
Param("version", version). Attr("itemId", itemId).
Delete() Where("version<:version").
return err Param("version", version).
Delete()
return err
})
} }
// DeleteItemStats 删除某个指标相关的统计数据 // DeleteItemStats 删除某个指标相关的统计数据
func (this *MetricStatDAO) DeleteItemStats(tx *dbs.Tx, itemId int64) error { func (this *MetricStatDAO) DeleteItemStats(tx *dbs.Tx, itemId int64) error {
_, err := this.Query(tx). return this.runBatch(func(table string, locker *sync.Mutex) error {
Attr("itemId", itemId). _, err := this.Query(tx).
Delete() Table(table).
return err Attr("itemId", itemId).
Delete()
return err
})
} }
// DeleteNodeItemStats 删除某个节点的统计数据 // DeleteNodeItemStats 删除某个节点的统计数据
func (this *MetricStatDAO) DeleteNodeItemStats(tx *dbs.Tx, nodeId int64, serverId int64, itemId int64, time string) error { func (this *MetricStatDAO) DeleteNodeItemStats(tx *dbs.Tx, nodeId int64, serverId int64, itemId int64, time string) error {
_, err := this.Query(tx). if serverId > 0 {
Attr("nodeId", nodeId). _, err := this.Query(tx).
Attr("serverId", serverId). Table(this.partialTable(serverId)).
Attr("itemId", itemId). Attr("nodeId", nodeId).
Attr("time", time). Attr("serverId", serverId).
Delete() Attr("itemId", itemId).
Attr("time", time).
Delete()
return err
}
err := this.runBatch(func(table string, locker *sync.Mutex) error {
_, err := this.Query(tx).
Table(table).
Attr("nodeId", nodeId).
Attr("serverId", serverId).
Attr("itemId", itemId).
Attr("time", time).
Delete()
return err
})
return err return err
} }
// CountItemStats 计算统计数据数量 // CountItemStats 计算统计数据数量
func (this *MetricStatDAO) CountItemStats(tx *dbs.Tx, itemId int64, version int32) (int64, error) { func (this *MetricStatDAO) CountItemStats(tx *dbs.Tx, itemId int64, version int32) (int64, error) {
return this.Query(tx). var total int64 = 0
Attr("itemId", itemId).
Attr("version", version). err := this.runBatch(func(table string, locker *sync.Mutex) error {
Count() count, err := this.Query(tx).
Table(table).
Attr("itemId", itemId).
Attr("version", version).
Count()
if err != nil {
return err
}
atomic.AddInt64(&total, count)
return nil
})
if err != nil {
return 0, err
}
return total, nil
} }
// ListItemStats 列出单页统计数据 // ListItemStats 列出单页统计数据
func (this *MetricStatDAO) ListItemStats(tx *dbs.Tx, itemId int64, version int32, offset int64, size int64) (result []*MetricStat, err error) { func (this *MetricStatDAO) ListItemStats(tx *dbs.Tx, itemId int64, version int32, offset int64, size int64) (result []*MetricStat, err error) {
_, err = this.Query(tx). err = this.runBatch(func(table string, locker *sync.Mutex) error {
Attr("itemId", itemId). var partialResult = []*MetricStat{}
Attr("version", version). _, err = this.Query(tx).
Offset(offset). Table(table).
Limit(size). Attr("itemId", itemId).
Desc("time"). Attr("version", version).
Desc("serverId"). Offset(offset).
Desc("value"). Limit(size).
Slice(&result). Desc("time").
FindAll() Desc("serverId").
Desc("value").
Slice(&partialResult).
FindAll()
if err != nil {
return err
}
locker.Lock()
result = append(result, partialResult...)
locker.Unlock()
return nil
})
if err != nil {
return nil, err
}
result = this.mergeStats(result)
sort.Slice(result, func(i, j int) bool {
if result[i].Time > result[j].Time {
return true
}
if result[i].ServerId > result[j].ServerId {
return true
}
if result[i].Value > result[j].Value {
return true
}
return false
})
return return
} }
@@ -139,92 +224,127 @@ func (this *MetricStatDAO) ListItemStats(tx *dbs.Tx, itemId int64, version int32
// 适合每条数据中包含不同的Key的场景 // 适合每条数据中包含不同的Key的场景
func (this *MetricStatDAO) FindItemStatsAtLastTime(tx *dbs.Tx, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) { func (this *MetricStatDAO) FindItemStatsAtLastTime(tx *dbs.Tx, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) {
// 最近一次时间 // 最近一次时间
statOne, err := this.Query(tx). lastTime, err := SharedMetricItemDAO.FindMetricLastTime(tx, itemId)
Attr("itemId", itemId). if err != nil || len(lastTime) == 0 {
Attr("version", version). return nil, err
DescPk(). }
Find()
err = this.runBatch(func(table string, locker *sync.Mutex) error {
var partialResult = []*MetricStat{}
var query = this.Query(tx).
Table(table).
Attr("itemId", itemId).
Attr("version", version).
Attr("time", lastTime).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Result("MIN(time) AS time", "SUM(value) AS value", "keys").
Desc("value").
Group("keys").
Limit(size).
Slice(&partialResult)
if ignoreEmptyKeys {
query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')")
}
if len(ignoreKeys) > 0 {
ignoreKeysJSON, err := json.Marshal(ignoreKeys)
if err != nil {
return err
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
}
_, err = query.
FindAll()
if err != nil {
return err
}
locker.Lock()
result = append(result, partialResult...)
locker.Unlock()
return nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if statOne == nil { result = this.mergeStats(result)
return nil, nil
sort.Slice(result, func(i, j int) bool {
return result[i].Value > result[j].Value
})
if len(result) > types.Int(size) {
result = result[:types.Int(size)]
} }
var lastStat = statOne.(*MetricStat)
var lastTime = lastStat.Time
var query = this.Query(tx).
Attr("itemId", itemId).
Attr("version", version).
Attr("time", lastTime).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Result("MIN(time) AS time", "SUM(value) AS value", "keys").
Desc("value").
Group("keys").
Limit(size).
Slice(&result)
if ignoreEmptyKeys {
query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')")
}
if len(ignoreKeys) > 0 {
ignoreKeysJSON, err := json.Marshal(ignoreKeys)
if err != nil {
return nil, err
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
}
_, err = query.
FindAll()
return return
} }
// FindItemStatsWithClusterIdAndLastTime 取得集群最近一次计时前 N 个数据 // FindItemStatsWithClusterIdAndLastTime 取得集群最近一次计时前 N 个数据
// 适合每条数据中包含不同的Key的场景 // 适合每条数据中包含不同的Key的场景
func (this *MetricStatDAO) FindItemStatsWithClusterIdAndLastTime(tx *dbs.Tx, clusterId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) { func (this *MetricStatDAO) FindItemStatsWithClusterIdAndLastTime(tx *dbs.Tx, clusterId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) {
// 最近一次时间 lastTime, err := SharedMetricItemDAO.FindMetricLastTime(tx, itemId)
statOne, err := this.Query(tx). if err != nil || len(lastTime) == 0 {
Attr("itemId", itemId). return nil, err
Attr("version", version). }
DescPk().
Find() err = this.runBatch(func(table string, locker *sync.Mutex) error {
var partialResult = []*MetricStat{}
var query = this.Query(tx).
Table(table).
UseIndex("cluster_item_time").
Attr("clusterId", clusterId).
Attr("itemId", itemId).
Attr("version", version).
Attr("time", lastTime).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Result("MIN(time) AS time", "SUM(value) AS value", "keys").
Desc("value").
Group("keys").
Limit(size).
Slice(&partialResult)
if ignoreEmptyKeys {
query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')")
}
if len(ignoreKeys) > 0 {
ignoreKeysJSON, err := json.Marshal(ignoreKeys)
if err != nil {
return err
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
}
_, err = query.
FindAll()
if err != nil {
return err
}
locker.Lock()
result = append(result, partialResult...)
locker.Unlock()
return nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if statOne == nil {
return nil, nil
}
var lastStat = statOne.(*MetricStat)
var lastTime = lastStat.Time
var query = this.Query(tx). result = this.mergeStats(result)
UseIndex("cluster_item_time").
Attr("clusterId", clusterId). sort.Slice(result, func(i, j int) bool {
Attr("itemId", itemId). return result[i].Value > result[j].Value
Attr("version", version). })
Attr("time", lastTime).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等 if len(result) > types.Int(size) {
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE result = result[:types.Int(size)]
Result("MIN(time) AS time", "SUM(value) AS value", "keys").
Desc("value").
Group("keys").
Limit(size).
Slice(&result)
if ignoreEmptyKeys {
query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')")
}
if len(ignoreKeys) > 0 {
ignoreKeysJSON, err := json.Marshal(ignoreKeys)
if err != nil {
return nil, err
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
} }
_, err = query.
FindAll()
return return
} }
@@ -232,68 +352,80 @@ func (this *MetricStatDAO) FindItemStatsWithClusterIdAndLastTime(tx *dbs.Tx, clu
// 适合每条数据中包含不同的Key的场景 // 适合每条数据中包含不同的Key的场景
func (this *MetricStatDAO) FindItemStatsWithNodeIdAndLastTime(tx *dbs.Tx, nodeId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) { func (this *MetricStatDAO) FindItemStatsWithNodeIdAndLastTime(tx *dbs.Tx, nodeId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) {
// 最近一次时间 // 最近一次时间
statOne, err := this.Query(tx). lastTime, err := SharedMetricItemDAO.FindMetricLastTime(tx, itemId)
Attr("itemId", itemId).
Attr("version", version).
DescPk().
Find()
if err != nil { if err != nil {
return nil, err return nil, err
} }
if statOne == nil {
return nil, nil err = this.runBatch(func(table string, locker *sync.Mutex) error {
} var partialResult = []*MetricStat{}
var lastStat = statOne.(*MetricStat) var query = this.Query(tx).
var lastTime = lastStat.Time Table(table).
var query = this.Query(tx). UseIndex("node_item_time").
UseIndex("node_item_time"). Attr("nodeId", nodeId).
Attr("nodeId", nodeId). Attr("itemId", itemId).
Attr("itemId", itemId). Attr("version", version).
Attr("version", version). Attr("time", lastTime).
Attr("time", lastTime). // TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等 // TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE Result("MIN(time) AS time", "SUM(value) AS value", "keys").
Result("MIN(time) AS time", "SUM(value) AS value", "keys"). Desc("value").
Desc("value"). Group("keys").
Group("keys"). Limit(size).
Limit(size). Slice(&partialResult)
Slice(&result) if ignoreEmptyKeys {
if ignoreEmptyKeys { query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')")
query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')")
}
if len(ignoreKeys) > 0 {
ignoreKeysJSON, err := json.Marshal(ignoreKeys)
if err != nil {
return nil, err
} }
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置 if len(ignoreKeys) > 0 {
query.Param("ignoredKeys", string(ignoreKeysJSON)) ignoreKeysJSON, err := json.Marshal(ignoreKeys)
if err != nil {
return err
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
}
_, err = query.
FindAll()
if err != nil {
return err
}
locker.Lock()
result = append(result, partialResult...)
locker.Unlock()
return nil
})
if err != nil {
return nil, err
}
result = this.mergeStats(result)
sort.Slice(result, func(i, j int) bool {
return result[i].Value > result[j].Value
})
if len(result) > types.Int(size) {
result = result[:types.Int(size)]
} }
_, err = query.
FindAll()
return return
} }
// FindItemStatsWithServerIdAndLastTime 取得节点最近一次计时前 N 个数据 // FindItemStatsWithServerIdAndLastTime 取得服务最近一次计时前 N 个数据
// 适合每条数据中包含不同的Key的场景 // 适合每条数据中包含不同的Key的场景
func (this *MetricStatDAO) FindItemStatsWithServerIdAndLastTime(tx *dbs.Tx, serverId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) { func (this *MetricStatDAO) FindItemStatsWithServerIdAndLastTime(tx *dbs.Tx, serverId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) {
// 最近一次时间 // 最近一次时间
statOne, err := this.Query(tx). lastTime, err := SharedMetricItemDAO.FindMetricLastTime(tx, itemId)
Attr("itemId", itemId). if err != nil || len(lastTime) == 0 {
Attr("version", version).
DescPk().
Find()
if err != nil {
return nil, err return nil, err
} }
if statOne == nil {
return nil, nil
}
var lastStat = statOne.(*MetricStat)
var lastTime = lastStat.Time
var query = this.Query(tx). var query = this.Query(tx).
Table(this.partialTable(serverId)).
UseIndex("server_item_time"). UseIndex("server_item_time").
Attr("serverId", serverId). Attr("serverId", serverId).
Attr("itemId", itemId). Attr("itemId", itemId).
@@ -326,68 +458,119 @@ func (this *MetricStatDAO) FindItemStatsWithServerIdAndLastTime(tx *dbs.Tx, serv
// FindLatestItemStats 取得所有集群上最近 N 个时间的数据 // FindLatestItemStats 取得所有集群上最近 N 个时间的数据
// 适合同个Key在不同时间段的变化场景 // 适合同个Key在不同时间段的变化场景
func (this *MetricStatDAO) FindLatestItemStats(tx *dbs.Tx, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) { func (this *MetricStatDAO) FindLatestItemStats(tx *dbs.Tx, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) {
var query = this.Query(tx). err = this.runBatch(func(table string, locker *sync.Mutex) error {
Attr("itemId", itemId). var partialResult = []*MetricStat{}
Attr("version", version). var query = this.Query(tx).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等 Table(table).
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE Attr("itemId", itemId).
Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`"). Attr("version", version).
Desc("time"). // TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
Group("time"). // TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Limit(size). Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`").
Slice(&result) Desc("time").
if ignoreEmptyKeys { Group("time").
query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')") Limit(size).
} Slice(&partialResult)
if len(ignoreKeys) > 0 { if ignoreEmptyKeys {
ignoreKeysJSON, err := json.Marshal(ignoreKeys) query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')")
if err != nil { }
return nil, err if len(ignoreKeys) > 0 {
ignoreKeysJSON, err := json.Marshal(ignoreKeys)
if err != nil {
return err
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
} }
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
}
_, err = query. _, err = query.
FindAll() FindAll()
if err != nil {
return err
}
locker.Lock()
result = append(result, partialResult...)
locker.Unlock()
return nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
result = this.mergeStats(result)
sort.Slice(result, func(i, j int) bool {
return result[i].Time > result[j].Time
})
if len(result) > types.Int(size) {
result = result[:types.Int(size)]
}
lists.Reverse(result) lists.Reverse(result)
return return
} }
// FindLatestItemStatsWithClusterId 取得集群最近 N 个时间的数据 // FindLatestItemStatsWithClusterId 取得集群最近 N 个时间的数据
// 适合同个Key在不同时间段的变化场景 // 适合同个Key在不同时间段的变化场景
func (this *MetricStatDAO) FindLatestItemStatsWithClusterId(tx *dbs.Tx, clusterId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) { func (this *MetricStatDAO) FindLatestItemStatsWithClusterId(tx *dbs.Tx, clusterId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) {
var query = this.Query(tx). err = this.runBatch(func(table string, locker *sync.Mutex) error {
Attr("clusterId", clusterId). var partialResult = []*MetricStat{}
Attr("itemId", itemId). var query = this.Query(tx).
Attr("version", version). Table(table).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等 Attr("clusterId", clusterId).
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE Attr("itemId", itemId).
Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`"). Attr("version", version).
Desc("time"). // TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
Group("time"). // TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Limit(size). Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`").
Slice(&result) Desc("time").
if ignoreEmptyKeys { Group("time").
query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')") Limit(size).
} Slice(&partialResult)
if len(ignoreKeys) > 0 { if ignoreEmptyKeys {
ignoreKeysJSON, err := json.Marshal(ignoreKeys) query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')")
if err != nil { }
return nil, err if len(ignoreKeys) > 0 {
ignoreKeysJSON, err := json.Marshal(ignoreKeys)
if err != nil {
return err
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
} }
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
}
_, err = query. _, err = query.
FindAll() FindAll()
if err != nil {
return err
}
locker.Lock()
result = append(result, partialResult...)
locker.Unlock()
return nil
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
result = this.mergeStats(result)
sort.Slice(result, func(i, j int) bool {
return result[i].Time > result[j].Time
})
if len(result) > types.Int(size) {
result = result[:types.Int(size)]
}
lists.Reverse(result) lists.Reverse(result)
return return
} }
@@ -395,34 +578,55 @@ func (this *MetricStatDAO) FindLatestItemStatsWithClusterId(tx *dbs.Tx, clusterI
// FindLatestItemStatsWithNodeId 取得节点最近 N 个时间的数据 // FindLatestItemStatsWithNodeId 取得节点最近 N 个时间的数据
// 适合同个Key在不同时间段的变化场景 // 适合同个Key在不同时间段的变化场景
func (this *MetricStatDAO) FindLatestItemStatsWithNodeId(tx *dbs.Tx, nodeId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) { func (this *MetricStatDAO) FindLatestItemStatsWithNodeId(tx *dbs.Tx, nodeId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) {
var query = this.Query(tx). err = this.runBatch(func(table string, locker *sync.Mutex) error {
Attr("nodeId", nodeId). var partialResult = []*MetricStat{}
Attr("itemId", itemId). var query = this.Query(tx).
Attr("version", version). Table(table).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等 Attr("nodeId", nodeId).
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE Attr("itemId", itemId).
Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`"). Attr("version", version).
Desc("time"). // TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
Group("time"). // TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Limit(size). Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`").
Slice(&result) Desc("time").
if ignoreEmptyKeys { Group("time").
query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')") Limit(size).
} Slice(&partialResult)
if len(ignoreKeys) > 0 { if ignoreEmptyKeys {
ignoreKeysJSON, err := json.Marshal(ignoreKeys) query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')")
if err != nil {
return nil, err
} }
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置 if len(ignoreKeys) > 0 {
query.Param("ignoredKeys", string(ignoreKeysJSON)) ignoreKeysJSON, err := json.Marshal(ignoreKeys)
if err != nil {
return err
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
}
_, err = query.
FindAll()
if err != nil {
return err
}
locker.Lock()
result = append(result, partialResult...)
locker.Unlock()
return nil
})
result = this.mergeStats(result)
sort.Slice(result, func(i, j int) bool {
return result[i].Time > result[j].Time
})
if len(result) > types.Int(size) {
result = result[:types.Int(size)]
} }
_, err = query.
FindAll()
if err != nil {
return nil, err
}
lists.Reverse(result) lists.Reverse(result)
return return
} }
@@ -431,6 +635,7 @@ func (this *MetricStatDAO) FindLatestItemStatsWithNodeId(tx *dbs.Tx, nodeId int6
// 适合同个Key在不同时间段的变化场景 // 适合同个Key在不同时间段的变化场景
func (this *MetricStatDAO) FindLatestItemStatsWithServerId(tx *dbs.Tx, serverId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) { func (this *MetricStatDAO) FindLatestItemStatsWithServerId(tx *dbs.Tx, serverId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) {
var query = this.Query(tx). var query = this.Query(tx).
Table(this.partialTable(serverId)).
Attr("serverId", serverId). Attr("serverId", serverId).
Attr("itemId", itemId). Attr("itemId", itemId).
Attr("version", version). Attr("version", version).
@@ -474,17 +679,22 @@ func (this *MetricStatDAO) Clean(tx *dbs.Tx) error {
} }
for _, item := range items { for _, item := range items {
var config = &serverconfigs.MetricItemConfig{ var config = &serverconfigs.MetricItemConfig{
Id: int64(item.Id), Id: int64(item.Id),
Period: int(item.Period), Period: int(item.Period),
PeriodUnit: item.PeriodUnit, PeriodUnit: item.PeriodUnit,
ExpiresPeriod: int(item.ExpiresPeriod),
} }
var expiresDay = config.ServerExpiresDay() var expiresDay = config.ServerExpiresDay()
_, err := this.Query(tx). err := this.runBatch(func(table string, locker *sync.Mutex) error {
Attr("itemId", item.Id). _, err := this.Query(tx).
Lte("createdDay", expiresDay). Table(table).
UseIndex("createdDay"). Attr("itemId", item.Id).
Limit(100_000). // 一次性不要删除太多,防止阻塞其他操作 Lte("createdDay", expiresDay).
Delete() UseIndex("createdDay").
Limit(10_000). // 一次性不要删除太多,防止阻塞其他操作
Delete()
return err
})
if err != nil { if err != nil {
return err return err
} }
@@ -499,3 +709,45 @@ func (this *MetricStatDAO) Clean(tx *dbs.Tx) error {
} }
return nil return nil
} }
// 获取分区表
func (this *MetricStatDAO) partialTable(serverId int64) string {
return this.Table + "_" + types.String(serverId%int64(MetricStatTablePartials))
}
// 批量执行
func (this *MetricStatDAO) runBatch(f func(table string, locker *sync.Mutex) error) error {
var locker = &sync.Mutex{}
var wg = sync.WaitGroup{}
wg.Add(MetricStatTablePartials)
var resultErr error
for i := 0; i < MetricStatTablePartials; i++ {
var table = this.partialTable(int64(i))
go func(table string) {
defer wg.Done()
err := f(table, locker)
if err != nil {
resultErr = err
}
}(table)
}
wg.Wait()
return resultErr
}
// 合并统计数据
func (this *MetricStatDAO) mergeStats(stats []*MetricStat) (result []*MetricStat) {
var m = map[string]*MetricStat{} // key+time => *MetricStat
for _, stat := range stats {
var uniqueKey = stat.Time + "@" + string(stat.Keys)
oldStat, ok := m[uniqueKey]
if !ok {
result = append(result, stat)
m[uniqueKey] = stat
} else {
oldStat.Value += stat.Value
}
}
return result
}

View File

@@ -1,6 +1,7 @@
package models package models_test
import ( import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
@@ -8,11 +9,12 @@ import (
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"testing" "testing"
"time"
) )
func TestNewMetricStatDAO_InsertMany(t *testing.T) { func TestNewMetricStatDAO_InsertMany(t *testing.T) {
for i := 0; i <= 1; i++ { for i := 0; i <= 1; i++ {
err := NewMetricStatDAO().CreateStat(nil, types.String(i)+"_v1", 18, int64(rands.Int(0, 10000)), int64(rands.Int(0, 10000)), int64(rands.Int(0, 100)), []string{"/html" + types.String(i)}, 1, timeutil.Format("Ymd"), 0) err := models.NewMetricStatDAO().CreateStat(nil, types.String(i)+"_v1", 18, int64(rands.Int(0, 10000)), int64(rands.Int(0, 10000)), int64(rands.Int(0, 100)), []string{"/html" + types.String(i)}, 1, timeutil.Format("Ymd"), 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -23,12 +25,38 @@ func TestNewMetricStatDAO_InsertMany(t *testing.T) {
t.Log("done") t.Log("done")
} }
func TestMetricStatDAO_Clean(t *testing.T) { func TestMetricStatDAO_Clean2(t *testing.T) {
dbs.NotifyReady() dbs.NotifyReady()
err := NewMetricStatDAO().Clean(nil) err := models.NewMetricStatDAO().Clean(nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log("ok") t.Log("ok")
} }
func TestMetricStatDAO_DeleteNodeItemStats(t *testing.T) {
var dao = models.NewMetricStatDAO()
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
err := dao.DeleteNodeItemStats(nil, 1, 0, 1, timeutil.Format("Ymd"))
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}
func TestMetricStatDAO_CountItemStats(t *testing.T) {
var dao = models.NewMetricStatDAO()
var before = time.Now()
defer func() {
t.Log(time.Since(before).Seconds()*1000, "ms")
}()
count, err := dao.CountItemStats(nil, 1, 1)
if err != nil {
t.Fatal(err)
}
t.Log("count:", count)
}

View File

@@ -2,19 +2,23 @@ package models
import ( import (
"github.com/TeaOSLab/EdgeAPI/internal/goman" "github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"sync"
"time" "time"
) )
type MetricSumStatDAO dbs.DAO type MetricSumStatDAO dbs.DAO
const MetricSumStatTablePartials = 20 // 表格Partial数量
func init() { func init() {
dbs.OnReadyDone(func() { dbs.OnReadyDone(func() {
// 清理数据任务 // 清理数据任务
@@ -23,7 +27,7 @@ func init() {
for range ticker.C { for range ticker.C {
err := SharedMetricSumStatDAO.Clean(nil) err := SharedMetricSumStatDAO.Clean(nil)
if err != nil { if err != nil {
logs.Println("SharedMetricSumStatDAO: clean expired data failed: " + err.Error()) remotelogs.Error("SharedMetricSumStatDAO", "clean expired data failed: "+err.Error())
} }
} }
}) })
@@ -52,6 +56,7 @@ func init() {
// UpdateSum 更新统计数据 // UpdateSum 更新统计数据
func (this *MetricSumStatDAO) UpdateSum(tx *dbs.Tx, clusterId int64, nodeId int64, serverId int64, time string, itemId int64, version int32, count int64, total float32) error { func (this *MetricSumStatDAO) UpdateSum(tx *dbs.Tx, clusterId int64, nodeId int64, serverId int64, time string, itemId int64, version int32, count int64, total float32) error {
return this.Query(tx). return this.Query(tx).
Table(this.partialTable(serverId)).
InsertOrUpdateQuickly(maps.Map{ InsertOrUpdateQuickly(maps.Map{
"clusterId": clusterId, "clusterId": clusterId,
"nodeId": nodeId, "nodeId": nodeId,
@@ -71,6 +76,7 @@ func (this *MetricSumStatDAO) UpdateSum(tx *dbs.Tx, clusterId int64, nodeId int6
// FindNodeServerSum 查找某个服务在某个节点上的统计数据 // FindNodeServerSum 查找某个服务在某个节点上的统计数据
func (this *MetricSumStatDAO) FindNodeServerSum(tx *dbs.Tx, nodeId int64, serverId int64, time string, itemId int64, version int32) (count int64, total float32, err error) { func (this *MetricSumStatDAO) FindNodeServerSum(tx *dbs.Tx, nodeId int64, serverId int64, time string, itemId int64, version int32) (count int64, total float32, err error) {
one, err := this.Query(tx). one, err := this.Query(tx).
Table(this.partialTable(serverId)).
Attr("nodeId", nodeId). Attr("nodeId", nodeId).
Attr("serverId", serverId). Attr("serverId", serverId).
Attr("time", time). Attr("time", time).
@@ -83,29 +89,42 @@ func (this *MetricSumStatDAO) FindNodeServerSum(tx *dbs.Tx, nodeId int64, server
if one == nil { if one == nil {
return return
} }
return int64(one.(*MetricSumStat).Count), float32(one.(*MetricSumStat).Total), nil
count = int64(one.(*MetricSumStat).Count)
total = float32(one.(*MetricSumStat).Total)
return
} }
// FindSumAtTime 查找某个时间的统计数据 // FindSumAtTime 查找某个时间的统计数据
func (this *MetricSumStatDAO) FindSumAtTime(tx *dbs.Tx, time string, itemId int64, version int32) (count int64, total float32, err error) { func (this *MetricSumStatDAO) FindSumAtTime(tx *dbs.Tx, time string, itemId int64, version int32) (count int64, total float32, err error) {
one, err := this.Query(tx). err = this.runBatch(func(table string, locker *sync.Mutex) error {
Attr("time", time). one, err := this.Query(tx).
Attr("itemId", itemId). Table(table).
Attr("version", version). Attr("time", time).
Result("SUM(count) AS `count`, SUM(total) AS total"). Attr("itemId", itemId).
Find() Attr("version", version).
if err != nil { Result("SUM(count) AS `count`, SUM(total) AS total").
return 0, 0, err Find()
} if err != nil {
if one == nil { return err
return }
} if one == nil {
return int64(one.(*MetricSumStat).Count), float32(one.(*MetricSumStat).Total), nil return nil
}
locker.Lock()
count += int64(one.(*MetricSumStat).Count)
total += float32(one.(*MetricSumStat).Total)
locker.Unlock()
return nil
})
return
} }
// FindServerSum 查找某个服务的统计数据 // FindServerSum 查找某个服务的统计数据
func (this *MetricSumStatDAO) FindServerSum(tx *dbs.Tx, serverId int64, time string, itemId int64, version int32) (count int64, total float32, err error) { func (this *MetricSumStatDAO) FindServerSum(tx *dbs.Tx, serverId int64, time string, itemId int64, version int32) (count int64, total float32, err error) {
one, err := this.Query(tx). one, err := this.Query(tx).
Table(this.partialTable(serverId)).
UseIndex("server_item_time"). UseIndex("server_item_time").
Attr("serverId", serverId). Attr("serverId", serverId).
Attr("time", time). Attr("time", time).
@@ -124,48 +143,69 @@ func (this *MetricSumStatDAO) FindServerSum(tx *dbs.Tx, serverId int64, time str
// FindClusterSum 查找集群上的统计数据 // FindClusterSum 查找集群上的统计数据
func (this *MetricSumStatDAO) FindClusterSum(tx *dbs.Tx, clusterId int64, time string, itemId int64, version int32) (count int64, total float32, err error) { func (this *MetricSumStatDAO) FindClusterSum(tx *dbs.Tx, clusterId int64, time string, itemId int64, version int32) (count int64, total float32, err error) {
one, err := this.Query(tx). err = this.runBatch(func(table string, locker *sync.Mutex) error {
UseIndex("cluster_item_time"). one, err := this.Query(tx).
Attr("clusterId", clusterId). Table(table).
Attr("time", time). UseIndex("cluster_item_time").
Attr("itemId", itemId). Attr("clusterId", clusterId).
Attr("version", version). Attr("time", time).
Result("SUM(count) AS `count`, SUM(total) AS total"). Attr("itemId", itemId).
Find() Attr("version", version).
if err != nil { Result("SUM(count) AS `count`, SUM(total) AS total").
return 0, 0, err Find()
} if err != nil {
if one == nil { return err
return }
} if one == nil {
return int64(one.(*MetricSumStat).Count), float32(one.(*MetricSumStat).Total), nil return nil
}
locker.Lock()
count += int64(one.(*MetricSumStat).Count)
total += float32(one.(*MetricSumStat).Total)
locker.Unlock()
return nil
})
return
} }
// FindNodeSum 查找节点上的统计数据 // FindNodeSum 查找节点上的统计数据
func (this *MetricSumStatDAO) FindNodeSum(tx *dbs.Tx, nodeId int64, time string, itemId int64, version int32) (count int64, total float32, err error) { func (this *MetricSumStatDAO) FindNodeSum(tx *dbs.Tx, nodeId int64, time string, itemId int64, version int32) (count int64, total float32, err error) {
one, err := this.Query(tx). err = this.runBatch(func(table string, locker *sync.Mutex) error {
UseIndex("node_item_time"). one, err := this.Query(tx).
Attr("nodeId", nodeId). Table(table).
Attr("time", time). UseIndex("node_item_time").
Attr("itemId", itemId). Attr("nodeId", nodeId).
Attr("version", version). Attr("time", time).
Result("SUM(count) AS `count`, SUM(total) AS total"). Attr("itemId", itemId).
Find() Attr("version", version).
if err != nil { Result("SUM(count) AS `count`, SUM(total) AS total").
return 0, 0, err Find()
} if err != nil {
if one == nil { return err
return }
} if one == nil {
return int64(one.(*MetricSumStat).Count), float32(one.(*MetricSumStat).Total), nil return nil
}
locker.Lock()
count += int64(one.(*MetricSumStat).Count)
total += float32(one.(*MetricSumStat).Total)
locker.Unlock()
return nil
})
return
} }
// DeleteItemStats 删除某个指标相关的统计数据 // DeleteItemStats 删除某个指标相关的统计数据
func (this *MetricSumStatDAO) DeleteItemStats(tx *dbs.Tx, itemId int64) error { func (this *MetricSumStatDAO) DeleteItemStats(tx *dbs.Tx, itemId int64) error {
_, err := this.Query(tx). return this.runBatch(func(table string, locker *sync.Mutex) error {
Attr("itemId", itemId). _, err := this.Query(tx).
Delete() Table(table).
return err Attr("itemId", itemId).
Delete()
return err
})
} }
// Clean 清理数据 // Clean 清理数据
@@ -180,18 +220,23 @@ func (this *MetricSumStatDAO) Clean(tx *dbs.Tx) error {
} }
for _, item := range items { for _, item := range items {
var config = &serverconfigs.MetricItemConfig{ var config = &serverconfigs.MetricItemConfig{
Id: int64(item.Id), Id: int64(item.Id),
Period: int(item.Period), Period: int(item.Period),
PeriodUnit: item.PeriodUnit, PeriodUnit: item.PeriodUnit,
ExpiresPeriod: int(item.ExpiresPeriod),
} }
var expiresDay = config.ServerExpiresDay() var expiresDay = config.ServerExpiresDay()
_, err := this.Query(tx). err = this.runBatch(func(table string, locker *sync.Mutex) error {
Attr("itemId", item.Id). _, err := this.Query(tx).
Where("(createdDay IS NULL OR createdDay<:day)"). Table(table).
Param("day", expiresDay). Attr("itemId", item.Id).
UseIndex("createdDay"). Where("(createdDay IS NULL OR createdDay<:day)").
Limit(100_000). // 一次性不要删除太多,防止阻塞其他操作 Param("day", expiresDay).
Delete() UseIndex("createdDay").
Limit(10_000). // 一次性不要删除太多,防止阻塞其他操作
Delete()
return err
})
if err != nil { if err != nil {
return err return err
} }
@@ -206,3 +251,29 @@ func (this *MetricSumStatDAO) Clean(tx *dbs.Tx) error {
} }
return nil return nil
} }
// 获取分区表
func (this *MetricSumStatDAO) partialTable(serverId int64) string {
return this.Table + "_" + types.String(serverId%int64(MetricSumStatTablePartials))
}
// 批量执行
func (this *MetricSumStatDAO) runBatch(f func(table string, locker *sync.Mutex) error) error {
var locker = &sync.Mutex{}
var wg = sync.WaitGroup{}
wg.Add(MetricSumStatTablePartials)
var resultErr error
for i := 0; i < MetricSumStatTablePartials; i++ {
var table = this.partialTable(int64(i))
go func(table string) {
defer wg.Done()
err := f(table, locker)
if err != nil {
resultErr = err
}
}(table)
}
wg.Wait()
return resultErr
}

View File

@@ -1,16 +1,22 @@
package models package models_test
import ( import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
"testing" "testing"
) )
func TestMetricSumStatDAO_FindNodeSum(t *testing.T) {
t.Log(models.NewMetricSumStatDAO().FindNodeSum(nil, 46, timeutil.Format("Ymd"), 1, 1))
}
func TestMetricSumStatDAO_Clean(t *testing.T) { func TestMetricSumStatDAO_Clean(t *testing.T) {
dbs.NotifyReady() dbs.NotifyReady()
err := NewMetricSumStatDAO().Clean(nil) err := models.NewMetricSumStatDAO().Clean(nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -2,6 +2,7 @@ package nameservers
import ( import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/db/models"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
@@ -167,7 +168,7 @@ func (this *NSDomainDAO) CountAllEnabledDomains(tx *dbs.Tx, clusterId int64, use
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword)"). query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query. return query.
@@ -190,7 +191,7 @@ func (this *NSDomainDAO) ListEnabledDomains(tx *dbs.Tx, clusterId int64, userId
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword)"). query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query. _, err = query.
State(NSDomainStateEnabled). State(NSDomainStateEnabled).

View File

@@ -3,6 +3,7 @@ package nameservers
import ( import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/db/models"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
@@ -177,7 +178,7 @@ func (this *NSRecordDAO) CountAllEnabledDomainRecords(tx *dbs.Tx, domainId int64
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR value LIKE :keyword OR description LIKE :keyword)"). query.Where("(name LIKE :keyword OR value LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(routeCode) > 0 { if len(routeCode) > 0 {
routeCodeJSON, err := json.Marshal(routeCode) routeCodeJSON, err := json.Marshal(routeCode)
@@ -207,7 +208,7 @@ func (this *NSRecordDAO) ListEnabledRecords(tx *dbs.Tx, domainId int64, dnsType
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR value LIKE :keyword OR description LIKE :keyword)"). query.Where("(name LIKE :keyword OR value LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(routeCode) > 0 { if len(routeCode) > 0 {
routeCodeJSON, err := json.Marshal(routeCode) routeCodeJSON, err := json.Marshal(routeCode)

View File

@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/dns" "github.com/TeaOSLab/EdgeAPI/internal/db/models/dns"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
@@ -96,6 +97,7 @@ func (this *NodeClusterDAO) FindAllEnableClusters(tx *dbs.Tx) (result []*NodeClu
_, err = this.Query(tx). _, err = this.Query(tx).
State(NodeClusterStateEnabled). State(NodeClusterStateEnabled).
Slice(&result). Slice(&result).
Desc("isPinned").
Desc("order"). Desc("order").
DescPk(). DescPk().
FindAll() FindAll()
@@ -221,7 +223,7 @@ func (this *NodeClusterDAO) CountAllEnabledClusters(tx *dbs.Tx, keyword string)
State(NodeClusterStateEnabled) State(NodeClusterStateEnabled)
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR dnsName like :keyword OR (dnsDomainId > 0 AND dnsDomainId IN (SELECT id FROM "+dns.SharedDNSDomainDAO.Table+" WHERE name LIKE :keyword AND state=1)))"). query.Where("(name LIKE :keyword OR dnsName like :keyword OR (dnsDomainId > 0 AND dnsDomainId IN (SELECT id FROM "+dns.SharedDNSDomainDAO.Table+" WHERE name LIKE :keyword AND state=1)))").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query.Count() return query.Count()
} }
@@ -232,7 +234,7 @@ func (this *NodeClusterDAO) ListEnabledClusters(tx *dbs.Tx, keyword string, offs
State(NodeClusterStateEnabled) State(NodeClusterStateEnabled)
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR dnsName like :keyword OR (dnsDomainId > 0 AND dnsDomainId IN (SELECT id FROM "+dns.SharedDNSDomainDAO.Table+" WHERE name LIKE :keyword AND state=1)))"). query.Where("(name LIKE :keyword OR dnsName like :keyword OR (dnsDomainId > 0 AND dnsDomainId IN (SELECT id FROM "+dns.SharedDNSDomainDAO.Table+" WHERE name LIKE :keyword AND state=1)))").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query. _, err = query.
Offset(offset). Offset(offset).
@@ -469,7 +471,29 @@ func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsNam
if clusterId <= 0 { if clusterId <= 0 {
return errors.New("invalid clusterId") return errors.New("invalid clusterId")
} }
op := NewNodeClusterOperator()
// 删除老的域名中相关记录
oldOne, err := this.Query(tx).
Pk(clusterId).
Result("dnsName", "dnsDomainId").
Find()
if err != nil {
return err
}
if oldOne == nil {
return nil
}
var oldCluster = oldOne.(*NodeCluster)
var oldDNSDomainId = int64(oldCluster.DnsDomainId)
if (oldDNSDomainId > 0 && oldDNSDomainId != dnsDomainId) || (oldCluster.DnsName != dnsName) {
err = dns.SharedDNSTaskDAO.CreateClusterRemoveTask(tx, clusterId, oldDNSDomainId, oldCluster.DnsName)
if err != nil {
return err
}
}
var op = NewNodeClusterOperator()
op.Id = clusterId op.Id = clusterId
op.DnsName = dnsName op.DnsName = dnsName
op.DnsDomainId = dnsDomainId op.DnsDomainId = dnsDomainId
@@ -478,7 +502,7 @@ func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsNam
cnameRecords = []string{} cnameRecords = []string{}
} }
dnsConfig := &dnsconfigs.ClusterDNSConfig{ var dnsConfig = &dnsconfigs.ClusterDNSConfig{
NodesAutoSync: nodesAutoSync, NodesAutoSync: nodesAutoSync,
ServersAutoSync: serversAutoSync, ServersAutoSync: serversAutoSync,
CNameRecords: cnameRecords, CNameRecords: cnameRecords,
@@ -897,7 +921,8 @@ func (this *NodeClusterDAO) FindClusterBasicInfo(tx *dbs.Tx, clusterId int64, ca
cluster, err := this.Query(tx). cluster, err := this.Query(tx).
Pk(clusterId). Pk(clusterId).
Result("timeZone", "nodeMaxThreads", "nodeTCPMaxConnections", "cachePolicyId", "httpFirewallPolicyId", "autoOpenPorts"). State(NodeClusterStateEnabled).
Result("timeZone", "nodeMaxThreads", "nodeTCPMaxConnections", "cachePolicyId", "httpFirewallPolicyId", "autoOpenPorts", "webp", "isOn").
Find() Find()
if err != nil || cluster == nil { if err != nil || cluster == nil {
return nil, err return nil, err
@@ -908,6 +933,65 @@ func (this *NodeClusterDAO) FindClusterBasicInfo(tx *dbs.Tx, clusterId int64, ca
return cluster.(*NodeCluster), nil return cluster.(*NodeCluster), nil
} }
// UpdateClusterWebPPolicy 修改WebP设置
func (this *NodeClusterDAO) UpdateClusterWebPPolicy(tx *dbs.Tx, clusterId int64, webpPolicy *nodeconfigs.WebPImagePolicy) error {
if webpPolicy == nil {
err := this.Query(tx).
Pk(clusterId).
Set("webp", dbs.SQL("null")).
UpdateQuickly()
if err != nil {
return err
}
return this.NotifyUpdate(tx, clusterId)
}
webpPolicyJSON, err := json.Marshal(webpPolicy)
if err != nil {
return err
}
err = this.Query(tx).
Pk(clusterId).
Set("webp", webpPolicyJSON).
UpdateQuickly()
if err != nil {
return err
}
return this.NotifyUpdate(tx, clusterId)
}
// FindClusterWebPPolicy 查询WebP设置
func (this *NodeClusterDAO) FindClusterWebPPolicy(tx *dbs.Tx, clusterId int64, cacheMap *utils.CacheMap) (*nodeconfigs.WebPImagePolicy, error) {
var cacheKey = this.Table + ":FindClusterWebPPolicy:" + types.String(clusterId)
if cacheMap != nil {
cache, ok := cacheMap.Get(cacheKey)
if ok {
return cache.(*nodeconfigs.WebPImagePolicy), nil
}
}
webpJSON, err := this.Query(tx).
Pk(clusterId).
Result("webp").
FindJSONCol()
if err != nil {
return nil, err
}
if IsNull(webpJSON) {
return nodeconfigs.DefaultWebPImagePolicy, nil
}
var policy = &nodeconfigs.WebPImagePolicy{}
err = json.Unmarshal(webpJSON, policy)
if err != nil {
return nil, err
}
return policy, nil
}
// NotifyUpdate 通知更新 // NotifyUpdate 通知更新
func (this *NodeClusterDAO) NotifyUpdate(tx *dbs.Tx, clusterId int64) error { func (this *NodeClusterDAO) NotifyUpdate(tx *dbs.Tx, clusterId int64) error {
return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, 0, NodeTaskTypeConfigChanged) return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, 0, NodeTaskTypeConfigChanged)

View File

@@ -33,6 +33,7 @@ type NodeCluster struct {
NodeTCPMaxConnections uint32 `field:"nodeTCPMaxConnections"` // TCP最大连接数 NodeTCPMaxConnections uint32 `field:"nodeTCPMaxConnections"` // TCP最大连接数
AutoOpenPorts uint8 `field:"autoOpenPorts"` // 是否自动尝试开放端口 AutoOpenPorts uint8 `field:"autoOpenPorts"` // 是否自动尝试开放端口
IsPinned bool `field:"isPinned"` // 是否置顶 IsPinned bool `field:"isPinned"` // 是否置顶
Webp dbs.JSON `field:"webp"` // WebP设置
} }
type NodeClusterOperator struct { type NodeClusterOperator struct {
@@ -65,6 +66,7 @@ type NodeClusterOperator struct {
NodeTCPMaxConnections interface{} // TCP最大连接数 NodeTCPMaxConnections interface{} // TCP最大连接数
AutoOpenPorts interface{} // 是否自动尝试开放端口 AutoOpenPorts interface{} // 是否自动尝试开放端口
IsPinned interface{} // 是否置顶 IsPinned interface{} // 是否置顶
Webp interface{} // WebP设置
} }
func NewNodeClusterOperator() *NodeClusterOperator { func NewNodeClusterOperator() *NodeClusterOperator {

View File

@@ -1,12 +1,16 @@
package models package models
import ( import (
"crypto/sha256"
"encoding/json" "encoding/json"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const" teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/dns" "github.com/TeaOSLab/EdgeAPI/internal/db/models/dns"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils" "github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/sizes"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
@@ -19,8 +23,10 @@ import (
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands" "github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"strconv" "strconv"
"strings" "strings"
"time"
) )
const ( const (
@@ -136,6 +142,7 @@ func (this *NodeDAO) CreateNode(tx *dbs.Tx, adminId int64, name string, clusterI
if teaconst.MaxNodes > 0 { if teaconst.MaxNodes > 0 {
count, err := this.Query(tx). count, err := this.Query(tx).
State(NodeStateEnabled). State(NodeStateEnabled).
Where("clusterId IN (SELECT id FROM " + SharedNodeClusterDAO.Table + " WHERE state=1)").
Count() Count()
if err != nil { if err != nil {
return 0, err return 0, err
@@ -190,7 +197,7 @@ func (this *NodeDAO) CreateNode(tx *dbs.Tx, adminId int64, name string, clusterI
} }
// UpdateNode 修改节点 // UpdateNode 修改节点
func (this *NodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, clusterId int64, secondaryClusterIds []int64, groupId int64, regionId int64, isOn bool) error { func (this *NodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, clusterId int64, secondaryClusterIds []int64, groupId int64, regionId int64, isOn bool, level int) error {
if nodeId <= 0 { if nodeId <= 0 {
return errors.New("invalid nodeId") return errors.New("invalid nodeId")
} }
@@ -201,7 +208,16 @@ func (this *NodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, clusterId
return err return err
} }
op := NewNodeOperator() // 老的级别
oldLevel, err := this.Query(tx).
Pk(nodeId).
Result("level").
FindIntCol(0)
if err != nil {
return err
}
var op = NewNodeOperator()
op.Id = nodeId op.Id = nodeId
op.Name = name op.Name = name
op.ClusterId = clusterId op.ClusterId = clusterId
@@ -227,6 +243,11 @@ func (this *NodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, clusterId
op.RegionId = regionId op.RegionId = regionId
op.LatestVersion = dbs.SQL("latestVersion+1") op.LatestVersion = dbs.SQL("latestVersion+1")
op.IsOn = isOn op.IsOn = isOn
if teaconst.IsPlus {
op.Level = level
}
err = this.Save(tx, op) err = this.Save(tx, op)
if err != nil { if err != nil {
return err return err
@@ -237,7 +258,7 @@ func (this *NodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, clusterId
return err return err
} }
// 通知老的集群更新 // 通知老的集群DNS更新
for _, oldClusterId := range oldClusterIds { for _, oldClusterId := range oldClusterIds {
if oldClusterId != clusterId && !lists.ContainsInt64(secondaryClusterIds, oldClusterId) { if oldClusterId != clusterId && !lists.ContainsInt64(secondaryClusterIds, oldClusterId) {
err = dns.SharedDNSTaskDAO.CreateClusterTask(tx, oldClusterId, dns.DNSTaskTypeClusterChange) err = dns.SharedDNSTaskDAO.CreateClusterTask(tx, oldClusterId, dns.DNSTaskTypeClusterChange)
@@ -247,6 +268,14 @@ func (this *NodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, clusterId
} }
} }
// 通知子级级别变更
if oldLevel > 1 || level > 1 {
err = this.NotifyLevelUpdate(tx, nodeId)
if err != nil {
return err
}
}
return this.NotifyDNSUpdate(tx, nodeId) return this.NotifyDNSUpdate(tx, nodeId)
} }
@@ -277,11 +306,13 @@ func (this *NodeDAO) ListEnabledNodesMatch(tx *dbs.Tx,
keyword string, keyword string,
groupId int64, groupId int64,
regionId int64, regionId int64,
level int32,
includeSecondaryNodes bool, includeSecondaryNodes bool,
order string, order string,
offset int64, offset int64,
size int64) (result []*Node, err error) { size int64) (result []*Node, err error) {
query := this.Query(tx). query := this.Query(tx).
Result(this.Table + ".*"). // must have table name for table joins below
State(NodeStateEnabled). State(NodeStateEnabled).
Offset(offset). Offset(offset).
Limit(size). Limit(size).
@@ -290,14 +321,14 @@ func (this *NodeDAO) ListEnabledNodesMatch(tx *dbs.Tx,
// 集群 // 集群
if clusterId > 0 { if clusterId > 0 {
if includeSecondaryNodes { if includeSecondaryNodes {
query.Where("(clusterId=:primaryClusterId OR JSON_CONTAINS(secondaryClusterIds, :primaryClusterIdString))"). query.Where("("+this.Table+".clusterId=:primaryClusterId OR JSON_CONTAINS(secondaryClusterIds, :primaryClusterIdString))").
Param("primaryClusterId", clusterId). Param("primaryClusterId", clusterId).
Param("primaryClusterIdString", types.String(clusterId)) Param("primaryClusterIdString", types.String(clusterId))
} else { } else {
query.Attr("clusterId", clusterId) query.Attr(this.Table+".clusterId", clusterId)
} }
} else { } else {
query.Where("clusterId IN (SELECT id FROM " + SharedNodeClusterDAO.Table + " WHERE state=1)") query.Where(this.Table + ".clusterId IN (SELECT id FROM " + SharedNodeClusterDAO.Table + " WHERE state=1)")
} }
// 安装状态 // 安装状态
@@ -323,7 +354,7 @@ func (this *NodeDAO) ListEnabledNodesMatch(tx *dbs.Tx,
// 关键词 // 关键词
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR JSON_EXTRACT(status,'$.hostname') LIKE :keyword OR id IN (SELECT nodeId FROM "+SharedNodeIPAddressDAO.Table+" WHERE ip LIKE :keyword))"). query.Where("(name LIKE :keyword OR JSON_EXTRACT(status,'$.hostname') LIKE :keyword OR id IN (SELECT nodeId FROM "+SharedNodeIPAddressDAO.Table+" WHERE ip LIKE :keyword))").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
// 分组 // 分组
@@ -336,25 +367,84 @@ func (this *NodeDAO) ListEnabledNodesMatch(tx *dbs.Tx,
query.Attr("regionId", regionId) query.Attr("regionId", regionId)
} }
// 级别
if level > 0 {
query.Attr("level", level)
}
// 排序 // 排序
var minute = timeutil.FormatTime("YmdHi", time.Now().Unix()-60)
var nodeValueTable = SharedNodeValueDAO.Table
var valueItem = ""
var valueField = ""
var isAsc = false
var ifNullValue int64 = 0
switch order { switch order {
case "cpuAsc": case "cpuAsc":
query.Asc("IF(JSON_EXTRACT(status, '$.updatedAt')>UNIX_TIMESTAMP()-120, IFNULL(JSON_EXTRACT(status, '$.cpuUsage'), 0), 0)") valueItem = "cpu"
valueField = "usage"
isAsc = true
ifNullValue = 100
case "cpuDesc": case "cpuDesc":
query.Desc("IF(JSON_EXTRACT(status, '$.updatedAt')>UNIX_TIMESTAMP()-120, IFNULL(JSON_EXTRACT(status, '$.cpuUsage'), 0), 0)") valueItem = "cpu"
valueField = "usage"
isAsc = false
ifNullValue = -1
case "memoryAsc": case "memoryAsc":
query.Asc("IF(JSON_EXTRACT(status, '$.updatedAt')>UNIX_TIMESTAMP()-120, IFNULL(JSON_EXTRACT(status, '$.memoryUsage'), 0), 0)") valueItem = "memory"
valueField = "usage"
isAsc = true
ifNullValue = 100
case "memoryDesc": case "memoryDesc":
query.Desc("IF(JSON_EXTRACT(status, '$.updatedAt')>UNIX_TIMESTAMP()-120, IFNULL(JSON_EXTRACT(status, '$.memoryUsage'), 0), 0)") valueItem = "memory"
valueField = "usage"
isAsc = false
ifNullValue = -1
case "trafficInAsc": case "trafficInAsc":
query.Asc("IF(JSON_EXTRACT(status, '$.updatedAt')>UNIX_TIMESTAMP()-120, IFNULL(JSON_EXTRACT(status, '$.trafficInBytes'), 0), 0)") valueItem = "trafficIn"
valueField = "total"
isAsc = true
ifNullValue = 60 * sizes.G
case "trafficInDesc": case "trafficInDesc":
query.Desc("IF(JSON_EXTRACT(status, '$.updatedAt')>UNIX_TIMESTAMP()-120, IFNULL(JSON_EXTRACT(status, '$.trafficInBytes'), 0), 0)") valueItem = "trafficIn"
valueField = "total"
isAsc = false
ifNullValue = -1
case "trafficOutAsc": case "trafficOutAsc":
query.Asc("IF(JSON_EXTRACT(status, '$.updatedAt')>UNIX_TIMESTAMP()-120, IFNULL(JSON_EXTRACT(status, '$.trafficOutBytes'), 0), 0)") valueItem = "trafficOut"
valueField = "total"
isAsc = true
ifNullValue = sizes.G
case "trafficOutDesc": case "trafficOutDesc":
query.Desc("IF(JSON_EXTRACT(status, '$.updatedAt')>UNIX_TIMESTAMP()-120, IFNULL(JSON_EXTRACT(status, '$.trafficOutBytes'), 0), 0)") valueItem = "trafficOut"
valueField = "total"
isAsc = false
ifNullValue = -1
case "loadAsc":
valueItem = "load"
valueField = "load1m"
isAsc = true
ifNullValue = 1000
case "loadDesc":
valueItem = "load"
valueField = "load1m"
isAsc = false
ifNullValue = -1
default:
query.Desc("level")
} }
if len(valueItem) > 0 {
query.Join(SharedNodeValueDAO, dbs.QueryJoinLeft, this.Table+".id="+nodeValueTable+".nodeId AND "+nodeValueTable+".role='"+nodeconfigs.NodeRoleNode+"' AND "+nodeValueTable+".item='"+valueItem+"' "+" AND "+nodeValueTable+".minute=:minute")
query.Param("minute", minute)
var ifNullSQL = "IFNULL(CAST(JSON_EXTRACT(value, '$." + valueField + "') AS DECIMAL (20, 6)), " + types.String(ifNullValue) + ")"
if isAsc {
query.Asc(ifNullSQL)
} else {
query.Desc(ifNullSQL)
}
}
query.DescPk() query.DescPk()
_, err = query.FindAll() _, err = query.FindAll()
@@ -399,6 +489,37 @@ func (this *NodeDAO) FindNodeClusterId(tx *dbs.Tx, nodeId int64) (int64, error)
return types.Int64(col), err return types.Int64(col), err
} }
// FindNodeLevel 获取节点级别
func (this *NodeDAO) FindNodeLevel(tx *dbs.Tx, nodeId int64) (int, error) {
level, err := this.Query(tx).
Pk(nodeId).
Result("level").
FindIntCol(0)
if err != nil {
return 0, err
}
if level < 1 {
level = 1
}
return level, nil
}
// FindNodeLevelInfo 获取节点级别相关信息
func (this *NodeDAO) FindNodeLevelInfo(tx *dbs.Tx, nodeId int64) (*Node, error) {
one, err := this.Query(tx).
Pk(nodeId).
Result("id", "clusterId", "secondaryClusterIds", "groupId", "level").
Find()
if err != nil || one == nil {
return nil, err
}
var node = one.(*Node)
if node.Level < 1 {
node.Level = 1
}
return node, nil
}
// FindEnabledAndOnNodeClusterIds 获取节点所属所有可用而且启用的集群ID // FindEnabledAndOnNodeClusterIds 获取节点所属所有可用而且启用的集群ID
func (this *NodeDAO) FindEnabledAndOnNodeClusterIds(tx *dbs.Tx, nodeId int64) (result []int64, err error) { func (this *NodeDAO) FindEnabledAndOnNodeClusterIds(tx *dbs.Tx, nodeId int64) (result []int64, err error) {
one, err := this.Query(tx). one, err := this.Query(tx).
@@ -473,6 +594,41 @@ func (this *NodeDAO) FindEnabledNodeIdsWithClusterId(tx *dbs.Tx, clusterId int64
return result, nil return result, nil
} }
// FindEnabledNodesWithGroupIdAndLevel 查找当前分组下的某个级别的所有节点
func (this *NodeDAO) FindEnabledNodesWithGroupIdAndLevel(tx *dbs.Tx, groupId int64, level int) (result []*Node, err error) {
if groupId <= 0 {
return
}
_, err = this.Query(tx).
State(NodeStateEnabled).
Result("id", "clusterId", "secondaryClusterIds", "uniqueId", "secret").
Attr("isOn", true).
Attr("groupId", groupId).
Attr("level", level).
Slice(&result).
FindAll()
return
}
// FindEnabledNodesWithClusterIdAndLevel 查找当前集群下的某个级别的所有节点
func (this *NodeDAO) FindEnabledNodesWithClusterIdAndLevel(tx *dbs.Tx, clusterId int64, level int) (result []*Node, err error) {
if clusterId <= 0 {
return
}
_, err = this.Query(tx).
State(NodeStateEnabled).
Result("id", "clusterId", "secondaryClusterIds", "uniqueId", "secret").
Attr("isOn", true).
Where("(clusterId=:primaryClusterId OR JSON_CONTAINS(secondaryClusterIds, :primaryClusterIdString))").
Param("primaryClusterId", clusterId).
Param("primaryClusterIdString", types.String(clusterId)).
Attr("groupId", 0). // 需要去掉已经有分组的
Attr("level", level).
Slice(&result).
FindAll()
return
}
// FindAllNodeIdsMatch 匹配节点并返回节点ID // FindAllNodeIdsMatch 匹配节点并返回节点ID
func (this *NodeDAO) FindAllNodeIdsMatch(tx *dbs.Tx, clusterId int64, includeSecondaryNodes bool, isOn configutils.BoolState) (result []int64, err error) { func (this *NodeDAO) FindAllNodeIdsMatch(tx *dbs.Tx, clusterId int64, includeSecondaryNodes bool, isOn configutils.BoolState) (result []int64, err error) {
var query = this.Query(tx) var query = this.Query(tx)
@@ -554,6 +710,7 @@ func (this *NodeDAO) CountAllEnabledNodesMatch(tx *dbs.Tx,
keyword string, keyword string,
groupId int64, groupId int64,
regionId int64, regionId int64,
level int32,
includeSecondaryNodes bool) (int64, error) { includeSecondaryNodes bool) (int64, error) {
query := this.Query(tx) query := this.Query(tx)
query.State(NodeStateEnabled) query.State(NodeStateEnabled)
@@ -594,7 +751,7 @@ func (this *NodeDAO) CountAllEnabledNodesMatch(tx *dbs.Tx,
// 关键词 // 关键词
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR JSON_EXTRACT(status,'$.hostname') LIKE :keyword OR id IN (SELECT nodeId FROM "+SharedNodeIPAddressDAO.Table+" WHERE ip LIKE :keyword))"). query.Where("(name LIKE :keyword OR JSON_EXTRACT(status,'$.hostname') LIKE :keyword OR id IN (SELECT nodeId FROM "+SharedNodeIPAddressDAO.Table+" WHERE ip LIKE :keyword))").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
// 分组 // 分组
@@ -607,6 +764,11 @@ func (this *NodeDAO) CountAllEnabledNodesMatch(tx *dbs.Tx,
query.Attr("regionId", regionId) query.Attr("regionId", regionId)
} }
// 级别
if level > 0 {
query.Attr("level", level)
}
return query.Count() return query.Count()
} }
@@ -732,7 +894,11 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, cacheMap *utils
return nil, errors.New("node not found '" + strconv.FormatInt(nodeId, 10) + "'") return nil, errors.New("node not found '" + strconv.FormatInt(nodeId, 10) + "'")
} }
config := &nodeconfigs.NodeConfig{ if node.Level < 1 {
node.Level = 1
}
var config = &nodeconfigs.NodeConfig{
Id: int64(node.Id), Id: int64(node.Id),
NodeId: node.UniqueId, NodeId: node.UniqueId,
Secret: node.Secret, Secret: node.Secret,
@@ -742,6 +908,8 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, cacheMap *utils
Name: node.Name, Name: node.Name,
MaxCPU: types.Int32(node.MaxCPU), MaxCPU: types.Int32(node.MaxCPU),
RegionId: int64(node.RegionId), RegionId: int64(node.RegionId),
Level: types.Int32(node.Level),
GroupId: int64(node.GroupId),
} }
// API节点IP // API节点IP
@@ -800,12 +968,13 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, cacheMap *utils
var clusterIds = []int64{primaryClusterId} var clusterIds = []int64{primaryClusterId}
clusterIds = append(clusterIds, node.DecodeSecondaryClusterIds()...) clusterIds = append(clusterIds, node.DecodeSecondaryClusterIds()...)
var clusterIndex = 0 var clusterIndex = 0
config.WebPImagePolicies = map[int64]*nodeconfigs.WebPImagePolicy{}
for _, clusterId := range clusterIds { for _, clusterId := range clusterIds {
nodeCluster, err := SharedNodeClusterDAO.FindClusterBasicInfo(tx, clusterId, cacheMap) nodeCluster, err := SharedNodeClusterDAO.FindClusterBasicInfo(tx, clusterId, cacheMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if nodeCluster == nil { if nodeCluster == nil || !nodeCluster.IsOn {
continue continue
} }
@@ -847,6 +1016,16 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, cacheMap *utils
config.AutoOpenPorts = nodeCluster.AutoOpenPorts == 1 config.AutoOpenPorts = nodeCluster.AutoOpenPorts == 1
} }
// webp
if IsNotNull(nodeCluster.Webp) {
var webpPolicy = &nodeconfigs.WebPImagePolicy{}
err = json.Unmarshal(nodeCluster.Webp, webpPolicy)
if err != nil {
return nil, err
}
config.WebPImagePolicies[clusterId] = webpPolicy
}
clusterIndex++ clusterIndex++
} }
@@ -955,6 +1134,12 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, cacheMap *utils
} }
config.OCSPVersion = ocspVersion config.OCSPVersion = ocspVersion
// 初始化扩展配置
err = this.composeExtConfig(tx, config, clusterIds, cacheMap)
if err != nil {
return nil, err
}
return config, nil return config, nil
} }
@@ -1491,19 +1676,99 @@ func (this *NodeDAO) TransferPrimaryClusterNodes(tx *dbs.Tx, primaryClusterId in
return nil return nil
} }
// NotifyUpdate 通知更新 // FindParentNodeConfigs 查找父级节点配置
func (this *NodeDAO) FindParentNodeConfigs(tx *dbs.Tx, nodeId int64, groupId int64, clusterIds []int64, level int) (result map[int64][]*nodeconfigs.ParentNodeConfig, err error) {
result = map[int64][]*nodeconfigs.ParentNodeConfig{} // clusterId => []*ParentNodeConfig
// 当前分组的L2
var parentNodes []*Node
if groupId > 0 {
parentNodes, err = this.FindEnabledNodesWithGroupIdAndLevel(tx, groupId, level+1)
if err != nil {
return nil, err
}
}
// 当前集群的L2
if len(parentNodes) == 0 {
for _, clusterId := range clusterIds {
clusterParentNodes, err := this.FindEnabledNodesWithClusterIdAndLevel(tx, clusterId, level+1)
if err != nil {
return nil, err
}
if len(clusterParentNodes) > 0 {
parentNodes = append(parentNodes, clusterParentNodes...)
}
}
}
if len(parentNodes) > 0 {
for _, node := range parentNodes {
addrs, err := SharedNodeIPAddressDAO.FindNodeAccessAndUpIPAddresses(tx, int64(node.Id), nodeconfigs.NodeRoleNode)
if err != nil {
return nil, err
}
var addrStrings = []string{}
for _, addr := range addrs {
if addr.IsOn {
addrStrings = append(addrStrings, addr.DNSIP())
}
}
// 没有地址就跳过
if len(addrStrings) == 0 {
continue
}
var secretHash = fmt.Sprintf("%x", sha256.Sum256([]byte(node.UniqueId+"@"+node.Secret)))
for _, clusterId := range node.AllClusterIds() {
parentNodeConfigs, _ := result[clusterId]
parentNodeConfigs = append(parentNodeConfigs, &nodeconfigs.ParentNodeConfig{
Id: int64(node.Id),
Addrs: addrStrings,
SecretHash: secretHash,
})
result[clusterId] = parentNodeConfigs
}
}
}
return
}
// NotifyUpdate 通知节点相关更新
func (this *NodeDAO) NotifyUpdate(tx *dbs.Tx, nodeId int64) error { func (this *NodeDAO) NotifyUpdate(tx *dbs.Tx, nodeId int64) error {
// 这里只需要通知单个集群即可,因为节点是公用的,更新一个就相当于更新了所有
clusterId, err := this.FindNodeClusterId(tx, nodeId) clusterId, err := this.FindNodeClusterId(tx, nodeId)
if err != nil { if err != nil {
return err return err
} }
if clusterId > 0 { err = SharedNodeTaskDAO.CreateNodeTask(tx, nodeconfigs.NodeRoleNode, clusterId, nodeId, 0, NodeTaskTypeConfigChanged, 0)
return SharedNodeTaskDAO.CreateNodeTask(tx, nodeconfigs.NodeRoleNode, clusterId, nodeId, 0, NodeTaskTypeConfigChanged, 0) if err != nil {
return err
}
return nil
}
// NotifyLevelUpdate 通知节点级别更新
func (this *NodeDAO) NotifyLevelUpdate(tx *dbs.Tx, nodeId int64) error {
// 这里只需要通知单个集群即可,因为节点是公用的,更新一个就相当于更新了所有
clusterIds, err := this.FindEnabledAndOnNodeClusterIds(tx, nodeId)
if err != nil {
return err
}
for _, clusterId := range clusterIds {
err = SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, 0, NodeTaskTypeNodeLevelChanged)
if err != nil {
return err
}
} }
return nil return nil
} }
// NotifyDNSUpdate 通知DNS更新 // NotifyDNSUpdate 通知节点相关DNS更新
func (this *NodeDAO) NotifyDNSUpdate(tx *dbs.Tx, nodeId int64) error { func (this *NodeDAO) NotifyDNSUpdate(tx *dbs.Tx, nodeId int64) error {
clusterIds, err := this.FindEnabledAndOnNodeClusterIds(tx, nodeId) clusterIds, err := this.FindEnabledAndOnNodeClusterIds(tx, nodeId)
if err != nil { if err != nil {

View File

@@ -0,0 +1,15 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
// +build !plus
package models
import (
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/iwind/TeaGo/dbs"
)
func (this *NodeDAO) composeExtConfig(tx *dbs.Tx, config *nodeconfigs.NodeConfig, clusterIds []int64, cacheMap *utils.CacheMap) error {
return nil
}

View File

@@ -1,16 +1,23 @@
package models //go:build plus
// +build plus
package models_test
import ( import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"testing" "testing"
"time" "time"
) )
func TestNodeDAO_FindAllNodeIdsMatch(t *testing.T) { func TestNodeDAO_FindAllNodeIdsMatch(t *testing.T) {
var tx *dbs.Tx var tx *dbs.Tx
nodeIds, err := SharedNodeDAO.FindAllNodeIdsMatch(tx, 1, true, 0) dbs.NotifyReady()
nodeIds, err := models.SharedNodeDAO.FindAllNodeIdsMatch(tx, 1, true, 0)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -20,7 +27,7 @@ func TestNodeDAO_FindAllNodeIdsMatch(t *testing.T) {
func TestNodeDAO_UpdateNodeUp(t *testing.T) { func TestNodeDAO_UpdateNodeUp(t *testing.T) {
dbs.NotifyReady() dbs.NotifyReady()
var tx *dbs.Tx var tx *dbs.Tx
err := SharedNodeDAO.UpdateNodeUp(tx, 57, false) err := models.SharedNodeDAO.UpdateNodeUp(tx, 57, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -30,7 +37,7 @@ func TestNodeDAO_UpdateNodeUp(t *testing.T) {
func TestNodeDAO_FindEnabledNodeClusterIds(t *testing.T) { func TestNodeDAO_FindEnabledNodeClusterIds(t *testing.T) {
dbs.NotifyReady() dbs.NotifyReady()
var tx *dbs.Tx var tx *dbs.Tx
clusterIds, err := NewNodeDAO().FindEnabledAndOnNodeClusterIds(tx, 48) clusterIds, err := models.NewNodeDAO().FindEnabledAndOnNodeClusterIds(tx, 48)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -47,7 +54,7 @@ func TestNodeDAO_ComposeNodeConfig(t *testing.T) {
var tx *dbs.Tx var tx *dbs.Tx
var cacheMap = utils.NewCacheMap() var cacheMap = utils.NewCacheMap()
nodeConfig, err := SharedNodeDAO.ComposeNodeConfig(tx, 48, cacheMap) nodeConfig, err := models.SharedNodeDAO.ComposeNodeConfig(tx, 48, cacheMap)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -56,3 +63,17 @@ func TestNodeDAO_ComposeNodeConfig(t *testing.T) {
// old: 77ms => new: 56ms // old: 77ms => new: 56ms
} }
func TestNodeDAO_ComposeNodeConfig_ParentNodes(t *testing.T) {
dbs.NotifyReady()
teaconst.IsPlus = true
var tx *dbs.Tx
var cacheMap = utils.NewCacheMap()
nodeConfig, err := models.SharedNodeDAO.ComposeNodeConfig(tx, 48, cacheMap)
if err != nil {
t.Fatal(err)
}
logs.PrintAsJSON(nodeConfig.ParentNodes, t)
}

View File

@@ -2,6 +2,7 @@ package models
import ( import (
"errors" "errors"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea" "github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
@@ -129,7 +130,7 @@ func (this *NodeGrantDAO) CountAllEnabledGrants(tx *dbs.Tx, keyword string) (int
State(NodeGrantStateEnabled) State(NodeGrantStateEnabled)
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR username LIKE :keyword OR description LIKE :keyword)"). query.Where("(name LIKE :keyword OR username LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query.Count() return query.Count()
} }
@@ -140,7 +141,7 @@ func (this *NodeGrantDAO) ListEnabledGrants(tx *dbs.Tx, keyword string, offset i
State(NodeGrantStateEnabled) State(NodeGrantStateEnabled)
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR username LIKE :keyword OR description LIKE :keyword)"). query.Where("(name LIKE :keyword OR username LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query. _, err = query.
Offset(offset). Offset(offset).

View File

@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/dns" "github.com/TeaOSLab/EdgeAPI/internal/db/models/dns"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/TeaOSLab/EdgeCommon/pkg/configutils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
@@ -124,7 +125,7 @@ func (this *NodeIPAddressDAO) CreateAddress(tx *dbs.Tx, adminId int64, nodeId in
role = nodeconfigs.NodeRoleNode role = nodeconfigs.NodeRoleNode
} }
op := NewNodeIPAddressOperator() var op = NewNodeIPAddressOperator()
op.NodeId = nodeId op.NodeId = nodeId
op.Role = role op.Role = role
op.Name = name op.Name = name
@@ -233,17 +234,20 @@ func (this *NodeIPAddressDAO) FindAllEnabledAddressesWithNode(tx *dbs.Tx, nodeId
} }
// FindFirstNodeAccessIPAddress 查找节点的第一个可访问的IP地址 // FindFirstNodeAccessIPAddress 查找节点的第一个可访问的IP地址
func (this *NodeIPAddressDAO) FindFirstNodeAccessIPAddress(tx *dbs.Tx, nodeId int64, role nodeconfigs.NodeRole) (ip string, addrId int64, err error) { func (this *NodeIPAddressDAO) FindFirstNodeAccessIPAddress(tx *dbs.Tx, nodeId int64, mustUp bool, role nodeconfigs.NodeRole) (ip string, addrId int64, err error) {
if len(role) == 0 { if len(role) == 0 {
role = nodeconfigs.NodeRoleNode role = nodeconfigs.NodeRoleNode
} }
one, err := this.Query(tx). var query = this.Query(tx).
Attr("nodeId", nodeId). Attr("nodeId", nodeId).
Attr("role", role). Attr("role", role).
State(NodeIPAddressStateEnabled). State(NodeIPAddressStateEnabled).
Attr("canAccess", true). Attr("canAccess", true).
Attr("isOn", true). Attr("isOn", true)
Attr("isUp", true). if mustUp {
query.Attr("isUp", true)
}
one, err := query.
Desc("order"). Desc("order").
AscPk(). AscPk().
Result("id", "ip"). Result("id", "ip").
@@ -260,17 +264,20 @@ func (this *NodeIPAddressDAO) FindFirstNodeAccessIPAddress(tx *dbs.Tx, nodeId in
} }
// FindFirstNodeAccessIPAddressId 查找节点的第一个可访问的IP地址ID // FindFirstNodeAccessIPAddressId 查找节点的第一个可访问的IP地址ID
func (this *NodeIPAddressDAO) FindFirstNodeAccessIPAddressId(tx *dbs.Tx, nodeId int64, role nodeconfigs.NodeRole) (int64, error) { func (this *NodeIPAddressDAO) FindFirstNodeAccessIPAddressId(tx *dbs.Tx, nodeId int64, mustUp bool, role nodeconfigs.NodeRole) (int64, error) {
if len(role) == 0 { if len(role) == 0 {
role = nodeconfigs.NodeRoleNode role = nodeconfigs.NodeRoleNode
} }
return this.Query(tx). var query = this.Query(tx).
Attr("nodeId", nodeId). Attr("nodeId", nodeId).
Attr("role", role). Attr("role", role).
State(NodeIPAddressStateEnabled). State(NodeIPAddressStateEnabled).
Attr("canAccess", true). Attr("canAccess", true).
Attr("isOn", true). Attr("isOn", true)
Attr("isUp", true). if mustUp {
query.Attr("isUp", true)
}
return query.
Desc("order"). Desc("order").
AscPk(). AscPk().
Result("id"). Result("id").
@@ -323,7 +330,7 @@ func (this *NodeIPAddressDAO) CountAllEnabledIPAddresses(tx *dbs.Tx, role string
// 关键词 // 关键词
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(ip LIKE :keyword OR name LIKE :keyword OR description LIKE :keyword OR nodeId IN (SELECT id FROM "+SharedNodeDAO.Table+" WHERE state=1 AND name LIKE :keyword))"). query.Where("(ip LIKE :keyword OR name LIKE :keyword OR description LIKE :keyword OR nodeId IN (SELECT id FROM "+SharedNodeDAO.Table+" WHERE state=1 AND name LIKE :keyword))").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query.Count() return query.Count()
@@ -355,7 +362,7 @@ func (this *NodeIPAddressDAO) ListEnabledIPAddresses(tx *dbs.Tx, role string, no
// 关键词 // 关键词
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(ip LIKE :keyword OR name LIKE :keyword OR description LIKE :keyword OR nodeId IN (SELECT id FROM "+SharedNodeDAO.Table+" WHERE state=1 AND name LIKE :keyword))"). query.Where("(ip LIKE :keyword OR name LIKE :keyword OR description LIKE :keyword OR nodeId IN (SELECT id FROM "+SharedNodeDAO.Table+" WHERE state=1 AND name LIKE :keyword))").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query.Offset(offset). _, err = query.Offset(offset).
@@ -455,13 +462,13 @@ func (this *NodeIPAddressDAO) UpdateAddressBackupIP(tx *dbs.Tx, addressId int64,
} }
// UpdateAddressHealthCount 计算IP健康状态 // UpdateAddressHealthCount 计算IP健康状态
func (this *NodeIPAddressDAO) UpdateAddressHealthCount(tx *dbs.Tx, addrId int64, isUp bool, maxUp int, maxDown int) (changed bool, err error) { func (this *NodeIPAddressDAO) UpdateAddressHealthCount(tx *dbs.Tx, addrId int64, newIsUp bool, maxUp int, maxDown int, autoUpDown bool) (changed bool, err error) {
if addrId <= 0 { if addrId <= 0 {
return false, errors.New("invalid address id") return false, errors.New("invalid address id")
} }
one, err := this.Query(tx). one, err := this.Query(tx).
Pk(addrId). Pk(addrId).
Result("isHealthy", "countUp", "countDown"). Result("isHealthy", "isUp", "countUp", "countDown").
Find() Find()
if err != nil { if err != nil {
return false, err return false, err
@@ -469,26 +476,57 @@ func (this *NodeIPAddressDAO) UpdateAddressHealthCount(tx *dbs.Tx, addrId int64,
if one == nil { if one == nil {
return false, nil return false, nil
} }
oldIsHealthy := one.(*NodeIPAddress).IsHealthy var oldIsHealthy = one.(*NodeIPAddress).IsHealthy
var oldIsUp = one.(*NodeIPAddress).IsUp
// 如果新老状态一致,则不做任何事情 // 如果新老状态一致,则不做任何事情
if oldIsHealthy == isUp { if oldIsHealthy == newIsUp {
// 如果自动上下线,则健康状况和是否在线保持一致
if autoUpDown {
if oldIsUp != oldIsHealthy {
err = this.Query(tx).
Pk(addrId).
Set("isUp", newIsUp).
UpdateQuickly()
if err != nil {
return false, err
}
err = this.NotifyUpdate(tx, addrId)
if err != nil {
return false, err
}
// 创建日志
if newIsUp {
err = SharedNodeIPAddressLogDAO.CreateLog(tx, 0, addrId, "健康检查上线")
} else {
err = SharedNodeIPAddressLogDAO.CreateLog(tx, 0, addrId, "健康检查下线")
}
if err != nil {
return true, err
}
return true, nil
}
}
return false, nil return false, nil
} }
countUp := int(one.(*NodeIPAddress).CountUp) var countUp = int(one.(*NodeIPAddress).CountUp)
countDown := int(one.(*NodeIPAddress).CountDown) var countDown = int(one.(*NodeIPAddress).CountDown)
op := NewNodeIPAddressOperator() var op = NewNodeIPAddressOperator()
op.Id = addrId op.Id = addrId
if isUp { if newIsUp {
countUp++ countUp++
countDown = 0 countDown = 0
if countUp >= maxUp { if countUp >= maxUp {
changed = true changed = true
//op.IsUp = true if autoUpDown {
op.IsUp = true
}
op.IsHealthy = true op.IsHealthy = true
} }
} else { } else {
@@ -497,7 +535,9 @@ func (this *NodeIPAddressDAO) UpdateAddressHealthCount(tx *dbs.Tx, addrId int64,
if countDown >= maxDown { if countDown >= maxDown {
changed = true changed = true
//op.IsUp = false if autoUpDown {
op.IsUp = false
}
op.IsHealthy = false op.IsHealthy = false
} }
} }
@@ -514,6 +554,15 @@ func (this *NodeIPAddressDAO) UpdateAddressHealthCount(tx *dbs.Tx, addrId int64,
if err != nil { if err != nil {
return true, err return true, err
} }
// 创建日志
if autoUpDown {
if newIsUp {
err = SharedNodeIPAddressLogDAO.CreateLog(tx, 0, addrId, "健康检查上线")
} else {
err = SharedNodeIPAddressLogDAO.CreateLog(tx, 0, addrId, "健康检查下线")
}
}
} }
return return
@@ -539,9 +588,21 @@ func (this *NodeIPAddressDAO) NotifyUpdate(tx *dbs.Tx, addressId int64) error {
switch role { switch role {
case nodeconfigs.NodeRoleNode: case nodeconfigs.NodeRoleNode:
err = dns.SharedDNSTaskDAO.CreateNodeTask(tx, nodeId, dns.DNSTaskTypeNodeChange) err = dns.SharedDNSTaskDAO.CreateNodeTask(tx, nodeId, dns.DNSTaskTypeNodeChange)
} if err != nil {
if err != nil { return err
return err }
// 检查是否为L2以上级别
level, err := SharedNodeDAO.FindNodeLevel(tx, nodeId)
if err != nil {
return err
}
if level > 1 {
err = SharedNodeDAO.NotifyLevelUpdate(tx, nodeId)
if err != nil {
return err
}
}
} }
return nil return nil
} }

View File

@@ -56,7 +56,7 @@ func TestNodeIPAddressDAO_UpdateAddressHealthCount(t *testing.T) {
dbs.NotifyReady() dbs.NotifyReady()
var tx *dbs.Tx var tx *dbs.Tx
isChanged, err := SharedNodeIPAddressDAO.UpdateAddressHealthCount(tx, 1, true, 3, 3) isChanged, err := SharedNodeIPAddressDAO.UpdateAddressHealthCount(tx, 1, true, 3, 3, false)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -9,6 +9,6 @@ import (
func TestNodeIPAddressDAO_FindFirstNodeAccessIPAddress(t *testing.T) { func TestNodeIPAddressDAO_FindFirstNodeAccessIPAddress(t *testing.T) {
var dao = NewNodeIPAddressDAO() var dao = NewNodeIPAddressDAO()
t.Log(dao.FindFirstNodeAccessIPAddress(nil, 48, nodeconfigs.NodeRoleNode)) t.Log(dao.FindFirstNodeAccessIPAddress(nil, 48, true, nodeconfigs.NodeRoleNode))
t.Log(dao.FindFirstNodeAccessIPAddressId(nil, 48, nodeconfigs.NodeRoleNode)) t.Log(dao.FindFirstNodeAccessIPAddressId(nil, 48, true, nodeconfigs.NodeRoleNode))
} }

View File

@@ -1,6 +1,7 @@
package models package models
import ( import (
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs" "github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/TeaOSLab/EdgeCommon/pkg/configutils"
@@ -59,7 +60,7 @@ func (this *NodeLogDAO) CreateLog(tx *dbs.Tx, nodeRole nodeconfigs.NodeRole, nod
} }
} }
hash := stringutil.Md5(nodeRole + "@" + types.String(nodeId) + "@" + types.String(serverId) + "@" + types.String(originId) + "@" + level + "@" + tag + "@" + description + "@" + string(paramsJSON)) var hash = stringutil.Md5(nodeRole + "@" + types.String(nodeId) + "@" + types.String(serverId) + "@" + types.String(originId) + "@" + level + "@" + tag + "@" + description + "@" + string(paramsJSON))
// 检查是否在重复最后一条,避免重复创建 // 检查是否在重复最后一条,避免重复创建
lastLog, err := this.Query(tx). lastLog, err := this.Query(tx).
@@ -80,7 +81,7 @@ func (this *NodeLogDAO) CreateLog(tx *dbs.Tx, nodeRole nodeconfigs.NodeRole, nod
} }
} }
op := NewNodeLogOperator() var op = NewNodeLogOperator()
op.Role = nodeRole op.Role = nodeRole
op.NodeId = nodeId op.NodeId = nodeId
op.ServerId = serverId op.ServerId = serverId
@@ -139,13 +140,15 @@ func (this *NodeLogDAO) CountNodeLogs(tx *dbs.Tx,
nodeId int64, nodeId int64,
serverId int64, serverId int64,
originId int64, originId int64,
allServers bool,
dayFrom string, dayFrom string,
dayTo string, dayTo string,
keyword string, keyword string,
level string, level string,
fixedState configutils.BoolState,
isUnread bool, isUnread bool,
tag string) (int64, error) { tag string) (int64, error) {
query := this.Query(tx) var query = this.Query(tx)
if len(role) > 0 { if len(role) > 0 {
query.Attr("role", role) query.Attr("role", role)
} }
@@ -166,6 +169,8 @@ func (this *NodeLogDAO) CountNodeLogs(tx *dbs.Tx,
} }
if serverId > 0 { if serverId > 0 {
query.Attr("serverId", serverId) query.Attr("serverId", serverId)
} else if allServers {
query.Where("serverId>0")
} }
if originId > 0 { if originId > 0 {
query.Attr("originId", originId) query.Attr("originId", originId)
@@ -180,16 +185,23 @@ func (this *NodeLogDAO) CountNodeLogs(tx *dbs.Tx,
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(tag LIKE :keyword OR description LIKE :keyword)"). query.Where("(tag LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(level) > 0 { if len(level) > 0 {
query.Attr("level", level) query.Attr("level", level)
} }
if fixedState == configutils.BoolStateYes {
query.Attr("isFixed", 1)
query.Where("level IN ('error', 'success', 'warning')")
} else if fixedState == configutils.BoolStateNo {
query.Attr("isFixed", 0)
query.Where("level IN ('error', 'success', 'warning')")
}
if isUnread { if isUnread {
query.Attr("isRead", 0) query.Attr("isRead", 0)
} }
if len(tag) > 0 { if len(tag) > 0 {
query.Like("tag", "%"+tag+"%") query.Like("tag", dbutils.QuoteLikeKeyword(tag))
} }
return query.Count() return query.Count()
@@ -212,7 +224,7 @@ func (this *NodeLogDAO) ListNodeLogs(tx *dbs.Tx,
tag string, tag string,
offset int64, offset int64,
size int64) (result []*NodeLog, err error) { size int64) (result []*NodeLog, err error) {
query := this.Query(tx) var query = this.Query(tx)
if len(role) > 0 { if len(role) > 0 {
query.Attr("role", role) query.Attr("role", role)
} }
@@ -241,8 +253,10 @@ func (this *NodeLogDAO) ListNodeLogs(tx *dbs.Tx,
} }
if fixedState == configutils.BoolStateYes { if fixedState == configutils.BoolStateYes {
query.Attr("isFixed", 1) query.Attr("isFixed", 1)
query.Where("level IN ('error', 'success', 'warning')")
} else if fixedState == configutils.BoolStateNo { } else if fixedState == configutils.BoolStateNo {
query.Attr("isFixed", 0) query.Attr("isFixed", 0)
query.Where("level IN ('error', 'success', 'warning')")
} }
if len(dayFrom) > 0 { if len(dayFrom) > 0 {
dayFrom = strings.ReplaceAll(dayFrom, "-", "") dayFrom = strings.ReplaceAll(dayFrom, "-", "")
@@ -254,7 +268,7 @@ func (this *NodeLogDAO) ListNodeLogs(tx *dbs.Tx,
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(tag LIKE :keyword OR description LIKE :keyword)"). query.Where("(tag LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if len(level) > 0 { if len(level) > 0 {
var pieces = strings.Split(level, ",") var pieces = strings.Split(level, ",")
@@ -268,7 +282,7 @@ func (this *NodeLogDAO) ListNodeLogs(tx *dbs.Tx,
query.Attr("isRead", 0) query.Attr("isRead", 0)
} }
if len(tag) > 0 { if len(tag) > 0 {
query.Like("tag", "%"+tag+"%") query.Like("tag", dbutils.QuoteLikeKeyword(tag))
} }
_, err = query. _, err = query.
Offset(offset). Offset(offset).
@@ -310,6 +324,14 @@ func (this *NodeLogDAO) UpdateNodeLogFixed(tx *dbs.Tx, logId int64) error {
return nil return nil
} }
// UpdateAllNodeLogsFixed 设置所有节点日志为已修复
func (this *NodeLogDAO) UpdateAllNodeLogsFixed(tx *dbs.Tx) error {
return this.Query(tx).
Attr("isFixed", 0).
Set("isFixed", 1).
UpdateQuickly()
}
// CountAllUnreadNodeLogs 计算未读的日志数量 // CountAllUnreadNodeLogs 计算未读的日志数量
func (this *NodeLogDAO) CountAllUnreadNodeLogs(tx *dbs.Tx) (int64, error) { func (this *NodeLogDAO) CountAllUnreadNodeLogs(tx *dbs.Tx) (int64, error) {
return this.Query(tx). return this.Query(tx).

View File

@@ -7,6 +7,7 @@ type Node struct {
Id uint32 `field:"id"` // ID Id uint32 `field:"id"` // ID
AdminId uint32 `field:"adminId"` // 管理员ID AdminId uint32 `field:"adminId"` // 管理员ID
UserId uint32 `field:"userId"` // 用户ID UserId uint32 `field:"userId"` // 用户ID
Level uint8 `field:"level"` // 级别
IsOn bool `field:"isOn"` // 是否启用 IsOn bool `field:"isOn"` // 是否启用
IsUp bool `field:"isUp"` // 是否在线 IsUp bool `field:"isUp"` // 是否在线
CountUp uint32 `field:"countUp"` // 连续在线次数 CountUp uint32 `field:"countUp"` // 连续在线次数
@@ -40,6 +41,7 @@ type NodeOperator struct {
Id interface{} // ID Id interface{} // ID
AdminId interface{} // 管理员ID AdminId interface{} // 管理员ID
UserId interface{} // 用户ID UserId interface{} // 用户ID
Level interface{} // 级别
IsOn interface{} // 是否启用 IsOn interface{} // 是否启用
IsUp interface{} // 是否在线 IsUp interface{} // 是否在线
CountUp interface{} // 连续在线次数 CountUp interface{} // 连续在线次数

View File

@@ -96,3 +96,15 @@ func (this *Node) DecodeSecondaryClusterIds() []int64 {
_ = json.Unmarshal(this.SecondaryClusterIds, &result) _ = json.Unmarshal(this.SecondaryClusterIds, &result)
return result return result
} }
func (this *Node) AllClusterIds() []int64 {
var result = []int64{}
if this.ClusterId > 0 {
result = append(result, int64(this.ClusterId))
}
result = append(result, this.DecodeSecondaryClusterIds()...)
return result
}

View File

@@ -17,6 +17,8 @@ const (
NodeTaskTypeConfigChanged NodeTaskType = "configChanged" NodeTaskTypeConfigChanged NodeTaskType = "configChanged"
NodeTaskTypeIPItemChanged NodeTaskType = "ipItemChanged" NodeTaskTypeIPItemChanged NodeTaskType = "ipItemChanged"
NodeTaskTypeNodeVersionChanged NodeTaskType = "nodeVersionChanged" NodeTaskTypeNodeVersionChanged NodeTaskType = "nodeVersionChanged"
NodeTaskTypeScriptsChanged NodeTaskType = "scriptsChanged"
NodeTaskTypeNodeLevelChanged NodeTaskType = "nodeLevelChanged"
// NS相关 // NS相关

View File

@@ -1,6 +1,7 @@
package models package models
import ( import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
@@ -9,6 +10,7 @@ import (
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types" "github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
"time" "time"
) )
@@ -35,9 +37,9 @@ func init() {
// CreateValue 创建值 // CreateValue 创建值
func (this *NodeValueDAO) CreateValue(tx *dbs.Tx, clusterId int64, role nodeconfigs.NodeRole, nodeId int64, item string, valueJSON []byte, createdAt int64) error { func (this *NodeValueDAO) CreateValue(tx *dbs.Tx, clusterId int64, role nodeconfigs.NodeRole, nodeId int64, item string, valueJSON []byte, createdAt int64) error {
day := timeutil.FormatTime("Ymd", createdAt) var day = timeutil.FormatTime("Ymd", createdAt)
hour := timeutil.FormatTime("YmdH", createdAt) var hour = timeutil.FormatTime("YmdH", createdAt)
minute := timeutil.FormatTime("YmdHi", createdAt) var minute = timeutil.FormatTime("YmdHi", createdAt)
return this.Query(tx). return this.Query(tx).
InsertOrUpdateQuickly(maps.Map{ InsertOrUpdateQuickly(maps.Map{
@@ -120,6 +122,17 @@ func (this *NodeValueDAO) ListValuesWithClusterId(tx *dbs.Tx, role string, clust
_, err = query.Slice(&result). _, err = query.Slice(&result).
FindAll() FindAll()
if err != nil {
return nil, err
}
for _, nodeValue := range result {
nodeValue.Value, _ = json.Marshal(maps.Map{
key: types.Float32(string(nodeValue.Value)),
})
}
return return
} }
@@ -144,6 +157,17 @@ func (this *NodeValueDAO) ListValuesForUserNodes(tx *dbs.Tx, item string, key st
_, err = query.Slice(&result). _, err = query.Slice(&result).
FindAll() FindAll()
if err != nil {
return nil, err
}
for _, nodeValue := range result {
nodeValue.Value, _ = json.Marshal(maps.Map{
key: types.Float32(string(nodeValue.Value)),
})
}
return return
} }
@@ -168,9 +192,48 @@ func (this *NodeValueDAO) ListValuesForNSNodes(tx *dbs.Tx, item string, key stri
_, err = query.Slice(&result). _, err = query.Slice(&result).
FindAll() FindAll()
if err != nil {
return nil, err
}
for _, nodeValue := range result {
nodeValue.Value, _ = json.Marshal(maps.Map{
key: types.Float32(string(nodeValue.Value)),
})
}
return 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 {
return 0, 0, 0, nil
}
var query = this.Query(tx).
Result("SUM(JSON_EXTRACT(value, '$."+param+"')) AS sumValue", "AVG(JSON_EXTRACT(value, '$."+param+"')) AS avgValue", "MAX(JSON_EXTRACT(value, '$."+param+"')) AS maxValueResult"). // maxValue 是个MySQL Keyword这里使用maxValueResult代替
Attr("role", role).
Attr("item", item)
switch durationUnit {
case nodeconfigs.NodeValueDurationUnitMinute:
fromMinute := timeutil.FormatTime("YmdHi", time.Now().Unix()-int64(duration*60))
query.Attr("minute", fromMinute)
default:
fromMinute := timeutil.FormatTime("YmdHi", time.Now().Unix()-int64(duration*60))
query.Attr("minute", fromMinute)
}
m, _, err := query.FindOne()
if err != nil {
return 0, 0, 0, err
}
return m.GetFloat64("sumValue"), m.GetFloat64("avgValue"), m.GetFloat64("maxValueResult"), nil
}
// SumNodeValues 计算节点的某项参数值 // SumNodeValues 计算节点的某项参数值
func (this *NodeValueDAO) SumNodeValues(tx *dbs.Tx, role string, nodeId int64, item string, param string, method nodeconfigs.NodeValueSumMethod, duration int32, durationUnit nodeconfigs.NodeValueDurationUnit) (float64, error) { func (this *NodeValueDAO) SumNodeValues(tx *dbs.Tx, role string, nodeId int64, item string, param string, method nodeconfigs.NodeValueSumMethod, duration int32, durationUnit nodeconfigs.NodeValueDurationUnit) (float64, error) {
if duration <= 0 { if duration <= 0 {
@@ -261,11 +324,13 @@ func (this *NodeValueDAO) SumNodeClusterValues(tx *dbs.Tx, role string, clusterI
// FindLatestNodeValue 获取最近一条数据 // FindLatestNodeValue 获取最近一条数据
func (this *NodeValueDAO) FindLatestNodeValue(tx *dbs.Tx, role string, nodeId int64, item string) (*NodeValue, error) { func (this *NodeValueDAO) FindLatestNodeValue(tx *dbs.Tx, role string, nodeId int64, item string) (*NodeValue, error) {
fromMinute := timeutil.FormatTime("YmdHi", time.Now().Unix()-int64(60))
one, err := this.Query(tx). one, err := this.Query(tx).
Attr("role", role). Attr("role", role).
Attr("nodeId", nodeId). Attr("nodeId", nodeId).
Attr("item", item). Attr("item", item).
DescPk(). Attr("minute", fromMinute).
Find() Find()
if err != nil { if err != nil {
return nil, err return nil, err
@@ -275,3 +340,62 @@ func (this *NodeValueDAO) FindLatestNodeValue(tx *dbs.Tx, role string, nodeId in
} }
return one.(*NodeValue), nil return one.(*NodeValue), nil
} }
// ComposeNodeStatus 组合节点状态值
func (this *NodeValueDAO) ComposeNodeStatus(tx *dbs.Tx, role string, nodeId int64, statusConfig *nodeconfigs.NodeStatus) error {
var items = []string{
nodeconfigs.NodeValueItemCPU,
nodeconfigs.NodeValueItemMemory,
nodeconfigs.NodeValueItemLoad,
nodeconfigs.NodeValueItemTrafficOut,
nodeconfigs.NodeValueItemTrafficIn,
}
ones, err := this.Query(tx).
Result("item", "value").
Attr("role", role).
Attr("nodeId", nodeId).
Attr("minute", timeutil.FormatTime("YmdHi", time.Now().Unix()-60)).
Where("item IN ('" + strings.Join(items, "', '") + "')").
FindAll()
if err != nil {
return err
}
for _, one := range ones {
var oneValue = one.(*NodeValue)
var valueMap = oneValue.DecodeMapValue()
switch oneValue.Item {
case nodeconfigs.NodeValueItemCPU:
statusConfig.CPUUsage = valueMap.GetFloat64("usage")
case nodeconfigs.NodeValueItemMemory:
statusConfig.MemoryUsage = valueMap.GetFloat64("usage")
case nodeconfigs.NodeValueItemLoad:
statusConfig.Load1m = valueMap.GetFloat64("load1m")
statusConfig.Load5m = valueMap.GetFloat64("load5m")
statusConfig.Load15m = valueMap.GetFloat64("load15m")
case nodeconfigs.NodeValueItemTrafficOut:
statusConfig.TrafficOutBytes = valueMap.GetUint64("total")
case nodeconfigs.NodeValueItemTrafficIn:
statusConfig.TrafficInBytes = valueMap.GetUint64("total")
}
}
return nil
}
// ComposeNodeStatusJSON 组合节点状态值并转换为JSON数据
func (this *NodeValueDAO) ComposeNodeStatusJSON(tx *dbs.Tx, role string, nodeId int64, statusJSON []byte) ([]byte, error) {
var statusConfig = &nodeconfigs.NodeStatus{}
if len(statusJSON) > 0 {
err := json.Unmarshal(statusJSON, statusConfig)
if err != nil {
return nil, err
}
}
err := this.ComposeNodeStatus(tx, role, nodeId, statusConfig)
if err != nil {
return nil, err
}
return json.Marshal(statusConfig)
}

View File

@@ -1,16 +1,21 @@
package models package models_test
import ( import (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap" _ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
"testing" "testing"
"time" "time"
) )
func TestNodeValueDAO_CreateValue(t *testing.T) { func TestNodeValueDAO_CreateValue(t *testing.T) {
var dao = NewNodeValueDAO() var dao = models.NewNodeValueDAO()
m := maps.Map{ m := maps.Map{
"hello": "world12344", "hello": "world12344",
} }
@@ -22,10 +27,55 @@ func TestNodeValueDAO_CreateValue(t *testing.T) {
} }
func TestNodeValueDAO_Clean(t *testing.T) { func TestNodeValueDAO_Clean(t *testing.T) {
var dao = NewNodeValueDAO() var dao = models.NewNodeValueDAO()
err := dao.Clean(nil) err := dao.Clean(nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
t.Log("ok") t.Log("ok")
} }
func TestNodeValueDAO_CreateManyValues(t *testing.T) {
var dao = models.NewNodeValueDAO()
var tx *dbs.Tx
for i := 0; i < 1; i++ {
if i%10000 == 0 {
t.Log(i)
}
var item = "connections" + types.String(i)
var clusterId int64 = 42
var nodeId = rands.Int(1, 100)
err := dao.CreateValue(tx, clusterId, nodeconfigs.NodeRoleNode, int64(nodeId), item, []byte(`{"total":1}`), time.Now().Unix())
if err != nil {
t.Fatal("item: " + item + ", err: " + err.Error())
}
}
t.Log("finished")
}
func TestNodeValueDAO_SumAllNodeValues(t *testing.T) {
var dao = models.NewNodeValueDAO()
sum, avg, max, err := dao.SumAllNodeValues(nil, nodeconfigs.NodeRoleNode, nodeconfigs.NodeValueItemCPU, "usage", 1, nodeconfigs.NodeValueDurationUnitMinute)
if err != nil {
t.Fatal(err)
}
t.Log("sum:", sum, "avg:", avg, "max:", max)
}
func TestNodeValueDAO_ComposeNodeStatus(t *testing.T) {
var dao = models.NewNodeValueDAO()
one, err := dao.Query(nil).DescPk().Find()
if err != nil {
t.Fatal(err)
}
if one != nil {
var config = &nodeconfigs.NodeStatus{}
err = dao.ComposeNodeStatus(nil, one.(*models.NodeValue).Role, int64(one.(*models.NodeValue).NodeId), config)
if err != nil {
t.Fatal(err)
}
logs.PrintAsJSON(config, t)
}
}

View File

@@ -2,6 +2,7 @@ package models
import ( import (
"encoding/json" "encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
@@ -198,7 +199,7 @@ func (this *NSAccessLogDAO) listAccessLogs(tx *dbs.Tx, lastRequestId string, siz
// keyword // keyword
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(JSON_EXTRACT(content, '$.remoteAddr') LIKE :keyword OR JSON_EXTRACT(content, '$.questionName') LIKE :keyword OR JSON_EXTRACT(content, '$.recordValue') LIKE :keyword)"). query.Where("(JSON_EXTRACT(content, '$.remoteAddr') LIKE :keyword OR JSON_EXTRACT(content, '$.questionName') LIKE :keyword OR JSON_EXTRACT(content, '$.recordValue') LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if !reverse { if !reverse {

View File

@@ -2,6 +2,7 @@ package models
import ( import (
"encoding/json" "encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/TeaOSLab/EdgeCommon/pkg/configutils"
@@ -139,7 +140,7 @@ func (this *NSNodeDAO) CountAllEnabledNodesMatch(tx *dbs.Tx, clusterId int64, in
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword)"). query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query. return query.
@@ -176,7 +177,7 @@ func (this *NSNodeDAO) ListAllEnabledNodesMatch(tx *dbs.Tx, clusterId int64, ins
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword)"). query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query. _, err = query.
State(NSNodeStateEnabled). State(NSNodeStateEnabled).

View File

@@ -2,6 +2,7 @@ package models
import ( import (
"encoding/json" "encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
@@ -169,7 +170,7 @@ func (this *ReportNodeDAO) CountAllEnabledReportNodes(tx *dbs.Tx, groupId int64,
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR location LIKE :keyword OR isp LIKE :keyword OR allowIPs LIKE :keyword OR (status IS NOT NULL AND JSON_EXTRACT(status, 'ip') LIKE :keyword))") query.Where("(name LIKE :keyword OR location LIKE :keyword OR isp LIKE :keyword OR allowIPs LIKE :keyword OR (status IS NOT NULL AND JSON_EXTRACT(status, 'ip') LIKE :keyword))")
query.Param("keyword", "%"+keyword+"%") query.Param("keyword", dbutils.QuoteLike(keyword))
} }
return query.Count() return query.Count()
} }
@@ -201,7 +202,7 @@ func (this *ReportNodeDAO) ListEnabledReportNodes(tx *dbs.Tx, groupId int64, key
OR (LENGTH(location)=0 AND JSON_EXTRACT(status, '$.location') LIKE :keyword) OR (LENGTH(location)=0 AND JSON_EXTRACT(status, '$.location') LIKE :keyword)
OR (LENGTH(isp)=0 AND JSON_EXTRACT(status, '$.isp') LIKE :keyword) OR (LENGTH(isp)=0 AND JSON_EXTRACT(status, '$.isp') LIKE :keyword)
))`) ))`)
query.Param("keyword", "%"+keyword+"%") query.Param("keyword", dbutils.QuoteLike(keyword))
} }
query.Slice(&result) query.Slice(&result)
_, err = query.Asc("isActive"). _, err = query.Asc("isActive").

View File

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

View File

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

View File

@@ -0,0 +1,24 @@
package models
// ScriptHistory 脚本历史记录
type ScriptHistory struct {
Id uint32 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
ScriptId uint64 `field:"scriptId"` // 脚本ID
Filename string `field:"filename"` // 文件名
Code string `field:"code"` // 代码
Version uint64 `field:"version"` // 版本号
}
type ScriptHistoryOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
ScriptId interface{} // 脚本ID
Filename interface{} // 文件名
Code interface{} // 代码
Version interface{} // 版本号
}
func NewScriptHistoryOperator() *ScriptHistoryOperator {
return &ScriptHistoryOperator{}
}

View File

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

View File

@@ -0,0 +1,30 @@
package models
// Script 脚本库
type Script struct {
Id uint64 `field:"id"` // ID
UserId uint64 `field:"userId"` // 用户ID
IsOn bool `field:"isOn"` // 是否启用
Name string `field:"name"` // 名称
Filename string `field:"filename"` // 文件名
Code string `field:"code"` // 代码
CreatedAt uint64 `field:"createdAt"` // 创建时间
UpdatedAt uint64 `field:"updatedAt"` // 修改时间
State uint8 `field:"state"` // 是否启用
}
type ScriptOperator struct {
Id interface{} // ID
UserId interface{} // 用户ID
IsOn interface{} // 是否启用
Name interface{} // 名称
Filename interface{} // 文件名
Code interface{} // 代码
CreatedAt interface{} // 创建时间
UpdatedAt interface{} // 修改时间
State interface{} // 是否启用
}
func NewScriptOperator() *ScriptOperator {
return &ScriptOperator{}
}

View File

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

View File

@@ -132,6 +132,23 @@ func (this *ServerDailyStatDAO) SaveStats(tx *dbs.Tx, stats []*pb.ServerDailySta
return nil return nil
} }
// SumCurrentDailyStat 查找当前时刻的数据统计
func (this *ServerDailyStatDAO) SumCurrentDailyStat(tx *dbs.Tx, serverId int64) (*ServerDailyStat, error) {
var day = timeutil.Format("Ymd")
var minute = timeutil.FormatTime("His", time.Now().Unix()/300*300-300)
one, err := this.Query(tx).
Result("MIN(id)", "MIN(serverId)", "SUM(bytes) AS bytes", "SUM(cachedBytes) AS cachedBytes", "SUM(attackBytes) AS attackBytes", "SUM(countRequests) AS countRequests", "SUM(countCachedRequests) AS countCachedRequests", "SUM(countAttackRequests) AS countAttackRequests").
Attr("serverId", serverId).
Attr("day", day).
Attr("timeFrom", minute).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*ServerDailyStat), nil
}
// SumServerMonthlyWithRegion 根据服务计算某月合计 // SumServerMonthlyWithRegion 根据服务计算某月合计
// month 格式为YYYYMM // month 格式为YYYYMM
func (this *ServerDailyStatDAO) SumServerMonthlyWithRegion(tx *dbs.Tx, serverId int64, regionId int64, month string) (int64, error) { func (this *ServerDailyStatDAO) SumServerMonthlyWithRegion(tx *dbs.Tx, serverId int64, regionId int64, month string) (int64, error) {
@@ -309,6 +326,40 @@ func (this *ServerDailyStatDAO) SumDailyStat(tx *dbs.Tx, serverId int64, day str
return return
} }
// SumDailyStatBeforeMinute 获取某天内某个时间之前的流量
// 用于同期流量对比
// day 格式为YYYYMMDD
// minute 格式为HHIISS
func (this *ServerDailyStatDAO) SumDailyStatBeforeMinute(tx *dbs.Tx, serverId int64, day string, minute string) (stat *pb.ServerDailyStat, err error) {
stat = &pb.ServerDailyStat{}
if !regexp.MustCompile(`^\d{8}$`).MatchString(day) {
return nil, errors.New("invalid day '" + day + "'")
}
one, _, err := this.Query(tx).
Result("SUM(bytes) AS bytes, SUM(cachedBytes) AS cachedBytes, SUM(countRequests) AS countRequests, SUM(countCachedRequests) AS countCachedRequests, SUM(countAttackRequests) AS countAttackRequests, SUM(attackBytes) AS attackBytes").
Attr("serverId", serverId).
Attr("day", day).
Lte("minute", minute).
FindOne()
if err != nil {
return nil, err
}
if one == nil {
return
}
stat.Bytes = one.GetInt64("bytes")
stat.CachedBytes = one.GetInt64("cachedBytes")
stat.CountRequests = one.GetInt64("countRequests")
stat.CountCachedRequests = one.GetInt64("countCachedRequests")
stat.CountAttackRequests = one.GetInt64("countAttackRequests")
stat.AttackBytes = one.GetInt64("attackBytes")
return
}
// SumMonthlyStat 获取某月内的流量 // SumMonthlyStat 获取某月内的流量
// month 格式为YYYYMM // month 格式为YYYYMM
func (this *ServerDailyStatDAO) SumMonthlyStat(tx *dbs.Tx, serverId int64, month string) (stat *pb.ServerDailyStat, err error) { func (this *ServerDailyStatDAO) SumMonthlyStat(tx *dbs.Tx, serverId int64, month string) (stat *pb.ServerDailyStat, err error) {

View File

@@ -3,7 +3,9 @@ package models
import ( import (
"encoding/json" "encoding/json"
"errors" "errors"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/dns" "github.com/TeaOSLab/EdgeAPI/internal/db/models/dns"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils" "github.com/TeaOSLab/EdgeAPI/internal/utils/numberutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils" "github.com/TeaOSLab/EdgeCommon/pkg/configutils"
@@ -158,15 +160,27 @@ func (this *ServerDAO) CreateServer(tx *dbs.Tx,
excludeNodesJSON string, excludeNodesJSON string,
groupIds []int64, groupIds []int64,
userPlanId int64) (serverId int64, err error) { userPlanId int64) (serverId int64, err error) {
op := NewServerOperator() var op = NewServerOperator()
op.UserId = userId op.UserId = userId
op.AdminId = adminId op.AdminId = adminId
op.Name = name op.Name = name
op.Type = serverType op.Type = serverType
op.Description = description op.Description = description
if len(serverNamesJSON) > 0 { if IsNotNull(serverNamesJSON) {
var serverNames = []*serverconfigs.ServerNameConfig{}
err := json.Unmarshal(serverNamesJSON, &serverNames)
if err != nil {
return 0, err
}
op.ServerNames = serverNamesJSON op.ServerNames = serverNamesJSON
plainServerNamesJSON, err := json.Marshal(serverconfigs.PlainServerNames(serverNames))
if err != nil {
return 0, err
}
op.PlainServerNames = plainServerNamesJSON
} }
op.IsAuditing = isAuditing op.IsAuditing = isAuditing
op.AuditingAt = time.Now().Unix() op.AuditingAt = time.Now().Unix()
@@ -256,11 +270,21 @@ func (this *ServerDAO) CreateServer(tx *dbs.Tx,
} }
// UpdateServerBasic 修改服务基本信息 // UpdateServerBasic 修改服务基本信息
func (this *ServerDAO) UpdateServerBasic(tx *dbs.Tx, serverId int64, name string, description string, clusterId int64, isOn bool, groupIds []int64) error { func (this *ServerDAO) UpdateServerBasic(tx *dbs.Tx, serverId int64, name string, description string, clusterId int64, keepOldConfigs bool, isOn bool, groupIds []int64) error {
if serverId <= 0 { if serverId <= 0 {
return errors.New("serverId should not be smaller than 0") return errors.New("serverId should not be smaller than 0")
} }
op := NewServerOperator()
// 老的集群ID
oldClusterId, err := this.Query(tx).
Pk(serverId).
Result("clusterId").
FindInt64Col(0)
if err != nil {
return err
}
var op = NewServerOperator()
op.Id = serverId op.Id = serverId
op.Name = name op.Name = name
op.Description = description op.Description = description
@@ -277,7 +301,7 @@ func (this *ServerDAO) UpdateServerBasic(tx *dbs.Tx, serverId int64, name string
op.GroupIds = groupIdsJSON op.GroupIds = groupIdsJSON
} }
err := this.Save(tx, op) err = this.Save(tx, op)
if err != nil { if err != nil {
return err return err
} }
@@ -289,7 +313,29 @@ func (this *ServerDAO) UpdateServerBasic(tx *dbs.Tx, serverId int64, name string
} }
// 因为可能有isOn的原因所以需要修改 // 因为可能有isOn的原因所以需要修改
return this.NotifyDNSUpdate(tx, serverId) err = this.NotifyDNSUpdate(tx, serverId)
if err != nil {
return err
}
if clusterId != oldClusterId {
// 服务配置更新
if !keepOldConfigs {
err = this.NotifyClusterUpdate(tx, oldClusterId, serverId)
if err != nil {
return err
}
}
// DNS更新
// 这里不受 keepOldConfigs 的限制
err = this.NotifyClusterDNSUpdate(tx, oldClusterId, serverId)
if err != nil {
return err
}
}
return nil
} }
// UpdateUserServerBasic 设置用户相关的基本信息 // UpdateUserServerBasic 设置用户相关的基本信息
@@ -554,7 +600,7 @@ func (this *ServerDAO) FindServerServerNames(tx *dbs.Tx, serverId int64) (server
} }
// UpdateServerNames 修改ServerNames配置 // UpdateServerNames 修改ServerNames配置
func (this *ServerDAO) UpdateServerNames(tx *dbs.Tx, serverId int64, serverNames []byte) error { func (this *ServerDAO) UpdateServerNames(tx *dbs.Tx, serverId int64, serverNamesJSON []byte) error {
if serverId <= 0 { if serverId <= 0 {
return errors.New("serverId should not be smaller than 0") return errors.New("serverId should not be smaller than 0")
} }
@@ -562,10 +608,23 @@ func (this *ServerDAO) UpdateServerNames(tx *dbs.Tx, serverId int64, serverNames
op := NewServerOperator() op := NewServerOperator()
op.Id = serverId op.Id = serverId
if len(serverNames) == 0 { if IsNull(serverNamesJSON) {
serverNames = []byte("[]") serverNamesJSON = []byte("[]")
} else {
var serverNames = []*serverconfigs.ServerNameConfig{}
err := json.Unmarshal(serverNamesJSON, &serverNames)
if err != nil {
return err
}
op.ServerNames = serverNamesJSON
plainServerNamesJSON, err := json.Marshal(serverconfigs.PlainServerNames(serverNames))
if err != nil {
return err
}
op.PlainServerNames = plainServerNamesJSON
} }
op.ServerNames = serverNames op.ServerNames = serverNamesJSON
err := this.Save(tx, op) err := this.Save(tx, op)
if err != nil { if err != nil {
return err return err
@@ -585,7 +644,7 @@ func (this *ServerDAO) UpdateAuditingServerNames(tx *dbs.Tx, serverId int64, isA
if isAuditing { if isAuditing {
op.AuditingAt = time.Now().Unix() op.AuditingAt = time.Now().Unix()
} }
if len(auditingServerNamesJSON) == 0 { if IsNull(auditingServerNamesJSON) {
op.AuditingServerNames = "[]" op.AuditingServerNames = "[]"
} else { } else {
op.AuditingServerNames = auditingServerNamesJSON op.AuditingServerNames = auditingServerNamesJSON
@@ -613,12 +672,35 @@ func (this *ServerDAO) UpdateServerAuditing(tx *dbs.Tx, serverId int64, result *
return err return err
} }
op := NewServerOperator() auditingServerNamesJSON, err := this.Query(tx).
Pk(serverId).
Result("auditingServerNames").
FindJSONCol()
if err != nil {
return err
}
var op = NewServerOperator()
op.Id = serverId op.Id = serverId
op.IsAuditing = false op.IsAuditing = false
op.AuditingResult = resultJSON op.AuditingResult = resultJSON
if result.IsOk { if result.IsOk {
op.ServerNames = dbs.SQL("auditingServerNames") op.ServerNames = dbs.SQL("auditingServerNames")
if IsNotNull(auditingServerNamesJSON) {
var serverNames = []*serverconfigs.ServerNameConfig{}
err := json.Unmarshal(auditingServerNamesJSON, &serverNames)
if err != nil {
return err
}
plainServerNamesJSON, err := json.Marshal(serverconfigs.PlainServerNames(serverNames))
if err != nil {
return err
}
op.PlainServerNames = plainServerNamesJSON
} else {
op.PlainServerNames = "[]"
}
} }
err = this.Save(tx, op) err = this.Save(tx, op)
if err != nil { if err != nil {
@@ -657,21 +739,25 @@ func (this *ServerDAO) CountAllEnabledServers(tx *dbs.Tx) (int64, error) {
} }
// CountAllEnabledServersMatch 计算所有可用服务数量 // CountAllEnabledServersMatch 计算所有可用服务数量
// 参数:
// groupId 分组ID如果为-1则搜索没有分组的服务
func (this *ServerDAO) CountAllEnabledServersMatch(tx *dbs.Tx, groupId int64, keyword string, userId int64, clusterId int64, auditingFlag configutils.BoolState, protocolFamilies []string) (int64, error) { func (this *ServerDAO) CountAllEnabledServersMatch(tx *dbs.Tx, groupId int64, keyword string, userId int64, clusterId int64, auditingFlag configutils.BoolState, protocolFamilies []string) (int64, error) {
query := this.Query(tx). query := this.Query(tx).
State(ServerStateEnabled) State(ServerStateEnabled)
if groupId > 0 { if groupId > 0 {
query.Where("JSON_CONTAINS(groupIds, :groupId)"). query.Where("JSON_CONTAINS(groupIds, :groupId)").
Param("groupId", numberutils.FormatInt64(groupId)) Param("groupId", numberutils.FormatInt64(groupId))
} else if groupId < 0 { // 特殊的groupId
query.Where("JSON_LENGTH(groupIds)=0")
} }
if len(keyword) > 0 { if len(keyword) > 0 {
if regexp.MustCompile(`^\d+$`).MatchString(keyword) { if regexp.MustCompile(`^\d+$`).MatchString(keyword) {
query.Where("(name LIKE :keyword OR serverNames LIKE :keyword OR JSON_CONTAINS(http, :portRange, '$.listen') OR JSON_CONTAINS(https, :portRange, '$.listen') OR JSON_CONTAINS(tcp, :portRange, '$.listen') OR JSON_CONTAINS(tls, :portRange, '$.listen'))"). query.Where("(name LIKE :keyword OR serverNames LIKE :keyword OR JSON_CONTAINS(http, :portRange, '$.listen') OR JSON_CONTAINS(https, :portRange, '$.listen') OR JSON_CONTAINS(tcp, :portRange, '$.listen') OR JSON_CONTAINS(tls, :portRange, '$.listen'))").
Param("portRange", maps.Map{"portRange": keyword}.AsJSON()). Param("portRange", maps.Map{"portRange": keyword}.AsJSON()).
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} else { } else {
query.Where("(name LIKE :keyword OR serverNames LIKE :keyword)"). query.Where("(name LIKE :keyword OR serverNames LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
} }
if userId > 0 { if userId > 0 {
@@ -703,26 +789,29 @@ func (this *ServerDAO) CountAllEnabledServersMatch(tx *dbs.Tx, groupId int64, ke
} }
// ListEnabledServersMatch 列出单页的服务 // ListEnabledServersMatch 列出单页的服务
func (this *ServerDAO) ListEnabledServersMatch(tx *dbs.Tx, offset int64, size int64, groupId int64, keyword string, userId int64, clusterId int64, auditingFlag int32, protocolFamilies []string) (result []*Server, err error) { // 参数:
// groupId 分组ID如果为-1则搜索没有分组的服务
func (this *ServerDAO) ListEnabledServersMatch(tx *dbs.Tx, offset int64, size int64, groupId int64, keyword string, userId int64, clusterId int64, auditingFlag int32, protocolFamilies []string, order string) (result []*Server, err error) {
query := this.Query(tx). query := this.Query(tx).
State(ServerStateEnabled). State(ServerStateEnabled).
Offset(offset). Offset(offset).
Limit(size). Limit(size).
DescPk().
Slice(&result) Slice(&result)
if groupId > 0 { if groupId > 0 {
query.Where("JSON_CONTAINS(groupIds, :groupId)"). query.Where("JSON_CONTAINS(groupIds, :groupId)").
Param("groupId", numberutils.FormatInt64(groupId)) Param("groupId", numberutils.FormatInt64(groupId))
} else if groupId < 0 { // 特殊的groupId
query.Where("JSON_LENGTH(groupIds)=0")
} }
if len(keyword) > 0 { if len(keyword) > 0 {
if regexp.MustCompile(`^\d+$`).MatchString(keyword) { if regexp.MustCompile(`^\d+$`).MatchString(keyword) {
query.Where("(name LIKE :keyword OR serverNames LIKE :keyword OR JSON_CONTAINS(http, :portRange, '$.listen') OR JSON_CONTAINS(https, :portRange, '$.listen') OR JSON_CONTAINS(tcp, :portRange, '$.listen') OR JSON_CONTAINS(tls, :portRange, '$.listen'))"). query.Where("(name LIKE :keyword OR serverNames LIKE :keyword OR JSON_CONTAINS(http, :portRange, '$.listen') OR JSON_CONTAINS(https, :portRange, '$.listen') OR JSON_CONTAINS(tcp, :portRange, '$.listen') OR JSON_CONTAINS(tls, :portRange, '$.listen'))").
Param("portRange", string(maps.Map{"portRange": keyword}.AsJSON())). Param("portRange", string(maps.Map{"portRange": keyword}.AsJSON())).
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} else { } else {
query.Where("(name LIKE :keyword OR serverNames LIKE :keyword)"). query.Where("(name LIKE :keyword OR serverNames LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
} }
if userId > 0 { if userId > 0 {
@@ -749,7 +838,52 @@ func (this *ServerDAO) ListEnabledServersMatch(tx *dbs.Tx, offset int64, size in
query.Where("(" + strings.Join(protocolConds, " OR ") + ")") query.Where("(" + strings.Join(protocolConds, " OR ") + ")")
} }
// 排序
var day = timeutil.Format("Ymd")
var minute = timeutil.FormatTime("His", time.Now().Unix()/300*300-300)
var selfTable = this.Table
var statTable = SharedServerDailyStatDAO.Table
var hasOnlyIds = false
switch order {
case "trafficOutAsc":
query.Result("id")
query.Join(SharedServerDailyStatDAO, dbs.QueryJoinLeft, selfTable+".id="+statTable+".serverId AND "+statTable+".day=:day AND "+statTable+".timeFrom=:minute")
query.Param("day", day)
query.Param("minute", minute)
query.Group(selfTable + ".id")
query.Asc("SUM(" + statTable + ".bytes)").
DescPk()
hasOnlyIds = true
case "trafficOutDesc":
query.Result("id")
query.Join(SharedServerDailyStatDAO, dbs.QueryJoinLeft, selfTable+".id="+statTable+".serverId AND "+statTable+".day=:day AND "+statTable+".timeFrom=:minute")
query.Param("day", day)
query.Param("minute", minute)
query.Group(selfTable + ".id")
query.Desc("SUM(" + statTable + ".bytes)").
DescPk()
hasOnlyIds = true
default:
query.DescPk()
}
_, err = query.FindAll() _, err = query.FindAll()
if hasOnlyIds {
var newResult = []*Server{}
for _, one := range result {
server, err := this.Find(tx, one.Id)
if err != nil {
return nil, err
}
if server == nil {
continue
}
newResult = append(newResult, server.(*Server))
}
result = newResult
}
return return
} }
@@ -1129,6 +1263,18 @@ func (this *ServerDAO) ComposeServerConfig(tx *dbs.Tx, server *Server, cacheMap
} }
} }
// UAM
if teaconst.IsPlus && IsNotNull(server.Uam) {
var uamConfig = &serverconfigs.UAMConfig{}
err = json.Unmarshal(server.Uam, uamConfig)
if err != nil {
return nil, err
}
if uamConfig.IsOn {
config.UAM = uamConfig
}
}
if cacheMap != nil { if cacheMap != nil {
cacheMap.Put(cacheKey, config) cacheMap.Put(cacheKey, config)
} }
@@ -2275,7 +2421,36 @@ func (this *ServerDAO) FindServerLastUserPlanIdAndUserId(tx *dbs.Tx, serverId in
return int64(one.(*Server).LastUserPlanId), int64(one.(*Server).UserId), nil return int64(one.(*Server).LastUserPlanId), int64(one.(*Server).UserId), nil
} }
// NotifyUpdate 同步集群 // UpdateServerUAM 开启UAM
func (this *ServerDAO) UpdateServerUAM(tx *dbs.Tx, serverId int64, uamConfig *serverconfigs.UAMConfig) error {
if uamConfig == nil {
return nil
}
configJSON, err := json.Marshal(uamConfig)
if err != nil {
return err
}
err = this.Query(tx).
Pk(serverId).
Set("uam", configJSON).
UpdateQuickly()
if err != nil {
return err
}
return this.NotifyUpdate(tx, serverId)
}
// FindServerUAM 查找服务的UAM配置
func (this *ServerDAO) FindServerUAM(tx *dbs.Tx, serverId int64) ([]byte, error) {
return this.Query(tx).
Pk(serverId).
Result("uam").
FindJSONCol()
}
// NotifyUpdate 同步服务所在的集群
func (this *ServerDAO) NotifyUpdate(tx *dbs.Tx, serverId int64) error { func (this *ServerDAO) NotifyUpdate(tx *dbs.Tx, serverId int64) error {
// 创建任务 // 创建任务
clusterId, err := this.FindServerClusterId(tx, serverId) clusterId, err := this.FindServerClusterId(tx, serverId)
@@ -2288,7 +2463,15 @@ func (this *ServerDAO) NotifyUpdate(tx *dbs.Tx, serverId int64) error {
return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, serverId, NodeTaskTypeConfigChanged) return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, serverId, NodeTaskTypeConfigChanged)
} }
// NotifyDNSUpdate 通知DNS更新 // NotifyClusterUpdate 同步指定的集群
func (this *ServerDAO) NotifyClusterUpdate(tx *dbs.Tx, clusterId, serverId int64) error {
if clusterId <= 0 {
return nil
}
return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, serverId, NodeTaskTypeConfigChanged)
}
// NotifyDNSUpdate 通知当前集群DNS更新
func (this *ServerDAO) NotifyDNSUpdate(tx *dbs.Tx, serverId int64) error { func (this *ServerDAO) NotifyDNSUpdate(tx *dbs.Tx, serverId int64) error {
clusterId, err := this.Query(tx). clusterId, err := this.Query(tx).
Pk(serverId). Pk(serverId).
@@ -2310,7 +2493,22 @@ func (this *ServerDAO) NotifyDNSUpdate(tx *dbs.Tx, serverId int64) error {
if len(dnsInfo.DnsName) == 0 || dnsInfo.DnsDomainId <= 0 { if len(dnsInfo.DnsName) == 0 || dnsInfo.DnsDomainId <= 0 {
return nil return nil
} }
return dns.SharedDNSTaskDAO.CreateServerTask(tx, serverId, dns.DNSTaskTypeServerChange) return dns.SharedDNSTaskDAO.CreateServerTask(tx, clusterId, serverId, dns.DNSTaskTypeServerChange)
}
// NotifyClusterDNSUpdate 通知某个集群中的DNS更新
func (this *ServerDAO) NotifyClusterDNSUpdate(tx *dbs.Tx, clusterId int64, serverId int64) error {
dnsInfo, err := SharedNodeClusterDAO.FindClusterDNSInfo(tx, clusterId, nil)
if err != nil {
return err
}
if dnsInfo == nil {
return nil
}
if len(dnsInfo.DnsName) == 0 || dnsInfo.DnsDomainId <= 0 {
return nil
}
return dns.SharedDNSTaskDAO.CreateServerTask(tx, clusterId, serverId, dns.DNSTaskTypeServerChange)
} }
// NotifyDisable 通知禁用 // NotifyDisable 通知禁用

View File

@@ -11,6 +11,7 @@ type Server struct {
Type string `field:"type"` // 服务类型 Type string `field:"type"` // 服务类型
Name string `field:"name"` // 名称 Name string `field:"name"` // 名称
Description string `field:"description"` // 描述 Description string `field:"description"` // 描述
PlainServerNames dbs.JSON `field:"plainServerNames"` // 扁平化域名列表
ServerNames dbs.JSON `field:"serverNames"` // 域名列表 ServerNames dbs.JSON `field:"serverNames"` // 域名列表
AuditingAt uint64 `field:"auditingAt"` // 审核提交时间 AuditingAt uint64 `field:"auditingAt"` // 审核提交时间
AuditingServerNames dbs.JSON `field:"auditingServerNames"` // 审核中的域名 AuditingServerNames dbs.JSON `field:"auditingServerNames"` // 审核中的域名
@@ -46,6 +47,7 @@ type Server struct {
TotalTraffic float64 `field:"totalTraffic"` // 总流量 TotalTraffic float64 `field:"totalTraffic"` // 总流量
UserPlanId uint32 `field:"userPlanId"` // 所属套餐ID UserPlanId uint32 `field:"userPlanId"` // 所属套餐ID
LastUserPlanId uint32 `field:"lastUserPlanId"` // 上一次使用的套餐 LastUserPlanId uint32 `field:"lastUserPlanId"` // 上一次使用的套餐
Uam dbs.JSON `field:"uam"` // UAM设置
} }
type ServerOperator struct { type ServerOperator struct {
@@ -56,6 +58,7 @@ type ServerOperator struct {
Type interface{} // 服务类型 Type interface{} // 服务类型
Name interface{} // 名称 Name interface{} // 名称
Description interface{} // 描述 Description interface{} // 描述
PlainServerNames interface{} // 扁平化域名列表
ServerNames interface{} // 域名列表 ServerNames interface{} // 域名列表
AuditingAt interface{} // 审核提交时间 AuditingAt interface{} // 审核提交时间
AuditingServerNames interface{} // 审核中的域名 AuditingServerNames interface{} // 审核中的域名
@@ -91,6 +94,7 @@ type ServerOperator struct {
TotalTraffic interface{} // 总流量 TotalTraffic interface{} // 总流量
UserPlanId interface{} // 所属套餐ID UserPlanId interface{} // 所属套餐ID
LastUserPlanId interface{} // 上一次使用的套餐 LastUserPlanId interface{} // 上一次使用的套餐
Uam interface{} // UAM设置
} }
func NewServerOperator() *ServerOperator { func NewServerOperator() *ServerOperator {

View File

@@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"errors" "errors"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
@@ -281,7 +282,7 @@ func (this *SSLCertDAO) CountCerts(tx *dbs.Tx, isCA bool, isAvailable bool, isEx
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR description LIKE :keyword OR dnsNames LIKE :keyword OR commonNames LIKE :keyword)"). query.Where("(name LIKE :keyword OR description LIKE :keyword OR dnsNames LIKE :keyword OR commonNames LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if userId > 0 { if userId > 0 {
query.Attr("userId", userId) query.Attr("userId", userId)
@@ -311,7 +312,7 @@ func (this *SSLCertDAO) ListCertIds(tx *dbs.Tx, isCA bool, isAvailable bool, isE
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR description LIKE :keyword OR dnsNames LIKE :keyword OR commonNames LIKE :keyword)"). query.Where("(name LIKE :keyword OR description LIKE :keyword OR dnsNames LIKE :keyword OR commonNames LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if userId > 0 { if userId > 0 {
query.Attr("userId", userId) query.Attr("userId", userId)
@@ -514,7 +515,7 @@ func (this *SSLCertDAO) CountAllSSLCertsWithOCSPError(tx *dbs.Tx, keyword string
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR description LIKE :keyword OR dnsNames LIKE :keyword OR commonNames LIKE :keyword OR ocspError LIKE :keyword)"). query.Where("(name LIKE :keyword OR description LIKE :keyword OR dnsNames LIKE :keyword OR commonNames LIKE :keyword OR ocspError LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
return query. return query.
@@ -530,7 +531,7 @@ func (this *SSLCertDAO) ListSSLCertsWithOCSPError(tx *dbs.Tx, keyword string, of
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR description LIKE :keyword OR dnsNames LIKE :keyword OR commonNames LIKE :keyword OR ocspError LIKE :keyword)"). query.Where("(name LIKE :keyword OR description LIKE :keyword OR dnsNames LIKE :keyword OR commonNames LIKE :keyword OR ocspError LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
_, err = query. _, err = query.

View File

@@ -334,7 +334,7 @@ func (this *ServerDomainHourlyStatDAO) FindTopDomainStatsWithServerId(tx *dbs.Tx
Table(table). Table(table).
Attr("serverId", serverId). Attr("serverId", serverId).
Between("hour", hourFrom, hourTo). Between("hour", hourFrom, hourTo).
UseIndex("hour"). UseIndex("serverId", "hour").
Result("domain, MIN(serverId) AS serverId, SUM(bytes) AS bytes, SUM(cachedBytes) AS cachedBytes, SUM(countRequests) AS countRequests, SUM(countCachedRequests) AS countCachedRequests, SUM(countAttackRequests) AS countAttackRequests, SUM(attackBytes) AS attackBytes"). Result("domain, MIN(serverId) AS serverId, SUM(bytes) AS bytes, SUM(cachedBytes) AS cachedBytes, SUM(countRequests) AS countRequests, SUM(countCachedRequests) AS countCachedRequests, SUM(countAttackRequests) AS countAttackRequests, SUM(attackBytes) AS attackBytes").
Group("domain"). Group("domain").
Desc("countRequests"). Desc("countRequests").

View File

@@ -33,12 +33,13 @@ func TestServerDomainHourlyStatDAO_FindAllPartitionTables(t *testing.T) {
t.Log(dao.FindAllPartitionTables()) t.Log(dao.FindAllPartitionTables())
} }
func TestServerDomainHourlyStatDAO_IncreaseHourlyStat(t *testing.T) { func TestServerDomainHourlyStatDAO_InsertManyHourlyStat(t *testing.T) {
dbs.NotifyReady() dbs.NotifyReady()
for i := 0; i < 1_000_000; i++ { var count = 1
for i := 0; i < count; i++ {
var f = string([]rune{int32(rands.Int('0', '9'))}) var f = string([]rune{int32(rands.Int('0', '9'))})
if i % 30 > 0 { if i%30 > 0 {
f = string([]rune{int32(rands.Int('a', 'z'))}) f = string([]rune{int32(rands.Int('a', 'z'))})
} }

View File

@@ -112,6 +112,18 @@ func (this *TrafficDailyStatDAO) FindDailyStats(tx *dbs.Tx, dayFrom string, dayT
return result, nil return result, nil
} }
// FindDailyStat 查找某天的统计
func (this *TrafficDailyStatDAO) FindDailyStat(tx *dbs.Tx, day string) (*TrafficDailyStat, error) {
one, err := this.Query(tx).
Attr("day", day).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*TrafficDailyStat), nil
}
// Clean 清理历史数据 // Clean 清理历史数据
func (this *TrafficDailyStatDAO) Clean(tx *dbs.Tx, days int) error { func (this *TrafficDailyStatDAO) Clean(tx *dbs.Tx, days int) error {
var day = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -days)) var day = timeutil.Format("Ymd", time.Now().AddDate(0, 0, -days))

View File

@@ -112,6 +112,31 @@ func (this *TrafficHourlyStatDAO) FindHourlyStats(tx *dbs.Tx, hourFrom string, h
return result, nil return result, nil
} }
// FindHourlyStat 查FindHourlyStat 找单个小时的统计
func (this *TrafficHourlyStatDAO) FindHourlyStat(tx *dbs.Tx, hour string) (*TrafficHourlyStat, error) {
one, err := this.Query(tx).
Attr("hour", hour).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*TrafficHourlyStat), err
}
// SumHourlyStats 计算多个小时的统计总和
func (this *TrafficHourlyStatDAO) SumHourlyStats(tx *dbs.Tx, hourFrom string, hourTo string) (*TrafficHourlyStat, error) {
one, err := this.Query(tx).
Result("SUM(bytes) AS bytes", "SUM(cachedBytes) AS cachedBytes", "SUM(countRequests) AS countRequests", "SUM(countCachedRequests) AS countCachedRequests", "SUM(countAttackRequests) AS countAttackRequests", "SUM(attackBytes) AS attackBytes").
Between("hour", hourFrom, hourTo).
Find()
if err != nil || one == nil {
return nil, err
}
return one.(*TrafficHourlyStat), nil
}
// Clean 清理历史数据 // Clean 清理历史数据
func (this *TrafficHourlyStatDAO) Clean(tx *dbs.Tx, days int) error { func (this *TrafficHourlyStatDAO) Clean(tx *dbs.Tx, days int) error {
var hour = timeutil.Format("Ymd00", time.Now().AddDate(0, 0, -days)) var hour = timeutil.Format("Ymd00", time.Now().AddDate(0, 0, -days))

View File

@@ -2,6 +2,7 @@ package models
import ( import (
"encoding/json" "encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors" "github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils" "github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
@@ -222,7 +223,7 @@ func (this *UserDAO) CountAllEnabledUsers(tx *dbs.Tx, clusterId int64, keyword s
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(username LIKE :keyword OR fullname LIKE :keyword OR mobile LIKE :keyword OR email LIKE :keyword OR tel LIKE :keyword OR remark LIKE :keyword)"). query.Where("(username LIKE :keyword OR fullname LIKE :keyword OR mobile LIKE :keyword OR email LIKE :keyword OR tel LIKE :keyword OR remark LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if isVerifying { if isVerifying {
query.Attr("isVerified", 0) query.Attr("isVerified", 0)
@@ -247,7 +248,7 @@ func (this *UserDAO) ListEnabledUsers(tx *dbs.Tx, clusterId int64, keyword strin
} }
if len(keyword) > 0 { if len(keyword) > 0 {
query.Where("(username LIKE :keyword OR fullname LIKE :keyword OR mobile LIKE :keyword OR email LIKE :keyword OR tel LIKE :keyword OR remark LIKE :keyword)"). query.Where("(username LIKE :keyword OR fullname LIKE :keyword OR mobile LIKE :keyword OR email LIKE :keyword OR tel LIKE :keyword OR remark LIKE :keyword)").
Param("keyword", "%"+keyword+"%") Param("keyword", dbutils.QuoteLike(keyword))
} }
if isVerifying { if isVerifying {
query.Attr("isVerified", 0) query.Attr("isVerified", 0)

View File

@@ -30,6 +30,17 @@ func IsNotNull(data []byte) bool {
return true return true
} }
// IsNull 判断JSON是否为空
func IsNull(data []byte) bool {
if len(data) == 0 {
return true
}
if len(data) == 4 && string(data) == "null" {
return true
}
return false
}
// NewQuery 构造Query // NewQuery 构造Query
func NewQuery(tx *dbs.Tx, dao dbs.DAOWrapper, adminId int64, userId int64) *dbs.Query { func NewQuery(tx *dbs.Tx, dao dbs.DAOWrapper, adminId int64, userId int64) *dbs.Query {
query := dao.Object().Query(tx) query := dao.Object().Query(tx)

View File

@@ -2,24 +2,9 @@ package dbutils
import ( import (
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
"sync" "strings"
) )
var SharedCacheLocker = sync.RWMutex{}
// JSONBytes 处理JSON字节Slice
func JSONBytes(data []byte) []byte {
if len(data) == 0 {
return []byte("null")
}
return data
}
// IsNotNull 判断JSON是否不为空
func IsNotNull(data string) bool {
return len(data) > 0 && data != "null"
}
// NewQuery 构造Query // NewQuery 构造Query
func NewQuery(tx *dbs.Tx, dao dbs.DAOWrapper, adminId int64, userId int64) *dbs.Query { func NewQuery(tx *dbs.Tx, dao dbs.DAOWrapper, adminId int64, userId int64) *dbs.Query {
query := dao.Object().Query(tx) query := dao.Object().Query(tx)
@@ -31,3 +16,22 @@ func NewQuery(tx *dbs.Tx, dao dbs.DAOWrapper, adminId int64, userId int64) *dbs.
} }
return query return query
} }
// QuoteLikeKeyword 处理关键词中的特殊字符
func QuoteLikeKeyword(keyword string) string {
keyword = strings.ReplaceAll(keyword, "%", "\\%")
keyword = strings.ReplaceAll(keyword, "_", "\\_")
return keyword
}
func QuoteLike(keyword string) string {
return "%" + QuoteLikeKeyword(keyword) + "%"
}
func QuoteLikePrefix(keyword string) string {
return QuoteLikeKeyword(keyword) + "%"
}
func QuoteLikeSuffix(keyword string) string {
return "%" + QuoteLikeKeyword(keyword)
}

View File

@@ -0,0 +1,14 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package dbutils_test
import (
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"testing"
)
func TestQuoteLike(t *testing.T) {
for _, s := range []string{"abc", "abc%", "_abc%%%"} {
t.Log(s + " => " + dbutils.QuoteLike(s))
}
}

1
internal/dnsclients/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*_plus_test.go

View File

@@ -279,15 +279,15 @@ func (this *CloudFlareProvider) doAPI(method string, apiPath string, args map[st
return errors.New("invalid response status '" + strconv.Itoa(resp.StatusCode) + "', response '" + string(data) + "'") return errors.New("invalid response status '" + strconv.Itoa(resp.StatusCode) + "', response '" + string(data) + "'")
} }
err = json.Unmarshal(data, respPtr)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
return errors.New("response error: " + string(data)) return errors.New("response error: " + string(data))
} }
err = json.Unmarshal(data, respPtr)
if err != nil {
return errors.New("decode json failed: " + err.Error() + ", response text: " + string(data))
}
return nil return nil
} }

View File

@@ -1,6 +1,7 @@
package dnsclients package dnsclients
import ( import (
"crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes" "github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
@@ -11,16 +12,28 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"time"
) )
const ( const (
DNSPodMaxTTL int32 = 604800 DNSPodMaxTTL int32 = 604800
DNSPodInternational = "international"
) )
var dnsPodHTTPClient = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
// DNSPodProvider DNSPod服务商 // DNSPodProvider DNSPod服务商
type DNSPodProvider struct { type DNSPodProvider struct {
BaseProvider BaseProvider
region string
apiId string apiId string
apiToken string apiToken string
} }
@@ -29,6 +42,7 @@ type DNSPodProvider struct {
func (this *DNSPodProvider) Auth(params maps.Map) error { func (this *DNSPodProvider) Auth(params maps.Map) error {
this.apiId = params.GetString("id") this.apiId = params.GetString("id")
this.apiToken = params.GetString("token") this.apiToken = params.GetString("token")
this.region = params.GetString("region")
if len(this.apiId) == 0 { if len(this.apiId) == 0 {
return errors.New("'id' should be not empty") return errors.New("'id' should be not empty")
@@ -43,8 +57,9 @@ func (this *DNSPodProvider) Auth(params maps.Map) error {
func (this *DNSPodProvider) GetDomains() (domains []string, err error) { func (this *DNSPodProvider) GetDomains() (domains []string, err error) {
offset := 0 offset := 0
size := 100 size := 100
for { for {
domainsResp, err := this.post("/Domain.list", map[string]string{ domainsResp, err := this.post("/Domain.List", map[string]string{
"offset": numberutils.FormatInt(offset), "offset": numberutils.FormatInt(offset),
"length": numberutils.FormatInt(size), "length": numberutils.FormatInt(size),
}) })
@@ -78,7 +93,7 @@ func (this *DNSPodProvider) GetRecords(domain string) (records []*dnstypes.Recor
offset := 0 offset := 0
size := 100 size := 100
for { for {
recordsResp, err := this.post("/Record.list", map[string]string{ recordsResp, err := this.post("/Record.List", map[string]string{
"domain": domain, "domain": domain,
"offset": numberutils.FormatInt(offset), "offset": numberutils.FormatInt(offset),
"length": numberutils.FormatInt(size), "length": numberutils.FormatInt(size),
@@ -114,7 +129,7 @@ func (this *DNSPodProvider) GetRecords(domain string) (records []*dnstypes.Recor
// GetRoutes 读取线路数据 // GetRoutes 读取线路数据
func (this *DNSPodProvider) GetRoutes(domain string) (routes []*dnstypes.Route, err error) { func (this *DNSPodProvider) GetRoutes(domain string) (routes []*dnstypes.Route, err error) {
infoResp, err := this.post("/Domain.info", map[string]string{ infoResp, err := this.post("/Domain.Info", map[string]string{
"domain": domain, "domain": domain,
}) })
if err != nil { if err != nil {
@@ -230,44 +245,50 @@ func (this *DNSPodProvider) DeleteRecord(domain string, record *dnstypes.Record)
// 发送请求 // 发送请求
func (this *DNSPodProvider) post(path string, params map[string]string) (maps.Map, error) { func (this *DNSPodProvider) post(path string, params map[string]string) (maps.Map, error) {
apiHost := "https://dnsapi.cn" var apiHost = "https://dnsapi.cn"
query := url.Values{ var lang = "cn"
if this.isInternational() { // 国际版
apiHost = "https://api.dnspod.com"
lang = "en"
}
var query = url.Values{
"login_token": []string{this.apiId + "," + this.apiToken}, "login_token": []string{this.apiId + "," + this.apiToken},
"format": []string{"json"}, "format": []string{"json"},
"lang": []string{"cn"}, "lang": []string{lang},
} }
for p, v := range params { for p, v := range params {
query[p] = []string{v} query[p] = []string{v}
} }
req, err := http.NewRequest(http.MethodPost, apiHost+path, strings.NewReader(query.Encode())) req, err := http.NewRequest(http.MethodPost, apiHost+path, strings.NewReader(query.Encode()))
if err != nil { if err != nil {
return nil, err return nil, err
} }
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("User-Agent", "GoEdge Client/1.0.0 (iwind.liu@gmail.com)") req.Header.Set("User-Agent", "GoEdge-Client/1.0.0 (iwind.liu@gmail.com)")
req.Header.Set("Accept", "*/*")
client := http.Client{} resp, err := dnsPodHTTPClient.Do(req)
resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() { defer func() {
_ = resp.Body.Close() _ = resp.Body.Close()
client.CloseIdleConnections()
}() }()
body, err := ioutil.ReadAll(resp.Body) body, err := ioutil.ReadAll(resp.Body)
if err != nil { if err != nil {
return nil, err return nil, err
} }
m := maps.Map{} var m = maps.Map{}
err = json.Unmarshal(body, &m) err = json.Unmarshal(body, &m)
if err != nil { if err != nil {
return nil, err return nil, err
} }
status := m.GetMap("status") var status = m.GetMap("status")
code := status.GetString("code") var code = status.GetString("code")
if code != "1" { if code != "1" {
return nil, errors.New("code: " + code + ", message: " + status.GetString("message")) return nil, errors.New("API response error: code: " + code + ", message: " + status.GetString("message"))
} }
return m, nil return m, nil
@@ -275,5 +296,12 @@ func (this *DNSPodProvider) post(path string, params map[string]string) (maps.Ma
// DefaultRoute 默认线路 // DefaultRoute 默认线路
func (this *DNSPodProvider) DefaultRoute() string { func (this *DNSPodProvider) DefaultRoute() string {
if this.isInternational() {
return "Default"
}
return "默认" return "默认"
} }
func (this *DNSPodProvider) isInternational() bool {
return this.region == DNSPodInternational
}

View File

@@ -1,7 +1,8 @@
package dnsclients package dnsclients_test
import ( import (
"encoding/json" "encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes" "github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/iwind/TeaGo/dbs" "github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs" "github.com/iwind/TeaGo/logs"
@@ -9,8 +10,10 @@ import (
"testing" "testing"
) )
const DNSPodTestDomain = "yun4s.cn"
func TestDNSPodProvider_GetDomains(t *testing.T) { func TestDNSPodProvider_GetDomains(t *testing.T) {
provider, err := testDNSPodProvider() provider, _, err := testDNSPodProvider()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -22,11 +25,11 @@ func TestDNSPodProvider_GetDomains(t *testing.T) {
} }
func TestDNSPodProvider_GetRoutes(t *testing.T) { func TestDNSPodProvider_GetRoutes(t *testing.T) {
provider, err := testDNSPodProvider() provider, _, err := testDNSPodProvider()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
routes, err := provider.GetRoutes("yun4s.cn") routes, err := provider.GetRoutes(DNSPodTestDomain)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -34,11 +37,11 @@ func TestDNSPodProvider_GetRoutes(t *testing.T) {
} }
func TestDNSPodProvider_GetRecords(t *testing.T) { func TestDNSPodProvider_GetRecords(t *testing.T) {
provider, err := testDNSPodProvider() provider, _, err := testDNSPodProvider()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
records, err := provider.GetRecords("yun4s.cn") records, err := provider.GetRecords(DNSPodTestDomain)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -48,17 +51,22 @@ func TestDNSPodProvider_GetRecords(t *testing.T) {
} }
func TestDNSPodProvider_AddRecord(t *testing.T) { func TestDNSPodProvider_AddRecord(t *testing.T) {
provider, err := testDNSPodProvider() provider, isInternational, err := testDNSPodProvider()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = provider.AddRecord("yun4s.cn", &dnstypes.Record{ var route = "联通"
if isInternational {
route = "Default"
}
err = provider.AddRecord(DNSPodTestDomain, &dnstypes.Record{
Type: dnstypes.RecordTypeCNAME, Type: dnstypes.RecordTypeCNAME,
Name: "hello-forward", Name: "hello-forward",
Value: "hello.yun4s.cn", Value: "hello." + DNSPodTestDomain,
Route: "联通", Route: route,
TTL: 300, TTL: 600,
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -67,18 +75,25 @@ func TestDNSPodProvider_AddRecord(t *testing.T) {
} }
func TestDNSPodProvider_UpdateRecord(t *testing.T) { func TestDNSPodProvider_UpdateRecord(t *testing.T) {
provider, err := testDNSPodProvider() provider, isInternational, err := testDNSPodProvider()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = provider.UpdateRecord("yun4s.cn", &dnstypes.Record{ var route = "联通"
Id: "697036856", var id = "1093875360"
if isInternational {
route = "Default"
id = "28507333"
}
err = provider.UpdateRecord(DNSPodTestDomain, &dnstypes.Record{
Id: id,
}, &dnstypes.Record{ }, &dnstypes.Record{
Type: dnstypes.RecordTypeA, Type: dnstypes.RecordTypeA,
Name: "hello", Name: "hello",
Value: "192.168.1.102", Value: "192.168.1.102",
Route: "联通", Route: route,
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -87,13 +102,17 @@ func TestDNSPodProvider_UpdateRecord(t *testing.T) {
} }
func TestDNSPodProvider_DeleteRecord(t *testing.T) { func TestDNSPodProvider_DeleteRecord(t *testing.T) {
provider, err := testDNSPodProvider() provider, isInternational, err := testDNSPodProvider()
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = provider.DeleteRecord("yun4s.cn", &dnstypes.Record{ var id = "1093875360"
Id: "697040986", if isInternational {
id = "28507333"
}
err = provider.DeleteRecord(DNSPodTestDomain, &dnstypes.Record{
Id: id,
}) })
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@@ -101,24 +120,24 @@ func TestDNSPodProvider_DeleteRecord(t *testing.T) {
t.Log("ok") t.Log("ok")
} }
func testDNSPodProvider() (ProviderInterface, error) { func testDNSPodProvider() (provider dnsclients.ProviderInterface, isInternational bool, err error) {
db, err := dbs.Default() db, err := dbs.Default()
if err != nil { if err != nil {
return nil, err return nil, false, err
} }
one, err := db.FindOne("SELECT * FROM edgeDNSProviders WHERE type='dnspod' ORDER BY id DESC") one, err := db.FindOne("SELECT * FROM edgeDNSProviders WHERE type='dnspod' AND id='14' ORDER BY id DESC")
if err != nil { if err != nil {
return nil, err return nil, false, err
} }
apiParams := maps.Map{} apiParams := maps.Map{}
err = json.Unmarshal([]byte(one.GetString("apiParams")), &apiParams) err = json.Unmarshal([]byte(one.GetString("apiParams")), &apiParams)
if err != nil { if err != nil {
return nil, err return nil, false, err
} }
provider := &DNSPodProvider{} provider = &dnsclients.DNSPodProvider{}
err = provider.Auth(apiParams) err = provider.Auth(apiParams)
if err != nil { if err != nil {
return nil, err return nil, false, err
} }
return provider, nil return provider, apiParams.GetString("region") == "international", nil
} }

View File

@@ -1,7 +1,6 @@
package dnsclients package dnsclients
import ( import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/iwind/TeaGo/maps" "github.com/iwind/TeaGo/maps"
) )
@@ -49,21 +48,7 @@ func FindAllProviderTypes() []maps.Map {
}, },
} }
if teaconst.IsPlus { typeMaps = filterTypeMaps(typeMaps)
typeMaps = append(typeMaps, []maps.Map{
{
"name": "EdgeDNS",
"code": ProviderTypeLocalEdgeDNS,
"description": "GoEdge商业版提供的智能DNS服务。",
},
// TODO 需要实现用户使用AccessId/AccessKey来连接DNS服务
/**{
"name": "用户EdgeDNS",
"code": ProviderTypeUserEdgeDNS,
"description": "通过API连接企业版提供的DNS服务。",
},**/
}...)
}
typeMaps = append(typeMaps, maps.Map{ typeMaps = append(typeMaps, maps.Map{
"name": "自定义HTTP DNS", "name": "自定义HTTP DNS",
@@ -91,7 +76,8 @@ func FindProvider(providerType ProviderType) ProviderInterface {
case ProviderTypeCustomHTTP: case ProviderTypeCustomHTTP:
return &CustomHTTPProvider{} return &CustomHTTPProvider{}
} }
return nil
return filterProvider(providerType)
} }
// FindProviderTypeName 查找服务商名称 // FindProviderTypeName 查找服务商名称

View File

@@ -0,0 +1,15 @@
// Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
//go:build !plus
// +build !plus
package dnsclients
import "github.com/iwind/TeaGo/maps"
func filterTypeMaps(typeMaps []maps.Map) []maps.Map {
return typeMaps
}
func filterProvider(providerType string) ProviderInterface {
return nil
}

View File

@@ -99,7 +99,7 @@ func (this *IP2Region) MemorySearch(ipStr string) (ipInfo *IpInfo, err error) {
} }
} }
if dataPtr == 0 { if dataPtr == 0 {
return nil, errors.New("not found") return nil, nil
} }
dataLen := (dataPtr >> 24) & 0xFF dataLen := (dataPtr >> 24) & 0xFF

View File

@@ -284,6 +284,9 @@ func (this *APINode) autoUpgrade() error {
if err != nil { if err != nil {
return errors.New("load database failed: " + err.Error()) return errors.New("load database failed: " + err.Error())
} }
defer func() {
_ = db.Close()
}()
one, err := db.FindOne("SELECT version FROM edgeVersions LIMIT 1") one, err := db.FindOne("SELECT version FROM edgeVersions LIMIT 1")
if err != nil { if err != nil {
return errors.New("query version failed: " + err.Error()) return errors.New("query version failed: " + err.Error())
@@ -346,10 +349,18 @@ func (this *APINode) listenPorts(apiNode *models.APINode) (isListening bool) {
remotelogs.Error("API_NODE", "decode http config: "+err.Error()) remotelogs.Error("API_NODE", "decode http config: "+err.Error())
return return
} }
var ports = []int{}
isListening = false isListening = false
if httpConfig != nil && httpConfig.IsOn && len(httpConfig.Listen) > 0 { if httpConfig != nil && httpConfig.IsOn && len(httpConfig.Listen) > 0 {
for _, listen := range httpConfig.Listen { for _, listen := range httpConfig.Listen {
for _, addr := range listen.Addresses() { for _, addr := range listen.Addresses() {
// 收集Port
_, portString, _ := net.SplitHostPort(addr)
var port = types.Int(portString)
if port > 0 && !lists.ContainsInt(ports, port) {
ports = append(ports, port)
}
listener, err := net.Listen("tcp", addr) listener, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
remotelogs.Error("API_NODE", "listening '"+addr+"' failed: "+err.Error()+", we will try to listen port only") remotelogs.Error("API_NODE", "listening '"+addr+"' failed: "+err.Error()+", we will try to listen port only")
@@ -398,6 +409,13 @@ func (this *APINode) listenPorts(apiNode *models.APINode) (isListening bool) {
for _, listen := range httpsConfig.Listen { for _, listen := range httpsConfig.Listen {
for _, addr := range listen.Addresses() { for _, addr := range listen.Addresses() {
// 收集Port
_, portString, _ := net.SplitHostPort(addr)
var port = types.Int(portString)
if port > 0 && !lists.ContainsInt(ports, port) {
ports = append(ports, port)
}
listener, err := net.Listen("tcp", addr) listener, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
remotelogs.Error("API_NODE", "listening '"+addr+"' failed: "+err.Error()+", we will try to listen port only") remotelogs.Error("API_NODE", "listening '"+addr+"' failed: "+err.Error()+", we will try to listen port only")
@@ -437,6 +455,13 @@ func (this *APINode) listenPorts(apiNode *models.APINode) (isListening bool) {
if restHTTPConfig != nil && restHTTPConfig.IsOn && len(restHTTPConfig.Listen) > 0 { if restHTTPConfig != nil && restHTTPConfig.IsOn && len(restHTTPConfig.Listen) > 0 {
for _, listen := range restHTTPConfig.Listen { for _, listen := range restHTTPConfig.Listen {
for _, addr := range listen.Addresses() { for _, addr := range listen.Addresses() {
// 收集Port
_, portString, _ := net.SplitHostPort(addr)
var port = types.Int(portString)
if port > 0 && !lists.ContainsInt(ports, port) {
ports = append(ports, port)
}
listener, err := net.Listen("tcp", addr) listener, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
remotelogs.Error("API_NODE", "listening REST 'http://"+addr+"' failed: "+err.Error()) remotelogs.Error("API_NODE", "listening REST 'http://"+addr+"' failed: "+err.Error())
@@ -470,6 +495,13 @@ func (this *APINode) listenPorts(apiNode *models.APINode) (isListening bool) {
len(restHTTPSConfig.SSLPolicy.Certs) > 0 { len(restHTTPSConfig.SSLPolicy.Certs) > 0 {
for _, listen := range restHTTPSConfig.Listen { for _, listen := range restHTTPSConfig.Listen {
for _, addr := range listen.Addresses() { for _, addr := range listen.Addresses() {
// 收集Port
_, portString, _ := net.SplitHostPort(addr)
var port = types.Int(portString)
if port > 0 && !lists.ContainsInt(ports, port) {
ports = append(ports, port)
}
listener, err := net.Listen("tcp", addr) listener, err := net.Listen("tcp", addr)
if err != nil { if err != nil {
remotelogs.Error("API_NODE", "listening REST 'https://"+addr+"' failed: "+err.Error()) remotelogs.Error("API_NODE", "listening REST 'https://"+addr+"' failed: "+err.Error())
@@ -497,6 +529,11 @@ func (this *APINode) listenPorts(apiNode *models.APINode) (isListening bool) {
} }
} }
// add to local firewall
if len(ports) > 0 {
utils.AddPortsToFirewall(ports)
}
return return
} }
@@ -582,6 +619,22 @@ func (this *APINode) listenSock() error {
_ = cmd.Reply(&gosock.Command{ _ = cmd.Reply(&gosock.Command{
Params: map[string]interface{}{"debug": teaconst.Debug}, Params: map[string]interface{}{"debug": teaconst.Debug},
}) })
case "db.stmt.prepare":
dbs.ShowPreparedStatements = !dbs.ShowPreparedStatements
_ = cmd.Reply(&gosock.Command{
Params: map[string]interface{}{"isOn": dbs.ShowPreparedStatements},
})
case "db.stmt.count":
db, _ := dbs.Default()
if db != nil {
_ = cmd.Reply(&gosock.Command{
Params: map[string]interface{}{"count": db.StmtManager().Len()},
})
} else {
_ = cmd.Reply(&gosock.Command{
Params: map[string]interface{}{"count": 0},
})
}
} }
}) })
@@ -622,5 +675,9 @@ func (this *APINode) unaryInterceptor(ctx context.Context, req interface{}, info
return return
} }
return handler(ctx, req) result, err := handler(ctx, req)
if err != nil {
err = errors.New("'" + info.FullMethod + "()' says: " + err.Error())
}
return result, err
} }

View File

@@ -558,6 +558,18 @@ func (this *APINode) registerServices(server *grpc.Server) {
this.rest(instance) this.rest(instance)
} }
{
instance := this.serviceInstance(&services.ServerDomainHourlyStatService{}).(*services.ServerDomainHourlyStatService)
pb.RegisterServerDomainHourlyStatServiceServer(server, instance)
this.rest(instance)
}
{
instance := this.serviceInstance(&services.TrafficDailyStatService{}).(*services.TrafficDailyStatService)
pb.RegisterTrafficDailyStatServiceServer(server, instance)
this.rest(instance)
}
APINodeServicesRegister(this, server) APINodeServicesRegister(this, server)
// TODO check service names // TODO check service names

View File

@@ -4,13 +4,11 @@ package nameservers
import ( import (
"context" "context"
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models" "github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/nameservers" "github.com/TeaOSLab/EdgeAPI/internal/db/models/nameservers"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services" "github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time" timeutil "github.com/iwind/TeaGo/utils/time"
"time" "time"
) )
@@ -144,12 +142,8 @@ func (this *NSService) ComposeNSBoard(ctx context.Context, req *pb.ComposeNSBoar
return nil, err return nil, err
} }
for _, v := range cpuValues { for _, v := range cpuValues {
valueJSON, err := json.Marshal(types.Float32(v.Value))
if err != nil {
return nil, err
}
result.CpuNodeValues = append(result.CpuNodeValues, &pb.NodeValue{ result.CpuNodeValues = append(result.CpuNodeValues, &pb.NodeValue{
ValueJSON: valueJSON, ValueJSON: v.Value,
CreatedAt: int64(v.CreatedAt), CreatedAt: int64(v.CreatedAt),
}) })
} }
@@ -159,27 +153,19 @@ func (this *NSService) ComposeNSBoard(ctx context.Context, req *pb.ComposeNSBoar
return nil, err return nil, err
} }
for _, v := range memoryValues { for _, v := range memoryValues {
valueJSON, err := json.Marshal(types.Float32(v.Value))
if err != nil {
return nil, err
}
result.MemoryNodeValues = append(result.MemoryNodeValues, &pb.NodeValue{ result.MemoryNodeValues = append(result.MemoryNodeValues, &pb.NodeValue{
ValueJSON: valueJSON, ValueJSON: v.Value,
CreatedAt: int64(v.CreatedAt), CreatedAt: int64(v.CreatedAt),
}) })
} }
loadValues, err := models.SharedNodeValueDAO.ListValuesForNSNodes(tx, nodeconfigs.NodeValueItemLoad, "load5m", nodeconfigs.NodeValueRangeMinute) loadValues, err := models.SharedNodeValueDAO.ListValuesForNSNodes(tx, nodeconfigs.NodeValueItemLoad, "load1m", nodeconfigs.NodeValueRangeMinute)
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, v := range loadValues { for _, v := range loadValues {
valueJSON, err := json.Marshal(types.Float32(v.Value))
if err != nil {
return nil, err
}
result.LoadNodeValues = append(result.LoadNodeValues, &pb.NodeValue{ result.LoadNodeValues = append(result.LoadNodeValues, &pb.NodeValue{
ValueJSON: valueJSON, ValueJSON: v.Value,
CreatedAt: int64(v.CreatedAt), CreatedAt: int64(v.CreatedAt),
}) })
} }

View File

@@ -39,7 +39,7 @@ func (this *APINodeService) UpdateAPINode(ctx context.Context, req *pb.UpdateAPI
tx := this.NullTx() tx := this.NullTx()
err = models.SharedAPINodeDAO.UpdateAPINode(tx, req.ApiNodeId, req.Name, req.Description, req.HttpJSON, req.HttpsJSON, req.RestIsOn, req.RestHTTPJSON, req.RestHTTPSJSON, req.AccessAddrsJSON, req.IsOn) err = models.SharedAPINodeDAO.UpdateAPINode(tx, req.ApiNodeId, req.Name, req.Description, req.HttpJSON, req.HttpsJSON, req.RestIsOn, req.RestHTTPJSON, req.RestHTTPSJSON, req.AccessAddrsJSON, req.IsOn, req.IsPrimary)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -97,6 +97,7 @@ func (this *APINodeService) FindAllEnabledAPINodes(ctx context.Context, req *pb.
HttpsJSON: node.Https, HttpsJSON: node.Https,
AccessAddrsJSON: node.AccessAddrs, AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs, AccessAddrs: accessAddrs,
IsPrimary: node.IsPrimary,
}) })
} }
@@ -174,6 +175,7 @@ func (this *APINodeService) ListEnabledAPINodes(ctx context.Context, req *pb.Lis
AccessAddrsJSON: node.AccessAddrs, AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs, AccessAddrs: accessAddrs,
StatusJSON: node.Status, StatusJSON: node.Status,
IsPrimary: node.IsPrimary,
}) })
} }
@@ -218,6 +220,7 @@ func (this *APINodeService) FindEnabledAPINode(ctx context.Context, req *pb.Find
RestHTTPSJSON: node.RestHTTPS, RestHTTPSJSON: node.RestHTTPS,
AccessAddrsJSON: node.AccessAddrs, AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs, AccessAddrs: accessAddrs,
IsPrimary: node.IsPrimary,
} }
return &pb.FindEnabledAPINodeResponse{ApiNode: result}, nil return &pb.FindEnabledAPINodeResponse{ApiNode: result}, nil
} }
@@ -270,6 +273,7 @@ func (this *APINodeService) FindCurrentAPINode(ctx context.Context, req *pb.Find
AccessAddrsJSON: node.AccessAddrs, AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs, AccessAddrs: accessAddrs,
StatusJSON: nil, StatusJSON: nil,
IsPrimary: node.IsPrimary,
}}, nil }}, nil
} }

View File

@@ -25,7 +25,7 @@ func (this *DBService) FindAllDBTables(ctx context.Context, req *pb.FindAllDBTab
if err != nil { if err != nil {
return nil, err return nil, err
} }
ones, _, err := db.FindOnes("SELECT * FROM information_schema.`TABLES` WHERE TABLE_SCHEMA=?", db.Name()) ones, _, err := db.FindPreparedOnes("SELECT * FROM information_schema.`TABLES` WHERE TABLE_SCHEMA=?", db.Name())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -37,7 +37,7 @@ func (this *DBService) FindAllDBTables(ctx context.Context, req *pb.FindAllDBTab
if strings.HasPrefix(lowerTableName, "edgehttpaccesslogs_") { if strings.HasPrefix(lowerTableName, "edgehttpaccesslogs_") {
canDelete = true canDelete = true
canClean = true canClean = true
} else if lists.ContainsString([]string{"edgemessages", "edgelogs", "edgenodelogs"}, lowerTableName) { } else if lists.ContainsString([]string{"edgemessages", "edgelogs", "edgenodelogs", "edgemetricstats", "edgemetricsumstats", "edgeserverdomainhourlystats", "edgeserverregionprovincemonthlystats", "edgeserverregionprovidermonthlystats", "edgeserverregioncountrymonthlystats", "edgeserverregioncountrydailystats", "edgeserverregioncitymonthlystats", "edgeserverhttpfirewallhourlystats", "edgeserverhttpfirewalldailystats", "edgenodeclustertrafficdailystats", "edgenodetrafficdailystats", "edgenodetraffichourlystats", "edgensrecordhourlystats", "edgeserverclientbrowsermonthlystats", "edgeserverclientsystemmonthlystats"}, lowerTableName) || strings.HasPrefix(lowerTableName, "edgeserverdomainhourlystats_") || strings.HasPrefix(lowerTableName, "edgemetricstats_") || strings.HasPrefix(lowerTableName, "edgemetricsumstats_") {
canClean = true canClean = true
} }

View File

@@ -199,7 +199,7 @@ func (this *DBNodeService) FindAllDBNodeTables(ctx context.Context, req *pb.Find
_ = db.Close() _ = db.Close()
}() }()
ones, _, err := db.FindOnes("SELECT * FROM information_schema.`TABLES` WHERE TABLE_SCHEMA=?", db.Name()) ones, _, err := db.FindPreparedOnes("SELECT * FROM information_schema.`TABLES` WHERE TABLE_SCHEMA=?", db.Name())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -211,7 +211,7 @@ func (this *DBNodeService) FindAllDBNodeTables(ctx context.Context, req *pb.Find
if strings.HasPrefix(lowerTableName, "edgehttpaccesslogs_") || strings.HasPrefix(lowerTableName, "edgensaccesslogs_") { if strings.HasPrefix(lowerTableName, "edgehttpaccesslogs_") || strings.HasPrefix(lowerTableName, "edgensaccesslogs_") {
canDelete = true canDelete = true
canClean = true canClean = true
} else if lists.ContainsString([]string{"edgemessages", "edgelogs", "edgenodelogs"}, lowerTableName) { } else if lists.ContainsString([]string{"edgemessages", "edgelogs", "edgenodelogs", "edgemetricstats", "edgemetricsumstats", "edgeserverdomainhourlystats", "edgeserverregionprovincemonthlystats", "edgeserverregionprovidermonthlystats", "edgeserverregioncountrymonthlystats", "edgeserverregioncountrydailystats", "edgeserverregioncitymonthlystats", "edgeserverhttpfirewallhourlystats", "edgeserverhttpfirewalldailystats", "edgenodeclustertrafficdailystats", "edgenodetrafficdailystats", "edgenodetraffichourlystats", "edgensrecordhourlystats", "edgeserverclientbrowsermonthlystats", "edgeserverclientsystemmonthlystats"}, lowerTableName) || strings.HasPrefix(lowerTableName, "edgeserverdomainhourlystats_") || strings.HasPrefix(lowerTableName, "edgemetricstats_") || strings.HasPrefix(lowerTableName, "edgemetricsumstats_") {
canClean = true canClean = true
} }

View File

@@ -11,7 +11,7 @@ func TestDBService_FindAllDBTables(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
ones, _, err := db.FindOnes("SELECT * FROM information_schema.`TABLES` WHERE TABLE_SCHEMA=?", db.Name()) ones, _, err := db.FindPreparedOnes("SELECT * FROM information_schema.`TABLES` WHERE TABLE_SCHEMA=?", db.Name())
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@@ -59,7 +59,7 @@ func (this *DNSProviderService) CountAllEnabledDNSProviders(ctx context.Context,
tx := this.NullTx() tx := this.NullTx()
count, err := dns.SharedDNSProviderDAO.CountAllEnabledDNSProviders(tx, req.AdminId, req.UserId, req.Keyword) count, err := dns.SharedDNSProviderDAO.CountAllEnabledDNSProviders(tx, req.AdminId, req.UserId, req.Keyword, req.Domain)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -78,7 +78,7 @@ func (this *DNSProviderService) ListEnabledDNSProviders(ctx context.Context, req
tx := this.NullTx() tx := this.NullTx()
providers, err := dns.SharedDNSProviderDAO.ListEnabledDNSProviders(tx, req.AdminId, req.UserId, req.Keyword, req.Offset, req.Size) providers, err := dns.SharedDNSProviderDAO.ListEnabledDNSProviders(tx, req.AdminId, req.UserId, req.Keyword, req.Domain, req.Offset, req.Size)
if err != nil { if err != nil {
return nil, err return nil, err
} }

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