diff --git a/README.md b/README.md index 0bd04bf..ccfee60 100644 --- a/README.md +++ b/README.md @@ -38,16 +38,68 @@ func main() { } // 监听哪类事件就需要实现哪类的 handler,定义:websocket/event_handler.go - var atMessage websocket.ATMessageEventHandler = func(event *dto.WSPayload, data *dto.WSATMessageData) error { - fmt.Println(event, data) - return nil - } + var atMessage event.ATMessageEventHandler = func(event *dto.WSPayload, data *dto.WSATMessageData) error { + fmt.Println(event, data) + return nil + } + intent := websocket.RegisterHandlers(atMessage) // 启动 session manager 进行 ws 连接的管理,如果接口返回需要启动多个 shard 的连接,这里也会自动启动多个 botgo.NewSessionManager().Start(ws, token, &intent) } ``` +### 3.群聊示例 + +```golang +func main() { + token := token.BotToken(conf.AppID, conf.Token) + api := botgo.NewOpenAPI(token).WithTimeout(3 * time.Second) + ctx := context.Background() + ws, err := api.WS(ctx, nil, "") + if err != nil { + log.Printf("%+v, err:%v", ws, err) + } + + // 监听哪类事件就需要实现哪类的 handler,定义:websocket/event_handler.go + var atMessage event.ATMessageEventHandler = func(event *dto.WSPayload, data *dto.WSATMessageData) error { + fmt.Println(event, data) + return nil + } + + var groupMessage event.GroupAtMessageEventHandler = func(event *dto.WSPayload, data *dto.WSGroupATMessageData) error { + groupId := data.GroupId + //userId := data.Author.UserId + content := strings.TrimSpace(data.Content) + msgId := data.MsgId + + resp, err := api.PostGroupRichMediaMessage(ctx, groupId, &dto.GroupRichMediaMessageToCreate{FileType: 1, Url: "图片Url", SrvSendMsg: false}) + if err != nil { + newMsg := &dto.GroupMessageToCreate{ + Content: "图片上传失败", + MsgID: msgId, + MsgType: 0, + } + api.PostGroupMessage(ctx, groupId, newMsg) + return nil + } + + newMsg := &dto.GroupMessageToCreate{ + Content: content, + Media: &dto.FileInfo{FileInfo: resp.FileInfo}, + MsgID: msgId, + MsgType: 7, + } + api.PostGroupMessage(ctx, groupId, newMsg) + return nil + } + + intent := websocket.RegisterHandlers(atMessage, groupMessage) + // 启动 session manager 进行 ws 连接的管理,如果接口返回需要启动多个 shard 的连接,这里也会自动启动多个 + botgo.NewSessionManager().Start(ws, token, &intent) +} +``` + ## 二、什么是 SessionManager SessionManager,用于管理 websocket 连接的启动,重连等。接口定义在:`session_manager.go`。开发者也可以自己实现自己的 SessionManager。 diff --git a/dto/message.go b/dto/message.go index 160cd6a..35e262b 100644 --- a/dto/message.go +++ b/dto/message.go @@ -38,6 +38,21 @@ type Message struct { SrcGuildID string `json:"src_guild_id"` } +type GroupMessage struct { + GroupId string `json:"group_id"` + GroupOpenId string `json:"group_openid"` + Content string `json:"content"` + MsgId string `json:"id"` + Author Author `json:"author"` + Timestamp Timestamp `json:"timestamp"` + Attachments []*MessageAttachment `json:"attachments"` +} + +type Author struct { + UserId string `json:"id"` + UserOpenId string `json:"member_openid"` +} + // Embed 结构 type Embed struct { Title string `json:"title,omitempty"` diff --git a/dto/message_create.go b/dto/message_create.go index 0e11150..79f30bd 100644 --- a/dto/message_create.go +++ b/dto/message_create.go @@ -1,6 +1,8 @@ package dto -import "github.com/tencent-connect/botgo/dto/keyboard" +import ( + "github.com/tencent-connect/botgo/dto/keyboard" +) // MessageToCreate 发送消息结构体定义 type MessageToCreate struct { @@ -16,6 +18,33 @@ type MessageToCreate struct { EventID string `json:"event_id,omitempty"` // 要回复的事件id, 逻辑同MsgID } +// 消息类型: 0 是文本,1 图文混排,2 markdown, 3 ark,4 embed 7 富媒体 +type GroupMessageToCreate struct { + Content string `json:"content,omitempty"` + MsgType int `json:"msg_type"` + Markdown *Markdown `json:"markdown,omitempty"` + Keyboard *keyboard.MessageKeyboard `json:"keyboard,omitempty"` // 消息按钮组件 + Media *FileInfo `json:"media,omitempty"` + Ark *Ark `json:"ark,omitempty"` + Image string `json:"image,omitempty"` + MessageReference *MessageReference `json:"message_reference,omitempty"` + EventID string `json:"event_id,omitempty"` // 要回复的事件id, 逻辑同MsgID + MsgID string `json:"msg_id,omitempty"` + MsgReq uint `json:"msg_req,omitempty"` +} + +type FileInfo struct { + FileInfo string `json:"file_info,omitempty"` +} + +// 媒体类型:1 图片,2 视频,3 语音,4 文件(暂不开放) 资源格式要求: 图片:png/jpg,视频:mp4,语音:silk, +type GroupRichMediaMessageToCreate struct { + FileType int `json:"file_type"` + Url string `json:"url"` + SrvSendMsg bool `json:"srv_send_msg"` + FileData []byte `json:"file_data"` +} + // MessageReference 引用消息 type MessageReference struct { MessageID string `json:"message_id"` // 消息 id @@ -24,9 +53,10 @@ type MessageReference struct { // Markdown markdown 消息 type Markdown struct { - TemplateID int `json:"template_id"` // 模版 id - Params []*MarkdownParams `json:"params"` // 模版参数 - Content string `json:"content"` // 原生 markdown + TemplateID int `json:"template_id,omitempty"` // 模版 id + CustomTemplateId string `json:"custom_template_id,omitempty"` + Params []*MarkdownParams `json:"params"` // 模版参数 + Content string `json:"content"` // 原生 markdown } // MarkdownParams markdown 模版参数 键值对 @@ -46,3 +76,14 @@ type SettingGuide struct { // 频道ID, 当通过私信发送设置引导消息时,需要指定guild_id GuildID string `json:"guild_id"` } + +type RichMediaMsgResp struct { + FileUuid string `json:"file_uuid,omitempty"` + FileInfo string `json:"file_info,omitempty"` + Ttl uint `json:"ttl,omitempty"` +} + +type GroupMsgResp struct { + Id string `json:"id"` + Timestamp Timestamp `json:"timestamp"` +} diff --git a/dto/pager.go b/dto/pager.go index e23c2c1..05dfd47 100644 --- a/dto/pager.go +++ b/dto/pager.go @@ -24,24 +24,6 @@ func (g *GuildMembersPager) QueryParams() map[string]string { return query } -// GuildRoleMembersPager 分页器 -type GuildRoleMembersPager struct { - StartIndex string `json:"start_index"` - Limit string `json:"limit"` -} - -// QueryParams 转换为 query 参数 -func (g *GuildRoleMembersPager) QueryParams() map[string]string { - query := make(map[string]string) - if g.Limit != "" { - query["limit"] = g.Limit - } - if g.StartIndex != "" { - query["start_index"] = g.StartIndex - } - return query -} - // GuildPager 分页器 type GuildPager struct { Before string `json:"before"` // 读此id之前的数据 diff --git a/dto/websocket_event.go b/dto/websocket_event.go index 952e07e..7c31b70 100644 --- a/dto/websocket_event.go +++ b/dto/websocket_event.go @@ -38,6 +38,17 @@ const ( EventForumReplyDelete EventType = "FORUM_REPLY_DELETE" EventForumAuditResult EventType = "FORUM_PUBLISH_AUDIT_RESULT" EventInteractionCreate EventType = "INTERACTION_CREATE" + EventC2CMessageCreate EventType = "C2C_MESSAGE_CREATE" + EventGroupATMessageCreate EventType = "GROUP_AT_MESSAGE_CREATE" + EventGroupMessageCreate EventType = "GROUP_MESSAGE_CREATE" + EventGroupAddRobbot EventType = "GROUP_ADD_ROBBOT" + EventGroupDelRobbot EventType = "GROUP_DEL_ROBBOT" + EventGroupMsgReject EventType = "GROUP_MSG_REJECT" + EventGroupMsgReceive EventType = "GROUP_MSG_RECEIVE" + EventFriendAdd EventType = "FRIEND_ADD" + EventFriendDel EventType = "FRIEND_DEL" + EventC2CMsgReject EventType = "C2C_MSG_REJECT" + EventC2CMsgReceive EventType = "C2C_MSG_RECEIVE" ) // intentEventMap 不同 intent 对应的事件定义 @@ -58,6 +69,7 @@ var intentEventMap = map[Intent][]EventType{ EventForumPostDelete, EventForumReplyCreate, EventForumReplyDelete, EventForumAuditResult, }, IntentInteraction: {EventInteractionCreate}, + IntentQQ: {EventC2CMessageCreate, EventC2CMsgReceive, EventC2CMsgReject, EventGroupATMessageCreate, EventGroupMessageCreate, EventGroupAddRobbot, EventGroupDelRobbot, EventGroupMsgReceive, EventGroupMsgReject, EventFriendAdd, EventFriendDel}, } var eventIntentMap = transposeIntentEventMap(intentEventMap) diff --git a/dto/websocket_intent.go b/dto/websocket_intent.go index 97fde99..10d676f 100644 --- a/dto/websocket_intent.go +++ b/dto/websocket_intent.go @@ -43,6 +43,11 @@ const ( IntentDirectMessageReactions IntentDirectMessageTyping + // IntentQQ 包含 + // - C2C_MESSAGE_CREATE + // - GROUP_AT_MESSAGE_CREATE + IntentQQ Intent = 1 << 25 + IntentInteraction Intent = 1 << 26 // 互动事件 IntentAudit Intent = 1 << 27 // 审核事件 // IntentForum 论坛事件 diff --git a/dto/websocket_payload.go b/dto/websocket_payload.go index 2e09db0..a59fe9c 100644 --- a/dto/websocket_payload.go +++ b/dto/websocket_payload.go @@ -72,6 +72,10 @@ type WSMessageData Message // WSATMessageData only at 机器人的消息 payload type WSATMessageData Message +type WSGroupATMessageData GroupMessage + +type WSGroupMessageData GroupMessage + // WSDirectMessageData 私信消息 payload type WSDirectMessageData Message diff --git a/event/event.go b/event/event.go index 3c2e650..5a6f6cc 100644 --- a/event/event.go +++ b/event/event.go @@ -51,6 +51,9 @@ var eventParseFuncMap = map[dto.OPCode]map[dto.EventType]eventParseFunc{ dto.EventForumAuditResult: forumAuditHandler, dto.EventInteractionCreate: interactionHandler, + + dto.EventGroupATMessageCreate: groupAtMessageHandler, + dto.EventGroupMessageCreate: groupMessageHandler, }, } @@ -58,6 +61,9 @@ type eventParseFunc func(event *dto.WSPayload, message []byte) error // ParseAndHandle 处理回调事件 func ParseAndHandle(payload *dto.WSPayload) error { + if (DefaultHandlers.Check != nil) && DefaultHandlers.Check(payload, payload.RawMessage) == false { + return nil + } // 指定类型的 handler if h, ok := eventParseFuncMap[payload.OPCode][payload.Type]; ok { return h(payload, payload.RawMessage) @@ -72,7 +78,8 @@ func ParseAndHandle(payload *dto.WSPayload) error { // ParseData 解析数据 func ParseData(message []byte, target interface{}) error { data := gjson.Get(string(message), "d") - return json.Unmarshal([]byte(data.String()), target) + err := json.Unmarshal([]byte(data.String()), target) + return err } func guildHandler(payload *dto.WSPayload, message []byte) error { @@ -261,3 +268,25 @@ func interactionHandler(payload *dto.WSPayload, message []byte) error { } return nil } + +func groupAtMessageHandler(payload *dto.WSPayload, message []byte) error { + data := &dto.WSGroupATMessageData{} + if err := ParseData(message, data); err != nil { + return err + } + if DefaultHandlers.GroupAtMessage != nil { + return DefaultHandlers.GroupAtMessage(payload, data) + } + return nil +} + +func groupMessageHandler(payload *dto.WSPayload, message []byte) error { + data := &dto.WSGroupMessageData{} + if err := ParseData(message, data); err != nil { + return err + } + if DefaultHandlers.GroupMessage != nil { + return DefaultHandlers.GroupMessage(payload, data) + } + return nil +} diff --git a/event/register.go b/event/register.go index 1a48300..664db0c 100644 --- a/event/register.go +++ b/event/register.go @@ -9,6 +9,7 @@ var DefaultHandlers struct { Ready ReadyHandler ErrorNotify ErrorNotifyHandler Plain PlainEventHandler + Check CheckEventHandler Guild GuildEventHandler GuildMember GuildMemberEventHandler @@ -31,6 +32,9 @@ var DefaultHandlers struct { ForumAudit ForumAuditEventHandler Interaction InteractionEventHandler + + GroupAtMessage GroupAtMessageEventHandler + GroupMessage GroupMessageEventHandler } // ReadyHandler 可以处理 ws 的 ready 事件 @@ -43,6 +47,9 @@ type ErrorNotifyHandler func(err error) // PlainEventHandler 透传handler type PlainEventHandler func(event *dto.WSPayload, message []byte) error +// CheckEventHandler 消息前置检测 +type CheckEventHandler func(event *dto.WSPayload, message []byte) bool + // GuildEventHandler 频道事件handler type GuildEventHandler func(event *dto.WSPayload, data *dto.WSGuildData) error @@ -94,6 +101,10 @@ type ForumAuditEventHandler func(event *dto.WSPayload, data *dto.WSForumAuditDat // InteractionEventHandler 互动事件 handler type InteractionEventHandler func(event *dto.WSPayload, data *dto.WSInteractionData) error +type GroupAtMessageEventHandler func(event *dto.WSPayload, data *dto.WSGroupATMessageData) error + +type GroupMessageEventHandler func(event *dto.WSPayload, data *dto.WSGroupMessageData) error + // RegisterHandlers 注册事件回调,并返回 intent 用于 websocket 的鉴权 func RegisterHandlers(handlers ...interface{}) dto.Intent { var i dto.Intent @@ -103,6 +114,8 @@ func RegisterHandlers(handlers ...interface{}) dto.Intent { DefaultHandlers.Ready = handle case ErrorNotifyHandler: DefaultHandlers.ErrorNotify = handle + case CheckEventHandler: + DefaultHandlers.Check = handle case PlainEventHandler: DefaultHandlers.Plain = handle case AudioEventHandler: @@ -194,6 +207,12 @@ func registerMessageHandlers(i dto.Intent, handlers ...interface{}) dto.Intent { case MessageAuditEventHandler: DefaultHandlers.MessageAudit = handle i = i | dto.EventToIntent(dto.EventMessageAuditPass, dto.EventMessageAuditReject) + case GroupAtMessageEventHandler: + DefaultHandlers.GroupAtMessage = handle + i = i | dto.EventToIntent(dto.EventGroupATMessageCreate) + case GroupMessageEventHandler: + DefaultHandlers.GroupMessage = handle + i = i | dto.EventToIntent(dto.EventGroupMessageCreate) default: } } diff --git a/examples/apitest/main_test.go b/examples/apitest/main_test.go index 34eaf1e..30b06bf 100644 --- a/examples/apitest/main_test.go +++ b/examples/apitest/main_test.go @@ -25,7 +25,7 @@ var ( testGuildID = "3326534247441079828" // replace your guild id testChannelID = "1595028" // replace your channel id testMessageID = `08e092eeb983afef9e0110f9bb5d1a1231343431313532313836373838333234303420801e -28003091c4bb02380c400c48d8a7928d06` // replace your channel id +28003091c4bb02380c400c48d8a7928d06` // replace your channel id testRolesID = `10054557` // replace your roles id testMemberID = `1201318637970874066` // replace your member id testMarkdownTemplateID = 1231231231231231 // replace your markdown template id @@ -46,7 +46,7 @@ func TestMain(m *testing.M) { } log.Println(conf) - botToken = token.BotToken(conf.AppID, conf.Token) + botToken = token.BotToken(conf.AppID, conf.Token, "Bot") api = botgo.NewOpenAPI(botToken).WithTimeout(3 * time.Second) os.Exit(m.Run()) diff --git a/openapi/iface.go b/openapi/iface.go index f7a0ce7..ea95acf 100644 --- a/openapi/iface.go +++ b/openapi/iface.go @@ -67,6 +67,8 @@ type MessageAPI interface { Message(ctx context.Context, channelID string, messageID string) (*dto.Message, error) Messages(ctx context.Context, channelID string, pager *dto.MessagesPager) ([]*dto.Message, error) PostMessage(ctx context.Context, channelID string, msg *dto.MessageToCreate) (*dto.Message, error) + PostGroupMessage(ctx context.Context, groupId string, msg *dto.GroupMessageToCreate) (*dto.GroupMsgResp, error) + PostGroupRichMediaMessage(ctx context.Context, groupId string, msg *dto.GroupRichMediaMessageToCreate) (*dto.RichMediaMsgResp, error) PatchMessage(ctx context.Context, channelID string, messageID string, msg *dto.MessageToCreate) (*dto.Message, error) RetractMessage(ctx context.Context, channelID, msgID string, options ...RetractMessageOption) error @@ -79,8 +81,6 @@ type GuildAPI interface { Guild(ctx context.Context, guildID string) (*dto.Guild, error) GuildMember(ctx context.Context, guildID, userID string) (*dto.Member, error) GuildMembers(ctx context.Context, guildID string, pager *dto.GuildMembersPager) ([]*dto.Member, error) - GuildRoleMembers(ctx context.Context, guildID string, roleID string, pager *dto.GuildRoleMembersPager) ( - []*dto.Member, string, error) DeleteGuildMember(ctx context.Context, guildID, userID string, opts ...dto.MemberDeleteOption) error // 频道禁言 GuildMute(ctx context.Context, guildID string, mute *dto.UpdateGuildMute) error @@ -120,10 +120,6 @@ type ChannelPermissionsAPI interface { type AudioAPI interface { // PostAudio 执行音频播放,暂停等操作 PostAudio(ctx context.Context, channelID string, value *dto.AudioControl) (*dto.AudioControl, error) - // PutMic 机器人上麦 - PutMic(ctx context.Context, channelID string) error - // DeleteMic 机器人下麦 - DeleteMic(ctx context.Context, channelID string) error } // RoleAPI 用户组相关接口 diff --git a/openapi/v1/audio.go b/openapi/v1/audio.go index d29301a..26018d2 100644 --- a/openapi/v1/audio.go +++ b/openapi/v1/audio.go @@ -2,7 +2,6 @@ package v1 import ( "context" - "github.com/tencent-connect/botgo/log" "github.com/tencent-connect/botgo/dto" ) @@ -21,25 +20,3 @@ func (o openAPI) PostAudio(ctx context.Context, channelID string, value *dto.Aud return value, nil } - -// PutMic 上麦接口实现 -func (o openAPI) PutMic(ctx context.Context, channelID string) error { - _, err := o.request(ctx). - SetPathParam("channel_id", channelID). - Put(o.getURL(micURI)) - if err != nil { - log.Errorf("put mic fail:%+v", err) - } - return err -} - -// DeleteMic 上麦接口实现 -func (o openAPI) DeleteMic(ctx context.Context, channelID string) error { - _, err := o.request(ctx). - SetPathParam("channel_id", channelID). - Delete(o.getURL(micURI)) - if err != nil { - log.Errorf("delete mic fail:%+v", err) - } - return err -} diff --git a/openapi/v1/member.go b/openapi/v1/member.go index c45f5f8..c916299 100644 --- a/openapi/v1/member.go +++ b/openapi/v1/member.go @@ -80,34 +80,6 @@ func (o *openAPI) GuildMembers( return members, nil } -// GuildRoleMembers 分页拉取频道内身份组成员列表 -func (o *openAPI) GuildRoleMembers( - ctx context.Context, guildID string, roleID string, pager *dto.GuildRoleMembersPager, -) ([]*dto.Member, string, error) { - if pager == nil { - return nil, "", errs.ErrPagerIsNil - } - resp, err := o.request(ctx). - SetPathParam("guild_id", guildID). - SetPathParam("role_id", roleID). - SetQueryParams(pager.QueryParams()). - Get(o.getURL(guildRoleMemberURI)) - if err != nil { - return nil, "", err - } - - type res struct { - Data []*dto.Member `json:"data"` - Next string `json:"next"` - } - var roleMembersRsp res - if err := json.Unmarshal(resp.Body(), &roleMembersRsp); err != nil { - return nil, "", err - } - - return roleMembersRsp.Data, roleMembersRsp.Next, nil -} - // DeleteGuildMember 将指定成员踢出频道 func (o *openAPI) DeleteGuildMember(ctx context.Context, guildID, userID string, opts ...dto.MemberDeleteOption) error { opt := &dto.MemberDeleteOpts{} diff --git a/openapi/v1/message.go b/openapi/v1/message.go index 76a6d96..320e9dd 100644 --- a/openapi/v1/message.go +++ b/openapi/v1/message.go @@ -68,6 +68,32 @@ func (o *openAPI) PostMessage(ctx context.Context, channelID string, msg *dto.Me return resp.Result().(*dto.Message), nil } +func (o *openAPI) PostGroupMessage(ctx context.Context, groupID string, msg *dto.GroupMessageToCreate) (*dto.GroupMsgResp, error) { + resp, err := o.request(ctx). + SetResult(dto.GroupMsgResp{}). + SetPathParam("group_openid", groupID). + SetBody(msg). + Post(o.getURL(groupMessageUri)) + if err != nil { + return nil, err + } + + return resp.Result().(*dto.GroupMsgResp), nil +} + +func (o *openAPI) PostGroupRichMediaMessage(ctx context.Context, groupID string, msg *dto.GroupRichMediaMessageToCreate) (*dto.RichMediaMsgResp, error) { + resp, err := o.request(ctx). + SetResult(dto.RichMediaMsgResp{}). + SetPathParam("group_openid", groupID). + SetBody(msg). + Post(o.getURL(groupRichMediaMessageUri)) + if err != nil { + return nil, err + } + + return resp.Result().(*dto.RichMediaMsgResp), nil +} + // PatchMessage 编辑消息 func (o *openAPI) PatchMessage(ctx context.Context, channelID string, messageID string, msg *dto.MessageToCreate) (*dto.Message, error) { diff --git a/openapi/v1/resource.go b/openapi/v1/resource.go index 55115d8..8350948 100644 --- a/openapi/v1/resource.go +++ b/openapi/v1/resource.go @@ -6,6 +6,7 @@ import ( const domain = "api.sgroup.qq.com" const sandBoxDomain = "sandbox.api.sgroup.qq.com" +const getAppAccessTokenDomain = "https://bots.qq.com" const scheme = "https" @@ -13,10 +14,16 @@ type uri string // 目前提供的接口的 uri const ( + getAppAccessTokenUri uri = "/app/getAppAccessToken" + + privateMessageUri uri = "/v2/users/{openid}/messages" + groupMessageUri uri = "/v2/groups/{group_openid}/messages" + privateRichMediaMessageUri uri = "/v2/users/{openid}/files" + groupRichMediaMessageUri uri = "/v2/groups/{group_openid}/files" + guildURI uri = "/guilds/{guild_id}" guildMembersURI uri = "/guilds/{guild_id}/members" guildMemberURI uri = "/guilds/{guild_id}/members/{user_id}" - guildRoleMemberURI uri = "/guilds/{guild_id}/roles/{role_id}/members" guildMuteURI uri = "/guilds/{guild_id}/mute" // 频道禁言 guildMembersMuteURI uri = "/guilds/{guild_id}/members/{user_id}/mute" // 频道指定成员禁言 @@ -37,7 +44,6 @@ const ( gatewayBotURI uri = "/gateway/bot" audioControlURI uri = "/channels/{channel_id}/audio" - micURI uri = "/channels/{channel_id}/mic" rolesURI uri = "/guilds/{guild_id}/roles" roleURI uri = "/guilds/{guild_id}/roles/{role_id}" @@ -85,3 +91,8 @@ func (o *openAPI) getURL(endpoint uri) string { } return fmt.Sprintf("%s://%s%s", scheme, d, endpoint) } + +func (o *openAPI) getQQURL(endpoint uri) string { + d := getAppAccessTokenDomain + return fmt.Sprintf("%s://%s%s", scheme, d, endpoint) +} diff --git a/token/token.go b/token/token.go index 6cc7b2b..c18eba1 100644 --- a/token/token.go +++ b/token/token.go @@ -16,6 +16,7 @@ type Type string const ( TypeBot Type = "Bot" TypeNormal Type = "Bearer" + TypeQQBot Type = "QQBot" ) // Token 用于调用接口的 token 结构 @@ -33,11 +34,11 @@ func New(tokenType Type) *Token { } // BotToken 机器人身份的 token -func BotToken(appID uint64, accessToken string) *Token { +func BotToken(appID uint64, accessToken string, qqType string) *Token { return &Token{ AppID: appID, AccessToken: accessToken, - Type: TypeBot, + Type: Type(qqType), } } diff --git a/websocket/client/client.go b/websocket/client/client.go index 5a02a9d..3d1e7e3 100644 --- a/websocket/client/client.go +++ b/websocket/client/client.go @@ -19,6 +19,7 @@ import ( // DefaultQueueSize 监听队列的缓冲长度 const DefaultQueueSize = 10000 +const DefaultHandleSize = 1000 // Setup 依赖注册 func Setup() { @@ -28,22 +29,25 @@ func Setup() { // New 新建一个连接对象 func (c *Client) New(session dto.Session) websocket.WebSocket { return &Client{ - messageQueue: make(messageChan, DefaultQueueSize), - session: &session, - closeChan: make(closeErrorChan, 10), - heartBeatTicker: time.NewTicker(60 * time.Second), // 先给一个默认 ticker,在收到 hello 包之后,会 reset + + messageQueue: make(messageChan, DefaultQueueSize), + session: &session, + closeChan: make(closeErrorChan, 10), + heartBeatTicker: time.NewTicker(60 * time.Second), // 先给一个默认 ticker,在收到 hello 包之后,会 reset + handleMessageChan: make(chan bool, DefaultHandleSize), } } // Client websocket 连接客户端 type Client struct { - version int - conn *wss.Conn - messageQueue messageChan - session *dto.Session - user *dto.WSUser - closeChan closeErrorChan - heartBeatTicker *time.Ticker // 用于维持定时心跳 + version int + conn *wss.Conn + messageQueue messageChan + session *dto.Session + user *dto.WSUser + closeChan closeErrorChan + heartBeatTicker *time.Ticker // 用于维持定时心跳 + handleMessageChan chan bool } type messageChan chan *dto.WSPayload @@ -211,20 +215,28 @@ func (c *Client) listenMessageAndHandle() { } }() for payload := range c.messageQueue { - c.saveSeq(payload.Seq) - // ready 事件需要特殊处理 - if payload.Type == "READY" { - c.readyHandler(payload) - continue - } - // 解析具体事件,并投递给业务注册的 handler - if err := event.ParseAndHandle(payload); err != nil { - log.Errorf("%s parseAndHandle failed, %v", c.session, err) - } + c.handleMessageChan <- true + go c.listenMessageAndHandleMessage(payload) } log.Infof("%s message queue is closed", c.session) } +func (c *Client) listenMessageAndHandleMessage(payload *dto.WSPayload) { + defer func() { + <-c.handleMessageChan + }() + c.saveSeq(payload.Seq) + // ready 事件需要特殊处理 + if payload.Type == "READY" { + c.readyHandler(payload) + return + } + // 解析具体事件,并投递给业务注册的 handler + if err := event.ParseAndHandle(payload); err != nil { + log.Errorf("%s parseAndHandle failed, %v", c.session, err) + } +} + func (c *Client) saveSeq(seq uint32) { if seq > 0 { c.session.LastSeq = seq