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/bootstrap"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"github.com/iwind/gosock/pkg/gosock"
"log"
"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() {
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-sql-driver/mysql v1.5.0
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/json-iterator/go v1.1.12 // indirect
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/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-20220321112016-5a2cd71d3151 h1:jksmjwlGC8QMpyHZmzxr7J+3NeMOr9Zy2+yNJxVSIjI=
github.com/iwind/TeaGo v0.0.0-20220321112016-5a2cd71d3151/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-20220321131553-fd7b112ba7e7/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/TeaGo v0.0.0-20220322141208-22f88d04004d h1:e8fkTKras/RXQWECApM9fKlFWujjYjEClpshkmZmtYg=
github.com/iwind/TeaGo v0.0.0-20220322141208-22f88d04004d/go.mod h1:HRHK0zoC/og3c9/hKosD9yYVMTnnzm3PgXUdhRYHaLc=
github.com/iwind/TeaGo v0.0.0-20220408064305-92be81dc2f7c h1:ugjYZ74FJGWlfDKKraNgMyDTeS4vbXHe89JGUVQIJMo=
github.com/iwind/TeaGo v0.0.0-20220408064305-92be81dc2f7c/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/go.mod h1:H5Q7SXwbx3a97ecJkaS2sD77gspzE7HFUafBO0peEyA=
github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik=

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,51 +1,108 @@
package apps
import (
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/utils/sizes"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/files"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/utils/time"
timeutil "github.com/iwind/TeaGo/utils/time"
"log"
"os"
"runtime"
"strconv"
"strings"
)
type LogWriter struct {
fileAppender *files.Appender
fp *os.File
c chan string
}
func (this *LogWriter) Init() {
// 创建目录
dir := files.NewFile(Tea.LogDir())
var dir = files.NewFile(Tea.LogDir())
if !dir.Exists() {
err := dir.Mkdir()
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 {
logs.Error(err)
log.Println("[LOG]open log file failed: " + err.Error())
} 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) {
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 {
_, err := this.fileAppender.AppendString(timeutil.Format("Y/m/d H:i:s ") + message + "\n")
if err != nil {
log.Println("[error]" + err.Error())
if len(file) > 0 {
log.Println(message + " (" + file + ":" + strconv.Itoa(line) + ")")
} else {
log.Println(message)
}
}
this.c <- message
}
func (this *LogWriter) Close() {
if this.fileAppender != nil {
_ = this.fileAppender.Close()
if this.fp != nil {
_ = 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
const (
Version = "0.4.6"
Version = "0.4.7"
ProductName = "Edge API"
ProcessName = "edge-api"
@@ -18,13 +18,13 @@ const (
// 其他节点版本号,用来检测是否有需要升级的节点
NodeVersion = "0.4.5"
UserNodeVersion = "0.3.2"
NodeVersion = "0.4.7"
UserNodeVersion = "0.3.3"
AuthorityNodeVersion = "0.0.2"
MonitorNodeVersion = "0.0.3"
DNSNodeVersion = "0.2.1"
DNSNodeVersion = "0.2.2"
ReportNodeVersion = "0.1.0"
// SQLVersion SQL版本号
SQLVersion = "5"
SQLVersion = "8"
)

View File

@@ -3,6 +3,7 @@ package db
import (
"database/sql"
"database/sql/driver"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
_ "github.com/iwind/TeaGo/bootstrap"
@@ -42,3 +43,13 @@ func TestDB_Instance(t *testing.T) {
}
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 (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/userconfigs"
_ "github.com/go-sql-driver/mysql"
@@ -79,7 +80,7 @@ func (this *UserAccountLogDAO) CountAccountLogs(tx *dbs.Tx, userId int64, accoun
}
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.Param("keyword", "%"+keyword+"%")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
if len(eventType) > 0 {
query.Attr("eventType", eventType)
@@ -98,7 +99,7 @@ func (this *UserAccountLogDAO) ListAccountLogs(tx *dbs.Tx, userId int64, account
}
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.Param("keyword", "%"+keyword+"%")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
if len(eventType) > 0 {
query.Attr("eventType", eventType)

View File

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

View File

@@ -3,6 +3,8 @@ package models
import (
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/configs"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
@@ -49,7 +51,10 @@ func (this *APINodeDAO) EnableAPINode(tx *dbs.Tx, id int64) error {
Pk(id).
Set("state", APINodeStateEnabled).
Update()
return err
if err != nil {
return err
}
return this.NotifyUpdate(tx, id)
}
// DisableAPINode 禁用条目
@@ -58,7 +63,10 @@ func (this *APINodeDAO) DisableAPINode(tx *dbs.Tx, id int64) error {
Pk(id).
Set("state", APINodeStateDisabled).
Update()
return err
if err != nil {
return err
}
return this.NotifyUpdate(tx, id)
}
// FindEnabledAPINode 查找启用中的条目
@@ -149,16 +157,33 @@ func (this *APINodeDAO) CreateAPINode(tx *dbs.Tx, name string, description strin
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
}
// 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 {
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.Name = name
op.Description = description
@@ -191,8 +216,13 @@ func (this *APINodeDAO) UpdateAPINode(tx *dbs.Tx, nodeId int64, name string, des
op.AccessAddrs = "[]"
}
op.IsPrimary = isPrimary
err := this.Save(tx, op)
return err
if err != nil {
return err
}
return this.NotifyUpdate(tx, nodeId)
}
// FindAllEnabledAPINodes 列出所有可用API节点
@@ -294,23 +324,6 @@ func (this *APINodeDAO) UpdateAPINodeStatus(tx *dbs.Tx, apiNodeId int64, statusJ
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 计算所有节点中低于某个版本的节点数量
func (this *APINodeDAO) CountAllLowerVersionNodes(tx *dbs.Tx, version string) (int64, error) {
return this.Query(tx).
@@ -384,3 +397,114 @@ func (this *APINodeDAO) FindAllEnabledAPIAccessIPs(tx *dbs.Tx, cacheMap *utils.C
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))
}
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) {
runtime.GOMAXPROCS(1)
for i := 0; i < b.N; i++ {

View File

@@ -23,6 +23,7 @@ type APINode struct {
AdminId uint32 `field:"adminId"` // 管理员ID
Weight uint32 `field:"weight"` // 权重
Status dbs.JSON `field:"status"` // 运行状态
IsPrimary bool `field:"isPrimary"` // 是否为主API节点
}
type APINodeOperator struct {
@@ -45,6 +46,7 @@ type APINodeOperator struct {
AdminId interface{} // 管理员ID
Weight interface{} // 权重
Status interface{} // 运行状态
IsPrimary interface{} // 是否为主API节点
}
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
func randomHTTPAccessLogDAO() (dao *HTTPAccessLogDAOWrapper) {
accessLogLocker.RLock()
@@ -237,7 +256,7 @@ func (this *DBNodeInitializer) loop() error {
}
if db == nil {
config := &dbs.DBConfig{
var config = &dbs.DBConfig{
Driver: "mysql",
Dsn: dsn,
Prefix: "edge",
@@ -251,7 +270,7 @@ func (this *DBNodeInitializer) loop() error {
// 检查表是否存在
// httpAccessLog
{
tableDef, err := SharedHTTPAccessLogManager.FindTable(db, timeutil.Format("Ymd"), true)
tableDef, err := SharedHTTPAccessLogManager.FindLastTable(db, timeutil.Format("Ymd"), true)
if err != nil {
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 计算服务商数量
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)
if len(keyword) > 0 {
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).
Count()
}
// 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)
if len(keyword) > 0 {
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.
State(DNSProviderStateEnabled).

View File

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

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ package models
import (
"bytes"
"encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
@@ -22,6 +23,7 @@ import (
timeutil "github.com/iwind/TeaGo/utils/time"
"net"
"net/http"
"net/url"
"regexp"
"sort"
"strings"
@@ -181,7 +183,7 @@ Loop:
// CreateHTTPAccessLog 写入单条访问日志
func (this *HTTPAccessLogDAO) CreateHTTPAccessLog(tx *dbs.Tx, dao *HTTPAccessLogDAO, accessLog *pb.HTTPAccessLog) error {
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 {
return err
}
@@ -248,7 +250,9 @@ func (this *HTTPAccessLogDAO) CreateHTTPAccessLog(tx *dbs.Tx, dao *HTTPAccessLog
}
// ListAccessLogs 读取往前的 单页访问日志
func (this *HTTPAccessLogDAO) ListAccessLogs(tx *dbs.Tx, lastRequestId string,
func (this *HTTPAccessLogDAO) ListAccessLogs(tx *dbs.Tx,
partition int32,
lastRequestId string,
size int64,
day string,
hourFrom string,
@@ -275,18 +279,19 @@ func (this *HTTPAccessLogDAO) ListAccessLogs(tx *dbs.Tx, lastRequestId string,
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 {
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
return
}
// 读取往前的单页访问日志
func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
partition int32,
lastRequestId string,
size int64,
day string,
@@ -309,7 +314,7 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
return nil, lastRequestId, nil
}
serverIds := []int64{}
var serverIds = []int64{}
if userId > 0 {
serverIds, err = SharedServerDAO.FindAllEnabledServerIdsWithUserId(tx, userId)
if err != nil {
@@ -321,7 +326,7 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
}
accessLogLocker.RLock()
daoList := []*HTTPAccessLogDAOWrapper{}
var daoList = []*HTTPAccessLogDAOWrapper{}
for _, daoWrapper := range httpAccessLogDAOMapping {
daoList = append(daoList, daoWrapper)
}
@@ -339,7 +344,7 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
if clusterId > 0 {
nodeIds, err = SharedNodeDAO.FindAllEnabledNodeIdsWithClusterId(tx, clusterId)
if err != nil {
remotelogs.Error("DBNODE", err.Error())
remotelogs.Error("DB_NODE", err.Error())
return
}
sort.Slice(nodeIds, func(i, j int) bool {
@@ -349,32 +354,56 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
// 准备查询
var tableQueries = []*accessLogTableQuery{}
var maxTableName = ""
for _, daoWrapper := range daoList {
var instance = daoWrapper.DAO.Instance
tableDefs, err := SharedHTTPAccessLogManager.FindTables(instance, day)
def, err := SharedHTTPAccessLogManager.FindPartitionTable(instance, day, partition)
if err != nil {
return nil, "", err
}
for _, def := range tableDefs {
tableQueries = append(tableQueries, &accessLogTableQuery{
daoWrapper: daoWrapper,
name: def.Name,
hasRemoteAddrField: def.HasRemoteAddr,
hasDomainField: def.HasDomain,
})
if !def.Exists {
continue
}
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 statusPrefixReg = regexp.MustCompile(`status:\s*(\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 wg = &sync.WaitGroup{}
wg.Add(count)
for _, tableQuery := range tableQueries {
go func(tableQuery *accessLogTableQuery) {
go func(tableQuery *accessLogTableQuery, keyword string) {
defer wg.Done()
var dao = tableQuery.daoWrapper.DAO
@@ -462,27 +491,41 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
}
if len(keyword) > 0 {
// remoteAddr
if tableQuery.hasRemoteAddrField && net.ParseIP(keyword) != nil {
var isSpecialKeyword = false
if tableQuery.hasRemoteAddrField && net.ParseIP(keyword) != nil { // ip
isSpecialKeyword = true
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:]
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])
} else {
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)
query.Between("status", types.Int(matches[1]), types.Int(matches[2]))
// TODO 处理剩余的关键词
} else if statusPrefixReg.MatchString(keyword) {
} else if statusPrefixReg.MatchString(keyword) { // status:200
isSpecialKeyword = true
var matches = statusPrefixReg.FindStringSubmatch(keyword)
query.Attr("status", matches[1])
// 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) {
keyword = keyword[3:]
}
@@ -530,7 +573,7 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
}
query.Where("("+where+")").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
if useOriginKeyword {
query.Param("originKeyword", keyword)
}
@@ -574,17 +617,17 @@ func (this *HTTPAccessLogDAO) listAccessLogs(tx *dbs.Tx,
Limit(size).
FindAll()
if err != nil {
logs.Println("[DB_NODE]" + err.Error())
remotelogs.Println("DB_NODE", err.Error())
return
}
locker.Lock()
for _, one := range ones {
accessLog := one.(*HTTPAccessLog)
var accessLog = one.(*HTTPAccessLog)
result = append(result, accessLog)
}
locker.Unlock()
}(tableQuery)
}(tableQuery, keyword)
}
wg.Wait()

View File

@@ -1,6 +1,7 @@
package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
@@ -53,7 +54,7 @@ func TestHTTPAccessLogDAO_ListAccessLogs(t *testing.T) {
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 {
t.Fatal(err)
}
@@ -80,7 +81,7 @@ func TestHTTPAccessLogDAO_ListAccessLogs_Page(t *testing.T) {
times := 0 // 防止循环次数太多
for {
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()
if err != nil {
t.Fatal(err)
@@ -111,7 +112,7 @@ func TestHTTPAccessLogDAO_ListAccessLogs_Reverse(t *testing.T) {
}
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()
if err != nil {
t.Fatal(err)
@@ -136,7 +137,7 @@ func TestHTTPAccessLogDAO_ListAccessLogs_Page_NotExists(t *testing.T) {
times := 0 // 防止循环次数太多
for {
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()
if err != nil {
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 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 + "%"} {
ones, columnNames, err := db.FindOnes(`SHOW TABLES LIKE '` + prefix + `'`)
ones, columnNames, err := db.FindPreparedOnes(`SHOW TABLES LIKE '` + prefix + `'`)
if err != nil {
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 tableNames = []string{}
config, err := db.Config()
if err != nil {
return nil, err
}
var cachePrefix = config.Dsn
// 需要防止用户设置了表名自动小写
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 {
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) {
tableNames = append(tableNames, tableName)
hasRemoteAddrField, hasDomainField, err := this.checkTableFields(db, tableName)
if err != nil {
return nil, err
}
// 查找已有的表格信息避免SHOW FIELDS
var tableDay = tableName[strings.LastIndex(tableName, "_")+1:]
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{
Name: tableName,
HasRemoteAddr: hasRemoteAddrField,
HasDomain: hasDomainField,
Exists: true,
})
results = append(results, &httpAccessLogDefinition{
Name: tableName,
HasRemoteAddr: hasRemoteAddrField,
HasDomain: hasDomainField,
Exists: true,
})
}
} else if accessLogTablePartialReg.MatchString(tableName) {
tableNames = append(tableNames, tableName)
@@ -128,11 +150,55 @@ func (this *HTTPAccessLogManager) FindTables(db *dbs.DB, day string) ([]*httpAcc
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_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()
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))
}
// 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) {
tableNames, err := this.FindTableNames(db, day)
@@ -296,7 +416,7 @@ func (this *HTTPAccessLogManager) findTableWithoutCache(db *dbs.DB, day string,
// TODO 考虑缓存检查结果
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 {
return false, false, err
}

View File

@@ -6,6 +6,7 @@ import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
"testing"
"time"
)
@@ -30,6 +31,9 @@ func TestNewHTTPAccessLogManager(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
var manager = models.SharedHTTPAccessLogManager
err = manager.CreateTable(db, "accessLog_1")
@@ -58,6 +62,9 @@ func TestHTTPAccessLogManager_FindTableNames(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
for i := 0; i < 3; i++ {
var before = time.Now()
@@ -74,7 +81,6 @@ func TestHTTPAccessLogManager_FindTableNames(t *testing.T) {
}
}
func TestHTTPAccessLogManager_FindTables(t *testing.T) {
var config = &dbs.DBConfig{
Driver: "mysql",
@@ -95,6 +101,9 @@ func TestHTTPAccessLogManager_FindTables(t *testing.T) {
if err != nil {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
for i := 0; i < 3; i++ {
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{
Driver: "mysql",
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 {
t.Fatal(err)
}
defer func() {
_ = db.Close()
}()
for i := 0; i < 3; i++ {
var before = time.Now()
tableDef, err := models.SharedHTTPAccessLogManager.FindTable(db, "20220306", false)
tableDef, err := models.SharedHTTPAccessLogManager.FindLastTable(db, "20220306", false)
if err != nil {
t.Fatal(err)
}
@@ -146,3 +158,32 @@ func TestHTTPAccessLogManager_FindTable(t *testing.T) {
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
import (
"bytes"
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/maps"
)
const (
@@ -109,7 +106,7 @@ func (this *HTTPAccessLogPolicyDAO) FindAllEnabledAndOnPolicies(tx *dbs.Tx) (res
}
// CreatePolicy 创建策略
func (this *HTTPAccessLogPolicyDAO) CreatePolicy(tx *dbs.Tx, name string, policyType string, optionsJSON []byte, condsJSON []byte, isPublic bool) (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()
op.Name = name
op.Type = policyType
@@ -121,12 +118,13 @@ func (this *HTTPAccessLogPolicyDAO) CreatePolicy(tx *dbs.Tx, name string, policy
}
op.IsPublic = isPublic
op.IsOn = true
op.FirewallOnly = firewallOnly
op.State = HTTPAccessLogPolicyStateEnabled
return this.SaveInt64(tx, op)
}
// UpdatePolicy 修改策略
func (this *HTTPAccessLogPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, name string, optionsJSON []byte, condsJSON []byte, isPublic bool, 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 {
return errors.New("invalid policyId")
}
@@ -140,7 +138,6 @@ func (this *HTTPAccessLogPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, nam
if oldOne == nil {
return nil
}
var oldPolicy = oldOne.(*HTTPAccessLogPolicy)
var op = NewHTTPAccessLogPolicyOperator()
op.Id = policyId
@@ -156,22 +153,11 @@ func (this *HTTPAccessLogPolicyDAO) UpdatePolicy(tx *dbs.Tx, policyId int64, nam
op.Conds = "{}"
}
// 版本号
if len(oldPolicy.Options) == 0 || len(optionsJSON) == 0 {
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")
}
}
// 版本号总是加1
op.Version = dbs.SQL("version+1")
op.IsPublic = isPublic
op.FirewallOnly = firewallOnly
op.IsOn = isOn
return this.Save(tx, op)
}

View File

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

View File

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

View File

@@ -2,6 +2,7 @@ package models
import (
"encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/firewallconfigs"
@@ -259,7 +260,18 @@ func (this *HTTPFirewallPolicyDAO) UpdateFirewallPolicyInbound(tx *dbs.Tx, polic
}
// 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 {
return errors.New("invalid policyId")
}
@@ -293,6 +305,16 @@ func (this *HTTPFirewallPolicyDAO) UpdateFirewallPolicy(tx *dbs.Tx, policyId int
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
err := this.Save(tx, op)
if err != nil {
@@ -311,7 +333,7 @@ func (this *HTTPFirewallPolicyDAO) CountAllEnabledFirewallPolicies(tx *dbs.Tx, c
}
if len(keyword) > 0 {
query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
return query.
State(HTTPFirewallPolicyStateEnabled).
@@ -330,7 +352,7 @@ func (this *HTTPFirewallPolicyDAO) ListEnabledFirewallPolicies(tx *dbs.Tx, clust
}
if len(keyword) > 0 {
query.Where("(name LIKE :keyword)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
_, err = query.
State(HTTPFirewallPolicyStateEnabled).
@@ -364,7 +386,7 @@ func (this *HTTPFirewallPolicyDAO) ComposeFirewallPolicy(tx *dbs.Tx, policyId in
return nil, nil
}
config := &firewallconfigs.HTTPFirewallPolicy{}
var config = &firewallconfigs.HTTPFirewallPolicy{}
config.Id = int64(policy.Id)
config.IsOn = policy.IsOn
config.Name = policy.Name
@@ -452,6 +474,18 @@ func (this *HTTPFirewallPolicyDAO) ComposeFirewallPolicy(tx *dbs.Tx, policyId in
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 {
cacheMap.Put(cacheKey, config)
}

View File

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

View File

@@ -77,8 +77,9 @@ func (this *HTTPPageDAO) FindEnabledHTTPPage(tx *dbs.Tx, id int64) (*HTTPPage, e
}
// 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.UserId = userId
op.IsOn = true
op.State = HTTPPageStateEnabled
@@ -182,6 +183,26 @@ func (this *HTTPPageDAO) ComposePageConfig(tx *dbs.Tx, pageId int64, cacheMap *u
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 通知更新
func (this *HTTPPageDAO) NotifyUpdate(tx *dbs.Tx, pageId int64) error {
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
}
config := &serverconfigs.HTTPWebConfig{}
var config = &serverconfigs.HTTPWebConfig{}
config.Id = webId
config.IsOn = web.IsOn

View File

@@ -1,6 +1,7 @@
package models
import (
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
@@ -264,13 +265,13 @@ func (this *IPItemDAO) UpdateIPItem(tx *dbs.Tx, itemId int64, ipFrom string, ipT
}
// 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).
State(IPItemStateEnabled).
Attr("listId", listId)
if len(keyword) > 0 {
query.Where("(ipFrom LIKE :keyword OR ipTo LIKE :keyword)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
if len(ipFrom) > 0 {
query.Attr("ipFrom", ipFrom)
@@ -278,17 +279,20 @@ func (this *IPItemDAO) CountIPItemsWithListId(tx *dbs.Tx, listId int64, ipFrom s
if len(ipTo) > 0 {
query.Attr("ipTo", ipTo)
}
if len(eventLevel) > 0 {
query.Attr("eventLevel", eventLevel)
}
return query.Count()
}
// 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).
State(IPItemStateEnabled).
Attr("listId", listId)
if len(keyword) > 0 {
query.Where("(ipFrom LIKE :keyword OR ipTo LIKE :keyword)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
if len(ipFrom) > 0 {
query.Attr("ipFrom", ipFrom)
@@ -296,6 +300,9 @@ func (this *IPItemDAO) ListIPItemsWithListId(tx *dbs.Tx, listId int64, keyword s
if len(ipTo) > 0 {
query.Attr("ipTo", ipTo)
}
if len(eventLevel) > 0 {
query.Attr("eventLevel", eventLevel)
}
_, err = query.
DescPk().
Slice(&result).
@@ -371,7 +378,7 @@ func (this *IPItemDAO) ExistsEnabledItem(tx *dbs.Tx, itemId int64) (bool, error)
}
// 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)
if len(ip) > 0 {
query.Attr("ipFrom", ip)
@@ -379,11 +386,20 @@ func (this *IPItemDAO) CountAllEnabledIPItems(tx *dbs.Tx, ip string, listId int6
if listId > 0 {
query.Attr("listId", listId)
} 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 {
query.Attr("isRead", 0)
}
if len(eventLevel) > 0 {
query.Attr("eventLevel", eventLevel)
}
return query.
State(IPItemStateEnabled).
Where("(expiredAt=0 OR expiredAt>:expiredAt)").
@@ -392,7 +408,7 @@ func (this *IPItemDAO) CountAllEnabledIPItems(tx *dbs.Tx, ip string, listId int6
}
// 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)
if len(ip) > 0 {
query.Attr("ipFrom", ip)
@@ -400,11 +416,19 @@ func (this *IPItemDAO) ListAllEnabledIPItems(tx *dbs.Tx, ip string, listId int64
if listId > 0 {
query.Attr("listId", listId)
} 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 {
query.Attr("isRead", 0)
}
if len(eventLevel) > 0 {
query.Attr("eventLevel", eventLevel)
}
_, err = query.
State(IPItemStateEnabled).
Where("(expiredAt=0 OR expiredAt>:expiredAt)").

View File

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

View File

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

View File

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

View File

@@ -12,6 +12,7 @@ import (
"github.com/iwind/TeaGo/types"
"sort"
"strings"
"sync"
)
const (
@@ -34,6 +35,9 @@ func NewMetricItemDAO() *MetricItemDAO {
var SharedMetricItemDAO *MetricItemDAO
var metricItemLastTimeCacheMap = map[int64]string{} // itemId => time
var metricItemLastTimeCacheLocker = &sync.Mutex{}
func init() {
dbs.OnReady(func() {
SharedMetricItemDAO = NewMetricItemDAO()
@@ -108,10 +112,10 @@ func (this *MetricItemDAO) FindMetricItemName(tx *dbs.Tx, id int64) (string, err
}
// 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)
op := NewMetricItemOperator()
var op = NewMetricItemOperator()
op.Code = code
op.Category = category
op.Name = name
@@ -126,6 +130,7 @@ func (this *MetricItemDAO) CreateItem(tx *dbs.Tx, code string, category string,
}
op.Period = period
op.PeriodUnit = periodUnit
op.ExpiresPeriod = expiresPeriod
op.Value = value
op.IsPublic = isPublic
op.IsOn = true
@@ -146,7 +151,7 @@ func (this *MetricItemDAO) CreateItem(tx *dbs.Tx, code string, category string,
}
// 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 {
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.Name = name
if len(keys) > 0 {
@@ -182,6 +187,7 @@ func (this *MetricItemDAO) UpdateItem(tx *dbs.Tx, itemId int64, name string, key
}
op.Period = period
op.PeriodUnit = periodUnit
op.ExpiresPeriod = expiresPeriod
op.Value = value
op.IsOn = isOn
if versionChanged {
@@ -282,14 +288,15 @@ func (this *MetricItemDAO) ComposeItemConfig(tx *dbs.Tx, itemId int64) (*serverc
}
var item = one.(*MetricItem)
var config = &serverconfigs.MetricItemConfig{
Id: int64(item.Id),
IsOn: item.IsOn,
Period: types.Int(item.Period),
PeriodUnit: item.PeriodUnit,
Category: item.Category,
Value: item.Value,
Keys: item.DecodeKeys(),
Version: types.Int32(item.Version),
Id: int64(item.Id),
IsOn: item.IsOn,
Period: types.Int(item.Period),
PeriodUnit: item.PeriodUnit,
ExpiresPeriod: types.Int(item.ExpiresPeriod),
Category: item.Category,
Value: item.Value,
Keys: item.DecodeKeys(),
Version: types.Int32(item.Version),
}
return config, nil
@@ -301,14 +308,15 @@ func (this *MetricItemDAO) ComposeItemConfigWithItem(item *MetricItem) *serverco
return nil
}
var config = &serverconfigs.MetricItemConfig{
Id: int64(item.Id),
IsOn: item.IsOn,
Period: types.Int(item.Period),
PeriodUnit: item.PeriodUnit,
Category: item.Category,
Value: item.Value,
Keys: item.DecodeKeys(),
Version: types.Int32(item.Version),
Id: int64(item.Id),
IsOn: item.IsOn,
Period: types.Int(item.Period),
PeriodUnit: item.PeriodUnit,
ExpiresPeriod: types.Int(item.ExpiresPeriod),
Category: item.Category,
Value: item.Value,
Keys: item.DecodeKeys(),
Version: types.Int32(item.Version),
}
return config
@@ -326,6 +334,32 @@ func (this *MetricItemDAO) FindItemVersion(tx *dbs.Tx, itemId int64) (int32, err
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 通知更新
func (this *MetricItemDAO) NotifyUpdate(tx *dbs.Tx, itemId int64, isPublic bool) error {
if isPublic {

View File

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

View File

@@ -3,16 +3,21 @@ package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"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/dbs"
"github.com/iwind/TeaGo/lists"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"sort"
"strconv"
"sync"
"sync/atomic"
"time"
)
@@ -26,13 +31,15 @@ func init() {
for range ticker.C {
err := SharedMetricStatDAO.Clean(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 {
return dbs.NewDAO(&MetricStatDAO{
DAOObject: dbs.DAOObject{
@@ -65,7 +72,8 @@ func (this *MetricStatDAO) CreateStat(tx *dbs.Tx, hash string, clusterId int64,
} else {
keysString = "[]"
}
return this.Query(tx).
err := this.Query(tx).
Table(this.partialTable(serverId)).
Param("value", value).
InsertOrUpdateQuickly(maps.Map{
"hash": hash,
@@ -81,57 +89,134 @@ func (this *MetricStatDAO) CreateStat(tx *dbs.Tx, hash string, clusterId int64,
}, maps.Map{
"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 删除以前版本的统计数据
func (this *MetricStatDAO) DeleteOldVersionItemStats(tx *dbs.Tx, itemId int64, version int32) error {
_, err := this.Query(tx).
Attr("itemId", itemId).
Where("version<:version").
Param("version", version).
Delete()
return err
return this.runBatch(func(table string, locker *sync.Mutex) error {
_, err := this.Query(tx).
Table(table).
Attr("itemId", itemId).
Where("version<:version").
Param("version", version).
Delete()
return err
})
}
// DeleteItemStats 删除某个指标相关的统计数据
func (this *MetricStatDAO) DeleteItemStats(tx *dbs.Tx, itemId int64) error {
_, err := this.Query(tx).
Attr("itemId", itemId).
Delete()
return err
return this.runBatch(func(table string, locker *sync.Mutex) error {
_, err := this.Query(tx).
Table(table).
Attr("itemId", itemId).
Delete()
return err
})
}
// DeleteNodeItemStats 删除某个节点的统计数据
func (this *MetricStatDAO) DeleteNodeItemStats(tx *dbs.Tx, nodeId int64, serverId int64, itemId int64, time string) error {
_, err := this.Query(tx).
Attr("nodeId", nodeId).
Attr("serverId", serverId).
Attr("itemId", itemId).
Attr("time", time).
Delete()
if serverId > 0 {
_, err := this.Query(tx).
Table(this.partialTable(serverId)).
Attr("nodeId", nodeId).
Attr("serverId", serverId).
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
}
// CountItemStats 计算统计数据数量
func (this *MetricStatDAO) CountItemStats(tx *dbs.Tx, itemId int64, version int32) (int64, error) {
return this.Query(tx).
Attr("itemId", itemId).
Attr("version", version).
Count()
var total int64 = 0
err := this.runBatch(func(table string, locker *sync.Mutex) error {
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 列出单页统计数据
func (this *MetricStatDAO) ListItemStats(tx *dbs.Tx, itemId int64, version int32, offset int64, size int64) (result []*MetricStat, err error) {
_, err = this.Query(tx).
Attr("itemId", itemId).
Attr("version", version).
Offset(offset).
Limit(size).
Desc("time").
Desc("serverId").
Desc("value").
Slice(&result).
FindAll()
err = this.runBatch(func(table string, locker *sync.Mutex) error {
var partialResult = []*MetricStat{}
_, err = this.Query(tx).
Table(table).
Attr("itemId", itemId).
Attr("version", version).
Offset(offset).
Limit(size).
Desc("time").
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
}
@@ -139,92 +224,127 @@ func (this *MetricStatDAO) ListItemStats(tx *dbs.Tx, itemId int64, version int32
// 适合每条数据中包含不同的Key的场景
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).
Attr("itemId", itemId).
Attr("version", version).
DescPk().
Find()
lastTime, err := SharedMetricItemDAO.FindMetricLastTime(tx, itemId)
if err != nil || len(lastTime) == 0 {
return nil, err
}
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 {
return nil, err
}
if statOne == nil {
return nil, nil
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)]
}
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
}
// FindItemStatsWithClusterIdAndLastTime 取得集群最近一次计时前 N 个数据
// 适合每条数据中包含不同的Key的场景
func (this *MetricStatDAO) FindItemStatsWithClusterIdAndLastTime(tx *dbs.Tx, clusterId int64, itemId int64, ignoreEmptyKeys bool, ignoreKeys []string, version int32, size int64) (result []*MetricStat, err error) {
// 最近一次时间
statOne, err := this.Query(tx).
Attr("itemId", itemId).
Attr("version", version).
DescPk().
Find()
lastTime, err := SharedMetricItemDAO.FindMetricLastTime(tx, itemId)
if err != nil || len(lastTime) == 0 {
return nil, err
}
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 {
return nil, err
}
if statOne == nil {
return nil, nil
}
var lastStat = statOne.(*MetricStat)
var lastTime = lastStat.Time
var query = this.Query(tx).
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(&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))
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
}
@@ -232,68 +352,80 @@ func (this *MetricStatDAO) FindItemStatsWithClusterIdAndLastTime(tx *dbs.Tx, clu
// 适合每条数据中包含不同的Key的场景
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).
Attr("itemId", itemId).
Attr("version", version).
DescPk().
Find()
lastTime, err := SharedMetricItemDAO.FindMetricLastTime(tx, itemId)
if err != nil {
return nil, err
}
if statOne == nil {
return nil, nil
}
var lastStat = statOne.(*MetricStat)
var lastTime = lastStat.Time
var query = this.Query(tx).
UseIndex("node_item_time").
Attr("nodeId", nodeId).
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
err = this.runBatch(func(table string, locker *sync.Mutex) error {
var partialResult = []*MetricStat{}
var query = this.Query(tx).
Table(table).
UseIndex("node_item_time").
Attr("nodeId", nodeId).
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`, '\"\"')")
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
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 {
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
}
// FindItemStatsWithServerIdAndLastTime 取得节点最近一次计时前 N 个数据
// FindItemStatsWithServerIdAndLastTime 取得服务最近一次计时前 N 个数据
// 适合每条数据中包含不同的Key的场景
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).
Attr("itemId", itemId).
Attr("version", version).
DescPk().
Find()
if err != nil {
lastTime, err := SharedMetricItemDAO.FindMetricLastTime(tx, itemId)
if err != nil || len(lastTime) == 0 {
return nil, err
}
if statOne == nil {
return nil, nil
}
var lastStat = statOne.(*MetricStat)
var lastTime = lastStat.Time
var query = this.Query(tx).
Table(this.partialTable(serverId)).
UseIndex("server_item_time").
Attr("serverId", serverId).
Attr("itemId", itemId).
@@ -326,68 +458,119 @@ func (this *MetricStatDAO) FindItemStatsWithServerIdAndLastTime(tx *dbs.Tx, serv
// FindLatestItemStats 取得所有集群上最近 N 个时间的数据
// 适合同个Key在不同时间段的变化场景
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).
Attr("itemId", itemId).
Attr("version", version).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`").
Desc("time").
Group("time").
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
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).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`").
Desc("time").
Group("time").
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))
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
}
_, err = query.
FindAll()
_, 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].Time > result[j].Time
})
if len(result) > types.Int(size) {
result = result[:types.Int(size)]
}
lists.Reverse(result)
return
}
// FindLatestItemStatsWithClusterId 取得集群最近 N 个时间的数据
// 适合同个Key在不同时间段的变化场景
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).
Attr("clusterId", clusterId).
Attr("itemId", itemId).
Attr("version", version).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`").
Desc("time").
Group("time").
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
err = this.runBatch(func(table string, locker *sync.Mutex) error {
var partialResult = []*MetricStat{}
var query = this.Query(tx).
Table(table).
Attr("clusterId", clusterId).
Attr("itemId", itemId).
Attr("version", version).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`").
Desc("time").
Group("time").
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))
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
}
_, err = query.
FindAll()
_, 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].Time > result[j].Time
})
if len(result) > types.Int(size) {
result = result[:types.Int(size)]
}
lists.Reverse(result)
return
}
@@ -395,34 +578,55 @@ func (this *MetricStatDAO) FindLatestItemStatsWithClusterId(tx *dbs.Tx, clusterI
// FindLatestItemStatsWithNodeId 取得节点最近 N 个时间的数据
// 适合同个Key在不同时间段的变化场景
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).
Attr("nodeId", nodeId).
Attr("itemId", itemId).
Attr("version", version).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`").
Desc("time").
Group("time").
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
err = this.runBatch(func(table string, locker *sync.Mutex) error {
var partialResult = []*MetricStat{}
var query = this.Query(tx).
Table(table).
Attr("nodeId", nodeId).
Attr("itemId", itemId).
Attr("version", version).
// TODO 增加更多聚合算法,比如 AVG、MEDIAN、MIN、MAX 等
// TODO 这里的 MIN(`keys`) 在MySQL8中可以换成FIRST_VALUE
Result("time", "SUM(value) AS value", "MIN(`keys`) AS `keys`").
Desc("time").
Group("time").
Limit(size).
Slice(&partialResult)
if ignoreEmptyKeys {
query.Where("NOT JSON_CONTAINS(`keys`, '\"\"')")
}
query.Where("NOT JSON_CONTAINS(:ignoredKeys, JSON_EXTRACT(`keys`, '$[0]'))") // TODO $[0] 需要换成keys中的primary key位置
query.Param("ignoredKeys", string(ignoreKeysJSON))
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
})
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)
return
}
@@ -431,6 +635,7 @@ func (this *MetricStatDAO) FindLatestItemStatsWithNodeId(tx *dbs.Tx, nodeId int6
// 适合同个Key在不同时间段的变化场景
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).
Table(this.partialTable(serverId)).
Attr("serverId", serverId).
Attr("itemId", itemId).
Attr("version", version).
@@ -474,17 +679,22 @@ func (this *MetricStatDAO) Clean(tx *dbs.Tx) error {
}
for _, item := range items {
var config = &serverconfigs.MetricItemConfig{
Id: int64(item.Id),
Period: int(item.Period),
PeriodUnit: item.PeriodUnit,
Id: int64(item.Id),
Period: int(item.Period),
PeriodUnit: item.PeriodUnit,
ExpiresPeriod: int(item.ExpiresPeriod),
}
var expiresDay = config.ServerExpiresDay()
_, err := this.Query(tx).
Attr("itemId", item.Id).
Lte("createdDay", expiresDay).
UseIndex("createdDay").
Limit(100_000). // 一次性不要删除太多,防止阻塞其他操作
Delete()
err := this.runBatch(func(table string, locker *sync.Mutex) error {
_, err := this.Query(tx).
Table(table).
Attr("itemId", item.Id).
Lte("createdDay", expiresDay).
UseIndex("createdDay").
Limit(10_000). // 一次性不要删除太多,防止阻塞其他操作
Delete()
return err
})
if err != nil {
return err
}
@@ -499,3 +709,45 @@ func (this *MetricStatDAO) Clean(tx *dbs.Tx) error {
}
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 (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
@@ -8,11 +9,12 @@ import (
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"testing"
"time"
)
func TestNewMetricStatDAO_InsertMany(t *testing.T) {
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 {
t.Fatal(err)
}
@@ -23,12 +25,38 @@ func TestNewMetricStatDAO_InsertMany(t *testing.T) {
t.Log("done")
}
func TestMetricStatDAO_Clean(t *testing.T) {
func TestMetricStatDAO_Clean2(t *testing.T) {
dbs.NotifyReady()
err := NewMetricStatDAO().Clean(nil)
err := models.NewMetricStatDAO().Clean(nil)
if err != nil {
t.Fatal(err)
}
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 (
"github.com/TeaOSLab/EdgeAPI/internal/goman"
"github.com/TeaOSLab/EdgeAPI/internal/remotelogs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"sync"
"time"
)
type MetricSumStatDAO dbs.DAO
const MetricSumStatTablePartials = 20 // 表格Partial数量
func init() {
dbs.OnReadyDone(func() {
// 清理数据任务
@@ -23,7 +27,7 @@ func init() {
for range ticker.C {
err := SharedMetricSumStatDAO.Clean(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 更新统计数据
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).
Table(this.partialTable(serverId)).
InsertOrUpdateQuickly(maps.Map{
"clusterId": clusterId,
"nodeId": nodeId,
@@ -71,6 +76,7 @@ func (this *MetricSumStatDAO) UpdateSum(tx *dbs.Tx, clusterId int64, nodeId int6
// FindNodeServerSum 查找某个服务在某个节点上的统计数据
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).
Table(this.partialTable(serverId)).
Attr("nodeId", nodeId).
Attr("serverId", serverId).
Attr("time", time).
@@ -83,29 +89,42 @@ func (this *MetricSumStatDAO) FindNodeServerSum(tx *dbs.Tx, nodeId int64, server
if one == nil {
return
}
return int64(one.(*MetricSumStat).Count), float32(one.(*MetricSumStat).Total), nil
count = int64(one.(*MetricSumStat).Count)
total = float32(one.(*MetricSumStat).Total)
return
}
// FindSumAtTime 查找某个时间的统计数据
func (this *MetricSumStatDAO) FindSumAtTime(tx *dbs.Tx, time string, itemId int64, version int32) (count int64, total float32, err error) {
one, err := this.Query(tx).
Attr("time", time).
Attr("itemId", itemId).
Attr("version", version).
Result("SUM(count) AS `count`, SUM(total) AS total").
Find()
if err != nil {
return 0, 0, err
}
if one == nil {
return
}
return int64(one.(*MetricSumStat).Count), float32(one.(*MetricSumStat).Total), nil
err = this.runBatch(func(table string, locker *sync.Mutex) error {
one, err := this.Query(tx).
Table(table).
Attr("time", time).
Attr("itemId", itemId).
Attr("version", version).
Result("SUM(count) AS `count`, SUM(total) AS total").
Find()
if err != nil {
return err
}
if one == nil {
return nil
}
locker.Lock()
count += int64(one.(*MetricSumStat).Count)
total += float32(one.(*MetricSumStat).Total)
locker.Unlock()
return nil
})
return
}
// FindServerSum 查找某个服务的统计数据
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).
Table(this.partialTable(serverId)).
UseIndex("server_item_time").
Attr("serverId", serverId).
Attr("time", time).
@@ -124,48 +143,69 @@ func (this *MetricSumStatDAO) FindServerSum(tx *dbs.Tx, serverId int64, time str
// FindClusterSum 查找集群上的统计数据
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).
UseIndex("cluster_item_time").
Attr("clusterId", clusterId).
Attr("time", time).
Attr("itemId", itemId).
Attr("version", version).
Result("SUM(count) AS `count`, SUM(total) AS total").
Find()
if err != nil {
return 0, 0, err
}
if one == nil {
return
}
return int64(one.(*MetricSumStat).Count), float32(one.(*MetricSumStat).Total), nil
err = this.runBatch(func(table string, locker *sync.Mutex) error {
one, err := this.Query(tx).
Table(table).
UseIndex("cluster_item_time").
Attr("clusterId", clusterId).
Attr("time", time).
Attr("itemId", itemId).
Attr("version", version).
Result("SUM(count) AS `count`, SUM(total) AS total").
Find()
if err != nil {
return err
}
if one == nil {
return nil
}
locker.Lock()
count += int64(one.(*MetricSumStat).Count)
total += float32(one.(*MetricSumStat).Total)
locker.Unlock()
return nil
})
return
}
// FindNodeSum 查找节点上的统计数据
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).
UseIndex("node_item_time").
Attr("nodeId", nodeId).
Attr("time", time).
Attr("itemId", itemId).
Attr("version", version).
Result("SUM(count) AS `count`, SUM(total) AS total").
Find()
if err != nil {
return 0, 0, err
}
if one == nil {
return
}
return int64(one.(*MetricSumStat).Count), float32(one.(*MetricSumStat).Total), nil
err = this.runBatch(func(table string, locker *sync.Mutex) error {
one, err := this.Query(tx).
Table(table).
UseIndex("node_item_time").
Attr("nodeId", nodeId).
Attr("time", time).
Attr("itemId", itemId).
Attr("version", version).
Result("SUM(count) AS `count`, SUM(total) AS total").
Find()
if err != nil {
return err
}
if one == nil {
return nil
}
locker.Lock()
count += int64(one.(*MetricSumStat).Count)
total += float32(one.(*MetricSumStat).Total)
locker.Unlock()
return nil
})
return
}
// DeleteItemStats 删除某个指标相关的统计数据
func (this *MetricSumStatDAO) DeleteItemStats(tx *dbs.Tx, itemId int64) error {
_, err := this.Query(tx).
Attr("itemId", itemId).
Delete()
return err
return this.runBatch(func(table string, locker *sync.Mutex) error {
_, err := this.Query(tx).
Table(table).
Attr("itemId", itemId).
Delete()
return err
})
}
// Clean 清理数据
@@ -180,18 +220,23 @@ func (this *MetricSumStatDAO) Clean(tx *dbs.Tx) error {
}
for _, item := range items {
var config = &serverconfigs.MetricItemConfig{
Id: int64(item.Id),
Period: int(item.Period),
PeriodUnit: item.PeriodUnit,
Id: int64(item.Id),
Period: int(item.Period),
PeriodUnit: item.PeriodUnit,
ExpiresPeriod: int(item.ExpiresPeriod),
}
var expiresDay = config.ServerExpiresDay()
_, err := this.Query(tx).
Attr("itemId", item.Id).
Where("(createdDay IS NULL OR createdDay<:day)").
Param("day", expiresDay).
UseIndex("createdDay").
Limit(100_000). // 一次性不要删除太多,防止阻塞其他操作
Delete()
err = this.runBatch(func(table string, locker *sync.Mutex) error {
_, err := this.Query(tx).
Table(table).
Attr("itemId", item.Id).
Where("(createdDay IS NULL OR createdDay<:day)").
Param("day", expiresDay).
UseIndex("createdDay").
Limit(10_000). // 一次性不要删除太多,防止阻塞其他操作
Delete()
return err
})
if err != nil {
return err
}
@@ -206,3 +251,29 @@ func (this *MetricSumStatDAO) Clean(tx *dbs.Tx) error {
}
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 (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
timeutil "github.com/iwind/TeaGo/utils/time"
"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) {
dbs.NotifyReady()
err := NewMetricSumStatDAO().Clean(nil)
err := models.NewMetricSumStatDAO().Clean(nil)
if err != nil {
t.Fatal(err)
}

View File

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

View File

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

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"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/EdgeCommon/pkg/dnsconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
@@ -96,6 +97,7 @@ func (this *NodeClusterDAO) FindAllEnableClusters(tx *dbs.Tx) (result []*NodeClu
_, err = this.Query(tx).
State(NodeClusterStateEnabled).
Slice(&result).
Desc("isPinned").
Desc("order").
DescPk().
FindAll()
@@ -221,7 +223,7 @@ func (this *NodeClusterDAO) CountAllEnabledClusters(tx *dbs.Tx, keyword string)
State(NodeClusterStateEnabled)
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)))").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
return query.Count()
}
@@ -232,7 +234,7 @@ func (this *NodeClusterDAO) ListEnabledClusters(tx *dbs.Tx, keyword string, offs
State(NodeClusterStateEnabled)
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)))").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
_, err = query.
Offset(offset).
@@ -469,7 +471,29 @@ func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsNam
if clusterId <= 0 {
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.DnsName = dnsName
op.DnsDomainId = dnsDomainId
@@ -478,7 +502,7 @@ func (this *NodeClusterDAO) UpdateClusterDNS(tx *dbs.Tx, clusterId int64, dnsNam
cnameRecords = []string{}
}
dnsConfig := &dnsconfigs.ClusterDNSConfig{
var dnsConfig = &dnsconfigs.ClusterDNSConfig{
NodesAutoSync: nodesAutoSync,
ServersAutoSync: serversAutoSync,
CNameRecords: cnameRecords,
@@ -897,7 +921,8 @@ func (this *NodeClusterDAO) FindClusterBasicInfo(tx *dbs.Tx, clusterId int64, ca
cluster, err := this.Query(tx).
Pk(clusterId).
Result("timeZone", "nodeMaxThreads", "nodeTCPMaxConnections", "cachePolicyId", "httpFirewallPolicyId", "autoOpenPorts").
State(NodeClusterStateEnabled).
Result("timeZone", "nodeMaxThreads", "nodeTCPMaxConnections", "cachePolicyId", "httpFirewallPolicyId", "autoOpenPorts", "webp", "isOn").
Find()
if err != nil || cluster == nil {
return nil, err
@@ -908,6 +933,65 @@ func (this *NodeClusterDAO) FindClusterBasicInfo(tx *dbs.Tx, clusterId int64, ca
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 通知更新
func (this *NodeClusterDAO) NotifyUpdate(tx *dbs.Tx, clusterId int64) error {
return SharedNodeTaskDAO.CreateClusterTask(tx, nodeconfigs.NodeRoleNode, clusterId, 0, NodeTaskTypeConfigChanged)

View File

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

View File

@@ -1,12 +1,16 @@
package models
import (
"crypto/sha256"
"encoding/json"
"fmt"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"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/utils"
"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/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
@@ -19,8 +23,10 @@ import (
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"strconv"
"strings"
"time"
)
const (
@@ -136,6 +142,7 @@ func (this *NodeDAO) CreateNode(tx *dbs.Tx, adminId int64, name string, clusterI
if teaconst.MaxNodes > 0 {
count, err := this.Query(tx).
State(NodeStateEnabled).
Where("clusterId IN (SELECT id FROM " + SharedNodeClusterDAO.Table + " WHERE state=1)").
Count()
if err != nil {
return 0, err
@@ -190,7 +197,7 @@ func (this *NodeDAO) CreateNode(tx *dbs.Tx, adminId int64, name string, clusterI
}
// 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 {
return errors.New("invalid nodeId")
}
@@ -201,7 +208,16 @@ func (this *NodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, clusterId
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.Name = name
op.ClusterId = clusterId
@@ -227,6 +243,11 @@ func (this *NodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, clusterId
op.RegionId = regionId
op.LatestVersion = dbs.SQL("latestVersion+1")
op.IsOn = isOn
if teaconst.IsPlus {
op.Level = level
}
err = this.Save(tx, op)
if err != nil {
return err
@@ -237,7 +258,7 @@ func (this *NodeDAO) UpdateNode(tx *dbs.Tx, nodeId int64, name string, clusterId
return err
}
// 通知老的集群更新
// 通知老的集群DNS更新
for _, oldClusterId := range oldClusterIds {
if oldClusterId != clusterId && !lists.ContainsInt64(secondaryClusterIds, oldClusterId) {
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)
}
@@ -277,11 +306,13 @@ func (this *NodeDAO) ListEnabledNodesMatch(tx *dbs.Tx,
keyword string,
groupId int64,
regionId int64,
level int32,
includeSecondaryNodes bool,
order string,
offset int64,
size int64) (result []*Node, err error) {
query := this.Query(tx).
Result(this.Table + ".*"). // must have table name for table joins below
State(NodeStateEnabled).
Offset(offset).
Limit(size).
@@ -290,14 +321,14 @@ func (this *NodeDAO) ListEnabledNodesMatch(tx *dbs.Tx,
// 集群
if clusterId > 0 {
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("primaryClusterIdString", types.String(clusterId))
} else {
query.Attr("clusterId", clusterId)
query.Attr(this.Table+".clusterId", clusterId)
}
} 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 {
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)
}
// 级别
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 {
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":
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":
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":
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":
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":
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":
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":
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()
_, err = query.FindAll()
@@ -399,6 +489,37 @@ func (this *NodeDAO) FindNodeClusterId(tx *dbs.Tx, nodeId int64) (int64, error)
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
func (this *NodeDAO) FindEnabledAndOnNodeClusterIds(tx *dbs.Tx, nodeId int64) (result []int64, err error) {
one, err := this.Query(tx).
@@ -473,6 +594,41 @@ func (this *NodeDAO) FindEnabledNodeIdsWithClusterId(tx *dbs.Tx, clusterId int64
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
func (this *NodeDAO) FindAllNodeIdsMatch(tx *dbs.Tx, clusterId int64, includeSecondaryNodes bool, isOn configutils.BoolState) (result []int64, err error) {
var query = this.Query(tx)
@@ -554,6 +710,7 @@ func (this *NodeDAO) CountAllEnabledNodesMatch(tx *dbs.Tx,
keyword string,
groupId int64,
regionId int64,
level int32,
includeSecondaryNodes bool) (int64, error) {
query := this.Query(tx)
query.State(NodeStateEnabled)
@@ -594,7 +751,7 @@ func (this *NodeDAO) CountAllEnabledNodesMatch(tx *dbs.Tx,
// 关键词
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))").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
// 分组
@@ -607,6 +764,11 @@ func (this *NodeDAO) CountAllEnabledNodesMatch(tx *dbs.Tx,
query.Attr("regionId", regionId)
}
// 级别
if level > 0 {
query.Attr("level", level)
}
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) + "'")
}
config := &nodeconfigs.NodeConfig{
if node.Level < 1 {
node.Level = 1
}
var config = &nodeconfigs.NodeConfig{
Id: int64(node.Id),
NodeId: node.UniqueId,
Secret: node.Secret,
@@ -742,6 +908,8 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, cacheMap *utils
Name: node.Name,
MaxCPU: types.Int32(node.MaxCPU),
RegionId: int64(node.RegionId),
Level: types.Int32(node.Level),
GroupId: int64(node.GroupId),
}
// API节点IP
@@ -800,12 +968,13 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, cacheMap *utils
var clusterIds = []int64{primaryClusterId}
clusterIds = append(clusterIds, node.DecodeSecondaryClusterIds()...)
var clusterIndex = 0
config.WebPImagePolicies = map[int64]*nodeconfigs.WebPImagePolicy{}
for _, clusterId := range clusterIds {
nodeCluster, err := SharedNodeClusterDAO.FindClusterBasicInfo(tx, clusterId, cacheMap)
if err != nil {
return nil, err
}
if nodeCluster == nil {
if nodeCluster == nil || !nodeCluster.IsOn {
continue
}
@@ -847,6 +1016,16 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, cacheMap *utils
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++
}
@@ -955,6 +1134,12 @@ func (this *NodeDAO) ComposeNodeConfig(tx *dbs.Tx, nodeId int64, cacheMap *utils
}
config.OCSPVersion = ocspVersion
// 初始化扩展配置
err = this.composeExtConfig(tx, config, clusterIds, cacheMap)
if err != nil {
return nil, err
}
return config, nil
}
@@ -1491,19 +1676,99 @@ func (this *NodeDAO) TransferPrimaryClusterNodes(tx *dbs.Tx, primaryClusterId in
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 {
// 这里只需要通知单个集群即可,因为节点是公用的,更新一个就相当于更新了所有
clusterId, err := this.FindNodeClusterId(tx, nodeId)
if err != nil {
return err
}
if clusterId > 0 {
return SharedNodeTaskDAO.CreateNodeTask(tx, nodeconfigs.NodeRoleNode, clusterId, nodeId, 0, NodeTaskTypeConfigChanged, 0)
err = 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
}
// NotifyDNSUpdate 通知DNS更新
// NotifyDNSUpdate 通知节点相关DNS更新
func (this *NodeDAO) NotifyDNSUpdate(tx *dbs.Tx, nodeId int64) error {
clusterIds, err := this.FindEnabledAndOnNodeClusterIds(tx, nodeId)
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 (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"testing"
"time"
)
func TestNodeDAO_FindAllNodeIdsMatch(t *testing.T) {
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 {
t.Fatal(err)
}
@@ -20,7 +27,7 @@ func TestNodeDAO_FindAllNodeIdsMatch(t *testing.T) {
func TestNodeDAO_UpdateNodeUp(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
err := SharedNodeDAO.UpdateNodeUp(tx, 57, false)
err := models.SharedNodeDAO.UpdateNodeUp(tx, 57, false)
if err != nil {
t.Fatal(err)
}
@@ -30,7 +37,7 @@ func TestNodeDAO_UpdateNodeUp(t *testing.T) {
func TestNodeDAO_FindEnabledNodeClusterIds(t *testing.T) {
dbs.NotifyReady()
var tx *dbs.Tx
clusterIds, err := NewNodeDAO().FindEnabledAndOnNodeClusterIds(tx, 48)
clusterIds, err := models.NewNodeDAO().FindEnabledAndOnNodeClusterIds(tx, 48)
if err != nil {
t.Fatal(err)
}
@@ -47,7 +54,7 @@ func TestNodeDAO_ComposeNodeConfig(t *testing.T) {
var tx *dbs.Tx
var cacheMap = utils.NewCacheMap()
nodeConfig, err := SharedNodeDAO.ComposeNodeConfig(tx, 48, cacheMap)
nodeConfig, err := models.SharedNodeDAO.ComposeNodeConfig(tx, 48, cacheMap)
if err != nil {
t.Fatal(err)
}
@@ -56,3 +63,17 @@ func TestNodeDAO_ComposeNodeConfig(t *testing.T) {
// 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 (
"errors"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
_ "github.com/go-sql-driver/mysql"
"github.com/iwind/TeaGo/Tea"
"github.com/iwind/TeaGo/dbs"
@@ -129,7 +130,7 @@ func (this *NodeGrantDAO) CountAllEnabledGrants(tx *dbs.Tx, keyword string) (int
State(NodeGrantStateEnabled)
if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR username LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
return query.Count()
}
@@ -140,7 +141,7 @@ func (this *NodeGrantDAO) ListEnabledGrants(tx *dbs.Tx, keyword string, offset i
State(NodeGrantStateEnabled)
if len(keyword) > 0 {
query.Where("(name LIKE :keyword OR username LIKE :keyword OR description LIKE :keyword)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
_, err = query.
Offset(offset).

View File

@@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"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/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
@@ -124,7 +125,7 @@ func (this *NodeIPAddressDAO) CreateAddress(tx *dbs.Tx, adminId int64, nodeId in
role = nodeconfigs.NodeRoleNode
}
op := NewNodeIPAddressOperator()
var op = NewNodeIPAddressOperator()
op.NodeId = nodeId
op.Role = role
op.Name = name
@@ -233,17 +234,20 @@ func (this *NodeIPAddressDAO) FindAllEnabledAddressesWithNode(tx *dbs.Tx, nodeId
}
// 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 {
role = nodeconfigs.NodeRoleNode
}
one, err := this.Query(tx).
var query = this.Query(tx).
Attr("nodeId", nodeId).
Attr("role", role).
State(NodeIPAddressStateEnabled).
Attr("canAccess", true).
Attr("isOn", true).
Attr("isUp", true).
Attr("isOn", true)
if mustUp {
query.Attr("isUp", true)
}
one, err := query.
Desc("order").
AscPk().
Result("id", "ip").
@@ -260,17 +264,20 @@ func (this *NodeIPAddressDAO) FindFirstNodeAccessIPAddress(tx *dbs.Tx, nodeId in
}
// 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 {
role = nodeconfigs.NodeRoleNode
}
return this.Query(tx).
var query = this.Query(tx).
Attr("nodeId", nodeId).
Attr("role", role).
State(NodeIPAddressStateEnabled).
Attr("canAccess", true).
Attr("isOn", true).
Attr("isUp", true).
Attr("isOn", true)
if mustUp {
query.Attr("isUp", true)
}
return query.
Desc("order").
AscPk().
Result("id").
@@ -323,7 +330,7 @@ func (this *NodeIPAddressDAO) CountAllEnabledIPAddresses(tx *dbs.Tx, role string
// 关键词
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))").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
return query.Count()
@@ -355,7 +362,7 @@ func (this *NodeIPAddressDAO) ListEnabledIPAddresses(tx *dbs.Tx, role string, no
// 关键词
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))").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
_, err = query.Offset(offset).
@@ -455,13 +462,13 @@ func (this *NodeIPAddressDAO) UpdateAddressBackupIP(tx *dbs.Tx, addressId int64,
}
// 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 {
return false, errors.New("invalid address id")
}
one, err := this.Query(tx).
Pk(addrId).
Result("isHealthy", "countUp", "countDown").
Result("isHealthy", "isUp", "countUp", "countDown").
Find()
if err != nil {
return false, err
@@ -469,26 +476,57 @@ func (this *NodeIPAddressDAO) UpdateAddressHealthCount(tx *dbs.Tx, addrId int64,
if one == 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
}
countUp := int(one.(*NodeIPAddress).CountUp)
countDown := int(one.(*NodeIPAddress).CountDown)
var countUp = int(one.(*NodeIPAddress).CountUp)
var countDown = int(one.(*NodeIPAddress).CountDown)
op := NewNodeIPAddressOperator()
var op = NewNodeIPAddressOperator()
op.Id = addrId
if isUp {
if newIsUp {
countUp++
countDown = 0
if countUp >= maxUp {
changed = true
//op.IsUp = true
if autoUpDown {
op.IsUp = true
}
op.IsHealthy = true
}
} else {
@@ -497,7 +535,9 @@ func (this *NodeIPAddressDAO) UpdateAddressHealthCount(tx *dbs.Tx, addrId int64,
if countDown >= maxDown {
changed = true
//op.IsUp = false
if autoUpDown {
op.IsUp = false
}
op.IsHealthy = false
}
}
@@ -514,6 +554,15 @@ func (this *NodeIPAddressDAO) UpdateAddressHealthCount(tx *dbs.Tx, addrId int64,
if err != nil {
return true, err
}
// 创建日志
if autoUpDown {
if newIsUp {
err = SharedNodeIPAddressLogDAO.CreateLog(tx, 0, addrId, "健康检查上线")
} else {
err = SharedNodeIPAddressLogDAO.CreateLog(tx, 0, addrId, "健康检查下线")
}
}
}
return
@@ -539,9 +588,21 @@ func (this *NodeIPAddressDAO) NotifyUpdate(tx *dbs.Tx, addressId int64) error {
switch role {
case nodeconfigs.NodeRoleNode:
err = dns.SharedDNSTaskDAO.CreateNodeTask(tx, nodeId, dns.DNSTaskTypeNodeChange)
}
if err != nil {
return err
if err != nil {
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
}

View File

@@ -56,7 +56,7 @@ func TestNodeIPAddressDAO_UpdateAddressHealthCount(t *testing.T) {
dbs.NotifyReady()
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 {
t.Fatal(err)
}

View File

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

View File

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

View File

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

View File

@@ -96,3 +96,15 @@ func (this *Node) DecodeSecondaryClusterIds() []int64 {
_ = json.Unmarshal(this.SecondaryClusterIds, &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"
NodeTaskTypeIPItemChanged NodeTaskType = "ipItemChanged"
NodeTaskTypeNodeVersionChanged NodeTaskType = "nodeVersionChanged"
NodeTaskTypeScriptsChanged NodeTaskType = "scriptsChanged"
NodeTaskTypeNodeLevelChanged NodeTaskType = "nodeLevelChanged"
// NS相关

View File

@@ -1,6 +1,7 @@
package models
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
@@ -9,6 +10,7 @@ import (
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"strings"
"time"
)
@@ -35,9 +37,9 @@ func init() {
// CreateValue 创建值
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)
hour := timeutil.FormatTime("YmdH", createdAt)
minute := timeutil.FormatTime("YmdHi", createdAt)
var day = timeutil.FormatTime("Ymd", createdAt)
var hour = timeutil.FormatTime("YmdH", createdAt)
var minute = timeutil.FormatTime("YmdHi", createdAt)
return this.Query(tx).
InsertOrUpdateQuickly(maps.Map{
@@ -120,6 +122,17 @@ func (this *NodeValueDAO) ListValuesWithClusterId(tx *dbs.Tx, role string, clust
_, err = query.Slice(&result).
FindAll()
if err != nil {
return nil, err
}
for _, nodeValue := range result {
nodeValue.Value, _ = json.Marshal(maps.Map{
key: types.Float32(string(nodeValue.Value)),
})
}
return
}
@@ -144,6 +157,17 @@ func (this *NodeValueDAO) ListValuesForUserNodes(tx *dbs.Tx, item string, key st
_, err = query.Slice(&result).
FindAll()
if err != nil {
return nil, err
}
for _, nodeValue := range result {
nodeValue.Value, _ = json.Marshal(maps.Map{
key: types.Float32(string(nodeValue.Value)),
})
}
return
}
@@ -168,9 +192,48 @@ func (this *NodeValueDAO) ListValuesForNSNodes(tx *dbs.Tx, item string, key stri
_, err = query.Slice(&result).
FindAll()
if err != nil {
return nil, err
}
for _, nodeValue := range result {
nodeValue.Value, _ = json.Marshal(maps.Map{
key: types.Float32(string(nodeValue.Value)),
})
}
return
}
// SumAllNodeValues 计算所有节点的某项参数值
func (this *NodeValueDAO) SumAllNodeValues(tx *dbs.Tx, role string, item nodeconfigs.NodeValueItem, param string, duration int32, durationUnit nodeconfigs.NodeValueDurationUnit) (total float64, avg float64, max float64, err error) {
if duration <= 0 {
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 计算节点的某项参数值
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 {
@@ -261,11 +324,13 @@ func (this *NodeValueDAO) SumNodeClusterValues(tx *dbs.Tx, role string, clusterI
// FindLatestNodeValue 获取最近一条数据
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).
Attr("role", role).
Attr("nodeId", nodeId).
Attr("item", item).
DescPk().
Attr("minute", fromMinute).
Find()
if err != nil {
return nil, err
@@ -275,3 +340,62 @@ func (this *NodeValueDAO) FindLatestNodeValue(tx *dbs.Tx, role string, nodeId in
}
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 (
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
_ "github.com/go-sql-driver/mysql"
_ "github.com/iwind/TeaGo/bootstrap"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/rands"
"github.com/iwind/TeaGo/types"
"testing"
"time"
)
func TestNodeValueDAO_CreateValue(t *testing.T) {
var dao = NewNodeValueDAO()
var dao = models.NewNodeValueDAO()
m := maps.Map{
"hello": "world12344",
}
@@ -22,10 +27,55 @@ func TestNodeValueDAO_CreateValue(t *testing.T) {
}
func TestNodeValueDAO_Clean(t *testing.T) {
var dao = NewNodeValueDAO()
var dao = models.NewNodeValueDAO()
err := dao.Clean(nil)
if err != nil {
t.Fatal(err)
}
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 (
"encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
_ "github.com/go-sql-driver/mysql"
@@ -198,7 +199,7 @@ func (this *NSAccessLogDAO) listAccessLogs(tx *dbs.Tx, lastRequestId string, siz
// keyword
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)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
if !reverse {

View File

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

View File

@@ -2,6 +2,7 @@ package models
import (
"encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
@@ -169,7 +170,7 @@ func (this *ReportNodeDAO) CountAllEnabledReportNodes(tx *dbs.Tx, groupId int64,
}
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.Param("keyword", "%"+keyword+"%")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
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(isp)=0 AND JSON_EXTRACT(status, '$.isp') LIKE :keyword)
))`)
query.Param("keyword", "%"+keyword+"%")
query.Param("keyword", dbutils.QuoteLike(keyword))
}
query.Slice(&result)
_, 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
}
// 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 根据服务计算某月合计
// month 格式为YYYYMM
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
}
// 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 获取某月内的流量
// month 格式为YYYYMM
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 (
"encoding/json"
"errors"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"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/numberutils"
"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
@@ -158,15 +160,27 @@ func (this *ServerDAO) CreateServer(tx *dbs.Tx,
excludeNodesJSON string,
groupIds []int64,
userPlanId int64) (serverId int64, err error) {
op := NewServerOperator()
var op = NewServerOperator()
op.UserId = userId
op.AdminId = adminId
op.Name = name
op.Type = serverType
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
plainServerNamesJSON, err := json.Marshal(serverconfigs.PlainServerNames(serverNames))
if err != nil {
return 0, err
}
op.PlainServerNames = plainServerNamesJSON
}
op.IsAuditing = isAuditing
op.AuditingAt = time.Now().Unix()
@@ -256,11 +270,21 @@ func (this *ServerDAO) CreateServer(tx *dbs.Tx,
}
// 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 {
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.Name = name
op.Description = description
@@ -277,7 +301,7 @@ func (this *ServerDAO) UpdateServerBasic(tx *dbs.Tx, serverId int64, name string
op.GroupIds = groupIdsJSON
}
err := this.Save(tx, op)
err = this.Save(tx, op)
if err != nil {
return err
}
@@ -289,7 +313,29 @@ func (this *ServerDAO) UpdateServerBasic(tx *dbs.Tx, serverId int64, name string
}
// 因为可能有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 设置用户相关的基本信息
@@ -554,7 +600,7 @@ func (this *ServerDAO) FindServerServerNames(tx *dbs.Tx, serverId int64) (server
}
// 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 {
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.Id = serverId
if len(serverNames) == 0 {
serverNames = []byte("[]")
if IsNull(serverNamesJSON) {
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)
if err != nil {
return err
@@ -585,7 +644,7 @@ func (this *ServerDAO) UpdateAuditingServerNames(tx *dbs.Tx, serverId int64, isA
if isAuditing {
op.AuditingAt = time.Now().Unix()
}
if len(auditingServerNamesJSON) == 0 {
if IsNull(auditingServerNamesJSON) {
op.AuditingServerNames = "[]"
} else {
op.AuditingServerNames = auditingServerNamesJSON
@@ -613,12 +672,35 @@ func (this *ServerDAO) UpdateServerAuditing(tx *dbs.Tx, serverId int64, result *
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.IsAuditing = false
op.AuditingResult = resultJSON
if result.IsOk {
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)
if err != nil {
@@ -657,21 +739,25 @@ func (this *ServerDAO) CountAllEnabledServers(tx *dbs.Tx) (int64, error) {
}
// 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) {
query := this.Query(tx).
State(ServerStateEnabled)
if groupId > 0 {
query.Where("JSON_CONTAINS(groupIds, :groupId)").
Param("groupId", numberutils.FormatInt64(groupId))
} else if groupId < 0 { // 特殊的groupId
query.Where("JSON_LENGTH(groupIds)=0")
}
if len(keyword) > 0 {
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'))").
Param("portRange", maps.Map{"portRange": keyword}.AsJSON()).
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
} else {
query.Where("(name LIKE :keyword OR serverNames LIKE :keyword)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
}
if userId > 0 {
@@ -703,26 +789,29 @@ func (this *ServerDAO) CountAllEnabledServersMatch(tx *dbs.Tx, groupId int64, ke
}
// 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).
State(ServerStateEnabled).
Offset(offset).
Limit(size).
DescPk().
Slice(&result)
if groupId > 0 {
query.Where("JSON_CONTAINS(groupIds, :groupId)").
Param("groupId", numberutils.FormatInt64(groupId))
} else if groupId < 0 { // 特殊的groupId
query.Where("JSON_LENGTH(groupIds)=0")
}
if len(keyword) > 0 {
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'))").
Param("portRange", string(maps.Map{"portRange": keyword}.AsJSON())).
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
} else {
query.Where("(name LIKE :keyword OR serverNames LIKE :keyword)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
}
if userId > 0 {
@@ -749,7 +838,52 @@ func (this *ServerDAO) ListEnabledServersMatch(tx *dbs.Tx, offset int64, size in
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()
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
}
@@ -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 {
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
}
// 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 {
// 创建任务
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)
}
// 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 {
clusterId, err := this.Query(tx).
Pk(serverId).
@@ -2310,7 +2493,22 @@ func (this *ServerDAO) NotifyDNSUpdate(tx *dbs.Tx, serverId int64) error {
if len(dnsInfo.DnsName) == 0 || dnsInfo.DnsDomainId <= 0 {
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 通知禁用

View File

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

View File

@@ -4,6 +4,7 @@ import (
"bytes"
"encoding/json"
"errors"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/sslconfigs"
_ "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 {
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 {
query.Attr("userId", userId)
@@ -311,7 +312,7 @@ func (this *SSLCertDAO) ListCertIds(tx *dbs.Tx, isCA bool, isAvailable bool, isE
}
if len(keyword) > 0 {
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 {
query.Attr("userId", userId)
@@ -514,7 +515,7 @@ func (this *SSLCertDAO) CountAllSSLCertsWithOCSPError(tx *dbs.Tx, keyword string
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)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
return query.
@@ -530,7 +531,7 @@ func (this *SSLCertDAO) ListSSLCertsWithOCSPError(tx *dbs.Tx, keyword string, of
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)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
_, err = query.

View File

@@ -334,7 +334,7 @@ func (this *ServerDomainHourlyStatDAO) FindTopDomainStatsWithServerId(tx *dbs.Tx
Table(table).
Attr("serverId", serverId).
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").
Group("domain").
Desc("countRequests").

View File

@@ -33,12 +33,13 @@ func TestServerDomainHourlyStatDAO_FindAllPartitionTables(t *testing.T) {
t.Log(dao.FindAllPartitionTables())
}
func TestServerDomainHourlyStatDAO_IncreaseHourlyStat(t *testing.T) {
func TestServerDomainHourlyStatDAO_InsertManyHourlyStat(t *testing.T) {
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'))})
if i % 30 > 0 {
if i%30 > 0 {
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
}
// 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 清理历史数据
func (this *TrafficDailyStatDAO) Clean(tx *dbs.Tx, days int) error {
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
}
// 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 清理历史数据
func (this *TrafficHourlyStatDAO) Clean(tx *dbs.Tx, days int) error {
var hour = timeutil.Format("Ymd00", time.Now().AddDate(0, 0, -days))

View File

@@ -2,6 +2,7 @@ package models
import (
"encoding/json"
dbutils "github.com/TeaOSLab/EdgeAPI/internal/db/utils"
"github.com/TeaOSLab/EdgeAPI/internal/errors"
"github.com/TeaOSLab/EdgeAPI/internal/utils"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
@@ -222,7 +223,7 @@ func (this *UserDAO) CountAllEnabledUsers(tx *dbs.Tx, clusterId int64, keyword s
}
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)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
if isVerifying {
query.Attr("isVerified", 0)
@@ -247,7 +248,7 @@ func (this *UserDAO) ListEnabledUsers(tx *dbs.Tx, clusterId int64, keyword strin
}
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)").
Param("keyword", "%"+keyword+"%")
Param("keyword", dbutils.QuoteLike(keyword))
}
if isVerifying {
query.Attr("isVerified", 0)

View File

@@ -30,6 +30,17 @@ func IsNotNull(data []byte) bool {
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
func NewQuery(tx *dbs.Tx, dao dbs.DAOWrapper, adminId int64, userId int64) *dbs.Query {
query := dao.Object().Query(tx)

View File

@@ -2,24 +2,9 @@ package dbutils
import (
"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
func NewQuery(tx *dbs.Tx, dao dbs.DAOWrapper, adminId int64, userId int64) *dbs.Query {
query := dao.Object().Query(tx)
@@ -31,3 +16,22 @@ func NewQuery(tx *dbs.Tx, dao dbs.DAOWrapper, adminId int64, userId int64) *dbs.
}
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) + "'")
}
err = json.Unmarshal(data, respPtr)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
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
}

View File

@@ -1,6 +1,7 @@
package dnsclients
import (
"crypto/tls"
"encoding/json"
"errors"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
@@ -11,16 +12,28 @@ import (
"net/http"
"net/url"
"strings"
"time"
)
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服务商
type DNSPodProvider struct {
BaseProvider
region string
apiId string
apiToken string
}
@@ -29,6 +42,7 @@ type DNSPodProvider struct {
func (this *DNSPodProvider) Auth(params maps.Map) error {
this.apiId = params.GetString("id")
this.apiToken = params.GetString("token")
this.region = params.GetString("region")
if len(this.apiId) == 0 {
return errors.New("'id' should be not empty")
@@ -43,8 +57,9 @@ func (this *DNSPodProvider) Auth(params maps.Map) error {
func (this *DNSPodProvider) GetDomains() (domains []string, err error) {
offset := 0
size := 100
for {
domainsResp, err := this.post("/Domain.list", map[string]string{
domainsResp, err := this.post("/Domain.List", map[string]string{
"offset": numberutils.FormatInt(offset),
"length": numberutils.FormatInt(size),
})
@@ -78,7 +93,7 @@ func (this *DNSPodProvider) GetRecords(domain string) (records []*dnstypes.Recor
offset := 0
size := 100
for {
recordsResp, err := this.post("/Record.list", map[string]string{
recordsResp, err := this.post("/Record.List", map[string]string{
"domain": domain,
"offset": numberutils.FormatInt(offset),
"length": numberutils.FormatInt(size),
@@ -114,7 +129,7 @@ func (this *DNSPodProvider) GetRecords(domain string) (records []*dnstypes.Recor
// GetRoutes 读取线路数据
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,
})
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) {
apiHost := "https://dnsapi.cn"
query := url.Values{
var apiHost = "https://dnsapi.cn"
var lang = "cn"
if this.isInternational() { // 国际版
apiHost = "https://api.dnspod.com"
lang = "en"
}
var query = url.Values{
"login_token": []string{this.apiId + "," + this.apiToken},
"format": []string{"json"},
"lang": []string{"cn"},
"lang": []string{lang},
}
for p, v := range params {
query[p] = []string{v}
}
req, err := http.NewRequest(http.MethodPost, apiHost+path, strings.NewReader(query.Encode()))
if err != nil {
return nil, err
}
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 := client.Do(req)
resp, err := dnsPodHTTPClient.Do(req)
if err != nil {
return nil, err
}
defer func() {
_ = resp.Body.Close()
client.CloseIdleConnections()
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
m := maps.Map{}
var m = maps.Map{}
err = json.Unmarshal(body, &m)
if err != nil {
return nil, err
}
status := m.GetMap("status")
code := status.GetString("code")
var status = m.GetMap("status")
var code = status.GetString("code")
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
@@ -275,5 +296,12 @@ func (this *DNSPodProvider) post(path string, params map[string]string) (maps.Ma
// DefaultRoute 默认线路
func (this *DNSPodProvider) DefaultRoute() string {
if this.isInternational() {
return "Default"
}
return "默认"
}
func (this *DNSPodProvider) isInternational() bool {
return this.region == DNSPodInternational
}

View File

@@ -1,7 +1,8 @@
package dnsclients
package dnsclients_test
import (
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/iwind/TeaGo/dbs"
"github.com/iwind/TeaGo/logs"
@@ -9,8 +10,10 @@ import (
"testing"
)
const DNSPodTestDomain = "yun4s.cn"
func TestDNSPodProvider_GetDomains(t *testing.T) {
provider, err := testDNSPodProvider()
provider, _, err := testDNSPodProvider()
if err != nil {
t.Fatal(err)
}
@@ -22,11 +25,11 @@ func TestDNSPodProvider_GetDomains(t *testing.T) {
}
func TestDNSPodProvider_GetRoutes(t *testing.T) {
provider, err := testDNSPodProvider()
provider, _, err := testDNSPodProvider()
if err != nil {
t.Fatal(err)
}
routes, err := provider.GetRoutes("yun4s.cn")
routes, err := provider.GetRoutes(DNSPodTestDomain)
if err != nil {
t.Fatal(err)
}
@@ -34,11 +37,11 @@ func TestDNSPodProvider_GetRoutes(t *testing.T) {
}
func TestDNSPodProvider_GetRecords(t *testing.T) {
provider, err := testDNSPodProvider()
provider, _, err := testDNSPodProvider()
if err != nil {
t.Fatal(err)
}
records, err := provider.GetRecords("yun4s.cn")
records, err := provider.GetRecords(DNSPodTestDomain)
if err != nil {
t.Fatal(err)
}
@@ -48,17 +51,22 @@ func TestDNSPodProvider_GetRecords(t *testing.T) {
}
func TestDNSPodProvider_AddRecord(t *testing.T) {
provider, err := testDNSPodProvider()
provider, isInternational, err := testDNSPodProvider()
if err != nil {
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,
Name: "hello-forward",
Value: "hello.yun4s.cn",
Route: "联通",
TTL: 300,
Value: "hello." + DNSPodTestDomain,
Route: route,
TTL: 600,
})
if err != nil {
t.Fatal(err)
@@ -67,18 +75,25 @@ func TestDNSPodProvider_AddRecord(t *testing.T) {
}
func TestDNSPodProvider_UpdateRecord(t *testing.T) {
provider, err := testDNSPodProvider()
provider, isInternational, err := testDNSPodProvider()
if err != nil {
t.Fatal(err)
}
err = provider.UpdateRecord("yun4s.cn", &dnstypes.Record{
Id: "697036856",
var route = "联通"
var id = "1093875360"
if isInternational {
route = "Default"
id = "28507333"
}
err = provider.UpdateRecord(DNSPodTestDomain, &dnstypes.Record{
Id: id,
}, &dnstypes.Record{
Type: dnstypes.RecordTypeA,
Name: "hello",
Value: "192.168.1.102",
Route: "联通",
Route: route,
})
if err != nil {
t.Fatal(err)
@@ -87,13 +102,17 @@ func TestDNSPodProvider_UpdateRecord(t *testing.T) {
}
func TestDNSPodProvider_DeleteRecord(t *testing.T) {
provider, err := testDNSPodProvider()
provider, isInternational, err := testDNSPodProvider()
if err != nil {
t.Fatal(err)
}
err = provider.DeleteRecord("yun4s.cn", &dnstypes.Record{
Id: "697040986",
var id = "1093875360"
if isInternational {
id = "28507333"
}
err = provider.DeleteRecord(DNSPodTestDomain, &dnstypes.Record{
Id: id,
})
if err != nil {
t.Fatal(err)
@@ -101,24 +120,24 @@ func TestDNSPodProvider_DeleteRecord(t *testing.T) {
t.Log("ok")
}
func testDNSPodProvider() (ProviderInterface, error) {
func testDNSPodProvider() (provider dnsclients.ProviderInterface, isInternational bool, err error) {
db, err := dbs.Default()
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 {
return nil, err
return nil, false, err
}
apiParams := maps.Map{}
err = json.Unmarshal([]byte(one.GetString("apiParams")), &apiParams)
if err != nil {
return nil, err
return nil, false, err
}
provider := &DNSPodProvider{}
provider = &dnsclients.DNSPodProvider{}
err = provider.Auth(apiParams)
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
import (
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/iwind/TeaGo/maps"
)
@@ -49,21 +48,7 @@ func FindAllProviderTypes() []maps.Map {
},
}
if teaconst.IsPlus {
typeMaps = append(typeMaps, []maps.Map{
{
"name": "EdgeDNS",
"code": ProviderTypeLocalEdgeDNS,
"description": "GoEdge商业版提供的智能DNS服务。",
},
// TODO 需要实现用户使用AccessId/AccessKey来连接DNS服务
/**{
"name": "用户EdgeDNS",
"code": ProviderTypeUserEdgeDNS,
"description": "通过API连接企业版提供的DNS服务。",
},**/
}...)
}
typeMaps = filterTypeMaps(typeMaps)
typeMaps = append(typeMaps, maps.Map{
"name": "自定义HTTP DNS",
@@ -91,7 +76,8 @@ func FindProvider(providerType ProviderType) ProviderInterface {
case ProviderTypeCustomHTTP:
return &CustomHTTPProvider{}
}
return nil
return filterProvider(providerType)
}
// 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 {
return nil, errors.New("not found")
return nil, nil
}
dataLen := (dataPtr >> 24) & 0xFF

View File

@@ -284,6 +284,9 @@ func (this *APINode) autoUpgrade() error {
if err != nil {
return errors.New("load database failed: " + err.Error())
}
defer func() {
_ = db.Close()
}()
one, err := db.FindOne("SELECT version FROM edgeVersions LIMIT 1")
if err != nil {
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())
return
}
var ports = []int{}
isListening = false
if httpConfig != nil && httpConfig.IsOn && len(httpConfig.Listen) > 0 {
for _, listen := range httpConfig.Listen {
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)
if err != nil {
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 _, 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)
if err != nil {
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 {
for _, listen := range restHTTPConfig.Listen {
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)
if err != nil {
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 {
for _, listen := range restHTTPSConfig.Listen {
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)
if err != nil {
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
}
@@ -582,6 +619,22 @@ func (this *APINode) listenSock() error {
_ = cmd.Reply(&gosock.Command{
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 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)
}
{
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)
// TODO check service names

View File

@@ -4,13 +4,11 @@ package nameservers
import (
"context"
"encoding/json"
"github.com/TeaOSLab/EdgeAPI/internal/db/models"
"github.com/TeaOSLab/EdgeAPI/internal/db/models/nameservers"
"github.com/TeaOSLab/EdgeAPI/internal/rpc/services"
"github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs"
"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
"github.com/iwind/TeaGo/types"
timeutil "github.com/iwind/TeaGo/utils/time"
"time"
)
@@ -144,12 +142,8 @@ func (this *NSService) ComposeNSBoard(ctx context.Context, req *pb.ComposeNSBoar
return nil, err
}
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{
ValueJSON: valueJSON,
ValueJSON: v.Value,
CreatedAt: int64(v.CreatedAt),
})
}
@@ -159,27 +153,19 @@ func (this *NSService) ComposeNSBoard(ctx context.Context, req *pb.ComposeNSBoar
return nil, err
}
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{
ValueJSON: valueJSON,
ValueJSON: v.Value,
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 {
return nil, err
}
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{
ValueJSON: valueJSON,
ValueJSON: v.Value,
CreatedAt: int64(v.CreatedAt),
})
}

View File

@@ -39,7 +39,7 @@ func (this *APINodeService) UpdateAPINode(ctx context.Context, req *pb.UpdateAPI
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 {
return nil, err
}
@@ -97,6 +97,7 @@ func (this *APINodeService) FindAllEnabledAPINodes(ctx context.Context, req *pb.
HttpsJSON: node.Https,
AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs,
IsPrimary: node.IsPrimary,
})
}
@@ -174,6 +175,7 @@ func (this *APINodeService) ListEnabledAPINodes(ctx context.Context, req *pb.Lis
AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs,
StatusJSON: node.Status,
IsPrimary: node.IsPrimary,
})
}
@@ -218,6 +220,7 @@ func (this *APINodeService) FindEnabledAPINode(ctx context.Context, req *pb.Find
RestHTTPSJSON: node.RestHTTPS,
AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs,
IsPrimary: node.IsPrimary,
}
return &pb.FindEnabledAPINodeResponse{ApiNode: result}, nil
}
@@ -270,6 +273,7 @@ func (this *APINodeService) FindCurrentAPINode(ctx context.Context, req *pb.Find
AccessAddrsJSON: node.AccessAddrs,
AccessAddrs: accessAddrs,
StatusJSON: nil,
IsPrimary: node.IsPrimary,
}}, nil
}

View File

@@ -25,7 +25,7 @@ func (this *DBService) FindAllDBTables(ctx context.Context, req *pb.FindAllDBTab
if err != nil {
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 {
return nil, err
}
@@ -37,7 +37,7 @@ func (this *DBService) FindAllDBTables(ctx context.Context, req *pb.FindAllDBTab
if strings.HasPrefix(lowerTableName, "edgehttpaccesslogs_") {
canDelete = 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
}

View File

@@ -199,7 +199,7 @@ func (this *DBNodeService) FindAllDBNodeTables(ctx context.Context, req *pb.Find
_ = 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 {
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_") {
canDelete = 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
}

View File

@@ -11,7 +11,7 @@ func TestDBService_FindAllDBTables(t *testing.T) {
if err != nil {
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 {
t.Fatal(err)
}

View File

@@ -59,7 +59,7 @@ func (this *DNSProviderService) CountAllEnabledDNSProviders(ctx context.Context,
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 {
return nil, err
}
@@ -78,7 +78,7 @@ func (this *DNSProviderService) ListEnabledDNSProviders(ctx context.Context, req
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 {
return nil, err
}

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