AndrewZuo01 2 years ago
commit 0e20553d81

@ -33,7 +33,7 @@ env:
OPEN_IM_SERVER_CLA_DOCUMENT: https://github.com/openim-sigs/cla/blob/main/README.md OPEN_IM_SERVER_CLA_DOCUMENT: https://github.com/openim-sigs/cla/blob/main/README.md
OPEN_IM_SERVER_SIGNATURES_PATH: signatures/${{ github.event.repository.name }}/cla.json OPEN_IM_SERVER_SIGNATURES_PATH: signatures/${{ github.event.repository.name }}/cla.json
OPEN_IM_SERVER_ALLOWLIST: kubbot,bot*,bot-*,bot/*,bot-/*,bot,*[bot] OPEN_IM_SERVER_ALLOWLIST: kubbot,openimbot,bot*,dependabot,sweep-ai,*bot,bot-*,bot/*,bot-/*,bot,*[bot]
jobs: jobs:
CLAAssistant: CLAAssistant:

@ -106,6 +106,16 @@ jobs:
ghcr.io/openimsdk/openim-api ghcr.io/openimsdk/openim-api
openim/openim-api openim/openim-api
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-api registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-api
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-api - name: Build and push Docker image for openim-api
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -127,6 +137,16 @@ jobs:
ghcr.io/openimsdk/openim-cmdutils ghcr.io/openimsdk/openim-cmdutils
openim/openim-cmdutils openim/openim-cmdutils
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-cmdutils registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-cmdutils
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-cmdutils - name: Build and push Docker image for openim-cmdutils
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -148,6 +168,16 @@ jobs:
ghcr.io/openimsdk/openim-crontask ghcr.io/openimsdk/openim-crontask
openim/openim-crontask openim/openim-crontask
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-crontask registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-crontask
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-crontask - name: Build and push Docker image for openim-crontask
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -169,6 +199,16 @@ jobs:
ghcr.io/openimsdk/openim-msggateway ghcr.io/openimsdk/openim-msggateway
openim/openim-msggateway openim/openim-msggateway
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-msggateway registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-msggateway
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-msggateway - name: Build and push Docker image for openim-msggateway
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -190,6 +230,16 @@ jobs:
ghcr.io/openimsdk/openim-msgtransfer ghcr.io/openimsdk/openim-msgtransfer
openim/openim-msgtransfer openim/openim-msgtransfer
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-msgtransfer registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-msgtransfer
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-msgtransfer - name: Build and push Docker image for openim-msgtransfer
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -211,6 +261,16 @@ jobs:
ghcr.io/openimsdk/openim-push ghcr.io/openimsdk/openim-push
openim/openim-push openim/openim-push
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-push registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-push
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-push - name: Build and push Docker image for openim-push
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -232,6 +292,16 @@ jobs:
ghcr.io/openimsdk/openim-rpc-auth ghcr.io/openimsdk/openim-rpc-auth
openim/openim-rpc-auth openim/openim-rpc-auth
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-auth registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-auth
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-rpc-auth - name: Build and push Docker image for openim-rpc-auth
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -253,6 +323,16 @@ jobs:
ghcr.io/openimsdk/openim-rpc-conversation ghcr.io/openimsdk/openim-rpc-conversation
openim/openim-rpc-conversation openim/openim-rpc-conversation
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-conversation registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-conversation
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-rpc-conversation - name: Build and push Docker image for openim-rpc-conversation
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -274,6 +354,16 @@ jobs:
ghcr.io/openimsdk/openim-rpc-friend ghcr.io/openimsdk/openim-rpc-friend
openim/openim-rpc-friend openim/openim-rpc-friend
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-friend registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-friend
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-rpc-friend - name: Build and push Docker image for openim-rpc-friend
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -295,6 +385,16 @@ jobs:
ghcr.io/openimsdk/openim-rpc-group ghcr.io/openimsdk/openim-rpc-group
openim/openim-rpc-group openim/openim-rpc-group
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-group registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-group
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-rpc-group - name: Build and push Docker image for openim-rpc-group
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -316,6 +416,16 @@ jobs:
ghcr.io/openimsdk/openim-rpc-msg ghcr.io/openimsdk/openim-rpc-msg
openim/openim-rpc-msg openim/openim-rpc-msg
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-msg registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-msg
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-rpc-msg - name: Build and push Docker image for openim-rpc-msg
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -337,6 +447,16 @@ jobs:
ghcr.io/openimsdk/openim-rpc-third ghcr.io/openimsdk/openim-rpc-third
openim/openim-rpc-third openim/openim-rpc-third
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-third registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-third
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-rpc-third - name: Build and push Docker image for openim-rpc-third
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5
@ -358,6 +478,16 @@ jobs:
ghcr.io/openimsdk/openim-rpc-user ghcr.io/openimsdk/openim-rpc-user
openim/openim-rpc-user openim/openim-rpc-user
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-user registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-rpc-user
tags: |
type=ref,event=tag
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Build and push Docker image for openim-rpc-user - name: Build and push Docker image for openim-rpc-user
uses: docker/build-push-action@v5 uses: docker/build-push-action@v5

@ -92,11 +92,40 @@ jobs:
- name: Exec OpenIM API test - name: Exec OpenIM API test
run: | run: |
sudo make test-api touch /tmp/test.md
echo "# OpenIM Test" >> /tmp/test.md
- name: Exec OpenIM E2E test echo "## OpenIM API Test" >> /tmp/test.md
echo "<details><summary>Command Output for OpenIM API Test</summary>" >> /tmp/test.md
echo "<pre><code>" >> /tmp/test.md
sudo make test-api | tee -a /tmp/test.md
echo "</code></pre>" >> /tmp/test.md
echo "</details>" >> /tmp/test.md
- name: Exec OpenIM E2E Test
run: | run: |
sudo make test-e2e echo "" >> /tmp/test.md
echo "## OpenIM E2E Test" >> /tmp/test.md
echo "<details><summary>Command Output for OpenIM E2E Test</summary>" >> /tmp/test.md
echo "<pre><code>" >> /tmp/test.md
sudo make test-e2e | tee -a /tmp/test.md
echo "</code></pre>" >> /tmp/test.md
echo "</details>" >> /tmp/test.md
- name: Comment PR with file
uses: thollander/actions-comment-pull-request@v2
with:
filePath: /tmp/test.md
comment_tag: nrt_file
reactions: eyes, rocket
mode: recreate
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
continue-on-error: true
- name: Check outputs
run: |
echo "id : ${{ steps.nrt_message.outputs.id }}"
echo "body : ${{ steps.nrt_message.outputs.body }}"
echo "html_url : ${{ steps.nrt_message.outputs.html_url }}"
- name: Exec OpenIM System uninstall - name: Exec OpenIM System uninstall
run: | run: |

@ -36,21 +36,19 @@ env:
GO_VERSION: "1.19" GO_VERSION: "1.19"
GOLANGCI_VERSION: "v1.50.1" GOLANGCI_VERSION: "v1.50.1"
jobs: jobs:
openim: openim:
name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }} name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions: permissions:
contents: write contents: write
pull-requests: write
environment: environment:
name: openim name: openim
strategy: strategy:
matrix: matrix:
go_version: ["1.19","1.20","1.21"] go_version: ["1.19","1.20","1.21"]
os: [ubuntu-latest] os: [ubuntu-latest]
steps: steps:
- name: Setup - name: Setup
uses: actions/checkout@v4 uses: actions/checkout@v4
@ -111,6 +109,9 @@ jobs:
openim-start: openim-start:
name: Test OpenIM install/start on ${{ matrix.os }} name: Test OpenIM install/start on ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions:
contents: write
pull-requests: write
environment: environment:
name: openim name: openim
strategy: strategy:
@ -129,9 +130,46 @@ jobs:
run: | run: |
sudo make install sudo make install
# - name: Check the OpenIM environment and status
# run: |
# sudo docker images
# sudo docker ps
- name: Check the OpenIM environment and status
id: docker_info
run: |
sleep 30
echo "images<<EOF" >> $GITHUB_ENV
sudo docker images >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
echo "containers<<EOF" >> $GITHUB_ENV
sudo docker ps >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
- name: Comment PR
uses: thollander/actions-comment-pull-request@v2
with:
message: |
> [!TIP]
> Run make install to check the status
### Docker Images:
```
${{ env.images }}
```
### Docker Processes:
```
${{ env.containers }}
```
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
continue-on-error: true
execute-scripts: execute-scripts:
name: Execute OpenIM Script On ${{ matrix.os }} name: Execute OpenIM Script On ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
permissions:
contents: write
pull-requests: write
environment: environment:
name: openim name: openim
strategy: strategy:
@ -201,10 +239,9 @@ jobs:
- name: Build, Start, Check Services and Print Logs for Ubuntu - name: Build, Start, Check Services and Print Logs for Ubuntu
if: runner.os == 'Linux' if: runner.os == 'Linux'
run: | run: |
sudo make build && \ sudo make build
sudo make start && \ sudo make start
sudo make check || \ sudo make check
(echo "An error occurred, printing logs:" && sudo cat ./_output/logs/* 2>/dev/null)
- name: Restart Services and Print Logs for Ubuntu - name: Restart Services and Print Logs for Ubuntu
if: runner.os == 'Linux' if: runner.os == 'Linux'
@ -212,18 +249,17 @@ jobs:
sudo make restart sudo make restart
sudo make check sudo make check
# - name: Build, Start, Check Services and Print Logs for macOS - name: Build, Start, Check Services and Print Logs for macOS
# if: runner.os == 'macOS' if: runner.os == 'macOS'
# run: | run: |
# make init && \ make build
# make build && \
# make start && \
# make check || \
# (echo "An error occurred, printing logs:" && sudo cat ./_output/logs/* 2>/dev/null)
openim-test-build-image: openim-test-build-image:
name: Build OpenIM Docker Image name: Build OpenIM Docker Image
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
environment: environment:
name: openim name: openim
steps: steps:
@ -245,3 +281,9 @@ jobs:
run: | run: |
sudo make init sudo make init
sudo make image sudo make image
- name: Get OpenIM Docker Images Status
id: docker_processes
run: |
sudo docker images
sudo docker ps

@ -35,52 +35,62 @@
## Ⓜ️ 关于 OpenIM ## Ⓜ️ 关于 OpenIM
OpenIM 不仅仅是一个开源的即时消息组件,它是你的应用程序生态系统的一个不可或缺的部分。查看下面的图表,了解 AppServer、AppClient、OpenIMServer 和 OpenIMSDK 是如何交互的。 OpenIM 是一个专门设计用于在应用程序中集成聊天、音视频通话、通知以及AI聊天机器人等通信功能的服务平台。它通过提供一系列强大的API和Webhooks使开发者可以轻松地在他们的应用中加入这些交互特性。OpenIM 本身并不是一个独立运行的聊天应用,而是作为一个平台,为其他应用提供支持,实现丰富的通信功能。下图展示 AppServer、AppClient、OpenIMServer 和 OpenIMSDK 之间的交互关系来具体说明。
![App-OpenIM 关系](./docs/images/oepnim-design.png) ![App-OpenIM 关系](./docs/images/oepnim-design.png)
## 🚀 关于 OpenIMSDK ## 🚀 关于 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)** 👉 **[探索 GO SDK](https://github.com/openimsdk/openim-sdk-core)**
## 🌐 关于 OpenIMServer ## 🌐 关于 OpenIMServer
精心用 Golang 开发的 **OpenIMServer** 通过多重方式确保了卓越的即时消息服务器能力: + **OpenIMServer** 具有以下特点:
- 🌐 微服务架构:支持集群模式,包括网关(gateway)和多个rpc服务。
+ **模块组成**:它由多个模块组成,例如网关和多个 RPC 服务,提供一个多功能的消息环境。 - 🚀 部署方式多样支持源代码、kubernetes或docker部署。
+ **微服务架构**:支持集群模式,确保出色的性能和可伸缩性,以有效管理各个实例间的通信。 - 海量用户支持:十万超级大群,千万用户,及百亿消息
+ **多样的部署选项**适应你的操作偏好通过源代码、Kubernetes 或 Docker 提供部署选项。
### 增强的业务功能: ### 增强的业务功能:
+ **REST API**OpenIMServer 为业务系统提供 REST API旨在通过后端接口为您的操作提供附加功能如群组创建和消息推送 + **REST API**OpenIMServer 提供了REST API供业务系统使用旨在赋予业务更多功能例如通过后台接口建立群组、发送推送消息等
+ **回调**为了扩展其在各种业务形式中的实用性OpenIMServer 提供了回调能力。即,在事件发生之前或之后,它向业务服务器发送请求,比如发送消息,丰富通信过程中的交互和数据交换流 + **Webhooks**OpenIMServer提供了回调能力以扩展更多的业务形态所谓回调即OpenIMServer会在某一事件发生之前或者之后向业务服务器发送请求如发送消息之前或之后的回调
👉 **[了解更多](https://doc.rentsoft.cn/guides/introduction/product)** 👉 **[了解更多](https://docs.openim.io/guides/introduction/product)**
## :rocket: 快速开始 ## :rocket: 快速开始
你只需要一个简单的命令,就可以快速学习 OpenIM 的工程解决方案 在线体验iOS/Android/H5/PC/Web
``` 👉 **[OpenIM online demo](https://www.openim.io/zh/commercial)**
bashCopy code
$ make demo
```
🤲 为了方便用户体验,我们提供了多种部署解决方案,您可以根据下面的列表选择自己的部署方法: 🤲 为了方便用户体验,我们提供了多种部署解决方案,您可以根据下面的列表选择自己的部署方法:
+ **[源代码部署指南](https://doc.rentsoft.cn/guides/gettingStarted/imSourceCodeDeployment)** + **[源代码部署指南](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Docker 部署指南](https://doc.rentsoft.cn/guides/gettingStarted/dockerCompose)** + **[Docker 部署指南](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Kubernetes 部署指南](https://github.com/openimsdk/open-im-server/tree/main/deployments)** + **[Kubernetes 部署指南](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
## :hammer_and_wrench: 开始开发 OpenIM ## :hammer_and_wrench: 开始开发 OpenIM

@ -25,178 +25,56 @@
</p> </p>
## Ⓜ️ About OpenIM ## Ⓜ️ 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) ![App-OpenIM Relationship](./docs/images/oepnim-design.png)
## 🚀 About OpenIMSDK ## 🚀 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.
It's crafted in Golang and supports cross-platform deployment, ensuring a coherent messaging 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:
+ **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 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)**
<!--
## :star2: Why OpenIM
**🔍 Function screenshot display**
<div align="center">
| 💻🔄📱 Multi Terminal Synchronization 🔄🖥️ | 📅⚡ Efficient Meetings 🚀💼 |
| :----------------------------------------------------------: | :---------------------------------------------------------: |
| ![multiple-message](./assets/demo/multi-terminal-synchronization.png) | ![efficient-meetings](./assets/demo/efficient-meetings.png) |
| 📲🔄 **One-to-one and Group Chats** 👥🗣️ | 🎁💻 **Special Features - Custom Messages** ✉️🎨|
| ![group-chat](./assets/demo/group-chat.png) | ![special-function](./assets/demo/special-function.png) |
</div> + 🌟 Main Features:
**OpenIM** offers a powerful and reliable instant messaging platform, ensuring versatile communication across multiple platforms with the following key features: - 📦 Local storage
- 🔔 Listener callbacks
- 🛡️ API wrapping
- 🌐 Connection management
**Versatile Messaging:** Support for text, images, emojis, voice, video, and more, alongside one-on-one and multi-person audio/video calls. ## 📚 Main Modules:
**Robust Chat Capabilities:** Including roles (application administrator, group owner, etc.) and features like muting, group announcements, and dynamic message loading. 1. 🚀 Initialization and Login
2. 👤 User Management
3. 👫 Friend Management
4. 🤖 Group Functions
5. 💬 Conversation Handling
**Unique Interaction Features:** Offering read-and-burn private chats and a message editing function to broaden social scenarios. It is built using Golang and supports cross-platform deployment, ensuring a consistent access experience across all platforms.
**Open Source:** The code of OpenIM is open source and aims to build a leading global IM open source community. [GitHub Repository](https://github.com/OpenIMSDK) 👉 **[Explore GO SDK](https://github.com/openimsdk/openim-sdk-core)**
**Extensibility:** Implemented in Golang, OpenIM introduces an "everything is a message" communication model, simplifying custom messages and feature extension. ## 🌐 About OpenIMServer
**High Performance:** Supports a hierarchical governance architecture tested and abstracts the storage model of various message types. + **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.
**Full Platform Support:** Native support for iOS, Android, Flutter, uni-app, ReactNative, Electron, and Web. ### Enhanced Business Functionality:
--> + **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 ## :rocket: Quick Start
We support many platforms. Here are the addresses for quick experience on the web side 🤲 To facilitate user experience, we offer various deployment solutions. You can choose your deployment method from the list below:
👉 **[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:
<!--
<details> <summary>Deploying with Docker Compose</summary>
It is recommended to use Docker Compose for deployment, which can easily and quickly deploy the entire OpenIM service on a single node
+ [https://github.com/openimsdk/openim-docker](https://github.com/openimsdk/openim-docker)
> **Note**
>
> If you don't know OpenIM's versioning policy, 📚Read our release policy: https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/version.md
</details>
<details> <summary>Compile from Source</summary>
Ur need `Go 1.20` or higher version, and `make`.
```bash
go version && make --version || echo "Error: One of the commands failed."
```
Version Details: https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/version.md
You can get the version number from the command below or from [github releases](https://github.com/openimsdk/open-im-server/tags).
```bash
$ curl --silent "https://api.github.com/repos/openimsdk/open-im-server/releases" | jq -r '.[].tag_name'
```
We have our own version management policy, if you are interested in our version management, I recommend reading [📚 OpenIM Version](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/version.md), We recommend using stable versions such as `v3.3.0` and `v3.2.0` whenever possible. `v3.1.1-alpha.3` as well as `v3.3.0-beta.0` and `v3.2.0-rc.0` are pre-release or beta versions and are not recommended.
Set `OPENIM_VERSION` environment variables for the latest `OPENIM_VERSION` number, or replace the `OPENIM_VERSION` for you to install the OpenIM-Server `OPENIM_VERSION`:
```bash
$ OPENIM_VERSION=`curl -s https://api.github.com/repos/openimsdk/open-im-server/releases/latest | grep -oE '"tag_name": "[^"]+"' | head -n1 | cut -d'"' -f4`
# OPENIM_VERSION=v3.3.0
```
Deploy basic components at the click of a command:
```bash
# install openim dependency
$ git clone https://github.com/openimsdk/open-im-server openim/openim-server && export openim=$(pwd)/openim/openim-server && cd $openim/openim-server && git checkout $OPENIM_VERSION
$ make init && docker compose up -d && make start && make check
```
> `make help` to help you see the instructions supported by OpenIM.
You can use the `make help-all` see OpenIM in action.
</details>
<details> <summary>Component Configuration Instructions</summary>
Read: Configuration center documenthttps://github.com/openimsdk/open-im-server/blob/main/docs/contrib/environment.md
</details>
<details> <summary>Deployed with kubernetes</summary>
+ https://github.com/openimsdk/open-im-server/blob/main/deployments/README.md
</details>
-->
+ **[Source Code Deployment Guide](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** + **[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)** + **[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)**
<!--
## :link: OpenIM and your application
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.
![App-OpenIM Relationship](./docs/images/oepnim-design.png)
## :building_construction: Overall Architecture
Delve into the heart of Open-IM-Server's functionality with our architecture diagram.
![Overall Architecture](./docs/images/Architecture.jpg) -->
## :hammer_and_wrench: To start developing OpenIM ## :hammer_and_wrench: To start developing OpenIM

@ -1,3 +1,17 @@
# Copyright © 2024 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ----------------------------------------------------------------------------- # -----------------------------------------------------------------------------
# General Configuration # General Configuration
# This section contains general configuration options for the entire environment. # This section contains general configuration options for the entire environment.
@ -84,19 +98,23 @@ OPENIM_IP=${OPENIM_IP}
# Default: ZOOKEEPER_PORT=12181 # Default: ZOOKEEPER_PORT=12181
ZOOKEEPER_PORT=${ZOOKEEPER_PORT} ZOOKEEPER_PORT=${ZOOKEEPER_PORT}
# Port on which MongoDB service is running. # MongoDB service port configuration.
# Default: MONGO_PORT=37017 # Default: MONGO_PORT=37017
# MONGO_PORT=${MONGO_PORT} # MONGO_PORT=${MONGO_PORT}
# Username to authenticate with the MongoDB service. # Password for MongoDB admin user. Used for service authentication.
# Default: MONGO_USERNAME=root
# MONGO_USERNAME=${MONGO_USERNAME}
# Password to authenticate with the MongoDB service.
# Default: MONGO_PASSWORD=openIM123 # Default: MONGO_PASSWORD=openIM123
MONGO_PASSWORD=${MONGO_PASSWORD} MONGO_PASSWORD=${MONGO_PASSWORD}
# Name of the database in MongoDB to be used. # Username for a regular OpenIM user in MongoDB.
# Default: MONGO_OPENIM_USERNAME=openIM
MONGO_OPENIM_USERNAME=${MONGO_OPENIM_USERNAME}
# Password for a regular OpenIM user in MongoDB.
# Default: MONGO_OPENIM_PASSWORD=openIM123456
MONGO_OPENIM_PASSWORD=${MONGO_OPENIM_PASSWORD}
# Specifies the database name to be used within MongoDB.
# Default: MONGO_DATABASE=openIM_v3 # Default: MONGO_DATABASE=openIM_v3
MONGO_DATABASE=${MONGO_DATABASE} MONGO_DATABASE=${MONGO_DATABASE}
@ -187,18 +205,12 @@ CHAT_IMAGE_VERSION=${CHAT_IMAGE_VERSION}
# Port for the OpenIM chat API. # Port for the OpenIM chat API.
# Default: OPENIM_CHAT_API_PORT=10008 # Default: OPENIM_CHAT_API_PORT=10008
# !!! TODO: Do not change the chat port https://github.com/openimsdk/chat/issues/365
OPENIM_CHAT_API_PORT=${OPENIM_CHAT_API_PORT} OPENIM_CHAT_API_PORT=${OPENIM_CHAT_API_PORT}
# Port for the OpenIM admin API. # Port for the OpenIM admin API.
# Default: OPENIM_ADMIN_API_PORT=10009 # Default: OPENIM_ADMIN_API_PORT=10009
# !!! TODO: Do not change the chat port https://github.com/openimsdk/chat/issues/365
OPENIM_ADMIN_API_PORT=${OPENIM_ADMIN_API_PORT} OPENIM_ADMIN_API_PORT=${OPENIM_ADMIN_API_PORT}
# Directory path for storing data files or related information for OpenIM chat.
# Default: OPENIM_CHAT_DATA_DIR=./openim-chat/main
OPENIM_CHAT_DATA_DIR=${OPENIM_CHAT_DATA_DIR}
# ====================================== # ======================================
# ========== OpenIM Admin ============== # ========== OpenIM Admin ==============
# ====================================== # ======================================

@ -53,8 +53,8 @@ mongo:
# Maximum connection pool size # Maximum connection pool size
address: [ ${MONGO_ADDRESS}:${MONGO_PORT} ] address: [ ${MONGO_ADDRESS}:${MONGO_PORT} ]
database: ${MONGO_DATABASE} database: ${MONGO_DATABASE}
username: ${MONGO_USERNAME} username: ${MONGO_OPENIM_USERNAME}
password: ${MONGO_PASSWORD} password: ${MONGO_OPENIM_PASSWORD}
maxPoolSize: ${MONGO_MAX_POOL_SIZE} maxPoolSize: ${MONGO_MAX_POOL_SIZE}
###################### Redis configuration information ###################### ###################### Redis configuration information ######################
@ -320,6 +320,14 @@ callback:
enable: ${CALLBACK_ENABLE} enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT} timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE} 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: afterSendSingleMsg:
enable: ${CALLBACK_ENABLE} enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT} timeout: ${CALLBACK_TIMEOUT}

@ -16,18 +16,20 @@ services:
ports: ports:
- "${MONGO_PORT:-37017}:27017" - "${MONGO_PORT:-37017}:27017"
container_name: mongo container_name: mongo
command: --wiredTigerCacheSizeGB 1 --auth command: ["/bin/bash", "-c", "/docker-entrypoint-initdb.d/mongo-init.sh || true; docker-entrypoint.sh mongod --wiredTigerCacheSizeGB 1 --auth"]
volumes: volumes:
- "${DATA_DIR:-./}/components/mongodb/data/db:/data/db" - "${DATA_DIR:-./}/components/mongodb/data/db:/data/db"
- "${DATA_DIR:-./}/components/mongodb/data/logs:/data/logs" - "${DATA_DIR:-./}/components/mongodb/data/logs:/data/logs"
- "${DATA_DIR:-./}/components/mongodb/data/conf:/etc/mongo" - "${DATA_DIR:-./}/components/mongodb/data/conf:/etc/mongo"
- ./scripts/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh:ro - "./scripts/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh:ro"
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- wiredTigerCacheSizeGB=1 - wiredTigerCacheSizeGB=1
- MONGO_INITDB_ROOT_USERNAME=${MONGO_USERNAME:-root} - MONGO_INITDB_ROOT_USERNAME=${MONGO_USERNAME:-root}
- MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD:-openIM123} - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD:-openIM123}
- MONGO_INITDB_DATABASE=${MONGO_DATABASE:-openIM_v3} - MONGO_INITDB_DATABASE=${MONGO_DATABASE:-openIM_v3}
- MONGO_OPENIM_USERNAME=${MONGO_OPENIM_USERNAME:-openIM} # Non-root username
- MONGO_OPENIM_PASSWORD=${MONGO_OPENIM_PASSWORD:-openIM123456} # Non-root password
restart: always restart: always
networks: networks:
server: server:
@ -122,9 +124,9 @@ services:
server: server:
ipv4_address: ${OPENIM_WEB_NETWORK_ADDRESS:-172.28.0.7} ipv4_address: ${OPENIM_WEB_NETWORK_ADDRESS:-172.28.0.7}
## Uncomment and configure the following services as needed # Uncomment and configure the following services as needed
# openim-admin: # openim-admin:
# image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-admin:toc-base-open-docker.35 # image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-admin-front:v3.4.0
# container_name: openim-admin # container_name: openim-admin
# restart: always # restart: always
# ports: # ports:
@ -167,12 +169,6 @@ services:
# hostname: grafana # hostname: grafana
# user: root # user: root
# restart: always # restart: always
# environment:
# - GF_SECURITY_ALLOW_EMBEDDING=true
# - GF_SESSION_COOKIE_SAMESITE=none
# - GF_SESSION_COOKIE_SECURE=true
# - GF_AUTH_ANONYMOUS_ENABLED=true
# - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
# ports: # ports:
# - "${GRAFANA_PORT:-13000}:3000" # - "${GRAFANA_PORT:-13000}:3000"
# volumes: # volumes:

@ -104,8 +104,8 @@ Docker deployment offers a slightly more intricate template. Within the [openim-
Configuration file modifications can be made by specifying corresponding environment variables, for instance: Configuration file modifications can be made by specifying corresponding environment variables, for instance:
```bash ```bash
export CHAT_IMAGE_VERSION="main" export CHAT_IMAGE_VERSION="main"
export SERVER_IMAGE_VERSION="main" export SERVER_IMAGE_VERSION="main"
``` ```
These variables are stored within the [`environment.sh`](https://github.com/OpenIMSDK/openim-docker/blob/main/scripts/install/environment.sh) configuration: These variables are stored within the [`environment.sh`](https://github.com/OpenIMSDK/openim-docker/blob/main/scripts/install/environment.sh) configuration:
@ -114,6 +114,9 @@ These variables are stored within the [`environment.sh`](https://github.com/Open
readonly CHAT_IMAGE_VERSION=${CHAT_IMAGE_VERSION:-'main'} readonly CHAT_IMAGE_VERSION=${CHAT_IMAGE_VERSION:-'main'}
readonly SERVER_IMAGE_VERSION=${SERVER_IMAGE_VERSION:-'main'} readonly SERVER_IMAGE_VERSION=${SERVER_IMAGE_VERSION:-'main'}
``` ```
> [!IMPORTANT]
> Can learn to read our mirror version strategy: https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/images.md
Setting a variable, e.g., `export CHAT_IMAGE_VERSION="release-v1.3"`, will prioritize `CHAT_IMAGE_VERSION="release-v1.3"` as the variable value. Ultimately, the chosen image version is determined, and rendering is achieved through `make init` (or `./scripts/init-config.sh`). Setting a variable, e.g., `export CHAT_IMAGE_VERSION="release-v1.3"`, will prioritize `CHAT_IMAGE_VERSION="release-v1.3"` as the variable value. Ultimately, the chosen image version is determined, and rendering is achieved through `make init` (or `./scripts/init-config.sh`).
@ -127,7 +130,7 @@ For convenience, configuration through modifying environment variables is recomm
+ PASSWORD + PASSWORD
+ **Description**: Password for mysql, mongodb, redis, and minio. + **Description**: Password for mongodb, redis, and minio.
+ **Default**: `openIM123` + **Default**: `openIM123`
+ Notes: + Notes:
+ Minimum password length: 8 characters. + Minimum password length: 8 characters.
@ -139,20 +142,22 @@ For convenience, configuration through modifying environment variables is recomm
+ OPENIM_USER + OPENIM_USER
+ **Description**: Username for mysql, mongodb, redis, and minio. + **Description**: Username for redis, and minio.
+ **Default**: `root` + **Default**: `root`
```bash ```bash
export OPENIM_USER="root" export OPENIM_USER="root"
``` ```
+ API_URL > mongo is `openIM`, use `export MONGO_OPENIM_USERNAME="openIM"` to modify
+ OPENIM_IP
+ **Description**: API address. + **Description**: API address.
+ **Note**: If the server has an external IP, it will be automatically obtained. For internal networks, set this variable to the IP serving internally. + **Note**: If the server has an external IP, it will be automatically obtained. For internal networks, set this variable to the IP serving internally.
```bash ```bash
export API_URL="http://ip:10002" export OPENIM_IP="ip"
``` ```
+ DATA_DIR + DATA_DIR
@ -304,8 +309,10 @@ This section involves setting up MongoDB, including its port, address, and crede
| -------------- | -------------- | ----------------------- | | -------------- | -------------- | ----------------------- |
| MONGO_PORT | "27017" | Port used by MongoDB. | | MONGO_PORT | "27017" | Port used by MongoDB. |
| MONGO_ADDRESS | [Generated IP] | IP address for MongoDB. | | MONGO_ADDRESS | [Generated IP] | IP address for MongoDB. |
| MONGO_USERNAME | [User Defined] | Username for MongoDB. | | MONGO_USERNAME | [User Defined] | Admin Username for MongoDB. |
| MONGO_PASSWORD | [User Defined] | Password for MongoDB. | | MONGO_PASSWORD | [User Defined] | Admin Password for MongoDB. |
| MONGO_OPENIM_PASSWORD | [User Defined] | OpenIM Username for MongoDB. |
| MONGO_OPENIM_PASSWORD | [User Defined] | OpenIM Password for MongoDB. |
### 2.8. <a name='TencentCloudCOSConfiguration'></a>Tencent Cloud COS Configuration ### 2.8. <a name='TencentCloudCOSConfiguration'></a>Tencent Cloud COS Configuration

@ -26,6 +26,7 @@ We provide multiple versions of our images to meet different project requirement
1. `main`: This image corresponds to the latest version of the main branch in OpenIM. It is updated frequently, making it perfect for users who want to stay at the cutting edge of our features. 1. `main`: This image corresponds to the latest version of the main branch in OpenIM. It is updated frequently, making it perfect for users who want to stay at the cutting edge of our features.
2. `release-v3.*`: This is the image that corresponds to the latest version of OpenIM's stable release branch. It's ideal for users who prefer a balance between new features and stability. 2. `release-v3.*`: This is the image that corresponds to the latest version of OpenIM's stable release branch. It's ideal for users who prefer a balance between new features and stability.
3. `v3.*.*`: These images are specific to each tag in OpenIM. They are preserved in their original state and are never overwritten. These are the go-to images for users who need a specific, unchanging version of OpenIM. 3. `v3.*.*`: These images are specific to each tag in OpenIM. They are preserved in their original state and are never overwritten. These are the go-to images for users who need a specific, unchanging version of OpenIM.
4. The image versions adhere to Semantic Versioning 2.0.0 strategy. Taking the `openim-server` image as an example, available at [openim-server container package](https://github.com/openimsdk/open-im-server/pkgs/container/openim-server): upon tagging with v3.5.0, the CI automatically releases the following tags - `openim-server:3`, `openim-server:3.5`, `openim-server:3.5.0`, `openim-server:v3.5.0`, `openim-server:latest`, and `sha-e0244d9`. It's important to note that only `sha-e0244d9` is absolutely unique, whereas `openim-server:v3.5.0` and `openim-server:3.5.0` maintain a degree of uniqueness.
### Multi-Architecture Images ### Multi-Architecture Images

@ -1,6 +1,7 @@
# OpenIM Branch Management and Versioning: A Blueprint for High-Grade Software Development # OpenIM Branch Management and Versioning: A Blueprint for High-Grade Software Development
[📚 **OpenIM TOC**](#openim-branch-management-and-versioning-a-blueprint-for-high-grade-software-development) [📚 **OpenIM TOC**](#openim-branch-management-and-versioning-a-blueprint-for-high-grade-software-development)
- [OpenIM Branch Management and Versioning: A Blueprint for High-Grade Software Development](#openim-branch-management-and-versioning-a-blueprint-for-high-grade-software-development)
- [Unfolding the Mechanism of OpenIM Version Maintenance](#unfolding-the-mechanism-of-openim-version-maintenance) - [Unfolding the Mechanism of OpenIM Version Maintenance](#unfolding-the-mechanism-of-openim-version-maintenance)
- [Main Branch: The Heart of OpenIM Development](#main-branch-the-heart-of-openim-development) - [Main Branch: The Heart of OpenIM Development](#main-branch-the-heart-of-openim-development)
- [Release Branch: The Beacon of Stability](#release-branch-the-beacon-of-stability) - [Release Branch: The Beacon of Stability](#release-branch-the-beacon-of-stability)
@ -8,8 +9,21 @@
- [Release Management: A Guided Tour](#release-management-a-guided-tour) - [Release Management: A Guided Tour](#release-management-a-guided-tour)
- [Milestones, Branching, and Addressing Major Bugs](#milestones-branching-and-addressing-major-bugs) - [Milestones, Branching, and Addressing Major Bugs](#milestones-branching-and-addressing-major-bugs)
- [Version Skew Policy](#version-skew-policy) - [Version Skew Policy](#version-skew-policy)
- [Supported version skew](#supported-version-skew)
- [OpenIM Versioning, Branching, and Tag Strategy](#openim-versioning-branching-and-tag-strategy)
- [Supported Version Skew](#supported-version-skew-1)
- [openim-api](#openim-api)
- [openim-rpc-\* Components](#openim-rpc--components)
- [Other OpenIM Services](#other-openim-services)
- [Supported Component Upgrade Order](#supported-component-upgrade-order)
- [openim-api](#openim-api-1)
- [openim-rpc-\* Components](#openim-rpc--components-1)
- [Other OpenIM Services](#other-openim-services-1)
- [Conclusion](#conclusion)
- [Applying Principles: A Git Workflow Example](#applying-principles-a-git-workflow-example) - [Applying Principles: A Git Workflow Example](#applying-principles-a-git-workflow-example)
- [Release Process](#release-process)
- [Docker Images Version Management](#docker-images-version-management) - [Docker Images Version Management](#docker-images-version-management)
- [More](#more)
At OpenIM, we acknowledge the profound impact of implementing a robust and efficient version management system, hence we abide by the established standards of [Semantic Versioning 2.0.0](https://semver.org/lang/zh-CN/). At OpenIM, we acknowledge the profound impact of implementing a robust and efficient version management system, hence we abide by the established standards of [Semantic Versioning 2.0.0](https://semver.org/lang/zh-CN/).
@ -213,3 +227,10 @@ Throughout this process, active communication within the team is pivotal to main
## Docker Images Version Management ## Docker Images Version Management
For more details on managing Docker image versions, visit [OpenIM Docker Images Administration](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/images.md). For more details on managing Docker image versions, visit [OpenIM Docker Images Administration](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/images.md).
## More
More on multi-branch version management design and version management design at helm charts
+ https://github.com/openimsdk/open-im-server/issues/1695
+ https://github.com/openimsdk/open-im-server/issues/1662

@ -4,8 +4,8 @@ go 1.19
require ( require (
firebase.google.com/go v3.13.0+incompatible firebase.google.com/go v3.13.0+incompatible
github.com/OpenIMSDK/protocol v0.0.43 github.com/OpenIMSDK/protocol v0.0.47
github.com/OpenIMSDK/tools v0.0.21 github.com/OpenIMSDK/tools v0.0.23
github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/bwmarrin/snowflake v0.3.0 // indirect
github.com/dtm-labs/rockscache v0.1.1 github.com/dtm-labs/rockscache v0.1.1
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1

@ -20,10 +20,10 @@ github.com/AndrewZuo01/protocol v0.0.0-20231219031520-648989b91fca/go.mod h1:F25
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 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 h1:MWBEJ12vHC8coMjdEXFq/6ftO6DUZnQlFYcxtOJFa7c=
github.com/IBM/sarama v1.41.3/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ= github.com/IBM/sarama v1.41.3/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ=
github.com/OpenIMSDK/protocol v0.0.43 h1:8B921vEyO7r0AfQfZd7kCycYja+hJ2vuIZsKge/WRhU= github.com/OpenIMSDK/protocol v0.0.47 h1:DTJMFSONzqT0i/wa4Q1CtDT/jVATVudIRHcpY1zSWYE=
github.com/OpenIMSDK/protocol v0.0.43/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y= github.com/OpenIMSDK/protocol v0.0.47/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y=
github.com/OpenIMSDK/tools v0.0.21 h1:iTapc2mIEVH/xl5Nd6jfwPub11Pgp44tVcE1rjB3a48= github.com/OpenIMSDK/tools v0.0.23 h1:xozfrGzhbpNPlDTap5DLVPk+JfgZ/ZyIj4Cuu3/bm9w=
github.com/OpenIMSDK/tools v0.0.21/go.mod h1:eg+q4A34Qmu73xkY0mt37FHGMCMfC6CtmOnm0kFEGFI= 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/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 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=

@ -33,8 +33,8 @@ func (o *ConversationApi) GetAllConversations(c *gin.Context) {
a2r.Call(conversation.ConversationClient.GetAllConversations, o.Client, c) a2r.Call(conversation.ConversationClient.GetAllConversations, o.Client, c)
} }
func (o *ConversationApi) GetConversationsList(c *gin.Context) { func (o *ConversationApi) GetSortedConversationList(c *gin.Context) {
a2r.Call(conversation.ConversationClient.GetConversationList, o.Client, c) a2r.Call(conversation.ConversationClient.GetSortedConversationList, o.Client, c)
} }
func (o *ConversationApi) GetConversation(c *gin.Context) { func (o *ConversationApi) GetConversation(c *gin.Context) {

@ -164,6 +164,8 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM
data = apistruct.VideoElem{} data = apistruct.VideoElem{}
case constant.File: case constant.File:
data = apistruct.FileElem{} data = apistruct.FileElem{}
case constant.AtText:
data = apistruct.AtElem{}
case constant.Custom: case constant.Custom:
data = apistruct.CustomElem{} data = apistruct.CustomElem{}
case constant.OANotification: 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 { if err = m.userRpcClient.GetNotificationByID(c, req.SendID); err != nil {
return nil, err return nil, err
} }
default: default:
return nil, errs.ErrArgs.WithDetail("not support err contentType") return nil, errs.ErrArgs.WithDetail("not support err contentType")
} }

@ -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_delete", ParseToken, u.ProcessUserCommandDelete)
userRouterGroup.POST("/process_user_command_update", ParseToken, u.ProcessUserCommandUpdate) userRouterGroup.POST("/process_user_command_update", ParseToken, u.ProcessUserCommandUpdate)
userRouterGroup.POST("/process_user_command_get", ParseToken, u.ProcessUserCommandGet) 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("/add_notification_account", ParseToken, u.AddNotificationAccount)
userRouterGroup.POST("/update_notification_account", ParseToken, u.UpdateNotificationAccountInfo) 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) conversationGroup := r.Group("/conversation", ParseToken)
{ {
c := NewConversationApi(*conversationRpc) 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_all_conversations", c.GetAllConversations)
conversationGroup.POST("/get_conversation", c.GetConversation) conversationGroup.POST("/get_conversation", c.GetConversation)
conversationGroup.POST("/get_conversations", c.GetConversations) conversationGroup.POST("/get_conversations", c.GetConversations)

@ -221,6 +221,11 @@ func (u *UserApi) ProcessUserCommandGet(c *gin.Context) {
a2r.Call(user.UserClient.ProcessUserCommandGet, u.Client, c) 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) { func (u *UserApi) AddNotificationAccount(c *gin.Context) {
a2r.Call(user.UserClient.AddNotificationAccount, u.Client, c) a2r.Call(user.UserClient.AddNotificationAccount, u.Client, c)
} }

@ -37,7 +37,7 @@ func callbackOfflinePush(
msg *sdkws.MsgData, msg *sdkws.MsgData,
offlinePushUserIDs *[]string, offlinePushUserIDs *[]string,
) error { ) error {
if !config.Config.Callback.CallbackOfflinePush.Enable { if !config.Config.Callback.CallbackOfflinePush.Enable || msg.ContentType == constant.Typing {
return nil return nil
} }
req := &callbackstruct.CallbackBeforePushReq{ req := &callbackstruct.CallbackBeforePushReq{
@ -73,7 +73,7 @@ func callbackOfflinePush(
} }
func callbackOnlinePush(ctx context.Context, userIDs []string, msg *sdkws.MsgData) error { 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 return nil
} }
req := callbackstruct.CallbackBeforePushReq{ req := callbackstruct.CallbackBeforePushReq{
@ -107,7 +107,7 @@ func callbackBeforeSuperGroupOnlinePush(
msg *sdkws.MsgData, msg *sdkws.MsgData,
pushToUserIDs *[]string, pushToUserIDs *[]string,
) error { ) error {
if !config.Config.Callback.CallbackBeforeSuperGroupOnlinePush.Enable { if !config.Config.Callback.CallbackBeforeSuperGroupOnlinePush.Enable || msg.ContentType == constant.Typing {
return nil return nil
} }
req := callbackstruct.CallbackBeforeSuperGroupOnlinePushReq{ req := callbackstruct.CallbackBeforeSuperGroupOnlinePushReq{

@ -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 { 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()) 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 { if err := callbackOnlinePush(ctx, userIDs, msg); err != nil {
return err return err
} }
// push // push
wsResults, err := p.GetConnsAndOnlinePush(ctx, msg, userIDs) wsResults, err := p.GetConnsAndOnlinePush(ctx, msg, userIDs)
if err != nil { if err != nil {
@ -120,7 +118,7 @@ func (p *Pusher) Push2User(ctx context.Context, userIDs []string, msg *sdkws.Msg
} }
for _, v := range wsResults { 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 { if err = callbackOfflinePush(ctx, userIDs, msg, &[]string{}); err != nil {
return err return err
} }
@ -130,6 +128,7 @@ func (p *Pusher) Push2User(ctx context.Context, userIDs []string, msg *sdkws.Msg
return err return err
} }
} }
} }
return nil return nil
} }

@ -89,8 +89,8 @@ func (c *conversationServer) GetConversation(ctx context.Context, req *pbconvers
return resp, nil return resp, nil
} }
func (m *conversationServer) GetConversationList(ctx context.Context, req *pbconversation.GetConversationListReq) (resp *pbconversation.GetConversationListResp, err error) { func (m *conversationServer) GetSortedConversationList(ctx context.Context, req *pbconversation.GetSortedConversationListReq) (resp *pbconversation.GetSortedConversationListResp, err error) {
log.ZDebug(ctx, "GetConversationList", "seqs", req, "userID", req.UserID) log.ZDebug(ctx, "GetSortedConversationList", "seqs", req, "userID", req.UserID)
var conversationIDs []string var conversationIDs []string
if len(req.ConversationIDs) == 0 { if len(req.ConversationIDs) == 0 {
conversationIDs, err = m.conversationDatabase.GetConversationIDs(ctx, req.UserID) conversationIDs, err = m.conversationDatabase.GetConversationIDs(ctx, req.UserID)
@ -129,30 +129,37 @@ func (m *conversationServer) GetConversationList(ctx context.Context, req *pbcon
return nil, err return nil, err
} }
var unreadTotal int64
conversation_unreadCount := make(map[string]int64) conversation_unreadCount := make(map[string]int64)
for conversationID, maxSeq := range maxSeqs { 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_isPinTime := make(map[int64]string)
conversation_notPinkTime := make(map[int64]string) conversation_notPinTime := make(map[int64]string)
for _, v := range conversations { for _, v := range conversations {
conversationID := v.ConversationID conversationID := v.ConversationID
time := conversationMsg[conversationID].MsgInfo.LatestMsgRecvTime time := conversationMsg[conversationID].MsgInfo.LatestMsgRecvTime
conversationMsg[conversationID].RecvMsgOpt = v.RecvMsgOpt conversationMsg[conversationID].RecvMsgOpt = v.RecvMsgOpt
if v.IsPinned { if v.IsPinned {
conversationMsg[conversationID].IsPinned = v.IsPinned conversationMsg[conversationID].IsPinned = v.IsPinned
conversation_isPinkTime[time] = conversationID conversation_isPinTime[time] = conversationID
continue continue
} }
conversation_notPinkTime[time] = conversationID conversation_notPinTime[time] = conversationID
} }
resp = &pbconversation.GetConversationListResp{ resp = &pbconversation.GetSortedConversationListResp{
ConversationTotal: int64(len(chatLogs)),
ConversationElems: []*pbconversation.ConversationElem{}, ConversationElems: []*pbconversation.ConversationElem{},
UnreadTotal: unreadTotal,
} }
m.conversationSort(conversation_isPinkTime, resp, conversation_unreadCount, conversationMsg) m.conversationSort(conversation_isPinTime, resp, conversation_unreadCount, conversationMsg)
m.conversationSort(conversation_notPinkTime, 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 return resp, nil
} }
@ -425,7 +432,7 @@ func (c *conversationServer) GetConversationOfflinePushUserIDs(
func (c *conversationServer) conversationSort( func (c *conversationServer) conversationSort(
conversations map[int64]string, conversations map[int64]string,
resp *pbconversation.GetConversationListResp, resp *pbconversation.GetSortedConversationListResp,
conversation_unreadCount map[string]int64, conversation_unreadCount map[string]int64,
conversationMsg map[string]*pbconversation.ConversationElem, conversationMsg map[string]*pbconversation.ConversationElem,
) { ) {

@ -452,22 +452,19 @@ func (s *friendServer) UpdateFriends(
return nil, err return nil, err
} }
for _, friendID := range req.FriendUserIDs { val := make(map[string]any)
if req.IsPinned != nil {
if err = s.friendDatabase.UpdateFriendPinStatus(ctx, req.OwnerUserID, friendID, req.IsPinned.Value); err != nil { if req.IsPinned != nil {
return nil, err val["is_pinned"] = req.IsPinned.Value
} }
} if req.Remark != nil {
if req.Remark != nil { val["remark"] = req.Remark.Value
if err = s.friendDatabase.UpdateFriendRemark(ctx, req.OwnerUserID, friendID, req.Remark.Value); err != nil { }
return nil, err if req.Ex != nil {
} val["ex"] = req.Ex.Value
} }
if req.Ex != nil { if err = s.friendDatabase.UpdateFriends(ctx, req.OwnerUserID, req.FriendUserIDs, val); err != nil {
if err = s.friendDatabase.UpdateFriendEx(ctx, req.OwnerUserID, friendID, req.Ex.Value); err != nil { return nil, err
return nil, err
}
}
} }
resp := &pbfriend.UpdateFriendsResp{} resp := &pbfriend.UpdateFriendsResp{}

@ -279,20 +279,20 @@ func CallbackApplyJoinGroupBefore(ctx context.Context, req *callbackstruct.Callb
return nil return nil
} }
func CallbackTransferGroupOwnerAfter(ctx context.Context, req *pbgroup.TransferGroupOwnerReq) (err error) { func CallbackAfterTransferGroupOwner(ctx context.Context, req *pbgroup.TransferGroupOwnerReq) (err error) {
if !config.Config.Callback.CallbackTransferGroupOwnerAfter.Enable { if !config.Config.Callback.CallbackAfterTransferGroupOwner.Enable {
return nil return nil
} }
cbReq := &callbackstruct.CallbackTransferGroupOwnerReq{ cbReq := &callbackstruct.CallbackTransferGroupOwnerReq{
CallbackCommand: callbackstruct.CallbackTransferGroupOwnerAfter, CallbackCommand: callbackstruct.CallbackAfterTransferGroupOwner,
GroupID: req.GroupID, GroupID: req.GroupID,
OldOwnerUserID: req.OldOwnerUserID, OldOwnerUserID: req.OldOwnerUserID,
NewOwnerUserID: req.NewOwnerUserID, NewOwnerUserID: req.NewOwnerUserID,
} }
resp := &callbackstruct.CallbackTransferGroupOwnerResp{} 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 err
} }
return nil return nil

@ -109,14 +109,11 @@ type groupServer struct {
} }
func (s *groupServer) NotificationUserInfoUpdate(ctx context.Context, req *pbgroup.NotificationUserInfoUpdateReq) (*pbgroup.NotificationUserInfoUpdateResp, error) { func (s *groupServer) NotificationUserInfoUpdate(ctx context.Context, req *pbgroup.NotificationUserInfoUpdateReq) (*pbgroup.NotificationUserInfoUpdateResp, error) {
defer log.ZDebug(ctx, "return") defer log.ZDebug(ctx, "NotificationUserInfoUpdate return")
members, err := s.db.FindGroupMemberUser(ctx, nil, req.UserID) members, err := s.db.FindGroupMemberUser(ctx, nil, req.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := s.PopulateGroupMember(ctx, members...); err != nil {
return nil, err
}
groupIDs := make([]string, 0, len(members)) groupIDs := make([]string, 0, len(members))
for _, member := range members { for _, member := range members {
if member.Nickname != "" && member.FaceURL != "" { if member.Nickname != "" && member.FaceURL != "" {
@ -476,13 +473,42 @@ func (s *groupServer) GetGroupAllMember(ctx context.Context, req *pbgroup.GetGro
func (s *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGroupMemberListReq) (*pbgroup.GetGroupMemberListResp, error) { func (s *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGroupMemberListReq) (*pbgroup.GetGroupMemberListResp, error) {
resp := &pbgroup.GetGroupMemberListResp{} resp := &pbgroup.GetGroupMemberListResp{}
total, members, err := s.db.PageGetGroupMember(ctx, req.GroupID, req.Pagination) var (
total int64
members []*relationtb.GroupMemberModel
err error
)
if req.Keyword == "" {
total, members, err = s.db.PageGetGroupMember(ctx, req.GroupID, req.Pagination)
} else {
members, err = s.db.FindGroupMemberAll(ctx, req.GroupID)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := s.PopulateGroupMember(ctx, members...); err != nil { if err := s.PopulateGroupMember(ctx, members...); err != nil {
return nil, err return nil, err
} }
if req.Keyword != "" {
groupMembers := make([]*relationtb.GroupMemberModel, 0)
for _, member := range members {
if member.UserID == req.Keyword {
groupMembers = append(groupMembers, member)
total++
continue
}
if member.Nickname == req.Keyword {
groupMembers = append(groupMembers, member)
total++
continue
}
}
GMembers := utils.Paginate(groupMembers, int(req.Pagination.GetPageNumber()), int(req.Pagination.GetShowNumber()))
resp.Members = utils.Batch(convert.Db2PbGroupMember, GMembers)
resp.Total = uint32(total)
return resp, nil
}
resp.Total = uint32(total) resp.Total = uint32(total)
resp.Members = utils.Batch(convert.Db2PbGroupMember, members) resp.Members = utils.Batch(convert.Db2PbGroupMember, members)
return resp, nil return resp, nil
@ -1032,7 +1058,7 @@ func (s *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
return nil, err return nil, err
} }
if err := CallbackTransferGroupOwnerAfter(ctx, req); err != nil { if err := CallbackAfterTransferGroupOwner(ctx, req); err != nil {
return nil, err return nil, err
} }
s.Notification.GroupOwnerTransferredNotification(ctx, req) s.Notification.GroupOwnerTransferredNotification(ctx, req)
@ -1042,20 +1068,29 @@ func (s *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq) (*pbgroup.GetGroupsResp, error) { func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq) (*pbgroup.GetGroupsResp, error) {
resp := &pbgroup.GetGroupsResp{} resp := &pbgroup.GetGroupsResp{}
var ( var (
groups []*relationtb.GroupModel group []*relationtb.GroupModel
err error err error
) )
if req.GroupID != "" { if req.GroupID != "" {
groups, err = s.db.FindGroup(ctx, []string{req.GroupID}) group, err = s.db.FindGroup(ctx, []string{req.GroupID})
resp.Total = uint32(len(groups)) resp.Total = uint32(len(group))
} else { } else {
var total int64 var total int64
total, groups, err = s.db.SearchGroup(ctx, req.GroupName, req.Pagination) total, group, err = s.db.SearchGroup(ctx, req.GroupName, req.Pagination)
resp.Total = uint32(total) resp.Total = uint32(total)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
var groups []*relationtb.GroupModel
for _, v := range group {
if v.Status == constant.GroupStatusDismissed {
resp.Total--
continue
}
groups = append(groups, v)
}
groupIDs := utils.Slice(groups, func(e *relationtb.GroupModel) string { groupIDs := utils.Slice(groups, func(e *relationtb.GroupModel) string {
return e.GroupID return e.GroupID
}) })

@ -70,7 +70,7 @@ func GetContent(msg *sdkws.MsgData) string {
} }
func callbackBeforeSendSingleMsg(ctx context.Context, msg *pbchat.SendMsgReq) error { 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 return nil
} }
req := &cbapi.CallbackBeforeSendSingleMsgReq{ 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 { 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 return nil
} }
req := &cbapi.CallbackAfterSendSingleMsgReq{ 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 { 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 return nil
} }
req := &cbapi.CallbackBeforeSendGroupMsgReq{ 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 { 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 return nil
} }
req := &cbapi.CallbackAfterSendGroupMsgReq{ req := &cbapi.CallbackAfterSendGroupMsgReq{

@ -65,6 +65,7 @@ func (m *msgServer) sendMsgSuperGroupChat(
if err = callbackBeforeSendGroupMsg(ctx, req); err != nil { if err = callbackBeforeSendGroupMsg(ctx, req); err != nil {
return nil, err return nil, err
} }
if err := callbackMsgModify(ctx, req); err != nil { if err := callbackMsgModify(ctx, req); err != nil {
return nil, err return nil, err
} }
@ -167,6 +168,7 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq
if err = callbackBeforeSendSingleMsg(ctx, req); err != nil { if err = callbackBeforeSendSingleMsg(ctx, req); err != nil {
return nil, err return nil, err
} }
if err := callbackMsgModify(ctx, req); err != nil { if err := callbackMsgModify(ctx, req); err != nil {
return nil, err return nil, err
} }

@ -17,6 +17,7 @@ package user
import ( import (
"context" "context"
"errors" "errors"
"github.com/OpenIMSDK/tools/pagination"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
"math/rand" "math/rand"
"strings" "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) { 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 { if err != nil {
return nil, err return nil, err
} }
@ -379,38 +380,94 @@ func (s *userServer) GetSubscribeUsersStatus(ctx context.Context,
// ProcessUserCommandAdd user general function add // ProcessUserCommandAdd user general function add
func (s *userServer) ProcessUserCommandAdd(ctx context.Context, req *pbuser.ProcessUserCommandAddReq) (*pbuser.ProcessUserCommandAddResp, error) { 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 := authverify.CheckAccessV3(ctx, req.UserID)
err := s.UserDatabase.AddUserCommand(ctx, req.UserID, req.Type, req.Uuid, req.Value)
if err != nil { if err != nil {
return nil, err 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 return &pbuser.ProcessUserCommandAddResp{}, nil
} }
// ProcessUserCommandDelete user general function delete // ProcessUserCommandDelete user general function delete
func (s *userServer) ProcessUserCommandDelete(ctx context.Context, req *pbuser.ProcessUserCommandDeleteReq) (*pbuser.ProcessUserCommandDeleteResp, error) { 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 := authverify.CheckAccessV3(ctx, req.UserID)
err := s.UserDatabase.DeleteUserCommand(ctx, req.UserID, req.Type, req.Uuid)
if err != nil { if err != nil {
return nil, err 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 return &pbuser.ProcessUserCommandDeleteResp{}, nil
} }
// ProcessUserCommandUpdate user general function update // ProcessUserCommandUpdate user general function update
func (s *userServer) ProcessUserCommandUpdate(ctx context.Context, req *pbuser.ProcessUserCommandUpdateReq) (*pbuser.ProcessUserCommandUpdateResp, error) { 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 := authverify.CheckAccessV3(ctx, req.UserID)
err := s.UserDatabase.UpdateUserCommand(ctx, req.UserID, req.Type, req.Uuid, req.Value)
if err != nil { if err != nil {
return nil, err 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 return &pbuser.ProcessUserCommandUpdateResp{}, nil
} }
func (s *userServer) ProcessUserCommandGet(ctx context.Context, req *pbuser.ProcessUserCommandGetReq) (*pbuser.ProcessUserCommandGetResp, error) { 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 // Fetch user commands from the database
commands, err := s.UserDatabase.GetUserCommands(ctx, req.UserID, req.Type) commands, err := s.UserDatabase.GetUserCommands(ctx, req.UserID, req.Type)
if err != nil { if err != nil {
@ -423,14 +480,45 @@ func (s *userServer) ProcessUserCommandGet(ctx context.Context, req *pbuser.Proc
for _, command := range commands { for _, command := range commands {
// No need to use index since command is already a pointer // No need to use index since command is already a pointer
commandInfoSlice = append(commandInfoSlice, &pbuser.CommandInfoResp{ 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{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, Uuid: command.Uuid,
Value: command.Value, Value: command.Value,
CreateTime: command.CreateTime, CreateTime: command.CreateTime,
Ex: command.Ex,
}) })
} }
// Return the response with the slice // Return the response with the slice
return &pbuser.ProcessUserCommandGetResp{KVArray: commandInfoSlice}, nil return &pbuser.ProcessUserCommandGetAllResp{CommandResp: commandInfoSlice}, nil
} }
func (s *userServer) AddNotificationAccount(ctx context.Context, req *pbuser.AddNotificationAccountReq) (*pbuser.AddNotificationAccountResp, error) { 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 return nil, err
} }
var userID string if req.UserID == "" {
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
userId := s.genUserID() userId := s.genUserID()
_, err := s.UserDatabase.FindWithError(ctx, []string{userId}) _, 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 { if err == nil {
continue 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{ user := &tablerelation.UserModel{
UserID: userID, UserID: req.UserID,
Nickname: req.NickName, Nickname: req.NickName,
FaceURL: req.FaceURL, FaceURL: req.FaceURL,
CreateTime: time.Now(), CreateTime: time.Now(),
@ -463,7 +557,11 @@ func (s *userServer) AddNotificationAccount(ctx context.Context, req *pbuser.Add
return nil, err 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) { 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 return nil, err
} }
if req.NickName != "" { var users []*relation.UserModel
users, err := s.UserDatabase.FindByNickname(ctx, req.NickName) var err error
if req.Keyword != "" {
users, err = s.UserDatabase.Find(ctx, []string{req.Keyword})
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp := s.userModelToResp(users) resp := s.userModelToResp(users, req.Pagination)
return resp, nil if resp.Total != 0 {
} return resp, nil
}
if req.UserID != "" { users, err = s.UserDatabase.FindByNickname(ctx, req.Keyword)
users, err := s.UserDatabase.Find(ctx, []string{req.UserID})
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp := s.userModelToResp(users) resp = s.userModelToResp(users, req.Pagination)
return resp, nil
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 { if err != nil {
return nil, err return nil, err
} }
resp := s.userModelToResp(users) resp := s.userModelToResp(users, req.Pagination)
return resp, nil return resp, nil
} }
@ -554,7 +655,7 @@ func (s *userServer) genUserID() string {
return string(data) 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) accounts := make([]*pbuser.NotificationAccountInfo, 0)
var total int64 var total int64
for _, v := range users { for _, v := range users {
@ -568,5 +669,8 @@ func (s *userServer) userModelToResp(users []*relation.UserModel) *pbuser.Search
total += 1 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}
} }

@ -76,7 +76,6 @@ func ParseRedisInterfaceToken(redisToken any) (*tokenverify.Claims, error) {
func IsManagerUserID(opUserID string) bool { func IsManagerUserID(opUserID string) bool {
return utils.IsContain(opUserID, config.Config.Manager.UserID) || utils.IsContain(opUserID, config.Config.IMAdmin.UserID) return utils.IsContain(opUserID, config.Config.Manager.UserID) || utils.IsContain(opUserID, config.Config.IMAdmin.UserID)
} }
func WsVerifyToken(token, userID string, platformID int) error { func WsVerifyToken(token, userID string, platformID int) error {

@ -41,7 +41,7 @@ const (
CallbackBeforeUpdateUserInfoExCommand = "callbackBeforeUpdateUserInfoExCommand" CallbackBeforeUpdateUserInfoExCommand = "callbackBeforeUpdateUserInfoExCommand"
CallbackBeforeUserRegisterCommand = "callbackBeforeUserRegisterCommand" CallbackBeforeUserRegisterCommand = "callbackBeforeUserRegisterCommand"
CallbackAfterUserRegisterCommand = "callbackAfterUserRegisterCommand" CallbackAfterUserRegisterCommand = "callbackAfterUserRegisterCommand"
CallbackTransferGroupOwnerAfter = "callbackTransferGroupOwnerAfter" CallbackAfterTransferGroupOwner = "callbackAfterTransferGroupOwner"
CallbackBeforeSetFriendRemark = "callbackBeforeSetFriendRemark" CallbackBeforeSetFriendRemark = "callbackBeforeSetFriendRemark"
CallbackAfterSetFriendRemark = "callbackAfterSetFriendRemark" CallbackAfterSetFriendRemark = "callbackAfterSetFriendRemark"
CallbackSingleMsgRead = "callbackSingleMsgRead" CallbackSingleMsgRead = "callbackSingleMsgRead"

@ -296,7 +296,7 @@ type configStruct struct {
CallbackKillGroupMember CallBackConfig `yaml:"killGroupMember"` CallbackKillGroupMember CallBackConfig `yaml:"killGroupMember"`
CallbackDismissGroup CallBackConfig `yaml:"dismissGroup"` CallbackDismissGroup CallBackConfig `yaml:"dismissGroup"`
CallbackBeforeJoinGroup CallBackConfig `yaml:"joinGroup"` CallbackBeforeJoinGroup CallBackConfig `yaml:"joinGroup"`
CallbackTransferGroupOwnerAfter CallBackConfig `yaml:"transferGroupOwner"` CallbackAfterTransferGroupOwner CallBackConfig `yaml:"transferGroupOwner"`
CallbackBeforeInviteUserToGroup CallBackConfig `yaml:"beforeInviteUserToGroup"` CallbackBeforeInviteUserToGroup CallBackConfig `yaml:"beforeInviteUserToGroup"`
CallbackAfterJoinGroup CallBackConfig `yaml:"joinGroupAfter"` CallbackAfterJoinGroup CallBackConfig `yaml:"joinGroupAfter"`
CallbackAfterSetGroupInfo CallBackConfig `yaml:"setGroupInfoAfter"` CallbackAfterSetGroupInfo CallBackConfig `yaml:"setGroupInfoAfter"`

@ -44,6 +44,8 @@ type FriendCache interface {
GetFriend(ctx context.Context, ownerUserID, friendUserID string) (friend *relationtb.FriendModel, err error) GetFriend(ctx context.Context, ownerUserID, friendUserID string) (friend *relationtb.FriendModel, err error)
// Delete friend when friend info changed // Delete friend when friend info changed
DelFriend(ownerUserID, friendUserID string) FriendCache 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. // FriendCacheRedis is an implementation of the FriendCache interface using Redis.
@ -152,3 +154,15 @@ func (f *FriendCacheRedis) DelFriend(ownerUserID, friendUserID string) FriendCac
return newFriendCache 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
}

@ -279,7 +279,7 @@ func (c *conversationDatabase) CreateGroupChatConversation(ctx context.Context,
for _, v := range existConversationUserIDs { for _, v := range existConversationUserIDs {
cache = cache.DelConversations(v, conversationID) cache = cache.DelConversations(v, conversationID)
} }
return c.cache.ExecDel(ctx) return cache.ExecDel(ctx)
}) })
} }

@ -74,15 +74,8 @@ type FriendDatabase interface {
// FindBothFriendRequests finds friend requests sent and received // FindBothFriendRequests finds friend requests sent and received
FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*relation.FriendRequestModel, err error) FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*relation.FriendRequestModel, err error)
// UpdateFriendPinStatus updates the pinned status of a friend // UpdateFriends updates fields for friends
UpdateFriendPinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error) UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (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)
} }
type friendDatabase struct { 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) { func (f *friendDatabase) FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*relation.FriendRequestModel, err error) {
return f.friendRequest.FindBothFriendRequests(ctx, fromUserID, toUserID) return f.friendRequest.FindBothFriendRequests(ctx, fromUserID, toUserID)
} }
func (f *friendDatabase) UpdateFriendPinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error) { func (f *friendDatabase) UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (err error) {
if err := f.friend.UpdatePinStatus(ctx, ownerUserID, friendUserID, isPinned); err != nil { if len(val) == 0 {
return err return nil
} }
return f.cache.DelFriend(ownerUserID, friendUserID).ExecDel(ctx) if err := f.friend.UpdateFriends(ctx, ownerUserID, friendUserIDs, val); err != nil {
}
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 err
} }
return f.cache.DelFriend(ownerUserID, friendUserID).ExecDel(ctx) return f.cache.DelFriends(ownerUserID, friendUserIDs).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)
} }

@ -50,6 +50,8 @@ type UserDatabase interface {
UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error) UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error)
// Page If not found, no error is returned // Page If not found, no error is returned
Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) 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 true as long as one exists
IsExist(ctx context.Context, userIDs []string) (exist bool, err error) IsExist(ctx context.Context, userIDs []string) (exist bool, err error)
// GetAllUserID Get all user IDs // GetAllUserID Get all user IDs
@ -76,10 +78,11 @@ type UserDatabase interface {
SetUserStatus(ctx context.Context, userID string, status, platformID int32) error SetUserStatus(ctx context.Context, userID string, status, platformID int32) error
//CRUD user command //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 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) GetUserCommands(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error)
GetAllUserCommands(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error)
} }
type userDatabase struct { type userDatabase struct {
@ -182,6 +185,10 @@ func (u *userDatabase) Page(ctx context.Context, pagination pagination.Paginatio
return u.userDB.Page(ctx, pagination) 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. // 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) { func (u *userDatabase) IsExist(ctx context.Context, userIDs []string) (exist bool, err error) {
users, err := u.userDB.Find(ctx, userIDs) 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 { func (u *userDatabase) SetUserStatus(ctx context.Context, userID string, status, platformID int32) error {
return u.cache.SetUserStatus(ctx, userID, status, platformID) return u.cache.SetUserStatus(ctx, userID, status, platformID)
} }
func (u *userDatabase) AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string) error { 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) return u.userDB.AddUserCommand(ctx, userID, Type, UUID, value, ex)
} }
func (u *userDatabase) DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error { func (u *userDatabase) DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error {
return u.userDB.DeleteUserCommand(ctx, userID, Type, UUID) return u.userDB.DeleteUserCommand(ctx, userID, Type, UUID)
} }
func (u *userDatabase) UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string) error { 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, value) return u.userDB.UpdateUserCommand(ctx, userID, Type, UUID, val)
} }
func (u *userDatabase) GetUserCommands(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error) { func (u *userDatabase) GetUserCommands(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error) {
commands, err := u.userDB.GetUserCommand(ctx, userID, Type) commands, err := u.userDB.GetUserCommand(ctx, userID, Type)
return commands, err 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
}

@ -16,7 +16,6 @@ package mgo
import ( import (
"context" "context"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/mgoutil" "github.com/OpenIMSDK/tools/mgoutil"
"github.com/OpenIMSDK/tools/pagination" "github.com/OpenIMSDK/tools/pagination"
"go.mongodb.org/mongo-driver/mongo/options" "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})) 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) UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) error {
func (f *FriendMgo) UpdatePinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error) { // Ensure there are IDs to update
if len(friendUserIDs) == 0 {
filter := bson.M{"owner_user_id": ownerUserID, "friend_user_id": friendUserID} return nil // Or return an error if you expect there to always be IDs
// 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")
} }
return nil // Create a filter to match documents with the specified ownerUserID and any of the friendUserIDs
} filter := bson.M{
func (f *FriendMgo) UpdateFriendRemark(ctx context.Context, ownerUserID string, friendUserID string, remark string) (err error) { "owner_user_id": ownerUserID,
"friend_user_id": bson.M{"$in": friendUserIDs},
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")
} }
return nil // Create an update document
} update := bson.M{"$set": val}
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 // Perform the update operation for all matching documents
_, err := mgoutil.UpdateMany(ctx, f.coll, filter, update)
return err
} }

@ -17,6 +17,7 @@ package mgo
import ( import (
"context" "context"
"github.com/OpenIMSDK/protocol/user" "github.com/OpenIMSDK/protocol/user"
"github.com/OpenIMSDK/tools/errs"
"time" "time"
"github.com/OpenIMSDK/tools/mgoutil" "github.com/OpenIMSDK/tools/mgoutil"
@ -77,8 +78,12 @@ func (u *UserMgo) Page(ctx context.Context, pagination pagination.Pagination) (c
return mgoutil.FindPage[*relation.UserModel](ctx, u.coll, bson.M{}, pagination) 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) { 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})) return mgoutil.FindPage[string](ctx, u.coll, bson.M{}, pagination, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1}))
} }
func (u *UserMgo) Exist(ctx context.Context, userID string) (exist bool, err error) { func (u *UserMgo) Exist(ctx context.Context, userID string) (exist bool, err error) {
@ -86,7 +91,7 @@ func (u *UserMgo) Exist(ctx context.Context, userID string) (exist bool, err err
} }
func (u *UserMgo) GetUserGlobalRecvMsgOpt(ctx context.Context, userID string) (opt int, err error) { func (u *UserMgo) GetUserGlobalRecvMsgOpt(ctx context.Context, userID string) (opt int, err error) {
return mgoutil.FindOne[int](ctx, u.coll, bson.M{"user_id": userID}, options.FindOne().SetProjection(bson.M{"global_recv_msg_opt": 1})) return mgoutil.FindOne[int](ctx, u.coll, bson.M{"user_id": userID}, options.FindOne().SetProjection(bson.M{"_id": 0, "global_recv_msg_opt": 1}))
} }
func (u *UserMgo) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) { func (u *UserMgo) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
@ -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}}) 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") collection := u.coll.Database().Collection("userCommands")
// Create a new document instead of updating an existing one // 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, "uuid": UUID,
"createTime": time.Now().Unix(), // assuming you want the creation time in Unix timestamp "createTime": time.Now().Unix(), // assuming you want the creation time in Unix timestamp
"value": value, "value": value,
"ex": ex,
} }
_, err := collection.InsertOne(ctx, doc) _, err := collection.InsertOne(ctx, doc)
return err return err
} }
func (u *UserMgo) DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error { func (u *UserMgo) DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error {
collection := u.coll.Database().Collection("userCommands") collection := u.coll.Database().Collection("userCommands")
filter := bson.M{"userID": userID, "type": Type, "uuid": UUID} 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 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") collection := u.coll.Database().Collection("userCommands")
filter := bson.M{"userID": userID, "type": Type, "uuid": UUID} 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) result, err := collection.UpdateOne(ctx, filter, update)
return err 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) { func (u *UserMgo) GetUserCommand(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error) {
collection := u.coll.Database().Collection("userCommands") collection := u.coll.Database().Collection("userCommands")
filter := bson.M{"userID": userID, "type": Type} 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) { for cursor.Next(ctx) {
var document struct { var document struct {
Type int32 `bson:"type"`
UUID string `bson:"uuid"` UUID string `bson:"uuid"`
Value string `bson:"value"` Value string `bson:"value"`
CreateTime int64 `bson:"createTime"` CreateTime int64 `bson:"createTime"`
Ex string `bson:"ex"`
} }
if err := cursor.Decode(&document); err != nil { if err := cursor.Decode(&document); err != nil {
return nil, err return nil, err
} }
commandInfo := &user.CommandInfoResp{ // Change here: use a pointer to the struct commandInfo := &user.CommandInfoResp{
Type: document.Type,
Uuid: document.UUID, Uuid: document.UUID,
Value: document.Value, Value: document.Value,
CreateTime: document.CreateTime, CreateTime: document.CreateTime,
Ex: document.Ex,
} }
commands = append(commands, commandInfo) commands = append(commands, commandInfo)
@ -167,7 +196,48 @@ func (u *UserMgo) GetUserCommand(ctx context.Context, userID string, Type int32)
return commands, nil 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) { func (u *UserMgo) CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) {
pipeline := bson.A{ pipeline := bson.A{
bson.M{ bson.M{

@ -57,10 +57,6 @@ type FriendModelInterface interface {
FindInWhoseFriends(ctx context.Context, friendUserID string, pagination pagination.Pagination) (total int64, friends []*FriendModel, err error) 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 retrieves a list of friend user IDs for a given owner.
FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error) FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error)
// UpdatePinStatus update friend's pin status // UpdateFriends update friends' fields
UpdatePinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error) UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) (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)
} }

@ -56,6 +56,7 @@ type UserModelInterface interface {
TakeNotification(ctx context.Context, level int64) (user []*UserModel, err error) TakeNotification(ctx context.Context, level int64) (user []*UserModel, err error)
TakeByNickname(ctx context.Context, nickname string) (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) 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) Exist(ctx context.Context, userID string) (exist bool, err error)
GetAllUserID(ctx context.Context, pagination pagination.Pagination) (count int64, userIDs []string, 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) 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) CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error)
//CRUD user command //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 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) GetUserCommand(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error)
GetAllUserCommand(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error)
} }

@ -78,8 +78,8 @@ func buildMongoURI() string {
return config.Config.Mongo.Uri return config.Config.Mongo.Uri
} }
username := os.Getenv("MONGO_USERNAME") username := os.Getenv("MONGO_OPENIM_USERNAME")
password := os.Getenv("MONGO_PASSWORD") password := os.Getenv("MONGO_OPENIM_PASSWORD")
address := os.Getenv("MONGO_ADDRESS") address := os.Getenv("MONGO_ADDRESS")
port := os.Getenv("MONGO_PORT") port := os.Getenv("MONGO_PORT")
database := os.Getenv("MONGO_DATABASE") database := os.Getenv("MONGO_DATABASE")
@ -103,9 +103,9 @@ func buildMongoURI() string {
maxPoolSize = fmt.Sprint(config.Config.Mongo.MaxPoolSize) maxPoolSize = fmt.Sprint(config.Config.Mongo.MaxPoolSize)
} }
uriFormat := "mongodb://%s/%s?maxPoolSize=%s&authSource=admin" uriFormat := "mongodb://%s/%s?maxPoolSize=%s"
if username != "" && password != "" { if username != "" && password != "" {
uriFormat = "mongodb://%s:%s@%s/%s?maxPoolSize=%s&authSource=admin" uriFormat = "mongodb://%s:%s@%s/%s?maxPoolSize=%s"
return fmt.Sprintf(uriFormat, username, password, address, database, maxPoolSize) return fmt.Sprintf(uriFormat, username, password, address, database, maxPoolSize)
} }
return fmt.Sprintf(uriFormat, address, database, maxPoolSize) return fmt.Sprintf(uriFormat, address, database, maxPoolSize)

@ -52,6 +52,7 @@ func GetChatConversationIDByMsg(msg *sdkws.MsgData) string {
case constant.NotificationChatType: case constant.NotificationChatType:
return "sn_" + msg.SendID + "_" + msg.RecvID return "sn_" + msg.SendID + "_" + msg.RecvID
} }
return "" return ""
} }

@ -37,6 +37,7 @@ func NewOptions(opts ...OptionsOpt) Options {
for _, opt := range opts { for _, opt := range opts {
opt(options) opt(options)
} }
return options return options
} }

@ -197,7 +197,7 @@ func (f *FriendNotificationSender) FriendRemarkSetNotification(ctx context.Conte
return f.Notification(ctx, fromUserID, toUserID, constant.FriendRemarkSetNotification, &tips) return f.Notification(ctx, fromUserID, toUserID, constant.FriendRemarkSetNotification, &tips)
} }
func (f *FriendNotificationSender) FriendsInfoUpdateNotification(ctx context.Context, toUserID string, friendIDs []string) error { 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.FromToUserID.ToUserID = toUserID
tips.FriendIDs = friendIDs tips.FriendIDs = friendIDs
return f.Notification(ctx, toUserID, toUserID, constant.FriendsInfoUpdateNotification, &tips) return f.Notification(ctx, toUserID, toUserID, constant.FriendsInfoUpdateNotification, &tips)

@ -409,11 +409,16 @@ func (g *GroupNotificationSender) GroupApplicationAcceptedNotification(ctx conte
if err != nil { if err != nil {
return err return err
} }
tips := &sdkws.GroupApplicationAcceptedTips{Group: group, HandleMsg: req.HandledMsg, ReceiverAs: 1} tips := &sdkws.GroupApplicationAcceptedTips{Group: group, HandleMsg: req.HandledMsg}
if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return err return err
} }
for _, userID := range append(userIDs, mcontext.GetOpUserID(ctx)) { for _, userID := range append(userIDs, req.FromUserID) {
if userID == req.FromUserID {
tips.ReceiverAs = 0
} else {
tips.ReceiverAs = 1
}
err = g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationAcceptedNotification, tips) err = g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationAcceptedNotification, tips)
if err != nil { if err != nil {
log.ZError(ctx, "failed", err) log.ZError(ctx, "failed", err)
@ -441,7 +446,12 @@ func (g *GroupNotificationSender) GroupApplicationRejectedNotification(ctx conte
if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return err return err
} }
for _, userID := range append(userIDs, mcontext.GetOpUserID(ctx)) { for _, userID := range append(userIDs, req.FromUserID) {
if userID == req.FromUserID {
tips.ReceiverAs = 0
} else {
tips.ReceiverAs = 1
}
err = g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationRejectedNotification, tips) err = g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationRejectedNotification, tips)
if err != nil { if err != nil {
log.ZError(ctx, "failed", err) log.ZError(ctx, "failed", err)

@ -103,3 +103,21 @@ func (u *UserNotificationSender) UserStatusChangeNotification(
) error { ) error {
return u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserStatusChangeNotification, tips) 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)
}

@ -36,4 +36,4 @@ sleep 5
"${OPENIM_ROOT}"/scripts/check-all.sh "${OPENIM_ROOT}"/scripts/check-all.sh
tail -f ${LOG_FILE} tail -f ${LOG_FILE}

@ -29,16 +29,8 @@ if [ $# -ne 2 ];then
exit 1 exit 1
fi fi
openim::util::require-dig if [ -z "${OPENIM_IP}" ]; then
result=$? openim::util::require-dig
if [ $result -ne 0 ]; then
openim::log::info "Please install 'dig' to use this feature."
openim::log::info "Installation instructions:"
openim::log::info " For Ubuntu/Debian: sudo apt-get install dnsutils"
openim::log::info " For CentOS/RedHat: sudo yum install bind-utils"
openim::log::info " For macOS: 'dig' should be preinstalled. If missing, try: brew install bind"
openim::log::info " For Windows: Install BIND9 tools from https://www.isc.org/download/"
openim::log::error_exit "Error: 'dig' command is required but not installed."
fi fi
source "${env_file}" source "${env_file}"

@ -1,5 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Copyright © 2023 OpenIM. All rights reserved. # Copyright © 2024 OpenIM. All rights reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); # Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License. # you may not use this file except in compliance with the License.
@ -13,8 +13,20 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
#
# OpenIM Docker Deployment Script
#
# This script automates the process of building the OpenIM server image
# and deploying it using Docker Compose.
#
# Variables:
# - SERVER_IMAGE_VERSION: Version of the server image (default: test)
# - IMAGE_REGISTRY: Docker image registry (default: openim)
# - DOCKER_COMPOSE_FILE_URL: URL to the docker-compose.yml file
#
# Usage:
# SERVER_IMAGE_VERSION=latest IMAGE_REGISTRY=myregistry ./this_script.sh
# Common utilities, variables and checks for all build scripts.
set -o errexit set -o errexit
set -o nounset set -o nounset
set -o pipefail set -o pipefail
@ -28,35 +40,46 @@ chmod +x "${OPENIM_ROOT}"/scripts/*.sh
openim::util::ensure_docker_daemon_connectivity openim::util::ensure_docker_daemon_connectivity
# Default values for variables
: ${SERVER_IMAGE_VERSION:=test}
: ${IMAGE_REGISTRY:=openim}
: ${DOCKER_COMPOSE_FILE_URL:="https://raw.githubusercontent.com/openimsdk/openim-docker/main/docker-compose.yaml"}
DOCKER_COMPOSE_COMMAND= DOCKER_COMPOSE_COMMAND=
# Check if docker-compose command is available # Check if docker-compose command is available
openim::util::check_docker_and_compose_versions openim::util::check_docker_and_compose_versions
if command -v docker compose &> /dev/null; then
if command -v docker compose &> /dev/null
then
openim::log::info "docker compose command is available" openim::log::info "docker compose command is available"
DOCKER_COMPOSE_COMMAND="docker compose" DOCKER_COMPOSE_COMMAND="docker compose"
else else
DOCKER_COMPOSE_COMMAND="docker-compose" DOCKER_COMPOSE_COMMAND="docker-compose"
fi fi
export SERVER_IMAGE_VERSION
export IMAGE_REGISTRY
"${OPENIM_ROOT}"/scripts/init-config.sh "${OPENIM_ROOT}"/scripts/init-config.sh
pushd "${OPENIM_ROOT}" pushd "${OPENIM_ROOT}"
docker build -t "${IMAGE_REGISTRY}/openim-server:${SERVER_IMAGE_VERSION}" .
${DOCKER_COMPOSE_COMMAND} stop ${DOCKER_COMPOSE_COMMAND} stop
curl https://raw.githubusercontent.com/openimsdk/openim-docker/main/docker-compose.yaml -o docker-compose.yml curl "${DOCKER_COMPOSE_FILE_URL}" -o docker-compose.yml
${DOCKER_COMPOSE_COMMAND} up -d ${DOCKER_COMPOSE_COMMAND} up -d
# Function to check container status
check_containers() {
if ! ${DOCKER_COMPOSE_COMMAND} ps | grep -q 'Up'; then
echo "Error: One or more docker containers failed to start."
${DOCKER_COMPOSE_COMMAND} logs
return 1
fi
return 0
}
# Wait for a short period to allow containers to initialize # Wait for a short period to allow containers to initialize
sleep 30 sleep 30
check_containers
# Check the status of the containers
if ! ${DOCKER_COMPOSE_COMMAND} ps | grep -q 'Up'; then
echo "Error: One or more docker containers failed to start."
${DOCKER_COMPOSE_COMMAND} logs
fi
sleep 30 # Keep the original 60-second wait, adjusted for the 10-second check above
${DOCKER_COMPOSE_COMMAND} logs openim-server ${DOCKER_COMPOSE_COMMAND} logs openim-server
${DOCKER_COMPOSE_COMMAND} ps ${DOCKER_COMPOSE_COMMAND} ps
popd popd

@ -35,8 +35,8 @@ docker run -d \
-e MONGO_INITDB_ROOT_USERNAME=${OPENIM_USER} \ -e MONGO_INITDB_ROOT_USERNAME=${OPENIM_USER} \
-e MONGO_INITDB_ROOT_PASSWORD=${PASSWORD} \ -e MONGO_INITDB_ROOT_PASSWORD=${PASSWORD} \
-e MONGO_INITDB_DATABASE=openIM \ -e MONGO_INITDB_DATABASE=openIM \
-e MONGO_USERNAME=${OPENIM_USER} \ -e MONGO_OPENIM_USERNAME=${OPENIM_USER} \
-e MONGO_PASSWORD=${PASSWORD} \ -e MONGO_OPENIM_PASSWORD=${PASSWORD} \
--restart always \ --restart always \
mongo:6.0.2 --wiredTigerCacheSizeGB 1 --auth mongo:6.0.2 --wiredTigerCacheSizeGB 1 --auth

@ -171,9 +171,14 @@ def "MONGO_URI" # MongoDB的URI
def "MONGO_PORT" "37017" # MongoDB的端口 def "MONGO_PORT" "37017" # MongoDB的端口
def "MONGO_ADDRESS" "${DOCKER_BRIDGE_GATEWAY}" # MongoDB的地址 def "MONGO_ADDRESS" "${DOCKER_BRIDGE_GATEWAY}" # MongoDB的地址
def "MONGO_DATABASE" "${DATABASE_NAME}" # MongoDB的数据库名 def "MONGO_DATABASE" "${DATABASE_NAME}" # MongoDB的数据库名
def "MONGO_USERNAME" "${OPENIM_USER}" # MongoDB的用户名 def "MONGO_USERNAME" "root" # MongoDB的管理员身份用户名
# MongoDB的密码 # MongoDB的管理员身份密码
readonly MONGO_PASSWORD=${MONGO_PASSWORD:-"${PASSWORD}"} readonly MONGO_PASSWORD=${MONGO_PASSWORD:-"${PASSWORD}"}
# Mongo OpenIM 身份用户名
def "MONGO_OPENIM_USERNAME" "openIM"
# Mongo OpenIM 身份密码
readonly MONGO_OPENIM_PASSWORD=${MONGO_OPENIM_PASSWORD:-"${PASSWORD}"}
def "MONGO_MAX_POOL_SIZE" "100" # 最大连接池大小 def "MONGO_MAX_POOL_SIZE" "100" # 最大连接池大小
###################### Object 配置信息 ###################### ###################### Object 配置信息 ######################

@ -1146,8 +1146,13 @@ function openim::util::require-jq {
# Checks whether dig is installed and provides installation instructions if it is not. # Checks whether dig is installed and provides installation instructions if it is not.
function openim::util::require-dig { function openim::util::require-dig {
if ! command -v dig &>/dev/null; then if ! command -v dig &>/dev/null; then
openim::log::error "dig command not found." openim::log::error "Please install 'dig' to use this feature."
return 1 openim::log::error "Installation instructions:"
openim::log::error " For Ubuntu/Debian: sudo apt-get install dnsutils"
openim::log::error " For CentOS/RedHat: sudo yum install bind-utils"
openim::log::error " For macOS: 'dig' should be preinstalled. If missing, try: brew install bind"
openim::log::error " For Windows: Install BIND9 tools from https://www.isc.org/download/"
openim::log::error_exit "dig command not found."
fi fi
return 0 return 0
} }

@ -129,7 +129,7 @@ FIND := find . ! -path './utils/*' ! -path './vendor/*' ! -path './third_party/*
XARGS := xargs -r --no-run-if-empty XARGS := xargs -r --no-run-if-empty
# Linux command settings-CODE DIRS Copyright # Linux command settings-CODE DIRS Copyright
CODE_DIRS := $(ROOT_DIR)/pkg $(ROOT_DIR)/cmd $(ROOT_DIR)/config $(ROOT_DIR)/.docker-compose_cfg $(ROOT_DIR)/internal $(ROOT_DIR)/scripts $(ROOT_DIR)/test $(ROOT_DIR)/.github $(ROOT_DIR)/build $(ROOT_DIR)/tools $(ROOT_DIR)/deployments CODE_DIRS := $(ROOT_DIR)/pkg $(ROOT_DIR)/cmd $(ROOT_DIR)/config $(ROOT_DIR)/internal $(ROOT_DIR)/scripts $(ROOT_DIR)/test $(ROOT_DIR)/.github $(ROOT_DIR)/build $(ROOT_DIR)/tools $(ROOT_DIR)/deployments
FINDS := find $(CODE_DIRS) FINDS := find $(CODE_DIRS)
# Makefile settings: Select different behaviors by determining whether V option is set # Makefile settings: Select different behaviors by determining whether V option is set

@ -12,15 +12,19 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
mongo -- "$MONGO_INITDB_DATABASE" <<EOF set -e
db = db.getSiblingDB('admin')
mongosh <<EOF
use admin
db.auth('$MONGO_INITDB_ROOT_USERNAME', '$MONGO_INITDB_ROOT_PASSWORD') db.auth('$MONGO_INITDB_ROOT_USERNAME', '$MONGO_INITDB_ROOT_PASSWORD')
db = db.getSiblingDB('$MONGO_INITDB_DATABASE') db = db.getSiblingDB('$MONGO_INITDB_DATABASE')
db.createUser({ db.createUser({
user: "$MONGO_USERNAME", user: "$MONGO_OPENIM_USERNAME",
pwd: "$MONGO_PASSWORD", pwd: "$MONGO_OPENIM_PASSWORD",
roles: [ roles: [
{ role: 'root', db: '$MONGO_INITDB_DATABASE' } // Assign appropriate roles here
{ role: 'readWrite', db: '$MONGO_INITDB_DATABASE' }
] ]
}) });
EOF EOF

@ -25,10 +25,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/minio/minio-go/v7"
"github.com/redis/go-redis/v9"
"gopkg.in/yaml.v3"
"github.com/IBM/sarama" "github.com/IBM/sarama"
"github.com/OpenIMSDK/tools/errs" "github.com/OpenIMSDK/tools/errs"
"github.com/go-zookeeper/zk" "github.com/go-zookeeper/zk"
@ -38,6 +34,9 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/minio/minio-go/v7/pkg/credentials" "github.com/minio/minio-go/v7/pkg/credentials"
"github.com/minio/minio-go/v7"
"github.com/redis/go-redis/v9"
"gopkg.in/yaml.v3"
) )
const ( const (
@ -172,10 +171,10 @@ func buildMongoURI() string {
mongodbHosts := strings.Join(config.Config.Mongo.Address, ",") mongodbHosts := strings.Join(config.Config.Mongo.Address, ",")
if username != "" && password != "" { if username != "" && password != "" {
return fmt.Sprintf("mongodb://%s:%s@%s/%s?maxPoolSize=%d&authSource=admin", return fmt.Sprintf("mongodb://%s:%s@%s/%s?maxPoolSize=%d",
username, password, mongodbHosts, database, maxPoolSize) username, password, mongodbHosts, database, maxPoolSize)
} }
return fmt.Sprintf("mongodb://%s/%s?maxPoolSize=%d&authSource=admin", return fmt.Sprintf("mongodb://%s/%s?maxPoolSize=%d",
mongodbHosts, database, maxPoolSize) mongodbHosts, database, maxPoolSize)
} }

Loading…
Cancel
Save