mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-04-28 14:29:19 +08:00
Compare commits
124 Commits
changelog-v3.8.3
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| d16a617ba8 | |||
| 942d155d2d | |||
| b7200c163c | |||
| 579db3bd48 | |||
| a0e6d9aa69 | |||
| fbca49d431 | |||
| 0a93fb1b6d | |||
| 78b255396f | |||
| 6f33c0a515 | |||
| c97d63754b | |||
| 1b8a3b0b75 | |||
| b8c4b459fa | |||
| 07badb162f | |||
| 3a1c8df3b9 | |||
| 0e3879aad6 | |||
| 390d253cea | |||
| cbd29a71de | |||
| ea6b7eb525 | |||
| b36f00f2ad | |||
| 7d6682ca4b | |||
| 11358404f9 | |||
| 277da378ea | |||
| bf0289075b | |||
| 219fb04f03 | |||
| 6856a864d0 | |||
| 349a8cd9af | |||
| 929c6704f3 | |||
| 68a735ba99 | |||
| 123abe9803 | |||
| 34971c8b96 | |||
| d542df7000 | |||
| d817cb0ffd | |||
| c208c3789f | |||
| 2d4cf99744 | |||
| b44e56b151 | |||
| b19b81b10e | |||
| 80de08e8ed | |||
| 4c9fdf70db | |||
| d6cd0258a5 | |||
| becc999d63 | |||
| f9475bdd87 | |||
| 839d7d90ac | |||
| 359201a005 | |||
| 865eaa719f | |||
| 097e033347 | |||
| 3ad24d9521 | |||
| 230c93cc16 | |||
| 4ae05d2e38 | |||
| b1659ef84d | |||
| 5e813ef079 | |||
| bd56bd23b8 | |||
| a5ac7f2a81 | |||
| 6912ad6f33 | |||
| 53bf8acc21 | |||
| 8f7b02979d | |||
| 1baf9a8e0f | |||
| 1e2375faca | |||
| 545125884e | |||
| d156e1e519 | |||
| 75367545ea | |||
| 04ee509b68 | |||
| b7ca3bd95f | |||
| 812c1e4127 | |||
| 8e61f30e9c | |||
| 4d3ec5367f | |||
| 6ae00dfab9 | |||
| 4d69194f62 | |||
| 8b23d4f5bb | |||
| 748d783d36 | |||
| 632a65303c | |||
| 1d7660bedb | |||
| 1178808ba7 | |||
| 56c5c1f015 | |||
| dd981b976d | |||
| 91db18168d | |||
| bed81af75c | |||
| 986237af2e | |||
| c54b948f1a | |||
| fa3d251dcb | |||
| 58994095a5 | |||
| 97b8c07d57 | |||
| 045afd74b9 | |||
| 8e824c7e8e | |||
| 52bd5e8be9 | |||
| 1697651100 | |||
| d385fdd0aa | |||
| aca0eac955 | |||
| 3b710fdfdb | |||
| 4dc9b45861 | |||
| 73934fd955 | |||
| 11044eac58 | |||
| b969827b9a | |||
| 0b9dbd301c | |||
| 3516f843db | |||
| e76e02fdba | |||
| c0c4ba21a8 | |||
| 7859f2ccb3 | |||
| 964ee7a8dd | |||
| 0541d0bf06 | |||
| 737169a466 | |||
| 68ee86c1d9 | |||
| ab6c9dca71 | |||
| 887e0b7314 | |||
| 1df02692bf | |||
| 4d2fce0e52 | |||
| b7f88138bd | |||
| bf6d77a267 | |||
| 46f1a9c7a3 | |||
| df1c8df693 | |||
| 14393b0f53 | |||
| 9ed6200e45 | |||
| e37ea50b94 | |||
| a3c43a49d8 | |||
| 489571f4b6 | |||
| ad8829c5a6 | |||
| 0bf076bb05 | |||
| ed416f8376 | |||
| 86aa26ef62 | |||
| d1b4d84628 | |||
| 274a9bee65 | |||
| 83c7b7134c | |||
| bbb5473d26 | |||
| 96baa5a0ff | |||
| 47e916aebe |
@@ -1,8 +1,8 @@
|
||||
MONGO_IMAGE=mongo:7.0
|
||||
REDIS_IMAGE=redis:7.0.0
|
||||
KAFKA_IMAGE=bitnami/kafka:3.5.1
|
||||
KAFKA_IMAGE=bitnamilegacy/kafka:3.5.1
|
||||
MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z
|
||||
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.13
|
||||
ETCD_IMAGE=bitnamilegacy/etcd:3.5.13
|
||||
PROMETHEUS_IMAGE=prom/prometheus:v2.45.6
|
||||
ALERTMANAGER_IMAGE=prom/alertmanager:v0.27.0
|
||||
GRAFANA_IMAGE=grafana/grafana:11.0.1
|
||||
@@ -17,6 +17,8 @@ OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.8.4
|
||||
|
||||
DATA_DIR=./
|
||||
|
||||
MONGO_BACKUP_DIR=${DATA_DIR}components/backup/mongo/
|
||||
|
||||
PROMETHEUS_PORT=19091
|
||||
ALERTMANAGER_PORT=19093
|
||||
GRAFANA_PORT=13000
|
||||
@@ -4,7 +4,7 @@ contact_links:
|
||||
# description: "Report a bug in the project"
|
||||
# file: "bug-report.yml"
|
||||
- name: 📢 Connect on slack
|
||||
url: https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg
|
||||
url: https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A
|
||||
about: Support OpenIM-related requests or issues, get in touch with developers and help on slack
|
||||
- name: 🌐 OpenIM Blog
|
||||
url: https://www.openim.io/
|
||||
|
||||
@@ -4,7 +4,6 @@ title: "[Other]: <give this problem a name>"
|
||||
labels: ["other"]
|
||||
# assignees: []
|
||||
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
@@ -26,5 +25,5 @@ body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
You can also join our Discord community [here](https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg)
|
||||
You can also join our Discord community [here](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
Feel free to check out other cool repositories of the openim Community [here](https://github.com/openimsdk)
|
||||
|
||||
@@ -11,7 +11,6 @@ jobs:
|
||||
permissions:
|
||||
issues: write
|
||||
steps:
|
||||
|
||||
- name: Invite user to join OpenIM Community
|
||||
uses: peter-evans/create-or-update-comment@v4
|
||||
with:
|
||||
@@ -20,11 +19,11 @@ jobs:
|
||||
body: |
|
||||
We value close connections with our users, developers, and contributors here at Open-IM-Server. With a large community and maintainer team, we're always here to help and support you. Whether you're looking to join our community or have any questions or suggestions, we welcome you to get in touch with us.
|
||||
|
||||
Our most recommended way to get in touch is through [Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q). Even if you're in China, Slack is usually not blocked by firewalls, making it an easy way to connect with us. Our Slack community is the ideal place to discuss and share ideas and suggestions with other users and developers of Open-IM-Server. You can ask technical questions, seek help, or share your experiences with other users of Open-IM-Server.
|
||||
|
||||
Our most recommended way to get in touch is through [Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A). Even if you're in China, Slack is usually not blocked by firewalls, making it an easy way to connect with us. Our Slack community is the ideal place to discuss and share ideas and suggestions with other users and developers of Open-IM-Server. You can ask technical questions, seek help, or share your experiences with other users of Open-IM-Server.
|
||||
|
||||
In addition to Slack, we also offer the following ways to get in touch:
|
||||
|
||||
+ <a href="https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q" target="_blank"><img src="https://img.shields.io/badge/Slack-OpenIM%2B-blueviolet?logo=slack&logoColor=white"></a> We also have Slack channels for you to communicate and discuss. To join, visit https://slack.com/ and join our [👀 Open-IM-Server slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) team channel.
|
||||
|
||||
+ <a href="https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A" target="_blank"><img src="https://img.shields.io/badge/Slack-OpenIM%2B-blueviolet?logo=slack&logoColor=white"></a> We also have Slack channels for you to communicate and discuss. To join, visit https://slack.com/ and join our [👀 Open-IM-Server slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) team channel.
|
||||
+ <a href="https://mail.google.com/mail/u/0/?fs=1&tf=cm&to=info@openim.io" target="_blank"><img src="https://img.shields.io/badge/gmail-%40OOpenIMSDKCore?style=social&logo=gmail"></a> Get in touch with us on [Gmail](https://mail.google.com/mail/u/0/?fs=1&tf=cm&to=winxu81@gmail.com). If you have any questions or issues that need resolving, or any suggestions and feedback for our open source projects, please feel free to contact us via email.
|
||||
+ <a href="https://doc.rentsoft.cn/" target="_blank"><img src="https://img.shields.io/badge/%E5%8D%9A%E5%AE%A2-%40OpenIMSDKCore-blue?style=social&logo=Octopus%20Deploy"></a> Read our [blog](https://doc.rentsoft.cn/). Our blog is a great place to stay up-to-date with Open-IM-Server projects and trends. On the blog, we share our latest developments, tech trends, and other interesting information.
|
||||
+ <a href="https://github.com/OpenIMSDK/OpenIM-Docs/blob/main/docs/images/WechatIMG20.jpeg" target="_blank"><img src="https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1-OpenIMSDKCore-brightgreen?logo=wechat&style=flat-square"></a> Add [Wechat](https://github.com/OpenIMSDK/OpenIM-Docs/blob/main/docs/images/WechatIMG20.jpeg) and indicate that you are a user or developer of Open-IM-Server. We will process your request as soon as possible.
|
||||
@@ -36,4 +35,4 @@ jobs:
|
||||
# issue-number: ${{ github.event.issue.number }}
|
||||
# comment: 🤖 Auto-closing issue, if you still need help please reopen the issue or ask for help in the community above
|
||||
# labels: |
|
||||
# accepted
|
||||
# accepted
|
||||
|
||||
@@ -19,26 +19,26 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
uses: docker/setup-buildx-action@v3.8.0
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log in to Aliyun Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: registry.cn-hangzhou.aliyuncs.com
|
||||
username: ${{ secrets.ALIREGISTRY_USERNAME }}
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
|
||||
- name: Extract metadata for Docker (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
uses: docker/metadata-action@v5.6.0
|
||||
with:
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
@@ -54,7 +54,6 @@ jobs:
|
||||
type=ref,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern=v{{version}}
|
||||
# type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern=release-{{raw}}
|
||||
type=sha
|
||||
type=raw,value=${{ github.event.inputs.tag }}
|
||||
|
||||
@@ -4,7 +4,7 @@ on:
|
||||
push:
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
- "**/*.md"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -12,13 +12,17 @@ jobs:
|
||||
go-build:
|
||||
name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
env:
|
||||
SHARE_CONFIG_PATH: config/share.yml
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
go_version: ["1.21.x", "1.22.x"]
|
||||
go_version: ["1.22.x"]
|
||||
|
||||
steps:
|
||||
- name: Checkout Server repository
|
||||
@@ -37,27 +41,24 @@ jobs:
|
||||
|
||||
- name: Set up infra services
|
||||
uses: hoverkraft-tech/compose-action@v2.0.1
|
||||
# Uncomment and set the correct path to your docker-compose file
|
||||
with:
|
||||
compose-file: "./docker-compose.yml"
|
||||
|
||||
# run: |
|
||||
# sudo docker compose up -d
|
||||
# sudo sleep 30 # Increased sleep time for better stability
|
||||
# timeout-minutes: 60 # Increased timeout for Docker setup
|
||||
- name: Modify Server Configuration
|
||||
run: |
|
||||
yq e '.secret = 123456' -i ${{ env.SHARE_CONFIG_PATH }}
|
||||
|
||||
# - name: Get Internal IP Address
|
||||
# id: get-ip
|
||||
# run: |
|
||||
# IP=$(hostname -I | awk '{print $1}')
|
||||
# echo "The IP Address is: $IP"
|
||||
# echo "::set-output name=ip::$IP"
|
||||
|
||||
# - name: Get Internal IP Address
|
||||
# id: get-ip
|
||||
# run: |
|
||||
# IP=$(hostname -I | awk '{print $1}')
|
||||
# echo "The IP Address is: $IP"
|
||||
# echo "::set-output name=ip::$IP"
|
||||
|
||||
# - name: Update .env
|
||||
# run: |
|
||||
# sed -i 's|externalAddress:.*|externalAddress: "http://${{ steps.get-ip.outputs.ip }}:10005"|' config/minio.yml
|
||||
# cat config/minio.yml
|
||||
# - name: Update .env
|
||||
# run: |
|
||||
# sed -i 's|externalAddress:.*|externalAddress: "http://${{ steps.get-ip.outputs.ip }}:10005"|' config/minio.yml
|
||||
# cat config/minio.yml
|
||||
|
||||
- name: Build and test Server Services
|
||||
run: |
|
||||
@@ -78,6 +79,11 @@ jobs:
|
||||
go mod download
|
||||
go install github.com/magefile/mage@latest
|
||||
|
||||
- name: Modify Chat Configuration
|
||||
run: |
|
||||
cd ${{ github.workspace }}/chat-repo
|
||||
yq e '.openIM.secret = 123456' -i ${{ env.SHARE_CONFIG_PATH }}
|
||||
|
||||
- name: Build and test Chat Services
|
||||
run: |
|
||||
cd ${{ github.workspace }}/chat-repo
|
||||
@@ -85,6 +91,90 @@ jobs:
|
||||
mage start
|
||||
mage check
|
||||
|
||||
- name: Test Server and Chat
|
||||
run: |
|
||||
check_error() {
|
||||
echo "Response: $1"
|
||||
errCode=$(echo $1 | jq -r '.errCode')
|
||||
if [ "$errCode" != "0" ]; then
|
||||
errMsg=$(echo $1 | jq -r '.errMsg')
|
||||
echo "Error: $errMsg"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Test register
|
||||
response1=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -d '{
|
||||
"verifyCode": "666666",
|
||||
"platform": 3,
|
||||
"autoLogin": true,
|
||||
"user":{
|
||||
"nickname": "test12312",
|
||||
"areaCode":"+86",
|
||||
"phoneNumber": "12345678190",
|
||||
"password":"test123456"
|
||||
}
|
||||
}' http://127.0.0.1:10008/account/register)
|
||||
check_error "$response1"
|
||||
userID1=$(echo $response1 | jq -r '.data.userID')
|
||||
echo "userID1: $userID1"
|
||||
|
||||
response2=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -d '{
|
||||
"verifyCode": "666666",
|
||||
"platform": 3,
|
||||
"autoLogin": true,
|
||||
"user":{
|
||||
"nickname": "test22312",
|
||||
"areaCode":"+86",
|
||||
"phoneNumber": "12345678290",
|
||||
"password":"test123456"
|
||||
}
|
||||
}' http://127.0.0.1:10008/account/register)
|
||||
check_error "$response2"
|
||||
userID2=$(echo $response2 | jq -r '.data.userID')
|
||||
echo "userID2: $userID2"
|
||||
|
||||
# Test login
|
||||
login_response=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -d '{
|
||||
"platform": 3,
|
||||
"areaCode":"+86",
|
||||
"phoneNumber": "12345678190",
|
||||
"password":"test123456"
|
||||
}' http://localhost:10008/account/login)
|
||||
check_error "$login_response"
|
||||
|
||||
# Test get admin token
|
||||
get_admin_token_response=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -d '{
|
||||
"secret": "123456",
|
||||
"platformID": 2,
|
||||
"userID": "imAdmin"
|
||||
}' http://127.0.0.1:10002/auth/get_admin_token)
|
||||
check_error "$get_admin_token_response"
|
||||
adminToken=$(echo $get_admin_token_response | jq -r '.data.token')
|
||||
echo "adminToken: $adminToken"
|
||||
|
||||
# Test send message
|
||||
send_msg_response=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -H "token: $adminToken" -d '{
|
||||
"sendID": "'$userID1'",
|
||||
"recvID": "'$userID2'",
|
||||
"senderPlatformID": 3,
|
||||
"content": {
|
||||
"content": "hello!!"
|
||||
},
|
||||
"contentType": 101,
|
||||
"sessionType": 1
|
||||
}' http://127.0.0.1:10002/msg/send_msg)
|
||||
check_error "$send_msg_response"
|
||||
|
||||
# Test get users
|
||||
get_users_response=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -H "token: $adminToken" -d '{
|
||||
"pagination": {
|
||||
"pageNumber": 1,
|
||||
"showNumber": 100
|
||||
}
|
||||
}' http://127.0.0.1:10002/user/get_users)
|
||||
check_error "$get_users_response"
|
||||
|
||||
go-test:
|
||||
name: Benchmark Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
@@ -92,12 +182,13 @@ jobs:
|
||||
contents: write
|
||||
env:
|
||||
SDK_DIR: openim-sdk-core
|
||||
CONFIG_PATH: config/notification.yml
|
||||
# pull-requests: write
|
||||
NOTIFICATION_CONFIG_PATH: config/notification.yml
|
||||
SHARE_CONFIG_PATH: config/share.yml
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
go_version: [ "1.22.x" ]
|
||||
os: [ubuntu-latest]
|
||||
go_version: ["1.22.x"]
|
||||
|
||||
steps:
|
||||
- name: Checkout Server repository
|
||||
@@ -106,7 +197,8 @@ jobs:
|
||||
- name: Checkout SDK repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'openimsdk/openim-sdk-core'
|
||||
repository: "openimsdk/openim-sdk-core"
|
||||
ref: "main"
|
||||
path: ${{ env.SDK_DIR }}
|
||||
|
||||
- name: Set up Go ${{ matrix.go_version }}
|
||||
@@ -119,15 +211,11 @@ jobs:
|
||||
go install github.com/magefile/mage@latest
|
||||
go mod download
|
||||
|
||||
- name: Install yq
|
||||
run: |
|
||||
sudo wget https://github.com/mikefarah/yq/releases/download/v4.34.1/yq_linux_amd64 -O /usr/bin/yq
|
||||
sudo chmod +x /usr/bin/yq
|
||||
|
||||
- name: Modify Server Configuration
|
||||
run: |
|
||||
yq e '.groupCreated.isSendMsg = true' -i ${{ env.CONFIG_PATH }}
|
||||
yq e '.friendApplicationApproved.isSendMsg = true' -i ${{ env.CONFIG_PATH }}
|
||||
yq e '.groupCreated.isSendMsg = true' -i ${{ env.NOTIFICATION_CONFIG_PATH }}
|
||||
yq e '.friendApplicationApproved.isSendMsg = true' -i ${{ env.NOTIFICATION_CONFIG_PATH }}
|
||||
yq e '.secret = 123456' -i ${{ env.SHARE_CONFIG_PATH }}
|
||||
|
||||
- name: Start Server Services
|
||||
run: |
|
||||
@@ -183,11 +271,3 @@ jobs:
|
||||
run: |
|
||||
CONTAINER_NAME="${{ github.event.repository.name }}-container"
|
||||
docker logs $CONTAINER_NAME
|
||||
|
||||
# - name: Cleanup Docker Container
|
||||
# run: |
|
||||
# CONTAINER_NAME="${{ github.event.repository.name }}-container"
|
||||
# IMAGE_NAME="${{ github.event.repository.name }}-test"
|
||||
# docker stop $CONTAINER_NAME
|
||||
# docker rm $CONTAINER_NAME
|
||||
# docker rmi $IMAGE_NAME
|
||||
|
||||
@@ -32,5 +32,5 @@ jobs:
|
||||
token: ${{ secrets.BOT_TOKEN }}
|
||||
body: |
|
||||
This issue is available for anyone to work on. **Make sure to reference this issue in your pull request.** :sparkles: Thank you for your contribution! :sparkles:
|
||||
[Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) to connect and communicate with our developers.
|
||||
[Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) to connect and communicate with our developers.
|
||||
If you wish to accept this assignment, please leave a comment in the comments section: `/accept`.🎯
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
name: Create Pre-Release PR from Milestone
|
||||
name: Create Individual PRs from Milestone
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
@@ -9,24 +9,24 @@ on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
milestone_name:
|
||||
description: 'Milestone name to collect closed PRs from'
|
||||
description: "Milestone name to collect closed PRs from"
|
||||
required: true
|
||||
default: 'v3.8.2'
|
||||
default: "v3.8.4"
|
||||
target_branch:
|
||||
description: 'Target branch to merge the consolidated PR'
|
||||
description: "Target branch to merge the consolidated PR"
|
||||
required: true
|
||||
default: 'pre-release-v3.8.2'
|
||||
default: "pre-release-v3.8.4"
|
||||
|
||||
env:
|
||||
MILESTONE_NAME: ${{ github.event.inputs.milestone_name || 'v3.8.2' }}
|
||||
TARGET_BRANCH: ${{ github.event.inputs.target_branch || 'pre-release-v3.8.2' }}
|
||||
MILESTONE_NAME: ${{ github.event.inputs.milestone_name || 'v3.8.4' }}
|
||||
TARGET_BRANCH: ${{ github.event.inputs.target_branch || 'pre-release-v3.8.4' }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
LABEL_NAME: cherry-picked
|
||||
TEMP_DIR: /tmp # Using /tmp as the temporary directory
|
||||
TEMP_DIR: /tmp
|
||||
|
||||
jobs:
|
||||
cherry_pick_milestone_prs:
|
||||
merge_milestone_prs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup temp directory
|
||||
@@ -47,7 +47,6 @@ jobs:
|
||||
|
||||
- name: Setup Git User for OpenIM-Robot
|
||||
run: |
|
||||
# Set up Git credentials for the bot
|
||||
git config --global user.email "OpenIM-Robot@users.noreply.github.com"
|
||||
git config --global user.name "OpenIM-Robot"
|
||||
|
||||
@@ -83,136 +82,100 @@ jobs:
|
||||
if ! echo "$labels" | grep -q "${LABEL_NAME}"; then
|
||||
echo "PR #$pr_number does not have the 'cherry-picked' label. Adding to the list."
|
||||
echo "$pr_number" >> ${{ env.TEMP_DIR }}/pr_numbers.txt
|
||||
else
|
||||
echo "PR #$pr_number already has the 'cherry-picked' label. Skipping."
|
||||
fi
|
||||
done
|
||||
|
||||
# Sort the filtered PR numbers
|
||||
sort -n ${{ env.TEMP_DIR }}/pr_numbers.txt -o ${{ env.TEMP_DIR }}/pr_numbers.txt
|
||||
|
||||
echo "Filtered and sorted PR numbers:"
|
||||
cat ${{ env.TEMP_DIR }}/pr_numbers.txt || echo "No closed PR numbers found for milestone."
|
||||
|
||||
- name: Fetch Merge Commits for PRs and Generate Title and Body
|
||||
- name: Create Individual PRs
|
||||
run: |
|
||||
# Ensure the files are initialized
|
||||
> ${{ env.TEMP_DIR }}/commit_hashes.txt
|
||||
> ${{ env.TEMP_DIR }}/pr_title.txt
|
||||
> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
|
||||
# Write description to the PR body
|
||||
echo "### Description:" >> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
echo "Merging PRs from milestone \`$MILESTONE_NAME\` into target branch \`$TARGET_BRANCH\`." >> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
echo "" >> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
echo "### Need Merge PRs:" >> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
|
||||
pr_numbers_in_title=""
|
||||
|
||||
# Process sorted PR numbers and generate commit hashes
|
||||
for pr_number in $(cat ${{ env.TEMP_DIR }}/pr_numbers.txt); do
|
||||
echo "Processing PR #$pr_number"
|
||||
pr_details=$(curl -s -H "Authorization: token $BOT_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/pulls/$pr_number")
|
||||
pr_title=$(echo "$pr_details" | jq -r '.title')
|
||||
pr_body=$(echo "$pr_details" | jq -r '.body')
|
||||
pr_creator=$(echo "$pr_details" | jq -r '.user.login')
|
||||
merge_commit=$(echo "$pr_details" | jq -r '.merge_commit_sha')
|
||||
short_commit_hash=$(echo "$merge_commit" | cut -c 1-7)
|
||||
|
||||
# Append PR details to the body
|
||||
echo "- $pr_title: (#$pr_number) ($short_commit_hash)" >> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
if [ "$merge_commit" != "null" ]; then
|
||||
git fetch origin
|
||||
|
||||
echo "Checking out target branch: $TARGET_BRANCH"
|
||||
git checkout $TARGET_BRANCH
|
||||
|
||||
if [ "$merge_commit" != "null" ];then
|
||||
echo "$merge_commit" >> ${{ env.TEMP_DIR }}/commit_hashes.txt
|
||||
echo "#$pr_number" >> ${{ env.TEMP_DIR }}/pr_title.txt
|
||||
pr_numbers_in_title="$pr_numbers_in_title #$pr_number"
|
||||
echo "Pulling latest changes from target branch: $TARGET_BRANCH"
|
||||
git pull origin $TARGET_BRANCH
|
||||
|
||||
cherry_pick_branch="cherry-pick-${short_commit_hash}"
|
||||
git checkout -b $cherry_pick_branch
|
||||
|
||||
echo "Cherry-picking commit: $merge_commit"
|
||||
if ! git cherry-pick "$merge_commit" --strategy=recursive -X theirs; then
|
||||
echo "Conflict detected for $merge_commit. Resolving with incoming changes."
|
||||
conflict_files=$(git diff --name-only --diff-filter=U)
|
||||
echo "Conflicting files:"
|
||||
echo "$conflict_files"
|
||||
|
||||
for file in $conflict_files; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "Resolving conflict for $file"
|
||||
git add "$file"
|
||||
else
|
||||
echo "File $file has been deleted. Skipping."
|
||||
git rm "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Conflicts resolved. Continuing cherry-pick."
|
||||
git cherry-pick --continue || { echo "Cherry-pick failed, but continuing to create PR."; }
|
||||
else
|
||||
echo "Cherry-pick successful for commit $merge_commit."
|
||||
fi
|
||||
|
||||
git remote set-url origin "https://${BOT_TOKEN}@github.com/${{ github.repository }}.git"
|
||||
|
||||
echo "Pushing branch: $cherry_pick_branch"
|
||||
if ! git push origin $cherry_pick_branch --force; then
|
||||
echo "Push failed, but continuing to create PR..."
|
||||
fi
|
||||
|
||||
new_pr_title="$pr_title [Created by @$pr_creator from #$pr_number]"
|
||||
new_pr_body="$pr_body
|
||||
> This PR is created from original PR #$pr_number."
|
||||
|
||||
response=$(curl -s -X POST -H "Authorization: token $BOT_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/${{ github.repository }}/pulls \
|
||||
-d "$(jq -n --arg title "$new_pr_title" \
|
||||
--arg head "$cherry_pick_branch" \
|
||||
--arg base "$TARGET_BRANCH" \
|
||||
--arg body "$new_pr_body" \
|
||||
'{title: $title, head: $head, base: $base, body: $body}')")
|
||||
|
||||
new_pr_number=$(echo "$response" | jq -r '.number')
|
||||
|
||||
if [[ "$new_pr_number" == "null" || -z "$new_pr_number" ]]; then
|
||||
echo "Failed to create PR. Response: $response"
|
||||
|
||||
git checkout $TARGET_BRANCH
|
||||
|
||||
git branch -D $cherry_pick_branch
|
||||
|
||||
echo "Deleted branch: $cherry_pick_branch"
|
||||
git push origin --delete $cherry_pick_branch
|
||||
else
|
||||
echo "Created PR #$new_pr_number"
|
||||
|
||||
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-d '{"labels": ["milestone-merge"]}' \
|
||||
"https://api.github.com/repos/${{ github.repository }}/issues/$new_pr_number/labels"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "----------------------------------------"
|
||||
echo ""
|
||||
fi
|
||||
done
|
||||
|
||||
commit_hashes=$(cat ${{ env.TEMP_DIR }}/commit_hashes.txt | tr '\n' ' ')
|
||||
first_commit_hash=$(head -n 1 ${{ env.TEMP_DIR }}/commit_hashes.txt)
|
||||
cherry_pick_branch="cherry-pick-${first_commit_hash:0:7}"
|
||||
echo "COMMIT_HASHES=$commit_hashes" >> $GITHUB_ENV
|
||||
echo "CHERRY_PICK_BRANCH=$cherry_pick_branch" >> $GITHUB_ENV
|
||||
echo "pr_numbers_in_title=$pr_numbers_in_title" >> $GITHUB_ENV
|
||||
|
||||
- name: Pull and Cherry-pick Commits, Then Push
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
run: |
|
||||
# Fetch and pull the latest changes from the target branch
|
||||
git fetch origin
|
||||
git checkout $TARGET_BRANCH
|
||||
git pull origin $TARGET_BRANCH
|
||||
|
||||
# Create a new branch for cherry-picking
|
||||
git checkout -b $CHERRY_PICK_BRANCH
|
||||
|
||||
# Cherry-pick the commits and handle conflicts
|
||||
for commit_hash in $COMMIT_HASHES; do
|
||||
echo "Attempting to cherry-pick commit $commit_hash"
|
||||
if ! git cherry-pick "$commit_hash" --strategy=recursive -X theirs; then
|
||||
echo "Conflict detected for $commit_hash. Resolving with incoming changes."
|
||||
conflict_files=$(git diff --name-only --diff-filter=U)
|
||||
echo "Conflicting files:"
|
||||
echo "$conflict_files"
|
||||
|
||||
for file in $conflict_files; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "Resolving conflict for $file"
|
||||
git add "$file"
|
||||
else
|
||||
echo "File $file has been deleted. Skipping."
|
||||
git rm "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Conflicts resolved. Continuing cherry-pick."
|
||||
git cherry-pick --continue
|
||||
else
|
||||
echo "Cherry-pick successful for commit $commit_hash."
|
||||
fi
|
||||
done
|
||||
|
||||
# Push the cherry-pick branch to the repository
|
||||
git remote set-url origin "https://${BOT_TOKEN}@github.com/${{ github.repository }}.git"
|
||||
git push origin $CHERRY_PICK_BRANCH --force
|
||||
|
||||
- name: Create Pull Request
|
||||
run: |
|
||||
# Prepare and create the PR
|
||||
pr_title="deps: Merge ${{ env.pr_numbers_in_title }} PRs into $TARGET_BRANCH"
|
||||
pr_body=$(cat ${{ env.TEMP_DIR }}/pr_body.txt)
|
||||
|
||||
echo "Prepared PR title:"
|
||||
echo "$pr_title"
|
||||
echo "Prepared PR body:"
|
||||
echo "$pr_body"
|
||||
|
||||
# Create the PR using the GitHub API
|
||||
response=$(curl -s -X POST -H "Authorization: token $BOT_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/${{ github.repository }}/pulls \
|
||||
-d "$(jq -n --arg title "$pr_title" \
|
||||
--arg head "$CHERRY_PICK_BRANCH" \
|
||||
--arg base "$TARGET_BRANCH" \
|
||||
--arg body "$pr_body" \
|
||||
'{title: $title, head: $head, base: $base, body: $body}')")
|
||||
|
||||
pr_number=$(echo "$response" | jq -r '.number')
|
||||
echo "$pr_number" > ${{ env.TEMP_DIR }}/created_pr_number.txt
|
||||
echo "Created PR #$pr_number"
|
||||
|
||||
- name: Add Label to Created Pull Request
|
||||
run: |
|
||||
# Add 'milestone-merge' label to the created PR
|
||||
pr_number=$(cat ${{ env.TEMP_DIR }}/created_pr_number.txt)
|
||||
echo "Adding label to PR #$pr_number"
|
||||
|
||||
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-d '{"labels": ["milestone-merge"]}' \
|
||||
"https://api.github.com/repos/${{ github.repository }}/issues/$pr_number/labels"
|
||||
|
||||
echo "Added 'milestone-merge' label to PR #$pr_number."
|
||||
|
||||
@@ -4,45 +4,80 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- release-*
|
||||
# tags:
|
||||
# - 'v*'
|
||||
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Tag version to be used for Docker image"
|
||||
required: true
|
||||
default: "v3.8.0"
|
||||
default: "v3.8.3"
|
||||
|
||||
env:
|
||||
GO_VERSION: "1.22"
|
||||
IMAGE_NAME: "openim-server"
|
||||
# IMAGE_NAME: ${{ github.event.repository.name }}
|
||||
DOCKER_BUILDKIT: 1
|
||||
|
||||
jobs:
|
||||
build-and-test:
|
||||
publish-docker-images:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.merged == false) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout main repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
path: main-repo
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
uses: docker/setup-qemu-action@v3.3.0
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
id: buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build Docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./main-repo
|
||||
load: true
|
||||
tags: "openim/openim-server:local"
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Save Docker image to file
|
||||
run: docker save -o image.tar openim/openim-server:local
|
||||
- name: Extract metadata for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5.6.0
|
||||
with:
|
||||
images: |
|
||||
${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}
|
||||
ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
|
||||
registry.cn-hangzhou.aliyuncs.com/openimsdk/${{ env.IMAGE_NAME }}
|
||||
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: Install skopeo
|
||||
run: |
|
||||
sudo apt-get update && sudo apt-get install -y skopeo
|
||||
|
||||
- name: Build multi-arch images as OCI
|
||||
run: |
|
||||
mkdir -p /tmp/oci-image /tmp/docker-cache
|
||||
|
||||
# Build multi-architecture image and save in OCI format
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
--output type=oci,dest=/tmp/oci-image/multi-arch.tar \
|
||||
--cache-to type=local,dest=/tmp/docker-cache \
|
||||
--cache-from type=gha \
|
||||
./main-repo
|
||||
|
||||
# Use skopeo to convert the amd64 image from OCI format to Docker format and load it
|
||||
skopeo copy --override-arch amd64 oci-archive:/tmp/oci-image/multi-arch.tar docker-daemon:${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:local
|
||||
|
||||
# check image
|
||||
docker image ls | grep openim
|
||||
|
||||
- name: Checkout compose repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -55,18 +90,19 @@ jobs:
|
||||
run: |
|
||||
IP=$(hostname -I | awk '{print $1}')
|
||||
echo "The IP Address is: $IP"
|
||||
echo "::set-output name=ip::$IP"
|
||||
echo "ip=$IP" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Update .env to use the local image
|
||||
run: |
|
||||
sed -i 's|OPENIM_SERVER_IMAGE=.*|OPENIM_SERVER_IMAGE=openim/openim-server:local|' ${{ github.workspace }}/compose-repo/.env
|
||||
sed -i 's|OPENIM_SERVER_IMAGE=.*|OPENIM_SERVER_IMAGE=${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:local|' ${{ github.workspace }}/compose-repo/.env
|
||||
sed -i 's|MINIO_EXTERNAL_ADDRESS=.*|MINIO_EXTERNAL_ADDRESS=http://${{ steps.get-ip.outputs.ip }}:10005|' ${{ github.workspace }}/compose-repo/.env
|
||||
|
||||
- name: Start services using Docker Compose
|
||||
run: |
|
||||
cd ${{ github.workspace }}/compose-repo
|
||||
docker compose up -d
|
||||
sleep 60
|
||||
|
||||
docker compose ps
|
||||
|
||||
# - name: Check openim-server health
|
||||
# run: |
|
||||
@@ -97,54 +133,62 @@ jobs:
|
||||
# exit 0
|
||||
# fi
|
||||
|
||||
- name: Load Docker image from file
|
||||
run: docker load -i image.tar
|
||||
|
||||
- name: Extract metadata for Docker (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5.5.1
|
||||
with:
|
||||
images: |
|
||||
openim/openim-server
|
||||
ghcr.io/openimsdk/openim-server
|
||||
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server
|
||||
tags: |
|
||||
type=ref,event=tag
|
||||
type=schedule
|
||||
type=ref,event=branch
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern=v{{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
type=semver,pattern={{major}}
|
||||
type=semver,pattern=release-{{raw}}
|
||||
type=sha
|
||||
type=raw,value=${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Log in to GitHub Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Log in to Aliyun Container Registry
|
||||
uses: docker/login-action@v2
|
||||
uses: docker/login-action@v3.3.0
|
||||
with:
|
||||
registry: registry.cn-hangzhou.aliyuncs.com
|
||||
username: ${{ secrets.ALIREGISTRY_USERNAME }}
|
||||
password: ${{ secrets.ALIREGISTRY_TOKEN }}
|
||||
|
||||
- name: Push Docker images
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./main-repo
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
- name: Push multi-architecture images
|
||||
if: success()
|
||||
run: |
|
||||
docker buildx build \
|
||||
--platform linux/amd64,linux/arm64 \
|
||||
$(echo "${{ steps.meta.outputs.tags }}" | sed 's/,/ --tag /g' | sed 's/^/--tag /') \
|
||||
--cache-from type=local,src=/tmp/docker-cache \
|
||||
--push \
|
||||
./main-repo
|
||||
|
||||
- name: Verify multi-platform support
|
||||
run: |
|
||||
images=(
|
||||
"${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}"
|
||||
"ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}"
|
||||
"registry.cn-hangzhou.aliyuncs.com/openimsdk/${{ env.IMAGE_NAME }}"
|
||||
)
|
||||
|
||||
for image in "${images[@]}"; do
|
||||
for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n' | cut -d':' -f2); do
|
||||
echo "Verifying multi-arch support for $image:$tag"
|
||||
manifest=$(docker manifest inspect "$image:$tag" || echo "error")
|
||||
if [[ "$manifest" == "error" ]]; then
|
||||
echo "Manifest not found for $image:$tag"
|
||||
exit 1
|
||||
fi
|
||||
amd64_found=$(echo "$manifest" | jq '.manifests[] | select(.platform.architecture == "amd64")')
|
||||
arm64_found=$(echo "$manifest" | jq '.manifests[] | select(.platform.architecture == "arm64")')
|
||||
if [[ -z "$amd64_found" ]]; then
|
||||
echo "Multi-platform support check failed for $image:$tag - missing amd64"
|
||||
exit 1
|
||||
fi
|
||||
if [[ -z "$arm64_found" ]]; then
|
||||
echo "Multi-platform support check failed for $image:$tag - missing arm64"
|
||||
exit 1
|
||||
fi
|
||||
echo "✅ $image:$tag supports both amd64 and arm64 architectures"
|
||||
done
|
||||
done
|
||||
|
||||
@@ -8,19 +8,40 @@ jobs:
|
||||
update-version:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TAG_VERSION: ${{ github.event.release.tag_name }}
|
||||
TAG_VERSION: ${{ github.event.release.tag_name }}
|
||||
steps:
|
||||
# Step 1: Checkout the original repository's code
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
# submodules: "recursive"
|
||||
|
||||
- name: Safe submodule initialization
|
||||
run: |
|
||||
echo "Checking for submodules..."
|
||||
if [ -f .gitmodules ]; then
|
||||
if [ -s .gitmodules ]; then
|
||||
echo "Initializing submodules..."
|
||||
if git submodule sync --recursive 2>/dev/null; then
|
||||
git submodule update --init --force --recursive || {
|
||||
echo "Warning: Some submodules failed to initialize, continuing anyway..."
|
||||
}
|
||||
else
|
||||
echo "Warning: Submodule sync failed, continuing without submodules..."
|
||||
fi
|
||||
else
|
||||
echo ".gitmodules exists but is empty, skipping submodule initialization"
|
||||
fi
|
||||
else
|
||||
echo "No .gitmodules file found, no submodules to initialize"
|
||||
fi
|
||||
|
||||
# Step 2: Set up Git with official account
|
||||
- name: Set up Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git config --global user.name "github-actions[bot]"
|
||||
git config --global user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Step 3: Check and delete existing tag
|
||||
- name: Check and delete existing tag
|
||||
@@ -33,7 +54,8 @@ jobs:
|
||||
# Step 4: Update version file
|
||||
- name: Update version file
|
||||
run: |
|
||||
echo "${{ env.TAG_VERSION }}" > version/version
|
||||
mkdir -p version
|
||||
echo -n "${{ env.TAG_VERSION }}" > version/version
|
||||
|
||||
# Step 5: Commit and push changes
|
||||
- name: Commit and push changes
|
||||
@@ -42,43 +64,56 @@ jobs:
|
||||
run: |
|
||||
git add version/version
|
||||
git commit -m "Update version to ${{ env.TAG_VERSION }}"
|
||||
git push origin HEAD:${{ github.ref }}
|
||||
|
||||
# Step 6: Create and push tag
|
||||
- name: Create and push tag
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
# Step 6: Update tag
|
||||
- name: Update tag
|
||||
run: |
|
||||
git tag ${{ env.TAG_VERSION }}
|
||||
git push origin ${{ env.TAG_VERSION }}
|
||||
git tag -fa ${{ env.TAG_VERSION }} -m "Update version to ${{ env.TAG_VERSION }}"
|
||||
git push origin ${{ env.TAG_VERSION }} --force
|
||||
|
||||
# Step 7: Find and Publish Draft Release
|
||||
- name: Find and Publish Draft Release
|
||||
uses: actions/github-script@v6
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
// Get the list of releases
|
||||
const releases = await github.rest.repos.listReleases({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo
|
||||
});
|
||||
const { owner, repo } = context.repo;
|
||||
const tagName = process.env.TAG_VERSION;
|
||||
|
||||
// Find the draft release where the title and tag_name are the same
|
||||
const draftRelease = releases.data.find(release =>
|
||||
release.draft && release.name === release.tag_name
|
||||
);
|
||||
|
||||
if (draftRelease) {
|
||||
// Publish the draft release using the release_id
|
||||
try {
|
||||
let release;
|
||||
try {
|
||||
const response = await github.rest.repos.getReleaseByTag({
|
||||
owner,
|
||||
repo,
|
||||
tag: tagName
|
||||
});
|
||||
release = response.data;
|
||||
} catch (tagError) {
|
||||
core.info(`Release not found by tag, searching all releases...`);
|
||||
const releases = await github.rest.repos.listReleases({
|
||||
owner,
|
||||
repo,
|
||||
per_page: 100
|
||||
});
|
||||
|
||||
release = releases.data.find(r => r.draft && r.tag_name === tagName);
|
||||
if (!release) {
|
||||
throw new Error(`No release found with tag ${tagName}`);
|
||||
}
|
||||
}
|
||||
|
||||
await github.rest.repos.updateRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
release_id: draftRelease.id, // Use release_id
|
||||
draft: false
|
||||
owner,
|
||||
repo,
|
||||
release_id: release.id,
|
||||
draft: false,
|
||||
prerelease: release.prerelease
|
||||
});
|
||||
|
||||
core.info(`Draft Release ${draftRelease.tag_name} published successfully.`);
|
||||
} else {
|
||||
core.info("No matching draft release found.");
|
||||
}
|
||||
|
||||
const status = release.draft ? "was draft" : "was already published";
|
||||
core.info(`Release ${tagName} ensured to be published (${status}).`);
|
||||
|
||||
} catch (error) {
|
||||
core.warning(`Could not find or update release for tag ${tagName}: ${error.message}`);
|
||||
}
|
||||
|
||||
@@ -17,12 +17,12 @@ jobs:
|
||||
repo-token: ${{ secrets.BOT_TOKEN }}
|
||||
pr-message: |
|
||||
Hello! Thank you for your contribution.
|
||||
|
||||
|
||||
If you are fixing a bug, please reference the issue number in the description.
|
||||
|
||||
If you are implementing a feature request, please check with the maintainers that the feature will be accepted first.
|
||||
|
||||
[Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) to connect and communicate with our developers.
|
||||
[Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) to connect and communicate with our developers.
|
||||
|
||||
Please leave your information in the [✨ discussions](https://github.com/orgs/OpenIMSDK/discussions/426), we expect anyone to join OpenIM developer community.
|
||||
|
||||
@@ -30,6 +30,6 @@ jobs:
|
||||
Hello! Thank you for filing an issue.
|
||||
|
||||
If this is a bug report, please include relevant logs to help us debug the problem.
|
||||
|
||||
[Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) to connect and communicate with our developers.
|
||||
continue-on-error: true
|
||||
|
||||
[Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) to connect and communicate with our developers.
|
||||
continue-on-error: true
|
||||
|
||||
@@ -28,6 +28,8 @@ run:
|
||||
# - util
|
||||
# - .*~
|
||||
# - api/swagger/docs
|
||||
|
||||
|
||||
# - server/docs
|
||||
# - components/mnt/config/certs
|
||||
# - logs
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
## [v3.8.3-patch.6](https://github.com/openimsdk/open-im-server/releases/tag/v3.8.3-patch.6) (2025-07-23)
|
||||
|
||||
### Bug Fixes
|
||||
* fix: Add friend DB in notification sender [#3438](https://github.com/openimsdk/open-im-server/pull/3438)
|
||||
* fix: remove update version file workflows have new line in 3.8.3-patch branch. [#3452](https://github.com/openimsdk/open-im-server/pull/3452)
|
||||
* fix: s3 aws init [#3454](https://github.com/openimsdk/open-im-server/pull/3454)
|
||||
* fix: use safe submodule init in workflows in v3.8.3-patch. [#3469](https://github.com/openimsdk/open-im-server/pull/3469)
|
||||
|
||||
**Full Changelog**: [v3.8.3-patch.5...v3.8.3-patch.6](https://github.com/openimsdk/open-im-server/compare/v3.8.3-patch.5...v3.8.3-patch.6)
|
||||
|
||||
## [v3.8.3-patch.5](https://github.com/openimsdk/open-im-server/releases/tag/v3.8.3-patch.5) (2025-06-10)
|
||||
|
||||
### New Features
|
||||
* feat: optimize friend and group applications [#3396](https://github.com/openimsdk/open-im-server/pull/3396)
|
||||
|
||||
### Bug Fixes
|
||||
* fix: solve unocrrect invite notification [Created [#3219](https://github.com/openimsdk/open-im-server/pull/3219)
|
||||
|
||||
### Builds
|
||||
* build: update gomake version in dockerfile.[Patch branch] [#3416](https://github.com/openimsdk/open-im-server/pull/3416)
|
||||
|
||||
**Full Changelog**: [v3.8.3...v3.8.3-patch.5](https://github.com/openimsdk/open-im-server/compare/v3.8.3...v3.8.3-patch.5)
|
||||
|
||||
## [v3.8.3-patch.4](https://github.com/openimsdk/open-im-server/releases/tag/v3.8.3-patch.4) (2025-03-13)
|
||||
|
||||
### Bug Fixes
|
||||
* fix: solve unocrrect invite notificationfrom #3213
|
||||
|
||||
**Full Changelog**: [v3.8.3-patch.3...v3.8.3-patch.4](https://github.com/openimsdk/open-im-server/compare/v3.8.3-patch.3...v3.8.3-patch.4)
|
||||
|
||||
## [v3.8.3-patch.3](https://github.com/openimsdk/open-im-server/releases/tag/v3.8.3-patch.3) (2025-03-07)
|
||||
|
||||
### New Features
|
||||
* feat: optimizing BatchGetIncrementalGroupMember #3180
|
||||
|
||||
### Bug Fixes
|
||||
* fix: solve uncorrect notification when set group info #3172
|
||||
* fix: the sorting is wrong after canceling the administrator in group settings #3185
|
||||
* fix: solve uncorrect GroupMember enter group notification type. #3188
|
||||
|
||||
### Refactors
|
||||
* refactor: change sendNotification to sendMessage to avoid ambiguity regarding message sending behavior. #3173
|
||||
|
||||
**Full Changelog**: [v3.8.3-patch.2...v3.8.3-patch.3](https://github.com/openimsdk/open-im-server/compare/v3.8.3-patch.2...v3.8.3-patch.3)
|
||||
|
||||
## [v3.8.3-patch.2](https://github.com/openimsdk/open-im-server/releases/tag/v3.8.3-patch.2) (2025-02-28)
|
||||
|
||||
### Bug Fixes
|
||||
* fix: Offline push does not have a badge && Android offline push (#3146) [#3174](https://github.com/openimsdk/open-im-server/pull/3174)
|
||||
|
||||
**Full Changelog**: [v3.8.3-patch.1...v3.8.3-patch.2](https://github.com/openimsdk/open-im-server/compare/v3.8.3-patch.1...v3.8.3-patch.2)
|
||||
|
||||
## [v3.8.3-patch.1](https://github.com/openimsdk/open-im-server/releases/tag/v3.8.3-patch.1) (2025-02-25)
|
||||
|
||||
### New Features
|
||||
* feat: add backup volume && optimize log print [Created [#3121](https://github.com/openimsdk/open-im-server/pull/3121)
|
||||
|
||||
### Bug Fixes
|
||||
* fix: seq conversion failed without exiting [Created [#3120](https://github.com/openimsdk/open-im-server/pull/3120)
|
||||
* fix: check error in BatchSetTokenMapByUidPid [Created [#3123](https://github.com/openimsdk/open-im-server/pull/3123)
|
||||
* fix: DeleteDoc crash [Created [#3124](https://github.com/openimsdk/open-im-server/pull/3124)
|
||||
* fix: the abnormal message has no sending time, causing the SDK to be abnormal [Created [#3126](https://github.com/openimsdk/open-im-server/pull/3126)
|
||||
* fix: crash caused [#3127](https://github.com/openimsdk/open-im-server/pull/3127)
|
||||
* fix: the user sets the conversation timer cleanup timestamp unit incorrectly [Created [#3128](https://github.com/openimsdk/open-im-server/pull/3128)
|
||||
* fix: seq conversion not reading env in docker environment [Created [#3131](https://github.com/openimsdk/open-im-server/pull/3131)
|
||||
|
||||
### Builds
|
||||
* build: improve workflows contents. [Created [#3125](https://github.com/openimsdk/open-im-server/pull/3125)
|
||||
|
||||
**Full Changelog**: [v3.8.3-e-v1.1.5...v3.8.3-patch.1-e-v1.1.5](https://github.com/openimsdk/open-im-server-enterprise/compare/v3.8.3-e-v1.1.5...v3.8.3-patch.1-e-v1.1.5)
|
||||
@@ -0,0 +1,4 @@
|
||||
# CHANGELOGs
|
||||
|
||||
- [CHANGELOG-3.8.md](./CHANGELOG-3.8.md)
|
||||
|
||||
@@ -12,13 +12,12 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
[](https://gurubase.io/g/openim)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="./README.md">English</a> ·
|
||||
<a href="./README_zh_CN.md">中文</a> ·
|
||||
@@ -47,16 +46,15 @@
|
||||
<a href="./docs/readme/README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
|
||||
## :busts_in_silhouette: Join Our Community
|
||||
|
||||
+ 💬 [Follow us on Twitter](https://twitter.com/founder_im63606)
|
||||
+ 🚀 [Join our Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
+ :eyes: [Join our WeChat Group](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 💬 [Follow us on Twitter](https://twitter.com/founder_im63606)
|
||||
- 🚀 [Join our Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [Join our WeChat Group](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## Ⓜ️ About OpenIM
|
||||
|
||||
@@ -68,13 +66,14 @@ Unlike standalone chat applications such as Telegram, Signal, and Rocket.Chat, O
|
||||
|
||||
**OpenIMSDK**, designed for **OpenIMServer**, is an IM SDK created specifically for integration into client applications. It supports various functionalities and modules:
|
||||
|
||||
+ 🌟 Main Features:
|
||||
- 🌟 Main Features:
|
||||
|
||||
- 📦 Local Storage
|
||||
- 🔔 Listener Callbacks
|
||||
- 🛡️ API Wrapping
|
||||
- 🌐 Connection Management
|
||||
|
||||
+ 📚 Main Modules:
|
||||
- 📚 Main Modules:
|
||||
1. 🚀 Initialization and Login
|
||||
2. 👤 User Management
|
||||
3. 👫 Friends Management
|
||||
@@ -85,18 +84,18 @@ Built with Golang and supports cross-platform deployment to ensure a consistent
|
||||
|
||||
👉 **[Explore the GO SDK](https://github.com/openimsdk/openim-sdk-core)**
|
||||
|
||||
## 🌐 Introduction to OpenIMServer
|
||||
## 🌐 Introduction to OpenIMServer
|
||||
|
||||
+ **OpenIMServer** features include:
|
||||
- **OpenIMServer** features include:
|
||||
- 🌐 Microservices Architecture: Supports cluster mode, including a gateway and multiple rpc services.
|
||||
- 🚀 Diverse Deployment Options: Supports source code, Kubernetes, or Docker deployment.
|
||||
- Massive User Support: Supports large-scale groups with hundreds of thousands, millions of users, and billions of messages.
|
||||
|
||||
### Enhanced Business Functions:
|
||||
|
||||
+ **REST API**: Provides a REST API for business systems to enhance functionality, such as group creation and message pushing through backend interfaces.
|
||||
- **REST API**: Provides a REST API for business systems to enhance functionality, such as group creation and message pushing through backend interfaces.
|
||||
|
||||
+ **Webhooks**: Expands business forms through callbacks, sending requests to business servers before or after certain events.
|
||||
- **Webhooks**: Expands business forms through callbacks, sending requests to business servers before or after certain events.
|
||||
|
||||

|
||||
|
||||
@@ -108,8 +107,8 @@ Experience online for iOS/Android/H5/PC/Web:
|
||||
|
||||
To facilitate user experience, we offer various deployment solutions. You can choose your preferred deployment method from the list below:
|
||||
|
||||
+ **[Source Code Deployment Guide](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Source Code Deployment Guide](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
|
||||
## System Support
|
||||
|
||||
@@ -117,24 +116,22 @@ Supports Linux, Windows, Mac systems, and ARM and AMD CPU architectures.
|
||||
|
||||
## :link: Links
|
||||
|
||||
+ **[Developer Manual](https://docs.openim.io/)**
|
||||
+ **[Changelog](https://github.com/openimsdk/open-im-server/blob/main/CHANGELOG.md)**
|
||||
- **[Developer Manual](https://docs.openim.io/)**
|
||||
- **[Changelog](https://github.com/openimsdk/open-im-server/blob/main/CHANGELOG.md)**
|
||||
|
||||
## :writing_hand: How to Contribute
|
||||
|
||||
We welcome contributions of any kind! Please make sure to read our [Contributor Documentation](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md) before submitting a Pull Request.
|
||||
|
||||
+ **[Report a Bug](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=bug&template=bug_report.md&title=)**
|
||||
+ **[Suggest a Feature](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)**
|
||||
+ **[Submit a Pull Request](https://github.com/openimsdk/open-im-server/pulls)**
|
||||
- **[Report a Bug](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=bug&template=bug_report.md&title=)**
|
||||
- **[Suggest a Feature](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)**
|
||||
- **[Submit a Pull Request](https://github.com/openimsdk/open-im-server/pulls)**
|
||||
|
||||
Thank you for contributing to building a powerful instant messaging solution!
|
||||
|
||||
## :closed_book: License
|
||||
|
||||
OpenIMSDK is available under the Apache License 2.0. See the [LICENSE file](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) for more information.
|
||||
|
||||
|
||||
This software is licensed under the Apache License 2.0
|
||||
|
||||
## 🔮 Thanks to our contributors!
|
||||
|
||||
|
||||
+26
-29
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="./README.md">English</a> ·
|
||||
<a href="./README_zh_CN.md">中文</a> ·
|
||||
@@ -46,34 +45,34 @@
|
||||
<a href="./docs/readme/README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
|
||||
## :busts_in_silhouette: 加入我们的社区
|
||||
|
||||
+ 💬 [关注我们的 Twitter](https://twitter.com/founder_im63606)
|
||||
+ 🚀 [加入我们的 Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2hljfom5u-9ZuzP3NfEKW~BJKbpLm0Hw)
|
||||
+ :eyes: [加入我们的微信群](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 💬 [关注我们的 Twitter](https://twitter.com/founder_im63606)
|
||||
- 🚀 [加入我们的 Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2hljfom5u-9ZuzP3NfEKW~BJKbpLm0Hw)
|
||||
- :eyes: [加入我们的微信群](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## Ⓜ️ 关于 OpenIM
|
||||
|
||||
与Telegram、Signal、Rocket.Chat等独立聊天应用不同,OpenIM提供了专为开发者设计的开源即时通讯解决方案,而不是直接安装使用的独立聊天应用。OpenIM由OpenIM SDK和OpenIM Server两大部分组成,为开发者提供了一整套集成即时通讯功能的工具和服务,包括消息发送接收、用户管理和群组管理等。总体来说,OpenIM旨在为开发者提供必要的工具和框架,帮助他们在自己的应用中实现高效的即时通讯解决方案。
|
||||
与 Telegram、Signal、Rocket.Chat 等独立聊天应用不同,OpenIM 提供了专为开发者设计的开源即时通讯解决方案,而不是直接安装使用的独立聊天应用。OpenIM 由 OpenIM SDK 和 OpenIM Server 两大部分组成,为开发者提供了一整套集成即时通讯功能的工具和服务,包括消息发送接收、用户管理和群组管理等。总体来说,OpenIM 旨在为开发者提供必要的工具和框架,帮助他们在自己的应用中实现高效的即时通讯解决方案。
|
||||
|
||||

|
||||
|
||||
## 🚀 OpenIMSDK 介绍
|
||||
|
||||
**OpenIMSDK** 是为 **OpenIMServer** 设计的IM SDK,专为集成到客户端应用而生。它支持多种功能和模块:
|
||||
**OpenIMSDK** 是为 **OpenIMServer** 设计的 IM SDK,专为集成到客户端应用而生。它支持多种功能和模块:
|
||||
|
||||
- 🌟 主要功能:
|
||||
|
||||
+ 🌟 主要功能:
|
||||
- 📦 本地存储
|
||||
- 🔔 监听器回调
|
||||
- 🛡️ API封装
|
||||
- 🛡️ API 封装
|
||||
- 🌐 连接管理
|
||||
|
||||
+ 📚 主要模块:
|
||||
- 📚 主要模块:
|
||||
1. 🚀 初始化及登录
|
||||
2. 👤 用户管理
|
||||
3. 👫 好友管理
|
||||
@@ -86,31 +85,29 @@
|
||||
|
||||
## 🌐 OpenIMServer 介绍
|
||||
|
||||
+ **OpenIMServer** 的特点包括:
|
||||
- 🌐 微服务架构:支持集群模式,包括网关(gateway)和多个rpc服务。
|
||||
- 🚀 多样的部署方式:支持源代码、Kubernetes或Docker部署。
|
||||
- **OpenIMServer** 的特点包括:
|
||||
- 🌐 微服务架构:支持集群模式,包括网关(gateway)和多个 rpc 服务。
|
||||
- 🚀 多样的部署方式:支持源代码、Kubernetes 或 Docker 部署。
|
||||
- 海量用户支持:支持十万级超大群组,千万级用户和百亿级消息。
|
||||
|
||||
### 增强的业务功能:
|
||||
|
||||
+ **REST API**:为业务系统提供REST API,增加群组创建、消息推送等后台接口功能。
|
||||
- **REST API**:为业务系统提供 REST API,增加群组创建、消息推送等后台接口功能。
|
||||
|
||||
+ **Webhooks**:通过事件前后的回调,向业务服务器发送请求,扩展更多的业务形态。
|
||||
- **Webhooks**:通过事件前后的回调,向业务服务器发送请求,扩展更多的业务形态。
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## :rocket: 快速入门
|
||||
|
||||
在线体验iOS/Android/H5/PC/Web:
|
||||
在线体验 iOS/Android/H5/PC/Web:
|
||||
|
||||
👉 **[OpenIM在线演示](https://www.openim.io/en/commercial)**
|
||||
👉 **[OpenIM 在线演示](https://www.openim.io/en/commercial)**
|
||||
|
||||
为了便于用户体验,我们提供了多种部署解决方案,您可以根据以下列表选择适合您的部署方式:
|
||||
|
||||
+ **[源代码部署指南](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Docker 部署指南](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[源代码部署指南](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Docker 部署指南](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
|
||||
## 系统支持
|
||||
|
||||
@@ -118,22 +115,22 @@
|
||||
|
||||
## :link: 相关链接
|
||||
|
||||
+ **[开发手册](https://docs.openim.io/)**
|
||||
+ **[更新日志](https://github.com/openimsdk/open-im-server/blob/main/CHANGELOG.md)**
|
||||
- **[开发手册](https://docs.openim.io/)**
|
||||
- **[更新日志](https://github.com/openimsdk/open-im-server/blob/main/CHANGELOG.md)**
|
||||
|
||||
## :writing_hand: 如何贡献
|
||||
|
||||
我们欢迎任何形式的贡献!在提交 Pull Request 之前,请确保阅读我们的[贡献者文档](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)
|
||||
|
||||
+ **[报告 Bug](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=bug&template=bug_report.md&title=)**
|
||||
+ **[提出新特性](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)**
|
||||
+ **[提交 Pull Request](https://github.com/openimsdk/open-im-server/pulls)**
|
||||
- **[报告 Bug](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=bug&template=bug_report.md&title=)**
|
||||
- **[提出新特性](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)**
|
||||
- **[提交 Pull Request](https://github.com/openimsdk/open-im-server/pulls)**
|
||||
|
||||
感谢您的贡献,一起来打造强大的即时通讯解决方案!
|
||||
|
||||
## :closed_book: 许可证
|
||||
## :closed_book: 开源许可证 License
|
||||
|
||||
OpenIMSDK 在 Apache License 2.0 许可下可用。查看[LICENSE 文件](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)了解更多信息。
|
||||
This software is licensed under the Apache License 2.0
|
||||
|
||||
## 🔮 Thanks to our contributors!
|
||||
|
||||
|
||||
+406
@@ -0,0 +1,406 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/api"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/msggateway"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/msgtransfer"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/rpc/auth"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/rpc/conversation"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/rpc/group"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/rpc/msg"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/rpc/relation"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/rpc/third"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/rpc/user"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/tools/cron"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
"github.com/openimsdk/open-im-server/v3/version"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/discovery/standalone"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/system/program"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/spf13/viper"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func init() {
|
||||
config.SetStandalone()
|
||||
prommetrics.RegistryAll()
|
||||
}
|
||||
|
||||
func main() {
|
||||
var configPath string
|
||||
flag.StringVar(&configPath, "c", "", "config path")
|
||||
flag.Parse()
|
||||
if configPath == "" {
|
||||
_, _ = fmt.Fprintln(os.Stderr, "config path is empty")
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
cmd := newCmds(configPath)
|
||||
putCmd(cmd, false, auth.Start)
|
||||
putCmd(cmd, false, conversation.Start)
|
||||
putCmd(cmd, false, relation.Start)
|
||||
putCmd(cmd, false, group.Start)
|
||||
putCmd(cmd, false, msg.Start)
|
||||
putCmd(cmd, false, third.Start)
|
||||
putCmd(cmd, false, user.Start)
|
||||
putCmd(cmd, false, push.Start)
|
||||
putCmd(cmd, true, msggateway.Start)
|
||||
putCmd(cmd, true, msgtransfer.Start)
|
||||
putCmd(cmd, true, api.Start)
|
||||
putCmd(cmd, true, cron.Start)
|
||||
ctx := context.Background()
|
||||
if err := cmd.run(ctx); err != nil {
|
||||
_, _ = fmt.Fprintf(os.Stderr, "server exit %s", err)
|
||||
os.Exit(1)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func newCmds(confPath string) *cmds {
|
||||
return &cmds{confPath: confPath}
|
||||
}
|
||||
|
||||
type cmdName struct {
|
||||
Name string
|
||||
Func func(ctx context.Context) error
|
||||
Block bool
|
||||
}
|
||||
type cmds struct {
|
||||
confPath string
|
||||
cmds []cmdName
|
||||
config config.AllConfig
|
||||
conf map[string]reflect.Value
|
||||
}
|
||||
|
||||
func (x *cmds) getTypePath(typ reflect.Type) string {
|
||||
return path.Join(typ.PkgPath(), typ.Name())
|
||||
}
|
||||
|
||||
func (x *cmds) initDiscovery() {
|
||||
x.config.Discovery.Enable = "standalone"
|
||||
vof := reflect.ValueOf(&x.config.Discovery.RpcService).Elem()
|
||||
tof := reflect.TypeOf(&x.config.Discovery.RpcService).Elem()
|
||||
num := tof.NumField()
|
||||
for i := 0; i < num; i++ {
|
||||
field := tof.Field(i)
|
||||
if !field.IsExported() {
|
||||
continue
|
||||
}
|
||||
if field.Type.Kind() != reflect.String {
|
||||
continue
|
||||
}
|
||||
vof.Field(i).SetString(field.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *cmds) initAllConfig() error {
|
||||
x.conf = make(map[string]reflect.Value)
|
||||
vof := reflect.ValueOf(&x.config).Elem()
|
||||
num := vof.NumField()
|
||||
for i := 0; i < num; i++ {
|
||||
field := vof.Field(i)
|
||||
for ptr := true; ptr; {
|
||||
if field.Kind() == reflect.Ptr {
|
||||
field = field.Elem()
|
||||
} else {
|
||||
ptr = false
|
||||
}
|
||||
}
|
||||
x.conf[x.getTypePath(field.Type())] = field
|
||||
val := field.Addr().Interface()
|
||||
name := val.(interface{ GetConfigFileName() string }).GetConfigFileName()
|
||||
confData, err := os.ReadFile(filepath.Join(x.confPath, name))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
v := viper.New()
|
||||
v.SetConfigType("yaml")
|
||||
if err := v.ReadConfig(bytes.NewReader(confData)); err != nil {
|
||||
return err
|
||||
}
|
||||
opt := func(conf *mapstructure.DecoderConfig) {
|
||||
conf.TagName = config.StructTagName
|
||||
}
|
||||
if err := v.Unmarshal(val, opt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
x.initDiscovery()
|
||||
x.config.Redis.Disable = false
|
||||
x.config.LocalCache = config.LocalCache{}
|
||||
config.InitNotification(&x.config.Notification)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *cmds) parseConf(conf any) error {
|
||||
vof := reflect.ValueOf(conf)
|
||||
for {
|
||||
if vof.Kind() == reflect.Ptr {
|
||||
vof = vof.Elem()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
tof := vof.Type()
|
||||
numField := vof.NumField()
|
||||
for i := 0; i < numField; i++ {
|
||||
typeField := tof.Field(i)
|
||||
if !typeField.IsExported() {
|
||||
continue
|
||||
}
|
||||
field := vof.Field(i)
|
||||
pkt := x.getTypePath(field.Type())
|
||||
val, ok := x.conf[pkt]
|
||||
if !ok {
|
||||
switch field.Interface().(type) {
|
||||
case config.Index:
|
||||
case config.Path:
|
||||
field.SetString(x.confPath)
|
||||
case config.AllConfig:
|
||||
field.Set(reflect.ValueOf(x.config))
|
||||
case *config.AllConfig:
|
||||
field.Set(reflect.ValueOf(&x.config))
|
||||
default:
|
||||
return fmt.Errorf("config field %s %s not found", vof.Type().Name(), typeField.Name)
|
||||
}
|
||||
continue
|
||||
}
|
||||
field.Set(val)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *cmds) add(name string, block bool, fn func(ctx context.Context) error) {
|
||||
x.cmds = append(x.cmds, cmdName{Name: name, Block: block, Func: fn})
|
||||
}
|
||||
|
||||
func (x *cmds) initLog() error {
|
||||
conf := x.config.Log
|
||||
if err := log.InitLoggerFromConfig(
|
||||
"openim-server",
|
||||
program.GetProcessName(),
|
||||
"", "",
|
||||
conf.RemainLogLevel,
|
||||
conf.IsStdout,
|
||||
conf.IsJson,
|
||||
conf.StorageLocation,
|
||||
conf.RemainRotationCount,
|
||||
conf.RotationTime,
|
||||
strings.TrimSpace(version.Version),
|
||||
conf.IsSimplify,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (x *cmds) run(ctx context.Context) error {
|
||||
if len(x.cmds) == 0 {
|
||||
return fmt.Errorf("no command to run")
|
||||
}
|
||||
if err := x.initAllConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := x.initLog(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
log.ZError(ctx, "context server exit cause", context.Cause(ctx))
|
||||
}()
|
||||
|
||||
if prometheus := x.config.API.Prometheus; prometheus.Enable {
|
||||
var (
|
||||
port int
|
||||
err error
|
||||
)
|
||||
if !prometheus.AutoSetPorts {
|
||||
port, err = datautil.GetElemByIndex(prometheus.Ports, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
|
||||
if err != nil {
|
||||
return fmt.Errorf("prometheus listen %d error %w", port, err)
|
||||
}
|
||||
defer listener.Close()
|
||||
log.ZDebug(ctx, "prometheus start", "addr", listener.Addr())
|
||||
go func() {
|
||||
err := prommetrics.Start(listener)
|
||||
if err == nil {
|
||||
err = fmt.Errorf("http done")
|
||||
}
|
||||
cancel(fmt.Errorf("prometheus %w", err))
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case val := <-sigs:
|
||||
log.ZDebug(ctx, "recv signal", "signal", val.String())
|
||||
cancel(fmt.Errorf("signal %s", val.String()))
|
||||
}
|
||||
}()
|
||||
|
||||
for i := range x.cmds {
|
||||
cmd := x.cmds[i]
|
||||
if cmd.Block {
|
||||
continue
|
||||
}
|
||||
if err := cmd.Func(ctx); err != nil {
|
||||
cancel(fmt.Errorf("server %s exit %w", cmd.Name, err))
|
||||
return err
|
||||
}
|
||||
go func() {
|
||||
if cmd.Block {
|
||||
cancel(fmt.Errorf("server %s exit", cmd.Name))
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
var wait cmdManger
|
||||
for i := range x.cmds {
|
||||
cmd := x.cmds[i]
|
||||
if !cmd.Block {
|
||||
continue
|
||||
}
|
||||
wait.Start(cmd.Name)
|
||||
go func() {
|
||||
defer wait.Shutdown(cmd.Name)
|
||||
if err := cmd.Func(ctx); err != nil {
|
||||
cancel(fmt.Errorf("server %s exit %w", cmd.Name, err))
|
||||
return
|
||||
}
|
||||
cancel(fmt.Errorf("server %s exit", cmd.Name))
|
||||
}()
|
||||
}
|
||||
<-ctx.Done()
|
||||
exitCause := context.Cause(ctx)
|
||||
log.ZWarn(ctx, "notification of service closure", exitCause)
|
||||
done := wait.Wait()
|
||||
timeout := time.NewTimer(time.Second * 10)
|
||||
defer timeout.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timeout.C:
|
||||
log.ZWarn(ctx, "server exit timeout", nil, "running", wait.Running())
|
||||
return exitCause
|
||||
case _, ok := <-done:
|
||||
if ok {
|
||||
log.ZWarn(ctx, "waiting for the service to exit", nil, "running", wait.Running())
|
||||
} else {
|
||||
log.ZInfo(ctx, "all server exit done")
|
||||
return exitCause
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func putCmd[C any](cmd *cmds, block bool, fn func(ctx context.Context, config *C, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error) {
|
||||
name := path.Base(runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name())
|
||||
if index := strings.Index(name, "."); index >= 0 {
|
||||
name = name[:index]
|
||||
}
|
||||
cmd.add(name, block, func(ctx context.Context) error {
|
||||
var conf C
|
||||
if err := cmd.parseConf(&conf); err != nil {
|
||||
return err
|
||||
}
|
||||
return fn(ctx, &conf, standalone.GetSvcDiscoveryRegistry(), standalone.GetServiceRegistrar())
|
||||
})
|
||||
}
|
||||
|
||||
type cmdManger struct {
|
||||
lock sync.Mutex
|
||||
done chan struct{}
|
||||
count int
|
||||
names map[string]struct{}
|
||||
}
|
||||
|
||||
func (x *cmdManger) Start(name string) {
|
||||
x.lock.Lock()
|
||||
defer x.lock.Unlock()
|
||||
if x.names == nil {
|
||||
x.names = make(map[string]struct{})
|
||||
}
|
||||
if x.done == nil {
|
||||
x.done = make(chan struct{}, 1)
|
||||
}
|
||||
if _, ok := x.names[name]; ok {
|
||||
panic(fmt.Errorf("cmd %s already exists", name))
|
||||
}
|
||||
x.count++
|
||||
x.names[name] = struct{}{}
|
||||
}
|
||||
|
||||
func (x *cmdManger) Shutdown(name string) {
|
||||
x.lock.Lock()
|
||||
defer x.lock.Unlock()
|
||||
if _, ok := x.names[name]; !ok {
|
||||
panic(fmt.Errorf("cmd %s not exists", name))
|
||||
}
|
||||
delete(x.names, name)
|
||||
x.count--
|
||||
if x.count == 0 {
|
||||
close(x.done)
|
||||
} else {
|
||||
select {
|
||||
case x.done <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (x *cmdManger) Wait() <-chan struct{} {
|
||||
x.lock.Lock()
|
||||
defer x.lock.Unlock()
|
||||
if x.count == 0 || x.done == nil {
|
||||
tmp := make(chan struct{})
|
||||
close(tmp)
|
||||
return tmp
|
||||
}
|
||||
return x.done
|
||||
}
|
||||
|
||||
func (x *cmdManger) Running() []string {
|
||||
x.lock.Lock()
|
||||
defer x.lock.Unlock()
|
||||
names := make([]string, 0, len(x.names))
|
||||
for name := range x.names {
|
||||
names = append(names, name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
+64
-40
@@ -1,48 +1,66 @@
|
||||
---
|
||||
title: 'OpenIM Configuration Files and Common Configuration Item Modifications Guide'
|
||||
# OpenIM Configuration File Descriptions and Common Configuration Modifications
|
||||
|
||||
## Configuration Files Explanation
|
||||
## External Component Configurations
|
||||
|
||||
| Configuration File | Description |
|
||||
| ------------------------------- | ------------------------------------------------------------ |
|
||||
| **kafka.yml** | Configurations for Kafka username, password, address, etc. |
|
||||
| **redis.yml** | Configurations for Redis password, address, etc. |
|
||||
| **minio.yml** | Configurations for MinIO username, password, address, and external IP/domain; failing to modify external IP or domain may cause image file sending failures |
|
||||
| **zookeeper.yml** | Configurations for ZooKeeper user, password, address, etc. |
|
||||
| **mongodb.yml** | Configurations for MongoDB username, password, address, etc. |
|
||||
| **log.yml** | Configurations for log level and storage directory. |
|
||||
| **notification.yml** | Configurations for events like adding friends, creating groups, etc. |
|
||||
| **share.yml** | Common configurations needed by various OpenIM services, such as secret. |
|
||||
| **webhooks.yml** | Configurations for URLs in Webhook. |
|
||||
| **local-cache.yml** | Local cache configurations. |
|
||||
| **openim-rpc-third.yml** | Configurations for listening IP, port, and storage settings for images and videos in openim-rpc-third service. |
|
||||
| **openim-rpc-user.yml** | Configurations for listening IP and port in openim-rpc-user service. |
|
||||
| **openim-api.yml** | Configurations for listening IP, port, etc., in openim-api service. |
|
||||
| **openim-crontask.yml** | Configurations for openim-crontask service. |
|
||||
| **openim-msggateway.yml** | Configurations for listening IP, port, etc., in openim-msggateway service. |
|
||||
| **openim-msgtransfer.yml** | Configurations for openim-msgtransfer service. |
|
||||
| **openim-push.yml** | Configurations for listening IP, port, and offline push settings in openim-push service. |
|
||||
| **openim-rpc-auth.yml** | Configurations for listening IP, port, and token expiration settings in openim-rpc-auth service. |
|
||||
| **openim-rpc-conversation.yml** | Configurations for listening IP, port, etc., in openim-rpc-conversation service. |
|
||||
| **openim-rpc-friend.yml** | Configurations for listening IP, port, etc., in openim-rpc-friend service. |
|
||||
| **openim-rpc-group.yml** | Configurations for listening IP, port, etc., in openim-rpc-group service. |
|
||||
| **openim-rpc-msg.yml** | Configurations for listening IP, port, and whether to verify friendship before sending messages in openim-rpc-msg service. |
|
||||
| Configuration File | Description |
|
||||
| ------------------ |-------------------------------------------------------------|
|
||||
| **kafka.yml** | Configuration for Kafka username, password, address, etc. |
|
||||
| **redis.yml** | Configuration for Redis password, address, etc. |
|
||||
| **minio.yml** | Configuration for MinIO username, password, address, etc. |
|
||||
| **mongodb.yml** | Configuration for MongoDB username, password, address, etc. |
|
||||
| **discovery.yml** | Service discovery and etcd credentials and address. |
|
||||
|
||||
## Common Configuration Item Modifications
|
||||
## OpenIMServer Related Configurations
|
||||
| Configuration File | Description |
|
||||
| ------------------------------- | ---------------------------------------------- |
|
||||
| **log.yml** | Configuration for logging levels and storage directory |
|
||||
| **notification.yml** | Event notification settings (e.g., add friend, create group) |
|
||||
| **share.yml** | Common settings for all services (e.g., secrets) |
|
||||
| **webhooks.yml** | Webhook URLs and related settings |
|
||||
| **local-cache.yml** | Local cache settings (generally do not modify) |
|
||||
| **openim-rpc-third.yml** | openim-rpc-third listen IP, port, and object storage settings |
|
||||
| **openim-rpc-user.yml** | openim-rpc-user listen IP and port settings |
|
||||
| **openim-api.yml** | openim-api listen IP, port, and other settings |
|
||||
| **openim-crontask.yml** | openim-crontask scheduled task settings |
|
||||
| **openim-msggateway.yml** | openim-msggateway listen IP, port, and other settings |
|
||||
| **openim-msgtransfer.yml** | Settings for openim-msgtransfer service |
|
||||
| **openim-push.yml** | openim-push listen IP, port, and offline push settings |
|
||||
| **openim-rpc-auth.yml** | openim-rpc-auth listen IP, port, token validity settings |
|
||||
| **openim-rpc-conversation.yml** | openim-rpc-conversation listen IP and port settings |
|
||||
| **openim-rpc-friend.yml** | openim-rpc-friend listen IP and port settings |
|
||||
| **openim-rpc-group.yml** | openim-rpc-group listen IP and port settings |
|
||||
| **openim-rpc-msg.yml** | openim-rpc-msg listen IP and port settings |
|
||||
|
||||
| Configuration Item Modification | Configuration File |
|
||||
| ----------------------------------------------------- | ----------------------- |
|
||||
| Using MinIO for image and video file object storage | `minio.yml` |
|
||||
| Adjusting production environment logs | `log.yml` |
|
||||
| Verifying friendship before sending messages | `openim-rpc-msg.yml` |
|
||||
| Modifying secret | `share.yml` |
|
||||
| Using OSS, COS, AWS, Kodo for image and video storage | `openim-rpc-third.yml` |
|
||||
| Setting multiple login policy | `openim-msggateway.yml` |
|
||||
| Setting up offline push | `openim-push.yml` |
|
||||
|
||||
## Starting Multiple Instances of an OpenIM Service
|
||||
## Monitoring and Alerting Related Configurations
|
||||
| Configuration File | Description |
|
||||
| ------------------------------ | --------------- |
|
||||
| **prometheus.yml** | Prometheus configuration |
|
||||
| **instance-down-rules.yml** | Alert rules |
|
||||
| **alertmanager.yml** | Alertmanager configuration |
|
||||
| **email.tmpl** | Email alert template |
|
||||
| **grefana-template/Demo.json** | Default Grafana dashboard |
|
||||
|
||||
To start multiple instances of an OpenIM service, simply increase the corresponding port numbers and modify the `start-config.yml` file in the project root directory. Restart the service to take effect. For example, the configuration to start 2 instances of `openim-rpc-user` is as follows:
|
||||
## Common Configuration Modifications
|
||||
| Configuration Item | Configuration File |
|
||||
| -------------------------------------------------------- | ----------------------- |
|
||||
| Configure MinIO as object storage (focus on the externalAddress field) | `minio.yml` |
|
||||
| Adjust log level and number of log files | `log.yml` |
|
||||
| Enable or disable friend verification when sending messages | `openim-rpc-msg.yml` |
|
||||
| OpenIMServer secret | `share.yml` |
|
||||
| Configure OSS, COS, AWS, or Kodo as object storage | `openim-rpc-third.yml` |
|
||||
| Multi-end mutual kick strategy and max concurrent connections per gateway | `openim-msggateway.yml` |
|
||||
| Offline message push configuration | `openim-push.yml` |
|
||||
| Configure webhooks for callback notifications (e.g., before/after message send) | `webhooks.yml` |
|
||||
| Whether new group members can view historical messages | `openim-rpc-group.yml` |
|
||||
| Token expiration time settings | `openim-rpc-auth.yml` |
|
||||
| Scheduled task settings (e.g., how long to retain messages) | `openim-crontask.yml` |
|
||||
|
||||
## Starting Multiple Instances of a Service and Maximum File Descriptors
|
||||
|
||||
|
||||
To start multiple instances of an OpenIM service, simply add the corresponding port numbers and modify the `start-config.yml` file in the project’s root directory,
|
||||
then restart the service. For example, to start 2 instances of `openim-rpc-user`:
|
||||
|
||||
```yaml
|
||||
rpc:
|
||||
@@ -55,9 +73,15 @@ prometheus:
|
||||
ports: [ 20100, 20101 ]
|
||||
```
|
||||
|
||||
Modify `start-config.yml`:
|
||||
Modify`start-config.yml`:
|
||||
|
||||
```yaml
|
||||
serviceBinaries:
|
||||
openim-rpc-user: 2
|
||||
```
|
||||
|
||||
To set the maximum number of open file descriptors (typically one per online user):
|
||||
|
||||
```
|
||||
maxFileDescriptors: 10000
|
||||
```
|
||||
|
||||
+57
-36
@@ -1,45 +1,63 @@
|
||||
# OpenIM配置文件说明以及常用配置修改说明
|
||||
|
||||
## 配置文件说明
|
||||
## 外部组件相关配置
|
||||
|
||||
| Configuration File | Description |
|
||||
| ------------------------------- | ------------------------------------------------------------ |
|
||||
| **kafka.yml** | Kafka用户名、密码、地址等配置 |
|
||||
| **redis.yml** | Redis密码、地址等配置 |
|
||||
| **minio.yml** | MinIO用户名、密码、地址及外网IP域名等配置;未修改外网IP或域名可能导致图片文件发送失败 |
|
||||
| **zookeeper.yml** | ZooKeeper用户、密码、地址等配置 |
|
||||
| **mongodb.yml** | MongoDB用户名、密码、地址等配置 |
|
||||
| **log.yml** | 日志级别及存储目录等配置 |
|
||||
| **notification.yml** | 添加好友、创建群组等事件通知配置 |
|
||||
| **share.yml** | OpenIM各服务所需的公共配置,如secret等 |
|
||||
| **webhooks.yml** | Webhook中URL等配置 |
|
||||
| **local-cache.yml** | 本地缓存配置 |
|
||||
| **openim-rpc-third.yml** | openim-rpc-third服务的监听IP、端口及图片视频对象存储配置 |
|
||||
| **openim-rpc-user.yml** | openim-rpc-user服务的监听IP、端口配置 |
|
||||
| **openim-api.yml** | openim-api服务的监听IP、端口等配置项 |
|
||||
| **openim-crontask.yml** | openim-crontask服务配置 |
|
||||
| **openim-msggateway.yml** | openim-msggateway服务的监听IP、端口等配置 |
|
||||
| **openim-msgtransfer.yml** | openim-msgtransfer服务配置 |
|
||||
| **openim-push.yml** | openim-push服务的监听IP、端口及离线推送配置 |
|
||||
| **openim-rpc-auth.yml** | openim-rpc-auth服务的监听IP、端口及token有效期等配置 |
|
||||
| **openim-rpc-conversation.yml** | openim-rpc-conversation服务的监听IP、端口等配置 |
|
||||
| **openim-rpc-friend.yml** | openim-rpc-friend服务的监听IP、端口等配置 |
|
||||
| **openim-rpc-group.yml** | openim-rpc-group服务的监听IP、端口等配置 |
|
||||
| **openim-rpc-msg.yml** | openim-rpc-msg服务的监听IP、端口及消息发送是否验证好友关系等配置 |
|
||||
| Configuration File | Description |
|
||||
| ------------------ | ---------------------------------- |
|
||||
| **kafka.yml** | Kafka用户名、密码、地址等配置 |
|
||||
| **redis.yml** | Redis密码、地址等配置 |
|
||||
| **minio.yml** | MinIO用户名、密码、地址等配置 |
|
||||
| **mongodb.yml** | MongoDB用户名、密码、地址等配置 |
|
||||
| **discovery.yml** | 服务发现以及etcd用户名、密码、地址 |
|
||||
|
||||
## OpenIMServer相关配置
|
||||
| Configuration File | Description |
|
||||
| ------------------------------- | ---------------------------------------------- |
|
||||
| **log.yml** | 日志级别及存储目录等配置 |
|
||||
| **notification.yml** | 添加好友、创建群组等事件通知配置 |
|
||||
| **share.yml** | 各服务所需的公共配置,如secret等 |
|
||||
| **webhooks.yml** | Webhook中URL等配置 |
|
||||
| **local-cache.yml** | 本地缓存配置,一般不用修改 |
|
||||
| **openim-rpc-third.yml** | openim-rpc-third监听IP、端口及对象存储配置 |
|
||||
| **openim-rpc-user.yml** | openim-rpc-user监听IP、端口配置 |
|
||||
| **openim-api.yml** | openim-api监听IP、端口等配置 |
|
||||
| **openim-crontask.yml** | openim-crontask定时任务配置 |
|
||||
| **openim-msggateway.yml** | openim-msggateway监听IP、端口等配置 |
|
||||
| **openim-msgtransfer.yml** | openim-msgtransfer服务配置 |
|
||||
| **openim-push.yml** | openim-push监听IP、端口及离线推送配置 |
|
||||
| **openim-rpc-auth.yml** | openim-rpc-auth监听IP、端口及token有效期等配置 |
|
||||
| **openim-rpc-conversation.yml** | openim-rpc-conversation监听IP、端口等配置 |
|
||||
| **openim-rpc-friend.yml** | openim-rpc-friend监听IP、端口等配置 |
|
||||
| **openim-rpc-group.yml** | openim-rpc-group监听IP、端口等配置 |
|
||||
| **openim-rpc-msg.yml** | openim-rpc-msg服务的监听IP、端口等配置 |
|
||||
|
||||
|
||||
## 监控告警相关配置
|
||||
| Configuration File | Description |
|
||||
| ------------------------------ | --------------- |
|
||||
| **prometheus.yml** | prometheus配置 |
|
||||
| **instance-down-rules.yml** | 告警规则 |
|
||||
| **alertmanager.yml** | 告警管理配置 |
|
||||
| **email.tmpl** | 邮件告警模版 |
|
||||
| **grefana-template/Demo.json** | 默认的dashboard |
|
||||
|
||||
## 常用配置修改
|
||||
| 修改配置项 | 配置文件 |
|
||||
| -------------------------------------------------------- | ----------------------- |
|
||||
| 使用minio作为对象存储时配置,重点关注externalAddress字段 | `minio.yml` |
|
||||
| 日志级别及日志文件数量调整 | `log.yml` |
|
||||
| 发送消息是否需要验证好友关系 | `openim-rpc-msg.yml` |
|
||||
| OpenIMServer秘钥 | `share.yml` |
|
||||
| 使用oss, cos, aws, kodo作为对象存储时配置 | `openim-rpc-third.yml` |
|
||||
| 多端互踢策略,单个gateway同时最大连接数 | `openim-msggateway.yml` |
|
||||
| 消息离线推送 | `openim-push.yml` |
|
||||
| 配置webhook来通知回调服务器,如消息发送前后回调 | `webhooks.yml` |
|
||||
| 新入群用户是否可以查看历史消息 | `openim-rpc-group.yml` |
|
||||
| token 过期时间设置 | `openim-rpc-auth.yml` |
|
||||
| 定时任务设置,例如消息保存多长时间 | `openim-crontask.yml` |
|
||||
|
||||
| 修改配置项 | 配置文件 |
|
||||
| ----------------------------------------------- | ----------------------- |
|
||||
| 使用minio作为图片视频文件对象存储 | `minio.yml` |
|
||||
| 生产环境日志调整 | `log.yml` |
|
||||
| 发送消息是否验证好友关系 | `openim-rpc-msg.yml` |
|
||||
| 修改secret | `share.yml` |
|
||||
| 使用oss, cos, aws, kodo作为图片视频文件对象存储 | `openim-rpc-third.yml` |
|
||||
| 设置多端互踢策略 | `openim-msggateway.yml` |
|
||||
| 设置离线推送 | `openim-push.yml` |
|
||||
## 启动某个服务的多个实例和最大文件句柄数
|
||||
|
||||
## 启动某个OpenIM服务的多个实例
|
||||
|
||||
若要启动某个OpenIM的多个实例,只需增加对应的端口数,并修改项目根目录下的`start-config.yml`文件,重启服务即可生效。例如,启动2个`openim-rpc-user`实例的配置如下:
|
||||
|
||||
@@ -61,5 +79,8 @@ serviceBinaries:
|
||||
openim-rpc-user: 2
|
||||
```
|
||||
|
||||
修改最大同时打开的文件句柄数,一般是每个在线用户占用一个
|
||||
|
||||
|
||||
```
|
||||
maxFileDescriptors: 10000
|
||||
```
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
enable: etcd
|
||||
etcd:
|
||||
rootDirectory: openim
|
||||
address: [ localhost:12379 ]
|
||||
username: ''
|
||||
password: ''
|
||||
address: [localhost:12379]
|
||||
## Attention: If you set auth in etcd
|
||||
## you must also update the username and password in Chat project.
|
||||
username:
|
||||
password:
|
||||
|
||||
kubernetes:
|
||||
namespace: default
|
||||
@@ -17,4 +19,4 @@ rpcService:
|
||||
group: group-rpc-service
|
||||
auth: auth-rpc-service
|
||||
conversation: conversation-rpc-service
|
||||
third: third-rpc-service
|
||||
third: third-rpc-service
|
||||
|
||||
+10
-10
@@ -1,13 +1,13 @@
|
||||
# Username for authentication
|
||||
username: ''
|
||||
# Password for authentication
|
||||
password: ''
|
||||
## Kafka authentication
|
||||
username:
|
||||
password:
|
||||
|
||||
# Producer acknowledgment settings
|
||||
producerAck:
|
||||
producerAck:
|
||||
# Compression type to use (e.g., none, gzip, snappy)
|
||||
compressType: none
|
||||
# List of Kafka broker addresses
|
||||
address: [ localhost:19094 ]
|
||||
address: [localhost:19094]
|
||||
# Kafka topic for Redis integration
|
||||
toRedisTopic: toRedis
|
||||
# Kafka topic for MongoDB integration
|
||||
@@ -29,12 +29,12 @@ tls:
|
||||
# Enable or disable TLS
|
||||
enableTLS: false
|
||||
# CA certificate file path
|
||||
caCrt:
|
||||
caCrt:
|
||||
# Client certificate file path
|
||||
clientCrt:
|
||||
clientCrt:
|
||||
# Client key file path
|
||||
clientKey:
|
||||
clientKey:
|
||||
# Client key password
|
||||
clientKeyPwd:
|
||||
clientKeyPwd:
|
||||
# Whether to skip TLS verification (not recommended for production)
|
||||
insecureSkipVerify: false
|
||||
|
||||
@@ -1,3 +1,10 @@
|
||||
auth:
|
||||
topic: DELETE_CACHE_AUTH
|
||||
slotNum: 100
|
||||
slotSize: 2000
|
||||
successExpire: 300
|
||||
failedExpire: 5
|
||||
|
||||
user:
|
||||
topic: DELETE_CACHE_USER
|
||||
slotNum: 100
|
||||
|
||||
+37
-2
@@ -1,7 +1,7 @@
|
||||
# URI for database connection, leave empty if using address and credential settings directly
|
||||
uri:
|
||||
uri:
|
||||
# List of MongoDB server addresses
|
||||
address: [ localhost:37017 ]
|
||||
address: [localhost:37017]
|
||||
# Name of the database
|
||||
database: openim_v3
|
||||
# Username for database authentication
|
||||
@@ -14,3 +14,38 @@ authSource: openim_v3
|
||||
maxPoolSize: 100
|
||||
# Maximum number of retry attempts for a failed database connection
|
||||
maxRetry: 10
|
||||
# MongoDB Mode, including "standalone", "replicaSet"
|
||||
mongoMode: "standalone"
|
||||
|
||||
# The following configurations only take effect when mongoMode is set to "replicaSet"
|
||||
replicaSet:
|
||||
name: rs0
|
||||
hosts: [127.0.0.1:37017, 127.0.0.1:37018, 127.0.0.1:37019]
|
||||
# Read concern level: "local", "available", "majority", "linearizable", "snapshot"
|
||||
readConcern: majority
|
||||
# maximum staleness of data in seconds
|
||||
maxStaleness: 90s
|
||||
|
||||
# The following configurations only take effect when mongoMode is set to "replicaSet"
|
||||
readPreference:
|
||||
# Read preference mode, can be "primary", "primaryPreferred", "secondary", "secondaryPreferred", "nearest"
|
||||
mode: primary
|
||||
maxStaleness: 90s
|
||||
# TagSets is an array of maps with priority based on order, empty map must be placed last for fallback tagSets
|
||||
tagSets:
|
||||
- datacenter: "cn-east"
|
||||
rack: "1"
|
||||
storage: "ssd"
|
||||
- datacenter: "cn-east"
|
||||
storage: "ssd"
|
||||
- datacenter: "cn-east"
|
||||
- {} # Empty map, indicates any node
|
||||
|
||||
# The following configurations only take effect when mongoMode is set to "replicaSet"
|
||||
writeConcern:
|
||||
# Write node count or tag (int, "majority", or custom tag)
|
||||
w: majority
|
||||
# Whether to wait for journal confirmation
|
||||
j: true
|
||||
# Write timeout duration
|
||||
wtimeout: 30s
|
||||
|
||||
+13
-13
@@ -31,7 +31,7 @@ joinGroupApplication:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: false
|
||||
enable: true
|
||||
title: joinGroupApplication title
|
||||
desc: joinGroupApplication desc
|
||||
ext: joinGroupApplication ext
|
||||
@@ -51,7 +51,7 @@ groupApplicationAccepted:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: false
|
||||
enable: true
|
||||
title: groupApplicationAccepted title
|
||||
desc: groupApplicationAccepted desc
|
||||
ext: groupApplicationAccepted ext
|
||||
@@ -61,7 +61,7 @@ groupApplicationRejected:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: false
|
||||
enable: true
|
||||
title: groupApplicationRejected title
|
||||
desc: groupApplicationRejected desc
|
||||
ext: groupApplicationRejected ext
|
||||
@@ -198,7 +198,7 @@ friendApplicationAdded:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: false
|
||||
enable: true
|
||||
title: Somebody applies to add you as a friend
|
||||
desc: Somebody applies to add you as a friend
|
||||
ext: Somebody applies to add you as a friend
|
||||
@@ -228,7 +228,7 @@ friendAdded:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: true
|
||||
enable: false
|
||||
title: We have become friends
|
||||
desc: We have become friends
|
||||
ext: We have become friends
|
||||
@@ -238,7 +238,7 @@ friendDeleted:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: true
|
||||
enable: false
|
||||
title: deleted a friend
|
||||
desc: deleted a friend
|
||||
ext: deleted a friend
|
||||
@@ -248,7 +248,7 @@ friendRemarkSet:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: true
|
||||
enable: false
|
||||
title: Your friend's profile has been changed
|
||||
desc: Your friend's profile has been changed
|
||||
ext: Your friend's profile has been changed
|
||||
@@ -258,7 +258,7 @@ blackAdded:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: true
|
||||
enable: false
|
||||
title: blocked a user
|
||||
desc: blocked a user
|
||||
ext: blocked a user
|
||||
@@ -268,7 +268,7 @@ blackDeleted:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: true
|
||||
enable: false
|
||||
title: Remove a blocked user
|
||||
desc: Remove a blocked user
|
||||
ext: Remove a blocked user
|
||||
@@ -278,7 +278,7 @@ friendInfoUpdated:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: true
|
||||
enable: false
|
||||
title: friend info updated
|
||||
desc: friend info updated
|
||||
ext: friend info updated
|
||||
@@ -289,7 +289,7 @@ userInfoUpdated:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: true
|
||||
enable: false
|
||||
title: userInfo updated
|
||||
desc: userInfo updated
|
||||
ext: userInfo updated
|
||||
@@ -310,7 +310,7 @@ conversationChanged:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: true
|
||||
enable: false
|
||||
title: conversation changed
|
||||
desc: conversation changed
|
||||
ext: conversation changed
|
||||
@@ -320,7 +320,7 @@ conversationSetPrivate:
|
||||
reliabilityLevel: 1
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: true
|
||||
enable: false
|
||||
title: burn after reading
|
||||
desc: burn after reading
|
||||
ext: burn after reading
|
||||
|
||||
@@ -17,3 +17,13 @@ prometheus:
|
||||
ports:
|
||||
# This address can be accessed via a browser
|
||||
grafanaURL:
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
@@ -26,3 +26,20 @@ longConnSvr:
|
||||
websocketMaxMsgLen: 4096
|
||||
# WebSocket connection handshake timeout in seconds
|
||||
websocketTimeout: 10
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
circuitBreaker:
|
||||
enable: false
|
||||
window: 5s # Time window size (seconds)
|
||||
bucket: 100 # Number of buckets
|
||||
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||
@@ -6,3 +6,20 @@ prometheus:
|
||||
# List of ports that Prometheus listens on; each port corresponds to an instance of monitoring. Ensure these are managed accordingly
|
||||
# It will only take effect when autoSetPorts is set to false.
|
||||
ports:
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
circuitBreaker:
|
||||
enable: false
|
||||
window: 5s # Time window size (seconds)
|
||||
bucket: 100 # Number of buckets
|
||||
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||
+17
-1
@@ -10,10 +10,26 @@ rpc:
|
||||
# It will only take effect when autoSetPorts is set to false.
|
||||
ports:
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
circuitBreaker:
|
||||
enable: false
|
||||
window: 5s # Time window size (seconds)
|
||||
bucket: 100 # Number of buckets
|
||||
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||
|
||||
prometheus:
|
||||
# Enable or disable Prometheus monitoring
|
||||
enable: true
|
||||
enable: false
|
||||
# List of ports that Prometheus listens on; these must match the number of rpc.ports to ensure correct monitoring setup
|
||||
# It will only take effect when autoSetPorts is set to false.
|
||||
ports:
|
||||
|
||||
@@ -20,3 +20,20 @@ prometheus:
|
||||
tokenPolicy:
|
||||
# Token validity period, in days
|
||||
expire: 90
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
circuitBreaker:
|
||||
enable: false
|
||||
window: 5s # Time window size (seconds)
|
||||
bucket: 100 # Number of buckets
|
||||
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||
@@ -16,3 +16,20 @@ prometheus:
|
||||
# List of ports that Prometheus listens on; these must match the number of rpc.ports to ensure correct monitoring setup
|
||||
# It will only take effect when autoSetPorts is set to false.
|
||||
ports:
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
circuitBreaker:
|
||||
enable: false
|
||||
window: 5s # Time window size (seconds)
|
||||
bucket: 100 # Number of buckets
|
||||
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||
@@ -16,3 +16,20 @@ prometheus:
|
||||
# List of ports that Prometheus listens on; these must match the number of rpc.ports to ensure correct monitoring setup
|
||||
# It will only take effect when autoSetPorts is set to false.
|
||||
ports:
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
circuitBreaker:
|
||||
enable: false
|
||||
window: 5s # Time window size (seconds)
|
||||
bucket: 100 # Number of buckets
|
||||
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||
@@ -19,3 +19,20 @@ prometheus:
|
||||
|
||||
|
||||
enableHistoryForNewMembers: true
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
circuitBreaker:
|
||||
enable: false
|
||||
window: 5s # Time window size (seconds)
|
||||
bucket: 100 # Number of buckets
|
||||
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||
@@ -1,6 +1,6 @@
|
||||
rpc:
|
||||
# The IP address where this RPC service registers itself; if left blank, it defaults to the internal network IP
|
||||
registerIP:
|
||||
registerIP:
|
||||
# IP address that the RPC service listens on; setting to 0.0.0.0 listens on both internal and external IPs. If left blank, it automatically uses the internal network IP
|
||||
listenIP: 0.0.0.0
|
||||
# autoSetPorts indicates whether to automatically set the ports
|
||||
@@ -20,3 +20,20 @@ prometheus:
|
||||
|
||||
# Does sending messages require friend verification
|
||||
friendVerify: false
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
circuitBreaker:
|
||||
enable: false
|
||||
window: 5s # Time window size (seconds)
|
||||
bucket: 100 # Number of buckets
|
||||
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||
@@ -17,6 +17,22 @@ prometheus:
|
||||
# It will only take effect when autoSetPorts is set to false.
|
||||
ports:
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
circuitBreaker:
|
||||
enable: false
|
||||
window: 5s # Time window size (seconds)
|
||||
bucket: 100 # Number of buckets
|
||||
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||
|
||||
object:
|
||||
# Use MinIO as object storage, or set to "cos", "oss", "kodo", "aws", while also configuring the corresponding settings
|
||||
|
||||
@@ -16,3 +16,20 @@ prometheus:
|
||||
# Prometheus listening ports, must be consistent with the number of rpc.ports
|
||||
# It will only take effect when autoSetPorts is set to false.
|
||||
ports:
|
||||
|
||||
ratelimiter:
|
||||
# Whether to enable rate limiting
|
||||
enable: false
|
||||
# WindowSize defines time duration per window
|
||||
window: 20s
|
||||
# BucketNum defines bucket number for each window
|
||||
bucket: 500
|
||||
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
cpuThreshold: 850
|
||||
|
||||
circuitBreaker:
|
||||
enable: false
|
||||
window: 5s # Time window size (seconds)
|
||||
bucket: 100 # Number of buckets
|
||||
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||
+10
-3
@@ -1,7 +1,14 @@
|
||||
address: [ localhost:16379 ]
|
||||
username:
|
||||
address: [localhost:16379]
|
||||
username:
|
||||
password: openIM123
|
||||
clusterMode: false
|
||||
# redis Mode, including "standalone","cluster","sentinel"
|
||||
redisMode: "standalone"
|
||||
db: 0
|
||||
maxRetry: 10
|
||||
poolSize: 100
|
||||
# Sentinel configuration (only used when redisMode is "sentinel")
|
||||
sentinelMode:
|
||||
masterName: "redis-master"
|
||||
sentinelsAddrs: ["127.0.0.1:26379", "127.0.0.1:26380", "127.0.0.1:26381"]
|
||||
routeByLatency: true
|
||||
routeRandomly: true
|
||||
|
||||
+13
-2
@@ -1,9 +1,20 @@
|
||||
secret: openIM123
|
||||
|
||||
imAdminUserID: [ imAdmin ]
|
||||
# imAdminUser: Configuration for instant messaging system administrators
|
||||
imAdminUser:
|
||||
# userIDs: List of administrator user IDs.
|
||||
# Each entry here corresponds by index to the matching entry in the nicknames list below.
|
||||
userIDs: [imAdmin]
|
||||
# nicknames: List of administrator display names.
|
||||
# Each entry here corresponds by index to the matching entry in the userIDs list above.
|
||||
nicknames: [superAdmin]
|
||||
|
||||
# 1: For Android, iOS, Windows, Mac, and web platforms, only one instance can be online at a time
|
||||
multiLogin:
|
||||
policy: 1
|
||||
# max num of tokens in one end
|
||||
maxNumOneEnd: 30
|
||||
maxNumOneEnd: 30
|
||||
|
||||
rpcMaxBodySize:
|
||||
requestMaxBodySize: 8388608
|
||||
responseMaxBodySize: 8388608
|
||||
|
||||
+25
-14
@@ -3,52 +3,47 @@ beforeSendSingleMsg:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
# Only the contentType in allowedTypes will send the callback.
|
||||
# Supports two formats: a single type or a range. The range is defined by the lower and upper bounds connected with a hyphen ("-").
|
||||
# e.g. allowedTypes: [1, 100, 200-500, 600-700] means that only contentType within the range
|
||||
# {1, 100} ∪ [200, 500] ∪ [600, 700] will be allowed through the filter.
|
||||
# If not set, all contentType messages will through this filter.
|
||||
allowedTypes: []
|
||||
# Only the contentType not in deniedTypes will send the callback.
|
||||
# Supports two formats, same as allowedTypes.
|
||||
# If not set, all contentType messages will through this filter.
|
||||
deniedTypes: []
|
||||
beforeUpdateUserInfoEx:
|
||||
enable: false
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
afterUpdateUserInfoEx:
|
||||
enable: false
|
||||
enable: false
|
||||
timeout: 5
|
||||
afterSendSingleMsg:
|
||||
enable: false
|
||||
timeout: 5
|
||||
# Only the senID/recvID specified in attentionIds will send the callback
|
||||
# Only the recvIDs specified in attentionIds will send the callback
|
||||
# if not set, all user messages will be callback
|
||||
attentionIds: []
|
||||
# See beforeSendSingleMsg comment.
|
||||
allowedTypes: []
|
||||
deniedTypes: []
|
||||
beforeSendGroupMsg:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
# See beforeSendSingleMsg comment.
|
||||
allowedTypes: []
|
||||
deniedTypes: []
|
||||
beforeMsgModify:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
# See beforeSendSingleMsg comment.
|
||||
allowedTypes: []
|
||||
deniedTypes: []
|
||||
afterSendGroupMsg:
|
||||
enable: false
|
||||
timeout: 5
|
||||
# Only the GroupIDs specified in attentionIds will send the callback
|
||||
# if not set, all user messages will be callback
|
||||
attentionIds: []
|
||||
# See beforeSendSingleMsg comment.
|
||||
allowedTypes: []
|
||||
deniedTypes: []
|
||||
afterMsgSaveDB:
|
||||
enable: false
|
||||
timeout: 5
|
||||
afterUserOnline:
|
||||
enable: false
|
||||
timeout: 5
|
||||
@@ -189,3 +184,19 @@ afterImportFriends:
|
||||
afterRemoveBlack:
|
||||
enable: false
|
||||
timeout: 5
|
||||
beforeCreateSingleChatConversations:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: false
|
||||
afterCreateSingleChatConversations:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: false
|
||||
beforeCreateGroupChatConversations:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: false
|
||||
afterCreateGroupChatConversations:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: false
|
||||
|
||||
+149
-23
@@ -37,6 +37,7 @@ services:
|
||||
- "${DATA_DIR}/components/mongodb/data/db:/data/db"
|
||||
- "${DATA_DIR}/components/mongodb/data/logs:/data/logs"
|
||||
- "${DATA_DIR}/components/mongodb/data/conf:/etc/mongo"
|
||||
- "${MONGO_BACKUP_DIR}:/data/backup"
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- wiredTigerCacheSizeGB=1
|
||||
@@ -62,7 +63,12 @@ services:
|
||||
restart: always
|
||||
sysctls:
|
||||
net.core.somaxconn: 1024
|
||||
command: redis-server /usr/local/redis/config/redis.conf --requirepass openIM123 --appendonly yes
|
||||
command: >
|
||||
redis-server
|
||||
--requirepass openIM123
|
||||
--appendonly yes
|
||||
--aof-use-rdb-preamble yes
|
||||
--save ""
|
||||
networks:
|
||||
- openim
|
||||
|
||||
@@ -82,8 +88,83 @@ services:
|
||||
- ETCD_INITIAL_CLUSTER=s1=http://0.0.0.0:2380
|
||||
- ETCD_INITIAL_CLUSTER_TOKEN=tkn
|
||||
- ETCD_INITIAL_CLUSTER_STATE=new
|
||||
- ALLOW_NONE_AUTHENTICATION=no
|
||||
|
||||
## Optional: Enable etcd authentication by setting the following credentials
|
||||
# - ETCD_ROOT_USER=root
|
||||
# - ETCD_ROOT_PASSWORD=openIM123
|
||||
# - ETCD_USERNAME=openIM
|
||||
# - ETCD_PASSWORD=openIM123
|
||||
volumes:
|
||||
- "${DATA_DIR}/components/etcd:/etcd-data"
|
||||
command: >
|
||||
/bin/sh -c '
|
||||
etcd &
|
||||
export ETCDCTL_API=3
|
||||
echo "Waiting for etcd to become healthy..."
|
||||
until etcdctl --endpoints=http://127.0.0.1:2379 endpoint health &>/dev/null; do
|
||||
echo "Waiting for ETCD to start..."
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "etcd is healthy."
|
||||
|
||||
if [ -n "$${ETCD_ROOT_USER}" ] && [ -n "$${ETCD_ROOT_PASSWORD}" ] && [ -n "$${ETCD_USERNAME}" ] && [ -n "$${ETCD_PASSWORD}" ]; then
|
||||
echo "Authentication credentials provided. Setting up authentication..."
|
||||
|
||||
echo "Checking authentication status..."
|
||||
if ! etcdctl --endpoints=http://127.0.0.1:2379 auth status | grep -q "Authentication Status: true"; then
|
||||
echo "Authentication is disabled. Creating users and enabling..."
|
||||
|
||||
# Create users and setup permissions
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 user add $${ETCD_ROOT_USER} --new-user-password=$${ETCD_ROOT_PASSWORD} || true
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 user add $${ETCD_USERNAME} --new-user-password=$${ETCD_PASSWORD} || true
|
||||
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 role add openim-role || true
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 role grant-permission openim-role --prefix=true readwrite / || true
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 role grant-permission openim-role --prefix=true readwrite "" || true
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 user grant-role $${ETCD_USERNAME} openim-role || true
|
||||
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 user grant-role $${ETCD_ROOT_USER} $${ETCD_USERNAME} root || true
|
||||
|
||||
echo "Enabling authentication..."
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 auth enable
|
||||
echo "Authentication enabled successfully"
|
||||
else
|
||||
echo "Authentication is already enabled. Checking OpenIM user..."
|
||||
|
||||
# Check if openIM user exists and can perform operations
|
||||
if ! etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} put /test/auth "auth-check" &>/dev/null; then
|
||||
echo "OpenIM user test failed. Recreating user with root credentials..."
|
||||
|
||||
# Try to create/update the openIM user using root credentials
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} user add $${ETCD_USERNAME} --new-user-password=$${ETCD_PASSWORD} --no-password-file || true
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} role add openim-role || true
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} role grant-permission openim-role --prefix=true readwrite / || true
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} role grant-permission openim-role --prefix=true readwrite "" || true
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} user grant-role $${ETCD_USERNAME} openim-role || true
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 user grant-role $${ETCD_ROOT_USER} $${ETCD_USERNAME} root || true
|
||||
|
||||
echo "OpenIM user recreated with required permissions"
|
||||
else
|
||||
echo "OpenIM user exists and has correct permissions"
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} del /test/auth &>/dev/null
|
||||
fi
|
||||
fi
|
||||
echo "Testing authentication with OpenIM user..."
|
||||
if etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} put /test/auth "auth-works"; then
|
||||
echo "Authentication working properly"
|
||||
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} del /test/auth
|
||||
else
|
||||
echo "WARNING: Authentication test failed"
|
||||
fi
|
||||
else
|
||||
echo "No authentication credentials provided. Running in no-auth mode."
|
||||
echo "To enable authentication, set ETCD_ROOT_USER, ETCD_ROOT_PASSWORD, ETCD_USERNAME, and ETCD_PASSWORD environment variables."
|
||||
fi
|
||||
|
||||
tail -f /dev/null
|
||||
'
|
||||
restart: always
|
||||
networks:
|
||||
- openim
|
||||
@@ -100,15 +181,61 @@ services:
|
||||
environment:
|
||||
#KAFKA_HEAP_OPTS: "-Xms128m -Xmx256m"
|
||||
TZ: Asia/Shanghai
|
||||
# Unique identifier for the Kafka node (required in controller mode)
|
||||
KAFKA_CFG_NODE_ID: 0
|
||||
# Defines the roles this Kafka node plays: broker, controller, or both
|
||||
KAFKA_CFG_PROCESS_ROLES: controller,broker
|
||||
# Specifies which nodes are controller nodes for quorum voting.
|
||||
# The syntax follows the KRaft mode (no ZooKeeper): node.id@host:port
|
||||
# The controller listener endpoint here is kafka:9093
|
||||
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093
|
||||
KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094
|
||||
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:19094
|
||||
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
|
||||
# Specifies which listener is used for controller-to-controller communication
|
||||
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
||||
# Default number of partitions for new topics
|
||||
KAFKA_NUM_PARTITIONS: 8
|
||||
# Whether to enable automatic topic creation
|
||||
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true"
|
||||
# Kafka internal listeners; Kafka supports multiple ports with different protocols
|
||||
# Each port is used for a specific purpose: INTERNAL for internal broker communication,
|
||||
# CONTROLLER for controller communication, EXTERNAL for external client connections.
|
||||
# These logical listener names are mapped to actual protocols via KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP
|
||||
# In short, Kafka is listening on three logical ports: 9092 for internal communication,
|
||||
# 9093 for controller traffic, and 9094 for external access.
|
||||
KAFKA_CFG_LISTENERS: "INTERNAL://:9092,CONTROLLER://:9093,EXTERNAL://:9094"
|
||||
# Addresses advertised to clients. INTERNAL://kafka:9092 uses the internal Docker service name 'kafka',
|
||||
# so other containers can access Kafka via kafka:9092.
|
||||
# EXTERNAL://localhost:19094 is the address external clients (e.g., in the LAN) should use to connect.
|
||||
# If Kafka is deployed on a different machine than IM, 'localhost' should be replaced with the LAN IP.
|
||||
KAFKA_CFG_ADVERTISED_LISTENERS: "INTERNAL://kafka:9092,EXTERNAL://localhost:19094"
|
||||
# Maps logical listener names to actual protocols.
|
||||
# Supported protocols include: PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL
|
||||
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT"
|
||||
# Defines which listener is used for inter-broker communication within the Kafka cluster
|
||||
KAFKA_CFG_INTER_BROKER_LISTENER_NAME: "INTERNAL"
|
||||
|
||||
# Authentication configuration variables - comment out to disable auth
|
||||
# KAFKA_USERNAME: "openIM"
|
||||
# KAFKA_PASSWORD: "openIM123"
|
||||
command: >
|
||||
/bin/sh -c '
|
||||
if [ -n "$${KAFKA_USERNAME}" ] && [ -n "$${KAFKA_PASSWORD}" ]; then
|
||||
echo "=== Kafka SASL Authentication ENABLED ==="
|
||||
echo "Username: $${KAFKA_USERNAME}"
|
||||
|
||||
# Set environment variables for SASL authentication
|
||||
export KAFKA_CFG_LISTENERS="SASL_PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094"
|
||||
export KAFKA_CFG_ADVERTISED_LISTENERS="SASL_PLAINTEXT://kafka:9092,EXTERNAL://localhost:19094"
|
||||
export KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP="CONTROLLER:PLAINTEXT,EXTERNAL:SASL_PLAINTEXT,SASL_PLAINTEXT:SASL_PLAINTEXT"
|
||||
export KAFKA_CFG_SASL_ENABLED_MECHANISMS="PLAIN"
|
||||
export KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL="PLAIN"
|
||||
export KAFKA_CFG_INTER_BROKER_LISTENER_NAME="SASL_PLAINTEXT"
|
||||
export KAFKA_CLIENT_USERS="$${KAFKA_USERNAME}"
|
||||
export KAFKA_CLIENT_PASSWORDS="$${KAFKA_PASSWORD}"
|
||||
fi
|
||||
|
||||
# Start Kafka with the configured environment
|
||||
exec /opt/bitnami/scripts/kafka/entrypoint.sh /opt/bitnami/scripts/kafka/run.sh
|
||||
'
|
||||
networks:
|
||||
- openim
|
||||
|
||||
@@ -139,15 +266,15 @@ services:
|
||||
networks:
|
||||
- openim
|
||||
|
||||
openim-admin-front:
|
||||
image: ${OPENIM_ADMIN_FRONT_IMAGE}
|
||||
container_name: openim-admin-front
|
||||
restart: always
|
||||
ports:
|
||||
- "11002:80"
|
||||
networks:
|
||||
- openim
|
||||
|
||||
# openim-admin-front:
|
||||
# image: ${OPENIM_ADMIN_FRONT_IMAGE}
|
||||
# container_name: openim-admin-front
|
||||
# restart: always
|
||||
# ports:
|
||||
# - "11002:80"
|
||||
# networks:
|
||||
# - openim
|
||||
|
||||
prometheus:
|
||||
image: ${PROMETHEUS_IMAGE}
|
||||
container_name: prometheus
|
||||
@@ -160,9 +287,9 @@ services:
|
||||
- ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
|
||||
- ${DATA_DIR}/components/prometheus/data:/prometheus
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
- '--storage.tsdb.path=/prometheus'
|
||||
- '--web.listen-address=:${PROMETHEUS_PORT}'
|
||||
- "--config.file=/etc/prometheus/prometheus.yml"
|
||||
- "--storage.tsdb.path=/prometheus"
|
||||
- "--web.listen-address=:${PROMETHEUS_PORT}"
|
||||
network_mode: host
|
||||
|
||||
alertmanager:
|
||||
@@ -175,8 +302,8 @@ services:
|
||||
- ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml
|
||||
- ./config/email.tmpl:/etc/alertmanager/email.tmpl
|
||||
command:
|
||||
- '--config.file=/etc/alertmanager/alertmanager.yml'
|
||||
- '--web.listen-address=:${ALERTMANAGER_PORT}'
|
||||
- "--config.file=/etc/alertmanager/alertmanager.yml"
|
||||
- "--web.listen-address=:${ALERTMANAGER_PORT}"
|
||||
network_mode: host
|
||||
|
||||
grafana:
|
||||
@@ -208,9 +335,8 @@ services:
|
||||
- /sys:/host/sys:ro
|
||||
- /:/rootfs:ro
|
||||
command:
|
||||
- '--path.procfs=/host/proc'
|
||||
- '--path.sysfs=/host/sys'
|
||||
- '--path.rootfs=/rootfs'
|
||||
- '--web.listen-address=:19100'
|
||||
- "--path.procfs=/host/proc"
|
||||
- "--path.sysfs=/host/sys"
|
||||
- "--path.rootfs=/rootfs"
|
||||
- "--web.listen-address=:19100"
|
||||
network_mode: host
|
||||
|
||||
|
||||
+16
-20
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,7 +45,6 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
@@ -61,14 +59,14 @@ OpenIM je platforma služeb speciálně navržená pro integraci chatu, audio-vi
|
||||
|
||||
**OpenIMSDK** je IM SDK navržený pro**OpenIMServer**, vytvořený speciálně pro vkládání do klientských aplikací. Jeho hlavní vlastnosti a moduly jsou následující:
|
||||
|
||||
+ 🌟 Hlavní vlastnosti:
|
||||
- 🌟 Hlavní vlastnosti:
|
||||
|
||||
- 📦 Místní úložiště
|
||||
- 🔔 Zpětná volání posluchačů
|
||||
- 🛡️ API obalování
|
||||
- 🌐 Správa připojení
|
||||
|
||||
+ 📚 hlavní moduly:
|
||||
- 📚 hlavní moduly:
|
||||
|
||||
1. 🚀 Inicializace a přihlášení
|
||||
2. 👤 Správa uživatelů
|
||||
@@ -82,15 +80,15 @@ Je postaven pomocí Golang a podporuje nasazení napříč platformami, což zaj
|
||||
|
||||
## 🌐 O OpenIMServeru
|
||||
|
||||
+ **OpenIMServer** má následující vlastnosti:
|
||||
- **OpenIMServer** má následující vlastnosti:
|
||||
- 🌐 Architektura mikroslužeb: Podporuje režim clusteru, včetně brány a více služeb RPC.
|
||||
- 🚀 Různé metody nasazení: Podporuje nasazení prostřednictvím zdrojového kódu, Kubernetes nebo Docker.
|
||||
- Podpora masivní uživatelské základny: Super velké skupiny se stovkami tisíc uživatelů, desítkami milionů uživatelů a miliardami zpráv.
|
||||
|
||||
### Vylepšené obchodní funkce:
|
||||
|
||||
+ **REST API**: OpenIMServer nabízí REST API pro podnikové systémy, jejichž cílem je poskytnout podnikům více funkcí, jako je vytváření skupin a odesílání push zpráv přes backendová rozhraní.
|
||||
+ **Webhooks**: OpenIMServer poskytuje možnosti zpětného volání pro rozšíření více obchodních formulářů. Zpětné volání znamená, že OpenIMServer odešle požadavek na obchodní server před nebo po určité události, jako jsou zpětná volání před nebo po odeslání zprávy.
|
||||
- **REST API**: OpenIMServer nabízí REST API pro podnikové systémy, jejichž cílem je poskytnout podnikům více funkcí, jako je vytváření skupin a odesílání push zpráv přes backendová rozhraní.
|
||||
- **Webhooks**: OpenIMServer poskytuje možnosti zpětného volání pro rozšíření více obchodních formulářů. Zpětné volání znamená, že OpenIMServer odešle požadavek na obchodní server před nebo po určité události, jako jsou zpětná volání před nebo po odeslání zprávy.
|
||||
|
||||
👉 **[Další informace](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
@@ -100,7 +98,6 @@ Ponořte se do srdce funkčnosti Open-IM-Server s naším diagramem architektury
|
||||
|
||||

|
||||
|
||||
|
||||
## :rocket: Rychlý start
|
||||
|
||||
Podporujeme mnoho platforem. Zde jsou adresy pro rychlou práci na webové stránce:
|
||||
@@ -109,10 +106,10 @@ Podporujeme mnoho platforem. Zde jsou adresy pro rychlou práci na webové strá
|
||||
|
||||
🤲 Pro usnadnění uživatelské zkušenosti nabízíme různá řešení nasazení. Způsob nasazení si můžete vybrat ze seznamu níže:
|
||||
|
||||
+ **[Průvodce nasazením zdrojového kódu](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Průvodce nasazením Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Průvodce nasazením pro vývojáře Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[Průvodce nasazením zdrojového kódu](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Průvodce nasazením Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Průvodce nasazením pro vývojáře Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: Chcete-li začít vyvíjet OpenIM
|
||||
|
||||
@@ -122,7 +119,7 @@ OpenIM Naším cílem je vybudovat špičkovou open source komunitu. Máme soubo
|
||||
|
||||
Pokud byste chtěli přispět do tohoto úložiště Open-IM-Server, přečtěte si naši [dokumentaci pro přispěvatele](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
|
||||
|
||||
Než začnete, ujistěte se, že jsou vaše změny vyžadovány. Nejlepší pro to je vytvořit [nová diskuze](https://github.com/openimsdk/open-im-server/discussions/new/choose) NEBO [Slack Communication](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), nebo pokud narazíte na problém, [nahlásit jej](https://github.com/openimsdk/open-im-server/issues/new/choose) jako první.
|
||||
Než začnete, ujistěte se, že jsou vaše změny vyžadovány. Nejlepší pro to je vytvořit [nová diskuze](https://github.com/openimsdk/open-im-server/discussions/new/choose) NEBO [Slack Communication](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A), nebo pokud narazíte na problém, [nahlásit jej](https://github.com/openimsdk/open-im-server/issues/new/choose) jako první.
|
||||
|
||||
- [OpenIM API Reference](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [Protokolování OpenIM Bash](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
@@ -154,19 +151,18 @@ Než začnete, ujistěte se, že jsou vaše změny vyžadovány. Nejlepší pro
|
||||
- [Spravovat backend a monitorovat nasazení](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [Průvodce nasazením pro vývojáře Mac pro OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
## :busts_in_silhouette: Společenství
|
||||
|
||||
+ 📚 [Komunita OpenIM](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [Zájmová skupina OpenIM](https://github.com/Openim-sigs)
|
||||
+ 🚀 [Připojte se k naší komunitě Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [Připojte se k našemu wechatu](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 📚 [Komunita OpenIM](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [Zájmová skupina OpenIM](https://github.com/Openim-sigs)
|
||||
- 🚀 [Připojte se k naší komunitě Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [Připojte se k našemu wechatu](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## :calendar: Komunitní setkání
|
||||
|
||||
Chceme, aby se do naší komunity a přispívání kódu zapojil kdokoli, nabízíme dárky a odměny a vítáme vás, abyste se k nám připojili každý čtvrtek večer.
|
||||
|
||||
Naše konference je v [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, pak můžete vyhledat kanál Open-IM-Server a připojit se
|
||||
Naše konference je v [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, pak můžete vyhledat kanál Open-IM-Server a připojit se
|
||||
|
||||
Zaznamenáváme si každou [dvoutýdenní schůzku](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)do [diskuzí na GitHubu](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), naše historické poznámky ze schůzek a také záznamy schůzek jsou k dispozici na [Dokumenty Google :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
|
||||
|
||||
|
||||
+19
-26
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,35 +45,30 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
## :busts_in_silhouette: Fællesskab
|
||||
|
||||
+ 📚 [OpenIM-fællesskab](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [OpenIM-interessegruppe](https://github.com/Openim-sigs)
|
||||
+ 🚀 [Deltag i vores Slack-fællesskab](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [Deltag i vores WeChat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
+ 👫 [Deltag i vores Reddit](https://www.reddit.com/r/OpenIMessaging)
|
||||
+ 💬 [Følg vores Twitter-konto](https://twitter.com/openimsdk)
|
||||
|
||||
- 📚 [OpenIM-fællesskab](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [OpenIM-interessegruppe](https://github.com/Openim-sigs)
|
||||
- 🚀 [Deltag i vores Slack-fællesskab](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [Deltag i vores WeChat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 👫 [Deltag i vores Reddit](https://www.reddit.com/r/OpenIMessaging)
|
||||
- 💬 [Følg vores Twitter-konto](https://twitter.com/openimsdk)
|
||||
|
||||
## Ⓜ️ Om OpenIM
|
||||
|
||||
OpenIM er en serviceplatform designet specifikt til integration af chat, lyd-videoopkald, notifikationer og AI-chatbots i applikationer. Den tilbyder en række kraftfulde API'er og Webhooks, som gør det let for udviklere at integrere disse interaktive funktioner i deres applikationer. OpenIM er ikke en selvstændig chatapplikation, men fungerer snarere som en platform, der understøtter andre applikationer i at opnå omfattende kommunikationsfunktionaliteter. Følgende diagram illustrerer interaktionen mellem AppServer, AppClient, OpenIMServer og OpenIMSDK for at forklare detaljeret.
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
## 🚀 Om OpenIMSDK
|
||||
|
||||
**OpenIMSDK** er en IM SDK designet til **OpenIMServer**, skabt specifikt til indlejring i klientapplikationer. Dens vigtigste funktioner og moduler er som følger:
|
||||
**OpenIMSDK** er en IM SDK designet til **OpenIMServer**, skabt specifikt til indlejring i klientapplikationer. Dens vigtigste funktioner og moduler er som følger:
|
||||
|
||||
+ 🌟 Hovedfunktioner:
|
||||
- 🌟 Hovedfunktioner:
|
||||
|
||||
- 📦 Lokal lagring
|
||||
- 🔔 Lytter-callbacks
|
||||
@@ -95,15 +89,15 @@ Det er bygget ved hjælp af Golang og understøtter tværplatformsudrulning, hvi
|
||||
|
||||
## 🌐 Om OpenIMServer
|
||||
|
||||
+ **OpenIMServer** har følgende karakteristika:
|
||||
- **OpenIMServer** har følgende karakteristika:
|
||||
- 🌐 Mikroservicarkitektur: Understøtter klyngetilstand, inklusive en gateway og flere rpc-tjenester.
|
||||
- 🚀 Forskellige udrulningsmetoder: Understøtter udrulning via kildekode, Kubernetes eller Docker.
|
||||
- Støtte til massiv brugerbase: Super store grupper med hundredtusinder af brugere, titusinder af brugere og milliarder af beskeder.
|
||||
|
||||
### Forbedret forretningsfunktionalitet:
|
||||
|
||||
+ **REST API**:OpenIMServer tilbyder REST API'er til forretningssystemer, med det formål at give virksomheder flere funktioner, såsom at oprette grupper og sende push-beskeder gennem backend-grænseflader.
|
||||
+ **Webhooks**:OpenIMServer giver mulighed for callback-funktionalitet for at udvide flere forretningsformer. Et callback betyder, at OpenIMServer sender en anmodning til forretningsserveren før eller efter en bestemt begivenhed, som callbacks før eller efter at have sendt en besked.
|
||||
- **REST API**:OpenIMServer tilbyder REST API'er til forretningssystemer, med det formål at give virksomheder flere funktioner, såsom at oprette grupper og sende push-beskeder gennem backend-grænseflader.
|
||||
- **Webhooks**:OpenIMServer giver mulighed for callback-funktionalitet for at udvide flere forretningsformer. Et callback betyder, at OpenIMServer sender en anmodning til forretningsserveren før eller efter en bestemt begivenhed, som callbacks før eller efter at have sendt en besked.
|
||||
|
||||
👉 **[Lær mere](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
@@ -121,10 +115,10 @@ Vi understøtter mange platforme. Her er adresserne for hurtig oplevelse på web
|
||||
|
||||
🤲 For at lette brugeroplevelsen tilbyder vi forskellige udrulningsløsninger. Du kan vælge din udrulningsmetode fra listen nedenfor:
|
||||
|
||||
+ **[Vejledning til udrulning af kildekode](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Vejledning til Docker-udrulning](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Vejledning til Kubernetes-udrulning](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Vejledning til Mac-udviklerudrulning](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[Vejledning til udrulning af kildekode](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Vejledning til Docker-udrulning](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Vejledning til Kubernetes-udrulning](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Vejledning til Mac-udviklerudrulning](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: For at starte udviklingen af OpenIM
|
||||
|
||||
@@ -134,7 +128,7 @@ OpenIM Vores mål er at bygge et topniveau åben kildekode-fællesskab. Vi har e
|
||||
|
||||
Hvis du gerne vil bidrage til dette Open-IM-Server-repositorium, bedes du læse vores [dokumentation for bidragydere](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
|
||||
|
||||
Før du starter, skal du sikre dig, at dine ændringer er efterspurgte. Det bedste for det er at oprette en [ny diskussion](https://github.com/openimsdk/open-im-server/discussions/new/choose) ELLER [Slack-kommunikation](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), eller hvis du finder et problem, [rapportere det](https://github.com/openimsdk/open-im-server/issues/new/choose) først.
|
||||
Før du starter, skal du sikre dig, at dine ændringer er efterspurgte. Det bedste for det er at oprette en [ny diskussion](https://github.com/openimsdk/open-im-server/discussions/new/choose) ELLER [Slack-kommunikation](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A), eller hvis du finder et problem, [rapportere det](https://github.com/openimsdk/open-im-server/issues/new/choose) først.
|
||||
|
||||
- [OpenIM API-referencer](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [OpenIM Bash-logging](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
@@ -166,12 +160,11 @@ Før du starter, skal du sikre dig, at dine ændringer er efterspurgte. Det beds
|
||||
- [Administrer backend og overvåg udrulning](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [Mac-udviklerudrulningsguide for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
## :calendar: Fællesskabsmøder
|
||||
|
||||
Vi ønsker, at alle involverer sig i vores fællesskab og bidrager med kode, vi tilbyder gaver og belønninger, og vi byder dig velkommen til at deltage hver torsdag aften.
|
||||
|
||||
Vores konference er på [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, derefter kan du søge Open-IM-Server pipeline for at deltage.
|
||||
Vores konference er på [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, derefter kan du søge Open-IM-Server pipeline for at deltage.
|
||||
|
||||
Vi tager [notater](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) af hvert fjortendages møde i [GitHub-diskussioner](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Vores historiske mødenotater samt genudsendelser af møderne er tilgængelige på [Google Docs](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) 📑.
|
||||
|
||||
@@ -189,4 +182,4 @@ OpenIM-logoet, inklusive dets variationer og animerede versioner, vist i dette r
|
||||
|
||||
<a href="https://github.com/openimsdk/open-im-server/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=openimsdk/open-im-server" />
|
||||
</a>
|
||||
</a>
|
||||
|
||||
+18
-21
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,7 +45,6 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
@@ -61,14 +59,14 @@
|
||||
|
||||
Το **OpenIMSDK** είναι ένα SDK για αμεση ανταλλαγή μηνυμάτων σχεδιασμένο για το **OpenIMServer**, δημιουργήθηκε ειδικά για ενσωμάτωση σε εφαρμογές πελατών. Οι κύριες δυνατότητες και μονάδες του είναι οι εξής:
|
||||
|
||||
+ 🌟 Κύριες Δυνατότητες:
|
||||
- 🌟 Κύριες Δυνατότητες:
|
||||
|
||||
- 📦 Τοπική αποθήκευση
|
||||
- 🔔 Callbacks ακροατών
|
||||
- 🛡️ Περιτύλιγμα API
|
||||
- 🌐 Διαχείριση σύνδεσης
|
||||
|
||||
+ 📚 Κύριες Μονάδες:
|
||||
- 📚 Κύριες Μονάδες:
|
||||
|
||||
1. 🚀 Αρχικοποίηση και Σύνδεση
|
||||
2. 👤 Διαχείριση Χρηστών
|
||||
@@ -82,15 +80,15 @@
|
||||
|
||||
## 🌐 Σχετικά με το OpenIMServer
|
||||
|
||||
+ Το **OpenIMServer** έχει τις ακόλουθες χαρακτηριστικές:
|
||||
- Το **OpenIMServer** έχει τις ακόλουθες χαρακτηριστικές:
|
||||
- 🌐 Αρχιτεκτονική μικροϋπηρεσιών: Υποστηρίζει λειτουργία σε σύμπλεγμα, περιλαμβάνοντας έναν πύλη και πολλαπλές υπηρεσίες rpc.
|
||||
- 🚀 Διάφοροι τρόποι ανάπτυξης: Υποστηρίζει ανάπτυξη μέσω πηγαίου κώδικα, Kubernetes, ή Docker.
|
||||
- Υποστήριξη για τεράστια βάση χρηστών: Πολύ μεγάλες ομάδες με εκατοντάδες χιλιάδες χρήστες, δεκάδες εκατομμύρια χρήστες και δισεκατομμύρια μηνύματα.
|
||||
|
||||
### Ενισχυμένη Επιχειρηματική Λειτουργικότητα:
|
||||
|
||||
+ **REST API**: Το OpenIMServer προσφέρει REST APIs για επιχειρηματικά συστήματα, με στόχο την ενδυνάμωση των επιχειρήσεων με περισσότερες λειτουργικότητες, όπως η δημιουργία ομάδων και η αποστολή μηνυμάτων push μέσω backend διεπαφών.
|
||||
+ **Webhooks**: Το OpenIMServer παρέχει δυνατότητες επανάκλησης για την επέκταση περισσότερων επιχειρηματικών μορφών. Μια επανάκληση σημαίνει ότι το OpenIMServer στέλνει ένα αίτημα στον επιχειρηματικό διακομιστή πριν ή μετά από ένα συγκεκριμένο γεγονός, όπως επανακλήσεις πριν ή μετά την αποστολή ενός μηνύματος.
|
||||
- **REST API**: Το OpenIMServer προσφέρει REST APIs για επιχειρηματικά συστήματα, με στόχο την ενδυνάμωση των επιχειρήσεων με περισσότερες λειτουργικότητες, όπως η δημιουργία ομάδων και η αποστολή μηνυμάτων push μέσω backend διεπαφών.
|
||||
- **Webhooks**: Το OpenIMServer παρέχει δυνατότητες επανάκλησης για την επέκταση περισσότερων επιχειρηματικών μορφών. Μια επανάκληση σημαίνει ότι το OpenIMServer στέλνει ένα αίτημα στον επιχειρηματικό διακομιστή πριν ή μετά από ένα συγκεκριμένο γεγονός, όπως επανακλήσεις πριν ή μετά την αποστολή ενός μηνύματος.
|
||||
|
||||
👉 **[Μάθετε περισσότερα](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
@@ -100,7 +98,6 @@
|
||||
|
||||

|
||||
|
||||
|
||||
## :rocket: Γρήγορη Εκκίνηση
|
||||
|
||||
Υποστηρίζουμε πολλές πλατφόρμες. Εδώ είναι οι διευθύνσεις για γρήγορη εμπειρία στην πλευρά του διαδικτύου:
|
||||
@@ -109,10 +106,10 @@
|
||||
|
||||
🤲 Για να διευκολύνουμε την εμπειρία του χρήστη, προσφέρουμε διάφορες λύσεις ανάπτυξης. Μπορείτε να επιλέξετε τη μέθοδο ανάπτυξης σας από την παρακάτω λίστα:
|
||||
|
||||
+ **[Οδηγός Ανάπτυξης Κώδικα Πηγής](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[δηγός Ανάπτυξης μέσω Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Οδηγός Ανάπτυξης Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Οδηγός Ανάπτυξης για Αναπτυξιακούς στο Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[Οδηγός Ανάπτυξης Κώδικα Πηγής](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[δηγός Ανάπτυξης μέσω Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Οδηγός Ανάπτυξης Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Οδηγός Ανάπτυξης για Αναπτυξιακούς στο Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: Για να Αρχίσετε την Ανάπτυξη του OpenIM
|
||||
|
||||
@@ -122,7 +119,7 @@ OpenIM Στόχος μας είναι να δημιουργήσουμε μια
|
||||
|
||||
Εάν θέλετε να συνεισφέρετε σε αυτό το αποθετήριο Open-IM-Server, παρακαλούμε διαβάστε την [τεκμηρίωση συνεισφέροντος](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
|
||||
|
||||
Πριν ξεκινήσετε, παρακαλούμε βεβαιωθείτε ότι οι αλλαγές σας είναι ζητούμενες. Το καλύτερο για αυτό είναι να δημιουργήσετε ένα [νέα συζήτηση](https://github.com/openimsdk/open-im-server/discussions/new/choose) ή [Επικοινωνία Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), ή αν βρείτε ένα ζήτημα, [αναφέρετέ το](https://github.com/openimsdk/open-im-server/issues/new/choose) πρώτα.
|
||||
Πριν ξεκινήσετε, παρακαλούμε βεβαιωθείτε ότι οι αλλαγές σας είναι ζητούμενες. Το καλύτερο για αυτό είναι να δημιουργήσετε ένα [νέα συζήτηση](https://github.com/openimsdk/open-im-server/discussions/new/choose) ή [Επικοινωνία Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A), ή αν βρείτε ένα ζήτημα, [αναφέρετέ το](https://github.com/openimsdk/open-im-server/issues/new/choose) πρώτα.
|
||||
|
||||
- [Αναφορά API του OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [Καταγραφή Bash του OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
@@ -154,28 +151,28 @@ OpenIM Στόχος μας είναι να δημιουργήσουμε μια
|
||||
- [Διαχείριση backend και παρακολούθηση ανάπτυξης](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [Οδηγός Ανάπτυξης για Προγραμματιστές Mac του OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
## :busts_in_silhouette: Κοινότητα
|
||||
|
||||
+ 📚 [Κοινότητα OpenIM](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [Ομάδα Ενδιαφέροντος OpenIM](https://github.com/Openim-sigs)
|
||||
+ 🚀 [Εγγραφείτε στην κοινότητα Slack μας](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [γγραφείτε στην ομάδα μας wechat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 📚 [Κοινότητα OpenIM](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [Ομάδα Ενδιαφέροντος OpenIM](https://github.com/Openim-sigs)
|
||||
- 🚀 [Εγγραφείτε στην κοινότητα Slack μας](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [γγραφείτε στην ομάδα μας wechat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## :calendar: Συναντήσεις της κοινότητας
|
||||
|
||||
Θέλουμε οποιονδήποτε να εμπλακεί στην κοινότητά μας και να συνεισφέρει κώδικα. Προσφέρουμε δώρα και ανταμοιβές και σας καλωσορίζουμε να μας ενταχθείτε κάθε Πέμπτη βράδυ.
|
||||
|
||||
Η διάσκεψή μας είναι στο [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, στη συνέχεια μπορείτε να αναζητήσετε τη διαδικασία Open-IM-Server για να συμμετάσχετε
|
||||
Η διάσκεψή μας είναι στο [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, στη συνέχεια μπορείτε να αναζητήσετε τη διαδικασία Open-IM-Server για να συμμετάσχετε
|
||||
|
||||
Κάνουμε σημειώσεις για κάθε μια [Σημειώνουμε κάθε διμηνιαία συνάντηση](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) στις [συζητήσεις του GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Οι ιστορικές μας σημειώσεις συναντήσεων, καθώς και οι επαναλήψεις των συναντήσεων είναι διαθέσιμες στο[Έγγραφα της Google :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
|
||||
|
||||
## :eyes: Ποιοί Χρησιμοποιούν το OpenIM
|
||||
|
||||
Ελέγξτε τη σελίδα με τις [μελέτες περίπτωσης χρήσης ](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md) μας για μια λίστα των χρηστών του έργου. Μην διστάσετε να αφήσετε ένα[📝σχόλιο](https://github.com/openimsdk/open-im-server/issues/379) και να μοιραστείτε την περίπτωση χρήσης σας.
|
||||
|
||||
## :page_facing_up: Άδεια Χρήσης
|
||||
|
||||
Το OpenIM διατίθεται υπό την άδεια Apache 2.0. Δείτε τη [ΑΔΕΙΑ ΧΡΗΣΗΣ](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) για το πλήρες κείμενο της άδειας.
|
||||
Το OpenIM διατίθεται υπό την άδεια Apache 2.0. Δείτε τη [ΑΔΕΙΑ ΧΡΗΣΗΣ](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) για το πλήρες κείμενο της άδειας.
|
||||
|
||||
Το λογότυπο του OpenIM, συμπεριλαμβανομένων των παραλλαγών και των κινούμενων εικόνων, που εμφανίζονται σε αυτό το αποθετήριο[OpenIM](https://github.com/openimsdk/open-im-server) υπό τις διευθύνσεις [assets/logo](../../assets/logo) και [assets/logo-gif](../../assets/logo-gif) προστατεύονται από τους νόμους περί πνευματικής ιδιοκτησίας.
|
||||
|
||||
|
||||
+20
-28
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,7 +45,6 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
@@ -61,14 +59,14 @@ OpenIM es una plataforma de servicio diseñada específicamente para integrar ch
|
||||
|
||||
**OpenIMSDK** es un SDK de mensajería instantánea diseñado para **OpenIMServer**, creado específicamente para su incorporación en aplicaciones cliente. Sus principales características y módulos son los siguientes:
|
||||
|
||||
+ 🌟 Características Principales:
|
||||
- 🌟 Características Principales:
|
||||
|
||||
- 📦 Almacenamiento local
|
||||
- 🔔 Callbacks de escuchas
|
||||
- 🛡️ Envoltura de API
|
||||
- 🌐 Gestión de conexiones
|
||||
|
||||
+ 📚 Módulos Principales:
|
||||
- 📚 Módulos Principales:
|
||||
|
||||
1. 🚀 Inicialización y acceso
|
||||
2. 👤 Gestión de usuarios
|
||||
@@ -82,17 +80,15 @@ Está construido con Golang y soporta despliegue multiplataforma, asegurando una
|
||||
|
||||
## 🌐 Acerca de OpenIMServer
|
||||
|
||||
+ **OpenIMServer** tiene las siguientes características:
|
||||
- **OpenIMServer** tiene las siguientes características:
|
||||
- 🌐 Arquitectura de microservicios: Soporta modo cluster, incluyendo un gateway y múltiples servicios rpc.
|
||||
- 🚀 Métodos de despliegue diversos: Soporta el despliegue a través de código fuente, Kubernetes o Docker.
|
||||
- Soporte para una base de usuarios masiva: Grupos super grandes con cientos de miles de usuarios, decenas de millones de usuarios y miles de millones de mensajes.
|
||||
|
||||
|
||||
|
||||
### Funcionalidad Empresarial Mejorada:
|
||||
|
||||
+ **API REST**: OpenIMServer ofrece APIs REST para sistemas empresariales, destinadas a empoderar a las empresas con más funcionalidades, como la creación de grupos y el envío de mensajes push a través de interfaces de backend.
|
||||
+ **Webhooks**: OpenIMServer proporciona capacidades de callback para extender más formas de negocio. Un callback significa que OpenIMServer envía una solicitud al servidor empresarial antes o después de un cierto evento, como callbacks antes o después de enviar un mensaje.
|
||||
- **API REST**: OpenIMServer ofrece APIs REST para sistemas empresariales, destinadas a empoderar a las empresas con más funcionalidades, como la creación de grupos y el envío de mensajes push a través de interfaces de backend.
|
||||
- **Webhooks**: OpenIMServer proporciona capacidades de callback para extender más formas de negocio. Un callback significa que OpenIMServer envía una solicitud al servidor empresarial antes o después de un cierto evento, como callbacks antes o después de enviar un mensaje.
|
||||
|
||||
👉 **[Aprende más](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
@@ -102,10 +98,8 @@ Adéntrate en el corazón de la funcionalidad de Open-IM-Server con nuestro diag
|
||||
|
||||

|
||||
|
||||
|
||||
## :rocket: Inicio Rápido
|
||||
|
||||
|
||||
:rocket: Inicio Rápido
|
||||
Apoyamos muchas plataformas. Aquí están las direcciones para una experiencia rápida en el lado web:
|
||||
|
||||
@@ -113,22 +107,21 @@ Apoyamos muchas plataformas. Aquí están las direcciones para una experiencia r
|
||||
|
||||
🤲 Para facilitar la experiencia del usuario, ofrecemos varias soluciones de despliegue. Puedes elegir tu método de despliegue de la lista a continuación:
|
||||
|
||||
+ **[Guía de Despliegue de Código Fuente](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Guía de Despliegue con Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Guía de Despliegue con Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Guía de Despliegue para Desarrolladores en Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[Guía de Despliegue de Código Fuente](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Guía de Despliegue con Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Guía de Despliegue con Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Guía de Despliegue para Desarrolladores en Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: Para Comenzar a Desarrollar en OpenIM
|
||||
|
||||
[](https://vscode.dev/github/openimsdk/open-im-server)
|
||||
|
||||
Nuestro objetivo en OpenIM es construir una comunidad de código abierto de nivel superior. Tenemos un conjunto de estándares,
|
||||
Nuestro objetivo en OpenIM es construir una comunidad de código abierto de nivel superior. Tenemos un conjunto de estándares,
|
||||
en el [repositorio de la Comunidad.](https://github.com/OpenIMSDK/community).
|
||||
|
||||
Si te gustaría contribuir a este repositorio de Open-IM-Server, por favor lee nuestra [documentación para colaboradores](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
|
||||
|
||||
|
||||
Antes de comenzar, asegúrate de que tus cambios sean demandados. Lo mejor para eso es crear una [nueva discusión](https://github.com/openimsdk/open-im-server/discussions/new/choose) O [Comunicación en Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), o si encuentras un problema, [repórtalo](https://github.com/openimsdk/open-im-server/issues/new/choose) primero.
|
||||
Antes de comenzar, asegúrate de que tus cambios sean demandados. Lo mejor para eso es crear una [nueva discusión](https://github.com/openimsdk/open-im-server/discussions/new/choose) O [Comunicación en Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A), o si encuentras un problema, [repórtalo](https://github.com/openimsdk/open-im-server/issues/new/choose) primero.
|
||||
|
||||
- [Referencia de API de OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [Registro de Bash de OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
@@ -160,32 +153,31 @@ Antes de comenzar, asegúrate de que tus cambios sean demandados. Lo mejor para
|
||||
- [Gestión de backend y despliegue de monitoreo](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [Guía de Despliegue para Desarrolladores Mac de OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
## :busts_in_silhouette: Comunidad
|
||||
|
||||
+ 📚 [Comunidad de OpenIM](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [Grupo de Interés de OpenIM](https://github.com/Openim-sigs)
|
||||
+ 🚀 [Únete a nuestra comunidad de Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [Únete a nuestro wechat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 📚 [Comunidad de OpenIM](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [Grupo de Interés de OpenIM](https://github.com/Openim-sigs)
|
||||
- 🚀 [Únete a nuestra comunidad de Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [Únete a nuestro wechat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## :calendar: Reuniones de la Comunidad
|
||||
|
||||
Queremos que cualquiera se involucre en nuestra comunidad y contribuya con código, ofrecemos regalos y recompensas, y te damos la bienvenida para que te unas a nosotros cada jueves por la noche.
|
||||
|
||||
Nuestra conferencia está en [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, luego puedes buscar el pipeline de Open-IM-Server para unirte
|
||||
Nuestra conferencia está en [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, luego puedes buscar el pipeline de Open-IM-Server para unirte
|
||||
|
||||
Tomamos notas de cada [reunión quincenal](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) en [discusiones de GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Nuestras notas de reuniones históricas, así como las repeticiones de las reuniones están disponibles en [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
|
||||
|
||||
## :eyes: Quiénes Están Usando OpenIM
|
||||
|
||||
Consulta nuestros [estudios de caso de usuarios](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md) página para obtener una lista de los usuarios del proyecto. No dudes en dejar un [📝comentario](https://github.com/openimsdk/open-im-server/issues/379) y compartir tu caso de uso.
|
||||
|
||||
## :page_facing_up: Licencia
|
||||
|
||||
|
||||
OpenIM está bajo la licencia Apache 2.0. Consulta [LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) para ver el texto completo de la licencia.
|
||||
|
||||
OpenIM está bajo la licencia Apache 2.0. Consulta [LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) para ver el texto completo de la licencia.
|
||||
|
||||
El logotipo de OpenIM, incluyendo sus variaciones y versiones animadas, que se muestran en este repositorio [OpenIM](https://github.com/openimsdk/open-im-server) en los directorios [assets/logo](../../assets/logo) y [assets/logo-gif](assets/logo-gif) están protegidos por las leyes de derechos de autor.
|
||||
|
||||
## 🔮 iGracias a nuestros colaboradores!
|
||||
|
||||
<a href="https://github.com/openimsdk/open-im-server/graphs/contributors">
|
||||
|
||||
+17
-21
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,7 +45,6 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
@@ -61,14 +59,14 @@ OpenIM یک پلتفرم خدماتی است که به طور خاص برای ا
|
||||
|
||||
**OpenIMSDK** یک IM SDK است که برای **OpenIMServer** طراحی شده است که به طور خاص برای جاسازی در برنامه های مشتری ایجاد شده است. ویژگی ها و ماژول های اصلی آن به شرح زیر است:
|
||||
|
||||
+ 🌟 ویژگی های اصلی:
|
||||
- 🌟 ویژگی های اصلی:
|
||||
|
||||
- 📦 ذخیره سازی محلی
|
||||
- 🔔 پاسخ تماس شنونده
|
||||
- 🛡️ بسته بندی API
|
||||
- 🌐 مدیریت اتصال
|
||||
|
||||
+ 📚 ماژول های اصلی:
|
||||
- 📚 ماژول های اصلی:
|
||||
|
||||
1. 🚀 مقداردهی اولیه و ورود
|
||||
2. 👤 مدیریت کاربر
|
||||
@@ -82,15 +80,15 @@ OpenIM یک پلتفرم خدماتی است که به طور خاص برای ا
|
||||
|
||||
## 🌐 درباره OpenIMServer
|
||||
|
||||
+ **OpenIMServer** دارای ویژگی های زیر است:
|
||||
- **OpenIMServer** دارای ویژگی های زیر است:
|
||||
- 🌐 معماری Microservice: از حالت کلاستر، از جمله یک دروازه و چندین سرویس rpc پشتیبانی می کند.
|
||||
- 🚀 روشهای استقرار متنوع: از استقرار از طریق کد منبع، Kubernetes یا Docker پشتیبانی میکند.
|
||||
- پشتیبانی از پایگاه عظیم کاربران: گروه های فوق العاده بزرگ با صدها هزار کاربر، ده ها میلیون کاربر و میلیاردها پیام.
|
||||
|
||||
### عملکردهای تجاری پیشرفته:
|
||||
|
||||
+ **REST API**: OpenIMServer APIهای REST را برای سیستمهای تجاری ارائه میکند، با هدف توانمندسازی کسبوکارها با قابلیتهای بیشتر، مانند ایجاد گروهها و ارسال پیامهای فشار از طریق رابطهای باطنی.
|
||||
+ **Webhooks**: OpenIMServer قابلیت های پاسخ به تماس را برای گسترش بیشتر فرم های تجاری ارائه می دهد. پاسخ به تماس به این معنی است که OpenIMServer درخواستی را قبل یا بعد از یک رویداد خاص به سرور تجاری ارسال می کند، مانند تماس های قبل یا بعد از ارسال یک پیام.
|
||||
- **REST API**: OpenIMServer APIهای REST را برای سیستمهای تجاری ارائه میکند، با هدف توانمندسازی کسبوکارها با قابلیتهای بیشتر، مانند ایجاد گروهها و ارسال پیامهای فشار از طریق رابطهای باطنی.
|
||||
- **Webhooks**: OpenIMServer قابلیت های پاسخ به تماس را برای گسترش بیشتر فرم های تجاری ارائه می دهد. پاسخ به تماس به این معنی است که OpenIMServer درخواستی را قبل یا بعد از یک رویداد خاص به سرور تجاری ارسال می کند، مانند تماس های قبل یا بعد از ارسال یک پیام.
|
||||
|
||||
👉 **[بیشتر بدانید](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
@@ -100,7 +98,6 @@ OpenIM یک پلتفرم خدماتی است که به طور خاص برای ا
|
||||
|
||||

|
||||
|
||||
|
||||
## :rocket: شروع سریع
|
||||
|
||||
ما از بسیاری از پلتفرم ها پشتیبانی می کنیم. در اینجا آدرس هایی برای تجربه سریع در سمت وب آمده است:
|
||||
@@ -109,10 +106,10 @@ OpenIM یک پلتفرم خدماتی است که به طور خاص برای ا
|
||||
|
||||
🤲 برای تسهیل تجربه کاربر، ما راه حل های مختلف استقرار را ارائه می دهیم. می توانید روش استقرار خود را از لیست زیر انتخاب کنید:
|
||||
|
||||
+ **[راهنمای استقرار کد منبع](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[راهنمای استقرار داکر](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[راهنمای استقرار Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[راهنمای استقرار توسعه دهنده مک](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[راهنمای استقرار کد منبع](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[راهنمای استقرار داکر](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[راهنمای استقرار Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[راهنمای استقرار توسعه دهنده مک](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: برای شروع توسعه OpenIM
|
||||
|
||||
@@ -122,7 +119,7 @@ OpenIM هدف ما ایجاد یک جامعه منبع باز سطح بالا ا
|
||||
|
||||
اگر میخواهید در این مخزن Open-IM-Server مشارکت کنید، لطفاً [مستندات مشارکتکننده](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md) ما را بخوانید.
|
||||
|
||||
قبل از شروع، لطفاً مطمئن شوید که تغییرات شما مورد تقاضا هستند. بهترین کار برای آن این است که یک [بحث جدید](https://github.com/openimsdk/open-im-server/discussions/new/choose) یا [ارتباط اسلک](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) ایجاد کنید، یا اگر مشکلی پیدا کردید، ابتدا [آن را گزارش کنید](https://github.com/openimsdk/open-im-server/issues/new/choose).
|
||||
قبل از شروع، لطفاً مطمئن شوید که تغییرات شما مورد تقاضا هستند. بهترین کار برای آن این است که یک [بحث جدید](https://github.com/openimsdk/open-im-server/discussions/new/choose) یا [ارتباط اسلک](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) ایجاد کنید، یا اگر مشکلی پیدا کردید، ابتدا [آن را گزارش کنید](https://github.com/openimsdk/open-im-server/issues/new/choose).
|
||||
|
||||
- [مرجع OpenIM API](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [OpenIM Bash Logging](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
@@ -154,21 +151,20 @@ OpenIM هدف ما ایجاد یک جامعه منبع باز سطح بالا ا
|
||||
- [مدیریت استقرار باطن و نظارت](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [راهنمای استقرار توسعه دهنده مک برای OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
## :busts_in_silhouette: انجمن
|
||||
|
||||
+ 📚 [انجمن OpenIM](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [گروه علاقه OpenIM](https://github.com/Openim-sigs)
|
||||
+ 🚀 [به انجمن Slack ما بپیوندید](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [به وی چت ما بپیوندید](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 📚 [انجمن OpenIM](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [گروه علاقه OpenIM](https://github.com/Openim-sigs)
|
||||
- 🚀 [به انجمن Slack ما بپیوندید](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [به وی چت ما بپیوندید](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## :calendar: جلسات جامعه
|
||||
|
||||
ما میخواهیم هر کسی در انجمن ما مشارکت کند و در کد مشارکت کند، ما هدایا و جوایزی ارائه میکنیم، و از شما استقبال میکنیم که هر پنجشنبه شب به ما بپیوندید.
|
||||
|
||||
کنفرانس ما در [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯 است، سپس می توانید خط لوله Open-IM-Server را برای پیوستن جستجو کنید.
|
||||
کنفرانس ما در [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯 است، سپس می توانید خط لوله Open-IM-Server را برای پیوستن جستجو کنید.
|
||||
|
||||
ما از هر [جلسه دو هفتهای](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) در [بحثهای GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting) یادداشتبرداری میکنیم، یادداشتهای جلسه تاریخی ما، و همچنین بازپخش جلسات در [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) موجود است.
|
||||
ما از هر [جلسه دو هفتهای](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) در [بحثهای GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting) یادداشتبرداری میکنیم، یادداشتهای جلسه تاریخی ما، و همچنین بازپخش جلسات در [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) موجود است.
|
||||
|
||||
## :eyes: چه کسانی از OpenIM استفاده می کنند
|
||||
|
||||
|
||||
+18
-24
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,25 +45,21 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
## Ⓜ️ À propos de OpenIM
|
||||
|
||||
OpenIM est une plateforme de services conçue spécifiquement pour intégrer des fonctionnalités de communication telles que le chat, les appels audio et vidéo, les notifications, ainsi que les robots de chat IA dans les applications. Elle offre une série d'API puissantes et de Webhooks, permettant aux développeurs d'incorporer facilement ces caractéristiques interactives dans leurs applications. OpenIM n'est pas en soi une application de chat autonome, mais sert de plateforme supportant d'autres applications pour réaliser des fonctionnalités de communication enrichies. L'image ci-dessous montre les relations d'interaction entre AppServer, AppClient, OpenIMServer et OpenIMSDK pour illustrer spécifiquement.
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
## 🚀 À propos de OpenIMSDK
|
||||
|
||||
**OpenIMSDK** est un SDK IM conçu pour **OpenIMServer** spécialement créé pour être intégré dans les applications clientes. Ses principales fonctionnalités et modules comprennent :
|
||||
**OpenIMSDK** est un SDK IM conçu pour **OpenIMServer** spécialement créé pour être intégré dans les applications clientes. Ses principales fonctionnalités et modules comprennent :
|
||||
|
||||
+ 🌟 Fonctionnalités clés :
|
||||
- 🌟 Fonctionnalités clés :
|
||||
|
||||
- 📦 Stockage local
|
||||
- 🔔 Rappels de l'écouteur
|
||||
@@ -85,15 +80,15 @@ Il est construit avec Golang et supporte le déploiement multiplateforme, assura
|
||||
|
||||
## 🌐 À propos de OpenIMServer
|
||||
|
||||
+ **OpenIMServer** présente les caractéristiques suivantes :
|
||||
- **OpenIMServer** présente les caractéristiques suivantes :
|
||||
- 🌐 Architecture microservices : prend en charge le mode cluster, incluant le gateway (passerelle) et plusieurs services rpc。
|
||||
- 🚀 Divers modes de déploiement : supporte le déploiement via le code source, Kubernetes ou Docker。
|
||||
- Support d'une masse d'utilisateurs : plus de cent mille pour les super grands groupes, des millions d'utilisateurs, et des milliards de messages。
|
||||
|
||||
### Fonctionnalités commerciales améliorées :
|
||||
|
||||
+ **REST API**:OpenIMServer fournit une REST API pour les systèmes commerciaux, visant à accorder plus de fonctionnalités, telles que la création de groupes via l'interface backend, l'envoi de messages push, etc。
|
||||
+ **Webhooks**:OpenIMServer offre des capacités de rappel pour étendre davantage les formes d'entreprise. Un rappel signifie que OpenIMServer enverra une requête au serveur d'entreprise avant ou après qu'un événement se soit produit, comme un rappel avant ou après l'envoi d'un message。
|
||||
- **REST API**:OpenIMServer fournit une REST API pour les systèmes commerciaux, visant à accorder plus de fonctionnalités, telles que la création de groupes via l'interface backend, l'envoi de messages push, etc。
|
||||
- **Webhooks**:OpenIMServer offre des capacités de rappel pour étendre davantage les formes d'entreprise. Un rappel signifie que OpenIMServer enverra une requête au serveur d'entreprise avant ou après qu'un événement se soit produit, comme un rappel avant ou après l'envoi d'un message。
|
||||
|
||||
👉 **[En savoir plus](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
@@ -103,7 +98,6 @@ Plongez dans le cœur de la fonctionnalité d'Open-IM-Server avec notre diagramm
|
||||
|
||||

|
||||
|
||||
|
||||
## :rocket: Démarrage rapide
|
||||
|
||||
Nous prenons en charge de nombreuses plateformes. Voici les adresses pour une expérience rapide du côté web :
|
||||
@@ -112,17 +106,18 @@ Nous prenons en charge de nombreuses plateformes. Voici les adresses pour une ex
|
||||
|
||||
🤲 Pour faciliter l'expérience utilisateur, nous proposons plusieurs solutions de déploiement. Vous pouvez choisir votre méthode de déploiement selon la liste ci-dessous :
|
||||
|
||||
+ **[Guide de déploiement du code source](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Guide de déploiement Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Guide de déploiement Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Guide de déploiement pour développeur Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[Guide de déploiement du code source](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Guide de déploiement Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Guide de déploiement Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Guide de déploiement pour développeur Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: Commencer à développer avec OpenIM
|
||||
## :hammer_and_wrench: Commencer à développer avec OpenIM
|
||||
|
||||
Chez OpenIM, notre objectif est de construire une communauté open source de premier plan. Nous avons un ensemble de standards, disponibles dans le[ dépôt communautaire](https://github.com/OpenIMSDK/community)。
|
||||
Si vous souhaitez contribuer à ce dépôt Open-IM-Server, veuillez lire notre[ document pour les contributeurs](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)。
|
||||
|
||||
Avant de commencer, assurez-vous que vos modifications sont nécessaires. La meilleure manière est de créer une[ nouvelle discussion ](https://github.com/openimsdk/open-im-server/discussions/new/choose) ou une [ communication Slack,](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q),ou si vous identifiez un problème, de[ signaler d'abord ](https://github.com/openimsdk/open-im-server/issues/new/choose)。
|
||||
Avant de commencer, assurez-vous que vos modifications sont nécessaires. La meilleure manière est de créer une[ nouvelle discussion ](https://github.com/openimsdk/open-im-server/discussions/new/choose) ou une [ communication Slack,](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A),ou si vous identifiez un problème, de[ signaler d'abord ](https://github.com/openimsdk/open-im-server/issues/new/choose)。
|
||||
|
||||
- [Référence de l'API OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [Journalisation Bash OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
- [Actions CI/CD OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/cicd-actions.md)
|
||||
@@ -153,13 +148,12 @@ Avant de commencer, assurez-vous que vos modifications sont nécessaires. La mei
|
||||
- [Gérer le déploiement du backend et la surveillance](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [Guide de déploiement pour développeur Mac pour OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
>## :calendar: Réunions de la Communauté
|
||||
> ## :calendar: Réunions de la Communauté
|
||||
|
||||
Nous voulons que tout le monde s'implique dans notre communauté et contribue au code, nous offrons des cadeaux et des récompenses, et nous vous invitons à nous rejoindre chaque jeudi soir.
|
||||
Notre conférence se trouve dans le [ Slack OpenIM ](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, ensuite vous pouvez rechercher le pipeline Open-IM-Server pour rejoindre
|
||||
Notre conférence se trouve dans le [ Slack OpenIM ](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, ensuite vous pouvez rechercher le pipeline Open-IM-Server pour rejoindre
|
||||
|
||||
Nous prenons des notes de chaque [réunion bihebdomadaire ](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) dans les [discussions GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Nos notes de réunion historiques, ainsi que les rediffusions des réunions sont disponibles sur [ Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
|
||||
Nous prenons des notes de chaque [réunion bihebdomadaire ](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) dans les [discussions GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Nos notes de réunion historiques, ainsi que les rediffusions des réunions sont disponibles sur [ Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
|
||||
|
||||
## :eyes: Qui Utilise OpenIM
|
||||
|
||||
@@ -167,9 +161,9 @@ Consultez notre page [ études de cas d'utilisateurs ](https://github.com/OpenIM
|
||||
|
||||
## :page_facing_up: License
|
||||
|
||||
OpenIM est sous licence Apache 2.0. Voir [LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) pour le texte complet de la licence.
|
||||
OpenIM est sous licence Apache 2.0. Voir [LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) pour le texte complet de la licence.
|
||||
|
||||
Le logo OpenIM, y compris ses variations et versions animées, affiché dans ce dépôt[OpenIM](https://github.com/openimsdk/open-im-server) sous les répertoires [assets/logo](../../assets/logo) et [assets/logo-gif](assets/logo-gif) sont protégés par les lois sur le droit d'auteur.
|
||||
Le logo OpenIM, y compris ses variations et versions animées, affiché dans ce dépôt[OpenIM](https://github.com/openimsdk/open-im-server) sous les répertoires [assets/logo](../../assets/logo) et [assets/logo-gif](assets/logo-gif) sont protégés par les lois sur le droit d'auteur.
|
||||
|
||||
## 🔮 Merci à nos contributeurs !
|
||||
|
||||
|
||||
+16
-20
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,7 +45,6 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
@@ -61,14 +59,14 @@ Az OpenIM egy szolgáltatási platform, amelyet kifejezetten a csevegés, az aud
|
||||
|
||||
Az **OpenIMSDK** egy **OpenIMServer** számára készült azonnali üzenetküldő SDK, amelyet kifejezetten ügyfélalkalmazásokba való beágyazáshoz hoztak létre. Fő jellemzői és moduljai a következők:
|
||||
|
||||
+ 🌟 Főbb jellemzők:
|
||||
- 🌟 Főbb jellemzők:
|
||||
|
||||
- 📦 Helyi raktár
|
||||
- 🔔 Hallgatói visszahívások
|
||||
- 🛡️ API-csomagolás
|
||||
- 🌐 Kapcsolatkezelés
|
||||
|
||||
+ 📚 Fő modulok:
|
||||
- 📚 Fő modulok:
|
||||
|
||||
1. 🚀 Inicializálás és bejelentkezés
|
||||
2. 👤 Felhasználókezelés
|
||||
@@ -82,15 +80,15 @@ Golang használatával készült, és támogatja a többplatformos telepítést,
|
||||
|
||||
## 🌐 Az OpenIMServerről
|
||||
|
||||
+ **OpenIMServer** a következő jellemzőkkel rendelkezik:
|
||||
- **OpenIMServer** a következő jellemzőkkel rendelkezik:
|
||||
- 🌐 Mikroszolgáltatási architektúra: Támogatja a fürt módot, beleértve az átjárót és több rpc szolgáltatást.
|
||||
- 🚀 Változatos telepítési módszerek: Támogatja a forráskódon, Kubernetesen vagy Dockeren keresztül történő telepítést.
|
||||
- Hatalmas felhasználói bázis támogatása: Szuper nagy csoportok több százezer felhasználóval, több tízmillió felhasználóval és több milliárd üzenettel.
|
||||
|
||||
### Továbbfejlesztett üzleti funkcionalitás:
|
||||
|
||||
+ **REST API**: Az OpenIMServer REST API-kat kínál az üzleti rendszerek számára, amelyek célja, hogy a vállalkozásokat több funkcióval ruházza fel, mint például csoportok létrehozása és push üzenetek küldése háttérfelületeken keresztül.
|
||||
+ **Webhooks**: Az OpenIMServer visszahívási lehetőségeket biztosít több üzleti forma kiterjesztéséhez. A visszahívás azt jelenti, hogy az OpenIMServer kérelmet küld az üzleti szervernek egy bizonyos esemény előtt vagy után, például visszahívásokat üzenet küldése előtt vagy után.
|
||||
- **REST API**: Az OpenIMServer REST API-kat kínál az üzleti rendszerek számára, amelyek célja, hogy a vállalkozásokat több funkcióval ruházza fel, mint például csoportok létrehozása és push üzenetek küldése háttérfelületeken keresztül.
|
||||
- **Webhooks**: Az OpenIMServer visszahívási lehetőségeket biztosít több üzleti forma kiterjesztéséhez. A visszahívás azt jelenti, hogy az OpenIMServer kérelmet küld az üzleti szervernek egy bizonyos esemény előtt vagy után, például visszahívásokat üzenet küldése előtt vagy után.
|
||||
|
||||
👉 **[Tudj meg többet](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
@@ -100,7 +98,6 @@ Merüljön el az Open-IM-Server funkcióinak szívében az architektúra diagram
|
||||
|
||||

|
||||
|
||||
|
||||
## :rocket: Gyors indítás
|
||||
|
||||
Számos platformot támogatunk. Íme a címek a gyors weboldali használathoz:
|
||||
@@ -109,10 +106,10 @@ Számos platformot támogatunk. Íme a címek a gyors weboldali használathoz:
|
||||
|
||||
🤲 A felhasználói élmény megkönnyítése érdekében különféle telepítési megoldásokat kínálunk. Az alábbi listából választhatja ki a telepítési módot:
|
||||
|
||||
+ **[Forráskód-telepítési útmutató](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Docker telepítési útmutató](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Kubernetes telepítési útmutató](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Mac fejlesztői telepítési útmutató](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[Forráskód-telepítési útmutató](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Docker telepítési útmutató](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Kubernetes telepítési útmutató](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Mac fejlesztői telepítési útmutató](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: Az OpenIM fejlesztésének megkezdéséhez
|
||||
|
||||
@@ -122,7 +119,7 @@ OpenIM Célunk egy felső szintű nyílt forráskódú közösség felépítése
|
||||
|
||||
Ha hozzá szeretne járulni ehhez az Open-IM-Server adattárhoz, kérjük, olvassa el [közreműködői dokumentációnkat](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
|
||||
|
||||
Mielőtt elkezdené, győződjön meg arról, hogy a változtatásokra van-e igény. Erre a legjobb egy [új beszélgetés](https://github.com/openimsdk/open-im-server/discussions/new/choose) VAGY [Slack Communication](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)létrehozása, vagy ha problémát talál, először [jelentse](https://github.com/openimsdk/open-im-server/issues/new/choose) first.
|
||||
Mielőtt elkezdené, győződjön meg arról, hogy a változtatásokra van-e igény. Erre a legjobb egy [új beszélgetés](https://github.com/openimsdk/open-im-server/discussions/new/choose) VAGY [Slack Communication](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)létrehozása, vagy ha problémát talál, először [jelentse](https://github.com/openimsdk/open-im-server/issues/new/choose) first.
|
||||
|
||||
- [OpenIM API referencia](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [OpenIM Bash naplózás](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
@@ -154,19 +151,18 @@ Mielőtt elkezdené, győződjön meg arról, hogy a változtatásokra van-e ig
|
||||
- [A háttérrendszer kezelése és a telepítés figyelése](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [Mac Developer Deployment Guide for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
## :busts_in_silhouette: Közösség
|
||||
|
||||
+ 📚 [OpenIM közösség](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [OpenIM érdeklődési csoport](https://github.com/Openim-sigs)
|
||||
+ 🚀 [Csatlakozz a Slack közösségünkhöz](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [Csatlakozz a wechathez](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 📚 [OpenIM közösség](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [OpenIM érdeklődési csoport](https://github.com/Openim-sigs)
|
||||
- 🚀 [Csatlakozz a Slack közösségünkhöz](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [Csatlakozz a wechathez](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## :calendar: Közösségi Találkozók
|
||||
|
||||
Szeretnénk, ha bárki bekapcsolódna közösségünkbe és hozzájárulna kódunkhoz, ajándékokat és jutalmakat kínálunk, és szeretettel várjuk, hogy csatlakozzon hozzánk minden csütörtök este.
|
||||
|
||||
Konferenciánk az [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯alatt van, akkor kereshet az Open-IM-Server folyamatban a csatlakozáshoz
|
||||
Konferenciánk az [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯alatt van, akkor kereshet az Open-IM-Server folyamatban a csatlakozáshoz
|
||||
|
||||
A [GitHub-beszélgetések](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)minden [kéthetente történő megbeszélésről](https://github.com/openimsdk/open-im-server/discussions/categories/meeting) jegyzeteket készítünk. A találkozók történeti feljegyzései, valamint az értekezletek visszajátszásai a [Google Dokumentumok :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) webhelyen érhetők el.
|
||||
|
||||
|
||||
+38
-45
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,29 +45,25 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
|
||||
## Ⓜ️ OpenIM について
|
||||
|
||||
## Ⓜ️ OpenIMについて
|
||||
|
||||
OpenIMは、アプリケーション内でチャット、音声通話、通知、AIチャットボットなどの通信機能を統合するために特別に設計されたサービスプラットフォームです。一連の強力なAPIとWebhooksを提供することで、開発者はアプリケーションに簡単にこれらの通信機能を統合できます。OpenIM自体は独立したチャットアプリではなく、アプリケーションにサポートを提供し、豊富な通信機能を実現するプラットフォームです。以下の図は、AppServer、AppClient、OpenIMServer、OpenIMSDK間の相互作用を示しています。
|
||||
|
||||
|
||||
OpenIM は、アプリケーション内でチャット、音声通話、通知、AI チャットボットなどの通信機能を統合するために特別に設計されたサービスプラットフォームです。一連の強力な API と Webhooks を提供することで、開発者はアプリケーションに簡単にこれらの通信機能を統合できます。OpenIM 自体は独立したチャットアプリではなく、アプリケーションにサポートを提供し、豊富な通信機能を実現するプラットフォームです。以下の図は、AppServer、AppClient、OpenIMServer、OpenIMSDK 間の相互作用を示しています。
|
||||
|
||||

|
||||
|
||||
## 🚀 OpenIMSDKについて
|
||||
## 🚀 OpenIMSDK について
|
||||
|
||||
**OpenIMSDK**は、**OpenIMServer**用に設計されたIM SDKで、クライアントアプリケーションに組み込むためのものです。主な機能とモジュールは以下の通りです:
|
||||
**OpenIMSDK**は、**OpenIMServer**用に設計された IM SDK で、クライアントアプリケーションに組み込むためのものです。主な機能とモジュールは以下の通りです:
|
||||
|
||||
+ 🌟 主な機能:
|
||||
- 🌟 主な機能:
|
||||
|
||||
- 📦 ローカルストレージ
|
||||
- 📦 ローカルストレージ
|
||||
- 🔔 リスナーコールバック
|
||||
- 🛡️ APIのラッピング
|
||||
- 🛡️ API のラッピング
|
||||
- 🌐 接続管理
|
||||
|
||||
## 📚 主なモジュール:
|
||||
@@ -79,54 +74,54 @@ OpenIMは、アプリケーション内でチャット、音声通話、通知
|
||||
4. 🤖 グループ機能
|
||||
5. 💬 会話処理
|
||||
|
||||
Golangを使用して構築され、クロスプラットフォームの導入をサポートし、すべてのプラットフォームで一貫したアクセス体験を提供します。
|
||||
Golang を使用して構築され、クロスプラットフォームの導入をサポートし、すべてのプラットフォームで一貫したアクセス体験を提供します。
|
||||
|
||||
👉 **[GO SDKを探索する](https://github.com/openimsdk/openim-sdk-core)**
|
||||
👉 **[GO SDK を探索する](https://github.com/openimsdk/openim-sdk-core)**
|
||||
|
||||
## 🌐 OpenIMServerについて
|
||||
## 🌐 OpenIMServer について
|
||||
|
||||
+ **OpenIMServer** には以下の特徴があります:
|
||||
- 🌐 マイクロサービスアーキテクチャ:クラスターモードをサポートし、ゲートウェイ(gateway)と複数のrpcサービスを含みます。
|
||||
- 🚀 多様なデプロイメント方法:ソースコード、kubernetes、またはdockerでのデプロイメントをサポートします。
|
||||
- **OpenIMServer** には以下の特徴があります:
|
||||
- 🌐 マイクロサービスアーキテクチャ:クラスターモードをサポートし、ゲートウェイ(gateway)と複数の rpc サービスを含みます。
|
||||
- 🚀 多様なデプロイメント方法:ソースコード、kubernetes、または docker でのデプロイメントをサポートします。
|
||||
- 海量ユーザーサポート:十万人規模の超大型グループ、千万人のユーザー、および百億のメッセージ
|
||||
|
||||
### 強化されたビジネス機能:
|
||||
|
||||
+ **REST API**:OpenIMServerは、ビジネスシステム用のREST APIを提供しており、ビジネスにさらに多くの機能を提供することを目指しています。たとえば、バックエンドインターフェースを通じてグループを作成したり、プッシュメッセージを送信したりするなどです。
|
||||
+ **Webhooks**:OpenIMServerは、より多くのビジネス形態を拡張するためのコールバック機能を提供しています。コールバックとは、特定のイベントが発生する前後に、OpenIMServerがビジネスサーバーにリクエストを送信することを意味します。例えば、メッセージ送信の前後のコールバックなどです。
|
||||
- **REST API**:OpenIMServer は、ビジネスシステム用の REST API を提供しており、ビジネスにさらに多くの機能を提供することを目指しています。たとえば、バックエンドインターフェースを通じてグループを作成したり、プッシュメッセージを送信したりするなどです。
|
||||
- **Webhooks**:OpenIMServer は、より多くのビジネス形態を拡張するためのコールバック機能を提供しています。コールバックとは、特定のイベントが発生する前後に、OpenIMServer がビジネスサーバーにリクエストを送信することを意味します。例えば、メッセージ送信の前後のコールバックなどです。
|
||||
|
||||
👉 **[もっと詳しく知る](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
## :building_construction: 全体のアーキテクチャ
|
||||
|
||||
Open-IM-Serverの機能の核心に迫るために、アーキテクチャダイアグラムをご覧ください。
|
||||
Open-IM-Server の機能の核心に迫るために、アーキテクチャダイアグラムをご覧ください。
|
||||
|
||||

|
||||
|
||||
## :rocket: クイックスタート
|
||||
|
||||
iOS/Android/H5/PC/Webでのオンライン体験:
|
||||
iOS/Android/H5/PC/Web でのオンライン体験:
|
||||
|
||||
👉 **[OpenIM online demo](https://www.openim.io/zh/commercial)**
|
||||
|
||||
🤲 ユーザー体験を容易にするために、私たちは様々なデプロイメントソリューションを提供しています。以下のリストから、ご自身のデプロイメント方法を選択できます:
|
||||
|
||||
+ **[ソースコードデプロイメントガイド](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Docker デプロイメントガイド](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Kubernetes デプロイメントガイド](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Mac 開発者向けデプロイメントガイド](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[ソースコードデプロイメントガイド](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Docker デプロイメントガイド](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Kubernetes デプロイメントガイド](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Mac 開発者向けデプロイメントガイド](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: OpenIMの開発を始める
|
||||
## :hammer_and_wrench: OpenIM の開発を始める
|
||||
|
||||
[](https://vscode.dev/github/openimsdk/open-im-server)
|
||||
|
||||
OpenIM 私たちの目標は、トップレベルのオープンソースコミュニティを構築することです。[コミュニティリポジトリ](https://github.com/OpenIMSDK/community)には一連の基準があります。
|
||||
|
||||
このOpen-IM-Serverリポジトリに貢献したい場合は、[貢献者ドキュメントをお読みください](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)。
|
||||
この Open-IM-Server リポジトリに貢献したい場合は、[貢献者ドキュメントをお読みください](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)。
|
||||
|
||||
始める前に、変更に必要があることを確認してください。最良の方法は、[新しいディスカッション](https://github.com/openimsdk/open-im-server/discussions/new/choose)や[Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)での通信を作成すること、または問題を発見した場合は、まずそれを[報告](https://github.com/openimsdk/open-im-server/issues/new/choose)することです。
|
||||
始める前に、変更に必要があることを確認してください。最良の方法は、[新しいディスカッション](https://github.com/openimsdk/open-im-server/discussions/new/choose)や[Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)での通信を作成すること、または問題を発見した場合は、まずそれを[報告](https://github.com/openimsdk/open-im-server/issues/new/choose)することです。
|
||||
|
||||
- [OpenIM APIリファレンス](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [OpenIM API リファレンス](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [OpenIM Bash ロギング](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
- [OpenIM CI/CD アクション](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/cicd-actions.md)
|
||||
- [OpenIM コード規約](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/code-conventions.md)
|
||||
@@ -149,37 +144,35 @@ OpenIM 私たちの目標は、トップレベルのオープンソースコミ
|
||||
- [OpenIM オフラインデプロイメント](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/offline-deployment.md)
|
||||
- [OpenIM Protoc ツール](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/protoc-tools.md)
|
||||
- [OpenIM テスティングガイド](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/test.md)
|
||||
- [OpenIM ユーティリティGo](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-go.md)
|
||||
- [OpenIM ユーティリティ Go](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-go.md)
|
||||
- [OpenIM Makefile ユーティリティ](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-makefile.md)
|
||||
- [OpenIM スクリプトユーティリティ](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-scripts.md)
|
||||
- [OpenIM バージョニング](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/version.md)
|
||||
- [バックエンド管理とモニターデプロイメント](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [OpenIM用Mac開発者デプロイメントガイド](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
- [OpenIM 用 Mac 開発者デプロイメントガイド](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
## :busts_in_silhouette: コミュニティ
|
||||
|
||||
+ 📚 [OpenIM コミュニティ](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [OpenIM 興味グループ](https://github.com/Openim-sigs)
|
||||
+ 🚀 [私たちのSlackコミュニティに参加する](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [私たちのWeChat(微信群)に参加する](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 📚 [OpenIM コミュニティ](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [OpenIM 興味グループ](https://github.com/Openim-sigs)
|
||||
- 🚀 [私たちの Slack コミュニティに参加する](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [私たちの WeChat(微信群)に参加する](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## :calendar: コミュニティミーティング
|
||||
|
||||
私たちは、誰もがコミュニティに参加し、コードに貢献してもらいたいと考えています。私たちは、ギフトや報酬を提供し、毎週木曜日の夜に参加していただくことを歓迎します。
|
||||
|
||||
私たちの会議は[OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)🎯で行われます。そこでOpen-IM-Serverパイプラインを検索して参加できます。
|
||||
私たちの会議は[OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)🎯 で行われます。そこで Open-IM-Server パイプラインを検索して参加できます。
|
||||
|
||||
私たちは[隔週の会議](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)のメモを[GitHub ディスカッション](https://github.com/openimsdk/open-im-server/discussions/categories/meeting)に記録しています。歴史的な会議のメモや会議のリプレイは[Google Docs📑](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing)で利用可能です。
|
||||
|
||||
私たちは[隔週の会議](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)のメモを[GitHubディスカッション](https://github.com/openimsdk/open-im-server/discussions/categories/meeting)に記録しています。歴史的な会議のメモや会議のリプレイは[Google Docs📑](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing)で利用可能です。
|
||||
## :eyes: OpenIM を使用している人たち
|
||||
|
||||
## :eyes: OpenIMを使用している人たち
|
||||
|
||||
プロジェクトユーザーのリストについては、[ユーザーケーススタディ](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md)ページをご覧ください。[コメント📝](https://github.com/openimsdk/open-im-server/issues/379)を残して、あなたの使用例を共有することを躊躇しないでください。
|
||||
プロジェクトユーザーのリストについては、[ユーザーケーススタディ](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md)ページをご覧ください。[コメント 📝](https://github.com/openimsdk/open-im-server/issues/379)を残して、あなたの使用例を共有することを躊躇しないでください。
|
||||
|
||||
## :page_facing_up: ライセンス
|
||||
|
||||
OpenIMはApache 2.0ライセンスの下でライセンスされています。完全なライセンステキストについては、[LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE)を参照してください。
|
||||
OpenIM は Apache 2.0 ライセンスの下でライセンスされています。完全なライセンステキストについては、[LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE)を参照してください。
|
||||
|
||||
このリポジトリに表示される[OpenIM](https://github.com/openimsdk/open-im-server)ロゴ、そのバリエーション、およびアニメーションバージョン([assets/logo](./assets/logo)および[assets/logo-gif](assets/logo-gif)ディレクトリ内)は、著作権法によって保護されています。
|
||||
|
||||
@@ -187,4 +180,4 @@ OpenIMはApache 2.0ライセンスの下でライセンスされています。
|
||||
|
||||
<a href="https://github.com/openimsdk/open-im-server/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=openimsdk/open-im-server" />
|
||||
</a>
|
||||
</a>
|
||||
|
||||
+18
-27
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,26 +45,21 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
## Ⓜ️ OpenIM에 대하여
|
||||
|
||||
OpenIM은 채팅, 오디오-비디오 통화, 알림 및 AI 챗봇을 애플리케이션에 통합하기 위해 특별히 설계된 서비스 플랫폼입니다. 이 플랫폼은 강력한 API와 웹훅을 제공하여 개발자가 이러한 상호작용 기능을 애플리케이션에 쉽게 통합할 수 있게 합니다. OpenIM은 독립 실행형 채팅 애플리케이션이 아니라, 다른 애플리케이션들이 풍부한 커뮤니케이션 기능을 달성할 수 있도록 지원하는 플랫폼으로서의 역할을 합니다. 다음 다이어그램은 AppServer, AppClient, OpenIMServer, 및 OpenIMSDK 간의 상호작용을 자세히 설명하기 위해 제시되었습니다.
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
## 🚀 OpenIMSDK에 대하여
|
||||
|
||||
**OpenIMSDK**는**OpenIMServer**를 위해 특별히 제작된 IM SDK로, 클라이언트 애플리케이션 내에 내장하기 위해 설계되었습니다. 그 주요 기능 및 모듈은 다음과 같습니다:
|
||||
**OpenIMSDK**는**OpenIMServer**를 위해 특별히 제작된 IM SDK로, 클라이언트 애플리케이션 내에 내장하기 위해 설계되었습니다. 그 주요 기능 및 모듈은 다음과 같습니다:
|
||||
|
||||
|
||||
+ 🌟 주요 기능:
|
||||
- 🌟 주요 기능:
|
||||
|
||||
- 📦 로컬 스토리지
|
||||
- 🔔 리스너 콜백
|
||||
@@ -86,15 +80,15 @@ OpenIM은 채팅, 오디오-비디오 통화, 알림 및 AI 챗봇을 애플리
|
||||
|
||||
## 🌐 OpenIMServer에 대하여
|
||||
|
||||
+ **OpenIMServer** 는 다음과 같은 특성을 가지고 있습니다:
|
||||
- **OpenIMServer** 는 다음과 같은 특성을 가지고 있습니다:
|
||||
- 🌐 마이크로서비스 아키텍처: 게이트웨이 및 다수의 rpc 서비스를 포함하는 클러스터 모드를 지원합니다.
|
||||
- 🚀 다양한 배포 방법: 소스 코드, 쿠버네티스 또는 도커를 통한 배포를 지원합니다.
|
||||
- 🚀 다양한 배포 방법: 소스 코드, 쿠버네티스 또는 도커를 통한 배포를 지원합니다.
|
||||
- 대규모 사용자 기반 지원: 수십만 명의 사용자를 포함하는 초대형 그룹, 수천만 명의 사용자 및 수십억 건의 메시지를 지원합니다.
|
||||
|
||||
### 강화된 비즈니스 기능:
|
||||
|
||||
+ **REST API**:OpenIMServer는 비즈니스 시스템을 위한 REST API를 제공하여, 백엔드 인터페이스를 통해 그룹 생성 및 푸시 메시지 전송과 같은 더 많은 기능을 비즈니스에 제공하기 위해 설계되었습니다.
|
||||
+ **Webhooks**:OpenIMServer는 더 많은 비즈니스 형태를 확장할 수 있는 콜백 기능을 제공합니다. 콜백이란 메시지 전송 전후와 같은 특정 이벤트 전후에 OpenIMServer가 비즈니스 서버로 요청을 보내는 것을 의미합니다.
|
||||
- **REST API**:OpenIMServer는 비즈니스 시스템을 위한 REST API를 제공하여, 백엔드 인터페이스를 통해 그룹 생성 및 푸시 메시지 전송과 같은 더 많은 기능을 비즈니스에 제공하기 위해 설계되었습니다.
|
||||
- **Webhooks**:OpenIMServer는 더 많은 비즈니스 형태를 확장할 수 있는 콜백 기능을 제공합니다. 콜백이란 메시지 전송 전후와 같은 특정 이벤트 전후에 OpenIMServer가 비즈니스 서버로 요청을 보내는 것을 의미합니다.
|
||||
|
||||
👉 **[더 알아보기](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
@@ -112,10 +106,10 @@ Open-IM-Server의 기능의 핵심으로 들어가 우리의 아키텍처 다이
|
||||
|
||||
🤲 사용자 경험을 용이하게 하기 위해, 다양한 배포 솔루션을 제공합니다. 아래 목록에서 배포 방법을 선택할 수 있습니다:
|
||||
|
||||
+ **[소스 코드 배포 가이드](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[docker 배포 가이드](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Kubernetes 배포 가이드](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Mac 개발자 배포 가이드](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[소스 코드 배포 가이드](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[docker 배포 가이드](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Kubernetes 배포 가이드](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Mac 개발자 배포 가이드](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: OpenIM 개발 시작하기
|
||||
|
||||
@@ -125,7 +119,7 @@ OpenIM의 목표는 최상위 수준의 오픈 소스 커뮤니티를 구축하
|
||||
|
||||
이 Open-IM-Server 리포지토리에 기여하고 싶다면, 우리의 [기여자 문서](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)를 읽어주세요.
|
||||
|
||||
시작하기 전에, 변경 사항이 필요한지 확인해 주세요. 가장 좋은 방법은 [새로운 토론](https://github.com/openimsdk/open-im-server/discussions/new/choose)을 생성하거나 [Slack 통신을](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 하거나, 문제를 발견했다면 먼저 [보고](https://github.com/openimsdk/open-im-server/issues/new/choose)하는 것입니다.
|
||||
시작하기 전에, 변경 사항이 필요한지 확인해 주세요. 가장 좋은 방법은 [새로운 토론](https://github.com/openimsdk/open-im-server/discussions/new/choose)을 생성하거나 [Slack 통신을](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 하거나, 문제를 발견했다면 먼저 [보고](https://github.com/openimsdk/open-im-server/issues/new/choose)하는 것입니다.
|
||||
|
||||
- [OpenIM API 참조](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [OpenIM Bash 로깅](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
@@ -157,23 +151,21 @@ OpenIM의 목표는 최상위 수준의 오픈 소스 커뮤니티를 구축하
|
||||
- [백엔드 관리 및 모니터 배포](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [맥 개발자 배포 가이드 for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
## :busts_in_silhouette: 커뮤니티
|
||||
|
||||
+ 📚 [OpenIM 커뮤니티](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [OpenIM 관심 그룹](https://github.com/Openim-sigs)
|
||||
+ 🚀 [우리의 Slack 커뮤니티에 가입하기](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [우리의 위챗(微信群)에 가입하기](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 📚 [OpenIM 커뮤니티](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [OpenIM 관심 그룹](https://github.com/Openim-sigs)
|
||||
- 🚀 [우리의 Slack 커뮤니티에 가입하기](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [우리의 위챗(微信群)에 가입하기](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## :calendar: 커뮤니티 미팅
|
||||
|
||||
우리는 누구나 커뮤니티에 참여하고 코드를 기여할 수 있도록 하며, 선물과 보상을 제공하며, 매주 목요일 밤에 여러분을 환영합니다.
|
||||
|
||||
우리의 회의는 [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯에서 이루어지며, Open-IM-Server 파이프라인을 검색하여 참여할 수 있습니다.
|
||||
우리의 회의는 [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯에서 이루어지며, Open-IM-Server 파이프라인을 검색하여 참여할 수 있습니다.
|
||||
|
||||
우리는 격주 회의의 메모를 [GitHub 토론](https://github.com/openimsdk/open-im-server/discussions/categories/meeting)에서 기록하며, 우리의 역사적 [회의](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) 노트와 회의 재생은 [Google Docs 📑](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing)에서 이용할 수 있습니다.
|
||||
|
||||
|
||||
## :eyes: OpenIM을 사용하는 사람들
|
||||
|
||||
프로젝트 사용자 목록을 위한 우리의 [사용자 사례 연구](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md) 페이지를 확인하세요. 사용 사례를 공유하고 싶다면 주저하지 말고 [📝코멘트](https://github.com/openimsdk/open-im-server/issues/379)를 남겨주세요.
|
||||
@@ -184,9 +176,8 @@ OpenIM은 Apache 2.0 라이선스에 따라 라이선스가 부여됩니다. 전
|
||||
|
||||
이 리포지토리 [OpenIM](https://github.com/openimsdk/open-im-server)에 표시된 OpenIM 로고, 그 변형 및 애니메이션 버전은 [assets/logo](../../assets/logo) 및 [assets/logo-gif](../../assets/logo-gif) 디렉토리 아래에 있으며, 저작권 법에 의해 보호됩니다.
|
||||
|
||||
|
||||
## 🔮 우리의 기여자들에게 감사합니다!
|
||||
|
||||
<a href="https://github.com/openimsdk/open-im-server/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=openimsdk/open-im-server" />
|
||||
</a>
|
||||
</a>
|
||||
|
||||
+17
-23
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,25 +45,21 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
|
||||
|
||||
## Ⓜ️ OpenIM Hakkında
|
||||
|
||||
OpenIM, uygulamalara sohbet, sesli-görüntülü aramalar, bildirimler ve AI sohbet robotları entegre etmek için özel olarak tasarlanmış bir hizmet platformudur. Güçlü API'ler ve Webhook'lar sunarak, geliştiricilerin bu etkileşimli özellikleri uygulamalarına kolayca dahil etmelerini sağlar. OpenIM bağımsız bir sohbet uygulaması değildir, ancak zengin iletişim işlevselliği sağlama amacıyla diğer uygulamaları destekleyen bir platform olarak hizmet verir. Aşağıdaki diyagram, AppServer, AppClient, OpenIMServer ve OpenIMSDK arasındaki etkileşimi detaylandırmak için açıklar.
|
||||
|
||||
|
||||
|
||||

|
||||
|
||||
## 🚀 OpenIMSDK Hakkında
|
||||
|
||||
**OpenIMSDK**, müşteri uygulamalarına gömülmek üzere özel olarak oluşturulan **OpenIMServer** için tasarlanmış bir IM SDK'sıdır. Ana özellikleri ve modülleri aşağıdaki gibidir:
|
||||
**OpenIMSDK**, müşteri uygulamalarına gömülmek üzere özel olarak oluşturulan **OpenIMServer** için tasarlanmış bir IM SDK'sıdır. Ana özellikleri ve modülleri aşağıdaki gibidir:
|
||||
|
||||
+ 🌟 Ana Özellikler:
|
||||
- 🌟 Ana Özellikler:
|
||||
|
||||
- 📦 Yerel depolama
|
||||
- 🔔 Dinleyici geri çağırmaları
|
||||
@@ -85,15 +80,15 @@ Golang kullanılarak inşa edilmiş ve tüm platformlarda tutarlı bir erişim d
|
||||
|
||||
## 🌐 OpenIMServer Hakkında
|
||||
|
||||
+ **OpenIMServer** aşağıdaki özelliklere sahiptir:
|
||||
- **OpenIMServer** aşağıdaki özelliklere sahiptir:
|
||||
- 🌐 Mikroservis mimarisi: Bir kapı ve çoklu rpc servisleri içeren küme modunu destekler.
|
||||
- 🚀 Çeşitli dağıtım yöntemleri: Kaynak kodu, Kubernetes veya Docker aracılığıyla dağıtımı destekler.
|
||||
- Büyük kullanıcı tabanı desteği: Yüz binlerce kullanıcısı olan süper büyük gruplar, on milyonlarca kullanıcı ve milyarlarca mesaj.
|
||||
|
||||
### Geliştirilmiş İşlevsellik:
|
||||
|
||||
+ **REST API**:OpenIMServer, işletmeleri gruplar oluşturma ve arka plan arayüzleri aracılığıyla itme mesajları gönderme gibi daha fazla işlevsellikle güçlendirmeyi amaçlayan iş sistemleri için REST API'leri sunar.
|
||||
+ **Webhooks**:OpenIMServer, daha fazla iş formunu genişletme yetenekleri sağlayan geri çağırma özellikleri sunar. Geri çağırma, OpenIMServer'ın belirli bir olaydan önce veya sonra, örneğin bir mesaj göndermeden önce veya sonra iş sunucusuna bir istek göndermesi anlamına gelir.
|
||||
- **REST API**:OpenIMServer, işletmeleri gruplar oluşturma ve arka plan arayüzleri aracılığıyla itme mesajları gönderme gibi daha fazla işlevsellikle güçlendirmeyi amaçlayan iş sistemleri için REST API'leri sunar.
|
||||
- **Webhooks**:OpenIMServer, daha fazla iş formunu genişletme yetenekleri sağlayan geri çağırma özellikleri sunar. Geri çağırma, OpenIMServer'ın belirli bir olaydan önce veya sonra, örneğin bir mesaj göndermeden önce veya sonra iş sunucusuna bir istek göndermesi anlamına gelir.
|
||||
|
||||
👉 **[Daha fazla bilgi edinin](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
@@ -111,10 +106,10 @@ Birçok platformu destekliyoruz. Web tarafında hızlı deneyim için adresler
|
||||
|
||||
🤲 Kullanıcı deneyimini kolaylaştırmak için çeşitli dağıtım çözümleri sunuyoruz. Aşağıdaki listeden dağıtım yönteminizi seçebilirsiniz:
|
||||
|
||||
+ **[Kaynak Kodu Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Docker Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Kubernetes Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Mac Geliştirici Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[Kaynak Kodu Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Docker Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Kubernetes Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Mac Geliştirici Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: OpenIM Geliştirmeye Başlamak
|
||||
|
||||
@@ -124,7 +119,7 @@ OpenIM Amacımız, üst düzey bir açık kaynak topluluğu oluşturmaktır. [To
|
||||
|
||||
Bu Open-IM-Server deposuna katkıda bulunmak istiyorsanız, lütfen katkıda bulunanlar için [dokümantasyonumuzu](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md) okuyun.
|
||||
|
||||
Başlamadan önce, lütfen değişikliklerinizin talep edildiğinden emin olun. Bunun için en iyisi, [yeni bir tartışma OLUŞTURMAK](https://github.com/openimsdk/open-im-server/discussions/new/choose) veya [Slack İletişimi](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) kurmak, ya da bir sorun bulursanız, önce bunu [rapor](https://github.com/openimsdk/open-im-server/issues/new/choose) etmektir.
|
||||
Başlamadan önce, lütfen değişikliklerinizin talep edildiğinden emin olun. Bunun için en iyisi, [yeni bir tartışma OLUŞTURMAK](https://github.com/openimsdk/open-im-server/discussions/new/choose) veya [Slack İletişimi](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) kurmak, ya da bir sorun bulursanız, önce bunu [rapor](https://github.com/openimsdk/open-im-server/issues/new/choose) etmektir.
|
||||
|
||||
- [OpenIM API Referansı](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [OpenIM Bash Günlüğü](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
@@ -156,19 +151,18 @@ Başlamadan önce, lütfen değişikliklerinizin talep edildiğinden emin olun.
|
||||
- [Arka uç yönetimi ve izleme dağıtımı](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [Mac Geliştirici Dağıtım Kılavuzu for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
## :busts_in_silhouette: Topluluk
|
||||
|
||||
+ 📚 [OpenIM Topluluğu](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [OpenIM İlgi Grubu](https://github.com/Openim-sigs)
|
||||
+ 🚀 [Slack topluluğumuza katılın](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [Wechat grubumuza katılın (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 📚 [OpenIM Topluluğu](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [OpenIM İlgi Grubu](https://github.com/Openim-sigs)
|
||||
- 🚀 [Slack topluluğumuza katılın](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [Wechat grubumuza katılın (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## :calendar: Topluluk Toplantıları
|
||||
|
||||
Topluluğumuza herkesin katılmasını ve kod katkısında bulunmasını istiyoruz, hediyeler ve ödüller sunuyoruz ve sizi her Perşembe gecesi bize katılmaya davet ediyoruz.
|
||||
|
||||
Konferansımız [OpenIM Slack'te](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, ardından Open-IM-Server boru hattını arayıp katılabilirsiniz.
|
||||
Konferansımız [OpenIM Slack'te](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, ardından Open-IM-Server boru hattını arayıp katılabilirsiniz.
|
||||
|
||||
İki haftada bir yapılan toplantının [notlarını](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) [GitHub tartışmalarında alıyoruz](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Tarihi toplantı notlarımız ve toplantıların tekrarları [Google Docs'ta](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) 📑 mevcut.
|
||||
|
||||
@@ -186,4 +180,4 @@ Bu depoda, [assets/logo](../../assets/logo) ve [assets/logo-gif](../../assets/lo
|
||||
|
||||
<a href="https://github.com/openimsdk/open-im-server/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=openimsdk/open-im-server" />
|
||||
</a>
|
||||
</a>
|
||||
|
||||
+16
-20
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,7 +45,6 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
@@ -61,14 +59,14 @@ OpenIM — це сервісна платформа, спеціально роз
|
||||
|
||||
**OpenIMSDK** – це пакет IM SDK, розроблений для **OpenIMServer**, створений спеціально для вбудовування в клієнтські програми. Його основні функції та модулі такі:
|
||||
|
||||
+ 🌟 Основні характеристики:
|
||||
- 🌟 Основні характеристики:
|
||||
|
||||
- 📦 Локальне сховище
|
||||
- 🔔 Зворотні виклики слухача
|
||||
- 🛡️ Обгортка API
|
||||
- 🌐 Керування підключенням
|
||||
|
||||
+ 📚 Основні модулі:
|
||||
- 📚 Основні модулі:
|
||||
|
||||
1. 🚀 Ініціалізація та вхід
|
||||
2. 👤 Керування користувачами
|
||||
@@ -82,15 +80,15 @@ OpenIM — це сервісна платформа, спеціально роз
|
||||
|
||||
## 🌐 Про OpenIMServer
|
||||
|
||||
+ **OpenIMServer** має такі характеристики:
|
||||
- **OpenIMServer** має такі характеристики:
|
||||
- 🌐 Архітектура мікросервісу: підтримує режим кластера, включаючи шлюз і кілька служб rpc.
|
||||
- 🚀 Різноманітні методи розгортання: підтримує розгортання через вихідний код, Kubernetes або Docker.
|
||||
- Підтримка величезної бази користувачів: надвеликі групи із сотнями тисяч користувачів, десятками мільйонів користувачів і мільярдами повідомлень.
|
||||
|
||||
### Розширена бізнес-функціональність:
|
||||
|
||||
+ **REST API**: OpenIMServer пропонує REST API для бізнес-систем, спрямованих на надання компаніям додаткових можливостей, таких як створення груп і надсилання push-повідомлень через серверні інтерфейси.
|
||||
+ **Веб-перехоплення**: OpenIMServer надає можливості зворотного виклику, щоб розширити більше бізнес-форм. Зворотний виклик означає, що OpenIMServer надсилає запит на бізнес-сервер до або після певної події, як зворотні виклики до або після надсилання повідомлення.
|
||||
- **REST API**: OpenIMServer пропонує REST API для бізнес-систем, спрямованих на надання компаніям додаткових можливостей, таких як створення груп і надсилання push-повідомлень через серверні інтерфейси.
|
||||
- **Веб-перехоплення**: OpenIMServer надає можливості зворотного виклику, щоб розширити більше бізнес-форм. Зворотний виклик означає, що OpenIMServer надсилає запит на бізнес-сервер до або після певної події, як зворотні виклики до або після надсилання повідомлення.
|
||||
|
||||
👉 **[Докладніше](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
@@ -100,7 +98,6 @@ OpenIM — це сервісна платформа, спеціально роз
|
||||
|
||||

|
||||
|
||||
|
||||
## :rocket: Швидкий початок
|
||||
|
||||
Ми підтримуємо багато платформ. Ось адреси для швидкого використання веб-сайту:
|
||||
@@ -109,10 +106,10 @@ OpenIM — це сервісна платформа, спеціально роз
|
||||
|
||||
🤲 Щоб полегшити роботу користувача, ми пропонуємо різні рішення для розгортання. Ви можете вибрати спосіб розгортання зі списку нижче:
|
||||
|
||||
+ **[Посібник із розгортання вихідного коду](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Посібник із розгортання Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Посібник із розгортання Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Посібник із розгортання розробника Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[Посібник із розгортання вихідного коду](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Посібник із розгортання Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Посібник із розгортання Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Посібник із розгортання розробника Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: Щоб розпочати розробку OpenIM
|
||||
|
||||
@@ -122,7 +119,7 @@ OpenIM. Наша мета — побудувати спільноту з від
|
||||
|
||||
Якщо ви хочете внести свій внесок у це сховище Open-IM-Server, прочитайте нашу [документацію для учасників](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
|
||||
|
||||
Перш ніж почати, переконайтеся, що ваші зміни затребувані. Найкраще для цього створити [нове обговорення](https://github.com/openimsdk/open-im-server/discussions/new/choose) АБО [Нездійснене спілкування](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)або, якщо ви виявите проблему, спершу [повідомити про неї](https://github.com/openimsdk/open-im-server/issues/new/choose).
|
||||
Перш ніж почати, переконайтеся, що ваші зміни затребувані. Найкраще для цього створити [нове обговорення](https://github.com/openimsdk/open-im-server/discussions/new/choose) АБО [Нездійснене спілкування](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)або, якщо ви виявите проблему, спершу [повідомити про неї](https://github.com/openimsdk/open-im-server/issues/new/choose).
|
||||
|
||||
- [Довідка щодо API OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [Ведення журналу OpenIM Bash](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
@@ -154,19 +151,18 @@ OpenIM. Наша мета — побудувати спільноту з від
|
||||
- [Керування серверною частиною та моніторинг розгортання](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [Посібник із розгортання розробника Mac для OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
## :busts_in_silhouette: Спільнота
|
||||
|
||||
+ 📚 [Спільнота OpenIM](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [Група інтересів OpenIM](https://github.com/Openim-sigs)
|
||||
+ 🚀 [Приєднайтеся до нашої спільноти Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [Приєднайтеся до нашого wechat](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 📚 [Спільнота OpenIM](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [Група інтересів OpenIM](https://github.com/Openim-sigs)
|
||||
- 🚀 [Приєднайтеся до нашої спільноти Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [Приєднайтеся до нашого wechat](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## :calendar: Збори громади
|
||||
|
||||
Ми хочемо, щоб будь-хто долучився до нашої спільноти та додав код, ми пропонуємо подарунки та нагороди, і ми запрошуємо вас приєднатися до нас щочетверга ввечері.
|
||||
|
||||
Наша конференція знаходиться в [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, тоді ви можете шукати конвеєр Open-IM-Server, щоб приєднатися.
|
||||
Наша конференція знаходиться в [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, тоді ви можете шукати конвеєр Open-IM-Server, щоб приєднатися.
|
||||
|
||||
Ми робимо нотатки про кожну [двотижневу зустріч](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)в [обговореннях GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting). Наші історичні нотатки зустрічей, а також повтори зустрічей доступні в[Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
|
||||
|
||||
|
||||
+18
-23
@@ -12,12 +12,11 @@
|
||||
[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
|
||||
[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
|
||||
[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
[](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
[](https://www.bestpractices.dev/projects/8045)
|
||||
[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
|
||||
[](https://golang.org/)
|
||||
|
||||
|
||||
<p align="center">
|
||||
<a href="../../README.md">English</a> ·
|
||||
<a href="../../README_zh_CN.md">中文</a> ·
|
||||
@@ -46,7 +45,6 @@
|
||||
<a href="./README_tr.md">Türkçe</a>
|
||||
</p>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
</p>
|
||||
@@ -61,14 +59,14 @@ OpenIM là một nền tảng dịch vụ được thiết kế đặc biệt ch
|
||||
|
||||
**OpenIMSDK** là một SDK IM được thiết kế cho **OpenIMServer**, được tạo ra đặc biệt để nhúng vào các ứng dụng khách. Các tính năng chính và các mô-đun của nó như sau:
|
||||
|
||||
+ 🌟 Các Tính Năng Chính:
|
||||
- 🌟 Các Tính Năng Chính:
|
||||
|
||||
- 📦 Lưu trữ cục bộ
|
||||
- 🔔 Gọi lại sự kiện (Listener callbacks)
|
||||
- 🛡️ Bọc API
|
||||
- 🌐 Quản lý kết nối
|
||||
|
||||
+ 📚 Các Mô-đun Chính:
|
||||
- 📚 Các Mô-đun Chính:
|
||||
|
||||
1. 🚀 Khởi tạo và Đăng nhập
|
||||
2. 👤 Quản lý Người dùng
|
||||
@@ -82,25 +80,24 @@ Nó được xây dựng bằng Golang và hỗ trợ triển khai đa nền t
|
||||
|
||||
## 🌐 Về OpenIMServer
|
||||
|
||||
+ **OpenIMServer** có những đặc điểm sau:
|
||||
- **OpenIMServer** có những đặc điểm sau:
|
||||
- 🌐 Kiến trúc vi dịch vụ: Hỗ trợ chế độ cluster, bao gồm một gateway và nhiều dịch vụ rpc.
|
||||
- 🚀 Phương pháp triển khai đa dạng: Hỗ trợ triển khai qua mã nguồn, Kubernetes hoặc Docker.
|
||||
- Hỗ trợ cho cơ sở người dùng lớn: Nhóm siêu lớn với hàng trăm nghìn người dùng, hàng chục triệu người dùng và hàng tỷ tin nhắn.
|
||||
|
||||
### Tăng cường Chức năng Kinh doanh:
|
||||
|
||||
+ **REST API**: OpenIMServer cung cấp REST APIs cho các hệ thống kinh doanh, nhằm tăng cường khả năng cho doanh nghiệp với nhiều chức năng hơn, như tạo nhóm và gửi tin nhắn đẩy qua giao diện backend.
|
||||
+ **Webhooks**: OpenIMServer cung cấp khả năng gọi lại để mở rộng thêm hình thức kinh doanh. Một gọi lại có nghĩa là OpenIMServer gửi một yêu cầu đến máy chủ kinh doanh trước hoặc sau một sự kiện nhất định, giống như gọi lại trước hoặc sau khi gửi một tin nhắn.
|
||||
- **REST API**: OpenIMServer cung cấp REST APIs cho các hệ thống kinh doanh, nhằm tăng cường khả năng cho doanh nghiệp với nhiều chức năng hơn, như tạo nhóm và gửi tin nhắn đẩy qua giao diện backend.
|
||||
- **Webhooks**: OpenIMServer cung cấp khả năng gọi lại để mở rộng thêm hình thức kinh doanh. Một gọi lại có nghĩa là OpenIMServer gửi một yêu cầu đến máy chủ kinh doanh trước hoặc sau một sự kiện nhất định, giống như gọi lại trước hoặc sau khi gửi một tin nhắn.
|
||||
|
||||
👉 **[Learn more](https://docs.openim.io/guides/introduction/product)**
|
||||
|
||||
## :building_construction: Kiến trúc tổng thể
|
||||
|
||||
Làm sâu sắc vào trái tim của chức năng Open-IM-Server với sơ đồ kiến trúc của chúng tôi.
|
||||
Làm sâu sắc vào trái tim của chức năng Open-IM-Server với sơ đồ kiến trúc của chúng tôi.
|
||||
|
||||

|
||||
|
||||
|
||||
## :rocket: Bắt đầu nhanh
|
||||
|
||||
Chúng tôi hỗ trợ nhiều nền tảng. Dưới đây là các địa chỉ để trải nghiệm nhanh trên phía web:
|
||||
@@ -109,12 +106,12 @@ Chúng tôi hỗ trợ nhiều nền tảng. Dưới đây là các địa chỉ
|
||||
|
||||
🤲 Để tạo thuận lợi cho trải nghiệm người dùng, chúng tôi cung cấp các giải pháp triển khai đa dạng. Bạn có thể chọn phương thức triển khai từ danh sách dưới đây:
|
||||
|
||||
+ **[Hướng dẫn Triển khai Mã Nguồn](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
+ **[Hướng dẫn Triển khai Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
+ **[Hướng dẫn Triển khai Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
+ **[Hướng dẫn Triển khai cho Nhà Phát Triển Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
- **[Hướng dẫn Triển khai Mã Nguồn](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
|
||||
- **[Hướng dẫn Triển khai Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
|
||||
- **[Hướng dẫn Triển khai Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
|
||||
- **[Hướng dẫn Triển khai cho Nhà Phát Triển Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
|
||||
|
||||
## :hammer_and_wrench: Để Bắt Đầu Phát Triển OpenIM
|
||||
## :hammer_and_wrench: Để Bắt Đầu Phát Triển OpenIM
|
||||
|
||||
[](https://vscode.dev/github/openimsdk/open-im-server)
|
||||
|
||||
@@ -122,8 +119,7 @@ Mục tiêu của OpenIM là xây dựng một cộng đồng mã nguồn mở c
|
||||
|
||||
Nếu bạn muốn đóng góp cho kho lưu trữ Open-IM-Server này, vui lòng đọc [tài liệu hướng dẫn cho người đóng góp](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
|
||||
|
||||
|
||||
Trước khi bạn bắt đầu, hãy chắc chắn rằng các thay đổi của bạn được yêu cầu. Cách tốt nhất là tạo một [cuộc thảo luận mới](https://github.com/openimsdk/open-im-server/discussions/new/choose) hoặc [Giao tiếp Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), hoặc nếu bạn tìm thấy một vấn đề, [báo cáo nó ](https://github.com/openimsdk/open-im-server/issues/new/choose) trước.
|
||||
Trước khi bạn bắt đầu, hãy chắc chắn rằng các thay đổi của bạn được yêu cầu. Cách tốt nhất là tạo một [cuộc thảo luận mới](https://github.com/openimsdk/open-im-server/discussions/new/choose) hoặc [Giao tiếp Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A), hoặc nếu bạn tìm thấy một vấn đề, [báo cáo nó ](https://github.com/openimsdk/open-im-server/issues/new/choose) trước.
|
||||
|
||||
- [Tham khảo API OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
|
||||
- [Nhật ký Bash OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
|
||||
@@ -155,19 +151,18 @@ Trước khi bạn bắt đầu, hãy chắc chắn rằng các thay đổi củ
|
||||
- [Quản lý triển khai và giám sát backend](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
|
||||
- [Hướng dẫn Triển khai cho Nhà Phát triển Mac OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
|
||||
|
||||
|
||||
## :busts_in_silhouette: Cộng đồng
|
||||
|
||||
+ 📚 [Cộng đồng OpenIM](https://github.com/OpenIMSDK/community)
|
||||
+ 💕 [Nhóm Quan tâm OpenIM](https://github.com/Openim-sigs)
|
||||
+ 🚀 [Tham gia cộng đồng Slack của chúng tôi](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
|
||||
+ :eyes: [Tham gia nhóm WeChat của chúng tôi (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
- 📚 [Cộng đồng OpenIM](https://github.com/OpenIMSDK/community)
|
||||
- 💕 [Nhóm Quan tâm OpenIM](https://github.com/Openim-sigs)
|
||||
- 🚀 [Tham gia cộng đồng Slack của chúng tôi](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
|
||||
- :eyes: [Tham gia nhóm WeChat của chúng tôi (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
|
||||
|
||||
## :calendar: Cuộc họp Cộng đồng
|
||||
|
||||
Chúng tôi muốn bất kỳ ai cũng có thể tham gia cộng đồng và đóng góp mã nguồn, chúng tôi cung cấp quà tặng và phần thưởng, và chúng tôi chào đón bạn tham gia cùng chúng tôi mỗi tối thứ Năm.
|
||||
|
||||
Hội nghị của chúng tôi được tổ chức trên Slack của [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, sau đó bạn có thể tìm kiếm pipeline Open-IM-Server để tham gia
|
||||
Hội nghị của chúng tôi được tổ chức trên Slack của [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, sau đó bạn có thể tìm kiếm pipeline Open-IM-Server để tham gia
|
||||
|
||||
Chúng tôi ghi chú mỗi [cuộc họp hai tuần một lần](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) trong [các cuộc thảo luận GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), ghi chú cuộc họp lịch sử của chúng tôi cũng như các bản ghi lại của cuộc họp có sẵn tại [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module github.com/openimsdk/open-im-server/v3
|
||||
|
||||
go 1.22.7
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
firebase.google.com/go/v4 v4.14.1
|
||||
@@ -12,15 +12,15 @@ require (
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/openimsdk/protocol v0.0.72-alpha.71
|
||||
github.com/openimsdk/tools v0.0.50-alpha.65
|
||||
github.com/openimsdk/protocol v0.0.73-alpha.19
|
||||
github.com/openimsdk/tools v0.0.50-alpha.113
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
go.mongodb.org/mongo-driver v1.14.0
|
||||
google.golang.org/api v0.170.0
|
||||
google.golang.org/grpc v1.68.0
|
||||
google.golang.org/protobuf v1.35.1
|
||||
google.golang.org/grpc v1.71.0
|
||||
google.golang.org/protobuf v1.36.4
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
)
|
||||
|
||||
@@ -35,14 +35,14 @@ require (
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/kelindar/bitmap v1.5.2
|
||||
github.com/likexian/gokit v0.25.13
|
||||
github.com/openimsdk/gomake v0.0.15-alpha.2
|
||||
github.com/openimsdk/gomake v0.0.17
|
||||
github.com/redis/go-redis/v9 v9.4.0
|
||||
github.com/robfig/cron/v3 v3.0.1
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||
github.com/spf13/viper v1.18.2
|
||||
go.etcd.io/etcd/client/v3 v3.5.13
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/sync v0.8.0
|
||||
golang.org/x/sync v0.10.0
|
||||
k8s.io/api v0.31.2
|
||||
k8s.io/apimachinery v0.31.2
|
||||
k8s.io/client-go v0.31.2
|
||||
@@ -50,7 +50,7 @@ require (
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.112.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
cloud.google.com/go/firestore v1.15.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.7 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.5 // indirect
|
||||
@@ -76,6 +76,7 @@ require (
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect
|
||||
github.com/aws/smithy-go v1.22.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
@@ -90,15 +91,16 @@ require (
|
||||
github.com/eapache/go-resiliency v1.6.0 // indirect
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
|
||||
github.com/eapache/queue v1.1.0 // indirect
|
||||
github.com/ebitengine/purego v0.10.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||
github.com/go-openapi/swag v0.22.4 // indirect
|
||||
@@ -108,7 +110,7 @@ require (
|
||||
github.com/golang/protobuf v1.5.4 // indirect
|
||||
github.com/golang/snappy v0.0.4 // indirect
|
||||
github.com/google/gnostic-models v0.6.8 // indirect
|
||||
github.com/google/go-cmp v0.6.0 // indirect
|
||||
github.com/google/go-cmp v0.7.0 // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/gofuzz v1.2.0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
@@ -135,6 +137,7 @@ require (
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/lestrrat-go/strftime v1.0.6 // indirect
|
||||
github.com/lithammer/shortuuid v3.0.0+incompatible // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||
github.com/magefile/mage v1.15.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
@@ -151,6 +154,7 @@ require (
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
@@ -159,6 +163,10 @@ require (
|
||||
github.com/rs/xid v1.5.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sercand/kuberesolver/v6 v6.0.1 // indirect
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
|
||||
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
|
||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
github.com/spf13/afero v1.11.0 // indirect
|
||||
github.com/spf13/cast v1.6.0 // indirect
|
||||
@@ -166,8 +174,8 @@ require (
|
||||
github.com/stretchr/objx v0.5.2 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.47 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
||||
github.com/tklauser/numcpus v0.7.0 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||
@@ -178,26 +186,27 @@ require (
|
||||
go.etcd.io/etcd/api/v3 v3.5.13 // indirect
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||
golang.org/x/image v0.15.0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/term v0.24.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/net v0.34.0 // indirect
|
||||
golang.org/x/oauth2 v0.25.0 // indirect
|
||||
golang.org/x/sys v0.42.0 // indirect
|
||||
golang.org/x/term v0.28.0 // indirect
|
||||
golang.org/x/text v0.21.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/appengine/v2 v2.0.2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gorm.io/gorm v1.25.8 // indirect
|
||||
@@ -216,6 +225,6 @@ require (
|
||||
github.com/spf13/cobra v1.8.0
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/crypto v0.32.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
||||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||
cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
|
||||
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
|
||||
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
|
||||
@@ -61,6 +61,8 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
|
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||
@@ -103,6 +105,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4A
|
||||
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
|
||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
@@ -117,8 +121,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
|
||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||
@@ -134,8 +138,9 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||
@@ -202,8 +207,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||
@@ -303,6 +308,8 @@ github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVk
|
||||
github.com/likexian/gokit v0.25.13/go.mod h1:qQhEWFBEfqLCO3/vOEo2EDKd+EycekVtUK4tex+l2H4=
|
||||
github.com/lithammer/shortuuid v3.0.0+incompatible h1:NcD0xWW/MZYXEHa6ITy6kaXN5nwm/V115vj2YXfhS0w=
|
||||
github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHTK2Tciko1/vKuIKS9dSkDrA4w=
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
|
||||
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
@@ -345,12 +352,12 @@ github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA
|
||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
|
||||
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
|
||||
github.com/openimsdk/gomake v0.0.15-alpha.2 h1:5Q8yl8ezy2yx+q8/ucU/t4kJnDfCzNOrkXcDACCqtyM=
|
||||
github.com/openimsdk/gomake v0.0.15-alpha.2/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
|
||||
github.com/openimsdk/protocol v0.0.72-alpha.71 h1:R3utzOlqepaJWTAmnfJi4ccUM/XIoFasSyjQMOipM70=
|
||||
github.com/openimsdk/protocol v0.0.72-alpha.71/go.mod h1:WF7EuE55vQvpyUAzDXcqg+B+446xQyEba0X35lTINmw=
|
||||
github.com/openimsdk/tools v0.0.50-alpha.65 h1:BRtxkyWxDWPHuHphSwEyHZj7kJSR98am/fHOH84naK8=
|
||||
github.com/openimsdk/tools v0.0.50-alpha.65/go.mod h1:B+oqV0zdewN7OiEHYJm+hW+8/Te7B8tHHgD8rK5ZLZk=
|
||||
github.com/openimsdk/gomake v0.0.17 h1:q8haP48VOH45WhJRiLj1YSBJyUFJqD8CTedH65i1YH8=
|
||||
github.com/openimsdk/gomake v0.0.17/go.mod h1:nnjS8yCtrPJAt1knMbyPiUwCH2gpyBzj/EZAONfUOXg=
|
||||
github.com/openimsdk/protocol v0.0.73-alpha.19 h1:CvXoDF2U73UcMhLnrtMFks2Aw+bXiDgH8AITEt783/s=
|
||||
github.com/openimsdk/protocol v0.0.73-alpha.19/go.mod h1:WF7EuE55vQvpyUAzDXcqg+B+446xQyEba0X35lTINmw=
|
||||
github.com/openimsdk/tools v0.0.50-alpha.113 h1:rhLWaSJuhjgJFNVzmpChLCG7dPXS0+bte+CPI0008Us=
|
||||
github.com/openimsdk/tools v0.0.50-alpha.113/go.mod h1:x9i/e+WJFW4tocy6RNJQ9NofQiP3KJ1Y576/06TqOG4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
@@ -361,6 +368,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||
@@ -384,8 +393,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
||||
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -393,8 +402,18 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke
|
||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sercand/kuberesolver/v6 v6.0.1 h1:XZUTA0gy/lgDYp/UhEwv7Js24F1j8NJ833QrWv0Xux4=
|
||||
github.com/sercand/kuberesolver/v6 v6.0.1/go.mod h1:C0tsTuRMONSY+Xf7pv7RMW1/JlewY1+wS8SZE+1lf1s=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
|
||||
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
@@ -420,18 +439,19 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.47 h1:uoS4Sob16qEYoapkqJq1D1Vnsy9ira9BfNUMtoFYTI4=
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.47/go.mod h1:DH9US8nB+AJXqwu/AMOrCFN1COv3dpytXuJWHgdg7kE=
|
||||
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
||||
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
||||
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
@@ -461,18 +481,22 @@ go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
@@ -493,8 +517,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||
@@ -522,25 +546,26 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -549,14 +574,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
@@ -565,8 +590,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@@ -597,17 +622,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||
@@ -617,8 +642,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
|
||||
+1
-1
@@ -531,7 +531,7 @@ O:::::::OOO:::::::O p:::::ppppp:::::::pe::::::::e n::::n n::::nII:
|
||||
# Set text color to yellow for the Slack link
|
||||
echo -e "\033[1;33m"
|
||||
|
||||
print_with_delay "Join our developer community on Slack: https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q" 0.01
|
||||
print_with_delay "Join our developer community on Slack: https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A" 0.01
|
||||
|
||||
# Reset text color back to normal
|
||||
echo -e "\033[0m"
|
||||
|
||||
+109
-12
@@ -15,6 +15,7 @@ import (
|
||||
"github.com/openimsdk/tools/apiresp"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/runtimeenv"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
@@ -30,22 +31,20 @@ type ConfigManager struct {
|
||||
client *clientv3.Client
|
||||
|
||||
configPath string
|
||||
runtimeEnv string
|
||||
}
|
||||
|
||||
func NewConfigManager(IMAdminUserID []string, cfg *config.AllConfig, client *clientv3.Client, configPath string, runtimeEnv string) *ConfigManager {
|
||||
func NewConfigManager(IMAdminUserID []string, cfg *config.AllConfig, client *clientv3.Client, configPath string) *ConfigManager {
|
||||
cm := &ConfigManager{
|
||||
imAdminUserID: IMAdminUserID,
|
||||
config: cfg,
|
||||
client: client,
|
||||
configPath: configPath,
|
||||
runtimeEnv: runtimeEnv,
|
||||
}
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *ConfigManager) CheckAdmin(c *gin.Context) {
|
||||
if err := authverify.CheckAdmin(c, cm.imAdminUserID); err != nil {
|
||||
if err := authverify.CheckAdmin(c); err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
c.Abort()
|
||||
}
|
||||
@@ -73,7 +72,7 @@ func (cm *ConfigManager) GetConfig(c *gin.Context) {
|
||||
func (cm *ConfigManager) GetConfigList(c *gin.Context) {
|
||||
var resp apistruct.GetConfigListResp
|
||||
resp.ConfigNames = cm.config.GetConfigNames()
|
||||
resp.Environment = runtimeenv.PrintRuntimeEnvironment()
|
||||
resp.Environment = runtimeenv.RuntimeEnvironment()
|
||||
resp.Version = version.Version
|
||||
|
||||
apiresp.GinSuccess(c, resp)
|
||||
@@ -146,6 +145,110 @@ func (cm *ConfigManager) SetConfig(c *gin.Context) {
|
||||
apiresp.GinSuccess(c, nil)
|
||||
}
|
||||
|
||||
func (cm *ConfigManager) SetConfigs(c *gin.Context) {
|
||||
if cm.config.Discovery.Enable != config.ETCD {
|
||||
apiresp.GinError(c, errs.New("only etcd support set config").Wrap())
|
||||
return
|
||||
}
|
||||
var req apistruct.SetConfigsReq
|
||||
if err := c.BindJSON(&req); err != nil {
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||
return
|
||||
}
|
||||
var (
|
||||
err error
|
||||
ops []*clientv3.Op
|
||||
)
|
||||
|
||||
for _, cf := range req.Configs {
|
||||
var op *clientv3.Op
|
||||
switch cf.ConfigName {
|
||||
case cm.config.Discovery.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Discovery](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Kafka.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Kafka](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.LocalCache.GetConfigFileName():
|
||||
op, err = compareAndOp[config.LocalCache](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Log.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Log](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Minio.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Minio](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Mongo.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Mongo](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Notification.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Notification](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.API.GetConfigFileName():
|
||||
op, err = compareAndOp[config.API](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.CronTask.GetConfigFileName():
|
||||
op, err = compareAndOp[config.CronTask](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.MsgGateway.GetConfigFileName():
|
||||
op, err = compareAndOp[config.MsgGateway](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.MsgTransfer.GetConfigFileName():
|
||||
op, err = compareAndOp[config.MsgTransfer](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Push.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Push](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Auth.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Auth](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Conversation.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Conversation](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Friend.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Friend](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Group.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Group](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Msg.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Msg](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Third.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Third](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.User.GetConfigFileName():
|
||||
op, err = compareAndOp[config.User](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Redis.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Redis](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Share.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Share](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
case cm.config.Webhooks.GetConfigFileName():
|
||||
op, err = compareAndOp[config.Webhooks](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
|
||||
default:
|
||||
apiresp.GinError(c, errs.ErrArgs.Wrap())
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||
return
|
||||
}
|
||||
if op != nil {
|
||||
ops = append(ops, op)
|
||||
}
|
||||
}
|
||||
if len(ops) > 0 {
|
||||
tx := cm.client.Txn(c)
|
||||
if _, err = tx.Then(datautil.Batch(func(op *clientv3.Op) clientv3.Op { return *op }, ops)...).Commit(); err != nil {
|
||||
apiresp.GinError(c, errs.WrapMsg(err, "save to etcd failed"))
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
apiresp.GinSuccess(c, nil)
|
||||
}
|
||||
|
||||
func compareAndOp[T any](c *gin.Context, old any, req *apistruct.SetConfigReq, cm *ConfigManager) (*clientv3.Op, error) {
|
||||
conf := new(T)
|
||||
err := json.Unmarshal([]byte(req.Data), &conf)
|
||||
if err != nil {
|
||||
return nil, errs.ErrArgs.WithDetail(err.Error()).Wrap()
|
||||
}
|
||||
eq := reflect.DeepEqual(old, conf)
|
||||
if eq {
|
||||
return nil, nil
|
||||
}
|
||||
data, err := json.Marshal(conf)
|
||||
if err != nil {
|
||||
return nil, errs.ErrArgs.WithDetail(err.Error()).Wrap()
|
||||
}
|
||||
op := clientv3.OpPut(etcd.BuildKey(req.ConfigName), string(data))
|
||||
return &op, nil
|
||||
}
|
||||
|
||||
func compareAndSave[T any](c *gin.Context, old any, req *apistruct.SetConfigReq, cm *ConfigManager) error {
|
||||
conf := new(T)
|
||||
err := json.Unmarshal([]byte(req.Data), &conf)
|
||||
@@ -209,13 +312,7 @@ func (cm *ConfigManager) resetConfig(c *gin.Context, checkChange bool, ops ...cl
|
||||
|
||||
changedKeys := make([]string, 0, len(configMap))
|
||||
for k, v := range configMap {
|
||||
err := config.Load(
|
||||
cm.configPath,
|
||||
k,
|
||||
config.EnvPrefixMap[k],
|
||||
cm.runtimeEnv,
|
||||
v.new,
|
||||
)
|
||||
err := config.Load(cm.configPath, k, config.EnvPrefixMap[k], v.new)
|
||||
if err != nil {
|
||||
log.ZError(c, "load config failed", err)
|
||||
continue
|
||||
|
||||
@@ -16,6 +16,7 @@ package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/openimsdk/protocol/conversation"
|
||||
"github.com/openimsdk/tools/a2r"
|
||||
)
|
||||
@@ -48,9 +49,9 @@ func (o *ConversationApi) SetConversations(c *gin.Context) {
|
||||
a2r.Call(c, conversation.ConversationClient.SetConversations, o.Client)
|
||||
}
|
||||
|
||||
func (o *ConversationApi) GetConversationOfflinePushUserIDs(c *gin.Context) {
|
||||
a2r.Call(c, conversation.ConversationClient.GetConversationOfflinePushUserIDs, o.Client)
|
||||
}
|
||||
//func (o *ConversationApi) GetConversationOfflinePushUserIDs(c *gin.Context) {
|
||||
// a2r.Call(c, conversation.ConversationClient.GetConversationOfflinePushUserIDs, o.Client)
|
||||
//}
|
||||
|
||||
func (o *ConversationApi) GetFullOwnerConversationIDs(c *gin.Context) {
|
||||
a2r.Call(c, conversation.ConversationClient.GetFullOwnerConversationIDs, o.Client)
|
||||
@@ -71,3 +72,11 @@ func (o *ConversationApi) GetNotNotifyConversationIDs(c *gin.Context) {
|
||||
func (o *ConversationApi) GetPinnedConversationIDs(c *gin.Context) {
|
||||
a2r.Call(c, conversation.ConversationClient.GetPinnedConversationIDs, o.Client)
|
||||
}
|
||||
|
||||
func (o *ConversationApi) UpdateConversationsByUser(c *gin.Context) {
|
||||
a2r.Call(c, conversation.ConversationClient.UpdateConversationsByUser, o.Client)
|
||||
}
|
||||
|
||||
func (o *ConversationApi) DeleteConversations(c *gin.Context) {
|
||||
a2r.Call(c, conversation.ConversationClient.DeleteConversations, o.Client)
|
||||
}
|
||||
|
||||
@@ -114,3 +114,7 @@ func (o *FriendApi) GetIncrementalBlacks(c *gin.Context) {
|
||||
func (o *FriendApi) GetFullFriendUserIDs(c *gin.Context) {
|
||||
a2r.Call(c, relation.FriendClient.GetFullFriendUserIDs, o.Client)
|
||||
}
|
||||
|
||||
func (o *FriendApi) GetSelfUnhandledApplyCount(c *gin.Context) {
|
||||
a2r.Call(c, relation.FriendClient.GetSelfUnhandledApplyCount, o.Client)
|
||||
}
|
||||
|
||||
@@ -165,3 +165,7 @@ func (o *GroupApi) GetFullGroupMemberUserIDs(c *gin.Context) {
|
||||
func (o *GroupApi) GetFullJoinGroupIDs(c *gin.Context) {
|
||||
a2r.Call(c, group.GroupClient.GetFullJoinGroupIDs, o.Client)
|
||||
}
|
||||
|
||||
func (o *GroupApi) GetGroupApplicationUnhandledCount(c *gin.Context) {
|
||||
a2r.Call(c, group.GroupClient.GetGroupApplicationUnhandledCount, o.Client)
|
||||
}
|
||||
|
||||
+48
-120
@@ -20,157 +20,85 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
conf "github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discovery"
|
||||
disetcd "github.com/openimsdk/open-im-server/v3/pkg/common/discovery/etcd"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
"github.com/openimsdk/tools/discovery/etcd"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mw"
|
||||
"github.com/openimsdk/tools/system/program"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/jsonutil"
|
||||
"github.com/openimsdk/tools/utils/network"
|
||||
"github.com/openimsdk/tools/utils/runtimeenv"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
*conf.AllConfig
|
||||
conf.AllConfig
|
||||
|
||||
RuntimeEnv string
|
||||
ConfigPath string
|
||||
ConfigPath conf.Path
|
||||
Index conf.Index
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, index int, config *Config) error {
|
||||
apiPort, err := datautil.GetElemByIndex(config.API.Api.Ports, index)
|
||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, service grpc.ServiceRegistrar) error {
|
||||
apiPort, err := datautil.GetElemByIndex(config.API.Api.Ports, int(config.Index))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
config.RuntimeEnv = runtimeenv.PrintRuntimeEnvironment()
|
||||
|
||||
client, err := kdisc.NewDiscoveryRegister(&config.Discovery, config.RuntimeEnv, []string{
|
||||
config.Discovery.RpcService.MessageGateway,
|
||||
})
|
||||
if err != nil {
|
||||
return errs.WrapMsg(err, "failed to register discovery service")
|
||||
}
|
||||
client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin")))
|
||||
|
||||
var (
|
||||
netDone = make(chan struct{}, 1)
|
||||
netErr error
|
||||
prometheusPort int
|
||||
)
|
||||
|
||||
registerIP, err := network.GetRpcRegisterIP("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
getAutoPort := func() (net.Listener, int, error) {
|
||||
registerAddr := net.JoinHostPort(registerIP, "0")
|
||||
listener, err := net.Listen("tcp", registerAddr)
|
||||
if err != nil {
|
||||
return nil, 0, errs.WrapMsg(err, "listen err", "registerAddr", registerAddr)
|
||||
}
|
||||
_, portStr, _ := net.SplitHostPort(listener.Addr().String())
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
return listener, port, nil
|
||||
}
|
||||
|
||||
if config.API.Prometheus.AutoSetPorts && config.Discovery.Enable != conf.ETCD {
|
||||
return errs.New("only etcd support autoSetPorts", "RegisterName", "api").Wrap()
|
||||
}
|
||||
|
||||
router, err := newGinRouter(ctx, client, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.API.Prometheus.Enable {
|
||||
var (
|
||||
listener net.Listener
|
||||
)
|
||||
|
||||
if config.API.Prometheus.AutoSetPorts {
|
||||
listener, prometheusPort, err = getAutoPort()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
etcdClient := client.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
|
||||
|
||||
_, err = etcdClient.Put(ctx, prommetrics.BuildDiscoveryKey(prommetrics.APIKeyName), jsonutil.StructToJsonString(prommetrics.BuildDefaultTarget(registerIP, prometheusPort)))
|
||||
if err != nil {
|
||||
return errs.WrapMsg(err, "etcd put err")
|
||||
}
|
||||
} else {
|
||||
prometheusPort, err = datautil.GetElemByIndex(config.API.Prometheus.Ports, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listener, err = net.Listen("tcp", fmt.Sprintf(":%d", prometheusPort))
|
||||
if err != nil {
|
||||
return errs.WrapMsg(err, "listen err", "addr", fmt.Sprintf(":%d", prometheusPort))
|
||||
}
|
||||
apiCtx, apiCancel := context.WithCancelCause(context.Background())
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
httpServer := &http.Server{
|
||||
Handler: router,
|
||||
Addr: net.JoinHostPort(network.GetListenIP(config.API.Api.ListenIP), strconv.Itoa(apiPort)),
|
||||
}
|
||||
|
||||
go func() {
|
||||
if err := prommetrics.ApiInit(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
netErr = errs.WrapMsg(err, fmt.Sprintf("api prometheus start err: %d", prometheusPort))
|
||||
netDone <- struct{}{}
|
||||
defer close(done)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
apiCancel(fmt.Errorf("recv ctx %w", context.Cause(ctx)))
|
||||
case <-apiCtx.Done():
|
||||
}
|
||||
log.ZDebug(ctx, "api server is shutting down")
|
||||
if err := httpServer.Shutdown(context.Background()); err != nil {
|
||||
log.ZWarn(ctx, "api server shutdown err", err)
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
address := net.JoinHostPort(network.GetListenIP(config.API.Api.ListenIP), strconv.Itoa(apiPort))
|
||||
|
||||
server := http.Server{Addr: address, Handler: router}
|
||||
log.CInfo(ctx, "API server is initializing", "runtimeEnv", config.RuntimeEnv, "address", address, "apiPort", apiPort, "prometheusPort", prometheusPort)
|
||||
go func() {
|
||||
err = server.ListenAndServe()
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
netErr = errs.WrapMsg(err, fmt.Sprintf("api start err: %s", server.Addr))
|
||||
netDone <- struct{}{}
|
||||
log.CInfo(ctx, "api server is init", "runtimeEnv", runtimeenv.RuntimeEnvironment(), "address", httpServer.Addr, "apiPort", apiPort)
|
||||
err := httpServer.ListenAndServe()
|
||||
if err == nil {
|
||||
err = errors.New("api done")
|
||||
}
|
||||
apiCancel(err)
|
||||
}()
|
||||
|
||||
if config.Discovery.Enable == conf.ETCD {
|
||||
cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), config.GetConfigNames())
|
||||
cm.Watch(ctx)
|
||||
}
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGTERM)
|
||||
|
||||
shutdown := func() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
defer cancel()
|
||||
err := server.Shutdown(ctx)
|
||||
if err != nil {
|
||||
return errs.WrapMsg(err, "shutdown err")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
disetcd.RegisterShutDown(shutdown)
|
||||
//if config.Discovery.Enable == conf.ETCD {
|
||||
// cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), config.GetConfigNames())
|
||||
// cm.Watch(ctx)
|
||||
//}
|
||||
//sigs := make(chan os.Signal, 1)
|
||||
//signal.Notify(sigs, syscall.SIGTERM)
|
||||
//select {
|
||||
//case val := <-sigs:
|
||||
// log.ZDebug(ctx, "recv exit", "signal", val.String())
|
||||
// cancel(fmt.Errorf("signal %s", val.String()))
|
||||
//case <-ctx.Done():
|
||||
//}
|
||||
<-apiCtx.Done()
|
||||
exitCause := context.Cause(apiCtx)
|
||||
log.ZWarn(ctx, "api server exit", exitCause)
|
||||
timer := time.NewTimer(time.Second * 15)
|
||||
defer timer.Stop()
|
||||
select {
|
||||
case <-sigs:
|
||||
program.SIGTERMExit()
|
||||
if err := shutdown(); err != nil {
|
||||
return err
|
||||
}
|
||||
case <-netDone:
|
||||
close(netDone)
|
||||
return netErr
|
||||
case <-timer.C:
|
||||
log.ZWarn(ctx, "api server graceful stop timeout", nil)
|
||||
case <-done:
|
||||
log.ZDebug(ctx, "api server graceful stop done")
|
||||
}
|
||||
return nil
|
||||
return exitCause
|
||||
}
|
||||
|
||||
+59
-21
@@ -2,10 +2,14 @@ package jssdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"sort"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/log"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/openimsdk/protocol/conversation"
|
||||
"github.com/openimsdk/protocol/jssdk"
|
||||
"github.com/openimsdk/protocol/msg"
|
||||
@@ -109,10 +113,7 @@ func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActive
|
||||
if len(conversationIDs) == 0 {
|
||||
return &jssdk.GetActiveConversationsResp{}, nil
|
||||
}
|
||||
readSeq, err := x.msgClient.GetHasReadSeqs(ctx, conversationIDs, req.OwnerUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
activeConversation, err := x.msgClient.GetActiveConversation(ctx, conversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -120,6 +121,10 @@ func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActive
|
||||
if len(activeConversation) == 0 {
|
||||
return &jssdk.GetActiveConversationsResp{}, nil
|
||||
}
|
||||
readSeq, err := x.msgClient.GetHasReadSeqs(ctx, conversationIDs, req.OwnerUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sortConversations := sortActiveConversations{
|
||||
Conversation: activeConversation,
|
||||
}
|
||||
@@ -147,6 +152,7 @@ func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActive
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
x.checkMessagesAndGetLastMessage(ctx, req.OwnerUserID, msgs)
|
||||
conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string {
|
||||
return c.ConversationID
|
||||
})
|
||||
@@ -156,16 +162,15 @@ func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActive
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var lastMsg *sdkws.MsgData
|
||||
if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
|
||||
lastMsg = msgList.Msgs[0]
|
||||
resp = append(resp, &jssdk.ConversationMsg{
|
||||
Conversation: conv,
|
||||
LastMsg: msgList.Msgs[0],
|
||||
MaxSeq: c.MaxSeq,
|
||||
ReadSeq: readSeq[c.ConversationID],
|
||||
})
|
||||
}
|
||||
resp = append(resp, &jssdk.ConversationMsg{
|
||||
Conversation: conv,
|
||||
LastMsg: lastMsg,
|
||||
MaxSeq: c.MaxSeq,
|
||||
ReadSeq: readSeq[c.ConversationID],
|
||||
})
|
||||
|
||||
}
|
||||
if err := x.fillConversations(ctx, resp); err != nil {
|
||||
return nil, err
|
||||
@@ -219,18 +224,18 @@ func (x *JSSdk) getConversations(ctx context.Context, req *jssdk.GetConversation
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
x.checkMessagesAndGetLastMessage(ctx, req.OwnerUserID, msgs)
|
||||
resp := make([]*jssdk.ConversationMsg, 0, len(conversations))
|
||||
for _, c := range conversations {
|
||||
var lastMsg *sdkws.MsgData
|
||||
if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
|
||||
lastMsg = msgList.Msgs[0]
|
||||
resp = append(resp, &jssdk.ConversationMsg{
|
||||
Conversation: c,
|
||||
LastMsg: msgList.Msgs[0],
|
||||
MaxSeq: maxSeqs[c.ConversationID],
|
||||
ReadSeq: readSeqs[c.ConversationID],
|
||||
})
|
||||
}
|
||||
resp = append(resp, &jssdk.ConversationMsg{
|
||||
Conversation: c,
|
||||
LastMsg: lastMsg,
|
||||
MaxSeq: maxSeqs[c.ConversationID],
|
||||
ReadSeq: readSeqs[c.ConversationID],
|
||||
})
|
||||
|
||||
}
|
||||
if err := x.fillConversations(ctx, resp); err != nil {
|
||||
return nil, err
|
||||
@@ -247,3 +252,36 @@ func (x *JSSdk) getConversations(ctx context.Context, req *jssdk.GetConversation
|
||||
UnreadCount: unreadCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// This function checks whether the latest MaxSeq message is valid.
|
||||
// If not, it needs to fetch a valid message again.
|
||||
func (x *JSSdk) checkMessagesAndGetLastMessage(ctx context.Context, userID string, messages map[string]*sdkws.PullMsgs) {
|
||||
var conversationIDs []string
|
||||
|
||||
for conversationID, message := range messages {
|
||||
allInValid := true
|
||||
for _, data := range message.Msgs {
|
||||
if data.Status < constant.MsgStatusHasDeleted {
|
||||
allInValid = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if allInValid {
|
||||
conversationIDs = append(conversationIDs, conversationID)
|
||||
}
|
||||
}
|
||||
if len(conversationIDs) > 0 {
|
||||
resp, err := x.msgClient.GetLastMessage(ctx, &msg.GetLastMessageReq{
|
||||
UserID: userID,
|
||||
ConversationIDs: conversationIDs,
|
||||
})
|
||||
if err != nil {
|
||||
log.ZError(ctx, "fetchLatestValidMessages", err, "conversationIDs", conversationIDs)
|
||||
return
|
||||
}
|
||||
for conversationID, message := range resp.Msgs {
|
||||
messages[conversationID] = &sdkws.PullMsgs{Msgs: []*sdkws.MsgData{message}}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+216
-42
@@ -15,12 +15,19 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"google.golang.org/protobuf/reflect/protoreflect"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/apistruct"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/protocol/msg"
|
||||
@@ -36,6 +43,39 @@ import (
|
||||
"github.com/openimsdk/tools/utils/timeutil"
|
||||
)
|
||||
|
||||
var (
|
||||
msgDataDescriptor []protoreflect.FieldDescriptor
|
||||
msgDataDescriptorOnce sync.Once
|
||||
)
|
||||
|
||||
func getMsgDataDescriptor() []protoreflect.FieldDescriptor {
|
||||
msgDataDescriptorOnce.Do(func() {
|
||||
skip := make(map[string]struct{})
|
||||
respFields := new(msg.SendMsgResp).ProtoReflect().Descriptor().Fields()
|
||||
for i := 0; i < respFields.Len(); i++ {
|
||||
field := respFields.Get(i)
|
||||
if !field.HasJSONName() {
|
||||
continue
|
||||
}
|
||||
skip[field.JSONName()] = struct{}{}
|
||||
}
|
||||
fields := new(sdkws.MsgData).ProtoReflect().Descriptor().Fields()
|
||||
num := fields.Len()
|
||||
msgDataDescriptor = make([]protoreflect.FieldDescriptor, 0, num)
|
||||
for i := 0; i < num; i++ {
|
||||
field := fields.Get(i)
|
||||
if !field.HasJSONName() {
|
||||
continue
|
||||
}
|
||||
if _, ok := skip[field.JSONName()]; ok {
|
||||
continue
|
||||
}
|
||||
msgDataDescriptor = append(msgDataDescriptor, fields.Get(i))
|
||||
}
|
||||
})
|
||||
return msgDataDescriptor
|
||||
}
|
||||
|
||||
type MessageApi struct {
|
||||
Client msg.MsgClient
|
||||
userClient *rpcli.UserClient
|
||||
@@ -54,7 +94,22 @@ func (*MessageApi) SetOptions(options map[string]bool, value bool) {
|
||||
datautil.SetSwitchFromOptions(options, constant.IsConversationUpdate, value)
|
||||
}
|
||||
|
||||
func (m *MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg) *msg.SendMsgReq {
|
||||
func (m *MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg, data any) *msg.SendMsgReq {
|
||||
msgData := &sdkws.MsgData{
|
||||
SendID: params.SendID,
|
||||
GroupID: params.GroupID,
|
||||
ClientMsgID: idutil.GetMsgIDByMD5(params.SendID),
|
||||
SenderPlatformID: params.SenderPlatformID,
|
||||
SenderNickname: params.SenderNickname,
|
||||
SenderFaceURL: params.SenderFaceURL,
|
||||
SessionType: params.SessionType,
|
||||
MsgFrom: constant.SysMsgType,
|
||||
ContentType: params.ContentType,
|
||||
CreateTime: timeutil.GetCurrentTimestampByMill(),
|
||||
SendTime: params.SendTime,
|
||||
OfflinePushInfo: params.OfflinePushInfo,
|
||||
Ex: params.Ex,
|
||||
}
|
||||
var newContent string
|
||||
options := make(map[string]bool, 5)
|
||||
switch params.ContentType {
|
||||
@@ -64,6 +119,11 @@ func (m *MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg
|
||||
newContent = jsonutil.StructToJsonString(¬ification)
|
||||
case constant.Text:
|
||||
fallthrough
|
||||
case constant.AtText:
|
||||
if atElem, ok := data.(*apistruct.AtElem); ok {
|
||||
msgData.AtUserIDList = atElem.AtUserList
|
||||
}
|
||||
fallthrough
|
||||
case constant.Picture:
|
||||
fallthrough
|
||||
case constant.Custom:
|
||||
@@ -83,24 +143,10 @@ func (m *MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg
|
||||
if params.NotOfflinePush {
|
||||
datautil.SetSwitchFromOptions(options, constant.IsOfflinePush, false)
|
||||
}
|
||||
msgData.Content = []byte(newContent)
|
||||
msgData.Options = options
|
||||
pbData := msg.SendMsgReq{
|
||||
MsgData: &sdkws.MsgData{
|
||||
SendID: params.SendID,
|
||||
GroupID: params.GroupID,
|
||||
ClientMsgID: idutil.GetMsgIDByMD5(params.SendID),
|
||||
SenderPlatformID: params.SenderPlatformID,
|
||||
SenderNickname: params.SenderNickname,
|
||||
SenderFaceURL: params.SenderFaceURL,
|
||||
SessionType: params.SessionType,
|
||||
MsgFrom: constant.SysMsgType,
|
||||
ContentType: params.ContentType,
|
||||
Content: []byte(newContent),
|
||||
CreateTime: timeutil.GetCurrentTimestampByMill(),
|
||||
SendTime: params.SendTime,
|
||||
Options: options,
|
||||
OfflinePushInfo: params.OfflinePushInfo,
|
||||
Ex: params.Ex,
|
||||
},
|
||||
MsgData: msgData,
|
||||
}
|
||||
return &pbData
|
||||
}
|
||||
@@ -158,21 +204,25 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM
|
||||
log.ZDebug(c, "getSendMsgReq", "req", req.Content)
|
||||
switch req.ContentType {
|
||||
case constant.Text:
|
||||
data = apistruct.TextElem{}
|
||||
data = &apistruct.TextElem{}
|
||||
case constant.Picture:
|
||||
data = apistruct.PictureElem{}
|
||||
data = &apistruct.PictureElem{}
|
||||
case constant.Voice:
|
||||
data = apistruct.SoundElem{}
|
||||
data = &apistruct.SoundElem{}
|
||||
case constant.Video:
|
||||
data = apistruct.VideoElem{}
|
||||
data = &apistruct.VideoElem{}
|
||||
case constant.File:
|
||||
data = apistruct.FileElem{}
|
||||
data = &apistruct.FileElem{}
|
||||
case constant.AtText:
|
||||
data = apistruct.AtElem{}
|
||||
data = &apistruct.AtElem{}
|
||||
case constant.Custom:
|
||||
data = apistruct.CustomElem{}
|
||||
data = &apistruct.CustomElem{}
|
||||
case constant.MarkdownText:
|
||||
data = &apistruct.MarkdownTextElem{}
|
||||
case constant.Quote:
|
||||
data = &apistruct.QuoteElem{}
|
||||
case constant.OANotification:
|
||||
data = apistruct.OANotificationElem{}
|
||||
data = &apistruct.OANotificationElem{}
|
||||
req.SessionType = constant.NotificationChatType
|
||||
if err = m.userClient.GetNotificationByID(c, req.SendID); err != nil {
|
||||
return nil, err
|
||||
@@ -180,14 +230,50 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM
|
||||
default:
|
||||
return nil, errs.WrapMsg(errs.ErrArgs, "unsupported content type", "contentType", req.ContentType)
|
||||
}
|
||||
if err := mapstructure.WeakDecode(req.Content, &data); err != nil {
|
||||
if err := mapstructure.WeakDecode(req.Content, data); err != nil {
|
||||
return nil, errs.WrapMsg(err, "failed to decode message content")
|
||||
}
|
||||
log.ZDebug(c, "getSendMsgReq", "decodedContent", data)
|
||||
if err := m.validate.Struct(data); err != nil {
|
||||
return nil, errs.WrapMsg(err, "validation error")
|
||||
}
|
||||
return m.newUserSendMsgReq(c, &req), nil
|
||||
return m.newUserSendMsgReq(c, &req, data), nil
|
||||
}
|
||||
|
||||
func (m *MessageApi) getModifyFields(req, respModify *sdkws.MsgData) map[string]any {
|
||||
if req == nil || respModify == nil {
|
||||
return nil
|
||||
}
|
||||
fields := make(map[string]any)
|
||||
reqProtoReflect := req.ProtoReflect()
|
||||
respProtoReflect := respModify.ProtoReflect()
|
||||
for _, descriptor := range getMsgDataDescriptor() {
|
||||
reqValue := reqProtoReflect.Get(descriptor)
|
||||
respValue := respProtoReflect.Get(descriptor)
|
||||
if !reqValue.Equal(respValue) {
|
||||
val := respValue.Interface()
|
||||
name := descriptor.JSONName()
|
||||
if name == "content" {
|
||||
if bs, ok := val.([]byte); ok {
|
||||
val = string(bs)
|
||||
}
|
||||
}
|
||||
fields[name] = val
|
||||
}
|
||||
}
|
||||
if len(fields) == 0 {
|
||||
fields = nil
|
||||
}
|
||||
return fields
|
||||
}
|
||||
|
||||
func (m *MessageApi) ginRespSendMsg(c *gin.Context, req *msg.SendMsgReq, resp *msg.SendMsgResp) {
|
||||
res := m.getModifyFields(req.MsgData, resp.Modify)
|
||||
resp.Modify = nil
|
||||
apiresp.GinSuccess(c, &apistruct.SendMsgResp{
|
||||
SendMsgResp: resp,
|
||||
Modify: res,
|
||||
})
|
||||
}
|
||||
|
||||
// SendMessage handles the sending of a message. It's an HTTP handler function to be used with Gin framework.
|
||||
@@ -203,7 +289,7 @@ func (m *MessageApi) SendMessage(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Check if the user has the app manager role.
|
||||
if !authverify.IsAppManagerUid(c, m.imAdminUserID) {
|
||||
if !authverify.IsAdmin(c) {
|
||||
// Respond with a permission error if the user is not an app manager.
|
||||
apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message"))
|
||||
return
|
||||
@@ -243,7 +329,7 @@ func (m *MessageApi) SendMessage(c *gin.Context) {
|
||||
}
|
||||
|
||||
// Respond with a success message and the response payload.
|
||||
apiresp.GinSuccess(c, respPb)
|
||||
m.ginRespSendMsg(c, sendMsgReq, respPb)
|
||||
}
|
||||
|
||||
func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
|
||||
@@ -277,7 +363,7 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
|
||||
if req.ReliabilityLevel == nil {
|
||||
req.ReliabilityLevel = datautil.ToPtr(1)
|
||||
}
|
||||
if !authverify.IsAppManagerUid(c, m.imAdminUserID) {
|
||||
if !authverify.IsAdmin(c) {
|
||||
apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message"))
|
||||
return
|
||||
}
|
||||
@@ -301,7 +387,7 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
|
||||
IsSendMsg: req.SendMsg,
|
||||
ReliabilityLevel: *req.ReliabilityLevel,
|
||||
UnreadCount: false,
|
||||
}),
|
||||
}, nil),
|
||||
},
|
||||
}
|
||||
respPb, err := m.Client.SendMsg(c, &sendMsgReq)
|
||||
@@ -309,7 +395,7 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
apiresp.GinSuccess(c, respPb)
|
||||
m.ginRespSendMsg(c, &sendMsgReq, respPb)
|
||||
}
|
||||
|
||||
func (m *MessageApi) BatchSendMsg(c *gin.Context) {
|
||||
@@ -321,7 +407,7 @@ func (m *MessageApi) BatchSendMsg(c *gin.Context) {
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||
return
|
||||
}
|
||||
if err := authverify.CheckAdmin(c, m.imAdminUserID); err != nil {
|
||||
if err := authverify.CheckAdmin(c); err != nil {
|
||||
apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message"))
|
||||
return
|
||||
}
|
||||
@@ -363,11 +449,107 @@ func (m *MessageApi) BatchSendMsg(c *gin.Context) {
|
||||
ClientMsgID: rpcResp.ClientMsgID,
|
||||
SendTime: rpcResp.SendTime,
|
||||
RecvID: recvID,
|
||||
Modify: m.getModifyFields(sendMsgReq.MsgData, rpcResp.Modify),
|
||||
})
|
||||
}
|
||||
apiresp.GinSuccess(c, resp)
|
||||
}
|
||||
|
||||
func (m *MessageApi) SendSimpleMessage(c *gin.Context) {
|
||||
encodedKey, ok := c.GetQuery(webhook.Key)
|
||||
if !ok {
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail("missing key in query").Wrap())
|
||||
return
|
||||
}
|
||||
|
||||
decodedData, err := base64.StdEncoding.DecodeString(encodedKey)
|
||||
if err != nil {
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||
return
|
||||
}
|
||||
var (
|
||||
req apistruct.SendSingleMsgReq
|
||||
keyMsgData apistruct.KeyMsgData
|
||||
|
||||
sendID string
|
||||
sessionType int32
|
||||
recvID string
|
||||
)
|
||||
if err = c.BindJSON(&req); err != nil {
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||
return
|
||||
}
|
||||
err = json.Unmarshal(decodedData, &keyMsgData)
|
||||
if err != nil {
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||
return
|
||||
}
|
||||
if keyMsgData.GroupID != "" {
|
||||
sessionType = constant.ReadGroupChatType
|
||||
sendID = req.SendID
|
||||
} else {
|
||||
sessionType = constant.SingleChatType
|
||||
sendID = keyMsgData.RecvID
|
||||
recvID = keyMsgData.SendID
|
||||
}
|
||||
// check param
|
||||
if keyMsgData.SendID == "" {
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail("missing recvID or GroupID").Wrap())
|
||||
return
|
||||
}
|
||||
if sendID == "" {
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail("missing sendID").Wrap())
|
||||
return
|
||||
}
|
||||
|
||||
content, err := jsonutil.JsonMarshal(apistruct.MarkdownTextElem{Content: req.Content})
|
||||
if err != nil {
|
||||
apiresp.GinError(c, errs.Wrap(err))
|
||||
return
|
||||
}
|
||||
msgData := &sdkws.MsgData{
|
||||
SendID: sendID,
|
||||
RecvID: recvID,
|
||||
GroupID: keyMsgData.GroupID,
|
||||
ClientMsgID: idutil.GetMsgIDByMD5(sendID),
|
||||
SenderPlatformID: constant.AdminPlatformID,
|
||||
SessionType: sessionType,
|
||||
MsgFrom: constant.UserMsgType,
|
||||
ContentType: constant.MarkdownText,
|
||||
Content: content,
|
||||
OfflinePushInfo: req.OfflinePushInfo,
|
||||
Ex: req.Ex,
|
||||
}
|
||||
|
||||
sendReq := &msg.SendSimpleMsgReq{
|
||||
MsgData: msgData,
|
||||
}
|
||||
|
||||
respPb, err := m.Client.SendSimpleMsg(c, sendReq)
|
||||
if err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
var status = constant.MsgSendSuccessed
|
||||
|
||||
_, err = m.Client.SetSendMsgStatus(c, &msg.SetSendMsgStatusReq{
|
||||
Status: int32(status),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
m.ginRespSendMsg(c, &msg.SendMsgReq{MsgData: sendReq.MsgData}, &msg.SendMsgResp{
|
||||
ServerMsgID: respPb.ServerMsgID,
|
||||
ClientMsgID: respPb.ClientMsgID,
|
||||
SendTime: respPb.SendTime,
|
||||
Modify: respPb.Modify,
|
||||
})
|
||||
}
|
||||
|
||||
func (m *MessageApi) CheckMsgIsSendSuccess(c *gin.Context) {
|
||||
a2r.Call(c, msg.MsgClient.GetSendMsgStatus, m.Client)
|
||||
}
|
||||
@@ -391,11 +573,3 @@ func (m *MessageApi) SearchMsg(c *gin.Context) {
|
||||
func (m *MessageApi) GetServerTime(c *gin.Context) {
|
||||
a2r.Call(c, msg.MsgClient.GetServerTime, m.Client)
|
||||
}
|
||||
|
||||
func (m *MessageApi) GetStreamMsg(c *gin.Context) {
|
||||
a2r.Call(c, msg.MsgClient.GetServerTime, m.Client)
|
||||
}
|
||||
|
||||
func (m *MessageApi) AppendStreamMsg(c *gin.Context) {
|
||||
a2r.Call(c, msg.MsgClient.GetServerTime, m.Client)
|
||||
}
|
||||
|
||||
@@ -2,71 +2,56 @@ package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
conf "github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
"github.com/openimsdk/tools/apiresp"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/discovery/etcd"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
type PrometheusDiscoveryApi struct {
|
||||
config *Config
|
||||
client *clientv3.Client
|
||||
kv discovery.KeyValue
|
||||
}
|
||||
|
||||
func NewPrometheusDiscoveryApi(config *Config, client discovery.SvcDiscoveryRegistry) *PrometheusDiscoveryApi {
|
||||
api := &PrometheusDiscoveryApi{
|
||||
config: config,
|
||||
}
|
||||
if config.Discovery.Enable == conf.ETCD {
|
||||
api.client = client.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
|
||||
kv: client,
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
func (p *PrometheusDiscoveryApi) Enable(c *gin.Context) {
|
||||
if p.config.Discovery.Enable != conf.ETCD {
|
||||
c.JSON(http.StatusOK, []struct{}{})
|
||||
c.Abort()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *PrometheusDiscoveryApi) discovery(c *gin.Context, key string) {
|
||||
eResp, err := p.client.Get(c, prommetrics.BuildDiscoveryKey(key))
|
||||
value, err := p.kv.GetKeyWithPrefix(c, prommetrics.BuildDiscoveryKeyPrefix(key))
|
||||
if err != nil {
|
||||
// Log and respond with an error if preparation fails.
|
||||
apiresp.GinError(c, errs.WrapMsg(err, "etcd get err"))
|
||||
if errors.Is(err, discovery.ErrNotSupported) {
|
||||
c.JSON(http.StatusOK, []struct{}{})
|
||||
return
|
||||
}
|
||||
apiresp.GinError(c, errs.WrapMsg(err, "get key value"))
|
||||
return
|
||||
}
|
||||
if len(eResp.Kvs) == 0 {
|
||||
c.JSON(http.StatusOK, []*prommetrics.Target{})
|
||||
if len(value) == 0 {
|
||||
c.JSON(http.StatusOK, []*prommetrics.RespTarget{})
|
||||
return
|
||||
}
|
||||
var resp prommetrics.RespTarget
|
||||
for i := range value {
|
||||
var tmp prommetrics.Target
|
||||
if err = json.Unmarshal(value[i], &tmp); err != nil {
|
||||
apiresp.GinError(c, errs.WrapMsg(err, "json unmarshal err"))
|
||||
return
|
||||
}
|
||||
|
||||
resp.Targets = append(resp.Targets, tmp.Target)
|
||||
resp.Labels = tmp.Labels // default label is fixed. See prommetrics.BuildDefaultTarget
|
||||
}
|
||||
|
||||
var (
|
||||
resp = &prommetrics.RespTarget{
|
||||
Targets: make([]string, 0, len(eResp.Kvs)),
|
||||
}
|
||||
)
|
||||
|
||||
for i := range eResp.Kvs {
|
||||
var target prommetrics.Target
|
||||
err = json.Unmarshal(eResp.Kvs[i].Value, &target)
|
||||
if err != nil {
|
||||
log.ZError(c, "prometheus unmarshal err", errs.Wrap(err))
|
||||
}
|
||||
resp.Targets = append(resp.Targets, target.Target)
|
||||
if resp.Labels == nil {
|
||||
resp.Labels = target.Labels
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(200, []*prommetrics.RespTarget{resp})
|
||||
c.JSON(http.StatusOK, []*prommetrics.RespTarget{&resp})
|
||||
}
|
||||
|
||||
func (p *PrometheusDiscoveryApi) Api(c *gin.Context) {
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/openimsdk/tools/apiresp"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/stability/ratelimit"
|
||||
"github.com/openimsdk/tools/stability/ratelimit/bbr"
|
||||
)
|
||||
|
||||
type RateLimiter struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
Window time.Duration `yaml:"window"` // time duration per window
|
||||
Bucket int `yaml:"bucket"` // bucket number for each window
|
||||
CPUThreshold int64 `yaml:"cpuThreshold"` // CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||
}
|
||||
|
||||
func RateLimitMiddleware(config *RateLimiter) gin.HandlerFunc {
|
||||
if !config.Enable {
|
||||
return func(c *gin.Context) {
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
limiter := bbr.NewBBRLimiter(
|
||||
bbr.WithWindow(config.Window),
|
||||
bbr.WithBucket(config.Bucket),
|
||||
bbr.WithCPUThreshold(config.CPUThreshold),
|
||||
)
|
||||
|
||||
return func(c *gin.Context) {
|
||||
status := limiter.Stat()
|
||||
|
||||
c.Header("X-BBR-CPU", strconv.FormatInt(status.CPU, 10))
|
||||
c.Header("X-BBR-MinRT", strconv.FormatInt(status.MinRt, 10))
|
||||
c.Header("X-BBR-MaxPass", strconv.FormatInt(status.MaxPass, 10))
|
||||
c.Header("X-BBR-MaxInFlight", strconv.FormatInt(status.MaxInFlight, 10))
|
||||
c.Header("X-BBR-InFlight", strconv.FormatInt(status.InFlight, 10))
|
||||
|
||||
done, err := limiter.Allow()
|
||||
if err != nil {
|
||||
|
||||
c.Header("X-RateLimit-Policy", "BBR")
|
||||
c.Header("Retry-After", calculateBBRRetryAfter(status))
|
||||
c.Header("X-RateLimit-Limit", strconv.FormatInt(status.MaxInFlight, 10))
|
||||
c.Header("X-RateLimit-Remaining", "0") // There is no concept of remaining quota in BBR.
|
||||
|
||||
fmt.Println("rate limited:", err, "path:", c.Request.URL.Path)
|
||||
log.ZWarn(c, "rate limited", err, "path", c.Request.URL.Path)
|
||||
c.AbortWithStatus(http.StatusTooManyRequests)
|
||||
apiresp.GinError(c, errs.NewCodeError(http.StatusTooManyRequests, "too many requests, please try again later"))
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
done(ratelimit.DoneInfo{})
|
||||
}
|
||||
}
|
||||
|
||||
func calculateBBRRetryAfter(status bbr.Stat) string {
|
||||
loadRatio := float64(status.CPU) / float64(status.CPU)
|
||||
|
||||
if loadRatio < 0.8 {
|
||||
return "1"
|
||||
}
|
||||
if loadRatio < 0.95 {
|
||||
return "2"
|
||||
}
|
||||
|
||||
backoff := 1 + int64(math.Pow(loadRatio-0.95, 2)*50)
|
||||
if backoff > 5 {
|
||||
backoff = 5
|
||||
}
|
||||
return strconv.FormatInt(backoff, 10)
|
||||
}
|
||||
+41
-9
@@ -10,6 +10,7 @@ import (
|
||||
"github.com/gin-gonic/gin/binding"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/api/jssdk"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
|
||||
@@ -27,6 +28,7 @@ import (
|
||||
"github.com/openimsdk/tools/discovery/etcd"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mw"
|
||||
"github.com/openimsdk/tools/mw/api"
|
||||
clientv3 "go.etcd.io/etcd/client/v3"
|
||||
)
|
||||
|
||||
@@ -95,8 +97,25 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
||||
case BestSpeed:
|
||||
r.Use(gzip.Gzip(gzip.BestSpeed))
|
||||
}
|
||||
r.Use(prommetricsGin(), gin.RecoveryWithWriter(gin.DefaultErrorWriter, mw.GinPanicErr), mw.CorsHandler(),
|
||||
mw.GinParseOperationID(), GinParseToken(rpcli.NewAuthClient(authConn)))
|
||||
|
||||
// Use rate limiter middleware
|
||||
if cfg.API.RateLimiter.Enable {
|
||||
rl := &RateLimiter{
|
||||
Enable: cfg.API.RateLimiter.Enable,
|
||||
Window: cfg.API.RateLimiter.Window,
|
||||
Bucket: cfg.API.RateLimiter.Bucket,
|
||||
CPUThreshold: cfg.API.RateLimiter.CPUThreshold,
|
||||
}
|
||||
r.Use(RateLimitMiddleware(rl))
|
||||
}
|
||||
|
||||
if config.Standalone() {
|
||||
r.Use(func(c *gin.Context) {
|
||||
c.Set(authverify.CtxAdminUserIDsKey, cfg.Share.IMAdminUser.UserIDs)
|
||||
})
|
||||
}
|
||||
r.Use(api.GinLogger(), prommetricsGin(), gin.RecoveryWithWriter(gin.DefaultErrorWriter, mw.GinPanicErr), mw.CorsHandler(),
|
||||
mw.GinParseOperationID(), GinParseToken(rpcli.NewAuthClient(authConn)), setGinIsAdmin(cfg.Share.IMAdminUser.UserIDs))
|
||||
|
||||
u := NewUserApi(user.NewUserClient(userConn), client, cfg.Discovery.RpcService)
|
||||
{
|
||||
@@ -124,6 +143,11 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
||||
userRouterGroup.POST("/add_notification_account", u.AddNotificationAccount)
|
||||
userRouterGroup.POST("/update_notification_account", u.UpdateNotificationAccountInfo)
|
||||
userRouterGroup.POST("/search_notification_account", u.SearchNotificationAccount)
|
||||
|
||||
userRouterGroup.POST("/get_user_client_config", u.GetUserClientConfig)
|
||||
userRouterGroup.POST("/set_user_client_config", u.SetUserClientConfig)
|
||||
userRouterGroup.POST("/del_user_client_config", u.DelUserClientConfig)
|
||||
userRouterGroup.POST("/page_user_client_config", u.PageUserClientConfig)
|
||||
}
|
||||
// friend routing group
|
||||
{
|
||||
@@ -150,6 +174,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
||||
friendRouterGroup.POST("/update_friends", f.UpdateFriends)
|
||||
friendRouterGroup.POST("/get_incremental_friends", f.GetIncrementalFriends)
|
||||
friendRouterGroup.POST("/get_full_friend_user_ids", f.GetFullFriendUserIDs)
|
||||
friendRouterGroup.POST("/get_self_unhandled_apply_count", f.GetSelfUnhandledApplyCount)
|
||||
}
|
||||
|
||||
g := NewGroupApi(group.NewGroupClient(groupConn))
|
||||
@@ -186,6 +211,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
||||
groupRouterGroup.POST("/get_incremental_group_members_batch", g.GetIncrementalGroupMemberBatch)
|
||||
groupRouterGroup.POST("/get_full_group_member_user_ids", g.GetFullGroupMemberUserIDs)
|
||||
groupRouterGroup.POST("/get_full_join_group_ids", g.GetFullJoinGroupIDs)
|
||||
groupRouterGroup.POST("/get_group_application_unhandled_count", g.GetGroupApplicationUnhandledCount)
|
||||
}
|
||||
// certificate
|
||||
{
|
||||
@@ -223,7 +249,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
||||
objectGroup.GET("/*name", t.ObjectRedirect)
|
||||
}
|
||||
// Message
|
||||
m := NewMessageApi(msg.NewMsgClient(msgConn), rpcli.NewUserClient(userConn), cfg.Share.IMAdminUserID)
|
||||
m := NewMessageApi(msg.NewMsgClient(msgConn), rpcli.NewUserClient(userConn), cfg.Share.IMAdminUser.UserIDs)
|
||||
{
|
||||
msgGroup := r.Group("/msg")
|
||||
msgGroup.POST("/newest_seq", m.GetSeq)
|
||||
@@ -244,10 +270,9 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
||||
msgGroup.POST("/delete_msg_physical", m.DeleteMsgPhysical)
|
||||
|
||||
msgGroup.POST("/batch_send_msg", m.BatchSendMsg)
|
||||
msgGroup.POST("/send_simple_msg", m.SendSimpleMessage)
|
||||
msgGroup.POST("/check_msg_is_send_success", m.CheckMsgIsSendSuccess)
|
||||
msgGroup.POST("/get_server_time", m.GetServerTime)
|
||||
msgGroup.POST("/get_stream_msg", m.GetStreamMsg)
|
||||
msgGroup.POST("/append_stream_msg", m.AppendStreamMsg)
|
||||
}
|
||||
// Conversation
|
||||
{
|
||||
@@ -258,12 +283,14 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
||||
conversationGroup.POST("/get_conversation", c.GetConversation)
|
||||
conversationGroup.POST("/get_conversations", c.GetConversations)
|
||||
conversationGroup.POST("/set_conversations", c.SetConversations)
|
||||
conversationGroup.POST("/get_conversation_offline_push_user_ids", c.GetConversationOfflinePushUserIDs)
|
||||
//conversationGroup.POST("/get_conversation_offline_push_user_ids", c.GetConversationOfflinePushUserIDs)
|
||||
conversationGroup.POST("/get_full_conversation_ids", c.GetFullOwnerConversationIDs)
|
||||
conversationGroup.POST("/get_incremental_conversations", c.GetIncrementalConversation)
|
||||
conversationGroup.POST("/get_owner_conversation", c.GetOwnerConversation)
|
||||
conversationGroup.POST("/get_not_notify_conversation_ids", c.GetNotNotifyConversationIDs)
|
||||
conversationGroup.POST("/get_pinned_conversation_ids", c.GetPinnedConversationIDs)
|
||||
conversationGroup.POST("/delete_conversations", c.DeleteConversations)
|
||||
conversationGroup.POST("/update_conversations_by_user", c.UpdateConversationsByUser)
|
||||
}
|
||||
|
||||
{
|
||||
@@ -283,7 +310,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
||||
}
|
||||
{
|
||||
pd := NewPrometheusDiscoveryApi(cfg, client)
|
||||
proDiscoveryGroup := r.Group("/prometheus_discovery", pd.Enable)
|
||||
proDiscoveryGroup := r.Group("/prometheus_discovery")
|
||||
proDiscoveryGroup.GET("/api", pd.Api)
|
||||
proDiscoveryGroup.GET("/user", pd.User)
|
||||
proDiscoveryGroup.GET("/group", pd.Group)
|
||||
@@ -301,9 +328,8 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
||||
if cfg.Discovery.Enable == config.ETCD {
|
||||
etcdClient = client.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
|
||||
}
|
||||
cm := NewConfigManager(cfg.Share.IMAdminUserID, cfg.AllConfig, etcdClient, cfg.ConfigPath, cfg.RuntimeEnv)
|
||||
cm := NewConfigManager(cfg.Share.IMAdminUser.UserIDs, &cfg.AllConfig, etcdClient, string(cfg.ConfigPath))
|
||||
{
|
||||
|
||||
configGroup := r.Group("/config", cm.CheckAdmin)
|
||||
configGroup.POST("/get_config_list", cm.GetConfigList)
|
||||
configGroup.POST("/get_config", cm.GetConfig)
|
||||
@@ -349,6 +375,12 @@ func GinParseToken(authClient *rpcli.AuthClient) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func setGinIsAdmin(imAdminUserID []string) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
c.Set(authverify.CtxAdminUserIDsKey, imAdminUserID)
|
||||
}
|
||||
}
|
||||
|
||||
// Whitelist api not parse token
|
||||
var Whitelist = []string{
|
||||
"/auth/get_admin_token",
|
||||
|
||||
+18
-2
@@ -29,11 +29,11 @@ import (
|
||||
|
||||
type UserApi struct {
|
||||
Client user.UserClient
|
||||
discov discovery.SvcDiscoveryRegistry
|
||||
discov discovery.Conn
|
||||
config config.RpcService
|
||||
}
|
||||
|
||||
func NewUserApi(client user.UserClient, discov discovery.SvcDiscoveryRegistry, config config.RpcService) UserApi {
|
||||
func NewUserApi(client user.UserClient, discov discovery.Conn, config config.RpcService) UserApi {
|
||||
return UserApi{Client: client, discov: discov, config: config}
|
||||
}
|
||||
|
||||
@@ -242,3 +242,19 @@ func (u *UserApi) UpdateNotificationAccountInfo(c *gin.Context) {
|
||||
func (u *UserApi) SearchNotificationAccount(c *gin.Context) {
|
||||
a2r.Call(c, user.UserClient.SearchNotificationAccount, u.Client)
|
||||
}
|
||||
|
||||
func (u *UserApi) GetUserClientConfig(c *gin.Context) {
|
||||
a2r.Call(c, user.UserClient.GetUserClientConfig, u.Client)
|
||||
}
|
||||
|
||||
func (u *UserApi) SetUserClientConfig(c *gin.Context) {
|
||||
a2r.Call(c, user.UserClient.SetUserClientConfig, u.Client)
|
||||
}
|
||||
|
||||
func (u *UserApi) DelUserClientConfig(c *gin.Context) {
|
||||
a2r.Call(c, user.UserClient.DelUserClientConfig, u.Client)
|
||||
}
|
||||
|
||||
func (u *UserApi) PageUserClientConfig(c *gin.Context) {
|
||||
a2r.Call(c, user.UserClient.PageUserClientConfig, u.Client)
|
||||
}
|
||||
|
||||
+12
-151
@@ -16,7 +16,6 @@ package msggateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -31,7 +30,6 @@ import (
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -64,12 +62,13 @@ type PingPongHandler func(string) error
|
||||
|
||||
type Client struct {
|
||||
w *sync.Mutex
|
||||
conn LongConn
|
||||
conn ClientConn
|
||||
PlatformID int `json:"platformID"`
|
||||
IsCompress bool `json:"isCompress"`
|
||||
UserID string `json:"userID"`
|
||||
IsBackground bool `json:"isBackground"`
|
||||
SDKType string `json:"sdkType"`
|
||||
SDKVersion string `json:"sdkVersion"`
|
||||
Encoder Encoder
|
||||
ctx *UserConnContext
|
||||
longConnServer LongConnServer
|
||||
@@ -83,10 +82,10 @@ type Client struct {
|
||||
}
|
||||
|
||||
// ResetClient updates the client's state with new connection and context information.
|
||||
func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer LongConnServer) {
|
||||
func (c *Client) ResetClient(ctx *UserConnContext, conn ClientConn, longConnServer LongConnServer) {
|
||||
c.w = new(sync.Mutex)
|
||||
c.conn = conn
|
||||
c.PlatformID = stringutil.StringToInt(ctx.GetPlatformID())
|
||||
c.PlatformID = ctx.GetPlatformID()
|
||||
c.IsCompress = ctx.GetCompression()
|
||||
c.IsBackground = ctx.GetBackground()
|
||||
c.UserID = ctx.GetUserID()
|
||||
@@ -97,6 +96,7 @@ func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer
|
||||
c.closedErr = nil
|
||||
c.token = ctx.GetToken()
|
||||
c.SDKType = ctx.GetSDKType()
|
||||
c.SDKVersion = ctx.GetSDKVersion()
|
||||
c.hbCtx, c.hbCancel = context.WithCancel(c.ctx)
|
||||
c.subLock = new(sync.Mutex)
|
||||
if c.subUserIDs != nil {
|
||||
@@ -110,22 +110,6 @@ func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer
|
||||
c.subUserIDs = make(map[string]struct{})
|
||||
}
|
||||
|
||||
func (c *Client) pingHandler(appData string) error {
|
||||
if err := c.conn.SetReadDeadline(pongWait); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.ZDebug(c.ctx, "ping Handler Success.", "appData", appData)
|
||||
return c.writePongMsg(appData)
|
||||
}
|
||||
|
||||
func (c *Client) pongHandler(_ string) error {
|
||||
if err := c.conn.SetReadDeadline(pongWait); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// readMessage continuously reads messages from the connection.
|
||||
func (c *Client) readMessage() {
|
||||
defer func() {
|
||||
@@ -136,52 +120,25 @@ func (c *Client) readMessage() {
|
||||
c.close()
|
||||
}()
|
||||
|
||||
c.conn.SetReadLimit(maxMessageSize)
|
||||
_ = c.conn.SetReadDeadline(pongWait)
|
||||
c.conn.SetPongHandler(c.pongHandler)
|
||||
c.conn.SetPingHandler(c.pingHandler)
|
||||
c.activeHeartbeat(c.hbCtx)
|
||||
|
||||
for {
|
||||
log.ZDebug(c.ctx, "readMessage")
|
||||
messageType, message, returnErr := c.conn.ReadMessage()
|
||||
message, returnErr := c.conn.ReadMessage()
|
||||
if returnErr != nil {
|
||||
log.ZWarn(c.ctx, "readMessage", returnErr, "messageType", messageType)
|
||||
log.ZWarn(c.ctx, "readMessage", returnErr)
|
||||
c.closedErr = returnErr
|
||||
return
|
||||
}
|
||||
|
||||
log.ZDebug(c.ctx, "readMessage", "messageType", messageType)
|
||||
if c.closed.Load() {
|
||||
// The scenario where the connection has just been closed, but the coroutine has not exited
|
||||
c.closedErr = ErrConnClosed
|
||||
return
|
||||
}
|
||||
|
||||
switch messageType {
|
||||
case MessageBinary:
|
||||
_ = c.conn.SetReadDeadline(pongWait)
|
||||
parseDataErr := c.handleMessage(message)
|
||||
if parseDataErr != nil {
|
||||
c.closedErr = parseDataErr
|
||||
return
|
||||
}
|
||||
case MessageText:
|
||||
_ = c.conn.SetReadDeadline(pongWait)
|
||||
parseDataErr := c.handlerTextMessage(message)
|
||||
if parseDataErr != nil {
|
||||
c.closedErr = parseDataErr
|
||||
return
|
||||
}
|
||||
case PingMessage:
|
||||
err := c.writePongMsg("")
|
||||
log.ZError(c.ctx, "writePongMsg", err)
|
||||
|
||||
case CloseMessage:
|
||||
c.closedErr = ErrClientClosed
|
||||
parseDataErr := c.handleMessage(message)
|
||||
if parseDataErr != nil {
|
||||
c.closedErr = parseDataErr
|
||||
return
|
||||
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -356,109 +313,13 @@ func (c *Client) writeBinaryMsg(resp Resp) error {
|
||||
c.w.Lock()
|
||||
defer c.w.Unlock()
|
||||
|
||||
err = c.conn.SetWriteDeadline(writeWait)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.IsCompress {
|
||||
resultBuf, compressErr := c.longConnServer.CompressWithPool(encodedBuf)
|
||||
if compressErr != nil {
|
||||
return compressErr
|
||||
}
|
||||
return c.conn.WriteMessage(MessageBinary, resultBuf)
|
||||
return c.conn.WriteMessage(resultBuf)
|
||||
}
|
||||
|
||||
return c.conn.WriteMessage(MessageBinary, encodedBuf)
|
||||
}
|
||||
|
||||
// Actively initiate Heartbeat when platform in Web.
|
||||
func (c *Client) activeHeartbeat(ctx context.Context) {
|
||||
if c.PlatformID == constant.WebPlatformID {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.ZPanic(ctx, "activeHeartbeat Panic", errs.ErrPanic(r))
|
||||
}
|
||||
}()
|
||||
log.ZDebug(ctx, "server initiative send heartbeat start.")
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
if err := c.writePingMsg(); err != nil {
|
||||
log.ZWarn(c.ctx, "send Ping Message error.", err)
|
||||
return
|
||||
}
|
||||
case <-c.hbCtx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
func (c *Client) writePingMsg() error {
|
||||
if c.closed.Load() {
|
||||
return nil
|
||||
}
|
||||
|
||||
c.w.Lock()
|
||||
defer c.w.Unlock()
|
||||
|
||||
err := c.conn.SetWriteDeadline(writeWait)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.conn.WriteMessage(PingMessage, nil)
|
||||
}
|
||||
|
||||
func (c *Client) writePongMsg(appData string) error {
|
||||
log.ZDebug(c.ctx, "write Pong Msg in Server", "appData", appData)
|
||||
if c.closed.Load() {
|
||||
log.ZWarn(c.ctx, "is closed in server", nil, "appdata", appData, "closed err", c.closedErr)
|
||||
return nil
|
||||
}
|
||||
|
||||
c.w.Lock()
|
||||
defer c.w.Unlock()
|
||||
|
||||
err := c.conn.SetWriteDeadline(writeWait)
|
||||
if err != nil {
|
||||
log.ZWarn(c.ctx, "SetWriteDeadline in Server have error", errs.Wrap(err), "writeWait", writeWait, "appData", appData)
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
err = c.conn.WriteMessage(PongMessage, []byte(appData))
|
||||
if err != nil {
|
||||
log.ZWarn(c.ctx, "Write Message have error", errs.Wrap(err), "Pong msg", PongMessage)
|
||||
}
|
||||
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *Client) handlerTextMessage(b []byte) error {
|
||||
var msg TextMessage
|
||||
if err := json.Unmarshal(b, &msg); err != nil {
|
||||
return err
|
||||
}
|
||||
switch msg.Type {
|
||||
case TextPong:
|
||||
return nil
|
||||
case TextPing:
|
||||
msg.Type = TextPong
|
||||
msgData, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.w.Lock()
|
||||
defer c.w.Unlock()
|
||||
if err := c.conn.SetWriteDeadline(writeWait); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.conn.WriteMessage(MessageText, msgData)
|
||||
default:
|
||||
return fmt.Errorf("not support message type %s", msg.Type)
|
||||
}
|
||||
return c.conn.WriteMessage(encodedBuf)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
package msggateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
var ErrWriteFull = fmt.Errorf("websocket write buffer full,close connection")
|
||||
|
||||
type ClientConn interface {
|
||||
ReadMessage() ([]byte, error)
|
||||
WriteMessage(message []byte) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
type websocketMessage struct {
|
||||
MessageType int
|
||||
Data []byte
|
||||
}
|
||||
|
||||
func NewWebSocketClientConn(conn *websocket.Conn, readLimit int64, readTimeout time.Duration, pingInterval time.Duration) ClientConn {
|
||||
c := &websocketClientConn{
|
||||
readTimeout: readTimeout,
|
||||
conn: conn,
|
||||
writer: make(chan *websocketMessage, 256),
|
||||
done: make(chan struct{}),
|
||||
}
|
||||
if readLimit > 0 {
|
||||
c.conn.SetReadLimit(readLimit)
|
||||
}
|
||||
c.conn.SetPingHandler(c.pingHandler)
|
||||
c.conn.SetPongHandler(c.pongHandler)
|
||||
|
||||
go c.loopSend()
|
||||
if pingInterval > 0 {
|
||||
go c.doPing(pingInterval)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
type websocketClientConn struct {
|
||||
readTimeout time.Duration
|
||||
conn *websocket.Conn
|
||||
writer chan *websocketMessage
|
||||
done chan struct{}
|
||||
err atomic.Pointer[error]
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) ReadMessage() ([]byte, error) {
|
||||
buf, err := c.readMessage()
|
||||
if err != nil {
|
||||
return nil, c.closeBy(fmt.Errorf("read message %w", err))
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) WriteMessage(message []byte) error {
|
||||
return c.writeMessage(websocket.BinaryMessage, message)
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) Close() error {
|
||||
return c.closeBy(fmt.Errorf("websocket connection closed"))
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) closeBy(err error) error {
|
||||
if !c.err.CompareAndSwap(nil, &err) {
|
||||
return *c.err.Load()
|
||||
}
|
||||
close(c.done)
|
||||
log.ZWarn(context.Background(), "websocket connection closed", err, "remoteAddr", c.conn.RemoteAddr(),
|
||||
"chan length", len(c.writer))
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) writeMessage(messageType int, data []byte) error {
|
||||
if errPtr := c.err.Load(); errPtr != nil {
|
||||
return *errPtr
|
||||
}
|
||||
select {
|
||||
case c.writer <- &websocketMessage{MessageType: messageType, Data: data}:
|
||||
return nil
|
||||
default:
|
||||
return c.closeBy(ErrWriteFull)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) loopSend() {
|
||||
defer func() {
|
||||
_ = c.conn.Close()
|
||||
}()
|
||||
var err error
|
||||
for {
|
||||
select {
|
||||
case <-c.done:
|
||||
for {
|
||||
select {
|
||||
case msg := <-c.writer:
|
||||
switch msg.MessageType {
|
||||
case websocket.TextMessage, websocket.BinaryMessage:
|
||||
err = c.conn.WriteMessage(msg.MessageType, msg.Data)
|
||||
default:
|
||||
err = c.conn.WriteControl(msg.MessageType, msg.Data, time.Time{})
|
||||
}
|
||||
if err != nil {
|
||||
_ = c.closeBy(err)
|
||||
return
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
}
|
||||
case msg := <-c.writer:
|
||||
switch msg.MessageType {
|
||||
case websocket.TextMessage, websocket.BinaryMessage:
|
||||
err = c.conn.WriteMessage(msg.MessageType, msg.Data)
|
||||
default:
|
||||
err = c.conn.WriteControl(msg.MessageType, msg.Data, time.Time{})
|
||||
}
|
||||
if err != nil {
|
||||
_ = c.closeBy(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) setReadDeadline() error {
|
||||
deadline := time.Now().Add(c.readTimeout)
|
||||
return c.conn.SetReadDeadline(deadline)
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) readMessage() ([]byte, error) {
|
||||
for {
|
||||
if err := c.setReadDeadline(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
messageType, buf, err := c.conn.ReadMessage()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch messageType {
|
||||
case websocket.BinaryMessage:
|
||||
return buf, nil
|
||||
case websocket.TextMessage:
|
||||
if err := c.onReadTextMessage(buf); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case websocket.PingMessage:
|
||||
if err := c.pingHandler(string(buf)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case websocket.PongMessage:
|
||||
if err := c.pongHandler(string(buf)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
case websocket.CloseMessage:
|
||||
if len(buf) == 0 {
|
||||
return nil, errors.New("websocket connection closed by peer")
|
||||
}
|
||||
return nil, fmt.Errorf("websocket connection closed by peer, data %s", string(buf))
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown websocket message type %d", messageType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) onReadTextMessage(buf []byte) error {
|
||||
var msg struct {
|
||||
Type string `json:"type"`
|
||||
Body json.RawMessage `json:"body"`
|
||||
}
|
||||
if err := json.Unmarshal(buf, &msg); err != nil {
|
||||
return err
|
||||
}
|
||||
switch msg.Type {
|
||||
case TextPong:
|
||||
return nil
|
||||
case TextPing:
|
||||
msg.Type = TextPong
|
||||
msgData, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.writeMessage(websocket.TextMessage, msgData)
|
||||
default:
|
||||
return fmt.Errorf("not support text message type %s", msg.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) pingHandler(appData string) error {
|
||||
log.ZDebug(context.Background(), "ping handler recv ping", "remoteAddr", c.conn.RemoteAddr(), "appData", appData)
|
||||
if err := c.setReadDeadline(); err != nil {
|
||||
return err
|
||||
}
|
||||
err := c.conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(time.Second*1))
|
||||
if err != nil {
|
||||
log.ZWarn(context.Background(), "ping handler write pong error", err, "remoteAddr", c.conn.RemoteAddr(), "appData", appData)
|
||||
}
|
||||
log.ZDebug(context.Background(), "ping handler write pong success", "remoteAddr", c.conn.RemoteAddr(), "appData", appData)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) pongHandler(string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *websocketClientConn) doPing(d time.Duration) {
|
||||
ticker := time.NewTicker(d)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-c.done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
if err := c.writeMessage(websocket.PingMessage, nil); err != nil {
|
||||
_ = c.closeBy(fmt.Errorf("send ping %w", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ const (
|
||||
BackgroundStatus = "isBackground"
|
||||
SendResponse = "isMsgResp"
|
||||
SDKType = "sdkType"
|
||||
SDKVersion = "sdkVersion"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
+144
-82
@@ -15,18 +15,32 @@
|
||||
package msggateway
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
|
||||
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/utils/encrypt"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"github.com/openimsdk/tools/utils/timeutil"
|
||||
)
|
||||
|
||||
type UserConnContextInfo struct {
|
||||
Token string `json:"token"`
|
||||
UserID string `json:"userID"`
|
||||
PlatformID int `json:"platformID"`
|
||||
OperationID string `json:"operationID"`
|
||||
Compression string `json:"compression"`
|
||||
SDKType string `json:"sdkType"`
|
||||
SendResponse bool `json:"sendResponse"`
|
||||
Background bool `json:"background"`
|
||||
SDKVersion string `json:"sdkVersion"`
|
||||
}
|
||||
|
||||
type UserConnContext struct {
|
||||
RespWriter http.ResponseWriter
|
||||
Req *http.Request
|
||||
@@ -34,6 +48,7 @@ type UserConnContext struct {
|
||||
Method string
|
||||
RemoteAddr string
|
||||
ConnID string
|
||||
info *UserConnContextInfo
|
||||
}
|
||||
|
||||
func (c *UserConnContext) Deadline() (deadline time.Time, ok bool) {
|
||||
@@ -57,9 +72,11 @@ func (c *UserConnContext) Value(key any) any {
|
||||
case constant.ConnID:
|
||||
return c.GetConnID()
|
||||
case constant.OpUserPlatform:
|
||||
return constant.PlatformIDToName(stringutil.StringToInt(c.GetPlatformID()))
|
||||
return c.GetPlatformID()
|
||||
case constant.RemoteAddr:
|
||||
return c.RemoteAddr
|
||||
case SDKVersion:
|
||||
return c.info.SDKVersion
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
@@ -82,30 +99,92 @@ func newContext(respWriter http.ResponseWriter, req *http.Request) *UserConnCont
|
||||
|
||||
func newTempContext() *UserConnContext {
|
||||
return &UserConnContext{
|
||||
Req: &http.Request{URL: &url.URL{}},
|
||||
Req: &http.Request{URL: &url.URL{}},
|
||||
info: &UserConnContextInfo{},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UserConnContext) ParseEssentialArgs() error {
|
||||
query := c.Req.URL.Query()
|
||||
if data := query.Get("v"); data != "" {
|
||||
return c.parseByJson(data)
|
||||
} else {
|
||||
return c.parseByQuery(query, c.Req.Header)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UserConnContext) parseByQuery(query url.Values, header http.Header) error {
|
||||
info := UserConnContextInfo{
|
||||
Token: query.Get(Token),
|
||||
UserID: query.Get(WsUserID),
|
||||
OperationID: query.Get(OperationID),
|
||||
Compression: query.Get(Compression),
|
||||
SDKType: query.Get(SDKType),
|
||||
SDKVersion: query.Get(SDKVersion),
|
||||
}
|
||||
platformID, err := strconv.Atoi(query.Get(PlatformID))
|
||||
if err != nil {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("platformID is not int")
|
||||
}
|
||||
info.PlatformID = platformID
|
||||
if val := query.Get(SendResponse); val != "" {
|
||||
ok, err := strconv.ParseBool(val)
|
||||
if err != nil {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("isMsgResp is not bool")
|
||||
}
|
||||
info.SendResponse = ok
|
||||
}
|
||||
if info.Compression == "" {
|
||||
info.Compression = header.Get(Compression)
|
||||
}
|
||||
background, err := strconv.ParseBool(query.Get(BackgroundStatus))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info.Background = background
|
||||
return c.checkInfo(&info)
|
||||
}
|
||||
|
||||
func (c *UserConnContext) parseByJson(data string) error {
|
||||
reqInfo, err := base64.RawURLEncoding.DecodeString(data)
|
||||
if err != nil {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("data is not base64")
|
||||
}
|
||||
var info UserConnContextInfo
|
||||
if err := json.Unmarshal(reqInfo, &info); err != nil {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("data is not json", "info", err.Error())
|
||||
}
|
||||
return c.checkInfo(&info)
|
||||
}
|
||||
|
||||
func (c *UserConnContext) checkInfo(info *UserConnContextInfo) error {
|
||||
if info.OperationID == "" {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("operationID is empty")
|
||||
}
|
||||
if info.Token == "" {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("token is empty")
|
||||
}
|
||||
if info.UserID == "" {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("sendID is empty")
|
||||
}
|
||||
if _, ok := constant.PlatformID2Name[info.PlatformID]; !ok {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("platformID is invalid")
|
||||
}
|
||||
switch info.SDKType {
|
||||
case "":
|
||||
info.SDKType = GoSDK
|
||||
case GoSDK, JsSDK:
|
||||
default:
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("sdkType is invalid")
|
||||
}
|
||||
c.info = info
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetRemoteAddr() string {
|
||||
return c.RemoteAddr
|
||||
}
|
||||
|
||||
func (c *UserConnContext) Query(key string) (string, bool) {
|
||||
var value string
|
||||
if value = c.Req.URL.Query().Get(key); value == "" {
|
||||
return value, false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetHeader(key string) (string, bool) {
|
||||
var value string
|
||||
if value = c.Req.Header.Get(key); value == "" {
|
||||
return value, false
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
func (c *UserConnContext) SetHeader(key, value string) {
|
||||
c.RespWriter.Header().Set(key, value)
|
||||
}
|
||||
@@ -119,93 +198,76 @@ func (c *UserConnContext) GetConnID() string {
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetUserID() string {
|
||||
return c.Req.URL.Query().Get(WsUserID)
|
||||
if c == nil || c.info == nil {
|
||||
return ""
|
||||
}
|
||||
return c.info.UserID
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetPlatformID() string {
|
||||
return c.Req.URL.Query().Get(PlatformID)
|
||||
func (c *UserConnContext) GetPlatformID() int {
|
||||
if c == nil || c.info == nil {
|
||||
return 0
|
||||
}
|
||||
return c.info.PlatformID
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetOperationID() string {
|
||||
return c.Req.URL.Query().Get(OperationID)
|
||||
if c == nil || c.info == nil {
|
||||
return ""
|
||||
}
|
||||
return c.info.OperationID
|
||||
}
|
||||
|
||||
func (c *UserConnContext) SetOperationID(operationID string) {
|
||||
values := c.Req.URL.Query()
|
||||
values.Set(OperationID, operationID)
|
||||
c.Req.URL.RawQuery = values.Encode()
|
||||
if c.info == nil {
|
||||
c.info = &UserConnContextInfo{}
|
||||
}
|
||||
c.info.OperationID = operationID
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetToken() string {
|
||||
return c.Req.URL.Query().Get(Token)
|
||||
if c == nil || c.info == nil {
|
||||
return ""
|
||||
}
|
||||
return c.info.Token
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetCompression() bool {
|
||||
compression, exists := c.Query(Compression)
|
||||
if exists && compression == GzipCompressionProtocol {
|
||||
return true
|
||||
} else {
|
||||
compression, exists := c.GetHeader(Compression)
|
||||
if exists && compression == GzipCompressionProtocol {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
return c != nil && c.info != nil && c.info.Compression == GzipCompressionProtocol
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetSDKType() string {
|
||||
sdkType := c.Req.URL.Query().Get(SDKType)
|
||||
if sdkType == "" {
|
||||
sdkType = GoSDK
|
||||
if c == nil || c.info == nil {
|
||||
return GoSDK
|
||||
}
|
||||
return sdkType
|
||||
switch c.info.SDKType {
|
||||
case "", GoSDK:
|
||||
return GoSDK
|
||||
case JsSDK:
|
||||
return JsSDK
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetSDKVersion() string {
|
||||
if c == nil || c.info == nil {
|
||||
return ""
|
||||
}
|
||||
return c.info.SDKVersion
|
||||
}
|
||||
|
||||
func (c *UserConnContext) ShouldSendResp() bool {
|
||||
errResp, exists := c.Query(SendResponse)
|
||||
if exists {
|
||||
b, err := strconv.ParseBool(errResp)
|
||||
if err != nil {
|
||||
return false
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
return false
|
||||
return c != nil && c.info != nil && c.info.SendResponse
|
||||
}
|
||||
|
||||
func (c *UserConnContext) SetToken(token string) {
|
||||
c.Req.URL.RawQuery = Token + "=" + token
|
||||
if c.info == nil {
|
||||
c.info = &UserConnContextInfo{}
|
||||
}
|
||||
c.info.Token = token
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetBackground() bool {
|
||||
b, err := strconv.ParseBool(c.Req.URL.Query().Get(BackgroundStatus))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return b
|
||||
}
|
||||
func (c *UserConnContext) ParseEssentialArgs() error {
|
||||
_, exists := c.Query(Token)
|
||||
if !exists {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("token is empty")
|
||||
}
|
||||
_, exists = c.Query(WsUserID)
|
||||
if !exists {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("sendID is empty")
|
||||
}
|
||||
platformIDStr, exists := c.Query(PlatformID)
|
||||
if !exists {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("platformID is empty")
|
||||
}
|
||||
_, err := strconv.Atoi(platformIDStr)
|
||||
if err != nil {
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("platformID is not int")
|
||||
}
|
||||
switch sdkType, _ := c.Query(SDKType); sdkType {
|
||||
case "", GoSDK, JsSDK:
|
||||
default:
|
||||
return servererrs.ErrConnArgsErr.WrapMsg("sdkType is not go or js")
|
||||
}
|
||||
return nil
|
||||
return c != nil && c.info != nil && c.info.Background
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/protocol/msggateway"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
@@ -35,7 +34,7 @@ import (
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func (s *Server) InitServer(ctx context.Context, config *Config, disCov discovery.SvcDiscoveryRegistry, server *grpc.Server) error {
|
||||
func (s *Server) InitServer(ctx context.Context, config *Config, disCov discovery.Conn, server grpc.ServiceRegistrar) error {
|
||||
userConn, err := disCov.GetConn(ctx, config.Discovery.RpcService.User)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -51,26 +50,26 @@ func (s *Server) InitServer(ctx context.Context, config *Config, disCov discover
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) Start(ctx context.Context, index int, conf *Config) error {
|
||||
return startrpc.Start(ctx, &conf.Discovery, &conf.MsgGateway.Prometheus, conf.MsgGateway.ListenIP,
|
||||
conf.MsgGateway.RPC.RegisterIP,
|
||||
conf.MsgGateway.RPC.AutoSetPorts, conf.MsgGateway.RPC.Ports, index,
|
||||
conf.Discovery.RpcService.MessageGateway,
|
||||
nil,
|
||||
conf,
|
||||
[]string{
|
||||
conf.Share.GetConfigFileName(),
|
||||
conf.Discovery.GetConfigFileName(),
|
||||
conf.MsgGateway.GetConfigFileName(),
|
||||
conf.WebhooksConfig.GetConfigFileName(),
|
||||
conf.RedisConfig.GetConfigFileName(),
|
||||
},
|
||||
[]string{
|
||||
conf.Discovery.RpcService.MessageGateway,
|
||||
},
|
||||
s.InitServer,
|
||||
)
|
||||
}
|
||||
//func (s *Server) Start(ctx context.Context, index int, conf *Config) error {
|
||||
// return startrpc.Start(ctx, &conf.Discovery, &conf.MsgGateway.Prometheus, conf.MsgGateway.ListenIP,
|
||||
// conf.MsgGateway.RPC.RegisterIP,
|
||||
// conf.MsgGateway.RPC.AutoSetPorts, conf.MsgGateway.RPC.Ports, index,
|
||||
// conf.Discovery.RpcService.MessageGateway,
|
||||
// nil,
|
||||
// conf,
|
||||
// []string{
|
||||
// conf.Share.GetConfigFileName(),
|
||||
// conf.Discovery.GetConfigFileName(),
|
||||
// conf.MsgGateway.GetConfigFileName(),
|
||||
// conf.WebhooksConfig.GetConfigFileName(),
|
||||
// conf.RedisConfig.GetConfigFileName(),
|
||||
// },
|
||||
// []string{
|
||||
// conf.Discovery.RpcService.MessageGateway,
|
||||
// },
|
||||
// s.InitServer,
|
||||
// )
|
||||
//}
|
||||
|
||||
type Server struct {
|
||||
msggateway.UnimplementedMsgGatewayServer
|
||||
@@ -101,7 +100,7 @@ func NewServer(longConnServer LongConnServer, conf *Config, ready func(srv *Serv
|
||||
}
|
||||
|
||||
func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUsersOnlineStatusReq) (*msggateway.GetUsersOnlineStatusResp, error) {
|
||||
if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) {
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("only app manager")
|
||||
}
|
||||
var resp msggateway.GetUsersOnlineStatusResp
|
||||
@@ -153,19 +152,16 @@ func (s *Server) pushToUser(ctx context.Context, userID string, msgData *sdkws.M
|
||||
userPlatform := &msggateway.SingleMsgToUserPlatform{
|
||||
RecvPlatFormID: int32(client.PlatformID),
|
||||
}
|
||||
if !client.IsBackground ||
|
||||
(client.IsBackground && client.PlatformID != constant.IOSPlatformID) {
|
||||
err := client.PushMessage(ctx, msgData)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "online push msg failed", err, "userID", userID, "platformID", client.PlatformID)
|
||||
userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code())
|
||||
} else {
|
||||
if _, ok := s.pushTerminal[client.PlatformID]; ok {
|
||||
result.OnlinePush = true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if client.IsBackground && client.PlatformID == constant.IOSPlatformID {
|
||||
userPlatform.ResultCode = int64(servererrs.ErrIOSBackgroundPushErr.Code())
|
||||
result.Resp = append(result.Resp, userPlatform)
|
||||
continue
|
||||
}
|
||||
if err := client.PushMessage(ctx, msgData); err != nil {
|
||||
log.ZWarn(ctx, "online push msg failed", err, "userID", userID, "platformID", client.PlatformID)
|
||||
userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code())
|
||||
} else if _, ok := s.pushTerminal[client.PlatformID]; ok {
|
||||
result.OnlinePush = true
|
||||
}
|
||||
result.Resp = append(result.Resp, userPlatform)
|
||||
}
|
||||
@@ -250,6 +246,7 @@ func (s *Server) MultiTerminalLoginCheck(ctx context.Context, req *msggateway.Mu
|
||||
tempUserCtx.SetOperationID(mcontext.GetOperationID(ctx))
|
||||
client := &Client{}
|
||||
client.ctx = tempUserCtx
|
||||
client.token = req.Token
|
||||
client.UserID = req.UserID
|
||||
client.PlatformID = int(req.PlatformID)
|
||||
i := &kickHandler{
|
||||
|
||||
+54
-15
@@ -19,10 +19,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||
"github.com/openimsdk/tools/db/redisutil"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/runtimeenv"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
@@ -33,26 +35,25 @@ type Config struct {
|
||||
RedisConfig config.Redis
|
||||
WebhooksConfig config.Webhooks
|
||||
Discovery config.Discovery
|
||||
|
||||
RuntimeEnv string
|
||||
Index config.Index
|
||||
}
|
||||
|
||||
// Start run ws server.
|
||||
func Start(ctx context.Context, index int, conf *Config) error {
|
||||
conf.RuntimeEnv = runtimeenv.PrintRuntimeEnvironment()
|
||||
|
||||
log.CInfo(ctx, "MSG-GATEWAY server is initializing", "runtimeEnv", conf.RuntimeEnv,
|
||||
func Start(ctx context.Context, conf *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
|
||||
log.CInfo(ctx, "MSG-GATEWAY server is initializing", "runtimeEnv", runtimeenv.RuntimeEnvironment(),
|
||||
"rpcPorts", conf.MsgGateway.RPC.Ports,
|
||||
"wsPort", conf.MsgGateway.LongConnSvr.Ports, "prometheusPorts", conf.MsgGateway.Prometheus.Ports)
|
||||
wsPort, err := datautil.GetElemByIndex(conf.MsgGateway.LongConnSvr.Ports, index)
|
||||
wsPort, err := datautil.GetElemByIndex(conf.MsgGateway.LongConnSvr.Ports, int(conf.Index))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rdb, err := redisutil.NewRedisClient(ctx, conf.RedisConfig.Build())
|
||||
dbb := dbbuild.NewBuilder(nil, &conf.RedisConfig)
|
||||
rdb, err := dbb.Redis(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
longServer := NewWsServer(
|
||||
conf,
|
||||
WithPort(wsPort),
|
||||
@@ -67,12 +68,50 @@ func Start(ctx context.Context, index int, conf *Config) error {
|
||||
return err
|
||||
})
|
||||
|
||||
if err := hubServer.InitServer(ctx, conf, client, server); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
go longServer.ChangeOnlineStatus(4)
|
||||
|
||||
netDone := make(chan error)
|
||||
go func() {
|
||||
err = hubServer.Start(ctx, index, conf)
|
||||
netDone <- err
|
||||
}()
|
||||
return hubServer.LongConnServer.Run(netDone)
|
||||
return hubServer.LongConnServer.Run(ctx)
|
||||
}
|
||||
|
||||
//
|
||||
//// Start run ws server.
|
||||
//func Start(ctx context.Context, index int, conf *Config) error {
|
||||
// log.CInfo(ctx, "MSG-GATEWAY server is initializing", "runtimeEnv", runtimeenv.RuntimeEnvironment(),
|
||||
// "rpcPorts", conf.MsgGateway.RPC.Ports,
|
||||
// "wsPort", conf.MsgGateway.LongConnSvr.Ports, "prometheusPorts", conf.MsgGateway.Prometheus.Ports)
|
||||
// wsPort, err := datautil.GetElemByIndex(conf.MsgGateway.LongConnSvr.Ports, index)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
//
|
||||
// rdb, err := redisutil.NewRedisClient(ctx, conf.RedisConfig.Build())
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// longServer := NewWsServer(
|
||||
// conf,
|
||||
// WithPort(wsPort),
|
||||
// WithMaxConnNum(int64(conf.MsgGateway.LongConnSvr.WebsocketMaxConnNum)),
|
||||
// WithHandshakeTimeout(time.Duration(conf.MsgGateway.LongConnSvr.WebsocketTimeout)*time.Second),
|
||||
// WithMessageMaxMsgLength(conf.MsgGateway.LongConnSvr.WebsocketMaxMsgLen),
|
||||
// )
|
||||
//
|
||||
// hubServer := NewServer(longServer, conf, func(srv *Server) error {
|
||||
// var err error
|
||||
// longServer.online, err = rpccache.NewOnlineCache(srv.userClient, nil, rdb, false, longServer.subscriberUserOnlineStatusChanges)
|
||||
// return err
|
||||
// })
|
||||
//
|
||||
// go longServer.ChangeOnlineStatus(4)
|
||||
//
|
||||
// netDone := make(chan error)
|
||||
// go func() {
|
||||
// err = hubServer.Start(ctx, index, conf)
|
||||
// netDone <- err
|
||||
// }()
|
||||
// return hubServer.LongConnServer.Run(netDone)
|
||||
//}
|
||||
|
||||
@@ -1,179 +0,0 @@
|
||||
// Copyright © 2023 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.
|
||||
|
||||
package msggateway
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/tools/apiresp"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
)
|
||||
|
||||
type LongConn interface {
|
||||
// Close this connection
|
||||
Close() error
|
||||
// WriteMessage Write message to connection,messageType means data type,can be set binary(2) and text(1).
|
||||
WriteMessage(messageType int, message []byte) error
|
||||
// ReadMessage Read message from connection.
|
||||
ReadMessage() (int, []byte, error)
|
||||
// SetReadDeadline sets the read deadline on the underlying network connection,
|
||||
// after a read has timed out, will return an error.
|
||||
SetReadDeadline(timeout time.Duration) error
|
||||
// SetWriteDeadline sets to write deadline when send message,when read has timed out,will return error.
|
||||
SetWriteDeadline(timeout time.Duration) error
|
||||
// Dial Try to dial a connection,url must set auth args,header can control compress data
|
||||
Dial(urlStr string, requestHeader http.Header) (*http.Response, error)
|
||||
// IsNil Whether the connection of the current long connection is nil
|
||||
IsNil() bool
|
||||
// SetConnNil Set the connection of the current long connection to nil
|
||||
SetConnNil()
|
||||
// SetReadLimit sets the maximum size for a message read from the peer.bytes
|
||||
SetReadLimit(limit int64)
|
||||
SetPongHandler(handler PingPongHandler)
|
||||
SetPingHandler(handler PingPongHandler)
|
||||
// GenerateLongConn Check the connection of the current and when it was sent are the same
|
||||
GenerateLongConn(w http.ResponseWriter, r *http.Request) error
|
||||
}
|
||||
type GWebSocket struct {
|
||||
protocolType int
|
||||
conn *websocket.Conn
|
||||
handshakeTimeout time.Duration
|
||||
writeBufferSize int
|
||||
}
|
||||
|
||||
func newGWebSocket(protocolType int, handshakeTimeout time.Duration, wbs int) *GWebSocket {
|
||||
return &GWebSocket{protocolType: protocolType, handshakeTimeout: handshakeTimeout, writeBufferSize: wbs}
|
||||
}
|
||||
|
||||
func (d *GWebSocket) Close() error {
|
||||
return d.conn.Close()
|
||||
}
|
||||
|
||||
func (d *GWebSocket) GenerateLongConn(w http.ResponseWriter, r *http.Request) error {
|
||||
upgrader := &websocket.Upgrader{
|
||||
HandshakeTimeout: d.handshakeTimeout,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
}
|
||||
if d.writeBufferSize > 0 { // default is 4kb.
|
||||
upgrader.WriteBufferSize = d.writeBufferSize
|
||||
}
|
||||
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
// The upgrader.Upgrade method usually returns enough error messages to diagnose problems that may occur during the upgrade
|
||||
return errs.WrapMsg(err, "GenerateLongConn: WebSocket upgrade failed")
|
||||
}
|
||||
d.conn = conn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *GWebSocket) WriteMessage(messageType int, message []byte) error {
|
||||
// d.setSendConn(d.conn)
|
||||
return d.conn.WriteMessage(messageType, message)
|
||||
}
|
||||
|
||||
// func (d *GWebSocket) setSendConn(sendConn *websocket.Conn) {
|
||||
// d.sendConn = sendConn
|
||||
//}
|
||||
|
||||
func (d *GWebSocket) ReadMessage() (int, []byte, error) {
|
||||
return d.conn.ReadMessage()
|
||||
}
|
||||
|
||||
func (d *GWebSocket) SetReadDeadline(timeout time.Duration) error {
|
||||
return d.conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
}
|
||||
|
||||
func (d *GWebSocket) SetWriteDeadline(timeout time.Duration) error {
|
||||
if timeout <= 0 {
|
||||
return errs.New("timeout must be greater than 0")
|
||||
}
|
||||
|
||||
// TODO SetWriteDeadline Future add error handling
|
||||
if err := d.conn.SetWriteDeadline(time.Now().Add(timeout)); err != nil {
|
||||
return errs.WrapMsg(err, "GWebSocket.SetWriteDeadline failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *GWebSocket) Dial(urlStr string, requestHeader http.Header) (*http.Response, error) {
|
||||
conn, httpResp, err := websocket.DefaultDialer.Dial(urlStr, requestHeader)
|
||||
if err != nil {
|
||||
return httpResp, errs.WrapMsg(err, "GWebSocket.Dial failed", "url", urlStr)
|
||||
}
|
||||
d.conn = conn
|
||||
return httpResp, nil
|
||||
}
|
||||
|
||||
func (d *GWebSocket) IsNil() bool {
|
||||
return d.conn == nil
|
||||
//
|
||||
// if d.conn != nil {
|
||||
// return false
|
||||
// }
|
||||
// return true
|
||||
}
|
||||
|
||||
func (d *GWebSocket) SetConnNil() {
|
||||
d.conn = nil
|
||||
}
|
||||
|
||||
func (d *GWebSocket) SetReadLimit(limit int64) {
|
||||
d.conn.SetReadLimit(limit)
|
||||
}
|
||||
|
||||
func (d *GWebSocket) SetPongHandler(handler PingPongHandler) {
|
||||
d.conn.SetPongHandler(handler)
|
||||
}
|
||||
|
||||
func (d *GWebSocket) SetPingHandler(handler PingPongHandler) {
|
||||
d.conn.SetPingHandler(handler)
|
||||
}
|
||||
|
||||
func (d *GWebSocket) RespondWithError(err error, w http.ResponseWriter, r *http.Request) error {
|
||||
if err := d.GenerateLongConn(w, r); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := json.Marshal(apiresp.ParseError(err))
|
||||
if err != nil {
|
||||
_ = d.Close()
|
||||
return errs.WrapMsg(err, "json marshal failed")
|
||||
}
|
||||
|
||||
if err := d.WriteMessage(MessageText, data); err != nil {
|
||||
_ = d.Close()
|
||||
return errs.WrapMsg(err, "WriteMessage failed")
|
||||
}
|
||||
_ = d.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *GWebSocket) RespondWithSuccess() error {
|
||||
data, err := json.Marshal(apiresp.ParseError(nil))
|
||||
if err != nil {
|
||||
_ = d.Close()
|
||||
return errs.WrapMsg(err, "json marshal failed")
|
||||
}
|
||||
|
||||
if err := d.WriteMessage(MessageText, data); err != nil {
|
||||
_ = d.Close()
|
||||
return errs.WrapMsg(err, "WriteMessage failed")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -2,16 +2,17 @@ package msggateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"github.com/openimsdk/tools/apiresp"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/discovery/etcd"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||
pbAuth "github.com/openimsdk/protocol/auth"
|
||||
@@ -23,19 +24,19 @@ import (
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/protocol/msggateway"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var wsSuccessResponse, _ = json.Marshal(&apiresp.ApiResponse{})
|
||||
|
||||
type LongConnServer interface {
|
||||
Run(done chan error) error
|
||||
Run(ctx context.Context) error
|
||||
wsHandler(w http.ResponseWriter, r *http.Request)
|
||||
GetUserAllCons(userID string) ([]*Client, bool)
|
||||
GetUserPlatformCons(userID string, platform int) ([]*Client, bool, bool)
|
||||
Validate(s any) error
|
||||
SetDiscoveryRegistry(ctx context.Context, client discovery.SvcDiscoveryRegistry, config *Config) error
|
||||
SetDiscoveryRegistry(ctx context.Context, client discovery.Conn, config *Config) error
|
||||
KickUserConn(client *Client) error
|
||||
UnRegister(c *Client)
|
||||
SetKickHandlerInfo(i *kickHandler)
|
||||
@@ -45,6 +46,7 @@ type LongConnServer interface {
|
||||
}
|
||||
|
||||
type WsServer struct {
|
||||
websocket *websocket.Upgrader
|
||||
msgGatewayConfig *Config
|
||||
port int
|
||||
wsMaxConnNum int64
|
||||
@@ -52,7 +54,7 @@ type WsServer struct {
|
||||
unregisterChan chan *Client
|
||||
kickHandlerChan chan *kickHandler
|
||||
clients UserMap
|
||||
online *rpccache.OnlineCache
|
||||
online rpccache.OnlineCache
|
||||
subscription *Subscription
|
||||
clientPool sync.Pool
|
||||
onlineUserNum atomic.Int64
|
||||
@@ -60,13 +62,15 @@ type WsServer struct {
|
||||
handshakeTimeout time.Duration
|
||||
writeBufferSize int
|
||||
validate *validator.Validate
|
||||
disCov discovery.SvcDiscoveryRegistry
|
||||
disCov discovery.Conn
|
||||
Compressor
|
||||
//Encoder
|
||||
MessageHandler
|
||||
webhookClient *webhook.Client
|
||||
userClient *rpcli.UserClient
|
||||
authClient *rpcli.AuthClient
|
||||
|
||||
ready atomic.Bool
|
||||
}
|
||||
|
||||
type kickHandler struct {
|
||||
@@ -75,7 +79,7 @@ type kickHandler struct {
|
||||
newClient *Client
|
||||
}
|
||||
|
||||
func (ws *WsServer) SetDiscoveryRegistry(ctx context.Context, disCov discovery.SvcDiscoveryRegistry, config *Config) error {
|
||||
func (ws *WsServer) SetDiscoveryRegistry(ctx context.Context, disCov discovery.Conn, config *Config) error {
|
||||
userConn, err := disCov.GetConn(ctx, config.Discovery.RpcService.User)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -96,6 +100,8 @@ func (ws *WsServer) SetDiscoveryRegistry(ctx context.Context, disCov discovery.S
|
||||
ws.authClient = rpcli.NewAuthClient(authConn)
|
||||
ws.MessageHandler = NewGrpcHandler(ws.validate, rpcli.NewMsgClient(msgConn), rpcli.NewPushMsgServiceClient(pushConn))
|
||||
ws.disCov = disCov
|
||||
|
||||
ws.ready.Store(true)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -133,10 +139,14 @@ func NewWsServer(msgGatewayConfig *Config, opts ...Option) *WsServer {
|
||||
for _, o := range opts {
|
||||
o(&config)
|
||||
}
|
||||
//userRpcClient := rpcclient.NewUserRpcClient(client, config.Discovery.RpcService.User, config.Share.IMAdminUserID)
|
||||
|
||||
//userRpcClient := rpcclient.NewUserRpcClient(client, config.Discovery.RpcService.User, config.Share.IMAdminUser)
|
||||
upgrader := &websocket.Upgrader{
|
||||
HandshakeTimeout: config.handshakeTimeout,
|
||||
CheckOrigin: func(r *http.Request) bool { return true },
|
||||
}
|
||||
v := validator.New()
|
||||
return &WsServer{
|
||||
websocket: upgrader,
|
||||
msgGatewayConfig: msgGatewayConfig,
|
||||
port: config.port,
|
||||
wsMaxConnNum: config.maxConnNum,
|
||||
@@ -158,19 +168,14 @@ func NewWsServer(msgGatewayConfig *Config, opts ...Option) *WsServer {
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *WsServer) Run(done chan error) error {
|
||||
var (
|
||||
client *Client
|
||||
netErr error
|
||||
shutdownDone = make(chan struct{}, 1)
|
||||
)
|
||||
|
||||
server := http.Server{Addr: ":" + stringutil.IntToString(ws.port), Handler: nil}
|
||||
func (ws *WsServer) Run(ctx context.Context) error {
|
||||
var client *Client
|
||||
|
||||
ctx, cancel := context.WithCancelCause(ctx)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-shutdownDone:
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case client = <-ws.registerChan:
|
||||
ws.registerClient(client)
|
||||
@@ -181,57 +186,56 @@ func (ws *WsServer) Run(done chan error) error {
|
||||
}
|
||||
}
|
||||
}()
|
||||
netDone := make(chan struct{}, 1)
|
||||
go func() {
|
||||
http.HandleFunc("/", ws.wsHandler)
|
||||
err := server.ListenAndServe()
|
||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
netErr = errs.WrapMsg(err, "ws start err", server.Addr)
|
||||
netDone <- struct{}{}
|
||||
}
|
||||
}()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||
shutDown := func() error {
|
||||
sErr := server.Shutdown(ctx)
|
||||
if sErr != nil {
|
||||
return errs.WrapMsg(sErr, "shutdown err")
|
||||
}
|
||||
close(shutdownDone)
|
||||
return nil
|
||||
}
|
||||
etcd.RegisterShutDown(shutDown)
|
||||
defer cancel()
|
||||
var err error
|
||||
select {
|
||||
case err = <-done:
|
||||
if err := shutDown(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-netDone:
|
||||
}
|
||||
return netErr
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
wsServer := http.Server{Addr: fmt.Sprintf(":%d", ws.port), Handler: nil}
|
||||
http.HandleFunc("/", ws.wsHandler)
|
||||
go func() {
|
||||
defer close(done)
|
||||
<-ctx.Done()
|
||||
_ = wsServer.Shutdown(context.Background())
|
||||
}()
|
||||
err := wsServer.ListenAndServe()
|
||||
if err == nil {
|
||||
err = fmt.Errorf("http server closed")
|
||||
}
|
||||
cancel(fmt.Errorf("msg gateway %w", err))
|
||||
}()
|
||||
|
||||
<-ctx.Done()
|
||||
|
||||
timeout := time.NewTimer(time.Second * 15)
|
||||
defer timeout.Stop()
|
||||
select {
|
||||
case <-timeout.C:
|
||||
log.ZWarn(ctx, "msg gateway graceful stop timeout", nil)
|
||||
case <-done:
|
||||
log.ZDebug(ctx, "msg gateway graceful stop done")
|
||||
}
|
||||
return context.Cause(ctx)
|
||||
}
|
||||
|
||||
var concurrentRequest = 3
|
||||
const concurrentRequest = 3
|
||||
|
||||
func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *Client) error {
|
||||
conns, err := ws.disCov.GetConns(ctx, ws.msgGatewayConfig.Discovery.RpcService.MessageGateway)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(conns) == 0 || (len(conns) == 1 && ws.disCov.IsSelfNode(conns[0])) {
|
||||
return nil
|
||||
}
|
||||
|
||||
wg := errgroup.Group{}
|
||||
wg.SetLimit(concurrentRequest)
|
||||
|
||||
// Online push user online message to other node
|
||||
for _, v := range conns {
|
||||
v := v
|
||||
log.ZDebug(ctx, " sendUserOnlineInfoToOtherNode conn ", "target", v.Target())
|
||||
if v.Target() == ws.disCov.GetSelfConnTarget() {
|
||||
log.ZDebug(ctx, "Filter out this node", "node", v.Target())
|
||||
log.ZDebug(ctx, "sendUserOnlineInfoToOtherNode conn")
|
||||
if ws.disCov.IsSelfNode(v) {
|
||||
log.ZDebug(ctx, "Filter out this node")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -242,7 +246,7 @@ func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *C
|
||||
PlatformID: int32(client.PlatformID), Token: client.token,
|
||||
})
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "MultiTerminalLoginCheck err", err, "node", v.Target())
|
||||
log.ZWarn(ctx, "MultiTerminalLoginCheck err", err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
@@ -263,6 +267,9 @@ func (ws *WsServer) registerClient(client *Client) {
|
||||
oldClients []*Client
|
||||
)
|
||||
oldClients, userOK, clientOK = ws.clients.Get(client.UserID, client.PlatformID)
|
||||
|
||||
log.ZInfo(client.ctx, "registerClient", "userID", client.UserID, "platformID", client.PlatformID)
|
||||
|
||||
if !userOK {
|
||||
ws.clients.Set(client.UserID, client)
|
||||
log.ZDebug(client.ctx, "user not exist", "userID", client.UserID, "platformID", client.PlatformID)
|
||||
@@ -343,17 +350,51 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
|
||||
}
|
||||
}
|
||||
|
||||
// If reconnect: When multiple msgGateway instances are deployed, a client may disconnect from instance A and reconnect to instance B.
|
||||
// During this process, instance A might still be executing, resulting in two clients with the same token existing simultaneously.
|
||||
// This situation needs to be filtered to prevent duplicate clients.
|
||||
checkSameTokenFunc := func(oldClients []*Client) []*Client {
|
||||
var clientsNeedToKick []*Client
|
||||
|
||||
for _, c := range oldClients {
|
||||
if c.token == newClient.token {
|
||||
log.ZDebug(newClient.ctx, "token is same, not kick",
|
||||
"userID", newClient.UserID,
|
||||
"platformID", newClient.PlatformID,
|
||||
"token", newClient.token)
|
||||
continue
|
||||
}
|
||||
|
||||
clientsNeedToKick = append(clientsNeedToKick, c)
|
||||
}
|
||||
|
||||
return clientsNeedToKick
|
||||
}
|
||||
|
||||
switch ws.msgGatewayConfig.Share.MultiLogin.Policy {
|
||||
case constant.DefalutNotKick:
|
||||
case constant.PCAndOther:
|
||||
if constant.PlatformIDToClass(newClient.PlatformID) == constant.TerminalPC {
|
||||
return
|
||||
}
|
||||
clients, ok := ws.clients.GetAll(newClient.UserID)
|
||||
clientOK = ok
|
||||
oldClients = make([]*Client, 0, len(clients))
|
||||
for _, c := range clients {
|
||||
if constant.PlatformIDToClass(c.PlatformID) == constant.TerminalPC {
|
||||
continue
|
||||
}
|
||||
oldClients = append(oldClients, c)
|
||||
}
|
||||
|
||||
fallthrough
|
||||
case constant.AllLoginButSameTermKick:
|
||||
if !clientOK {
|
||||
return
|
||||
}
|
||||
|
||||
oldClients = checkSameTokenFunc(oldClients)
|
||||
|
||||
ws.clients.DeleteClients(newClient.UserID, oldClients)
|
||||
for _, c := range oldClients {
|
||||
err := c.KickOnlineMessage()
|
||||
@@ -361,6 +402,7 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
|
||||
log.ZWarn(c.ctx, "KickOnlineMessage", err)
|
||||
}
|
||||
}
|
||||
|
||||
ctx := mcontext.WithMustInfoCtx(
|
||||
[]string{newClient.ctx.GetOperationID(), newClient.ctx.GetUserID(),
|
||||
constant.PlatformIDToName(newClient.PlatformID), newClient.ctx.GetConnID()},
|
||||
@@ -379,14 +421,17 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var (
|
||||
kickClients []*Client
|
||||
)
|
||||
|
||||
var kickClients []*Client
|
||||
for _, client := range clients {
|
||||
if constant.PlatformIDToClass(client.PlatformID) == constant.PlatformIDToClass(newClient.PlatformID) {
|
||||
kickClients = append(kickClients, client)
|
||||
{
|
||||
kickClients = append(kickClients, client)
|
||||
}
|
||||
}
|
||||
}
|
||||
kickClients = checkSameTokenFunc(kickClients)
|
||||
|
||||
kickTokenFunc(kickClients)
|
||||
}
|
||||
}
|
||||
@@ -410,7 +455,7 @@ func (ws *WsServer) unregisterClient(client *Client) {
|
||||
// validateRespWithRequest checks if the response matches the expected userID and platformID.
|
||||
func (ws *WsServer) validateRespWithRequest(ctx *UserConnContext, resp *pbAuth.ParseTokenResp) error {
|
||||
userID := ctx.GetUserID()
|
||||
platformID := stringutil.StringToInt32(ctx.GetPlatformID())
|
||||
platformID := int32(ctx.GetPlatformID())
|
||||
if resp.UserID != userID {
|
||||
return servererrs.ErrTokenInvalid.WrapMsg(fmt.Sprintf("token uid %s != userID %s", resp.UserID, userID))
|
||||
}
|
||||
@@ -420,6 +465,29 @@ func (ws *WsServer) validateRespWithRequest(ctx *UserConnContext, resp *pbAuth.P
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ws *WsServer) handlerError(ctx *UserConnContext, w http.ResponseWriter, r *http.Request, err error) {
|
||||
if !ctx.ShouldSendResp() {
|
||||
httpError(ctx, err)
|
||||
return
|
||||
}
|
||||
// the browser cannot get the response of upgrade failure
|
||||
data, err := json.Marshal(apiresp.ParseError(err))
|
||||
if err != nil {
|
||||
log.ZError(ctx, "json marshal failed", err)
|
||||
return
|
||||
}
|
||||
conn, upgradeErr := ws.websocket.Upgrade(w, r, nil)
|
||||
if upgradeErr != nil {
|
||||
log.ZWarn(ctx, "websocket upgrade failed", upgradeErr, "respErr", err, "resp", string(data))
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||
log.ZWarn(ctx, "WriteMessage failed", err, "respErr", err, "resp", string(data))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Create a new connection context
|
||||
connContext := newContext(w, r)
|
||||
@@ -427,7 +495,7 @@ func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Check if the current number of online user connections exceeds the maximum limit
|
||||
if ws.onlineUserConnNum.Load() >= ws.wsMaxConnNum {
|
||||
// If it exceeds the maximum connection number, return an error via HTTP and stop processing
|
||||
httpError(connContext, servererrs.ErrConnOverMaxNumLimit.WrapMsg("over max conn num limit"))
|
||||
ws.handlerError(connContext, w, r, servererrs.ErrConnOverMaxNumLimit.WrapMsg("over max conn num limit"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -435,26 +503,14 @@ func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
err := connContext.ParseEssentialArgs()
|
||||
if err != nil {
|
||||
// If there's an error during parsing, return an error via HTTP and stop processing
|
||||
|
||||
httpError(connContext, err)
|
||||
ws.handlerError(connContext, w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Call the authentication client to parse the Token obtained from the context
|
||||
resp, err := ws.authClient.ParseToken(connContext, connContext.GetToken())
|
||||
if err != nil {
|
||||
// If there's an error parsing the Token, decide whether to send the error message via WebSocket based on the context flag
|
||||
shouldSendError := connContext.ShouldSendResp()
|
||||
if shouldSendError {
|
||||
// Create a WebSocket connection object and attempt to send the error message via WebSocket
|
||||
wsLongConn := newGWebSocket(WebSocket, ws.handshakeTimeout, ws.writeBufferSize)
|
||||
if err := wsLongConn.RespondWithError(err, w, r); err == nil {
|
||||
// If the error message is successfully sent via WebSocket, stop processing
|
||||
return
|
||||
}
|
||||
}
|
||||
// If sending via WebSocket is not required or fails, return the error via HTTP and stop processing
|
||||
httpError(connContext, err)
|
||||
ws.handlerError(connContext, w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -462,32 +518,30 @@ func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
err = ws.validateRespWithRequest(connContext, resp)
|
||||
if err != nil {
|
||||
// If validation fails, return an error via HTTP and stop processing
|
||||
httpError(connContext, err)
|
||||
ws.handlerError(connContext, w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
log.ZDebug(connContext, "new conn", "token", connContext.GetToken())
|
||||
// Create a WebSocket long connection object
|
||||
wsLongConn := newGWebSocket(WebSocket, ws.handshakeTimeout, ws.writeBufferSize)
|
||||
if err := wsLongConn.GenerateLongConn(w, r); err != nil {
|
||||
//If the creation of the long connection fails, the error is handled internally during the handshake process.
|
||||
log.ZWarn(connContext, "long connection fails", err)
|
||||
conn, err := ws.websocket.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.ZWarn(connContext, "websocket upgrade failed", err)
|
||||
return
|
||||
} else {
|
||||
// Check if a normal response should be sent via WebSocket
|
||||
shouldSendSuccessResp := connContext.ShouldSendResp()
|
||||
if shouldSendSuccessResp {
|
||||
// Attempt to send a success message through WebSocket
|
||||
if err := wsLongConn.RespondWithSuccess(); err != nil {
|
||||
// If the success message is successfully sent, end further processing
|
||||
return
|
||||
}
|
||||
}
|
||||
if connContext.ShouldSendResp() {
|
||||
if err := conn.WriteMessage(websocket.TextMessage, wsSuccessResponse); err != nil {
|
||||
log.ZWarn(connContext, "WriteMessage first response", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve a client object from the client pool, reset its state, and associate it with the current WebSocket long connection
|
||||
client := ws.clientPool.Get().(*Client)
|
||||
client.ResetClient(connContext, wsLongConn, ws)
|
||||
log.ZDebug(connContext, "new conn", "token", connContext.GetToken())
|
||||
|
||||
var pingInterval time.Duration
|
||||
if connContext.GetPlatformID() == constant.WebPlatformID {
|
||||
pingInterval = pingPeriod
|
||||
}
|
||||
|
||||
client := new(Client)
|
||||
client.ResetClient(connContext, NewWebSocketClientConn(conn, maxMessageSize, pongWait, pingInterval), ws)
|
||||
|
||||
// Register the client with the server and start message processing
|
||||
ws.registerChan <- client
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package msgtransfer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/apistruct"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||||
)
|
||||
|
||||
func toCommonCallback(ctx context.Context, msg *sdkws.MsgData, command string) cbapi.CommonCallbackReq {
|
||||
return cbapi.CommonCallbackReq{
|
||||
SendID: msg.SendID,
|
||||
ServerMsgID: msg.ServerMsgID,
|
||||
CallbackCommand: command,
|
||||
ClientMsgID: msg.ClientMsgID,
|
||||
OperationID: mcontext.GetOperationID(ctx),
|
||||
SenderPlatformID: msg.SenderPlatformID,
|
||||
SenderNickname: msg.SenderNickname,
|
||||
SessionType: msg.SessionType,
|
||||
MsgFrom: msg.MsgFrom,
|
||||
ContentType: msg.ContentType,
|
||||
Status: msg.Status,
|
||||
SendTime: msg.SendTime,
|
||||
CreateTime: msg.CreateTime,
|
||||
AtUserIDList: msg.AtUserIDList,
|
||||
SenderFaceURL: msg.SenderFaceURL,
|
||||
Content: GetContent(msg),
|
||||
Seq: uint32(msg.Seq),
|
||||
Ex: msg.Ex,
|
||||
}
|
||||
}
|
||||
|
||||
func GetContent(msg *sdkws.MsgData) string {
|
||||
if msg.ContentType >= constant.NotificationBegin && msg.ContentType <= constant.NotificationEnd {
|
||||
var tips sdkws.TipsComm
|
||||
_ = proto.Unmarshal(msg.Content, &tips)
|
||||
content := tips.JsonDetail
|
||||
return content
|
||||
} else {
|
||||
return string(msg.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func (mc *OnlineHistoryMongoConsumerHandler) webhookAfterMsgSaveDB(ctx context.Context, after *config.AfterConfig, msg *sdkws.MsgData) {
|
||||
if !filterAfterMsg(msg, after) {
|
||||
return
|
||||
}
|
||||
|
||||
cbReq := &cbapi.CallbackAfterMsgSaveDBReq{
|
||||
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterMsgSaveDBCommand),
|
||||
}
|
||||
|
||||
switch msg.SessionType {
|
||||
case constant.SingleChatType, constant.NotificationChatType:
|
||||
cbReq.RecvID = msg.RecvID
|
||||
case constant.ReadGroupChatType:
|
||||
cbReq.GroupID = msg.GroupID
|
||||
default:
|
||||
}
|
||||
|
||||
mc.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterMsgSaveDBResp{}, after, buildKeyMsgDataQuery(msg))
|
||||
}
|
||||
|
||||
func buildKeyMsgDataQuery(msg *sdkws.MsgData) map[string]string {
|
||||
keyMsgData := apistruct.KeyMsgData{
|
||||
SendID: msg.SendID,
|
||||
RecvID: msg.RecvID,
|
||||
GroupID: msg.GroupID,
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
webhook.Key: base64.StdEncoding.EncodeToString(stringutil.StructToJsonBytes(keyMsgData)),
|
||||
}
|
||||
}
|
||||
|
||||
func filterAfterMsg(msg *sdkws.MsgData, after *config.AfterConfig) bool {
|
||||
return filterMsg(msg, after.AttentionIds, after.DeniedTypes)
|
||||
}
|
||||
|
||||
func filterMsg(msg *sdkws.MsgData, attentionIds []string, deniedTypes []int32) bool {
|
||||
// According to the attentionIds configuration, only some users are sent
|
||||
if len(attentionIds) != 0 && msg.ContentType == constant.SingleChatType && !datautil.Contain(msg.RecvID, attentionIds...) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(attentionIds) != 0 && msg.ContentType == constant.ReadGroupChatType && !datautil.Contain(msg.GroupID, attentionIds...) {
|
||||
return false
|
||||
}
|
||||
|
||||
if defaultDeniedTypes(msg.ContentType) {
|
||||
return false
|
||||
}
|
||||
|
||||
if len(deniedTypes) != 0 && datautil.Contain(msg.ContentType, deniedTypes...) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func defaultDeniedTypes(contentType int32) bool {
|
||||
if contentType >= constant.NotificationBegin && contentType <= constant.NotificationEnd {
|
||||
return true
|
||||
}
|
||||
if contentType == constant.Typing {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
+93
-154
@@ -16,51 +16,35 @@ package msgtransfer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"syscall"
|
||||
|
||||
disetcd "github.com/openimsdk/open-im-server/v3/pkg/common/discovery/etcd"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/discovery/etcd"
|
||||
"github.com/openimsdk/tools/utils/jsonutil"
|
||||
"github.com/openimsdk/tools/utils/network"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/mcache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/redisutil"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/mqbuild"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/mq"
|
||||
"github.com/openimsdk/tools/utils/runtimeenv"
|
||||
|
||||
conf "github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
discRegister "github.com/openimsdk/open-im-server/v3/pkg/common/discovery"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mw"
|
||||
"github.com/openimsdk/tools/system/program"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
type MsgTransfer struct {
|
||||
historyConsumer mq.Consumer
|
||||
historyMongoConsumer mq.Consumer
|
||||
// This consumer aggregated messages, subscribed to the topic:toRedis,
|
||||
// the message is stored in redis, Incr Redis, and then the message is sent to toPush topic for push,
|
||||
// and the message is sent to toMongo topic for persistence
|
||||
historyCH *OnlineHistoryRedisConsumerHandler
|
||||
historyHandler *OnlineHistoryRedisConsumerHandler
|
||||
//This consumer handle message to mongo
|
||||
historyMongoCH *OnlineHistoryMongoConsumerHandler
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
runTimeEnv string
|
||||
historyMongoHandler *OnlineHistoryMongoConsumerHandler
|
||||
ctx context.Context
|
||||
//cancel context.CancelFunc
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -71,48 +55,59 @@ type Config struct {
|
||||
Share conf.Share
|
||||
WebhooksConfig conf.Webhooks
|
||||
Discovery conf.Discovery
|
||||
Index conf.Index
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, index int, config *Config) error {
|
||||
runTimeEnv := runtimeenv.PrintRuntimeEnvironment()
|
||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
|
||||
builder := mqbuild.NewBuilder(&config.KafkaConfig)
|
||||
|
||||
log.CInfo(ctx, "MSG-TRANSFER server is initializing", "runTimeEnv", runTimeEnv, "prometheusPorts",
|
||||
config.MsgTransfer.Prometheus.Ports, "index", index)
|
||||
|
||||
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build())
|
||||
log.CInfo(ctx, "MSG-TRANSFER server is initializing", "runTimeEnv", runtimeenv.RuntimeEnvironment(), "prometheusPorts",
|
||||
config.MsgTransfer.Prometheus.Ports, "index", config.Index)
|
||||
dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
|
||||
mgocli, err := dbb.Mongo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build())
|
||||
rdb, err := dbb.Redis(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client, err := discRegister.NewDiscoveryRegister(&config.Discovery, runTimeEnv, nil)
|
||||
|
||||
//if config.Discovery.Enable == conf.ETCD {
|
||||
// cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), []string{
|
||||
// config.MsgTransfer.GetConfigFileName(),
|
||||
// config.RedisConfig.GetConfigFileName(),
|
||||
// config.MongodbConfig.GetConfigFileName(),
|
||||
// config.KafkaConfig.GetConfigFileName(),
|
||||
// config.Share.GetConfigFileName(),
|
||||
// config.WebhooksConfig.GetConfigFileName(),
|
||||
// config.Discovery.GetConfigFileName(),
|
||||
// conf.LogConfigFileName,
|
||||
// })
|
||||
// cm.Watch(ctx)
|
||||
//}
|
||||
mongoProducer, err := builder.GetTopicProducer(ctx, config.KafkaConfig.ToMongoTopic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()),
|
||||
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin")))
|
||||
|
||||
if config.Discovery.Enable == conf.ETCD {
|
||||
cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), []string{
|
||||
config.MsgTransfer.GetConfigFileName(),
|
||||
config.RedisConfig.GetConfigFileName(),
|
||||
config.MongodbConfig.GetConfigFileName(),
|
||||
config.KafkaConfig.GetConfigFileName(),
|
||||
config.Share.GetConfigFileName(),
|
||||
config.WebhooksConfig.GetConfigFileName(),
|
||||
config.Discovery.GetConfigFileName(),
|
||||
conf.LogConfigFileName,
|
||||
})
|
||||
cm.Watch(ctx)
|
||||
pushProducer, err := builder.GetTopicProducer(ctx, config.KafkaConfig.ToPushTopic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgDocModel, err := mgo.NewMsgMongo(mgocli.GetDB())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgModel := redis.NewMsgCache(rdb, msgDocModel)
|
||||
var msgModel cache.MsgCache
|
||||
if rdb == nil {
|
||||
cm, err := mgo.NewCacheMgo(mgocli.GetDB())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgModel = mcache.NewMsgCache(cm, msgDocModel)
|
||||
} else {
|
||||
msgModel = redis.NewMsgCache(rdb, msgDocModel)
|
||||
}
|
||||
seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB())
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -123,124 +118,68 @@ func Start(ctx context.Context, index int, config *Config) error {
|
||||
return err
|
||||
}
|
||||
seqUserCache := redis.NewSeqUserCacheRedis(rdb, seqUser)
|
||||
msgTransferDatabase, err := controller.NewMsgTransferDatabase(msgDocModel, msgModel, seqUserCache, seqConversationCache, &config.KafkaConfig)
|
||||
msgTransferDatabase, err := controller.NewMsgTransferDatabase(msgDocModel, msgModel, seqUserCache, seqConversationCache, mongoProducer, pushProducer)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
historyCH, err := NewOnlineHistoryRedisConsumerHandler(ctx, client, config, msgTransferDatabase)
|
||||
historyConsumer, err := builder.GetTopicConsumer(ctx, config.KafkaConfig.ToRedisTopic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
historyMongoCH, err := NewOnlineHistoryMongoConsumerHandler(&config.KafkaConfig, msgTransferDatabase)
|
||||
historyMongoConsumer, err := builder.GetTopicConsumer(ctx, config.KafkaConfig.ToMongoTopic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
historyHandler, err := NewOnlineHistoryRedisConsumerHandler(ctx, client, config, msgTransferDatabase)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
historyMongoHandler := NewOnlineHistoryMongoConsumerHandler(msgTransferDatabase, config)
|
||||
|
||||
msgTransfer := &MsgTransfer{
|
||||
historyCH: historyCH,
|
||||
historyMongoCH: historyMongoCH,
|
||||
runTimeEnv: runTimeEnv,
|
||||
historyConsumer: historyConsumer,
|
||||
historyMongoConsumer: historyMongoConsumer,
|
||||
historyHandler: historyHandler,
|
||||
historyMongoHandler: historyMongoHandler,
|
||||
}
|
||||
|
||||
return msgTransfer.Start(index, config, client)
|
||||
return msgTransfer.Start(ctx)
|
||||
}
|
||||
|
||||
func (m *MsgTransfer) Start(index int, config *Config, client discovery.SvcDiscoveryRegistry) error {
|
||||
m.ctx, m.cancel = context.WithCancel(context.Background())
|
||||
var (
|
||||
netDone = make(chan struct{}, 1)
|
||||
netErr error
|
||||
)
|
||||
func (m *MsgTransfer) Start(ctx context.Context) error {
|
||||
var cancel context.CancelCauseFunc
|
||||
m.ctx, cancel = context.WithCancelCause(ctx)
|
||||
|
||||
go m.historyCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyCH)
|
||||
go m.historyMongoCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyMongoCH)
|
||||
go m.historyCH.HandleUserHasReadSeqMessages(m.ctx)
|
||||
err := m.historyCH.redisMessageBatches.Start()
|
||||
go func() {
|
||||
for {
|
||||
if err := m.historyConsumer.Subscribe(m.ctx, m.historyHandler.HandlerRedisMessage); err != nil {
|
||||
cancel(fmt.Errorf("history consumer %w", err))
|
||||
log.ZError(m.ctx, "historyConsumer err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
fn := func(msg mq.Message) error {
|
||||
m.historyMongoHandler.HandleChatWs2Mongo(msg)
|
||||
return nil
|
||||
}
|
||||
for {
|
||||
if err := m.historyMongoConsumer.Subscribe(m.ctx, fn); err != nil {
|
||||
cancel(fmt.Errorf("history mongo consumer %w", err))
|
||||
log.ZError(m.ctx, "historyMongoConsumer err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go m.historyHandler.HandleUserHasReadSeqMessages(m.ctx)
|
||||
|
||||
err := m.historyHandler.redisMessageBatches.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
registerIP, err := network.GetRpcRegisterIP("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
getAutoPort := func() (net.Listener, int, error) {
|
||||
registerAddr := net.JoinHostPort(registerIP, "0")
|
||||
listener, err := net.Listen("tcp", registerAddr)
|
||||
if err != nil {
|
||||
return nil, 0, errs.WrapMsg(err, "listen err", "registerAddr", registerAddr)
|
||||
}
|
||||
_, portStr, _ := net.SplitHostPort(listener.Addr().String())
|
||||
port, _ := strconv.Atoi(portStr)
|
||||
return listener, port, nil
|
||||
}
|
||||
|
||||
if config.MsgTransfer.Prometheus.AutoSetPorts && config.Discovery.Enable != conf.ETCD {
|
||||
return errs.New("only etcd support autoSetPorts", "RegisterName", "api").Wrap()
|
||||
}
|
||||
|
||||
if config.MsgTransfer.Prometheus.Enable {
|
||||
var (
|
||||
listener net.Listener
|
||||
prometheusPort int
|
||||
)
|
||||
|
||||
if config.MsgTransfer.Prometheus.AutoSetPorts {
|
||||
listener, prometheusPort, err = getAutoPort()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
etcdClient := client.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
|
||||
|
||||
_, err = etcdClient.Put(context.TODO(), prommetrics.BuildDiscoveryKey(prommetrics.MessageTransferKeyName), jsonutil.StructToJsonString(prommetrics.BuildDefaultTarget(registerIP, prometheusPort)))
|
||||
if err != nil {
|
||||
return errs.WrapMsg(err, "etcd put err")
|
||||
}
|
||||
} else {
|
||||
prometheusPort, err = datautil.GetElemByIndex(config.MsgTransfer.Prometheus.Ports, index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
listener, err = net.Listen("tcp", fmt.Sprintf(":%d", prometheusPort))
|
||||
if err != nil {
|
||||
return errs.WrapMsg(err, "listen err", "addr", fmt.Sprintf(":%d", prometheusPort))
|
||||
}
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.ZPanic(m.ctx, "MsgTransfer Start Panic", errs.ErrPanic(r))
|
||||
}
|
||||
}()
|
||||
if err := prommetrics.TransferInit(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||
netErr = errs.WrapMsg(err, "prometheus start error", "prometheusPort", prometheusPort)
|
||||
netDone <- struct{}{}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
sigs := make(chan os.Signal, 1)
|
||||
signal.Notify(sigs, syscall.SIGTERM)
|
||||
select {
|
||||
case <-sigs:
|
||||
program.SIGTERMExit()
|
||||
// graceful close kafka client.
|
||||
m.cancel()
|
||||
m.historyCH.redisMessageBatches.Close()
|
||||
m.historyCH.Close()
|
||||
m.historyCH.historyConsumerGroup.Close()
|
||||
m.historyMongoCH.historyConsumerGroup.Close()
|
||||
return nil
|
||||
case <-netDone:
|
||||
m.cancel()
|
||||
m.historyCH.redisMessageBatches.Close()
|
||||
m.historyCH.Close()
|
||||
m.historyCH.historyConsumerGroup.Close()
|
||||
m.historyMongoCH.historyConsumerGroup.Close()
|
||||
close(netDone)
|
||||
return netErr
|
||||
}
|
||||
<-m.ctx.Done()
|
||||
return context.Cause(m.ctx)
|
||||
}
|
||||
|
||||
@@ -18,15 +18,18 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/openimsdk/tools/mq"
|
||||
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
|
||||
"github.com/go-redis/redis"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||
@@ -37,9 +40,7 @@ import (
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/mq/kafka"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -64,9 +65,7 @@ type userHasReadSeq struct {
|
||||
}
|
||||
|
||||
type OnlineHistoryRedisConsumerHandler struct {
|
||||
historyConsumerGroup *kafka.MConsumerGroup
|
||||
|
||||
redisMessageBatches *batcher.Batcher[sarama.ConsumerMessage]
|
||||
redisMessageBatches *batcher.Batcher[ConsumerMessage]
|
||||
|
||||
msgTransferDatabase controller.MsgTransferDatabase
|
||||
conversationUserHasReadChan chan *userHasReadSeq
|
||||
@@ -76,12 +75,14 @@ type OnlineHistoryRedisConsumerHandler struct {
|
||||
conversationClient *rpcli.ConversationClient
|
||||
}
|
||||
|
||||
func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.SvcDiscoveryRegistry, config *Config, database controller.MsgTransferDatabase) (*OnlineHistoryRedisConsumerHandler, error) {
|
||||
kafkaConf := config.KafkaConfig
|
||||
historyConsumerGroup, err := kafka.NewMConsumerGroup(kafkaConf.Build(), kafkaConf.ToRedisGroupID, []string{kafkaConf.ToRedisTopic}, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
type ConsumerMessage struct {
|
||||
Ctx context.Context
|
||||
Key string
|
||||
Value []byte
|
||||
Raw mq.Message
|
||||
}
|
||||
|
||||
func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.Conn, config *Config, database controller.MsgTransferDatabase) (*OnlineHistoryRedisConsumerHandler, error) {
|
||||
groupConn, err := client.GetConn(ctx, config.Discovery.RpcService.Group)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -97,7 +98,7 @@ func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.
|
||||
och.conversationClient = rpcli.NewConversationClient(conversationConn)
|
||||
och.wg.Add(1)
|
||||
|
||||
b := batcher.New[sarama.ConsumerMessage](
|
||||
b := batcher.New[ConsumerMessage](
|
||||
batcher.WithSize(size),
|
||||
batcher.WithWorker(worker),
|
||||
batcher.WithInterval(interval),
|
||||
@@ -109,16 +110,20 @@ func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.
|
||||
hashCode := stringutil.GetHashCode(key)
|
||||
return int(hashCode) % och.redisMessageBatches.Worker()
|
||||
}
|
||||
b.Key = func(consumerMessage *sarama.ConsumerMessage) string {
|
||||
return string(consumerMessage.Key)
|
||||
b.Key = func(consumerMessage *ConsumerMessage) string {
|
||||
return consumerMessage.Key
|
||||
}
|
||||
b.Do = och.do
|
||||
och.redisMessageBatches = b
|
||||
och.historyConsumerGroup = historyConsumerGroup
|
||||
|
||||
och.redisMessageBatches.OnComplete = func(lastMessage *ConsumerMessage, totalCount int) {
|
||||
lastMessage.Raw.Mark()
|
||||
lastMessage.Raw.Commit()
|
||||
}
|
||||
|
||||
return &och, nil
|
||||
}
|
||||
func (och *OnlineHistoryRedisConsumerHandler) do(ctx context.Context, channelID int, val *batcher.Msg[sarama.ConsumerMessage]) {
|
||||
func (och *OnlineHistoryRedisConsumerHandler) do(ctx context.Context, channelID int, val *batcher.Msg[ConsumerMessage]) {
|
||||
ctx = mcontext.WithTriggerIDContext(ctx, val.TriggerID())
|
||||
ctxMessages := och.parseConsumerMessages(ctx, val.Val())
|
||||
ctx = withAggregationCtx(ctx, ctxMessages)
|
||||
@@ -138,58 +143,53 @@ func (och *OnlineHistoryRedisConsumerHandler) do(ctx context.Context, channelID
|
||||
|
||||
func (och *OnlineHistoryRedisConsumerHandler) doSetReadSeq(ctx context.Context, msgs []*ContextMsg) {
|
||||
|
||||
var conversationID string
|
||||
var userSeqMap map[string]int64
|
||||
// Outer map: conversationID -> (userID -> maxHasReadSeq)
|
||||
conversationUserSeq := make(map[string]map[string]int64)
|
||||
|
||||
for _, msg := range msgs {
|
||||
if msg.message.ContentType != constant.HasReadReceipt {
|
||||
continue
|
||||
}
|
||||
var elem sdkws.NotificationElem
|
||||
if err := json.Unmarshal(msg.message.Content, &elem); err != nil {
|
||||
log.ZWarn(ctx, "handlerConversationRead Unmarshal NotificationElem msg err", err, "msg", msg)
|
||||
log.ZWarn(ctx, "Unmarshal NotificationElem error", err, "msg", msg)
|
||||
continue
|
||||
}
|
||||
var tips sdkws.MarkAsReadTips
|
||||
if err := json.Unmarshal([]byte(elem.Detail), &tips); err != nil {
|
||||
log.ZWarn(ctx, "handlerConversationRead Unmarshal MarkAsReadTips msg err", err, "msg", msg)
|
||||
log.ZWarn(ctx, "Unmarshal MarkAsReadTips error", err, "msg", msg)
|
||||
continue
|
||||
}
|
||||
//The conversation ID for each batch of messages processed by the batcher is the same.
|
||||
conversationID = tips.ConversationID
|
||||
if len(tips.Seqs) > 0 {
|
||||
for _, seq := range tips.Seqs {
|
||||
if tips.HasReadSeq < seq {
|
||||
tips.HasReadSeq = seq
|
||||
}
|
||||
}
|
||||
clear(tips.Seqs)
|
||||
tips.Seqs = nil
|
||||
}
|
||||
if tips.HasReadSeq < 0 {
|
||||
if len(tips.ConversationID) == 0 || tips.HasReadSeq < 0 {
|
||||
continue
|
||||
}
|
||||
if userSeqMap == nil {
|
||||
userSeqMap = make(map[string]int64)
|
||||
}
|
||||
|
||||
if userSeqMap[tips.MarkAsReadUserID] > tips.HasReadSeq {
|
||||
continue
|
||||
// Calculate the max seq from tips.Seqs
|
||||
for _, seq := range tips.Seqs {
|
||||
if tips.HasReadSeq < seq {
|
||||
tips.HasReadSeq = seq
|
||||
}
|
||||
}
|
||||
|
||||
if _, ok := conversationUserSeq[tips.ConversationID]; !ok {
|
||||
conversationUserSeq[tips.ConversationID] = make(map[string]int64)
|
||||
}
|
||||
if conversationUserSeq[tips.ConversationID][tips.MarkAsReadUserID] < tips.HasReadSeq {
|
||||
conversationUserSeq[tips.ConversationID][tips.MarkAsReadUserID] = tips.HasReadSeq
|
||||
}
|
||||
userSeqMap[tips.MarkAsReadUserID] = tips.HasReadSeq
|
||||
}
|
||||
if userSeqMap == nil {
|
||||
return
|
||||
}
|
||||
if len(conversationID) == 0 {
|
||||
log.ZWarn(ctx, "conversation err", nil, "conversationID", conversationID)
|
||||
}
|
||||
if err := och.msgTransferDatabase.SetHasReadSeqToDB(ctx, conversationID, userSeqMap); err != nil {
|
||||
log.ZWarn(ctx, "set read seq to db error", err, "conversationID", conversationID, "userSeqMap", userSeqMap)
|
||||
log.ZInfo(ctx, "doSetReadSeq", "conversationUserSeq", conversationUserSeq)
|
||||
|
||||
// persist to db
|
||||
for convID, userSeqMap := range conversationUserSeq {
|
||||
if err := och.msgTransferDatabase.SetHasReadSeqToDB(ctx, convID, userSeqMap); err != nil {
|
||||
log.ZWarn(ctx, "SetHasReadSeqToDB error", err, "conversationID", convID, "userSeqMap", userSeqMap)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (och *OnlineHistoryRedisConsumerHandler) parseConsumerMessages(ctx context.Context, consumerMessages []*sarama.ConsumerMessage) []*ContextMsg {
|
||||
func (och *OnlineHistoryRedisConsumerHandler) parseConsumerMessages(ctx context.Context, consumerMessages []*ConsumerMessage) []*ContextMsg {
|
||||
var ctxMessages []*ContextMsg
|
||||
for i := 0; i < len(consumerMessages); i++ {
|
||||
ctxMsg := &ContextMsg{}
|
||||
@@ -199,16 +199,9 @@ func (och *OnlineHistoryRedisConsumerHandler) parseConsumerMessages(ctx context.
|
||||
log.ZWarn(ctx, "msg_transfer Unmarshal msg err", err, string(consumerMessages[i].Value))
|
||||
continue
|
||||
}
|
||||
var arr []string
|
||||
for i, header := range consumerMessages[i].Headers {
|
||||
arr = append(arr, strconv.Itoa(i), string(header.Key), string(header.Value))
|
||||
}
|
||||
log.ZDebug(ctx, "consumer.kafka.GetContextWithMQHeader", "len", len(consumerMessages[i].Headers),
|
||||
"header", strings.Join(arr, ", "))
|
||||
ctxMsg.ctx = kafka.GetContextWithMQHeader(consumerMessages[i].Headers)
|
||||
ctxMsg.ctx = consumerMessages[i].Ctx
|
||||
ctxMsg.message = msgFromMQ
|
||||
log.ZDebug(ctx, "message parse finish", "message", msgFromMQ, "key",
|
||||
string(consumerMessages[i].Key))
|
||||
log.ZDebug(ctx, "message parse finish", "message", msgFromMQ, "key", consumerMessages[i].Key)
|
||||
ctxMessages = append(ctxMessages, ctxMsg)
|
||||
}
|
||||
return ctxMessages
|
||||
@@ -383,7 +376,9 @@ func (och *OnlineHistoryRedisConsumerHandler) Close() {
|
||||
func (och *OnlineHistoryRedisConsumerHandler) toPushTopic(ctx context.Context, key, conversationID string, msgs []*ContextMsg) {
|
||||
for _, v := range msgs {
|
||||
log.ZDebug(ctx, "push msg to topic", "msg", v.message.String())
|
||||
_, _, _ = och.msgTransferDatabase.MsgToPushMQ(v.ctx, key, conversationID, v.message)
|
||||
if err := och.msgTransferDatabase.MsgToPushMQ(v.ctx, key, conversationID, v.message); err != nil {
|
||||
log.ZError(ctx, "msg to push topic error", err, "msg", v.message.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -401,35 +396,10 @@ func withAggregationCtx(ctx context.Context, values []*ContextMsg) context.Conte
|
||||
return mcontext.SetOperationID(ctx, allMessageOperationID)
|
||||
}
|
||||
|
||||
func (och *OnlineHistoryRedisConsumerHandler) Setup(_ sarama.ConsumerGroupSession) error { return nil }
|
||||
func (och *OnlineHistoryRedisConsumerHandler) Cleanup(_ sarama.ConsumerGroupSession) error {
|
||||
func (och *OnlineHistoryRedisConsumerHandler) HandlerRedisMessage(msg mq.Message) error { // a instance in the consumer group
|
||||
err := och.redisMessageBatches.Put(msg.Context(), &ConsumerMessage{Ctx: msg.Context(), Key: msg.Key(), Value: msg.Value(), Raw: msg})
|
||||
if err != nil {
|
||||
log.ZWarn(msg.Context(), "put msg to error", err, "key", msg.Key(), "value", msg.Value())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (och *OnlineHistoryRedisConsumerHandler) ConsumeClaim(session sarama.ConsumerGroupSession,
|
||||
claim sarama.ConsumerGroupClaim) error { // a instance in the consumer group
|
||||
log.ZDebug(context.Background(), "online new session msg come", "highWaterMarkOffset",
|
||||
claim.HighWaterMarkOffset(), "topic", claim.Topic(), "partition", claim.Partition())
|
||||
och.redisMessageBatches.OnComplete = func(lastMessage *sarama.ConsumerMessage, totalCount int) {
|
||||
session.MarkMessage(lastMessage, "")
|
||||
session.Commit()
|
||||
}
|
||||
for {
|
||||
select {
|
||||
case msg, ok := <-claim.Messages():
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
|
||||
if len(msg.Value) == 0 {
|
||||
continue
|
||||
}
|
||||
err := och.redisMessageBatches.Put(context.Background(), msg)
|
||||
if err != nil {
|
||||
log.ZWarn(context.Background(), "put msg to error", err, "msg", msg)
|
||||
}
|
||||
case <-session.Context().Done():
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,38 +15,34 @@
|
||||
package msgtransfer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/tools/mq"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
|
||||
pbmsg "github.com/openimsdk/protocol/msg"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mq/kafka"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type OnlineHistoryMongoConsumerHandler struct {
|
||||
historyConsumerGroup *kafka.MConsumerGroup
|
||||
msgTransferDatabase controller.MsgTransferDatabase
|
||||
msgTransferDatabase controller.MsgTransferDatabase
|
||||
config *Config
|
||||
webhookClient *webhook.Client
|
||||
}
|
||||
|
||||
func NewOnlineHistoryMongoConsumerHandler(kafkaConf *config.Kafka, database controller.MsgTransferDatabase) (*OnlineHistoryMongoConsumerHandler, error) {
|
||||
historyConsumerGroup, err := kafka.NewMConsumerGroup(kafkaConf.Build(), kafkaConf.ToMongoGroupID, []string{kafkaConf.ToMongoTopic}, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func NewOnlineHistoryMongoConsumerHandler(database controller.MsgTransferDatabase, config *Config) *OnlineHistoryMongoConsumerHandler {
|
||||
return &OnlineHistoryMongoConsumerHandler{
|
||||
msgTransferDatabase: database,
|
||||
config: config,
|
||||
webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL),
|
||||
}
|
||||
|
||||
mc := &OnlineHistoryMongoConsumerHandler{
|
||||
historyConsumerGroup: historyConsumerGroup,
|
||||
msgTransferDatabase: database,
|
||||
}
|
||||
return mc, nil
|
||||
}
|
||||
|
||||
func (mc *OnlineHistoryMongoConsumerHandler) handleChatWs2Mongo(ctx context.Context, cMsg *sarama.ConsumerMessage, key string, session sarama.ConsumerGroupSession) {
|
||||
msg := cMsg.Value
|
||||
func (mc *OnlineHistoryMongoConsumerHandler) HandleChatWs2Mongo(val mq.Message) {
|
||||
ctx := val.Context()
|
||||
key := val.Key()
|
||||
msg := val.Value()
|
||||
msgFromMQ := pbmsg.MsgDataToMongoByMQ{}
|
||||
err := proto.Unmarshal(msg, &msgFromMQ)
|
||||
if err != nil {
|
||||
@@ -54,46 +50,29 @@ func (mc *OnlineHistoryMongoConsumerHandler) handleChatWs2Mongo(ctx context.Cont
|
||||
return
|
||||
}
|
||||
if len(msgFromMQ.MsgData) == 0 {
|
||||
log.ZError(ctx, "msgFromMQ.MsgData is empty", nil, "cMsg", cMsg)
|
||||
log.ZError(ctx, "msgFromMQ.MsgData is empty", nil, "key", key, "msg", msg)
|
||||
return
|
||||
}
|
||||
log.ZDebug(ctx, "mongo consumer recv msg", "msgs", msgFromMQ.String())
|
||||
err = mc.msgTransferDatabase.BatchInsertChat2DB(ctx, msgFromMQ.ConversationID, msgFromMQ.MsgData, msgFromMQ.LastSeq)
|
||||
if err != nil {
|
||||
log.ZError(
|
||||
ctx,
|
||||
"single data insert to mongo err",
|
||||
err,
|
||||
"msg",
|
||||
msgFromMQ.MsgData,
|
||||
"conversationID",
|
||||
msgFromMQ.ConversationID,
|
||||
)
|
||||
log.ZError(ctx, "batch data insert to mongo err", err, "msg", msgFromMQ.MsgData, "conversationID", msgFromMQ.ConversationID)
|
||||
prommetrics.MsgInsertMongoFailedCounter.Inc()
|
||||
} else {
|
||||
prommetrics.MsgInsertMongoSuccessCounter.Inc()
|
||||
val.Mark()
|
||||
}
|
||||
var seqs []int64
|
||||
for _, msg := range msgFromMQ.MsgData {
|
||||
seqs = append(seqs, msg.Seq)
|
||||
|
||||
for _, msgData := range msgFromMQ.MsgData {
|
||||
mc.webhookAfterMsgSaveDB(ctx, &mc.config.WebhooksConfig.AfterMsgSaveDB, msgData)
|
||||
}
|
||||
}
|
||||
|
||||
func (*OnlineHistoryMongoConsumerHandler) Setup(_ sarama.ConsumerGroupSession) error { return nil }
|
||||
|
||||
func (*OnlineHistoryMongoConsumerHandler) Cleanup(_ sarama.ConsumerGroupSession) error { return nil }
|
||||
|
||||
func (mc *OnlineHistoryMongoConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { // an instance in the consumer group
|
||||
log.ZDebug(context.Background(), "online new session msg come", "highWaterMarkOffset",
|
||||
claim.HighWaterMarkOffset(), "topic", claim.Topic(), "partition", claim.Partition())
|
||||
for msg := range claim.Messages() {
|
||||
ctx := mc.historyConsumerGroup.GetContextFromMsg(msg)
|
||||
if len(msg.Value) != 0 {
|
||||
mc.handleChatWs2Mongo(ctx, msg, string(msg.Key), sess)
|
||||
} else {
|
||||
log.ZError(ctx, "mongo msg get from kafka but is nil", nil, "conversationID", msg.Key)
|
||||
}
|
||||
sess.MarkMessage(msg, "")
|
||||
}
|
||||
return nil
|
||||
|
||||
//var seqs []int64
|
||||
//for _, msg := range msgFromMQ.MsgData {
|
||||
// seqs = append(seqs, msg.Seq)
|
||||
//}
|
||||
//if err := mc.msgTransferDatabase.DeleteMessagesFromCache(ctx, msgFromMQ.ConversationID, seqs); err != nil {
|
||||
// log.ZError(ctx, "remove cache msg from redis err", err, "msg",
|
||||
// msgFromMQ.MsgData, "conversationID", msgFromMQ.ConversationID)
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package push
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||||
|
||||
@@ -16,12 +16,14 @@ package fcm
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
||||
"github.com/openimsdk/tools/utils/httputil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
||||
"github.com/openimsdk/tools/utils/httputil"
|
||||
|
||||
firebase "firebase.google.com/go/v4"
|
||||
"firebase.google.com/go/v4/messaging"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
@@ -133,7 +135,7 @@ func (f *Fcm) Push(ctx context.Context, userIDs []string, title, content string,
|
||||
unreadCountSum, err := f.cache.GetUserBadgeUnreadCountSum(ctx, userID)
|
||||
if err == nil && unreadCountSum != 0 {
|
||||
apns.Payload.Aps.Badge = &unreadCountSum
|
||||
} else if err == redis.Nil || unreadCountSum == 0 {
|
||||
} else if errors.Is(err, redis.Nil) || unreadCountSum == 0 {
|
||||
zero := 1
|
||||
apns.Payload.Aps.Badge = &zero
|
||||
} else {
|
||||
|
||||
@@ -18,6 +18,16 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
)
|
||||
|
||||
var (
|
||||
incOne = datautil.ToPtr("+1")
|
||||
addNum = "1"
|
||||
defaultStrategy = strategy{
|
||||
Default: 1,
|
||||
}
|
||||
msgCategory = "CATEGORY_MESSAGE"
|
||||
)
|
||||
|
||||
type Resp struct {
|
||||
@@ -58,7 +68,24 @@ type TaskResp struct {
|
||||
}
|
||||
|
||||
type Settings struct {
|
||||
TTL *int64 `json:"ttl"`
|
||||
TTL *int64 `json:"ttl"`
|
||||
Strategy strategy `json:"strategy"`
|
||||
}
|
||||
|
||||
type strategy struct {
|
||||
Default int64 `json:"default"`
|
||||
//IOS int64 `json:"ios"`
|
||||
//St int64 `json:"st"`
|
||||
//Hw int64 `json:"hw"`
|
||||
//Ho int64 `json:"ho"`
|
||||
//XM int64 `json:"xm"`
|
||||
//XMG int64 `json:"xmg"`
|
||||
//VV int64 `json:"vv"`
|
||||
//Op int64 `json:"op"`
|
||||
//OpG int64 `json:"opg"`
|
||||
//MZ int64 `json:"mz"`
|
||||
//HosHw int64 `json:"hoshw"`
|
||||
//WX int64 `json:"wx"`
|
||||
}
|
||||
|
||||
type Audience struct {
|
||||
@@ -112,6 +139,8 @@ type Notification struct {
|
||||
ChannelID string `json:"channelID"`
|
||||
ChannelName string `json:"ChannelName"`
|
||||
ClickType string `json:"click_type"`
|
||||
BadgeAddNum string `json:"badge_add_num"`
|
||||
Category string `json:"category"`
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
@@ -120,6 +149,7 @@ type Options struct {
|
||||
ChannelID string `json:"/message/android/notification/channel_id"`
|
||||
Sound string `json:"/message/android/notification/sound"`
|
||||
Importance string `json:"/message/android/notification/importance"`
|
||||
Category string `json:"/message/android/category"`
|
||||
} `json:"HW"`
|
||||
XM struct {
|
||||
ChannelID string `json:"/extra.channel_id"`
|
||||
@@ -140,6 +170,8 @@ func newPushReq(pushConf *config.Push, title, content string) PushReq {
|
||||
ClickType: "startapp",
|
||||
ChannelID: pushConf.GeTui.ChannelID,
|
||||
ChannelName: pushConf.GeTui.ChannelName,
|
||||
BadgeAddNum: addNum,
|
||||
Category: msgCategory,
|
||||
}}}
|
||||
return pushReq
|
||||
}
|
||||
@@ -156,6 +188,7 @@ func (pushReq *PushReq) setPushChannel(title string, body string) {
|
||||
notify := "notify"
|
||||
pushReq.PushChannel.Ios.NotificationType = ¬ify
|
||||
pushReq.PushChannel.Ios.Aps.Sound = "default"
|
||||
pushReq.PushChannel.Ios.AutoBadge = incOne
|
||||
pushReq.PushChannel.Ios.Aps.Alert = Alert{
|
||||
Title: title,
|
||||
Body: body,
|
||||
@@ -172,7 +205,8 @@ func (pushReq *PushReq) setPushChannel(title string, body string) {
|
||||
ChannelID string `json:"/message/android/notification/channel_id"`
|
||||
Sound string `json:"/message/android/notification/sound"`
|
||||
Importance string `json:"/message/android/notification/importance"`
|
||||
}{ChannelID: "RingRing4", Sound: "/raw/ring001", Importance: "NORMAL"},
|
||||
Category string `json:"/message/android/category"`
|
||||
}{ChannelID: "RingRing4", Sound: "/raw/ring001", Importance: "NORMAL", Category: "IM"},
|
||||
XM: struct {
|
||||
ChannelID string `json:"/extra.channel_id"`
|
||||
}{ChannelID: "high_system"},
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -70,7 +71,7 @@ func NewClient(pushConf *config.Push, cache cache.ThirdCache) *Client {
|
||||
func (g *Client) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error {
|
||||
token, err := g.cache.GetGetuiToken(ctx)
|
||||
if err != nil {
|
||||
if errs.Unwrap(err) == redis.Nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
log.ZDebug(ctx, "getui token not exist in redis")
|
||||
token, err = g.getTokenAndSave2Redis(ctx)
|
||||
if err != nil {
|
||||
@@ -144,7 +145,7 @@ func (g *Client) Auth(ctx context.Context, timeStamp int64) (token string, expir
|
||||
func (g *Client) GetTaskID(ctx context.Context, token string, pushReq PushReq) (string, error) {
|
||||
respTask := TaskResp{}
|
||||
ttl := int64(1000 * 60 * 5)
|
||||
pushReq.Settings = &Settings{TTL: &ttl}
|
||||
pushReq.Settings = &Settings{TTL: &ttl, Strategy: defaultStrategy}
|
||||
err := g.request(ctx, taskURL, pushReq, token, &respTask)
|
||||
if err != nil {
|
||||
return "", errs.Wrap(err)
|
||||
@@ -188,6 +189,7 @@ func (g *Client) postReturn(
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.ZDebug(ctx, "postReturn", "url", url, "header", header, "input", input, "timeout", timeout, "output", output)
|
||||
return output.parseError()
|
||||
}
|
||||
|
||||
@@ -204,7 +206,7 @@ func (g *Client) getTokenAndSave2Redis(ctx context.Context) (token string, err e
|
||||
}
|
||||
|
||||
func (g *Client) GetTaskIDAndSave2Redis(ctx context.Context, token string, pushReq PushReq) (taskID string, err error) {
|
||||
pushReq.Settings = &Settings{TTL: &g.taskIDTTL}
|
||||
pushReq.Settings = &Settings{TTL: &g.taskIDTTL, Strategy: defaultStrategy}
|
||||
taskID, err = g.GetTaskID(ctx, token, pushReq)
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -3,7 +3,6 @@ package push
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
@@ -12,40 +11,21 @@ import (
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mq/kafka"
|
||||
"github.com/openimsdk/tools/utils/jsonutil"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type OfflinePushConsumerHandler struct {
|
||||
OfflinePushConsumerGroup *kafka.MConsumerGroup
|
||||
offlinePusher offlinepush.OfflinePusher
|
||||
offlinePusher offlinepush.OfflinePusher
|
||||
}
|
||||
|
||||
func NewOfflinePushConsumerHandler(config *Config, offlinePusher offlinepush.OfflinePusher) (*OfflinePushConsumerHandler, error) {
|
||||
var offlinePushConsumerHandler OfflinePushConsumerHandler
|
||||
var err error
|
||||
offlinePushConsumerHandler.offlinePusher = offlinePusher
|
||||
offlinePushConsumerHandler.OfflinePushConsumerGroup, err = kafka.NewMConsumerGroup(config.KafkaConfig.Build(), config.KafkaConfig.ToOfflineGroupID,
|
||||
[]string{config.KafkaConfig.ToOfflinePushTopic}, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func NewOfflinePushConsumerHandler(offlinePusher offlinepush.OfflinePusher) *OfflinePushConsumerHandler {
|
||||
return &OfflinePushConsumerHandler{
|
||||
offlinePusher: offlinePusher,
|
||||
}
|
||||
return &offlinePushConsumerHandler, nil
|
||||
}
|
||||
|
||||
func (*OfflinePushConsumerHandler) Setup(sarama.ConsumerGroupSession) error { return nil }
|
||||
func (*OfflinePushConsumerHandler) Cleanup(sarama.ConsumerGroupSession) error { return nil }
|
||||
func (o *OfflinePushConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
|
||||
for msg := range claim.Messages() {
|
||||
ctx := o.OfflinePushConsumerGroup.GetContextFromMsg(msg)
|
||||
o.handleMsg2OfflinePush(ctx, msg.Value)
|
||||
sess.MarkMessage(msg, "")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OfflinePushConsumerHandler) handleMsg2OfflinePush(ctx context.Context, msg []byte) {
|
||||
func (o *OfflinePushConsumerHandler) HandleMsg2OfflinePush(ctx context.Context, msg []byte) {
|
||||
offlinePushMsg := pbpush.PushMsgReq{}
|
||||
if err := proto.Unmarshal(msg, &offlinePushMsg); err != nil {
|
||||
log.ZError(ctx, "offline push Unmarshal msg err", err, "msg", string(msg))
|
||||
|
||||
@@ -2,7 +2,7 @@ package push
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/openimsdk/protocol/msggateway"
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/runtimeenv"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
@@ -30,37 +31,36 @@ func newEmptyOnlinePusher() *emptyOnlinePusher {
|
||||
return &emptyOnlinePusher{}
|
||||
}
|
||||
|
||||
func (emptyOnlinePusher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData,
|
||||
pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) {
|
||||
func (emptyOnlinePusher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) {
|
||||
log.ZInfo(ctx, "emptyOnlinePusher GetConnsAndOnlinePush", nil)
|
||||
return nil, nil
|
||||
}
|
||||
func (u emptyOnlinePusher) GetOnlinePushFailedUserIDs(ctx context.Context, msg *sdkws.MsgData,
|
||||
wsResults []*msggateway.SingleMsgToUserResults, pushToUserIDs *[]string) []string {
|
||||
func (u emptyOnlinePusher) GetOnlinePushFailedUserIDs(ctx context.Context, msg *sdkws.MsgData, wsResults []*msggateway.SingleMsgToUserResults, pushToUserIDs *[]string) []string {
|
||||
log.ZInfo(ctx, "emptyOnlinePusher GetOnlinePushFailedUserIDs", nil)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewOnlinePusher(disCov discovery.SvcDiscoveryRegistry, config *Config) OnlinePusher {
|
||||
|
||||
if config.runTimeEnv == conf.KUBERNETES {
|
||||
return NewDefaultAllNode(disCov, config)
|
||||
func NewOnlinePusher(disCov discovery.Conn, config *Config) (OnlinePusher, error) {
|
||||
if conf.Standalone() {
|
||||
return NewDefaultAllNode(disCov, config), nil
|
||||
}
|
||||
if runtimeenv.RuntimeEnvironment() == conf.KUBERNETES {
|
||||
return NewDefaultAllNode(disCov, config), nil
|
||||
}
|
||||
switch config.Discovery.Enable {
|
||||
case conf.ETCD:
|
||||
return NewDefaultAllNode(disCov, config)
|
||||
return NewDefaultAllNode(disCov, config), nil
|
||||
default:
|
||||
log.ZError(context.Background(), "NewOnlinePusher is error", errs.Wrap(errors.New("unsupported discovery type")), "type", config.Discovery.Enable)
|
||||
return nil
|
||||
return nil, errs.New(fmt.Sprintf("unsupported discovery type %s", config.Discovery.Enable))
|
||||
}
|
||||
}
|
||||
|
||||
type DefaultAllNode struct {
|
||||
disCov discovery.SvcDiscoveryRegistry
|
||||
disCov discovery.Conn
|
||||
config *Config
|
||||
}
|
||||
|
||||
func NewDefaultAllNode(disCov discovery.SvcDiscoveryRegistry, config *Config) *DefaultAllNode {
|
||||
func NewDefaultAllNode(disCov discovery.Conn, config *Config) *DefaultAllNode {
|
||||
return &DefaultAllNode{disCov: disCov, config: config}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ func (k *K8sStaticConsistentHash) GetConnsAndOnlinePush(ctx context.Context, msg
|
||||
}
|
||||
}
|
||||
log.ZDebug(ctx, "genUsers send hosts struct:", "usersHost", usersHost)
|
||||
var usersConns = make(map[*grpc.ClientConn][]string)
|
||||
var usersConns = make(map[grpc.ClientConnInterface][]string)
|
||||
for host, userIds := range usersHost {
|
||||
tconn, _ := k.disCov.GetConn(ctx, host)
|
||||
usersConns[tconn] = userIds
|
||||
|
||||
+79
-25
@@ -2,39 +2,45 @@ package push
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/tools/mq"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/mcache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/mqbuild"
|
||||
pbpush "github.com/openimsdk/protocol/push"
|
||||
"github.com/openimsdk/tools/db/redisutil"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/utils/runtimeenv"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type pushServer struct {
|
||||
pbpush.UnimplementedPushMsgServiceServer
|
||||
database controller.PushDatabase
|
||||
disCov discovery.SvcDiscoveryRegistry
|
||||
disCov discovery.Conn
|
||||
offlinePusher offlinepush.OfflinePusher
|
||||
pushCh *ConsumerHandler
|
||||
offlinePushCh *OfflinePushConsumerHandler
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
RpcConfig config.Push
|
||||
RedisConfig config.Redis
|
||||
MongoConfig config.Mongo
|
||||
KafkaConfig config.Kafka
|
||||
NotificationConfig config.Notification
|
||||
Share config.Share
|
||||
WebhooksConfig config.Webhooks
|
||||
LocalCacheConfig config.LocalCache
|
||||
Discovery config.Discovery
|
||||
FcmConfigPath string
|
||||
|
||||
runTimeEnv string
|
||||
FcmConfigPath config.Path
|
||||
}
|
||||
|
||||
func (p pushServer) DelUserPushToken(ctx context.Context,
|
||||
@@ -45,42 +51,90 @@ func (p pushServer) DelUserPushToken(ctx context.Context,
|
||||
return &pbpush.DelUserPushTokenResp{}, nil
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error {
|
||||
config.runTimeEnv = runtimeenv.PrintRuntimeEnvironment()
|
||||
|
||||
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build())
|
||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
|
||||
dbb := dbbuild.NewBuilder(&config.MongoConfig, &config.RedisConfig)
|
||||
rdb, err := dbb.Redis(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cacheModel := redis.NewThirdCache(rdb)
|
||||
offlinePusher, err := offlinepush.NewOfflinePusher(&config.RpcConfig, cacheModel, config.FcmConfigPath)
|
||||
var cacheModel cache.ThirdCache
|
||||
if rdb == nil {
|
||||
mdb, err := dbb.Mongo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mc, err := mgo.NewCacheMgo(mdb.GetDB())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cacheModel = mcache.NewThirdCache(mc)
|
||||
} else {
|
||||
cacheModel = redis.NewThirdCache(rdb)
|
||||
}
|
||||
offlinePusher, err := offlinepush.NewOfflinePusher(&config.RpcConfig, cacheModel, string(config.FcmConfigPath))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
builder := mqbuild.NewBuilder(&config.KafkaConfig)
|
||||
|
||||
offlinePushProducer, err := builder.GetTopicProducer(ctx, config.KafkaConfig.ToOfflinePushTopic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
database := controller.NewPushDatabase(cacheModel, offlinePushProducer)
|
||||
|
||||
pushConsumer, err := builder.GetTopicConsumer(ctx, config.KafkaConfig.ToPushTopic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offlinePushConsumer, err := builder.GetTopicConsumer(ctx, config.KafkaConfig.ToOfflinePushTopic)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
database := controller.NewPushDatabase(cacheModel, &config.KafkaConfig)
|
||||
|
||||
consumer, err := NewConsumerHandler(ctx, config, database, offlinePusher, rdb, client)
|
||||
pushHandler, err := NewConsumerHandler(ctx, config, database, offlinePusher, rdb, client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
offlinePushConsumer, err := NewOfflinePushConsumerHandler(config, offlinePusher)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
offlineHandler := NewOfflinePushConsumerHandler(offlinePusher)
|
||||
|
||||
pbpush.RegisterPushMsgServiceServer(server, &pushServer{
|
||||
database: database,
|
||||
disCov: client,
|
||||
offlinePusher: offlinePusher,
|
||||
pushCh: consumer,
|
||||
offlinePushCh: offlinePushConsumer,
|
||||
})
|
||||
|
||||
go consumer.pushConsumerGroup.RegisterHandleAndConsumer(ctx, consumer)
|
||||
go func() {
|
||||
pushHandler.WaitCache()
|
||||
fn := func(msg mq.Message) error {
|
||||
pushHandler.HandleMs2PsChat(authverify.WithTempAdmin(msg.Context()), msg.Value())
|
||||
return nil
|
||||
}
|
||||
consumerCtx := mcontext.SetOperationID(context.Background(), "push_"+strconv.Itoa(int(rand.Uint32())))
|
||||
log.ZInfo(consumerCtx, "begin consume messages")
|
||||
for {
|
||||
if err := pushConsumer.Subscribe(consumerCtx, fn); err != nil {
|
||||
log.ZError(consumerCtx, "subscribe err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
go offlinePushConsumer.OfflinePushConsumerGroup.RegisterHandleAndConsumer(ctx, offlinePushConsumer)
|
||||
go func() {
|
||||
fn := func(msg mq.Message) error {
|
||||
offlineHandler.HandleMsg2OfflinePush(msg.Context(), msg.Value())
|
||||
return nil
|
||||
}
|
||||
consumerCtx := mcontext.SetOperationID(context.Background(), "push_"+strconv.Itoa(int(rand.Uint32())))
|
||||
log.ZInfo(consumerCtx, "begin consume messages")
|
||||
for {
|
||||
if err := offlinePushConsumer.Subscribe(consumerCtx, fn); err != nil {
|
||||
log.ZError(consumerCtx, "subscribe err", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,12 +3,8 @@ package push
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
@@ -16,6 +12,7 @@ import (
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/protocol/msggateway"
|
||||
@@ -24,7 +21,6 @@ import (
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/mq/kafka"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/jsonutil"
|
||||
"github.com/openimsdk/tools/utils/timeutil"
|
||||
@@ -33,11 +29,11 @@ import (
|
||||
)
|
||||
|
||||
type ConsumerHandler struct {
|
||||
pushConsumerGroup *kafka.MConsumerGroup
|
||||
//pushConsumerGroup mq.Consumer
|
||||
offlinePusher offlinepush.OfflinePusher
|
||||
onlinePusher OnlinePusher
|
||||
pushDatabase controller.PushDatabase
|
||||
onlineCache *rpccache.OnlineCache
|
||||
onlineCache rpccache.OnlineCache
|
||||
groupLocalCache *rpccache.GroupLocalCache
|
||||
conversationLocalCache *rpccache.ConversationLocalCache
|
||||
webhookClient *webhook.Client
|
||||
@@ -48,15 +44,7 @@ type ConsumerHandler struct {
|
||||
conversationClient *rpcli.ConversationClient
|
||||
}
|
||||
|
||||
func NewConsumerHandler(ctx context.Context, config *Config, database controller.PushDatabase, offlinePusher offlinepush.OfflinePusher, rdb redis.UniversalClient,
|
||||
client discovery.SvcDiscoveryRegistry) (*ConsumerHandler, error) {
|
||||
var consumerHandler ConsumerHandler
|
||||
var err error
|
||||
consumerHandler.pushConsumerGroup, err = kafka.NewMConsumerGroup(config.KafkaConfig.Build(), config.KafkaConfig.ToPushGroupID,
|
||||
[]string{config.KafkaConfig.ToPushTopic}, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
func NewConsumerHandler(ctx context.Context, config *Config, database controller.PushDatabase, offlinePusher offlinepush.OfflinePusher, rdb redis.UniversalClient, client discovery.Conn) (*ConsumerHandler, error) {
|
||||
userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -73,13 +61,18 @@ func NewConsumerHandler(ctx context.Context, config *Config, database controller
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
onlinePusher, err := NewOnlinePusher(client, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var consumerHandler ConsumerHandler
|
||||
consumerHandler.userClient = rpcli.NewUserClient(userConn)
|
||||
consumerHandler.groupClient = rpcli.NewGroupClient(groupConn)
|
||||
consumerHandler.msgClient = rpcli.NewMsgClient(msgConn)
|
||||
consumerHandler.conversationClient = rpcli.NewConversationClient(conversationConn)
|
||||
|
||||
consumerHandler.offlinePusher = offlinePusher
|
||||
consumerHandler.onlinePusher = NewOnlinePusher(client, config)
|
||||
consumerHandler.onlinePusher = onlinePusher
|
||||
consumerHandler.groupLocalCache = rpccache.NewGroupLocalCache(consumerHandler.groupClient, &config.LocalCacheConfig, rdb)
|
||||
consumerHandler.conversationLocalCache = rpccache.NewConversationLocalCache(consumerHandler.conversationClient, &config.LocalCacheConfig, rdb)
|
||||
consumerHandler.webhookClient = webhook.NewWebhookClient(config.WebhooksConfig.URL)
|
||||
@@ -92,7 +85,7 @@ func NewConsumerHandler(ctx context.Context, config *Config, database controller
|
||||
return &consumerHandler, nil
|
||||
}
|
||||
|
||||
func (c *ConsumerHandler) handleMs2PsChat(ctx context.Context, msg []byte) {
|
||||
func (c *ConsumerHandler) HandleMs2PsChat(ctx context.Context, msg []byte) {
|
||||
msgFromMQ := pbpush.PushMsgReq{}
|
||||
if err := proto.Unmarshal(msg, &msgFromMQ); err != nil {
|
||||
log.ZError(ctx, "push Unmarshal msg err", err, "msg", string(msg))
|
||||
@@ -126,25 +119,8 @@ func (c *ConsumerHandler) handleMs2PsChat(ctx context.Context, msg []byte) {
|
||||
}
|
||||
}
|
||||
|
||||
func (*ConsumerHandler) Setup(sarama.ConsumerGroupSession) error { return nil }
|
||||
|
||||
func (*ConsumerHandler) Cleanup(sarama.ConsumerGroupSession) error { return nil }
|
||||
|
||||
func (c *ConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
|
||||
c.onlineCache.Lock.Lock()
|
||||
for c.onlineCache.CurrentPhase.Load() < rpccache.DoSubscribeOver {
|
||||
c.onlineCache.Cond.Wait()
|
||||
}
|
||||
c.onlineCache.Lock.Unlock()
|
||||
ctx := mcontext.SetOperationID(context.TODO(), strconv.FormatInt(time.Now().UnixNano()+int64(rand.Uint32()), 10))
|
||||
log.ZInfo(ctx, "begin consume messages")
|
||||
|
||||
for msg := range claim.Messages() {
|
||||
ctx := c.pushConsumerGroup.GetContextFromMsg(msg)
|
||||
c.handleMs2PsChat(ctx, msg.Value)
|
||||
sess.MarkMessage(msg, "")
|
||||
}
|
||||
return nil
|
||||
func (c *ConsumerHandler) WaitCache() {
|
||||
c.onlineCache.WaitCache()
|
||||
}
|
||||
|
||||
// Push2User Suitable for two types of conversations, one is SingleChatType and the other is NotificationChatType.
|
||||
@@ -152,24 +128,24 @@ func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *
|
||||
log.ZInfo(ctx, "Get msg from msg_transfer And push msg", "userIDs", userIDs, "msg", msg.String())
|
||||
defer func(duration time.Time) {
|
||||
t := time.Since(duration)
|
||||
log.ZInfo(ctx, "Get msg from msg_transfer And push msg", "msg", msg.String(), "time cost", t)
|
||||
log.ZInfo(ctx, "Get msg from msg_transfer And push msg end", "msg", msg.String(), "time cost", t)
|
||||
}(time.Now())
|
||||
if err := c.webhookBeforeOnlinePush(ctx, &c.config.WebhooksConfig.BeforeOnlinePush, userIDs, msg); err != nil {
|
||||
return err
|
||||
}
|
||||
log.ZInfo(ctx, "webhookBeforeOnlinePush end")
|
||||
|
||||
wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, userIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.ZInfo(ctx, "single and notification push result", "result", wsResults, "msg", msg, "push_to_userID", userIDs)
|
||||
log.ZDebug(ctx, "single and notification push result", "result", wsResults, "msg", msg, "push_to_userID", userIDs)
|
||||
log.ZInfo(ctx, "single and notification push end")
|
||||
|
||||
if !c.shouldPushOffline(ctx, msg) {
|
||||
return nil
|
||||
}
|
||||
log.ZInfo(ctx, "shouldPushOffline end")
|
||||
log.ZInfo(ctx, "pushOffline start")
|
||||
|
||||
for _, v := range wsResults {
|
||||
//message sender do not need offline push
|
||||
@@ -188,14 +164,14 @@ func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *
|
||||
if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, needOfflinePushUserID, msg, &offlinePushUserID); err != nil {
|
||||
return err
|
||||
}
|
||||
log.ZInfo(ctx, "webhookBeforeOfflinePush end")
|
||||
|
||||
if len(offlinePushUserID) > 0 {
|
||||
needOfflinePushUserID = offlinePushUserID
|
||||
}
|
||||
err = c.offlinePushMsg(ctx, msg, needOfflinePushUserID)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "offlinePushMsg failed", err, "needOfflinePushUserID", needOfflinePushUserID, "msg", msg)
|
||||
log.ZDebug(ctx, "offlinePushMsg failed", err, "needOfflinePushUserID", needOfflinePushUserID, "msg", msg)
|
||||
log.ZWarn(ctx, "offlinePushMsg failed", err, "needOfflinePushUserID length", len(needOfflinePushUserID), "msg", msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -207,7 +183,10 @@ func (c *ConsumerHandler) shouldPushOffline(_ context.Context, msg *sdkws.MsgDat
|
||||
if !isOfflinePush {
|
||||
return false
|
||||
}
|
||||
if msg.ContentType == constant.SignalingNotification {
|
||||
switch msg.ContentType {
|
||||
case constant.RoomParticipantsConnectedNotification:
|
||||
return false
|
||||
case constant.RoomParticipantsDisconnectedNotification:
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -250,26 +229,24 @@ func (c *ConsumerHandler) Push2Group(ctx context.Context, groupID string, msg *s
|
||||
&pushToUserIDs); err != nil {
|
||||
return err
|
||||
}
|
||||
log.ZInfo(ctx, "webhookBeforeGroupOnlinePush end")
|
||||
|
||||
err = c.groupMessagesHandler(ctx, groupID, &pushToUserIDs, msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.ZInfo(ctx, "groupMessagesHandler end")
|
||||
|
||||
wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.ZInfo(ctx, "group push result", "result", wsResults, "msg", msg)
|
||||
log.ZDebug(ctx, "group push result", "result", wsResults, "msg", msg)
|
||||
log.ZInfo(ctx, "online group push end")
|
||||
|
||||
if !c.shouldPushOffline(ctx, msg) {
|
||||
return nil
|
||||
}
|
||||
needOfflinePushUserIDs := c.onlinePusher.GetOnlinePushFailedUserIDs(ctx, msg, wsResults, &pushToUserIDs)
|
||||
log.ZInfo(ctx, "GetOnlinePushFailedUserIDs end")
|
||||
//filter some user, like don not disturb or don't need offline push etc.
|
||||
needOfflinePushUserIDs, err = c.filterGroupMessageOfflinePush(ctx, groupID, msg, needOfflinePushUserIDs)
|
||||
if err != nil {
|
||||
@@ -297,9 +274,11 @@ func (c *ConsumerHandler) asyncOfflinePush(ctx context.Context, needOfflinePushU
|
||||
needOfflinePushUserIDs = offlinePushUserIDs
|
||||
}
|
||||
if err := c.pushDatabase.MsgToOfflinePushMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(msg.SendID, msg.RecvID), needOfflinePushUserIDs, msg); err != nil {
|
||||
log.ZError(ctx, "Msg To OfflinePush MQ error", err, "needOfflinePushUserIDs",
|
||||
log.ZDebug(ctx, "Msg To OfflinePush MQ error", err, "needOfflinePushUserIDs",
|
||||
needOfflinePushUserIDs, "msg", msg)
|
||||
prommetrics.SingleChatMsgProcessFailedCounter.Inc()
|
||||
log.ZWarn(ctx, "Msg To OfflinePush MQ error", err, "needOfflinePushUserIDs length",
|
||||
len(needOfflinePushUserIDs), "msg", msg)
|
||||
prommetrics.GroupChatMsgProcessFailedCounter.Inc()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -338,8 +317,8 @@ func (c *ConsumerHandler) groupMessagesHandler(ctx context.Context, groupID stri
|
||||
return err
|
||||
}
|
||||
log.ZDebug(ctx, "GroupDismissedNotificationInfo****", "groupID", groupID, "num", len(*pushToUserIDs), "list", pushToUserIDs)
|
||||
if len(c.config.Share.IMAdminUserID) > 0 {
|
||||
ctx = mcontext.WithOpUserIDContext(ctx, c.config.Share.IMAdminUserID[0])
|
||||
if len(c.config.Share.IMAdminUser.UserIDs) > 0 {
|
||||
ctx = mcontext.WithOpUserIDContext(ctx, c.config.Share.IMAdminUser.UserIDs[0])
|
||||
}
|
||||
defer func(groupID string) {
|
||||
if err := c.groupClient.DismissGroup(ctx, groupID, true); err != nil {
|
||||
|
||||
+81
-23
@@ -18,11 +18,17 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/mcache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/localcache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
||||
"github.com/openimsdk/tools/db/redisutil"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
@@ -43,38 +49,66 @@ import (
|
||||
type authServer struct {
|
||||
pbauth.UnimplementedAuthServer
|
||||
authDatabase controller.AuthDatabase
|
||||
RegisterCenter discovery.SvcDiscoveryRegistry
|
||||
AuthLocalCache *rpccache.AuthLocalCache
|
||||
RegisterCenter discovery.Conn
|
||||
config *Config
|
||||
userClient *rpcli.UserClient
|
||||
adminUserIDs []string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
RpcConfig config.Auth
|
||||
RedisConfig config.Redis
|
||||
Share config.Share
|
||||
Discovery config.Discovery
|
||||
RpcConfig config.Auth
|
||||
RedisConfig config.Redis
|
||||
MongoConfig config.Mongo
|
||||
Share config.Share
|
||||
LocalCacheConfig config.LocalCache
|
||||
Discovery config.Discovery
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error {
|
||||
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build())
|
||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
|
||||
dbb := dbbuild.NewBuilder(&config.MongoConfig, &config.RedisConfig)
|
||||
rdb, err := dbb.Redis(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var token cache.TokenModel
|
||||
if rdb == nil {
|
||||
mdb, err := dbb.Mongo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mc, err := mgo.NewCacheMgo(mdb.GetDB())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
token = mcache.NewTokenCacheModel(mc, config.RpcConfig.TokenPolicy.Expire)
|
||||
} else {
|
||||
token = redis2.NewTokenCacheModel(rdb, &config.LocalCacheConfig, config.RpcConfig.TokenPolicy.Expire)
|
||||
}
|
||||
userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
authConn, err := client.GetConn(ctx, config.Discovery.RpcService.Auth)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localcache.InitLocalCache(&config.LocalCacheConfig)
|
||||
|
||||
pbauth.RegisterAuthServer(server, &authServer{
|
||||
RegisterCenter: client,
|
||||
authDatabase: controller.NewAuthDatabase(
|
||||
redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire),
|
||||
token,
|
||||
config.Share.Secret,
|
||||
config.RpcConfig.TokenPolicy.Expire,
|
||||
config.Share.MultiLogin,
|
||||
config.Share.IMAdminUserID,
|
||||
config.Share.IMAdminUser.UserIDs,
|
||||
),
|
||||
config: config,
|
||||
userClient: rpcli.NewUserClient(userConn),
|
||||
AuthLocalCache: rpccache.NewAuthLocalCache(rpcli.NewAuthClient(authConn), &config.LocalCacheConfig, rdb),
|
||||
config: config,
|
||||
userClient: rpcli.NewUserClient(userConn),
|
||||
adminUserIDs: config.Share.IMAdminUser.UserIDs,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
@@ -85,8 +119,8 @@ func (s *authServer) GetAdminToken(ctx context.Context, req *pbauth.GetAdminToke
|
||||
return nil, errs.ErrNoPermission.WrapMsg("secret invalid")
|
||||
}
|
||||
|
||||
if !datautil.Contain(req.UserID, s.config.Share.IMAdminUserID...) {
|
||||
return nil, errs.ErrArgs.WrapMsg("userID is error.", "userID", req.UserID, "adminUserID", s.config.Share.IMAdminUserID)
|
||||
if !datautil.Contain(req.UserID, s.adminUserIDs...) {
|
||||
return nil, errs.ErrArgs.WrapMsg("userID is error.", "userID", req.UserID, "adminUserID", s.adminUserIDs)
|
||||
|
||||
}
|
||||
|
||||
@@ -100,13 +134,14 @@ func (s *authServer) GetAdminToken(ctx context.Context, req *pbauth.GetAdminToke
|
||||
}
|
||||
|
||||
prommetrics.UserLoginCounter.Inc()
|
||||
|
||||
resp.Token = token
|
||||
resp.ExpireTimeSeconds = s.config.RpcConfig.TokenPolicy.Expire * 24 * 60 * 60
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenReq) (*pbauth.GetUserTokenResp, error) {
|
||||
if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil {
|
||||
if err := authverify.CheckAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -116,7 +151,7 @@ func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenR
|
||||
|
||||
resp := pbauth.GetUserTokenResp{}
|
||||
|
||||
if authverify.IsManagerUserID(req.UserID, s.config.Share.IMAdminUserID) {
|
||||
if authverify.CheckUserIsAdmin(ctx, req.UserID) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("don't get Admin token")
|
||||
}
|
||||
user, err := s.userClient.GetUserInfo(ctx, req.UserID)
|
||||
@@ -130,25 +165,41 @@ func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenR
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Token = token
|
||||
resp.ExpireTimeSeconds = s.config.RpcConfig.TokenPolicy.Expire * 24 * 60 * 60
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (s *authServer) GetExistingToken(ctx context.Context, req *pbauth.GetExistingTokenReq) (*pbauth.GetExistingTokenResp, error) {
|
||||
m, err := s.authDatabase.GetTokensWithoutError(ctx, req.UserID, int(req.PlatformID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbauth.GetExistingTokenResp{
|
||||
TokenStates: convert.TokenMapDB2Pb(m),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *authServer) parseToken(ctx context.Context, tokensString string) (claims *tokenverify.Claims, err error) {
|
||||
claims, err = tokenverify.GetClaimFromToken(tokensString, authverify.Secret(s.config.Share.Secret))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
isAdmin := authverify.IsManagerUserID(claims.UserID, s.config.Share.IMAdminUserID)
|
||||
if isAdmin {
|
||||
return claims, nil
|
||||
}
|
||||
m, err := s.authDatabase.GetTokensWithoutError(ctx, claims.UserID, claims.PlatformID)
|
||||
|
||||
m, err := s.AuthLocalCache.GetExistingToken(ctx, claims.UserID, claims.PlatformID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(m) == 0 {
|
||||
isAdmin := authverify.CheckUserIsAdmin(ctx, claims.UserID)
|
||||
if isAdmin {
|
||||
if err = s.authDatabase.GetTemporaryTokensWithoutError(ctx, claims.UserID, claims.PlatformID, tokensString); err == nil {
|
||||
return claims, nil
|
||||
}
|
||||
}
|
||||
return nil, servererrs.ErrTokenNotExist.Wrap()
|
||||
}
|
||||
if v, ok := m[tokensString]; ok {
|
||||
@@ -160,6 +211,13 @@ func (s *authServer) parseToken(ctx context.Context, tokensString string) (claim
|
||||
default:
|
||||
return nil, errs.Wrap(errs.ErrTokenUnknown)
|
||||
}
|
||||
} else {
|
||||
isAdmin := authverify.CheckUserIsAdmin(ctx, claims.UserID)
|
||||
if isAdmin {
|
||||
if err = s.authDatabase.GetTemporaryTokensWithoutError(ctx, claims.UserID, claims.PlatformID, tokensString); err == nil {
|
||||
return claims, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil, servererrs.ErrTokenNotExist.Wrap()
|
||||
}
|
||||
@@ -177,7 +235,7 @@ func (s *authServer) ParseToken(ctx context.Context, req *pbauth.ParseTokenReq)
|
||||
}
|
||||
|
||||
func (s *authServer) ForceLogout(ctx context.Context, req *pbauth.ForceLogoutReq) (*pbauth.ForceLogoutResp, error) {
|
||||
if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil {
|
||||
if err := authverify.CheckAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.forceKickOff(ctx, req.UserID, req.PlatformID); err != nil {
|
||||
@@ -192,7 +250,7 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID
|
||||
return err
|
||||
}
|
||||
for _, v := range conns {
|
||||
log.ZDebug(ctx, "forceKickOff", "conn", v.Target())
|
||||
log.ZDebug(ctx, "forceKickOff", "userID", userID, "platformID", platformID)
|
||||
client := msggateway.NewMsgGatewayClient(v)
|
||||
kickReq := &msggateway.KickUserOfflineReq{KickUserIDList: []string{userID}, PlatformID: platformID}
|
||||
_, err := client.KickUserOffline(ctx, kickReq)
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
package conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
dbModel "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
)
|
||||
|
||||
func (c *conversationServer) webhookBeforeCreateSingleChatConversations(ctx context.Context, before *config.BeforeConfig, req *dbModel.Conversation) error {
|
||||
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
|
||||
cbReq := &callbackstruct.CallbackBeforeCreateSingleChatConversationsReq{
|
||||
CallbackCommand: callbackstruct.CallbackBeforeCreateSingleChatConversationsCommand,
|
||||
OwnerUserID: req.OwnerUserID,
|
||||
ConversationID: req.ConversationID,
|
||||
ConversationType: req.ConversationType,
|
||||
UserID: req.UserID,
|
||||
RecvMsgOpt: req.RecvMsgOpt,
|
||||
IsPinned: req.IsPinned,
|
||||
IsPrivateChat: req.IsPrivateChat,
|
||||
BurnDuration: req.BurnDuration,
|
||||
GroupAtType: req.GroupAtType,
|
||||
AttachedInfo: req.AttachedInfo,
|
||||
Ex: req.Ex,
|
||||
}
|
||||
|
||||
resp := &callbackstruct.CallbackBeforeCreateSingleChatConversationsResp{}
|
||||
|
||||
if err := c.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
datautil.NotNilReplace(&req.RecvMsgOpt, resp.RecvMsgOpt)
|
||||
datautil.NotNilReplace(&req.IsPinned, resp.IsPinned)
|
||||
datautil.NotNilReplace(&req.IsPrivateChat, resp.IsPrivateChat)
|
||||
datautil.NotNilReplace(&req.BurnDuration, resp.BurnDuration)
|
||||
datautil.NotNilReplace(&req.GroupAtType, resp.GroupAtType)
|
||||
datautil.NotNilReplace(&req.AttachedInfo, resp.AttachedInfo)
|
||||
datautil.NotNilReplace(&req.Ex, resp.Ex)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *conversationServer) webhookAfterCreateSingleChatConversations(ctx context.Context, after *config.AfterConfig, req *dbModel.Conversation) error {
|
||||
cbReq := &callbackstruct.CallbackAfterCreateSingleChatConversationsReq{
|
||||
CallbackCommand: callbackstruct.CallbackAfterCreateSingleChatConversationsCommand,
|
||||
OwnerUserID: req.OwnerUserID,
|
||||
ConversationID: req.ConversationID,
|
||||
ConversationType: req.ConversationType,
|
||||
UserID: req.UserID,
|
||||
RecvMsgOpt: req.RecvMsgOpt,
|
||||
IsPinned: req.IsPinned,
|
||||
IsPrivateChat: req.IsPrivateChat,
|
||||
BurnDuration: req.BurnDuration,
|
||||
GroupAtType: req.GroupAtType,
|
||||
AttachedInfo: req.AttachedInfo,
|
||||
Ex: req.Ex,
|
||||
}
|
||||
|
||||
c.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterCreateSingleChatConversationsResp{}, after)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) webhookBeforeCreateGroupChatConversations(ctx context.Context, before *config.BeforeConfig, req *dbModel.Conversation) error {
|
||||
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
|
||||
cbReq := &callbackstruct.CallbackBeforeCreateGroupChatConversationsReq{
|
||||
CallbackCommand: callbackstruct.CallbackBeforeCreateGroupChatConversationsCommand,
|
||||
ConversationID: req.ConversationID,
|
||||
ConversationType: req.ConversationType,
|
||||
GroupID: req.GroupID,
|
||||
RecvMsgOpt: req.RecvMsgOpt,
|
||||
IsPinned: req.IsPinned,
|
||||
IsPrivateChat: req.IsPrivateChat,
|
||||
BurnDuration: req.BurnDuration,
|
||||
GroupAtType: req.GroupAtType,
|
||||
AttachedInfo: req.AttachedInfo,
|
||||
Ex: req.Ex,
|
||||
}
|
||||
|
||||
resp := &callbackstruct.CallbackBeforeCreateGroupChatConversationsResp{}
|
||||
|
||||
if err := c.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
datautil.NotNilReplace(&req.RecvMsgOpt, resp.RecvMsgOpt)
|
||||
datautil.NotNilReplace(&req.IsPinned, resp.IsPinned)
|
||||
datautil.NotNilReplace(&req.IsPrivateChat, resp.IsPrivateChat)
|
||||
datautil.NotNilReplace(&req.BurnDuration, resp.BurnDuration)
|
||||
datautil.NotNilReplace(&req.GroupAtType, resp.GroupAtType)
|
||||
datautil.NotNilReplace(&req.AttachedInfo, resp.AttachedInfo)
|
||||
datautil.NotNilReplace(&req.Ex, resp.Ex)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (c *conversationServer) webhookAfterCreateGroupChatConversations(ctx context.Context, after *config.AfterConfig, req *dbModel.Conversation) error {
|
||||
cbReq := &callbackstruct.CallbackAfterCreateGroupChatConversationsReq{
|
||||
CallbackCommand: callbackstruct.CallbackAfterCreateGroupChatConversationsCommand,
|
||||
ConversationID: req.ConversationID,
|
||||
ConversationType: req.ConversationType,
|
||||
GroupID: req.GroupID,
|
||||
RecvMsgOpt: req.RecvMsgOpt,
|
||||
IsPinned: req.IsPinned,
|
||||
IsPrivateChat: req.IsPrivateChat,
|
||||
BurnDuration: req.BurnDuration,
|
||||
GroupAtType: req.GroupAtType,
|
||||
AttachedInfo: req.AttachedInfo,
|
||||
Ex: req.Ex,
|
||||
}
|
||||
|
||||
c.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterCreateGroupChatConversationsResp{}, after)
|
||||
return nil
|
||||
}
|
||||
@@ -16,31 +16,33 @@ package conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
dbModel "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/localcache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||
"github.com/openimsdk/tools/db/redisutil"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
||||
dbModel "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/localcache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
pbconversation "github.com/openimsdk/protocol/conversation"
|
||||
"github.com/openimsdk/protocol/msg"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type conversationServer struct {
|
||||
@@ -50,9 +52,10 @@ type conversationServer struct {
|
||||
conversationNotificationSender *ConversationNotificationSender
|
||||
config *Config
|
||||
|
||||
userClient *rpcli.UserClient
|
||||
msgClient *rpcli.MsgClient
|
||||
groupClient *rpcli.GroupClient
|
||||
webhookClient *webhook.Client
|
||||
userClient *rpcli.UserClient
|
||||
msgClient *rpcli.MsgClient
|
||||
groupClient *rpcli.GroupClient
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -61,16 +64,18 @@ type Config struct {
|
||||
MongodbConfig config.Mongo
|
||||
NotificationConfig config.Notification
|
||||
Share config.Share
|
||||
WebhooksConfig config.Webhooks
|
||||
LocalCacheConfig config.LocalCache
|
||||
Discovery config.Discovery
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error {
|
||||
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build())
|
||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
|
||||
dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
|
||||
mgocli, err := dbb.Mongo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build())
|
||||
rdb, err := dbb.Redis(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -90,20 +95,32 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgClient := rpcli.NewMsgClient(msgConn)
|
||||
|
||||
cs := conversationServer{
|
||||
config: config,
|
||||
webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL),
|
||||
userClient: rpcli.NewUserClient(userConn),
|
||||
groupClient: rpcli.NewGroupClient(groupConn),
|
||||
msgClient: msgClient,
|
||||
}
|
||||
|
||||
cs.conversationNotificationSender = NewConversationNotificationSender(&config.NotificationConfig, msgClient)
|
||||
cs.conversationDatabase = controller.NewConversationDatabase(
|
||||
conversationDB,
|
||||
redis.NewConversationRedis(rdb, &config.LocalCacheConfig, conversationDB),
|
||||
mgocli.GetTx())
|
||||
|
||||
localcache.InitLocalCache(&config.LocalCacheConfig)
|
||||
pbconversation.RegisterConversationServer(server, &conversationServer{
|
||||
conversationNotificationSender: NewConversationNotificationSender(&config.NotificationConfig, msgClient),
|
||||
conversationDatabase: controller.NewConversationDatabase(conversationDB,
|
||||
redis.NewConversationRedis(rdb, &config.LocalCacheConfig, redis.GetRocksCacheOptions(), conversationDB), mgocli.GetTx()),
|
||||
userClient: rpcli.NewUserClient(userConn),
|
||||
groupClient: rpcli.NewGroupClient(groupConn),
|
||||
msgClient: msgClient,
|
||||
})
|
||||
pbconversation.RegisterConversationServer(server, &cs)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetConversation(ctx context.Context, req *pbconversation.GetConversationReq) (*pbconversation.GetConversationResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversations, err := c.conversationDatabase.FindConversations(ctx, req.OwnerUserID, []string{req.ConversationID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -116,8 +133,11 @@ func (c *conversationServer) GetConversation(ctx context.Context, req *pbconvers
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Deprecated: Use `GetConversations` instead.
|
||||
func (c *conversationServer) GetSortedConversationList(ctx context.Context, req *pbconversation.GetSortedConversationListReq) (resp *pbconversation.GetSortedConversationListResp, err error) {
|
||||
log.ZDebug(ctx, "GetSortedConversationList", "seqs", req, "userID", req.UserID)
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var conversationIDs []string
|
||||
if len(req.ConversationIDs) == 0 {
|
||||
conversationIDs, err = c.conversationDatabase.GetConversationIDs(ctx, req.UserID)
|
||||
@@ -165,9 +185,21 @@ func (c *conversationServer) GetSortedConversationList(ctx context.Context, req
|
||||
|
||||
conversation_isPinTime := make(map[int64]string)
|
||||
conversation_notPinTime := make(map[int64]string)
|
||||
|
||||
for _, v := range conversations {
|
||||
conversationID := v.ConversationID
|
||||
time := conversationMsg[conversationID].MsgInfo.LatestMsgRecvTime
|
||||
var time int64
|
||||
if _, ok := conversationMsg[conversationID]; ok {
|
||||
time = conversationMsg[conversationID].MsgInfo.LatestMsgRecvTime
|
||||
} else {
|
||||
conversationMsg[conversationID] = &pbconversation.ConversationElem{
|
||||
ConversationID: conversationID,
|
||||
IsPinned: v.IsPinned,
|
||||
MsgInfo: nil,
|
||||
}
|
||||
time = v.CreateTime.UnixMilli()
|
||||
}
|
||||
|
||||
conversationMsg[conversationID].RecvMsgOpt = v.RecvMsgOpt
|
||||
if v.IsPinned {
|
||||
conversationMsg[conversationID].IsPinned = v.IsPinned
|
||||
@@ -190,6 +222,9 @@ func (c *conversationServer) GetSortedConversationList(ctx context.Context, req
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetAllConversations(ctx context.Context, req *pbconversation.GetAllConversationsReq) (*pbconversation.GetAllConversationsResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversations, err := c.conversationDatabase.GetUserAllConversation(ctx, req.OwnerUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -200,6 +235,9 @@ func (c *conversationServer) GetAllConversations(ctx context.Context, req *pbcon
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetConversations(ctx context.Context, req *pbconversation.GetConversationsReq) (*pbconversation.GetConversationsResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversations, err := c.getConversations(ctx, req.OwnerUserID, req.ConversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -219,8 +257,14 @@ func (c *conversationServer) getConversations(ctx context.Context, ownerUserID s
|
||||
return convert.ConversationsDB2Pb(conversations), nil
|
||||
}
|
||||
|
||||
// Deprecated
|
||||
func (c *conversationServer) SetConversation(ctx context.Context, req *pbconversation.SetConversationReq) (*pbconversation.SetConversationResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.GetConversation().GetUserID()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var conversation dbModel.Conversation
|
||||
conversation.CreateTime = time.Now()
|
||||
|
||||
if err := datautil.CopyStructFields(&conversation, req.Conversation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -234,8 +278,10 @@ func (c *conversationServer) SetConversation(ctx context.Context, req *pbconvers
|
||||
}
|
||||
|
||||
func (c *conversationServer) SetConversations(ctx context.Context, req *pbconversation.SetConversationsReq) (*pbconversation.SetConversationsResp, error) {
|
||||
if req.Conversation == nil {
|
||||
return nil, errs.ErrArgs.WrapMsg("conversation must not be nil")
|
||||
for _, userID := range req.UserIDs {
|
||||
if err := authverify.CheckAccess(ctx, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if req.Conversation.ConversationType == constant.WriteGroupChatType {
|
||||
groupInfo, err := c.groupClient.GetGroupInfo(ctx, req.Conversation.GroupID)
|
||||
@@ -270,110 +316,31 @@ func (c *conversationServer) SetConversations(ctx context.Context, req *pbconver
|
||||
conversation.ConversationType = req.Conversation.ConversationType
|
||||
conversation.UserID = req.Conversation.UserID
|
||||
conversation.GroupID = req.Conversation.GroupID
|
||||
conversation.CreateTime = time.Now()
|
||||
|
||||
m := make(map[string]any)
|
||||
|
||||
setConversationFieldsFunc := func() {
|
||||
if req.Conversation.RecvMsgOpt != nil {
|
||||
conversation.RecvMsgOpt = req.Conversation.RecvMsgOpt.Value
|
||||
m["recv_msg_opt"] = req.Conversation.RecvMsgOpt.Value
|
||||
}
|
||||
if req.Conversation.AttachedInfo != nil {
|
||||
conversation.AttachedInfo = req.Conversation.AttachedInfo.Value
|
||||
m["attached_info"] = req.Conversation.AttachedInfo.Value
|
||||
}
|
||||
if req.Conversation.Ex != nil {
|
||||
conversation.Ex = req.Conversation.Ex.Value
|
||||
m["ex"] = req.Conversation.Ex.Value
|
||||
}
|
||||
if req.Conversation.IsPinned != nil {
|
||||
conversation.IsPinned = req.Conversation.IsPinned.Value
|
||||
m["is_pinned"] = req.Conversation.IsPinned.Value
|
||||
}
|
||||
if req.Conversation.GroupAtType != nil {
|
||||
conversation.GroupAtType = req.Conversation.GroupAtType.Value
|
||||
m["group_at_type"] = req.Conversation.GroupAtType.Value
|
||||
}
|
||||
if req.Conversation.MsgDestructTime != nil {
|
||||
conversation.MsgDestructTime = req.Conversation.MsgDestructTime.Value
|
||||
m["msg_destruct_time"] = req.Conversation.MsgDestructTime.Value
|
||||
}
|
||||
if req.Conversation.IsMsgDestruct != nil {
|
||||
conversation.IsMsgDestruct = req.Conversation.IsMsgDestruct.Value
|
||||
m["is_msg_destruct"] = req.Conversation.IsMsgDestruct.Value
|
||||
}
|
||||
if req.Conversation.BurnDuration != nil {
|
||||
conversation.BurnDuration = req.Conversation.BurnDuration.Value
|
||||
m["burn_duration"] = req.Conversation.BurnDuration.Value
|
||||
}
|
||||
m, conversation, err := UpdateConversationsMap(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// set need set field in conversation
|
||||
setConversationFieldsFunc()
|
||||
|
||||
for userID := range conversationMap {
|
||||
unequal := len(m)
|
||||
unequal := UserUpdateCheckMap(ctx, userID, req.Conversation, conversationMap[userID])
|
||||
|
||||
if req.Conversation.RecvMsgOpt != nil {
|
||||
if req.Conversation.RecvMsgOpt.Value == conversationMap[userID].RecvMsgOpt {
|
||||
unequal--
|
||||
}
|
||||
}
|
||||
|
||||
if req.Conversation.AttachedInfo != nil {
|
||||
if req.Conversation.AttachedInfo.Value == conversationMap[userID].AttachedInfo {
|
||||
unequal--
|
||||
}
|
||||
}
|
||||
|
||||
if req.Conversation.Ex != nil {
|
||||
if req.Conversation.Ex.Value == conversationMap[userID].Ex {
|
||||
unequal--
|
||||
}
|
||||
}
|
||||
if req.Conversation.IsPinned != nil {
|
||||
if req.Conversation.IsPinned.Value == conversationMap[userID].IsPinned {
|
||||
unequal--
|
||||
}
|
||||
}
|
||||
|
||||
if req.Conversation.GroupAtType != nil {
|
||||
if req.Conversation.GroupAtType.Value == conversationMap[userID].GroupAtType {
|
||||
unequal--
|
||||
}
|
||||
}
|
||||
|
||||
if req.Conversation.MsgDestructTime != nil {
|
||||
if req.Conversation.MsgDestructTime.Value == conversationMap[userID].MsgDestructTime {
|
||||
unequal--
|
||||
}
|
||||
}
|
||||
|
||||
if req.Conversation.IsMsgDestruct != nil {
|
||||
if req.Conversation.IsMsgDestruct.Value == conversationMap[userID].IsMsgDestruct {
|
||||
unequal--
|
||||
}
|
||||
}
|
||||
|
||||
if req.Conversation.BurnDuration != nil {
|
||||
if req.Conversation.BurnDuration.Value == conversationMap[userID].BurnDuration {
|
||||
unequal--
|
||||
}
|
||||
}
|
||||
|
||||
if unequal > 0 {
|
||||
if unequal {
|
||||
needUpdateUsersList = append(needUpdateUsersList, userID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(m) != 0 && len(needUpdateUsersList) != 0 {
|
||||
if err := c.conversationDatabase.SetUsersConversationFieldTx(ctx, needUpdateUsersList, &conversation, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range needUpdateUsersList {
|
||||
c.conversationNotificationSender.ConversationChangeNotification(ctx, v, []string{req.Conversation.ConversationID})
|
||||
for _, userID := range needUpdateUsersList {
|
||||
c.conversationNotificationSender.ConversationChangeNotification(ctx, userID, []string{req.Conversation.ConversationID})
|
||||
}
|
||||
}
|
||||
|
||||
if req.Conversation.IsPrivateChat != nil && req.Conversation.ConversationType != constant.ReadGroupChatType {
|
||||
var conversations []*dbModel.Conversation
|
||||
for _, ownerUserID := range req.UserIDs {
|
||||
@@ -396,58 +363,103 @@ func (c *conversationServer) SetConversations(ctx context.Context, req *pbconver
|
||||
return &pbconversation.SetConversationsResp{}, nil
|
||||
}
|
||||
|
||||
// Get user IDs with "Do Not Disturb" enabled in super large groups.
|
||||
func (c *conversationServer) GetRecvMsgNotNotifyUserIDs(ctx context.Context, req *pbconversation.GetRecvMsgNotNotifyUserIDsReq) (*pbconversation.GetRecvMsgNotNotifyUserIDsResp, error) {
|
||||
return nil, errs.New("deprecated")
|
||||
func (c *conversationServer) UpdateConversationsByUser(ctx context.Context, req *pbconversation.UpdateConversationsByUserReq) (*pbconversation.UpdateConversationsByUserResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m := make(map[string]any)
|
||||
if req.Ex != nil {
|
||||
m["ex"] = req.Ex.Value
|
||||
}
|
||||
if len(m) > 0 {
|
||||
if err := c.conversationDatabase.UpdateUserConversations(ctx, req.UserID, m); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &pbconversation.UpdateConversationsByUserResp{}, nil
|
||||
}
|
||||
|
||||
// create conversation without notification for msg redis transfer.
|
||||
func (c *conversationServer) CreateSingleChatConversations(ctx context.Context,
|
||||
req *pbconversation.CreateSingleChatConversationsReq,
|
||||
) (*pbconversation.CreateSingleChatConversationsResp, error) {
|
||||
func (c *conversationServer) CreateSingleChatConversations(ctx context.Context, req *pbconversation.CreateSingleChatConversationsReq) (*pbconversation.CreateSingleChatConversationsResp, error) {
|
||||
var conversation dbModel.Conversation
|
||||
conversation.CreateTime = time.Now()
|
||||
|
||||
switch req.ConversationType {
|
||||
case constant.SingleChatType:
|
||||
var conversation dbModel.Conversation
|
||||
// sendUser create
|
||||
conversation.ConversationID = req.ConversationID
|
||||
conversation.ConversationType = req.ConversationType
|
||||
conversation.OwnerUserID = req.SendID
|
||||
conversation.UserID = req.RecvID
|
||||
|
||||
if err := c.webhookBeforeCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.BeforeCreateSingleChatConversations, &conversation); err != nil && err != servererrs.ErrCallbackContinue {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := c.conversationDatabase.CreateConversation(ctx, []*dbModel.Conversation{&conversation})
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "create conversation failed", err, "conversation", conversation)
|
||||
}
|
||||
|
||||
c.webhookAfterCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.AfterCreateSingleChatConversations, &conversation)
|
||||
|
||||
// recvUser create
|
||||
conversation2 := conversation
|
||||
conversation2.OwnerUserID = req.RecvID
|
||||
conversation2.UserID = req.SendID
|
||||
|
||||
if err := c.webhookBeforeCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.BeforeCreateSingleChatConversations, &conversation); err != nil && err != servererrs.ErrCallbackContinue {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.conversationDatabase.CreateConversation(ctx, []*dbModel.Conversation{&conversation2})
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "create conversation failed", err, "conversation2", conversation)
|
||||
}
|
||||
|
||||
c.webhookAfterCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.AfterCreateSingleChatConversations, &conversation2)
|
||||
case constant.NotificationChatType:
|
||||
var conversation dbModel.Conversation
|
||||
conversation.ConversationID = req.ConversationID
|
||||
conversation.ConversationType = req.ConversationType
|
||||
conversation.OwnerUserID = req.RecvID
|
||||
conversation.UserID = req.SendID
|
||||
|
||||
if err := c.webhookBeforeCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.BeforeCreateSingleChatConversations, &conversation); err != nil && err != servererrs.ErrCallbackContinue {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := c.conversationDatabase.CreateConversation(ctx, []*dbModel.Conversation{&conversation})
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "create conversation failed", err, "conversation2", conversation)
|
||||
}
|
||||
|
||||
c.webhookAfterCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.AfterCreateSingleChatConversations, &conversation)
|
||||
}
|
||||
|
||||
return &pbconversation.CreateSingleChatConversationsResp{}, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) CreateGroupChatConversations(ctx context.Context, req *pbconversation.CreateGroupChatConversationsReq) (*pbconversation.CreateGroupChatConversationsResp, error) {
|
||||
err := c.conversationDatabase.CreateGroupChatConversation(ctx, req.GroupID, req.UserIDs)
|
||||
var conversation dbModel.Conversation
|
||||
|
||||
conversation.ConversationID = msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID)
|
||||
conversation.GroupID = req.GroupID
|
||||
conversation.ConversationType = constant.ReadGroupChatType
|
||||
conversation.CreateTime = time.Now()
|
||||
|
||||
if err := c.webhookBeforeCreateGroupChatConversations(ctx, &c.config.WebhooksConfig.BeforeCreateGroupChatConversations, &conversation); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err := c.conversationDatabase.CreateGroupChatConversation(ctx, req.GroupID, req.UserIDs, &conversation)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID)
|
||||
if err := c.msgClient.SetUserConversationMaxSeq(ctx, conversationID, req.UserIDs, 0); err != nil {
|
||||
if err := c.msgClient.SetUserConversationMaxSeq(ctx, conversation.ConversationID, req.UserIDs, 0); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
c.webhookAfterCreateGroupChatConversations(ctx, &c.config.WebhooksConfig.AfterCreateGroupChatConversations, &conversation)
|
||||
return &pbconversation.CreateGroupChatConversationsResp{}, nil
|
||||
}
|
||||
|
||||
@@ -480,6 +492,9 @@ func (c *conversationServer) SetConversationMinSeq(ctx context.Context, req *pbc
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetConversationIDs(ctx context.Context, req *pbconversation.GetConversationIDsReq) (*pbconversation.GetConversationIDsResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversationIDs, err := c.conversationDatabase.GetConversationIDs(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -488,6 +503,9 @@ func (c *conversationServer) GetConversationIDs(ctx context.Context, req *pbconv
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetUserConversationIDsHash(ctx context.Context, req *pbconversation.GetUserConversationIDsHashReq) (*pbconversation.GetUserConversationIDsHashResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hash, err := c.conversationDatabase.GetUserConversationIDsHash(ctx, req.OwnerUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -495,17 +513,6 @@ func (c *conversationServer) GetUserConversationIDsHash(ctx context.Context, req
|
||||
return &pbconversation.GetUserConversationIDsHashResp{Hash: hash}, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetConversationsByConversationID(
|
||||
ctx context.Context,
|
||||
req *pbconversation.GetConversationsByConversationIDReq,
|
||||
) (*pbconversation.GetConversationsByConversationIDResp, error) {
|
||||
conversations, err := c.conversationDatabase.GetConversationsByConversationID(ctx, req.ConversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbconversation.GetConversationsByConversationIDResp{Conversations: convert.ConversationsDB2Pb(conversations)}, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetConversationOfflinePushUserIDs(ctx context.Context, req *pbconversation.GetConversationOfflinePushUserIDsReq) (*pbconversation.GetConversationOfflinePushUserIDsResp, error) {
|
||||
if req.ConversationID == "" {
|
||||
return nil, errs.ErrArgs.WrapMsg("conversationID is empty")
|
||||
@@ -552,10 +559,7 @@ func (c *conversationServer) conversationSort(conversations map[int64]string, re
|
||||
resp.ConversationElems = append(resp.ConversationElems, cons...)
|
||||
}
|
||||
|
||||
func (c *conversationServer) getConversationInfo(
|
||||
ctx context.Context,
|
||||
chatLogs map[string]*sdkws.MsgData,
|
||||
userID string) (map[string]*pbconversation.ConversationElem, error) {
|
||||
func (c *conversationServer) getConversationInfo(ctx context.Context, chatLogs map[string]*sdkws.MsgData, userID string) (map[string]*pbconversation.ConversationElem, error) {
|
||||
var (
|
||||
sendIDs []string
|
||||
groupIDs []string
|
||||
@@ -641,6 +645,11 @@ func (c *conversationServer) GetConversationNotReceiveMessageUserIDs(ctx context
|
||||
}
|
||||
|
||||
func (c *conversationServer) UpdateConversation(ctx context.Context, req *pbconversation.UpdateConversationReq) (*pbconversation.UpdateConversationResp, error) {
|
||||
for _, userID := range req.UserIDs {
|
||||
if err := authverify.CheckAccess(ctx, userID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
m := make(map[string]any)
|
||||
if req.RecvMsgOpt != nil {
|
||||
m["recv_msg_opt"] = req.RecvMsgOpt.Value
|
||||
@@ -687,6 +696,9 @@ func (c *conversationServer) UpdateConversation(ctx context.Context, req *pbconv
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetOwnerConversation(ctx context.Context, req *pbconversation.GetOwnerConversationReq) (*pbconversation.GetOwnerConversationResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total, conversations, err := c.conversationDatabase.GetOwnerConversation(ctx, req.UserID, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -697,57 +709,10 @@ func (c *conversationServer) GetOwnerConversation(ctx context.Context, req *pbco
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetConversationsNeedClearMsg(ctx context.Context, _ *pbconversation.GetConversationsNeedClearMsgReq) (*pbconversation.GetConversationsNeedClearMsgResp, error) {
|
||||
num, err := c.conversationDatabase.GetAllConversationIDsNumber(ctx)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetAllConversationIDsNumber failed", err)
|
||||
func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, req *pbconversation.GetNotNotifyConversationIDsReq) (*pbconversation.GetNotNotifyConversationIDsResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
const batchNum = 100
|
||||
|
||||
if num == 0 {
|
||||
return nil, errs.New("Need Destruct Msg is nil").Wrap()
|
||||
}
|
||||
|
||||
maxPage := (num + batchNum - 1) / batchNum
|
||||
|
||||
temp := make([]*model.Conversation, 0, maxPage*batchNum)
|
||||
|
||||
for pageNumber := 0; pageNumber < int(maxPage); pageNumber++ {
|
||||
pagination := &sdkws.RequestPagination{
|
||||
PageNumber: int32(pageNumber),
|
||||
ShowNumber: batchNum,
|
||||
}
|
||||
|
||||
conversationIDs, err := c.conversationDatabase.PageConversationIDs(ctx, pagination)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "PageConversationIDs failed", err, "pageNumber", pageNumber)
|
||||
continue
|
||||
}
|
||||
|
||||
// log.ZDebug(ctx, "PageConversationIDs success", "pageNumber", pageNumber, "conversationIDsNum", len(conversationIDs), "conversationIDs", conversationIDs)
|
||||
if len(conversationIDs) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
conversations, err := c.conversationDatabase.GetConversationsByConversationID(ctx, conversationIDs)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetConversationsByConversationID failed", err, "conversationIDs", conversationIDs)
|
||||
continue
|
||||
}
|
||||
|
||||
for _, conversation := range conversations {
|
||||
if conversation.IsMsgDestruct && conversation.MsgDestructTime != 0 && ((time.Now().UnixMilli() > (conversation.MsgDestructTime + conversation.LatestMsgDestructTime.UnixMilli() + 8*60*60)) || // 8*60*60 is UTC+8
|
||||
conversation.LatestMsgDestructTime.IsZero()) {
|
||||
temp = append(temp, conversation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &pbconversation.GetConversationsNeedClearMsgResp{Conversations: convert.ConversationsDB2Pb(temp)}, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, req *pbconversation.GetNotNotifyConversationIDsReq) (*pbconversation.GetNotNotifyConversationIDsResp, error) {
|
||||
conversationIDs, err := c.conversationDatabase.GetNotNotifyConversationIDs(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -756,6 +721,9 @@ func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, re
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetPinnedConversationIDs(ctx context.Context, req *pbconversation.GetPinnedConversationIDsReq) (*pbconversation.GetPinnedConversationIDsResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversationIDs, err := c.conversationDatabase.GetPinnedConversationIDs(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -770,10 +738,10 @@ func (c *conversationServer) ClearUserConversationMsg(ctx context.Context, req *
|
||||
}
|
||||
latestMsgDestructTime := time.UnixMilli(req.Timestamp)
|
||||
for i, conversation := range conversations {
|
||||
if conversation.IsMsgDestruct == false || conversation.MsgDestructTime == 0 {
|
||||
if !conversation.IsMsgDestruct || conversation.MsgDestructTime == 0 {
|
||||
continue
|
||||
}
|
||||
seq, err := c.msgClient.GetLastMessageSeqByTime(ctx, conversation.ConversationID, req.Timestamp-conversation.MsgDestructTime)
|
||||
seq, err := c.msgClient.GetLastMessageSeqByTime(ctx, conversation.ConversationID, req.Timestamp-(conversation.MsgDestructTime*1000))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -810,3 +778,53 @@ func (c *conversationServer) setConversationMinSeqAndLatestMsgDestructTime(ctx c
|
||||
c.conversationNotificationSender.ConversationChangeNotification(ctx, ownerUserID, []string{conversationID})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) DeleteConversations(ctx context.Context, req *pbconversation.DeleteConversationsReq) (resp *pbconversation.DeleteConversationsResp, err error) {
|
||||
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.NeedDeleteTime == 0 && len(req.ConversationIDs) == 0 {
|
||||
return nil, errs.ErrArgs.WrapMsg("need_delete_time or conversationIDs need be set")
|
||||
}
|
||||
|
||||
if req.NeedDeleteTime != 0 && len(req.ConversationIDs) != 0 {
|
||||
return nil, errs.ErrArgs.WrapMsg("need_delete_time and conversationIDs cannot both be set")
|
||||
}
|
||||
|
||||
var needDeleteConversationIDs []string
|
||||
|
||||
if len(req.ConversationIDs) == 0 {
|
||||
deleteTimeThreshold := time.Now().AddDate(0, 0, -int(req.NeedDeleteTime)).UnixMilli()
|
||||
conversationIDs, err := c.conversationDatabase.GetConversationIDs(ctx, req.OwnerUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
latestMsgs, err := c.msgClient.GetLastMessage(ctx, &msg.GetLastMessageReq{
|
||||
UserID: req.OwnerUserID,
|
||||
ConversationIDs: conversationIDs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for conversationID, msg := range latestMsgs.Msgs {
|
||||
if msg.SendTime < deleteTimeThreshold {
|
||||
needDeleteConversationIDs = append(needDeleteConversationIDs, conversationID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(needDeleteConversationIDs) == 0 {
|
||||
return &pbconversation.DeleteConversationsResp{}, nil
|
||||
}
|
||||
} else {
|
||||
needDeleteConversationIDs = req.ConversationIDs
|
||||
}
|
||||
|
||||
if err := c.conversationDatabase.DeleteUsersConversations(ctx, req.OwnerUserID, needDeleteConversationIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// c.conversationNotificationSender.ConversationDeleteNotification(ctx, req.OwnerUserID, needDeleteConversationIDs)
|
||||
|
||||
return &pbconversation.DeleteConversationsResp{}, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
package conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
dbModel "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/protocol/conversation"
|
||||
)
|
||||
|
||||
func UpdateConversationsMap(ctx context.Context, req *conversation.SetConversationsReq) (m map[string]any, conversation dbModel.Conversation, err error) {
|
||||
m = make(map[string]any)
|
||||
|
||||
conversation.ConversationID = req.Conversation.ConversationID
|
||||
conversation.ConversationType = req.Conversation.ConversationType
|
||||
conversation.UserID = req.Conversation.UserID
|
||||
conversation.GroupID = req.Conversation.GroupID
|
||||
|
||||
if req.Conversation.RecvMsgOpt != nil {
|
||||
conversation.RecvMsgOpt = req.Conversation.RecvMsgOpt.Value
|
||||
m["recv_msg_opt"] = req.Conversation.RecvMsgOpt.Value
|
||||
}
|
||||
|
||||
if req.Conversation.AttachedInfo != nil {
|
||||
conversation.AttachedInfo = req.Conversation.AttachedInfo.Value
|
||||
m["attached_info"] = req.Conversation.AttachedInfo.Value
|
||||
}
|
||||
|
||||
if req.Conversation.Ex != nil {
|
||||
conversation.Ex = req.Conversation.Ex.Value
|
||||
m["ex"] = req.Conversation.Ex.Value
|
||||
}
|
||||
if req.Conversation.IsPinned != nil {
|
||||
conversation.IsPinned = req.Conversation.IsPinned.Value
|
||||
m["is_pinned"] = req.Conversation.IsPinned.Value
|
||||
}
|
||||
if req.Conversation.GroupAtType != nil {
|
||||
conversation.GroupAtType = req.Conversation.GroupAtType.Value
|
||||
m["group_at_type"] = req.Conversation.GroupAtType.Value
|
||||
}
|
||||
if req.Conversation.MsgDestructTime != nil {
|
||||
conversation.MsgDestructTime = req.Conversation.MsgDestructTime.Value
|
||||
m["msg_destruct_time"] = req.Conversation.MsgDestructTime.Value
|
||||
}
|
||||
if req.Conversation.IsMsgDestruct != nil {
|
||||
conversation.IsMsgDestruct = req.Conversation.IsMsgDestruct.Value
|
||||
m["is_msg_destruct"] = req.Conversation.IsMsgDestruct.Value
|
||||
}
|
||||
if req.Conversation.BurnDuration != nil {
|
||||
conversation.BurnDuration = req.Conversation.BurnDuration.Value
|
||||
m["burn_duration"] = req.Conversation.BurnDuration.Value
|
||||
}
|
||||
|
||||
return m, conversation, nil
|
||||
}
|
||||
|
||||
func UserUpdateCheckMap(ctx context.Context, userID string, req *conversation.ConversationReq, conversation *dbModel.Conversation) (unequal bool) {
|
||||
unequal = false
|
||||
|
||||
if req.RecvMsgOpt != nil && conversation.RecvMsgOpt != req.RecvMsgOpt.Value {
|
||||
unequal = true
|
||||
}
|
||||
if req.AttachedInfo != nil && conversation.AttachedInfo != req.AttachedInfo.Value {
|
||||
unequal = true
|
||||
}
|
||||
if req.Ex != nil && conversation.Ex != req.Ex.Value {
|
||||
unequal = true
|
||||
}
|
||||
if req.IsPinned != nil && conversation.IsPinned != req.IsPinned.Value {
|
||||
unequal = true
|
||||
}
|
||||
if req.GroupAtType != nil && conversation.GroupAtType != req.GroupAtType.Value {
|
||||
unequal = true
|
||||
}
|
||||
if req.MsgDestructTime != nil && conversation.MsgDestructTime != req.MsgDestructTime.Value {
|
||||
unequal = true
|
||||
}
|
||||
if req.IsMsgDestruct != nil && conversation.IsMsgDestruct != req.IsMsgDestruct.Value {
|
||||
unequal = true
|
||||
}
|
||||
if req.BurnDuration != nil && conversation.BurnDuration != req.BurnDuration.Value {
|
||||
unequal = true
|
||||
}
|
||||
|
||||
return unequal
|
||||
}
|
||||
@@ -16,6 +16,7 @@ package conversation
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"github.com/openimsdk/protocol/msg"
|
||||
|
||||
@@ -26,11 +27,11 @@ import (
|
||||
)
|
||||
|
||||
type ConversationNotificationSender struct {
|
||||
*rpcclient.NotificationSender
|
||||
*notification.NotificationSender
|
||||
}
|
||||
|
||||
func NewConversationNotificationSender(conf *config.Notification, msgClient *rpcli.MsgClient) *ConversationNotificationSender {
|
||||
return &ConversationNotificationSender{rpcclient.NewNotificationSender(conf, rpcclient.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) {
|
||||
return &ConversationNotificationSender{notification.NewNotificationSender(conf, notification.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) {
|
||||
return msgClient.SendMsg(ctx, req)
|
||||
}))}
|
||||
}
|
||||
@@ -72,3 +73,12 @@ func (c *ConversationNotificationSender) ConversationUnreadChangeNotification(
|
||||
|
||||
c.Notification(ctx, userID, userID, constant.ConversationUnreadNotification, tips)
|
||||
}
|
||||
|
||||
func (c *ConversationNotificationSender) ConversationDeleteNotification(ctx context.Context, userID string, conversationIDs []string) {
|
||||
tips := &sdkws.ConversationDeleteTips{
|
||||
UserID: userID,
|
||||
ConversationIDs: conversationIDs,
|
||||
}
|
||||
|
||||
c.Notification(ctx, userID, userID, constant.ConversationDeleteNotification, tips)
|
||||
}
|
||||
|
||||
@@ -4,12 +4,16 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/util/hashutil"
|
||||
"github.com/openimsdk/protocol/conversation"
|
||||
)
|
||||
|
||||
func (c *conversationServer) GetFullOwnerConversationIDs(ctx context.Context, req *conversation.GetFullOwnerConversationIDsReq) (*conversation.GetFullOwnerConversationIDsResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vl, err := c.conversationDatabase.FindMaxConversationUserVersionCache(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -23,7 +27,7 @@ func (c *conversationServer) GetFullOwnerConversationIDs(ctx context.Context, re
|
||||
conversationIDs = nil
|
||||
}
|
||||
return &conversation.GetFullOwnerConversationIDsResp{
|
||||
Version: idHash,
|
||||
Version: uint64(vl.Version),
|
||||
VersionID: vl.ID.Hex(),
|
||||
Equal: req.IdHash == idHash,
|
||||
ConversationIDs: conversationIDs,
|
||||
@@ -31,6 +35,9 @@ func (c *conversationServer) GetFullOwnerConversationIDs(ctx context.Context, re
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetIncrementalConversation(ctx context.Context, req *conversation.GetIncrementalConversationReq) (*conversation.GetIncrementalConversationResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opt := incrversion.Option[*conversation.Conversation, conversation.GetIncrementalConversationResp]{
|
||||
Ctx: ctx,
|
||||
VersionKey: req.UserID,
|
||||
|
||||
@@ -33,6 +33,9 @@ func (g *groupServer) GetGroupInfoCache(ctx context.Context, req *pbgroup.GetGro
|
||||
}
|
||||
|
||||
func (g *groupServer) GetGroupMemberCache(ctx context.Context, req *pbgroup.GetGroupMemberCacheReq) (*pbgroup.GetGroupMemberCacheResp, error) {
|
||||
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
members, err := g.db.TakeGroupMember(ctx, req.GroupID, req.GroupMemberID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -16,6 +16,7 @@ package group
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
pbgroup "github.com/openimsdk/protocol/group"
|
||||
@@ -55,41 +56,52 @@ func UpdateGroupInfoMap(ctx context.Context, group *sdkws.GroupInfoForSet) map[s
|
||||
return m
|
||||
}
|
||||
|
||||
func UpdateGroupInfoExMap(ctx context.Context, group *pbgroup.SetGroupInfoExReq) (map[string]any, error) {
|
||||
m := make(map[string]any)
|
||||
func UpdateGroupInfoExMap(ctx context.Context, group *pbgroup.SetGroupInfoExReq) (m map[string]any, normalFlag, groupNameFlag, notificationFlag bool, err error) {
|
||||
m = make(map[string]any)
|
||||
|
||||
if group.GroupName != nil {
|
||||
if group.GroupName.Value != "" {
|
||||
if strings.TrimSpace(group.GroupName.Value) != "" {
|
||||
m["group_name"] = group.GroupName.Value
|
||||
groupNameFlag = true
|
||||
} else {
|
||||
return nil, errs.ErrArgs.WrapMsg("group name is empty")
|
||||
return nil, normalFlag, notificationFlag, groupNameFlag, errs.ErrArgs.WrapMsg("group name is empty")
|
||||
}
|
||||
}
|
||||
|
||||
if group.Notification != nil {
|
||||
notificationFlag = true
|
||||
group.Notification.Value = strings.TrimSpace(group.Notification.Value) // if Notification only contains spaces, set it to empty string
|
||||
|
||||
m["notification"] = group.Notification.Value
|
||||
m["notification_update_time"] = time.Now()
|
||||
m["notification_user_id"] = mcontext.GetOpUserID(ctx)
|
||||
m["notification_update_time"] = time.Now()
|
||||
}
|
||||
if group.Introduction != nil {
|
||||
m["introduction"] = group.Introduction.Value
|
||||
normalFlag = true
|
||||
}
|
||||
if group.FaceURL != nil {
|
||||
m["face_url"] = group.FaceURL.Value
|
||||
normalFlag = true
|
||||
}
|
||||
if group.NeedVerification != nil {
|
||||
m["need_verification"] = group.NeedVerification.Value
|
||||
normalFlag = true
|
||||
}
|
||||
if group.LookMemberInfo != nil {
|
||||
m["look_member_info"] = group.LookMemberInfo.Value
|
||||
normalFlag = true
|
||||
}
|
||||
if group.ApplyMemberFriend != nil {
|
||||
m["apply_member_friend"] = group.ApplyMemberFriend.Value
|
||||
normalFlag = true
|
||||
}
|
||||
if group.Ex != nil {
|
||||
m["ex"] = group.Ex.Value
|
||||
normalFlag = true
|
||||
}
|
||||
|
||||
return m, nil
|
||||
return m, normalFlag, groupNameFlag, notificationFlag, nil
|
||||
}
|
||||
|
||||
func UpdateGroupStatusMap(status int) map[string]any {
|
||||
|
||||
+215
-100
@@ -17,13 +17,16 @@ package group
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
@@ -42,8 +45,6 @@ import (
|
||||
pbgroup "github.com/openimsdk/protocol/group"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/protocol/wrapperspb"
|
||||
"github.com/openimsdk/tools/db/mongoutil"
|
||||
"github.com/openimsdk/tools/db/redisutil"
|
||||
"github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
@@ -51,7 +52,6 @@ import (
|
||||
"github.com/openimsdk/tools/mw/specialerror"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/encrypt"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type groupServer struct {
|
||||
@@ -63,6 +63,7 @@ type groupServer struct {
|
||||
userClient *rpcli.UserClient
|
||||
msgClient *rpcli.MsgClient
|
||||
conversationClient *rpcli.ConversationClient
|
||||
adminUserIDs []string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
@@ -76,12 +77,13 @@ type Config struct {
|
||||
Discovery config.Discovery
|
||||
}
|
||||
|
||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error {
|
||||
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build())
|
||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
|
||||
dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
|
||||
mgocli, err := dbb.Mongo(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build())
|
||||
rdb, err := dbb.Redis(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -97,11 +99,6 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID)
|
||||
//msgRpcClient := rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg)
|
||||
//conversationRpcClient := rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation)
|
||||
|
||||
userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -120,6 +117,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
||||
userClient: rpcli.NewUserClient(userConn),
|
||||
msgClient: rpcli.NewMsgClient(msgConn),
|
||||
conversationClient: rpcli.NewConversationClient(conversationConn),
|
||||
adminUserIDs: config.Share.IMAdminUser.UserIDs,
|
||||
}
|
||||
gs.db = controller.NewGroupDatabase(rdb, &config.LocalCacheConfig, groupDB, groupMemberDB, groupRequestDB, mgocli.GetTx(), grouphash.NewGroupHashFromGroupServer(&gs))
|
||||
gs.notification = NewNotificationSender(gs.db, config, gs.userClient, gs.msgClient, gs.conversationClient)
|
||||
@@ -155,11 +153,15 @@ func (g *groupServer) NotificationUserInfoUpdate(ctx context.Context, req *pbgro
|
||||
}
|
||||
|
||||
func (g *groupServer) CheckGroupAdmin(ctx context.Context, groupID string) error {
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
groupMember, err := g.db.TakeGroupMember(ctx, groupID, mcontext.GetOpUserID(ctx))
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
members, err := g.db.FindGroupMembers(ctx, groupID, []string{mcontext.GetOpUserID(ctx)})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(members) == 0 {
|
||||
return errs.ErrNoPermission.WrapMsg("op user not in group")
|
||||
}
|
||||
groupMember := members[0]
|
||||
if !(groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin) {
|
||||
return errs.ErrNoPermission.WrapMsg("no group owner or admin")
|
||||
}
|
||||
@@ -207,7 +209,7 @@ func (g *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR
|
||||
if req.OwnerUserID == "" {
|
||||
return nil, errs.ErrArgs.WrapMsg("no group owner")
|
||||
}
|
||||
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, g.config.Share.IMAdminUserID); err != nil {
|
||||
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIDs := append(append(req.MemberUserIDs, req.AdminUserIDs...), req.OwnerUserID)
|
||||
@@ -288,13 +290,14 @@ func (g *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR
|
||||
break
|
||||
}
|
||||
}
|
||||
g.notification.GroupCreatedNotification(ctx, tips)
|
||||
g.notification.GroupCreatedNotification(ctx, tips, req.SendMessage)
|
||||
|
||||
if req.GroupInfo.Notification != "" {
|
||||
notificationFlag := true
|
||||
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{
|
||||
Group: tips.Group,
|
||||
OpUser: tips.OpUser,
|
||||
})
|
||||
}, ¬ificationFlag)
|
||||
}
|
||||
|
||||
reqCallBackAfter := &pbgroup.CreateGroupReq{
|
||||
@@ -310,7 +313,7 @@ func (g *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR
|
||||
}
|
||||
|
||||
func (g *groupServer) GetJoinedGroupList(ctx context.Context, req *pbgroup.GetJoinedGroupListReq) (*pbgroup.GetJoinedGroupListResp, error) {
|
||||
if err := authverify.CheckAccessV3(ctx, req.FromUserID, g.config.Share.IMAdminUserID); err != nil {
|
||||
if err := authverify.CheckAccess(ctx, req.FromUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total, members, err := g.db.PageGetJoinGroup(ctx, req.FromUserID, req.Pagination)
|
||||
@@ -371,6 +374,10 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
|
||||
return nil, servererrs.ErrDismissedAlready.WrapMsg("group dismissed checking group status found it dismissed")
|
||||
}
|
||||
|
||||
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userMap, err := g.userClient.GetUsersInfoMap(ctx, req.InvitedUserIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -381,9 +388,9 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
|
||||
}
|
||||
|
||||
var groupMember *model.GroupMember
|
||||
var opUserID string
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
opUserID = mcontext.GetOpUserID(ctx)
|
||||
opUserID := mcontext.GetOpUserID(ctx)
|
||||
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
var err error
|
||||
groupMember, err = g.db.TakeGroupMember(ctx, req.GroupID, opUserID)
|
||||
if err != nil {
|
||||
@@ -399,7 +406,7 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
|
||||
}
|
||||
|
||||
if group.NeedVerification == constant.AllNeedVerification {
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
if !(groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin) {
|
||||
var requests []*model.GroupRequest
|
||||
for _, userID := range req.InvitedUserIDs {
|
||||
@@ -421,12 +428,13 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
|
||||
ReqMessage: request.ReqMsg,
|
||||
JoinSource: request.JoinSource,
|
||||
InviterUserID: request.InviterUserID,
|
||||
})
|
||||
}, request)
|
||||
}
|
||||
return &pbgroup.InviteUserToGroupResp{}, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var groupMembers []*model.GroupMember
|
||||
for _, userID := range req.InvitedUserIDs {
|
||||
member := &model.GroupMember{
|
||||
@@ -447,11 +455,24 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := g.db.CreateGroup(ctx, nil, groupMembers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
const singleQuantity = 50
|
||||
for start := 0; start < len(groupMembers); start += singleQuantity {
|
||||
end := min(start+singleQuantity, len(groupMembers))
|
||||
currentMembers := groupMembers[start:end]
|
||||
|
||||
if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, opUserID, req.InvitedUserIDs...); err != nil {
|
||||
if err := g.db.CreateGroup(ctx, nil, currentMembers); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userIDs := datautil.Slice(currentMembers, func(e *model.GroupMember) string {
|
||||
return e.UserID
|
||||
})
|
||||
|
||||
if len(userIDs) != 0 {
|
||||
g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, req.SendMessage, opUserID, userIDs...)
|
||||
}
|
||||
}
|
||||
if err := g.setMemberJoinSeq(ctx, req.GroupID, req.InvitedUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbgroup.InviteUserToGroupResp{}, nil
|
||||
@@ -462,6 +483,19 @@ func (g *groupServer) GetGroupAllMember(ctx context.Context, req *pbgroup.GetGro
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
var inGroup bool
|
||||
opUserID := mcontext.GetOpUserID(ctx)
|
||||
for _, member := range members {
|
||||
if member.UserID == opUserID {
|
||||
inGroup = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inGroup {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("opuser not in group")
|
||||
}
|
||||
}
|
||||
if err := g.PopulateGroupMember(ctx, members...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -472,7 +506,25 @@ func (g *groupServer) GetGroupAllMember(ctx context.Context, req *pbgroup.GetGro
|
||||
return &resp, nil
|
||||
}
|
||||
|
||||
func (g *groupServer) checkAdminOrInGroup(ctx context.Context, groupID string) error {
|
||||
if authverify.IsAdmin(ctx) {
|
||||
return nil
|
||||
}
|
||||
opUserID := mcontext.GetOpUserID(ctx)
|
||||
members, err := g.db.FindGroupMembers(ctx, groupID, []string{opUserID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(members) == 0 {
|
||||
return errs.ErrNoPermission.WrapMsg("op user not in group")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGroupMemberListReq) (*pbgroup.GetGroupMemberListResp, error) {
|
||||
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
total int64
|
||||
members []*model.GroupMember
|
||||
@@ -481,7 +533,7 @@ func (g *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGr
|
||||
if req.Keyword == "" {
|
||||
total, members, err = g.db.PageGetGroupMember(ctx, req.GroupID, req.Pagination)
|
||||
} else {
|
||||
members, err = g.db.FindGroupMemberAll(ctx, req.GroupID)
|
||||
total, members, err = g.db.SearchGroupMember(ctx, req.GroupID, req.Keyword, req.Pagination)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -489,27 +541,6 @@ func (g *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGr
|
||||
if err := g.PopulateGroupMember(ctx, members...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.Keyword != "" {
|
||||
groupMembers := make([]*model.GroupMember, 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
|
||||
}
|
||||
}
|
||||
|
||||
members := datautil.Paginate(groupMembers, int(req.Pagination.GetPageNumber()), int(req.Pagination.GetShowNumber()))
|
||||
return &pbgroup.GetGroupMemberListResp{
|
||||
Total: uint32(total),
|
||||
Members: datautil.Batch(convert.Db2PbGroupMember, members),
|
||||
}, nil
|
||||
}
|
||||
return &pbgroup.GetGroupMemberListResp{
|
||||
Total: uint32(total),
|
||||
Members: datautil.Batch(convert.Db2PbGroupMember, members),
|
||||
@@ -550,7 +581,7 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
|
||||
for i, member := range members {
|
||||
memberMap[member.UserID] = members[i]
|
||||
}
|
||||
isAppManagerUid := authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID)
|
||||
isAppManagerUid := authverify.IsAdmin(ctx)
|
||||
opMember := memberMap[opUserID]
|
||||
for _, userID := range req.KickedUserIDs {
|
||||
member, ok := memberMap[userID]
|
||||
@@ -574,10 +605,6 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
|
||||
}
|
||||
}
|
||||
}
|
||||
num, err := g.db.FindGroupMemberNum(ctx, req.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ownerUserIDs, err := g.db.GetGroupRoleLevelMemberIDs(ctx, req.GroupID, constant.GroupOwner)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -589,6 +616,10 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
|
||||
if err := g.db.DeleteGroupMember(ctx, group.GroupID, req.KickedUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
num, err := g.db.FindGroupMemberNum(ctx, req.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tips := &sdkws.MemberKickedTips{
|
||||
Group: &sdkws.GroupInfo{
|
||||
GroupID: group.GroupID,
|
||||
@@ -598,7 +629,7 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
|
||||
FaceURL: group.FaceURL,
|
||||
OwnerUserID: ownerUserID,
|
||||
CreateTime: group.CreateTime.UnixMilli(),
|
||||
MemberCount: num - uint32(len(req.KickedUserIDs)),
|
||||
MemberCount: num,
|
||||
Ex: group.Ex,
|
||||
Status: group.Status,
|
||||
CreatorUserID: group.CreatorUserID,
|
||||
@@ -617,7 +648,7 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
|
||||
for _, userID := range req.KickedUserIDs {
|
||||
tips.KickedUserList = append(tips.KickedUserList, convert.Db2PbGroupMember(memberMap[userID]))
|
||||
}
|
||||
g.notification.MemberKickedNotification(ctx, tips)
|
||||
g.notification.MemberKickedNotification(ctx, tips, req.SendMessage)
|
||||
if err := g.deleteMemberAndSetConversationSeq(ctx, req.GroupID, req.KickedUserIDs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -633,6 +664,9 @@ func (g *groupServer) GetGroupMembersInfo(ctx context.Context, req *pbgroup.GetG
|
||||
if req.GroupID == "" {
|
||||
return nil, errs.ErrArgs.WrapMsg("groupID empty")
|
||||
}
|
||||
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
members, err := g.getGroupMembersInfo(ctx, req.GroupID, req.UserIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -660,15 +694,37 @@ func (g *groupServer) getGroupMembersInfo(ctx context.Context, groupID string, u
|
||||
|
||||
// GetGroupApplicationList handles functions that get a list of group requests.
|
||||
func (g *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup.GetGroupApplicationListReq) (*pbgroup.GetGroupApplicationListResp, error) {
|
||||
groupIDs, err := g.db.FindUserManagedGroupID(ctx, req.FromUserID)
|
||||
if err != nil {
|
||||
if err := authverify.CheckAccess(ctx, req.FromUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
groupIDs []string
|
||||
err error
|
||||
)
|
||||
if len(req.GroupIDs) == 0 {
|
||||
groupIDs, err = g.db.FindUserManagedGroupID(ctx, req.FromUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
req.GroupIDs = datautil.Distinct(req.GroupIDs)
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
for _, groupID := range req.GroupIDs {
|
||||
if err := g.CheckGroupAdmin(ctx, groupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
groupIDs = req.GroupIDs
|
||||
}
|
||||
resp := &pbgroup.GetGroupApplicationListResp{}
|
||||
if len(groupIDs) == 0 {
|
||||
return resp, nil
|
||||
}
|
||||
total, groupRequests, err := g.db.PageGroupRequest(ctx, groupIDs, req.Pagination)
|
||||
handleResults := datautil.Slice(req.HandleResults, func(e int32) int {
|
||||
return int(e)
|
||||
})
|
||||
total, groupRequests, err := g.db.PageGroupRequest(ctx, groupIDs, handleResults, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -733,6 +789,23 @@ func (g *groupServer) GetGroupsInfo(ctx context.Context, req *pbgroup.GetGroupsI
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *groupServer) GetGroupApplicationUnhandledCount(ctx context.Context, req *pbgroup.GetGroupApplicationUnhandledCountReq) (*pbgroup.GetGroupApplicationUnhandledCountResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
groupIDs, err := g.db.FindUserManagedGroupID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
count, err := g.db.GetGroupApplicationUnhandledCount(ctx, groupIDs, req.Time)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbgroup.GetGroupApplicationUnhandledCountResp{
|
||||
Count: count,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *groupServer) getGroupsInfo(ctx context.Context, groupIDs []string) ([]*sdkws.GroupInfo, error) {
|
||||
if len(groupIDs) == 0 {
|
||||
return nil, nil
|
||||
@@ -768,7 +841,7 @@ func (g *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup
|
||||
if !datautil.Contain(req.HandleResult, constant.GroupResponseAgree, constant.GroupResponseRefuse) {
|
||||
return nil, errs.ErrArgs.WrapMsg("HandleResult unknown")
|
||||
}
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
groupMember, err := g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -826,7 +899,16 @@ func (g *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup
|
||||
if member == nil {
|
||||
log.ZDebug(ctx, "GroupApplicationResponse", "member is nil")
|
||||
} else {
|
||||
if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, groupRequest.InviterUserID, req.FromUserID); err != nil {
|
||||
if groupRequest.InviterUserID == "" {
|
||||
if err = g.notification.MemberEnterNotification(ctx, req.GroupID, req.FromUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, nil, groupRequest.InviterUserID, req.FromUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := g.setMemberJoinSeq(ctx, req.GroupID, []string{req.FromUserID}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -891,6 +973,9 @@ func (g *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq)
|
||||
if err = g.notification.MemberEnterNotification(ctx, req.GroupID, req.InviterUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := g.setMemberJoinSeq(ctx, req.GroupID, []string{req.InviterUserID}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.webhookAfterJoinGroup(ctx, &g.config.WebhooksConfig.AfterJoinGroup, req)
|
||||
|
||||
return &pbgroup.JoinGroupResp{}, nil
|
||||
@@ -908,7 +993,7 @@ func (g *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq)
|
||||
if err = g.db.CreateGroupRequest(ctx, []*model.GroupRequest{&groupRequest}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
g.notification.JoinGroupApplicationNotification(ctx, req)
|
||||
g.notification.JoinGroupApplicationNotification(ctx, req, &groupRequest)
|
||||
return &pbgroup.JoinGroupResp{}, nil
|
||||
}
|
||||
|
||||
@@ -916,7 +1001,7 @@ func (g *groupServer) QuitGroup(ctx context.Context, req *pbgroup.QuitGroupReq)
|
||||
if req.UserID == "" {
|
||||
req.UserID = mcontext.GetOpUserID(ctx)
|
||||
} else {
|
||||
if err := authverify.CheckAccessV3(ctx, req.UserID, g.config.Share.IMAdminUserID); err != nil {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
@@ -952,9 +1037,14 @@ func (g *groupServer) deleteMemberAndSetConversationSeq(ctx context.Context, gro
|
||||
return g.conversationClient.SetConversationMaxSeq(ctx, conversationID, userIDs, maxSeq)
|
||||
}
|
||||
|
||||
func (g *groupServer) setMemberJoinSeq(ctx context.Context, groupID string, userIDs []string) error {
|
||||
conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, groupID)
|
||||
return g.conversationClient.SetConversationMaxSeq(ctx, conversationID, userIDs, 0)
|
||||
}
|
||||
|
||||
func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInfoReq) (*pbgroup.SetGroupInfoResp, error) {
|
||||
var opMember *model.GroupMember
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
var err error
|
||||
opMember, err = g.db.TakeGroupMember(ctx, req.GroupInfoForSet.GroupID, mcontext.GetOpUserID(ctx))
|
||||
if err != nil {
|
||||
@@ -1029,7 +1119,8 @@ func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
|
||||
log.ZWarn(ctx, "SetConversations", err, "UserIDs", resp.UserIDs, "conversation", conversation)
|
||||
}
|
||||
}()
|
||||
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser})
|
||||
notficationFlag := true
|
||||
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}, ¬ficationFlag)
|
||||
}
|
||||
if req.GroupInfoForSet.GroupName != "" {
|
||||
num--
|
||||
@@ -1047,7 +1138,7 @@ func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
|
||||
func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupInfoExReq) (*pbgroup.SetGroupInfoExResp, error) {
|
||||
var opMember *model.GroupMember
|
||||
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
var err error
|
||||
|
||||
opMember, err = g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx))
|
||||
@@ -1090,7 +1181,7 @@ func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupI
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedData, err := UpdateGroupInfoExMap(ctx, req)
|
||||
updatedData, normalFlag, groupNameFlag, notificationFlag, err := UpdateGroupInfoExMap(ctx, req)
|
||||
if len(updatedData) == 0 {
|
||||
return &pbgroup.SetGroupInfoExResp{}, nil
|
||||
}
|
||||
@@ -1118,41 +1209,38 @@ func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupI
|
||||
tips.OpUser = g.groupMemberDB2PB(opMember, 0)
|
||||
}
|
||||
|
||||
num := len(updatedData)
|
||||
|
||||
if req.Notification != nil {
|
||||
num -= 3
|
||||
|
||||
if notificationFlag {
|
||||
if req.Notification.Value != "" {
|
||||
func() {
|
||||
conversation := &pbconv.ConversationReq{
|
||||
ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID),
|
||||
ConversationType: constant.ReadGroupChatType,
|
||||
GroupID: req.GroupID,
|
||||
}
|
||||
conversation := &pbconv.ConversationReq{
|
||||
ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID),
|
||||
ConversationType: constant.ReadGroupChatType,
|
||||
GroupID: req.GroupID,
|
||||
}
|
||||
|
||||
resp, err := g.GetGroupMemberUserIDs(ctx, &pbgroup.GetGroupMemberUserIDsReq{GroupID: req.GroupID})
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "GetGroupMemberIDs is failed.", err)
|
||||
return
|
||||
}
|
||||
resp, err := g.GetGroupMemberUserIDs(ctx, &pbgroup.GetGroupMemberUserIDsReq{GroupID: req.GroupID})
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "GetGroupMemberIDs is failed.", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.GroupNotification}
|
||||
if err := g.conversationClient.SetConversations(ctx, resp.UserIDs, conversation); err != nil {
|
||||
log.ZWarn(ctx, "SetConversations", err, "UserIDs", resp.UserIDs, "conversation", conversation)
|
||||
}
|
||||
}()
|
||||
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.GroupNotification}
|
||||
if err := g.conversationClient.SetConversations(ctx, resp.UserIDs, conversation); err != nil {
|
||||
log.ZWarn(ctx, "SetConversations", err, "UserIDs", resp.UserIDs, "conversation", conversation)
|
||||
}
|
||||
|
||||
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser})
|
||||
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}, ¬ificationFlag)
|
||||
} else {
|
||||
notificationFlag = false
|
||||
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}, ¬ificationFlag)
|
||||
}
|
||||
}
|
||||
|
||||
if req.GroupName != nil {
|
||||
num--
|
||||
if groupNameFlag {
|
||||
g.notification.GroupInfoSetNameNotification(ctx, &sdkws.GroupInfoSetNameTips{Group: tips.Group, OpUser: tips.OpUser})
|
||||
}
|
||||
|
||||
if num > 0 {
|
||||
// if updatedData > 0, send the normal notification
|
||||
if normalFlag {
|
||||
g.notification.GroupInfoSetNotification(ctx, tips)
|
||||
}
|
||||
|
||||
@@ -1199,7 +1287,7 @@ func (g *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
|
||||
return nil, errs.ErrArgs.WrapMsg("NewOwnerUser not in group " + req.NewOwnerUserID)
|
||||
}
|
||||
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
if !(mcontext.GetOpUserID(ctx) == oldOwner.UserID && oldOwner.RoleLevel == constant.GroupOwner) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("no permission transfer group owner")
|
||||
}
|
||||
@@ -1274,6 +1362,9 @@ func (g *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq)
|
||||
}
|
||||
|
||||
func (g *groupServer) GetGroupMembersCMS(ctx context.Context, req *pbgroup.GetGroupMembersCMSReq) (*pbgroup.GetGroupMembersCMSResp, error) {
|
||||
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total, members, err := g.db.SearchGroupMember(ctx, req.UserName, req.GroupID, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1290,11 +1381,17 @@ func (g *groupServer) GetGroupMembersCMS(ctx context.Context, req *pbgroup.GetGr
|
||||
}
|
||||
|
||||
func (g *groupServer) GetUserReqApplicationList(ctx context.Context, req *pbgroup.GetUserReqApplicationListReq) (*pbgroup.GetUserReqApplicationListResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := g.userClient.GetUserInfo(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total, requests, err := g.db.PageGroupRequestUser(ctx, req.UserID, req.Pagination)
|
||||
handleResults := datautil.Slice(req.HandleResults, func(e int32) int {
|
||||
return int(e)
|
||||
})
|
||||
total, requests, err := g.db.PageGroupRequestUser(ctx, req.UserID, req.GroupIDs, handleResults, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1342,7 +1439,7 @@ func (g *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGrou
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
if owner.UserID != mcontext.GetOpUserID(ctx) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("not group owner")
|
||||
}
|
||||
@@ -1365,6 +1462,7 @@ func (g *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGrou
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
group.Status = constant.GroupStatusDismissed
|
||||
tips := &sdkws.GroupDismissedTips{
|
||||
Group: g.groupDB2PB(group, owner.UserID, num),
|
||||
OpUser: &sdkws.GroupMemberFullInfo{},
|
||||
@@ -1372,7 +1470,7 @@ func (g *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGrou
|
||||
if mcontext.GetOpUserID(ctx) == owner.UserID {
|
||||
tips.OpUser = g.groupMemberDB2PB(owner, 0)
|
||||
}
|
||||
g.notification.GroupDismissedNotification(ctx, tips)
|
||||
g.notification.GroupDismissedNotification(ctx, tips, req.SendMessage)
|
||||
}
|
||||
membersID, err := g.db.FindGroupMemberUserID(ctx, group.GroupID)
|
||||
if err != nil {
|
||||
@@ -1398,7 +1496,7 @@ func (g *groupServer) MuteGroupMember(ctx context.Context, req *pbgroup.MuteGrou
|
||||
if err := g.PopulateGroupMember(ctx, member); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
opMember, err := g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1434,7 +1532,7 @@ func (g *groupServer) CancelMuteGroupMember(ctx context.Context, req *pbgroup.Ca
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
if !authverify.IsAdmin(ctx) {
|
||||
opMember, err := g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1494,7 +1592,7 @@ func (g *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
|
||||
if opUserID == "" {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("no op user id")
|
||||
}
|
||||
isAppManagerUid := authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID)
|
||||
isAppManagerUid := authverify.IsAdmin(ctx)
|
||||
groupMembers := make(map[string][]*pbgroup.SetGroupMemberInfo)
|
||||
for i, member := range req.Members {
|
||||
if member.RoleLevel != nil {
|
||||
@@ -1649,6 +1747,11 @@ func (g *groupServer) GetGroupAbstractInfo(ctx context.Context, req *pbgroup.Get
|
||||
if datautil.Duplicate(req.GroupIDs) {
|
||||
return nil, errs.ErrArgs.WrapMsg("groupIDs duplicate")
|
||||
}
|
||||
for _, groupID := range req.GroupIDs {
|
||||
if err := g.checkAdminOrInGroup(ctx, groupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
groups, err := g.db.FindGroup(ctx, req.GroupIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1677,6 +1780,9 @@ func (g *groupServer) GetUserInGroupMembers(ctx context.Context, req *pbgroup.Ge
|
||||
if len(req.GroupIDs) == 0 {
|
||||
return nil, errs.ErrArgs.WrapMsg("groupIDs empty")
|
||||
}
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
members, err := g.db.FindGroupMemberUser(ctx, req.GroupIDs, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1696,6 +1802,9 @@ func (g *groupServer) GetGroupMemberUserIDs(ctx context.Context, req *pbgroup.Ge
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := authverify.CheckAccessIn(ctx, userIDs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbgroup.GetGroupMemberUserIDsResp{
|
||||
UserIDs: userIDs,
|
||||
}, nil
|
||||
@@ -1705,6 +1814,9 @@ func (g *groupServer) GetGroupMemberRoleLevel(ctx context.Context, req *pbgroup.
|
||||
if len(req.RoleLevels) == 0 {
|
||||
return nil, errs.ErrArgs.WrapMsg("RoleLevels empty")
|
||||
}
|
||||
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
members, err := g.db.FindGroupMemberRoleLevels(ctx, req.GroupID, req.RoleLevels)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1720,6 +1832,9 @@ func (g *groupServer) GetGroupMemberRoleLevel(ctx context.Context, req *pbgroup.
|
||||
}
|
||||
|
||||
func (g *groupServer) GetGroupUsersReqApplicationList(ctx context.Context, req *pbgroup.GetGroupUsersReqApplicationListReq) (*pbgroup.GetGroupUsersReqApplicationListResp, error) {
|
||||
if err := g.CheckGroupAdmin(ctx, req.GroupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
requests, err := g.db.FindGroupRequests(ctx, req.GroupID, req.UserIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1802,7 +1917,7 @@ func (g *groupServer) GetSpecifiedUserGroupRequestInfo(ctx context.Context, req
|
||||
}
|
||||
|
||||
adminIDs = append(adminIDs, owners[0].UserID)
|
||||
adminIDs = append(adminIDs, g.config.Share.IMAdminUserID...)
|
||||
adminIDs = append(adminIDs, g.adminUserIDs...)
|
||||
|
||||
if !datautil.Contain(opUserID, adminIDs...) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("opUser no permission")
|
||||
|
||||
@@ -20,8 +20,11 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
|
||||
@@ -41,7 +44,6 @@ import (
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
)
|
||||
|
||||
// GroupApplicationReceiver
|
||||
@@ -52,11 +54,11 @@ const (
|
||||
|
||||
func NewNotificationSender(db controller.GroupDatabase, config *Config, userClient *rpcli.UserClient, msgClient *rpcli.MsgClient, conversationClient *rpcli.ConversationClient) *NotificationSender {
|
||||
return &NotificationSender{
|
||||
NotificationSender: rpcclient.NewNotificationSender(&config.NotificationConfig,
|
||||
rpcclient.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) {
|
||||
NotificationSender: notification.NewNotificationSender(&config.NotificationConfig,
|
||||
notification.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) {
|
||||
return msgClient.SendMsg(ctx, req)
|
||||
}),
|
||||
rpcclient.WithUserRpcClient(userClient.GetUserInfo),
|
||||
notification.WithUserRpcClient(userClient.GetUserInfo),
|
||||
),
|
||||
getUsersInfo: func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error) {
|
||||
users, err := userClient.GetUsersInfo(ctx, userIDs)
|
||||
@@ -73,7 +75,7 @@ func NewNotificationSender(db controller.GroupDatabase, config *Config, userClie
|
||||
}
|
||||
|
||||
type NotificationSender struct {
|
||||
*rpcclient.NotificationSender
|
||||
*notification.NotificationSender
|
||||
getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error)
|
||||
db controller.GroupDatabase
|
||||
config *Config
|
||||
@@ -233,17 +235,17 @@ func (g *NotificationSender) groupMemberDB2PB(member *model.GroupMember, appMang
|
||||
return result, nil
|
||||
} */
|
||||
|
||||
func (g *NotificationSender) fillOpUser(ctx context.Context, opUser **sdkws.GroupMemberFullInfo, groupID string) (err error) {
|
||||
return g.fillOpUserByUserID(ctx, mcontext.GetOpUserID(ctx), opUser, groupID)
|
||||
func (g *NotificationSender) fillOpUser(ctx context.Context, targetUser **sdkws.GroupMemberFullInfo, groupID string) (err error) {
|
||||
return g.fillUserByUserID(ctx, mcontext.GetOpUserID(ctx), targetUser, groupID)
|
||||
}
|
||||
|
||||
func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID string, opUser **sdkws.GroupMemberFullInfo, groupID string) error {
|
||||
if opUser == nil {
|
||||
func (g *NotificationSender) fillUserByUserID(ctx context.Context, userID string, targetUser **sdkws.GroupMemberFullInfo, groupID string) error {
|
||||
if targetUser == nil {
|
||||
return errs.ErrInternalServer.WrapMsg("**sdkws.GroupMemberFullInfo is nil")
|
||||
}
|
||||
if groupID != "" {
|
||||
if authverify.IsManagerUserID(userID, g.config.Share.IMAdminUserID) {
|
||||
*opUser = &sdkws.GroupMemberFullInfo{
|
||||
if authverify.CheckUserIsAdmin(ctx, userID) {
|
||||
*targetUser = &sdkws.GroupMemberFullInfo{
|
||||
GroupID: groupID,
|
||||
UserID: userID,
|
||||
RoleLevel: constant.GroupAdmin,
|
||||
@@ -252,7 +254,7 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
|
||||
} else {
|
||||
member, err := g.db.TakeGroupMember(ctx, groupID, userID)
|
||||
if err == nil {
|
||||
*opUser = g.groupMemberDB2PB(member, 0)
|
||||
*targetUser = g.groupMemberDB2PB(member, 0)
|
||||
} else if !(errors.Is(err, mongo.ErrNoDocuments) || errs.ErrRecordNotFound.Is(err)) {
|
||||
return err
|
||||
}
|
||||
@@ -262,8 +264,8 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if *opUser == nil {
|
||||
*opUser = &sdkws.GroupMemberFullInfo{
|
||||
if *targetUser == nil {
|
||||
*targetUser = &sdkws.GroupMemberFullInfo{
|
||||
GroupID: groupID,
|
||||
UserID: userID,
|
||||
Nickname: user.Nickname,
|
||||
@@ -271,11 +273,11 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
|
||||
OperatorUserID: userID,
|
||||
}
|
||||
} else {
|
||||
if (*opUser).Nickname == "" {
|
||||
(*opUser).Nickname = user.Nickname
|
||||
if (*targetUser).Nickname == "" {
|
||||
(*targetUser).Nickname = user.Nickname
|
||||
}
|
||||
if (*opUser).FaceURL == "" {
|
||||
(*opUser).FaceURL = user.FaceURL
|
||||
if (*targetUser).FaceURL == "" {
|
||||
(*targetUser).FaceURL = user.FaceURL
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -283,7 +285,8 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
|
||||
|
||||
func (g *NotificationSender) setVersion(ctx context.Context, version *uint64, versionID *string, collName string, id string) {
|
||||
versions := versionctx.GetVersionLog(ctx).Get()
|
||||
for _, coll := range versions {
|
||||
for i := len(versions) - 1; i >= 0; i-- {
|
||||
coll := versions[i]
|
||||
if coll.Name == collName && coll.Doc.DID == id {
|
||||
*version = uint64(coll.Doc.Version)
|
||||
*versionID = coll.Doc.ID.Hex()
|
||||
@@ -307,7 +310,7 @@ func (g *NotificationSender) setSortVersion(ctx context.Context, version *uint64
|
||||
}
|
||||
}
|
||||
|
||||
func (g *NotificationSender) GroupCreatedNotification(ctx context.Context, tips *sdkws.GroupCreatedTips) {
|
||||
func (g *NotificationSender) GroupCreatedNotification(ctx context.Context, tips *sdkws.GroupCreatedTips, SendMessage *bool) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@@ -318,7 +321,7 @@ func (g *NotificationSender) GroupCreatedNotification(ctx context.Context, tips
|
||||
return
|
||||
}
|
||||
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupCreatedNotification, tips)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupCreatedNotification, tips, notification.WithSendMessage(SendMessage))
|
||||
}
|
||||
|
||||
func (g *NotificationSender) GroupInfoSetNotification(ctx context.Context, tips *sdkws.GroupInfoSetTips) {
|
||||
@@ -332,7 +335,7 @@ func (g *NotificationSender) GroupInfoSetNotification(ctx context.Context, tips
|
||||
return
|
||||
}
|
||||
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNotification, tips, rpcclient.WithRpcGetUserName())
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNotification, tips, notification.WithRpcGetUserName())
|
||||
}
|
||||
|
||||
func (g *NotificationSender) GroupInfoSetNameNotification(ctx context.Context, tips *sdkws.GroupInfoSetNameTips) {
|
||||
@@ -349,7 +352,7 @@ func (g *NotificationSender) GroupInfoSetNameNotification(ctx context.Context, t
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNameNotification, tips)
|
||||
}
|
||||
|
||||
func (g *NotificationSender) GroupInfoSetAnnouncementNotification(ctx context.Context, tips *sdkws.GroupInfoSetAnnouncementTips) {
|
||||
func (g *NotificationSender) GroupInfoSetAnnouncementNotification(ctx context.Context, tips *sdkws.GroupInfoSetAnnouncementTips, sendMessage *bool) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@@ -360,16 +363,49 @@ func (g *NotificationSender) GroupInfoSetAnnouncementNotification(ctx context.Co
|
||||
return
|
||||
}
|
||||
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetAnnouncementNotification, tips, rpcclient.WithRpcGetUserName())
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetAnnouncementNotification, tips, notification.WithRpcGetUserName(), notification.WithSendMessage(sendMessage))
|
||||
}
|
||||
|
||||
func (g *NotificationSender) JoinGroupApplicationNotification(ctx context.Context, req *pbgroup.JoinGroupReq) {
|
||||
func (g *NotificationSender) uuid() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
func (g *NotificationSender) getGroupRequest(ctx context.Context, groupID string, userID string) (*sdkws.GroupRequest, error) {
|
||||
request, err := g.db.TakeGroupRequest(ctx, groupID, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
users, err := g.getUsersInfo(ctx, []string{userID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return nil, servererrs.ErrUserIDNotFound.WrapMsg(fmt.Sprintf("user %s not found", userID))
|
||||
}
|
||||
info, ok := users[0].(*sdkws.UserInfo)
|
||||
if !ok {
|
||||
info = &sdkws.UserInfo{
|
||||
UserID: users[0].GetUserID(),
|
||||
Nickname: users[0].GetNickname(),
|
||||
FaceURL: users[0].GetFaceURL(),
|
||||
Ex: users[0].GetEx(),
|
||||
}
|
||||
}
|
||||
return convert.Db2PbGroupRequest(request, info, nil), nil
|
||||
}
|
||||
|
||||
func (g *NotificationSender) JoinGroupApplicationNotification(ctx context.Context, req *pbgroup.JoinGroupReq, dbReq *model.GroupRequest) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err)
|
||||
}
|
||||
}()
|
||||
request, err := g.getGroupRequest(ctx, dbReq.GroupID, dbReq.UserID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "JoinGroupApplicationNotification getGroupRequest", err, "dbReq", dbReq)
|
||||
return
|
||||
}
|
||||
var group *sdkws.GroupInfo
|
||||
group, err = g.getGroupInfo(ctx, req.GroupID)
|
||||
if err != nil {
|
||||
@@ -385,7 +421,13 @@ func (g *NotificationSender) JoinGroupApplicationNotification(ctx context.Contex
|
||||
return
|
||||
}
|
||||
userIDs = append(userIDs, req.InviterUserID, mcontext.GetOpUserID(ctx))
|
||||
tips := &sdkws.JoinGroupApplicationTips{Group: group, Applicant: user, ReqMsg: req.ReqMessage}
|
||||
tips := &sdkws.JoinGroupApplicationTips{
|
||||
Group: group,
|
||||
Applicant: user,
|
||||
ReqMsg: req.ReqMessage,
|
||||
Uuid: g.uuid(),
|
||||
Request: request,
|
||||
}
|
||||
for _, userID := range datautil.Distinct(userIDs) {
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.JoinGroupApplicationNotification, tips)
|
||||
}
|
||||
@@ -415,6 +457,11 @@ func (g *NotificationSender) GroupApplicationAcceptedNotification(ctx context.Co
|
||||
log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err)
|
||||
}
|
||||
}()
|
||||
request, err := g.getGroupRequest(ctx, req.GroupID, req.FromUserID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GroupApplicationAcceptedNotification getGroupRequest", err, "req", req)
|
||||
return
|
||||
}
|
||||
var group *sdkws.GroupInfo
|
||||
group, err = g.getGroupInfo(ctx, req.GroupID)
|
||||
if err != nil {
|
||||
@@ -430,8 +477,14 @@ func (g *NotificationSender) GroupApplicationAcceptedNotification(ctx context.Co
|
||||
if err = g.fillOpUser(ctx, &opUser, group.GroupID); err != nil {
|
||||
return
|
||||
}
|
||||
tips := &sdkws.GroupApplicationAcceptedTips{
|
||||
Group: group,
|
||||
OpUser: opUser,
|
||||
HandleMsg: req.HandledMsg,
|
||||
Uuid: g.uuid(),
|
||||
Request: request,
|
||||
}
|
||||
for _, userID := range append(userIDs, req.FromUserID) {
|
||||
tips := &sdkws.GroupApplicationAcceptedTips{Group: group, OpUser: opUser, HandleMsg: req.HandledMsg}
|
||||
if userID == req.FromUserID {
|
||||
tips.ReceiverAs = applicantReceiver
|
||||
} else {
|
||||
@@ -448,6 +501,11 @@ func (g *NotificationSender) GroupApplicationRejectedNotification(ctx context.Co
|
||||
log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err)
|
||||
}
|
||||
}()
|
||||
request, err := g.getGroupRequest(ctx, req.GroupID, req.FromUserID)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GroupApplicationAcceptedNotification getGroupRequest", err, "req", req)
|
||||
return
|
||||
}
|
||||
var group *sdkws.GroupInfo
|
||||
group, err = g.getGroupInfo(ctx, req.GroupID)
|
||||
if err != nil {
|
||||
@@ -463,8 +521,14 @@ func (g *NotificationSender) GroupApplicationRejectedNotification(ctx context.Co
|
||||
if err = g.fillOpUser(ctx, &opUser, group.GroupID); err != nil {
|
||||
return
|
||||
}
|
||||
tips := &sdkws.GroupApplicationRejectedTips{
|
||||
Group: group,
|
||||
OpUser: opUser,
|
||||
HandleMsg: req.HandledMsg,
|
||||
Uuid: g.uuid(),
|
||||
Request: request,
|
||||
}
|
||||
for _, userID := range append(userIDs, req.FromUserID) {
|
||||
tips := &sdkws.GroupApplicationAcceptedTips{Group: group, OpUser: opUser, HandleMsg: req.HandledMsg}
|
||||
if userID == req.FromUserID {
|
||||
tips.ReceiverAs = applicantReceiver
|
||||
} else {
|
||||
@@ -505,7 +569,7 @@ func (g *NotificationSender) GroupOwnerTransferredNotification(ctx context.Conte
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupOwnerTransferredNotification, tips)
|
||||
}
|
||||
|
||||
func (g *NotificationSender) MemberKickedNotification(ctx context.Context, tips *sdkws.MemberKickedTips) {
|
||||
func (g *NotificationSender) MemberKickedNotification(ctx context.Context, tips *sdkws.MemberKickedTips, SendMessage *bool) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@@ -516,10 +580,14 @@ func (g *NotificationSender) MemberKickedNotification(ctx context.Context, tips
|
||||
return
|
||||
}
|
||||
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.MemberKickedNotification, tips)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.MemberKickedNotification, tips, notification.WithSendMessage(SendMessage))
|
||||
}
|
||||
|
||||
func (g *NotificationSender) GroupApplicationAgreeMemberEnterNotification(ctx context.Context, groupID string, invitedOpUserID string, entrantUserID ...string) error {
|
||||
func (g *NotificationSender) GroupApplicationAgreeMemberEnterNotification(ctx context.Context, groupID string, SendMessage *bool, invitedOpUserID string, entrantUserID ...string) error {
|
||||
return g.groupApplicationAgreeMemberEnterNotification(ctx, groupID, SendMessage, invitedOpUserID, entrantUserID...)
|
||||
}
|
||||
|
||||
func (g *NotificationSender) groupApplicationAgreeMemberEnterNotification(ctx context.Context, groupID string, SendMessage *bool, invitedOpUserID string, entrantUserID ...string) error {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@@ -556,20 +624,18 @@ func (g *NotificationSender) GroupApplicationAgreeMemberEnterNotification(ctx co
|
||||
InvitedUserList: users,
|
||||
}
|
||||
opUserID := mcontext.GetOpUserID(ctx)
|
||||
if err = g.fillOpUserByUserID(ctx, opUserID, &tips.OpUser, tips.Group.GroupID); err != nil {
|
||||
if err = g.fillUserByUserID(ctx, opUserID, &tips.OpUser, tips.Group.GroupID); err != nil {
|
||||
return nil
|
||||
}
|
||||
switch {
|
||||
case invitedOpUserID == "":
|
||||
case invitedOpUserID == opUserID:
|
||||
if invitedOpUserID == opUserID {
|
||||
tips.InviterUser = tips.OpUser
|
||||
default:
|
||||
if err = g.fillOpUserByUserID(ctx, invitedOpUserID, &tips.InviterUser, tips.Group.GroupID); err != nil {
|
||||
} else {
|
||||
if err = g.fillUserByUserID(ctx, invitedOpUserID, &tips.InviterUser, tips.Group.GroupID); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberInvitedNotification, tips)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberInvitedNotification, tips, notification.WithSendMessage(SendMessage))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -614,7 +680,7 @@ func (g *NotificationSender) MemberEnterNotification(ctx context.Context, groupI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *NotificationSender) GroupDismissedNotification(ctx context.Context, tips *sdkws.GroupDismissedTips) {
|
||||
func (g *NotificationSender) GroupDismissedNotification(ctx context.Context, tips *sdkws.GroupDismissedTips, SendMessage *bool) {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
@@ -624,7 +690,7 @@ func (g *NotificationSender) GroupDismissedNotification(ctx context.Context, tip
|
||||
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
|
||||
return
|
||||
}
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupDismissedNotification, tips)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupDismissedNotification, tips, notification.WithSendMessage(SendMessage))
|
||||
}
|
||||
|
||||
func (g *NotificationSender) GroupMemberMutedNotification(ctx context.Context, groupID, groupMemberUserID string, mutedSeconds uint32) {
|
||||
@@ -781,7 +847,7 @@ func (g *NotificationSender) GroupMemberSetToAdminNotification(ctx context.Conte
|
||||
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
|
||||
return
|
||||
}
|
||||
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
|
||||
g.setSortVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID, &tips.GroupSortVersion)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToAdminNotification, tips)
|
||||
}
|
||||
|
||||
@@ -806,6 +872,6 @@ func (g *NotificationSender) GroupMemberSetToOrdinaryUserNotification(ctx contex
|
||||
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
|
||||
return
|
||||
}
|
||||
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
|
||||
g.setSortVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID, &tips.GroupSortVersion)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToOrdinaryUserNotification, tips)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/protocol/group"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
)
|
||||
@@ -26,6 +27,9 @@ func (g *groupServer) GroupCreateCount(ctx context.Context, req *group.GroupCrea
|
||||
if req.Start > req.End {
|
||||
return nil, errs.ErrArgs.WrapMsg("start > end: %d > %d", req.Start, req.End)
|
||||
}
|
||||
if err := authverify.CheckAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
total, err := g.db.CountTotal(ctx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
+44
-150
@@ -2,6 +2,7 @@ package group
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
@@ -11,16 +12,20 @@ import (
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
pbgroup "github.com/openimsdk/protocol/group"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
)
|
||||
|
||||
const versionSyncLimit = 500
|
||||
|
||||
func (g *groupServer) GetFullGroupMemberUserIDs(ctx context.Context, req *pbgroup.GetFullGroupMemberUserIDsReq) (*pbgroup.GetFullGroupMemberUserIDsResp, error) {
|
||||
vl, err := g.db.FindMaxGroupMemberVersionCache(ctx, req.GroupID)
|
||||
userIDs, err := g.db.FindGroupMemberUserID(ctx, req.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userIDs, err := g.db.FindGroupMemberUserID(ctx, req.GroupID)
|
||||
if err := authverify.CheckAccessIn(ctx, userIDs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vl, err := g.db.FindMaxGroupMemberVersionCache(ctx, req.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -29,7 +34,7 @@ func (g *groupServer) GetFullGroupMemberUserIDs(ctx context.Context, req *pbgrou
|
||||
userIDs = nil
|
||||
}
|
||||
return &pbgroup.GetFullGroupMemberUserIDsResp{
|
||||
Version: idHash,
|
||||
Version: uint64(vl.Version),
|
||||
VersionID: vl.ID.Hex(),
|
||||
Equal: req.IdHash == idHash,
|
||||
UserIDs: userIDs,
|
||||
@@ -37,6 +42,9 @@ func (g *groupServer) GetFullGroupMemberUserIDs(ctx context.Context, req *pbgrou
|
||||
}
|
||||
|
||||
func (g *groupServer) GetFullJoinGroupIDs(ctx context.Context, req *pbgroup.GetFullJoinGroupIDsReq) (*pbgroup.GetFullJoinGroupIDsResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vl, err := g.db.FindMaxJoinGroupVersionCache(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -50,7 +58,7 @@ func (g *groupServer) GetFullJoinGroupIDs(ctx context.Context, req *pbgroup.GetF
|
||||
groupIDs = nil
|
||||
}
|
||||
return &pbgroup.GetFullJoinGroupIDsResp{
|
||||
Version: idHash,
|
||||
Version: uint64(vl.Version),
|
||||
VersionID: vl.ID.Hex(),
|
||||
Equal: req.IdHash == idHash,
|
||||
GroupIDs: groupIDs,
|
||||
@@ -58,6 +66,9 @@ func (g *groupServer) GetFullJoinGroupIDs(ctx context.Context, req *pbgroup.GetF
|
||||
}
|
||||
|
||||
func (g *groupServer) GetIncrementalGroupMember(ctx context.Context, req *pbgroup.GetIncrementalGroupMemberReq) (*pbgroup.GetIncrementalGroupMemberResp, error) {
|
||||
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
group, err := g.db.TakeGroup(ctx, req.GroupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -132,152 +143,8 @@ func (g *groupServer) GetIncrementalGroupMember(ctx context.Context, req *pbgrou
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (g *groupServer) BatchGetIncrementalGroupMember(ctx context.Context, req *pbgroup.BatchGetIncrementalGroupMemberReq) (resp *pbgroup.BatchGetIncrementalGroupMemberResp, err error) {
|
||||
type VersionInfo struct {
|
||||
GroupID string
|
||||
VersionID string
|
||||
VersionNumber uint64
|
||||
}
|
||||
|
||||
var groupIDs []string
|
||||
|
||||
groupsVersionMap := make(map[string]*VersionInfo)
|
||||
groupsMap := make(map[string]*model.Group)
|
||||
hasGroupUpdateMap := make(map[string]bool)
|
||||
sortVersionMap := make(map[string]uint64)
|
||||
|
||||
var targetKeys, versionIDs []string
|
||||
var versionNumbers []uint64
|
||||
|
||||
var requestBodyLen int
|
||||
|
||||
for _, group := range req.ReqList {
|
||||
groupsVersionMap[group.GroupID] = &VersionInfo{
|
||||
GroupID: group.GroupID,
|
||||
VersionID: group.VersionID,
|
||||
VersionNumber: group.Version,
|
||||
}
|
||||
|
||||
groupIDs = append(groupIDs, group.GroupID)
|
||||
}
|
||||
|
||||
groups, err := g.db.FindGroup(ctx, groupIDs)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if group.Status == constant.GroupStatusDismissed {
|
||||
err = servererrs.ErrDismissedAlready.Wrap()
|
||||
log.ZError(ctx, "This group is Dismissed Already", err, "group is", group.GroupID)
|
||||
|
||||
delete(groupsVersionMap, group.GroupID)
|
||||
} else {
|
||||
groupsMap[group.GroupID] = group
|
||||
}
|
||||
}
|
||||
|
||||
for groupID, vInfo := range groupsVersionMap {
|
||||
targetKeys = append(targetKeys, groupID)
|
||||
versionIDs = append(versionIDs, vInfo.VersionID)
|
||||
versionNumbers = append(versionNumbers, vInfo.VersionNumber)
|
||||
}
|
||||
|
||||
opt := incrversion.BatchOption[[]*sdkws.GroupMemberFullInfo, pbgroup.BatchGetIncrementalGroupMemberResp]{
|
||||
Ctx: ctx,
|
||||
TargetKeys: targetKeys,
|
||||
VersionIDs: versionIDs,
|
||||
VersionNumbers: versionNumbers,
|
||||
Versions: func(ctx context.Context, groupIDs []string, versions []uint64, limits []int) (map[string]*model.VersionLog, error) {
|
||||
vLogs, err := g.db.BatchFindMemberIncrVersion(ctx, groupIDs, versions, limits)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
for groupID, vlog := range vLogs {
|
||||
vlogElems := make([]model.VersionLogElem, 0, len(vlog.Logs))
|
||||
for i, log := range vlog.Logs {
|
||||
switch log.EID {
|
||||
case model.VersionGroupChangeID:
|
||||
vlog.LogLen--
|
||||
hasGroupUpdateMap[groupID] = true
|
||||
case model.VersionSortChangeID:
|
||||
vlog.LogLen--
|
||||
sortVersionMap[groupID] = uint64(log.Version)
|
||||
default:
|
||||
vlogElems = append(vlogElems, vlog.Logs[i])
|
||||
}
|
||||
}
|
||||
vlog.Logs = vlogElems
|
||||
if vlog.LogLen > 0 {
|
||||
hasGroupUpdateMap[groupID] = true
|
||||
}
|
||||
}
|
||||
|
||||
return vLogs, nil
|
||||
},
|
||||
CacheMaxVersions: g.db.BatchFindMaxGroupMemberVersionCache,
|
||||
Find: func(ctx context.Context, groupID string, ids []string) ([]*sdkws.GroupMemberFullInfo, error) {
|
||||
memberInfo, err := g.getGroupMembersInfo(ctx, groupID, ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return memberInfo, err
|
||||
},
|
||||
Resp: func(versions map[string]*model.VersionLog, deleteIdsMap map[string][]string, insertListMap, updateListMap map[string][]*sdkws.GroupMemberFullInfo, fullMap map[string]bool) *pbgroup.BatchGetIncrementalGroupMemberResp {
|
||||
resList := make(map[string]*pbgroup.GetIncrementalGroupMemberResp)
|
||||
|
||||
for groupID, versionLog := range versions {
|
||||
resList[groupID] = &pbgroup.GetIncrementalGroupMemberResp{
|
||||
VersionID: versionLog.ID.Hex(),
|
||||
Version: uint64(versionLog.Version),
|
||||
Full: fullMap[groupID],
|
||||
Delete: deleteIdsMap[groupID],
|
||||
Insert: insertListMap[groupID],
|
||||
Update: updateListMap[groupID],
|
||||
SortVersion: sortVersionMap[groupID],
|
||||
}
|
||||
|
||||
requestBodyLen += len(insertListMap[groupID]) + len(updateListMap[groupID]) + len(deleteIdsMap[groupID])
|
||||
if requestBodyLen > 200 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &pbgroup.BatchGetIncrementalGroupMemberResp{
|
||||
RespList: resList,
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
resp, err = opt.Build()
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
|
||||
for groupID, val := range resp.RespList {
|
||||
if val.Full || hasGroupUpdateMap[groupID] {
|
||||
count, err := g.db.FindGroupMemberNum(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
owner, err := g.db.TakeGroupOwner(ctx, groupID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.RespList[groupID].Group = g.groupDB2PB(groupsMap[groupID], owner.UserID, count)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
|
||||
}
|
||||
|
||||
func (g *groupServer) GetIncrementalJoinGroup(ctx context.Context, req *pbgroup.GetIncrementalJoinGroupReq) (*pbgroup.GetIncrementalJoinGroupResp, error) {
|
||||
if err := authverify.CheckAccessV3(ctx, req.UserID, g.config.Share.IMAdminUserID); err != nil {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opt := incrversion.Option[*sdkws.GroupInfo, pbgroup.GetIncrementalJoinGroupResp]{
|
||||
@@ -301,3 +168,30 @@ func (g *groupServer) GetIncrementalJoinGroup(ctx context.Context, req *pbgroup.
|
||||
}
|
||||
return opt.Build()
|
||||
}
|
||||
|
||||
func (g *groupServer) BatchGetIncrementalGroupMember(ctx context.Context, req *pbgroup.BatchGetIncrementalGroupMemberReq) (*pbgroup.BatchGetIncrementalGroupMemberResp, error) {
|
||||
var num int
|
||||
resp := make(map[string]*pbgroup.GetIncrementalGroupMemberResp)
|
||||
|
||||
for _, memberReq := range req.ReqList {
|
||||
if _, ok := resp[memberReq.GroupID]; ok {
|
||||
continue
|
||||
}
|
||||
memberResp, err := g.GetIncrementalGroupMember(ctx, memberReq)
|
||||
if err != nil {
|
||||
if errors.Is(err, servererrs.ErrDismissedAlready) {
|
||||
log.ZWarn(ctx, "Failed to get incremental group member", err, "groupID", memberReq.GroupID, "request", memberReq)
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp[memberReq.GroupID] = memberResp
|
||||
num += len(memberResp.Insert) + len(memberResp.Update) + len(memberResp.Delete)
|
||||
if num >= versionSyncLimit {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return &pbgroup.BatchGetIncrementalGroupMemberResp{RespList: resp}, nil
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/protocol/msg"
|
||||
@@ -29,6 +30,9 @@ import (
|
||||
)
|
||||
|
||||
func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *msg.GetConversationsHasReadAndMaxSeqReq) (*msg.GetConversationsHasReadAndMaxSeqResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var conversationIDs []string
|
||||
if len(req.ConversationIDs) == 0 {
|
||||
var err error
|
||||
@@ -61,6 +65,13 @@ func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *m
|
||||
return nil, err
|
||||
}
|
||||
resp := &msg.GetConversationsHasReadAndMaxSeqResp{Seqs: make(map[string]*msg.Seqs)}
|
||||
if req.ReturnPinned {
|
||||
pinnedConversationIDs, err := m.ConversationLocalCache.GetPinnedConversationIDs(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp.PinnedConversationIDs = pinnedConversationIDs
|
||||
}
|
||||
for conversationID, maxSeq := range maxSeqs {
|
||||
resp.Seqs[conversationID] = &msg.Seqs{
|
||||
HasReadSeq: hasReadSeqs[conversationID],
|
||||
@@ -75,6 +86,9 @@ func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *m
|
||||
}
|
||||
|
||||
func (m *msgServer) SetConversationHasReadSeq(ctx context.Context, req *msg.SetConversationHasReadSeqReq) (*msg.SetConversationHasReadSeqResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -90,8 +104,8 @@ func (m *msgServer) SetConversationHasReadSeq(ctx context.Context, req *msg.SetC
|
||||
}
|
||||
|
||||
func (m *msgServer) MarkMsgsAsRead(ctx context.Context, req *msg.MarkMsgsAsReadReq) (*msg.MarkMsgsAsReadResp, error) {
|
||||
if len(req.Seqs) < 1 {
|
||||
return nil, errs.ErrArgs.WrapMsg("seqs must not be empty")
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID)
|
||||
if err != nil {
|
||||
@@ -132,6 +146,9 @@ func (m *msgServer) MarkMsgsAsRead(ctx context.Context, req *msg.MarkMsgsAsReadR
|
||||
}
|
||||
|
||||
func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkConversationAsReadReq) (*msg.MarkConversationAsReadResp, error) {
|
||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversation, err := m.ConversationLocalCache.GetConversation(ctx, req.UserID, req.ConversationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -209,5 +226,4 @@ func (m *msgServer) sendMarkAsReadNotification(ctx context.Context, conversation
|
||||
HasReadSeq: hasReadSeq,
|
||||
}
|
||||
m.notificationSender.NotificationWithSessionType(ctx, sendID, recvID, constant.HasReadReceipt, sessionType, tips)
|
||||
|
||||
}
|
||||
|
||||
@@ -16,7 +16,12 @@ package msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/apistruct"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
|
||||
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
@@ -25,6 +30,7 @@ import (
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
@@ -83,6 +89,7 @@ func (m *msgServer) webhookBeforeSendSingleMsg(ctx context.Context, before *conf
|
||||
})
|
||||
}
|
||||
|
||||
// Move to msgtransfer
|
||||
func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) {
|
||||
if msg.MsgData.ContentType == constant.Typing {
|
||||
return
|
||||
@@ -94,7 +101,7 @@ func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config
|
||||
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand),
|
||||
RecvID: msg.MsgData.RecvID,
|
||||
}
|
||||
m.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendSingleMsgResp{}, after)
|
||||
m.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendSingleMsgResp{}, after, buildKeyMsgDataQuery(msg.MsgData))
|
||||
}
|
||||
|
||||
func (m *msgServer) webhookBeforeSendGroupMsg(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error {
|
||||
@@ -128,14 +135,15 @@ func (m *msgServer) webhookAfterSendGroupMsg(ctx context.Context, after *config.
|
||||
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand),
|
||||
GroupID: msg.MsgData.GroupID,
|
||||
}
|
||||
m.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendGroupMsgResp{}, after)
|
||||
|
||||
m.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendGroupMsgResp{}, after, buildKeyMsgDataQuery(msg.MsgData))
|
||||
}
|
||||
|
||||
func (m *msgServer) webhookBeforeMsgModify(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error {
|
||||
func (m *msgServer) webhookBeforeMsgModify(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq, beforeMsgData **sdkws.MsgData) error {
|
||||
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
|
||||
if msg.MsgData.ContentType != constant.Text {
|
||||
return nil
|
||||
}
|
||||
//if msg.MsgData.ContentType != constant.Text {
|
||||
// return nil
|
||||
//}
|
||||
if !filterBeforeMsg(msg, before) {
|
||||
return nil
|
||||
}
|
||||
@@ -146,9 +154,14 @@ func (m *msgServer) webhookBeforeMsgModify(ctx context.Context, before *config.B
|
||||
if err := m.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if beforeMsgData != nil {
|
||||
*beforeMsgData = proto.Clone(msg.MsgData).(*sdkws.MsgData)
|
||||
}
|
||||
if resp.Content != nil {
|
||||
msg.MsgData.Content = []byte(*resp.Content)
|
||||
if err := json.Unmarshal(msg.MsgData.Content, &struct{}{}); err != nil {
|
||||
return errs.ErrArgs.WrapMsg("webhook msg modify content is not json", "content", string(msg.MsgData.Content))
|
||||
}
|
||||
}
|
||||
datautil.NotNilReplace(msg.MsgData.OfflinePushInfo, resp.OfflinePushInfo)
|
||||
datautil.NotNilReplace(&msg.MsgData.RecvID, resp.RecvID)
|
||||
@@ -192,3 +205,15 @@ func (m *msgServer) webhookAfterRevokeMsg(ctx context.Context, after *config.Aft
|
||||
}
|
||||
m.webhookClient.AsyncPost(ctx, callbackReq.GetCallbackCommand(), callbackReq, &cbapi.CallbackAfterRevokeMsgResp{}, after)
|
||||
}
|
||||
|
||||
func buildKeyMsgDataQuery(msg *sdkws.MsgData) map[string]string {
|
||||
keyMsgData := apistruct.KeyMsgData{
|
||||
SendID: msg.SendID,
|
||||
RecvID: msg.RecvID,
|
||||
GroupID: msg.GroupID,
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
webhook.Key: base64.StdEncoding.EncodeToString(stringutil.StructToJsonBytes(keyMsgData)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,16 @@ package msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/protocol/msg"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// DestructMsgs hard delete in Database.
|
||||
func (m *msgServer) DestructMsgs(ctx context.Context, req *msg.DestructMsgsReq) (*msg.DestructMsgsResp, error) {
|
||||
if err := authverify.CheckAdmin(ctx, m.config.Share.IMAdminUserID); err != nil {
|
||||
if err := authverify.CheckAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
docs, err := m.MsgDatabase.GetRandBeforeMsg(ctx, req.Timestamp, int(req.Limit))
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user