Compare commits

...

27 Commits

Author SHA1 Message Date
chao 964ee7a8dd feat: sending messages supports returning fields modified by webhook (#3192)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook
2025-03-05 09:04:57 +00:00
Monet Lee 0541d0bf06 fix: solve uncorrect GroupMember enter group notification type. (#3188) 2025-03-04 10:12:35 +00:00
icey-yu 737169a466 feat: system account send msg doesn't need friend verify (#3187) 2025-03-04 10:04:21 +00:00
chao 68ee86c1d9 fix: the sorting is wrong after canceling the administrator in group settings (#3185)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings
2025-03-04 08:09:29 +00:00
chao ab6c9dca71 feat: optimizing BatchGetIncrementalGroupMember (#3180)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: optimizing BatchGetIncrementalGroupMember
2025-03-03 09:59:35 +00:00
Monet Lee 887e0b7314 fix: solve uncorrect notification when set group info (#3172)
* fix: setGroupInfoEx uncorrect call.

* update notification logic.

* update group notification logic.

* update update group announcement notication.

* fix errror.

* refresh

* solve conflict.

* update args.
2025-03-03 02:39:53 +00:00
OpenIM-Gordon 1df02692bf refactor: change sendNotification to sendMessage to avoid ambiguity regarding message sending behavior. (#3173)
* feat: add a field to specify whether to send a notification message when creating a group.

* feat: add a field to specify whether to send a notification message when creating a group.

* refactor: change sendNotification to sendMessage to avoid ambiguity regarding message sending behavior.

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
2025-02-28 08:59:04 +00:00
OpenIM-Gordon 4d2fce0e52 feat: add a field to specify whether to send a notification message w… (#3163)
* feat: add a field to specify whether to send a notification message when creating a group.

* feat: add a field to specify whether to send a notification message when creating a group.

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
2025-02-28 08:38:57 +00:00
OpenIM-Gordon b7f88138bd feat: add a new message type: Markdown text (#3162) 2025-02-28 08:22:00 +00:00
chao bf6d77a267 feat: the default notification.yml is not configured properly (#3168)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline
2025-02-27 08:17:00 +00:00
icey-yu 46f1a9c7a3 fix: PCAndOther multi login policy can`t get old clients correctly (#3158) 2025-02-24 07:29:39 +00:00
icey-yu df1c8df693 feat: Change after webhook filter && feat SendSimpleMsg (#3151)
* feat: msg filter and search system account

* feat: search system account

* chore: msg

* chore: msg

* chore: msg

* chore: webhook filter && sendSimpleMessage
2025-02-20 08:13:47 +00:00
icey-yu 14393b0f53 fix: Offline push does not have a badge && Android offline push (#3146)
* fix: offline push can display badge

* feat: strategy

* feat: log

* feat: log

* chore: offlinepush

* fix: offlinepush

* fix: log
2025-02-17 10:20:31 +00:00
chao 9ed6200e45 feat: optimize code and support running in single process mode (#3142)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* monolithic

* fix: DeleteDoc crash

* fix: DeleteDoc crash

* fix: monolithic

* fix: monolithic

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: mq

* fix: mq

* fix: user msg timestamp

* fix: mq

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* seq read config

* seq read config

* 1

* 1

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1
2025-02-14 08:18:27 +00:00
chao e37ea50b94 fix: the source message of the reference is withdrawn, and the referenced message is deleted (#3137)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted
2025-02-12 10:46:38 +00:00
chao a3c43a49d8 fix: seq conversion not reading env in docker environment (#3130)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config
2025-02-10 03:44:57 +00:00
Monet Lee 489571f4b6 fix: solve stop when merge failed (#3106) 2025-02-08 07:26:35 +00:00
Monet Lee ad8829c5a6 build: keep conflict is true. (#3103) 2025-02-08 04:03:29 +00:00
chao 0bf076bb05 fix: the user sets the conversation timer cleanup timestamp unit incorrectly (#3102)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp
2025-02-08 02:36:00 +00:00
chao ed416f8376 fix: crash caused by withdrawing messages from users who have left the group (#3100)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group
2025-02-06 02:38:46 +00:00
skiffer-git 86aa26ef62 Update README.md 2025-02-05 08:28:22 +08:00
skiffer-git d1b4d84628 Update LICENSE 2025-02-04 23:25:46 +08:00
chao 274a9bee65 fix: the abnormal message has no sending time, causing the SDK to be abnormal (#3087)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time
2025-01-24 08:13:12 +00:00
Monet Lee 83c7b7134c refactor: improve workflows logic. (#3072)
* refactor: improve workflows logic.

* update args.

* remove unused contents.

* update milestone merge contents.

* update contents.
2025-01-23 07:18:39 +00:00
chao bbb5473d26 fix: DeleteDoc crash (#3078)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash
2025-01-22 07:24:40 +00:00
icey-yu 96baa5a0ff fix: check error in BatchSetTokenMapByUidPid (#3076) 2025-01-22 06:38:27 +00:00
icey-yu 47e916aebe feat: add backup volume && optimize log print (#3066)
* feat: backup

* feat: backup

* feat: backup

* feat: optimize log print
2025-01-17 01:38:55 +00:00
148 changed files with 4520 additions and 2894 deletions
+2
View File
@@ -17,6 +17,8 @@ OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.8.4
DATA_DIR=./ DATA_DIR=./
MONGO_BACKUP_DIR=${DATA_DIR}components/backup/mongo/
PROMETHEUS_PORT=19091 PROMETHEUS_PORT=19091
ALERTMANAGER_PORT=19093 ALERTMANAGER_PORT=19093
GRAFANA_PORT=13000 GRAFANA_PORT=13000
@@ -19,26 +19,26 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Docker Buildx - 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 - name: Log in to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Aliyun Container Registry - name: Log in to Aliyun Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
registry: registry.cn-hangzhou.aliyuncs.com registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALIREGISTRY_USERNAME }} username: ${{ secrets.ALIREGISTRY_USERNAME }}
@@ -46,7 +46,7 @@ jobs:
- name: Extract metadata for Docker (tags, labels) - name: Extract metadata for Docker (tags, labels)
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5.6.0
with: with:
tags: | tags: |
type=ref,event=tag type=ref,event=tag
@@ -54,7 +54,6 @@ jobs:
type=ref,event=branch type=ref,event=branch
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern=v{{version}} type=semver,pattern=v{{version}}
# type=semver,pattern={{major}}.{{minor}}
type=semver,pattern=release-{{raw}} type=semver,pattern=release-{{raw}}
type=sha type=sha
type=raw,value=${{ github.event.inputs.tag }} type=raw,value=${{ github.event.inputs.tag }}
+101 -36
View File
@@ -4,7 +4,7 @@ on:
push: push:
pull_request: pull_request:
paths-ignore: paths-ignore:
- '**/*.md' - "**/*.md"
workflow_dispatch: workflow_dispatch:
@@ -18,7 +18,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
go_version: ["1.21.x", "1.22.x"] go_version: ["1.22.x"]
steps: steps:
- name: Checkout Server repository - name: Checkout Server repository
@@ -37,27 +37,20 @@ jobs:
- name: Set up infra services - name: Set up infra services
uses: hoverkraft-tech/compose-action@v2.0.1 uses: hoverkraft-tech/compose-action@v2.0.1
# Uncomment and set the correct path to your docker-compose file
with: with:
compose-file: "./docker-compose.yml" compose-file: "./docker-compose.yml"
# run: | # - name: Get Internal IP Address
# sudo docker compose up -d # id: get-ip
# sudo sleep 30 # Increased sleep time for better stability # run: |
# timeout-minutes: 60 # Increased timeout for Docker setup # IP=$(hostname -I | awk '{print $1}')
# echo "The IP Address is: $IP"
# echo "::set-output name=ip::$IP"
# - name: Update .env
# - name: Get Internal IP Address # run: |
# id: get-ip # sed -i 's|externalAddress:.*|externalAddress: "http://${{ steps.get-ip.outputs.ip }}:10005"|' config/minio.yml
# run: | # cat config/minio.yml
# 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: Build and test Server Services - name: Build and test Server Services
run: | run: |
@@ -85,6 +78,90 @@ jobs:
mage start mage start
mage check 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": "openIM123",
"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: go-test:
name: Benchmark Test with go ${{ matrix.go_version }} on ${{ matrix.os }} name: Benchmark Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@@ -93,11 +170,11 @@ jobs:
env: env:
SDK_DIR: openim-sdk-core SDK_DIR: openim-sdk-core
CONFIG_PATH: config/notification.yml CONFIG_PATH: config/notification.yml
# pull-requests: write
strategy: strategy:
matrix: matrix:
os: [ ubuntu-latest ] os: [ubuntu-latest]
go_version: [ "1.22.x" ] go_version: ["1.22.x"]
steps: steps:
- name: Checkout Server repository - name: Checkout Server repository
@@ -106,7 +183,8 @@ jobs:
- name: Checkout SDK repository - name: Checkout SDK repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
repository: 'openimsdk/openim-sdk-core' repository: "openimsdk/openim-sdk-core"
ref: "release-v3.8"
path: ${{ env.SDK_DIR }} path: ${{ env.SDK_DIR }}
- name: Set up Go ${{ matrix.go_version }} - name: Set up Go ${{ matrix.go_version }}
@@ -119,11 +197,6 @@ jobs:
go install github.com/magefile/mage@latest go install github.com/magefile/mage@latest
go mod download 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 - name: Modify Server Configuration
run: | run: |
yq e '.groupCreated.isSendMsg = true' -i ${{ env.CONFIG_PATH }} yq e '.groupCreated.isSendMsg = true' -i ${{ env.CONFIG_PATH }}
@@ -183,11 +256,3 @@ jobs:
run: | run: |
CONTAINER_NAME="${{ github.event.repository.name }}-container" CONTAINER_NAME="${{ github.event.repository.name }}-container"
docker logs $CONTAINER_NAME 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
+73 -126
View File
@@ -1,4 +1,4 @@
name: Create Pre-Release PR from Milestone name: Create Individual PRs from Milestone
permissions: permissions:
contents: write contents: write
@@ -9,24 +9,24 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
milestone_name: milestone_name:
description: 'Milestone name to collect closed PRs from' description: "Milestone name to collect closed PRs from"
required: true required: true
default: 'v3.8.2' default: "v3.8.4"
target_branch: target_branch:
description: 'Target branch to merge the consolidated PR' description: "Target branch to merge the consolidated PR"
required: true required: true
default: 'pre-release-v3.8.2' default: "pre-release-v3.8.4"
env: env:
MILESTONE_NAME: ${{ github.event.inputs.milestone_name || 'v3.8.2' }} MILESTONE_NAME: ${{ github.event.inputs.milestone_name || 'v3.8.4' }}
TARGET_BRANCH: ${{ github.event.inputs.target_branch || 'pre-release-v3.8.2' }} TARGET_BRANCH: ${{ github.event.inputs.target_branch || 'pre-release-v3.8.4' }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }} BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
LABEL_NAME: cherry-picked LABEL_NAME: cherry-picked
TEMP_DIR: /tmp # Using /tmp as the temporary directory TEMP_DIR: /tmp
jobs: jobs:
cherry_pick_milestone_prs: merge_milestone_prs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Setup temp directory - name: Setup temp directory
@@ -47,7 +47,6 @@ jobs:
- name: Setup Git User for OpenIM-Robot - name: Setup Git User for OpenIM-Robot
run: | run: |
# Set up Git credentials for the bot
git config --global user.email "OpenIM-Robot@users.noreply.github.com" git config --global user.email "OpenIM-Robot@users.noreply.github.com"
git config --global user.name "OpenIM-Robot" git config --global user.name "OpenIM-Robot"
@@ -83,136 +82,84 @@ jobs:
if ! echo "$labels" | grep -q "${LABEL_NAME}"; then 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 #$pr_number does not have the 'cherry-picked' label. Adding to the list."
echo "$pr_number" >> ${{ env.TEMP_DIR }}/pr_numbers.txt echo "$pr_number" >> ${{ env.TEMP_DIR }}/pr_numbers.txt
else
echo "PR #$pr_number already has the 'cherry-picked' label. Skipping."
fi fi
done done
# Sort the filtered PR numbers
sort -n ${{ env.TEMP_DIR }}/pr_numbers.txt -o ${{ env.TEMP_DIR }}/pr_numbers.txt sort -n ${{ env.TEMP_DIR }}/pr_numbers.txt -o ${{ env.TEMP_DIR }}/pr_numbers.txt
echo "Filtered and sorted PR numbers:" - name: Create Individual PRs
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
run: | 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 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" \ pr_details=$(curl -s -H "Authorization: token $BOT_TOKEN" \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/pulls/$pr_number") "https://api.github.com/repos/${{ github.repository }}/pulls/$pr_number")
pr_title=$(echo "$pr_details" | jq -r '.title') 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') merge_commit=$(echo "$pr_details" | jq -r '.merge_commit_sha')
short_commit_hash=$(echo "$merge_commit" | cut -c 1-7) short_commit_hash=$(echo "$merge_commit" | cut -c 1-7)
# Append PR details to the body if [ "$merge_commit" != "null" ]; then
echo "- $pr_title: (#$pr_number) ($short_commit_hash)" >> ${{ env.TEMP_DIR }}/pr_body.txt git fetch origin
echo "Checking out target branch: $TARGET_BRANCH"
git checkout $TARGET_BRANCH
if [ "$merge_commit" != "null" ];then echo "Pulling latest changes from target branch: $TARGET_BRANCH"
echo "$merge_commit" >> ${{ env.TEMP_DIR }}/commit_hashes.txt git pull origin $TARGET_BRANCH
echo "#$pr_number" >> ${{ env.TEMP_DIR }}/pr_title.txt
pr_numbers_in_title="$pr_numbers_in_title #$pr_number" 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')
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 fi
done 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."
+36 -47
View File
@@ -25,11 +25,11 @@ jobs:
with: with:
path: main-repo path: main-repo
- name: Set up QEMU # - name: Set up QEMU
uses: docker/setup-qemu-action@v3 # uses: docker/setup-qemu-action@v3.3.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3.8.0
- name: Build Docker image - name: Build Docker image
id: build id: build
@@ -38,11 +38,8 @@ jobs:
context: ./main-repo context: ./main-repo
load: true load: true
tags: "openim/openim-server:local" tags: "openim/openim-server:local"
cache-from: type=gha cache-from: type=gha,scope=build
cache-to: type=gha,mode=max cache-to: type=gha,mode=max,scope=build
- name: Save Docker image to file
run: docker save -o image.tar openim/openim-server:local
- name: Checkout compose repository - name: Checkout compose repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -66,43 +63,12 @@ jobs:
run: | run: |
cd ${{ github.workspace }}/compose-repo cd ${{ github.workspace }}/compose-repo
docker compose up -d docker compose up -d
sleep 60
# - name: Check openim-server health docker compose ps
# run: |
# timeout=300
# interval=30
# elapsed=0
# while [[ $elapsed -le $timeout ]]; do
# if ! docker exec openim-server mage check; then
# echo "openim-server is not ready, waiting..."
# sleep $interval
# elapsed=$(($elapsed + $interval))
# else
# echo "Health check successful"
# exit 0
# fi
# done
# echo "Health check failed after 5 minutes"
# exit 1
# - name: Check openim-chat health
# if: success()
# run: |
# if ! docker exec openim-chat mage check; then
# echo "openim-chat check failed"
# exit 1
# else
# echo "Health check successful"
# exit 0
# fi
- name: Load Docker image from file
run: docker load -i image.tar
- name: Extract metadata for Docker (tags, labels) - name: Extract metadata for Docker (tags, labels)
id: meta id: meta
uses: docker/metadata-action@v5.5.1 uses: docker/metadata-action@v5.6.0
with: with:
images: | images: |
openim/openim-server openim/openim-server
@@ -112,29 +78,27 @@ jobs:
type=ref,event=tag type=ref,event=tag
type=schedule type=schedule
type=ref,event=branch type=ref,event=branch
type=semver,pattern={{version}} # type=semver,pattern={{version}}
type=semver,pattern=v{{version}} type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=semver,pattern=release-{{raw}} type=semver,pattern=release-{{raw}}
type=sha type=sha
type=raw,value=${{ github.event.inputs.tag }} type=raw,value=${{ github.event.inputs.tag }}
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Aliyun Container Registry - name: Log in to Aliyun Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
registry: registry.cn-hangzhou.aliyuncs.com registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALIREGISTRY_USERNAME }} username: ${{ secrets.ALIREGISTRY_USERNAME }}
@@ -148,3 +112,28 @@ jobs:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=build
cache-to: type=gha,mode=max,scope=build
- name: Verify multi-platform support
run: |
images=("openim/openim-server" "ghcr.io/openimsdk/openim-server" "registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server")
for image in "${images[@]}"; do
for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n'); do
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
done
done
+22 -188
View File
@@ -1,201 +1,35 @@
Apache License # Open Source License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION OpenIM is licensed under the Apache License 2.0, with the following additional conditions:
1. Definitions. 1. OpenIM may be utilized commercially, including as a backend service for other applications or as an application development platform for enterprises.
A commercial license must be obtained from the producer if:
- Under no circumstances may you operate a multi-tenant or multi-business environment using the OpenIM source code, whether or not you have modified the repository code. In other words, a single instance of OpenIM may not simultaneously serve multiple enterprises, nor may it serve multiple lines of business within the same enterprise.
- If you intend to operate in such a multi-tenant or multi-business manner, you must obtain a commercial license from the producer in advance.
"License" shall mean the terms and conditions for use, reproduction, 2. As a contributor, you should agree that:
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by a. The producer can adjust the open-source agreement to be more strict or more relaxed as deemed necessary.
the copyright owner that is granting the License. b. Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations.
"Legal Entity" shall mean the union of the acting entity and all Apart from the specific conditions mentioned above, all other rights and restrictions follow the Apache License 2.0. Detailed information about the Apache License 2.0 can be found at http://www.apache.org/licenses/LICENSE-2.0.
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, For any licensing-related questions or to obtain a commercial license, please contact contact@openim.io.
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or © 2024 OpenIMSDK
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object ----------
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including Licensed under the Apache License, Version 2.0 (the "License");
the original version of the Work and any modifications or additions you may not use this file except in compliance with the License.
to that Work or Derivative Works thereof, that is intentionally You may obtain a copy of the License at
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity http://www.apache.org/licenses/LICENSE-2.0
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of Unless required by applicable law or agreed to in writing, software
this License, each Contributor hereby grants to You a perpetual, distributed under the License is distributed on an "AS IS" BASIS,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
copyright license to reproduce, prepare Derivative Works of, See the License for the specific language governing permissions and
publicly display, publicly perform, sublicense, and distribute the limitations under the License.
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
+2 -1
View File
@@ -132,7 +132,8 @@ Thank you for contributing to building a powerful instant messaging solution!
## :closed_book: License ## :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. For more details, please refer to [here](./LICENSE).
+419
View File
@@ -0,0 +1,419 @@
package main
import (
"bytes"
"context"
"encoding/json"
"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/openimsdk/tools/utils/network"
"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
}
}
ip, err := network.GetLocalIP()
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())
target, err := json.Marshal(prommetrics.BuildDefaultTarget(ip, listener.Addr().(*net.TCPAddr).Port))
if err != nil {
return err
}
if err := standalone.GetKeyValue().SetKey(ctx, prommetrics.BuildDiscoveryKey(prommetrics.APIKeyName), target); err != nil {
return err
}
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.Conn, 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.GetDiscoveryConn(), 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
}
+13 -13
View File
@@ -31,7 +31,7 @@ joinGroupApplication:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: false enable: true
title: joinGroupApplication title title: joinGroupApplication title
desc: joinGroupApplication desc desc: joinGroupApplication desc
ext: joinGroupApplication ext ext: joinGroupApplication ext
@@ -51,7 +51,7 @@ groupApplicationAccepted:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: false enable: true
title: groupApplicationAccepted title title: groupApplicationAccepted title
desc: groupApplicationAccepted desc desc: groupApplicationAccepted desc
ext: groupApplicationAccepted ext ext: groupApplicationAccepted ext
@@ -61,7 +61,7 @@ groupApplicationRejected:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: false enable: true
title: groupApplicationRejected title title: groupApplicationRejected title
desc: groupApplicationRejected desc desc: groupApplicationRejected desc
ext: groupApplicationRejected ext ext: groupApplicationRejected ext
@@ -198,7 +198,7 @@ friendApplicationAdded:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: false enable: true
title: Somebody applies to add you as a friend title: Somebody applies to add you as a friend
desc: 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 ext: Somebody applies to add you as a friend
@@ -228,7 +228,7 @@ friendAdded:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: We have become friends title: We have become friends
desc: We have become friends desc: We have become friends
ext: We have become friends ext: We have become friends
@@ -238,7 +238,7 @@ friendDeleted:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: deleted a friend title: deleted a friend
desc: deleted a friend desc: deleted a friend
ext: deleted a friend ext: deleted a friend
@@ -248,7 +248,7 @@ friendRemarkSet:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: Your friend's profile has been changed title: Your friend's profile has been changed
desc: Your friend's profile has been changed desc: Your friend's profile has been changed
ext: Your friend's profile has been changed ext: Your friend's profile has been changed
@@ -258,7 +258,7 @@ blackAdded:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: blocked a user title: blocked a user
desc: blocked a user desc: blocked a user
ext: blocked a user ext: blocked a user
@@ -268,7 +268,7 @@ blackDeleted:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: Remove a blocked user title: Remove a blocked user
desc: Remove a blocked user desc: Remove a blocked user
ext: Remove a blocked user ext: Remove a blocked user
@@ -278,7 +278,7 @@ friendInfoUpdated:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: friend info updated title: friend info updated
desc: friend info updated desc: friend info updated
ext: friend info updated ext: friend info updated
@@ -289,7 +289,7 @@ userInfoUpdated:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: userInfo updated title: userInfo updated
desc: userInfo updated desc: userInfo updated
ext: userInfo updated ext: userInfo updated
@@ -310,7 +310,7 @@ conversationChanged:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: conversation changed title: conversation changed
desc: conversation changed desc: conversation changed
ext: conversation changed ext: conversation changed
@@ -320,7 +320,7 @@ conversationSetPrivate:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: burn after reading title: burn after reading
desc: burn after reading desc: burn after reading
ext: burn after reading ext: burn after reading
+4 -12
View File
@@ -3,14 +3,7 @@ beforeSendSingleMsg:
enable: false enable: false
timeout: 5 timeout: 5
failedContinue: true 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. # 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. # If not set, all contentType messages will through this filter.
deniedTypes: [] deniedTypes: []
beforeUpdateUserInfoEx: beforeUpdateUserInfoEx:
@@ -23,31 +16,30 @@ afterUpdateUserInfoEx:
afterSendSingleMsg: afterSendSingleMsg:
enable: false enable: false
timeout: 5 timeout: 5
# Only the senID/recvID specified in attentionIds will send the callback # Only the recvID specified in attentionIds will send the callback
# if not set, all user messages will be callback # if not set, all user messages will be callback
attentionIds: [] attentionIds: []
# See beforeSendSingleMsg comment. # See beforeSendSingleMsg comment.
allowedTypes: []
deniedTypes: [] deniedTypes: []
beforeSendGroupMsg: beforeSendGroupMsg:
enable: false enable: false
timeout: 5 timeout: 5
failedContinue: true failedContinue: true
# See beforeSendSingleMsg comment. # See beforeSendSingleMsg comment.
allowedTypes: []
deniedTypes: [] deniedTypes: []
beforeMsgModify: beforeMsgModify:
enable: false enable: false
timeout: 5 timeout: 5
failedContinue: true failedContinue: true
# See beforeSendSingleMsg comment. # See beforeSendSingleMsg comment.
allowedTypes: []
deniedTypes: [] deniedTypes: []
afterSendGroupMsg: afterSendGroupMsg:
enable: false enable: false
timeout: 5 timeout: 5
# Only the recvID specified in attentionIds will send the callback
# if not set, all user messages will be callback
attentionIds: []
# See beforeSendSingleMsg comment. # See beforeSendSingleMsg comment.
allowedTypes: []
deniedTypes: [] deniedTypes: []
afterUserOnline: afterUserOnline:
enable: false enable: false
+1
View File
@@ -37,6 +37,7 @@ services:
- "${DATA_DIR}/components/mongodb/data/db:/data/db" - "${DATA_DIR}/components/mongodb/data/db:/data/db"
- "${DATA_DIR}/components/mongodb/data/logs:/data/logs" - "${DATA_DIR}/components/mongodb/data/logs:/data/logs"
- "${DATA_DIR}/components/mongodb/data/conf:/etc/mongo" - "${DATA_DIR}/components/mongodb/data/conf:/etc/mongo"
- "${MONGO_BACKUP_DIR}:/data/backup"
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- wiredTigerCacheSizeGB=1 - wiredTigerCacheSizeGB=1
+3 -3
View File
@@ -12,8 +12,8 @@ require (
github.com/gorilla/websocket v1.5.1 github.com/gorilla/websocket v1.5.1
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/openimsdk/protocol v0.0.72-alpha.71 github.com/openimsdk/protocol v0.0.72-alpha.79
github.com/openimsdk/tools v0.0.50-alpha.65 github.com/openimsdk/tools v0.0.50-alpha.74
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_golang v1.18.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
@@ -27,7 +27,6 @@ require (
require github.com/google/uuid v1.6.0 require github.com/google/uuid v1.6.0
require ( require (
github.com/IBM/sarama v1.43.0
github.com/fatih/color v1.14.1 github.com/fatih/color v1.14.1
github.com/gin-contrib/gzip v1.0.1 github.com/gin-contrib/gzip v1.0.1
github.com/go-redis/redis v6.15.9+incompatible github.com/go-redis/redis v6.15.9+incompatible
@@ -55,6 +54,7 @@ require (
cloud.google.com/go/iam v1.1.7 // indirect cloud.google.com/go/iam v1.1.7 // indirect
cloud.google.com/go/longrunning v0.5.5 // indirect cloud.google.com/go/longrunning v0.5.5 // indirect
cloud.google.com/go/storage v1.40.0 // indirect cloud.google.com/go/storage v1.40.0 // indirect
github.com/IBM/sarama v1.43.0 // indirect
github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
github.com/aws/aws-sdk-go-v2 v1.32.5 // indirect github.com/aws/aws-sdk-go-v2 v1.32.5 // indirect
+4 -4
View File
@@ -347,10 +347,10 @@ 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/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 h1:5Q8yl8ezy2yx+q8/ucU/t4kJnDfCzNOrkXcDACCqtyM=
github.com/openimsdk/gomake v0.0.15-alpha.2/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI= 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.79 h1:e46no8WVAsmTzyy405klrdoUiG7u+1ohDsXvQuFng4s=
github.com/openimsdk/protocol v0.0.72-alpha.71/go.mod h1:WF7EuE55vQvpyUAzDXcqg+B+446xQyEba0X35lTINmw= github.com/openimsdk/protocol v0.0.72-alpha.79/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.74 h1:yh10SiMiivMEjicEQg+QAsH4pvaO+4noMPdlw+ew0Kc=
github.com/openimsdk/tools v0.0.50-alpha.65/go.mod h1:B+oqV0zdewN7OiEHYJm+hW+8/Te7B8tHHgD8rK5ZLZk= github.com/openimsdk/tools v0.0.50-alpha.74/go.mod h1:n2poR3asX1e1XZce4O+MOWAp+X02QJRFvhcLCXZdzRo=
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= 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/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
+3 -11
View File
@@ -30,16 +30,14 @@ type ConfigManager struct {
client *clientv3.Client client *clientv3.Client
configPath string 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{ cm := &ConfigManager{
imAdminUserID: IMAdminUserID, imAdminUserID: IMAdminUserID,
config: cfg, config: cfg,
client: client, client: client,
configPath: configPath, configPath: configPath,
runtimeEnv: runtimeEnv,
} }
return cm return cm
} }
@@ -73,7 +71,7 @@ func (cm *ConfigManager) GetConfig(c *gin.Context) {
func (cm *ConfigManager) GetConfigList(c *gin.Context) { func (cm *ConfigManager) GetConfigList(c *gin.Context) {
var resp apistruct.GetConfigListResp var resp apistruct.GetConfigListResp
resp.ConfigNames = cm.config.GetConfigNames() resp.ConfigNames = cm.config.GetConfigNames()
resp.Environment = runtimeenv.PrintRuntimeEnvironment() resp.Environment = runtimeenv.RuntimeEnvironment()
resp.Version = version.Version resp.Version = version.Version
apiresp.GinSuccess(c, resp) apiresp.GinSuccess(c, resp)
@@ -209,13 +207,7 @@ func (cm *ConfigManager) resetConfig(c *gin.Context, checkChange bool, ops ...cl
changedKeys := make([]string, 0, len(configMap)) changedKeys := make([]string, 0, len(configMap))
for k, v := range configMap { for k, v := range configMap {
err := config.Load( err := config.Load(cm.configPath, k, config.EnvPrefixMap[k], v.new)
cm.configPath,
k,
config.EnvPrefixMap[k],
cm.runtimeEnv,
v.new,
)
if err != nil { if err != nil {
log.ZError(c, "load config failed", err) log.ZError(c, "load config failed", err)
continue continue
+48 -120
View File
@@ -20,157 +20,85 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"os"
"os/signal"
"strconv" "strconv"
"syscall"
"time" "time"
conf "github.com/openimsdk/open-im-server/v3/pkg/common/config" conf "github.com/openimsdk/open-im-server/v3/pkg/common/config"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discovery" "github.com/openimsdk/tools/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/log" "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/datautil"
"github.com/openimsdk/tools/utils/jsonutil"
"github.com/openimsdk/tools/utils/network" "github.com/openimsdk/tools/utils/network"
"github.com/openimsdk/tools/utils/runtimeenv" "github.com/openimsdk/tools/utils/runtimeenv"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
) )
type Config struct { type Config struct {
*conf.AllConfig conf.AllConfig
RuntimeEnv string ConfigPath conf.Path
ConfigPath string Index conf.Index
} }
func Start(ctx context.Context, index int, config *Config) error { func Start(ctx context.Context, config *Config, client discovery.Conn, service grpc.ServiceRegistrar) error {
apiPort, err := datautil.GetElemByIndex(config.API.Api.Ports, index) apiPort, err := datautil.GetElemByIndex(config.API.Api.Ports, int(config.Index))
if err != nil { if err != nil {
return err 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) router, err := newGinRouter(ctx, client, config)
if err != nil { if err != nil {
return err return err
} }
if config.API.Prometheus.Enable {
var (
listener net.Listener
)
if config.API.Prometheus.AutoSetPorts { apiCtx, apiCancel := context.WithCancelCause(context.Background())
listener, prometheusPort, err = getAutoPort() done := make(chan struct{})
if err != nil { go func() {
return err httpServer := &http.Server{
} Handler: router,
Addr: net.JoinHostPort(network.GetListenIP(config.API.Api.ListenIP), strconv.Itoa(apiPort)),
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))
}
} }
go func() { go func() {
if err := prommetrics.ApiInit(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { defer close(done)
netErr = errs.WrapMsg(err, fmt.Sprintf("api prometheus start err: %d", prometheusPort)) select {
netDone <- struct{}{} 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)
} }
}() }()
log.CInfo(ctx, "api server is init", "runtimeEnv", runtimeenv.RuntimeEnvironment(), "address", httpServer.Addr, "apiPort", apiPort)
} err := httpServer.ListenAndServe()
address := net.JoinHostPort(network.GetListenIP(config.API.Api.ListenIP), strconv.Itoa(apiPort)) if err == nil {
err = errors.New("api done")
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{}{}
} }
apiCancel(err)
}() }()
if config.Discovery.Enable == conf.ETCD { //if config.Discovery.Enable == conf.ETCD {
cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), config.GetConfigNames()) // cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), config.GetConfigNames())
cm.Watch(ctx) // cm.Watch(ctx)
} //}
//sigs := make(chan os.Signal, 1)
sigs := make(chan os.Signal, 1) //signal.Notify(sigs, syscall.SIGTERM)
signal.Notify(sigs, syscall.SIGTERM) //select {
//case val := <-sigs:
shutdown := func() error { // log.ZDebug(ctx, "recv exit", "signal", val.String())
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) // cancel(fmt.Errorf("signal %s", val.String()))
defer cancel() //case <-ctx.Done():
err := server.Shutdown(ctx) //}
if err != nil { <-apiCtx.Done()
return errs.WrapMsg(err, "shutdown err") exitCause := context.Cause(ctx)
} log.ZWarn(ctx, "api server exit", exitCause)
return nil timer := time.NewTimer(time.Second * 15)
} defer timer.Stop()
disetcd.RegisterShutDown(shutdown)
select { select {
case <-sigs: case <-timer.C:
program.SIGTERMExit() log.ZWarn(ctx, "api server graceful stop timeout", nil)
if err := shutdown(); err != nil { case <-done:
return err log.ZDebug(ctx, "api server graceful stop done")
}
case <-netDone:
close(netDone)
return netErr
} }
return nil return exitCause
} }
+163 -3
View File
@@ -15,12 +15,19 @@
package api package api
import ( import (
"encoding/base64"
"encoding/json"
"sync"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/mitchellh/mapstructure" "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/apistruct"
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "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/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
@@ -36,6 +43,39 @@ import (
"github.com/openimsdk/tools/utils/timeutil" "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 { type MessageApi struct {
Client msg.MsgClient Client msg.MsgClient
userClient *rpcli.UserClient userClient *rpcli.UserClient
@@ -171,6 +211,8 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM
data = apistruct.AtElem{} data = apistruct.AtElem{}
case constant.Custom: case constant.Custom:
data = apistruct.CustomElem{} data = apistruct.CustomElem{}
case constant.MarkdownText:
data = apistruct.MarkdownTextElem{}
case constant.OANotification: case constant.OANotification:
data = apistruct.OANotificationElem{} data = apistruct.OANotificationElem{}
req.SessionType = constant.NotificationChatType req.SessionType = constant.NotificationChatType
@@ -190,6 +232,42 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM
return m.newUserSendMsgReq(c, &req), nil return m.newUserSendMsgReq(c, &req), 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. // SendMessage handles the sending of a message. It's an HTTP handler function to be used with Gin framework.
func (m *MessageApi) SendMessage(c *gin.Context) { func (m *MessageApi) SendMessage(c *gin.Context) {
// Initialize a request struct for sending a message. // Initialize a request struct for sending a message.
@@ -243,7 +321,7 @@ func (m *MessageApi) SendMessage(c *gin.Context) {
} }
// Respond with a success message and the response payload. // 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) { func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
@@ -301,7 +379,7 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
IsSendMsg: req.SendMsg, IsSendMsg: req.SendMsg,
ReliabilityLevel: *req.ReliabilityLevel, ReliabilityLevel: *req.ReliabilityLevel,
UnreadCount: false, UnreadCount: false,
}), }, nil),
}, },
} }
respPb, err := m.Client.SendMsg(c, &sendMsgReq) respPb, err := m.Client.SendMsg(c, &sendMsgReq)
@@ -309,7 +387,7 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
apiresp.GinError(c, err) apiresp.GinError(c, err)
return return
} }
apiresp.GinSuccess(c, respPb) m.ginRespSendMsg(c, &sendMsgReq, respPb)
} }
func (m *MessageApi) BatchSendMsg(c *gin.Context) { func (m *MessageApi) BatchSendMsg(c *gin.Context) {
@@ -363,11 +441,93 @@ func (m *MessageApi) BatchSendMsg(c *gin.Context) {
ClientMsgID: rpcResp.ClientMsgID, ClientMsgID: rpcResp.ClientMsgID,
SendTime: rpcResp.SendTime, SendTime: rpcResp.SendTime,
RecvID: recvID, RecvID: recvID,
Modify: m.getModifyFields(sendMsgReq.MsgData, rpcResp.Modify),
}) })
} }
apiresp.GinSuccess(c, resp) 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
)
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
}
msgData := &sdkws.MsgData{
SendID: sendID,
RecvID: recvID,
GroupID: keyMsgData.GroupID,
ClientMsgID: idutil.GetMsgIDByMD5(sendID),
SenderPlatformID: constant.AdminPlatformID,
SessionType: sessionType,
MsgFrom: constant.UserMsgType,
ContentType: constant.Text,
Content: []byte(req.Content),
OfflinePushInfo: req.OfflinePushInfo,
Ex: req.Ex,
}
sendReq := &msg.SendMsgReq{
MsgData: msgData,
}
respPb, err := m.Client.SendMsg(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, sendReq, respPb)
}
func (m *MessageApi) CheckMsgIsSendSuccess(c *gin.Context) { func (m *MessageApi) CheckMsgIsSendSuccess(c *gin.Context) {
a2r.Call(c, msg.MsgClient.GetSendMsgStatus, m.Client) a2r.Call(c, msg.MsgClient.GetSendMsgStatus, m.Client)
} }
+17 -33
View File
@@ -2,6 +2,7 @@ package api
import ( import (
"encoding/json" "encoding/json"
"errors"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -11,16 +12,16 @@ import (
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/discovery/etcd" "github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
type PrometheusDiscoveryApi struct { type PrometheusDiscoveryApi struct {
config *Config config *Config
client *clientv3.Client client *clientv3.Client
kv discovery.KeyValue
} }
func NewPrometheusDiscoveryApi(config *Config, client discovery.SvcDiscoveryRegistry) *PrometheusDiscoveryApi { func NewPrometheusDiscoveryApi(config *Config, client discovery.Conn) *PrometheusDiscoveryApi {
api := &PrometheusDiscoveryApi{ api := &PrometheusDiscoveryApi{
config: config, config: config,
} }
@@ -30,43 +31,26 @@ func NewPrometheusDiscoveryApi(config *Config, client discovery.SvcDiscoveryRegi
return api 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) { func (p *PrometheusDiscoveryApi) discovery(c *gin.Context, key string) {
eResp, err := p.client.Get(c, prommetrics.BuildDiscoveryKey(key)) value, err := p.kv.GetKey(c, prommetrics.BuildDiscoveryKey(key))
if err != nil { if err != nil {
// Log and respond with an error if preparation fails. if errors.Is(err, discovery.ErrNotSupportedKeyValue) {
apiresp.GinError(c, errs.WrapMsg(err, "etcd get err")) c.JSON(http.StatusOK, []struct{}{})
return
}
apiresp.GinError(c, errs.WrapMsg(err, "get key value"))
return return
} }
if len(eResp.Kvs) == 0 { if len(value) == 0 {
c.JSON(http.StatusOK, []*prommetrics.Target{}) c.JSON(http.StatusOK, []*prommetrics.RespTarget{})
return
} }
var resp prommetrics.RespTarget
var ( if err := json.Unmarshal(value, &resp); err != nil {
resp = &prommetrics.RespTarget{ apiresp.GinError(c, errs.WrapMsg(err, "json unmarshal err"))
Targets: make([]string, 0, len(eResp.Kvs)), return
}
)
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(http.StatusOK, []*prommetrics.RespTarget{&resp})
c.JSON(200, []*prommetrics.RespTarget{resp})
} }
func (p *PrometheusDiscoveryApi) Api(c *gin.Context) { func (p *PrometheusDiscoveryApi) Api(c *gin.Context) {
+3 -4
View File
@@ -52,7 +52,7 @@ func prommetricsGin() gin.HandlerFunc {
} }
} }
func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cfg *Config) (*gin.Engine, error) { func newGinRouter(ctx context.Context, client discovery.Conn, cfg *Config) (*gin.Engine, error) {
authConn, err := client.GetConn(ctx, cfg.Discovery.RpcService.Auth) authConn, err := client.GetConn(ctx, cfg.Discovery.RpcService.Auth)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -283,7 +283,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
} }
{ {
pd := NewPrometheusDiscoveryApi(cfg, client) pd := NewPrometheusDiscoveryApi(cfg, client)
proDiscoveryGroup := r.Group("/prometheus_discovery", pd.Enable) proDiscoveryGroup := r.Group("/prometheus_discovery")
proDiscoveryGroup.GET("/api", pd.Api) proDiscoveryGroup.GET("/api", pd.Api)
proDiscoveryGroup.GET("/user", pd.User) proDiscoveryGroup.GET("/user", pd.User)
proDiscoveryGroup.GET("/group", pd.Group) proDiscoveryGroup.GET("/group", pd.Group)
@@ -301,9 +301,8 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
if cfg.Discovery.Enable == config.ETCD { if cfg.Discovery.Enable == config.ETCD {
etcdClient = client.(*etcd.SvcDiscoveryRegistryImpl).GetClient() etcdClient = client.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
} }
cm := NewConfigManager(cfg.Share.IMAdminUserID, cfg.AllConfig, etcdClient, cfg.ConfigPath, cfg.RuntimeEnv) cm := NewConfigManager(cfg.Share.IMAdminUserID, &cfg.AllConfig, etcdClient, string(cfg.ConfigPath))
{ {
configGroup := r.Group("/config", cm.CheckAdmin) configGroup := r.Group("/config", cm.CheckAdmin)
configGroup.POST("/get_config_list", cm.GetConfigList) configGroup.POST("/get_config_list", cm.GetConfigList)
configGroup.POST("/get_config", cm.GetConfig) configGroup.POST("/get_config", cm.GetConfig)
+2 -2
View File
@@ -29,11 +29,11 @@ import (
type UserApi struct { type UserApi struct {
Client user.UserClient Client user.UserClient
discov discovery.SvcDiscoveryRegistry discov discovery.Conn
config config.RpcService 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} return UserApi{Client: client, discov: discov, config: config}
} }
+21 -22
View File
@@ -22,7 +22,6 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "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/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msggateway" "github.com/openimsdk/protocol/msggateway"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
@@ -35,7 +34,7 @@ import (
"google.golang.org/grpc" "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) userConn, err := disCov.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return err return err
@@ -51,26 +50,26 @@ func (s *Server) InitServer(ctx context.Context, config *Config, disCov discover
return nil return nil
} }
func (s *Server) Start(ctx context.Context, index int, conf *Config) error { //func (s *Server) Start(ctx context.Context, index int, conf *Config) error {
return startrpc.Start(ctx, &conf.Discovery, &conf.MsgGateway.Prometheus, conf.MsgGateway.ListenIP, // return startrpc.Start(ctx, &conf.Discovery, &conf.MsgGateway.Prometheus, conf.MsgGateway.ListenIP,
conf.MsgGateway.RPC.RegisterIP, // conf.MsgGateway.RPC.RegisterIP,
conf.MsgGateway.RPC.AutoSetPorts, conf.MsgGateway.RPC.Ports, index, // conf.MsgGateway.RPC.AutoSetPorts, conf.MsgGateway.RPC.Ports, index,
conf.Discovery.RpcService.MessageGateway, // conf.Discovery.RpcService.MessageGateway,
nil, // nil,
conf, // conf,
[]string{ // []string{
conf.Share.GetConfigFileName(), // conf.Share.GetConfigFileName(),
conf.Discovery.GetConfigFileName(), // conf.Discovery.GetConfigFileName(),
conf.MsgGateway.GetConfigFileName(), // conf.MsgGateway.GetConfigFileName(),
conf.WebhooksConfig.GetConfigFileName(), // conf.WebhooksConfig.GetConfigFileName(),
conf.RedisConfig.GetConfigFileName(), // conf.RedisConfig.GetConfigFileName(),
}, // },
[]string{ // []string{
conf.Discovery.RpcService.MessageGateway, // conf.Discovery.RpcService.MessageGateway,
}, // },
s.InitServer, // s.InitServer,
) // )
} //}
type Server struct { type Server struct {
msggateway.UnimplementedMsgGatewayServer msggateway.UnimplementedMsgGatewayServer
+54 -15
View File
@@ -19,10 +19,12 @@ import (
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "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/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/datautil"
"github.com/openimsdk/tools/utils/runtimeenv" "github.com/openimsdk/tools/utils/runtimeenv"
"google.golang.org/grpc"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
) )
@@ -33,26 +35,25 @@ type Config struct {
RedisConfig config.Redis RedisConfig config.Redis
WebhooksConfig config.Webhooks WebhooksConfig config.Webhooks
Discovery config.Discovery Discovery config.Discovery
Index config.Index
RuntimeEnv string
} }
// Start run ws server. // Start run ws server.
func Start(ctx context.Context, index int, conf *Config) error { func Start(ctx context.Context, conf *Config, client discovery.Conn, server grpc.ServiceRegistrar) error {
conf.RuntimeEnv = runtimeenv.PrintRuntimeEnvironment() log.CInfo(ctx, "MSG-GATEWAY server is initializing", "runtimeEnv", runtimeenv.RuntimeEnvironment(),
log.CInfo(ctx, "MSG-GATEWAY server is initializing", "runtimeEnv", conf.RuntimeEnv,
"rpcPorts", conf.MsgGateway.RPC.Ports, "rpcPorts", conf.MsgGateway.RPC.Ports,
"wsPort", conf.MsgGateway.LongConnSvr.Ports, "prometheusPorts", conf.MsgGateway.Prometheus.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 { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, conf.RedisConfig.Build()) dbb := dbbuild.NewBuilder(nil, &conf.RedisConfig)
rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
longServer := NewWsServer( longServer := NewWsServer(
conf, conf,
WithPort(wsPort), WithPort(wsPort),
@@ -67,12 +68,50 @@ func Start(ctx context.Context, index int, conf *Config) error {
return err return err
}) })
if err := hubServer.InitServer(ctx, conf, client, server); err != nil {
return err
}
go longServer.ChangeOnlineStatus(4) go longServer.ChangeOnlineStatus(4)
netDone := make(chan error) return hubServer.LongConnServer.Run(ctx)
go func() {
err = hubServer.Start(ctx, index, conf)
netDone <- err
}()
return hubServer.LongConnServer.Run(netDone)
} }
//
//// 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)
//}
+53 -53
View File
@@ -2,7 +2,6 @@ package msggateway
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"sync" "sync"
@@ -11,7 +10,6 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"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/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/rpccache" "github.com/openimsdk/open-im-server/v3/pkg/rpccache"
pbAuth "github.com/openimsdk/protocol/auth" pbAuth "github.com/openimsdk/protocol/auth"
@@ -23,19 +21,18 @@ import (
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msggateway" "github.com/openimsdk/protocol/msggateway"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/stringutil" "github.com/openimsdk/tools/utils/stringutil"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
type LongConnServer interface { type LongConnServer interface {
Run(done chan error) error Run(ctx context.Context) error
wsHandler(w http.ResponseWriter, r *http.Request) wsHandler(w http.ResponseWriter, r *http.Request)
GetUserAllCons(userID string) ([]*Client, bool) GetUserAllCons(userID string) ([]*Client, bool)
GetUserPlatformCons(userID string, platform int) ([]*Client, bool, bool) GetUserPlatformCons(userID string, platform int) ([]*Client, bool, bool)
Validate(s any) error 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 KickUserConn(client *Client) error
UnRegister(c *Client) UnRegister(c *Client)
SetKickHandlerInfo(i *kickHandler) SetKickHandlerInfo(i *kickHandler)
@@ -60,7 +57,7 @@ type WsServer struct {
handshakeTimeout time.Duration handshakeTimeout time.Duration
writeBufferSize int writeBufferSize int
validate *validator.Validate validate *validator.Validate
disCov discovery.SvcDiscoveryRegistry disCov discovery.Conn
Compressor Compressor
//Encoder //Encoder
MessageHandler MessageHandler
@@ -75,7 +72,7 @@ type kickHandler struct {
newClient *Client 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) userConn, err := disCov.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return err return err
@@ -158,19 +155,14 @@ func NewWsServer(msgGatewayConfig *Config, opts ...Option) *WsServer {
} }
} }
func (ws *WsServer) Run(done chan error) error { func (ws *WsServer) Run(ctx context.Context) error {
var ( var client *Client
client *Client
netErr error
shutdownDone = make(chan struct{}, 1)
)
server := http.Server{Addr: ":" + stringutil.IntToString(ws.port), Handler: nil}
ctx, cancel := context.WithCancelCause(ctx)
go func() { go func() {
for { for {
select { select {
case <-shutdownDone: case <-ctx.Done():
return return
case client = <-ws.registerChan: case client = <-ws.registerChan:
ws.registerClient(client) ws.registerClient(client)
@@ -181,57 +173,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 { func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *Client) error {
conns, err := ws.disCov.GetConns(ctx, ws.msgGatewayConfig.Discovery.RpcService.MessageGateway) conns, err := ws.disCov.GetConns(ctx, ws.msgGatewayConfig.Discovery.RpcService.MessageGateway)
if err != nil { if err != nil {
return err return err
} }
if len(conns) == 0 || (len(conns) == 1 && ws.disCov.IsSelfNode(conns[0])) {
return nil
}
wg := errgroup.Group{} wg := errgroup.Group{}
wg.SetLimit(concurrentRequest) wg.SetLimit(concurrentRequest)
// Online push user online message to other node // Online push user online message to other node
for _, v := range conns { for _, v := range conns {
v := v v := v
log.ZDebug(ctx, " sendUserOnlineInfoToOtherNode conn ", "target", v.Target()) log.ZDebug(ctx, "sendUserOnlineInfoToOtherNode conn")
if v.Target() == ws.disCov.GetSelfConnTarget() { if ws.disCov.IsSelfNode(v) {
log.ZDebug(ctx, "Filter out this node", "node", v.Target()) log.ZDebug(ctx, "Filter out this node")
continue continue
} }
@@ -242,7 +233,7 @@ func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *C
PlatformID: int32(client.PlatformID), Token: client.token, PlatformID: int32(client.PlatformID), Token: client.token,
}) })
if err != nil { if err != nil {
log.ZWarn(ctx, "MultiTerminalLoginCheck err", err, "node", v.Target()) log.ZWarn(ctx, "MultiTerminalLoginCheck err", err)
} }
return nil return nil
}) })
@@ -349,6 +340,15 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
if constant.PlatformIDToClass(newClient.PlatformID) == constant.TerminalPC { if constant.PlatformIDToClass(newClient.PlatformID) == constant.TerminalPC {
return 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 fallthrough
case constant.AllLoginButSameTermKick: case constant.AllLoginButSameTermKick:
if !clientOK { if !clientOK {
+93 -154
View File
@@ -16,51 +16,35 @@ package msgtransfer
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
disetcd "github.com/openimsdk/open-im-server/v3/pkg/common/discovery/etcd" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/mcache"
"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/redis" "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/database/mgo"
"github.com/openimsdk/tools/db/mongoutil" "github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/tools/db/redisutil" "github.com/openimsdk/open-im-server/v3/pkg/mqbuild"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/mq"
"github.com/openimsdk/tools/utils/runtimeenv" "github.com/openimsdk/tools/utils/runtimeenv"
conf "github.com/openimsdk/open-im-server/v3/pkg/common/config" 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/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mw"
"github.com/openimsdk/tools/system/program"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
) )
type MsgTransfer struct { type MsgTransfer struct {
historyConsumer mq.Consumer
historyMongoConsumer mq.Consumer
// This consumer aggregated messages, subscribed to the topic:toRedis, // 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, // 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 // and the message is sent to toMongo topic for persistence
historyCH *OnlineHistoryRedisConsumerHandler historyHandler *OnlineHistoryRedisConsumerHandler
//This consumer handle message to mongo //This consumer handle message to mongo
historyMongoCH *OnlineHistoryMongoConsumerHandler historyMongoHandler *OnlineHistoryMongoConsumerHandler
ctx context.Context ctx context.Context
cancel context.CancelFunc //cancel context.CancelFunc
runTimeEnv string
} }
type Config struct { type Config struct {
@@ -71,48 +55,59 @@ type Config struct {
Share conf.Share Share conf.Share
WebhooksConfig conf.Webhooks WebhooksConfig conf.Webhooks
Discovery conf.Discovery Discovery conf.Discovery
Index conf.Index
} }
func Start(ctx context.Context, index int, config *Config) error { func Start(ctx context.Context, config *Config, client discovery.Conn, server grpc.ServiceRegistrar) error {
runTimeEnv := runtimeenv.PrintRuntimeEnvironment() builder := mqbuild.NewBuilder(&config.KafkaConfig)
log.CInfo(ctx, "MSG-TRANSFER server is initializing", "runTimeEnv", runTimeEnv, "prometheusPorts", log.CInfo(ctx, "MSG-TRANSFER server is initializing", "runTimeEnv", runtimeenv.RuntimeEnvironment(), "prometheusPorts",
config.MsgTransfer.Prometheus.Ports, "index", index) config.MsgTransfer.Prometheus.Ports, "index", config.Index)
dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), pushProducer, err := builder.GetTopicProducer(ctx, config.KafkaConfig.ToPushTopic)
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) if err != nil {
return err
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)
} }
msgDocModel, err := mgo.NewMsgMongo(mgocli.GetDB()) msgDocModel, err := mgo.NewMsgMongo(mgocli.GetDB())
if err != nil { if err != nil {
return err 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()) seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB())
if err != nil { if err != nil {
return err return err
@@ -123,124 +118,68 @@ func Start(ctx context.Context, index int, config *Config) error {
return err return err
} }
seqUserCache := redis.NewSeqUserCacheRedis(rdb, seqUser) 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 { if err != nil {
return err return err
} }
historyCH, err := NewOnlineHistoryRedisConsumerHandler(ctx, client, config, msgTransferDatabase) historyConsumer, err := builder.GetTopicConsumer(ctx, config.KafkaConfig.ToRedisTopic)
if err != nil { if err != nil {
return err return err
} }
historyMongoCH, err := NewOnlineHistoryMongoConsumerHandler(&config.KafkaConfig, msgTransferDatabase) historyMongoConsumer, err := builder.GetTopicConsumer(ctx, config.KafkaConfig.ToMongoTopic)
if err != nil { if err != nil {
return err return err
} }
historyHandler, err := NewOnlineHistoryRedisConsumerHandler(ctx, client, config, msgTransferDatabase)
if err != nil {
return err
}
historyMongoHandler := NewOnlineHistoryMongoConsumerHandler(msgTransferDatabase)
msgTransfer := &MsgTransfer{ msgTransfer := &MsgTransfer{
historyCH: historyCH, historyConsumer: historyConsumer,
historyMongoCH: historyMongoCH, historyMongoConsumer: historyMongoConsumer,
runTimeEnv: runTimeEnv, 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 { func (m *MsgTransfer) Start(ctx context.Context) error {
m.ctx, m.cancel = context.WithCancel(context.Background()) var cancel context.CancelCauseFunc
var ( m.ctx, cancel = context.WithCancelCause(ctx)
netDone = make(chan struct{}, 1)
netErr error
)
go m.historyCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyCH) go func() {
go m.historyMongoCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyMongoCH) for {
go m.historyCH.HandleUserHasReadSeqMessages(m.ctx) if err := m.historyConsumer.Subscribe(m.ctx, m.historyHandler.HandlerRedisMessage); err != nil {
err := m.historyCH.redisMessageBatches.Start() cancel(fmt.Errorf("history consumer %w", err))
log.ZError(m.ctx, "historyConsumer err", err)
return
}
}
}()
go func() {
fn := func(ctx context.Context, key string, value []byte) error {
m.historyMongoHandler.HandleChatWs2Mongo(ctx, key, value)
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 { if err != nil {
return err return err
} }
<-m.ctx.Done()
registerIP, err := network.GetRpcRegisterIP("") return context.Cause(m.ctx)
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
}
} }
@@ -18,14 +18,13 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/tools/discovery"
"strconv"
"strings"
"sync" "sync"
"time" "time"
"github.com/IBM/sarama" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/tools/discovery"
"github.com/go-redis/redis" "github.com/go-redis/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "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/storage/controller"
@@ -37,7 +36,6 @@ import (
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/mq/kafka"
"github.com/openimsdk/tools/utils/stringutil" "github.com/openimsdk/tools/utils/stringutil"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@@ -64,9 +62,7 @@ type userHasReadSeq struct {
} }
type OnlineHistoryRedisConsumerHandler struct { type OnlineHistoryRedisConsumerHandler struct {
historyConsumerGroup *kafka.MConsumerGroup redisMessageBatches *batcher.Batcher[ConsumerMessage]
redisMessageBatches *batcher.Batcher[sarama.ConsumerMessage]
msgTransferDatabase controller.MsgTransferDatabase msgTransferDatabase controller.MsgTransferDatabase
conversationUserHasReadChan chan *userHasReadSeq conversationUserHasReadChan chan *userHasReadSeq
@@ -76,12 +72,13 @@ type OnlineHistoryRedisConsumerHandler struct {
conversationClient *rpcli.ConversationClient conversationClient *rpcli.ConversationClient
} }
func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.SvcDiscoveryRegistry, config *Config, database controller.MsgTransferDatabase) (*OnlineHistoryRedisConsumerHandler, error) { type ConsumerMessage struct {
kafkaConf := config.KafkaConfig Ctx context.Context
historyConsumerGroup, err := kafka.NewMConsumerGroup(kafkaConf.Build(), kafkaConf.ToRedisGroupID, []string{kafkaConf.ToRedisTopic}, false) Key string
if err != nil { Value []byte
return nil, err }
}
func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.Conn, config *Config, database controller.MsgTransferDatabase) (*OnlineHistoryRedisConsumerHandler, error) {
groupConn, err := client.GetConn(ctx, config.Discovery.RpcService.Group) groupConn, err := client.GetConn(ctx, config.Discovery.RpcService.Group)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -97,7 +94,7 @@ func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.
och.conversationClient = rpcli.NewConversationClient(conversationConn) och.conversationClient = rpcli.NewConversationClient(conversationConn)
och.wg.Add(1) och.wg.Add(1)
b := batcher.New[sarama.ConsumerMessage]( b := batcher.New[ConsumerMessage](
batcher.WithSize(size), batcher.WithSize(size),
batcher.WithWorker(worker), batcher.WithWorker(worker),
batcher.WithInterval(interval), batcher.WithInterval(interval),
@@ -109,16 +106,15 @@ func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.
hashCode := stringutil.GetHashCode(key) hashCode := stringutil.GetHashCode(key)
return int(hashCode) % och.redisMessageBatches.Worker() return int(hashCode) % och.redisMessageBatches.Worker()
} }
b.Key = func(consumerMessage *sarama.ConsumerMessage) string { b.Key = func(consumerMessage *ConsumerMessage) string {
return string(consumerMessage.Key) return consumerMessage.Key
} }
b.Do = och.do b.Do = och.do
och.redisMessageBatches = b och.redisMessageBatches = b
och.historyConsumerGroup = historyConsumerGroup
return &och, nil 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()) ctx = mcontext.WithTriggerIDContext(ctx, val.TriggerID())
ctxMessages := och.parseConsumerMessages(ctx, val.Val()) ctxMessages := och.parseConsumerMessages(ctx, val.Val())
ctx = withAggregationCtx(ctx, ctxMessages) ctx = withAggregationCtx(ctx, ctxMessages)
@@ -189,7 +185,7 @@ func (och *OnlineHistoryRedisConsumerHandler) doSetReadSeq(ctx context.Context,
} }
func (och *OnlineHistoryRedisConsumerHandler) parseConsumerMessages(ctx context.Context, consumerMessages []*sarama.ConsumerMessage) []*ContextMsg { func (och *OnlineHistoryRedisConsumerHandler) parseConsumerMessages(ctx context.Context, consumerMessages []*ConsumerMessage) []*ContextMsg {
var ctxMessages []*ContextMsg var ctxMessages []*ContextMsg
for i := 0; i < len(consumerMessages); i++ { for i := 0; i < len(consumerMessages); i++ {
ctxMsg := &ContextMsg{} ctxMsg := &ContextMsg{}
@@ -199,16 +195,9 @@ func (och *OnlineHistoryRedisConsumerHandler) parseConsumerMessages(ctx context.
log.ZWarn(ctx, "msg_transfer Unmarshal msg err", err, string(consumerMessages[i].Value)) log.ZWarn(ctx, "msg_transfer Unmarshal msg err", err, string(consumerMessages[i].Value))
continue continue
} }
var arr []string ctxMsg.ctx = consumerMessages[i].Ctx
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.message = msgFromMQ ctxMsg.message = msgFromMQ
log.ZDebug(ctx, "message parse finish", "message", msgFromMQ, "key", log.ZDebug(ctx, "message parse finish", "message", msgFromMQ, "key", consumerMessages[i].Key)
string(consumerMessages[i].Key))
ctxMessages = append(ctxMessages, ctxMsg) ctxMessages = append(ctxMessages, ctxMsg)
} }
return ctxMessages return ctxMessages
@@ -383,7 +372,9 @@ func (och *OnlineHistoryRedisConsumerHandler) Close() {
func (och *OnlineHistoryRedisConsumerHandler) toPushTopic(ctx context.Context, key, conversationID string, msgs []*ContextMsg) { func (och *OnlineHistoryRedisConsumerHandler) toPushTopic(ctx context.Context, key, conversationID string, msgs []*ContextMsg) {
for _, v := range msgs { for _, v := range msgs {
log.ZDebug(ctx, "push msg to topic", "msg", v.message.String()) 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 +392,10 @@ func withAggregationCtx(ctx context.Context, values []*ContextMsg) context.Conte
return mcontext.SetOperationID(ctx, allMessageOperationID) return mcontext.SetOperationID(ctx, allMessageOperationID)
} }
func (och *OnlineHistoryRedisConsumerHandler) Setup(_ sarama.ConsumerGroupSession) error { return nil } func (och *OnlineHistoryRedisConsumerHandler) HandlerRedisMessage(ctx context.Context, key string, value []byte) error { // a instance in the consumer group
func (och *OnlineHistoryRedisConsumerHandler) Cleanup(_ sarama.ConsumerGroupSession) error { err := och.redisMessageBatches.Put(ctx, &ConsumerMessage{Ctx: ctx, Key: key, Value: value})
if err != nil {
log.ZWarn(ctx, "put msg to error", err, "key", key, "value", value)
}
return nil 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
}
}
}
@@ -17,36 +17,24 @@ package msgtransfer
import ( import (
"context" "context"
"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/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
pbmsg "github.com/openimsdk/protocol/msg" pbmsg "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mq/kafka"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
type OnlineHistoryMongoConsumerHandler struct { type OnlineHistoryMongoConsumerHandler struct {
historyConsumerGroup *kafka.MConsumerGroup msgTransferDatabase controller.MsgTransferDatabase
msgTransferDatabase controller.MsgTransferDatabase
} }
func NewOnlineHistoryMongoConsumerHandler(kafkaConf *config.Kafka, database controller.MsgTransferDatabase) (*OnlineHistoryMongoConsumerHandler, error) { func NewOnlineHistoryMongoConsumerHandler(database controller.MsgTransferDatabase) *OnlineHistoryMongoConsumerHandler {
historyConsumerGroup, err := kafka.NewMConsumerGroup(kafkaConf.Build(), kafkaConf.ToMongoGroupID, []string{kafkaConf.ToMongoTopic}, true) return &OnlineHistoryMongoConsumerHandler{
if err != nil { msgTransferDatabase: database,
return nil, err
} }
mc := &OnlineHistoryMongoConsumerHandler{
historyConsumerGroup: historyConsumerGroup,
msgTransferDatabase: database,
}
return mc, nil
} }
func (mc *OnlineHistoryMongoConsumerHandler) handleChatWs2Mongo(ctx context.Context, cMsg *sarama.ConsumerMessage, key string, session sarama.ConsumerGroupSession) { func (mc *OnlineHistoryMongoConsumerHandler) HandleChatWs2Mongo(ctx context.Context, key string, msg []byte) {
msg := cMsg.Value
msgFromMQ := pbmsg.MsgDataToMongoByMQ{} msgFromMQ := pbmsg.MsgDataToMongoByMQ{}
err := proto.Unmarshal(msg, &msgFromMQ) err := proto.Unmarshal(msg, &msgFromMQ)
if err != nil { if err != nil {
@@ -54,7 +42,7 @@ func (mc *OnlineHistoryMongoConsumerHandler) handleChatWs2Mongo(ctx context.Cont
return return
} }
if len(msgFromMQ.MsgData) == 0 { 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 return
} }
log.ZDebug(ctx, "mongo consumer recv msg", "msgs", msgFromMQ.String()) log.ZDebug(ctx, "mongo consumer recv msg", "msgs", msgFromMQ.String())
@@ -73,27 +61,12 @@ func (mc *OnlineHistoryMongoConsumerHandler) handleChatWs2Mongo(ctx context.Cont
} else { } else {
prommetrics.MsgInsertMongoSuccessCounter.Inc() prommetrics.MsgInsertMongoSuccessCounter.Inc()
} }
var seqs []int64 //var seqs []int64
for _, msg := range msgFromMQ.MsgData { //for _, msg := range msgFromMQ.MsgData {
seqs = append(seqs, msg.Seq) // 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",
func (*OnlineHistoryMongoConsumerHandler) Setup(_ sarama.ConsumerGroupSession) error { return nil } // msgFromMQ.MsgData, "conversationID", msgFromMQ.ConversationID)
//}
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
} }
+1
View File
@@ -17,6 +17,7 @@ package push
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
+5 -3
View File
@@ -16,12 +16,14 @@ package fcm
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
"github.com/openimsdk/tools/utils/httputil"
"path/filepath" "path/filepath"
"strings" "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 "firebase.google.com/go/v4"
"firebase.google.com/go/v4/messaging" "firebase.google.com/go/v4/messaging"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "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) unreadCountSum, err := f.cache.GetUserBadgeUnreadCountSum(ctx, userID)
if err == nil && unreadCountSum != 0 { if err == nil && unreadCountSum != 0 {
apns.Payload.Aps.Badge = &unreadCountSum apns.Payload.Aps.Badge = &unreadCountSum
} else if err == redis.Nil || unreadCountSum == 0 { } else if errors.Is(err, redis.Nil) || unreadCountSum == 0 {
zero := 1 zero := 1
apns.Payload.Aps.Badge = &zero apns.Payload.Aps.Badge = &zero
} else { } else {
+36 -2
View File
@@ -18,6 +18,16 @@ import (
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "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 { type Resp struct {
@@ -58,7 +68,24 @@ type TaskResp struct {
} }
type Settings 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 { type Audience struct {
@@ -112,6 +139,8 @@ type Notification struct {
ChannelID string `json:"channelID"` ChannelID string `json:"channelID"`
ChannelName string `json:"ChannelName"` ChannelName string `json:"ChannelName"`
ClickType string `json:"click_type"` ClickType string `json:"click_type"`
BadgeAddNum string `json:"badge_add_num"`
Category string `json:"category"`
} }
type Options struct { type Options struct {
@@ -120,6 +149,7 @@ type Options struct {
ChannelID string `json:"/message/android/notification/channel_id"` ChannelID string `json:"/message/android/notification/channel_id"`
Sound string `json:"/message/android/notification/sound"` Sound string `json:"/message/android/notification/sound"`
Importance string `json:"/message/android/notification/importance"` Importance string `json:"/message/android/notification/importance"`
Category string `json:"/message/android/category"`
} `json:"HW"` } `json:"HW"`
XM struct { XM struct {
ChannelID string `json:"/extra.channel_id"` ChannelID string `json:"/extra.channel_id"`
@@ -140,6 +170,8 @@ func newPushReq(pushConf *config.Push, title, content string) PushReq {
ClickType: "startapp", ClickType: "startapp",
ChannelID: pushConf.GeTui.ChannelID, ChannelID: pushConf.GeTui.ChannelID,
ChannelName: pushConf.GeTui.ChannelName, ChannelName: pushConf.GeTui.ChannelName,
BadgeAddNum: addNum,
Category: msgCategory,
}}} }}}
return pushReq return pushReq
} }
@@ -156,6 +188,7 @@ func (pushReq *PushReq) setPushChannel(title string, body string) {
notify := "notify" notify := "notify"
pushReq.PushChannel.Ios.NotificationType = &notify pushReq.PushChannel.Ios.NotificationType = &notify
pushReq.PushChannel.Ios.Aps.Sound = "default" pushReq.PushChannel.Ios.Aps.Sound = "default"
pushReq.PushChannel.Ios.AutoBadge = incOne
pushReq.PushChannel.Ios.Aps.Alert = Alert{ pushReq.PushChannel.Ios.Aps.Alert = Alert{
Title: title, Title: title,
Body: body, Body: body,
@@ -172,7 +205,8 @@ func (pushReq *PushReq) setPushChannel(title string, body string) {
ChannelID string `json:"/message/android/notification/channel_id"` ChannelID string `json:"/message/android/notification/channel_id"`
Sound string `json:"/message/android/notification/sound"` Sound string `json:"/message/android/notification/sound"`
Importance string `json:"/message/android/notification/importance"` 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 { XM: struct {
ChannelID string `json:"/extra.channel_id"` ChannelID string `json:"/extra.channel_id"`
}{ChannelID: "high_system"}, }{ChannelID: "high_system"},
+5 -3
View File
@@ -18,6 +18,7 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors"
"strconv" "strconv"
"sync" "sync"
"time" "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 { func (g *Client) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error {
token, err := g.cache.GetGetuiToken(ctx) token, err := g.cache.GetGetuiToken(ctx)
if err != nil { if err != nil {
if errs.Unwrap(err) == redis.Nil { if errors.Is(err, redis.Nil) {
log.ZDebug(ctx, "getui token not exist in redis") log.ZDebug(ctx, "getui token not exist in redis")
token, err = g.getTokenAndSave2Redis(ctx) token, err = g.getTokenAndSave2Redis(ctx)
if err != nil { 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) { func (g *Client) GetTaskID(ctx context.Context, token string, pushReq PushReq) (string, error) {
respTask := TaskResp{} respTask := TaskResp{}
ttl := int64(1000 * 60 * 5) 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) err := g.request(ctx, taskURL, pushReq, token, &respTask)
if err != nil { if err != nil {
return "", errs.Wrap(err) return "", errs.Wrap(err)
@@ -188,6 +189,7 @@ func (g *Client) postReturn(
if err != nil { if err != nil {
return err return err
} }
log.ZDebug(ctx, "postReturn", "url", url, "header", header, "input", input, "timeout", timeout, "output", output)
return output.parseError() 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) { 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) taskID, err = g.GetTaskID(ctx, token, pushReq)
if err != nil { if err != nil {
return return
+5 -25
View File
@@ -3,7 +3,6 @@ package push
import ( import (
"context" "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"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
@@ -12,40 +11,21 @@ import (
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mq/kafka"
"github.com/openimsdk/tools/utils/jsonutil" "github.com/openimsdk/tools/utils/jsonutil"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
type OfflinePushConsumerHandler struct { type OfflinePushConsumerHandler struct {
OfflinePushConsumerGroup *kafka.MConsumerGroup offlinePusher offlinepush.OfflinePusher
offlinePusher offlinepush.OfflinePusher
} }
func NewOfflinePushConsumerHandler(config *Config, offlinePusher offlinepush.OfflinePusher) (*OfflinePushConsumerHandler, error) { func NewOfflinePushConsumerHandler(offlinePusher offlinepush.OfflinePusher) *OfflinePushConsumerHandler {
var offlinePushConsumerHandler OfflinePushConsumerHandler return &OfflinePushConsumerHandler{
var err error offlinePusher: offlinePusher,
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
} }
return &offlinePushConsumerHandler, nil
} }
func (*OfflinePushConsumerHandler) Setup(sarama.ConsumerGroupSession) error { return nil } func (o *OfflinePushConsumerHandler) HandleMsg2OfflinePush(ctx context.Context, msg []byte) {
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) {
offlinePushMsg := pbpush.PushMsgReq{} offlinePushMsg := pbpush.PushMsgReq{}
if err := proto.Unmarshal(msg, &offlinePushMsg); err != nil { if err := proto.Unmarshal(msg, &offlinePushMsg); err != nil {
log.ZError(ctx, "offline push Unmarshal msg err", err, "msg", string(msg)) log.ZError(ctx, "offline push Unmarshal msg err", err, "msg", string(msg))
+15 -15
View File
@@ -2,7 +2,7 @@ package push
import ( import (
"context" "context"
"errors" "fmt"
"sync" "sync"
"github.com/openimsdk/protocol/msggateway" "github.com/openimsdk/protocol/msggateway"
@@ -11,6 +11,7 @@ import (
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/runtimeenv"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -30,37 +31,36 @@ func newEmptyOnlinePusher() *emptyOnlinePusher {
return &emptyOnlinePusher{} return &emptyOnlinePusher{}
} }
func (emptyOnlinePusher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, func (emptyOnlinePusher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) {
pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) {
log.ZInfo(ctx, "emptyOnlinePusher GetConnsAndOnlinePush", nil) log.ZInfo(ctx, "emptyOnlinePusher GetConnsAndOnlinePush", nil)
return nil, nil return nil, nil
} }
func (u emptyOnlinePusher) GetOnlinePushFailedUserIDs(ctx context.Context, msg *sdkws.MsgData, func (u emptyOnlinePusher) GetOnlinePushFailedUserIDs(ctx context.Context, msg *sdkws.MsgData, wsResults []*msggateway.SingleMsgToUserResults, pushToUserIDs *[]string) []string {
wsResults []*msggateway.SingleMsgToUserResults, pushToUserIDs *[]string) []string {
log.ZInfo(ctx, "emptyOnlinePusher GetOnlinePushFailedUserIDs", nil) log.ZInfo(ctx, "emptyOnlinePusher GetOnlinePushFailedUserIDs", nil)
return nil return nil
} }
func NewOnlinePusher(disCov discovery.SvcDiscoveryRegistry, config *Config) OnlinePusher { func NewOnlinePusher(disCov discovery.Conn, config *Config) (OnlinePusher, error) {
if conf.Standalone() {
if config.runTimeEnv == conf.KUBERNETES { return NewDefaultAllNode(disCov, config), nil
return NewDefaultAllNode(disCov, config) }
if runtimeenv.RuntimeEnvironment() == conf.KUBERNETES {
return NewDefaultAllNode(disCov, config), nil
} }
switch config.Discovery.Enable { switch config.Discovery.Enable {
case conf.ETCD: case conf.ETCD:
return NewDefaultAllNode(disCov, config) return NewDefaultAllNode(disCov, config), nil
default: default:
log.ZError(context.Background(), "NewOnlinePusher is error", errs.Wrap(errors.New("unsupported discovery type")), "type", config.Discovery.Enable) return nil, errs.New(fmt.Sprintf("unsupported discovery type %s", config.Discovery.Enable))
return nil
} }
} }
type DefaultAllNode struct { type DefaultAllNode struct {
disCov discovery.SvcDiscoveryRegistry disCov discovery.Conn
config *Config config *Config
} }
func NewDefaultAllNode(disCov discovery.SvcDiscoveryRegistry, config *Config) *DefaultAllNode { func NewDefaultAllNode(disCov discovery.Conn, config *Config) *DefaultAllNode {
return &DefaultAllNode{disCov: disCov, config: config} 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) 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 { for host, userIds := range usersHost {
tconn, _ := k.disCov.GetConn(ctx, host) tconn, _ := k.disCov.GetConn(ctx, host)
usersConns[tconn] = userIds usersConns[tconn] = userIds
+77 -25
View File
@@ -2,39 +2,43 @@ package push
import ( import (
"context" "context"
"math/rand"
"strconv"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush" "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "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/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller" "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" pbpush "github.com/openimsdk/protocol/push"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/tools/discovery" "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" "google.golang.org/grpc"
) )
type pushServer struct { type pushServer struct {
pbpush.UnimplementedPushMsgServiceServer pbpush.UnimplementedPushMsgServiceServer
database controller.PushDatabase database controller.PushDatabase
disCov discovery.SvcDiscoveryRegistry disCov discovery.Conn
offlinePusher offlinepush.OfflinePusher offlinePusher offlinepush.OfflinePusher
pushCh *ConsumerHandler
offlinePushCh *OfflinePushConsumerHandler
} }
type Config struct { type Config struct {
RpcConfig config.Push RpcConfig config.Push
RedisConfig config.Redis RedisConfig config.Redis
MongoConfig config.Mongo
KafkaConfig config.Kafka KafkaConfig config.Kafka
NotificationConfig config.Notification NotificationConfig config.Notification
Share config.Share Share config.Share
WebhooksConfig config.Webhooks WebhooksConfig config.Webhooks
LocalCacheConfig config.LocalCache LocalCacheConfig config.LocalCache
Discovery config.Discovery Discovery config.Discovery
FcmConfigPath string FcmConfigPath config.Path
runTimeEnv string
} }
func (p pushServer) DelUserPushToken(ctx context.Context, func (p pushServer) DelUserPushToken(ctx context.Context,
@@ -45,42 +49,90 @@ func (p pushServer) DelUserPushToken(ctx context.Context,
return &pbpush.DelUserPushTokenResp{}, nil return &pbpush.DelUserPushTokenResp{}, nil
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.Conn, server grpc.ServiceRegistrar) error {
config.runTimeEnv = runtimeenv.PrintRuntimeEnvironment() dbb := dbbuild.NewBuilder(&config.MongoConfig, &config.RedisConfig)
rdb, err := dbb.Redis(ctx)
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build())
if err != nil { if err != nil {
return err return err
} }
cacheModel := redis.NewThirdCache(rdb) var cacheModel cache.ThirdCache
offlinePusher, err := offlinepush.NewOfflinePusher(&config.RpcConfig, cacheModel, config.FcmConfigPath) 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 { if err != nil {
return err return err
} }
database := controller.NewPushDatabase(cacheModel, &config.KafkaConfig) pushHandler, err := NewConsumerHandler(ctx, config, database, offlinePusher, rdb, client)
consumer, err := NewConsumerHandler(ctx, config, database, offlinePusher, rdb, client)
if err != nil { if err != nil {
return err return err
} }
offlinePushConsumer, err := NewOfflinePushConsumerHandler(config, offlinePusher) offlineHandler := NewOfflinePushConsumerHandler(offlinePusher)
if err != nil {
return err
}
pbpush.RegisterPushMsgServiceServer(server, &pushServer{ pbpush.RegisterPushMsgServiceServer(server, &pushServer{
database: database, database: database,
disCov: client, disCov: client,
offlinePusher: offlinePusher, offlinePusher: offlinePusher,
pushCh: consumer,
offlinePushCh: offlinePushConsumer,
}) })
go consumer.pushConsumerGroup.RegisterHandleAndConsumer(ctx, consumer) go func() {
pushHandler.WaitCache()
fn := func(ctx context.Context, key string, value []byte) error {
pushHandler.HandleMs2PsChat(ctx, 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(ctx context.Context, key string, value []byte) error {
offlineHandler.HandleMsg2OfflinePush(ctx, 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 return nil
} }
+27 -44
View File
@@ -3,12 +3,8 @@ package push
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"math/rand"
"strconv"
"time" "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"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "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/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" "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/rpccache"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil" "github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msggateway" "github.com/openimsdk/protocol/msggateway"
@@ -24,7 +21,6 @@ import (
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/mq/kafka"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/jsonutil" "github.com/openimsdk/tools/utils/jsonutil"
"github.com/openimsdk/tools/utils/timeutil" "github.com/openimsdk/tools/utils/timeutil"
@@ -33,7 +29,7 @@ import (
) )
type ConsumerHandler struct { type ConsumerHandler struct {
pushConsumerGroup *kafka.MConsumerGroup //pushConsumerGroup mq.Consumer
offlinePusher offlinepush.OfflinePusher offlinePusher offlinepush.OfflinePusher
onlinePusher OnlinePusher onlinePusher OnlinePusher
pushDatabase controller.PushDatabase pushDatabase controller.PushDatabase
@@ -48,15 +44,7 @@ type ConsumerHandler struct {
conversationClient *rpcli.ConversationClient conversationClient *rpcli.ConversationClient
} }
func NewConsumerHandler(ctx context.Context, config *Config, database controller.PushDatabase, offlinePusher offlinepush.OfflinePusher, rdb redis.UniversalClient, func NewConsumerHandler(ctx context.Context, config *Config, database controller.PushDatabase, offlinePusher offlinepush.OfflinePusher, rdb redis.UniversalClient, client discovery.Conn) (*ConsumerHandler, error) {
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
}
userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User) userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -73,13 +61,18 @@ func NewConsumerHandler(ctx context.Context, config *Config, database controller
if err != nil { if err != nil {
return nil, err return nil, err
} }
onlinePusher, err := NewOnlinePusher(client, config)
if err != nil {
return nil, err
}
var consumerHandler ConsumerHandler
consumerHandler.userClient = rpcli.NewUserClient(userConn) consumerHandler.userClient = rpcli.NewUserClient(userConn)
consumerHandler.groupClient = rpcli.NewGroupClient(groupConn) consumerHandler.groupClient = rpcli.NewGroupClient(groupConn)
consumerHandler.msgClient = rpcli.NewMsgClient(msgConn) consumerHandler.msgClient = rpcli.NewMsgClient(msgConn)
consumerHandler.conversationClient = rpcli.NewConversationClient(conversationConn) consumerHandler.conversationClient = rpcli.NewConversationClient(conversationConn)
consumerHandler.offlinePusher = offlinePusher consumerHandler.offlinePusher = offlinePusher
consumerHandler.onlinePusher = NewOnlinePusher(client, config) consumerHandler.onlinePusher = onlinePusher
consumerHandler.groupLocalCache = rpccache.NewGroupLocalCache(consumerHandler.groupClient, &config.LocalCacheConfig, rdb) consumerHandler.groupLocalCache = rpccache.NewGroupLocalCache(consumerHandler.groupClient, &config.LocalCacheConfig, rdb)
consumerHandler.conversationLocalCache = rpccache.NewConversationLocalCache(consumerHandler.conversationClient, &config.LocalCacheConfig, rdb) consumerHandler.conversationLocalCache = rpccache.NewConversationLocalCache(consumerHandler.conversationClient, &config.LocalCacheConfig, rdb)
consumerHandler.webhookClient = webhook.NewWebhookClient(config.WebhooksConfig.URL) consumerHandler.webhookClient = webhook.NewWebhookClient(config.WebhooksConfig.URL)
@@ -92,7 +85,7 @@ func NewConsumerHandler(ctx context.Context, config *Config, database controller
return &consumerHandler, nil return &consumerHandler, nil
} }
func (c *ConsumerHandler) handleMs2PsChat(ctx context.Context, msg []byte) { func (c *ConsumerHandler) HandleMs2PsChat(ctx context.Context, msg []byte) {
msgFromMQ := pbpush.PushMsgReq{} msgFromMQ := pbpush.PushMsgReq{}
if err := proto.Unmarshal(msg, &msgFromMQ); err != nil { if err := proto.Unmarshal(msg, &msgFromMQ); err != nil {
log.ZError(ctx, "push Unmarshal msg err", err, "msg", string(msg)) log.ZError(ctx, "push Unmarshal msg err", err, "msg", string(msg))
@@ -126,25 +119,12 @@ func (c *ConsumerHandler) handleMs2PsChat(ctx context.Context, msg []byte) {
} }
} }
func (*ConsumerHandler) Setup(sarama.ConsumerGroupSession) error { return nil } func (c *ConsumerHandler) WaitCache() {
func (*ConsumerHandler) Cleanup(sarama.ConsumerGroupSession) error { return nil }
func (c *ConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
c.onlineCache.Lock.Lock() c.onlineCache.Lock.Lock()
for c.onlineCache.CurrentPhase.Load() < rpccache.DoSubscribeOver { for c.onlineCache.CurrentPhase.Load() < rpccache.DoSubscribeOver {
c.onlineCache.Cond.Wait() c.onlineCache.Cond.Wait()
} }
c.onlineCache.Lock.Unlock() 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
} }
// Push2User Suitable for two types of conversations, one is SingleChatType and the other is NotificationChatType. // Push2User Suitable for two types of conversations, one is SingleChatType and the other is NotificationChatType.
@@ -152,24 +132,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()) log.ZInfo(ctx, "Get msg from msg_transfer And push msg", "userIDs", userIDs, "msg", msg.String())
defer func(duration time.Time) { defer func(duration time.Time) {
t := time.Since(duration) 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()) }(time.Now())
if err := c.webhookBeforeOnlinePush(ctx, &c.config.WebhooksConfig.BeforeOnlinePush, userIDs, msg); err != nil { if err := c.webhookBeforeOnlinePush(ctx, &c.config.WebhooksConfig.BeforeOnlinePush, userIDs, msg); err != nil {
return err return err
} }
log.ZInfo(ctx, "webhookBeforeOnlinePush end")
wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, userIDs) wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, userIDs)
if err != nil { if err != nil {
return err 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) { if !c.shouldPushOffline(ctx, msg) {
return nil return nil
} }
log.ZInfo(ctx, "shouldPushOffline end") log.ZInfo(ctx, "pushOffline start")
for _, v := range wsResults { for _, v := range wsResults {
//message sender do not need offline push //message sender do not need offline push
@@ -188,14 +168,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 { if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, needOfflinePushUserID, msg, &offlinePushUserID); err != nil {
return err return err
} }
log.ZInfo(ctx, "webhookBeforeOfflinePush end")
if len(offlinePushUserID) > 0 { if len(offlinePushUserID) > 0 {
needOfflinePushUserID = offlinePushUserID needOfflinePushUserID = offlinePushUserID
} }
err = c.offlinePushMsg(ctx, msg, needOfflinePushUserID) err = c.offlinePushMsg(ctx, msg, needOfflinePushUserID)
if err != nil { 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 return nil
} }
@@ -207,7 +187,10 @@ func (c *ConsumerHandler) shouldPushOffline(_ context.Context, msg *sdkws.MsgDat
if !isOfflinePush { if !isOfflinePush {
return false return false
} }
if msg.ContentType == constant.SignalingNotification { switch msg.ContentType {
case constant.RoomParticipantsConnectedNotification:
return false
case constant.RoomParticipantsDisconnectedNotification:
return false return false
} }
return true return true
@@ -250,26 +233,24 @@ func (c *ConsumerHandler) Push2Group(ctx context.Context, groupID string, msg *s
&pushToUserIDs); err != nil { &pushToUserIDs); err != nil {
return err return err
} }
log.ZInfo(ctx, "webhookBeforeGroupOnlinePush end")
err = c.groupMessagesHandler(ctx, groupID, &pushToUserIDs, msg) err = c.groupMessagesHandler(ctx, groupID, &pushToUserIDs, msg)
if err != nil { if err != nil {
return err return err
} }
log.ZInfo(ctx, "groupMessagesHandler end")
wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs) wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs)
if err != nil { if err != nil {
return err 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) { if !c.shouldPushOffline(ctx, msg) {
return nil return nil
} }
needOfflinePushUserIDs := c.onlinePusher.GetOnlinePushFailedUserIDs(ctx, msg, wsResults, &pushToUserIDs) 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. //filter some user, like don not disturb or don't need offline push etc.
needOfflinePushUserIDs, err = c.filterGroupMessageOfflinePush(ctx, groupID, msg, needOfflinePushUserIDs) needOfflinePushUserIDs, err = c.filterGroupMessageOfflinePush(ctx, groupID, msg, needOfflinePushUserIDs)
if err != nil { if err != nil {
@@ -297,9 +278,11 @@ func (c *ConsumerHandler) asyncOfflinePush(ctx context.Context, needOfflinePushU
needOfflinePushUserIDs = offlinePushUserIDs needOfflinePushUserIDs = offlinePushUserIDs
} }
if err := c.pushDatabase.MsgToOfflinePushMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(msg.SendID, msg.RecvID), needOfflinePushUserIDs, msg); err != nil { 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) 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 return
} }
} }
+25 -6
View File
@@ -18,11 +18,14 @@ import (
"context" "context"
"errors" "errors"
"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/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" 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/openimsdk/tools/utils/datautil"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
@@ -43,7 +46,7 @@ import (
type authServer struct { type authServer struct {
pbauth.UnimplementedAuthServer pbauth.UnimplementedAuthServer
authDatabase controller.AuthDatabase authDatabase controller.AuthDatabase
RegisterCenter discovery.SvcDiscoveryRegistry RegisterCenter discovery.Conn
config *Config config *Config
userClient *rpcli.UserClient userClient *rpcli.UserClient
} }
@@ -51,15 +54,31 @@ type authServer struct {
type Config struct { type Config struct {
RpcConfig config.Auth RpcConfig config.Auth
RedisConfig config.Redis RedisConfig config.Redis
MongoConfig config.Mongo
Share config.Share Share config.Share
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.Conn, server grpc.ServiceRegistrar) error {
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongoConfig, &config.RedisConfig)
rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err 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.RpcConfig.TokenPolicy.Expire)
}
userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User) userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return err return err
@@ -67,7 +86,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
pbauth.RegisterAuthServer(server, &authServer{ pbauth.RegisterAuthServer(server, &authServer{
RegisterCenter: client, RegisterCenter: client,
authDatabase: controller.NewAuthDatabase( authDatabase: controller.NewAuthDatabase(
redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire), token,
config.Share.Secret, config.Share.Secret,
config.RpcConfig.TokenPolicy.Expire, config.RpcConfig.TokenPolicy.Expire,
config.Share.MultiLogin, config.Share.MultiLogin,
@@ -192,7 +211,7 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID
return err return err
} }
for _, v := range conns { for _, v := range conns {
log.ZDebug(ctx, "forceKickOff", "conn", v.Target()) log.ZDebug(ctx, "forceKickOff", "userID", userID, "platformID", platformID)
client := msggateway.NewMsgGatewayClient(v) client := msggateway.NewMsgGatewayClient(v)
kickReq := &msggateway.KickUserOfflineReq{KickUserIDList: []string{userID}, PlatformID: platformID} kickReq := &msggateway.KickUserOfflineReq{KickUserIDList: []string{userID}, PlatformID: platformID}
_, err := client.KickUserOffline(ctx, kickReq) _, err := client.KickUserOffline(ctx, kickReq)
+12 -12
View File
@@ -16,26 +16,25 @@ package conversation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"sort" "sort"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "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/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/common/storage/database/mgo"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
dbModel "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/localcache"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/tools/db/redisutil"
"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/controller"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
pbconversation "github.com/openimsdk/protocol/conversation" pbconversation "github.com/openimsdk/protocol/conversation"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
@@ -65,12 +64,13 @@ type Config struct {
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.Conn, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
@@ -95,7 +95,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
pbconversation.RegisterConversationServer(server, &conversationServer{ pbconversation.RegisterConversationServer(server, &conversationServer{
conversationNotificationSender: NewConversationNotificationSender(&config.NotificationConfig, msgClient), conversationNotificationSender: NewConversationNotificationSender(&config.NotificationConfig, msgClient),
conversationDatabase: controller.NewConversationDatabase(conversationDB, conversationDatabase: controller.NewConversationDatabase(conversationDB,
redis.NewConversationRedis(rdb, &config.LocalCacheConfig, redis.GetRocksCacheOptions(), conversationDB), mgocli.GetTx()), redis.NewConversationRedis(rdb, &config.LocalCacheConfig, conversationDB), mgocli.GetTx()),
userClient: rpcli.NewUserClient(userConn), userClient: rpcli.NewUserClient(userConn),
groupClient: rpcli.NewGroupClient(groupConn), groupClient: rpcli.NewGroupClient(groupConn),
msgClient: msgClient, msgClient: msgClient,
@@ -773,7 +773,7 @@ func (c *conversationServer) ClearUserConversationMsg(ctx context.Context, req *
if conversation.IsMsgDestruct == false || conversation.MsgDestructTime == 0 { if conversation.IsMsgDestruct == false || conversation.MsgDestructTime == 0 {
continue 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 { if err != nil {
return nil, err return nil, err
} }
+3 -2
View File
@@ -16,6 +16,7 @@ package conversation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
@@ -26,11 +27,11 @@ import (
) )
type ConversationNotificationSender struct { type ConversationNotificationSender struct {
*rpcclient.NotificationSender *notification.NotificationSender
} }
func NewConversationNotificationSender(conf *config.Notification, msgClient *rpcli.MsgClient) *ConversationNotificationSender { 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) return msgClient.SendMsg(ctx, req)
}))} }))}
} }
+18 -6
View File
@@ -16,6 +16,7 @@ package group
import ( import (
"context" "context"
"strings"
"time" "time"
pbgroup "github.com/openimsdk/protocol/group" pbgroup "github.com/openimsdk/protocol/group"
@@ -55,41 +56,52 @@ func UpdateGroupInfoMap(ctx context.Context, group *sdkws.GroupInfoForSet) map[s
return m return m
} }
func UpdateGroupInfoExMap(ctx context.Context, group *pbgroup.SetGroupInfoExReq) (map[string]any, error) { func UpdateGroupInfoExMap(ctx context.Context, group *pbgroup.SetGroupInfoExReq) (m map[string]any, normalFlag, groupNameFlag, notificationFlag bool, err error) {
m := make(map[string]any) m = make(map[string]any)
if group.GroupName != nil { if group.GroupName != nil {
if group.GroupName.Value != "" { if strings.TrimSpace(group.GroupName.Value) != "" {
m["group_name"] = group.GroupName.Value m["group_name"] = group.GroupName.Value
groupNameFlag = true
} else { } 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 { 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"] = group.Notification.Value
m["notification_update_time"] = time.Now()
m["notification_user_id"] = mcontext.GetOpUserID(ctx) m["notification_user_id"] = mcontext.GetOpUserID(ctx)
m["notification_update_time"] = time.Now()
} }
if group.Introduction != nil { if group.Introduction != nil {
m["introduction"] = group.Introduction.Value m["introduction"] = group.Introduction.Value
normalFlag = true
} }
if group.FaceURL != nil { if group.FaceURL != nil {
m["face_url"] = group.FaceURL.Value m["face_url"] = group.FaceURL.Value
normalFlag = true
} }
if group.NeedVerification != nil { if group.NeedVerification != nil {
m["need_verification"] = group.NeedVerification.Value m["need_verification"] = group.NeedVerification.Value
normalFlag = true
} }
if group.LookMemberInfo != nil { if group.LookMemberInfo != nil {
m["look_member_info"] = group.LookMemberInfo.Value m["look_member_info"] = group.LookMemberInfo.Value
normalFlag = true
} }
if group.ApplyMemberFriend != nil { if group.ApplyMemberFriend != nil {
m["apply_member_friend"] = group.ApplyMemberFriend.Value m["apply_member_friend"] = group.ApplyMemberFriend.Value
normalFlag = true
} }
if group.Ex != nil { if group.Ex != nil {
m["ex"] = group.Ex.Value m["ex"] = group.Ex.Value
normalFlag = true
} }
return m, nil return m, normalFlag, groupNameFlag, notificationFlag, nil
} }
func UpdateGroupStatusMap(status int) map[string]any { func UpdateGroupStatusMap(status int) map[string]any {
+48 -46
View File
@@ -17,13 +17,17 @@ package group
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"math/big" "math/big"
"math/rand" "math/rand"
"strconv" "strconv"
"strings" "strings"
"time" "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/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
@@ -42,8 +46,6 @@ import (
pbgroup "github.com/openimsdk/protocol/group" pbgroup "github.com/openimsdk/protocol/group"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/protocol/wrapperspb" "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/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
@@ -51,7 +53,6 @@ import (
"github.com/openimsdk/tools/mw/specialerror" "github.com/openimsdk/tools/mw/specialerror"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/encrypt" "github.com/openimsdk/tools/utils/encrypt"
"google.golang.org/grpc"
) )
type groupServer struct { type groupServer struct {
@@ -76,12 +77,13 @@ type Config struct {
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.Conn, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
@@ -97,11 +99,6 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
if err != nil { if err != nil {
return err 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) userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return err return err
@@ -288,13 +285,14 @@ func (g *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR
break break
} }
} }
g.notification.GroupCreatedNotification(ctx, tips) g.notification.GroupCreatedNotification(ctx, tips, req.SendMessage)
if req.GroupInfo.Notification != "" { if req.GroupInfo.Notification != "" {
notificationFlag := true
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{ g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{
Group: tips.Group, Group: tips.Group,
OpUser: tips.OpUser, OpUser: tips.OpUser,
}) }, &notificationFlag)
} }
reqCallBackAfter := &pbgroup.CreateGroupReq{ reqCallBackAfter := &pbgroup.CreateGroupReq{
@@ -451,7 +449,7 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
return nil, err return nil, err
} }
if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, opUserID, req.InvitedUserIDs...); err != nil { if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, req.SendMessage, opUserID, req.InvitedUserIDs...); err != nil {
return nil, err return nil, err
} }
return &pbgroup.InviteUserToGroupResp{}, nil return &pbgroup.InviteUserToGroupResp{}, nil
@@ -617,7 +615,7 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
for _, userID := range req.KickedUserIDs { for _, userID := range req.KickedUserIDs {
tips.KickedUserList = append(tips.KickedUserList, convert.Db2PbGroupMember(memberMap[userID])) 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 { if err := g.deleteMemberAndSetConversationSeq(ctx, req.GroupID, req.KickedUserIDs); err != nil {
return nil, err return nil, err
} }
@@ -826,8 +824,14 @@ func (g *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup
if member == nil { if member == nil {
log.ZDebug(ctx, "GroupApplicationResponse", "member is nil") log.ZDebug(ctx, "GroupApplicationResponse", "member is nil")
} else { } else {
if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, groupRequest.InviterUserID, req.FromUserID); err != nil { if groupRequest.InviterUserID == "" {
return nil, err 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
}
} }
} }
case constant.GroupResponseRefuse: case constant.GroupResponseRefuse:
@@ -1029,7 +1033,8 @@ func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
log.ZWarn(ctx, "SetConversations", err, "UserIDs", resp.UserIDs, "conversation", conversation) 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}, &notficationFlag)
} }
if req.GroupInfoForSet.GroupName != "" { if req.GroupInfoForSet.GroupName != "" {
num-- num--
@@ -1090,7 +1095,7 @@ func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupI
return nil, err return nil, err
} }
updatedData, err := UpdateGroupInfoExMap(ctx, req) updatedData, normalFlag, groupNameFlag, notificationFlag, err := UpdateGroupInfoExMap(ctx, req)
if len(updatedData) == 0 { if len(updatedData) == 0 {
return &pbgroup.SetGroupInfoExResp{}, nil return &pbgroup.SetGroupInfoExResp{}, nil
} }
@@ -1118,41 +1123,38 @@ func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupI
tips.OpUser = g.groupMemberDB2PB(opMember, 0) tips.OpUser = g.groupMemberDB2PB(opMember, 0)
} }
num := len(updatedData) if notificationFlag {
if req.Notification != nil {
num -= 3
if req.Notification.Value != "" { if req.Notification.Value != "" {
func() { conversation := &pbconv.ConversationReq{
conversation := &pbconv.ConversationReq{ ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID),
ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID), ConversationType: constant.ReadGroupChatType,
ConversationType: constant.ReadGroupChatType, GroupID: req.GroupID,
GroupID: req.GroupID, }
}
resp, err := g.GetGroupMemberUserIDs(ctx, &pbgroup.GetGroupMemberUserIDsReq{GroupID: req.GroupID}) resp, err := g.GetGroupMemberUserIDs(ctx, &pbgroup.GetGroupMemberUserIDsReq{GroupID: req.GroupID})
if err != nil { if err != nil {
log.ZWarn(ctx, "GetGroupMemberIDs is failed.", err) log.ZWarn(ctx, "GetGroupMemberIDs is failed.", err)
return return nil, err
} }
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.GroupNotification} conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.GroupNotification}
if err := g.conversationClient.SetConversations(ctx, resp.UserIDs, conversation); err != nil { if err := g.conversationClient.SetConversations(ctx, resp.UserIDs, conversation); err != nil {
log.ZWarn(ctx, "SetConversations", err, "UserIDs", resp.UserIDs, "conversation", conversation) 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}, &notificationFlag)
} else {
notificationFlag = false
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}, &notificationFlag)
} }
} }
if req.GroupName != nil { if groupNameFlag {
num--
g.notification.GroupInfoSetNameNotification(ctx, &sdkws.GroupInfoSetNameTips{Group: tips.Group, OpUser: tips.OpUser}) 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) g.notification.GroupInfoSetNotification(ctx, tips)
} }
@@ -1372,7 +1374,7 @@ func (g *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGrou
if mcontext.GetOpUserID(ctx) == owner.UserID { if mcontext.GetOpUserID(ctx) == owner.UserID {
tips.OpUser = g.groupMemberDB2PB(owner, 0) 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) membersID, err := g.db.FindGroupMemberUserID(ctx, group.GroupID)
if err != nil { if err != nil {
+35 -36
View File
@@ -22,6 +22,8 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "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/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/convert" "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/servererrs"
@@ -41,7 +43,6 @@ import (
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/stringutil" "github.com/openimsdk/tools/utils/stringutil"
"go.mongodb.org/mongo-driver/mongo"
) )
// GroupApplicationReceiver // GroupApplicationReceiver
@@ -52,11 +53,11 @@ const (
func NewNotificationSender(db controller.GroupDatabase, config *Config, userClient *rpcli.UserClient, msgClient *rpcli.MsgClient, conversationClient *rpcli.ConversationClient) *NotificationSender { func NewNotificationSender(db controller.GroupDatabase, config *Config, userClient *rpcli.UserClient, msgClient *rpcli.MsgClient, conversationClient *rpcli.ConversationClient) *NotificationSender {
return &NotificationSender{ return &NotificationSender{
NotificationSender: rpcclient.NewNotificationSender(&config.NotificationConfig, NotificationSender: notification.NewNotificationSender(&config.NotificationConfig,
rpcclient.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) { notification.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) {
return msgClient.SendMsg(ctx, req) return msgClient.SendMsg(ctx, req)
}), }),
rpcclient.WithUserRpcClient(userClient.GetUserInfo), notification.WithUserRpcClient(userClient.GetUserInfo),
), ),
getUsersInfo: func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error) { getUsersInfo: func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error) {
users, err := userClient.GetUsersInfo(ctx, userIDs) users, err := userClient.GetUsersInfo(ctx, userIDs)
@@ -73,7 +74,7 @@ func NewNotificationSender(db controller.GroupDatabase, config *Config, userClie
} }
type NotificationSender struct { type NotificationSender struct {
*rpcclient.NotificationSender *notification.NotificationSender
getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error) getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error)
db controller.GroupDatabase db controller.GroupDatabase
config *Config config *Config
@@ -233,17 +234,17 @@ func (g *NotificationSender) groupMemberDB2PB(member *model.GroupMember, appMang
return result, nil return result, nil
} */ } */
func (g *NotificationSender) fillOpUser(ctx context.Context, opUser **sdkws.GroupMemberFullInfo, groupID string) (err error) { func (g *NotificationSender) fillOpUser(ctx context.Context, targetUser **sdkws.GroupMemberFullInfo, groupID string) (err error) {
return g.fillOpUserByUserID(ctx, mcontext.GetOpUserID(ctx), opUser, groupID) return g.fillUserByUserID(ctx, mcontext.GetOpUserID(ctx), targetUser, groupID)
} }
func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID string, opUser **sdkws.GroupMemberFullInfo, groupID string) error { func (g *NotificationSender) fillUserByUserID(ctx context.Context, userID string, targetUser **sdkws.GroupMemberFullInfo, groupID string) error {
if opUser == nil { if targetUser == nil {
return errs.ErrInternalServer.WrapMsg("**sdkws.GroupMemberFullInfo is nil") return errs.ErrInternalServer.WrapMsg("**sdkws.GroupMemberFullInfo is nil")
} }
if groupID != "" { if groupID != "" {
if authverify.IsManagerUserID(userID, g.config.Share.IMAdminUserID) { if authverify.IsManagerUserID(userID, g.config.Share.IMAdminUserID) {
*opUser = &sdkws.GroupMemberFullInfo{ *targetUser = &sdkws.GroupMemberFullInfo{
GroupID: groupID, GroupID: groupID,
UserID: userID, UserID: userID,
RoleLevel: constant.GroupAdmin, RoleLevel: constant.GroupAdmin,
@@ -252,7 +253,7 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
} else { } else {
member, err := g.db.TakeGroupMember(ctx, groupID, userID) member, err := g.db.TakeGroupMember(ctx, groupID, userID)
if err == nil { if err == nil {
*opUser = g.groupMemberDB2PB(member, 0) *targetUser = g.groupMemberDB2PB(member, 0)
} else if !(errors.Is(err, mongo.ErrNoDocuments) || errs.ErrRecordNotFound.Is(err)) { } else if !(errors.Is(err, mongo.ErrNoDocuments) || errs.ErrRecordNotFound.Is(err)) {
return err return err
} }
@@ -262,8 +263,8 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
if err != nil { if err != nil {
return err return err
} }
if *opUser == nil { if *targetUser == nil {
*opUser = &sdkws.GroupMemberFullInfo{ *targetUser = &sdkws.GroupMemberFullInfo{
GroupID: groupID, GroupID: groupID,
UserID: userID, UserID: userID,
Nickname: user.Nickname, Nickname: user.Nickname,
@@ -271,11 +272,11 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
OperatorUserID: userID, OperatorUserID: userID,
} }
} else { } else {
if (*opUser).Nickname == "" { if (*targetUser).Nickname == "" {
(*opUser).Nickname = user.Nickname (*targetUser).Nickname = user.Nickname
} }
if (*opUser).FaceURL == "" { if (*targetUser).FaceURL == "" {
(*opUser).FaceURL = user.FaceURL (*targetUser).FaceURL = user.FaceURL
} }
} }
return nil return nil
@@ -307,7 +308,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 var err error
defer func() { defer func() {
if err != nil { if err != nil {
@@ -318,7 +319,7 @@ func (g *NotificationSender) GroupCreatedNotification(ctx context.Context, tips
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) 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) { func (g *NotificationSender) GroupInfoSetNotification(ctx context.Context, tips *sdkws.GroupInfoSetTips) {
@@ -332,7 +333,7 @@ func (g *NotificationSender) GroupInfoSetNotification(ctx context.Context, tips
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) 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) { func (g *NotificationSender) GroupInfoSetNameNotification(ctx context.Context, tips *sdkws.GroupInfoSetNameTips) {
@@ -349,7 +350,7 @@ func (g *NotificationSender) GroupInfoSetNameNotification(ctx context.Context, t
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNameNotification, tips) 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 var err error
defer func() { defer func() {
if err != nil { if err != nil {
@@ -360,7 +361,7 @@ func (g *NotificationSender) GroupInfoSetAnnouncementNotification(ctx context.Co
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) 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) JoinGroupApplicationNotification(ctx context.Context, req *pbgroup.JoinGroupReq) {
@@ -505,7 +506,7 @@ func (g *NotificationSender) GroupOwnerTransferredNotification(ctx context.Conte
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupOwnerTransferredNotification, tips) 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 var err error
defer func() { defer func() {
if err != nil { if err != nil {
@@ -516,10 +517,10 @@ func (g *NotificationSender) MemberKickedNotification(ctx context.Context, tips
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) 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 {
var err error var err error
defer func() { defer func() {
if err != nil { if err != nil {
@@ -556,20 +557,18 @@ func (g *NotificationSender) GroupApplicationAgreeMemberEnterNotification(ctx co
InvitedUserList: users, InvitedUserList: users,
} }
opUserID := mcontext.GetOpUserID(ctx) 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 return nil
} }
switch { if invitedOpUserID == opUserID {
case invitedOpUserID == "":
case invitedOpUserID == opUserID:
tips.InviterUser = tips.OpUser tips.InviterUser = tips.OpUser
default: } else {
if err = g.fillOpUserByUserID(ctx, invitedOpUserID, &tips.InviterUser, tips.Group.GroupID); err != nil { if err = g.fillUserByUserID(ctx, invitedOpUserID, &tips.InviterUser, tips.Group.GroupID); err != nil {
return err return err
} }
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) 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 return nil
} }
@@ -614,7 +613,7 @@ func (g *NotificationSender) MemberEnterNotification(ctx context.Context, groupI
return nil 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 var err error
defer func() { defer func() {
if err != nil { if err != nil {
@@ -624,7 +623,7 @@ func (g *NotificationSender) GroupDismissedNotification(ctx context.Context, tip
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return 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) { func (g *NotificationSender) GroupMemberMutedNotification(ctx context.Context, groupID, groupMemberUserID string, mutedSeconds uint32) {
@@ -781,7 +780,7 @@ func (g *NotificationSender) GroupMemberSetToAdminNotification(ctx context.Conte
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return 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) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToAdminNotification, tips)
} }
@@ -806,6 +805,6 @@ func (g *NotificationSender) GroupMemberSetToOrdinaryUserNotification(ctx contex
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return 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) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToOrdinaryUserNotification, tips)
} }
+22 -146
View File
@@ -11,10 +11,10 @@ import (
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
pbgroup "github.com/openimsdk/protocol/group" pbgroup "github.com/openimsdk/protocol/group"
"github.com/openimsdk/protocol/sdkws" "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) { func (g *groupServer) GetFullGroupMemberUserIDs(ctx context.Context, req *pbgroup.GetFullGroupMemberUserIDsReq) (*pbgroup.GetFullGroupMemberUserIDsResp, error) {
vl, err := g.db.FindMaxGroupMemberVersionCache(ctx, req.GroupID) vl, err := g.db.FindMaxGroupMemberVersionCache(ctx, req.GroupID)
if err != nil { if err != nil {
@@ -132,150 +132,6 @@ func (g *groupServer) GetIncrementalGroupMember(ctx context.Context, req *pbgrou
return resp, nil 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) { 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.CheckAccessV3(ctx, req.UserID, g.config.Share.IMAdminUserID); err != nil {
return nil, err return nil, err
@@ -301,3 +157,23 @@ func (g *groupServer) GetIncrementalJoinGroup(ctx context.Context, req *pbgroup.
} }
return opt.Build() 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 {
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
}
+31 -7
View File
@@ -16,7 +16,13 @@ package msg
import ( import (
"context" "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/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/stringutil"
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
@@ -94,7 +100,7 @@ func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand), CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand),
RecvID: msg.MsgData.RecvID, 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 { func (m *msgServer) webhookBeforeSendGroupMsg(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error {
@@ -128,14 +134,15 @@ func (m *msgServer) webhookAfterSendGroupMsg(ctx context.Context, after *config.
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand), CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand),
GroupID: msg.MsgData.GroupID, 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 { return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
if msg.MsgData.ContentType != constant.Text { //if msg.MsgData.ContentType != constant.Text {
return nil // return nil
} //}
if !filterBeforeMsg(msg, before) { if !filterBeforeMsg(msg, before) {
return nil return nil
} }
@@ -146,9 +153,14 @@ func (m *msgServer) webhookBeforeMsgModify(ctx context.Context, before *config.B
if err := m.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { if err := m.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
return err return err
} }
if beforeMsgData != nil {
*beforeMsgData = proto.Clone(msg.MsgData).(*sdkws.MsgData)
}
if resp.Content != nil { if resp.Content != nil {
msg.MsgData.Content = []byte(*resp.Content) 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.OfflinePushInfo, resp.OfflinePushInfo)
datautil.NotNilReplace(&msg.MsgData.RecvID, resp.RecvID) datautil.NotNilReplace(&msg.MsgData.RecvID, resp.RecvID)
@@ -192,3 +204,15 @@ func (m *msgServer) webhookAfterRevokeMsg(ctx context.Context, after *config.Aft
} }
m.webhookClient.AsyncPost(ctx, callbackReq.GetCallbackCommand(), callbackReq, &cbapi.CallbackAfterRevokeMsgResp{}, after) 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)),
}
}
+36 -12
View File
@@ -1,11 +1,13 @@
package msg package msg
import ( import (
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
pbchat "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/tools/utils/datautil"
"strconv" "strconv"
"strings" "strings"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/protocol/constant"
pbchat "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/tools/utils/datautil"
) )
const ( const (
@@ -13,28 +15,50 @@ const (
) )
func filterAfterMsg(msg *pbchat.SendMsgReq, after *config.AfterConfig) bool { func filterAfterMsg(msg *pbchat.SendMsgReq, after *config.AfterConfig) bool {
return filterMsg(msg, after.AttentionIds, after.AllowedTypes, after.DeniedTypes) return filterMsg(msg, after.AttentionIds, after.DeniedTypes)
} }
func filterBeforeMsg(msg *pbchat.SendMsgReq, before *config.BeforeConfig) bool { func filterBeforeMsg(msg *pbchat.SendMsgReq, before *config.BeforeConfig) bool {
return filterMsg(msg, nil, before.AllowedTypes, before.DeniedTypes) return filterMsg(msg, nil, before.DeniedTypes)
} }
func filterMsg(msg *pbchat.SendMsgReq, attentionIds, allowedTypes, deniedTypes []string) bool { func filterMsg(msg *pbchat.SendMsgReq, attentionIds []string, deniedTypes []int32) bool {
// According to the attentionIds configuration, only some users are sent // According to the attentionIds configuration, only some users are sent
if len(attentionIds) != 0 && !datautil.Contains([]string{msg.MsgData.SendID, msg.MsgData.RecvID}, attentionIds...) { if len(attentionIds) != 0 && !datautil.Contain(msg.MsgData.RecvID, attentionIds...) {
return false return false
} }
if len(allowedTypes) != 0 && !isInInterval(msg.MsgData.ContentType, allowedTypes) {
if defaultDeniedTypes(msg.MsgData.ContentType) {
return false return false
} }
if len(deniedTypes) != 0 && isInInterval(msg.MsgData.ContentType, deniedTypes) {
if len(deniedTypes) != 0 && datautil.Contain(msg.MsgData.ContentType, deniedTypes...) {
return false return false
} }
//if len(allowedTypes) != 0 && !isInInterval(msg.MsgData.ContentType, allowedTypes) {
// return false
//}
//if len(deniedTypes) != 0 && isInInterval(msg.MsgData.ContentType, deniedTypes) {
// return false
//}
return true return true
} }
func isInInterval(contentType int32, interval []string) bool { func defaultDeniedTypes(contentType int32) bool {
if contentType >= constant.NotificationBegin && contentType <= constant.NotificationEnd {
return true
}
if contentType == constant.Typing {
return true
}
return false
}
// isInInterval if data is in interval
// 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. [1, 100, 200-500, 600-700] means that only data within the range
// {1, 100} [200, 500] [600, 700] will return true.
func isInInterval(data int32, interval []string) bool {
for _, v := range interval { for _, v := range interval {
if strings.Contains(v, separator) { if strings.Contains(v, separator) {
// is interval // is interval
@@ -50,7 +74,7 @@ func isInInterval(contentType int32, interval []string) bool {
if err != nil { if err != nil {
continue continue
} }
if datautil.BetweenEq(int(contentType), bottom, top) { if datautil.BetweenEq(int(data), bottom, top) {
return true return true
} }
} else { } else {
@@ -58,7 +82,7 @@ func isInInterval(contentType int32, interval []string) bool {
if err != nil { if err != nil {
continue continue
} }
if int(contentType) == iv { if int(data) == iv {
return true return true
} }
} }
+3 -3
View File
@@ -23,11 +23,11 @@ import (
) )
type MsgNotificationSender struct { type MsgNotificationSender struct {
*rpcclient.NotificationSender *notification.NotificationSender
} }
func NewMsgNotificationSender(config *Config, opts ...rpcclient.NotificationSenderOptions) *MsgNotificationSender { func NewMsgNotificationSender(config *Config, opts ...notification.NotificationSenderOptions) *MsgNotificationSender {
return &MsgNotificationSender{rpcclient.NewNotificationSender(&config.NotificationConfig, opts...)} return &MsgNotificationSender{notification.NewNotificationSender(&config.NotificationConfig, opts...)}
} }
func (m *MsgNotificationSender) UserDeleteMsgsNotification(ctx context.Context, userID, conversationID string, seqs []int64) { func (m *MsgNotificationSender) UserDeleteMsgsNotification(ctx context.Context, userID, conversationID string, seqs []int64) {
+6 -3
View File
@@ -17,9 +17,10 @@ package msg
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "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/servererrs"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
@@ -79,8 +80,10 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg.
switch members[req.UserID].RoleLevel { switch members[req.UserID].RoleLevel {
case constant.GroupOwner: case constant.GroupOwner:
case constant.GroupAdmin: case constant.GroupAdmin:
if members[msgs[0].SendID].RoleLevel != constant.GroupOrdinaryUsers { if sendMember, ok := members[msgs[0].SendID]; ok {
return nil, errs.ErrNoPermission.WrapMsg("no permission") if sendMember.RoleLevel != constant.GroupOrdinaryUsers {
return nil, errs.ErrNoPermission.WrapMsg("no permission")
}
} }
default: default:
return nil, errs.ErrNoPermission.WrapMsg("no permission") return nil, errs.ErrNoPermission.WrapMsg("no permission")
+37 -25
View File
@@ -29,31 +29,44 @@ import (
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"google.golang.org/protobuf/proto"
) )
func (m *msgServer) SendMsg(ctx context.Context, req *pbmsg.SendMsgReq) (*pbmsg.SendMsgResp, error) { func (m *msgServer) SendMsg(ctx context.Context, req *pbmsg.SendMsgReq) (*pbmsg.SendMsgResp, error) {
if req.MsgData != nil { if req.MsgData == nil {
m.encapsulateMsgData(req.MsgData) return nil, errs.ErrArgs.WrapMsg("msgData is nil")
if req.MsgData.ContentType == constant.Stream {
if err := m.handlerStreamMsg(ctx, req.MsgData); err != nil {
return nil, err
}
}
switch req.MsgData.SessionType {
case constant.SingleChatType:
return m.sendMsgSingleChat(ctx, req)
case constant.NotificationChatType:
return m.sendMsgNotification(ctx, req)
case constant.ReadGroupChatType:
return m.sendMsgGroupChat(ctx, req)
default:
return nil, errs.ErrArgs.WrapMsg("unknown sessionType")
}
} }
return nil, errs.ErrArgs.WrapMsg("msgData is nil") before := new(*sdkws.MsgData)
resp, err := m.sendMsg(ctx, req, before)
if err != nil {
return nil, err
}
if *before != nil && proto.Equal(*before, req.MsgData) == false {
resp.Modify = req.MsgData
}
return resp, nil
} }
func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, err error) { func (m *msgServer) sendMsg(ctx context.Context, req *pbmsg.SendMsgReq, before **sdkws.MsgData) (*pbmsg.SendMsgResp, error) {
m.encapsulateMsgData(req.MsgData)
if req.MsgData.ContentType == constant.Stream {
if err := m.handlerStreamMsg(ctx, req.MsgData); err != nil {
return nil, err
}
}
switch req.MsgData.SessionType {
case constant.SingleChatType:
return m.sendMsgSingleChat(ctx, req, before)
case constant.NotificationChatType:
return m.sendMsgNotification(ctx, req, before)
case constant.ReadGroupChatType:
return m.sendMsgGroupChat(ctx, req, before)
default:
return nil, errs.ErrArgs.WrapMsg("unknown sessionType")
}
}
func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq, before **sdkws.MsgData) (resp *pbmsg.SendMsgResp, err error) {
if err = m.messageVerification(ctx, req); err != nil { if err = m.messageVerification(ctx, req); err != nil {
prommetrics.GroupChatMsgProcessFailedCounter.Inc() prommetrics.GroupChatMsgProcessFailedCounter.Inc()
return nil, err return nil, err
@@ -62,7 +75,7 @@ func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq)
if err = m.webhookBeforeSendGroupMsg(ctx, &m.config.WebhooksConfig.BeforeSendGroupMsg, req); err != nil { if err = m.webhookBeforeSendGroupMsg(ctx, &m.config.WebhooksConfig.BeforeSendGroupMsg, req); err != nil {
return nil, err return nil, err
} }
if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil { if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req, before); err != nil {
return nil, err return nil, err
} }
err = m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForGroup(req.MsgData.GroupID), req.MsgData) err = m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForGroup(req.MsgData.GroupID), req.MsgData)
@@ -144,7 +157,7 @@ func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgDa
} }
} }
func (m *msgServer) sendMsgNotification(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, err error) { func (m *msgServer) sendMsgNotification(ctx context.Context, req *pbmsg.SendMsgReq, before **sdkws.MsgData) (resp *pbmsg.SendMsgResp, err error) {
if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil { if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil {
return nil, err return nil, err
} }
@@ -156,7 +169,7 @@ func (m *msgServer) sendMsgNotification(ctx context.Context, req *pbmsg.SendMsgR
return resp, nil return resp, nil
} }
func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, err error) { func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq, before **sdkws.MsgData) (resp *pbmsg.SendMsgResp, err error) {
if err := m.messageVerification(ctx, req); err != nil { if err := m.messageVerification(ctx, req); err != nil {
return nil, err return nil, err
} }
@@ -176,12 +189,11 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq
} }
if !isSend { if !isSend {
prommetrics.SingleChatMsgProcessFailedCounter.Inc() prommetrics.SingleChatMsgProcessFailedCounter.Inc()
return nil, nil return nil, errs.ErrArgs.WrapMsg("message is not sent")
} else { } else {
if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil { if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req, before); err != nil {
return nil, err return nil, err
} }
if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil { if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil {
prommetrics.SingleChatMsgProcessFailedCounter.Inc() prommetrics.SingleChatMsgProcessFailedCounter.Inc()
return nil, err return nil, err
+34 -19
View File
@@ -16,24 +16,27 @@ package msg
import ( import (
"context" "context"
"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/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/mqbuild"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "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/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" "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/common/storage/database/mgo"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/open-im-server/v3/pkg/notification" "github.com/openimsdk/open-im-server/v3/pkg/notification"
"github.com/openimsdk/open-im-server/v3/pkg/rpccache" "github.com/openimsdk/open-im-server/v3/pkg/rpccache"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/conversation" "github.com/openimsdk/protocol/conversation"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"google.golang.org/grpc"
) )
type MessageInterceptorFunc func(ctx context.Context, globalConfig *Config, req *msg.SendMsgReq) (*sdkws.MsgData, error) type MessageInterceptorFunc func(ctx context.Context, globalConfig *Config, req *msg.SendMsgReq) (*sdkws.MsgData, error)
@@ -56,15 +59,15 @@ type Config struct {
// MsgServer encapsulates dependencies required for message handling. // MsgServer encapsulates dependencies required for message handling.
type msgServer struct { type msgServer struct {
msg.UnimplementedMsgServer msg.UnimplementedMsgServer
RegisterCenter discovery.SvcDiscoveryRegistry // Service discovery registry for service registration. RegisterCenter discovery.Conn // Service discovery registry for service registration.
MsgDatabase controller.CommonMsgDatabase // Interface for message database operations. MsgDatabase controller.CommonMsgDatabase // Interface for message database operations.
StreamMsgDatabase controller.StreamMsgDatabase StreamMsgDatabase controller.StreamMsgDatabase
UserLocalCache *rpccache.UserLocalCache // Local cache for user data. UserLocalCache *rpccache.UserLocalCache // Local cache for user data.
FriendLocalCache *rpccache.FriendLocalCache // Local cache for friend data. FriendLocalCache *rpccache.FriendLocalCache // Local cache for friend data.
GroupLocalCache *rpccache.GroupLocalCache // Local cache for group data. GroupLocalCache *rpccache.GroupLocalCache // Local cache for group data.
ConversationLocalCache *rpccache.ConversationLocalCache // Local cache for conversation data. ConversationLocalCache *rpccache.ConversationLocalCache // Local cache for conversation data.
Handlers MessageInterceptorChain // Chain of handlers for processing messages. Handlers MessageInterceptorChain // Chain of handlers for processing messages.
notificationSender *rpcclient.NotificationSender // RPC client for sending notifications. notificationSender *notification.NotificationSender // RPC client for sending notifications.
msgNotificationSender *MsgNotificationSender // RPC client for sending msg notifications. msgNotificationSender *MsgNotificationSender // RPC client for sending msg notifications.
config *Config // Global configuration settings. config *Config // Global configuration settings.
webhookClient *webhook.Client webhookClient *webhook.Client
@@ -76,12 +79,18 @@ func (m *msgServer) addInterceptorHandler(interceptorFunc ...MessageInterceptorF
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.Conn, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) builder := mqbuild.NewBuilder(&config.KafkaConfig)
redisProducer, err := builder.GetTopicProducer(ctx, config.KafkaConfig.ToRedisTopic)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil {
return err
}
rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
@@ -89,7 +98,16 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
if err != nil { if err != nil {
return err 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()) seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB())
if err != nil { if err != nil {
return err return err
@@ -104,10 +122,6 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
return err return err
} }
seqUserCache := redis.NewSeqUserCacheRedis(rdb, seqUser) seqUserCache := redis.NewSeqUserCacheRedis(rdb, seqUser)
msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqUserCache, seqConversationCache, &config.KafkaConfig)
if err != nil {
return err
}
userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User) userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return err return err
@@ -125,6 +139,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
return err return err
} }
conversationClient := rpcli.NewConversationClient(conversationConn) conversationClient := rpcli.NewConversationClient(conversationConn)
msgDatabase := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqUserCache, seqConversationCache, redisProducer)
s := &msgServer{ s := &msgServer{
MsgDatabase: msgDatabase, MsgDatabase: msgDatabase,
StreamMsgDatabase: controller.NewStreamMsgDatabase(streamMsg), StreamMsgDatabase: controller.NewStreamMsgDatabase(streamMsg),
@@ -138,8 +153,8 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
conversationClient: conversationClient, conversationClient: conversationClient,
} }
s.notificationSender = rpcclient.NewNotificationSender(&config.NotificationConfig, rpcclient.WithLocalSendMsg(s.SendMsg)) s.notificationSender = notification.NewNotificationSender(&config.NotificationConfig, notification.WithLocalSendMsg(s.SendMsg))
s.msgNotificationSender = NewMsgNotificationSender(config, rpcclient.WithLocalSendMsg(s.SendMsg)) s.msgNotificationSender = NewMsgNotificationSender(config, notification.WithLocalSendMsg(s.SendMsg))
msg.RegisterMsgServer(server, s) msg.RegisterMsgServer(server, s)
+15 -24
View File
@@ -16,13 +16,15 @@ package msg
import ( import (
"context" "context"
"math/rand"
"strconv"
"time"
"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/servererrs"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/encrypt" "github.com/openimsdk/tools/utils/encrypt"
"github.com/openimsdk/tools/utils/timeutil" "github.com/openimsdk/tools/utils/timeutil"
"math/rand"
"strconv"
"time"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
@@ -62,6 +64,13 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe
if err := m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, data); err != nil { if err := m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, data); err != nil {
return err return err
} }
u, err := m.UserLocalCache.GetUserInfo(ctx, data.MsgData.SendID)
if err != nil {
return err
}
if authverify.CheckSystemAccount(ctx, u.AppMangerLevel) {
return nil
}
black, err := m.FriendLocalCache.IsBlack(ctx, data.MsgData.SendID, data.MsgData.RecvID) black, err := m.FriendLocalCache.IsBlack(ctx, data.MsgData.SendID, data.MsgData.RecvID)
if err != nil { if err != nil {
return err return err
@@ -137,27 +146,9 @@ func (m *msgServer) encapsulateMsgData(msg *sdkws.MsgData) {
msg.SendTime = timeutil.GetCurrentTimestampByMill() msg.SendTime = timeutil.GetCurrentTimestampByMill()
} }
switch msg.ContentType { switch msg.ContentType {
case constant.Text: case constant.Text, constant.Picture, constant.Voice, constant.Video,
fallthrough constant.File, constant.AtText, constant.Merger, constant.Card,
case constant.Picture: constant.Location, constant.Custom, constant.Quote, constant.AdvancedText, constant.MarkdownText:
fallthrough
case constant.Voice:
fallthrough
case constant.Video:
fallthrough
case constant.File:
fallthrough
case constant.AtText:
fallthrough
case constant.Merger:
fallthrough
case constant.Card:
fallthrough
case constant.Location:
fallthrough
case constant.Custom:
fallthrough
case constant.Quote:
case constant.Revoke: case constant.Revoke:
datautil.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false) datautil.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false)
datautil.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false) datautil.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false)
+13 -13
View File
@@ -16,26 +16,25 @@ package relation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/tools/mq/memamq" "github.com/openimsdk/tools/mq/memamq"
"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/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/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/common/storage/database/mgo"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" "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/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/localcache" "github.com/openimsdk/open-im-server/v3/pkg/localcache"
"github.com/openimsdk/tools/db/redisutil"
"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"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/relation" "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
@@ -47,7 +46,7 @@ type friendServer struct {
db controller.FriendDatabase db controller.FriendDatabase
blackDatabase controller.BlackDatabase blackDatabase controller.BlackDatabase
notificationSender *FriendNotificationSender notificationSender *FriendNotificationSender
RegisterCenter discovery.SvcDiscoveryRegistry RegisterCenter discovery.Conn
config *Config config *Config
webhookClient *webhook.Client webhookClient *webhook.Client
queue *memamq.MemoryQueue queue *memamq.MemoryQueue
@@ -66,12 +65,13 @@ type Config struct {
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.Conn, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
@@ -114,12 +114,12 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
db: controller.NewFriendDatabase( db: controller.NewFriendDatabase(
friendMongoDB, friendMongoDB,
friendRequestMongoDB, friendRequestMongoDB,
redis.NewFriendCacheRedis(rdb, &config.LocalCacheConfig, friendMongoDB, redis.GetRocksCacheOptions()), redis.NewFriendCacheRedis(rdb, &config.LocalCacheConfig, friendMongoDB),
mgocli.GetTx(), mgocli.GetTx(),
), ),
blackDatabase: controller.NewBlackDatabase( blackDatabase: controller.NewBlackDatabase(
blackMongoDB, blackMongoDB,
redis.NewBlackCacheRedis(rdb, &config.LocalCacheConfig, blackMongoDB, redis.GetRocksCacheOptions()), redis.NewBlackCacheRedis(rdb, &config.LocalCacheConfig, blackMongoDB),
), ),
notificationSender: notificationSender, notificationSender: notificationSender,
RegisterCenter: client, RegisterCenter: client,
+3 -2
View File
@@ -16,6 +16,7 @@ package relation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
@@ -36,7 +37,7 @@ import (
) )
type FriendNotificationSender struct { type FriendNotificationSender struct {
*rpcclient.NotificationSender *notification.NotificationSender
// Target not found err // Target not found err
getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error) getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error)
// db controller // db controller
@@ -89,7 +90,7 @@ func WithRpcFunc(
func NewFriendNotificationSender(conf *config.Notification, msgClient *rpcli.MsgClient, opts ...friendNotificationSenderOptions) *FriendNotificationSender { func NewFriendNotificationSender(conf *config.Notification, msgClient *rpcli.MsgClient, opts ...friendNotificationSenderOptions) *FriendNotificationSender {
f := &FriendNotificationSender{ f := &FriendNotificationSender{
NotificationSender: rpcclient.NewNotificationSender(conf, rpcclient.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) { NotificationSender: notification.NewNotificationSender(conf, notification.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) {
return msgClient.SendMsg(ctx, req) return msgClient.SendMsg(ctx, req)
})), })),
} }
+6 -2
View File
@@ -19,11 +19,12 @@ import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"path" "path"
"strconv" "strconv"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
@@ -37,7 +38,10 @@ import (
) )
func (t *thirdServer) PartLimit(ctx context.Context, req *third.PartLimitReq) (*third.PartLimitResp, error) { func (t *thirdServer) PartLimit(ctx context.Context, req *third.PartLimitReq) (*third.PartLimitResp, error) {
limit := t.s3dataBase.PartLimit() limit, err := t.s3dataBase.PartLimit()
if err != nil {
return nil, err
}
return &third.PartLimitResp{ return &third.PartLimitResp{
MinPartSize: limit.MinPartSize, MinPartSize: limit.MinPartSize,
MaxPartSize: limit.MaxPartSize, MaxPartSize: limit.MaxPartSize,
+37 -14
View File
@@ -17,9 +17,14 @@ package third
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"time" "time"
"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/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/tools/s3/disable"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "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/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
@@ -29,8 +34,6 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/protocol/third" "github.com/openimsdk/protocol/third"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/s3" "github.com/openimsdk/tools/s3"
"github.com/openimsdk/tools/s3/cos" "github.com/openimsdk/tools/s3/cos"
@@ -60,15 +63,17 @@ type Config struct {
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.Conn, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
logdb, err := mgo.NewLogMongo(mgocli.GetDB()) logdb, err := mgo.NewLogMongo(mgocli.GetDB())
if err != nil { if err != nil {
return err return err
@@ -77,15 +82,31 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
if err != nil { if err != nil {
return err return err
} }
var thirdCache cache.ThirdCache
if rdb == nil {
tc, err := mgo.NewCacheMgo(mgocli.GetDB())
if err != nil {
return err
}
thirdCache = mcache.NewThirdCache(tc)
} else {
thirdCache = redis.NewThirdCache(rdb)
}
// Select the oss method according to the profile policy // Select the oss method according to the profile policy
enable := config.RpcConfig.Object.Enable var o s3.Interface
var ( switch enable := config.RpcConfig.Object.Enable; enable {
o s3.Interface
)
switch enable {
case "minio": case "minio":
o, err = minio.NewMinio(ctx, redis.NewMinioCache(rdb), *config.MinioConfig.Build()) var minioCache minio.Cache
if rdb == nil {
mc, err := mgo.NewCacheMgo(mgocli.GetDB())
if err != nil {
return err
}
minioCache = mcache.NewMinioCache(mc)
} else {
minioCache = redis.NewMinioCache(rdb)
}
o, err = minio.NewMinio(ctx, minioCache, *config.MinioConfig.Build())
case "cos": case "cos":
o, err = cos.NewCos(*config.RpcConfig.Object.Cos.Build()) o, err = cos.NewCos(*config.RpcConfig.Object.Cos.Build())
case "oss": case "oss":
@@ -94,6 +115,8 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
o, err = kodo.NewKodo(*config.RpcConfig.Object.Kodo.Build()) o, err = kodo.NewKodo(*config.RpcConfig.Object.Kodo.Build())
case "aws": case "aws":
o, err = aws.NewAws(*config.RpcConfig.Object.Aws.Build()) o, err = aws.NewAws(*config.RpcConfig.Object.Aws.Build())
case "":
o = disable.NewDisable()
default: default:
err = fmt.Errorf("invalid object enable: %s", enable) err = fmt.Errorf("invalid object enable: %s", enable)
} }
@@ -106,7 +129,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
} }
localcache.InitLocalCache(&config.LocalCacheConfig) localcache.InitLocalCache(&config.LocalCacheConfig)
third.RegisterThirdServer(server, &thirdServer{ third.RegisterThirdServer(server, &thirdServer{
thirdDatabase: controller.NewThirdDatabase(redis.NewThirdCache(rdb), logdb), thirdDatabase: controller.NewThirdDatabase(thirdCache, logdb),
s3dataBase: controller.NewS3Database(rdb, o, s3db), s3dataBase: controller.NewS3Database(rdb, o, s3db),
defaultExpire: time.Hour * 24 * 7, defaultExpire: time.Hour * 24 * 7,
config: config, config: config,
+3 -2
View File
@@ -16,6 +16,7 @@ package user
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
@@ -29,7 +30,7 @@ import (
) )
type UserNotificationSender struct { type UserNotificationSender struct {
*rpcclient.NotificationSender *notification.NotificationSender
getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error) getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error)
// db controller // db controller
db controller.UserDatabase db controller.UserDatabase
@@ -63,7 +64,7 @@ func WithUserFunc(
func NewUserNotificationSender(config *Config, msgClient *rpcli.MsgClient, opts ...userNotificationSenderOptions) *UserNotificationSender { func NewUserNotificationSender(config *Config, msgClient *rpcli.MsgClient, opts ...userNotificationSenderOptions) *UserNotificationSender {
f := &UserNotificationSender{ f := &UserNotificationSender{
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) return msgClient.SendMsg(ctx, req)
})), })),
} }
+32 -20
View File
@@ -23,29 +23,27 @@ import (
"time" "time"
"github.com/openimsdk/open-im-server/v3/internal/rpc/relation" "github.com/openimsdk/open-im-server/v3/internal/rpc/relation"
"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/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" "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/common/storage/database/mgo"
tablerelation "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" tablerelation "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/common/webhook"
"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/localcache"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/group" "github.com/openimsdk/protocol/group"
friendpb "github.com/openimsdk/protocol/relation" friendpb "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/tools/db/redisutil"
"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"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
pbuser "github.com/openimsdk/protocol/user" pbuser "github.com/openimsdk/protocol/user"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination" "github.com/openimsdk/tools/db/pagination"
registry "github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -57,7 +55,7 @@ type userServer struct {
db controller.UserDatabase db controller.UserDatabase
friendNotificationSender *relation.FriendNotificationSender friendNotificationSender *relation.FriendNotificationSender
userNotificationSender *UserNotificationSender userNotificationSender *UserNotificationSender
RegisterCenter registry.SvcDiscoveryRegistry RegisterCenter discovery.Conn
config *Config config *Config
webhookClient *webhook.Client webhookClient *webhook.Client
groupClient *rpcli.GroupClient groupClient *rpcli.GroupClient
@@ -76,15 +74,17 @@ type Config struct {
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client registry.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.Conn, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
users := make([]*tablerelation.User, 0) users := make([]*tablerelation.User, 0)
for _, v := range config.Share.IMAdminUserID { for _, v := range config.Share.IMAdminUserID {
@@ -566,7 +566,7 @@ func (s *userServer) SearchNotificationAccount(ctx context.Context, req *pbuser.
} }
// Convert users to response format // Convert users to response format
resp := s.userModelToResp(users, req.Pagination) resp := s.userModelToResp(users, req.Pagination, req.AppManagerLevel)
if resp.Total != 0 { if resp.Total != 0 {
return resp, nil return resp, nil
} }
@@ -576,17 +576,24 @@ func (s *userServer) SearchNotificationAccount(ctx context.Context, req *pbuser.
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp = s.userModelToResp(users, req.Pagination) resp = s.userModelToResp(users, req.Pagination, req.AppManagerLevel)
return resp, nil return resp, nil
} }
// If no keyword, find users with notification settings // If no keyword, find users with notification settings
users, err = s.db.FindNotification(ctx, constant.AppNotificationAdmin) if req.AppManagerLevel != nil {
if err != nil { users, err = s.db.FindNotification(ctx, int64(*req.AppManagerLevel))
return nil, err if err != nil {
return nil, err
}
} else {
users, err = s.db.FindSystemAccount(ctx)
if err != nil {
return nil, err
}
} }
resp := s.userModelToResp(users, req.Pagination) resp := s.userModelToResp(users, req.Pagination, req.AppManagerLevel)
return resp, nil return resp, nil
} }
@@ -625,11 +632,16 @@ func (s *userServer) genUserID() string {
return string(data) return string(data)
} }
func (s *userServer) userModelToResp(users []*tablerelation.User, pagination pagination.Pagination) *pbuser.SearchNotificationAccountResp { func (s *userServer) userModelToResp(users []*tablerelation.User, pagination pagination.Pagination, appManagerLevel *int32) *pbuser.SearchNotificationAccountResp {
accounts := make([]*pbuser.NotificationAccountInfo, 0) accounts := make([]*pbuser.NotificationAccountInfo, 0)
var total int64 var total int64
for _, v := range users { for _, v := range users {
if v.AppMangerLevel >= constant.AppNotificationAdmin && !datautil.Contain(v.UserID, s.config.Share.IMAdminUserID...) { if v.AppMangerLevel >= constant.AppNotificationAdmin && !datautil.Contain(v.UserID, s.config.Share.IMAdminUserID...) {
if appManagerLevel != nil {
if v.AppMangerLevel != *appManagerLevel {
continue
}
}
temp := &pbuser.NotificationAccountInfo{ temp := &pbuser.NotificationAccountInfo{
UserID: v.UserID, UserID: v.UserID,
FaceURL: v.FaceURL, FaceURL: v.FaceURL,
@@ -1,47 +1,37 @@
package tools package cron
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "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" disetcd "github.com/openimsdk/open-im-server/v3/pkg/common/discovery/etcd"
pbconversation "github.com/openimsdk/protocol/conversation" pbconversation "github.com/openimsdk/protocol/conversation"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/protocol/third" "github.com/openimsdk/protocol/third"
"github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/discovery/etcd" "github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/mw"
"github.com/openimsdk/tools/utils/runtimeenv"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/runtimeenv"
"github.com/robfig/cron/v3" "github.com/robfig/cron/v3"
"google.golang.org/grpc"
) )
type CronTaskConfig struct { type Config struct {
CronTask config.CronTask CronTask config.CronTask
Share config.Share Share config.Share
Discovery config.Discovery Discovery config.Discovery
runTimeEnv string
} }
func Start(ctx context.Context, conf *CronTaskConfig) error { func Start(ctx context.Context, conf *Config, client discovery.Conn, service grpc.ServiceRegistrar) error {
conf.runTimeEnv = runtimeenv.PrintRuntimeEnvironment() log.CInfo(ctx, "CRON-TASK server is initializing", "runTimeEnv", runtimeenv.RuntimeEnvironment(), "chatRecordsClearTime", conf.CronTask.CronExecuteTime, "msgDestructTime", conf.CronTask.RetainChatRecords)
log.CInfo(ctx, "CRON-TASK server is initializing", "runTimeEnv", conf.runTimeEnv, "chatRecordsClearTime", conf.CronTask.CronExecuteTime, "msgDestructTime", conf.CronTask.RetainChatRecords)
if conf.CronTask.RetainChatRecords < 1 { if conf.CronTask.RetainChatRecords < 1 {
return errs.New("msg destruct time must be greater than 1").Wrap() log.ZInfo(ctx, "disable cron")
<-ctx.Done()
return nil
} }
client, err := kdisc.NewDiscoveryRegister(&conf.Discovery, conf.runTimeEnv, nil)
if err != nil {
return errs.WrapMsg(err, "failed to register discovery service")
}
client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()))
ctx = mcontext.SetOpUserID(ctx, conf.Share.IMAdminUserID[0]) ctx = mcontext.SetOpUserID(ctx, conf.Share.IMAdminUserID[0])
msgConn, err := client.GetConn(ctx, conf.Discovery.RpcService.Msg) msgConn, err := client.GetConn(ctx, conf.Discovery.RpcService.Msg)
@@ -88,13 +78,15 @@ func Start(ctx context.Context, conf *CronTaskConfig) error {
} }
log.ZDebug(ctx, "start cron task", "CronExecuteTime", conf.CronTask.CronExecuteTime) log.ZDebug(ctx, "start cron task", "CronExecuteTime", conf.CronTask.CronExecuteTime)
srv.cron.Start() srv.cron.Start()
log.ZDebug(ctx, "cron task server is running")
<-ctx.Done() <-ctx.Done()
log.ZDebug(ctx, "cron task server is shutting down")
return nil return nil
} }
type cronServer struct { type cronServer struct {
ctx context.Context ctx context.Context
config *CronTaskConfig config *Config
cron *cron.Cron cron *cron.Cron
msgClient msg.MsgClient msgClient msg.MsgClient
conversationClient pbconversation.ConversationClient conversationClient pbconversation.ConversationClient
@@ -1,4 +1,4 @@
package tools package cron
import ( import (
"context" "context"
@@ -24,7 +24,7 @@ func TestName(t *testing.T) {
Address: []string{"localhost:12379"}, Address: []string{"localhost:12379"},
}, },
} }
client, err := kdisc.NewDiscoveryRegister(conf, "source") client, err := kdisc.NewDiscoveryRegister(conf, nil)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@@ -1,12 +1,13 @@
package tools package cron
import ( import (
"fmt" "fmt"
"os"
"time"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"os"
"time"
) )
func (c *cronServer) deleteMsg() { func (c *cronServer) deleteMsg() {
@@ -1,12 +1,13 @@
package tools package cron
import ( import (
"fmt" "fmt"
"os"
"time"
"github.com/openimsdk/protocol/third" "github.com/openimsdk/protocol/third"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"os"
"time"
) )
func (c *cronServer) clearS3() { func (c *cronServer) clearS3() {
@@ -1,12 +1,13 @@
package tools package cron
import ( import (
"fmt" "fmt"
"os"
"time"
pbconversation "github.com/openimsdk/protocol/conversation" pbconversation "github.com/openimsdk/protocol/conversation"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"os"
"time"
) )
func (c *cronServer) clearUserMsg() { func (c *cronServer) clearUserMsg() {
+27
View File
@@ -15,6 +15,7 @@
package apistruct package apistruct
import ( import (
pbmsg "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
) )
@@ -111,6 +112,21 @@ type BatchSendMsgResp struct {
FailedIDs []string `json:"failedUserIDs"` FailedIDs []string `json:"failedUserIDs"`
} }
// SendSingleMsgReq defines the structure for sending a message to multiple recipients.
type SendSingleMsgReq struct {
// groupMsg should appoint sendID
SendID string `json:"sendID"`
Content string `json:"content" binding:"required"`
OfflinePushInfo *sdkws.OfflinePushInfo `json:"offlinePushInfo"`
Ex string `json:"ex"`
}
type KeyMsgData struct {
SendID string `json:"sendID"`
RecvID string `json:"recvID"`
GroupID string `json:"groupID"`
}
// SingleReturnResult encapsulates the result of a single message send attempt. // SingleReturnResult encapsulates the result of a single message send attempt.
type SingleReturnResult struct { type SingleReturnResult struct {
// ServerMsgID is the message identifier on the server-side. // ServerMsgID is the message identifier on the server-side.
@@ -124,4 +140,15 @@ type SingleReturnResult struct {
// RecvID uniquely identifies the receiver of the message. // RecvID uniquely identifies the receiver of the message.
RecvID string `json:"recvID"` RecvID string `json:"recvID"`
// Modify fields modified via webhook.
Modify map[string]any `json:"modify,omitempty"`
}
type SendMsgResp struct {
// SendMsgResp original response.
*pbmsg.SendMsgResp
// Modify fields modified via webhook.
Modify map[string]any `json:"modify,omitempty"`
} }
+4
View File
@@ -83,6 +83,10 @@ type TextElem struct {
Content string `json:"content" validate:"required"` Content string `json:"content" validate:"required"`
} }
type MarkdownTextElem struct {
Content string `mapstructure:"content" validate:"required"`
}
type StreamMsgElem struct { type StreamMsgElem struct {
Type string `mapstructure:"type" validate:"required"` Type string `mapstructure:"type" validate:"required"`
Content string `mapstructure:"content" validate:"required"` Content string `mapstructure:"content" validate:"required"`
-1
View File
@@ -1 +0,0 @@
package apistruct
+5
View File
@@ -20,6 +20,7 @@ import (
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/protocol/constant"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
) )
@@ -55,3 +56,7 @@ func CheckAdmin(ctx context.Context, imAdminUserID []string) error {
func IsManagerUserID(opUserID string, imAdminUserID []string) bool { func IsManagerUserID(opUserID string, imAdminUserID []string) bool {
return datautil.Contain(opUserID, imAdminUserID...) return datautil.Contain(opUserID, imAdminUserID...)
} }
func CheckSystemAccount(ctx context.Context, level int32) bool {
return level >= constant.AppAdmin
}
+21 -3
View File
@@ -19,6 +19,7 @@ import (
"github.com/openimsdk/open-im-server/v3/internal/api" "github.com/openimsdk/open-im-server/v3/internal/api"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
"github.com/openimsdk/open-im-server/v3/version" "github.com/openimsdk/open-im-server/v3/version"
"github.com/openimsdk/tools/system/program" "github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -32,7 +33,7 @@ type ApiCmd struct {
} }
func NewApiCmd() *ApiCmd { func NewApiCmd() *ApiCmd {
apiConfig := api.Config{AllConfig: &config.AllConfig{}} var apiConfig api.Config
ret := &ApiCmd{apiConfig: &apiConfig} ret := &ApiCmd{apiConfig: &apiConfig}
ret.configMap = map[string]any{ ret.configMap = map[string]any{
config.DiscoveryConfigFilename: &apiConfig.Discovery, config.DiscoveryConfigFilename: &apiConfig.Discovery,
@@ -61,7 +62,7 @@ func NewApiCmd() *ApiCmd {
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version) ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error { ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
apiConfig.ConfigPath = ret.configPath apiConfig.ConfigPath = config.Path(ret.configPath)
return ret.runE() return ret.runE()
} }
return ret return ret
@@ -72,5 +73,22 @@ func (a *ApiCmd) Exec() error {
} }
func (a *ApiCmd) runE() error { func (a *ApiCmd) runE() error {
return api.Start(a.ctx, a.Index(), a.apiConfig) a.apiConfig.Index = config.Index(a.Index())
prometheus := config.Prometheus{
Enable: a.apiConfig.API.Prometheus.Enable,
Ports: a.apiConfig.API.Prometheus.Ports,
}
return startrpc.Start(
a.ctx, &a.apiConfig.Discovery,
&prometheus,
a.apiConfig.API.Api.ListenIP, "",
a.apiConfig.API.Prometheus.AutoSetPorts,
nil, int(a.apiConfig.Index),
a.apiConfig.Discovery.RpcService.MessageGateway,
&a.apiConfig.Notification,
a.apiConfig,
[]string{},
[]string{},
api.Start,
)
} }
+1
View File
@@ -38,6 +38,7 @@ func NewAuthRpcCmd() *AuthRpcCmd {
ret.configMap = map[string]any{ ret.configMap = map[string]any{
config.OpenIMRPCAuthCfgFileName: &authConfig.RpcConfig, config.OpenIMRPCAuthCfgFileName: &authConfig.RpcConfig,
config.RedisConfigFileName: &authConfig.RedisConfig, config.RedisConfigFileName: &authConfig.RedisConfig,
config.MongodbConfigFileName: &authConfig.MongoConfig,
config.ShareFileName: &authConfig.Share, config.ShareFileName: &authConfig.Share,
config.DiscoveryConfigFilename: &authConfig.Discovery, config.DiscoveryConfigFilename: &authConfig.Discovery,
} }
+18 -4
View File
@@ -17,8 +17,9 @@ package cmd
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/internal/tools" "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/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
"github.com/openimsdk/open-im-server/v3/version" "github.com/openimsdk/open-im-server/v3/version"
"github.com/openimsdk/tools/system/program" "github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -28,11 +29,11 @@ type CronTaskCmd struct {
*RootCmd *RootCmd
ctx context.Context ctx context.Context
configMap map[string]any configMap map[string]any
cronTaskConfig *tools.CronTaskConfig cronTaskConfig *cron.Config
} }
func NewCronTaskCmd() *CronTaskCmd { func NewCronTaskCmd() *CronTaskCmd {
var cronTaskConfig tools.CronTaskConfig var cronTaskConfig cron.Config
ret := &CronTaskCmd{cronTaskConfig: &cronTaskConfig} ret := &CronTaskCmd{cronTaskConfig: &cronTaskConfig}
ret.configMap = map[string]any{ ret.configMap = map[string]any{
config.OpenIMCronTaskCfgFileName: &cronTaskConfig.CronTask, config.OpenIMCronTaskCfgFileName: &cronTaskConfig.CronTask,
@@ -52,5 +53,18 @@ func (a *CronTaskCmd) Exec() error {
} }
func (a *CronTaskCmd) runE() error { func (a *CronTaskCmd) runE() error {
return tools.Start(a.ctx, a.cronTaskConfig) var prometheus config.Prometheus
return startrpc.Start(
a.ctx, &a.cronTaskConfig.Discovery,
&prometheus,
"", "",
true,
nil, 0,
"",
nil,
a.cronTaskConfig,
[]string{},
[]string{},
cron.Start,
)
} }
+17 -1
View File
@@ -19,6 +19,7 @@ import (
"github.com/openimsdk/open-im-server/v3/internal/msggateway" "github.com/openimsdk/open-im-server/v3/internal/msggateway"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
"github.com/openimsdk/open-im-server/v3/version" "github.com/openimsdk/open-im-server/v3/version"
"github.com/openimsdk/tools/system/program" "github.com/openimsdk/tools/system/program"
@@ -55,5 +56,20 @@ func (m *MsgGatewayCmd) Exec() error {
} }
func (m *MsgGatewayCmd) runE() error { func (m *MsgGatewayCmd) runE() error {
return msggateway.Start(m.ctx, m.Index(), m.msgGatewayConfig) m.msgGatewayConfig.Index = config.Index(m.Index())
rpc := m.msgGatewayConfig.MsgGateway.RPC
var prometheus config.Prometheus
return startrpc.Start(
m.ctx, &m.msgGatewayConfig.Discovery,
&prometheus,
rpc.ListenIP, rpc.RegisterIP,
rpc.AutoSetPorts,
rpc.Ports, int(m.msgGatewayConfig.Index),
m.msgGatewayConfig.Discovery.RpcService.MessageGateway,
nil,
m.msgGatewayConfig,
[]string{},
[]string{},
msggateway.Start,
)
} }
+16 -1
View File
@@ -19,6 +19,7 @@ import (
"github.com/openimsdk/open-im-server/v3/internal/msgtransfer" "github.com/openimsdk/open-im-server/v3/internal/msgtransfer"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
"github.com/openimsdk/open-im-server/v3/version" "github.com/openimsdk/open-im-server/v3/version"
"github.com/openimsdk/tools/system/program" "github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@@ -56,5 +57,19 @@ func (m *MsgTransferCmd) Exec() error {
} }
func (m *MsgTransferCmd) runE() error { func (m *MsgTransferCmd) runE() error {
return msgtransfer.Start(m.ctx, m.Index(), m.msgTransferConfig) m.msgTransferConfig.Index = config.Index(m.Index())
var prometheus config.Prometheus
return startrpc.Start(
m.ctx, &m.msgTransferConfig.Discovery,
&prometheus,
"", "",
true,
nil, int(m.msgTransferConfig.Index),
"",
nil,
m.msgTransferConfig,
[]string{},
[]string{},
msgtransfer.Start,
)
} }
+2 -1
View File
@@ -38,6 +38,7 @@ func NewPushRpcCmd() *PushRpcCmd {
ret.configMap = map[string]any{ ret.configMap = map[string]any{
config.OpenIMPushCfgFileName: &pushConfig.RpcConfig, config.OpenIMPushCfgFileName: &pushConfig.RpcConfig,
config.RedisConfigFileName: &pushConfig.RedisConfig, config.RedisConfigFileName: &pushConfig.RedisConfig,
config.MongodbConfigFileName: &pushConfig.MongoConfig,
config.KafkaConfigFileName: &pushConfig.KafkaConfig, config.KafkaConfigFileName: &pushConfig.KafkaConfig,
config.ShareFileName: &pushConfig.Share, config.ShareFileName: &pushConfig.Share,
config.NotificationFileName: &pushConfig.NotificationConfig, config.NotificationFileName: &pushConfig.NotificationConfig,
@@ -48,7 +49,7 @@ func NewPushRpcCmd() *PushRpcCmd {
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version) ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error { ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
ret.pushConfig.FcmConfigPath = ret.ConfigPath() ret.pushConfig.FcmConfigPath = config.Path(ret.ConfigPath())
return ret.runE() return ret.runE()
} }
return ret return ret
+4 -10
View File
@@ -12,7 +12,6 @@ import (
"github.com/openimsdk/tools/discovery/etcd" "github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/runtimeenv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
@@ -86,14 +85,12 @@ func (r *RootCmd) initEtcd() error {
return err return err
} }
disConfig := config.Discovery{} disConfig := config.Discovery{}
env := runtimeenv.PrintRuntimeEnvironment() err = config.Load(configDirectory, config.DiscoveryConfigFilename, config.EnvPrefixMap[config.DiscoveryConfigFilename], &disConfig)
err = config.Load(configDirectory, config.DiscoveryConfigFilename, config.EnvPrefixMap[config.DiscoveryConfigFilename],
env, &disConfig)
if err != nil { if err != nil {
return err return err
} }
if disConfig.Enable == config.ETCD { if disConfig.Enable == config.ETCD {
discov, _ := kdisc.NewDiscoveryRegister(&disConfig, env, nil) discov, _ := kdisc.NewDiscoveryRegister(&disConfig, nil)
r.etcdClient = discov.(*etcd.SvcDiscoveryRegistryImpl).GetClient() r.etcdClient = discov.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
} }
return nil return nil
@@ -125,18 +122,16 @@ func (r *RootCmd) initializeConfiguration(cmd *cobra.Command, opts *CmdOpts) err
return err return err
} }
runtimeEnv := runtimeenv.PrintRuntimeEnvironment()
// Load common configuration file // Load common configuration file
//opts.configMap[ShareFileName] = StructEnvPrefix{EnvPrefix: shareEnvPrefix, ConfigStruct: &r.share} //opts.configMap[ShareFileName] = StructEnvPrefix{EnvPrefix: shareEnvPrefix, ConfigStruct: &r.share}
for configFileName, configStruct := range opts.configMap { for configFileName, configStruct := range opts.configMap {
err := config.Load(configDirectory, configFileName, config.EnvPrefixMap[configFileName], runtimeEnv, configStruct) err := config.Load(configDirectory, configFileName, config.EnvPrefixMap[configFileName], configStruct)
if err != nil { if err != nil {
return err return err
} }
} }
// Load common log configuration file // Load common log configuration file
return config.Load(configDirectory, config.LogConfigFileName, config.EnvPrefixMap[config.LogConfigFileName], runtimeEnv, &r.log) return config.Load(configDirectory, config.LogConfigFileName, config.EnvPrefixMap[config.LogConfigFileName], &r.log)
} }
func (r *RootCmd) updateConfigFromEtcd(opts *CmdOpts) error { func (r *RootCmd) updateConfigFromEtcd(opts *CmdOpts) error {
@@ -208,7 +203,6 @@ func (r *RootCmd) applyOptions(opts ...func(*CmdOpts)) *CmdOpts {
func (r *RootCmd) initializeLogger(cmdOpts *CmdOpts) error { func (r *RootCmd) initializeLogger(cmdOpts *CmdOpts) error {
err := log.InitLoggerFromConfig( err := log.InitLoggerFromConfig(
cmdOpts.loggerPrefixName, cmdOpts.loggerPrefixName,
r.processName, r.processName,
"", "", "", "",
+297 -329
View File
@@ -28,378 +28,346 @@ import (
"github.com/openimsdk/tools/s3/oss" "github.com/openimsdk/tools/s3/oss"
) )
const StructTagName = "yaml"
type Path string
type Index int
type CacheConfig struct { type CacheConfig struct {
Topic string `mapstructure:"topic"` Topic string `yaml:"topic"`
SlotNum int `mapstructure:"slotNum"` SlotNum int `yaml:"slotNum"`
SlotSize int `mapstructure:"slotSize"` SlotSize int `yaml:"slotSize"`
SuccessExpire int `mapstructure:"successExpire"` SuccessExpire int `yaml:"successExpire"`
FailedExpire int `mapstructure:"failedExpire"` FailedExpire int `yaml:"failedExpire"`
} }
type LocalCache struct { type LocalCache struct {
User CacheConfig `mapstructure:"user"` User CacheConfig `yaml:"user"`
Group CacheConfig `mapstructure:"group"` Group CacheConfig `yaml:"group"`
Friend CacheConfig `mapstructure:"friend"` Friend CacheConfig `yaml:"friend"`
Conversation CacheConfig `mapstructure:"conversation"` Conversation CacheConfig `yaml:"conversation"`
} }
type Log struct { type Log struct {
StorageLocation string `mapstructure:"storageLocation"` StorageLocation string `yaml:"storageLocation"`
RotationTime uint `mapstructure:"rotationTime"` RotationTime uint `yaml:"rotationTime"`
RemainRotationCount uint `mapstructure:"remainRotationCount"` RemainRotationCount uint `yaml:"remainRotationCount"`
RemainLogLevel int `mapstructure:"remainLogLevel"` RemainLogLevel int `yaml:"remainLogLevel"`
IsStdout bool `mapstructure:"isStdout"` IsStdout bool `yaml:"isStdout"`
IsJson bool `mapstructure:"isJson"` IsJson bool `yaml:"isJson"`
IsSimplify bool `mapstructure:"isSimplify"` IsSimplify bool `yaml:"isSimplify"`
WithStack bool `mapstructure:"withStack"` WithStack bool `yaml:"withStack"`
} }
type Minio struct { type Minio struct {
Bucket string `mapstructure:"bucket"` Bucket string `yaml:"bucket"`
AccessKeyID string `mapstructure:"accessKeyID"` AccessKeyID string `yaml:"accessKeyID"`
SecretAccessKey string `mapstructure:"secretAccessKey"` SecretAccessKey string `yaml:"secretAccessKey"`
SessionToken string `mapstructure:"sessionToken"` SessionToken string `yaml:"sessionToken"`
InternalAddress string `mapstructure:"internalAddress"` InternalAddress string `yaml:"internalAddress"`
ExternalAddress string `mapstructure:"externalAddress"` ExternalAddress string `yaml:"externalAddress"`
PublicRead bool `mapstructure:"publicRead"` PublicRead bool `yaml:"publicRead"`
} }
type Mongo struct { type Mongo struct {
URI string `mapstructure:"uri"` URI string `yaml:"uri"`
Address []string `mapstructure:"address"` Address []string `yaml:"address"`
Database string `mapstructure:"database"` Database string `yaml:"database"`
Username string `mapstructure:"username"` Username string `yaml:"username"`
Password string `mapstructure:"password"` Password string `yaml:"password"`
AuthSource string `mapstructure:"authSource"` AuthSource string `yaml:"authSource"`
MaxPoolSize int `mapstructure:"maxPoolSize"` MaxPoolSize int `yaml:"maxPoolSize"`
MaxRetry int `mapstructure:"maxRetry"` MaxRetry int `yaml:"maxRetry"`
} }
type Kafka struct { type Kafka struct {
Username string `mapstructure:"username"` Username string `yaml:"username"`
Password string `mapstructure:"password"` Password string `yaml:"password"`
ProducerAck string `mapstructure:"producerAck"` ProducerAck string `yaml:"producerAck"`
CompressType string `mapstructure:"compressType"` CompressType string `yaml:"compressType"`
Address []string `mapstructure:"address"` Address []string `yaml:"address"`
ToRedisTopic string `mapstructure:"toRedisTopic"` ToRedisTopic string `yaml:"toRedisTopic"`
ToMongoTopic string `mapstructure:"toMongoTopic"` ToMongoTopic string `yaml:"toMongoTopic"`
ToPushTopic string `mapstructure:"toPushTopic"` ToPushTopic string `yaml:"toPushTopic"`
ToOfflinePushTopic string `mapstructure:"toOfflinePushTopic"` ToOfflinePushTopic string `yaml:"toOfflinePushTopic"`
ToRedisGroupID string `mapstructure:"toRedisGroupID"` ToRedisGroupID string `yaml:"toRedisGroupID"`
ToMongoGroupID string `mapstructure:"toMongoGroupID"` ToMongoGroupID string `yaml:"toMongoGroupID"`
ToPushGroupID string `mapstructure:"toPushGroupID"` ToPushGroupID string `yaml:"toPushGroupID"`
ToOfflineGroupID string `mapstructure:"toOfflinePushGroupID"` ToOfflineGroupID string `yaml:"toOfflinePushGroupID"`
Tls TLSConfig `mapstructure:"tls"` Tls TLSConfig `yaml:"tls"`
} }
type TLSConfig struct { type TLSConfig struct {
EnableTLS bool `mapstructure:"enableTLS"` EnableTLS bool `yaml:"enableTLS"`
CACrt string `mapstructure:"caCrt"` CACrt string `yaml:"caCrt"`
ClientCrt string `mapstructure:"clientCrt"` ClientCrt string `yaml:"clientCrt"`
ClientKey string `mapstructure:"clientKey"` ClientKey string `yaml:"clientKey"`
ClientKeyPwd string `mapstructure:"clientKeyPwd"` ClientKeyPwd string `yaml:"clientKeyPwd"`
InsecureSkipVerify bool `mapstructure:"insecureSkipVerify"` InsecureSkipVerify bool `yaml:"insecureSkipVerify"`
} }
type API struct { type API struct {
Api struct { Api struct {
ListenIP string `mapstructure:"listenIP"` ListenIP string `yaml:"listenIP"`
Ports []int `mapstructure:"ports"` Ports []int `yaml:"ports"`
CompressionLevel int `mapstructure:"compressionLevel"` CompressionLevel int `yaml:"compressionLevel"`
} `mapstructure:"api"` } `yaml:"api"`
Prometheus struct { Prometheus struct {
Enable bool `mapstructure:"enable"` Enable bool `yaml:"enable"`
AutoSetPorts bool `mapstructure:"autoSetPorts"` AutoSetPorts bool `yaml:"autoSetPorts"`
Ports []int `mapstructure:"ports"` Ports []int `yaml:"ports"`
GrafanaURL string `mapstructure:"grafanaURL"` GrafanaURL string `yaml:"grafanaURL"`
} `mapstructure:"prometheus"` } `yaml:"prometheus"`
} }
type CronTask struct { type CronTask struct {
CronExecuteTime string `mapstructure:"cronExecuteTime"` CronExecuteTime string `yaml:"cronExecuteTime"`
RetainChatRecords int `mapstructure:"retainChatRecords"` RetainChatRecords int `yaml:"retainChatRecords"`
FileExpireTime int `mapstructure:"fileExpireTime"` FileExpireTime int `yaml:"fileExpireTime"`
DeleteObjectType []string `mapstructure:"deleteObjectType"` DeleteObjectType []string `yaml:"deleteObjectType"`
} }
type OfflinePushConfig struct { type OfflinePushConfig struct {
Enable bool `mapstructure:"enable"` Enable bool `yaml:"enable"`
Title string `mapstructure:"title"` Title string `yaml:"title"`
Desc string `mapstructure:"desc"` Desc string `yaml:"desc"`
Ext string `mapstructure:"ext"` Ext string `yaml:"ext"`
} }
type NotificationConfig struct { type NotificationConfig struct {
IsSendMsg bool `mapstructure:"isSendMsg"` IsSendMsg bool `yaml:"isSendMsg"`
ReliabilityLevel int `mapstructure:"reliabilityLevel"` ReliabilityLevel int `yaml:"reliabilityLevel"`
UnreadCount bool `mapstructure:"unreadCount"` UnreadCount bool `yaml:"unreadCount"`
OfflinePush OfflinePushConfig `mapstructure:"offlinePush"` OfflinePush OfflinePushConfig `yaml:"offlinePush"`
} }
type Notification struct { type Notification struct {
GroupCreated NotificationConfig `mapstructure:"groupCreated"` GroupCreated NotificationConfig `yaml:"groupCreated"`
GroupInfoSet NotificationConfig `mapstructure:"groupInfoSet"` GroupInfoSet NotificationConfig `yaml:"groupInfoSet"`
JoinGroupApplication NotificationConfig `mapstructure:"joinGroupApplication"` JoinGroupApplication NotificationConfig `yaml:"joinGroupApplication"`
MemberQuit NotificationConfig `mapstructure:"memberQuit"` MemberQuit NotificationConfig `yaml:"memberQuit"`
GroupApplicationAccepted NotificationConfig `mapstructure:"groupApplicationAccepted"` GroupApplicationAccepted NotificationConfig `yaml:"groupApplicationAccepted"`
GroupApplicationRejected NotificationConfig `mapstructure:"groupApplicationRejected"` GroupApplicationRejected NotificationConfig `yaml:"groupApplicationRejected"`
GroupOwnerTransferred NotificationConfig `mapstructure:"groupOwnerTransferred"` GroupOwnerTransferred NotificationConfig `yaml:"groupOwnerTransferred"`
MemberKicked NotificationConfig `mapstructure:"memberKicked"` MemberKicked NotificationConfig `yaml:"memberKicked"`
MemberInvited NotificationConfig `mapstructure:"memberInvited"` MemberInvited NotificationConfig `yaml:"memberInvited"`
MemberEnter NotificationConfig `mapstructure:"memberEnter"` MemberEnter NotificationConfig `yaml:"memberEnter"`
GroupDismissed NotificationConfig `mapstructure:"groupDismissed"` GroupDismissed NotificationConfig `yaml:"groupDismissed"`
GroupMuted NotificationConfig `mapstructure:"groupMuted"` GroupMuted NotificationConfig `yaml:"groupMuted"`
GroupCancelMuted NotificationConfig `mapstructure:"groupCancelMuted"` GroupCancelMuted NotificationConfig `yaml:"groupCancelMuted"`
GroupMemberMuted NotificationConfig `mapstructure:"groupMemberMuted"` GroupMemberMuted NotificationConfig `yaml:"groupMemberMuted"`
GroupMemberCancelMuted NotificationConfig `mapstructure:"groupMemberCancelMuted"` GroupMemberCancelMuted NotificationConfig `yaml:"groupMemberCancelMuted"`
GroupMemberInfoSet NotificationConfig `mapstructure:"groupMemberInfoSet"` GroupMemberInfoSet NotificationConfig `yaml:"groupMemberInfoSet"`
GroupMemberSetToAdmin NotificationConfig `yaml:"groupMemberSetToAdmin"` GroupMemberSetToAdmin NotificationConfig `yaml:"groupMemberSetToAdmin"`
GroupMemberSetToOrdinary NotificationConfig `yaml:"groupMemberSetToOrdinaryUser"` GroupMemberSetToOrdinary NotificationConfig `yaml:"groupMemberSetToOrdinaryUser"`
GroupInfoSetAnnouncement NotificationConfig `mapstructure:"groupInfoSetAnnouncement"` GroupInfoSetAnnouncement NotificationConfig `yaml:"groupInfoSetAnnouncement"`
GroupInfoSetName NotificationConfig `mapstructure:"groupInfoSetName"` GroupInfoSetName NotificationConfig `yaml:"groupInfoSetName"`
FriendApplicationAdded NotificationConfig `mapstructure:"friendApplicationAdded"` FriendApplicationAdded NotificationConfig `yaml:"friendApplicationAdded"`
FriendApplicationApproved NotificationConfig `mapstructure:"friendApplicationApproved"` FriendApplicationApproved NotificationConfig `yaml:"friendApplicationApproved"`
FriendApplicationRejected NotificationConfig `mapstructure:"friendApplicationRejected"` FriendApplicationRejected NotificationConfig `yaml:"friendApplicationRejected"`
FriendAdded NotificationConfig `mapstructure:"friendAdded"` FriendAdded NotificationConfig `yaml:"friendAdded"`
FriendDeleted NotificationConfig `mapstructure:"friendDeleted"` FriendDeleted NotificationConfig `yaml:"friendDeleted"`
FriendRemarkSet NotificationConfig `mapstructure:"friendRemarkSet"` FriendRemarkSet NotificationConfig `yaml:"friendRemarkSet"`
BlackAdded NotificationConfig `mapstructure:"blackAdded"` BlackAdded NotificationConfig `yaml:"blackAdded"`
BlackDeleted NotificationConfig `mapstructure:"blackDeleted"` BlackDeleted NotificationConfig `yaml:"blackDeleted"`
FriendInfoUpdated NotificationConfig `mapstructure:"friendInfoUpdated"` FriendInfoUpdated NotificationConfig `yaml:"friendInfoUpdated"`
UserInfoUpdated NotificationConfig `mapstructure:"userInfoUpdated"` UserInfoUpdated NotificationConfig `yaml:"userInfoUpdated"`
UserStatusChanged NotificationConfig `mapstructure:"userStatusChanged"` UserStatusChanged NotificationConfig `yaml:"userStatusChanged"`
ConversationChanged NotificationConfig `mapstructure:"conversationChanged"` ConversationChanged NotificationConfig `yaml:"conversationChanged"`
ConversationSetPrivate NotificationConfig `mapstructure:"conversationSetPrivate"` ConversationSetPrivate NotificationConfig `yaml:"conversationSetPrivate"`
} }
type Prometheus struct { type Prometheus struct {
Enable bool `mapstructure:"enable"` Enable bool `yaml:"enable"`
Ports []int `mapstructure:"ports"` Ports []int `yaml:"ports"`
} }
type MsgGateway struct { type MsgGateway struct {
RPC struct { RPC RPC `yaml:"rpc"`
RegisterIP string `mapstructure:"registerIP"` Prometheus Prometheus `yaml:"prometheus"`
AutoSetPorts bool `mapstructure:"autoSetPorts"` ListenIP string `yaml:"listenIP"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
Prometheus Prometheus `mapstructure:"prometheus"`
ListenIP string `mapstructure:"listenIP"`
LongConnSvr struct { LongConnSvr struct {
Ports []int `mapstructure:"ports"` Ports []int `yaml:"ports"`
WebsocketMaxConnNum int `mapstructure:"websocketMaxConnNum"` WebsocketMaxConnNum int `yaml:"websocketMaxConnNum"`
WebsocketMaxMsgLen int `mapstructure:"websocketMaxMsgLen"` WebsocketMaxMsgLen int `yaml:"websocketMaxMsgLen"`
WebsocketTimeout int `mapstructure:"websocketTimeout"` WebsocketTimeout int `yaml:"websocketTimeout"`
} `mapstructure:"longConnSvr"` } `yaml:"longConnSvr"`
} }
type MsgTransfer struct { type MsgTransfer struct {
Prometheus struct { Prometheus struct {
Enable bool `mapstructure:"enable"` Enable bool `yaml:"enable"`
AutoSetPorts bool `mapstructure:"autoSetPorts"` AutoSetPorts bool `yaml:"autoSetPorts"`
Ports []int `mapstructure:"ports"` Ports []int `yaml:"ports"`
} `mapstructure:"prometheus"` } `yaml:"prometheus"`
} }
type Push struct { type Push struct {
RPC struct { RPC RPC `yaml:"rpc"`
RegisterIP string `mapstructure:"registerIP"` Prometheus Prometheus `yaml:"prometheus"`
ListenIP string `mapstructure:"listenIP"` MaxConcurrentWorkers int `yaml:"maxConcurrentWorkers"`
AutoSetPorts bool `mapstructure:"autoSetPorts"` Enable string `yaml:"enable"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
Prometheus Prometheus `mapstructure:"prometheus"`
MaxConcurrentWorkers int `mapstructure:"maxConcurrentWorkers"`
Enable string `mapstructure:"enable"`
GeTui struct { GeTui struct {
PushUrl string `mapstructure:"pushUrl"` PushUrl string `yaml:"pushUrl"`
MasterSecret string `mapstructure:"masterSecret"` MasterSecret string `yaml:"masterSecret"`
AppKey string `mapstructure:"appKey"` AppKey string `yaml:"appKey"`
Intent string `mapstructure:"intent"` Intent string `yaml:"intent"`
ChannelID string `mapstructure:"channelID"` ChannelID string `yaml:"channelID"`
ChannelName string `mapstructure:"channelName"` ChannelName string `yaml:"channelName"`
} `mapstructure:"geTui"` } `yaml:"geTui"`
FCM struct { FCM struct {
FilePath string `mapstructure:"filePath"` FilePath string `yaml:"filePath"`
AuthURL string `mapstructure:"authURL"` AuthURL string `yaml:"authURL"`
} `mapstructure:"fcm"` } `yaml:"fcm"`
JPush struct { JPush struct {
AppKey string `mapstructure:"appKey"` AppKey string `yaml:"appKey"`
MasterSecret string `mapstructure:"masterSecret"` MasterSecret string `yaml:"masterSecret"`
PushURL string `mapstructure:"pushURL"` PushURL string `yaml:"pushURL"`
PushIntent string `mapstructure:"pushIntent"` PushIntent string `yaml:"pushIntent"`
} `mapstructure:"jpush"` } `yaml:"jpush"`
IOSPush struct { IOSPush struct {
PushSound string `mapstructure:"pushSound"` PushSound string `yaml:"pushSound"`
BadgeCount bool `mapstructure:"badgeCount"` BadgeCount bool `yaml:"badgeCount"`
Production bool `mapstructure:"production"` Production bool `yaml:"production"`
} `mapstructure:"iosPush"` } `yaml:"iosPush"`
FullUserCache bool `mapstructure:"fullUserCache"` FullUserCache bool `yaml:"fullUserCache"`
} }
type Auth struct { type Auth struct {
RPC struct { RPC RPC `yaml:"rpc"`
RegisterIP string `mapstructure:"registerIP"` Prometheus Prometheus `yaml:"prometheus"`
ListenIP string `mapstructure:"listenIP"`
AutoSetPorts bool `mapstructure:"autoSetPorts"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
Prometheus Prometheus `mapstructure:"prometheus"`
TokenPolicy struct { TokenPolicy struct {
Expire int64 `mapstructure:"expire"` Expire int64 `yaml:"expire"`
} `mapstructure:"tokenPolicy"` } `yaml:"tokenPolicy"`
} }
type Conversation struct { type Conversation struct {
RPC struct { RPC RPC `yaml:"rpc"`
RegisterIP string `mapstructure:"registerIP"` Prometheus Prometheus `yaml:"prometheus"`
ListenIP string `mapstructure:"listenIP"`
AutoSetPorts bool `mapstructure:"autoSetPorts"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
Prometheus Prometheus `mapstructure:"prometheus"`
} }
type Friend struct { type Friend struct {
RPC struct { RPC RPC `yaml:"rpc"`
RegisterIP string `mapstructure:"registerIP"` Prometheus Prometheus `yaml:"prometheus"`
ListenIP string `mapstructure:"listenIP"`
AutoSetPorts bool `mapstructure:"autoSetPorts"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
Prometheus Prometheus `mapstructure:"prometheus"`
} }
type Group struct { type Group struct {
RPC struct { RPC RPC `yaml:"rpc"`
RegisterIP string `mapstructure:"registerIP"` Prometheus Prometheus `yaml:"prometheus"`
ListenIP string `mapstructure:"listenIP"` EnableHistoryForNewMembers bool `yaml:"enableHistoryForNewMembers"`
AutoSetPorts bool `mapstructure:"autoSetPorts"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
Prometheus Prometheus `mapstructure:"prometheus"`
EnableHistoryForNewMembers bool `mapstructure:"enableHistoryForNewMembers"`
} }
type Msg struct { type Msg struct {
RPC struct { RPC RPC `yaml:"rpc"`
RegisterIP string `mapstructure:"registerIP"` Prometheus Prometheus `yaml:"prometheus"`
ListenIP string `mapstructure:"listenIP"` FriendVerify bool `yaml:"friendVerify"`
AutoSetPorts bool `mapstructure:"autoSetPorts"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
Prometheus Prometheus `mapstructure:"prometheus"`
FriendVerify bool `mapstructure:"friendVerify"`
} }
type Third struct { type Third struct {
RPC struct { RPC RPC `yaml:"rpc"`
RegisterIP string `mapstructure:"registerIP"` Prometheus Prometheus `yaml:"prometheus"`
ListenIP string `mapstructure:"listenIP"`
AutoSetPorts bool `mapstructure:"autoSetPorts"`
Ports []int `mapstructure:"ports"`
} `mapstructure:"rpc"`
Prometheus Prometheus `mapstructure:"prometheus"`
Object struct { Object struct {
Enable string `mapstructure:"enable"` Enable string `yaml:"enable"`
Cos Cos `mapstructure:"cos"` Cos Cos `yaml:"cos"`
Oss Oss `mapstructure:"oss"` Oss Oss `yaml:"oss"`
Kodo Kodo `mapstructure:"kodo"` Kodo Kodo `yaml:"kodo"`
Aws Aws `mapstructure:"aws"` Aws Aws `yaml:"aws"`
} `mapstructure:"object"` } `yaml:"object"`
} }
type Cos struct { type Cos struct {
BucketURL string `mapstructure:"bucketURL"` BucketURL string `yaml:"bucketURL"`
SecretID string `mapstructure:"secretID"` SecretID string `yaml:"secretID"`
SecretKey string `mapstructure:"secretKey"` SecretKey string `yaml:"secretKey"`
SessionToken string `mapstructure:"sessionToken"` SessionToken string `yaml:"sessionToken"`
PublicRead bool `mapstructure:"publicRead"` PublicRead bool `yaml:"publicRead"`
} }
type Oss struct { type Oss struct {
Endpoint string `mapstructure:"endpoint"` Endpoint string `yaml:"endpoint"`
Bucket string `mapstructure:"bucket"` Bucket string `yaml:"bucket"`
BucketURL string `mapstructure:"bucketURL"` BucketURL string `yaml:"bucketURL"`
AccessKeyID string `mapstructure:"accessKeyID"` AccessKeyID string `yaml:"accessKeyID"`
AccessKeySecret string `mapstructure:"accessKeySecret"` AccessKeySecret string `yaml:"accessKeySecret"`
SessionToken string `mapstructure:"sessionToken"` SessionToken string `yaml:"sessionToken"`
PublicRead bool `mapstructure:"publicRead"` PublicRead bool `yaml:"publicRead"`
} }
type Kodo struct { type Kodo struct {
Endpoint string `mapstructure:"endpoint"` Endpoint string `yaml:"endpoint"`
Bucket string `mapstructure:"bucket"` Bucket string `yaml:"bucket"`
BucketURL string `mapstructure:"bucketURL"` BucketURL string `yaml:"bucketURL"`
AccessKeyID string `mapstructure:"accessKeyID"` AccessKeyID string `yaml:"accessKeyID"`
AccessKeySecret string `mapstructure:"accessKeySecret"` AccessKeySecret string `yaml:"accessKeySecret"`
SessionToken string `mapstructure:"sessionToken"` SessionToken string `yaml:"sessionToken"`
PublicRead bool `mapstructure:"publicRead"` PublicRead bool `yaml:"publicRead"`
} }
type Aws struct { type Aws struct {
Region string `mapstructure:"region"` Region string `yaml:"region"`
Bucket string `mapstructure:"bucket"` Bucket string `yaml:"bucket"`
AccessKeyID string `mapstructure:"accessKeyID"` AccessKeyID string `yaml:"accessKeyID"`
SecretAccessKey string `mapstructure:"secretAccessKey"` SecretAccessKey string `yaml:"secretAccessKey"`
SessionToken string `mapstructure:"sessionToken"` SessionToken string `yaml:"sessionToken"`
PublicRead bool `mapstructure:"publicRead"` PublicRead bool `yaml:"publicRead"`
} }
type User struct { type User struct {
RPC struct { RPC RPC `yaml:"rpc"`
RegisterIP string `mapstructure:"registerIP"` Prometheus Prometheus `yaml:"prometheus"`
ListenIP string `mapstructure:"listenIP"` }
AutoSetPorts bool `mapstructure:"autoSetPorts"`
Ports []int `mapstructure:"ports"` type RPC struct {
} `mapstructure:"rpc"` RegisterIP string `yaml:"registerIP"`
Prometheus Prometheus `mapstructure:"prometheus"` ListenIP string `yaml:"listenIP"`
AutoSetPorts bool `yaml:"autoSetPorts"`
Ports []int `yaml:"ports"`
} }
type Redis struct { type Redis struct {
Address []string `mapstructure:"address"` Disable bool `yaml:"-"`
Username string `mapstructure:"username"` Address []string `yaml:"address"`
Password string `mapstructure:"password"` Username string `yaml:"username"`
ClusterMode bool `mapstructure:"clusterMode"` Password string `yaml:"password"`
DB int `mapstructure:"storage"` ClusterMode bool `yaml:"clusterMode"`
MaxRetry int `mapstructure:"maxRetry"` DB int `yaml:"storage"`
PoolSize int `mapstructure:"poolSize"` MaxRetry int `yaml:"maxRetry"`
PoolSize int `yaml:"poolSize"`
} }
type BeforeConfig struct { type BeforeConfig struct {
Enable bool `mapstructure:"enable"` Enable bool `yaml:"enable"`
Timeout int `mapstructure:"timeout"` Timeout int `yaml:"timeout"`
FailedContinue bool `mapstructure:"failedContinue"` FailedContinue bool `yaml:"failedContinue"`
AllowedTypes []string `mapstructure:"allowedTypes"` DeniedTypes []int32 `yaml:"deniedTypes"`
DeniedTypes []string `mapstructure:"deniedTypes"`
} }
type AfterConfig struct { type AfterConfig struct {
Enable bool `mapstructure:"enable"` Enable bool `yaml:"enable"`
Timeout int `mapstructure:"timeout"` Timeout int `yaml:"timeout"`
AttentionIds []string `mapstructure:"attentionIds"` AttentionIds []string `yaml:"attentionIds"`
AllowedTypes []string `mapstructure:"allowedTypes"` DeniedTypes []int32 `yaml:"deniedTypes"`
DeniedTypes []string `mapstructure:"deniedTypes"`
} }
type Share struct { type Share struct {
Secret string `mapstructure:"secret"` Secret string `yaml:"secret"`
IMAdminUserID []string `mapstructure:"imAdminUserID"` IMAdminUserID []string `yaml:"imAdminUserID"`
MultiLogin MultiLogin `mapstructure:"multiLogin"` MultiLogin MultiLogin `yaml:"multiLogin"`
} }
type MultiLogin struct { type MultiLogin struct {
Policy int `mapstructure:"policy"` Policy int `yaml:"policy"`
MaxNumOneEnd int `mapstructure:"maxNumOneEnd"` MaxNumOneEnd int `yaml:"maxNumOneEnd"`
} }
type RpcService struct { type RpcService struct {
User string `mapstructure:"user"` User string `yaml:"user"`
Friend string `mapstructure:"friend"` Friend string `yaml:"friend"`
Msg string `mapstructure:"msg"` Msg string `yaml:"msg"`
Push string `mapstructure:"push"` Push string `yaml:"push"`
MessageGateway string `mapstructure:"messageGateway"` MessageGateway string `yaml:"messageGateway"`
Group string `mapstructure:"group"` Group string `yaml:"group"`
Auth string `mapstructure:"auth"` Auth string `yaml:"auth"`
Conversation string `mapstructure:"conversation"` Conversation string `yaml:"conversation"`
Third string `mapstructure:"third"` Third string `yaml:"third"`
} }
func (r *RpcService) GetServiceNames() []string { func (r *RpcService) GetServiceNames() []string {
@@ -418,80 +386,80 @@ func (r *RpcService) GetServiceNames() []string {
// FullConfig stores all configurations for before and after events // FullConfig stores all configurations for before and after events
type Webhooks struct { type Webhooks struct {
URL string `mapstructure:"url"` URL string `yaml:"url"`
BeforeSendSingleMsg BeforeConfig `mapstructure:"beforeSendSingleMsg"` BeforeSendSingleMsg BeforeConfig `yaml:"beforeSendSingleMsg"`
BeforeUpdateUserInfoEx BeforeConfig `mapstructure:"beforeUpdateUserInfoEx"` BeforeUpdateUserInfoEx BeforeConfig `yaml:"beforeUpdateUserInfoEx"`
AfterUpdateUserInfoEx AfterConfig `mapstructure:"afterUpdateUserInfoEx"` AfterUpdateUserInfoEx AfterConfig `yaml:"afterUpdateUserInfoEx"`
AfterSendSingleMsg AfterConfig `mapstructure:"afterSendSingleMsg"` AfterSendSingleMsg AfterConfig `yaml:"afterSendSingleMsg"`
BeforeSendGroupMsg BeforeConfig `mapstructure:"beforeSendGroupMsg"` BeforeSendGroupMsg BeforeConfig `yaml:"beforeSendGroupMsg"`
BeforeMsgModify BeforeConfig `mapstructure:"beforeMsgModify"` BeforeMsgModify BeforeConfig `yaml:"beforeMsgModify"`
AfterSendGroupMsg AfterConfig `mapstructure:"afterSendGroupMsg"` AfterSendGroupMsg AfterConfig `yaml:"afterSendGroupMsg"`
AfterUserOnline AfterConfig `mapstructure:"afterUserOnline"` AfterUserOnline AfterConfig `yaml:"afterUserOnline"`
AfterUserOffline AfterConfig `mapstructure:"afterUserOffline"` AfterUserOffline AfterConfig `yaml:"afterUserOffline"`
AfterUserKickOff AfterConfig `mapstructure:"afterUserKickOff"` AfterUserKickOff AfterConfig `yaml:"afterUserKickOff"`
BeforeOfflinePush BeforeConfig `mapstructure:"beforeOfflinePush"` BeforeOfflinePush BeforeConfig `yaml:"beforeOfflinePush"`
BeforeOnlinePush BeforeConfig `mapstructure:"beforeOnlinePush"` BeforeOnlinePush BeforeConfig `yaml:"beforeOnlinePush"`
BeforeGroupOnlinePush BeforeConfig `mapstructure:"beforeGroupOnlinePush"` BeforeGroupOnlinePush BeforeConfig `yaml:"beforeGroupOnlinePush"`
BeforeAddFriend BeforeConfig `mapstructure:"beforeAddFriend"` BeforeAddFriend BeforeConfig `yaml:"beforeAddFriend"`
BeforeUpdateUserInfo BeforeConfig `mapstructure:"beforeUpdateUserInfo"` BeforeUpdateUserInfo BeforeConfig `yaml:"beforeUpdateUserInfo"`
AfterUpdateUserInfo AfterConfig `mapstructure:"afterUpdateUserInfo"` AfterUpdateUserInfo AfterConfig `yaml:"afterUpdateUserInfo"`
BeforeCreateGroup BeforeConfig `mapstructure:"beforeCreateGroup"` BeforeCreateGroup BeforeConfig `yaml:"beforeCreateGroup"`
AfterCreateGroup AfterConfig `mapstructure:"afterCreateGroup"` AfterCreateGroup AfterConfig `yaml:"afterCreateGroup"`
BeforeMemberJoinGroup BeforeConfig `mapstructure:"beforeMemberJoinGroup"` BeforeMemberJoinGroup BeforeConfig `yaml:"beforeMemberJoinGroup"`
BeforeSetGroupMemberInfo BeforeConfig `mapstructure:"beforeSetGroupMemberInfo"` BeforeSetGroupMemberInfo BeforeConfig `yaml:"beforeSetGroupMemberInfo"`
AfterSetGroupMemberInfo AfterConfig `mapstructure:"afterSetGroupMemberInfo"` AfterSetGroupMemberInfo AfterConfig `yaml:"afterSetGroupMemberInfo"`
AfterQuitGroup AfterConfig `mapstructure:"afterQuitGroup"` AfterQuitGroup AfterConfig `yaml:"afterQuitGroup"`
AfterKickGroupMember AfterConfig `mapstructure:"afterKickGroupMember"` AfterKickGroupMember AfterConfig `yaml:"afterKickGroupMember"`
AfterDismissGroup AfterConfig `mapstructure:"afterDismissGroup"` AfterDismissGroup AfterConfig `yaml:"afterDismissGroup"`
BeforeApplyJoinGroup BeforeConfig `mapstructure:"beforeApplyJoinGroup"` BeforeApplyJoinGroup BeforeConfig `yaml:"beforeApplyJoinGroup"`
AfterGroupMsgRead AfterConfig `mapstructure:"afterGroupMsgRead"` AfterGroupMsgRead AfterConfig `yaml:"afterGroupMsgRead"`
AfterSingleMsgRead AfterConfig `mapstructure:"afterSingleMsgRead"` AfterSingleMsgRead AfterConfig `yaml:"afterSingleMsgRead"`
BeforeUserRegister BeforeConfig `mapstructure:"beforeUserRegister"` BeforeUserRegister BeforeConfig `yaml:"beforeUserRegister"`
AfterUserRegister AfterConfig `mapstructure:"afterUserRegister"` AfterUserRegister AfterConfig `yaml:"afterUserRegister"`
AfterTransferGroupOwner AfterConfig `mapstructure:"afterTransferGroupOwner"` AfterTransferGroupOwner AfterConfig `yaml:"afterTransferGroupOwner"`
BeforeSetFriendRemark BeforeConfig `mapstructure:"beforeSetFriendRemark"` BeforeSetFriendRemark BeforeConfig `yaml:"beforeSetFriendRemark"`
AfterSetFriendRemark AfterConfig `mapstructure:"afterSetFriendRemark"` AfterSetFriendRemark AfterConfig `yaml:"afterSetFriendRemark"`
AfterGroupMsgRevoke AfterConfig `mapstructure:"afterGroupMsgRevoke"` AfterGroupMsgRevoke AfterConfig `yaml:"afterGroupMsgRevoke"`
AfterJoinGroup AfterConfig `mapstructure:"afterJoinGroup"` AfterJoinGroup AfterConfig `yaml:"afterJoinGroup"`
BeforeInviteUserToGroup BeforeConfig `mapstructure:"beforeInviteUserToGroup"` BeforeInviteUserToGroup BeforeConfig `yaml:"beforeInviteUserToGroup"`
AfterSetGroupInfo AfterConfig `mapstructure:"afterSetGroupInfo"` AfterSetGroupInfo AfterConfig `yaml:"afterSetGroupInfo"`
BeforeSetGroupInfo BeforeConfig `mapstructure:"beforeSetGroupInfo"` BeforeSetGroupInfo BeforeConfig `yaml:"beforeSetGroupInfo"`
AfterSetGroupInfoEx AfterConfig `mapstructure:"afterSetGroupInfoEx"` AfterSetGroupInfoEx AfterConfig `yaml:"afterSetGroupInfoEx"`
BeforeSetGroupInfoEx BeforeConfig `mapstructure:"beforeSetGroupInfoEx"` BeforeSetGroupInfoEx BeforeConfig `yaml:"beforeSetGroupInfoEx"`
AfterRevokeMsg AfterConfig `mapstructure:"afterRevokeMsg"` AfterRevokeMsg AfterConfig `yaml:"afterRevokeMsg"`
BeforeAddBlack BeforeConfig `mapstructure:"beforeAddBlack"` BeforeAddBlack BeforeConfig `yaml:"beforeAddBlack"`
AfterAddFriend AfterConfig `mapstructure:"afterAddFriend"` AfterAddFriend AfterConfig `yaml:"afterAddFriend"`
BeforeAddFriendAgree BeforeConfig `mapstructure:"beforeAddFriendAgree"` BeforeAddFriendAgree BeforeConfig `yaml:"beforeAddFriendAgree"`
AfterAddFriendAgree AfterConfig `mapstructure:"afterAddFriendAgree"` AfterAddFriendAgree AfterConfig `yaml:"afterAddFriendAgree"`
AfterDeleteFriend AfterConfig `mapstructure:"afterDeleteFriend"` AfterDeleteFriend AfterConfig `yaml:"afterDeleteFriend"`
BeforeImportFriends BeforeConfig `mapstructure:"beforeImportFriends"` BeforeImportFriends BeforeConfig `yaml:"beforeImportFriends"`
AfterImportFriends AfterConfig `mapstructure:"afterImportFriends"` AfterImportFriends AfterConfig `yaml:"afterImportFriends"`
AfterRemoveBlack AfterConfig `mapstructure:"afterRemoveBlack"` AfterRemoveBlack AfterConfig `yaml:"afterRemoveBlack"`
} }
type ZooKeeper struct { type ZooKeeper struct {
Schema string `mapstructure:"schema"` Schema string `yaml:"schema"`
Address []string `mapstructure:"address"` Address []string `yaml:"address"`
Username string `mapstructure:"username"` Username string `yaml:"username"`
Password string `mapstructure:"password"` Password string `yaml:"password"`
} }
type Discovery struct { type Discovery struct {
Enable string `mapstructure:"enable"` Enable string `yaml:"enable"`
Etcd Etcd `mapstructure:"etcd"` Etcd Etcd `yaml:"etcd"`
Kubernetes Kubernetes `mapstructure:"kubernetes"` Kubernetes Kubernetes `yaml:"kubernetes"`
RpcService RpcService `mapstructure:"rpcService"` RpcService RpcService `yaml:"rpcService"`
} }
type Kubernetes struct { type Kubernetes struct {
Namespace string `mapstructure:"namespace"` Namespace string `yaml:"namespace"`
} }
type Etcd struct { type Etcd struct {
RootDirectory string `mapstructure:"rootDirectory"` RootDirectory string `yaml:"rootDirectory"`
Address []string `mapstructure:"address"` Address []string `yaml:"address"`
Username string `mapstructure:"username"` Username string `yaml:"username"`
Password string `mapstructure:"password"` Password string `yaml:"password"`
} }
func (m *Mongo) Build() *mongoutil.Config { func (m *Mongo) Build() *mongoutil.Config {
@@ -783,7 +751,7 @@ func (a *AllConfig) GetConfigNames() []string {
} }
} }
var ( const (
FileName = "config.yaml" FileName = "config.yaml"
DiscoveryConfigFilename = "discovery.yml" DiscoveryConfigFilename = "discovery.yml"
KafkaConfigFileName = "kafka.yml" KafkaConfigFileName = "kafka.yml"
+4 -1
View File
@@ -14,13 +14,16 @@
package config package config
import "github.com/openimsdk/tools/utils/runtimeenv"
const ConfKey = "conf" const ConfKey = "conf"
const ( const (
MountConfigFilePath = "CONFIG_PATH" MountConfigFilePath = "CONFIG_PATH"
DeploymentType = "DEPLOYMENT_TYPE" DeploymentType = "DEPLOYMENT_TYPE"
KUBERNETES = "kubernetes" KUBERNETES = runtimeenv.Kubernetes
ETCD = "etcd" ETCD = "etcd"
//Standalone = "standalone"
) )
const ( const (
+11
View File
@@ -0,0 +1,11 @@
package config
var standalone bool
func SetStandalone() {
standalone = true
}
func Standalone() bool {
return standalone
}
+4 -3
View File
@@ -7,11 +7,12 @@ import (
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/runtimeenv"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
func Load(configDirectory string, configFileName string, envPrefix string, runtimeEnv string, config any) error { func Load(configDirectory string, configFileName string, envPrefix string, config any) error {
if runtimeEnv == KUBERNETES { if runtimeenv.RuntimeEnvironment() == KUBERNETES {
mountPath := os.Getenv(MountConfigFilePath) mountPath := os.Getenv(MountConfigFilePath)
if mountPath == "" { if mountPath == "" {
return errs.ErrArgs.WrapMsg(MountConfigFilePath + " env is empty") return errs.ErrArgs.WrapMsg(MountConfigFilePath + " env is empty")
@@ -35,7 +36,7 @@ func loadConfig(path string, envPrefix string, config any) error {
} }
if err := v.Unmarshal(config, func(config *mapstructure.DecoderConfig) { if err := v.Unmarshal(config, func(config *mapstructure.DecoderConfig) {
config.TagName = "mapstructure" config.TagName = StructTagName
}); err != nil { }); err != nil {
return errs.WrapMsg(err, "failed to unmarshal config", "path", path, "envPrefix", envPrefix) return errs.WrapMsg(err, "failed to unmarshal config", "path", path, "envPrefix", envPrefix)
} }
+6 -2
View File
@@ -18,11 +18,12 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gopkg.in/yaml.v3"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/field" "github.com/openimsdk/tools/field"
"gopkg.in/yaml.v3"
) )
const ( const (
@@ -56,9 +57,12 @@ func GetProjectRoot() (string, error) {
return projectRoot, nil return projectRoot, nil
} }
func GetOptionsByNotification(cfg NotificationConfig) msgprocessor.Options { func GetOptionsByNotification(cfg NotificationConfig, sendMessage *bool) msgprocessor.Options {
opts := msgprocessor.NewOptions() opts := msgprocessor.NewOptions()
if sendMessage != nil {
cfg.IsSendMsg = *sendMessage
}
if cfg.IsSendMsg { if cfg.IsSendMsg {
opts = msgprocessor.WithOptions(opts, msgprocessor.WithUnreadCount(true)) opts = msgprocessor.WithOptions(opts, msgprocessor.WithUnreadCount(true))
} }
+7 -2
View File
@@ -19,6 +19,8 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/discovery/standalone"
"github.com/openimsdk/tools/utils/runtimeenv"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/openimsdk/tools/discovery/kubernetes" "github.com/openimsdk/tools/discovery/kubernetes"
@@ -28,8 +30,11 @@ import (
) )
// NewDiscoveryRegister creates a new service discovery and registry client based on the provided environment type. // NewDiscoveryRegister creates a new service discovery and registry client based on the provided environment type.
func NewDiscoveryRegister(discovery *config.Discovery, runtimeEnv string, watchNames []string) (discovery.SvcDiscoveryRegistry, error) { func NewDiscoveryRegister(discovery *config.Discovery, watchNames []string) (discovery.SvcDiscoveryRegistry, error) {
if runtimeEnv == config.KUBERNETES { if config.Standalone() {
return standalone.GetSvcDiscoveryRegistry(), nil
}
if runtimeenv.RuntimeEnvironment() == config.KUBERNETES {
return kubernetes.NewKubernetesConnManager(discovery.Kubernetes.Namespace, return kubernetes.NewKubernetesConnManager(discovery.Kubernetes.Namespace,
grpc.WithDefaultCallOptions( grpc.WithDefaultCallOptions(
grpc.MaxCallSendMsgSize(1024*1024*20), grpc.MaxCallSendMsgSize(1024*1024*20),
+7 -8
View File
@@ -1,10 +1,11 @@
package prommetrics package prommetrics
import ( import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"net" "net"
"strconv" "strconv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
) )
var ( var (
@@ -24,6 +25,10 @@ var (
) )
) )
func RegistryApi() {
registry.MustRegister(apiCounter, httpCounter)
}
func ApiInit(listener net.Listener) error { func ApiInit(listener net.Listener) error {
apiRegistry := prometheus.NewRegistry() apiRegistry := prometheus.NewRegistry()
cs := append( cs := append(
@@ -41,9 +46,3 @@ func APICall(path string, method string, apiCode int) {
func HttpCall(path string, method string, status int) { func HttpCall(path string, method string, status int) {
httpCounter.With(prometheus.Labels{"path": path, "method": method, "status": strconv.Itoa(status)}).Inc() httpCounter.With(prometheus.Labels{"path": path, "method": method, "status": strconv.Itoa(status)}).Inc()
} }
//func ApiHandler() http.Handler {
// return promhttp.InstrumentMetricHandler(
// apiRegistry, promhttp.HandlerFor(apiRegistry, promhttp.HandlerOpts{}),
// )
//}
-31
View File
@@ -1,31 +0,0 @@
package prommetrics
import "fmt"
const (
APIKeyName = "api"
MessageTransferKeyName = "message-transfer"
)
type Target struct {
Target string `json:"target"`
Labels map[string]string `json:"labels"`
}
type RespTarget struct {
Targets []string `json:"targets"`
Labels map[string]string `json:"labels"`
}
func BuildDiscoveryKey(name string) string {
return fmt.Sprintf("%s/%s/%s", "openim", "prometheus_discovery", name)
}
func BuildDefaultTarget(host string, ip int) Target {
return Target{
Target: fmt.Sprintf("%s:%d", host, ip),
Labels: map[string]string{
"namespace": "default",
},
}
}
-15
View File
@@ -1,15 +0,0 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prommetrics // import "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
+4
View File
@@ -24,3 +24,7 @@ var (
Help: "The number of user login", Help: "The number of user login",
}) })
) )
func RegistryAuth() {
registry.MustRegister(UserLoginCounter)
}
+9
View File
@@ -36,3 +36,12 @@ var (
Help: "The number of group chat msg failed processed", Help: "The number of group chat msg failed processed",
}) })
) )
func RegistryMsg() {
registry.MustRegister(
SingleChatMsgProcessSuccessCounter,
SingleChatMsgProcessFailedCounter,
GroupChatMsgProcessSuccessCounter,
GroupChatMsgProcessFailedCounter,
)
}
@@ -24,3 +24,7 @@ var (
Help: "The number of online user num", Help: "The number of online user num",
}) })
) )
func RegistryMsgGateway() {
registry.MustRegister(OnlineUserGauge)
}
+7
View File
@@ -28,3 +28,10 @@ var (
Help: "The number of messages with a push time exceeding 10 seconds", Help: "The number of messages with a push time exceeding 10 seconds",
}) })
) )
func RegistryPush() {
registry.MustRegister(
MsgOfflinePushFailedCounter,
MsgLoneTimePushCounter,
)
}
+4
View File
@@ -8,3 +8,7 @@ var (
Help: "The number of user login", Help: "The number of user login",
}) })
) )
func RegistryUser() {
registry.MustRegister(UserRegisterCounter)
}
+75 -2
View File
@@ -15,14 +15,42 @@
package prommetrics package prommetrics
import ( import (
"github.com/prometheus/client_golang/prometheus" "errors"
"github.com/prometheus/client_golang/prometheus/collectors" "fmt"
"net" "net"
"net/http" "net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
) )
const commonPath = "/metrics" const commonPath = "/metrics"
var registry = &prometheusRegistry{prometheus.NewRegistry()}
type prometheusRegistry struct {
*prometheus.Registry
}
func (x *prometheusRegistry) MustRegister(cs ...prometheus.Collector) {
for _, c := range cs {
if err := x.Registry.Register(c); err != nil {
if errors.As(err, &prometheus.AlreadyRegisteredError{}) {
continue
}
panic(err)
}
}
}
func init() {
registry.MustRegister(
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
collectors.NewGoCollector(),
)
}
var ( var (
baseCollector = []prometheus.Collector{ baseCollector = []prometheus.Collector{
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}), collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
@@ -36,3 +64,48 @@ func Init(registry *prometheus.Registry, listener net.Listener, path string, han
srv.Handle(path, handler) srv.Handle(path, handler)
return http.Serve(listener, srv) return http.Serve(listener, srv)
} }
func RegistryAll() {
RegistryApi()
RegistryAuth()
RegistryMsg()
RegistryMsgGateway()
RegistryPush()
RegistryUser()
RegistryRpc()
RegistryTransfer()
}
func Start(listener net.Listener) error {
srv := http.NewServeMux()
srv.Handle(commonPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
return http.Serve(listener, srv)
}
const (
APIKeyName = "api"
MessageTransferKeyName = "message-transfer"
)
type Target struct {
Target string `json:"target"`
Labels map[string]string `json:"labels"`
}
type RespTarget struct {
Targets []string `json:"targets"`
Labels map[string]string `json:"labels"`
}
func BuildDiscoveryKey(name string) string {
return fmt.Sprintf("%s/%s/%s", "openim", "prometheus_discovery", name)
}
func BuildDefaultTarget(host string, ip int) Target {
return Target{
Target: fmt.Sprintf("%s:%d", host, ip),
Labels: map[string]string{
"namespace": "default",
},
}
}
@@ -14,6 +14,8 @@
package prommetrics package prommetrics
import "testing"
//func TestNewGrpcPromObj(t *testing.T) { //func TestNewGrpcPromObj(t *testing.T) {
// // Create a custom metric to pass into the NewGrpcPromObj function. // // Create a custom metric to pass into the NewGrpcPromObj function.
// customMetric := prometheus.NewCounter(prometheus.CounterOpts{ // customMetric := prometheus.NewCounter(prometheus.CounterOpts{
@@ -67,3 +69,9 @@ package prommetrics
// }) // })
// } // }
//} //}
func TestName(t *testing.T) {
RegistryApi()
RegistryApi()
}
+7 -2
View File
@@ -1,12 +1,13 @@
package prommetrics package prommetrics
import ( import (
"net"
"strconv"
gp "github.com/grpc-ecosystem/go-grpc-prometheus" gp "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"net"
"strconv"
) )
const rpcPath = commonPath const rpcPath = commonPath
@@ -22,6 +23,10 @@ var (
) )
) )
func RegistryRpc() {
registry.MustRegister(rpcCounter)
}
func RpcInit(cs []prometheus.Collector, listener net.Listener) error { func RpcInit(cs []prometheus.Collector, listener net.Listener) error {
reg := prometheus.NewRegistry() reg := prometheus.NewRegistry()
cs = append(append( cs = append(append(
+12 -1
View File
@@ -15,9 +15,10 @@
package prommetrics package prommetrics
import ( import (
"net"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"net"
) )
var ( var (
@@ -43,6 +44,16 @@ var (
}) })
) )
func RegistryTransfer() {
registry.MustRegister(
MsgInsertRedisSuccessCounter,
MsgInsertRedisFailedCounter,
MsgInsertMongoSuccessCounter,
MsgInsertMongoFailedCounter,
SeqSetFailedCounter,
)
}
func TransferInit(listener net.Listener) error { func TransferInit(listener net.Listener) error {
reg := prometheus.NewRegistry() reg := prometheus.NewRegistry()
cs := append( cs := append(
-15
View File
@@ -1,15 +0,0 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package redispubsub // import "github.com/openimsdk/open-im-server/v3/pkg/common/redispubsub"
-30
View File
@@ -1,30 +0,0 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package redispubsub
import "github.com/redis/go-redis/v9"
type Publisher struct {
client redis.UniversalClient
channel string
}
func NewPublisher(client redis.UniversalClient, channel string) *Publisher {
return &Publisher{client: client, channel: channel}
}
func (p *Publisher) Publish(message string) error {
return p.client.Publish(ctx, p.channel, message).Err()
}
-49
View File
@@ -1,49 +0,0 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package redispubsub
import (
"context"
"github.com/redis/go-redis/v9"
)
var ctx = context.Background()
type Subscriber struct {
client redis.UniversalClient
channel string
}
func NewSubscriber(client redis.UniversalClient, channel string) *Subscriber {
return &Subscriber{client: client, channel: channel}
}
func (s *Subscriber) OnMessage(ctx context.Context, callback func(string)) error {
messageChannel := s.client.Subscribe(ctx, s.channel).Channel()
go func() {
for {
select {
case <-ctx.Done():
return
case msg := <-messageChannel:
callback(msg.Payload)
}
}
}()
return nil
}
-15
View File
@@ -1,15 +0,0 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package startrpc // import "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
+140 -152
View File
@@ -19,7 +19,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/http"
"os" "os"
"os/signal" "os/signal"
"strconv" "strconv"
@@ -27,205 +26,186 @@ import (
"time" "time"
conf "github.com/openimsdk/open-im-server/v3/pkg/common/config" conf "github.com/openimsdk/open-im-server/v3/pkg/common/config"
disetcd "github.com/openimsdk/open-im-server/v3/pkg/common/discovery/etcd"
"github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/jsonutil" "github.com/openimsdk/tools/utils/jsonutil"
"github.com/openimsdk/tools/utils/network"
"google.golang.org/grpc/status" "google.golang.org/grpc/status"
"github.com/openimsdk/tools/utils/runtimeenv"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discovery" kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discovery"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mw" "github.com/openimsdk/tools/mw"
"github.com/openimsdk/tools/utils/network"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
) )
// Start rpc server. func init() {
func Start[T any](ctx context.Context, discovery *conf.Discovery, prometheusConfig *conf.Prometheus, listenIP, prommetrics.RegistryAll()
}
func Start[T any](ctx context.Context, disc *conf.Discovery, prometheusConfig *conf.Prometheus, listenIP,
registerIP string, autoSetPorts bool, rpcPorts []int, index int, rpcRegisterName string, notification *conf.Notification, config T, registerIP string, autoSetPorts bool, rpcPorts []int, index int, rpcRegisterName string, notification *conf.Notification, config T,
watchConfigNames []string, watchServiceNames []string, watchConfigNames []string, watchServiceNames []string,
rpcFn func(ctx context.Context, config T, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error, rpcFn func(ctx context.Context, config T, client discovery.Conn, server grpc.ServiceRegistrar) error,
options ...grpc.ServerOption) error { options ...grpc.ServerOption) error {
watchConfigNames = append(watchConfigNames, conf.LogConfigFileName)
var (
rpcTcpAddr string
netDone = make(chan struct{}, 2)
netErr error
prometheusPort int
)
if notification != nil { if notification != nil {
conf.InitNotification(notification) conf.InitNotification(notification)
} }
options = append(options, mw.GrpcServer())
registerIP, err := network.GetRpcRegisterIP(registerIP) registerIP, err := network.GetRpcRegisterIP(registerIP)
if err != nil { if err != nil {
return err return err
} }
var prometheusListenAddr string
runTimeEnv := runtimeenv.PrintRuntimeEnvironment() if autoSetPorts {
prometheusListenAddr = net.JoinHostPort(listenIP, "0")
if !autoSetPorts { } else {
rpcPort, err := datautil.GetElemByIndex(rpcPorts, index) prometheusPort, err := datautil.GetElemByIndex(prometheusConfig.Ports, index)
if err != nil { if err != nil {
return err return err
} }
rpcTcpAddr = net.JoinHostPort(network.GetListenIP(listenIP), strconv.Itoa(rpcPort)) prometheusListenAddr = net.JoinHostPort(listenIP, strconv.Itoa(prometheusPort))
} else {
rpcTcpAddr = net.JoinHostPort(network.GetListenIP(listenIP), "0")
} }
getAutoPort := func() (net.Listener, int, error) { watchConfigNames = append(watchConfigNames, conf.LogConfigFileName)
listener, err := net.Listen("tcp", rpcTcpAddr)
if err != nil {
return nil, 0, errs.WrapMsg(err, "listen err", "rpcTcpAddr", rpcTcpAddr)
}
_, portStr, _ := net.SplitHostPort(listener.Addr().String())
port, _ := strconv.Atoi(portStr)
return listener, port, nil
}
if autoSetPorts && discovery.Enable != conf.ETCD { client, err := kdisc.NewDiscoveryRegister(disc, watchServiceNames)
return errs.New("only etcd support autoSetPorts", "rpcRegisterName", rpcRegisterName).Wrap()
}
client, err := kdisc.NewDiscoveryRegister(discovery, runTimeEnv, watchServiceNames)
if err != nil { if err != nil {
return err return err
} }
defer client.Close() defer client.Close()
client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) client.AddOption(
mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin")),
)
// var reg *prometheus.Registry ctx, cancel := context.WithCancelCause(ctx)
// var metric *grpcprometheus.ServerMetrics
if prometheusConfig.Enable { go func() {
// cusMetrics := prommetrics.GetGrpcCusMetrics(rpcRegisterName, share) sigs := make(chan os.Signal, 1)
// reg, metric, _ = prommetrics.NewGrpcPromObj(cusMetrics) signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
// options = append(options, mw.GrpcServer(), grpc.StreamInterceptor(metric.StreamServerInterceptor()), select {
// grpc.UnaryInterceptor(metric.UnaryServerInterceptor())) case <-ctx.Done():
return
case val := <-sigs:
log.ZDebug(ctx, "recv signal", "signal", val.String())
cancel(fmt.Errorf("signal %s", val.String()))
}
}()
if prometheusListenAddr != "" {
options = append( options = append(
options, mw.GrpcServer(), options,
prommetricsUnaryInterceptor(rpcRegisterName), prommetricsUnaryInterceptor(rpcRegisterName),
prommetricsStreamInterceptor(rpcRegisterName), prommetricsStreamInterceptor(rpcRegisterName),
) )
prometheusListener, prometheusPort, err := listenTCP(prometheusListenAddr)
var ( if err != nil {
listener net.Listener
)
if autoSetPorts {
listener, prometheusPort, err = getAutoPort()
if err != nil {
return err
}
etcdClient := client.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
_, err = etcdClient.Put(ctx, prommetrics.BuildDiscoveryKey(rpcRegisterName), jsonutil.StructToJsonString(prommetrics.BuildDefaultTarget(registerIP, prometheusPort)))
if err != nil {
return errs.WrapMsg(err, "etcd put err")
}
} else {
prometheusPort, err = datautil.GetElemByIndex(prometheusConfig.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", "rpcTcpAddr", rpcTcpAddr)
}
}
cs := prommetrics.GetGrpcCusMetrics(rpcRegisterName, discovery)
go func() {
if err := prommetrics.RpcInit(cs, listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
netErr = errs.WrapMsg(err, fmt.Sprintf("rpc %s prometheus start err: %d", rpcRegisterName, prometheusPort))
netDone <- struct{}{}
}
//metric.InitializeMetrics(srv)
// Create a HTTP server for prometheus.
// httpServer = &http.Server{Handler: promhttp.HandlerFor(reg, promhttp.HandlerOpts{}), Addr: fmt.Sprintf("0.0.0.0:%d", prometheusPort)}
// if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
// netErr = errs.WrapMsg(err, "prometheus start err", httpServer.Addr)
// netDone <- struct{}{}
// }
}()
} else {
options = append(options, mw.GrpcServer())
}
listener, port, err := getAutoPort()
if err != nil {
return err
}
log.CInfo(ctx, "RPC server is initializing", "rpcRegisterName", rpcRegisterName, "rpcPort", port,
"prometheusPort", prometheusPort)
defer listener.Close()
srv := grpc.NewServer(options...)
err = rpcFn(ctx, config, client, srv)
if err != nil {
return err
}
err = client.Register(
rpcRegisterName,
registerIP,
port,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
return err
}
go func() {
err := srv.Serve(listener)
if err != nil && !errors.Is(err, http.ErrServerClosed) {
netErr = errs.WrapMsg(err, "rpc start err: ", rpcTcpAddr)
netDone <- struct{}{}
}
}()
if discovery.Enable == conf.ETCD {
cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), watchConfigNames)
cm.Watch(ctx)
}
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM)
select {
case <-sigs:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := gracefulStopWithCtx(ctx, srv.GracefulStop); err != nil {
return err return err
} }
return nil log.ZDebug(ctx, "prometheus start", "addr", prometheusListener.Addr(), "rpcRegisterName", rpcRegisterName)
case <-netDone: target, err := jsonutil.JsonMarshal(prommetrics.BuildDefaultTarget(registerIP, prometheusPort))
return netErr if err != nil {
return err
}
if err := client.SetKey(ctx, prommetrics.BuildDiscoveryKey(prommetrics.APIKeyName), target); err != nil {
if !errors.Is(err, discovery.ErrNotSupportedKeyValue) {
return err
}
}
go func() {
err := prommetrics.Start(prometheusListener)
if err == nil {
err = fmt.Errorf("listener done")
}
cancel(fmt.Errorf("prommetrics %s %w", rpcRegisterName, err))
}()
} }
var (
rpcServer *grpc.Server
rpcGracefulStop chan struct{}
)
onGrpcServiceRegistrar := func(desc *grpc.ServiceDesc, impl any) {
if rpcServer != nil {
rpcServer.RegisterService(desc, impl)
return
}
var rpcListenAddr string
if autoSetPorts {
rpcListenAddr = net.JoinHostPort(listenIP, "0")
} else {
rpcPort, err := datautil.GetElemByIndex(rpcPorts, index)
if err != nil {
cancel(fmt.Errorf("rpcPorts index out of range %s %w", rpcRegisterName, err))
return
}
rpcListenAddr = net.JoinHostPort(listenIP, strconv.Itoa(rpcPort))
}
rpcListener, err := net.Listen("tcp", rpcListenAddr)
if err != nil {
cancel(fmt.Errorf("listen rpc %s %s %w", rpcRegisterName, rpcListenAddr, err))
return
}
rpcServer = grpc.NewServer(options...)
rpcServer.RegisterService(desc, impl)
rpcGracefulStop = make(chan struct{})
rpcPort := rpcListener.Addr().(*net.TCPAddr).Port
log.ZDebug(ctx, "rpc start register", "rpcRegisterName", rpcRegisterName, "registerIP", registerIP, "rpcPort", rpcPort)
grpcOpt := grpc.WithTransportCredentials(insecure.NewCredentials())
rpcGracefulStop = make(chan struct{})
go func() {
<-ctx.Done()
rpcServer.GracefulStop()
close(rpcGracefulStop)
}()
if err := client.Register(ctx, rpcRegisterName, registerIP, rpcListener.Addr().(*net.TCPAddr).Port, grpcOpt); err != nil {
cancel(fmt.Errorf("rpc register %s %w", rpcRegisterName, err))
return
}
go func() {
err := rpcServer.Serve(rpcListener)
if err == nil {
err = fmt.Errorf("serve end")
}
cancel(fmt.Errorf("rpc %s %w", rpcRegisterName, err))
}()
}
err = rpcFn(ctx, config, client, &grpcServiceRegistrar{onRegisterService: onGrpcServiceRegistrar})
if err != nil {
return err
}
<-ctx.Done()
log.ZDebug(ctx, "cmd wait done", "err", context.Cause(ctx))
if rpcGracefulStop != nil {
timeout := time.NewTimer(time.Second * 15)
defer timeout.Stop()
select {
case <-timeout.C:
log.ZWarn(ctx, "rcp graceful stop timeout", nil)
case <-rpcGracefulStop:
log.ZDebug(ctx, "rcp graceful stop done")
}
}
return context.Cause(ctx)
} }
func gracefulStopWithCtx(ctx context.Context, f func()) error { func listenTCP(addr string) (net.Listener, int, error) {
done := make(chan struct{}, 1) listener, err := net.Listen("tcp", addr)
go func() { if err != nil {
f() return nil, 0, errs.WrapMsg(err, "listen err", "addr", addr)
close(done)
}()
select {
case <-ctx.Done():
return errs.New("timeout, ctx graceful stop")
case <-done:
return nil
} }
return listener, listener.Addr().(*net.TCPAddr).Port, nil
} }
func prommetricsUnaryInterceptor(rpcRegisterName string) grpc.ServerOption { func prommetricsUnaryInterceptor(rpcRegisterName string) grpc.ServerOption {
@@ -249,3 +229,11 @@ func prommetricsUnaryInterceptor(rpcRegisterName string) grpc.ServerOption {
func prommetricsStreamInterceptor(rpcRegisterName string) grpc.ServerOption { func prommetricsStreamInterceptor(rpcRegisterName string) grpc.ServerOption {
return grpc.ChainStreamInterceptor() return grpc.ChainStreamInterceptor()
} }
type grpcServiceRegistrar struct {
onRegisterService func(desc *grpc.ServiceDesc, impl any)
}
func (x *grpcServiceRegistrar) RegisterService(desc *grpc.ServiceDesc, impl any) {
x.onRegisterService(desc, impl)
}
+50
View File
@@ -0,0 +1,50 @@
package mcache
import (
"context"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
"github.com/openimsdk/tools/s3/minio"
)
func NewMinioCache(cache database.Cache) minio.Cache {
return &minioCache{
cache: cache,
expireTime: time.Hour * 24 * 7,
}
}
type minioCache struct {
cache database.Cache
expireTime time.Duration
}
func (g *minioCache) getObjectImageInfoKey(key string) string {
return cachekey.GetObjectImageInfoKey(key)
}
func (g *minioCache) getMinioImageThumbnailKey(key string, format string, width int, height int) string {
return cachekey.GetMinioImageThumbnailKey(key, format, width, height)
}
func (g *minioCache) DelObjectImageInfoKey(ctx context.Context, keys ...string) error {
ks := make([]string, 0, len(keys))
for _, key := range keys {
ks = append(ks, g.getObjectImageInfoKey(key))
}
return g.cache.Del(ctx, ks)
}
func (g *minioCache) DelImageThumbnailKey(ctx context.Context, key string, format string, width int, height int) error {
return g.cache.Del(ctx, []string{g.getMinioImageThumbnailKey(key, format, width, height)})
}
func (g *minioCache) GetImageObjectKeyInfo(ctx context.Context, key string, fn func(ctx context.Context) (*minio.ImageInfo, error)) (*minio.ImageInfo, error) {
return getCache[*minio.ImageInfo](ctx, g.cache, g.getObjectImageInfoKey(key), g.expireTime, fn)
}
func (g *minioCache) GetThumbnailKey(ctx context.Context, key string, format string, width int, height int, minioCache func(ctx context.Context) (string, error)) (string, error) {
return getCache[string](ctx, g.cache, g.getMinioImageThumbnailKey(key, format, width, height), g.expireTime, minioCache)
}
+132
View File
@@ -0,0 +1,132 @@
package mcache
import (
"context"
"strconv"
"sync"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
"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/localcache/lru"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/datautil"
"github.com/redis/go-redis/v9"
)
var (
memMsgCache lru.LRU[string, *model.MsgInfoModel]
initMemMsgCache sync.Once
)
func NewMsgCache(cache database.Cache, msgDocDatabase database.Msg) cache.MsgCache {
initMemMsgCache.Do(func() {
memMsgCache = lru.NewLayLRU[string, *model.MsgInfoModel](1024*8, time.Hour, time.Second*10, localcache.EmptyTarget{}, nil)
})
return &msgCache{
cache: cache,
msgDocDatabase: msgDocDatabase,
memMsgCache: memMsgCache,
}
}
type msgCache struct {
cache database.Cache
msgDocDatabase database.Msg
memMsgCache lru.LRU[string, *model.MsgInfoModel]
}
func (x *msgCache) getSendMsgKey(id string) string {
return cachekey.GetSendMsgKey(id)
}
func (x *msgCache) SetSendMsgStatus(ctx context.Context, id string, status int32) error {
return x.cache.Set(ctx, x.getSendMsgKey(id), strconv.Itoa(int(status)), time.Hour*24)
}
func (x *msgCache) GetSendMsgStatus(ctx context.Context, id string) (int32, error) {
key := x.getSendMsgKey(id)
res, err := x.cache.Get(ctx, []string{key})
if err != nil {
return 0, err
}
val, ok := res[key]
if !ok {
return 0, errs.Wrap(redis.Nil)
}
status, err := strconv.Atoi(val)
if err != nil {
return 0, errs.WrapMsg(err, "GetSendMsgStatus strconv.Atoi error", "val", val)
}
return int32(status), nil
}
func (x *msgCache) getMsgCacheKey(conversationID string, seq int64) string {
return cachekey.GetMsgCacheKey(conversationID, seq)
}
func (x *msgCache) GetMessageBySeqs(ctx context.Context, conversationID string, seqs []int64) ([]*model.MsgInfoModel, error) {
if len(seqs) == 0 {
return nil, nil
}
keys := make([]string, 0, len(seqs))
keySeq := make(map[string]int64, len(seqs))
for _, seq := range seqs {
key := x.getMsgCacheKey(conversationID, seq)
keys = append(keys, key)
keySeq[key] = seq
}
res, err := x.memMsgCache.GetBatch(keys, func(keys []string) (map[string]*model.MsgInfoModel, error) {
findSeqs := make([]int64, 0, len(keys))
for _, key := range keys {
seq, ok := keySeq[key]
if !ok {
continue
}
findSeqs = append(findSeqs, seq)
}
res, err := x.msgDocDatabase.FindSeqs(ctx, conversationID, seqs)
if err != nil {
return nil, err
}
kv := make(map[string]*model.MsgInfoModel)
for i := range res {
msg := res[i]
if msg == nil || msg.Msg == nil || msg.Msg.Seq <= 0 {
continue
}
key := x.getMsgCacheKey(conversationID, msg.Msg.Seq)
kv[key] = msg
}
return kv, nil
})
if err != nil {
return nil, err
}
return datautil.Values(res), nil
}
func (x msgCache) DelMessageBySeqs(ctx context.Context, conversationID string, seqs []int64) error {
if len(seqs) == 0 {
return nil
}
for _, seq := range seqs {
x.memMsgCache.Del(x.getMsgCacheKey(conversationID, seq))
}
return nil
}
func (x *msgCache) SetMessageBySeqs(ctx context.Context, conversationID string, msgs []*model.MsgInfoModel) error {
for i := range msgs {
msg := msgs[i]
if msg == nil || msg.Msg == nil || msg.Msg.Seq <= 0 {
continue
}
x.memMsgCache.Set(x.getMsgCacheKey(conversationID, msg.Msg.Seq), msg)
}
return nil
}
+82
View File
@@ -0,0 +1,82 @@
package mcache
import (
"context"
"sync"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
)
var (
globalOnlineCache cache.OnlineCache
globalOnlineOnce sync.Once
)
func NewOnlineCache() cache.OnlineCache {
globalOnlineOnce.Do(func() {
globalOnlineCache = &onlineCache{
user: make(map[string]map[int32]struct{}),
}
})
return globalOnlineCache
}
type onlineCache struct {
lock sync.RWMutex
user map[string]map[int32]struct{}
}
func (x *onlineCache) GetOnline(ctx context.Context, userID string) ([]int32, error) {
x.lock.RLock()
defer x.lock.RUnlock()
pSet, ok := x.user[userID]
if !ok {
return nil, nil
}
res := make([]int32, 0, len(pSet))
for k := range pSet {
res = append(res, k)
}
return res, nil
}
func (x *onlineCache) SetUserOnline(ctx context.Context, userID string, online, offline []int32) error {
x.lock.Lock()
defer x.lock.Unlock()
pSet, ok := x.user[userID]
if ok {
for _, p := range offline {
delete(pSet, p)
}
}
if len(online) > 0 {
if !ok {
pSet = make(map[int32]struct{})
x.user[userID] = pSet
}
for _, p := range online {
pSet[p] = struct{}{}
}
}
if len(pSet) == 0 {
delete(x.user, userID)
}
return nil
}
func (x *onlineCache) GetAllOnlineUsers(ctx context.Context, cursor uint64) (map[string][]int32, uint64, error) {
if cursor != 0 {
return nil, 0, nil
}
x.lock.RLock()
defer x.lock.RUnlock()
res := make(map[string][]int32)
for k, v := range x.user {
pSet := make([]int32, 0, len(v))
for p := range v {
pSet = append(pSet, p)
}
res[k] = pSet
}
return res, 0, nil
}
+79
View File
@@ -0,0 +1,79 @@
package mcache
import (
"context"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
)
func NewSeqConversationCache(sc database.SeqConversation) cache.SeqConversationCache {
return &seqConversationCache{
sc: sc,
}
}
type seqConversationCache struct {
sc database.SeqConversation
}
func (x *seqConversationCache) Malloc(ctx context.Context, conversationID string, size int64) (int64, error) {
return x.sc.Malloc(ctx, conversationID, size)
}
func (x *seqConversationCache) SetMinSeq(ctx context.Context, conversationID string, seq int64) error {
return x.sc.SetMinSeq(ctx, conversationID, seq)
}
func (x *seqConversationCache) GetMinSeq(ctx context.Context, conversationID string) (int64, error) {
return x.sc.GetMinSeq(ctx, conversationID)
}
func (x *seqConversationCache) GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) {
res := make(map[string]int64)
for _, conversationID := range conversationIDs {
seq, err := x.GetMinSeq(ctx, conversationID)
if err != nil {
return nil, err
}
res[conversationID] = seq
}
return res, nil
}
func (x *seqConversationCache) GetMaxSeqsWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error) {
res := make(map[string]database.SeqTime)
for _, conversationID := range conversationIDs {
seq, err := x.GetMinSeq(ctx, conversationID)
if err != nil {
return nil, err
}
res[conversationID] = database.SeqTime{Seq: seq}
}
return res, nil
}
func (x *seqConversationCache) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
return x.sc.GetMaxSeq(ctx, conversationID)
}
func (x *seqConversationCache) GetMaxSeqWithTime(ctx context.Context, conversationID string) (database.SeqTime, error) {
seq, err := x.GetMinSeq(ctx, conversationID)
if err != nil {
return database.SeqTime{}, err
}
return database.SeqTime{Seq: seq}, nil
}
func (x *seqConversationCache) SetMinSeqs(ctx context.Context, seqs map[string]int64) error {
for conversationID, seq := range seqs {
if err := x.sc.SetMinSeq(ctx, conversationID, seq); err != nil {
return err
}
}
return nil
}
func (x *seqConversationCache) GetCacheMaxSeqWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error) {
return x.GetMaxSeqsWithTime(ctx, conversationIDs)
}
+98
View File
@@ -0,0 +1,98 @@
package mcache
import (
"context"
"strconv"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
"github.com/openimsdk/tools/errs"
"github.com/redis/go-redis/v9"
)
func NewThirdCache(cache database.Cache) cache.ThirdCache {
return &thirdCache{
cache: cache,
}
}
type thirdCache struct {
cache database.Cache
}
func (c *thirdCache) getGetuiTokenKey() string {
return cachekey.GetGetuiTokenKey()
}
func (c *thirdCache) getGetuiTaskIDKey() string {
return cachekey.GetGetuiTaskIDKey()
}
func (c *thirdCache) getUserBadgeUnreadCountSumKey(userID string) string {
return cachekey.GetUserBadgeUnreadCountSumKey(userID)
}
func (c *thirdCache) getFcmAccountTokenKey(account string, platformID int) string {
return cachekey.GetFcmAccountTokenKey(account, platformID)
}
func (c *thirdCache) get(ctx context.Context, key string) (string, error) {
res, err := c.cache.Get(ctx, []string{key})
if err != nil {
return "", err
}
if val, ok := res[key]; ok {
return val, nil
}
return "", errs.Wrap(redis.Nil)
}
func (c *thirdCache) SetFcmToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) (err error) {
return errs.Wrap(c.cache.Set(ctx, c.getFcmAccountTokenKey(account, platformID), fcmToken, time.Duration(expireTime)*time.Second))
}
func (c *thirdCache) GetFcmToken(ctx context.Context, account string, platformID int) (string, error) {
return c.get(ctx, c.getFcmAccountTokenKey(account, platformID))
}
func (c *thirdCache) DelFcmToken(ctx context.Context, account string, platformID int) error {
return c.cache.Del(ctx, []string{c.getFcmAccountTokenKey(account, platformID)})
}
func (c *thirdCache) IncrUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) {
return c.cache.Incr(ctx, c.getUserBadgeUnreadCountSumKey(userID), 1)
}
func (c *thirdCache) SetUserBadgeUnreadCountSum(ctx context.Context, userID string, value int) error {
return c.cache.Set(ctx, c.getUserBadgeUnreadCountSumKey(userID), strconv.Itoa(value), 0)
}
func (c *thirdCache) GetUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) {
str, err := c.get(ctx, c.getUserBadgeUnreadCountSumKey(userID))
if err != nil {
return 0, err
}
val, err := strconv.Atoi(str)
if err != nil {
return 0, errs.WrapMsg(err, "strconv.Atoi", "str", str)
}
return val, nil
}
func (c *thirdCache) SetGetuiToken(ctx context.Context, token string, expireTime int64) error {
return c.cache.Set(ctx, c.getGetuiTokenKey(), token, time.Duration(expireTime)*time.Second)
}
func (c *thirdCache) GetGetuiToken(ctx context.Context) (string, error) {
return c.get(ctx, c.getGetuiTokenKey())
}
func (c *thirdCache) SetGetuiTaskID(ctx context.Context, taskID string, expireTime int64) error {
return c.cache.Set(ctx, c.getGetuiTaskIDKey(), taskID, time.Duration(expireTime)*time.Second)
}
func (c *thirdCache) GetGetuiTaskID(ctx context.Context) (string, error) {
return c.get(ctx, c.getGetuiTaskIDKey())
}
+130
View File
@@ -0,0 +1,130 @@
package mcache
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
)
func NewTokenCacheModel(cache database.Cache, accessExpire int64) cache.TokenModel {
c := &tokenCache{cache: cache}
c.accessExpire = c.getExpireTime(accessExpire)
return c
}
type tokenCache struct {
cache database.Cache
accessExpire time.Duration
}
func (x *tokenCache) getTokenKey(userID string, platformID int, token string) string {
return cachekey.GetTokenKey(userID, platformID) + ":" + token
}
func (x *tokenCache) SetTokenFlag(ctx context.Context, userID string, platformID int, token string, flag int) error {
return x.cache.Set(ctx, x.getTokenKey(userID, platformID, token), strconv.Itoa(flag), x.accessExpire)
}
// SetTokenFlagEx set token and flag with expire time
func (x *tokenCache) SetTokenFlagEx(ctx context.Context, userID string, platformID int, token string, flag int) error {
return x.SetTokenFlag(ctx, userID, platformID, token, flag)
}
func (x *tokenCache) GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error) {
prefix := x.getTokenKey(userID, platformID, "")
m, err := x.cache.Prefix(ctx, prefix)
if err != nil {
return nil, errs.Wrap(err)
}
mm := make(map[string]int)
for k, v := range m {
state, err := strconv.Atoi(v)
if err != nil {
log.ZError(ctx, "token value is not int", err, "value", v, "userID", userID, "platformID", platformID)
continue
}
mm[strings.TrimPrefix(k, prefix)] = state
}
return mm, nil
}
func (x *tokenCache) GetAllTokensWithoutError(ctx context.Context, userID string) (map[int]map[string]int, error) {
prefix := cachekey.UidPidToken + userID + ":"
tokens, err := x.cache.Prefix(ctx, prefix)
if err != nil {
return nil, err
}
res := make(map[int]map[string]int)
for key, flagStr := range tokens {
flag, err := strconv.Atoi(flagStr)
if err != nil {
log.ZError(ctx, "token value is not int", err, "key", key, "value", flagStr, "userID", userID)
continue
}
arr := strings.SplitN(strings.TrimPrefix(key, prefix), ":", 2)
if len(arr) != 2 {
log.ZError(ctx, "token value is not int", err, "key", key, "value", flagStr, "userID", userID)
continue
}
platformID, err := strconv.Atoi(arr[0])
if err != nil {
log.ZError(ctx, "token value is not int", err, "key", key, "value", flagStr, "userID", userID)
continue
}
token := arr[1]
if token == "" {
log.ZError(ctx, "token value is not int", err, "key", key, "value", flagStr, "userID", userID)
continue
}
tk, ok := res[platformID]
if !ok {
tk = make(map[string]int)
res[platformID] = tk
}
tk[token] = flag
}
return res, nil
}
func (x *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error {
for token, flag := range m {
err := x.SetTokenFlag(ctx, userID, platformID, token, flag)
if err != nil {
return err
}
}
return nil
}
func (x *tokenCache) BatchSetTokenMapByUidPid(ctx context.Context, tokens map[string]map[string]any) error {
for prefix, tokenFlag := range tokens {
for token, flag := range tokenFlag {
flagStr := fmt.Sprintf("%v", flag)
if err := x.cache.Set(ctx, prefix+":"+token, flagStr, x.accessExpire); err != nil {
return err
}
}
}
return nil
}
func (x *tokenCache) DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error {
keys := make([]string, 0, len(fields))
for _, token := range fields {
keys = append(keys, x.getTokenKey(userID, platformID, token))
}
return x.cache.Del(ctx, keys)
}
func (x *tokenCache) getExpireTime(t int64) time.Duration {
return time.Hour * 24 * time.Duration(t)
}
+63
View File
@@ -0,0 +1,63 @@
package mcache
import (
"context"
"encoding/json"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
"github.com/openimsdk/tools/log"
)
func getCache[V any](ctx context.Context, cache database.Cache, key string, expireTime time.Duration, fn func(ctx context.Context) (V, error)) (V, error) {
getDB := func() (V, bool, error) {
res, err := cache.Get(ctx, []string{key})
if err != nil {
var val V
return val, false, err
}
var val V
if str, ok := res[key]; ok {
if json.Unmarshal([]byte(str), &val) != nil {
return val, false, err
}
return val, true, nil
}
return val, false, nil
}
dbVal, ok, err := getDB()
if err != nil {
return dbVal, err
}
if ok {
return dbVal, nil
}
lockValue, err := cache.Lock(ctx, key, time.Minute)
if err != nil {
return dbVal, err
}
defer func() {
if err := cache.Unlock(ctx, key, lockValue); err != nil {
log.ZError(ctx, "unlock cache key", err, "key", key, "value", lockValue)
}
}()
dbVal, ok, err = getDB()
if err != nil {
return dbVal, err
}
if ok {
return dbVal, nil
}
val, err := fn(ctx)
if err != nil {
return val, err
}
data, err := json.Marshal(val)
if err != nil {
return val, err
}
if err := cache.Set(ctx, key, string(data), expireTime); err != nil {
return val, err
}
return val, nil
}
+50 -13
View File
@@ -3,28 +3,65 @@ package redis
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"time"
"github.com/dtm-labs/rockscache" "github.com/dtm-labs/rockscache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"golang.org/x/sync/singleflight"
"time"
"unsafe"
) )
func getRocksCacheRedisClient(cli *rockscache.Client) redis.UniversalClient { // GetRocksCacheOptions returns the default configuration options for RocksCache.
type Client struct { func GetRocksCacheOptions() *rockscache.Options {
rdb redis.UniversalClient opts := rockscache.NewDefaultOptions()
_ rockscache.Options opts.LockExpire = rocksCacheTimeout
_ singleflight.Group opts.WaitReplicasTimeout = rocksCacheTimeout
} opts.StrongConsistency = true
return (*Client)(unsafe.Pointer(cli)).rdb opts.RandomExpireAdjustment = 0.2
return &opts
} }
func batchGetCache2[K comparable, V any](ctx context.Context, rcClient *rockscache.Client, expire time.Duration, ids []K, idKey func(id K) string, vId func(v *V) K, fn func(ctx context.Context, ids []K) ([]*V, error)) ([]*V, error) { func newRocksCacheClient(rdb redis.UniversalClient) *rocksCacheClient {
if rdb == nil {
return &rocksCacheClient{}
}
rc := &rocksCacheClient{
rdb: rdb,
client: rockscache.NewClient(rdb, *GetRocksCacheOptions()),
}
return rc
}
type rocksCacheClient struct {
rdb redis.UniversalClient
client *rockscache.Client
}
func (x *rocksCacheClient) GetClient() *rockscache.Client {
return x.client
}
func (x *rocksCacheClient) Disable() bool {
return x.client == nil
}
func (x *rocksCacheClient) GetRedis() redis.UniversalClient {
return x.rdb
}
func (x *rocksCacheClient) GetBatchDeleter(topics ...string) cache.BatchDeleter {
return NewBatchDeleterRedis(x, topics)
}
func batchGetCache2[K comparable, V any](ctx context.Context, rcClient *rocksCacheClient, expire time.Duration, ids []K, idKey func(id K) string, vId func(v *V) K, fn func(ctx context.Context, ids []K) ([]*V, error)) ([]*V, error) {
if len(ids) == 0 { if len(ids) == 0 {
return nil, nil return nil, nil
} }
if rcClient.Disable() {
return fn(ctx, ids)
}
findKeys := make([]string, 0, len(ids)) findKeys := make([]string, 0, len(ids))
keyId := make(map[string]K) keyId := make(map[string]K)
for _, id := range ids { for _, id := range ids {
@@ -35,13 +72,13 @@ func batchGetCache2[K comparable, V any](ctx context.Context, rcClient *rockscac
keyId[key] = id keyId[key] = id
findKeys = append(findKeys, key) findKeys = append(findKeys, key)
} }
slotKeys, err := groupKeysBySlot(ctx, getRocksCacheRedisClient(rcClient), findKeys) slotKeys, err := groupKeysBySlot(ctx, rcClient.GetRedis(), findKeys)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result := make([]*V, 0, len(findKeys)) result := make([]*V, 0, len(findKeys))
for _, keys := range slotKeys { for _, keys := range slotKeys {
indexCache, err := rcClient.FetchBatch2(ctx, keys, expire, func(idx []int) (map[int]string, error) { indexCache, err := rcClient.GetClient().FetchBatch2(ctx, keys, expire, func(idx []int) (map[int]string, error) {
queryIds := make([]K, 0, len(idx)) queryIds := make([]K, 0, len(idx))
idIndex := make(map[K]int) idIndex := make(map[K]int)
for _, index := range idx { for _, index := range idx {

Some files were not shown because too many files have changed in this diff Show More