diff --git a/.github/workflows/openimci.yml b/.github/workflows/openimci.yml index 96e03f214..00339110c 100644 --- a/.github/workflows/openimci.yml +++ b/.github/workflows/openimci.yml @@ -161,7 +161,8 @@ jobs: ``` ${{ env.containers }} ``` - GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + continue-on-error: true execute-scripts: name: Execute OpenIM Script On ${{ matrix.os }} @@ -285,4 +286,4 @@ jobs: id: docker_processes run: | sudo docker images - sudo docker ps \ No newline at end of file + sudo docker ps diff --git a/README-zh_CN.md b/README-zh_CN.md index e2df68a56..12a56d4f6 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -35,52 +35,62 @@ ## Ⓜ️ 关于 OpenIM -OpenIM 不仅仅是一个开源的即时消息组件,它是你的应用程序生态系统的一个不可或缺的部分。查看下面的图表,了解 AppServer、AppClient、OpenIMServer 和 OpenIMSDK 是如何交互的。 +OpenIM 是一个专门设计用于在应用程序中集成聊天、音视频通话、通知以及AI聊天机器人等通信功能的服务平台。它通过提供一系列强大的API和Webhooks,使开发者可以轻松地在他们的应用中加入这些交互特性。OpenIM 本身并不是一个独立运行的聊天应用,而是作为一个平台,为其他应用提供支持,实现丰富的通信功能。下图展示 AppServer、AppClient、OpenIMServer 和 OpenIMSDK 之间的交互关系来具体说明。 + + + + ![App-OpenIM 关系](./docs/images/oepnim-design.png) ## 🚀 关于 OpenIMSDK -**OpenIMSDK** 无缝集成到您的应用中,提供丰富、实时的消息体验,无需复杂的 UI 集成。它提供: +**OpenIMSDK** 是为 **OpenIMServer** 设计的IM SDK,专为嵌入客户端应用而生。其主要功能及模块如下: -+ **本地存储**:用于快速数据检索和消息同步。 -+ **监听器回调**:确保实时消息交互性。 -+ **API 封装**:简化开发流程。 -+ **连接管理**:保证可靠的消息传递。 ++ 🌟 主要功能: -它使用 Golang 构建,并支持跨平台部署,确保在所有平台上提供一致的消息体验。 + - 📦 本地存储 + - 🔔 监听器回调 + - 🛡️ API封装 + - 🌐 连接管理 + + ## 📚 主要模块: + + 1. 🚀 初始化及登录 + 2. 👤 用户管理 + 3. 👫 好友管理 + 4. 🤖 群组功能 + 5. 💬 会话处理 + +它使用 Golang 构建,并支持跨平台部署,确保在所有平台上提供一致的接入体验。 👉 **[探索 GO SDK](https://github.com/openimsdk/openim-sdk-core)** ## 🌐 关于 OpenIMServer -精心用 Golang 开发的 **OpenIMServer** 通过多重方式确保了卓越的即时消息服务器能力: - -+ **模块组成**:它由多个模块组成,例如网关和多个 RPC 服务,提供一个多功能的消息环境。 -+ **微服务架构**:支持集群模式,确保出色的性能和可伸缩性,以有效管理各个实例间的通信。 -+ **多样的部署选项**:适应你的操作偏好,通过源代码、Kubernetes 或 Docker 提供部署选项。 ++ **OpenIMServer** 具有以下特点: + - 🌐 微服务架构:支持集群模式,包括网关(gateway)和多个rpc服务。 + - 🚀 部署方式多样:支持源代码、kubernetes或docker部署。 + - 海量用户支持:十万超级大群,千万用户,及百亿消息 ### 增强的业务功能: -+ **REST API**:OpenIMServer 为业务系统提供 REST API,旨在通过后端接口为您的操作提供附加功能,如群组创建和消息推送。 -+ **回调**:为了扩展其在各种业务形式中的实用性,OpenIMServer 提供了回调能力。即,在事件发生之前或之后,它向业务服务器发送请求,比如发送消息,丰富通信过程中的交互和数据交换流。 ++ **REST API**:OpenIMServer 提供了REST API供业务系统使用,旨在赋予业务更多功能,例如通过后台接口建立群组、发送推送消息等。 ++ **Webhooks**:OpenIMServer提供了回调能力以扩展更多的业务形态,所谓回调,即OpenIMServer会在某一事件发生之前或者之后,向业务服务器发送请求,如发送消息之前或之后的回调。 -👉 **[了解更多](https://doc.rentsoft.cn/guides/introduction/product)** +👉 **[了解更多](https://docs.openim.io/guides/introduction/product)** ## :rocket: 快速开始 -你只需要一个简单的命令,就可以快速学习 OpenIM 的工程解决方案: +在线体验iOS/Android/H5/PC/Web: -``` -bashCopy code -$ make demo -``` +👉 **[OpenIM online demo](https://www.openim.io/zh/commercial)** 🤲 为了方便用户体验,我们提供了多种部署解决方案,您可以根据下面的列表选择自己的部署方法: -+ **[源代码部署指南](https://doc.rentsoft.cn/guides/gettingStarted/imSourceCodeDeployment)** -+ **[Docker 部署指南](https://doc.rentsoft.cn/guides/gettingStarted/dockerCompose)** -+ **[Kubernetes 部署指南](https://github.com/openimsdk/open-im-server/tree/main/deployments)** ++ **[源代码部署指南](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** ++ **[Docker 部署指南](https://docs.openim.io/guides/gettingStarted/dockerCompose)** ++ **[Kubernetes 部署指南](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** ## :hammer_and_wrench: 开始开发 OpenIM diff --git a/README.md b/README.md index 781db1217..025672a0b 100644 --- a/README.md +++ b/README.md @@ -25,178 +25,56 @@

