Procházet zdrojové kódy

增加向工作手机发送消息功能

boweniac před 3 měsíci
rodič
revize
1e51f0dc09

+ 17 - 7
crontask/send_msg.go

@@ -175,23 +175,33 @@ func (l *CronTask) sendMsg() {
 			l.Logger.Errorf("wxInfo err: %v", err)
 			continue
 		}
-		serverInfo, err := l.svcCtx.DB.Server.Get(l.ctx, wxInfo.ServerID)
-		if err != nil {
-			l.Logger.Errorf("serverInfo err: %v", err)
-			continue
+
+		privateIP := ""
+		adminPort := ""
+		port := ""
+
+		if wxInfo.ServerID != 0 {
+			serverInfo, err := l.svcCtx.DB.Server.Get(l.ctx, wxInfo.ServerID)
+			if err != nil {
+				l.Logger.Errorf("serverInfo err: %v", err)
+				continue
+			}
+			privateIP = serverInfo.PrivateIP
+			adminPort = serverInfo.AdminPort
+			port = wxInfo.Port
 		}
 
-		hookClient := hook.NewHook(serverInfo.PrivateIP, serverInfo.AdminPort, wxInfo.Port)
+		hookClient := hook.NewHook(privateIP, adminPort, port)
 
 		//循环发送消息
 		for _, msg := range msglist {
 			// 这里之前只有文字消息(既 msgtype=1) 目前增加了图片 所以增加了msgtype=2
 			// 所以增加了一个判断,判断发送的内容类型,如果是文字就调用SendTextMsg,如果是图片就调用SendPicMsg
 			if msg.Msgtype == 1 {
-				err = hookClient.SendTextMsg(msg.Toid, msg.Msg)
+				err = hookClient.SendTextMsg(msg.Toid, msg.Msg, wxInfo.Wxid)
 			} else if msg.Msgtype == 2 {
 				diyfilename := getFileName(msg.Msg)
-				err = hookClient.SendPicMsg(msg.Toid, msg.Msg, diyfilename)
+				err = hookClient.SendPicMsg(msg.Toid, msg.Msg, diyfilename, wxInfo.Wxid)
 			}
 
 			// 每次发完暂停1秒

+ 17 - 10
crontask/send_wx.go

@@ -84,13 +84,15 @@ func (l *CronTask) sendWx() {
 				l.Logger.Errorf("get wx info failed wxid=%v err=%v", wxid, err)
 				return "", err
 			}
-			server, err := l.svcCtx.DB.Server.Query().Where(server.IDEQ(wx.ServerID)).First(l.ctx)
-			if err != nil {
-				l.Logger.Errorf("get server info failed wxid=%v err=%v", wxid, err)
-				return "", err
+			if wx.ServerID != 0 {
+				server, err := l.svcCtx.DB.Server.Query().Where(server.IDEQ(wx.ServerID)).First(l.ctx)
+				if err != nil {
+					l.Logger.Errorf("get server info failed wxid=%v err=%v", wxid, err)
+					return "", err
+				}
+				val = server.PrivateIP + ":" + server.AdminPort + ":" + wx.Port
+				l.svcCtx.Rds.HSet(ctx, key, wxid, val)
 			}
-			val = server.PrivateIP + ":" + server.AdminPort + ":" + wx.Port
-			l.svcCtx.Rds.HSet(ctx, key, wxid, val)
 		}
 
 		return val, nil
@@ -108,8 +110,13 @@ func (l *CronTask) sendWx() {
 		tx, _ := l.svcCtx.DB.Tx(l.ctx)
 		if v.Content != "" {
 			serverInfo, _ := getServerInfo(v.BotWxid)
-			infoArray := strings.Split(serverInfo, ":")
-			serverIp, adminPort, wxPort := infoArray[0], infoArray[1], infoArray[2]
+			serverIp := ""
+			adminPort := ""
+			wxPort := ""
+			if serverInfo != "" {
+				infoArray := strings.Split(serverInfo, ":")
+				serverIp, adminPort, wxPort = infoArray[0], infoArray[1], infoArray[2]
+			}
 
 			_, err = tx.MessageRecords.UpdateOneID(v.ID).SetStatus(2).Save(ctx)
 			if err != nil {
@@ -124,9 +131,9 @@ func (l *CronTask) sendWx() {
 					contactInfo, _ := getContactInfo(v.BotWxid, v.ContactWxid)
 					content = varReplace(content, contactInfo)
 				}
-				err = hookClient.SendTextMsg(v.ContactWxid, content)
+				err = hookClient.SendTextMsg(v.ContactWxid, content, v.BotWxid)
 			} else {
-				err = hookClient.SendPicMsg(v.ContactWxid, v.Content, v.Meta.Filename)
+				err = hookClient.SendPicMsg(v.ContactWxid, v.Content, v.Meta.Filename, v.BotWxid)
 			}
 
 			if err != nil {

+ 3 - 0
desc/wechat/label_relationship.api

@@ -20,6 +20,9 @@ type (
         // 属主微信id
         WxWxid  *string `json:"wxWxid,optional"`
 
+        // 属主微信昵称
+        WxWxidNickname  *string `json:"wxWxidNickname,optional"`
+
         // 联系人类型:1好友,2群组,3公众号,4企业微信联系人
         Type  *int `json:"type,optional"`
 

+ 3 - 3
etc/wechat.yaml

@@ -26,7 +26,7 @@ Log:
 DatabaseConf:
   Type: mysql
   Host: localhost
-  Port: 3306
+  Port: 3307
   DBName: wechat
   Username: root
   Password: simple-admin.
@@ -41,12 +41,12 @@ CoreRpc:
   Enabled: true
 
 RedisConf:
-  Host: localhost:6379
+  Host: localhost:6380
 
 CasbinDatabaseConf:
   Type: mysql
   Host: localhost
-  Port: 3306
+  Port: 3307
   DBName: wechat-admin
   Username: root
   Password: simple-admin.

+ 2 - 1
go.mod

@@ -14,7 +14,7 @@ require (
 	github.com/go-resty/resty/v2 v2.14.0
 	github.com/gofrs/uuid/v5 v5.0.0
 	github.com/golang-jwt/jwt/v5 v5.2.1
-	github.com/golang/protobuf v1.5.4
+	github.com/gorilla/websocket v1.5.0
 	github.com/imroc/req/v3 v3.43.1
 	github.com/redis/go-redis/v9 v9.6.1
 	github.com/robfig/cron/v3 v3.0.1
@@ -72,6 +72,7 @@ require (
 	github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/golang/mock v1.6.0 // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/google/gnostic-models v0.6.8 // indirect
 	github.com/google/go-cmp v0.6.0 // indirect
 	github.com/google/gofuzz v1.2.0 // indirect

+ 2 - 0
go.sum

@@ -296,6 +296,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
 github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=

+ 23 - 8
hook/init.go

@@ -1,15 +1,18 @@
 package hook
 
 import (
+	"github.com/gorilla/websocket"
 	"github.com/imroc/req/v3"
+	"net/url"
 	"time"
 )
 
 type Hook struct {
-	ServerIp  string
-	AdminPort string
-	WxPort    string
-	Client    *req.Client
+	ServerIp     string
+	AdminPort    string
+	WxPort       string
+	Client       *req.Client
+	WorkPhoneUrl string
 }
 
 func NewHook(serverIp string, adminPort string, WxPort string) *Hook {
@@ -19,11 +22,14 @@ func NewHook(serverIp string, adminPort string, WxPort string) *Hook {
 		SetCommonRetryBackoffInterval(1*time.Second, 5*time.Second).
 		SetCommonRetryFixedInterval(2 * time.Second).SetTimeout(30 * time.Second)
 
+	workPhoneUrl := url.URL{Scheme: "ws", Host: "chat.gkscrm.com:13088"}
+
 	return &Hook{
-		ServerIp:  serverIp,
-		AdminPort: adminPort,
-		WxPort:    WxPort,
-		Client:    req.C().DevMode(),
+		ServerIp:     serverIp,
+		AdminPort:    adminPort,
+		WxPort:       WxPort,
+		Client:       req.C().DevMode(),
+		WorkPhoneUrl: workPhoneUrl.String(),
 	}
 }
 
@@ -41,3 +47,12 @@ func (h *Hook) setWxPort(port string) *Hook {
 	h.WxPort = port
 	return h
 }
+
+func (h *Hook) connWorkPhone() (*websocket.Conn, error) {
+	dialer := websocket.DefaultDialer
+	conn, _, err := dialer.Dial(h.WorkPhoneUrl, nil)
+	if err != nil {
+		return nil, err
+	}
+	return conn, nil
+}

+ 154 - 26
hook/message.go

@@ -1,6 +1,12 @@
 package hook
 
-import "fmt"
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"github.com/gorilla/websocket"
+	"wechat-api/workphone"
+)
 
 // 开启/关闭实时消息接收功能
 func (h *Hook) ConfigureMsgRecive(isEnable string, url string) (err error) {
@@ -19,36 +25,158 @@ func (h *Hook) ConfigureMsgRecive(isEnable string, url string) (err error) {
 }
 
 // 发送微信文本消息
-func (h *Hook) SendTextMsg(wxid, msg string) (err error) {
-	resp, err := h.Client.R().SetBody(&SendTextMsgReq{
-		Wxid: wxid,
-		Msg:  msg,
-	}).Post("http://" + h.ServerIp + ":" + h.WxPort + "/SendTextMsg")
-	if err != nil {
-		return
-	}
-	if !resp.IsSuccessState() {
-		err = fmt.Errorf("SendTextMsg failed with status code %d", resp.StatusCode)
-		return
+func (h *Hook) SendTextMsg(wxid, msg, wxWxid string) error {
+	if h.ServerIp == "" {
+		conn, err := h.connWorkPhone()
+		if err != nil {
+			err = fmt.Errorf("SendTextMsg failed")
+			return err
+		}
+		defer func(conn *websocket.Conn) {
+			err = conn.Close()
+			if err != nil {
+				err = fmt.Errorf("SendTextMsg failed")
+			}
+		}(conn)
+		encodedString := base64.StdEncoding.EncodeToString([]byte(msg))
+
+		//sendMsg := &workphone.TalkToFriendTaskMessage{
+		//	WeChatId:    wxWxid,
+		//	FriendId:    wxid,
+		//	ContentType: workphone.EnumContentType_Text,
+		//	Content:     []byte(encodedString),
+		//}
+		//content, err := anypb.New(sendMsg) <-- 这里有坑,会增加字段 "@type": "type.googleapis.com/Jubo.JuLiao.IM.Wx.Proto.TalkToFriendTaskMessage",
+		//if err != nil {
+		//	return fmt.Errorf("failed to pack message: %v", err)
+		//}
+		//transportMessage := &workphone.TransportMessage{
+		//	MsgType: workphone.EnumMsgType_TalkToFriendTask,
+		//	Content: content,
+		//}
+
+		// 将 TransportMessage 转换为 JSON
+		//transportMessageJSON, err := protojson.MarshalOptions{
+		//	UseProtoNames: true,
+		//}.Marshal(transportMessage)
+		//if err != nil {
+		//	return fmt.Errorf("failed to marshal transport message: %v", err)
+		//}
+		message := map[string]interface{}{
+			"MsgType": "TalkToFriendTask",
+			"Content": map[string]interface{}{
+				"WeChatId":    wxWxid,
+				"FriendId":    wxid,
+				"ContentType": "Text",
+				"Content":     encodedString,
+			},
+		}
+		transportMessageJSON, err := json.Marshal(message)
+		if err != nil {
+			return err
+		}
+		// 发送 JSON 消息
+		err = conn.WriteMessage(websocket.TextMessage, transportMessageJSON)
+		if err != nil {
+			return fmt.Errorf("failed to send message: %v", err)
+		}
+		// 读取回复消息
+		_, replyMessage, err := conn.ReadMessage()
+		if err != nil {
+			return fmt.Errorf("failed to read message: %v", err)
+		}
+		var replyMessageData map[string]interface{}
+		err = json.Unmarshal([]byte(replyMessage), &replyMessageData)
+		if err != nil {
+			return fmt.Errorf("failed to send message: %v", err)
+		}
+
+		if replyMessageData["msgType"] == nil || replyMessageData["msgType"] != "MsgReceivedAck" {
+			return fmt.Errorf("failed to send message: %v")
+		}
+
+		return nil
+
+	} else {
+		resp, err := h.Client.R().SetBody(&SendTextMsgReq{
+			Wxid: wxid,
+			Msg:  msg,
+		}).Post("http://" + h.ServerIp + ":" + h.WxPort + "/SendTextMsg")
+		if err != nil {
+			return err
+		}
+		if !resp.IsSuccessState() {
+			err = fmt.Errorf("SendTextMsg failed with status code %d", resp.StatusCode)
+			return err
+		}
+		return nil
 	}
-	return
 }
 
 // 发送微信图片
-func (h *Hook) SendPicMsg(wxid, picpath, diyfilename string) (err error) {
-	resp, err := h.Client.R().SetBody(&SendPicMsgReq{
-		Wxid:        wxid,
-		Picpath:     picpath,
-		Diyfilename: diyfilename,
-	}).Post("http://" + h.ServerIp + ":" + h.WxPort + "/SendPicMsg")
-	if err != nil {
-		return
-	}
-	if !resp.IsSuccessState() {
-		err = fmt.Errorf("SendPicMsg failed with status code %d", resp.StatusCode)
-		return
+func (h *Hook) SendPicMsg(wxid, picpath, diyfilename, wxWxid string) (err error) {
+	if h.ServerIp == "" {
+		conn, err := h.connWorkPhone()
+		if err != nil {
+			err = fmt.Errorf("SendTextMsg failed")
+			return err
+		}
+		defer func(conn *websocket.Conn) {
+			err = conn.Close()
+			if err != nil {
+				err = fmt.Errorf("SendTextMsg failed")
+			}
+		}(conn)
+		encodedString := base64.StdEncoding.EncodeToString([]byte(picpath))
+
+		message := map[string]interface{}{
+			"MsgType": "TalkToFriendTask",
+			"Content": map[string]interface{}{
+				"WeChatId":    wxWxid,
+				"FriendId":    wxid,
+				"ContentType": "Picture",
+				"Content":     encodedString,
+			},
+		}
+		transportMessageJSON, err := json.Marshal(message)
+		if err != nil {
+			return err
+		}
+		// 发送 JSON 消息
+		err = conn.WriteMessage(websocket.TextMessage, transportMessageJSON)
+		if err != nil {
+			return fmt.Errorf("failed to send message: %v", err)
+		}
+		// 读取回复消息
+		_, replyMessage, err := conn.ReadMessage()
+		if err != nil {
+			return fmt.Errorf("failed to read message: %v", err)
+		}
+		var replyMessageData map[string]interface{}
+		err = json.Unmarshal([]byte(replyMessage), &replyMessageData)
+		if err != nil {
+			return fmt.Errorf("failed to send message: %v", err)
+		}
+		if replyMessageData["msgType"] == nil || replyMessageData["msgType"] != workphone.EnumMsgType_name[int32(workphone.EnumMsgType_MsgReceivedAck)] {
+			return fmt.Errorf("failed to send message: %v")
+		}
+
+		return nil
+	} else {
+		resp, err := h.Client.R().SetBody(&SendPicMsgReq{
+			Wxid:        wxid,
+			Picpath:     picpath,
+			Diyfilename: diyfilename,
+		}).Post("http://" + h.ServerIp + ":" + h.WxPort + "/SendPicMsg")
+		if err != nil {
+			return err
+		}
+		if !resp.IsSuccessState() {
+			err = fmt.Errorf("SendPicMsg failed with status code %d", resp.StatusCode)
+			return err
+		}
+		return nil
 	}
-	return
 }
 
 // 发送微信图片(本地测试)

+ 17 - 8
internal/logic/Wxhook/send_pic_msg_logic.go

@@ -47,19 +47,28 @@ func (l *SendPicMsgLogic) SendPicMsg(req *types.SendPicMsgReq) (resp *types.Base
 		return
 	}
 
-	serverInfo, err := l.svcCtx.DB.Server.Get(l.ctx, wxInfo.ServerID)
-	if err != nil {
-		l.Error("查询服务器信息失败", err)
-		return
+	privateIP := ""
+	adminPort := ""
+	port := ""
+
+	if wxInfo.ServerID != 0 {
+		serverInfo, err := l.svcCtx.DB.Server.Get(l.ctx, wxInfo.ServerID)
+		if err != nil {
+			l.Error("查询服务器信息失败", err)
+			return nil, err
+		}
+		privateIP = serverInfo.PrivateIP
+		adminPort = serverInfo.AdminPort
+		port = wxInfo.Port
 	}
 
-	hookClient := hook.NewHook(serverInfo.PrivateIP, serverInfo.AdminPort, wxInfo.Port)
+	hookClient := hook.NewHook(privateIP, adminPort, port)
 
-	err = hookClient.SendPicMsg(*req.Wxid, *req.Picpath, *req.Diyfilename)
+	err = hookClient.SendPicMsg(*req.Wxid, *req.Picpath, *req.Diyfilename, wxInfo.Wxid)
 
 	if err != nil {
 		l.Error("发送微信图片失败", err)
-		return
+		return nil, err
 	}
 
 	resp = &types.BaseMsgResp{
@@ -67,5 +76,5 @@ func (l *SendPicMsgLogic) SendPicMsg(req *types.SendPicMsgReq) (resp *types.Base
 		Code: errorcode.OK,
 	}
 
-	return
+	return resp, nil
 }

+ 17 - 8
internal/logic/Wxhook/send_text_msg_logic.go

@@ -46,19 +46,28 @@ func (l *SendTextMsgLogic) SendTextMsg(req *types.SendTextMsgReq) (resp *types.B
 		return
 	}
 
-	serverInfo, err := l.svcCtx.DB.Server.Get(l.ctx, wxInfo.ServerID)
-	if err != nil {
-		l.Error("查询服务器信息失败", err)
-		return
+	privateIP := ""
+	adminPort := ""
+	port := ""
+
+	if wxInfo.ServerID != 0 {
+		serverInfo, err := l.svcCtx.DB.Server.Get(l.ctx, wxInfo.ServerID)
+		if err != nil {
+			l.Error("查询服务器信息失败", err)
+			return nil, err
+		}
+		privateIP = serverInfo.PrivateIP
+		adminPort = serverInfo.AdminPort
+		port = wxInfo.Port
 	}
 
-	hookClient := hook.NewHook(serverInfo.PrivateIP, serverInfo.AdminPort, wxInfo.Port)
+	hookClient := hook.NewHook(privateIP, adminPort, port)
 
-	err = hookClient.SendTextMsg(*req.Wxid, *req.Msg)
+	err = hookClient.SendTextMsg(*req.Wxid, *req.Msg, wxInfo.Wxid)
 
 	if err != nil {
 		l.Error("发送微信文本消息失败", err)
-		return
+		return nil, err
 	}
 
 	resp = &types.BaseMsgResp{
@@ -66,5 +75,5 @@ func (l *SendTextMsgLogic) SendTextMsg(req *types.SendTextMsgReq) (resp *types.B
 		Code: errorcode.OK,
 	}
 
-	return
+	return resp, nil
 }

+ 2 - 8
internal/logic/contact/get_contact_list_logic.go

@@ -111,12 +111,6 @@ func (l *GetContactListLogic) GetContactList(req *types.ContactListReq) (*types.
 				})
 			}
 		}
-		//wxWxName := ""
-		//l.Logger.Errorf("------------------------v.Edges.ContactWxWxid--------------------------- %+v", v.Edges.ContactWxWxid)
-		//if v.Edges.ContactWxWxid != nil {
-		//	wxWxName = v.Edges.ContactWxWxid[0].Wxid
-		//}
-		l.Logger.Errorf("------------------------v.WxWxid--------------------------- %+v", v.WxWxid)
 		var wxNickname string
 		if wxWxidsSet[v.WxWxid] == "" {
 			wxNickname = v.WxWxid
@@ -124,7 +118,6 @@ func (l *GetContactListLogic) GetContactList(req *types.ContactListReq) (*types.
 			wxNickname = wxWxidsSet[v.WxWxid]
 		}
 
-		l.Logger.Errorf("------------------------wxNickname--------------------------- %+v", wxNickname)
 		resp.Data.Data = append(resp.Data.Data,
 			types.ContactInfo{
 				BaseIDInfo: types.BaseIDInfo{
@@ -133,7 +126,8 @@ func (l *GetContactListLogic) GetContactList(req *types.ContactListReq) (*types.
 					UpdatedAt: pointy.GetPointer(v.UpdatedAt.UnixMilli()),
 				},
 				Status:             &v.Status,
-				WxWxid:             &wxNickname,
+				WxWxid:             &v.WxWxid,
+				WxWxidNickname:     &wxNickname,
 				Type:               &v.Type,
 				Wxid:               &v.Wxid,
 				Account:            &v.Account,

+ 2 - 0
internal/types/types.go

@@ -732,6 +732,8 @@ type ContactInfo struct {
 	Status *uint8 `json:"status,optional"`
 	// 属主微信id
 	WxWxid *string `json:"wxWxid,optional"`
+	// 属主微信昵称
+	WxWxidNickname *string `json:"wxWxidNickname,optional"`
 	// 联系人类型:1好友,2群组,3公众号,4企业微信联系人
 	Type *int `json:"type,optional"`
 	// 微信id 公众号微信ID