Browse Source

Merge branch 'bzq1/monitor_custom' of eta_server/eta_api into master

baoziqiang 3 months ago
parent
commit
0efe0287ac

+ 398 - 0
controllers/edb_monitor/edb_monitor.go

@@ -0,0 +1,398 @@
+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   State   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.EdbInfoId <= 0 {
+		br.Msg = "请选择指标"
+		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, sysUser.AdminId)
+	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
+}
+
+// GetMonitorUser
+// @Title 预警管理用户列表
+// @Description 预警管理用户列表
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /monitor_user/list [get]
+func (m *EdbMonitorController) GetMonitorUser() {
+	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
+	}
+	resp, msg, err := edbmonitor.GetEdbMonitorInfoUserList()
+	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
+}

+ 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.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 {
+		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
+}

+ 251 - 0
controllers/edb_monitor/edb_monitor_message.go

@@ -0,0 +1,251 @@
+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"
+	"eta/eta_api/utils"
+	"net/http"
+	"strconv"
+	"time"
+
+	"github.com/gorilla/websocket"
+)
+
+type EdbMonitorMessageController struct {
+	controllers.BaseAuthController
+}
+
+var upgrader = websocket.Upgrader{
+	ReadBufferSize:  1024,
+	WriteBufferSize: 1024,
+	CheckOrigin: func(r *http.Request) bool {
+		return true
+	},
+}
+
+// GetMonitorLevel
+// @Title 预警管理消息
+// @Description 预警管理消息
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /message/connect [get]
+func (m *EdbMonitorMessageController) Connect() {
+	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 conn *websocket.Conn
+	connKey := edbmonitor.EDB_MONITOR_MESSAGE_CONNECT_CACHE + strconv.Itoa(sysUser.AdminId)
+	ok := utils.Rc.IsExist(connKey)
+	if !ok {
+		conn = edbmonitor.MonitorMessageConn[sysUser.AdminId]
+		if conn != nil {
+			conn.Close()
+		}
+	}
+	err := utils.Rc.Put(connKey, "1", time.Minute*1)
+	if err != nil {
+		br.Msg = "系统错误"
+		br.ErrMsg = "连接失败,err:" + err.Error()
+		return
+	}
+	conn, err = upgrader.Upgrade(m.Ctx.ResponseWriter, m.Ctx.Request, nil)
+	if err != nil {
+		br.Msg = "连接失败"
+		br.ErrMsg = "连接失败,err:" + err.Error()
+		return
+	}
+	defer conn.Close()
+
+	edbmonitor.MonitorMessageConn[sysUser.AdminId] = conn
+	conn.SetCloseHandler(func(code int, text string) error {
+		delete(edbmonitor.MonitorMessageConn, sysUser.AdminId)
+		utils.Rc.Delete(connKey)
+		return nil
+	})
+
+	go func() {
+		// 心跳检测
+		for {
+			isClose, err := edbmonitor.EdbMonitorMessageHealth(sysUser.AdminId)
+			if err != nil {
+				utils.FileLog.Error("指标预警信息健康检查失败,err:%s, adminId:%d", err.Error(), sysUser.AdminId)
+				return
+			}
+			if isClose {
+				return
+			}
+		}
+	}()
+
+	messageList, err := edbmonitor.GetHistoryMessages(sysUser.AdminId)
+	if err != nil {
+		utils.FileLog.Error("获取指标预警信息历史失败,err:%s, adminId:%d", err.Error(), sysUser.AdminId)
+	}
+	success := make(chan int, 10)
+	go func() {
+		defer close(success)
+		for i, msg := range messageList {
+			if i == 0 {
+				// 多条消息仅发送最新一条
+				err = edbmonitor.SendMessages(sysUser.AdminId, msg.EdbInfoId, msg.EdbInfoType, msg.EdbClassifyId, msg.EdbUniqueCode, msg.Message, msg.TriggerTime)
+				if err != nil {
+					utils.FileLog.Error("指标预警信息发送失败,err:%s, adminId:%d", err.Error(), sysUser.AdminId)
+				} else {
+					success <- msg.EdbMonitorMessageId
+				}
+			} else {
+				success <- msg.EdbMonitorMessageId
+			}
+		}
+	}()
+	go func() {
+		readList := make([]int, 0)
+		for {
+			msgId, ok := <-success
+			if !ok {
+				break
+			}
+			readList = append(readList, msgId)
+		}
+		_, err = edbmonitor.ReadEdbMonitorMessageList(readList, sysUser.AdminId)
+		if err != nil {
+			utils.FileLog.Error("指标预警信息已读失败,err:%s, adminId:%d", err.Error(), sysUser.AdminId)
+		}
+	}()
+
+	for {
+		ok = utils.Rc.IsExist(connKey)
+		if !ok {
+			br.Msg = "连接已断开"
+			br.Ret = 200
+			br.Success = true
+			return
+		}
+		time.Sleep(10 * time.Second)
+	}
+}
+
+// Close
+// @Title 预警管理消息
+// @Description 预警管理消息
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /message/close [post]
+func (m *EdbMonitorMessageController) 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
+	}
+
+	conn := edbmonitor.MonitorMessageConn[sysUser.AdminId]
+	if conn != nil {
+		conn.Close()
+	}
+
+	br.Msg = "关闭成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// List
+// @Title 预警管理消息列表
+// @Description 预警管理消息列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Success 200 {object} response.EdbMonitorClassifyTree
+// @router /message/list [get]
+func (c *EdbMonitorMessageController) 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
+	}
+
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	resp, err := edbmonitor.GetMessageList(sysUser.AdminId, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Data = resp
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// Read
+// @Title 预警管理消息已读
+// @Description 预警管理消息已读
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /message/read [post]
+func (m *EdbMonitorMessageController) Read() {
+	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.EdbMonitorMessageReadReq
+	if err := json.Unmarshal(m.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,err:" + err.Error()
+		return
+	}
+	if req.EdbMonitorMessageId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	msg, err := edbmonitor.ReadEdbMonitorMessage(req.EdbMonitorMessageId, sysUser.AdminId)
+	if err != nil {
+		if msg == "" {
+			msg = "系统错误"
+		}
+		br.Msg = msg
+		br.ErrMsg = "读取消息失败,err:" + err.Error()
+		return
+	}
+
+	br.Msg = "已读成功"
+	br.Ret = 200
+	br.Success = true
+}

+ 19 - 2
controllers/message.go

@@ -3,6 +3,7 @@ package controllers
 import (
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage/data_manage_permission"
+	edbmonitor "eta/eta_api/models/edb_monitor"
 	"eta/eta_api/models/report_approve"
 	"fmt"
 )
@@ -35,7 +36,7 @@ func (c *MessageController) UnReadMessageNum() {
 		return
 	}
 
-	var unReadReportNum, unReadDataPermissionNum int
+	var unReadReportNum, unReadDataPermissionNum, unReadEdbMonitorNum int
 
 	// 获取报告审批消息
 	{
@@ -75,9 +76,25 @@ func (c *MessageController) UnReadMessageNum() {
 		}
 		unReadDataPermissionNum = unreadTotal
 	}
+	// 获取预警消息
+	{
+		pars := make([]interface{}, 0)
+		cond := ` AND admin_id = ? AND is_read = ?`
+		pars = append(pars, sysUser.AdminId, 0)
+
+		// 未读消息数
+		messageOb := new(edbmonitor.EdbMonitorMessage)
+		unreadTotal, e := messageOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取资产消息列表总数失败, Err: " + e.Error()
+			return
+		}
+		unReadEdbMonitorNum = unreadTotal
+	}
 
 	// 汇总数
