Просмотр исходного кода

Merge branch 'bzq1/monitor_custom_cf' of eta_server/eta_api into debug

鲍自强 6 месяцев назад
Родитель
Сommit
af8bdc7421

+ 358 - 0
controllers/edb_monitor/edb_monitor.go

@@ -0,0 +1,358 @@
+package edb_monitor
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/edb_monitor/request"
+	edbmonitor "eta/eta_api/services/edb_monitor"
+	"strings"
+)
+
+type EdbMonitorController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 预警管理列表
+// @Description 预警管理列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyId   query   string  true       "分类ID,多选用逗号分隔"
+// @Param   Level   query   string  true       "预警等级,多选用逗号分隔"
+// @Param   UserId   query   string  true       "创建人ID,多选用逗号分隔"
+// @Success 200 {object} response.EdbMonitorInfoListResp
+// @router /list [get]
+func (m *EdbMonitorController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		m.Data["json"] = br
+		m.ServeJSON()
+	}()
+	sysUser := m.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	pageSize, _ := m.GetInt("PageSize")
+	currentIndex, _ := m.GetInt("CurrentIndex")
+	classifyId := m.GetString("ClassifyId")
+	level := m.GetString("Level")
+	state := m.GetString("State")
+	userId := m.GetString("UserId")
+
+	resp, msg, err := edbmonitor.GetMonitorList(classifyId, level, state, userId, pageSize, currentIndex)
+	if err != nil {
+		if msg == "" {
+			msg = "获取列表失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Data = resp
+	br.Msg = "获取列表成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// Add
+// @Title 预警管理添加
+// @Description 预警管理添加
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /add [post]
+func (m *EdbMonitorController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		m.Data["json"] = br
+		m.ServeJSON()
+	}()
+	sysUser := m.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.EdbMonitorInfoSaveReq
+	if err := json.Unmarshal(m.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	switch req.MonitorType {
+	case edbmonitor.EDB_MONITOR_TYPE_DOWN, edbmonitor.EDB_MONITOR_TYPE_UP:
+	default:
+		br.Msg = "请选择预警等级"
+		return
+	}
+	req.MonitorData = strings.TrimSpace(req.MonitorData)
+	if req.MonitorData == "" {
+		br.Msg = "请输入预警值"
+		return
+	}
+	req.EdbMonitorName = strings.TrimSpace(req.EdbMonitorName)
+	if req.EdbMonitorName == "" {
+		br.Msg = "请输入预警名称"
+		return
+	}
+
+	msg, err := edbmonitor.SaveEdbMonitorInfo(req, sysUser.AdminId)
+	if err != nil {
+		if msg == "" {
+			msg = "添加失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Msg = "添加成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// edit
+// @Title 预警管理编辑
+// @Description 预警管理编辑
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /edit [post]
+func (m *EdbMonitorController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		m.Data["json"] = br
+		m.ServeJSON()
+	}()
+	sysUser := m.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.EdbMonitorInfoSaveReq
+	if err := json.Unmarshal(m.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+	if req.EdbMonitorId <= 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	switch req.MonitorType {
+	case edbmonitor.EDB_MONITOR_TYPE_DOWN, edbmonitor.EDB_MONITOR_TYPE_UP:
+	default:
+		br.Msg = "请选择预警等级"
+		return
+	}
+	req.MonitorData = strings.TrimSpace(req.MonitorData)
+	if req.MonitorData == "" {
+		br.Msg = "请输入预警值"
+		return
+	}
+	req.EdbMonitorName = strings.TrimSpace(req.EdbMonitorName)
+	if req.EdbMonitorName == "" {
+		br.Msg = "请输入预警名称"
+		return
+	}
+
+	msg, err := edbmonitor.SaveEdbMonitorInfo(req, sysUser.AdminId)
+	if err != nil {
+		if msg == "" {
+			msg = "编辑失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Msg = "编辑成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// delete
+// @Title 预警管理编辑
+// @Description 预警管理编辑
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /delete [post]
+func (m *EdbMonitorController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		m.Data["json"] = br
+		m.ServeJSON()
+	}()
+	sysUser := m.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.EdbMonitorInfoDeleteReq
+	if err := json.Unmarshal(m.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+	if req.EdbMonitorId <= 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+
+	msg, err := edbmonitor.DeleteEdbMonitorInfo(req)
+	if err != nil {
+		if msg == "" {
+			msg = "删除失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Msg = "删除成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// Close
+// @Title 预警管理关闭
+// @Description 预警管理关闭
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /close [post]
+func (m *EdbMonitorController) Close() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		m.Data["json"] = br
+		m.ServeJSON()
+	}()
+	sysUser := m.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.EdbMonitorInfoCloseReq
+	if err := json.Unmarshal(m.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+	if req.EdbMonitorId <= 0 {
+		br.Msg = "请选择预警"
+		return
+	}
+
+	msg, err := edbmonitor.CloseEdbMonitorInfo(req, sysUser.AdminId)
+	if err != nil {
+		if msg == "" {
+			msg = "关闭失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Msg = "关闭成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// Restart
+// @Title 预警管理重启
+// @Description 预警管理重启
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /restart [post]
+func (m *EdbMonitorController) Restart() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		m.Data["json"] = br
+		m.ServeJSON()
+	}()
+	sysUser := m.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req request.EdbMonitorInfoRestartReq
+	if err := json.Unmarshal(m.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+	if req.EdbMonitorId <= 0 {
+		br.Msg = "请选择预警"
+		return
+	}
+
+	msg, err := edbmonitor.RestartEdbMonitorInfo(req, sysUser.AdminId)
+	if err != nil {
+		if msg == "" {
+			msg = "重启失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Msg = "重启成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// GetMonitorLevel
+// @Title 预警管理等级列表
+// @Description 预警管理等级列表
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /monitor_level/list [get]
+func (m *EdbMonitorController) GetMonitorLevel() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		m.Data["json"] = br
+		m.ServeJSON()
+	}()
+	sysUser := m.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	list, msg, err := edbmonitor.GetEdbMonitorLevelList()
+	if err != nil {
+		if msg == "" {
+			msg = "获取预警等级失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Data = list
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}

+ 240 - 0
controllers/edb_monitor/edb_monitor_classify.go

@@ -0,0 +1,240 @@
+package edb_monitor
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/edb_monitor/request"
+	edbmonitor "eta/eta_api/services/edb_monitor"
+	"strings"
+)
+
+type EdbMonitorClassifyController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 预警管理分类列表
+// @Description 预警管理分类列表
+// @Success 200 {object} response.EdbMonitorClassifyTree
+// @router /classify/list [get]
+func (c *EdbMonitorClassifyController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	resp, msg, err := edbmonitor.GetEdbMonitorClassifyTree()
+	if err != nil {
+		if msg == "" {
+			msg = "获取分类列表失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Data = resp
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// Add
+// @Title 预警管理分类添加
+// @Description 预警管理分类添加
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /classify/add [post]
+func (c *EdbMonitorClassifyController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.EdbMonitorClassifySaveReq
+	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "分类名称不能为空"
+		return
+	}
+	if req.Level <= 0 || (req.ParentId > 0 && req.RootId <= 0) || req.ClassifyId > 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	msg, err := edbmonitor.SaveEdbMonitorClassify(req)
+	if err != nil {
+		if msg == "" {
+			msg = "分类添加失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Msg = "分类添加成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// Edit
+// @Title 预警管理分类编辑
+// @Description 预警管理分类编辑
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @router /classify/edit [post]
+func (c *EdbMonitorClassifyController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.EdbMonitorClassifySaveReq
+	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "分类不存在,请刷新页面"
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "分类名称不能为空"
+		return
+	}
+	if req.Level <= 0 || (req.ParentId > 0 && req.RootId <= 0) {
+		br.Msg = "参数错误"
+		return
+	}
+
+	msg, err := edbmonitor.SaveEdbMonitorClassify(req)
+	if err != nil {
+		if msg == "" {
+			msg = "分类添加失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Msg = "分类编辑成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// Delete
+// @Title 预警管理分类删除
+// @Description 预警管理分类删除
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @router /classify/delete [post]
+func (c *EdbMonitorClassifyController) Delete() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	var req request.EdbMonitorClassifyDeleteReq
+	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "分类不存在,请刷新页面"
+		return
+	}
+
+	msg, err := edbmonitor.DeleteEdbMonitorClassify(req)
+	if err != nil {
+		if msg == "" {
+			msg = "分类删除失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Msg = "分类删除成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// Move
+// @Title 预警分类移动接口
+// @Description 预警分类移动接口
+// @Param   request body request.MoveEdbMonitorClassifyReq  true  "每页数据条数"
+// @router /classify/move [post]
+func (c *EdbMonitorClassifyController) Move() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	var req request.MoveEdbMonitorClassifyReq
+	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "分类id小于等于0"
+		return
+	}
+	msg, err := edbmonitor.MoveEdbMonitorClassify(req)
+	if err != nil {
+		if msg == "" {
+			msg = "分类移动失败"
+		}
+		br.Msg = msg
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Msg = "分类移动成功"
+	br.Ret = 200
+	br.Success = true
+}

+ 19 - 6
go.mod

@@ -21,11 +21,13 @@ require (
 	github.com/dengsgo/math-engine v0.0.0-20230823154425-78f211b48149
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/go-ldap/ldap v3.0.3+incompatible
-	github.com/go-redis/redis/v8 v8.11.5
+	github.com/go-mysql-org/go-mysql v1.9.1
+	github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc
 	github.com/go-sql-driver/mysql v1.8.1
 	github.com/go-xorm/xorm v0.7.9
 	github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b
-	github.com/gorilla/websocket v1.5.3
+	github.com/google/uuid v1.6.0
+	github.com/gorilla/websocket v1.5.1
 	github.com/h2non/filetype v1.1.3
 	github.com/jung-kurt/gofpdf v1.16.2
 	github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53
@@ -35,6 +37,7 @@ require (
 	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
 	github.com/olivere/elastic/v7 v7.0.32
 	github.com/pdfcpu/pdfcpu v0.8.0
+	github.com/pingcap/errors v0.11.5-0.20221009092201-b66cddb77c32
 	github.com/qiniu/qmgo v1.1.8
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.4.0
@@ -52,6 +55,8 @@ require (
 
 require (
 	filippo.io/edwards25519 v1.1.0 // indirect
+	github.com/BurntSushi/toml v1.3.2 // indirect
+	github.com/Masterminds/semver v1.5.0 // indirect
 	github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4 // indirect
 	github.com/alibabacloud-go/debug v1.0.0 // indirect
 	github.com/alibabacloud-go/endpoint-util v1.1.0 // indirect
@@ -69,6 +74,7 @@ require (
 	github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
 	github.com/clbanning/mxj/v2 v2.5.5 // indirect
+	github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 // indirect
 	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
 	github.com/dustin/go-humanize v1.0.1 // indirect
 	github.com/fatih/structs v1.1.0 // indirect
@@ -86,7 +92,6 @@ require (
 	github.com/gonum/internal v0.0.0-20181124074243-f884aa714029 // indirect
 	github.com/gonum/lapack v0.0.0-20181123203213-e4cdc5a0bff9 // indirect
 	github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 // indirect
-	github.com/google/uuid v1.6.0 // indirect
 	github.com/gorilla/css v1.0.1 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
@@ -109,11 +114,15 @@ require (
 	github.com/montanaflynn/stats v0.7.1 // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
 	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
+	github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c // indirect
+	github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22 // indirect
+	github.com/pingcap/tidb/pkg/parser v0.0.0-20231103042308-035ad5ccbe67 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
 	github.com/prometheus/client_golang v1.19.0 // indirect
 	github.com/prometheus/client_model v0.5.0 // indirect
 	github.com/prometheus/common v0.48.0 // indirect
 	github.com/prometheus/procfs v0.12.0 // indirect
+	github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
 	github.com/richardlehane/mscfb v1.0.4 // indirect
 	github.com/richardlehane/msoleps v1.0.3 // indirect
 	github.com/rivo/uniseg v0.4.7 // indirect
@@ -121,6 +130,8 @@ require (
 	github.com/sagikazarmark/locafero v0.4.0 // indirect
 	github.com/sagikazarmark/slog-shim v0.1.0 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
+	github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 // indirect
+	github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/sourcegraph/conc v0.3.0 // indirect
 	github.com/spf13/afero v1.11.0 // indirect
@@ -138,10 +149,11 @@ require (
 	github.com/xuri/efp v0.0.0-20231025114914-d1ff6096ae53 // indirect
 	github.com/xuri/nfp v0.0.0-20230919160717-d98342af3f05 // indirect
 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
-	go.uber.org/atomic v1.9.0 // indirect
-	go.uber.org/multierr v1.9.0 // indirect
+	go.uber.org/atomic v1.11.0 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	go.uber.org/zap v1.26.0 // indirect
 	golang.org/x/crypto v0.25.0 // indirect
-	golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
+	golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
 	golang.org/x/image v0.15.0 // indirect
 	golang.org/x/sync v0.7.0 // indirect
 	golang.org/x/sys v0.22.0 // indirect
@@ -151,6 +163,7 @@ require (
 	gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
 	gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
+	gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 	xorm.io/builder v0.3.6 // indirect

+ 56 - 9
go.sum

@@ -9,9 +9,13 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
 filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
 filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
+github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo=
 github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww=
+github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y=
 github.com/PuerkitoBio/goquery v1.9.2 h1:4/wZksC3KgkQw7SQgkKotmKljk0M6V8TUvA8Wb4yPeE=
 github.com/PuerkitoBio/goquery v1.9.2/go.mod h1:GHPCaP0ODyyxqcNoFGYlAprUFH81NuRPd0GX3Zu2Mvk=
 github.com/SebastiaanKlippert/go-wkhtmltopdf v1.9.3 h1:vrA6+R1BMLKMTbos8jAeuBrImHPGtY4gTlcue3OIej8=
@@ -102,6 +106,7 @@ github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkY
 github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
 github.com/beevik/etree v1.4.1 h1:PmQJDDYahBGNKDcpdX8uPy1xRCwoCGVUiW669MEirVI=
 github.com/beevik/etree v1.4.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -128,6 +133,8 @@ github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:sr
 github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
 github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
+github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548 h1:iwZdTE0PVqJCos1vaoKsclOGD3ADKpshg3SRtYBbwso=
+github.com/cznic/mathutil v0.0.0-20181122101859-297441e03548/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -172,6 +179,8 @@ github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHj
 github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-mysql-org/go-mysql v1.9.1 h1:W2ZKkHkoM4mmkasJCoSYfaE4RQNxXTb6VqiaMpKFrJc=
+github.com/go-mysql-org/go-mysql v1.9.1/go.mod h1:+SgFgTlqjqOQoMc98n9oyUWEgn2KkOL1VmXDoq2ONOs=
 github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A=
 github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
 github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
@@ -181,8 +190,9 @@ github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+
 github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
 github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
 github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
-github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
 github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
+github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc h1:jZY+lpZB92nvBo2f31oPC/ivGll6NcsnEOORm8Fkr4M=
+github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc/go.mod h1:25mL1NKxbJhB63ihiK8MnNeTRd+xAizd6bOdydrTLUQ=
 github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -259,8 +269,8 @@ github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51
 github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8=
 github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0=
 github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
-github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
-github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
+github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY=
 github.com/h2non/filetype v1.1.3 h1:FKkx9QbD7HR/zjK1Ia5XiBsq9zdLi5Kf3zGyFTAFkGg=
 github.com/h2non/filetype v1.1.3/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY=
 github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
@@ -375,8 +385,9 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
-github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw=
+github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro=
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
 github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
 github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
@@ -389,6 +400,16 @@ github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h
 github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
 github.com/phpdave11/gofpdi v1.0.7/go.mod h1:vBmVV0Do6hSBHC8uKUQ71JGW+ZGQq74llk/7bXwjDoI=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
+github.com/pingcap/errors v0.11.0/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
+github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
+github.com/pingcap/errors v0.11.5-0.20221009092201-b66cddb77c32 h1:m5ZsBa5o/0CkzZXfXLaThzKuR85SnHHetqBCpzQ30h8=
+github.com/pingcap/errors v0.11.5-0.20221009092201-b66cddb77c32/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg=
+github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c h1:CgbKAHto5CQgWM9fSBIvaxsJHuGP0uM74HXtv3MyyGQ=
+github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c/go.mod h1:4qGtCB0QK0wBzKtFEGDhxXnSnbQApw1gc9siScUl8ew=
+github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22 h1:2SOzvGvE8beiC1Y4g9Onkvu6UmuBBOeWRGQEjJaT/JY=
+github.com/pingcap/log v1.1.1-0.20230317032135-a0d097d16e22/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4=
+github.com/pingcap/tidb/pkg/parser v0.0.0-20231103042308-035ad5ccbe67 h1:m0RZ583HjzG3NweDi4xAcK54NBBPJh+zXp5Fp60dHtw=
+github.com/pingcap/tidb/pkg/parser v0.0.0-20231103042308-035ad5ccbe67/go.mod h1:yRkiqLFwIqibYg2P7h4bclHjHcJiIFRLKhGRyBcKYus=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -424,6 +445,8 @@ github.com/qiniu/qmgo v1.1.8/go.mod h1:QvZkzWNEv0buWPx0kdZsSs6URhESVubacxFPlITmv
 github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
 github.com/rdlucklib/rdluck_tools v1.0.3 h1:iOtK2QPlPQ6CL6c1htCk5VnFCHzyG6DCfJtunrMswK0=
 github.com/rdlucklib/rdluck_tools v1.0.3/go.mod h1:9Onw9o4w19C8KE5lxb8GyxgRBbZweRVkQSc79v38EaA=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
 github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
 github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
 github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
@@ -443,6 +466,7 @@ github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgY
 github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
 github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
 github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
 github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
@@ -450,6 +474,10 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz
 github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
 github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
 github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
+github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726 h1:xT+JlYxNGqyT+XcU8iUrN18JYed2TvG9yN5ULG2jATM=
+github.com/siddontang/go v0.0.0-20180604090527-bdc77568d726/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
+github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07 h1:oI+RNwuC9jF2g2lP0u0cVEEZrc/AYBCuFdvwrLWM/6Q=
+github.com/siddontang/go-log v0.0.0-20180807004314-8d05993dda07/go.mod h1:yFdBgwXP24JziuRl2NMUahT7nGLNOKi1SIiFxMttVD4=
 github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
 github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
 github.com/silenceper/wechat/v2 v2.1.6 h1:2br2DxNzhksmvIBJ+PfMqjqsvoZmd/5BnMIfjKYUBgc=
@@ -550,10 +578,21 @@ go.mongodb.org/mongo-driver v1.11.6/go.mod h1:G9TgswdsWjX4tmDA5zfs2+6AEPpYJwqbly
 go.mongodb.org/mongo-driver v1.16.0 h1:tpRsfBJMROVHKpdGyc1BBEzzjDUWjItxbVSZ8Ls4BQ4=
 go.mongodb.org/mongo-driver v1.16.0/go.mod h1:oB6AhJQvFQL4LEHyXi6aJzQJtBiTQHiAd83l0GdFaiw=
 go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
-go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
-go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
-go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
+go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
+go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
+go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
+go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
+go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
+go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
+go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -575,8 +614,8 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL
 golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
-golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
-golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
+golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
 golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
 golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
 golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@@ -587,6 +626,7 @@ golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE
 golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
 golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
 golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
 golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -703,9 +743,12 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
 golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
 golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
@@ -758,6 +801,9 @@ gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
 gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@@ -769,6 +815,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 37 - 0
models/binlog/binlog.go

@@ -0,0 +1,37 @@
+package binlog
+
+import "github.com/beego/beego/v2/client/orm"
+
+// BinlogFormatStruct
+// @Description: 数据库的binlog格式
+type BinlogFormatStruct struct {
+	VariableName string `json:"Variable_name"`
+	Value        string `json:"Value"`
+}
+
+// GetBinlogFormat
+// @Description: 获取数据库的binlog格式
+// @return item
+// @return err
+func GetBinlogFormat() (item *BinlogFormatStruct, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SHOW VARIABLES LIKE 'binlog_format';`
+	err = o.Raw(sql).QueryRow(&item)
+	return
+}
+
+type BinlogFileStruct struct {
+	File     string `json:"File"`
+	Position uint32 `json:"Position"`
+}
+
+// GetShowMaster
+// @Description: 获取master的状态
+// @return item
+// @return err
+func GetShowMaster() (item *BinlogFileStruct, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `show master status;`
+	err = o.Raw(sql).QueryRow(&item)
+	return
+}

+ 64 - 0
models/binlog/business_sys_interaction_log.go

@@ -0,0 +1,64 @@
+package binlog
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+// BusinessSysInteractionLog 商家系统交互记录表
+type BusinessSysInteractionLog struct {
+	ID             uint32    `gorm:"primaryKey;column:id;type:int(10) unsigned;not null" json:"-"`
+	InteractionKey string    `gorm:"unique;column:interaction_key;type:varchar(128);not null;default:''" json:"interactionKey"` // 记录Key
+	InteractionVal string    `gorm:"column:interaction_val;type:text;default:null" json:"interactionVal"`                       // 记录值
+	Remark         string    `gorm:"column:remark;type:varchar(128);not null;default:''" json:"remark"`                         // 备注
+	ModifyTime     time.Time `gorm:"column:modify_time;type:datetime;default:null" json:"modifyTime"`                           // 修改日期
+	CreateTime     time.Time `gorm:"column:create_time;type:datetime;default:null" json:"createTime"`                           // 创建时间
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *BusinessSysInteractionLog) TableName() string {
+	return "business_sys_interaction_log"
+}
+
+// BusinessSysInteractionLogColumns get sql column name.获取数据库列名
+var BusinessSysInteractionLogColumns = struct {
+	ID             string
+	InteractionKey string
+	InteractionVal string
+	Remark         string
+	ModifyTime     string
+	CreateTime     string
+}{
+	ID:             "id",
+	InteractionKey: "interaction_key",
+	InteractionVal: "interaction_val",
+	Remark:         "remark",
+	ModifyTime:     "modify_time",
+	CreateTime:     "create_time",
+}
+
+// Create 添加数据
+func (m *BusinessSysInteractionLog) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Insert(m)
+	return
+}
+
+// Update 更新数据
+func (m *BusinessSysInteractionLog) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+var BinlogFileNameKey = "binlog_edb_info_filename" // binlog文件名
+var BinlogPositionKey = "binlog_edb_info_position" // binlog位置
+
+// GetBusinessSysInteractionLogByKey 根据记录key获取数据
+func GetBusinessSysInteractionLogByKey(key string) (item *BusinessSysInteractionLog, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM business_sys_interaction_log WHERE interaction_key = ?"
+	err = o.Raw(sql, key).QueryRow(&item)
+	return
+}

+ 10 - 0
models/db.go

@@ -14,6 +14,7 @@ import (
 	future_good2 "eta/eta_api/models/data_manage/future_good"
 	"eta/eta_api/models/data_manage/supply_analysis"
 	"eta/eta_api/models/data_stat"
+	edbmonitor "eta/eta_api/models/edb_monitor"
 	"eta/eta_api/models/eta_trial"
 	"eta/eta_api/models/fe_calendar"
 	"eta/eta_api/models/ppt_english"
@@ -205,6 +206,8 @@ func init() {
 	// 初始化因子指标系列
 	initFactorEdbSeries()
 
+	// 初始化指标监控
+	initEdbMonitor()
 	// 初始化部分数据表变量(直接init会有顺序问题=_=!)
 	afterInitTable()
 
@@ -641,6 +644,13 @@ func initFactorEdbSeries() {
 	)
 }
 
+func initEdbMonitor() {
+	orm.RegisterModel(
+		new(edbmonitor.EdbMonitorInfo),     // 指标监控表
+		new(edbmonitor.EdbMonitorClassify), // 指标监控日志表
+	)
+}
+
 // initBiDashBoard 智能看板
 func initBiDashBoard() {
 	orm.RegisterModel(

+ 106 - 0
models/edb_monitor/edb_monitor.go

@@ -0,0 +1,106 @@
+package edbmonitor
+
+import (
+	"eta/eta_api/utils"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type EdbMonitorInfo struct {
+	EdbMonitorId         int       `orm:"column(edb_monitor_id);pk"`
+	EdbMonitorClassifyId int       `description:"预警分类id"`
+	EdbMonitorName       string    `description:"预警名称"`
+	EdbInfoId            int       `description:"指标id"`
+	EdbInfoType          int       `description:"指标类型"`
+	EdbCode              string    `description:"指标编码"`
+	Source               int       `description:"指标来源"`
+	SubSource            int       `description:"指标子来源: 0-经济数据库;1-日期序列;2-高频数据"`
+	EdbLatestDate        string    `description:"最新日期"`
+	EdbLatestValue       float64   `description:"指标最新值"`
+	MonitorType          int       `description:"突破方式: 0-向上突破;1-向下突破"`
+	MonitorData          float64   `description:"预警值"`
+	MonitorLevel         string    `description:"预警等级"`
+	State                int       `description:"预警状态: 0-已关闭;1-未触发;2-已触发"`
+	EdbTriggerDate       time.Time `description:"触发日期"`
+	MonitorTriggerTime   time.Time `description:"预警触发时间"`
+	CreateUserId         int       `description:"创建人id"`
+	CreateTime           time.Time `description:"创建时间"`
+	ModifyTime           time.Time `description:"修改时间"`
+}
+
+func (m *EdbMonitorInfo) Insert() (int64, error) {
+	o := orm.NewOrmUsingDB("data")
+	return o.Insert(m)
+}
+
+func (m *EdbMonitorInfo) Update(cols []string) (err error) {
+	if len(cols) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func GetEdbMonitorLevelList() (list []string, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT DISTINCT monitor_level FROM edb_monitor_info`
+	_, err = o.Raw(sql).QueryRows(&list)
+	return
+}
+
+func DeleteEdbMonitorInfoById(id int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `DELETE FROM edb_monitor_info WHERE edb_monitor_id =?`
+	_, err = o.Raw(sql, id).Exec()
+	return
+}
+
+func DeleteEdbMonitorInfoByIdList(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `DELETE FROM edb_monitor_info WHERE edb_monitor_id IN(` + utils.GetOrmInReplace(len(ids)) + `)`
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func GetEdbMonitorInfoById(id int) (item *EdbMonitorInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM edb_monitor_info WHERE edb_monitor_id =?`
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func GetEdbMonitorInfoCountByClassifyId(classifyId []int) (count int, err error) {
+	if len(classifyId) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(*) AS count FROM edb_monitor_info WHERE edb_monitor_classify_id IN(` + utils.GetOrmInReplace(len(classifyId)) + `)`
+	err = o.Raw(sql, classifyId).QueryRow(&count)
+	return
+}
+
+func GetEdbMonitorInfoCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(*) AS count FROM edb_monitor_info WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetEdbMonitorInfoPageByCondition(condition string, pars []interface{}, startSize int, pageSize int) (items []*EdbMonitorInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM edb_monitor_info WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY create_time DESC LIMIT?,?`
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}

+ 191 - 0
models/edb_monitor/edb_monitor_classify.go

@@ -0,0 +1,191 @@
+package edbmonitor
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type EdbMonitorClassify struct {
+	ClassifyId   int       `orm:"column(classify_id);pk"`
+	ClassifyName string    `description:"分类名称"`
+	Level        int       `description:"分类层级"`
+	ParentId     int       `description:"父分类ID"`
+	RootId       int       `description:"根分类ID"`
+	Sort         int       `description:"排序"`
+	CreateTime   time.Time `description:"创建时间"`
+}
+
+func (e *EdbMonitorClassify) Insert() (id int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+
+	var edbMonitorIds []int
+	if e.ParentId != 0 {
+		sql := `SELECT edb_monitor_id FROM edb_monitor_info WHERE edb_monitor_classify_id = ?`
+		_, err = o.Raw(sql, e.ParentId).QueryRows(&edbMonitorIds)
+		if err != nil {
+			return
+		}
+	}
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+
+	id, err = tx.Insert(e)
+	if len(edbMonitorIds) > 0 {
+		sql := `UPDATE edb_monitor_info SET edb_monitor_classify_id = ? WHERE edb_monitor_id IN (` + utils.GetOrmInReplace(len(edbMonitorIds)) + `)`
+		_, err = tx.Raw(sql, id, edbMonitorIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+func (e *EdbMonitorClassify) Update(cols []string, classifyId []int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	var edbMonitorIds []int
+	if e.ParentId != 0 {
+		sql := `SELECT edb_monitor_id FROM edb_monitor_info WHERE edb_monitor_classify_id = ?`
+		_, err = o.Raw(sql, e.ParentId).QueryRows(&edbMonitorIds)
+		if err != nil {
+			return
+		}
+	}
+
+	tx, err := o.Begin()
+	if err != nil {
+		return
+	}
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+	_, err = tx.Update(e, cols...)
+	if err != nil {
+		return
+	}
+	if len(classifyId) > 0 {
+		sql := `UPDATE edb_monitor_classify SET root_id = ? WHERE classify_id IN (` + utils.GetOrmInReplace(len(classifyId)) + `)`
+		_, err = tx.Raw(sql, e.RootId, classifyId).Exec()
+		if err != nil {
+			return
+		}
+	}
+	if len(edbMonitorIds) > 0 {
+		sql := `UPDATE edb_monitor_info SET edb_monitor_classify_id = ? WHERE edb_monitor_id IN (` + utils.GetOrmInReplace(len(edbMonitorIds)) + `)`
+		_, err = tx.Raw(sql, e.ClassifyId, edbMonitorIds).Exec()
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+func DeleteEdbMonitorClassifyById(id int) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Raw("DELETE FROM edb_monitor_classify WHERE classify_id =?", id).Exec()
+	return
+}
+
+func DeleteEdbMonitorClassifyByIdList(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := "DELETE FROM edb_monitor_classify WHERE classify_id IN (" + utils.GetOrmInReplace(len(ids)) + ")"
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func GetEdbMonitorClassifyList() (items []*EdbMonitorClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM edb_monitor_classify ORDER BY sort ASC"
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetEdbMonitorClassifyById(id int) (item *EdbMonitorClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM edb_monitor_classify WHERE classify_id =?"
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func GetEdbMonitorClassifyMaxSortByParentId(parentId int) (sort int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT MAX(sort) FROM edb_monitor_classify WHERE parent_id =?"
+	err = o.Raw(sql, parentId).QueryRow(&sort)
+	return
+}
+
+func GetChildEdbMonitorClassifyIdById(id int) (classifyId []int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT classify_id FROM edb_monitor_classify WHERE parent_id =?"
+	_, err = o.Raw(sql, id).QueryRows(&classifyId)
+	return
+}
+
+func GetChildEdbMonitorClassifyById(id int) (items []*EdbMonitorClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM edb_monitor_classify WHERE parent_id =?"
+	_, err = o.Raw(sql, id).QueryRows(&items)
+	return
+}
+
+func GetChildEdbMonitorClassifyByRootId(rootId int) (items []*EdbMonitorClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM edb_monitor_classify WHERE root_id =?"
+	_, err = o.Raw(sql, rootId).QueryRows(&items)
+	return
+}
+
+func GetEdbMonitorClassifyCountByIdList(ids []int) (count int, err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT COUNT(*) FROM edb_monitor_classify WHERE classify_id IN (` + utils.GetOrmInReplace(len(ids)) + `)`
+	err = o.Raw(sql, ids).QueryRow(&count)
+	return
+}
+
+// GetEdbMonitorClassifyByParentIdAndName 根据父级ID和名称获取分类
+func GetEdbMonitorClassifyByParentIdAndName(parentId int, classifyName string, classifyId int) (item *EdbMonitorClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM edb_monitor_classify WHERE parent_id = ? AND classify_name = ? AND classify_id <> ? LIMIT 1`
+	err = o.Raw(sql, parentId, classifyName, classifyId).QueryRow(&item)
+	return
+}
+
+// UpdateEdbMonitorClassifySortByParentId 根据分类父类id更新排序
+func UpdateEdbMonitorClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` update edb_monitor_classify set sort = ` + updateSort + ` WHERE parent_id=? and sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( classify_id > ` + fmt.Sprint(classifyId) + ` and sort= ` + fmt.Sprint(nowSort) + `)`
+	}
+	_, err = o.Raw(sql, parentId, nowSort).Exec()
+	return
+}
+
+// GetFirstEdbMonitorClassifyByParentId 获取当前父级图表分类下的排序第一条的数据
+func GetFirstEdbMonitorClassifyByParentId(parentId int) (item *EdbMonitorClassify, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM edb_monitor_classify WHERE parent_id=? order by sort asc,classify_id asc limit 1`
+	err = o.Raw(sql, parentId).QueryRow(&item)
+	return
+}

+ 23 - 0
models/edb_monitor/request/edb_monitor.go

@@ -0,0 +1,23 @@
+package request
+
+type EdbMonitorInfoSaveReq struct {
+	EdbMonitorId   int    `description:"预警ID"`
+	EdbInfoId      int    `description:"指标ID"`
+	MonitorType    int    `description:"突破方式:0-向上突破;1-向下突破"`
+	MonitorData    string `description:"预警值"`
+	EdbMonitorName string `description:"预警名称"`
+	MonitorLevel   string `description:"预警级别"`
+	ClassifyId     int    `description:"预警分类"`
+}
+
+type EdbMonitorInfoDeleteReq struct {
+	EdbMonitorId int `description:"预警ID"`
+}
+
+type EdbMonitorInfoCloseReq struct {
+	EdbMonitorId int `description:"预警ID"`
+}
+
+type EdbMonitorInfoRestartReq struct {
+	EdbMonitorId int `description:"预警ID"`
+}

+ 20 - 0
models/edb_monitor/request/edb_monitor_classify.go

@@ -0,0 +1,20 @@
+package request
+
+type EdbMonitorClassifySaveReq struct {
+	ClassifyId   int
+	ClassifyName string
+	Level        int
+	ParentId     int
+	RootId       int
+}
+
+type EdbMonitorClassifyDeleteReq struct {
+	ClassifyId int
+}
+
+type MoveEdbMonitorClassifyReq struct {
+	ClassifyId       int `description:"分类id"`
+	ParentClassifyId int `description:"父级分类id"`
+	PrevClassifyId   int `description:"上一个兄弟节点分类id"`
+	NextClassifyId   int `description:"下一个兄弟节点分类id"`
+}

+ 32 - 0
models/edb_monitor/response/edb_monitor.go

@@ -0,0 +1,32 @@
+package response
+
+import "github.com/rdlucklib/rdluck_tools/paging"
+
+type EdbMonitorInfoItem struct {
+	EdbMonitorId           int     `orm:"column(edb_monitor_id);pk"`
+	EdbMonitorClassifyId   int     `description:"预警分类id"`
+	EdbMonitorClassifyName string  `description:"预警分类名称"`
+	EdbMonitorName         string  `description:"预警名称"`
+	EdbInfoId              int     `description:"指标id"`
+	EdbInfoType            int     `description:"指标类型"`
+	EdbCode                string  `description:"指标编码"`
+	Source                 int     `description:"指标来源" json:"-"`
+	SubSource              int     `description:"指标子来源: 0-经济数据库;1-日期序列;2-高频数据" json:"-"`
+	EdbLatestDate          string  `description:"最新日期"`
+	EdbLatestValue         float64 `description:"最新值"`
+	MonitorType            int     `description:"突破方式: 0-向上突破;1-向下突破"`
+	MonitorData            float64 `description:"预警值"`
+	MonitorLevel           string  `description:"预警等级"`
+	State                  int     `description:"预警状态: 0-已关闭;1-未触发;2-已触发"`
+	EdbTriggerDate         string  `description:"触发日期"`
+	MonitorTriggerTime     string  `description:"预警触发时间"`
+	CreateUserId           int     `description:"创建人id"`
+	CreateUserName         string  `description:"创建人姓名"`
+	CreateTime             string  `description:"创建时间" json:"-"`
+	ModifyTime             string  `description:"修改时间" json:"-"`
+}
+
+type EdbMonitorInfoListResp struct {
+	List   []*EdbMonitorInfoItem `description:"预警列表"`
+	Paging *paging.PagingItem    `description:"分页信息"`
+}

+ 8 - 0
models/edb_monitor/response/edb_monitor_classify.go

@@ -0,0 +1,8 @@
+package response
+
+type EdbMonitorClassifyTree struct {
+	ClassifyId   int                       `description:"分类id"`
+	ClassifyName string                    `description:"分类名称"`
+	Level        int                       `description:"分类层级"`
+	Children     []*EdbMonitorClassifyTree `description:"子分类"`
+}

+ 108 - 0
routers/commentsRouter.go

@@ -6703,6 +6703,114 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorClassifyController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorClassifyController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/classify/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorClassifyController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorClassifyController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorClassifyController"],
+        beego.ControllerComments{
+            Method: "Move",
+            Router: `/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"],
+        beego.ControllerComments{
+            Method: "Close",
+            Router: `/close`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"],
+        beego.ControllerComments{
+            Method: "Delete",
+            Router: `/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"],
+        beego.ControllerComments{
+            Method: "GetMonitorLevel",
+            Router: `/monitor_level/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorController"],
+        beego.ControllerComments{
+            Method: "Restart",
+            Router: `/restart`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnPermissionController"],
         beego.ControllerComments{
             Method: "Add",

+ 7 - 0
routers/router.go

@@ -23,6 +23,7 @@ import (
 	"eta/eta_api/controllers/data_source"
 	"eta/eta_api/controllers/data_stat"
 	"eta/eta_api/controllers/document_manage"
+	"eta/eta_api/controllers/edb_monitor"
 	"eta/eta_api/controllers/english_report"
 	"eta/eta_api/controllers/eta_trial"
 	"eta/eta_api/controllers/fe_calendar"
@@ -406,6 +407,12 @@ func init() {
 				&document_manage.DocumentManageController{},
 			),
 		),
+		web.NSNamespace("/edb_monitor",
+			web.NSInclude(
+				&edb_monitor.EdbMonitorController{},
+				&edb_monitor.EdbMonitorClassifyController{},
+			),
+		),
 		web.NSNamespace("/bi_dashborad",
 			web.NSInclude(
 				&controllers.BIDaShboardController{},

+ 270 - 0
services/binlog/binlog.go

@@ -0,0 +1,270 @@
+package binlog
+
+import (
+	"eta/eta_api/models/binlog"
+	"eta/eta_api/utils"
+	"fmt"
+	"math/rand"
+	"strconv"
+	"time"
+
+	"github.com/go-mysql-org/go-mysql/canal"
+	"github.com/go-mysql-org/go-mysql/mysql"
+	_ "github.com/go-sql-driver/mysql"
+)
+
+func ListenMysql() {
+	var err error
+	defer func() {
+		if err != nil {
+			fmt.Println("数据库监听服务异常,err:", err)
+		}
+	}()
+	if utils.MYSQL_DATA_BINLOG_URL == "" {
+		panic("mysql url is empty")
+	}
+
+	if utils.MYSQL_DATA_BINLOG_USER == "" {
+		panic("mysql user is empty")
+	}
+	if utils.MYSQL_DATA_BINLOG_PWD == "" {
+		panic("mysql password is empty")
+	}
+	if utils.MYSQL_DATA_BINLOG_DB == "" {
+		panic("mysql db is empty")
+	}
+
+	includeTableRegex := []string{
+		utils.MYSQL_DATA_BINLOG_DB + ".edb_info$",
+		// utils.MYSQL_DATA_BINLOG_DB + ".edb_classify$",
+		// utils.MYSQL_DATA_BINLOG_DB + ".base_from_mysteel_chemical_index$",
+		// utils.MYSQL_DATA_BINLOG_DB + ".base_from_smm_index$",
+		// utils.MYSQL_DATA_BINLOG_DB + ".edb_data*",
+	}
+
+	// 主从复制的身份id配置,必须全局唯一,如果没有配置的话,那么会随机生成一个
+	var serverId uint32
+	if utils.MYSQL_DATA_BINLOG_SERVER_ID != "" {
+		id, _ := strconv.ParseUint(utils.MYSQL_DATA_BINLOG_SERVER_ID, 10, 32)
+		serverId = uint32(id)
+	}
+	if serverId == 0 {
+		serverId = uint32(rand.New(rand.NewSource(time.Now().Unix())).Intn(1000)) + 1001
+	}
+	cfg := &canal.Config{
+		// 一个32位无符号整数,用于标识当前 Canal 实例在 MySQL 主从复制体系中的身份。这里使用了一个随机数生成器确保每次启动时分配的 ServerID 是唯一的(在1001到1099之间)。在实际生产环境中,你需要手动指定一个全局唯一的 ServerID。
+		ServerID: serverId,
+		// 指定 Canal 要连接的数据库类型,默认为 "mysql",表明这是一个 MySQL 数据库。
+		Flavor: "mysql",
+		// 设置 MySQL 服务器地址(主机名或 IP 地址)和端口,例如 "127.0.0.1:3306"。
+		Addr:     utils.MYSQL_DATA_BINLOG_URL,
+		User:     utils.MYSQL_DATA_BINLOG_USER,
+		Password: utils.MYSQL_DATA_BINLOG_PWD,
+		// 如果设置为 true,Canal 将以原始二进制格式获取 binlog,否则将以解析后的 SQL 语句形式提供。
+		//RawModeEnabled:  false,
+		// 是否启用半同步复制。当设置为 true 时,MySQL 主库在事务提交后会等待至少一个从库确认已接收并写入 binlog 才返回成功,提高了数据一致性。
+		SemiSyncEnabled: false,
+		//  是否将 MySQL 中的 decimal 类型字段解析为 Go 的 Decimal 类型,而不是 float 或者 string。如果业务中有精确小数计算的需求,应开启此选项以避免精度丢失问题。
+		UseDecimal: true,
+		// 用于控制初始数据导出的相关配置,在 Canal 启动时是否需要全量同步表数据。
+		//Dump:              dumpConf,
+		// 正则表达式字符串,用于定义 Canal 应该监听哪些表的 binlog 事件。只有名称匹配该正则表达式的表才会被 Canal 同步处理。
+		IncludeTableRegex: includeTableRegex,
+	}
+
+	// 校验mysql binlog format,目前仅支持row格式
+	{
+		binlogFormat, tmpErr := binlog.GetBinlogFormat()
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		if binlogFormat.Value != "ROW" {
+			panic("mysql binlog format is not ROW")
+		}
+	}
+
+	//  获取上一次启动时的binlog文件名称和位置
+	fileName, position, err := getBinlogNamePosition()
+	if err != nil {
+		return
+	}
+	// 修改记录本次启动时的binlog文件名称和位置
+	modifyBinlogNamePosition(fileName, position)
+	// 定时修改binlog文件名称和位置
+	go timingModifyBinlogNamePosition()
+
+	c, err := canal.NewCanal(cfg)
+	if err != nil {
+		fmt.Println("err:", err)
+		return
+	}
+	utils.FileLog.Debug("记录上一次启动时的fileName:", fileName, ";position:", position)
+
+	binlogHandler := &EdbEventHandler{}
+	binlogHandler.SetBinlogFileName(fileName, position)
+	c.SetEventHandler(binlogHandler)
+	//c.Run()
+
+	pos := mysql.Position{
+		Name: fileName,
+		Pos:  position,
+	}
+	err = c.RunFrom(pos)
+}
+
+// getBinlogNamePosition
+// @Description: 获取当前binlog文件名称和位置
+// @author: Roc
+// @datetime 2024-05-17 13:18:19
+// @return fileName string
+// @return position uint32
+// @return err error
+func getBinlogNamePosition() (fileName string, position uint32, err error) {
+	// 优先从redis获取
+	fileName = utils.Rc.GetStr(utils.CACHE_MYSQL_DATA_FILENAME)
+	position64, err := utils.Rc.GetUInt64(utils.CACHE_MYSQL_DATA_POSITION)
+	if err != nil {
+		if err.Error() != utils.RedisNoKeyErr {
+			panic("mysql binlog position is not found,err:" + err.Error())
+			return
+		}
+		err = nil
+	}
+
+	position = uint32(position64)
+
+	// 如果没有从redis中获取到上次监听到的binlog的文件名称,或者位置为0,则从mysql中获取,则从 MySQL 中获取最新的文件名和位置。
+	if fileName == `` || position == 0 {
+
+		// binlog文件名
+		fileNameKey := binlog.BinlogFileNameKey
+		fileNameLog, tmpErr := binlog.GetBusinessSysInteractionLogByKey(fileNameKey)
+		if tmpErr == nil {
+			fileName = fileNameLog.InteractionKey
+		}
+
+		// binlog位置
+		positionKey := binlog.BinlogPositionKey
+		positionLog, tmpErr := binlog.GetBusinessSysInteractionLogByKey(positionKey)
+		if tmpErr == nil {
+			positionStr := positionLog.InteractionKey
+			positionInt, tmpErr := strconv.Atoi(positionStr)
+			if tmpErr == nil {
+				position = uint32(positionInt)
+			}
+		}
+	}
+
+	// 如果从表中没有取到数据,则从mysql中获取,则从 MySQL 中获取最新的文件名和位置。
+	if fileName == `` || position == 0 {
+		item, tmpErr := binlog.GetShowMaster()
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		fileName = item.File
+		position = item.Position
+	}
+
+	return
+}
+
+// modifyBinlogNamePosition
+// @Description: 修改记录本次启动时的binlog文件名称和位置
+// @author: Roc
+// @datetime 2024-05-17 11:32:32
+// @param fileName string
+// @param position uint32
+// @return err error
+func modifyBinlogNamePosition(fileName string, position uint32) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("修改binlog文件名称和位置异常,fileName", fileName, ",position:", position, ",err:", err)
+		}
+	}()
+
+	// fileName 变更
+	fileNameKey := binlog.BinlogFileNameKey
+	fileNameLog, err := binlog.GetBusinessSysInteractionLogByKey(fileNameKey)
+	if err != nil {
+		if err.Error() != utils.ErrNoRow() {
+			return
+		}
+		err = nil
+		fileNameLog = &binlog.BusinessSysInteractionLog{
+			//ID:             0,
+			InteractionKey: fileNameKey,
+			InteractionVal: fileName,
+			Remark:         "mysql中binlog的filename名称",
+			ModifyTime:     time.Now(),
+			CreateTime:     time.Now(),
+		}
+		err = fileNameLog.Create()
+		if err != nil {
+			return
+		}
+	} else {
+		fileNameLog.InteractionVal = fileName
+		fileNameLog.ModifyTime = time.Now()
+		err = fileNameLog.Update([]string{"InteractionVal", "ModifyTime"})
+		if err != nil {
+			return
+		}
+	}
+
+	// position 变更
+	positionKey := binlog.BinlogPositionKey
+	positionLog, err := binlog.GetBusinessSysInteractionLogByKey(positionKey)
+	if err != nil {
+		if err.Error() != utils.ErrNoRow() {
+			return
+		}
+		err = nil
+		positionLog = &binlog.BusinessSysInteractionLog{
+			//ID:             0,
+			InteractionKey: positionKey,
+			InteractionVal: fmt.Sprint(position),
+			Remark:         "mysql中binlog的position位置",
+			ModifyTime:     time.Now(),
+			CreateTime:     time.Now(),
+		}
+		err = positionLog.Create()
+		if err != nil {
+			return
+		}
+	} else {
+		positionLog.InteractionVal = fmt.Sprint(position)
+		positionLog.ModifyTime = time.Now()
+		err = positionLog.Update([]string{"InteractionVal", "ModifyTime"})
+		if err != nil {
+			return
+		}
+	}
+
+	return
+}
+
+// timingModifyBinlogNamePosition
+// @Description: 定时修改binlog文件名称和位置
+// @author: Roc
+// @datetime 2024-05-17 13:08:13
+func timingModifyBinlogNamePosition() {
+	for {
+		// 延时30s执行
+		time.Sleep(30 * time.Second)
+
+		// 获取最新的binlog文件名称和位置
+		fileName, position, err := getBinlogNamePosition()
+		if err != nil {
+			return
+		}
+
+		if fileName != `` && position != 0 {
+			// 修改记录本次启动时的binlog文件名称和位置
+			modifyBinlogNamePosition(fileName, position)
+		}
+	}
+}

+ 173 - 0
services/binlog/handler.go

@@ -0,0 +1,173 @@
+package binlog
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"reflect"
+	"time"
+
+	"github.com/go-mysql-org/go-mysql/canal"
+	"github.com/go-mysql-org/go-mysql/mysql"
+	"github.com/go-mysql-org/go-mysql/replication"
+	"github.com/pingcap/errors"
+)
+
+type EdbEventHandler struct {
+	canal.DummyEventHandler
+	fileName string
+	position uint32
+}
+
+func (h *EdbEventHandler) OnRow(e *canal.RowsEvent) (err error) {
+
+	// 监听逻辑
+	switch e.Action {
+	case canal.InsertAction:
+		err = h.Insert(e)
+	case canal.UpdateAction:
+		err = h.Update(e)
+	case canal.DeleteAction:
+		err = h.Delete(e)
+	default:
+		return errors.New("操作异常")
+	}
+
+	fmt.Println("fileName:", h.fileName, ";position:", h.position)
+
+	// 每次操作完成后都将当前位置记录到缓存
+	utils.Rc.Put(utils.CACHE_MYSQL_DATA_FILENAME, h.fileName, 31*24*time.Hour)
+	utils.Rc.Put(utils.CACHE_MYSQL_DATA_POSITION, h.position, 31*24*time.Hour)
+
+	return nil
+}
+
+func (h *EdbEventHandler) OnPosSynced(header *replication.EventHeader, p mysql.Position, set mysql.GTIDSet, f bool) error {
+	h.fileName = p.Name
+	h.position = p.Pos
+
+	// 旋转binlog日志的时候,需要将当前位置记录到缓存
+	utils.Rc.Put(utils.CACHE_MYSQL_DATA_FILENAME, h.fileName, 31*24*time.Hour)
+	utils.Rc.Put(utils.CACHE_MYSQL_DATA_POSITION, h.position, 31*24*time.Hour)
+
+	return nil
+}
+
+func (h *EdbEventHandler) String() string {
+	return "MyEventHandler"
+}
+
+func (h *EdbEventHandler) Insert(e *canal.RowsEvent) error {
+	// 批量插入的时候,e.Rows的长度会大于0
+	fmt.Println(e.Header.ServerID, ";", e.Table.Schema, ".", e.Table.Name)
+
+	for _, row := range e.Rows { // 遍历当前插入的数据列表(存在批量插入的情况,所以是list)
+		logData := make(map[string]interface{})
+		dataLen := len(row)
+		for i, v := range e.Table.Columns {
+			if i < dataLen {
+				tmpData := row[i]
+				if tmpData != nil && reflect.TypeOf(tmpData).Kind() == reflect.Slice {
+					tmpOld := tmpData.([]byte)
+					tmpData = string(tmpOld)
+				}
+				logData[v.Name] = tmpData
+			}
+		}
+	}
+
+	return nil
+}
+
+func (h *EdbEventHandler) Update(e *canal.RowsEvent) error {
+	if len(e.Rows) != 2 {
+		fmt.Println("更新数据异常,没有原始数据和新数据:", e.Rows)
+		return nil
+	}
+
+	logOldData := make(map[string]interface{})
+	logNewData := make(map[string]interface{})
+
+	oldDataLen := len(e.Rows[0])
+	newDataLen := len(e.Rows[0])
+	for i, v := range e.Table.Columns {
+
+		if i < oldDataLen {
+			oldData := e.Rows[0][i]
+			if oldData != nil && reflect.TypeOf(oldData).Kind() == reflect.Slice {
+				tmpOld := oldData.([]byte)
+				oldData = string(tmpOld)
+			}
+			logOldData[v.Name] = oldData
+		}
+		if i < newDataLen {
+			newData := e.Rows[1][i]
+			if newData != nil && reflect.TypeOf(newData).Kind() == reflect.Slice {
+				tmpNew := newData.([]byte)
+				newData = string(tmpNew)
+			}
+			logNewData[v.Name] = newData
+		}
+
+	}
+
+	return nil
+}
+
+func (h *EdbEventHandler) Delete(e *canal.RowsEvent) error {
+	// 批量删除的时候,e.Rows的长度会大于0
+	fmt.Println(e.Header.ServerID, ";", e.Table.Schema, ".", e.Table.Name)
+
+	for _, row := range e.Rows { // 遍历当前插入的数据列表(存在批量插入的情况,所以是list)
+		logData := make(map[string]interface{})
+		dataLen := len(row)
+		for i, v := range e.Table.Columns {
+			if i < dataLen {
+				tmpData := row[i]
+				if tmpData != nil && reflect.TypeOf(tmpData).Kind() == reflect.Slice {
+					tmpOld := tmpData.([]byte)
+					tmpData = string(tmpOld)
+				}
+				logData[v.Name] = tmpData
+			}
+		}
+	}
+
+	return nil
+}
+
+func (h *EdbEventHandler) Delete3(e *canal.RowsEvent) error {
+	if len(e.Rows) != 1 {
+		fmt.Println("删除数据异常,没有原始数据:", e.Rows)
+		return nil
+	}
+	fmt.Println(e.Header.ServerID, ";", e.Table.Schema, ".", e.Table.Name)
+
+	dataLen := len(e.Rows[0])
+	logData := make(map[string]interface{})
+	for i, v := range e.Table.Columns {
+		if i < dataLen {
+			tmpData := e.Rows[0][i]
+			if tmpData != nil && reflect.TypeOf(tmpData).Kind() == reflect.Slice {
+				tmpOld := tmpData.([]byte)
+				tmpData = string(tmpOld)
+			}
+			logData[v.Name] = tmpData
+		}
+	}
+
+	return nil
+}
+
+// SetBinlogFileName
+// @Description: 设置当前的binlog文件名和位置
+// @author: Roc
+// @receiver h
+// @datetime 2024-02-29 18:09:36
+// @param fileName string
+// @param position uint32
+func (h *EdbEventHandler) SetBinlogFileName(fileName string, position uint32) {
+	h.fileName = fileName
+	h.position = position
+
+	fmt.Println("init fileName:", h.fileName, ";position:", h.position)
+}

+ 415 - 0
services/edb_monitor/edb_monitor.go

@@ -0,0 +1,415 @@
+package edbmonitor
+
+import (
+	"eta/eta_api/models/data_manage"
+	edbmonitor "eta/eta_api/models/edb_monitor"
+	"eta/eta_api/models/edb_monitor/request"
+	"eta/eta_api/models/edb_monitor/response"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// 预警触发状态
+const (
+	EDB_MONITOR_STATE_CLOSE = iota
+	EDB_MONITOR_STATE_NO_TRIGGER
+	EDB_MONITOR_STATE_TRIGGER_SUCCESS
+)
+
+// 预警突破方式
+const (
+	EDB_MONITOR_TYPE_UP = iota
+	EDB_MONITOR_TYPE_DOWN
+)
+
+func GetMonitorList(classifyId, level, state, userId string, pageSize, currentIndex int) (resp response.EdbMonitorInfoListResp, msg string, err error) {
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize := utils.StartIndex(currentIndex, pageSize)
+
+	var classifyIdList []int
+	if classifyId != "" {
+		tmp := strings.Split(classifyId, ",")
+		for _, v := range tmp {
+			cv, _ := strconv.Atoi(v)
+			classifyIdList = append(classifyIdList, cv)
+		}
+	}
+	var levelList []int
+	if level != "" {
+		tmp := strings.Split(level, ",")
+		for _, v := range tmp {
+			lv, _ := strconv.Atoi(v)
+			levelList = append(levelList, lv)
+		}
+	}
+	var stateList []int
+	if state != "" {
+		tmp := strings.Split(state, ",")
+		for _, v := range tmp {
+			st, _ := strconv.Atoi(v)
+			stateList = append(stateList, st)
+		}
+	}
+	var userIdList []int
+	if userId != "" {
+		tmp := strings.Split(userId, ",")
+		for _, v := range tmp {
+			ui, _ := strconv.Atoi(v)
+			userIdList = append(userIdList, ui)
+		}
+	}
+
+	var conditon string
+	var pars []interface{}
+	if len(classifyIdList) != 0 {
+		conditon += ` AND edb_monitor_classify_id IN (` + utils.GetOrmInReplace(len(classifyIdList)) + ` )`
+		pars = append(pars, classifyIdList)
+	}
+	if len(levelList) != 0 {
+		conditon += ` AND monitor_level IN (` + utils.GetOrmInReplace(len(levelList)) + ` )`
+		pars = append(pars, levelList)
+	}
+	if len(stateList) != 0 {
+		conditon += ` AND state IN (` + utils.GetOrmInReplace(len(stateList)) + ` )`
+		pars = append(pars, stateList)
+	}
+	if len(userIdList) != 0 {
+		conditon += ` AND create_user_id IN (` + utils.GetOrmInReplace(len(userIdList)) + ` )`
+		pars = append(pars, userIdList)
+	}
+
+	total, err := edbmonitor.GetEdbMonitorInfoCountByCondition(conditon, pars)
+	if err != nil {
+		msg = "获取监控列表失败"
+		err = fmt.Errorf("GetEdbMonitorInfoCountByCondition err:%w", err)
+		return
+	}
+
+	edbMonitorList, err := edbmonitor.GetEdbMonitorInfoPageByCondition(conditon, pars, startSize, pageSize)
+	if err != nil {
+		msg = "获取监控列表失败"
+		err = fmt.Errorf("GetEdbMonitorInfoPageByCondition err:%w", err)
+		return
+	}
+	edbUserId := make([]int, 0)
+	for _, v := range edbMonitorList {
+		edbUserId = append(edbUserId, v.CreateUserId)
+	}
+	userInfoList, err := system.GetSysAdminByIdList(edbUserId)
+	if err != nil {
+		msg = "获取监控列表失败"
+		err = fmt.Errorf("GetSysAdminByIdList err:%w", err)
+		return
+	}
+	userMap := make(map[int]string)
+	for _, v := range userInfoList {
+		userMap[v.AdminId] = v.RealName
+	}
+	classifyList, err := edbmonitor.GetEdbMonitorClassifyList()
+	if err != nil {
+		msg = "获取监控列表失败"
+		err = fmt.Errorf("GetEdbMonitorClassifyList err:%w", err)
+		return
+	}
+	classifyMap := make(map[int]string)
+	for _, v := range classifyList {
+		classifyMap[v.ClassifyId] = v.ClassifyName
+	}
+	classifyPathMap := make(map[int]string)
+	for _, v := range classifyList {
+		var path []string
+		if v.ParentId != 0 {
+			parentName := classifyMap[v.ParentId]
+			if v.RootId != 0 && v.RootId != v.ParentId {
+				rootName := classifyMap[v.RootId]
+				path = append(path, rootName)
+			}
+			curName := v.ClassifyName
+			path = append(path, parentName, curName)
+		} else {
+			path = append(path, v.ClassifyName)
+		}
+		classifyPathMap[v.ClassifyId] = strings.Join(path, "/")
+	}
+
+	edbMonitorItems := toEdbMonitorInfoItems(edbMonitorList, userMap, classifyPathMap)
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	resp.List = edbMonitorItems
+	resp.Paging = page
+
+	return
+}
+
+func SaveEdbMonitorInfo(req request.EdbMonitorInfoSaveReq, adminId int) (msg string, err error) {
+	edb, er := data_manage.GetEdbInfoById(req.EdbInfoId)
+	if er != nil {
+		if er.Error() == utils.ErrNoRow() {
+			msg = "指标不存在,请刷新重试"
+			err = er
+			return
+		}
+		msg = "添加指标预警失败"
+		err = er
+		return
+	}
+	var edbMonitorInfo *edbmonitor.EdbMonitorInfo
+	if req.EdbMonitorId > 0 {
+		edbMonitorInfo, er = edbmonitor.GetEdbMonitorInfoById(req.EdbMonitorId)
+		if er != nil {
+			if er.Error() == utils.ErrNoRow() {
+				msg = "指标预警不存在,请刷新重试"
+				err = er
+				return
+			}
+			msg = "编辑指标预警失败"
+			err = fmt.Errorf("GetEdbMonitorInfoById err:%w", er)
+			return
+		}
+		var updateCols []string
+		if edbMonitorInfo.EdbInfoId != req.EdbInfoId {
+			updateCols = append(updateCols, []string{"edb_info_id", "edb_info_type", "edb_code", "source", "sub_source", "edb_latest_date", "edb_latest_value"}...)
+			edbMonitorInfo.EdbInfoId = req.EdbInfoId
+			edbMonitorInfo.EdbInfoType = edb.EdbInfoType
+			edbMonitorInfo.EdbCode = edb.EdbCode
+			edbMonitorInfo.Source = edb.Source
+			edbMonitorInfo.SubSource = edb.SubSource
+			edbMonitorInfo.EdbLatestDate = edb.LatestDate
+			edbMonitorInfo.EdbLatestValue = edb.LatestValue
+		}
+		if edbMonitorInfo.EdbMonitorName != req.EdbMonitorName {
+			updateCols = append(updateCols, "edb_monitor_name")
+			edbMonitorInfo.EdbMonitorName = req.EdbMonitorName
+		}
+		if edbMonitorInfo.EdbMonitorClassifyId != req.ClassifyId {
+			updateCols = append(updateCols, "edb_monitor_classify_id")
+			edbMonitorInfo.EdbMonitorClassifyId = req.ClassifyId
+		}
+		if edbMonitorInfo.MonitorType != req.MonitorType {
+			updateCols = append(updateCols, "monitor_type")
+			edbMonitorInfo.MonitorType = req.MonitorType
+		}
+		monitorData, _ := strconv.ParseFloat(req.MonitorData, 64)
+		if edbMonitorInfo.MonitorData != monitorData {
+			updateCols = append(updateCols, "monitor_data")
+			edbMonitorInfo.MonitorData = monitorData
+		}
+		if edbMonitorInfo.MonitorLevel != req.MonitorLevel {
+			updateCols = append(updateCols, "monitor_level")
+			edbMonitorInfo.MonitorLevel = req.MonitorLevel
+		}
+		if len(updateCols) > 0 {
+			updateCols = append(updateCols, "modify_time")
+			edbMonitorInfo.ModifyTime = time.Now()
+			er := edbMonitorInfo.Update(updateCols)
+			if er != nil {
+				msg = "编辑指标预警失败"
+				err = fmt.Errorf("UpdateEdbMonitorInfoById err:%w", er)
+				return
+			}
+		}
+	} else {
+		edbMonitorInfo = new(edbmonitor.EdbMonitorInfo)
+		edbMonitorInfo.EdbMonitorClassifyId = req.ClassifyId
+		edbMonitorInfo.EdbMonitorName = req.EdbMonitorName
+		edbMonitorInfo.EdbInfoId = req.EdbInfoId
+		edbMonitorInfo.EdbInfoType = edb.EdbInfoType
+		edbMonitorInfo.EdbCode = edb.EdbCode
+		edbMonitorInfo.Source = edb.Source
+		edbMonitorInfo.SubSource = edb.SubSource
+		edbMonitorInfo.EdbLatestDate = edb.LatestDate
+		edbMonitorInfo.EdbLatestValue = edb.LatestValue
+		edbMonitorInfo.MonitorType = req.MonitorType
+		monitorData, _ := strconv.ParseFloat(req.MonitorData, 64)
+		edbMonitorInfo.MonitorData = monitorData
+		edbMonitorInfo.MonitorLevel = req.MonitorLevel
+		edbMonitorInfo.CreateUserId = adminId
+		edbMonitorInfo.CreateTime = time.Now()
+		edbMonitorInfo.ModifyTime = time.Now()
+
+		insertId, er := edbMonitorInfo.Insert()
+		if er != nil {
+			msg = "添加指标预警失败"
+			err = fmt.Errorf("insert err:%w", er)
+			return
+		}
+		edbMonitorInfo.EdbMonitorId = int(insertId)
+	}
+	err = ModifyEdbMonitorState(edbMonitorInfo, edb.EdbCode, edb.Source, edb.SubSource)
+	if err != nil {
+		msg = "更新指标预警失败"
+		err = fmt.Errorf("ModifyEdbMonitorState err:%w", err)
+		return
+	}
+	return
+}
+
+func ModifyEdbMonitorState(edbMonitorInfo *edbmonitor.EdbMonitorInfo, edbCode string, source, subSource int) (err error) {
+	cond := ` AND edb_code = ? `
+	pars := []interface{}{edbCode}
+	latestTwoData, er := data_manage.GetEdbDataListByCondition(cond, pars, source, subSource, 0, 2)
+	if er != nil {
+		err = fmt.Errorf("GetEdbDataListByCondition err:%w", er)
+		return
+	}
+	triggerState := cmpEdbMonitorState(latestTwoData, edbMonitorInfo.MonitorType, edbMonitorInfo.MonitorData)
+	var updateCols []string
+	if edbMonitorInfo.State != EDB_MONITOR_STATE_TRIGGER_SUCCESS {
+		edbMonitorInfo.State = triggerState
+		updateCols = append(updateCols, "state")
+	} else {
+		tmpTime, _ := time.Parse(utils.FormatDate, latestTwoData[0].DataTime)
+		edbMonitorInfo.EdbTriggerDate = tmpTime
+		edbMonitorInfo.MonitorTriggerTime = time.Now()
+		updateCols = append(updateCols, []string{"edb_trigger_date", "monitor_trigger_time"}...)
+	}
+	err = edbMonitorInfo.Update(updateCols)
+	if err != nil {
+		return
+	}
+	return
+}
+
+func cmpEdbMonitorState(twoData []*data_manage.EdbData, monitorType int, monitorData float64) int {
+	if len(twoData) < 2 {
+		return EDB_MONITOR_STATE_NO_TRIGGER
+	}
+	switch monitorType {
+	case EDB_MONITOR_TYPE_UP:
+		if twoData[0].Value >= monitorData && twoData[1].Value < monitorData {
+			return EDB_MONITOR_STATE_TRIGGER_SUCCESS
+		} else {
+			return EDB_MONITOR_STATE_NO_TRIGGER
+		}
+	case EDB_MONITOR_TYPE_DOWN:
+		if twoData[0].Value <= monitorData && twoData[1].Value > monitorData {
+			return EDB_MONITOR_STATE_TRIGGER_SUCCESS
+		} else {
+			return EDB_MONITOR_STATE_NO_TRIGGER
+		}
+	default:
+		return EDB_MONITOR_STATE_NO_TRIGGER
+	}
+}
+
+func DeleteEdbMonitorInfo(req request.EdbMonitorInfoDeleteReq) (msg string, err error) {
+	err = edbmonitor.DeleteEdbMonitorInfoById(req.EdbMonitorId)
+	if err != nil {
+		msg = "删除失败"
+		err = fmt.Errorf("DeleteEdbMonitorInfoById err:%w", err)
+	}
+	return
+}
+
+func GetEdbMonitorLevelList() (list []string, msg string, err error) {
+	list, err = edbmonitor.GetEdbMonitorLevelList()
+	if err != nil {
+		msg = "获取等级等级列表失败"
+		err = fmt.Errorf("GetEdbMonitorLevelList err:%w", err)
+		return
+	}
+	return
+}
+
+func CloseEdbMonitorInfo(req request.EdbMonitorInfoCloseReq, adminId int) (msg string, err error) {
+	edbMonitor, er := edbmonitor.GetEdbMonitorInfoById(req.EdbMonitorId)
+	if er != nil {
+		if er.Error() == utils.ErrNoRow() {
+			msg = "指标预警不存在,请刷新重试"
+			err = er
+		}
+		msg = "关闭失败"
+		err = fmt.Errorf("获取预警详情失败, err:%w", er)
+		return
+	}
+	if edbMonitor.CreateUserId != adminId {
+		msg = "无权限操作"
+		err = fmt.Errorf("无权限操作")
+		return
+	}
+	if edbMonitor.State == EDB_MONITOR_STATE_CLOSE {
+		msg = "预警已关闭"
+		err = fmt.Errorf("预警已关闭")
+		return
+	}
+	edbMonitor.State = EDB_MONITOR_STATE_CLOSE
+	edbMonitor.ModifyTime = time.Now()
+	er = edbMonitor.Update([]string{"state", "modify_time"})
+	if er != nil {
+		msg = "关闭失败"
+		err = fmt.Errorf("修改预警状态失败, err:%w", er)
+		return
+	}
+	return
+}
+
+func RestartEdbMonitorInfo(req request.EdbMonitorInfoRestartReq, adminId int) (msg string, err error) {
+	edbMonitor, er := edbmonitor.GetEdbMonitorInfoById(req.EdbMonitorId)
+	if er != nil {
+		if er.Error() == utils.ErrNoRow() {
+			msg = "指标预警不存在,请刷新重试"
+			err = er
+		}
+		msg = "重启失败"
+		err = fmt.Errorf("获取预警详情失败, err:%w", er)
+		return
+	}
+	if edbMonitor.CreateUserId != adminId {
+		msg = "无权限操作"
+		err = fmt.Errorf("无权限操作")
+		return
+	}
+	if edbMonitor.State != EDB_MONITOR_STATE_CLOSE {
+		msg = "预警未关闭,无需重启"
+		err = fmt.Errorf("预警未关闭,无需重启")
+		return
+	}
+	err = ModifyEdbMonitorState(edbMonitor, edbMonitor.EdbCode, edbMonitor.Source, edbMonitor.SubSource)
+	if err != nil {
+		msg = "重启失败"
+		err = fmt.Errorf("修改预警状态失败, err:%w", err)
+		return
+	}
+	return
+}
+
+func toEdbMonitorInfoItems(edbmonitor []*edbmonitor.EdbMonitorInfo, userMap map[int]string, classifyPathMap map[int]string) []*response.EdbMonitorInfoItem {
+	res := make([]*response.EdbMonitorInfoItem, 0, len(edbmonitor))
+	for _, v := range edbmonitor {
+		tmp := new(response.EdbMonitorInfoItem)
+		tmp.EdbMonitorId = v.EdbMonitorId
+		tmp.EdbMonitorName = v.EdbMonitorName
+		tmp.EdbMonitorClassifyId = v.EdbMonitorClassifyId
+		tmp.EdbMonitorClassifyName = classifyPathMap[v.EdbMonitorClassifyId]
+		tmp.EdbInfoId = v.EdbInfoId
+		tmp.EdbInfoType = v.EdbInfoType
+		tmp.EdbCode = v.EdbCode
+		tmp.Source = v.Source
+		tmp.SubSource = v.SubSource
+		tmp.EdbLatestDate = v.EdbLatestDate
+		tmp.EdbLatestValue = v.EdbLatestValue
+		tmp.MonitorType = v.MonitorType
+		tmp.MonitorData = v.MonitorData
+		tmp.MonitorLevel = v.MonitorLevel
+		tmp.State = v.State
+		tmp.EdbTriggerDate = utils.TimeTransferString(utils.FormatDate, v.EdbTriggerDate)
+		tmp.MonitorTriggerTime = utils.TimeTransferString(utils.FormatDateTime, v.MonitorTriggerTime)
+		tmp.CreateUserId = v.CreateUserId
+		tmp.CreateUserName = userMap[v.CreateUserId]
+		tmp.CreateTime = utils.TimeTransferString(utils.FormatDateTime, v.CreateTime)
+		tmp.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, v.ModifyTime)
+		res = append(res, tmp)
+	}
+	return res
+}

+ 350 - 0
services/edb_monitor/edb_monitor_classify.go

@@ -0,0 +1,350 @@
+package edbmonitor
+
+import (
+	"errors"
+	edbmonitor "eta/eta_api/models/edb_monitor"
+	"eta/eta_api/models/edb_monitor/request"
+	"eta/eta_api/models/edb_monitor/response"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+func GetEdbMonitorClassifyTree() (resp []*response.EdbMonitorClassifyTree, msg string, err error) {
+	classifyList, err := edbmonitor.GetEdbMonitorClassifyList()
+	if err != nil {
+		msg = "获取分类列表失败"
+		return
+	}
+	resp = generateClassifyTree(classifyList, 0)
+	return
+}
+
+func SaveEdbMonitorClassify(req request.EdbMonitorClassifySaveReq) (msg string, err error) {
+	childClassify, er := edbmonitor.GetChildEdbMonitorClassifyById(req.ParentId)
+	if er != nil {
+		msg = "分类信息保存失败"
+		err = fmt.Errorf("get parent classify err:%w", er)
+		return
+	}
+	var parentId []int
+	if req.ParentId != req.RootId {
+		if req.ParentId > 0 {
+			parentId = append(parentId, req.ParentId)
+		}
+		if req.RootId > 0 {
+			parentId = append(parentId, req.RootId)
+		}
+	} else {
+		if req.ParentId > 0 {
+			parentId = append(parentId, req.ParentId)
+		}
+	}
+	count, er := edbmonitor.GetEdbMonitorClassifyCountByIdList(parentId)
+	if er != nil {
+		msg = "分类信息保存失败"
+		err = fmt.Errorf("get classify count err:%w", er)
+		return
+	}
+	if len(parentId) != count {
+		msg = "父级分类不存在"
+		err = errors.New("parent classify not exist")
+		return
+	}
+
+	if req.ClassifyId > 0 {
+		classifyInfo, er := edbmonitor.GetEdbMonitorClassifyById(req.ClassifyId)
+		if er != nil {
+			if er.Error() == utils.ErrNoRow() {
+				msg = "分类不存在"
+				return
+			}
+			msg = "分类信息保存失败"
+			return
+		}
+		if classifyInfo.Level != req.Level {
+			msg = "分类层级不能修改"
+			return
+		}
+		var updateCols []string
+		if classifyInfo.ClassifyName != req.ClassifyName {
+			classifyInfo.ClassifyName = req.ClassifyName
+			updateCols = append(updateCols, "classify_name")
+		}
+		if classifyInfo.ParentId != req.ParentId {
+			classifyInfo.ParentId = req.ParentId
+			updateCols = append(updateCols, "parent_id")
+
+			maxSort, er := edbmonitor.GetEdbMonitorClassifyMaxSortByParentId(classifyInfo.ParentId)
+			if er != nil {
+				msg = "分类信息保存失败"
+				err = fmt.Errorf("get max sort err:%w", er)
+				return
+			}
+			classifyInfo.Sort = maxSort + 1
+			updateCols = append(updateCols, "sort")
+		}
+
+		var childClassifyIds []int
+		for _, v := range childClassify {
+			if v.ClassifyName == req.ClassifyName {
+				msg = "分类名称重复"
+				err = errors.New("classify name repeat")
+				return
+			} else {
+				childClassifyIds = append(childClassifyIds, v.ClassifyId)
+			}
+		}
+
+		if classifyInfo.RootId != req.RootId {
+			classifyInfo.RootId = req.RootId
+			updateCols = append(updateCols, "root_id")
+
+		}
+		err = classifyInfo.Update(updateCols, childClassifyIds)
+		if err != nil {
+			msg = "分类信息保存失败"
+			return
+		}
+	} else {
+		for _, v := range childClassify {
+			if v.ClassifyName == req.ClassifyName {
+				msg = "分类名称重复"
+				err = errors.New("classify name repeat")
+				return
+			}
+		}
+
+		maxSort, er := edbmonitor.GetEdbMonitorClassifyMaxSortByParentId(req.ParentId)
+		if er != nil {
+			msg = "分类信息保存失败"
+			err = fmt.Errorf("get max sort err:%w", er)
+			return
+		}
+
+		tmp := new(edbmonitor.EdbMonitorClassify)
+		tmp.ClassifyName = req.ClassifyName
+		tmp.ParentId = req.ParentId
+		tmp.RootId = req.RootId
+		tmp.Level = req.Level
+		tmp.Sort = maxSort + 1
+		tmp.CreateTime = time.Now()
+		_, er = tmp.Insert()
+		if er != nil {
+			msg = "分类信息保存失败"
+			err = fmt.Errorf("insert classify err:%w", er)
+			return
+		}
+	}
+
+	return
+}
+
+func DeleteEdbMonitorClassify(req request.EdbMonitorClassifyDeleteReq) (msg string, err error) {
+	classifyInfo, er := edbmonitor.GetEdbMonitorClassifyById(req.ClassifyId)
+	if er != nil {
+		if er.Error() == utils.ErrNoRow() {
+			msg = "分类不存在"
+			return
+		}
+		msg = "分类信息删除失败"
+		return
+	}
+	var classifyIds []int
+	switch classifyInfo.Level {
+	case 1:
+		classifyList, er := edbmonitor.GetChildEdbMonitorClassifyByRootId(classifyInfo.ClassifyId)
+		if er != nil {
+			msg = "分类删除失败"
+			err = fmt.Errorf("get child classify by root err:%w", er)
+			return
+		}
+		classifyIds = append(classifyIds, classifyInfo.ClassifyId)
+		for _, v := range classifyList {
+			classifyIds = append(classifyIds, v.ClassifyId)
+		}
+	case 2:
+		classifyList, er := edbmonitor.GetChildEdbMonitorClassifyById(classifyInfo.ClassifyId)
+		if er != nil {
+			msg = "分类删除失败"
+			err = fmt.Errorf("get child classify err:%w", er)
+			return
+		}
+		classifyIds = append(classifyIds, classifyInfo.ClassifyId)
+		for _, v := range classifyList {
+			classifyIds = append(classifyIds, v.ClassifyId)
+		}
+	case 3:
+		classifyIds = append(classifyIds, classifyInfo.ClassifyId)
+	}
+
+	count, er := edbmonitor.GetEdbMonitorInfoCountByClassifyId(classifyIds)
+	if er != nil {
+		msg = "分类删除失败"
+		err = fmt.Errorf("get classify info count err:%w", er)
+		return
+	}
+	if count > 0 {
+		msg = "分类下存在关联预警,不能删除"
+		return
+	}
+	err = edbmonitor.DeleteEdbMonitorClassifyByIdList(classifyIds)
+	if err != nil {
+		msg = "分类删除失败"
+		return
+	}
+	return
+}
+
+// MoveEdbMonitorClassify 移动分类
+func MoveEdbMonitorClassify(req request.MoveEdbMonitorClassifyReq) (msg string, err error) {
+
+	classifyInfo, er := edbmonitor.GetEdbMonitorClassifyById(req.ClassifyId)
+	if er != nil {
+		err = er
+		if er.Error() == utils.ErrNoRow() {
+			msg = "分类不存在,请刷新页面"
+			return
+		}
+		msg = "移动失败"
+		return
+	}
+
+	// 校验移动的父级目录下是否有重名分类
+	exists, er := edbmonitor.GetEdbMonitorClassifyByParentIdAndName(req.ParentClassifyId, classifyInfo.ClassifyName, req.ClassifyId)
+	if er != nil && er.Error() != utils.ErrNoRow() {
+		msg = "移动失败"
+		err = er
+		return
+	}
+	if exists != nil {
+		msg = "移动失败,分类名称已存在"
+		err = errors.New("classify name repeat")
+		return
+	}
+
+	updateCol := make([]string, 0)
+
+	//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+	var parentRootId int
+	if classifyInfo.ParentId != req.ParentClassifyId && req.ParentClassifyId != 0 {
+		parentClassify, er := edbmonitor.GetEdbMonitorClassifyById(req.ParentClassifyId)
+		if er != nil {
+			err = er
+			if er.Error() == utils.ErrNoRow() {
+				msg = "父级分类不存在,请刷新页面"
+				return
+			}
+			msg = "移动失败"
+			return
+		}
+		if parentClassify.Level > 2 {
+			msg = "移动失败,不能移动到子分类"
+			err = errors.New("move to child classify")
+			return
+		}
+		if parentClassify.Level+1 != classifyInfo.Level {
+			msg = "移动失败,只能移动到同一层级的分类"
+			err = errors.New("move to child classify")
+			return
+		}
+		parentRootId = parentClassify.RootId
+		classifyInfo.ParentId = parentClassify.ClassifyId
+		classifyInfo.Level = parentClassify.Level + 1
+		updateCol = append(updateCol, "parent_id", "level")
+	}
+	//如果有传入 上一个兄弟节点分类id
+	if req.PrevClassifyId > 0 {
+		//上一个兄弟节点
+		prevClassify, er := edbmonitor.GetEdbMonitorClassifyById(req.PrevClassifyId)
+		if er != nil {
+			msg = "移动失败"
+			err = fmt.Errorf("获取下一个兄弟节点分类信息失败,Err:%w", er)
+			return
+		}
+
+		//如果是移动在两个兄弟节点之间
+		if req.NextClassifyId > 0 {
+			//下一个兄弟节点
+			nextClassify, er := edbmonitor.GetEdbMonitorClassifyById(req.NextClassifyId)
+			if er != nil {
+				msg = "移动失败"
+				err = fmt.Errorf("获取下一个兄弟节点分类信息失败,Err:%w", er)
+				return
+			}
+			//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+			if prevClassify.Sort == nextClassify.Sort || prevClassify.Sort == classifyInfo.Sort {
+				//变更兄弟节点的排序
+				updateSortStr := `sort + 2`
+				_ = edbmonitor.UpdateEdbMonitorClassifySortByParentId(prevClassify.ParentId, prevClassify.ClassifyId, prevClassify.Sort, updateSortStr)
+			} else {
+				//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+				if nextClassify.Sort-prevClassify.Sort == 1 {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 1`
+					_ = edbmonitor.UpdateEdbMonitorClassifySortByParentId(prevClassify.ParentId, 0, prevClassify.Sort, updateSortStr)
+				}
+			}
+		}
+
+		classifyInfo.Sort = prevClassify.Sort + 1
+		updateCol = append(updateCol, "Sort")
+
+	} else {
+		firstClassify, er := edbmonitor.GetFirstEdbMonitorClassifyByParentId(classifyInfo.ParentId)
+		if er != nil && er.Error() != utils.ErrNoRow() {
+			msg = "移动失败"
+			err = fmt.Errorf("获取父级分类下的排序第一条的分类信息失败,Err:%w", er)
+			return
+		}
+
+		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+		if firstClassify != nil && firstClassify.Sort == 0 {
+			updateSortStr := ` sort + 1 `
+			_ = edbmonitor.UpdateEdbMonitorClassifySortByParentId(firstClassify.ParentId, firstClassify.ClassifyId-1, 0, updateSortStr)
+		}
+
+		classifyInfo.Sort = 0 //那就是排在第一位
+		updateCol = append(updateCol, "Sort")
+	}
+	var classifyIds []int
+	if classifyInfo.Level == 2 {
+		classifyIds, er = edbmonitor.GetChildEdbMonitorClassifyIdById(classifyInfo.ClassifyId)
+		if er != nil {
+			msg = "分类移动失败"
+			err = fmt.Errorf("获取子分类id失败 err:%w", er)
+			return
+		}
+	}
+
+	if len(updateCol) > 0 {
+		if parentRootId != 0 {
+			classifyInfo.RootId = parentRootId
+		} else {
+			classifyInfo.RootId = req.ParentClassifyId
+		}
+		updateCol = append(updateCol, "root_id")
+		err = classifyInfo.Update(updateCol, classifyIds)
+		if err != nil {
+			msg = "移动失败"
+			return
+		}
+	}
+
+	return
+}
+
+func generateClassifyTree(classifyList []*edbmonitor.EdbMonitorClassify, parentId int) []*response.EdbMonitorClassifyTree {
+	res := make([]*response.EdbMonitorClassifyTree, 0)
+	for _, v := range classifyList {
+		if v.ParentId == parentId {
+			tmp := new(response.EdbMonitorClassifyTree)
+			tmp.ClassifyId = v.ClassifyId
+			tmp.ClassifyName = v.ClassifyName
+			tmp.Level = v.Level
+			tmp.Children = generateClassifyTree(classifyList, v.ClassifyId)
+			res = append(res, tmp)
+		}
+	}
+	return res
+}

+ 18 - 0
utils/config.go

@@ -31,6 +31,15 @@ var (
 	MgoDataDbName string       // mongodb指标数据的库名
 )
 
+// binlog
+var (
+	MYSQL_DATA_BINLOG_URL       string // 数据指标库binlog地址
+	MYSQL_DATA_BINLOG_USER      string // 数据指标库binlog用户名
+	MYSQL_DATA_BINLOG_PWD       string // 数据指标库binlog密码
+	MYSQL_DATA_BINLOG_DB        string // 数据指标库binlog位置
+	MYSQL_DATA_BINLOG_SERVER_ID string // 数据指标库binlog server_id
+)
+
 // 基础配置
 var (
 	STATIC_DIR       string
@@ -315,6 +324,15 @@ func init() {
 	//ETA-AI
 	MYSQL_AI_URL = config["mysql_url_ai"]
 
+	// binlog配置
+	{
+		MYSQL_DATA_BINLOG_URL = config["mysql_data_binlog_url"]
+		MYSQL_DATA_BINLOG_USER = config["mysql_data_binlog_user"]
+		MYSQL_DATA_BINLOG_PWD = config["mysql_data_binlog_pwd"]
+		MYSQL_DATA_BINLOG_DB = config["mysql_data_binlog_db"]
+		MYSQL_DATA_BINLOG_SERVER_ID = config["mysql_data_binlog_server_id"]
+	}
+
 	// mongodb数据库连接配置
 	MgoUrlData = config["mgo_url_data"]
 

+ 3 - 0
utils/constants.go

@@ -519,3 +519,6 @@ var (
 	BASE_START_DATE_UnSpace = "19000101"                                            //基础数据开始日期
 	BASE_END_DATE_UnSpace   = time.Now().AddDate(4, 0, 0).Format(FormatDateUnSpace) //基础数据结束日期
 )
+
+const CACHE_MYSQL_DATA_FILENAME = "eta:mysql:eta_index:binlog:filename"
+const CACHE_MYSQL_DATA_POSITION = "eta:mysql:eta_index:binlog:position"

+ 5 - 0
utils/redis.go

@@ -7,6 +7,8 @@ import (
 
 type RedisClient interface {
 	Get(key string) interface{}
+	GetStr(key string) string
+	GetUInt64(key string) (uint64, error)
 	RedisBytes(key string) (data []byte, err error)
 	RedisString(key string) (data string, err error)
 	RedisInt(key string) (data int, err error)
@@ -31,3 +33,6 @@ func initRedis(redisType string, conf string) (redisClient RedisClient, err erro
 
 	return
 }
+
+// redis没有key的错误
+const RedisNoKeyErr = "redis: nil"

+ 21 - 1
utils/redis/cluster_redis.go

@@ -5,9 +5,10 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/go-redis/redis/v8"
 	"strings"
 	"time"
+
+	"github.com/go-redis/redis/v8"
 )
 
 // ClusterRedisClient
@@ -86,6 +87,25 @@ func (rc *ClusterRedisClient) Get(key string) interface{} {
 	return data
 }
 
+// GetStr
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return string
+func (rc *ClusterRedisClient) GetStr(key string) string {
+	return rc.redisClient.Get(context.TODO(), key).Val()
+}
+
+// GetUInt64
+// @Description: 根据key获取uint64数据
+// @receiver rc
+// @param key
+// @return int
+// @return error
+func (rc *ClusterRedisClient) GetUInt64(key string) (uint64, error) {
+	return rc.redisClient.Get(context.TODO(), key).Uint64()
+}
+
 // RedisBytes
 // @Description: 根据key获取字节编码数据
 // @receiver rc

+ 21 - 1
utils/redis/standalone_redis.go

@@ -5,9 +5,10 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
-	"github.com/go-redis/redis/v8"
 	"strconv"
 	"time"
+
+	"github.com/go-redis/redis/v8"
 )
 
 // StandaloneRedisClient
@@ -78,6 +79,25 @@ func (rc *StandaloneRedisClient) Get(key string) interface{} {
 	return data
 }
 
+// GetStr
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return string
+func (rc *StandaloneRedisClient) GetStr(key string) string {
+	return rc.redisClient.Get(context.TODO(), key).Val()
+}
+
+// GetUInt64
+// @Description: 根据key获取uint64数据
+// @receiver rc
+// @param key
+// @return int
+// @return error
+func (rc *StandaloneRedisClient) GetUInt64(key string) (uint64, error) {
+	return rc.redisClient.Get(context.TODO(), key).Uint64()
+}
+
 // RedisBytes
 // @Description: 根据key获取字节编码数据
 // @receiver rc