- ## Ⓜ️ About OpenIM -OpenIM isn't just an open-source instant messaging component, it's an integral part of your application ecosystem. Check out this diagram to understand how AppServer, AppClient, OpenIMServer, and OpenIMSDK interact. +OpenIM is a service platform specifically designed for integrating chat, audio-video calls, notifications, and AI chatbots into applications. It provides a range of powerful APIs and Webhooks, enabling developers to easily incorporate these interactive features into their applications. OpenIM is not a standalone chat application, but rather serves as a platform to support other applications in achieving rich communication functionalities. The following diagram illustrates the interaction between AppServer, AppClient, OpenIMServer, and OpenIMSDK to explain in detail. ![App-OpenIM Relationship](./docs/images/oepnim-design.png) ## 🚀 About OpenIMSDK -**OpenIMSDK** seamlessly integrates into your application, delivering a rich, real-time messaging experience without requiring intricate UI integration. It provides: +**OpenIMSDK** is an IM SDK designed for **OpenIMServer**, created specifically for embedding in client applications. Its main features and modules are as follows: -+ **Local Storage**: For quick data retrieval and message synchronization. -+ **Listener Callbacks**: Ensuring real-time message interactivity. -+ **API Encapsulation**: Streamlining development processes. -+ **Connection Management**: Guaranteeing reliable message delivery. ++ 🌟 Main Features: -It's crafted in Golang and supports cross-platform deployment, ensuring a coherent messaging experience across all platforms. + - 📦 Local storage + - 🔔 Listener callbacks + - 🛡️ API wrapping + - 🌐 Connection management + + ## 📚 Main Modules: + + 1. 🚀 Initialization and Login + 2. 👤 User Management + 3. 👫 Friend Management + 4. 🤖 Group Functions + 5. 💬 Conversation Handling + +It is built using Golang and supports cross-platform deployment, ensuring a consistent access experience across all platforms. 👉 **[Explore GO SDK](https://github.com/openimsdk/openim-sdk-core)** ## 🌐 About OpenIMServer -**OpenIMServer**, meticulously developed in Golang, ensures a stellar instant messaging server capability with a multifold approach: ++ **OpenIMServer** has the following characteristics: + - 🌐 Microservice architecture: Supports cluster mode, including a gateway and multiple rpc services. + - 🚀 Diverse deployment methods: Supports deployment via source code, Kubernetes, or Docker. + - Support for massive user base: Super large groups with hundreds of thousands of users, tens of millions of users, and billions of messages. -+ **Modular Composition**: It's comprised of several modules, such as the gateway and multiple RPC services, offering a versatile messaging environment. -+ **Microservices Architecture**: Supporting cluster modes, it assures outstanding performance and scalability to manage communication effectively across various instances. -+ **Diverse Deployment Options**: Adapts to your operational preferences, offering deployment via source code, Kubernetes, or Docker. +### Enhanced Business Functionality: -### Enhanced Business Functionalities: - -+ **REST API**: OpenIMServer provides REST API for business systems, aiming to empower your operations with additional functionalities like group creation and message push via backend interfaces. -+ **Callbacks**: To expand its utility across varied business forms, OpenIMServer offers callback capabilities. That is, it sends a request to the business server before or after an event occurs, such as sending a message, enriching the interaction and data exchange flow in the communication processes. - -👉 **[Learn More](https://docs.openim.io/guides/introduction/product)** - - ++ **REST API**: OpenIMServer offers REST APIs for business systems, aimed at empowering businesses with more functionalities, such as creating groups and sending push messages through backend interfaces. ++ **Webhooks**: OpenIMServer provides callback capabilities to extend more business forms. A callback means that OpenIMServer sends a request to the business server before or after a certain event, like callbacks before or after sending a message. +👉 **[Learn more](https://docs.openim.io/guides/introduction/product)** ## :rocket: Quick Start -We support many platforms. Here are the addresses for quick experience on the web side: - -👉 **[OpenIM online web demo](https://web-enterprise.rentsoft.cn/)** - -You can quickly learn OpenIM engineering solutions, all it takes is one simple command: - -```bash -$ make demo -``` - -🤲 In order to facilitate the user experience, we have provided a variety of deployment solutions, you can choose your own deployment method according to the list below: - - +🤲 To facilitate user experience, we offer various deployment solutions. You can choose your deployment method from the list below: + **[Source Code Deployment Guide](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** -+ **[Production deployment of Linux systems](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/install-openim-linux-system.md)** + **[Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose)** -+ **[Kubernetes Deployment Guide](https://github.com/openimsdk/open-im-server/tree/main/deployments)** - - ++ **[Kubernetes Deployment Guide](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** ## :hammer_and_wrench: To start developing OpenIM diff --git a/deployments/templates/openim.yaml b/deployments/templates/openim.yaml index 6880e4c4e..4c84373a5 100644 --- a/deployments/templates/openim.yaml +++ b/deployments/templates/openim.yaml @@ -320,6 +320,14 @@ callback: enable: ${CALLBACK_ENABLE} timeout: ${CALLBACK_TIMEOUT} failedContinue: ${CALLBACK_FAILED_CONTINUE} + beforeUpdateUserInfoEx: + enable: ${CALLBACK_ENABLE} + timeout: ${CALLBACK_TIMEOUT} + failedContinue: ${CALLBACK_FAILED_CONTINUE} + afterUpdateUserInfoEx: + enable: ${CALLBACK_ENABLE} + timeout: ${CALLBACK_TIMEOUT} + failedContinue: ${CALLBACK_FAILED_CONTINUE} afterSendSingleMsg: enable: ${CALLBACK_ENABLE} timeout: ${CALLBACK_TIMEOUT} diff --git a/go.mod b/go.mod index f10e123a0..5e8e7275b 100644 --- a/go.mod +++ b/go.mod @@ -4,8 +4,8 @@ go 1.19 require ( firebase.google.com/go v3.13.0+incompatible - github.com/OpenIMSDK/protocol v0.0.44 - github.com/OpenIMSDK/tools v0.0.21 + github.com/OpenIMSDK/protocol v0.0.47 + github.com/OpenIMSDK/tools v0.0.23 github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/dtm-labs/rockscache v0.1.1 github.com/gin-gonic/gin v1.9.1 diff --git a/go.sum b/go.sum index 34f5d3ae9..80a02d20a 100644 --- a/go.sum +++ b/go.sum @@ -18,10 +18,10 @@ firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIw github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/IBM/sarama v1.41.3 h1:MWBEJ12vHC8coMjdEXFq/6ftO6DUZnQlFYcxtOJFa7c= github.com/IBM/sarama v1.41.3/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ= -github.com/OpenIMSDK/protocol v0.0.44 h1:P+9gJ9EW3y+VmzrjPludzn/5r1fjubaC19mKYJ7Oiew= -github.com/OpenIMSDK/protocol v0.0.44/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y= -github.com/OpenIMSDK/tools v0.0.21 h1:iTapc2mIEVH/xl5Nd6jfwPub11Pgp44tVcE1rjB3a48= -github.com/OpenIMSDK/tools v0.0.21/go.mod h1:eg+q4A34Qmu73xkY0mt37FHGMCMfC6CtmOnm0kFEGFI= +github.com/OpenIMSDK/protocol v0.0.47 h1:DTJMFSONzqT0i/wa4Q1CtDT/jVATVudIRHcpY1zSWYE= +github.com/OpenIMSDK/protocol v0.0.47/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y= +github.com/OpenIMSDK/tools v0.0.23 h1:xozfrGzhbpNPlDTap5DLVPk+JfgZ/ZyIj4Cuu3/bm9w= +github.com/OpenIMSDK/tools v0.0.23/go.mod h1:eg+q4A34Qmu73xkY0mt37FHGMCMfC6CtmOnm0kFEGFI= github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= diff --git a/internal/api/conversation.go b/internal/api/conversation.go index 6463cbde6..eb735e550 100644 --- a/internal/api/conversation.go +++ b/internal/api/conversation.go @@ -33,8 +33,8 @@ func (o *ConversationApi) GetAllConversations(c *gin.Context) { a2r.Call(conversation.ConversationClient.GetAllConversations, o.Client, c) } -func (o *ConversationApi) GetConversationsList(c *gin.Context) { - a2r.Call(conversation.ConversationClient.GetConversationList, o.Client, c) +func (o *ConversationApi) GetSortedConversationList(c *gin.Context) { + a2r.Call(conversation.ConversationClient.GetSortedConversationList, o.Client, c) } func (o *ConversationApi) GetConversation(c *gin.Context) { diff --git a/internal/api/msg.go b/internal/api/msg.go index 548450534..9348596ac 100644 --- a/internal/api/msg.go +++ b/internal/api/msg.go @@ -164,6 +164,8 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM data = apistruct.VideoElem{} case constant.File: data = apistruct.FileElem{} + case constant.AtText: + data = apistruct.AtElem{} case constant.Custom: data = apistruct.CustomElem{} case constant.OANotification: @@ -172,7 +174,6 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM if err = m.userRpcClient.GetNotificationByID(c, req.SendID); err != nil { return nil, err } - default: return nil, errs.ErrArgs.WithDetail("not support err contentType") } diff --git a/internal/api/route.go b/internal/api/route.go index 1c91f4dde..3f16d3e50 100644 --- a/internal/api/route.go +++ b/internal/api/route.go @@ -83,6 +83,7 @@ func NewGinRouter(discov discoveryregistry.SvcDiscoveryRegistry, rdb redis.Unive userRouterGroup.POST("/process_user_command_delete", ParseToken, u.ProcessUserCommandDelete) userRouterGroup.POST("/process_user_command_update", ParseToken, u.ProcessUserCommandUpdate) userRouterGroup.POST("/process_user_command_get", ParseToken, u.ProcessUserCommandGet) + userRouterGroup.POST("/process_user_command_get_all", ParseToken, u.ProcessUserCommandGetAll) userRouterGroup.POST("/add_notification_account", ParseToken, u.AddNotificationAccount) userRouterGroup.POST("/update_notification_account", ParseToken, u.UpdateNotificationAccountInfo) @@ -204,7 +205,7 @@ func NewGinRouter(discov discoveryregistry.SvcDiscoveryRegistry, rdb redis.Unive conversationGroup := r.Group("/conversation", ParseToken) { c := NewConversationApi(*conversationRpc) - conversationGroup.POST("/get_conversations_list", c.GetConversationsList) + conversationGroup.POST("/get_sorted_conversation_list", c.GetSortedConversationList) conversationGroup.POST("/get_all_conversations", c.GetAllConversations) conversationGroup.POST("/get_conversation", c.GetConversation) conversationGroup.POST("/get_conversations", c.GetConversations) diff --git a/internal/api/user.go b/internal/api/user.go index 5f0e23631..03d22c354 100644 --- a/internal/api/user.go +++ b/internal/api/user.go @@ -221,6 +221,11 @@ func (u *UserApi) ProcessUserCommandGet(c *gin.Context) { a2r.Call(user.UserClient.ProcessUserCommandGet, u.Client, c) } +// ProcessUserCommandGet user general function get all +func (u *UserApi) ProcessUserCommandGetAll(c *gin.Context) { + a2r.Call(user.UserClient.ProcessUserCommandGetAll, u.Client, c) +} + func (u *UserApi) AddNotificationAccount(c *gin.Context) { a2r.Call(user.UserClient.AddNotificationAccount, u.Client, c) } diff --git a/internal/push/callback.go b/internal/push/callback.go index 2085493c5..99a58fb07 100644 --- a/internal/push/callback.go +++ b/internal/push/callback.go @@ -37,7 +37,7 @@ func callbackOfflinePush( msg *sdkws.MsgData, offlinePushUserIDs *[]string, ) error { - if !config.Config.Callback.CallbackOfflinePush.Enable { + if !config.Config.Callback.CallbackOfflinePush.Enable || msg.ContentType == constant.Typing { return nil } req := &callbackstruct.CallbackBeforePushReq{ @@ -73,7 +73,7 @@ func callbackOfflinePush( } func callbackOnlinePush(ctx context.Context, userIDs []string, msg *sdkws.MsgData) error { - if !config.Config.Callback.CallbackOnlinePush.Enable || utils.Contain(msg.SendID, userIDs...) { + if !config.Config.Callback.CallbackOnlinePush.Enable || utils.Contain(msg.SendID, userIDs...) || msg.ContentType == constant.Typing { return nil } req := callbackstruct.CallbackBeforePushReq{ @@ -107,7 +107,7 @@ func callbackBeforeSuperGroupOnlinePush( msg *sdkws.MsgData, pushToUserIDs *[]string, ) error { - if !config.Config.Callback.CallbackBeforeSuperGroupOnlinePush.Enable { + if !config.Config.Callback.CallbackBeforeSuperGroupOnlinePush.Enable || msg.ContentType == constant.Typing { return nil } req := callbackstruct.CallbackBeforeSuperGroupOnlinePushReq{ diff --git a/internal/push/push_to_client.go b/internal/push/push_to_client.go index 12b78ea2d..7cee7b99d 100644 --- a/internal/push/push_to_client.go +++ b/internal/push/push_to_client.go @@ -101,11 +101,9 @@ func (p *Pusher) DeleteMemberAndSetConversationSeq(ctx context.Context, groupID func (p *Pusher) Push2User(ctx context.Context, userIDs []string, msg *sdkws.MsgData) error { log.ZDebug(ctx, "Get msg from msg_transfer And push msg", "userIDs", userIDs, "msg", msg.String()) - // callback if err := callbackOnlinePush(ctx, userIDs, msg); err != nil { return err } - // push wsResults, err := p.GetConnsAndOnlinePush(ctx, msg, userIDs) if err != nil { @@ -120,7 +118,7 @@ func (p *Pusher) Push2User(ctx context.Context, userIDs []string, msg *sdkws.Msg } for _, v := range wsResults { - if msg.SendID != v.UserID && (!v.OnlinePush) { + if !v.OnlinePush && msg.SendID == v.UserID { if err = callbackOfflinePush(ctx, userIDs, msg, &[]string{}); err != nil { return err } @@ -130,6 +128,7 @@ func (p *Pusher) Push2User(ctx context.Context, userIDs []string, msg *sdkws.Msg return err } } + } return nil } diff --git a/internal/rpc/conversation/conversaion.go b/internal/rpc/conversation/conversaion.go index b80e32953..d0d59547c 100644 --- a/internal/rpc/conversation/conversaion.go +++ b/internal/rpc/conversation/conversaion.go @@ -89,8 +89,8 @@ func (c *conversationServer) GetConversation(ctx context.Context, req *pbconvers return resp, nil } -func (m *conversationServer) GetConversationList(ctx context.Context, req *pbconversation.GetConversationListReq) (resp *pbconversation.GetConversationListResp, err error) { - log.ZDebug(ctx, "GetConversationList", "seqs", req, "userID", req.UserID) +func (m *conversationServer) GetSortedConversationList(ctx context.Context, req *pbconversation.GetSortedConversationListReq) (resp *pbconversation.GetSortedConversationListResp, err error) { + log.ZDebug(ctx, "GetSortedConversationList", "seqs", req, "userID", req.UserID) var conversationIDs []string if len(req.ConversationIDs) == 0 { conversationIDs, err = m.conversationDatabase.GetConversationIDs(ctx, req.UserID) @@ -129,30 +129,37 @@ func (m *conversationServer) GetConversationList(ctx context.Context, req *pbcon return nil, err } + var unreadTotal int64 conversation_unreadCount := make(map[string]int64) for conversationID, maxSeq := range maxSeqs { - conversation_unreadCount[conversationID] = maxSeq - hasReadSeqs[conversationID] + unreadCount := maxSeq - hasReadSeqs[conversationID] + conversation_unreadCount[conversationID] = unreadCount + unreadTotal += unreadCount } - conversation_isPinkTime := make(map[int64]string) - conversation_notPinkTime := make(map[int64]string) + conversation_isPinTime := make(map[int64]string) + conversation_notPinTime := make(map[int64]string) for _, v := range conversations { conversationID := v.ConversationID time := conversationMsg[conversationID].MsgInfo.LatestMsgRecvTime conversationMsg[conversationID].RecvMsgOpt = v.RecvMsgOpt if v.IsPinned { conversationMsg[conversationID].IsPinned = v.IsPinned - conversation_isPinkTime[time] = conversationID + conversation_isPinTime[time] = conversationID continue } - conversation_notPinkTime[time] = conversationID + conversation_notPinTime[time] = conversationID } - resp = &pbconversation.GetConversationListResp{ + resp = &pbconversation.GetSortedConversationListResp{ + ConversationTotal: int64(len(chatLogs)), ConversationElems: []*pbconversation.ConversationElem{}, + UnreadTotal: unreadTotal, } - m.conversationSort(conversation_isPinkTime, resp, conversation_unreadCount, conversationMsg) - m.conversationSort(conversation_notPinkTime, resp, conversation_unreadCount, conversationMsg) + m.conversationSort(conversation_isPinTime, resp, conversation_unreadCount, conversationMsg) + m.conversationSort(conversation_notPinTime, resp, conversation_unreadCount, conversationMsg) + + resp.ConversationElems = utils.Paginate(resp.ConversationElems, int(req.Pagination.GetPageNumber()), int(req.Pagination.GetShowNumber())) return resp, nil } @@ -425,7 +432,7 @@ func (c *conversationServer) GetConversationOfflinePushUserIDs( func (c *conversationServer) conversationSort( conversations map[int64]string, - resp *pbconversation.GetConversationListResp, + resp *pbconversation.GetSortedConversationListResp, conversation_unreadCount map[string]int64, conversationMsg map[string]*pbconversation.ConversationElem, ) { diff --git a/internal/rpc/friend/friend.go b/internal/rpc/friend/friend.go index c53cb88f5..84702f548 100644 --- a/internal/rpc/friend/friend.go +++ b/internal/rpc/friend/friend.go @@ -452,22 +452,19 @@ func (s *friendServer) UpdateFriends( return nil, err } - for _, friendID := range req.FriendUserIDs { - if req.IsPinned != nil { - if err = s.friendDatabase.UpdateFriendPinStatus(ctx, req.OwnerUserID, friendID, req.IsPinned.Value); err != nil { - return nil, err - } - } - if req.Remark != nil { - if err = s.friendDatabase.UpdateFriendRemark(ctx, req.OwnerUserID, friendID, req.Remark.Value); err != nil { - return nil, err - } - } - if req.Ex != nil { - if err = s.friendDatabase.UpdateFriendEx(ctx, req.OwnerUserID, friendID, req.Ex.Value); err != nil { - return nil, err - } - } + val := make(map[string]any) + + if req.IsPinned != nil { + val["is_pinned"] = req.IsPinned.Value + } + if req.Remark != nil { + val["remark"] = req.Remark.Value + } + if req.Ex != nil { + val["ex"] = req.Ex.Value + } + if err = s.friendDatabase.UpdateFriends(ctx, req.OwnerUserID, req.FriendUserIDs, val); err != nil { + return nil, err } resp := &pbfriend.UpdateFriendsResp{} diff --git a/internal/rpc/group/callback.go b/internal/rpc/group/callback.go index 8779cb89b..d891f4d1e 100644 --- a/internal/rpc/group/callback.go +++ b/internal/rpc/group/callback.go @@ -279,20 +279,20 @@ func CallbackApplyJoinGroupBefore(ctx context.Context, req *callbackstruct.Callb return nil } -func CallbackTransferGroupOwnerAfter(ctx context.Context, req *pbgroup.TransferGroupOwnerReq) (err error) { - if !config.Config.Callback.CallbackTransferGroupOwnerAfter.Enable { +func CallbackAfterTransferGroupOwner(ctx context.Context, req *pbgroup.TransferGroupOwnerReq) (err error) { + if !config.Config.Callback.CallbackAfterTransferGroupOwner.Enable { return nil } cbReq := &callbackstruct.CallbackTransferGroupOwnerReq{ - CallbackCommand: callbackstruct.CallbackTransferGroupOwnerAfter, + CallbackCommand: callbackstruct.CallbackAfterTransferGroupOwner, GroupID: req.GroupID, OldOwnerUserID: req.OldOwnerUserID, NewOwnerUserID: req.NewOwnerUserID, } resp := &callbackstruct.CallbackTransferGroupOwnerResp{} - if err = http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeJoinGroup); err != nil { + if err = http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackAfterTransferGroupOwner); err != nil { return err } return nil diff --git a/internal/rpc/group/group.go b/internal/rpc/group/group.go index b1ea0aa03..f9b73ad2b 100644 --- a/internal/rpc/group/group.go +++ b/internal/rpc/group/group.go @@ -1061,7 +1061,7 @@ func (s *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans return nil, err } - if err := CallbackTransferGroupOwnerAfter(ctx, req); err != nil { + if err := CallbackAfterTransferGroupOwner(ctx, req); err != nil { return nil, err } s.Notification.GroupOwnerTransferredNotification(ctx, req) diff --git a/internal/rpc/msg/callback.go b/internal/rpc/msg/callback.go index 5d192fb87..f98318bba 100644 --- a/internal/rpc/msg/callback.go +++ b/internal/rpc/msg/callback.go @@ -70,7 +70,7 @@ func GetContent(msg *sdkws.MsgData) string { } func callbackBeforeSendSingleMsg(ctx context.Context, msg *pbchat.SendMsgReq) error { - if !config.Config.Callback.CallbackBeforeSendSingleMsg.Enable { + if !config.Config.Callback.CallbackBeforeSendSingleMsg.Enable || msg.MsgData.ContentType == constant.Typing { return nil } req := &cbapi.CallbackBeforeSendSingleMsgReq{ @@ -85,7 +85,7 @@ func callbackBeforeSendSingleMsg(ctx context.Context, msg *pbchat.SendMsgReq) er } func callbackAfterSendSingleMsg(ctx context.Context, msg *pbchat.SendMsgReq) error { - if !config.Config.Callback.CallbackAfterSendSingleMsg.Enable { + if !config.Config.Callback.CallbackAfterSendSingleMsg.Enable || msg.MsgData.ContentType == constant.Typing { return nil } req := &cbapi.CallbackAfterSendSingleMsgReq{ @@ -100,7 +100,7 @@ func callbackAfterSendSingleMsg(ctx context.Context, msg *pbchat.SendMsgReq) err } func callbackBeforeSendGroupMsg(ctx context.Context, msg *pbchat.SendMsgReq) error { - if !config.Config.Callback.CallbackBeforeSendGroupMsg.Enable { + if !config.Config.Callback.CallbackBeforeSendGroupMsg.Enable || msg.MsgData.ContentType == constant.Typing { return nil } req := &cbapi.CallbackBeforeSendGroupMsgReq{ @@ -115,7 +115,7 @@ func callbackBeforeSendGroupMsg(ctx context.Context, msg *pbchat.SendMsgReq) err } func callbackAfterSendGroupMsg(ctx context.Context, msg *pbchat.SendMsgReq) error { - if !config.Config.Callback.CallbackAfterSendGroupMsg.Enable { + if !config.Config.Callback.CallbackAfterSendGroupMsg.Enable || msg.MsgData.ContentType == constant.Typing { return nil } req := &cbapi.CallbackAfterSendGroupMsgReq{ diff --git a/internal/rpc/msg/send.go b/internal/rpc/msg/send.go index dd08292bd..630b74a4a 100644 --- a/internal/rpc/msg/send.go +++ b/internal/rpc/msg/send.go @@ -65,6 +65,7 @@ func (m *msgServer) sendMsgSuperGroupChat( if err = callbackBeforeSendGroupMsg(ctx, req); err != nil { return nil, err } + if err := callbackMsgModify(ctx, req); err != nil { return nil, err } @@ -167,6 +168,7 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq if err = callbackBeforeSendSingleMsg(ctx, req); err != nil { return nil, err } + if err := callbackMsgModify(ctx, req); err != nil { return nil, err } diff --git a/internal/rpc/user/user.go b/internal/rpc/user/user.go index 51403d631..158e37d70 100644 --- a/internal/rpc/user/user.go +++ b/internal/rpc/user/user.go @@ -17,6 +17,7 @@ package user import ( "context" "errors" + "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" "math/rand" "strings" @@ -228,7 +229,7 @@ func (s *userServer) AccountCheck(ctx context.Context, req *pbuser.AccountCheckR } func (s *userServer) GetPaginationUsers(ctx context.Context, req *pbuser.GetPaginationUsersReq) (resp *pbuser.GetPaginationUsersResp, err error) { - total, users, err := s.Page(ctx, req.Pagination) + total, users, err := s.PageFindUser(ctx, constant.IMOrdinaryUser, req.Pagination) if err != nil { return nil, err } @@ -379,38 +380,94 @@ func (s *userServer) GetSubscribeUsersStatus(ctx context.Context, // ProcessUserCommandAdd user general function add func (s *userServer) ProcessUserCommandAdd(ctx context.Context, req *pbuser.ProcessUserCommandAddReq) (*pbuser.ProcessUserCommandAddResp, error) { - // Assuming you have a method in s.UserDatabase to add a user command - err := s.UserDatabase.AddUserCommand(ctx, req.UserID, req.Type, req.Uuid, req.Value) + err := authverify.CheckAccessV3(ctx, req.UserID) if err != nil { return nil, err } + var value string + if req.Value != nil { + value = req.Value.Value + } + var ex string + if req.Ex != nil { + value = req.Ex.Value + } + // Assuming you have a method in s.UserDatabase to add a user command + err = s.UserDatabase.AddUserCommand(ctx, req.UserID, req.Type, req.Uuid, value, ex) + if err != nil { + return nil, err + } + tips := &sdkws.UserCommandAddTips{ + FromUserID: req.UserID, + ToUserID: req.UserID, + } + err = s.userNotificationSender.UserCommandAddNotification(ctx, tips) + if err != nil { + return nil, err + } return &pbuser.ProcessUserCommandAddResp{}, nil } // ProcessUserCommandDelete user general function delete func (s *userServer) ProcessUserCommandDelete(ctx context.Context, req *pbuser.ProcessUserCommandDeleteReq) (*pbuser.ProcessUserCommandDeleteResp, error) { - // Assuming you have a method in s.UserDatabase to delete a user command - err := s.UserDatabase.DeleteUserCommand(ctx, req.UserID, req.Type, req.Uuid) + err := authverify.CheckAccessV3(ctx, req.UserID) if err != nil { return nil, err } + err = s.UserDatabase.DeleteUserCommand(ctx, req.UserID, req.Type, req.Uuid) + if err != nil { + return nil, err + } + tips := &sdkws.UserCommandDeleteTips{ + FromUserID: req.UserID, + ToUserID: req.UserID, + } + err = s.userNotificationSender.UserCommandDeleteNotification(ctx, tips) + if err != nil { + return nil, err + } return &pbuser.ProcessUserCommandDeleteResp{}, nil } // ProcessUserCommandUpdate user general function update func (s *userServer) ProcessUserCommandUpdate(ctx context.Context, req *pbuser.ProcessUserCommandUpdateReq) (*pbuser.ProcessUserCommandUpdateResp, error) { - // Assuming you have a method in s.UserDatabase to update a user command - err := s.UserDatabase.UpdateUserCommand(ctx, req.UserID, req.Type, req.Uuid, req.Value) + err := authverify.CheckAccessV3(ctx, req.UserID) if err != nil { return nil, err } + val := make(map[string]any) + // Map fields from eax to val + if req.Value != nil { + val["value"] = req.Value.Value + } + if req.Ex != nil { + val["ex"] = req.Ex.Value + } + + // Assuming you have a method in s.UserDatabase to update a user command + err = s.UserDatabase.UpdateUserCommand(ctx, req.UserID, req.Type, req.Uuid, val) + if err != nil { + return nil, err + } + tips := &sdkws.UserCommandUpdateTips{ + FromUserID: req.UserID, + ToUserID: req.UserID, + } + err = s.userNotificationSender.UserCommandUpdateNotification(ctx, tips) + if err != nil { + return nil, err + } return &pbuser.ProcessUserCommandUpdateResp{}, nil } func (s *userServer) ProcessUserCommandGet(ctx context.Context, req *pbuser.ProcessUserCommandGetReq) (*pbuser.ProcessUserCommandGetResp, error) { + err := authverify.CheckAccessV3(ctx, req.UserID) + if err != nil { + return nil, err + } // Fetch user commands from the database commands, err := s.UserDatabase.GetUserCommands(ctx, req.UserID, req.Type) if err != nil { @@ -423,14 +480,45 @@ func (s *userServer) ProcessUserCommandGet(ctx context.Context, req *pbuser.Proc for _, command := range commands { // No need to use index since command is already a pointer commandInfoSlice = append(commandInfoSlice, &pbuser.CommandInfoResp{ + Type: command.Type, Uuid: command.Uuid, Value: command.Value, CreateTime: command.CreateTime, + Ex: command.Ex, }) } // Return the response with the slice - return &pbuser.ProcessUserCommandGetResp{KVArray: commandInfoSlice}, nil + return &pbuser.ProcessUserCommandGetResp{CommandResp: commandInfoSlice}, nil +} + +func (s *userServer) ProcessUserCommandGetAll(ctx context.Context, req *pbuser.ProcessUserCommandGetAllReq) (*pbuser.ProcessUserCommandGetAllResp, error) { + err := authverify.CheckAccessV3(ctx, req.UserID) + if err != nil { + return nil, err + } + // Fetch user commands from the database + commands, err := s.UserDatabase.GetAllUserCommands(ctx, req.UserID) + if err != nil { + return nil, err + } + + // Initialize commandInfoSlice as an empty slice + commandInfoSlice := make([]*pbuser.AllCommandInfoResp, 0, len(commands)) + + for _, command := range commands { + // No need to use index since command is already a pointer + commandInfoSlice = append(commandInfoSlice, &pbuser.AllCommandInfoResp{ + Type: command.Type, + Uuid: command.Uuid, + Value: command.Value, + CreateTime: command.CreateTime, + Ex: command.Ex, + }) + } + + // Return the response with the slice + return &pbuser.ProcessUserCommandGetAllResp{CommandResp: commandInfoSlice}, nil } func (s *userServer) AddNotificationAccount(ctx context.Context, req *pbuser.AddNotificationAccountReq) (*pbuser.AddNotificationAccountResp, error) { @@ -438,22 +526,28 @@ func (s *userServer) AddNotificationAccount(ctx context.Context, req *pbuser.Add return nil, err } - var userID string - for i := 0; i < 20; i++ { - userId := s.genUserID() - _, err := s.UserDatabase.FindWithError(ctx, []string{userId}) - if err == nil { - continue + if req.UserID == "" { + for i := 0; i < 20; i++ { + userId := s.genUserID() + _, err := s.UserDatabase.FindWithError(ctx, []string{userId}) + if err == nil { + continue + } + req.UserID = userId + break + } + if req.UserID == "" { + return nil, errs.ErrInternalServer.Wrap("gen user id failed") + } + } else { + _, err := s.UserDatabase.FindWithError(ctx, []string{req.UserID}) + if err == nil { + return nil, errs.ErrArgs.Wrap("userID is used") } - userID = userId - break - } - if userID == "" { - return nil, errs.ErrInternalServer.Wrap("gen user id failed") } user := &tablerelation.UserModel{ - UserID: userID, + UserID: req.UserID, Nickname: req.NickName, FaceURL: req.FaceURL, CreateTime: time.Now(), @@ -463,7 +557,11 @@ func (s *userServer) AddNotificationAccount(ctx context.Context, req *pbuser.Add return nil, err } - return &pbuser.AddNotificationAccountResp{}, nil + return &pbuser.AddNotificationAccountResp{ + UserID: req.UserID, + NickName: req.NickName, + FaceURL: req.FaceURL, + }, nil } func (s *userServer) UpdateNotificationAccountInfo(ctx context.Context, req *pbuser.UpdateNotificationAccountInfoReq) (*pbuser.UpdateNotificationAccountInfoResp, error) { @@ -497,30 +595,33 @@ func (s *userServer) SearchNotificationAccount(ctx context.Context, req *pbuser. return nil, err } - if req.NickName != "" { - users, err := s.UserDatabase.FindByNickname(ctx, req.NickName) + var users []*relation.UserModel + var err error + if req.Keyword != "" { + users, err = s.UserDatabase.Find(ctx, []string{req.Keyword}) if err != nil { return nil, err } - resp := s.userModelToResp(users) - return resp, nil - } - - if req.UserID != "" { - users, err := s.UserDatabase.Find(ctx, []string{req.UserID}) + resp := s.userModelToResp(users, req.Pagination) + if resp.Total != 0 { + return resp, nil + } + users, err = s.UserDatabase.FindByNickname(ctx, req.Keyword) if err != nil { return nil, err } - resp := s.userModelToResp(users) + resp = s.userModelToResp(users, req.Pagination) + return resp, nil + return resp, nil } - users, err := s.UserDatabase.FindNotification(ctx, constant.AppNotificationAdmin) + users, err = s.UserDatabase.FindNotification(ctx, constant.AppNotificationAdmin) if err != nil { return nil, err } - resp := s.userModelToResp(users) + resp := s.userModelToResp(users, req.Pagination) return resp, nil } @@ -554,7 +655,7 @@ func (s *userServer) genUserID() string { return string(data) } -func (s *userServer) userModelToResp(users []*relation.UserModel) *pbuser.SearchNotificationAccountResp { +func (s *userServer) userModelToResp(users []*relation.UserModel, pagination pagination.Pagination) *pbuser.SearchNotificationAccountResp { accounts := make([]*pbuser.NotificationAccountInfo, 0) var total int64 for _, v := range users { @@ -568,5 +669,8 @@ func (s *userServer) userModelToResp(users []*relation.UserModel) *pbuser.Search total += 1 } } - return &pbuser.SearchNotificationAccountResp{Total: total, NotificationAccounts: accounts} + + notificationAccounts := utils.Paginate(accounts, int(pagination.GetPageNumber()), int(pagination.GetShowNumber())) + + return &pbuser.SearchNotificationAccountResp{Total: total, NotificationAccounts: notificationAccounts} } diff --git a/pkg/callbackstruct/constant.go b/pkg/callbackstruct/constant.go index cda98af16..f3bcf1383 100644 --- a/pkg/callbackstruct/constant.go +++ b/pkg/callbackstruct/constant.go @@ -41,7 +41,7 @@ const ( CallbackBeforeUpdateUserInfoExCommand = "callbackBeforeUpdateUserInfoExCommand" CallbackBeforeUserRegisterCommand = "callbackBeforeUserRegisterCommand" CallbackAfterUserRegisterCommand = "callbackAfterUserRegisterCommand" - CallbackTransferGroupOwnerAfter = "callbackTransferGroupOwnerAfter" + CallbackAfterTransferGroupOwner = "callbackAfterTransferGroupOwner" CallbackBeforeSetFriendRemark = "callbackBeforeSetFriendRemark" CallbackAfterSetFriendRemark = "callbackAfterSetFriendRemark" CallbackSingleMsgRead = "callbackSingleMsgRead" diff --git a/pkg/common/config/config.go b/pkg/common/config/config.go index 88e87e709..9696e9367 100644 --- a/pkg/common/config/config.go +++ b/pkg/common/config/config.go @@ -296,7 +296,7 @@ type configStruct struct { CallbackKillGroupMember CallBackConfig `yaml:"killGroupMember"` CallbackDismissGroup CallBackConfig `yaml:"dismissGroup"` CallbackBeforeJoinGroup CallBackConfig `yaml:"joinGroup"` - CallbackTransferGroupOwnerAfter CallBackConfig `yaml:"transferGroupOwner"` + CallbackAfterTransferGroupOwner CallBackConfig `yaml:"transferGroupOwner"` CallbackBeforeInviteUserToGroup CallBackConfig `yaml:"beforeInviteUserToGroup"` CallbackAfterJoinGroup CallBackConfig `yaml:"joinGroupAfter"` CallbackAfterSetGroupInfo CallBackConfig `yaml:"setGroupInfoAfter"` diff --git a/pkg/common/db/cache/friend.go b/pkg/common/db/cache/friend.go index 1708f7664..a2b60d48f 100644 --- a/pkg/common/db/cache/friend.go +++ b/pkg/common/db/cache/friend.go @@ -44,6 +44,8 @@ type FriendCache interface { GetFriend(ctx context.Context, ownerUserID, friendUserID string) (friend *relationtb.FriendModel, err error) // Delete friend when friend info changed DelFriend(ownerUserID, friendUserID string) FriendCache + // Delete friends when friends' info changed + DelFriends(ownerUserID string, friendUserIDs []string) FriendCache } // FriendCacheRedis is an implementation of the FriendCache interface using Redis. @@ -152,3 +154,15 @@ func (f *FriendCacheRedis) DelFriend(ownerUserID, friendUserID string) FriendCac return newFriendCache } + +// DelFriends deletes multiple friend infos from the cache. +func (f *FriendCacheRedis) DelFriends(ownerUserID string, friendUserIDs []string) FriendCache { + newFriendCache := f.NewCache() + + for _, friendUserID := range friendUserIDs { + key := f.getFriendKey(ownerUserID, friendUserID) + newFriendCache.AddKeys(key) // Assuming AddKeys marks the keys for deletion + } + + return newFriendCache +} diff --git a/pkg/common/db/controller/conversation.go b/pkg/common/db/controller/conversation.go index 2a0cb63e4..c6629e9c8 100644 --- a/pkg/common/db/controller/conversation.go +++ b/pkg/common/db/controller/conversation.go @@ -279,7 +279,7 @@ func (c *conversationDatabase) CreateGroupChatConversation(ctx context.Context, for _, v := range existConversationUserIDs { cache = cache.DelConversations(v, conversationID) } - return c.cache.ExecDel(ctx) + return cache.ExecDel(ctx) }) } diff --git a/pkg/common/db/controller/friend.go b/pkg/common/db/controller/friend.go index 924a179ba..3b98f5d7b 100644 --- a/pkg/common/db/controller/friend.go +++ b/pkg/common/db/controller/friend.go @@ -74,15 +74,8 @@ type FriendDatabase interface { // FindBothFriendRequests finds friend requests sent and received FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*relation.FriendRequestModel, err error) - // UpdateFriendPinStatus updates the pinned status of a friend - UpdateFriendPinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error) - - // UpdateFriendRemark updates the remark for a friend - UpdateFriendRemark(ctx context.Context, ownerUserID string, friendUserID string, remark string) (err error) - - // UpdateFriendEx updates the 'ex' field for a friend - UpdateFriendEx(ctx context.Context, ownerUserID string, friendUserID string, ex string) (err error) - + // UpdateFriends updates fields for friends + UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (err error) } type friendDatabase struct { @@ -323,21 +316,12 @@ func (f *friendDatabase) FindFriendUserIDs(ctx context.Context, ownerUserID stri func (f *friendDatabase) FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*relation.FriendRequestModel, err error) { return f.friendRequest.FindBothFriendRequests(ctx, fromUserID, toUserID) } -func (f *friendDatabase) UpdateFriendPinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error) { - if err := f.friend.UpdatePinStatus(ctx, ownerUserID, friendUserID, isPinned); err != nil { +func (f *friendDatabase) UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (err error) { + if len(val) == 0 { + return nil + } + if err := f.friend.UpdateFriends(ctx, ownerUserID, friendUserIDs, val); err != nil { return err } - return f.cache.DelFriend(ownerUserID, friendUserID).ExecDel(ctx) -} -func (f *friendDatabase) UpdateFriendRemark(ctx context.Context, ownerUserID string, friendUserID string, remark string) (err error) { - if err := f.friend.UpdateFriendRemark(ctx, ownerUserID, friendUserID, remark); err != nil { - return err - } - return f.cache.DelFriend(ownerUserID, friendUserID).ExecDel(ctx) -} -func (f *friendDatabase) UpdateFriendEx(ctx context.Context, ownerUserID string, friendUserID string, ex string) (err error) { - if err := f.friend.UpdateFriendEx(ctx, ownerUserID, friendUserID, ex); err != nil { - return err - } - return f.cache.DelFriend(ownerUserID, friendUserID).ExecDel(ctx) + return f.cache.DelFriends(ownerUserID, friendUserIDs).ExecDel(ctx) } diff --git a/pkg/common/db/controller/user.go b/pkg/common/db/controller/user.go index a109b81ef..cedae5c97 100644 --- a/pkg/common/db/controller/user.go +++ b/pkg/common/db/controller/user.go @@ -50,6 +50,8 @@ type UserDatabase interface { UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error) // Page If not found, no error is returned Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) + // FindUser + PageFindUser(ctx context.Context, level int64, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) // IsExist true as long as one exists IsExist(ctx context.Context, userIDs []string) (exist bool, err error) // GetAllUserID Get all user IDs @@ -76,10 +78,11 @@ type UserDatabase interface { SetUserStatus(ctx context.Context, userID string, status, platformID int32) error //CRUD user command - AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string) error + AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error - UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string) error + UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error GetUserCommands(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error) + GetAllUserCommands(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error) } type userDatabase struct { @@ -182,6 +185,10 @@ func (u *userDatabase) Page(ctx context.Context, pagination pagination.Paginatio return u.userDB.Page(ctx, pagination) } +func (u *userDatabase) PageFindUser(ctx context.Context, level int64, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) { + return u.userDB.PageFindUser(ctx, level, pagination) +} + // IsExist Does userIDs exist? As long as there is one, it will be true. func (u *userDatabase) IsExist(ctx context.Context, userIDs []string) (exist bool, err error) { users, err := u.userDB.Find(ctx, userIDs) @@ -253,16 +260,20 @@ func (u *userDatabase) GetUserStatus(ctx context.Context, userIDs []string) ([]* func (u *userDatabase) SetUserStatus(ctx context.Context, userID string, status, platformID int32) error { return u.cache.SetUserStatus(ctx, userID, status, platformID) } -func (u *userDatabase) AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string) error { - return u.userDB.AddUserCommand(ctx, userID, Type, UUID, value) +func (u *userDatabase) AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error { + return u.userDB.AddUserCommand(ctx, userID, Type, UUID, value, ex) } func (u *userDatabase) DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error { return u.userDB.DeleteUserCommand(ctx, userID, Type, UUID) } -func (u *userDatabase) UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string) error { - return u.userDB.UpdateUserCommand(ctx, userID, Type, UUID, value) +func (u *userDatabase) UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error { + return u.userDB.UpdateUserCommand(ctx, userID, Type, UUID, val) } func (u *userDatabase) GetUserCommands(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error) { commands, err := u.userDB.GetUserCommand(ctx, userID, Type) return commands, err } +func (u *userDatabase) GetAllUserCommands(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error) { + commands, err := u.userDB.GetAllUserCommand(ctx, userID) + return commands, err +} diff --git a/pkg/common/db/mgo/friend.go b/pkg/common/db/mgo/friend.go index 72289181b..b4172d0fb 100644 --- a/pkg/common/db/mgo/friend.go +++ b/pkg/common/db/mgo/friend.go @@ -16,7 +16,6 @@ package mgo import ( "context" - "github.com/OpenIMSDK/tools/errs" "github.com/OpenIMSDK/tools/mgoutil" "github.com/OpenIMSDK/tools/pagination" "go.mongodb.org/mongo-driver/mongo/options" @@ -144,49 +143,22 @@ func (f *FriendMgo) FindFriendUserIDs(ctx context.Context, ownerUserID string) ( return mgoutil.Find[string](ctx, f.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "friend_user_id": 1})) } -// UpdatePinStatus update friend's pin status -func (f *FriendMgo) UpdatePinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error) { - - filter := bson.M{"owner_user_id": ownerUserID, "friend_user_id": friendUserID} - // Create an update operation to set the "is_pinned" field to isPinned for all documents. - update := bson.M{"$set": bson.M{"is_pinned": isPinned}} - - // Perform the update operation for all documents in the collection. - _, err = f.coll.UpdateMany(ctx, filter, update) - - if err != nil { - return errs.Wrap(err, "update pin error") +func (f *FriendMgo) UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) error { + // Ensure there are IDs to update + if len(friendUserIDs) == 0 { + return nil // Or return an error if you expect there to always be IDs } - return nil -} -func (f *FriendMgo) UpdateFriendRemark(ctx context.Context, ownerUserID string, friendUserID string, remark string) (err error) { - - filter := bson.M{"owner_user_id": ownerUserID, "friend_user_id": friendUserID} - // Create an update operation to set the "is_pinned" field to isPinned for all documents. - update := bson.M{"$set": bson.M{"remark": remark}} - - // Perform the update operation for all documents in the collection. - _, err = f.coll.UpdateMany(ctx, filter, update) - - if err != nil { - return errs.Wrap(err, "update remark error") + // Create a filter to match documents with the specified ownerUserID and any of the friendUserIDs + filter := bson.M{ + "owner_user_id": ownerUserID, + "friend_user_id": bson.M{"$in": friendUserIDs}, } - return nil -} -func (f *FriendMgo) UpdateFriendEx(ctx context.Context, ownerUserID string, friendUserID string, ex string) (err error) { - - filter := bson.M{"owner_user_id": ownerUserID, "friend_user_id": friendUserID} - // Create an update operation to set the "is_pinned" field to isPinned for all documents. - update := bson.M{"$set": bson.M{"ex": ex}} - - // Perform the update operation for all documents in the collection. - _, err = f.coll.UpdateMany(ctx, filter, update) - - if err != nil { - return errs.Wrap(err, "update ex error") - } - - return nil + // Create an update document + update := bson.M{"$set": val} + + // Perform the update operation for all matching documents + _, err := mgoutil.UpdateMany(ctx, f.coll, filter, update) + return err } diff --git a/pkg/common/db/mgo/user.go b/pkg/common/db/mgo/user.go index 27ca264dd..b82966371 100644 --- a/pkg/common/db/mgo/user.go +++ b/pkg/common/db/mgo/user.go @@ -17,6 +17,7 @@ package mgo import ( "context" "github.com/OpenIMSDK/protocol/user" + "github.com/OpenIMSDK/tools/errs" "time" "github.com/OpenIMSDK/tools/mgoutil" @@ -77,6 +78,10 @@ func (u *UserMgo) Page(ctx context.Context, pagination pagination.Pagination) (c return mgoutil.FindPage[*relation.UserModel](ctx, u.coll, bson.M{}, pagination) } +func (u *UserMgo) PageFindUser(ctx context.Context, level int64, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) { + return mgoutil.FindPage[*relation.UserModel](ctx, u.coll, bson.M{"app_manger_level": level}, pagination) +} + func (u *UserMgo) GetAllUserID(ctx context.Context, pagination pagination.Pagination) (int64, []string, error) { return mgoutil.FindPage[string](ctx, u.coll, bson.M{}, pagination, options.Find().SetProjection(bson.M{"user_id": 1})) } @@ -96,7 +101,7 @@ func (u *UserMgo) CountTotal(ctx context.Context, before *time.Time) (count int6 return mgoutil.Count(ctx, u.coll, bson.M{"create_time": bson.M{"$lt": before}}) } -func (u *UserMgo) AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string) error { +func (u *UserMgo) AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error { collection := u.coll.Database().Collection("userCommands") // Create a new document instead of updating an existing one @@ -106,28 +111,48 @@ func (u *UserMgo) AddUserCommand(ctx context.Context, userID string, Type int32, "uuid": UUID, "createTime": time.Now().Unix(), // assuming you want the creation time in Unix timestamp "value": value, + "ex": ex, } _, err := collection.InsertOne(ctx, doc) return err } + func (u *UserMgo) DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error { collection := u.coll.Database().Collection("userCommands") filter := bson.M{"userID": userID, "type": Type, "uuid": UUID} - _, err := collection.DeleteOne(ctx, filter) + result, err := collection.DeleteOne(ctx, filter) + if result.DeletedCount == 0 { + // No records found to update + return errs.Wrap(errs.ErrRecordNotFound) + } return err } -func (u *UserMgo) UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string) error { +func (u *UserMgo) UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error { + if len(val) == 0 { + return nil + } + collection := u.coll.Database().Collection("userCommands") filter := bson.M{"userID": userID, "type": Type, "uuid": UUID} - update := bson.M{"$set": bson.M{"value": value}} + update := bson.M{"$set": val} - _, err := collection.UpdateOne(ctx, filter, update) - return err + result, err := collection.UpdateOne(ctx, filter, update) + if err != nil { + return err + } + + if result.MatchedCount == 0 { + // No records found to update + return errs.Wrap(errs.ErrRecordNotFound) + } + + return nil } + func (u *UserMgo) GetUserCommand(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error) { collection := u.coll.Database().Collection("userCommands") filter := bson.M{"userID": userID, "type": Type} @@ -143,19 +168,23 @@ func (u *UserMgo) GetUserCommand(ctx context.Context, userID string, Type int32) for cursor.Next(ctx) { var document struct { + Type int32 `bson:"type"` UUID string `bson:"uuid"` Value string `bson:"value"` CreateTime int64 `bson:"createTime"` + Ex string `bson:"ex"` } if err := cursor.Decode(&document); err != nil { return nil, err } - commandInfo := &user.CommandInfoResp{ // Change here: use a pointer to the struct + commandInfo := &user.CommandInfoResp{ + Type: document.Type, Uuid: document.UUID, Value: document.Value, CreateTime: document.CreateTime, + Ex: document.Ex, } commands = append(commands, commandInfo) @@ -167,7 +196,48 @@ func (u *UserMgo) GetUserCommand(ctx context.Context, userID string, Type int32) return commands, nil } +func (u *UserMgo) GetAllUserCommand(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error) { + collection := u.coll.Database().Collection("userCommands") + filter := bson.M{"userID": userID} + cursor, err := collection.Find(ctx, filter) + if err != nil { + return nil, err + } + defer cursor.Close(ctx) + + // Initialize commands as a slice of pointers + commands := []*user.AllCommandInfoResp{} + + for cursor.Next(ctx) { + var document struct { + Type int32 `bson:"type"` + UUID string `bson:"uuid"` + Value string `bson:"value"` + CreateTime int64 `bson:"createTime"` + Ex string `bson:"ex"` + } + + if err := cursor.Decode(&document); err != nil { + return nil, err + } + + commandInfo := &user.AllCommandInfoResp{ + Type: document.Type, + Uuid: document.UUID, + Value: document.Value, + CreateTime: document.CreateTime, + Ex: document.Ex, + } + + commands = append(commands, commandInfo) + } + + if err := cursor.Err(); err != nil { + return nil, err + } + return commands, nil +} func (u *UserMgo) CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) { pipeline := bson.A{ bson.M{ diff --git a/pkg/common/db/table/relation/friend.go b/pkg/common/db/table/relation/friend.go index cc337701d..73f7454df 100644 --- a/pkg/common/db/table/relation/friend.go +++ b/pkg/common/db/table/relation/friend.go @@ -57,10 +57,6 @@ type FriendModelInterface interface { FindInWhoseFriends(ctx context.Context, friendUserID string, pagination pagination.Pagination) (total int64, friends []*FriendModel, err error) // FindFriendUserIDs retrieves a list of friend user IDs for a given owner. FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error) - // UpdatePinStatus update friend's pin status - UpdatePinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error) - // UpdateFriendRemark update friend's remark - UpdateFriendRemark(ctx context.Context, ownerUserID string, friendUserID string, remark string) (err error) - // UpdateFriendEx update friend's ex - UpdateFriendEx(ctx context.Context, ownerUserID string, friendUserID string, ex string) (err error) + // UpdateFriends update friends' fields + UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (err error) } diff --git a/pkg/common/db/table/relation/user.go b/pkg/common/db/table/relation/user.go index fc116adc2..a1b4269d1 100644 --- a/pkg/common/db/table/relation/user.go +++ b/pkg/common/db/table/relation/user.go @@ -56,6 +56,7 @@ type UserModelInterface interface { TakeNotification(ctx context.Context, level int64) (user []*UserModel, err error) TakeByNickname(ctx context.Context, nickname string) (user []*UserModel, err error) Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*UserModel, err error) + PageFindUser(ctx context.Context, level int64, pagination pagination.Pagination) (count int64, users []*UserModel, err error) Exist(ctx context.Context, userID string) (exist bool, err error) GetAllUserID(ctx context.Context, pagination pagination.Pagination) (count int64, userIDs []string, err error) GetUserGlobalRecvMsgOpt(ctx context.Context, userID string) (opt int, err error) @@ -64,8 +65,9 @@ type UserModelInterface interface { // 获取范围内用户增量 CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) //CRUD user command - AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string) error + AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error - UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string) error + UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error GetUserCommand(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error) + GetAllUserCommand(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error) } diff --git a/pkg/rpcclient/notification/friend.go b/pkg/rpcclient/notification/friend.go index 00759b1b2..b98a1d38e 100644 --- a/pkg/rpcclient/notification/friend.go +++ b/pkg/rpcclient/notification/friend.go @@ -197,7 +197,7 @@ func (f *FriendNotificationSender) FriendRemarkSetNotification(ctx context.Conte return f.Notification(ctx, fromUserID, toUserID, constant.FriendRemarkSetNotification, &tips) } func (f *FriendNotificationSender) FriendsInfoUpdateNotification(ctx context.Context, toUserID string, friendIDs []string) error { - tips := sdkws.FriendsInfoUpdateTips{} + tips := sdkws.FriendsInfoUpdateTips{FromToUserID: &sdkws.FromToUserID{}} tips.FromToUserID.ToUserID = toUserID tips.FriendIDs = friendIDs return f.Notification(ctx, toUserID, toUserID, constant.FriendsInfoUpdateNotification, &tips) diff --git a/pkg/rpcclient/notification/user.go b/pkg/rpcclient/notification/user.go index 4feebf7b9..4347faece 100644 --- a/pkg/rpcclient/notification/user.go +++ b/pkg/rpcclient/notification/user.go @@ -103,3 +103,21 @@ func (u *UserNotificationSender) UserStatusChangeNotification( ) error { return u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserStatusChangeNotification, tips) } +func (u *UserNotificationSender) UserCommandUpdateNotification( + ctx context.Context, + tips *sdkws.UserCommandUpdateTips, +) error { + return u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserCommandUpdateNotification, tips) +} +func (u *UserNotificationSender) UserCommandAddNotification( + ctx context.Context, + tips *sdkws.UserCommandAddTips, +) error { + return u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserCommandAddNotification, tips) +} +func (u *UserNotificationSender) UserCommandDeleteNotification( + ctx context.Context, + tips *sdkws.UserCommandDeleteTips, +) error { + return u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserCommandDeleteNotification, tips) +}