From 0901cfc5739fe7989a0f6973429ca1f7e7684bea Mon Sep 17 00:00:00 2001 From: richboo111 Date: Tue, 25 Apr 2023 10:31:54 +0800 Subject: [PATCH] im --- platformapi/ebaiapi/callback.go | 63 ++++- platformapi/ebaiapi/elm_im_test.go | 39 ++- platformapi/ebaiapi/elm_lm.go | 99 +++---- platformapi/mtwmapi/im.go | 46 +++- platformapi/mtwmapi/im_server.go | 171 ++++++++++++ platformapi/mtwmapi/im_test.go | 258 +++++++++++++++++- .../tiktok_shop/tiktok_api/afs_test.go | 2 +- .../tiktok_shop/tiktok_api/store_test.go | 2 +- platformapi/trenditapi/trendit_test.go | 57 ++-- platformapi/trenditapi/trenditapi.go | 73 +++++ platformapi/xpyunapi/xpuapi_test.go | 17 +- platformapi/xpyunapi/xputil.go | 47 ++++ 12 files changed, 784 insertions(+), 90 deletions(-) create mode 100644 platformapi/mtwmapi/im_server.go diff --git a/platformapi/ebaiapi/callback.go b/platformapi/ebaiapi/callback.go index 9897a7ed..4f2ebdb3 100644 --- a/platformapi/ebaiapi/callback.go +++ b/platformapi/ebaiapi/callback.go @@ -3,11 +3,10 @@ package ebaiapi import ( "errors" "fmt" - "net/http" - "net/url" - "git.rosy.net.cn/baseapi" "git.rosy.net.cn/baseapi/utils" + "net/http" + "net/url" ) const ( @@ -20,6 +19,11 @@ const ( CmdShopMsgPush = "shop.msg.push" CmdShopBindMsg = "shop.bind.msg" CmdShopUnbindMsg = "shop.unbind.msg" + + //IM消息回调通知 + CmdImMessageSendEvent = "im.message.send.event" //用户/骑手消息通知 + CmdImMessageReadEvent = "im.message.read.event" //用户/骑手已读通知 + ) type CallbackResponse struct { @@ -85,6 +89,55 @@ type CBUserCancelInfo struct { CancelType int `json:"cancel_type"` } +//temp IM 用户/骑手消息通知 +type TempSent struct { + SubBizType string `json:"subBizType"` //业务子类型,枚举值:SEND_MESSAGE-发送消息 + BizType string `json:"bizType"` //业务类型,枚举值:IM-消息 + PayLoad struct { + SenderID string `json:"senderId"` //角色+随机数字串;角色:10(用户)、20(骑手)、30(商家)、32(连锁账号登录) + ReceiverIDs []string `json:"receiverIds"` //角色+随机数字串;角色:10(用户)、20(骑手)、30(商家)、32(连锁账号登录) + CreateTime int64 `json:"createTime"` //时间戳 + GroupID string `json:"groupId"` //会话id + MsgID string `json:"msgId"` //消息ID + ContentType int `json:"contentType"` //消息类型,枚举值:1-普通文本 + } `json:"payload"` + PlatformShopID string `json:"platformShopId"` //平台门店ID +} + +//IM 用户/骑手消息通知 +type ImMessageSend struct { + SubBizType string `json:"subBizType"` //业务子类型,枚举值:SEND_MESSAGE-发送消息 + BizType string `json:"bizType"` //业务类型,枚举值:IM-消息 + PayLoad PayLoad `json:"payLoad"` + PlatformShopID string `json:"platformShopId"` //平台门店ID +} +type PayLoad struct { + SenderID string `json:"senderId"` //角色+随机数字串;角色:10(用户)、20(骑手)、30(商家)、32(连锁账号登录) + ReceiverIDs []string `json:"receiverIds"` //角色+随机数字串;角色:10(用户)、20(骑手)、30(商家)、32(连锁账号登录) + CreateTime int64 `json:"createTime"` //时间戳 + GroupID string `json:"groupId"` //会话id + MsgID string `json:"msgId"` //消息ID + ContentType int `json:"contentType"` //消息类型,枚举值:1-普通文本 + Content string `json:"content"` +} + +//ContentType =1-普通文本,8-@消息 +type Content struct { + Text string `json:"text"` +} + +//IM 用户/骑手已读通知 +type ImMessageRead struct { + PlatformShopID string `json:"platformShopId"` //平台门店ID + BizType string `json:"bizType"` //业务类型,枚举值:IM-消息 + SubBizType string `json:"subBizType"` //业务子类型,枚举值:READ_MESSAGE-读取消息 + PayLoad struct { + MsgIDs string `json:"msgIds"` //消息ID 列表 + CID string `json:"cid"` //会话id + UID string `json:"uid"` //已读操作人 + } `json:"payLoad"` +} + func (a *API) Err2CallbackResponse(cmd string, err error, data interface{}) *CallbackResponse { response := &CallbackResponse{ Cmd: "resp." + cmd, @@ -168,6 +221,10 @@ func (a *API) GetCallbackMsg(request *http.Request) (msg *CallbackMsg, callbackR case CmdOrderUserCancel: var userCancelData CBUserCancelInfo tmpObj = &userCancelData + case CmdImMessageSendEvent: + tmpObj = &ImMessageSend{} + case CmdImMessageReadEvent: + tmpObj = &ImMessageRead{} } if tmpObj != nil { if utils.Map2StructByJson(msg.Body, tmpObj, true) == nil { diff --git a/platformapi/ebaiapi/elm_im_test.go b/platformapi/ebaiapi/elm_im_test.go index 25cc1dd4..710af94e 100644 --- a/platformapi/ebaiapi/elm_im_test.go +++ b/platformapi/ebaiapi/elm_im_test.go @@ -1,6 +1,10 @@ package ebaiapi -import "testing" +import ( + "encoding/json" + "fmt" + "testing" +) func TestGetStoreImStatus(t *testing.T) { data, err := api.GetStoreIMStatus("1139781155") @@ -9,3 +13,36 @@ func TestGetStoreImStatus(t *testing.T) { } t.Log(data) } + +//获取门店线上IM状态 +func TestAPI_GetImOnlineStatus(t *testing.T) { + +} + +func TestParseMultilayerJson(t *testing.T) { + //var data1 = `{"subBizType": "SEND_MESSAGE","bizType": "IM","payload": {"senderId":"20235760123","receiverIds":["105872382789","30506545123","20235760456"],"createTime":1642647893901,"groupId":"$2$10514249123$PNM","msgId": "1654907240123.PNM","contentType": "1","content":"{"text":"测试消息"}"},"platformShopId": "32267818868"}` + var data8 = `{ + "subBizType": "SEND_MESSAGE", + "bizType": "IM", + "payload": { + "senderId": "102000022769889", + "receiverIds": ["102000022769889", "30507511668"], + "createTime": 1680579669946, + "groupId": "$2$10996707119$PNM", + "msgId": "1734454964456.PNM", + "contentType": 8, + "content": "{\"elements\":[{\"elementContent\":\"{\\\"atAll\\\":false,\\\"defaultNick\\\":\\\"\\\",\\\"uid\\\":{\\\"appUid\\\":\\\"30507511668\\\",\\\"domain\\\":\\\"eleme\\\"}}\",\"elementType\":3},{\"elementContent\":\"{\\\"extensions\\\":{},\\\"text\\\":\\\"@商家 我选的就是退一个杯子呀\\\"}\",\"elementType\":1}]}" + }, + "platformShopId": "507511668" +}` + //retVal1 := ParseMultilayerJson(data1) + //fmt.Println(utils.Format4Output(retVal1, false)) + //retVal8 := ParseMultilayerJson(data8) + //fmt.Println(utils.Format4Output(retVal8, false)) + temp := ImMessageSent{} + err := json.Unmarshal([]byte(data8), &temp) + if err != nil { + fmt.Println(err) + } + fmt.Println(temp) +} diff --git a/platformapi/ebaiapi/elm_lm.go b/platformapi/ebaiapi/elm_lm.go index 61842d20..3ca13640 100644 --- a/platformapi/ebaiapi/elm_lm.go +++ b/platformapi/ebaiapi/elm_lm.go @@ -9,10 +9,60 @@ const ( IMStoreStatusOnLine = "ONLINE" // 门店im在线状态 IMStoreStatusBusy = "BUSY" // 忙碌状态 IMType = "IM" // 业务类型,消息默认IM - SubIMType = "SEND_MESSAGE" // 子业务类型,发送消息。默认值:SEND_MESSAGE + SubTypeDef = "SEND_MESSAGE" // 子业务类型,发送消息。默认值:SEND_MESSAGE ReadIMType = "READ_MESSAGE" + + //消息类型 + ContentTypeNormal = 1 //普通文本信息 + ContentTypeAt = 8 //@ 消息 ) +// BusinessSendMsgReq im发送消息 +type BusinessSendMsgReq struct { + PlatformShopId string `json:"platformShopId"` // 平台门店id + BizType string `json:"bizType"` // 业务类型,IM消息。默认值:IM + SubBizType string `json:"subBizType"` // 子业务类型,发送消息。默认值:SEND_MESSAGE + Payload BusinessMsgPayload `json:"payload"` +} + +type BusinessMsgPayload struct { + GroupId string `json:"groupId"` // 会话ID + MsgId string `json:"msgId"` // 消息ID + ReceiverIds []string `json:"receiverIds"` // 接收人列表 + Content string `json:"content"` // 发送内容,格式:JSON {"text":"msg"} + ContentType string `json:"contentType"` // 内容类型,目前只支持文本消息。枚举值: 1-普通文本 +} + +// BusinessSendMsg 门店老板发送消息 主要用这个 +func (a *API) BusinessSendMsg(param *BusinessSendMsgReq) error { + result, err := a.AccessAPI("im.message.send", utils.Struct2MapByJson(param)) + if err != nil { + return err + } + if result.ErrNo != 0 { + return errors.New(result.Error) + } + + return nil +} + +// SettingStoreMsgRead 设置消息已读 +func (a *API) SettingStoreMsgRead(platformShopId string, msgId string) error { + result, err := a.AccessAPI("im.message.read", map[string]interface{}{ + "platformShopId": platformShopId, + "bizType": IMType, + "subBizType": ReadIMType, + "payload": map[string]string{"msgId": msgId}, + }) + if err != nil { + return err + } + if result.ErrNo != 0 { + return errors.New(result.Error) + } + return nil +} + // GetStoreIMStatus 获取门店的im状态(这个应该不怎么用) func (a *API) GetStoreIMStatus(platformShopId string) (int, error) { result, err := a.AccessAPI("im.getIMStatus", map[string]interface{}{"platformShopId": platformShopId}) @@ -51,50 +101,3 @@ func (a *API) SetImOnlineStatus(platformShopId string, status string) error { return nil } - -// BusinessSendMsg 门店老板发送消息 -func (a *API) BusinessSendMsg(param *BusinessSendMsgReq) error { - result, err := a.AccessAPI("im.message", utils.Struct2MapByJson(param)) - if err != nil { - return err - } - if result.ErrNo != 0 { - return errors.New(result.Error) - } - - return nil -} - -// BusinessSendMsgReq im发送消息 -type BusinessSendMsgReq struct { - PlatformShopId string `json:"platformShopId"` // 平台门店id - BizType string `json:"bizType"` // 业务类型,IM消息。默认值:IM - SubBizType string `json:"subBizType"` // 子业务类型,发送消息。默认值:SEND_MESSAGE - Payload BusinessMsgPayload `json:"payload"` -} - -type BusinessMsgPayload struct { - GroupId string `json:"groupId"` // 会话ID - MsgId string `json:"msgId"` // 消息ID - ReceiverIds []string `json:"receiverIds"` // 接收人列表 - Content string `json:"content"` // 发送内容,格式:JSON {"text":"msg"} - ContentType string `json:"contentType"` // 内容类型,目前只支持文本消息。枚举值: 1-普通文本 -} - -// SettingStoreMsgRead 设置消息已读 -func (a *API) SettingStoreMsgRead(platformShopId string, msgId string) error { - result, err := a.AccessAPI("im.message.read", map[string]interface{}{ - "platformShopId": platformShopId, - "bizType": IMType, - "subBizType": ReadIMType, - "payload": map[string]string{"msgId": msgId}, - }) - if err != nil { - return err - } - if result.ErrNo != 0 { - return errors.New(result.Error) - } - - return nil -} diff --git a/platformapi/mtwmapi/im.go b/platformapi/mtwmapi/im.go index e28f2adb..9ce88f89 100644 --- a/platformapi/mtwmapi/im.go +++ b/platformapi/mtwmapi/im.go @@ -1,8 +1,50 @@ package mtwmapi +const ( + MsgSourceStore = 1 //商家 + MsgSourceUser = 2 //用户 + MsgTypeText = 1 //文字 + MsgTypePic = 2 //图片 + MsgTypeVoice = 3 //语音 + MsgTypeGoodsCard = 4 //商品卡片 + MsgTypeOrderCard = 5 //订单卡片 +) + +//单聊信息体 +type SingleChat struct { + AppID int `json:"app_id"` //应用标识 + AppPoiCode string `json:"app_poi_code"` //门店标识 + Cts int `json:"cts"` //消息发送时间,10位时间戳 + MsgContent string `json:"msg_content"` //消息内容 + MsgID int `json:"msg_id"` //消息id,确保消息唯一性,发送消息时,为三方的消息id,接收消息时,为美团的消息id + MsgSource int `json:"msg_source"` //消息发送方 商家:1,用户:2 + MsgType int `json:"msg_type"` //消息类型: 文字-1; 图片-2;语音-3,注意b2c不支持语音; 商品卡片-4(发送商品卡片类型则不关注msg_content); 订单卡片-5(订单卡片类型商家只能接收消息,不支持给用户发送消息,只支持单聊) + OpenUserID int `json:"open_user_id"` //用户id + OrderID int `json:"order_id"` // 订单id + AppSpuCodes string `json:"app_spu_codes"` //开放平台侧商品标识(无须加密) +} + +//获取长链接token返回参数 +type GetConnTokenResp struct { + ConnectionToken string `json:"connectionToken"` //建立长连接的token + UserCount int `json:"userCount"` //30分钟内,消息发送失败的用户数 + AppKey string `json:"appKey"` //建立长连接的appkey +} + //获取长连接的token //https://developer.waimai.meituan.com/home/docDetail/461 -func (a *API) GetConnectionToken() (err error) { - _, err = a.AccessAPI("wm/IM/getConnectionToken", false, nil) +func (a *API) GetConnectionToken() (retVal interface{}, err error) { + retVal, err = a.AccessAPI("wm/IM/getConnectionToken", false, nil) + return retVal, err +} + +//设置消息已读 +//https://open-shangou.meituan.com/home/docDetail/465 +func (a *API) MsgRead(appPoiCode string, msgID, openUserID int) error { + _, err := a.AccessAPI("/wm/IM/msgRead", false, map[string]interface{}{ + "app_poi_code": appPoiCode, + "msg_id": msgID, + "open_user_id": openUserID, + }) return err } diff --git a/platformapi/mtwmapi/im_server.go b/platformapi/mtwmapi/im_server.go new file mode 100644 index 00000000..0d37edf6 --- /dev/null +++ b/platformapi/mtwmapi/im_server.go @@ -0,0 +1,171 @@ +package mtwmapi + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "errors" + "io" + "log" + "sync" +) + +//ip配置信息 +type global struct { + LocalHost string //本机内网IP + RemoteHost string //远程端IP + RemotePort string //远程端口 + ServerList map[string]string + ServerListLock sync.RWMutex +} +type commonConf struct { + HttpPort string + RPCPort string + Cluster bool + CryptoKey string +} + +var ( + GlobalSetting = &global{} + CommonSetting = &commonConf{ + HttpPort: "6000", + RPCPort: "7000", + Cluster: false, + CryptoKey: "Adba723b7fe06819", + } +) + +/* +以下为clientID相关逻辑 +*/ + +//对称加密IP和端口,当做clientId +func GenClientId() string { + raw := []byte(GlobalSetting.LocalHost + ":" + CommonSetting.RPCPort) + //raw := []byte(hostStr) + str, err := Encrypt(raw, []byte(CommonSetting.CryptoKey)) + if err != nil { + log.Fatal(err) + } + return str +} + +func Encrypt(rawData, key []byte) (string, error) { + data, err := aesCBCEncrypt(rawData, key) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString(data), nil +} + +//AES加密 +func aesCBCEncrypt(rawData, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return []byte{}, err + } + + //填充原文 + blockSize := block.BlockSize() + + rawData = pKCS7Padding(rawData, blockSize) + //初始向量IV必须是唯一,但不需要保密 + cipherText := make([]byte, blockSize+len(rawData)) + //block大小 16 + iv := cipherText[:blockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return []byte{}, err + } + + //block大小和初始向量大小一定要一致 + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(cipherText[blockSize:], rawData) + + return cipherText, nil +} + +func DecryptDESECB(d, key []byte) string { + data, err := base64.StdEncoding.DecodeString(string(d)) + if err != nil { + return "" + } + block, err := aes.NewCipher(key) + if err != nil { + return "" + } + bs := block.BlockSize() + if len(data)%bs != 0 { + return "" + } + out := make([]byte, len(data)) + dst := out + for len(data) > 0 { + block.Decrypt(dst, data[:bs]) + data = data[bs:] + dst = dst[bs:] + } + out = PKCS5UnPadding(out) + return string(out) +} +func PKCS5UnPadding(origData []byte) []byte { + length := len(origData) + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} + +func Decrypt(rawData string, key []byte) (string, error) { + data, err := base64.StdEncoding.DecodeString(rawData) + if err != nil { + return "", err + } + dnData, err := aesCBCDncrypt(data, key) + if err != nil { + return "", err + } + return string(dnData), nil +} + +//AES解密 +func aesCBCDncrypt(encryptData, key []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return []byte{}, err + } + + blockSize := block.BlockSize() + + if len(encryptData) < blockSize { + return []byte{}, errors.New("ciphertext too short") + } + iv := encryptData[:blockSize] + encryptData = encryptData[blockSize:] + + if len(encryptData)%blockSize != 0 { + return []byte{}, errors.New("ciphertext is not a multiple of the block size") + } + + mode := cipher.NewCBCDecrypter(block, iv) + + mode.CryptBlocks(encryptData, encryptData) + //解填充 + encryptData, err = pKCS7UnPadding(encryptData) + return encryptData, err +} + +func pKCS7Padding(ciphertext []byte, blockSize int) []byte { + padding := blockSize - len(ciphertext)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(ciphertext, padText...) +} + +func pKCS7UnPadding(origData []byte) ([]byte, error) { + length := len(origData) + unPadding := int(origData[length-1]) + if length-unPadding < 0 || length-unPadding > len(origData) { + return nil, errors.New("unPadding error") + } + + return origData[:(length - unPadding)], nil +} diff --git a/platformapi/mtwmapi/im_test.go b/platformapi/mtwmapi/im_test.go index 8052d6a6..09ead645 100644 --- a/platformapi/mtwmapi/im_test.go +++ b/platformapi/mtwmapi/im_test.go @@ -1,13 +1,267 @@ package mtwmapi import ( + "encoding/json" + "fmt" + "git.rosy.net.cn/baseapi/utils" + "github.com/gazeboxu/mapstructure" + "github.com/go-redis/redis" + "github.com/gorilla/websocket" + "sync" "testing" + "time" ) +const ( + MTIMPushUrl = "wss://wpush.meituan.com/websocket" + TestAppID = "589_WMOPEN" + TestToken = "wo589i4VsZHFH2fh4uVsr6Dtc3k6vG8Xu0vxpreBQFy6QAvg" + + TestMTIMPushUrl = "wss://wpush.meituan.com/websocket/589_WMOPEN/wo589i4VsZHFH2fh4uVsr6Dtc3k6vG8Xu0vxpreBQFy6QAvg" +) + +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 + Clients map[string]*Client // 保存连接 + Accounts map[string][]string // 账号和连接关系,map的key是账号id即:AccountId,这里主要考虑到一个账号多个连接 + mu *sync.Mutex +} + +var Manager = NewClientManager() + +func NewClientManager() (clientManager *ClientManager) { + clientManager = &ClientManager{ + Accounts: make(map[string][]string), + ClientIdMap: make(map[string]*Client, 100), + Connect: make(chan *Client, 10000), + DisConnect: make(chan *Client, 10000), + mu: new(sync.Mutex), + } + + return +} + +var RegisterChan = make(chan *Client, 100) + +type Client struct { + ID string // 连接ID + AccountId string // 账号id, 一个账号可能有多个连接 + Socket *websocket.Conn // 连接 + HeartbeatTime int64 // 前一次心跳时间 +} + +var rdb = redis.NewClient(&redis.Options{ + //Addr: "www.jxc4.com:6379", + //Password: "", + Addr: "127.0.0.1:6379", + Password: "123456", + DB: 0, +}) + +//测试心跳 +func TestHeartCheck(t *testing.T) { + //go func() { + // ticker := time.NewTicker(5 * time.Second) + // defer ticker.Stop() + // for { + // <-ticker.C + //发送心跳 + conn, resp, err := websocket.DefaultDialer.Dial(TestMTIMPushUrl, nil) + fmt.Println(resp, err) + err1 := conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(time.Second)) + for { + _, msg, err := conn.ReadMessage() + if err != nil { + break + } + fmt.Printf("%s receive: %s\n", conn.RemoteAddr(), string(msg)) + } + fmt.Println(err1) + //} + //}() +} + func TestGetConnectionToken(t *testing.T) { - err := api.GetConnectionToken() + resp, err := api.GetConnectionToken() if err != nil { t.Fatal(err) } - // t.Log(utils.Format4Output(result, false)) + retVal := GetConnTokenResp{} + err = mapstructure.Decode(resp, &retVal) + fmt.Println(err) + fmt.Println(utils.Format4Output(retVal, false)) +} + +//解密测试 +func TestAesCBCDecrypt(t *testing.T) { + secret := "a81eb3df418d83d6a1a4b7c572156d2f" + key := secret[:16] + str := "qodhoVd4IGtgPKrvYwq6QrzBecJZkeSUPYR88iGRUsCRFmCFxDHpUhqsbBztNXQx" + //str := "Vv+Y/K8vfS42W+P7xq26aIb6uoaG/nL0ZoMMXpitc5QQ3XJm3Roh10NuSoojYrG/3JZwbzgtYA+kBvodoY2eJV00f9MBY+kLkxToP+aSofsYva9tHbipvjVtexebc+eP7aQMtzbwU4BNNnuRG6e7TkXP+BLdtiGsyvHolGfky+p2fZgWes9R6JIxkuRCXW/yBhUo8F+wWCZ2YQl/szp5lHJ3cmneD6cwem36E0FBcvxZNB9an4pRkBrqi1p43V8QBLO719oXkQ+dqTqJMi1/xDSBrCDYN8QORnARP8+j1oDuqE34Kklcse4WL9rwTJ2sOmOu/O2h6Gx3ZaFaMaWRXBDYv8JpzTZjCbRrLSENlEHTof29BmvXTJ0QZ7qi6iAD" + data, err := Decrypt(str, []byte(key)) + //data, err := DecryptAES(key, str) + fmt.Println(data) + fmt.Println(err) +} + +var wsList []*websocket.Conn + +func sendmsg() { + for _, conn := range wsList { + if err := conn.WriteMessage(websocket.TextMessage, []byte("~#HHHBBB#~")); err != nil { + fmt.Printf("%s", err) //"use of closed network connection" + } + } +} +func TestPut(t *testing.T) { + fmt.Println(wsList) +} + +func TestWebSocketClient(t *testing.T) { + //发送webSocket请求 + conn, resp, err := websocket.DefaultDialer.Dial(TestMTIMPushUrl, nil) + if err != nil { + fmt.Printf("连接失败:%v", err) + } + fmt.Printf("响应:%s", fmt.Sprint(resp)) + //wsList = append(wsList, conn) + //关闭 + conn.SetCloseHandler(func(code int, text string) error { + fmt.Printf("WebSocket connection closed with code %d and text: %s\n", code, text) + return nil + }) + defer func(conn *websocket.Conn) { + err := conn.Close() + if err != nil { + return + } + }(conn) + + //赋入全局变量 + //Default(conn) + //生成clientID + clientID := GenClientId() + + //创建实例连接 + client := &Client{ + ID: clientID, + //AccountId:conn. , + Socket: conn, + HeartbeatTime: time.Now().Unix(), + } + //rdb.Set("testPush", client, 0) + + //注册到连接管理 + RegisterChan <- client + //todo 暂时不确定放哪 + //go Start() + + done := make(chan SingleChat) + //err = conn.WriteControl(websocket.PingMessage, []byte{}, time.Now().Add(time.Second)) + err = conn.WriteMessage(websocket.TextMessage, []byte("~#HHHBBB#~")) + if err != nil { + fmt.Println(err) + } + + for { + _, msg, err := conn.ReadMessage() + if err != nil { + //log.Fatal(err) + break + } + fmt.Printf("%s receive: %s\n", conn.RemoteAddr(), string(msg)) + } + <-done + +} + +func TestPUSH(t *testing.T) { + key := "589:7954977:10" + //rdb.RPush(key, "1111111111") + //rdb.RPush(key, "{\"vendorID\":10,\"userID\":11158569333,\"NewMessageNum\":3,\"latestMsg\":\"hhhhhhhhhhh\",\"latestTime\":1681983980}") + //rdb.RPush(key, "{\"vendorID\":10,\"userID\":11158569333,\"NewMessageNum\":3,\"latestMsg\":\"oooooooooo\",\"latestTime\":1681983980}") + //rdb.RPush(key, "2222222222222") + rdb.RPush(key, "{\"vendorID\":10,\"userID\":11158569333,\"NewMessageNum\":4,\"latestMsg\":\"成功插入新数据,看下cnt\",\"latestTime\":1681983980}") + rdb.RPush(key, "{\"vendorID\":10,\"userID\":11158569333,\"NewMessageNum\":5,\"latestMsg\":\"成功插入新数据,看下cnt\",\"latestTime\":1681983980}") +} + +//用户消息列表 +type UserMessageList struct { + VendorID int `json:"vendorID"` //平台品牌 10-美团 11-饿了么 + UserID int `json:"userID"` //用户ID + NewMessageNum int `json:"NewMessageNum"` //新消息数量 + LatestMsg string `json:"latestMsg"` //最新一条消息 + LatestTime int `json:"latestTime"` //最新一条消息发送时间 +} + +func TestNewRedis(t *testing.T) { + var flag = 11158569333 + var key = "589:7954977:10" + s2 := rdb.LRange(key, 0, -1).Val() + fmt.Printf("before len %d\n", len(s2)) + fmt.Printf("before ans %s\n", s2) + cnt := 0 + n := rdb.Exists(key).Val() + if n > 0 { + for i := 0; i < len(s2); i++ { + v := UserMessageList{} + _ = json.Unmarshal([]byte(s2[i]), &v) + if v.UserID == flag { + rdb.LSet(key, int64(i), "del") + rdb.LRem(key, 0, "del") + s2 = append(s2[:i], s2[i+1:]...) + i-- + if v.NewMessageNum == 0 { //目前为首条 + cnt++ //赋值1 + } else { + cnt = v.NewMessageNum + } + } + } + } + fmt.Printf("after cnt %d\n", cnt) + fmt.Printf("after len %d\n", len(s2)) + fmt.Printf("after ans %s\n", s2) + //存入flag数据 + ans := UserMessageList{ + VendorID: 10, + UserID: 11158569333, + NewMessageNum: cnt, + LatestMsg: "成功插入新数据,看下cnt", + LatestTime: 1681983980, + } + param, _ := json.Marshal(ans) + rdb.RPush(key, param) +} + +// 根据账号获取连接 +func TestGetClient(t *testing.T) { + accountId := "QW+r2FtsRKGGLJnlgyDNlChzcKcSZ8Kfgh0qw//ONuQCDKzky4x+nlbnx3k1JX13" + clients := make([]*Client, 0) + + Manager.mu.Lock() + defer Manager.mu.Unlock() + + if len(Manager.Accounts[accountId]) > 0 { + for _, clientId := range Manager.Accounts[accountId] { + if c, ok := Manager.Clients[clientId]; ok { + clients = append(clients, c) + } + } + } + + fmt.Printf(utils.Format4Output(clients, false)) +} +func TestMal(t *testing.T) { + } diff --git a/platformapi/tiktok_shop/tiktok_api/afs_test.go b/platformapi/tiktok_shop/tiktok_api/afs_test.go index 243cfe48..135d168b 100644 --- a/platformapi/tiktok_shop/tiktok_api/afs_test.go +++ b/platformapi/tiktok_shop/tiktok_api/afs_test.go @@ -18,7 +18,7 @@ import ( // "authority_id": "" //}` -var token = `{"access_token":"2edc427e-9ab8-430b-a502-6802f1dee387","expires_in":1679861437,"scope":"SCOPE","shop_id":57939570,"shop_name":"京西菜市速食","refresh_token":"dda56ad5-521b-4b87-9d74-bd6df13df1aa","authority_id":""}` +var token = `{"access_token":"c2c6e258-847d-4e8f-a695-b20488a5a667","expires_in":1682270239,"scope":"SCOPE","shop_id":57939570,"shop_name":"京西菜市速食","refresh_token":"ebf0e9f1-b200-47c2-a2bb-bafefbdaed47","authority_id":""}` //var token = `{"access_token":"e3173e9f-266f-4d87-88e7-e7cd837bc9d9","expires_in":1672882632,"scope":"SCOPE","shop_id":68023619,"shop_name":"京西到家","refresh_token":"5070aae2-493f-46bd-b5d6-6ea0cd64729f","authority_id":""}` diff --git a/platformapi/tiktok_shop/tiktok_api/store_test.go b/platformapi/tiktok_shop/tiktok_api/store_test.go index f2c11a0d..20cab48d 100644 --- a/platformapi/tiktok_shop/tiktok_api/store_test.go +++ b/platformapi/tiktok_shop/tiktok_api/store_test.go @@ -281,7 +281,7 @@ func TestUpdateStore(t *testing.T) { //获取门店绑定仓库 func TestGetWarehouseByStore(t *testing.T) { - storeIDs := []int64{89519481, 89476907, 89127678, 89107647, 88868369, 88580829, 86794412, 86793595, 86763746, 85770993, 83727828, 81386642, 81385390, 78318835, 78118788, 76879546, 76877797, 76877419, 76876821, 76875172, 75323740, 71199379, 71199371, 71199369, 71199367, 71199362, 71199352, 70870393, 69395217, 65585133, 65402632, 65401463, 64270373, 64270365, 64270337, 64270333, 64270318, 64270279, 64270267, 64270240, 64270227, 64270222, 64270214, 64270206, 64270183, 64270137, 64270136, 64270122, 64270095, 64270063, 64270050, 64270027, 64269990, 64269953, 64251889, 64251887, 64251878, 64251875, 64251857, 64251634, 64251633, 64251632, 64251631, 64251630, 64251629, 64251624, 64251623, 64251622, 64251620, 64251617, 64251616, 64251614, 64251611, 64251610, 64251608, 64251604, 64251603, 64251600, 64251599, 64251224, 64251223, 64251221, 64251219, 64251214, 64251213, 64251208, 64251207, 64251204, 64251198, 64250754, 64250751, 64250746, 64250744, 64250742, 64250741, 64250739, 64250735, 64250734, 64250731, 64250730, 64250729, 64250728, 64250727, 64250722, 64250720, 64250709, 64250707, 64250704, 64212758, 64212030, 64208821, 64208482, 64208305, 64208009, 64089616, 64042829, 63841925, 63840103, 63836369, 63521502, 63521394, 63511878, 63468038, 63467659, 63467462, 63465423, 63463026, 63462817, 63224599, 63183340, 63181030, 63179331, 63178998, 62490423} + storeIDs := []int64{64363086} need := []int64{} errList := errlist.New() for _, v := range storeIDs { diff --git a/platformapi/trenditapi/trendit_test.go b/platformapi/trenditapi/trendit_test.go index 9593ff71..6dc939f3 100644 --- a/platformapi/trenditapi/trendit_test.go +++ b/platformapi/trenditapi/trendit_test.go @@ -14,35 +14,38 @@ func TestLen(t *testing.T) { //打印 func TestAPI_Print(t *testing.T) { - content := `美团外卖 --------------------------------- -下单时间:2023-03-27 13:22:05 -期望送达:2023-03-27 14:22:05 -客户姓名:梅朵(女士) -客户电话:163473526172 -订单编号: E22092832084572779
-美团外卖#20 -E22092832084572779 --------------------------------- -客户地址:四川省成都市武侯区双流县金华镇芳草街道小区5栋1单元104号 --------------------------------- -客户备注:缺货时电话与我沟通 收货人隐私号17882904902——5355,手机号 181****6752 --------------------------------- + name1 := "[优]猪肉馅约250g/份" + name2 := "鲜鸡蛋约250g/份" + name3 := "[精选优品][精选优品][精选优品][精选优品][精选优品]豌豆米-手工剥豆约100g/份" + name4 := "娃娃菜200g/个" + len1 := len(name1) + len2 := len(name2) + len3 := len(name3) + len4 := len(name4) + len5 := len("--------------------------------") + fmt.Println(len1, len2, len3, len4, len5) + content := ` 商品列表
` - content += "商品名" + StrRepeat(" ", 3) + "数量" + StrRepeat(" ", 4) + "单价" + StrRepeat(" ", 6) + "小计" + "
" + content += "商品名" + StrRepeat(" ", 13) + "数量" + "
" content += `--------------------------------` - content += FormatPrintOrderItem("[优]猪肉馅约250g/份", 1, 999) - content += FormatPrintOrderItem("鲜鸡蛋约250g/份", 1, 17.8) - content += FormatPrintOrderItem("豌豆米-手工剥豆约100g/份", 1, 40) - content += FormatPrintOrderItem("娃娃菜200g/个", 5, 2) - content += `共4种9件商品 -实付金额: 327.83元
---------------#20完-------------` + content += FormatPrintOrderItemV2(name1, 1, 1) + content += FormatPrintOrderItemV2(name2, 1, 2) + content += FormatPrintOrderItemV2(name3, 1, 3) + content += FormatPrintOrderItemV2(name4, 5, 4) + content += `
共4种9件商品
` + content += `--------------#20完-------------` msg, err := api.Print(TestSn, content, VoiceNewShort) fmt.Println(msg) fmt.Println(err) } +func TestCal(t *testing.T) { + str := "豌豆米-手工剥豆约100g/份" + //ans := "--------------------------------" + cnt := CalWidth(str) + fmt.Println(cnt) +} + //打印取消/退货模板 func TestAPI_Print2(t *testing.T) { content := `京西菜市
` @@ -81,7 +84,7 @@ func TestAPI_DelPrinter(t *testing.T) { //设置打印浓度 func TestAPI_SetDensity(t *testing.T) { - err := api.SetDensity(TestSn, DensityStronger) + err := api.SetDensity(TestSn, DensityStrong) fmt.Println(err) } @@ -93,7 +96,7 @@ func TestAPI_SetVolume(t *testing.T) { //查询打印机状态 func TestGetDevicesStatus(t *testing.T) { - onlineStatus, workStatus, err := api.GetDeviceStatus(TestSn) + onlineStatus, workStatus, err := api.GetDeviceStatus("570010021789") fmt.Println(onlineStatus) fmt.Println(workStatus) fmt.Println(err) @@ -104,3 +107,9 @@ func TestAPI_CleanWaitingQueue(t *testing.T) { err := api.CleanWaitingQueue(TestSn) fmt.Println(err) } + +func TestInt(t *testing.T) { + + temp := 1000 + fmt.Println(int(temp / 1000)) +} diff --git a/platformapi/trenditapi/trenditapi.go b/platformapi/trenditapi/trenditapi.go index 215d6746..6fececed 100644 --- a/platformapi/trenditapi/trenditapi.go +++ b/platformapi/trenditapi/trenditapi.go @@ -9,10 +9,12 @@ import ( "io/ioutil" r "math/rand" "net/http" + "regexp" "strconv" "strings" "sync" "time" + "unicode" ) type API struct { @@ -155,3 +157,74 @@ func FormatPrintOrderItem(foodName string, quantity int, price float64) string { result += "
" return result } + +const ( + ROW_MAX_CHAR_LEN = 34 + LAST_ROW_MAX_NAME_CHAR_LEN = 16 + MAX_NAME_CHAR_LEN = 16 + MaxLineLength = 30 + LineLength = 32 + NewLineLength = 28 +) + +//不带单价版本 +func FormatPrintOrderItemV2(foodName string, quantity, cnt int) string { + var ( + result = "" + restLen int + ) + quantityStr := strconv.Itoa(quantity) + foodNameLen := CalWidth(foodName) + 3 + if foodNameLen >= MaxLineLength { + if n := foodNameLen / LineLength; n > 0 { + restLen = foodNameLen % LineLength + } else { + restLen = foodNameLen - LineLength + } + result += `` + utils.Int2Str(cnt) + `.` + foodName + result += StrRepeat(" ", MaxLineLength-restLen) + `x` + quantityStr + `` + } else { + result += `` + utils.Int2Str(cnt) + `.` + foodName + StrRepeat(" ", MaxLineLength-foodNameLen) + `x` + quantityStr + `` + } + result += "
" + fmt.Println(result) + return result +} + +func FormatPrintOrderItemBigV2(foodName string, quantity, cnt int) string { + var ( + result = "" + restLen int + ) + quantityStr := strconv.Itoa(quantity) + foodNameLen := CalWidth(foodName) + 3 + if foodNameLen >= MaxLineLength { + if n := foodNameLen / LineLength; n > 0 { + restLen = foodNameLen % LineLength + } else { + restLen = foodNameLen - LineLength + } + result += `` + utils.Int2Str(cnt) + `.` + foodName + result += StrRepeat(" ", MaxLineLength-restLen) + `x` + quantityStr + `` + } else { + result += `` + utils.Int2Str(cnt) + `.` + foodName + StrRepeat(" ", MaxLineLength-foodNameLen) + `x` + quantityStr + `` + } + result += "
" + fmt.Println(result) + return result +} + +//正则计算宽度 +func CalWidth(str string) int { + hzc := 0 + for _, v := range str { + if unicode.Is(unicode.Han, v) { + hzc++ + } + } + updateStr := regexp.MustCompile("[\u4e00-\u9fa5]{1,}").ReplaceAllString(str, "") + l := len(updateStr) + fmt.Println(hzc) + ans := 2*hzc + l + return ans +} diff --git a/platformapi/xpyunapi/xpuapi_test.go b/platformapi/xpyunapi/xpuapi_test.go index e29f14fe..94728806 100644 --- a/platformapi/xpyunapi/xpuapi_test.go +++ b/platformapi/xpyunapi/xpuapi_test.go @@ -176,14 +176,15 @@ func TestPrintCancelOrRefund(t *testing.T) { //} // ////清空待打印队列 -//func TestEmptyPrinterQueue(t *testing.T) { -// request := &EmpPrinterQueueRequest{ -// RestRequest: api.GenerateRestRequest(), -// Sn: TestPrinterSN, -// } -// err := api.EmptyPrinterQueue(request) -// fmt.Println(err) -//} +func TestEmptyPrinterQueue(t *testing.T) { + //request := &EmpPrinterQueueRequest{ + // RestRequest: api.GenerateRestRequest(), + // Sn: TestPrinterSN, + //} + err := api.EmptyPrinterQueue(TestPrinterSN) + fmt.Println(err) +} + // ////查询订单状态 //func TestQueryOrderState(t *testing.T) { diff --git a/platformapi/xpyunapi/xputil.go b/platformapi/xpyunapi/xputil.go index 51b4e366..bb7b7dcc 100644 --- a/platformapi/xpyunapi/xputil.go +++ b/platformapi/xpyunapi/xputil.go @@ -10,9 +10,11 @@ import ( "golang.org/x/text/transform" "io/ioutil" "net/http" + "regexp" "strconv" "strings" "time" + "unicode" ) /** @@ -169,3 +171,48 @@ func FormatPrintOrderItem(foodName string, quantity int, price float64) string { result += "
" return result } + +//正则计算宽度 +func CalWidth(str string) int { + hzc := 0 + for _, v := range str { + if unicode.Is(unicode.Han, v) { + hzc++ + } + } + updateStr := regexp.MustCompile("[\u4e00-\u9fa5]{1,}").ReplaceAllString(str, "") + l := len(updateStr) + fmt.Println(hzc) + ans := 2*hzc + l + return ans +} + +const ( + MaxLineLength = 30 + LineLength = 32 + NewLineLength = 28 +) + +func FormatPrintOrderItemV2(foodName string, quantity, index int) string { + var ( + restLen int + quantityStr = strconv.Itoa(quantity) + //quantityLen = CalcAsciiLenForPrint(quantityStr) + foodNameLen = CalWidth(foodName) + 3 + result = "" + ) + if foodNameLen >= MaxLineLength { + if n := foodNameLen / LineLength; n > 0 { + restLen = foodNameLen % LineLength + } else { + restLen = foodNameLen - LineLength + } + result += "" + utils.Int2Str(index) + `.` + foodName + "" + result += "" + StrRepeat(" ", MaxLineLength-restLen) + `x` + quantityStr + "" + } else { + result += "" + utils.Int2Str(index) + `.` + foodName + StrRepeat(" ", MaxLineLength-foodNameLen) + `x` + quantityStr + "" + } + //result += orderNameEmpty + "x" + quantityStr + StrRepeat(" ", MAX_QUANTITY_CHAR_LEN-quantityLen) + result += "
" + return result +}