From 96de1697775f35e0709ec24793ad1ddccd2b1c21 Mon Sep 17 00:00:00 2001 From: TsMask <340112800@qq.com> Date: Tue, 22 Aug 2023 19:25:39 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/close.go | 15 -- api/heart_beat.go | 42 ----- api/login.go | 176 ------------------ api/req_sync_alarm.go | 61 ------ .../nbi_alarm/nbi_alarm.2023-08-21-205716.zip | Bin 0 -> 57007 bytes client_robot.go | 12 +- conf/global.go | 14 +- conf/nbi_alarm_agent.json | 32 +--- core/consts/consts.go | 7 + {db => core/db}/mysql.go | 3 +- {decoder => core/decoder}/decode_omc.go | 0 {dpack => core/dpack}/datapack_omc.go | 0 core/emun/orig_severity.go | 16 ++ {lib => core/file}/file.go | 2 +- {lib => core/file}/file_test.go | 2 +- core/{ => heart_beat}/heart_beat.go | 4 +- core/interceptor/global.go | 42 +++++ core/manage/manage.go | 51 +++++ core/{ => manage}/user.go | 4 +- core/{ => manage}/user_manager.go | 26 +-- core/model/body.go | 9 + core/parse/parse.go | 110 +++++++++++ core/result.go | 34 ++++ core/utils.go | 48 ----- lib/password.go => core/utils/bcrypt.go | 2 +- core/utils/strrand.go | 25 +++ handle/api/close_conn_alarm.go | 22 +++ handle/api/req_cmca_login_alarm.go | 46 +++++ handle/api/req_cmca_login_seq copy.go | 83 +++++++++ handle/api/req_cmca_login_seq.go | 47 +++++ handle/api/req_heart_beat.go | 40 ++++ handle/api/req_login_alarm.go | 81 ++++++++ {api => handle/api}/req_sync_alarm_file.go | 57 +++--- handle/api/req_sync_alarm_msg.go | 69 +++++++ {model => handle/model}/alarm.go | 0 {model => handle/model}/nbi_alarm_log.go | 0 {model => handle/model}/user.go | 0 {service => handle/service}/login.go | 17 +- .../service}/real_time_alarm.go | 9 +- .../service}/sysn_alarm_file.go | 41 ++-- nb_alarm_agent.go | 111 +---------- omc/msg.go | 31 --- omc/omc_pack.go | 45 ----- omc/omc_type.go | 39 ---- router/router.go | 82 ++++++++ 45 files changed, 881 insertions(+), 676 deletions(-) delete mode 100644 api/close.go delete mode 100644 api/heart_beat.go delete mode 100644 api/login.go delete mode 100644 api/req_sync_alarm.go create mode 100644 assets/nbi_alarm/nbi_alarm.2023-08-21-205716.zip create mode 100644 core/consts/consts.go rename {db => core/db}/mysql.go (99%) rename {decoder => core/decoder}/decode_omc.go (100%) rename {dpack => core/dpack}/datapack_omc.go (100%) create mode 100644 core/emun/orig_severity.go rename {lib => core/file}/file.go (99%) rename {lib => core/file}/file_test.go (96%) rename core/{ => heart_beat}/heart_beat.go (92%) create mode 100644 core/interceptor/global.go create mode 100644 core/manage/manage.go rename core/{ => manage}/user.go (97%) rename core/{ => manage}/user_manager.go (92%) create mode 100644 core/model/body.go create mode 100644 core/parse/parse.go create mode 100644 core/result.go delete mode 100644 core/utils.go rename lib/password.go => core/utils/bcrypt.go (96%) create mode 100644 core/utils/strrand.go create mode 100644 handle/api/close_conn_alarm.go create mode 100644 handle/api/req_cmca_login_alarm.go create mode 100644 handle/api/req_cmca_login_seq copy.go create mode 100644 handle/api/req_cmca_login_seq.go create mode 100644 handle/api/req_heart_beat.go create mode 100644 handle/api/req_login_alarm.go rename {api => handle/api}/req_sync_alarm_file.go (50%) create mode 100644 handle/api/req_sync_alarm_msg.go rename {model => handle/model}/alarm.go (100%) rename {model => handle/model}/nbi_alarm_log.go (100%) rename {model => handle/model}/user.go (100%) rename {service => handle/service}/login.go (78%) rename {service => handle/service}/real_time_alarm.go (96%) rename {service => handle/service}/sysn_alarm_file.go (88%) delete mode 100644 omc/msg.go delete mode 100644 omc/omc_pack.go delete mode 100644 omc/omc_type.go create mode 100644 router/router.go diff --git a/api/close.go b/api/close.go deleted file mode 100644 index f007fd5..0000000 --- a/api/close.go +++ /dev/null @@ -1,15 +0,0 @@ -package api - -import ( - "github.com/aceld/zinx/ziface" - "github.com/aceld/zinx/znet" -) - -// CloseApi 关闭连接API -type CloseApi struct { - znet.BaseRouter -} - -func (*CloseApi) Handle(request ziface.IRequest) { - request.GetConnection().Stop() -} diff --git a/api/heart_beat.go b/api/heart_beat.go deleted file mode 100644 index a668239..0000000 --- a/api/heart_beat.go +++ /dev/null @@ -1,42 +0,0 @@ -package api - -import ( - "github.com/aceld/zinx/ziface" - "github.com/aceld/zinx/zlog" - "github.com/aceld/zinx/znet" - "omc/omc" -) - -// HeartBeatApi 心跳请求 -type HeartBeatApi struct { - znet.BaseRouter -} - -func (*HeartBeatApi) Handle(request ziface.IRequest) { - // 解包 - msgBody := omc.MsgBody{ - RawData: request.GetData(), - Msg: make(map[string]string, 0), - } - if err := msgBody.Decode(); err != nil { - zlog.Ins().ErrorF("inlaid message body %s", err.Error()) - request.GetConnection().SendMsg(omc.AckHeartBeat, omc.ErrorMsg("ackHeartBeat", "", "inlaid message body")) - return - } - - reqId, ok := msgBody.Msg["reqId"] - if !ok { - zlog.Ins().ErrorF("missing parameter of message body") - request.GetConnection().SendMsg(omc.AckHeartBeat, omc.ErrorMsg("ackHeartBeat", "", "missing parameter of message body")) - return - } - - //ack - ackBody := omc.MsgBody{ - MsgName: "ackHeartBeat", - Msg: make(map[string]string, 0), - } - ackBody.Msg["reqId"] = reqId - ackBody.Pack() - request.GetConnection().SendMsg(omc.AckHeartBeat, ackBody.RawData) -} diff --git a/api/login.go b/api/login.go deleted file mode 100644 index 656f2a5..0000000 --- a/api/login.go +++ /dev/null @@ -1,176 +0,0 @@ -package api - -import ( - "encoding/hex" - "github.com/aceld/zinx/ziface" - "github.com/aceld/zinx/zlog" - "github.com/aceld/zinx/znet" - "github.com/google/uuid" - "omc/core" - "omc/omc" - "omc/service" - "strings" -) - -// LoginApi 登录API -type LoginApi struct { - znet.BaseRouter -} - -// Handle Login reqLoginAlarm;user=yiy;key=qw#$@;type=msg -func (*LoginApi) Handle(request ziface.IRequest) { - // 登录消息处理 - msgBody := omc.MsgBody{ - RawData: request.GetData(), - Msg: make(map[string]string, 0), - } - if err := msgBody.Decode(); err != nil { - zlog.Ins().ErrorF("inlaid message body %s", err.Error()) - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", "inlaid message body")) - return - } - - user, userOK := msgBody.Msg["user"] - pw, pwOK := msgBody.Msg["key"] - tp, tpOK := msgBody.Msg["type"] - if !userOK || !pwOK || !tpOK { - zlog.Ins().ErrorF("missing parameter of message body") - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", "missing parameter of message body")) - return - } - m := core.GetManager(request.GetConnection().GetName()) - if m == nil { - zlog.Ins().ErrorF("server internal error") - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", "server internal error")) - return - } - uID, err := request.GetConnection().GetProperty("UID") - if err != nil { - zlog.Ins().ErrorF("GetProperty UID error %s", err) - request.GetConnection().Stop() - return - } - - //登录信息check - if err := service.UserLogin(user, pw); err != nil { - zlog.Ins().ErrorF("LoginFail %s", err) - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", "Incorrect username and password")) - isClose, _ := m.LoginFail(uID.(string)) //登录错误超过3次,断开连接 - if isClose { - request.GetConnection().Stop() - return - } - return - } - - //manager 更新 - if err := m.LoginSuccess(uID.(string), user, tp); err != nil { - zlog.Ins().ErrorF("manager:%s", err) - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", err.Error())) - return - } - zlog.Ins().InfoF("user login loginSuccess,username:%s, type:%s, channel:%s", user, tp, request.GetConnection().GetName()) - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.SuccessMsg("ackLoginAlarm", "", "")) -} - -// CMCALoginSeq 登录API - -type CMCALoginSeq struct { - znet.BaseRouter -} - -//reqCMCALoginSeq - -func (*CMCALoginSeq) Handle(request ziface.IRequest) { - uid := uuid.New() - seqNo := hex.EncodeToString(uid[0:]) - seqNo = strings.ToUpper(seqNo) - //发送文件同步信息 - ackBody := omc.MsgBody{ - MsgName: "ackCMCALoginSeq", - Msg: make(map[string]string, 0), - } - ackBody.Msg["seqNo"] = seqNo - ackBody.Pack() - m := core.GetManager(request.GetConnection().GetName()) - uID, err := request.GetConnection().GetProperty("UID") - if err != nil { - zlog.Ins().ErrorF("GetProperty UID error %s", err) - request.GetConnection().Stop() - return - } - if m == nil { - zlog.Ins().ErrorF("server internal error") - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", "server internal error")) - return - } - m.SetSeqNo(uID.(string), seqNo) - - request.GetConnection().SendMsg(omc.AckCMCALoginSeq, ackBody.RawData) -} - -// - -type CMCALoginAlarm struct { - znet.BaseRouter -} - -//reqCMCALoginSeq -//reqCMCALoginAlarm;user=yiy;key=12313121213123;cert=AAAAAAAAAA;type=msg - -func (*CMCALoginAlarm) Handle(request ziface.IRequest) { - // 登录消息处理 - msgBody := omc.MsgBody{ - RawData: request.GetData(), - Msg: make(map[string]string, 0), - } - if err := msgBody.Decode(); err != nil { - zlog.Ins().ErrorF("inlaid message body %s", err.Error()) - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", "inlaid message body")) - return - } - - user, userOK := msgBody.Msg["user"] - key, keyOK := msgBody.Msg["key"] - cert, certOK := msgBody.Msg["cert"] - tp, tpOK := msgBody.Msg["type"] - if !userOK || !keyOK || certOK || !tpOK { - zlog.Ins().ErrorF("missing parameter of message body") - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", "missing parameter of message body")) - return - } - m := core.GetManager(request.GetConnection().GetName()) - if m == nil { - zlog.Ins().ErrorF("server internal error") - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", "server internal error")) - return - } - uID, err := request.GetConnection().GetProperty("UID") - if err != nil { - zlog.Ins().ErrorF("GetProperty UID error %s", err) - request.GetConnection().Stop() - return - } - - //登录信息check - seqNo := m.GetUserByPID(uID.(string)).SeqNo - if ok, err := service.CMCALogin(seqNo, key, cert); !ok || err != nil { - zlog.Ins().ErrorF("LoginFail %s", err) - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", "Incorrect username and password")) - isClose, _ := m.LoginFail(uID.(string)) //登录错误超过3次,断开连接 - if isClose { - request.GetConnection().Stop() - return - } - return - } - - //manager 更新 - if err := m.LoginSuccess(uID.(string), user, tp); err != nil { - zlog.Ins().ErrorF("manager:%s", err) - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.ErrorMsg("ackLoginAlarm", "", err.Error())) - return - } - zlog.Ins().InfoF("user login loginSuccess,username:%s, type:%s, channel:%s", user, tp, request.GetConnection().GetName()) - request.GetConnection().SendMsg(omc.AckLoginAlarm, omc.SuccessMsg("ackLoginAlarm", "", "")) -} diff --git a/api/req_sync_alarm.go b/api/req_sync_alarm.go deleted file mode 100644 index 6454adb..0000000 --- a/api/req_sync_alarm.go +++ /dev/null @@ -1,61 +0,0 @@ -package api - -import ( - "github.com/aceld/zinx/ziface" - "github.com/aceld/zinx/zlog" - "github.com/aceld/zinx/znet" - "omc/core" - "omc/omc" - "omc/service" - "strconv" -) - -// SyncAlarmApi 消息方式同步告警请求 -type SyncAlarmApi struct { - znet.BaseRouter -} - -func (*SyncAlarmApi) Handle(request ziface.IRequest) { - // 消息处理 - checker := []string{"reqId", "alarmSeq"} - msg, err := core.APIDecode(request, checker) - if err != nil { - zlog.Ins().ErrorF("inlaid message body %s", err.Error()) - request.GetConnection().SendMsg(omc.AckSyncAlarmMsg, omc.ErrorMsg("ackSyncAlarmMsg", "", err.Error())) - return - } - //管理模块 - m := core.GetManager(request.GetConnection().GetName()) - if m == nil { - zlog.Ins().ErrorF("server internal error") - request.GetConnection().SendMsg(omc.AckSyncAlarmFile, omc.ErrorMsg("ackSyncAlarmFile", msg.Msg["reqId"], "server internal error")) - return - } - - // 检查用户是否登录 - u := m.GetUserByPID(msg.UID) - if !u.LoginState || u.AlarmType != omc.MSG { - zlog.Ins().ErrorF("no permissions ") - request.GetConnection().SendMsg(omc.AckSyncAlarmMsg, omc.ErrorMsg("ackSyncAlarmMsg", msg.Msg["reqId"], "no permissions")) - return - } - - alarmSeq, err := strconv.Atoi(msg.Msg["alarmSeq"]) - if err != nil || alarmSeq < 1 { - zlog.Ins().ErrorF("invalid parameter of message body") - request.GetConnection().SendMsg(omc.AckSyncAlarmMsg, omc.ErrorMsg("ackSyncAlarmMsg", msg.Msg["reqId"], "invalid parameter of message body")) - return - } - - //check alarmSeq 是否存在 - neBind, _ := core.ConvertBindFlag(m.BindFlag) - alarms, _ := service.GetRealTimeAlarm(neBind.NeType, neBind.NeId, int32(alarmSeq)) - if len(alarms) == 0 { - request.GetConnection().SendMsg(omc.AckSyncAlarmMsg, omc.ErrorMsg("ackSyncAlarmMsg", msg.Msg["reqId"], "alarm seq does not exist")) - return - } - //更新实时上报的alarm seq - m.UpdateAlarmSeq(int32(alarmSeq)) - request.GetConnection().SendMsg(omc.AckSyncAlarmMsg, omc.SuccessMsg("ackSyncAlarmMsg", msg.Msg["reqId"], "")) - -} diff --git a/assets/nbi_alarm/nbi_alarm.2023-08-21-205716.zip b/assets/nbi_alarm/nbi_alarm.2023-08-21-205716.zip new file mode 100644 index 0000000000000000000000000000000000000000..2136736daefb1887fdca57fe6d7c404be6ffeefa GIT binary patch literal 57007 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 K{FE3$e*0fTWI(e3 literal 0 HcmV?d00001 diff --git a/client_robot.go b/client_robot.go index 7ab6376..c041c19 100644 --- a/client_robot.go +++ b/client_robot.go @@ -6,15 +6,15 @@ import ( "encoding/hex" "errors" "fmt" + "io" + "net" + "omc/handle/model" + "time" + "github.com/aceld/zinx/zconf" "github.com/aceld/zinx/zlog" "gorm.io/driver/mysql" "gorm.io/gorm" - "io" - "net" - "omc/model" - "omc/omc" - "time" ) type Message struct { @@ -156,7 +156,7 @@ func (this *TcpClient) Start() { //data = "reqSyncAlarmFile;reqId=35;alarmSeq=2000;syncSource=1" //data = "reqSyncAlarmFile;reqId=33;startTime=2023-01-08 00:00:00;syncSource=0" data = "reqSyncAlarmFile;reqId=34;startTime=2023-01-08 16:07:00;endTime=2023-07-19 23:59:59;syncSource=1" - this.SendMsg(omc.ReqSyncAlarmFile, []byte(data)) + this.SendMsg(11, []byte(data)) go this.Receive() } diff --git a/conf/global.go b/conf/global.go index 6d966a7..befca11 100644 --- a/conf/global.go +++ b/conf/global.go @@ -11,14 +11,16 @@ import ( "github.com/aceld/zinx/zlog" ) +type Channel struct { + TCPPort int `json:"tcp_port"` //当前通道的TCP监听端口 + BindFlag string `json:"bind_flag"` //当前通道bind 的网元信息 + Province string `json:"province"` //网元所在省份 + DeviceCode string `json:"device_code"` //主机编码 四位,每1位可用0-9、A-Z编码 +} + type Config struct { // 网元通道 - Channel []struct { - TCPPort int `json:"tcp_port"` //当前通道的TCP监听端口 - BindFlag string `json:"bind_flag"` //当前通道bind 的网元信息 - Province string `json:"province"` //网元所在省份 - DeviceCode string `json:"device_code"` //主机编码 四位,每1位可用0-9、A-Z编码 - } `json:"channel"` + Channel []Channel `json:"channel"` // 数据库连接 Mysql string `json:"mysql"` diff --git a/conf/nbi_alarm_agent.json b/conf/nbi_alarm_agent.json index a66a477..cb6a5fc 100644 --- a/conf/nbi_alarm_agent.json +++ b/conf/nbi_alarm_agent.json @@ -2,39 +2,9 @@ "channel": [ { "tcp_port": 31232, - "bind_flag": "SMF#1101RJHX1SMF01", + "bind_flag": "SMF#SZ_02", "province": "BJ", "device_code": "0001" - }, - { - "tcp_port": 31233, - "bind_flag": "UDM#1101RJHX1UDM01", - "province": "BJ", - "device_code": "0002" - }, - { - "tcp_port": 31234, - "bind_flag": "AUSF#1101RJHX1AUF01", - "province": "BJ", - "device_code": "0003" - }, - { - "tcp_port": 31235, - "bind_flag": "AMF#1101RJHX1AMF01", - "province": "BJ", - "device_code": "0004" - }, - { - "tcp_port": 31236, - "bind_flag": "UPF#1101RJHX1UPF01", - "province": "BJ", - "device_code": "0005" - }, - { - "tcp_port": 31237, - "bind_flag": "AMF#1101RJHX1AMF03", - "province": "BJ", - "device_code": "0006" } ], diff --git a/core/consts/consts.go b/core/consts/consts.go new file mode 100644 index 0000000..a5ba95f --- /dev/null +++ b/core/consts/consts.go @@ -0,0 +1,7 @@ +package consts + +//定义user type +const ( + MSG = "msg" + FILE = "ftp" +) diff --git a/db/mysql.go b/core/db/mysql.go similarity index 99% rename from db/mysql.go rename to core/db/mysql.go index b503d8e..26c4135 100644 --- a/db/mysql.go +++ b/core/db/mysql.go @@ -1,10 +1,11 @@ package db import ( + "omc/conf" + "github.com/aceld/zinx/zlog" "gorm.io/driver/mysql" "gorm.io/gorm" - "omc/conf" ) var Client *gorm.DB diff --git a/decoder/decode_omc.go b/core/decoder/decode_omc.go similarity index 100% rename from decoder/decode_omc.go rename to core/decoder/decode_omc.go diff --git a/dpack/datapack_omc.go b/core/dpack/datapack_omc.go similarity index 100% rename from dpack/datapack_omc.go rename to core/dpack/datapack_omc.go diff --git a/core/emun/orig_severity.go b/core/emun/orig_severity.go new file mode 100644 index 0000000..3b726bd --- /dev/null +++ b/core/emun/orig_severity.go @@ -0,0 +1,16 @@ +package emun + +func OrigSeverity(os string) int32 { + switch os { + case "Critical": + return 1 + case "Major": + return 2 + case "Minor": + return 3 + case "Warning": + return 4 + default: + return 0 + } +} diff --git a/lib/file.go b/core/file/file.go similarity index 99% rename from lib/file.go rename to core/file/file.go index 883d040..6d02b14 100644 --- a/lib/file.go +++ b/core/file/file.go @@ -1,4 +1,4 @@ -package lib +package file import ( "os" diff --git a/lib/file_test.go b/core/file/file_test.go similarity index 96% rename from lib/file_test.go rename to core/file/file_test.go index a2b9890..faa1bdd 100644 --- a/lib/file_test.go +++ b/core/file/file_test.go @@ -1,4 +1,4 @@ -package lib +package file import ( "fmt" diff --git a/core/heart_beat.go b/core/heart_beat/heart_beat.go similarity index 92% rename from core/heart_beat.go rename to core/heart_beat/heart_beat.go index 5ac2967..ee54237 100644 --- a/core/heart_beat.go +++ b/core/heart_beat/heart_beat.go @@ -1,10 +1,12 @@ -package core +package heartbeat import ( "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" ) +var HeadBeatMsgID uint32 = 255 + // MyHeartBeatMsg 用户自定义的心跳检测消息处理方法 func MyHeartBeatMsg(conn ziface.IConnection) []byte { return []byte("heartbeat, I am server, I am alive") diff --git a/core/interceptor/global.go b/core/interceptor/global.go new file mode 100644 index 0000000..606d4b0 --- /dev/null +++ b/core/interceptor/global.go @@ -0,0 +1,42 @@ +package interceptor + +import ( + "encoding/hex" + "fmt" + "omc/core/parse" + "time" + + "github.com/aceld/zinx/ziface" +) + +type GlobalInterceptor struct{} + +func (m *GlobalInterceptor) Intercept(chain ziface.IChain) ziface.IcResp { + request := chain.Request() //从责任链中获取当前拦截器的输入数据 + + // 这一层是自定义拦截器处理逻辑,这里只是简单打印输入 + iRequest := request.(ziface.IRequest) //注意:由于Zinx的Request类型,这里需要做一下断言转换 + + fmt.Println("\n\n=========自定义拦截器=====") + + body, err := parse.RequestBodyDecode(iRequest, nil) + fmt.Printf("消息ID: %v \n", iRequest.GetMsgID()) + fmt.Printf("原始数据: %v \n", body.RawData) + fmt.Printf("原始字符: %v \n", hex.EncodeToString(body.RawData)) + fmt.Printf("原始字符: %v \n", string(body.RawData)) + fmt.Printf("用户ID: %v \n", body.UID) + fmt.Printf("收到消息: %v %v \n", body.Name, body.Data) + fmt.Printf("错误:%v \n", err) + + // return chain.Proceed(chain.Request()) //进入并执行下一个拦截器 + + iMessage := chain.GetIMessage() + resp := chain.ProceedWithIMessage(iMessage, iRequest) + fmt.Printf("目标消息ID: %v \n", iMessage.GetMsgID()) + fmt.Printf("收到消息长度: %v \n", iMessage.GetDataLen()) + fmt.Printf("信息时间:%v \n", time.Now().String()) + + fmt.Print("=========自定义拦截器=====\n\n\n") + + return resp +} diff --git a/core/manage/manage.go b/core/manage/manage.go new file mode 100644 index 0000000..177b5f3 --- /dev/null +++ b/core/manage/manage.go @@ -0,0 +1,51 @@ +package manage + +import ( + "github.com/aceld/zinx/ziface" + "github.com/aceld/zinx/zlog" +) + +// OnConnectionAdd 当客户端建立连接的时候的hook函数 +func OnConnectionAdd(conn ziface.IConnection) { + //创建一个user + + user := NewUser(conn, conn.RemoteAddrString()) + //将当前新上线玩家添加到ChannelManager中 + m := GetManager(conn.GetName()) + if m == nil { + zlog.Ins().ErrorF("server internal error in GetManager") + conn.Stop() + return + } + m.AddUser(user) + + //将该连接绑定属性PID + conn.SetProperty("UID", user.UID) + + zlog.Ins().InfoF("====> User uID = %s", user.UID, " arrived ====", "") +} + +// OnConnectionLost 当客户端断开连接的时候的hook函数 +func OnConnectionLost(conn ziface.IConnection) { + //获取当前连接的PID属性 + uID, _ := conn.GetProperty("UID") + var userID string + if uID != nil { + userID = uID.(string) + } + + //根据pID获取对应usr + m := GetManager(conn.GetName()) + if m == nil { + zlog.Ins().ErrorF("server internal error in GetManager") + return + } + user := m.GetUserByPID(userID) + + //触发玩家下线业务 + if user != nil { + user.LostConnection(m) + } + + zlog.Ins().InfoF("====> User %s-%s", user.UID, user.UserName, " left =====") +} diff --git a/core/user.go b/core/manage/user.go similarity index 97% rename from core/user.go rename to core/manage/user.go index cc62852..6c8fb4b 100644 --- a/core/user.go +++ b/core/manage/user.go @@ -1,7 +1,8 @@ -package core +package manage import ( "fmt" + "github.com/aceld/zinx/ziface" "github.com/google/uuid" ) @@ -46,5 +47,4 @@ func (p *User) SendMsg(msgID uint32, msg []byte) { return } - return } diff --git a/core/user_manager.go b/core/manage/user_manager.go similarity index 92% rename from core/user_manager.go rename to core/manage/user_manager.go index 3504bfb..b14f19d 100644 --- a/core/user_manager.go +++ b/core/manage/user_manager.go @@ -1,15 +1,17 @@ -package core +package manage import ( "errors" - "github.com/aceld/zinx/zlog" - "omc/db" - "omc/model" - "omc/omc" - "omc/service" + "omc/core/consts" + "omc/core/db" + "omc/core/parse" + "omc/handle/model" + "omc/handle/service" "strings" "sync" "time" + + "github.com/aceld/zinx/zlog" ) /* @@ -65,7 +67,7 @@ func (wm *ChannelManager) Talk(msgID uint32, msg []byte) { //3. 向所有的user发送消息 for _, user := range users { - if user.LoginState && user.AlarmType == omc.MSG { + if user.LoginState && user.AlarmType == consts.MSG { user.SendMsg(msgID, msg) } } @@ -77,7 +79,7 @@ func (wm *ChannelManager) LoginSuccess(UID, name, tp string) error { defer wm.pLock.Unlock() //判断是否重复登录 for _, v := range wm.User { - if v.UserName == name && v.AlarmType == tp && v.LoginState == true { + if v.UserName == name && v.AlarmType == tp && v.LoginState { return errors.New("repeat login for the same account") } } @@ -147,7 +149,7 @@ func (wm *ChannelManager) GetAllUser() []*User { //添加切片 for _, v := range wm.User { - if v.LoginState && v.AlarmType == omc.MSG { + if v.LoginState && v.AlarmType == consts.MSG { User = append(User, v) } } @@ -162,7 +164,7 @@ func (wm *ChannelManager) RealTimeAlarm() { //查询 var newAlarmSeq = wm.AlarmSeq var alarms []service.OmcAlarm - neBind, _ := ConvertBindFlag(wm.BindFlag) + neBind, _ := parse.ConvertBindFlag(wm.BindFlag) if wm.AlarmSeq == 0 { newAlarmSeq = service.GetLastAlarmSeq(neBind.NeType, neBind.NeId) @@ -180,7 +182,7 @@ func (wm *ChannelManager) RealTimeAlarm() { } var users []string for _, user := range wm.User { - if user.LoginState && user.AlarmType == omc.MSG { + if user.LoginState && user.AlarmType == consts.MSG { userInfo := strings.Join([]string{user.UserName, user.RemoteIp}, ";") users = append(users, userInfo) } @@ -220,7 +222,7 @@ func (wm *ChannelManager) SendAlarm(alarms []service.OmcAlarm) error { //生产告警内容 data := service.GenAlarm(v) //发送告警内容 - wm.Talk(omc.RealTimeAlarm, data) + wm.Talk(0, data) } return nil } diff --git a/core/model/body.go b/core/model/body.go new file mode 100644 index 0000000..d8da77d --- /dev/null +++ b/core/model/body.go @@ -0,0 +1,9 @@ +package model + +// 请求数据 +type Body struct { + UID string // 连接实例ID + RawData []byte // 原始数据 + Name string // 请求名 + Data map[string]string // 数据Key +} diff --git a/core/parse/parse.go b/core/parse/parse.go new file mode 100644 index 0000000..c570f9b --- /dev/null +++ b/core/parse/parse.go @@ -0,0 +1,110 @@ +package parse + +import ( + "errors" + "fmt" + "omc/core/model" + "strings" + + "github.com/aceld/zinx/ziface" +) + +// 网元类型#网元标记 +type NeBind struct { + NeType string + NeId string +} + +// 转换解析服务端绑定的网元 +func ConvertBindFlag(bindFlag string) (NeBind, error) { + var neBind NeBind + nb := strings.Split(bindFlag, "#") + if len(nb) != 2 { + return neBind, errors.New("ne bind flag invalid") + } + neBind.NeType = nb[0] + neBind.NeId = nb[1] + return neBind, nil +} + +// RequestBodyDecode 请求消息解析 +// checker 检查参数必传 +func RequestBodyDecode(request ziface.IRequest, checker []string) (model.Body, error) { + // 消息处理 + body := model.Body{} + err := Decode(request.GetData(), &body) + if err != nil { + return body, errors.New("inlaid message body") + } + + // 检查key + if len(checker) > 0 { + for _, v := range checker { + if _, ok := body.Data[v]; !ok { + return body, errors.New("missing parameter of message body : " + v) + } + } + } + + // 当前连接实例ID + uID, err := request.GetConnection().GetProperty("UID") + if err != nil { + request.GetConnection().Stop() + return body, errors.New("server internal error") + } + body.UID = uID.(string) + return body, nil +} + +// Decode 数据解析 +// reqLoginAlarm;user=yiy;key=qw#$@;type=msg +func Decode(data []byte, body *model.Body) error { + body.RawData = data + + multi := strings.Split(string(data), ";") + if len(multi) < 1 { + return errors.New("invalid msg body") + } + + // 获取函数名 + if multi[0] != "" { + name := multi[0] + + idx := strings.LastIndex(name, "\x14") + if idx == -1 { + idx = strings.LastIndex(name, "\x00") + } + + if idx > 0 { + name = name[idx+1:] + name = strings.Replace(name, "\"", "", 1) + name = strings.Replace(name, "'", "", 1) + name = strings.Replace(name, "#", "", 1) + } + body.Name = name + } + + // 解析data KEY + body.Data = make(map[string]string) + for i := 1; i < len(multi); i++ { + m := strings.Split(multi[i], "=") + if len(m) != 2 { + return errors.New("invalid msg body") + } + body.Data[m[0]] = m[1] + } + + return nil +} + +// Pack 数据压缩 +func Pack(name string, data map[string]string) []byte { + var multi []string + multi = append(multi, name) + for i, v := range data { + item := fmt.Sprintf("%s=%s", i, v) + multi = append(multi, item) + } + raw := strings.Join(multi, ";") + return []byte(raw) +} diff --git a/core/result.go b/core/result.go new file mode 100644 index 0000000..84b0734 --- /dev/null +++ b/core/result.go @@ -0,0 +1,34 @@ +package core + +import ( + "omc/core/parse" +) + +// Result +// 配合 request.GetConnection().SendMsg() +func Result(name string, data map[string]string) []byte { + return parse.Pack(name, data) +} + +// ResultError ackLoginAlarm;result=fail;resDesc=username-error +// request.GetConnection().SendMsg(omc.AckSyncAlarmMsg, core.ResultError("ackSyncAlarmMsg", err.Error(), "")) +func ResultError(name string, desc, reqID string) []byte { + data := map[string]string{ + "result": "fail", + "reqId": reqID, + "resDesc": desc, + } + + return Result(name, data) +} + +// request.GetConnection().SendMsg(omc.AckSyncAlarmMsg, core.ResultSuccess("ackSyncAlarmMsg", err.Error(), "")) +func ResultSuccess(name string, desc, reqID string) []byte { + data := map[string]string{ + "result": "succ", + "reqId": reqID, + "resDesc": desc, + } + + return Result(name, data) +} diff --git a/core/utils.go b/core/utils.go deleted file mode 100644 index 9c436a8..0000000 --- a/core/utils.go +++ /dev/null @@ -1,48 +0,0 @@ -package core - -import ( - "errors" - "github.com/aceld/zinx/ziface" - "omc/omc" - "strings" -) - -type NeBind struct { - NeType string - NeId string -} - -func ConvertBindFlag(bindFlag string) (NeBind, error) { - var neBind NeBind - nb := strings.Split(bindFlag, "#") - if len(nb) != 2 { - return neBind, errors.New("ne bind flag invalid") - } - neBind.NeType = nb[0] - neBind.NeId = nb[1] - return neBind, nil -} - -// APIDecode 消息解析 -func APIDecode(request ziface.IRequest, checker []string) (*omc.MsgBody, error) { - // 消息处理 - msgBody := omc.MsgBody{ - RawData: request.GetData(), - Msg: make(map[string]string, 0), - } - if err := msgBody.Decode(); err != nil { - return nil, errors.New("inlaid message body") - } - for _, v := range checker { - if _, ok := msgBody.Msg[v]; !ok { - return nil, errors.New("missing parameter of message body") - } - } - uID, err := request.GetConnection().GetProperty("UID") - if err != nil { - request.GetConnection().Stop() - return nil, errors.New("server internal error") - } - msgBody.UID = uID.(string) - return &msgBody, nil -} diff --git a/lib/password.go b/core/utils/bcrypt.go similarity index 96% rename from lib/password.go rename to core/utils/bcrypt.go index 7ebf1eb..75aa7ae 100644 --- a/lib/password.go +++ b/core/utils/bcrypt.go @@ -1,4 +1,4 @@ -package lib +package utils import "golang.org/x/crypto/bcrypt" diff --git a/core/utils/strrand.go b/core/utils/strrand.go new file mode 100644 index 0000000..14d2a42 --- /dev/null +++ b/core/utils/strrand.go @@ -0,0 +1,25 @@ +package utils + +import ( + "math/rand" + "time" +) + +// 生成编号 SeqNo 0-9A-Z +func SeqNo(length int) string { + items := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"} + + // 创建基于时间的随机生成器 + source := rand.NewSource(time.Now().UnixNano()) + rng := rand.New(source) + + // 生成32位长度的字符串值 + result := "" + for i := 0; i < length; i++ { + randomIndex := rng.Intn(len(items)) + randomItem := items[randomIndex] + result += randomItem + } + + return result +} diff --git a/handle/api/close_conn_alarm.go b/handle/api/close_conn_alarm.go new file mode 100644 index 0000000..b000215 --- /dev/null +++ b/handle/api/close_conn_alarm.go @@ -0,0 +1,22 @@ +package api + +import ( + "omc/core" + + "github.com/aceld/zinx/ziface" + "github.com/aceld/zinx/znet" +) + +var CloseConnAlarmMsgID uint32 = 10 +var CloseConnAlarmMsgType uint32 = 10 +var CloseConnAlarmMsgName string = "closeConnAlarm" + +// closeConnAlarm 关闭连接 +type CloseConnAlarm struct { + znet.BaseRouter +} + +func (s *CloseConnAlarm) Handle(request ziface.IRequest) { + request.GetConnection().Stop() + request.GetConnection().SendMsg(CloseConnAlarmMsgType, core.ResultSuccess(CloseConnAlarmMsgName, "", "")) +} diff --git a/handle/api/req_cmca_login_alarm.go b/handle/api/req_cmca_login_alarm.go new file mode 100644 index 0000000..34b8103 --- /dev/null +++ b/handle/api/req_cmca_login_alarm.go @@ -0,0 +1,46 @@ +package api + +import ( + "omc/core" + "omc/core/manage" + "omc/core/utils" + + "github.com/aceld/zinx/ziface" + "github.com/aceld/zinx/zlog" + "github.com/aceld/zinx/znet" +) + +var ReqCMCALoginAlarmMsgID uint32 = 11 +var ReqCMCALoginAlarmMsgType uint32 = 11 +var ReqCMCALoginAlarmMsgName string = "reqCMCALoginAlarm" + +// reqCMCALoginAlarm CMCA认证方式登录 +type ReqCMCALoginAlarm struct { + znet.BaseRouter +} + +func (*ReqCMCALoginAlarm) Handle(request ziface.IRequest) { + + // 获取当前请求的通道 + m := manage.GetManager(request.GetConnection().GetName()) + if m == nil { + zlog.Ins().ErrorF("server internal error") + request.GetConnection().SendMsg(ReqLoginAlarmMsgType, core.ResultError(ReqLoginAlarmMsgName, "server internal error", "")) + return + } + uid, err := request.GetConnection().GetProperty("UID") + if err != nil { + zlog.Ins().ErrorF("GetProperty UID error %s", err) + request.GetConnection().Stop() + return + } + // seqNo 32位长度 0-9A-Z + seqNo := utils.SeqNo(32) + m.SetSeqNo(uid.(string), seqNo) + + // 发送信息 + msgData := map[string]string{ + "seqNo": seqNo, + } + request.GetConnection().SendMsg(ReqCMCALoginAlarmMsgType, core.Result(ReqCMCALoginAlarmMsgName, msgData)) +} diff --git a/handle/api/req_cmca_login_seq copy.go b/handle/api/req_cmca_login_seq copy.go new file mode 100644 index 0000000..e72870a --- /dev/null +++ b/handle/api/req_cmca_login_seq copy.go @@ -0,0 +1,83 @@ +package api + +// import ( +// "omc/core" +// "omc/core/manage" +// "omc/core/parse" +// "omc/handle/service" + +// "github.com/aceld/zinx/ziface" +// "github.com/aceld/zinx/zlog" +// "github.com/aceld/zinx/znet" +// ) + +// var ReqCMCALoginSeqMsgID uint32 = 12 +// var ReqCMCALoginSeqMsgType uint32 = 13 +// var ReqCMCALoginSeqMsgName string = "ackCMCALoginSeq" + +// // reqCMCALoginSeq CMCA认证方式登录随机码 +// type ReqCMCALoginSeq struct { +// znet.BaseRouter +// } + +// // reqCMCALoginAlarm;user=omc;key=base64Key;cert=cer;type=msg" +// func (s *ReqCMCALoginSeq) Handle(request ziface.IRequest) { +// // 登录消息处理 +// body, err := parse.RequestBodyDecode(request, []string{"user", "key", "cert", "type"}) +// if err != nil { +// zlog.Ins().ErrorF("inlaid message body %s", err.Error()) +// request.GetConnection().SendMsg(ReqCMCALoginSeqMsgType, core.ResultError(ReqCMCALoginSeqMsgName, err.Error(), "")) +// return +// } + +// // 获取当前请求的通道 +// m := manage.GetManager(request.GetConnection().GetName()) +// if m == nil { +// zlog.Ins().ErrorF("server internal error") +// request.GetConnection().SendMsg(ReqLoginAlarmMsgType, core.ResultError(ReqLoginAlarmMsgName, "server internal error", "")) +// return +// } +// uid, err := request.GetConnection().GetProperty("UID") +// if err != nil { +// zlog.Ins().ErrorF("GetProperty UID error %s", err) +// request.GetConnection().Stop() +// return +// } + +// // 账户和消息类型 +// username := body.Data["key"] +// tp := body.Data["type"] + +// //登录信息check +// seqNo := m.GetUserByPID(uid.(string)).SeqNo +// if ok, err := service.CMCALogin(seqNo, username, body.Data["cert"]); !ok || err != nil { +// zlog.Ins().ErrorF("LoginFail %s", err) +// request.GetConnection().SendMsg(ReqLoginAlarmMsgType, core.Result(ReqLoginAlarmMsgName, map[string]string{ +// "result": "autherror", +// "resDesc": err.Error(), +// })) +// // 已登录的,登录错误超过3次,断开连接 +// if uid != nil || uid != "" { +// isClose, _ := m.LoginFail(uid.(string)) +// if isClose { +// request.GetConnection().Stop() +// return +// } +// } +// return +// } + +// // manager 用户登录更新 +// err = m.LoginSuccess(uid.(string), username, tp) +// if err != nil { +// zlog.Ins().ErrorF("manager:%s", err) +// request.GetConnection().SendMsg(ReqLoginAlarmMsgType, core.Result(ReqLoginAlarmMsgName, map[string]string{ +// "result": "autherror", +// "resDesc": err.Error(), +// })) +// return +// } + +// zlog.Ins().InfoF("user login loginSuccess,username:%s, type:%s, channel:%s", username, tp, request.GetConnection().GetName()) +// request.GetConnection().SendMsg(ReqLoginAlarmMsgType, core.ResultSuccess(ReqLoginAlarmMsgName, "", "")) +// } diff --git a/handle/api/req_cmca_login_seq.go b/handle/api/req_cmca_login_seq.go new file mode 100644 index 0000000..2ea2456 --- /dev/null +++ b/handle/api/req_cmca_login_seq.go @@ -0,0 +1,47 @@ +package api + +import ( + "omc/core" + "omc/core/manage" + "omc/core/utils" + + "github.com/aceld/zinx/ziface" + "github.com/aceld/zinx/zlog" + "github.com/aceld/zinx/znet" +) + +var ReqCMCALoginSeqMsgID uint32 = 12 +var ReqCMCALoginSeqMsgType uint32 = 13 +var ReqCMCALoginSeqMsgName string = "ackCMCALoginSeq" + +// reqCMCALoginSeq CMCA认证方式登录随机码 +type ReqCMCALoginSeq struct { + znet.BaseRouter +} + +// reqCMCALoginAlarm;user=omc;key=base64Key;cert=cer;type=msg" +func (s *ReqCMCALoginSeq) Handle(request ziface.IRequest) { + + // 获取当前请求的通道 + m := manage.GetManager(request.GetConnection().GetName()) + if m == nil { + zlog.Ins().ErrorF("server internal error") + request.GetConnection().SendMsg(ReqCMCALoginSeqMsgType, core.ResultError(ReqCMCALoginSeqMsgName, "server internal error", "")) + return + } + uid, err := request.GetConnection().GetProperty("UID") + if err != nil { + zlog.Ins().ErrorF("GetProperty UID error %s", err) + request.GetConnection().Stop() + return + } + // seqNo 32位长度 0-9A-Z + seqNo := utils.SeqNo(32) + m.SetSeqNo(uid.(string), seqNo) + + // 发送信息 + msgData := map[string]string{ + "seqNo": seqNo, + } + request.GetConnection().SendMsg(ReqCMCALoginSeqMsgType, core.Result(ReqCMCALoginSeqMsgName, msgData)) +} diff --git a/handle/api/req_heart_beat.go b/handle/api/req_heart_beat.go new file mode 100644 index 0000000..61923cb --- /dev/null +++ b/handle/api/req_heart_beat.go @@ -0,0 +1,40 @@ +package api + +import ( + "omc/core" + "omc/core/parse" + + "github.com/aceld/zinx/ziface" + "github.com/aceld/zinx/zlog" + "github.com/aceld/zinx/znet" +) + +var ReqHeartBeatMsgID uint32 = 8 +var ReqHeartBeatMsgType uint32 = 9 +var ReqHeartBeatName string = "ackHeartBeat" + +// ReqHeartBeat 心跳 +type ReqHeartBeat struct { + znet.BaseRouter +} + +// reqHeartBeat;reqId=12 +func (s *ReqHeartBeat) Handle(request ziface.IRequest) { + // 解包 + body, err := parse.RequestBodyDecode(request, nil) + if err != nil { + zlog.Ins().ErrorF("inlaid message body %s", err.Error()) + request.GetConnection().SendMsg(ReqHeartBeatMsgType, core.ResultError(ReqHeartBeatName, "inlaid message body", "")) + return + } + + reqId, ok := body.Data["reqId"] + if !ok { + zlog.Ins().ErrorF("missing parameter of message body") + request.GetConnection().SendMsg(ReqHeartBeatMsgType, core.ResultError(ReqHeartBeatName, "missing parameter of message body", "")) + return + } + + //ack + request.GetConnection().SendMsg(ReqHeartBeatMsgType, core.ResultSuccess(ReqHeartBeatName, "ok", reqId)) +} diff --git a/handle/api/req_login_alarm.go b/handle/api/req_login_alarm.go new file mode 100644 index 0000000..291ffcc --- /dev/null +++ b/handle/api/req_login_alarm.go @@ -0,0 +1,81 @@ +package api + +import ( + "omc/core" + "omc/core/manage" + "omc/core/parse" + "omc/handle/service" + + "github.com/aceld/zinx/ziface" + "github.com/aceld/zinx/zlog" + "github.com/aceld/zinx/znet" +) + +var ReqLoginAlarmMsgID uint32 = 1 +var ReqLoginAlarmMsgType uint32 = 2 +var ReqLoginAlarmMsgName string = "ackLoginAlarm" + +// reqLoginAlarm 登录 +type ReqLoginAlarm struct { + znet.BaseRouter +} + +// reqLoginAlarm;user=omc;key=omc;type=msg +func (s *ReqLoginAlarm) Handle(request ziface.IRequest) { + // 登录消息处理 + body, err := parse.RequestBodyDecode(request, []string{"user", "key", "type"}) + // 账户密码 + username := body.Data["user"] + key := body.Data["key"] + if err != nil || username == "" || key == "" { + zlog.Ins().ErrorF("inlaid message body %s", err.Error()) + request.GetConnection().SendMsg(ReqLoginAlarmMsgType, core.ResultError(ReqLoginAlarmMsgName, "inlaid message body", "")) + return + } + + // 获取当前请求的通道 + m := manage.GetManager(request.GetConnection().GetName()) + if m == nil { + zlog.Ins().ErrorF("server internal error") + request.GetConnection().SendMsg(ReqLoginAlarmMsgType, core.ResultError(ReqLoginAlarmMsgName, "server internal error", "")) + return + } + uid, err := request.GetConnection().GetProperty("UID") + if err != nil { + zlog.Ins().ErrorF("GetProperty UID error %s", err) + request.GetConnection().Stop() + return + } + + // 登录信息 + err = service.UserLogin(username, key) + if err != nil { + zlog.Ins().ErrorF("LoginFail %s", err) + request.GetConnection().SendMsg(ReqLoginAlarmMsgType, core.Result(ReqLoginAlarmMsgName, map[string]string{ + "result": "autherror", + "resDesc": err.Error(), + })) + // 已登录的,登录错误超过3次,断开连接 + if uid != nil || uid != "" { + isClose, _ := m.LoginFail(uid.(string)) + if isClose { + request.GetConnection().Stop() + return + } + } + return + } + + // manager 用户登录更新 + err = m.LoginSuccess(uid.(string), username, key) + if err != nil { + zlog.Ins().ErrorF("manager:%s", err) + request.GetConnection().SendMsg(ReqLoginAlarmMsgType, core.Result(ReqLoginAlarmMsgName, map[string]string{ + "result": "autherror", + "resDesc": err.Error(), + })) + return + } + zlog.Ins().InfoF("user login loginSuccess,username:%s, type:%s, channel:%s", username, key, request.GetConnection().GetName()) + request.GetConnection().SendMsg(ReqLoginAlarmMsgType, core.ResultSuccess(ReqLoginAlarmMsgName, "", "")) +} diff --git a/api/req_sync_alarm_file.go b/handle/api/req_sync_alarm_file.go similarity index 50% rename from api/req_sync_alarm_file.go rename to handle/api/req_sync_alarm_file.go index bb54b62..7953377 100644 --- a/api/req_sync_alarm_file.go +++ b/handle/api/req_sync_alarm_file.go @@ -2,47 +2,56 @@ package api import ( "fmt" + "omc/conf" + "omc/core" + "omc/core/consts" + "omc/core/file" + "omc/core/manage" + "omc/core/parse" + "omc/handle/service" + "strconv" + "time" + "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" "github.com/aceld/zinx/znet" - "omc/conf" - "omc/core" - "omc/lib" - "omc/omc" - "omc/service" - "strconv" - "time" ) +var ackSyncAlarmFileMsgID uint32 = 6 +var ackSyncAlarmFileMsgName string = "ackSyncAlarmFile" + // SyncAlarmFileApi 文件方式同步告警请求 type SyncAlarmFileApi struct { znet.BaseRouter } // Handle -//reqSyncAlarmFile;reqId=33;startTime=2014-11-27 10:00:00;endTime=2014-11-27 10:30:00; syncSource =0 +// reqSyncAlarmFile;reqId=33;startTime=2014-11-27 10:00:00;endTime=2014-11-27 10:30:00; syncSource =0 func (*SyncAlarmFileApi) Handle(request ziface.IRequest) { // 消息处理 checker := []string{"reqId", "syncSource"} - msg, err := core.APIDecode(request, checker) + body, err := parse.RequestBodyDecode(request, checker) if err != nil { zlog.Ins().ErrorF("inlaid message body %s", err.Error()) - request.GetConnection().SendMsg(omc.AckSyncAlarmFile, omc.ErrorMsg("ackSyncAlarmFile", "", err.Error())) + request.GetConnection().SendMsg(ackSyncAlarmFileMsgID, core.ResultError(ackSyncAlarmFileMsgName, err.Error(), "")) return } + + reqId := body.Data["reqId"] + //管理模块 - m := core.GetManager(request.GetConnection().GetName()) + m := manage.GetManager(request.GetConnection().GetName()) if m == nil { zlog.Ins().ErrorF("server internal error") - request.GetConnection().SendMsg(omc.AckSyncAlarmFile, omc.ErrorMsg("ackSyncAlarmFile", msg.Msg["reqId"], "server internal error")) + request.GetConnection().SendMsg(ackSyncAlarmFileMsgID, core.ResultError(ackSyncAlarmFileMsgName, "server internal error", reqId)) return } // 检查用户是否登录 - u := m.GetUserByPID(msg.UID) - if !u.LoginState || u.AlarmType != omc.FILE { + u := m.GetUserByPID(body.UID) + if !u.LoginState || u.AlarmType != consts.FILE { zlog.Ins().ErrorF("no permissions ") - request.GetConnection().SendMsg(omc.AckSyncAlarmFile, omc.ErrorMsg("ackSyncAlarmFile", msg.Msg["reqId"], "no permissions")) + request.GetConnection().SendMsg(ackSyncAlarmFileMsgID, core.ResultError(ackSyncAlarmFileMsgName, "no permissions", reqId)) return } //查询需要上报的告警信息 @@ -50,36 +59,36 @@ func (*SyncAlarmFileApi) Handle(request ziface.IRequest) { end := "" syncSource := "" alarmSeq := 0 - fmt.Println("msg.Msg:", msg.Msg) + fmt.Println("body.Data:", body.Data) //map[alarmSeq:1 reqId:35 syncSource:1] // map[endTime:2023-07-15 23:59:59 reqId:34 startTime:2023-01-08 16:07:00 syncSource:0] - if v, ok := msg.Msg["startTime"]; ok { + if v, ok := body.Data["startTime"]; ok { start = v } - if v, ok := msg.Msg["endTime"]; ok { + if v, ok := body.Data["endTime"]; ok { end = v } - if v, ok := msg.Msg["syncSource"]; ok { + if v, ok := body.Data["syncSource"]; ok { syncSource = v } - if v, ok := msg.Msg["alarmSeq"]; ok { + if v, ok := body.Data["alarmSeq"]; ok { if seq, err := strconv.Atoi(v); err == nil { alarmSeq = seq } } - neBind, _ := core.ConvertBindFlag(m.BindFlag) + neBind, _ := parse.ConvertBindFlag(m.BindFlag) alarms, err := service.GetAlarm(neBind.NeType, neBind.NeId, start, end, syncSource, alarmSeq) if err != nil || len(alarms) == 0 { - request.GetConnection().SendMsg(omc.AckSyncAlarmFile, omc.ErrorMsg("ackSyncAlarmFile", msg.Msg["reqId"], "not find record")) + request.GetConnection().SendMsg(ackSyncAlarmFileMsgID, core.ResultError(ackSyncAlarmFileMsgName, "not find record", reqId)) return } //ack - request.GetConnection().SendMsg(omc.AckSyncAlarmFile, omc.SuccessMsg("ackSyncAlarmFile", msg.Msg["reqId"], "")) + request.GetConnection().SendMsg(ackSyncAlarmFileMsgID, core.ResultSuccess(ackSyncAlarmFileMsgName, "", reqId)) //打包结果文件 //打包生成文件 - var meta lib.FileMeta + var meta file.FileMeta meta.DirRoot = conf.OmcConf.FTPRoot meta.Province = m.Province meta.DeviceCode = m.DeviceCode diff --git a/handle/api/req_sync_alarm_msg.go b/handle/api/req_sync_alarm_msg.go new file mode 100644 index 0000000..77f4b9a --- /dev/null +++ b/handle/api/req_sync_alarm_msg.go @@ -0,0 +1,69 @@ +package api + +import ( + "omc/core" + "omc/core/manage" + "omc/core/parse" + "omc/handle/service" + "strconv" + + "github.com/aceld/zinx/ziface" + "github.com/aceld/zinx/zlog" + "github.com/aceld/zinx/znet" +) + +var ReqSyncAlarmMsgID uint32 = 3 +var ReqSyncAlarmMsgType uint32 = 4 +var ReqSyncAlarmMsgName string = "ackSyncAlarmMsg" + +// reqSyncAlarmMsg 消息同步 +type ReqSyncAlarmMsg struct { + znet.BaseRouter +} + +// reqSyncAlarmMsg +func (s *ReqSyncAlarmMsg) Handle(request ziface.IRequest) { + // 消息处理 + checker := []string{"reqId", "alarmSeq"} + body, err := parse.RequestBodyDecode(request, checker) + reqId := body.Data["reqId"] + if err != nil || reqId == "" { + zlog.Ins().ErrorF("inlaid message body %s", err.Error()) + request.GetConnection().SendMsg(ReqSyncAlarmMsgType, core.ResultError(ReqSyncAlarmMsgName, err.Error(), "")) + return + } + + //管理模块 + m := manage.GetManager(request.GetConnection().GetName()) + if m == nil { + zlog.Ins().ErrorF("server internal error") + request.GetConnection().SendMsg(ReqSyncAlarmMsgType, core.ResultError(ReqSyncAlarmMsgName, "server internal error", reqId)) + return + } + + // 检查用户是否登录 + u := m.GetUserByPID(body.UID) + if !u.LoginState { + zlog.Ins().ErrorF("no permissions ") + request.GetConnection().SendMsg(ReqSyncAlarmMsgType, core.ResultError(ReqSyncAlarmMsgName, "no permissions", reqId)) + return + } + + alarmSeq, err := strconv.Atoi(body.Data["alarmSeq"]) + if err != nil || alarmSeq < 1 { + zlog.Ins().ErrorF("invalid parameter of message body") + request.GetConnection().SendMsg(ReqSyncAlarmMsgType, core.ResultError(ReqSyncAlarmMsgName, "invalid parameter of message body", reqId)) + return + } + + //check alarmSeq 是否存在 + neBind, _ := parse.ConvertBindFlag(m.BindFlag) + alarms, _ := service.GetRealTimeAlarm(neBind.NeType, neBind.NeId, int32(alarmSeq)) + if len(alarms) == 0 { + request.GetConnection().SendMsg(ReqSyncAlarmMsgType, core.ResultError(ReqSyncAlarmMsgName, "alarm seq does not exist", reqId)) + return + } + //更新实时上报的alarm seq + m.UpdateAlarmSeq(int32(alarmSeq)) + request.GetConnection().SendMsg(ReqSyncAlarmMsgType, core.ResultSuccess(ReqSyncAlarmMsgName, "ok", reqId)) +} diff --git a/model/alarm.go b/handle/model/alarm.go similarity index 100% rename from model/alarm.go rename to handle/model/alarm.go diff --git a/model/nbi_alarm_log.go b/handle/model/nbi_alarm_log.go similarity index 100% rename from model/nbi_alarm_log.go rename to handle/model/nbi_alarm_log.go diff --git a/model/user.go b/handle/model/user.go similarity index 100% rename from model/user.go rename to handle/model/user.go diff --git a/service/login.go b/handle/service/login.go similarity index 78% rename from service/login.go rename to handle/service/login.go index 4f49feb..249d63b 100644 --- a/service/login.go +++ b/handle/service/login.go @@ -5,22 +5,25 @@ import ( "encoding/base64" "encoding/pem" "errors" - "github.com/aceld/zinx/zlog" "omc/ca" "omc/conf" - "omc/db" - "omc/lib" - "omc/model" + "omc/core/db" + "omc/core/utils" + "omc/handle/model" + + "github.com/aceld/zinx/zlog" ) -func UserLogin(name, pw string) error { +// UserLogin 用户登录 +func UserLogin(name, passwd string) error { // 用户名密码校验 var user model.User - if err := db.Client.Model(&model.User{}).Where("account_id=?", name).First(&user).Error; err != nil { + err := db.Client.Model(&model.User{}).Where("account_id=?", name).First(&user).Error + if err != nil { return err } - if err := lib.Compare(user.Password, pw); err != nil { + if err := utils.Compare(user.Password, passwd); err != nil { zlog.Ins().ErrorF("Password Login[%s]:%s", name, err) return errors.New("incorrect username and password") } diff --git a/service/real_time_alarm.go b/handle/service/real_time_alarm.go similarity index 96% rename from service/real_time_alarm.go rename to handle/service/real_time_alarm.go index f74c6c1..42dc8ce 100644 --- a/service/real_time_alarm.go +++ b/handle/service/real_time_alarm.go @@ -2,10 +2,11 @@ package service import ( "encoding/json" + "omc/core/db" + "omc/core/emun" + "omc/handle/model" + "github.com/aceld/zinx/zlog" - "omc/db" - "omc/model" - "omc/omc" ) type OmcAlarm struct { @@ -44,7 +45,7 @@ func GetRealTimeAlarm(neType, neId string, alarmSeq int32) ([]OmcAlarm, error) { item.AlarmTitle = v.AlarmTitle item.AlarmStatus = int32(v.AlarmStatus) item.AlarmType = v.AlarmType - item.OrigSeverity = omc.OrigSeverity(v.OrigSeverity) + item.OrigSeverity = emun.OrigSeverity(v.OrigSeverity) item.EventTime = v.EventTime.Format("2006-01-02 15:04:05") item.AlarmId = v.AlarmId item.SpecificProblemID = v.SpecificProblemID diff --git a/service/sysn_alarm_file.go b/handle/service/sysn_alarm_file.go similarity index 88% rename from service/sysn_alarm_file.go rename to handle/service/sysn_alarm_file.go index 91a3b3c..aa3842c 100644 --- a/service/sysn_alarm_file.go +++ b/handle/service/sysn_alarm_file.go @@ -5,15 +5,19 @@ import ( "encoding/binary" "encoding/json" "errors" - "github.com/aceld/zinx/ziface" - "omc/db" - "omc/lib" - "omc/model" - "omc/omc" + "omc/core" + "omc/core/db" + "omc/core/emun" + "omc/core/file" + "omc/handle/model" "time" + + "github.com/aceld/zinx/ziface" ) -func GenFile(request ziface.IRequest, meta *lib.FileMeta, data []OmcAlarm) { +var AckSyncAlarmFileResultMsgID uint32 = 9 + +func GenFile(request ziface.IRequest, meta *file.FileMeta, data []OmcAlarm) { //生成文件内容 dataBuff := bytes.NewBuffer([]byte{}) for _, v := range data { @@ -23,22 +27,19 @@ func GenFile(request ziface.IRequest, meta *lib.FileMeta, data []OmcAlarm) { binary.Write(dataBuff, binary.BigEndian, '\n') } - file, err := lib.GenFile(meta, dataBuff.Bytes()) + file, err := file.GenFile(meta, dataBuff.Bytes()) if err != nil { return } //发送文件同步信息 - ackBody := omc.MsgBody{ - MsgName: "ackSyncOmcAlarmFileResult", - Msg: make(map[string]string, 0), - } - ackBody.Msg["reqId"] = meta.ReqId - ackBody.Msg["result"] = "succ" - ackBody.Msg["fileName"] = file - ackBody.Msg["resDesc"] = "" - ackBody.Pack() - request.GetConnection().SendMsg(omc.AckSyncAlarmFileResult, ackBody.RawData) + msgData := core.Result("ackSyncOmcAlarmFileResult", map[string]string{ + "reqId": meta.ReqId, + "result": "succ", + "fileName": file, + "resDesc": "", + }) + request.GetConnection().SendMsg(AckSyncAlarmFileResultMsgID, msgData) } // GetAlarmOfAlarmSeq 获取告警信息 @@ -56,7 +57,7 @@ func GetAlarmOfAlarmSeq(neType, neId string, alarmSeq int) ([]OmcAlarm, error) { item.AlarmTitle = v.AlarmTitle item.AlarmStatus = int32(v.AlarmStatus) item.AlarmType = v.AlarmType - item.OrigSeverity = omc.OrigSeverity(v.OrigSeverity) + item.OrigSeverity = emun.OrigSeverity(v.OrigSeverity) item.EventTime = v.EventTime.Format("2006-01-02 15:04:05") item.AlarmId = v.AlarmId item.SpecificProblemID = v.SpecificProblemID @@ -130,7 +131,7 @@ func GetAlarmOfEventTime(neType, neId, startTime, endTime string) ([]OmcAlarm, e item.AlarmTitle = v.AlarmTitle item.AlarmStatus = int32(v.AlarmStatus) item.AlarmType = v.AlarmType - item.OrigSeverity = omc.OrigSeverity(v.OrigSeverity) + item.OrigSeverity = emun.OrigSeverity(v.OrigSeverity) item.EventTime = v.EventTime.Format("2006-01-02 15:04:05") item.AlarmId = v.AlarmId item.SpecificProblemID = v.SpecificProblemID @@ -187,7 +188,7 @@ func GetAlarmOfLog(neType, neId, startTime, endTime string) ([]OmcAlarm, error) item.AlarmTitle = v.AlarmTitle item.AlarmStatus = int32(v.AlarmStatus) item.AlarmType = v.AlarmType - item.OrigSeverity = omc.OrigSeverity(v.OrigSeverity) + item.OrigSeverity = emun.OrigSeverity(v.OrigSeverity) item.EventTime = v.EventTime.Format("2006-01-02 15:04:05") item.AlarmId = v.AlarmId item.SpecificProblemID = v.SpecificProblemID diff --git a/nb_alarm_agent.go b/nb_alarm_agent.go index a09657f..83e43ea 100644 --- a/nb_alarm_agent.go +++ b/nb_alarm_agent.go @@ -6,124 +6,31 @@ import ( "github.com/aceld/zinx/zutils/commandline/args" "omc/conf" + "omc/core/db" + "omc/core/manage" + "omc/router" - api2 "omc/api" - "omc/core" - "omc/db" - "omc/decoder" - "omc/dpack" - "omc/omc" "os" "os/signal" - "time" - "github.com/aceld/zinx/zconf" - "github.com/aceld/zinx/ziface" "github.com/aceld/zinx/zlog" - "github.com/aceld/zinx/znet" ) -// OnConnectionAdd 当客户端建立连接的时候的hook函数 -func OnConnectionAdd(conn ziface.IConnection) { - //创建一个user - - user := core.NewUser(conn, conn.RemoteAddrString()) - //将当前新上线玩家添加到ChannelManager中 - m := core.GetManager(conn.GetName()) - if m == nil { - zlog.Ins().ErrorF("server internal error in GetManager") - conn.Stop() - return - } - m.AddUser(user) - - //将该连接绑定属性PID - conn.SetProperty("UID", user.UID) - - zlog.Ins().InfoF("====> User uID = %s", user.UID, " arrived ====", "") -} - -// OnConnectionLost 当客户端断开连接的时候的hook函数 -func OnConnectionLost(conn ziface.IConnection) { - //获取当前连接的PID属性 - uID, _ := conn.GetProperty("UID") - var userID string - if uID != nil { - userID = uID.(string) - } - - //根据pID获取对应usr - m := core.GetManager(conn.GetName()) - if m == nil { - zlog.Ins().ErrorF("server internal error in GetManager") - return - } - user := m.GetUserByPID(userID) - - //触发玩家下线业务 - if user != nil { - user.LostConnection(m) - } - - zlog.Ins().InfoF("====> User %s-%s", user.UID, user.UserName, " left =====") - -} - // go run nb_alarm_agent.go -c conf\nbi_alarm_agent.json func main() { - //配置初始化 + // 配置初始化 conf.Init(args.Args.ConfigFile) + // 初始数据库 db.Init() + //创建服务器句柄 for _, cg := range conf.OmcConf.Channel { - serverName := fmt.Sprintf("omc-tcp-Server-port:%d", cg.TCPPort) + serverName := fmt.Sprintf("%s:%d", cg.BindFlag, cg.TCPPort) //注册用户管理模块 - m := core.NewManager(serverName, cg.BindFlag, cg.Province, cg.DeviceCode) - - //new 一个TCP服务 - s := znet.NewUserConfServer(&zconf.Config{ - TCPPort: cg.TCPPort, - Name: serverName, - Host: "0.0.0.0", - MaxConn: conf.OmcConf.MaxConn, - WorkerPoolSize: uint32(conf.OmcConf.WorkerPoolSize), - HeartbeatMax: conf.OmcConf.HeartbeatMax, - LogDir: conf.OmcConf.LogDir, - LogFile: conf.OmcConf.LogFile, - }) - - //注册客户端连接建立和丢失函数 - s.SetOnConnStart(OnConnectionAdd) - s.SetOnConnStop(OnConnectionLost) - - //注册路由 - s.AddRouter(omc.ReqLoginAlarm, &api2.LoginApi{}) - s.AddRouter(omc.ReqHeartBeat, &api2.HeartBeatApi{}) - s.AddRouter(omc.CloseConnAlarm, &api2.CloseApi{}) - s.AddRouter(omc.ReqSyncAlarmMsg, &api2.SyncAlarmApi{}) - s.AddRouter(omc.ReqSyncAlarmFile, &api2.SyncAlarmFileApi{}) - s.AddRouter(omc.ReqCMCALoginSeq, &api2.SyncAlarmFileApi{}) - s.AddRouter(omc.ReqCMCALoginAlarm, &api2.SyncAlarmFileApi{}) - - s.AddRouter(290, &api2.ServerRouter{}) - - //添加LTV数据格式Decoder - s.SetDecoder(decoder.NewOmcDecoder()) - //添加LTV数据格式的Pack封包Encoder - s.SetPacket(dpack.NewDataPack()) - - // (启动心跳检测) - s.StartHeartBeatWithOption(60*time.Second, &ziface.HeartBeatOption{ - MakeMsg: core.MyHeartBeatMsg, - OnRemoteNotAlive: core.MyOnRemoteNotAlive, - Router: &api2.HeartBeatApi{}, - HeadBeatMsgID: uint32(0xFF), - }) - // 设置默认的心跳发送函数 - heart := s.GetHeartBeat() - heart.SetHeartbeatFunc(core.MyHeartBeat) + m := manage.NewManager(serverName, cg.BindFlag, cg.Province, cg.DeviceCode) //启动服务 + s := router.LoadServer(cg) go s.Serve() //启动实时告警 diff --git a/omc/msg.go b/omc/msg.go deleted file mode 100644 index a5980d1..0000000 --- a/omc/msg.go +++ /dev/null @@ -1,31 +0,0 @@ -package omc - -// ErrorMsg ackLoginAlarm;result=fail;resDesc=username-error -func ErrorMsg(msgType string, reqID string, desc string) []byte { - msgBody := MsgBody{ - MsgName: msgType, - Msg: make(map[string]string, 0), - } - if reqID != "" { - msgBody.Msg["reqId"] = reqID - } - msgBody.Msg["result"] = "fail" - msgBody.Msg["resDesc"] = desc - msgBody.Pack() - return msgBody.RawData -} - -func SuccessMsg(msgType string, reqID string, desc string) []byte { - msgBody := MsgBody{ - MsgName: msgType, - Msg: make(map[string]string, 0), - } - if reqID != "" { - msgBody.Msg["reqId"] = reqID - } - msgBody.Msg["result"] = "succ" - msgBody.Msg["resDesc"] = desc - - msgBody.Pack() - return msgBody.RawData -} diff --git a/omc/omc_pack.go b/omc/omc_pack.go deleted file mode 100644 index 03bf8e6..0000000 --- a/omc/omc_pack.go +++ /dev/null @@ -1,45 +0,0 @@ -package omc - -import ( - "errors" - "fmt" - "strings" -) - -type MsgBody struct { - UID string - RawData []byte - MsgName string - Msg map[string]string -} - -// Decode -//reqLoginAlarm;user=yiy;key=qw#$@;type=msg -func (o *MsgBody) Decode() error { - multi := strings.Split(string(o.RawData), ";") - if len(multi) < 1 { - return errors.New("invalid msg body") - } - for i := 1; i < len(multi); i++ { - m := strings.Split(multi[i], "=") - if len(m) != 2 { - return errors.New("invalid msg body") - } - o.Msg[m[0]] = m[1] - } - return nil -} - -// Pack -//reqLoginAlarm;user=yiy;key=qw#$@;type=msg -func (o *MsgBody) Pack() error { - var multi []string - multi = append(multi, o.MsgName) - for i, v := range o.Msg { - item := fmt.Sprintf("%s=%s", i, v) - multi = append(multi, item) - } - raw := strings.Join(multi, ";") - o.RawData = []byte(raw) - return nil -} diff --git a/omc/omc_type.go b/omc/omc_type.go deleted file mode 100644 index 92e28d9..0000000 --- a/omc/omc_type.go +++ /dev/null @@ -1,39 +0,0 @@ -package omc - -const ( - RealTimeAlarm = 0 - ReqLoginAlarm = 1 - AckLoginAlarm = 2 - ReqSyncAlarmMsg = 3 - AckSyncAlarmMsg = 4 - ReqSyncAlarmFile = 5 - AckSyncAlarmFile = 6 - AckSyncAlarmFileResult = 7 - ReqHeartBeat = 8 - AckHeartBeat = 9 - CloseConnAlarm = 10 - ReqCMCALoginAlarm = 11 - ReqCMCALoginSeq = 12 - AckCMCALoginSeq = 13 -) - -//定义user type -const ( - MSG = "msg" - FILE = "ftp" -) - -func OrigSeverity(os string) int32 { - switch os { - case "Critical": - return 1 - case "Major": - return 2 - case "Minor": - return 3 - case "Warning": - return 4 - default: - return 0 - } -} diff --git a/router/router.go b/router/router.go new file mode 100644 index 0000000..aa678be --- /dev/null +++ b/router/router.go @@ -0,0 +1,82 @@ +package router + +import ( + "fmt" + "omc/conf" + "omc/core/decoder" + "omc/core/dpack" + heartbeat "omc/core/heart_beat" + "omc/core/interceptor" + "omc/core/manage" + "omc/handle/api" + "time" + + "github.com/aceld/zinx/zconf" + "github.com/aceld/zinx/ziface" + "github.com/aceld/zinx/znet" +) + +const ( + RealTimeAlarm = 0 + ReqLoginAlarm = 1 + AckLoginAlarm = 2 + ReqSyncAlarmMsg = 3 + AckSyncAlarmMsg = 4 + ReqSyncAlarmFile = 5 + AckSyncAlarmFile = 6 + AckSyncAlarmFileResult = 7 + ReqHeartBeat = 8 + AckHeartBeat = 9 + CloseConnAlarm = 10 + ReqCMCALoginAlarm = 11 + ReqCMCALoginSeq = 12 + AckCMCALoginSeq = 13 +) + +func LoadServer(cg conf.Channel) ziface.IServer { + serverName := fmt.Sprintf("%s:%d", cg.BindFlag, cg.TCPPort) + + //new 一个TCP服务 + s := znet.NewUserConfServer(&zconf.Config{ + TCPPort: cg.TCPPort, + Name: serverName, + Host: "0.0.0.0", + MaxConn: conf.OmcConf.MaxConn, + WorkerPoolSize: uint32(conf.OmcConf.WorkerPoolSize), + HeartbeatMax: conf.OmcConf.HeartbeatMax, + LogDir: conf.OmcConf.LogDir, + LogFile: conf.OmcConf.LogFile, + }) + + //注册客户端连接建立和丢失函数 + s.SetOnConnStart(manage.OnConnectionAdd) + s.SetOnConnStop(manage.OnConnectionLost) + + //添加LTV数据格式Decoder + s.SetDecoder(decoder.NewOmcDecoder()) + //添加LTV数据格式的Pack封包Encoder + s.SetPacket(dpack.NewDataPack()) + + // (启动心跳检测) + s.StartHeartBeatWithOption(60*time.Second, &ziface.HeartBeatOption{ + MakeMsg: heartbeat.MyHeartBeatMsg, + OnRemoteNotAlive: heartbeat.MyOnRemoteNotAlive, + Router: &api.ReqHeartBeat{}, + HeadBeatMsgID: api.ReqHeartBeatMsgID, + }) + // 设置默认的心跳发送函数 + heart := s.GetHeartBeat() + heart.SetHeartbeatFunc(heartbeat.MyHeartBeat) + + // 添加自定义拦截器 + s.AddInterceptor(&interceptor.GlobalInterceptor{}) + + //注册路由 ================ + s.AddRouter(api.ReqLoginAlarmMsgID, &api.ReqLoginAlarm{}) + s.AddRouter(api.ReqCMCALoginSeqMsgID, &api.ReqCMCALoginSeq{}) + s.AddRouter(api.ReqCMCALoginAlarmMsgID, &api.ReqCMCALoginAlarm{}) + s.AddRouter(api.ReqSyncAlarmMsgID, &api.ReqSyncAlarmMsg{}) + s.AddRouter(api.CloseConnAlarmMsgID, &api.CloseConnAlarm{}) + + return s +}