package im import ( "encoding/json" "flag" "fmt" "io" "log" "net" "net/http" "sync" "time" "git.rosy.net.cn/baseapi/utils" "git.rosy.net.cn/baseapi/platformapi/mtwmapi" "git.rosy.net.cn/jx-callback/globals/api" "github.com/gazeboxu/mapstructure" "github.com/gorilla/websocket" "gopkg.in/ini.v1" ) // ClientManager 连接管理 type ClientManager struct { ClientIdMap map[string]*Client // 全部的连接 ClientIdMapLock sync.RWMutex // 读写锁 Connect chan *Client // 连接处理 DisConnect chan *Client // 断开连接处理 GroupLock sync.RWMutex Groups map[string][]string SystemClientsLock sync.RWMutex SystemClients map[string][]string } // Client 客户端连接信息 type Client struct { ClientId string // 标识ID Socket *websocket.Conn // 用户连接 ConnectTime uint64 // 首次连接时间 IsDeleted bool // 是否删除或下线 UserId string // 业务端标识用户ID //Extend string // 扩展字段,用户可以自定义 //GroupList []string } //channel通道结构体 type clientInfo struct { ClientId string `json:"clientId" validate:"required"` //链接ID Data interface{} SendUserId string MessageId string Code int Msg string } // RetData 统一返回值结构体 type RetData struct { Code int `json:"code"` //响应code Msg string `json:"msg"` //响应msg success/fail Data interface{} `json:"data"` //信息 //MessageType string `json:"messageType"` //消息类型 heart-心跳检测;send-发送消息;receive-接收消息 //MessageId string `json:"messageId"` //发送/接收信息 id //UserId string `json:"userId"` //必须是平台方userID } type global struct { LocalHost string //本机内网IP ServerList map[string]string ServerListLock sync.RWMutex } type commonConf struct { HttpPort string RPCPort string Cluster bool CryptoKey string } // SendData 客户端写入参数 type SendData struct { //ClientId string `json:"clientId" validate:"required"` //链接ID VendorID int `json:"vendorID"` //消息来源平台ID Data interface{} `json:"data"` //发送给平台 美团/饿了么消息结构体 //返回值 //Code int `json:"code"` //Msg string `json:"msg"` //SendUserId string `json:"sendUserId"` } // JXMsg 京西消息结构体 type JXMsg struct { SendType string `json:"sendType"` //消息发送方 jx-商家;mt-美团;elm-饿了么 Data interface{} `json:"data"` //美团/饿了么 单聊消息 } // GetUserListReq 获取门店用户聊天列表 type GetUserListReq struct { VendorStoreID string `json:"vendorStoreID"` //平台门店id VendorID string `json:"vendorID"` //平台标识id AppID string `json:"appID"` //应用ID } type GetChatDetailReq struct { VendorStoreID string `json:"vendorStoreID"` //平台门店id VendorID string `json:"vendorID"` //平台标识id AppID string `json:"appID"` //应用ID UserID string `json:"userID"` //userID/groupID } // UserMessageList 用户消息列表 type UserMessageList struct { VendorID int `json:"vendorID"` //平台品牌 10-美团 11-饿了么 UserID string `json:"userID"` //用户ID NewMessageNum int `json:"NewMessageNum"` //新消息数量 LatestMsg string `json:"latestMsg"` //最新一条消息 LatestTime int `json:"latestTime"` //最新一条消息发送时间 } type RelInfo struct { VendorStoreID string `json:"vendorStoreID"` //平台门店id VendorID string `json:"vendorID"` //平台标识id AppID string `json:"appID"` //应用ID } type UserRelInfo struct { VendorStoreID string `json:"vendorStoreID"` //平台门店id VendorID string `json:"vendorID"` //平台标识id AppID string `json:"appID"` //应用ID UserID string `json:"userID"` //用户id/groupID } var ( cfg *ini.File rdb = api.Cacher Manager = NewClientManager() // 管理者 CommonSetting = &commonConf{} GlobalSetting = &global{} ToClientChan chan clientInfo heartbeatInterval = 60 * time.Second // 心跳间隔 HeartCheckMsg = "~#HHHBBB#~" //心跳检测消息 HeartCheckSuccess = "HB" //成功发送返回心跳消息 VendorIDMT = 10 //im美团 VendorIDELM = 11 //im饿了么 SendTypeJx = "jx" //京西客户端发送方标识 SendTypeMt = "mt" //美团用户发送方标识符 SendTypeElm = "elm" //饿了么用户发送方标识符 MTIMPushUrl = "wss://wpush.meituan.com/websocket" //buildPushConnect建立长连接 ) const ( ExpireTimeDay = 24 * time.Hour //redis一天过期时间 maxMessageSize = 8192 // 最大的消息大小 ) type renderData struct { ClientId string `json:"clientId"` } const ( SuccessCode = 0 SuccessMsg = "success" Fail = -1 FailMsg = "fail" SYSTEM_ID_ERROR = -1001 ONLINE_MESSAGE_CODE = 1001 OFFLINE_MESSAGE_CODE = 1002 ) // Render 统一返回值 func Render(conn *websocket.Conn, messageId string, code int, message string, data interface{}) error { return conn.WriteJSON(RetData{ Code: code, Msg: message, Data: data, }) } func ClientRender(w http.ResponseWriter, code int, msg string, data interface{}) (str string) { var retData RetData retData.Code = code retData.Msg = msg retData.Data = data retJson, _ := json.Marshal(retData) str = string(retJson) w.Header().Set("Content-Type", "application/json; charset=utf-8") _, _ = io.WriteString(w, str) return } func ConnRender(conn *websocket.Conn, data interface{}) (err error) { err = conn.WriteJSON(RetData{ Code: SuccessCode, Msg: "success", Data: data, }) return } // Default 给默认值 func Default() { CommonSetting = &commonConf{ HttpPort: "6000", RPCPort: "7000", Cluster: false, CryptoKey: "Adba723b7fe06819", } GlobalSetting = &global{ LocalHost: getIntranetIp(), ServerList: make(map[string]string), } } // Setup 初始化全局设置变量 func Setup() { configFile := flag.String("c", "conf/app.ini", "-c conf/app.ini") var err error cfg, err = ini.Load(*configFile) if err != nil { log.Fatalf("setting.Setup, fail to parse 'conf/app.ini': %v", err) } mapTo("common", CommonSetting) GlobalSetting = &global{ LocalHost: getIntranetIp(), ServerList: make(map[string]string), } fmt.Printf("LocalHost=%s\n ServerList=%s\n", GlobalSetting.LocalHost, utils.Format4Output(GlobalSetting.ServerList, false)) } // mapTo map section func mapTo(section string, v interface{}) { err := cfg.Section(section).MapTo(v) if err != nil { log.Fatalf("Cfg.MapTo %s err: %v", section, err) } } //获取本机IP func getIntranetIp() string { addrs, _ := net.InterfaceAddrs() for _, addr := range addrs { // 检查ip地址判断是否回环地址 if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { if ipnet.IP.To4() != nil { return ipnet.IP.String() } } } return "" } // GenFullUrl 组装完整websocket url以及生成clientID func GenFullUrl() (fullUrl string) { resp, err := api.MtwmAPI.GetConnectionToken() if err != nil { return "" } retVal := mtwmapi.GetConnTokenResp{} err = mapstructure.Decode(resp, &retVal) fullUrl = MTIMPushUrl + "/" + retVal.AppKey + "/" + retVal.ConnectionToken //clientID = api.MtwmAPI.GetAppID() + ":" + retVal.ConnectionToken //打印输出 //fmt.Printf("Create websocket connect failCount:%d", retVal.UserCount) return fullUrl }