-	num := unReadReportNum + unReadDataPermissionNum
+	num := unReadReportNum + unReadDataPermissionNum + unReadEdbMonitorNum
 
 	br.Data = num
 	br.Ret = 200

+ 20 - 3
go.mod

@@ -20,10 +20,12 @@ require (
 	github.com/beevik/etree v1.3.0
 	github.com/dgrijalva/jwt-go v3.2.0+incompatible
 	github.com/go-ldap/ldap v3.0.3+incompatible
+	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.7.0
+	github.com/go-sql-driver/mysql v1.7.1
 	github.com/go-xorm/xorm v0.7.9
 	github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b
+	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
@@ -51,6 +53,8 @@ require (
 )
 
 require (
+	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 +73,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
@@ -76,6 +81,7 @@ require (
 	github.com/go-playground/locales v0.13.0 // indirect
 	github.com/go-playground/universal-translator v0.17.0 // indirect
 	github.com/go-playground/validator/v10 v10.4.1 // indirect
+	github.com/goccy/go-json v0.10.2 // indirect
 	github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
 	github.com/golang/snappy v0.0.1 // indirect
@@ -85,7 +91,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.0 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/hashicorp/hcl v1.0.0 // indirect
@@ -94,7 +99,7 @@ require (
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
-	github.com/klauspost/compress v1.17.6 // indirect
+	github.com/klauspost/compress v1.17.8 // indirect
 	github.com/klauspost/cpuid/v2 v2.2.6 // indirect
 	github.com/leodido/go-urn v1.2.0 // indirect
 	github.com/magiconair/properties v1.8.1 // indirect
@@ -110,16 +115,23 @@ require (
 	github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
 	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
 	github.com/pelletier/go-toml v1.9.2 // indirect
+	github.com/pingcap/errors v0.11.5-0.20221009092201-b66cddb77c32 // 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.16.0 // indirect
 	github.com/prometheus/client_model v0.3.0 // indirect
 	github.com/prometheus/common v0.42.0 // indirect
 	github.com/prometheus/procfs v0.10.1 // 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
 	github.com/rs/xid v1.5.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.3 // indirect
 	github.com/spf13/afero v1.1.2 // indirect
 	github.com/spf13/cast v1.5.0 // indirect
@@ -136,7 +148,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.11.0 // indirect
+	go.uber.org/multierr v1.11.0 // indirect
+	go.uber.org/zap v1.26.0 // indirect
 	golang.org/x/crypto v0.19.0 // indirect
+	golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect
 	golang.org/x/image v0.15.0 // indirect
 	golang.org/x/sync v0.2.0 // indirect
 	golang.org/x/sys v0.17.0 // indirect
@@ -146,6 +162,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 - 6
go.sum

@@ -16,9 +16,13 @@ cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2k
 cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
 dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
 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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/PuerkitoBio/goquery v1.9.1 h1:mTL6XjbJTZdpfL+Gwl5U2h1l9yEkJjhmlTeV9VPW7UI=
 github.com/PuerkitoBio/goquery v1.9.1/go.mod h1:cW1n6TmIMDoORQU5IU/P1T3tGFunOeXEpGP2WHRwkbY=
@@ -115,6 +119,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.3.0 h1:hQTc+pylzIKDb23yYprodCWWTt+ojFfUZyzU09a/hmU=
 github.com/beevik/etree v1.3.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc=
+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=
@@ -149,6 +154,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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -191,6 +198,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=
@@ -206,14 +215,16 @@ github.com/go-redis/redis/v8 v8.11.6-0.20220405070650-99c79f7041fc/go.mod h1:25m
 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=
-github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
-github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
+github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
 github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
 github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:9wScpmSP5A3Bk8V3XHWUcJmYTh+ZnlHVyc+A4oZYS3Y=
 github.com/go-xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:56xuuqnHyryaerycW3BfssRdxQstACi0Epw/yC5E2xM=
 github.com/go-xorm/xorm v0.7.9 h1:LZze6n1UvRmM5gpL9/U9Gucwqo6aWlFVlfcHKH10qA0=
 github.com/go-xorm/xorm v0.7.9/go.mod h1:XiVxrMMIhFkwSkh96BW7PACl7UhLtx2iJIHMdmjh5sQ=
+github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
+github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -352,8 +363,8 @@ github.com/kgiannakakis/mp3duration v0.0.0-20191013070830-d834f8d5ed53/go.mod h1
 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
 github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
-github.com/klauspost/compress v1.17.6 h1:60eq2E/jlfwQXtvZEeBUYADs+BwKBWURIY+Gj2eRGjI=
-github.com/klauspost/compress v1.17.6/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM=
+github.com/klauspost/compress v1.17.8 h1:YcnTYrq7MikUT7k0Yb5eceMmALQPYBW/Xltxn0NAMnU=
+github.com/klauspost/compress v1.17.8/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
 github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
 github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
@@ -458,6 +469,16 @@ github.com/pelletier/go-toml v1.9.2/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCko
 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=
@@ -498,6 +519,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=
@@ -517,6 +540,7 @@ github.com/ruudk/golang-pdf417 v0.0.0-20181029194003-1af4ab5afa58/go.mod h1:6lfF
 github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
 github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+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=
@@ -524,6 +548,10 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9Nz
 github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
 github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
 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=
@@ -554,8 +582,9 @@ github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5q
 github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
 github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -631,10 +660,23 @@ go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
-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/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.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+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.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+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-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -660,6 +702,8 @@ golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL
 golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
 golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+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=
@@ -820,6 +864,8 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
 golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 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-20191112195655-aa38f8e97acc/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=
@@ -892,6 +938,9 @@ gopkg.in/ini.v1 v1.66.2/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/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
 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=
@@ -905,6 +954,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 `orm:"column(Variable_name)"`
+	Value        string `orm:"column(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 `orm:"column(File)"`
+	Position uint32 `orm:"column(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    `orm:"column(id);pk"`
+	InteractionKey string    // 记录Key
+	InteractionVal string    // 记录值
+	Remark         string    // 备注
+	ModifyTime     time.Time // 修改日期
+	CreateTime     time.Time // 创建时间
+}
+
+// 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
+}

+ 3 - 0
models/data_manage/edb_info.go

@@ -507,6 +507,9 @@ func GetEdbDataCountByCondition(condition string, pars []interface{}, source, su
 func GetEdbDataListByCondition(condition string, pars []interface{}, source, subSource, pageSize, startSize int) (item []*EdbData, err error) {
 	o := orm.NewOrmUsingDB("data")
 	tableName := GetEdbDataTableName(source, subSource)
+	if source == utils.DATA_SOURCE_PREDICT {
+		tableName = "edb_data_predict_base"
+	}
 	sql := ` SELECT * FROM %s WHERE 1=1 `
 	sql = fmt.Sprintf(sql, tableName)
 

+ 26 - 0
models/db.go

@@ -4,6 +4,7 @@ import (
 	"eta/eta_api/models/ai_summary"
 	"eta/eta_api/models/aimod"
 	"eta/eta_api/models/bi_dashboard"
+	binlogDao "eta/eta_api/models/binlog"
 	"eta/eta_api/models/company"
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/data_manage/chart_theme"
@@ -14,6 +15,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"
@@ -25,6 +27,7 @@ import (
 	"eta/eta_api/models/speech_recognition"
 	"eta/eta_api/models/system"
 	"eta/eta_api/models/yb"
+	binlogSvr "eta/eta_api/services/binlog"
 	"eta/eta_api/utils"
 	"time"
 
@@ -205,6 +208,14 @@ func init() {
 	// 初始化因子指标系列
 	initFactorEdbSeries()
 
+	// 初始化指标监控
+	initEdbMonitor()
+
+	// 开启mysql binlog监听
+	if utils.MYSQL_DATA_BINLOG_URL != "" {
+		initBinlog()
+		go binlogSvr.ListenMysql()
+	}
 	// 初始化部分数据表变量(直接init会有顺序问题=_=!)
 	afterInitTable()
 
@@ -640,6 +651,21 @@ func initFactorEdbSeries() {
 	)
 }
 
+// 预警管理
+func initEdbMonitor() {
+	orm.RegisterModel(
+		new(edbmonitor.EdbMonitorInfo),     // 预警管理表
+		new(edbmonitor.EdbMonitorClassify), // 预警管理分类表
+		new(edbmonitor.EdbMonitorMessage),  // 预警管理消息表
+	)
+}
+
+func initBinlog() {
+	orm.RegisterModel(
+		new(binlogDao.BusinessSysInteractionLog), // binlog表
+	)
+}
+
 // initBiDashBoard 智能看板
 func initBiDashBoard() {
 	orm.RegisterModel(

+ 129 - 0
models/edb_monitor/edb_monitor.go

@@ -0,0 +1,129 @@
+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:"指标类型"`
+	EdbUniqueCode        string    `description:"指标唯一标识"`
+	EdbClassifyId        int       `description:"指标id"`
+	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 GetEdbMonitorEdbInfoList() (items []*EdbMonitorInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM edb_monitor_info WHERE state <> 0`
+	_, err = o.Raw(sql).QueryRows(&items)
+	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 GetEdbMonitorInfoByEdbInfoId(edbInfoId int) (items []*EdbMonitorInfo, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT * FROM edb_monitor_info WHERE edb_info_id =?`
+	_, err = o.Raw(sql, edbInfoId).QueryRows(&items)
+	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
+}
+
+func GetEdbMonitorCreateUserId() (userIds []int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := `SELECT DISTINCT create_user_id FROM edb_monitor_info`
+	_, err = o.Raw(sql).QueryRows(&userIds)
+	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
+}

+ 79 - 0
models/edb_monitor/edb_monitor_message.go

@@ -0,0 +1,79 @@
+package edbmonitor
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type EdbMonitorMessage struct {
+	EdbMonitorMessageId int       `orm:"column(edb_monitor_message_id);pk"`
+	EdbInfoId           int       `description:"指标id"`
+	EdbInfoType         int       `description:"指标类型: 0-普通指标;1-预测指标"`
+	EdbUniqueCode       string    `description:"指标唯一标识"`
+	EdbClassifyId       int       `description:"指标id"`
+	AdminId             int       `description:"用户id"`
+	IsRead              int       `description:"是否已读: 0-未读;1-已读"`
+	Message             string    `description:"消息内容"`
+	MonitorTriggerTime  time.Time `description:"预警触发时间"`
+	CreateTime          time.Time `description:"创建时间"`
+}
+
+func (m *EdbMonitorMessage) Insert() (insertId int64, err error) {
+	o := orm.NewOrmUsingDB("data")
+	insertId, err = o.Insert(m)
+	return
+}
+
+func (m *EdbMonitorMessage) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func BatchModifyEdbMonitorMessageIsRead(ids []int, adminId int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := `UPDATE edb_monitor_message SET is_read =1 WHERE admin_id =? AND is_read = 0 AND edb_monitor_message_id IN (` + utils.GetOrmInReplace(len(ids)) + `)`
+	_, err = o.Raw(sql, adminId, ids).Exec()
+	return
+}
+
+func (m *EdbMonitorMessage) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM edb_monitor_message WHERE 1=1 %s`, condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func GetEdbMonitorMessageById(id int) (item *EdbMonitorMessage, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM edb_monitor_message WHERE edb_monitor_message_id =?"
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func GetEdbMonitorMessageByAdminId(adminId int) (items []*EdbMonitorMessage, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM edb_monitor_message WHERE admin_id =? AND is_read = 0 ORDER BY create_time DESC"
+	_, err = o.Raw(sql, adminId).QueryRows(&items)
+	return
+}
+
+func GetEdbMonitorMessageCountByAdminId(adminId int) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT COUNT(*) FROM edb_monitor_message WHERE admin_id =? ORDER BY is_read ASC, create_time DESC"
+	err = o.Raw(sql, adminId).QueryRow(&count)
+	return
+}
+
+func GetEdbMonitorMessagePageByAdminId(adminId, startSize, pageSize int) (items []*EdbMonitorMessage, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := "SELECT * FROM edb_monitor_message WHERE admin_id =? ORDER BY is_read ASC, create_time DESC LIMIT?,?"
+	_, err = o.Raw(sql, adminId, startSize, pageSize).QueryRows(&items)
+	return
+}

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

@@ -0,0 +1,25 @@
+package request
+
+type EdbMonitorInfoSaveReq struct {
+	EdbMonitorId   int    `description:"预警ID"`
+	EdbInfoId      int    `description:"指标ID"`
+	EdbUniqueCode  string `description:"指标唯一标识"`
+	EdbClassifyId  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"`
+}

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

@@ -0,0 +1,19 @@
+package request
+
+type EdbMonitorClassifySaveReq struct {
+	ClassifyId   int
+	ClassifyName string
+	Level        int
+	ParentId     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"`
+}

+ 5 - 0
models/edb_monitor/request/edb_monitor_message.go

@@ -0,0 +1,5 @@
+package request
+
+type EdbMonitorMessageReadReq struct {
+	EdbMonitorMessageId int `description:"预警消息id"`
+}

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

@@ -0,0 +1,46 @@
+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"`
+	EdbInfoName            string  `description:"指标名称"`
+	EdbInfoType            int     `description:"指标类型"`
+	EdbUniqueCode          string  `description:"指标唯一标识"`
+	EdbClassifyId          int     `description:"指标id"`
+	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 EdbMonitorCreateUserItem struct {
+	AdminId  int    `description:"管理员id"`
+	RealName string `description:"管理员姓名"`
+}
+
+type EdbMonitorInfoCreateUserResp struct {
+	List []*EdbMonitorCreateUserItem `description:"创建人信息"`
+}
+
+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:"子分类"`
+}

+ 19 - 0
models/edb_monitor/response/edb_monitor_message.go

@@ -0,0 +1,19 @@
+package response
+
+import "github.com/rdlucklib/rdluck_tools/paging"
+
+type EdbMonitorMessageResp struct {
+	EdbMonitorMessageId int    `description:"消息id"`
+	EdbInfoId           int    `description:"指标id"`
+	EdbInfoType         int    `description:"指标类型:0-普通指标;1-预测指标"`
+	EdbUniqueCode       string `description:"指标唯一标识"`
+	EdbClassifyId       int    `description:"指标id"`
+	IsRead              int    `description:"是否已读:0-未读;1-已读"`
+	Message             string `description:"消息内容"`
+	TriggerTime         string `description:"触发时间"`
+}
+
+type EdbMonitorMessageListResp struct {
+	List   []*EdbMonitorMessageResp
+	Paging *paging.PagingItem
+}

+ 13 - 2
models/system/sys_admin.go

@@ -3,10 +3,11 @@ package system
 import (
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/beego/beego/v2/client/orm"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"strings"
 	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 type AdminItem struct {
@@ -45,6 +46,16 @@ type AdminItem struct {
 	IsLdap                  int    `description:"是否为域用户:0-系统账户;1-域用户"`
 }
 
+func GetAdminItemByIdList(adminId []int) (items []*AdminItem, err error) {
+	if len(adminId) == 0 {
+		return
+	}
+	o := orm.NewOrm()
+	sql := `SELECT * FROM admin WHERE admin_id IN (` + utils.GetOrmInReplace(len(adminId)) + `) ORDER BY created_time DESC`
+	_, err = o.Raw(sql, adminId).QueryRows(&items)
+	return
+}
+
 func GetSysuserList(condition string, pars []interface{}, startSize, pageSize int) (items []*AdminItem, err error) {
 	o := orm.NewOrm()
 	sql := `SELECT * FROM admin WHERE 1=1 `

+ 153 - 0
routers/commentsRouter.go

@@ -6658,6 +6658,159 @@ 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: "GetMonitorUser",
+            Router: `/monitor_user/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/edb_monitor:EdbMonitorMessageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"],
+        beego.ControllerComments{
+            Method: "Close",
+            Router: `/message/close`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"],
+        beego.ControllerComments{
+            Method: "Connect",
+            Router: `/message/connect`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/message/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"],
+        beego.ControllerComments{
+            Method: "Read",
+            Router: `/message/read`,
+            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",

+ 8 - 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"
@@ -397,6 +398,13 @@ func init() {
 				&document_manage.DocumentManageController{},
 			),
 		),
+		web.NSNamespace("/edb_monitor",
+			web.NSInclude(
+				&edb_monitor.EdbMonitorController{},
+				&edb_monitor.EdbMonitorClassifyController{},
+				&edb_monitor.EdbMonitorMessageController{},
+			),
+		),
 		web.NSNamespace("/bi_dashborad",
 			web.NSInclude(
 				&controllers.BIDaShboardController{},

+ 271 - 0
services/binlog/binlog.go

@@ -0,0 +1,271 @@
+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()
+	// 同步到redis
+	go binlogHandler.SyncToRedis()
+
+	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中获取最新的binlog文件名称和位置,名称不一致则以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)
+		}
+	}
+}

+ 203 - 0
services/binlog/handler.go

@@ -0,0 +1,203 @@
+package binlog
+
+import (
+	edbmonitorSvr "eta/eta_api/services/edb_monitor"
+	"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/go-mysql-org/go-mysql/schema"
+)
+
+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)
+		if err != nil {
+			utils.FileLog.Info("binlog insert error:", err)
+		}
+	case canal.UpdateAction:
+		err = h.Update(e)
+		if err != nil {
+			utils.FileLog.Info("binlog update error:", err)
+		}
+	default:
+		return nil
+	}
+
+	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
+
+	return nil
+}
+
+func (h *EdbEventHandler) SyncToRedis() {
+	for {
+		// 旋转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)
+		time.Sleep(3 * time.Second)
+	}
+}
+
+func (h *EdbEventHandler) String() string {
+	return "EdbEventHandler"
+}
+
+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)
+		newEdbInfo := h.MapRowToStruct(e.Table.Columns, row)
+		if ok := edbmonitorSvr.EdbLocalSet.IsExist(newEdbInfo.EdbInfoId); ok {
+			err := utils.Rc.LPush(edbmonitorSvr.EDB_MONITOR_HANDLE_LIST_CACHE, newEdbInfo)
+			if err != nil {
+				return err
+			}
+		} else {
+			ok, err := utils.Rc.SIsMember(edbmonitorSvr.EDB_MONITOR_ID_SET_CACHE, newEdbInfo)
+			if err != nil {
+				return err
+			}
+			if ok {
+				err := utils.Rc.LPush(edbmonitorSvr.EDB_MONITOR_HANDLE_LIST_CACHE, newEdbInfo)
+				if err != nil {
+					return err
+				}
+			}
+		}
+		// if monitors, ok := edbMonitorMap[newEdbInfo.EdbInfoId]; ok {
+		// 	for _, monitor := range monitors {
+		// 		err = edbmonitorSvr.ModifyEdbMonitorState(monitor, newEdbInfo.EdbCode, newEdbInfo.Source, newEdbInfo.SubSource)
+		// 		if err != nil {
+		// 			continue
+		// 		}
+		// 	}
+		// }
+	}
+	return nil
+}
+
+func (h *EdbEventHandler) Update(e *canal.RowsEvent) error {
+	if len(e.Rows) != 2 {
+		fmt.Println("更新数据异常,没有原始数据和新数据:", e.Rows)
+		return nil
+	}
+	oldEdbInfo := h.MapRowToStruct(e.Table.Columns, e.Rows[0])
+	newEdbInfo := h.MapRowToStruct(e.Table.Columns, e.Rows[1])
+	if oldEdbInfo.EndValue != newEdbInfo.EndValue {
+		if ok := edbmonitorSvr.EdbLocalSet.IsExist(newEdbInfo.EdbInfoId); ok {
+			err := utils.Rc.LPush(edbmonitorSvr.EDB_MONITOR_HANDLE_LIST_CACHE, newEdbInfo)
+			if err != nil {
+				return err
+			}
+		} else {
+			ok, err := utils.Rc.SIsMember(edbmonitorSvr.EDB_MONITOR_ID_SET_CACHE, newEdbInfo)
+			if err != nil {
+				return err
+			}
+			if ok {
+				err := utils.Rc.LPush(edbmonitorSvr.EDB_MONITOR_HANDLE_LIST_CACHE, newEdbInfo)
+				if err != nil {
+					return err
+				}
+			}
+		}
+	}
+
+	// if monitors, ok := edbMonitorMap[newEdbInfo.EdbInfoId]; ok {
+	// 	for _, monitor := range monitors {
+	// 		err = edbmonitorSvr.ModifyEdbMonitorState(monitor, newEdbInfo.EdbCode, newEdbInfo.Source, newEdbInfo.SubSource)
+	// 		if err != nil {
+	// 			continue
+	// 		}
+	// 	}
+	// }
+	return nil
+}
+
+func (h *EdbEventHandler) MapRowToStruct(columns []schema.TableColumn, row []interface{}) edbmonitorSvr.EdbInfoBingLog {
+	newEdbInfo := edbmonitorSvr.EdbInfoBingLog{}
+	for i, column := range columns {
+		value := reflect.ValueOf(row[i])
+		switch column.Name {
+		case "edb_info_id":
+			newEdbInfo.EdbInfoId = int(value.Int())
+		case "edb_info_type":
+			newEdbInfo.EdbInfoType = int(value.Uint())
+		case "source":
+			newEdbInfo.Source = int(value.Int())
+		case "edb_code":
+			newEdbInfo.EdbCode = value.String()
+		case "start_date":
+			newEdbInfo.StartDate = value.String()
+		case "end_date":
+			newEdbInfo.EndDate = value.String()
+		case "unique_code":
+			newEdbInfo.UniqueCode = value.String()
+		case "create_time":
+			newEdbInfo.CreateTime = value.String()
+		case "modify_time":
+			newEdbInfo.ModifyTime = value.String()
+		case "base_modify_time":
+			if value.IsValid() {
+				newEdbInfo.BaseModifyTime = value.String()
+			}
+		case "min_value":
+			newEdbInfo.MinValue = value.Float()
+		case "max_value":
+			newEdbInfo.MaxValue = value.Float()
+		case "latest_date":
+			newEdbInfo.LatestDate = value.String()
+		case "latest_value":
+			newEdbInfo.LatestValue = value.Float()
+		case "end_value":
+			newEdbInfo.EndValue = value.Float()
+		case "data_update_time":
+			newEdbInfo.DataUpdateTime = value.String()
+		case "er_data_update_date":
+			newEdbInfo.ErDataUpdateDate = value.String()
+		case "sub_source":
+			newEdbInfo.SubSource = int(value.Int())
+		default:
+			continue
+		}
+	}
+	return newEdbInfo
+}
+
+// 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)
+}

+ 38 - 0
services/edb_monitor/constants.go

@@ -0,0 +1,38 @@
+package edbmonitor
+
+const EDB_MONITOR_ID_SET_CACHE = "eta:edb_monitor:monitor:id1"
+const EDB_MONITOR_HANDLE_LIST_CACHE = "eta:edb_monitor:handle:id1"
+
+// 预警触发状态
+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
+)
+
+type EdbInfoBingLog struct {
+	EdbInfoId        int
+	EdbInfoType      int    `description:"指标类型,0:普通指标,1:预测指标"`
+	Source           int    `description:"来源id"`
+	EdbCode          string `description:"指标编码"`
+	StartDate        string `description:"起始日期"`
+	EndDate          string `description:"终止日期"`
+	UniqueCode       string `description:"指标唯一编码"`
+	CreateTime       string
+	ModifyTime       string
+	BaseModifyTime   string
+	MinValue         float64 `description:"指标最小值"`
+	MaxValue         float64 `description:"指标最大值"`
+	LatestDate       string  `description:"数据最新日期(实际日期)"`
+	LatestValue      float64 `description:"数据最新值(实际值)"`
+	EndValue         float64 `description:"数据的最新值(预测日期的最新值)"`
+	DataUpdateTime   string  `description:"最近一次数据发生变化的时间"`
+	ErDataUpdateDate string  `description:"本次更新,数据发生变化的最早日期"`
+	SubSource        int     `description:"子数据来源:0:经济数据库,1:日期序列"`
+}

+ 637 - 0
services/edb_monitor/edb_monitor.go

@@ -0,0 +1,637 @@
+package edbmonitor
+
+import (
+	"encoding/json"
+	"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"
+	"sync"
+	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type SafeEdbMonitorSet struct {
+	m  map[int]struct{}
+	mu sync.Mutex
+}
+
+func NewSafeEdbMonitorSet() *SafeEdbMonitorSet {
+	return &SafeEdbMonitorSet{
+		m: make(map[int]struct{}),
+	}
+}
+
+func (e *SafeEdbMonitorSet) Add(id int) {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+	e.m[id] = struct{}{}
+}
+
+func (e *SafeEdbMonitorSet) IsExist(id int) (ok bool) {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+	_, ok = e.m[id]
+	return
+}
+
+func (e *SafeEdbMonitorSet) Remove(id int) {
+	e.mu.Lock()
+	defer e.mu.Unlock()
+	delete(e.m, id)
+}
+
+var EdbLocalSet = NewSafeEdbMonitorSet()
+
+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 []string
+	if level != "" {
+		levelList = strings.Split(level, ",")
+	}
+	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
+	}
+	var edbInfoId, edbUserId []int
+	for _, v := range edbMonitorList {
+		edbUserId = append(edbUserId, v.CreateUserId)
+		edbInfoId = append(edbInfoId, v.EdbInfoId)
+	}
+	edbInfoList, err := data_manage.GetEdbInfoByIdList(edbInfoId)
+	if err != nil {
+		msg = "获取监控列表失败"
+		err = fmt.Errorf("GetEdbInfoByIdList err:%w", err)
+		return
+	}
+	infoMap := make(map[int]string)
+	for _, v := range edbInfoList {
+		infoMap[v.EdbInfoId] = v.EdbName
+	}
+	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, infoMap)
+	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 {
+		ok, msg1, er := OperatePermissionCheck(req.EdbMonitorId, adminId)
+		if !ok {
+			if msg1 == "" {
+				msg1 = "编辑失败"
+			}
+			msg = msg1
+			if er == nil {
+				er = fmt.Errorf("权限校验失败")
+			}
+			err = er
+			return
+		}
+		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", "edb_classify_id", "edb_unique_code", "source", "sub_source", "edb_latest_date", "edb_latest_value"}...)
+			edbMonitorInfo.EdbInfoId = req.EdbInfoId
+			edbMonitorInfo.EdbInfoType = edb.EdbInfoType
+			edbMonitorInfo.EdbCode = edb.EdbCode
+			edbMonitorInfo.EdbClassifyId = edb.ClassifyId
+			edbMonitorInfo.EdbUniqueCode = edb.UniqueCode
+			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.EdbUniqueCode = edb.UniqueCode
+		edbMonitorInfo.EdbClassifyId = edb.ClassifyId
+		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 = utils.Rc.SAdd(EDB_MONITOR_ID_SET_CACHE, edbMonitorInfo.EdbInfoId)
+	if err != nil {
+		utils.FileLog.Info("监控指标添加缓存失败", err)
+	}
+	EdbLocalSet.Add(edbMonitorInfo.EdbInfoId)
+	err = ModifyEdbMonitorStateAndSendMsg(edbMonitorInfo, edb.EdbCode, edb.Source, edb.SubSource)
+	if err != nil {
+		msg = "更新指标预警失败"
+		err = fmt.Errorf("ModifyEdbMonitorState err:%w", err)
+		return
+	}
+	return
+}
+
+func ModifyEdbMonitorStateAndSendMsg(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, 2, 0)
+	if er != nil {
+		err = fmt.Errorf("GetEdbDataListByCondition err:%w", er)
+		return
+	}
+	state, err := ModifyEdbMonitorState(edbMonitorInfo, latestTwoData)
+	if err != nil {
+		return
+	}
+
+	if state == EDB_MONITOR_STATE_TRIGGER_SUCCESS {
+		SendAndLogMessage(edbMonitorInfo)
+	}
+	return
+}
+
+func GetTwoEdbDataByDataTime(edbCode, dataTime string, source, subSource int) (twoData []*data_manage.EdbData, err error) {
+	cond := ` AND edb_code = ? AND data_time <= ? `
+	pars := []interface{}{edbCode, dataTime}
+	twoData, er := data_manage.GetEdbDataListByCondition(cond, pars, source, subSource, 2, 0)
+	if er != nil {
+		err = fmt.Errorf("GetEdbDataListByCondition err:%w", er)
+		return
+	}
+	return
+}
+
+func ModifyEdbMonitorState(edbMonitorInfo *edbmonitor.EdbMonitorInfo, latestTwoData []*data_manage.EdbData) (state int, err error) {
+	var updateCols []string
+	edbMonitorInfo.EdbLatestDate = latestTwoData[0].DataTime
+	edbMonitorInfo.EdbLatestValue = latestTwoData[0].Value
+	updateCols = append(updateCols, []string{"edb_latest_date", "edb_latest_value"}...)
+
+	state = cmpEdbMonitorState(latestTwoData, edbMonitorInfo.MonitorType, edbMonitorInfo.MonitorData)
+	if state == EDB_MONITOR_STATE_TRIGGER_SUCCESS {
+		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"}...)
+	}
+	if edbMonitorInfo.State == EDB_MONITOR_STATE_NO_TRIGGER || edbMonitorInfo.State == EDB_MONITOR_STATE_CLOSE {
+		edbMonitorInfo.State = state
+		updateCols = append(updateCols, "state")
+	}
+	err = edbMonitorInfo.Update(updateCols)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+func SendAndLogMessage(edbMonitorInfo *edbmonitor.EdbMonitorInfo) (err error) {
+	triggerTime := edbMonitorInfo.MonitorTriggerTime.Format(utils.FormatDateTime)
+	err = SendMessages(edbMonitorInfo.CreateUserId, edbMonitorInfo.EdbInfoId, edbMonitorInfo.EdbInfoType, edbMonitorInfo.EdbClassifyId, edbMonitorInfo.EdbUniqueCode, edbMonitorInfo.EdbMonitorName, triggerTime)
+	isRead := 1
+	if err != nil {
+		err = nil
+		isRead = 0
+	}
+	err = LogMessage(edbMonitorInfo.EdbMonitorName, edbMonitorInfo.EdbUniqueCode, edbMonitorInfo.MonitorTriggerTime, edbMonitorInfo.EdbInfoId, edbMonitorInfo.EdbInfoType, edbMonitorInfo.CreateUserId, isRead, edbMonitorInfo.EdbClassifyId)
+	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 OperatePermissionCheck(edbMonitorId int, adminId int) (ok bool, msg string, err error) {
+	monitorInfo, err := edbmonitor.GetEdbMonitorInfoById(edbMonitorId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			msg = "指标预警不存在,请刷新重试"
+			return
+		}
+		msg = "删除失败"
+		err = fmt.Errorf("获取预警详情失败, err:%w", err)
+		return
+	}
+	if monitorInfo.CreateUserId != adminId {
+		userInfo, er := system.GetSysAdminById(adminId)
+		if er != nil {
+			msg = "删除失败"
+			err = fmt.Errorf("查询用户信息失败 err:%w", er)
+			return
+		}
+		if userInfo.Role != utils.ROLE_TYPE_CODE_ADMIN {
+			msg = "无权限操作"
+			err = fmt.Errorf("无权限操作")
+			return
+		}
+	}
+	ok = true
+	return
+}
+
+func DeleteEdbMonitorInfo(req request.EdbMonitorInfoDeleteReq, adminId int) (msg string, err error) {
+	ok, msg, err := OperatePermissionCheck(req.EdbMonitorId, adminId)
+	if !ok {
+		if msg == "" {
+			msg = "删除失败"
+		}
+		if err == nil {
+			err = fmt.Errorf("权限校验失败")
+		}
+		return
+	}
+
+	err = edbmonitor.DeleteEdbMonitorInfoById(req.EdbMonitorId)
+	if err != nil {
+		msg = "删除失败"
+		err = fmt.Errorf("删除预警失败 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
+	}
+	ok, msg, err := OperatePermissionCheck(req.EdbMonitorId, adminId)
+	if !ok {
+		if msg == "" {
+			msg = "删除失败"
+		}
+		if err == nil {
+			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
+	}
+	ok, msg, err := OperatePermissionCheck(req.EdbMonitorId, adminId)
+	if !ok {
+		if msg == "" {
+			msg = "删除失败"
+		}
+		if err == nil {
+			err = fmt.Errorf("权限校验失败")
+		}
+		return
+	}
+	if edbMonitor.State != EDB_MONITOR_STATE_CLOSE {
+		msg = "预警未关闭,无需重启"
+		err = fmt.Errorf("预警未关闭,无需重启")
+		return
+	}
+	edbMonitor.EdbTriggerDate = time.Time{}
+	edbMonitor.MonitorTriggerTime = time.Time{}
+	edbMonitor.ModifyTime = time.Now()
+	err = ModifyEdbMonitorStateAndSendMsg(edbMonitor, edbMonitor.EdbCode, edbMonitor.Source, edbMonitor.SubSource)
+	if err != nil {
+		msg = "重启失败"
+		err = fmt.Errorf("修改预警状态失败, err:%w", err)
+		return
+	}
+	return
+}
+
+func GetEdbMonitorInfoUserList() (resp response.EdbMonitorInfoCreateUserResp, msg string, err error) {
+	userIdList, err := edbmonitor.GetEdbMonitorCreateUserId()
+	if err != nil {
+		msg = "获取用户列表失败"
+		return
+	}
+	adminList, err := system.GetAdminItemByIdList(userIdList)
+	if err != nil {
+		msg = "获取用户列表失败"
+		return
+	}
+	createUserItem := make([]*response.EdbMonitorCreateUserItem, 0)
+	for _, v := range adminList {
+		tmp := new(response.EdbMonitorCreateUserItem)
+		tmp.AdminId = v.AdminId
+		tmp.RealName = v.RealName
+		createUserItem = append(createUserItem, tmp)
+	}
+	resp.List = createUserItem
+	return
+}
+
+func InitEdbMonitorInfo() {
+	edbMonitorList, err := edbmonitor.GetEdbMonitorEdbInfoList()
+	if err != nil {
+		utils.FileLog.Error("获取预警列表失败, err:%w", err)
+	}
+	edbInfoIdList := make([]interface{}, 0)
+	for _, v := range edbMonitorList {
+		if v.EdbInfoId != 0 {
+			EdbLocalSet.Add(v.EdbInfoId)
+			edbInfoIdList = append(edbInfoIdList, v.EdbInfoId)
+		}
+	}
+	utils.Rc.SAdd(EDB_MONITOR_ID_SET_CACHE, edbInfoIdList...)
+}
+
+func HandleEdbMonitorEdbInfo() {
+	InitEdbMonitorInfo()
+	for {
+		utils.Rc.Brpop(EDB_MONITOR_HANDLE_LIST_CACHE, func(b []byte) {
+			edbInfo := new(EdbInfoBingLog)
+			if err := json.Unmarshal(b, &edbInfo); err != nil {
+				return
+			}
+			edbMonitorList, err := edbmonitor.GetEdbMonitorInfoByEdbInfoId(edbInfo.EdbInfoId)
+			if err != nil {
+				utils.FileLog.Error("获取预警列表失败, err:%w, edbInfoId:%d", err, edbInfo.EdbInfoId)
+				return
+			}
+			if len(edbMonitorList) == 0 {
+				utils.Rc.SRem(EDB_MONITOR_ID_SET_CACHE, edbInfo.EdbInfoId)
+				EdbLocalSet.Remove(edbInfo.EdbInfoId)
+				return
+			}
+			for _, v := range edbMonitorList {
+				twoData, err := GetTwoEdbDataByDataTime(v.EdbCode, edbInfo.EndDate, v.Source, v.SubSource)
+				if err != nil {
+					utils.FileLog.Error("获取数据失败, err:%w, edbInfoId:%d", err, edbInfo.EdbInfoId)
+					continue
+				}
+				state, err := ModifyEdbMonitorState(v, twoData)
+				if err != nil {
+					utils.FileLog.Error("更新预警状态失败, err:%w, edbMonitorId:%d", err, v.EdbMonitorId)
+					continue
+				}
+				if state == EDB_MONITOR_STATE_TRIGGER_SUCCESS {
+					err = SendAndLogMessage(v)
+					if err != nil {
+						utils.FileLog.Error("发送预警消息失败, err:%w, edbMonitorId:%d", err, v.EdbMonitorId)
+						continue
+					}
+				}
+			}
+		})
+	}
+}
+func toEdbMonitorInfoItems(edbmonitor []*edbmonitor.EdbMonitorInfo, userMap, classifyPathMap, infoMap 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.EdbInfoName = infoMap[v.EdbInfoId]
+		tmp.EdbUniqueCode = v.EdbUniqueCode
+		tmp.EdbClassifyId = v.EdbClassifyId
+		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
+}

+ 354 - 0
services/edb_monitor/edb_monitor_classify.go

@@ -0,0 +1,354 @@
+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 parentClassify *edbmonitor.EdbMonitorClassify
+	if req.ParentId > 0 {
+		parentClassify, er = edbmonitor.GetEdbMonitorClassifyById(req.ParentId)
+		if er != nil {
+			if er.Error() == utils.ErrNoRow() {
+				msg = "父级分类不存在"
+				err = errors.New("parent classify not exist")
+				return
+			}
+			msg = "分类信息保存失败"
+			err = fmt.Errorf("get parent classify err:%w", er)
+			return
+		}
+	}
+	var rootId int
+	if parentClassify != nil {
+		rootId = parentClassify.RootId
+		if rootId == 0 {
+			rootId = parentClassify.ParentId
+		}
+		if rootId == 0 {
+			rootId = parentClassify.ClassifyId
+		}
+	}
+
+	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")
+		}
+
+		for _, v := range childClassify {
+			if v.ClassifyName == req.ClassifyName {
+				msg = "分类名称重复"
+				err = errors.New("classify name repeat")
+				return
+			}
+		}
+		childClassifyIds, er := edbmonitor.GetChildEdbMonitorClassifyIdById(req.ClassifyId)
+		if er != nil {
+			msg = "分类信息保存失败"
+			err = fmt.Errorf("get child classify id err:%w", er)
+			return
+		}
+
+		if classifyInfo.RootId != rootId {
+			classifyInfo.RootId = 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 = 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 = "分类下存在关联预警,不能删除"
+		err = fmt.Errorf("分类下存在关联预警,不能删除")
+		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
+}

+ 158 - 0
services/edb_monitor/edb_monitor_message.go

@@ -0,0 +1,158 @@
+package edbmonitor
+
+import (
+	"errors"
+	edbmonitor "eta/eta_api/models/edb_monitor"
+	"eta/eta_api/models/edb_monitor/response"
+	"eta/eta_api/utils"
+	"strconv"
+	"time"
+
+	"github.com/gorilla/websocket"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+var (
+	EDB_MONITOR_MESSAGE_CONNECT_CACHE = "edb_monitor_message_cache:"
+)
+
+var MonitorMessageConn = make(map[int]*websocket.Conn)
+
+func ReadEdbMonitorMessage(messageId, adminId int) (msg string, err error) {
+	message, err := edbmonitor.GetEdbMonitorMessageById(messageId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			msg = "消息不存在"
+			return
+		}
+		msg = "获取消息失败"
+		return
+	}
+	if message.AdminId != adminId {
+		msg = "您没有权限查看该消息"
+		err = errors.New("no permission")
+		return
+	}
+	message.IsRead = 1
+	err = message.Update([]string{"IsRead"})
+	if err != nil {
+		msg = "已读失败"
+		return
+	}
+	return
+}
+
+func ReadEdbMonitorMessageList(messageId []int, adminId int) (msg string, err error) {
+	err = edbmonitor.BatchModifyEdbMonitorMessageIsRead(messageId, adminId)
+	if err != nil {
+		msg = "已读失败"
+		return
+	}
+	return
+}
+
+func EdbMonitorMessageHealth(adminId int) (isClose bool, err error) {
+	conn := MonitorMessageConn[adminId]
+	if conn == nil {
+		err = errors.New("no connection")
+		isClose = true
+		return
+	}
+	_, msg, err := conn.ReadMessage()
+	if err != nil {
+		isClose = true
+		return
+	}
+	if string(msg) == "ping" {
+		healthKey := EDB_MONITOR_MESSAGE_CONNECT_CACHE + strconv.Itoa(adminId)
+		err = utils.Rc.Put(healthKey, "1", time.Minute*1)
+		if err != nil {
+			return
+		}
+	}
+	return
+}
+
+func LogMessage(content, uniqueCode string, triggerTime time.Time, edbInfoId, edbInfoType, adminId, isRead, classifyId int) (err error) {
+	message := &edbmonitor.EdbMonitorMessage{
+		EdbInfoId:          edbInfoId,
+		EdbInfoType:        edbInfoType,
+		EdbUniqueCode:      uniqueCode,
+		EdbClassifyId:      classifyId,
+		AdminId:            adminId,
+		IsRead:             isRead,
+		Message:            content + "触发预警",
+		MonitorTriggerTime: triggerTime,
+		CreateTime:         time.Now(),
+	}
+	_, err = message.Insert()
+
+	return err
+}
+
+func SendMessages(adminId, edbInfoId, edbInfoType int, classifyId int, edbUniqueCode, message string, triggerTime string) (err error) {
+	conn := MonitorMessageConn[adminId]
+	if conn == nil {
+		return errors.New("no connection")
+	}
+	msg := response.EdbMonitorMessageResp{
+		EdbInfoId:     edbInfoId,
+		EdbInfoType:   edbInfoType,
+		EdbUniqueCode: edbUniqueCode,
+		EdbClassifyId: classifyId,
+		Message:       message,
+		TriggerTime:   triggerTime,
+	}
+	return conn.WriteJSON(msg)
+}
+
+func GetHistoryMessages(adminId int) (items []*response.EdbMonitorMessageResp, err error) {
+	messageList, err := edbmonitor.GetEdbMonitorMessageByAdminId(adminId)
+	if err != nil {
+		return
+	}
+
+	items = toEdbMonitorMessageResp(messageList)
+	return
+}
+
+func GetMessageList(adminid int, currentIndex, pageSize int) (resp response.EdbMonitorMessageListResp, err error) {
+	startSize := utils.StartIndex(currentIndex, pageSize)
+
+	total, err := edbmonitor.GetEdbMonitorMessageCountByAdminId(adminid)
+	if err != nil {
+		return
+	}
+	if total == 0 {
+		resp.List = make([]*response.EdbMonitorMessageResp, 0)
+		resp.Paging = paging.GetPaging(currentIndex, pageSize, total)
+		return
+	}
+
+	messageList, err := edbmonitor.GetEdbMonitorMessagePageByAdminId(adminid, startSize, pageSize)
+	if err != nil {
+		return
+	}
+
+	resp.List = toEdbMonitorMessageResp(messageList)
+	resp.Paging = paging.GetPaging(currentIndex, pageSize, total)
+	return
+}
+
+func toEdbMonitorMessageResp(items []*edbmonitor.EdbMonitorMessage) (list []*response.EdbMonitorMessageResp) {
+	list = make([]*response.EdbMonitorMessageResp, 0)
+	for _, message := range items {
+		item := response.EdbMonitorMessageResp{
+			EdbMonitorMessageId: message.EdbMonitorMessageId,
+			EdbInfoId:           message.EdbInfoId,
+			EdbInfoType:         message.EdbInfoType,
+			EdbUniqueCode:       message.EdbUniqueCode,
+			EdbClassifyId:       message.EdbClassifyId,
+			IsRead:              message.IsRead,
+			Message:             message.Message,
+			TriggerTime:         utils.TimeTransferString(utils.FormatDateTime, message.MonitorTriggerTime),
+		}
+		list = append(list, &item)
+	}
+	return
+}

+ 3 - 0
services/task.go

@@ -3,6 +3,7 @@ package services
 import (
 	"eta/eta_api/models"
 	"eta/eta_api/services/data"
+	edbmonitor "eta/eta_api/services/edb_monitor"
 	"eta/eta_api/utils"
 	"fmt"
 	"strings"
@@ -48,6 +49,8 @@ func Task() {
 	// 进行指标替换操作
 	go DealReplaceEdbCache()
 
+	go edbmonitor.HandleEdbMonitorEdbInfo()
+
 	// TODO:修复权限
 	//FixEnCompanyPermission()
 	fmt.Println("task end")

+ 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
@@ -316,6 +325,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

@@ -518,4 +518,7 @@ var (
 	BASE_END_DATE_UnSpace   = time.Now().AddDate(4, 0, 0).Format(FormatDateUnSpace) //基础数据结束日期
 )
 
+const CACHE_MYSQL_DATA_FILENAME = "eta:mysql:monitor:binlog:filename"
+const CACHE_MYSQL_DATA_POSITION = "eta:mysql:monitor:binlog:position"
+
 const ReportPptEditingWait = 30 // 报告/PPT编辑退出后其他人的等待时长(单位:s)

+ 8 - 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)
@@ -19,6 +21,9 @@ type RedisClient interface {
 	GetRedisTTL(key string) time.Duration
 	Incrby(key string, num int) (interface{}, error)
 	Do(commandName string, args ...interface{}) (reply interface{}, err error)
+	SAdd(key string, args ...interface{}) (err error)
+	SRem(key string, args ...interface{}) (err error)
+	SIsMember(key string, args interface{}) (bool, error)
 }
 
 func initRedis(redisType string, conf string) (redisClient RedisClient, err error) {
@@ -31,3 +36,6 @@ func initRedis(redisType string, conf string) (redisClient RedisClient, err erro
 
 	return
 }
+
+// redis没有key的错误
+const RedisNoKeyErr = "redis: nil"

+ 55 - 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
@@ -263,3 +283,37 @@ func (rc *ClusterRedisClient) Do(commandName string, args ...interface{}) (reply
 	newArgs = append(newArgs, args...)
 	return rc.redisClient.Do(context.TODO(), newArgs...).Result()
 }
+
+// SAdd
+// @Description: 写入set元素
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *ClusterRedisClient) SAdd(key string, args ...interface{}) (err error) {
+	return rc.redisClient.SAdd(context.TODO(), key, args...).Err()
+}
+
+// SAdd
+// @Description: 删除set集合中指定的元素
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *ClusterRedisClient) SRem(key string, args ...interface{}) (err error) {
+	return rc.redisClient.SRem(context.TODO(), key, args...).Err()
+}
+
+// SAdd
+// @Description: 判断元素是否在集合中
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *ClusterRedisClient) SIsMember(key string, args interface{}) (isMember bool, err error) {
+	isMember, err = rc.redisClient.SIsMember(context.TODO(), key, args).Result()
+	return
+}

+ 55 - 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
@@ -255,3 +275,37 @@ func (rc *StandaloneRedisClient) Do(commandName string, args ...interface{}) (re
 	newArgs = append(newArgs, args...)
 	return rc.redisClient.Do(context.TODO(), newArgs...).Result()
 }
+
+// SAdd
+// @Description: 写入set元素
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *StandaloneRedisClient) SAdd(key string, args ...interface{}) (err error) {
+	return rc.redisClient.SAdd(context.TODO(), key, args...).Err()
+}
+
+// SAdd
+// @Description: 删除set集合中指定的元素
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *StandaloneRedisClient) SRem(key string, args ...interface{}) (err error) {
+	return rc.redisClient.SRem(context.TODO(), key, args...).Err()
+}
+
+// SAdd
+// @Description: 判断元素是否在集合中
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *StandaloneRedisClient) SIsMember(key string, args interface{}) (isMember bool, err error) {
+	isMember, err = rc.redisClient.SIsMember(context.TODO(), key, args).Result()
+	return
+}