Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2439736830 | |||
| d16a617ba8 | |||
| 942d155d2d | |||
| b7200c163c | |||
| 579db3bd48 | |||
| a0e6d9aa69 | |||
| fbca49d431 | |||
| 0a93fb1b6d | |||
| 78b255396f | |||
| 6f33c0a515 | |||
| c97d63754b | |||
| 1b8a3b0b75 | |||
| b8c4b459fa | |||
| 07badb162f | |||
| 3a1c8df3b9 | |||
| 0e3879aad6 | |||
| 390d253cea | |||
| cbd29a71de | |||
| ea6b7eb525 | |||
| b36f00f2ad | |||
| 7d6682ca4b | |||
| 11358404f9 | |||
| 277da378ea | |||
| bf0289075b | |||
| 219fb04f03 | |||
| 6856a864d0 | |||
| 349a8cd9af | |||
| 929c6704f3 | |||
| 68a735ba99 | |||
| 123abe9803 | |||
| 34971c8b96 |
@@ -1,8 +1,8 @@
|
|||||||
MONGO_IMAGE=mongo:7.0
|
MONGO_IMAGE=mongo:7.0
|
||||||
REDIS_IMAGE=redis:7.0.0
|
REDIS_IMAGE=redis:7.0.0
|
||||||
KAFKA_IMAGE=bitnami/kafka:3.5.1
|
KAFKA_IMAGE=bitnamilegacy/kafka:3.5.1
|
||||||
MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z
|
MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z
|
||||||
ETCD_IMAGE=bitnami/etcd:3.5.13
|
ETCD_IMAGE=bitnamilegacy/etcd:3.5.13
|
||||||
PROMETHEUS_IMAGE=prom/prometheus:v2.45.6
|
PROMETHEUS_IMAGE=prom/prometheus:v2.45.6
|
||||||
ALERTMANAGER_IMAGE=prom/alertmanager:v0.27.0
|
ALERTMANAGER_IMAGE=prom/alertmanager:v0.27.0
|
||||||
GRAFANA_IMAGE=grafana/grafana:11.0.1
|
GRAFANA_IMAGE=grafana/grafana:11.0.1
|
||||||
|
|||||||
@@ -4,42 +4,80 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- release-*
|
- release-*
|
||||||
# tags:
|
|
||||||
# - 'v*'
|
|
||||||
|
|
||||||
release:
|
release:
|
||||||
types: [published]
|
types: [published]
|
||||||
|
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
inputs:
|
inputs:
|
||||||
tag:
|
tag:
|
||||||
description: "Tag version to be used for Docker image"
|
description: "Tag version to be used for Docker image"
|
||||||
required: true
|
required: true
|
||||||
default: "v3.8.0"
|
default: "v3.8.3"
|
||||||
|
|
||||||
|
env:
|
||||||
|
GO_VERSION: "1.22"
|
||||||
|
IMAGE_NAME: "openim-server"
|
||||||
|
# IMAGE_NAME: ${{ github.event.repository.name }}
|
||||||
|
DOCKER_BUILDKIT: 1
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-and-test:
|
publish-docker-images:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.merged == false) }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- name: Checkout main repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
path: main-repo
|
path: main-repo
|
||||||
|
|
||||||
# - name: Set up QEMU
|
- name: Set up QEMU
|
||||||
# uses: docker/setup-qemu-action@v3.3.0
|
uses: docker/setup-qemu-action@v3.3.0
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3.8.0
|
id: buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
- name: Build Docker image
|
|
||||||
id: build
|
|
||||||
uses: docker/build-push-action@v5
|
|
||||||
with:
|
with:
|
||||||
context: ./main-repo
|
driver-opts: network=host
|
||||||
load: true
|
|
||||||
tags: "openim/openim-server:local"
|
- name: Extract metadata for Docker
|
||||||
cache-from: type=gha,scope=build
|
id: meta
|
||||||
cache-to: type=gha,mode=max,scope=build
|
uses: docker/metadata-action@v5.6.0
|
||||||
|
with:
|
||||||
|
images: |
|
||||||
|
${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}
|
||||||
|
ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
|
||||||
|
registry.cn-hangzhou.aliyuncs.com/openimsdk/${{ env.IMAGE_NAME }}
|
||||||
|
tags: |
|
||||||
|
type=ref,event=tag
|
||||||
|
type=schedule
|
||||||
|
type=ref,event=branch
|
||||||
|
type=ref,event=pr
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern=v{{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
type=sha
|
||||||
|
|
||||||
|
- name: Install skopeo
|
||||||
|
run: |
|
||||||
|
sudo apt-get update && sudo apt-get install -y skopeo
|
||||||
|
|
||||||
|
- name: Build multi-arch images as OCI
|
||||||
|
run: |
|
||||||
|
mkdir -p /tmp/oci-image /tmp/docker-cache
|
||||||
|
|
||||||
|
# Build multi-architecture image and save in OCI format
|
||||||
|
docker buildx build \
|
||||||
|
--platform linux/amd64,linux/arm64 \
|
||||||
|
--output type=oci,dest=/tmp/oci-image/multi-arch.tar \
|
||||||
|
--cache-to type=local,dest=/tmp/docker-cache \
|
||||||
|
--cache-from type=gha \
|
||||||
|
./main-repo
|
||||||
|
|
||||||
|
# Use skopeo to convert the amd64 image from OCI format to Docker format and load it
|
||||||
|
skopeo copy --override-arch amd64 oci-archive:/tmp/oci-image/multi-arch.tar docker-daemon:${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:local
|
||||||
|
|
||||||
|
# check image
|
||||||
|
docker image ls | grep openim
|
||||||
|
|
||||||
- name: Checkout compose repository
|
- name: Checkout compose repository
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
@@ -52,11 +90,11 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
IP=$(hostname -I | awk '{print $1}')
|
IP=$(hostname -I | awk '{print $1}')
|
||||||
echo "The IP Address is: $IP"
|
echo "The IP Address is: $IP"
|
||||||
echo "::set-output name=ip::$IP"
|
echo "ip=$IP" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Update .env to use the local image
|
- name: Update .env to use the local image
|
||||||
run: |
|
run: |
|
||||||
sed -i 's|OPENIM_SERVER_IMAGE=.*|OPENIM_SERVER_IMAGE=openim/openim-server:local|' ${{ github.workspace }}/compose-repo/.env
|
sed -i 's|OPENIM_SERVER_IMAGE=.*|OPENIM_SERVER_IMAGE=${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}:local|' ${{ github.workspace }}/compose-repo/.env
|
||||||
sed -i 's|MINIO_EXTERNAL_ADDRESS=.*|MINIO_EXTERNAL_ADDRESS=http://${{ steps.get-ip.outputs.ip }}:10005|' ${{ github.workspace }}/compose-repo/.env
|
sed -i 's|MINIO_EXTERNAL_ADDRESS=.*|MINIO_EXTERNAL_ADDRESS=http://${{ steps.get-ip.outputs.ip }}:10005|' ${{ github.workspace }}/compose-repo/.env
|
||||||
|
|
||||||
- name: Start services using Docker Compose
|
- name: Start services using Docker Compose
|
||||||
@@ -66,23 +104,34 @@ jobs:
|
|||||||
|
|
||||||
docker compose ps
|
docker compose ps
|
||||||
|
|
||||||
- name: Extract metadata for Docker (tags, labels)
|
# - name: Check openim-server health
|
||||||
id: meta
|
# run: |
|
||||||
uses: docker/metadata-action@v5.6.0
|
# timeout=300
|
||||||
with:
|
# interval=30
|
||||||
images: |
|
# elapsed=0
|
||||||
openim/openim-server
|
# while [[ $elapsed -le $timeout ]]; do
|
||||||
ghcr.io/openimsdk/openim-server
|
# if ! docker exec openim-server mage check; then
|
||||||
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server
|
# echo "openim-server is not ready, waiting..."
|
||||||
tags: |
|
# sleep $interval
|
||||||
type=ref,event=tag
|
# elapsed=$(($elapsed + $interval))
|
||||||
type=schedule
|
# else
|
||||||
type=ref,event=branch
|
# echo "Health check successful"
|
||||||
# type=semver,pattern={{version}}
|
# exit 0
|
||||||
type=semver,pattern=v{{version}}
|
# fi
|
||||||
type=semver,pattern=release-{{raw}}
|
# done
|
||||||
type=sha
|
# echo "Health check failed after 5 minutes"
|
||||||
type=raw,value=${{ github.event.inputs.tag }}
|
# 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: Log in to Docker Hub
|
- name: Log in to Docker Hub
|
||||||
uses: docker/login-action@v3.3.0
|
uses: docker/login-action@v3.3.0
|
||||||
@@ -104,22 +153,27 @@ jobs:
|
|||||||
username: ${{ secrets.ALIREGISTRY_USERNAME }}
|
username: ${{ secrets.ALIREGISTRY_USERNAME }}
|
||||||
password: ${{ secrets.ALIREGISTRY_TOKEN }}
|
password: ${{ secrets.ALIREGISTRY_TOKEN }}
|
||||||
|
|
||||||
- name: Push Docker images
|
- name: Push multi-architecture images
|
||||||
uses: docker/build-push-action@v5
|
if: success()
|
||||||
with:
|
run: |
|
||||||
context: ./main-repo
|
docker buildx build \
|
||||||
push: true
|
--platform linux/amd64,linux/arm64 \
|
||||||
platforms: linux/amd64,linux/arm64
|
$(echo "${{ steps.meta.outputs.tags }}" | sed 's/,/ --tag /g' | sed 's/^/--tag /') \
|
||||||
tags: ${{ steps.meta.outputs.tags }}
|
--cache-from type=local,src=/tmp/docker-cache \
|
||||||
labels: ${{ steps.meta.outputs.labels }}
|
--push \
|
||||||
cache-from: type=gha,scope=build
|
./main-repo
|
||||||
cache-to: type=gha,mode=max,scope=build
|
|
||||||
|
|
||||||
- name: Verify multi-platform support
|
- name: Verify multi-platform support
|
||||||
run: |
|
run: |
|
||||||
images=("openim/openim-server" "ghcr.io/openimsdk/openim-server" "registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server")
|
images=(
|
||||||
|
"${{ secrets.DOCKER_USERNAME }}/${{ env.IMAGE_NAME }}"
|
||||||
|
"ghcr.io/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}"
|
||||||
|
"registry.cn-hangzhou.aliyuncs.com/openimsdk/${{ env.IMAGE_NAME }}"
|
||||||
|
)
|
||||||
|
|
||||||
for image in "${images[@]}"; do
|
for image in "${images[@]}"; do
|
||||||
for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n'); do
|
for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n' | cut -d':' -f2); do
|
||||||
|
echo "Verifying multi-arch support for $image:$tag"
|
||||||
manifest=$(docker manifest inspect "$image:$tag" || echo "error")
|
manifest=$(docker manifest inspect "$image:$tag" || echo "error")
|
||||||
if [[ "$manifest" == "error" ]]; then
|
if [[ "$manifest" == "error" ]]; then
|
||||||
echo "Manifest not found for $image:$tag"
|
echo "Manifest not found for $image:$tag"
|
||||||
@@ -135,5 +189,6 @@ jobs:
|
|||||||
echo "Multi-platform support check failed for $image:$tag - missing arm64"
|
echo "Multi-platform support check failed for $image:$tag - missing arm64"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
echo "✅ $image:$tag supports both amd64 and arm64 architectures"
|
||||||
done
|
done
|
||||||
done
|
done
|
||||||
|
|||||||
+244
-4
@@ -1,8 +1,248 @@
|
|||||||
## [v3.8.3-patch.8](https://github.com/openimsdk/open-im-server/releases/tag/v3.8.3-patch.8) (2025-08-13)
|
## [v3.8.3-patch.16](https://github.com/openimsdk/open-im-server/releases/tag/v3.8.3-patch.16) (2026-03-19)
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
* feat: provide the interface required [#2712](https://github.com/openimsdk/open-im-server/pull/2712)
|
||||||
|
* feat: add webhooks of online status and remove zookeeper configuration. [#2716](https://github.com/openimsdk/open-im-server/pull/2716)
|
||||||
|
* feat: Add More Multi Login Policy [#2770](https://github.com/openimsdk/open-im-server/pull/2770)
|
||||||
|
* feat: Push configuration can ignore case sensitivity [#2775](https://github.com/openimsdk/open-im-server/pull/2775)
|
||||||
|
* feat: support app update service [#2794](https://github.com/openimsdk/open-im-server/pull/2794)
|
||||||
|
* feat: implement merge milestone PR to target-branch. [#2796](https://github.com/openimsdk/open-im-server/pull/2796)
|
||||||
|
* feat: support app update service [#2811](https://github.com/openimsdk/open-im-server/pull/2811)
|
||||||
|
* feat: ApplicationVersion move chat [#2813](https://github.com/openimsdk/open-im-server/pull/2813)
|
||||||
|
* feat: Update login policy [#2822](https://github.com/openimsdk/open-im-server/pull/2822)
|
||||||
|
* feat: support stream message [#2824](https://github.com/openimsdk/open-im-server/pull/2824)
|
||||||
|
* feat: merge js sdk [#2856](https://github.com/openimsdk/open-im-server/pull/2856)
|
||||||
|
* feat: Print Panic Log [#2850](https://github.com/openimsdk/open-im-server/pull/2850)
|
||||||
|
* feat: seq user and conversation seq synchronization [#2924](https://github.com/openimsdk/open-im-server/pull/2924)
|
||||||
|
* feat: support aws [#2938](https://github.com/openimsdk/open-im-server/pull/2938)
|
||||||
|
* feat: Prometheus can auto set port [#2943](https://github.com/openimsdk/open-im-server/pull/2943)
|
||||||
|
* feat: Change upload logs systemType to AppFramework. [#2927](https://github.com/openimsdk/open-im-server/pull/2927)
|
||||||
|
* feat: support quote ContentType in SendMsg. [#2819](https://github.com/openimsdk/open-im-server/pull/2819)
|
||||||
|
* feat: Group Monitoring Components, Enable Host Mode && Deprecate reliabilityLevel and unreadCount in notification.yml [#2975](https://github.com/openimsdk/open-im-server/pull/2975)
|
||||||
|
* feat: Add node_exporter in docker-compose [#2979](https://github.com/openimsdk/open-im-server/pull/2979)
|
||||||
|
* feat: Optimize Scheduled Task [#2985](https://github.com/openimsdk/open-im-server/pull/2985)
|
||||||
|
* feat: Optimizing RPC call [#2993](https://github.com/openimsdk/open-im-server/pull/2993)
|
||||||
|
* feat: optimize error stack information [#2995](https://github.com/openimsdk/open-im-server/pull/2995)
|
||||||
|
* feat: config center [#2997](https://github.com/openimsdk/open-im-server/pull/2997)
|
||||||
|
* feat: support message cache [#3007](https://github.com/openimsdk/open-im-server/pull/3007)
|
||||||
|
* feat: optimize log output [#3026](https://github.com/openimsdk/open-im-server/pull/3026)
|
||||||
|
* feat: support GetLastMessage [#3029](https://github.com/openimsdk/open-im-server/pull/3029)
|
||||||
|
* feat: Add enable config center button && fix: grpc connection leakage [#3036](https://github.com/openimsdk/open-im-server/pull/3036)
|
||||||
|
* feat: change appNotificationAccount to appManagerAccount && fix: enable config center add env check && fix: error return [#3038](https://github.com/openimsdk/open-im-server/pull/3038)
|
||||||
|
* feat: SendBusinessNotification supported configuration parameters [#3048](https://github.com/openimsdk/open-im-server/pull/3048)
|
||||||
|
* feat: add backup volume && optimize log print [#3066](https://github.com/openimsdk/open-im-server/pull/3066)
|
||||||
|
* feat: optimize code and support running in single process mode [#3142](https://github.com/openimsdk/open-im-server/pull/3142)
|
||||||
|
* feat: Change after webhook filter && feat SendSimpleMsg [#3151](https://github.com/openimsdk/open-im-server/pull/3151)
|
||||||
|
* feat: the default notification.yml is not configured properly [#3168](https://github.com/openimsdk/open-im-server/pull/3168)
|
||||||
|
* feat: add a new message type: Markdown text [#3162](https://github.com/openimsdk/open-im-server/pull/3162)
|
||||||
|
* feat: add a field to specify whether to send a notification message w… [#3163](https://github.com/openimsdk/open-im-server/pull/3163)
|
||||||
|
* feat: optimizing BatchGetIncrementalGroupMember [#3180](https://github.com/openimsdk/open-im-server/pull/3180)
|
||||||
|
* feat: system account send msg doesn't need friend verify [#3187](https://github.com/openimsdk/open-im-server/pull/3187)
|
||||||
|
* feat: sending messages supports returning fields modified [#3192](https://github.com/openimsdk/open-im-server/pull/3192)
|
||||||
|
* feat: set configs api [#3183](https://github.com/openimsdk/open-im-server/pull/3183)
|
||||||
|
* feat: check if the secret in config/share.yml has been changed during registration [#3223](https://github.com/openimsdk/open-im-server/pull/3223)
|
||||||
|
* feat: Implement webhook in createConversation [#3228](https://github.com/openimsdk/open-im-server/pull/3228)
|
||||||
|
* feat: add a function for business info change to update related conve… [#3225](https://github.com/openimsdk/open-im-server/pull/3225)
|
||||||
|
* feat: add filtering for invalid messages and invalid conversations to… [#3239](https://github.com/openimsdk/open-im-server/pull/3239)
|
||||||
|
* feat: implement stress-test tools. [#3261](https://github.com/openimsdk/open-im-server/pull/3261)
|
||||||
|
* feat: support server-issued configuration, which can be set for individual users [#3271](https://github.com/openimsdk/open-im-server/pull/3271)
|
||||||
|
* feat: GetConversationsHasReadAndMaxSeq support pinned [#3281](https://github.com/openimsdk/open-im-server/pull/3281)
|
||||||
|
* feat: Implement stress test v2. [#3292](https://github.com/openimsdk/open-im-server/pull/3292)
|
||||||
|
* feat: GroupApplicationAgreeMemberEnterNotification splitting [#3297](https://github.com/openimsdk/open-im-server/pull/3297)
|
||||||
|
* feat: optimize server code [#3319](https://github.com/openimsdk/open-im-server/pull/3319)
|
||||||
|
* feat: add rpc interface permission check [#3366](https://github.com/openimsdk/open-im-server/pull/3366)
|
||||||
|
* feat: optimize friend and group applications [#3384](https://github.com/openimsdk/open-im-server/pull/3384)
|
||||||
|
* feat: support distributed lock in crontask. [#3401](https://github.com/openimsdk/open-im-server/pull/3401)
|
||||||
|
* feat: Implement etcd and kafka auth. [#3394](https://github.com/openimsdk/open-im-server/pull/3394)
|
||||||
|
* feat: support redis sentinel. [#3423](https://github.com/openimsdk/open-im-server/pull/3423)
|
||||||
|
* feat: add api logger [#3427](https://github.com/openimsdk/open-im-server/pull/3427)
|
||||||
|
* feat: add nickname for adminUser [#3435](https://github.com/openimsdk/open-im-server/pull/3435)
|
||||||
|
* feat: support mongo replicaset mode. [#3433](https://github.com/openimsdk/open-im-server/pull/3433)
|
||||||
|
* feat: enable redis aof-use-rdb-preamble && disable auto rdb [#3529](https://github.com/openimsdk/open-im-server/pull/3529)
|
||||||
|
* feat: implement auth local cache. [#3533](https://github.com/openimsdk/open-im-server/pull/3533)
|
||||||
|
* feat: add msgDBSave webhook when data save to DB. [#3578](https://github.com/openimsdk/open-im-server/pull/3578)
|
||||||
|
* feat: implement DeleteConversations interface. [#3549](https://github.com/openimsdk/open-im-server/pull/3549)
|
||||||
|
* feat: replace LongConn with ClientConn interface and simplify message handling [#3643](https://github.com/openimsdk/open-im-server/pull/3643)
|
||||||
|
* feat: add error code for handled friend requests and improve error handling in friend operations [#3670](https://github.com/openimsdk/open-im-server/pull/3670)
|
||||||
|
* feat: update protocol support botPlatform [#3696](https://github.com/openimsdk/open-im-server/pull/3696)
|
||||||
|
* feat: gomake upgrade [#3702](https://github.com/openimsdk/open-im-server/pull/3702)
|
||||||
|
|
||||||
### Bug Fixes
|
### Bug Fixes
|
||||||
* fix: fix incorrect kicked logic and PCAndOther Login policy In v3.8.3-patch [#3511](https://github.com/openimsdk/open-im-server/pull/3511)
|
* fix: the message I sent is not set to read seq in mongodb [#2718](https://github.com/openimsdk/open-im-server/pull/2718)
|
||||||
* fix: solve batch incorrect error in Find DocIDs in v3.8.3-patch branch. [#3515](https://github.com/openimsdk/open-im-server/pull/3515)
|
* fix: cannot modify group member avatars [#2719](https://github.com/openimsdk/open-im-server/pull/2719)
|
||||||
|
* fix: auth package import twice [#2724](https://github.com/openimsdk/open-im-server/pull/2724)
|
||||||
|
* fix: join the group chat directly, notification type error [#2772](https://github.com/openimsdk/open-im-server/pull/2772)
|
||||||
|
* fix: change update group member level logic [#2730](https://github.com/openimsdk/open-im-server/pull/2730)
|
||||||
|
* fix: joinSource check args error. [#2773](https://github.com/openimsdk/open-im-server/pull/2773)
|
||||||
|
* fix: Change group member roleLevel can`t send notification [#2777](https://github.com/openimsdk/open-im-server/pull/2777)
|
||||||
|
* fix: client sends message status error to server [#2779](https://github.com/openimsdk/open-im-server/pull/2779)
|
||||||
|
* fix: del UserB's conversation version cache when userA set conversati… [#2785](https://github.com/openimsdk/open-im-server/pull/2785)
|
||||||
|
* fix: improve setConversationAtInfo logic. [#2782](https://github.com/openimsdk/open-im-server/pull/2782)
|
||||||
|
* fix: improve transfer Owner logic when newOwner is mute. [#2790](https://github.com/openimsdk/open-im-server/pull/2790)
|
||||||
|
* fix: improve getUserInfo logic. [#2792](https://github.com/openimsdk/open-im-server/pull/2792)
|
||||||
|
* fix: improve time condition check mehtod. [#2804](https://github.com/openimsdk/open-im-server/pull/2804)
|
||||||
|
* fix: webhook before online push [#2805](https://github.com/openimsdk/open-im-server/pull/2805)
|
||||||
|
* fix: set own read seq in MongoDB when sender send a message. [#2808](https://github.com/openimsdk/open-im-server/pull/2808)
|
||||||
|
* fix: solve err Notification when setGroupInfo. [#2806](https://github.com/openimsdk/open-im-server/pull/2806)
|
||||||
|
* fix: improve condition check. [#2815](https://github.com/openimsdk/open-im-server/pull/2815)
|
||||||
|
* fix: Write back message to Redis [#2836](https://github.com/openimsdk/open-im-server/pull/2836)
|
||||||
|
* fix: get group return repeated result [#2842](https://github.com/openimsdk/open-im-server/pull/2842)
|
||||||
|
* fix: SetConversations can update new conversation [#2838](https://github.com/openimsdk/open-im-server/pull/2838)
|
||||||
|
* fix(push): push content with jpush [#2844](https://github.com/openimsdk/open-im-server/pull/2844)
|
||||||
|
* fix #2860 migrate jpns to jpush [#2861](https://github.com/openimsdk/open-im-server/pull/2861)
|
||||||
|
* fix: concurrent write to websocket connection [#2866](https://github.com/openimsdk/open-im-server/pull/2866)
|
||||||
|
* fix: Remove admin token in redis [#2871](https://github.com/openimsdk/open-im-server/pull/2871)
|
||||||
|
* Fix Push2User webhookBeforeOfflinePush [#2862](https://github.com/openimsdk/open-im-server/pull/2862)
|
||||||
|
* fix: move workflow to correct path [#2837](https://github.com/openimsdk/open-im-server/pull/2837)
|
||||||
|
* fix: del login Policy [#2825](https://github.com/openimsdk/open-im-server/pull/2825)
|
||||||
|
* fix: Wrong Redis Error Check [#2876](https://github.com/openimsdk/open-im-server/pull/2876)
|
||||||
|
* fix: minor log typo [#2881](https://github.com/openimsdk/open-im-server/pull/2881)
|
||||||
|
* fix: webhookAfterSingleMsgRead will be called correctly [#2884](https://github.com/openimsdk/open-im-server/pull/2884)
|
||||||
|
* fix: webhookBeforeSendSingleMsg will call before black and friend check [#2885](https://github.com/openimsdk/open-im-server/pull/2885)
|
||||||
|
* fix: Wrong Redis Error Check [#2891](https://github.com/openimsdk/open-im-server/pull/2891)
|
||||||
|
* fix: improve crontask delete outdated Data. [#2901](https://github.com/openimsdk/open-im-server/pull/2901)
|
||||||
|
* fix: go mod [#2906](https://github.com/openimsdk/open-im-server/pull/2906)
|
||||||
|
* fix: group member update face_url [#2910](https://github.com/openimsdk/open-im-server/pull/2910)
|
||||||
|
* fix: update set seq implement. [#2911](https://github.com/openimsdk/open-im-server/pull/2911)
|
||||||
|
* fix https://github.com/openimsdk/open-im-server/issues/2895 [#2896](https://github.com/openimsdk/open-im-server/pull/2896)
|
||||||
|
* fix: Can choose whether to set the port. [#2929](https://github.com/openimsdk/open-im-server/pull/2929)
|
||||||
|
* fix: Configure move service discovery into discovery [#2934](https://github.com/openimsdk/open-im-server/pull/2934)
|
||||||
|
* fix: compilation failed under Windows [#2940](https://github.com/openimsdk/open-im-server/pull/2940)
|
||||||
|
* fix: server can return isEnd to control fetch messages when sdk pull … [#2949](https://github.com/openimsdk/open-im-server/pull/2949)
|
||||||
|
* fix:Only print panic function frame && feat: log.ZPanic [#2947](https://github.com/openimsdk/open-im-server/pull/2947)
|
||||||
|
* fix: seq user and conversation seq synchronization [#2958](https://github.com/openimsdk/open-im-server/pull/2958)
|
||||||
|
* fix: fetch message return isEnd and endSeq panic. [#2959](https://github.com/openimsdk/open-im-server/pull/2959)
|
||||||
|
* fix: rpc panic recover [#2957](https://github.com/openimsdk/open-im-server/pull/2957)
|
||||||
|
* fix: modifying other fields while setting IsPrivateChat does not take effect [#2972](https://github.com/openimsdk/open-im-server/pull/2972)
|
||||||
|
* fix: when fetching a referenced message, it indicates that the original message has been deleted. [#2977](https://github.com/openimsdk/open-im-server/pull/2977)
|
||||||
|
* fix: when unable EnableHistoryForNewMembers, new group member can read last one message. [#3001](https://github.com/openimsdk/open-im-server/pull/3001)
|
||||||
|
* fix: redis save error when KickTokens [#3002](https://github.com/openimsdk/open-im-server/pull/3002)
|
||||||
|
* fix: The message @ information will be set only for members in the gr… [#3009](https://github.com/openimsdk/open-im-server/pull/3009)
|
||||||
|
* fix: restart permission check [#3011](https://github.com/openimsdk/open-im-server/pull/3011)
|
||||||
|
* fix: The system cannot be restarted the first time the configuration is set. [#3013](https://github.com/openimsdk/open-im-server/pull/3013)
|
||||||
|
* fix: jssdk not init [#3016](https://github.com/openimsdk/open-im-server/pull/3016)
|
||||||
|
* fix: online status error [#3022](https://github.com/openimsdk/open-im-server/pull/3022)
|
||||||
|
* fix: GetUsersOnline returns an error in the online list [#3040](https://github.com/openimsdk/open-im-server/pull/3040)
|
||||||
|
* fix: seq conversion failed without exiting [#3052](https://github.com/openimsdk/open-im-server/pull/3052)
|
||||||
|
* fix: check error in BatchSetTokenMapByUidPid [#3076](https://github.com/openimsdk/open-im-server/pull/3076)
|
||||||
|
* fix: DeleteDoc crash [#3078](https://github.com/openimsdk/open-im-server/pull/3078)
|
||||||
|
* fix: the abnormal message has no sending time, causing the SDK to be abnormal [#3087](https://github.com/openimsdk/open-im-server/pull/3087)
|
||||||
|
* fix: crash caused [#3100](https://github.com/openimsdk/open-im-server/pull/3100)
|
||||||
|
* fix: the user sets the conversation timer cleanup timestamp unit incorrectly [#3102](https://github.com/openimsdk/open-im-server/pull/3102)
|
||||||
|
* fix: solve workflows stop when merge failed [#3106](https://github.com/openimsdk/open-im-server/pull/3106)
|
||||||
|
* fix: seq conversion not reading env in docker environment [#3130](https://github.com/openimsdk/open-im-server/pull/3130)
|
||||||
|
* fix: the source message of the reference is withdrawn, and the referenced message is deleted [#3137](https://github.com/openimsdk/open-im-server/pull/3137)
|
||||||
|
* fix: Offline push does not have a badge && Android offline push [#3146](https://github.com/openimsdk/open-im-server/pull/3146)
|
||||||
|
* fix: PCAndOther multi login policy can`t get old clients correctly [#3158](https://github.com/openimsdk/open-im-server/pull/3158)
|
||||||
|
* fix: solve uncorrect notification when set group info [#3172](https://github.com/openimsdk/open-im-server/pull/3172)
|
||||||
|
* fix: the sorting is wrong after canceling the administrator in group settings [#3185](https://github.com/openimsdk/open-im-server/pull/3185)
|
||||||
|
* fix: solve uncorrect GroupMember enter group notification type. [#3188](https://github.com/openimsdk/open-im-server/pull/3188)
|
||||||
|
* fix: solve unocrrect invite notification [#3213](https://github.com/openimsdk/open-im-server/pull/3213)
|
||||||
|
* fix: AdminToken save to redis && limit 1 for each userID [#3224](https://github.com/openimsdk/open-im-server/pull/3224)
|
||||||
|
* fix: improve stress test tools parms. [#3265](https://github.com/openimsdk/open-im-server/pull/3265)
|
||||||
|
* fix: oss specifies content-type when uploading [#3267](https://github.com/openimsdk/open-im-server/pull/3267)
|
||||||
|
* fix: transferring the group owner to a muted member, incremental version error [#3284](https://github.com/openimsdk/open-im-server/pull/3284)
|
||||||
|
* fix: group status in GroupDismissedNotification [#3286](https://github.com/openimsdk/open-im-server/pull/3286)
|
||||||
|
* fix: data version SetVersion will add record [#3304](https://github.com/openimsdk/open-im-server/pull/3304)
|
||||||
|
* fix: delete token [#3313](https://github.com/openimsdk/open-im-server/pull/3313)
|
||||||
|
* fix: optimize grpc option and fix some interface permission checks [#3327](https://github.com/openimsdk/open-im-server/pull/3327)
|
||||||
|
* fix: standalone mode cannot be used [#3360](https://github.com/openimsdk/open-im-server/pull/3360)
|
||||||
|
* fix: solve user not found when notification invitedUserID is zero in … [#3375](https://github.com/openimsdk/open-im-server/pull/3375)
|
||||||
|
* fix: send simple msg [#3362](https://github.com/openimsdk/open-im-server/pull/3362)
|
||||||
|
* fix: solve updateUserInfoEx null pointer. [#3326](https://github.com/openimsdk/open-im-server/pull/3326)
|
||||||
|
* fix: add rpc interface permission check [#3377](https://github.com/openimsdk/open-im-server/pull/3377)
|
||||||
|
* fix: optimize friend and group applications [#3389](https://github.com/openimsdk/open-im-server/pull/3389)
|
||||||
|
* fix redis config db field [#3395](https://github.com/openimsdk/open-im-server/pull/3395)
|
||||||
|
* fix: prometheus discovery [#3408](https://github.com/openimsdk/open-im-server/pull/3408)
|
||||||
|
* fix: import friends send notification [#3420](https://github.com/openimsdk/open-im-server/pull/3420)
|
||||||
|
* fix: improve mileston PR workflows contents. [#3382](https://github.com/openimsdk/open-im-server/pull/3382)
|
||||||
|
* fix: solve webhook incorrect attentionID references. [#3411](https://github.com/openimsdk/open-im-server/pull/3411)
|
||||||
|
* fix: solve `createTime` not set in setConversation and Create Conversation. [#3447](https://github.com/openimsdk/open-im-server/pull/3447)
|
||||||
|
* fix: update log level in crontask dist look. [#3440](https://github.com/openimsdk/open-im-server/pull/3440)
|
||||||
|
* fix: use safe submodule init in workflows. [#3468](https://github.com/openimsdk/open-im-server/pull/3468)
|
||||||
|
* fix: fix incorrect kicked logic. [#3480](https://github.com/openimsdk/open-im-server/pull/3480)
|
||||||
|
* fix: added AtUserIDList to the @ message for API sending. [#3472](https://github.com/openimsdk/open-im-server/pull/3472)
|
||||||
|
* fix: solve batch incorrect error in Find DocIDs [#3476](https://github.com/openimsdk/open-im-server/pull/3476)
|
||||||
|
* fix: correctly aggregate read seqs [#3442](https://github.com/openimsdk/open-im-server/pull/3442)
|
||||||
|
* fix: performance issues with Kafka caused [#3485](https://github.com/openimsdk/open-im-server/pull/3485)
|
||||||
|
* fix: searchMessage method has potential NPE bug [Created [#3289](https://github.com/openimsdk/open-im-server/pull/3289)
|
||||||
|
* fix: admin token in standalone mode [#3499](https://github.com/openimsdk/open-im-server/pull/3499)
|
||||||
|
* fix: revert contentType in API msg [#3509](https://github.com/openimsdk/open-im-server/pull/3509)
|
||||||
|
* fix: optimize to lru local cache. [#3514](https://github.com/openimsdk/open-im-server/pull/3514)
|
||||||
|
* fix: fill in the most recent sendTime for a gap message to prevent th… [#3522](https://github.com/openimsdk/open-im-server/pull/3522)
|
||||||
|
* fix: solve incorrect batchGetIncrGroupMember when group dismissed. [#3526](https://github.com/openimsdk/open-im-server/pull/3526)
|
||||||
|
* fix: GetSortedConversationList nil pointer when chatlog not found. [#3531](https://github.com/openimsdk/open-im-server/pull/3531)
|
||||||
|
* fix: switch kafka & etcd image namespace to bitnamilegacy [#3555](https://github.com/openimsdk/open-im-server/pull/3555)
|
||||||
|
* fix: solve incorrect time.Unix and logger asyncwrite [#3584](https://github.com/openimsdk/open-im-server/pull/3584)
|
||||||
|
* fix: db manager [#3600](https://github.com/openimsdk/open-im-server/pull/3600)
|
||||||
|
* fix: update JSON field names to camelCase in conversation structs [#3609](https://github.com/openimsdk/open-im-server/pull/3609)
|
||||||
|
* Fix: Resolved the issue of incorrect generation of conversationID [#3581](https://github.com/openimsdk/open-im-server/pull/3581)
|
||||||
|
* fix: solve msg wsHandler panic. [#3595](https://github.com/openimsdk/open-im-server/pull/3595)
|
||||||
|
* fix: resolve deadlock in cache eviction and improve GetBatch implementation and full id version [#3591](https://github.com/openimsdk/open-im-server/pull/3591)
|
||||||
|
* fix: reset user conversation seq when rejoining group to resolve message recall issue [#3640](https://github.com/openimsdk/open-im-server/pull/3640)
|
||||||
|
* fix(group): move member count retrieval after member deletion for accurate updates [#3651](https://github.com/openimsdk/open-im-server/pull/3651)
|
||||||
|
* fix(group): set max_seq to 0 when join group [#3649](https://github.com/openimsdk/open-im-server/pull/3649)
|
||||||
|
* fix: Mongo Malloc upsert overwrites min_seq initialization [#3657](https://github.com/openimsdk/open-im-server/pull/3657)
|
||||||
|
|
||||||
**Full Changelog**: [v3.8.3-patch.7...v3.8.3-patch.8](https://github.com/openimsdk/open-im-server/compare/v3.8.3-patch.7...v3.8.3-patch.8)
|
### Chores
|
||||||
|
* chore: remove unused content [#2786](https://github.com/openimsdk/open-im-server/pull/2786)
|
||||||
|
* chore: update admin front image version [#2893](https://github.com/openimsdk/open-im-server/pull/2893)
|
||||||
|
|
||||||
|
### Refactors
|
||||||
|
* refactor: Refactor rpc call && auto gen rpc_call code [#2969](https://github.com/openimsdk/open-im-server/pull/2969)
|
||||||
|
* refactor: improve workflows logic. [#3072](https://github.com/openimsdk/open-im-server/pull/3072)
|
||||||
|
* refactor: change sendNotification to sendMessage to avoid ambiguity regarding message sending behavior. [#3173](https://github.com/openimsdk/open-im-server/pull/3173)
|
||||||
|
* refactor: improve setConversations method. [#3194](https://github.com/openimsdk/open-im-server/pull/3194)
|
||||||
|
* refactor: move stress-test tools location. [#3295](https://github.com/openimsdk/open-im-server/pull/3295)
|
||||||
|
* refactor: support modified config and args in mage. [#3466](https://github.com/openimsdk/open-im-server/pull/3466)
|
||||||
|
|
||||||
|
### Builds
|
||||||
|
* build: improve workflows logic. [#2801](https://github.com/openimsdk/open-im-server/pull/2801)
|
||||||
|
* build: implement version file update when release. [#2826](https://github.com/openimsdk/open-im-server/pull/2826)
|
||||||
|
* build: update mongo and kafka start logic. [#2858](https://github.com/openimsdk/open-im-server/pull/2858)
|
||||||
|
* build: create changelog tool and workflows. [#2869](https://github.com/openimsdk/open-im-server/pull/2869)
|
||||||
|
* build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 [#2851](https://github.com/openimsdk/open-im-server/pull/2851)
|
||||||
|
* build: update Server version. [#2887](https://github.com/openimsdk/open-im-server/pull/2887)
|
||||||
|
* build: implement services image build and CI release. [#2920](https://github.com/openimsdk/open-im-server/pull/2920)
|
||||||
|
* build: update kubernetes deployment Run. [#2919](https://github.com/openimsdk/open-im-server/pull/2919)
|
||||||
|
* build: fix uncorrect path. [#3020](https://github.com/openimsdk/open-im-server/pull/3020)
|
||||||
|
* build: fix docker images build. [#3024](https://github.com/openimsdk/open-im-server/pull/3024)
|
||||||
|
* build: keep conflict is true. [#3103](https://github.com/openimsdk/open-im-server/pull/3103)
|
||||||
|
* build: comment out admin services. [#3537](https://github.com/openimsdk/open-im-server/pull/3537)
|
||||||
|
* build: improve publish docker image workflow. [#3552](https://github.com/openimsdk/open-im-server/pull/3552)
|
||||||
|
* build: add sdk version log in registerClient [#3574](https://github.com/openimsdk/open-im-server/pull/3574)
|
||||||
|
|
||||||
|
### Others
|
||||||
|
* Revert: Change group member roleLevel can`t send notification [#2789](https://github.com/openimsdk/open-im-server/pull/2789)
|
||||||
|
* Introducing OpenIM Guru on Gurubase.io [#2788](https://github.com/openimsdk/open-im-server/pull/2788)
|
||||||
|
* revert: write msg to redis [#2883](https://github.com/openimsdk/open-im-server/pull/2883)
|
||||||
|
* Add a lead time for the token's issuance time. [#2914](https://github.com/openimsdk/open-im-server/pull/2914)
|
||||||
|
* docs: improve deployment docs in kubernetes. [#2973](https://github.com/openimsdk/open-im-server/pull/2973)
|
||||||
|
* update: env image version [#3055](https://github.com/openimsdk/open-im-server/pull/3055)
|
||||||
|
* License [#3293](https://github.com/openimsdk/open-im-server/pull/3293)
|
||||||
|
* docs: update slack link. [#3479](https://github.com/openimsdk/open-im-server/pull/3479)
|
||||||
|
* Update CHANGELOG for release v3.8.3-patch.1 [#3164](https://github.com/openimsdk/open-im-server/pull/3164)
|
||||||
|
* Update CHANGELOG for release v3.8.3-patch.2 [#3175](https://github.com/openimsdk/open-im-server/pull/3175)
|
||||||
|
* Update CHANGELOG for release v3.8.3-patch.3 [#3206](https://github.com/openimsdk/open-im-server/pull/3206)
|
||||||
|
* Update CHANGELOG for release v3.8.3-patch.4 [#3226](https://github.com/openimsdk/open-im-server/pull/3226)
|
||||||
|
* Update CHANGELOG for release v3.8.3-patch.5 [#3405](https://github.com/openimsdk/open-im-server/pull/3405)
|
||||||
|
* Update CHANGELOG for release v3.8.3-patch.6 [#3473](https://github.com/openimsdk/open-im-server/pull/3473)
|
||||||
|
* docs: update readme of config file. [#3356](https://github.com/openimsdk/open-im-server/pull/3356)
|
||||||
|
* Build: Implement rate limiting and circuit breaker for API and RPC services. [#3572](https://github.com/openimsdk/open-im-server/pull/3572)
|
||||||
|
* merge: pre-release-v3.8.4 [#3623](https://github.com/openimsdk/open-im-server/pull/3623)
|
||||||
|
* Simplify iOS background push gating (#3611) [#3612](https://github.com/openimsdk/open-im-server/pull/3612)
|
||||||
|
* bugfix(conversation):removed unexpectedly called functions and itself… [#3668](https://github.com/openimsdk/open-im-server/pull/3668)
|
||||||
|
* @lkzz made their first contribution in https://github.com/openimsdk/open-im-server/pull/2724
|
||||||
|
[#2724](https://github.com/openimsdk/open-im-server/pull/2724)
|
||||||
|
* @alilestera made their first contribution in https://github.com/openimsdk/open-im-server/pull/2773
|
||||||
|
[#2773](https://github.com/openimsdk/open-im-server/pull/2773)
|
||||||
|
* @kursataktas made their first contribution in https://github.com/openimsdk/open-im-server/pull/2788
|
||||||
|
[#2788](https://github.com/openimsdk/open-im-server/pull/2788)
|
||||||
|
* @yoyo930021 made their first contribution in https://github.com/openimsdk/open-im-server/pull/2844
|
||||||
|
[#2844](https://github.com/openimsdk/open-im-server/pull/2844)
|
||||||
|
* @wikylyu made their first contribution in https://github.com/openimsdk/open-im-server/pull/2861
|
||||||
|
[#2861](https://github.com/openimsdk/open-im-server/pull/2861)
|
||||||
|
* @storyn26383 made their first contribution in https://github.com/openimsdk/open-im-server/pull/2862
|
||||||
|
[#2862](https://github.com/openimsdk/open-im-server/pull/2862)
|
||||||
|
* @morya made their first contribution in https://github.com/openimsdk/open-im-server/pull/2881
|
||||||
[#2881](https://github.com/openimsdk/open-im-server/pull/2881)
|
[#2881](https://github.com/openimsdk/open-im-server/pull/2881)
|
||||||
|
|||||||
@@ -1,3 +1,10 @@
|
|||||||
|
auth:
|
||||||
|
topic: DELETE_CACHE_AUTH
|
||||||
|
slotNum: 100
|
||||||
|
slotSize: 2000
|
||||||
|
successExpire: 300
|
||||||
|
failedExpire: 5
|
||||||
|
|
||||||
user:
|
user:
|
||||||
topic: DELETE_CACHE_USER
|
topic: DELETE_CACHE_USER
|
||||||
slotNum: 100
|
slotNum: 100
|
||||||
|
|||||||
@@ -17,3 +17,13 @@ prometheus:
|
|||||||
ports:
|
ports:
|
||||||
# This address can be accessed via a browser
|
# This address can be accessed via a browser
|
||||||
grafanaURL:
|
grafanaURL:
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|||||||
@@ -26,3 +26,20 @@ longConnSvr:
|
|||||||
websocketMaxMsgLen: 4096
|
websocketMaxMsgLen: 4096
|
||||||
# WebSocket connection handshake timeout in seconds
|
# WebSocket connection handshake timeout in seconds
|
||||||
websocketTimeout: 10
|
websocketTimeout: 10
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|
||||||
|
circuitBreaker:
|
||||||
|
enable: false
|
||||||
|
window: 5s # Time window size (seconds)
|
||||||
|
bucket: 100 # Number of buckets
|
||||||
|
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||||
|
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||||
@@ -6,3 +6,20 @@ prometheus:
|
|||||||
# List of ports that Prometheus listens on; each port corresponds to an instance of monitoring. Ensure these are managed accordingly
|
# List of ports that Prometheus listens on; each port corresponds to an instance of monitoring. Ensure these are managed accordingly
|
||||||
# It will only take effect when autoSetPorts is set to false.
|
# It will only take effect when autoSetPorts is set to false.
|
||||||
ports:
|
ports:
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|
||||||
|
circuitBreaker:
|
||||||
|
enable: false
|
||||||
|
window: 5s # Time window size (seconds)
|
||||||
|
bucket: 100 # Number of buckets
|
||||||
|
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||||
|
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||||
+17
-1
@@ -10,10 +10,26 @@ rpc:
|
|||||||
# It will only take effect when autoSetPorts is set to false.
|
# It will only take effect when autoSetPorts is set to false.
|
||||||
ports:
|
ports:
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|
||||||
|
circuitBreaker:
|
||||||
|
enable: false
|
||||||
|
window: 5s # Time window size (seconds)
|
||||||
|
bucket: 100 # Number of buckets
|
||||||
|
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||||
|
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||||
|
|
||||||
prometheus:
|
prometheus:
|
||||||
# Enable or disable Prometheus monitoring
|
# Enable or disable Prometheus monitoring
|
||||||
enable: true
|
enable: false
|
||||||
# List of ports that Prometheus listens on; these must match the number of rpc.ports to ensure correct monitoring setup
|
# List of ports that Prometheus listens on; these must match the number of rpc.ports to ensure correct monitoring setup
|
||||||
# It will only take effect when autoSetPorts is set to false.
|
# It will only take effect when autoSetPorts is set to false.
|
||||||
ports:
|
ports:
|
||||||
|
|||||||
@@ -20,3 +20,20 @@ prometheus:
|
|||||||
tokenPolicy:
|
tokenPolicy:
|
||||||
# Token validity period, in days
|
# Token validity period, in days
|
||||||
expire: 90
|
expire: 90
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|
||||||
|
circuitBreaker:
|
||||||
|
enable: false
|
||||||
|
window: 5s # Time window size (seconds)
|
||||||
|
bucket: 100 # Number of buckets
|
||||||
|
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||||
|
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||||
@@ -16,3 +16,20 @@ prometheus:
|
|||||||
# List of ports that Prometheus listens on; these must match the number of rpc.ports to ensure correct monitoring setup
|
# List of ports that Prometheus listens on; these must match the number of rpc.ports to ensure correct monitoring setup
|
||||||
# It will only take effect when autoSetPorts is set to false.
|
# It will only take effect when autoSetPorts is set to false.
|
||||||
ports:
|
ports:
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|
||||||
|
circuitBreaker:
|
||||||
|
enable: false
|
||||||
|
window: 5s # Time window size (seconds)
|
||||||
|
bucket: 100 # Number of buckets
|
||||||
|
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||||
|
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||||
@@ -16,3 +16,20 @@ prometheus:
|
|||||||
# List of ports that Prometheus listens on; these must match the number of rpc.ports to ensure correct monitoring setup
|
# List of ports that Prometheus listens on; these must match the number of rpc.ports to ensure correct monitoring setup
|
||||||
# It will only take effect when autoSetPorts is set to false.
|
# It will only take effect when autoSetPorts is set to false.
|
||||||
ports:
|
ports:
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|
||||||
|
circuitBreaker:
|
||||||
|
enable: false
|
||||||
|
window: 5s # Time window size (seconds)
|
||||||
|
bucket: 100 # Number of buckets
|
||||||
|
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||||
|
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||||
@@ -19,3 +19,20 @@ prometheus:
|
|||||||
|
|
||||||
|
|
||||||
enableHistoryForNewMembers: true
|
enableHistoryForNewMembers: true
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|
||||||
|
circuitBreaker:
|
||||||
|
enable: false
|
||||||
|
window: 5s # Time window size (seconds)
|
||||||
|
bucket: 100 # Number of buckets
|
||||||
|
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||||
|
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||||
@@ -20,3 +20,20 @@ prometheus:
|
|||||||
|
|
||||||
# Does sending messages require friend verification
|
# Does sending messages require friend verification
|
||||||
friendVerify: false
|
friendVerify: false
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|
||||||
|
circuitBreaker:
|
||||||
|
enable: false
|
||||||
|
window: 5s # Time window size (seconds)
|
||||||
|
bucket: 100 # Number of buckets
|
||||||
|
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||||
|
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||||
@@ -17,6 +17,22 @@ prometheus:
|
|||||||
# It will only take effect when autoSetPorts is set to false.
|
# It will only take effect when autoSetPorts is set to false.
|
||||||
ports:
|
ports:
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|
||||||
|
circuitBreaker:
|
||||||
|
enable: false
|
||||||
|
window: 5s # Time window size (seconds)
|
||||||
|
bucket: 100 # Number of buckets
|
||||||
|
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||||
|
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||||
|
|
||||||
object:
|
object:
|
||||||
# Use MinIO as object storage, or set to "cos", "oss", "kodo", "aws", while also configuring the corresponding settings
|
# Use MinIO as object storage, or set to "cos", "oss", "kodo", "aws", while also configuring the corresponding settings
|
||||||
|
|||||||
@@ -16,3 +16,20 @@ prometheus:
|
|||||||
# Prometheus listening ports, must be consistent with the number of rpc.ports
|
# Prometheus listening ports, must be consistent with the number of rpc.ports
|
||||||
# It will only take effect when autoSetPorts is set to false.
|
# It will only take effect when autoSetPorts is set to false.
|
||||||
ports:
|
ports:
|
||||||
|
|
||||||
|
ratelimiter:
|
||||||
|
# Whether to enable rate limiting
|
||||||
|
enable: false
|
||||||
|
# WindowSize defines time duration per window
|
||||||
|
window: 20s
|
||||||
|
# BucketNum defines bucket number for each window
|
||||||
|
bucket: 500
|
||||||
|
# CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
cpuThreshold: 850
|
||||||
|
|
||||||
|
circuitBreaker:
|
||||||
|
enable: false
|
||||||
|
window: 5s # Time window size (seconds)
|
||||||
|
bucket: 100 # Number of buckets
|
||||||
|
success: 0.6 # Success rate threshold (0.6 means 60%)
|
||||||
|
request: 500 # Request threshold; circuit breaker evaluation occurs when reached
|
||||||
@@ -41,6 +41,9 @@ afterSendGroupMsg:
|
|||||||
attentionIds: []
|
attentionIds: []
|
||||||
# See beforeSendSingleMsg comment.
|
# See beforeSendSingleMsg comment.
|
||||||
deniedTypes: []
|
deniedTypes: []
|
||||||
|
afterMsgSaveDB:
|
||||||
|
enable: false
|
||||||
|
timeout: 5
|
||||||
afterUserOnline:
|
afterUserOnline:
|
||||||
enable: false
|
enable: false
|
||||||
timeout: 5
|
timeout: 5
|
||||||
|
|||||||
+14
-10
@@ -63,7 +63,12 @@ services:
|
|||||||
restart: always
|
restart: always
|
||||||
sysctls:
|
sysctls:
|
||||||
net.core.somaxconn: 1024
|
net.core.somaxconn: 1024
|
||||||
command: redis-server /usr/local/redis/config/redis.conf --requirepass openIM123 --appendonly yes
|
command: >
|
||||||
|
redis-server
|
||||||
|
--requirepass openIM123
|
||||||
|
--appendonly yes
|
||||||
|
--aof-use-rdb-preamble yes
|
||||||
|
--save ""
|
||||||
networks:
|
networks:
|
||||||
- openim
|
- openim
|
||||||
|
|
||||||
@@ -208,7 +213,6 @@ services:
|
|||||||
# Defines which listener is used for inter-broker communication within the Kafka cluster
|
# Defines which listener is used for inter-broker communication within the Kafka cluster
|
||||||
KAFKA_CFG_INTER_BROKER_LISTENER_NAME: "INTERNAL"
|
KAFKA_CFG_INTER_BROKER_LISTENER_NAME: "INTERNAL"
|
||||||
|
|
||||||
|
|
||||||
# Authentication configuration variables - comment out to disable auth
|
# Authentication configuration variables - comment out to disable auth
|
||||||
# KAFKA_USERNAME: "openIM"
|
# KAFKA_USERNAME: "openIM"
|
||||||
# KAFKA_PASSWORD: "openIM123"
|
# KAFKA_PASSWORD: "openIM123"
|
||||||
@@ -262,14 +266,14 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- openim
|
- openim
|
||||||
|
|
||||||
openim-admin-front:
|
# openim-admin-front:
|
||||||
image: ${OPENIM_ADMIN_FRONT_IMAGE}
|
# image: ${OPENIM_ADMIN_FRONT_IMAGE}
|
||||||
container_name: openim-admin-front
|
# container_name: openim-admin-front
|
||||||
restart: always
|
# restart: always
|
||||||
ports:
|
# ports:
|
||||||
- "11002:80"
|
# - "11002:80"
|
||||||
networks:
|
# networks:
|
||||||
- openim
|
# - openim
|
||||||
|
|
||||||
prometheus:
|
prometheus:
|
||||||
image: ${PROMETHEUS_IMAGE}
|
image: ${PROMETHEUS_IMAGE}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
module github.com/openimsdk/open-im-server/v3
|
module github.com/openimsdk/open-im-server/v3
|
||||||
|
|
||||||
go 1.22.7
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
firebase.google.com/go/v4 v4.14.1
|
firebase.google.com/go/v4 v4.14.1
|
||||||
@@ -12,21 +12,22 @@ 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.73-alpha.12
|
github.com/openimsdk/protocol v0.0.73-alpha.19
|
||||||
github.com/openimsdk/tools v0.0.50-alpha.97
|
github.com/openimsdk/tools v0.0.50-alpha.113
|
||||||
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.11.1
|
||||||
go.mongodb.org/mongo-driver v1.14.0
|
go.mongodb.org/mongo-driver v1.14.0
|
||||||
google.golang.org/api v0.170.0
|
google.golang.org/api v0.170.0
|
||||||
google.golang.org/grpc v1.68.0
|
google.golang.org/grpc v1.71.0
|
||||||
google.golang.org/protobuf v1.35.1
|
google.golang.org/protobuf v1.36.4
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
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
|
||||||
@@ -34,14 +35,14 @@ require (
|
|||||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||||
github.com/kelindar/bitmap v1.5.2
|
github.com/kelindar/bitmap v1.5.2
|
||||||
github.com/likexian/gokit v0.25.13
|
github.com/likexian/gokit v0.25.13
|
||||||
github.com/openimsdk/gomake v0.0.15-alpha.11
|
github.com/openimsdk/gomake v0.0.17
|
||||||
github.com/redis/go-redis/v9 v9.4.0
|
github.com/redis/go-redis/v9 v9.4.0
|
||||||
github.com/robfig/cron/v3 v3.0.1
|
github.com/robfig/cron/v3 v3.0.1
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible
|
github.com/shirou/gopsutil v3.21.11+incompatible
|
||||||
github.com/spf13/viper v1.18.2
|
github.com/spf13/viper v1.18.2
|
||||||
go.etcd.io/etcd/client/v3 v3.5.13
|
go.etcd.io/etcd/client/v3 v3.5.13
|
||||||
go.uber.org/automaxprocs v1.5.3
|
go.uber.org/automaxprocs v1.5.3
|
||||||
golang.org/x/sync v0.8.0
|
golang.org/x/sync v0.10.0
|
||||||
k8s.io/api v0.31.2
|
k8s.io/api v0.31.2
|
||||||
k8s.io/apimachinery v0.31.2
|
k8s.io/apimachinery v0.31.2
|
||||||
k8s.io/client-go v0.31.2
|
k8s.io/client-go v0.31.2
|
||||||
@@ -49,12 +50,11 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.112.1 // indirect
|
cloud.google.com/go v0.112.1 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||||
cloud.google.com/go/firestore v1.15.0 // indirect
|
cloud.google.com/go/firestore v1.15.0 // indirect
|
||||||
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
|
||||||
@@ -76,6 +76,7 @@ require (
|
|||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect
|
||||||
github.com/aws/smithy-go v1.22.1 // indirect
|
github.com/aws/smithy-go v1.22.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
@@ -90,15 +91,16 @@ require (
|
|||||||
github.com/eapache/go-resiliency v1.6.0 // indirect
|
github.com/eapache/go-resiliency v1.6.0 // indirect
|
||||||
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
|
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect
|
||||||
github.com/eapache/queue v1.1.0 // indirect
|
github.com/eapache/queue v1.1.0 // indirect
|
||||||
|
github.com/ebitengine/purego v0.10.0 // indirect
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.2 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
github.com/go-openapi/jsonpointer v0.19.6 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
github.com/go-openapi/jsonreference v0.20.2 // indirect
|
||||||
github.com/go-openapi/swag v0.22.4 // indirect
|
github.com/go-openapi/swag v0.22.4 // indirect
|
||||||
@@ -108,7 +110,7 @@ require (
|
|||||||
github.com/golang/protobuf v1.5.4 // indirect
|
github.com/golang/protobuf v1.5.4 // indirect
|
||||||
github.com/golang/snappy v0.0.4 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/google/gnostic-models v0.6.8 // indirect
|
github.com/google/gnostic-models v0.6.8 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.7.0 // indirect
|
||||||
github.com/google/go-querystring v1.1.0 // indirect
|
github.com/google/go-querystring v1.1.0 // indirect
|
||||||
github.com/google/gofuzz v1.2.0 // indirect
|
github.com/google/gofuzz v1.2.0 // indirect
|
||||||
github.com/google/s2a-go v0.1.7 // indirect
|
github.com/google/s2a-go v0.1.7 // indirect
|
||||||
@@ -135,6 +137,7 @@ require (
|
|||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.4.0 // indirect
|
||||||
github.com/lestrrat-go/strftime v1.0.6 // indirect
|
github.com/lestrrat-go/strftime v1.0.6 // indirect
|
||||||
github.com/lithammer/shortuuid v3.0.0+incompatible // indirect
|
github.com/lithammer/shortuuid v3.0.0+incompatible // indirect
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 // indirect
|
||||||
github.com/magefile/mage v1.15.0 // indirect
|
github.com/magefile/mage v1.15.0 // indirect
|
||||||
github.com/magiconair/properties v1.8.7 // indirect
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
github.com/mailru/easyjson v0.7.7 // indirect
|
github.com/mailru/easyjson v0.7.7 // indirect
|
||||||
@@ -151,6 +154,7 @@ require (
|
|||||||
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/prometheus/client_model v0.5.0 // indirect
|
github.com/prometheus/client_model v0.5.0 // indirect
|
||||||
github.com/prometheus/common v0.45.0 // indirect
|
github.com/prometheus/common v0.45.0 // indirect
|
||||||
github.com/prometheus/procfs v0.12.0 // indirect
|
github.com/prometheus/procfs v0.12.0 // indirect
|
||||||
@@ -159,6 +163,10 @@ require (
|
|||||||
github.com/rs/xid v1.5.0 // indirect
|
github.com/rs/xid v1.5.0 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
github.com/sagikazarmark/locafero v0.4.0 // indirect
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/sercand/kuberesolver/v6 v6.0.1 // indirect
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5 // indirect
|
||||||
|
github.com/shirou/gopsutil/v4 v4.26.2 // indirect
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
||||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
github.com/spf13/afero v1.11.0 // indirect
|
github.com/spf13/afero v1.11.0 // indirect
|
||||||
github.com/spf13/cast v1.6.0 // indirect
|
github.com/spf13/cast v1.6.0 // indirect
|
||||||
@@ -166,8 +174,8 @@ require (
|
|||||||
github.com/stretchr/objx v0.5.2 // indirect
|
github.com/stretchr/objx v0.5.2 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.47 // indirect
|
github.com/tencentyun/cos-go-sdk-v5 v0.7.47 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.13 // indirect
|
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||||
github.com/tklauser/numcpus v0.7.0 // indirect
|
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/x448/float16 v0.8.4 // indirect
|
github.com/x448/float16 v0.8.4 // indirect
|
||||||
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
|
||||||
@@ -178,26 +186,27 @@ require (
|
|||||||
go.etcd.io/etcd/api/v3 v3.5.13 // indirect
|
go.etcd.io/etcd/api/v3 v3.5.13 // indirect
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect
|
go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect
|
||||||
go.opencensus.io v0.24.0 // indirect
|
go.opencensus.io v0.24.0 // indirect
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
go.opentelemetry.io/otel v1.34.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
go.opentelemetry.io/otel/metric v1.34.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
go.opentelemetry.io/otel/trace v1.34.0 // indirect
|
||||||
go.uber.org/atomic v1.9.0 // indirect
|
go.uber.org/atomic v1.9.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
golang.org/x/arch v0.7.0 // indirect
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
|
||||||
golang.org/x/image v0.15.0 // indirect
|
golang.org/x/image v0.15.0 // indirect
|
||||||
golang.org/x/net v0.29.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/oauth2 v0.23.0 // indirect
|
golang.org/x/oauth2 v0.25.0 // indirect
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
golang.org/x/sys v0.42.0 // indirect
|
||||||
golang.org/x/term v0.24.0 // indirect
|
golang.org/x/term v0.28.0 // indirect
|
||||||
golang.org/x/text v0.18.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/appengine/v2 v2.0.2 // indirect
|
google.golang.org/appengine/v2 v2.0.2 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f // indirect
|
||||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gorm.io/gorm v1.25.8 // indirect
|
gorm.io/gorm v1.25.8 // indirect
|
||||||
@@ -216,6 +225,6 @@ require (
|
|||||||
github.com/spf13/cobra v1.8.0
|
github.com/spf13/cobra v1.8.0
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
go.uber.org/zap v1.24.0 // indirect
|
go.uber.org/zap v1.24.0 // indirect
|
||||||
golang.org/x/crypto v0.27.0 // indirect
|
golang.org/x/crypto v0.32.0 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||||
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
||||||
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I=
|
||||||
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg=
|
||||||
cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
|
cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
|
||||||
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
|
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
|
||||||
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
|
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
|
||||||
@@ -61,6 +61,8 @@ github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLj
|
|||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs=
|
||||||
|
github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
@@ -103,6 +105,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4A
|
|||||||
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
|
github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0=
|
||||||
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc=
|
||||||
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
|
||||||
|
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||||
|
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
|
||||||
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
@@ -117,8 +121,8 @@ github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8
|
|||||||
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
|
||||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
|
||||||
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
|
||||||
@@ -134,8 +138,9 @@ github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
|||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
|
||||||
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs=
|
||||||
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE=
|
||||||
@@ -202,8 +207,8 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||||
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
||||||
@@ -303,6 +308,8 @@ github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVk
|
|||||||
github.com/likexian/gokit v0.25.13/go.mod h1:qQhEWFBEfqLCO3/vOEo2EDKd+EycekVtUK4tex+l2H4=
|
github.com/likexian/gokit v0.25.13/go.mod h1:qQhEWFBEfqLCO3/vOEo2EDKd+EycekVtUK4tex+l2H4=
|
||||||
github.com/lithammer/shortuuid v3.0.0+incompatible h1:NcD0xWW/MZYXEHa6ITy6kaXN5nwm/V115vj2YXfhS0w=
|
github.com/lithammer/shortuuid v3.0.0+incompatible h1:NcD0xWW/MZYXEHa6ITy6kaXN5nwm/V115vj2YXfhS0w=
|
||||||
github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHTK2Tciko1/vKuIKS9dSkDrA4w=
|
github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHTK2Tciko1/vKuIKS9dSkDrA4w=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88 h1:PTw+yKnXcOFCR6+8hHTyWBeQ/P4Nb7dd4/0ohEcWQuM=
|
||||||
|
github.com/lufia/plan9stats v0.0.0-20260216142805-b3301c5f2a88/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg=
|
||||||
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
@@ -345,12 +352,12 @@ github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA
|
|||||||
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
|
||||||
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
|
github.com/onsi/gomega v1.25.0 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.11 h1:PQudYDRESYeYlUYrrLLJhYIlUPO5x7FAx+o5El9U/Bw=
|
github.com/openimsdk/gomake v0.0.17 h1:q8haP48VOH45WhJRiLj1YSBJyUFJqD8CTedH65i1YH8=
|
||||||
github.com/openimsdk/gomake v0.0.15-alpha.11/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
|
github.com/openimsdk/gomake v0.0.17/go.mod h1:nnjS8yCtrPJAt1knMbyPiUwCH2gpyBzj/EZAONfUOXg=
|
||||||
github.com/openimsdk/protocol v0.0.73-alpha.12 h1:2NYawXeHChYUeSme6QJ9pOLh+Empce2WmwEtbP4JvKk=
|
github.com/openimsdk/protocol v0.0.73-alpha.19 h1:CvXoDF2U73UcMhLnrtMFks2Aw+bXiDgH8AITEt783/s=
|
||||||
github.com/openimsdk/protocol v0.0.73-alpha.12/go.mod h1:WF7EuE55vQvpyUAzDXcqg+B+446xQyEba0X35lTINmw=
|
github.com/openimsdk/protocol v0.0.73-alpha.19/go.mod h1:WF7EuE55vQvpyUAzDXcqg+B+446xQyEba0X35lTINmw=
|
||||||
github.com/openimsdk/tools v0.0.50-alpha.97 h1:6ik5w3PpgDG6VjSo3nb3FT/fxN3JX7iIARVxVu9g7VY=
|
github.com/openimsdk/tools v0.0.50-alpha.113 h1:rhLWaSJuhjgJFNVzmpChLCG7dPXS0+bte+CPI0008Us=
|
||||||
github.com/openimsdk/tools v0.0.50-alpha.97/go.mod h1:n2poR3asX1e1XZce4O+MOWAp+X02QJRFvhcLCXZdzRo=
|
github.com/openimsdk/tools v0.0.50-alpha.113/go.mod h1:x9i/e+WJFW4tocy6RNJQ9NofQiP3KJ1Y576/06TqOG4=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.2.2 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=
|
||||||
@@ -361,6 +368,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
|
||||||
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
github.com/prashantv/gostub v1.1.0/go.mod h1:A5zLQHz7ieHGG7is6LLXLz7I8+3LZzsrV0P1IAHhP5U=
|
||||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||||
@@ -384,8 +393,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
|||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
@@ -393,8 +402,18 @@ github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6ke
|
|||||||
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/sercand/kuberesolver/v6 v6.0.1 h1:XZUTA0gy/lgDYp/UhEwv7Js24F1j8NJ833QrWv0Xux4=
|
||||||
|
github.com/sercand/kuberesolver/v6 v6.0.1/go.mod h1:C0tsTuRMONSY+Xf7pv7RMW1/JlewY1+wS8SZE+1lf1s=
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI=
|
||||||
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.26.2 h1:X8i6sicvUFih4BmYIGT1m2wwgw2VG9YgrDTi7cIRGUI=
|
||||||
|
github.com/shirou/gopsutil/v4 v4.26.2/go.mod h1:LZ6ewCSkBqUpvSOf+LsTGnRinC6iaNUNMGBtDkJBaLQ=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
||||||
|
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
||||||
|
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
||||||
|
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
||||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
@@ -420,18 +439,19 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
|||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
|
||||||
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
|
||||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.47 h1:uoS4Sob16qEYoapkqJq1D1Vnsy9ira9BfNUMtoFYTI4=
|
github.com/tencentyun/cos-go-sdk-v5 v0.7.47 h1:uoS4Sob16qEYoapkqJq1D1Vnsy9ira9BfNUMtoFYTI4=
|
||||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.47/go.mod h1:DH9US8nB+AJXqwu/AMOrCFN1COv3dpytXuJWHgdg7kE=
|
github.com/tencentyun/cos-go-sdk-v5 v0.7.47/go.mod h1:DH9US8nB+AJXqwu/AMOrCFN1COv3dpytXuJWHgdg7kE=
|
||||||
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
|
github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA=
|
||||||
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
|
github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI=
|
||||||
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
|
github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw=
|
||||||
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
|
github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
@@ -461,18 +481,22 @@ go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd
|
|||||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY=
|
||||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI=
|
||||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ=
|
||||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE=
|
||||||
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
|
go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A=
|
||||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU=
|
||||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk=
|
||||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w=
|
||||||
|
go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k=
|
||||||
|
go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE=
|
||||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||||
@@ -493,8 +517,8 @@ golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5y
|
|||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
||||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
|
||||||
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
|
||||||
@@ -522,25 +546,26 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
|
|||||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
||||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
golang.org/x/oauth2 v0.25.0 h1:CY4y7XT9v0cRI9oupztF8AgiIu99L/ksR/Xp/6jrZ70=
|
||||||
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.25.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@@ -549,14 +574,14 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
||||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
|
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||||
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
|
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
@@ -565,8 +590,8 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
|||||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@@ -597,17 +622,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
|||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
||||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422 h1:GVIKPyP/kLIyVOgOnTwFOrvQaQUzOzGMCxgFUOEmm24=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250106144421-5f5ef82da422/go.mod h1:b6h1vNKhxaSoEI+5jc3PJUCustfli/mRab7295pY7rw=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f h1:OxYkA3wjPsZyBylwymxSHa7ViiW1Sml4ToBrncvFehI=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||||
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -617,8 +642,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
||||||
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
|||||||
@@ -76,3 +76,7 @@ func (o *ConversationApi) GetPinnedConversationIDs(c *gin.Context) {
|
|||||||
func (o *ConversationApi) UpdateConversationsByUser(c *gin.Context) {
|
func (o *ConversationApi) UpdateConversationsByUser(c *gin.Context) {
|
||||||
a2r.Call(c, conversation.ConversationClient.UpdateConversationsByUser, o.Client)
|
a2r.Call(c, conversation.ConversationClient.UpdateConversationsByUser, o.Client)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *ConversationApi) DeleteConversations(c *gin.Context) {
|
||||||
|
a2r.Call(c, conversation.ConversationClient.DeleteConversations, o.Client)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
|
||||||
|
"github.com/openimsdk/tools/apiresp"
|
||||||
|
"github.com/openimsdk/tools/errs"
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
|
"github.com/openimsdk/tools/stability/ratelimit"
|
||||||
|
"github.com/openimsdk/tools/stability/ratelimit/bbr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RateLimiter struct {
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
Window time.Duration `yaml:"window"` // time duration per window
|
||||||
|
Bucket int `yaml:"bucket"` // bucket number for each window
|
||||||
|
CPUThreshold int64 `yaml:"cpuThreshold"` // CPU threshold; valid range 0–1000 (1000 = 100%)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RateLimitMiddleware(config *RateLimiter) gin.HandlerFunc {
|
||||||
|
if !config.Enable {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
limiter := bbr.NewBBRLimiter(
|
||||||
|
bbr.WithWindow(config.Window),
|
||||||
|
bbr.WithBucket(config.Bucket),
|
||||||
|
bbr.WithCPUThreshold(config.CPUThreshold),
|
||||||
|
)
|
||||||
|
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
status := limiter.Stat()
|
||||||
|
|
||||||
|
c.Header("X-BBR-CPU", strconv.FormatInt(status.CPU, 10))
|
||||||
|
c.Header("X-BBR-MinRT", strconv.FormatInt(status.MinRt, 10))
|
||||||
|
c.Header("X-BBR-MaxPass", strconv.FormatInt(status.MaxPass, 10))
|
||||||
|
c.Header("X-BBR-MaxInFlight", strconv.FormatInt(status.MaxInFlight, 10))
|
||||||
|
c.Header("X-BBR-InFlight", strconv.FormatInt(status.InFlight, 10))
|
||||||
|
|
||||||
|
done, err := limiter.Allow()
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
c.Header("X-RateLimit-Policy", "BBR")
|
||||||
|
c.Header("Retry-After", calculateBBRRetryAfter(status))
|
||||||
|
c.Header("X-RateLimit-Limit", strconv.FormatInt(status.MaxInFlight, 10))
|
||||||
|
c.Header("X-RateLimit-Remaining", "0") // There is no concept of remaining quota in BBR.
|
||||||
|
|
||||||
|
fmt.Println("rate limited:", err, "path:", c.Request.URL.Path)
|
||||||
|
log.ZWarn(c, "rate limited", err, "path", c.Request.URL.Path)
|
||||||
|
c.AbortWithStatus(http.StatusTooManyRequests)
|
||||||
|
apiresp.GinError(c, errs.NewCodeError(http.StatusTooManyRequests, "too many requests, please try again later"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
done(ratelimit.DoneInfo{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateBBRRetryAfter(status bbr.Stat) string {
|
||||||
|
loadRatio := float64(status.CPU) / float64(status.CPU)
|
||||||
|
|
||||||
|
if loadRatio < 0.8 {
|
||||||
|
return "1"
|
||||||
|
}
|
||||||
|
if loadRatio < 0.95 {
|
||||||
|
return "2"
|
||||||
|
}
|
||||||
|
|
||||||
|
backoff := 1 + int64(math.Pow(loadRatio-0.95, 2)*50)
|
||||||
|
if backoff > 5 {
|
||||||
|
backoff = 5
|
||||||
|
}
|
||||||
|
return strconv.FormatInt(backoff, 10)
|
||||||
|
}
|
||||||
@@ -97,6 +97,18 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
|||||||
case BestSpeed:
|
case BestSpeed:
|
||||||
r.Use(gzip.Gzip(gzip.BestSpeed))
|
r.Use(gzip.Gzip(gzip.BestSpeed))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Use rate limiter middleware
|
||||||
|
if cfg.API.RateLimiter.Enable {
|
||||||
|
rl := &RateLimiter{
|
||||||
|
Enable: cfg.API.RateLimiter.Enable,
|
||||||
|
Window: cfg.API.RateLimiter.Window,
|
||||||
|
Bucket: cfg.API.RateLimiter.Bucket,
|
||||||
|
CPUThreshold: cfg.API.RateLimiter.CPUThreshold,
|
||||||
|
}
|
||||||
|
r.Use(RateLimitMiddleware(rl))
|
||||||
|
}
|
||||||
|
|
||||||
if config.Standalone() {
|
if config.Standalone() {
|
||||||
r.Use(func(c *gin.Context) {
|
r.Use(func(c *gin.Context) {
|
||||||
c.Set(authverify.CtxAdminUserIDsKey, cfg.Share.IMAdminUser.UserIDs)
|
c.Set(authverify.CtxAdminUserIDsKey, cfg.Share.IMAdminUser.UserIDs)
|
||||||
@@ -277,6 +289,8 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
|
|||||||
conversationGroup.POST("/get_owner_conversation", c.GetOwnerConversation)
|
conversationGroup.POST("/get_owner_conversation", c.GetOwnerConversation)
|
||||||
conversationGroup.POST("/get_not_notify_conversation_ids", c.GetNotNotifyConversationIDs)
|
conversationGroup.POST("/get_not_notify_conversation_ids", c.GetNotNotifyConversationIDs)
|
||||||
conversationGroup.POST("/get_pinned_conversation_ids", c.GetPinnedConversationIDs)
|
conversationGroup.POST("/get_pinned_conversation_ids", c.GetPinnedConversationIDs)
|
||||||
|
conversationGroup.POST("/delete_conversations", c.DeleteConversations)
|
||||||
|
conversationGroup.POST("/update_conversations_by_user", c.UpdateConversationsByUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|||||||
+12
-151
@@ -16,7 +16,6 @@ package msggateway
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -31,7 +30,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/utils/stringutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -64,12 +62,13 @@ type PingPongHandler func(string) error
|
|||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
w *sync.Mutex
|
w *sync.Mutex
|
||||||
conn LongConn
|
conn ClientConn
|
||||||
PlatformID int `json:"platformID"`
|
PlatformID int `json:"platformID"`
|
||||||
IsCompress bool `json:"isCompress"`
|
IsCompress bool `json:"isCompress"`
|
||||||
UserID string `json:"userID"`
|
UserID string `json:"userID"`
|
||||||
IsBackground bool `json:"isBackground"`
|
IsBackground bool `json:"isBackground"`
|
||||||
SDKType string `json:"sdkType"`
|
SDKType string `json:"sdkType"`
|
||||||
|
SDKVersion string `json:"sdkVersion"`
|
||||||
Encoder Encoder
|
Encoder Encoder
|
||||||
ctx *UserConnContext
|
ctx *UserConnContext
|
||||||
longConnServer LongConnServer
|
longConnServer LongConnServer
|
||||||
@@ -83,10 +82,10 @@ type Client struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ResetClient updates the client's state with new connection and context information.
|
// ResetClient updates the client's state with new connection and context information.
|
||||||
func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer LongConnServer) {
|
func (c *Client) ResetClient(ctx *UserConnContext, conn ClientConn, longConnServer LongConnServer) {
|
||||||
c.w = new(sync.Mutex)
|
c.w = new(sync.Mutex)
|
||||||
c.conn = conn
|
c.conn = conn
|
||||||
c.PlatformID = stringutil.StringToInt(ctx.GetPlatformID())
|
c.PlatformID = ctx.GetPlatformID()
|
||||||
c.IsCompress = ctx.GetCompression()
|
c.IsCompress = ctx.GetCompression()
|
||||||
c.IsBackground = ctx.GetBackground()
|
c.IsBackground = ctx.GetBackground()
|
||||||
c.UserID = ctx.GetUserID()
|
c.UserID = ctx.GetUserID()
|
||||||
@@ -97,6 +96,7 @@ func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer
|
|||||||
c.closedErr = nil
|
c.closedErr = nil
|
||||||
c.token = ctx.GetToken()
|
c.token = ctx.GetToken()
|
||||||
c.SDKType = ctx.GetSDKType()
|
c.SDKType = ctx.GetSDKType()
|
||||||
|
c.SDKVersion = ctx.GetSDKVersion()
|
||||||
c.hbCtx, c.hbCancel = context.WithCancel(c.ctx)
|
c.hbCtx, c.hbCancel = context.WithCancel(c.ctx)
|
||||||
c.subLock = new(sync.Mutex)
|
c.subLock = new(sync.Mutex)
|
||||||
if c.subUserIDs != nil {
|
if c.subUserIDs != nil {
|
||||||
@@ -110,22 +110,6 @@ func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer
|
|||||||
c.subUserIDs = make(map[string]struct{})
|
c.subUserIDs = make(map[string]struct{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) pingHandler(appData string) error {
|
|
||||||
if err := c.conn.SetReadDeadline(pongWait); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.ZDebug(c.ctx, "ping Handler Success.", "appData", appData)
|
|
||||||
return c.writePongMsg(appData)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) pongHandler(_ string) error {
|
|
||||||
if err := c.conn.SetReadDeadline(pongWait); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// readMessage continuously reads messages from the connection.
|
// readMessage continuously reads messages from the connection.
|
||||||
func (c *Client) readMessage() {
|
func (c *Client) readMessage() {
|
||||||
defer func() {
|
defer func() {
|
||||||
@@ -136,52 +120,25 @@ func (c *Client) readMessage() {
|
|||||||
c.close()
|
c.close()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
c.conn.SetReadLimit(maxMessageSize)
|
|
||||||
_ = c.conn.SetReadDeadline(pongWait)
|
|
||||||
c.conn.SetPongHandler(c.pongHandler)
|
|
||||||
c.conn.SetPingHandler(c.pingHandler)
|
|
||||||
c.activeHeartbeat(c.hbCtx)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
log.ZDebug(c.ctx, "readMessage")
|
log.ZDebug(c.ctx, "readMessage")
|
||||||
messageType, message, returnErr := c.conn.ReadMessage()
|
message, returnErr := c.conn.ReadMessage()
|
||||||
if returnErr != nil {
|
if returnErr != nil {
|
||||||
log.ZWarn(c.ctx, "readMessage", returnErr, "messageType", messageType)
|
log.ZWarn(c.ctx, "readMessage", returnErr)
|
||||||
c.closedErr = returnErr
|
c.closedErr = returnErr
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.ZDebug(c.ctx, "readMessage", "messageType", messageType)
|
|
||||||
if c.closed.Load() {
|
if c.closed.Load() {
|
||||||
// The scenario where the connection has just been closed, but the coroutine has not exited
|
// The scenario where the connection has just been closed, but the coroutine has not exited
|
||||||
c.closedErr = ErrConnClosed
|
c.closedErr = ErrConnClosed
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch messageType {
|
parseDataErr := c.handleMessage(message)
|
||||||
case MessageBinary:
|
if parseDataErr != nil {
|
||||||
_ = c.conn.SetReadDeadline(pongWait)
|
c.closedErr = parseDataErr
|
||||||
parseDataErr := c.handleMessage(message)
|
|
||||||
if parseDataErr != nil {
|
|
||||||
c.closedErr = parseDataErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case MessageText:
|
|
||||||
_ = c.conn.SetReadDeadline(pongWait)
|
|
||||||
parseDataErr := c.handlerTextMessage(message)
|
|
||||||
if parseDataErr != nil {
|
|
||||||
c.closedErr = parseDataErr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case PingMessage:
|
|
||||||
err := c.writePongMsg("")
|
|
||||||
log.ZError(c.ctx, "writePongMsg", err)
|
|
||||||
|
|
||||||
case CloseMessage:
|
|
||||||
c.closedErr = ErrClientClosed
|
|
||||||
return
|
return
|
||||||
|
|
||||||
default:
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -356,109 +313,13 @@ func (c *Client) writeBinaryMsg(resp Resp) error {
|
|||||||
c.w.Lock()
|
c.w.Lock()
|
||||||
defer c.w.Unlock()
|
defer c.w.Unlock()
|
||||||
|
|
||||||
err = c.conn.SetWriteDeadline(writeWait)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.IsCompress {
|
if c.IsCompress {
|
||||||
resultBuf, compressErr := c.longConnServer.CompressWithPool(encodedBuf)
|
resultBuf, compressErr := c.longConnServer.CompressWithPool(encodedBuf)
|
||||||
if compressErr != nil {
|
if compressErr != nil {
|
||||||
return compressErr
|
return compressErr
|
||||||
}
|
}
|
||||||
return c.conn.WriteMessage(MessageBinary, resultBuf)
|
return c.conn.WriteMessage(resultBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.conn.WriteMessage(MessageBinary, encodedBuf)
|
return c.conn.WriteMessage(encodedBuf)
|
||||||
}
|
|
||||||
|
|
||||||
// Actively initiate Heartbeat when platform in Web.
|
|
||||||
func (c *Client) activeHeartbeat(ctx context.Context) {
|
|
||||||
if c.PlatformID == constant.WebPlatformID {
|
|
||||||
go func() {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
log.ZPanic(ctx, "activeHeartbeat Panic", errs.ErrPanic(r))
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
log.ZDebug(ctx, "server initiative send heartbeat start.")
|
|
||||||
ticker := time.NewTicker(pingPeriod)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-ticker.C:
|
|
||||||
if err := c.writePingMsg(); err != nil {
|
|
||||||
log.ZWarn(c.ctx, "send Ping Message error.", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
case <-c.hbCtx.Done():
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func (c *Client) writePingMsg() error {
|
|
||||||
if c.closed.Load() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.w.Lock()
|
|
||||||
defer c.w.Unlock()
|
|
||||||
|
|
||||||
err := c.conn.SetWriteDeadline(writeWait)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.conn.WriteMessage(PingMessage, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) writePongMsg(appData string) error {
|
|
||||||
log.ZDebug(c.ctx, "write Pong Msg in Server", "appData", appData)
|
|
||||||
if c.closed.Load() {
|
|
||||||
log.ZWarn(c.ctx, "is closed in server", nil, "appdata", appData, "closed err", c.closedErr)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.w.Lock()
|
|
||||||
defer c.w.Unlock()
|
|
||||||
|
|
||||||
err := c.conn.SetWriteDeadline(writeWait)
|
|
||||||
if err != nil {
|
|
||||||
log.ZWarn(c.ctx, "SetWriteDeadline in Server have error", errs.Wrap(err), "writeWait", writeWait, "appData", appData)
|
|
||||||
return errs.Wrap(err)
|
|
||||||
}
|
|
||||||
err = c.conn.WriteMessage(PongMessage, []byte(appData))
|
|
||||||
if err != nil {
|
|
||||||
log.ZWarn(c.ctx, "Write Message have error", errs.Wrap(err), "Pong msg", PongMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
return errs.Wrap(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) handlerTextMessage(b []byte) error {
|
|
||||||
var msg TextMessage
|
|
||||||
if err := json.Unmarshal(b, &msg); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
switch msg.Type {
|
|
||||||
case TextPong:
|
|
||||||
return nil
|
|
||||||
case TextPing:
|
|
||||||
msg.Type = TextPong
|
|
||||||
msgData, err := json.Marshal(msg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
c.w.Lock()
|
|
||||||
defer c.w.Unlock()
|
|
||||||
if err := c.conn.SetWriteDeadline(writeWait); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return c.conn.WriteMessage(MessageText, msgData)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("not support message type %s", msg.Type)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,229 @@
|
|||||||
|
package msggateway
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrWriteFull = fmt.Errorf("websocket write buffer full,close connection")
|
||||||
|
|
||||||
|
type ClientConn interface {
|
||||||
|
ReadMessage() ([]byte, error)
|
||||||
|
WriteMessage(message []byte) error
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type websocketMessage struct {
|
||||||
|
MessageType int
|
||||||
|
Data []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWebSocketClientConn(conn *websocket.Conn, readLimit int64, readTimeout time.Duration, pingInterval time.Duration) ClientConn {
|
||||||
|
c := &websocketClientConn{
|
||||||
|
readTimeout: readTimeout,
|
||||||
|
conn: conn,
|
||||||
|
writer: make(chan *websocketMessage, 256),
|
||||||
|
done: make(chan struct{}),
|
||||||
|
}
|
||||||
|
if readLimit > 0 {
|
||||||
|
c.conn.SetReadLimit(readLimit)
|
||||||
|
}
|
||||||
|
c.conn.SetPingHandler(c.pingHandler)
|
||||||
|
c.conn.SetPongHandler(c.pongHandler)
|
||||||
|
|
||||||
|
go c.loopSend()
|
||||||
|
if pingInterval > 0 {
|
||||||
|
go c.doPing(pingInterval)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
type websocketClientConn struct {
|
||||||
|
readTimeout time.Duration
|
||||||
|
conn *websocket.Conn
|
||||||
|
writer chan *websocketMessage
|
||||||
|
done chan struct{}
|
||||||
|
err atomic.Pointer[error]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) ReadMessage() ([]byte, error) {
|
||||||
|
buf, err := c.readMessage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, c.closeBy(fmt.Errorf("read message %w", err))
|
||||||
|
}
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) WriteMessage(message []byte) error {
|
||||||
|
return c.writeMessage(websocket.BinaryMessage, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) Close() error {
|
||||||
|
return c.closeBy(fmt.Errorf("websocket connection closed"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) closeBy(err error) error {
|
||||||
|
if !c.err.CompareAndSwap(nil, &err) {
|
||||||
|
return *c.err.Load()
|
||||||
|
}
|
||||||
|
close(c.done)
|
||||||
|
log.ZWarn(context.Background(), "websocket connection closed", err, "remoteAddr", c.conn.RemoteAddr(),
|
||||||
|
"chan length", len(c.writer))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) writeMessage(messageType int, data []byte) error {
|
||||||
|
if errPtr := c.err.Load(); errPtr != nil {
|
||||||
|
return *errPtr
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case c.writer <- &websocketMessage{MessageType: messageType, Data: data}:
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return c.closeBy(ErrWriteFull)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) loopSend() {
|
||||||
|
defer func() {
|
||||||
|
_ = c.conn.Close()
|
||||||
|
}()
|
||||||
|
var err error
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case msg := <-c.writer:
|
||||||
|
switch msg.MessageType {
|
||||||
|
case websocket.TextMessage, websocket.BinaryMessage:
|
||||||
|
err = c.conn.WriteMessage(msg.MessageType, msg.Data)
|
||||||
|
default:
|
||||||
|
err = c.conn.WriteControl(msg.MessageType, msg.Data, time.Time{})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
_ = c.closeBy(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case msg := <-c.writer:
|
||||||
|
switch msg.MessageType {
|
||||||
|
case websocket.TextMessage, websocket.BinaryMessage:
|
||||||
|
err = c.conn.WriteMessage(msg.MessageType, msg.Data)
|
||||||
|
default:
|
||||||
|
err = c.conn.WriteControl(msg.MessageType, msg.Data, time.Time{})
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
_ = c.closeBy(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) setReadDeadline() error {
|
||||||
|
deadline := time.Now().Add(c.readTimeout)
|
||||||
|
return c.conn.SetReadDeadline(deadline)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) readMessage() ([]byte, error) {
|
||||||
|
for {
|
||||||
|
if err := c.setReadDeadline(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
messageType, buf, err := c.conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
switch messageType {
|
||||||
|
case websocket.BinaryMessage:
|
||||||
|
return buf, nil
|
||||||
|
case websocket.TextMessage:
|
||||||
|
if err := c.onReadTextMessage(buf); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case websocket.PingMessage:
|
||||||
|
if err := c.pingHandler(string(buf)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case websocket.PongMessage:
|
||||||
|
if err := c.pongHandler(string(buf)); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
case websocket.CloseMessage:
|
||||||
|
if len(buf) == 0 {
|
||||||
|
return nil, errors.New("websocket connection closed by peer")
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("websocket connection closed by peer, data %s", string(buf))
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown websocket message type %d", messageType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) onReadTextMessage(buf []byte) error {
|
||||||
|
var msg struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Body json.RawMessage `json:"body"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(buf, &msg); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch msg.Type {
|
||||||
|
case TextPong:
|
||||||
|
return nil
|
||||||
|
case TextPing:
|
||||||
|
msg.Type = TextPong
|
||||||
|
msgData, err := json.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return c.writeMessage(websocket.TextMessage, msgData)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("not support text message type %s", msg.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) pingHandler(appData string) error {
|
||||||
|
log.ZDebug(context.Background(), "ping handler recv ping", "remoteAddr", c.conn.RemoteAddr(), "appData", appData)
|
||||||
|
if err := c.setReadDeadline(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err := c.conn.WriteControl(websocket.PongMessage, []byte(appData), time.Now().Add(time.Second*1))
|
||||||
|
if err != nil {
|
||||||
|
log.ZWarn(context.Background(), "ping handler write pong error", err, "remoteAddr", c.conn.RemoteAddr(), "appData", appData)
|
||||||
|
}
|
||||||
|
log.ZDebug(context.Background(), "ping handler write pong success", "remoteAddr", c.conn.RemoteAddr(), "appData", appData)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) pongHandler(string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *websocketClientConn) doPing(d time.Duration) {
|
||||||
|
ticker := time.NewTicker(d)
|
||||||
|
defer ticker.Stop()
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-c.done:
|
||||||
|
return
|
||||||
|
case <-ticker.C:
|
||||||
|
if err := c.writeMessage(websocket.PingMessage, nil); err != nil {
|
||||||
|
_ = c.closeBy(fmt.Errorf("send ping %w", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,7 @@ const (
|
|||||||
BackgroundStatus = "isBackground"
|
BackgroundStatus = "isBackground"
|
||||||
SendResponse = "isMsgResp"
|
SendResponse = "isMsgResp"
|
||||||
SDKType = "sdkType"
|
SDKType = "sdkType"
|
||||||
|
SDKVersion = "sdkVersion"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
+144
-82
@@ -15,18 +15,32 @@
|
|||||||
package msggateway
|
package msggateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
|
||||||
|
|
||||||
"github.com/openimsdk/protocol/constant"
|
"github.com/openimsdk/protocol/constant"
|
||||||
"github.com/openimsdk/tools/utils/encrypt"
|
"github.com/openimsdk/tools/utils/encrypt"
|
||||||
"github.com/openimsdk/tools/utils/stringutil"
|
|
||||||
"github.com/openimsdk/tools/utils/timeutil"
|
"github.com/openimsdk/tools/utils/timeutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type UserConnContextInfo struct {
|
||||||
|
Token string `json:"token"`
|
||||||
|
UserID string `json:"userID"`
|
||||||
|
PlatformID int `json:"platformID"`
|
||||||
|
OperationID string `json:"operationID"`
|
||||||
|
Compression string `json:"compression"`
|
||||||
|
SDKType string `json:"sdkType"`
|
||||||
|
SendResponse bool `json:"sendResponse"`
|
||||||
|
Background bool `json:"background"`
|
||||||
|
SDKVersion string `json:"sdkVersion"`
|
||||||
|
}
|
||||||
|
|
||||||
type UserConnContext struct {
|
type UserConnContext struct {
|
||||||
RespWriter http.ResponseWriter
|
RespWriter http.ResponseWriter
|
||||||
Req *http.Request
|
Req *http.Request
|
||||||
@@ -34,6 +48,7 @@ type UserConnContext struct {
|
|||||||
Method string
|
Method string
|
||||||
RemoteAddr string
|
RemoteAddr string
|
||||||
ConnID string
|
ConnID string
|
||||||
|
info *UserConnContextInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) Deadline() (deadline time.Time, ok bool) {
|
func (c *UserConnContext) Deadline() (deadline time.Time, ok bool) {
|
||||||
@@ -57,9 +72,11 @@ func (c *UserConnContext) Value(key any) any {
|
|||||||
case constant.ConnID:
|
case constant.ConnID:
|
||||||
return c.GetConnID()
|
return c.GetConnID()
|
||||||
case constant.OpUserPlatform:
|
case constant.OpUserPlatform:
|
||||||
return constant.PlatformIDToName(stringutil.StringToInt(c.GetPlatformID()))
|
return c.GetPlatformID()
|
||||||
case constant.RemoteAddr:
|
case constant.RemoteAddr:
|
||||||
return c.RemoteAddr
|
return c.RemoteAddr
|
||||||
|
case SDKVersion:
|
||||||
|
return c.info.SDKVersion
|
||||||
default:
|
default:
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@@ -82,30 +99,92 @@ func newContext(respWriter http.ResponseWriter, req *http.Request) *UserConnCont
|
|||||||
|
|
||||||
func newTempContext() *UserConnContext {
|
func newTempContext() *UserConnContext {
|
||||||
return &UserConnContext{
|
return &UserConnContext{
|
||||||
Req: &http.Request{URL: &url.URL{}},
|
Req: &http.Request{URL: &url.URL{}},
|
||||||
|
info: &UserConnContextInfo{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *UserConnContext) ParseEssentialArgs() error {
|
||||||
|
query := c.Req.URL.Query()
|
||||||
|
if data := query.Get("v"); data != "" {
|
||||||
|
return c.parseByJson(data)
|
||||||
|
} else {
|
||||||
|
return c.parseByQuery(query, c.Req.Header)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserConnContext) parseByQuery(query url.Values, header http.Header) error {
|
||||||
|
info := UserConnContextInfo{
|
||||||
|
Token: query.Get(Token),
|
||||||
|
UserID: query.Get(WsUserID),
|
||||||
|
OperationID: query.Get(OperationID),
|
||||||
|
Compression: query.Get(Compression),
|
||||||
|
SDKType: query.Get(SDKType),
|
||||||
|
SDKVersion: query.Get(SDKVersion),
|
||||||
|
}
|
||||||
|
platformID, err := strconv.Atoi(query.Get(PlatformID))
|
||||||
|
if err != nil {
|
||||||
|
return servererrs.ErrConnArgsErr.WrapMsg("platformID is not int")
|
||||||
|
}
|
||||||
|
info.PlatformID = platformID
|
||||||
|
if val := query.Get(SendResponse); val != "" {
|
||||||
|
ok, err := strconv.ParseBool(val)
|
||||||
|
if err != nil {
|
||||||
|
return servererrs.ErrConnArgsErr.WrapMsg("isMsgResp is not bool")
|
||||||
|
}
|
||||||
|
info.SendResponse = ok
|
||||||
|
}
|
||||||
|
if info.Compression == "" {
|
||||||
|
info.Compression = header.Get(Compression)
|
||||||
|
}
|
||||||
|
background, err := strconv.ParseBool(query.Get(BackgroundStatus))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
info.Background = background
|
||||||
|
return c.checkInfo(&info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserConnContext) parseByJson(data string) error {
|
||||||
|
reqInfo, err := base64.RawURLEncoding.DecodeString(data)
|
||||||
|
if err != nil {
|
||||||
|
return servererrs.ErrConnArgsErr.WrapMsg("data is not base64")
|
||||||
|
}
|
||||||
|
var info UserConnContextInfo
|
||||||
|
if err := json.Unmarshal(reqInfo, &info); err != nil {
|
||||||
|
return servererrs.ErrConnArgsErr.WrapMsg("data is not json", "info", err.Error())
|
||||||
|
}
|
||||||
|
return c.checkInfo(&info)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserConnContext) checkInfo(info *UserConnContextInfo) error {
|
||||||
|
if info.OperationID == "" {
|
||||||
|
return servererrs.ErrConnArgsErr.WrapMsg("operationID is empty")
|
||||||
|
}
|
||||||
|
if info.Token == "" {
|
||||||
|
return servererrs.ErrConnArgsErr.WrapMsg("token is empty")
|
||||||
|
}
|
||||||
|
if info.UserID == "" {
|
||||||
|
return servererrs.ErrConnArgsErr.WrapMsg("sendID is empty")
|
||||||
|
}
|
||||||
|
if _, ok := constant.PlatformID2Name[info.PlatformID]; !ok {
|
||||||
|
return servererrs.ErrConnArgsErr.WrapMsg("platformID is invalid")
|
||||||
|
}
|
||||||
|
switch info.SDKType {
|
||||||
|
case "":
|
||||||
|
info.SDKType = GoSDK
|
||||||
|
case GoSDK, JsSDK:
|
||||||
|
default:
|
||||||
|
return servererrs.ErrConnArgsErr.WrapMsg("sdkType is invalid")
|
||||||
|
}
|
||||||
|
c.info = info
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) GetRemoteAddr() string {
|
func (c *UserConnContext) GetRemoteAddr() string {
|
||||||
return c.RemoteAddr
|
return c.RemoteAddr
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) Query(key string) (string, bool) {
|
|
||||||
var value string
|
|
||||||
if value = c.Req.URL.Query().Get(key); value == "" {
|
|
||||||
return value, false
|
|
||||||
}
|
|
||||||
return value, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UserConnContext) GetHeader(key string) (string, bool) {
|
|
||||||
var value string
|
|
||||||
if value = c.Req.Header.Get(key); value == "" {
|
|
||||||
return value, false
|
|
||||||
}
|
|
||||||
return value, true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *UserConnContext) SetHeader(key, value string) {
|
func (c *UserConnContext) SetHeader(key, value string) {
|
||||||
c.RespWriter.Header().Set(key, value)
|
c.RespWriter.Header().Set(key, value)
|
||||||
}
|
}
|
||||||
@@ -119,93 +198,76 @@ func (c *UserConnContext) GetConnID() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) GetUserID() string {
|
func (c *UserConnContext) GetUserID() string {
|
||||||
return c.Req.URL.Query().Get(WsUserID)
|
if c == nil || c.info == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return c.info.UserID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) GetPlatformID() string {
|
func (c *UserConnContext) GetPlatformID() int {
|
||||||
return c.Req.URL.Query().Get(PlatformID)
|
if c == nil || c.info == nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return c.info.PlatformID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) GetOperationID() string {
|
func (c *UserConnContext) GetOperationID() string {
|
||||||
return c.Req.URL.Query().Get(OperationID)
|
if c == nil || c.info == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return c.info.OperationID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) SetOperationID(operationID string) {
|
func (c *UserConnContext) SetOperationID(operationID string) {
|
||||||
values := c.Req.URL.Query()
|
if c.info == nil {
|
||||||
values.Set(OperationID, operationID)
|
c.info = &UserConnContextInfo{}
|
||||||
c.Req.URL.RawQuery = values.Encode()
|
}
|
||||||
|
c.info.OperationID = operationID
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) GetToken() string {
|
func (c *UserConnContext) GetToken() string {
|
||||||
return c.Req.URL.Query().Get(Token)
|
if c == nil || c.info == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return c.info.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) GetCompression() bool {
|
func (c *UserConnContext) GetCompression() bool {
|
||||||
compression, exists := c.Query(Compression)
|
return c != nil && c.info != nil && c.info.Compression == GzipCompressionProtocol
|
||||||
if exists && compression == GzipCompressionProtocol {
|
|
||||||
return true
|
|
||||||
} else {
|
|
||||||
compression, exists := c.GetHeader(Compression)
|
|
||||||
if exists && compression == GzipCompressionProtocol {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) GetSDKType() string {
|
func (c *UserConnContext) GetSDKType() string {
|
||||||
sdkType := c.Req.URL.Query().Get(SDKType)
|
if c == nil || c.info == nil {
|
||||||
if sdkType == "" {
|
return GoSDK
|
||||||
sdkType = GoSDK
|
|
||||||
}
|
}
|
||||||
return sdkType
|
switch c.info.SDKType {
|
||||||
|
case "", GoSDK:
|
||||||
|
return GoSDK
|
||||||
|
case JsSDK:
|
||||||
|
return JsSDK
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *UserConnContext) GetSDKVersion() string {
|
||||||
|
if c == nil || c.info == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return c.info.SDKVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) ShouldSendResp() bool {
|
func (c *UserConnContext) ShouldSendResp() bool {
|
||||||
errResp, exists := c.Query(SendResponse)
|
return c != nil && c.info != nil && c.info.SendResponse
|
||||||
if exists {
|
|
||||||
b, err := strconv.ParseBool(errResp)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
} else {
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) SetToken(token string) {
|
func (c *UserConnContext) SetToken(token string) {
|
||||||
c.Req.URL.RawQuery = Token + "=" + token
|
if c.info == nil {
|
||||||
|
c.info = &UserConnContextInfo{}
|
||||||
|
}
|
||||||
|
c.info.Token = token
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *UserConnContext) GetBackground() bool {
|
func (c *UserConnContext) GetBackground() bool {
|
||||||
b, err := strconv.ParseBool(c.Req.URL.Query().Get(BackgroundStatus))
|
return c != nil && c.info != nil && c.info.Background
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
func (c *UserConnContext) ParseEssentialArgs() error {
|
|
||||||
_, exists := c.Query(Token)
|
|
||||||
if !exists {
|
|
||||||
return servererrs.ErrConnArgsErr.WrapMsg("token is empty")
|
|
||||||
}
|
|
||||||
_, exists = c.Query(WsUserID)
|
|
||||||
if !exists {
|
|
||||||
return servererrs.ErrConnArgsErr.WrapMsg("sendID is empty")
|
|
||||||
}
|
|
||||||
platformIDStr, exists := c.Query(PlatformID)
|
|
||||||
if !exists {
|
|
||||||
return servererrs.ErrConnArgsErr.WrapMsg("platformID is empty")
|
|
||||||
}
|
|
||||||
_, err := strconv.Atoi(platformIDStr)
|
|
||||||
if err != nil {
|
|
||||||
return servererrs.ErrConnArgsErr.WrapMsg("platformID is not int")
|
|
||||||
}
|
|
||||||
switch sdkType, _ := c.Query(SDKType); sdkType {
|
|
||||||
case "", GoSDK, JsSDK:
|
|
||||||
default:
|
|
||||||
return servererrs.ErrConnArgsErr.WrapMsg("sdkType is not go or js")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,19 +152,16 @@ func (s *Server) pushToUser(ctx context.Context, userID string, msgData *sdkws.M
|
|||||||
userPlatform := &msggateway.SingleMsgToUserPlatform{
|
userPlatform := &msggateway.SingleMsgToUserPlatform{
|
||||||
RecvPlatFormID: int32(client.PlatformID),
|
RecvPlatFormID: int32(client.PlatformID),
|
||||||
}
|
}
|
||||||
if !client.IsBackground ||
|
if client.IsBackground && client.PlatformID == constant.IOSPlatformID {
|
||||||
(client.IsBackground && client.PlatformID != constant.IOSPlatformID) {
|
|
||||||
err := client.PushMessage(ctx, msgData)
|
|
||||||
if err != nil {
|
|
||||||
log.ZWarn(ctx, "online push msg failed", err, "userID", userID, "platformID", client.PlatformID)
|
|
||||||
userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code())
|
|
||||||
} else {
|
|
||||||
if _, ok := s.pushTerminal[client.PlatformID]; ok {
|
|
||||||
result.OnlinePush = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
userPlatform.ResultCode = int64(servererrs.ErrIOSBackgroundPushErr.Code())
|
userPlatform.ResultCode = int64(servererrs.ErrIOSBackgroundPushErr.Code())
|
||||||
|
result.Resp = append(result.Resp, userPlatform)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := client.PushMessage(ctx, msgData); err != nil {
|
||||||
|
log.ZWarn(ctx, "online push msg failed", err, "userID", userID, "platformID", client.PlatformID)
|
||||||
|
userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code())
|
||||||
|
} else if _, ok := s.pushTerminal[client.PlatformID]; ok {
|
||||||
|
result.OnlinePush = true
|
||||||
}
|
}
|
||||||
result.Resp = append(result.Resp, userPlatform)
|
result.Resp = append(result.Resp, userPlatform)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
// Copyright © 2023 OpenIM. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package msggateway
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/openimsdk/tools/apiresp"
|
|
||||||
|
|
||||||
"github.com/gorilla/websocket"
|
|
||||||
"github.com/openimsdk/tools/errs"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LongConn interface {
|
|
||||||
// Close this connection
|
|
||||||
Close() error
|
|
||||||
// WriteMessage Write message to connection,messageType means data type,can be set binary(2) and text(1).
|
|
||||||
WriteMessage(messageType int, message []byte) error
|
|
||||||
// ReadMessage Read message from connection.
|
|
||||||
ReadMessage() (int, []byte, error)
|
|
||||||
// SetReadDeadline sets the read deadline on the underlying network connection,
|
|
||||||
// after a read has timed out, will return an error.
|
|
||||||
SetReadDeadline(timeout time.Duration) error
|
|
||||||
// SetWriteDeadline sets to write deadline when send message,when read has timed out,will return error.
|
|
||||||
SetWriteDeadline(timeout time.Duration) error
|
|
||||||
// Dial Try to dial a connection,url must set auth args,header can control compress data
|
|
||||||
Dial(urlStr string, requestHeader http.Header) (*http.Response, error)
|
|
||||||
// IsNil Whether the connection of the current long connection is nil
|
|
||||||
IsNil() bool
|
|
||||||
// SetConnNil Set the connection of the current long connection to nil
|
|
||||||
SetConnNil()
|
|
||||||
// SetReadLimit sets the maximum size for a message read from the peer.bytes
|
|
||||||
SetReadLimit(limit int64)
|
|
||||||
SetPongHandler(handler PingPongHandler)
|
|
||||||
SetPingHandler(handler PingPongHandler)
|
|
||||||
// GenerateLongConn Check the connection of the current and when it was sent are the same
|
|
||||||
GenerateLongConn(w http.ResponseWriter, r *http.Request) error
|
|
||||||
}
|
|
||||||
type GWebSocket struct {
|
|
||||||
protocolType int
|
|
||||||
conn *websocket.Conn
|
|
||||||
handshakeTimeout time.Duration
|
|
||||||
writeBufferSize int
|
|
||||||
}
|
|
||||||
|
|
||||||
func newGWebSocket(protocolType int, handshakeTimeout time.Duration, wbs int) *GWebSocket {
|
|
||||||
return &GWebSocket{protocolType: protocolType, handshakeTimeout: handshakeTimeout, writeBufferSize: wbs}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) Close() error {
|
|
||||||
return d.conn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) GenerateLongConn(w http.ResponseWriter, r *http.Request) error {
|
|
||||||
upgrader := &websocket.Upgrader{
|
|
||||||
HandshakeTimeout: d.handshakeTimeout,
|
|
||||||
CheckOrigin: func(r *http.Request) bool { return true },
|
|
||||||
}
|
|
||||||
if d.writeBufferSize > 0 { // default is 4kb.
|
|
||||||
upgrader.WriteBufferSize = d.writeBufferSize
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := upgrader.Upgrade(w, r, nil)
|
|
||||||
if err != nil {
|
|
||||||
// The upgrader.Upgrade method usually returns enough error messages to diagnose problems that may occur during the upgrade
|
|
||||||
return errs.WrapMsg(err, "GenerateLongConn: WebSocket upgrade failed")
|
|
||||||
}
|
|
||||||
d.conn = conn
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) WriteMessage(messageType int, message []byte) error {
|
|
||||||
// d.setSendConn(d.conn)
|
|
||||||
return d.conn.WriteMessage(messageType, message)
|
|
||||||
}
|
|
||||||
|
|
||||||
// func (d *GWebSocket) setSendConn(sendConn *websocket.Conn) {
|
|
||||||
// d.sendConn = sendConn
|
|
||||||
//}
|
|
||||||
|
|
||||||
func (d *GWebSocket) ReadMessage() (int, []byte, error) {
|
|
||||||
return d.conn.ReadMessage()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) SetReadDeadline(timeout time.Duration) error {
|
|
||||||
return d.conn.SetReadDeadline(time.Now().Add(timeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) SetWriteDeadline(timeout time.Duration) error {
|
|
||||||
if timeout <= 0 {
|
|
||||||
return errs.New("timeout must be greater than 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO SetWriteDeadline Future add error handling
|
|
||||||
if err := d.conn.SetWriteDeadline(time.Now().Add(timeout)); err != nil {
|
|
||||||
return errs.WrapMsg(err, "GWebSocket.SetWriteDeadline failed")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) Dial(urlStr string, requestHeader http.Header) (*http.Response, error) {
|
|
||||||
conn, httpResp, err := websocket.DefaultDialer.Dial(urlStr, requestHeader)
|
|
||||||
if err != nil {
|
|
||||||
return httpResp, errs.WrapMsg(err, "GWebSocket.Dial failed", "url", urlStr)
|
|
||||||
}
|
|
||||||
d.conn = conn
|
|
||||||
return httpResp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) IsNil() bool {
|
|
||||||
return d.conn == nil
|
|
||||||
//
|
|
||||||
// if d.conn != nil {
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
// return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) SetConnNil() {
|
|
||||||
d.conn = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) SetReadLimit(limit int64) {
|
|
||||||
d.conn.SetReadLimit(limit)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) SetPongHandler(handler PingPongHandler) {
|
|
||||||
d.conn.SetPongHandler(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) SetPingHandler(handler PingPongHandler) {
|
|
||||||
d.conn.SetPingHandler(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) RespondWithError(err error, w http.ResponseWriter, r *http.Request) error {
|
|
||||||
if err := d.GenerateLongConn(w, r); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
data, err := json.Marshal(apiresp.ParseError(err))
|
|
||||||
if err != nil {
|
|
||||||
_ = d.Close()
|
|
||||||
return errs.WrapMsg(err, "json marshal failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := d.WriteMessage(MessageText, data); err != nil {
|
|
||||||
_ = d.Close()
|
|
||||||
return errs.WrapMsg(err, "WriteMessage failed")
|
|
||||||
}
|
|
||||||
_ = d.Close()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *GWebSocket) RespondWithSuccess() error {
|
|
||||||
data, err := json.Marshal(apiresp.ParseError(nil))
|
|
||||||
if err != nil {
|
|
||||||
_ = d.Close()
|
|
||||||
return errs.WrapMsg(err, "json marshal failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := d.WriteMessage(MessageText, data); err != nil {
|
|
||||||
_ = d.Close()
|
|
||||||
return errs.WrapMsg(err, "WriteMessage failed")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@@ -2,13 +2,16 @@ package msggateway
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||||
|
"github.com/openimsdk/tools/apiresp"
|
||||||
|
|
||||||
"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"
|
||||||
@@ -22,10 +25,11 @@ import (
|
|||||||
"github.com/openimsdk/protocol/msggateway"
|
"github.com/openimsdk/protocol/msggateway"
|
||||||
"github.com/openimsdk/tools/discovery"
|
"github.com/openimsdk/tools/discovery"
|
||||||
"github.com/openimsdk/tools/log"
|
"github.com/openimsdk/tools/log"
|
||||||
"github.com/openimsdk/tools/utils/stringutil"
|
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var wsSuccessResponse, _ = json.Marshal(&apiresp.ApiResponse{})
|
||||||
|
|
||||||
type LongConnServer interface {
|
type LongConnServer interface {
|
||||||
Run(ctx context.Context) error
|
Run(ctx context.Context) error
|
||||||
wsHandler(w http.ResponseWriter, r *http.Request)
|
wsHandler(w http.ResponseWriter, r *http.Request)
|
||||||
@@ -42,6 +46,7 @@ type LongConnServer interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type WsServer struct {
|
type WsServer struct {
|
||||||
|
websocket *websocket.Upgrader
|
||||||
msgGatewayConfig *Config
|
msgGatewayConfig *Config
|
||||||
port int
|
port int
|
||||||
wsMaxConnNum int64
|
wsMaxConnNum int64
|
||||||
@@ -64,6 +69,8 @@ type WsServer struct {
|
|||||||
webhookClient *webhook.Client
|
webhookClient *webhook.Client
|
||||||
userClient *rpcli.UserClient
|
userClient *rpcli.UserClient
|
||||||
authClient *rpcli.AuthClient
|
authClient *rpcli.AuthClient
|
||||||
|
|
||||||
|
ready atomic.Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type kickHandler struct {
|
type kickHandler struct {
|
||||||
@@ -93,6 +100,8 @@ func (ws *WsServer) SetDiscoveryRegistry(ctx context.Context, disCov discovery.C
|
|||||||
ws.authClient = rpcli.NewAuthClient(authConn)
|
ws.authClient = rpcli.NewAuthClient(authConn)
|
||||||
ws.MessageHandler = NewGrpcHandler(ws.validate, rpcli.NewMsgClient(msgConn), rpcli.NewPushMsgServiceClient(pushConn))
|
ws.MessageHandler = NewGrpcHandler(ws.validate, rpcli.NewMsgClient(msgConn), rpcli.NewPushMsgServiceClient(pushConn))
|
||||||
ws.disCov = disCov
|
ws.disCov = disCov
|
||||||
|
|
||||||
|
ws.ready.Store(true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,9 +140,13 @@ func NewWsServer(msgGatewayConfig *Config, opts ...Option) *WsServer {
|
|||||||
o(&config)
|
o(&config)
|
||||||
}
|
}
|
||||||
//userRpcClient := rpcclient.NewUserRpcClient(client, config.Discovery.RpcService.User, config.Share.IMAdminUser)
|
//userRpcClient := rpcclient.NewUserRpcClient(client, config.Discovery.RpcService.User, config.Share.IMAdminUser)
|
||||||
|
upgrader := &websocket.Upgrader{
|
||||||
|
HandshakeTimeout: config.handshakeTimeout,
|
||||||
|
CheckOrigin: func(r *http.Request) bool { return true },
|
||||||
|
}
|
||||||
v := validator.New()
|
v := validator.New()
|
||||||
return &WsServer{
|
return &WsServer{
|
||||||
|
websocket: upgrader,
|
||||||
msgGatewayConfig: msgGatewayConfig,
|
msgGatewayConfig: msgGatewayConfig,
|
||||||
port: config.port,
|
port: config.port,
|
||||||
wsMaxConnNum: config.maxConnNum,
|
wsMaxConnNum: config.maxConnNum,
|
||||||
@@ -254,6 +267,9 @@ func (ws *WsServer) registerClient(client *Client) {
|
|||||||
oldClients []*Client
|
oldClients []*Client
|
||||||
)
|
)
|
||||||
oldClients, userOK, clientOK = ws.clients.Get(client.UserID, client.PlatformID)
|
oldClients, userOK, clientOK = ws.clients.Get(client.UserID, client.PlatformID)
|
||||||
|
|
||||||
|
log.ZInfo(client.ctx, "registerClient", "userID", client.UserID, "platformID", client.PlatformID)
|
||||||
|
|
||||||
if !userOK {
|
if !userOK {
|
||||||
ws.clients.Set(client.UserID, client)
|
ws.clients.Set(client.UserID, client)
|
||||||
log.ZDebug(client.ctx, "user not exist", "userID", client.UserID, "platformID", client.PlatformID)
|
log.ZDebug(client.ctx, "user not exist", "userID", client.UserID, "platformID", client.PlatformID)
|
||||||
@@ -439,7 +455,7 @@ func (ws *WsServer) unregisterClient(client *Client) {
|
|||||||
// validateRespWithRequest checks if the response matches the expected userID and platformID.
|
// validateRespWithRequest checks if the response matches the expected userID and platformID.
|
||||||
func (ws *WsServer) validateRespWithRequest(ctx *UserConnContext, resp *pbAuth.ParseTokenResp) error {
|
func (ws *WsServer) validateRespWithRequest(ctx *UserConnContext, resp *pbAuth.ParseTokenResp) error {
|
||||||
userID := ctx.GetUserID()
|
userID := ctx.GetUserID()
|
||||||
platformID := stringutil.StringToInt32(ctx.GetPlatformID())
|
platformID := int32(ctx.GetPlatformID())
|
||||||
if resp.UserID != userID {
|
if resp.UserID != userID {
|
||||||
return servererrs.ErrTokenInvalid.WrapMsg(fmt.Sprintf("token uid %s != userID %s", resp.UserID, userID))
|
return servererrs.ErrTokenInvalid.WrapMsg(fmt.Sprintf("token uid %s != userID %s", resp.UserID, userID))
|
||||||
}
|
}
|
||||||
@@ -449,6 +465,29 @@ func (ws *WsServer) validateRespWithRequest(ctx *UserConnContext, resp *pbAuth.P
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (ws *WsServer) handlerError(ctx *UserConnContext, w http.ResponseWriter, r *http.Request, err error) {
|
||||||
|
if !ctx.ShouldSendResp() {
|
||||||
|
httpError(ctx, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// the browser cannot get the response of upgrade failure
|
||||||
|
data, err := json.Marshal(apiresp.ParseError(err))
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "json marshal failed", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
conn, upgradeErr := ws.websocket.Upgrade(w, r, nil)
|
||||||
|
if upgradeErr != nil {
|
||||||
|
log.ZWarn(ctx, "websocket upgrade failed", upgradeErr, "respErr", err, "resp", string(data))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
if err := conn.WriteMessage(websocket.TextMessage, data); err != nil {
|
||||||
|
log.ZWarn(ctx, "WriteMessage failed", err, "respErr", err, "resp", string(data))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
|
func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
// Create a new connection context
|
// Create a new connection context
|
||||||
connContext := newContext(w, r)
|
connContext := newContext(w, r)
|
||||||
@@ -456,7 +495,7 @@ func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// Check if the current number of online user connections exceeds the maximum limit
|
// Check if the current number of online user connections exceeds the maximum limit
|
||||||
if ws.onlineUserConnNum.Load() >= ws.wsMaxConnNum {
|
if ws.onlineUserConnNum.Load() >= ws.wsMaxConnNum {
|
||||||
// If it exceeds the maximum connection number, return an error via HTTP and stop processing
|
// If it exceeds the maximum connection number, return an error via HTTP and stop processing
|
||||||
httpError(connContext, servererrs.ErrConnOverMaxNumLimit.WrapMsg("over max conn num limit"))
|
ws.handlerError(connContext, w, r, servererrs.ErrConnOverMaxNumLimit.WrapMsg("over max conn num limit"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,26 +503,14 @@ func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
err := connContext.ParseEssentialArgs()
|
err := connContext.ParseEssentialArgs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If there's an error during parsing, return an error via HTTP and stop processing
|
// If there's an error during parsing, return an error via HTTP and stop processing
|
||||||
|
ws.handlerError(connContext, w, r, err)
|
||||||
httpError(connContext, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the authentication client to parse the Token obtained from the context
|
// Call the authentication client to parse the Token obtained from the context
|
||||||
resp, err := ws.authClient.ParseToken(connContext, connContext.GetToken())
|
resp, err := ws.authClient.ParseToken(connContext, connContext.GetToken())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If there's an error parsing the Token, decide whether to send the error message via WebSocket based on the context flag
|
ws.handlerError(connContext, w, r, err)
|
||||||
shouldSendError := connContext.ShouldSendResp()
|
|
||||||
if shouldSendError {
|
|
||||||
// Create a WebSocket connection object and attempt to send the error message via WebSocket
|
|
||||||
wsLongConn := newGWebSocket(WebSocket, ws.handshakeTimeout, ws.writeBufferSize)
|
|
||||||
if err := wsLongConn.RespondWithError(err, w, r); err == nil {
|
|
||||||
// If the error message is successfully sent via WebSocket, stop processing
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If sending via WebSocket is not required or fails, return the error via HTTP and stop processing
|
|
||||||
httpError(connContext, err)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -491,32 +518,30 @@ func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
err = ws.validateRespWithRequest(connContext, resp)
|
err = ws.validateRespWithRequest(connContext, resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If validation fails, return an error via HTTP and stop processing
|
// If validation fails, return an error via HTTP and stop processing
|
||||||
httpError(connContext, err)
|
ws.handlerError(connContext, w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
conn, err := ws.websocket.Upgrade(w, r, nil)
|
||||||
log.ZDebug(connContext, "new conn", "token", connContext.GetToken())
|
if err != nil {
|
||||||
// Create a WebSocket long connection object
|
log.ZWarn(connContext, "websocket upgrade failed", err)
|
||||||
wsLongConn := newGWebSocket(WebSocket, ws.handshakeTimeout, ws.writeBufferSize)
|
|
||||||
if err := wsLongConn.GenerateLongConn(w, r); err != nil {
|
|
||||||
//If the creation of the long connection fails, the error is handled internally during the handshake process.
|
|
||||||
log.ZWarn(connContext, "long connection fails", err)
|
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
// Check if a normal response should be sent via WebSocket
|
if connContext.ShouldSendResp() {
|
||||||
shouldSendSuccessResp := connContext.ShouldSendResp()
|
if err := conn.WriteMessage(websocket.TextMessage, wsSuccessResponse); err != nil {
|
||||||
if shouldSendSuccessResp {
|
log.ZWarn(connContext, "WriteMessage first response", err)
|
||||||
// Attempt to send a success message through WebSocket
|
return
|
||||||
if err := wsLongConn.RespondWithSuccess(); err != nil {
|
|
||||||
// If the success message is successfully sent, end further processing
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve a client object from the client pool, reset its state, and associate it with the current WebSocket long connection
|
log.ZDebug(connContext, "new conn", "token", connContext.GetToken())
|
||||||
client := ws.clientPool.Get().(*Client)
|
|
||||||
client.ResetClient(connContext, wsLongConn, ws)
|
var pingInterval time.Duration
|
||||||
|
if connContext.GetPlatformID() == constant.WebPlatformID {
|
||||||
|
pingInterval = pingPeriod
|
||||||
|
}
|
||||||
|
|
||||||
|
client := new(Client)
|
||||||
|
client.ResetClient(connContext, NewWebSocketClientConn(conn, maxMessageSize, pongWait, pingInterval), ws)
|
||||||
|
|
||||||
// Register the client with the server and start message processing
|
// Register the client with the server and start message processing
|
||||||
ws.registerChan <- client
|
ws.registerChan <- client
|
||||||
|
|||||||
@@ -51,37 +51,24 @@ func GetContent(msg *sdkws.MsgData) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *OnlineHistoryMongoConsumerHandler) webhookAfterSendSingleMsg(ctx context.Context, after *config.AfterConfig, msg *sdkws.MsgData) {
|
func (mc *OnlineHistoryMongoConsumerHandler) webhookAfterMsgSaveDB(ctx context.Context, after *config.AfterConfig, msg *sdkws.MsgData) {
|
||||||
if msg.ContentType == constant.Typing {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !filterAfterMsg(msg, after) {
|
if !filterAfterMsg(msg, after) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
cbReq := &cbapi.CallbackAfterSendSingleMsgReq{
|
cbReq := &cbapi.CallbackAfterMsgSaveDBReq{
|
||||||
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand),
|
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterMsgSaveDBCommand),
|
||||||
RecvID: msg.RecvID,
|
|
||||||
}
|
|
||||||
mc.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendSingleMsgResp{}, after, buildKeyMsgDataQuery(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *OnlineHistoryMongoConsumerHandler) webhookAfterSendGroupMsg(ctx context.Context, after *config.AfterConfig, msg *sdkws.MsgData) {
|
|
||||||
if msg.ContentType == constant.Typing {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !filterAfterMsg(msg, after) {
|
switch msg.SessionType {
|
||||||
return
|
case constant.SingleChatType, constant.NotificationChatType:
|
||||||
|
cbReq.RecvID = msg.RecvID
|
||||||
|
case constant.ReadGroupChatType:
|
||||||
|
cbReq.GroupID = msg.GroupID
|
||||||
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
cbReq := &cbapi.CallbackAfterSendGroupMsgReq{
|
mc.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterMsgSaveDBResp{}, after, buildKeyMsgDataQuery(msg))
|
||||||
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand),
|
|
||||||
GroupID: msg.GroupID,
|
|
||||||
}
|
|
||||||
|
|
||||||
mc.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendGroupMsgResp{}, after, buildKeyMsgDataQuery(msg))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildKeyMsgDataQuery(msg *sdkws.MsgData) map[string]string {
|
func buildKeyMsgDataQuery(msg *sdkws.MsgData) map[string]string {
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/openimsdk/tools/mq"
|
"github.com/openimsdk/tools/mq"
|
||||||
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|||||||
@@ -15,7 +15,6 @@
|
|||||||
package msgtransfer
|
package msgtransfer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/openimsdk/protocol/constant"
|
|
||||||
"github.com/openimsdk/tools/mq"
|
"github.com/openimsdk/tools/mq"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||||
@@ -57,7 +56,7 @@ func (mc *OnlineHistoryMongoConsumerHandler) HandleChatWs2Mongo(val mq.Message)
|
|||||||
log.ZDebug(ctx, "mongo consumer recv msg", "msgs", msgFromMQ.String())
|
log.ZDebug(ctx, "mongo consumer recv msg", "msgs", msgFromMQ.String())
|
||||||
err = mc.msgTransferDatabase.BatchInsertChat2DB(ctx, msgFromMQ.ConversationID, msgFromMQ.MsgData, msgFromMQ.LastSeq)
|
err = mc.msgTransferDatabase.BatchInsertChat2DB(ctx, msgFromMQ.ConversationID, msgFromMQ.MsgData, msgFromMQ.LastSeq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZError(ctx, "single data insert to mongo err", err, "msg", msgFromMQ.MsgData, "conversationID", msgFromMQ.ConversationID)
|
log.ZError(ctx, "batch data insert to mongo err", err, "msg", msgFromMQ.MsgData, "conversationID", msgFromMQ.ConversationID)
|
||||||
prommetrics.MsgInsertMongoFailedCounter.Inc()
|
prommetrics.MsgInsertMongoFailedCounter.Inc()
|
||||||
} else {
|
} else {
|
||||||
prommetrics.MsgInsertMongoSuccessCounter.Inc()
|
prommetrics.MsgInsertMongoSuccessCounter.Inc()
|
||||||
@@ -65,12 +64,7 @@ func (mc *OnlineHistoryMongoConsumerHandler) HandleChatWs2Mongo(val mq.Message)
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, msgData := range msgFromMQ.MsgData {
|
for _, msgData := range msgFromMQ.MsgData {
|
||||||
switch msgData.SessionType {
|
mc.webhookAfterMsgSaveDB(ctx, &mc.config.WebhooksConfig.AfterMsgSaveDB, msgData)
|
||||||
case constant.SingleChatType:
|
|
||||||
mc.webhookAfterSendSingleMsg(ctx, &mc.config.WebhooksConfig.AfterSendSingleMsg, msgData)
|
|
||||||
case constant.ReadGroupChatType:
|
|
||||||
mc.webhookAfterSendGroupMsg(ctx, &mc.config.WebhooksConfig.AfterSendGroupMsg, msgData)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//var seqs []int64
|
//var seqs []int64
|
||||||
|
|||||||
+38
-10
@@ -18,10 +18,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/mcache"
|
"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/common/storage/database/mgo"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
|
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/localcache"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
@@ -46,6 +49,7 @@ import (
|
|||||||
type authServer struct {
|
type authServer struct {
|
||||||
pbauth.UnimplementedAuthServer
|
pbauth.UnimplementedAuthServer
|
||||||
authDatabase controller.AuthDatabase
|
authDatabase controller.AuthDatabase
|
||||||
|
AuthLocalCache *rpccache.AuthLocalCache
|
||||||
RegisterCenter discovery.Conn
|
RegisterCenter discovery.Conn
|
||||||
config *Config
|
config *Config
|
||||||
userClient *rpcli.UserClient
|
userClient *rpcli.UserClient
|
||||||
@@ -53,11 +57,12 @@ type authServer struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
RpcConfig config.Auth
|
RpcConfig config.Auth
|
||||||
RedisConfig config.Redis
|
RedisConfig config.Redis
|
||||||
MongoConfig config.Mongo
|
MongoConfig config.Mongo
|
||||||
Share config.Share
|
Share config.Share
|
||||||
Discovery config.Discovery
|
LocalCacheConfig config.LocalCache
|
||||||
|
Discovery config.Discovery
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
|
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
|
||||||
@@ -78,12 +83,19 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
|||||||
}
|
}
|
||||||
token = mcache.NewTokenCacheModel(mc, config.RpcConfig.TokenPolicy.Expire)
|
token = mcache.NewTokenCacheModel(mc, config.RpcConfig.TokenPolicy.Expire)
|
||||||
} else {
|
} else {
|
||||||
token = redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire)
|
token = redis2.NewTokenCacheModel(rdb, &config.LocalCacheConfig, 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
|
||||||
}
|
}
|
||||||
|
authConn, err := client.GetConn(ctx, config.Discovery.RpcService.Auth)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
localcache.InitLocalCache(&config.LocalCacheConfig)
|
||||||
|
|
||||||
pbauth.RegisterAuthServer(server, &authServer{
|
pbauth.RegisterAuthServer(server, &authServer{
|
||||||
RegisterCenter: client,
|
RegisterCenter: client,
|
||||||
authDatabase: controller.NewAuthDatabase(
|
authDatabase: controller.NewAuthDatabase(
|
||||||
@@ -93,9 +105,10 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
|||||||
config.Share.MultiLogin,
|
config.Share.MultiLogin,
|
||||||
config.Share.IMAdminUser.UserIDs,
|
config.Share.IMAdminUser.UserIDs,
|
||||||
),
|
),
|
||||||
config: config,
|
AuthLocalCache: rpccache.NewAuthLocalCache(rpcli.NewAuthClient(authConn), &config.LocalCacheConfig, rdb),
|
||||||
userClient: rpcli.NewUserClient(userConn),
|
config: config,
|
||||||
adminUserIDs: config.Share.IMAdminUser.UserIDs,
|
userClient: rpcli.NewUserClient(userConn),
|
||||||
|
adminUserIDs: config.Share.IMAdminUser.UserIDs,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -121,6 +134,7 @@ func (s *authServer) GetAdminToken(ctx context.Context, req *pbauth.GetAdminToke
|
|||||||
}
|
}
|
||||||
|
|
||||||
prommetrics.UserLoginCounter.Inc()
|
prommetrics.UserLoginCounter.Inc()
|
||||||
|
|
||||||
resp.Token = token
|
resp.Token = token
|
||||||
resp.ExpireTimeSeconds = s.config.RpcConfig.TokenPolicy.Expire * 24 * 60 * 60
|
resp.ExpireTimeSeconds = s.config.RpcConfig.TokenPolicy.Expire * 24 * 60 * 60
|
||||||
return &resp, nil
|
return &resp, nil
|
||||||
@@ -151,20 +165,34 @@ func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenR
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.Token = token
|
resp.Token = token
|
||||||
resp.ExpireTimeSeconds = s.config.RpcConfig.TokenPolicy.Expire * 24 * 60 * 60
|
resp.ExpireTimeSeconds = s.config.RpcConfig.TokenPolicy.Expire * 24 * 60 * 60
|
||||||
return &resp, nil
|
return &resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *authServer) GetExistingToken(ctx context.Context, req *pbauth.GetExistingTokenReq) (*pbauth.GetExistingTokenResp, error) {
|
||||||
|
m, err := s.authDatabase.GetTokensWithoutError(ctx, req.UserID, int(req.PlatformID))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pbauth.GetExistingTokenResp{
|
||||||
|
TokenStates: convert.TokenMapDB2Pb(m),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *authServer) parseToken(ctx context.Context, tokensString string) (claims *tokenverify.Claims, err error) {
|
func (s *authServer) parseToken(ctx context.Context, tokensString string) (claims *tokenverify.Claims, err error) {
|
||||||
claims, err = tokenverify.GetClaimFromToken(tokensString, authverify.Secret(s.config.Share.Secret))
|
claims, err = tokenverify.GetClaimFromToken(tokensString, authverify.Secret(s.config.Share.Secret))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
m, err := s.authDatabase.GetTokensWithoutError(ctx, claims.UserID, claims.PlatformID)
|
|
||||||
|
m, err := s.AuthLocalCache.GetExistingToken(ctx, claims.UserID, claims.PlatformID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(m) == 0 {
|
if len(m) == 0 {
|
||||||
isAdmin := authverify.CheckUserIsAdmin(ctx, claims.UserID)
|
isAdmin := authverify.CheckUserIsAdmin(ctx, claims.UserID)
|
||||||
if isAdmin {
|
if isAdmin {
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import (
|
|||||||
"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"
|
||||||
pbconversation "github.com/openimsdk/protocol/conversation"
|
pbconversation "github.com/openimsdk/protocol/conversation"
|
||||||
|
"github.com/openimsdk/protocol/msg"
|
||||||
"github.com/openimsdk/protocol/sdkws"
|
"github.com/openimsdk/protocol/sdkws"
|
||||||
"github.com/openimsdk/tools/discovery"
|
"github.com/openimsdk/tools/discovery"
|
||||||
"github.com/openimsdk/tools/errs"
|
"github.com/openimsdk/tools/errs"
|
||||||
@@ -132,6 +133,7 @@ func (c *conversationServer) GetConversation(ctx context.Context, req *pbconvers
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use `GetConversations` instead.
|
||||||
func (c *conversationServer) GetSortedConversationList(ctx context.Context, req *pbconversation.GetSortedConversationListReq) (resp *pbconversation.GetSortedConversationListResp, err error) {
|
func (c *conversationServer) GetSortedConversationList(ctx context.Context, req *pbconversation.GetSortedConversationListReq) (resp *pbconversation.GetSortedConversationListResp, err error) {
|
||||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -183,9 +185,21 @@ func (c *conversationServer) GetSortedConversationList(ctx context.Context, req
|
|||||||
|
|
||||||
conversation_isPinTime := make(map[int64]string)
|
conversation_isPinTime := make(map[int64]string)
|
||||||
conversation_notPinTime := make(map[int64]string)
|
conversation_notPinTime := make(map[int64]string)
|
||||||
|
|
||||||
for _, v := range conversations {
|
for _, v := range conversations {
|
||||||
conversationID := v.ConversationID
|
conversationID := v.ConversationID
|
||||||
time := conversationMsg[conversationID].MsgInfo.LatestMsgRecvTime
|
var time int64
|
||||||
|
if _, ok := conversationMsg[conversationID]; ok {
|
||||||
|
time = conversationMsg[conversationID].MsgInfo.LatestMsgRecvTime
|
||||||
|
} else {
|
||||||
|
conversationMsg[conversationID] = &pbconversation.ConversationElem{
|
||||||
|
ConversationID: conversationID,
|
||||||
|
IsPinned: v.IsPinned,
|
||||||
|
MsgInfo: nil,
|
||||||
|
}
|
||||||
|
time = v.CreateTime.UnixMilli()
|
||||||
|
}
|
||||||
|
|
||||||
conversationMsg[conversationID].RecvMsgOpt = v.RecvMsgOpt
|
conversationMsg[conversationID].RecvMsgOpt = v.RecvMsgOpt
|
||||||
if v.IsPinned {
|
if v.IsPinned {
|
||||||
conversationMsg[conversationID].IsPinned = v.IsPinned
|
conversationMsg[conversationID].IsPinned = v.IsPinned
|
||||||
@@ -499,14 +513,6 @@ func (c *conversationServer) GetUserConversationIDsHash(ctx context.Context, req
|
|||||||
return &pbconversation.GetUserConversationIDsHashResp{Hash: hash}, nil
|
return &pbconversation.GetUserConversationIDsHashResp{Hash: hash}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conversationServer) GetConversationsByConversationID(ctx context.Context, req *pbconversation.GetConversationsByConversationIDReq) (*pbconversation.GetConversationsByConversationIDResp, error) {
|
|
||||||
conversations, err := c.conversationDatabase.GetConversationsByConversationID(ctx, req.ConversationIDs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &pbconversation.GetConversationsByConversationIDResp{Conversations: convert.ConversationsDB2Pb(conversations)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conversationServer) GetConversationOfflinePushUserIDs(ctx context.Context, req *pbconversation.GetConversationOfflinePushUserIDsReq) (*pbconversation.GetConversationOfflinePushUserIDsResp, error) {
|
func (c *conversationServer) GetConversationOfflinePushUserIDs(ctx context.Context, req *pbconversation.GetConversationOfflinePushUserIDsReq) (*pbconversation.GetConversationOfflinePushUserIDsResp, error) {
|
||||||
if req.ConversationID == "" {
|
if req.ConversationID == "" {
|
||||||
return nil, errs.ErrArgs.WrapMsg("conversationID is empty")
|
return nil, errs.ErrArgs.WrapMsg("conversationID is empty")
|
||||||
@@ -703,56 +709,6 @@ func (c *conversationServer) GetOwnerConversation(ctx context.Context, req *pbco
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conversationServer) GetConversationsNeedClearMsg(ctx context.Context, _ *pbconversation.GetConversationsNeedClearMsgReq) (*pbconversation.GetConversationsNeedClearMsgResp, error) {
|
|
||||||
num, err := c.conversationDatabase.GetAllConversationIDsNumber(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.ZError(ctx, "GetAllConversationIDsNumber failed", err)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
const batchNum = 100
|
|
||||||
|
|
||||||
if num == 0 {
|
|
||||||
return nil, errs.New("Need Destruct Msg is nil").Wrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
maxPage := (num + batchNum - 1) / batchNum
|
|
||||||
|
|
||||||
temp := make([]*dbModel.Conversation, 0, maxPage*batchNum)
|
|
||||||
|
|
||||||
for pageNumber := 0; pageNumber < int(maxPage); pageNumber++ {
|
|
||||||
pagination := &sdkws.RequestPagination{
|
|
||||||
PageNumber: int32(pageNumber),
|
|
||||||
ShowNumber: batchNum,
|
|
||||||
}
|
|
||||||
|
|
||||||
conversationIDs, err := c.conversationDatabase.PageConversationIDs(ctx, pagination)
|
|
||||||
if err != nil {
|
|
||||||
log.ZError(ctx, "PageConversationIDs failed", err, "pageNumber", pageNumber)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// log.ZDebug(ctx, "PageConversationIDs success", "pageNumber", pageNumber, "conversationIDsNum", len(conversationIDs), "conversationIDs", conversationIDs)
|
|
||||||
if len(conversationIDs) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
conversations, err := c.conversationDatabase.GetConversationsByConversationID(ctx, conversationIDs)
|
|
||||||
if err != nil {
|
|
||||||
log.ZError(ctx, "GetConversationsByConversationID failed", err, "conversationIDs", conversationIDs)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, conversation := range conversations {
|
|
||||||
if conversation.IsMsgDestruct && conversation.MsgDestructTime != 0 && ((time.Now().UnixMilli() > (conversation.MsgDestructTime + conversation.LatestMsgDestructTime.UnixMilli() + 8*60*60)) || // 8*60*60 is UTC+8
|
|
||||||
conversation.LatestMsgDestructTime.IsZero()) {
|
|
||||||
temp = append(temp, conversation)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &pbconversation.GetConversationsNeedClearMsgResp{Conversations: convert.ConversationsDB2Pb(temp)}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, req *pbconversation.GetNotNotifyConversationIDsReq) (*pbconversation.GetNotNotifyConversationIDsResp, error) {
|
func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, req *pbconversation.GetNotNotifyConversationIDsReq) (*pbconversation.GetNotNotifyConversationIDsResp, error) {
|
||||||
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -782,7 +738,7 @@ func (c *conversationServer) ClearUserConversationMsg(ctx context.Context, req *
|
|||||||
}
|
}
|
||||||
latestMsgDestructTime := time.UnixMilli(req.Timestamp)
|
latestMsgDestructTime := time.UnixMilli(req.Timestamp)
|
||||||
for i, conversation := range conversations {
|
for i, conversation := range conversations {
|
||||||
if conversation.IsMsgDestruct == false || conversation.MsgDestructTime == 0 {
|
if !conversation.IsMsgDestruct || conversation.MsgDestructTime == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
seq, err := c.msgClient.GetLastMessageSeqByTime(ctx, conversation.ConversationID, req.Timestamp-(conversation.MsgDestructTime*1000))
|
seq, err := c.msgClient.GetLastMessageSeqByTime(ctx, conversation.ConversationID, req.Timestamp-(conversation.MsgDestructTime*1000))
|
||||||
@@ -822,3 +778,53 @@ func (c *conversationServer) setConversationMinSeqAndLatestMsgDestructTime(ctx c
|
|||||||
c.conversationNotificationSender.ConversationChangeNotification(ctx, ownerUserID, []string{conversationID})
|
c.conversationNotificationSender.ConversationChangeNotification(ctx, ownerUserID, []string{conversationID})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *conversationServer) DeleteConversations(ctx context.Context, req *pbconversation.DeleteConversationsReq) (resp *pbconversation.DeleteConversationsResp, err error) {
|
||||||
|
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if req.NeedDeleteTime == 0 && len(req.ConversationIDs) == 0 {
|
||||||
|
return nil, errs.ErrArgs.WrapMsg("need_delete_time or conversationIDs need be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.NeedDeleteTime != 0 && len(req.ConversationIDs) != 0 {
|
||||||
|
return nil, errs.ErrArgs.WrapMsg("need_delete_time and conversationIDs cannot both be set")
|
||||||
|
}
|
||||||
|
|
||||||
|
var needDeleteConversationIDs []string
|
||||||
|
|
||||||
|
if len(req.ConversationIDs) == 0 {
|
||||||
|
deleteTimeThreshold := time.Now().AddDate(0, 0, -int(req.NeedDeleteTime)).UnixMilli()
|
||||||
|
conversationIDs, err := c.conversationDatabase.GetConversationIDs(ctx, req.OwnerUserID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
latestMsgs, err := c.msgClient.GetLastMessage(ctx, &msg.GetLastMessageReq{
|
||||||
|
UserID: req.OwnerUserID,
|
||||||
|
ConversationIDs: conversationIDs,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for conversationID, msg := range latestMsgs.Msgs {
|
||||||
|
if msg.SendTime < deleteTimeThreshold {
|
||||||
|
needDeleteConversationIDs = append(needDeleteConversationIDs, conversationID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(needDeleteConversationIDs) == 0 {
|
||||||
|
return &pbconversation.DeleteConversationsResp{}, nil
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
needDeleteConversationIDs = req.ConversationIDs
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := c.conversationDatabase.DeleteUsersConversations(ctx, req.OwnerUserID, needDeleteConversationIDs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// c.conversationNotificationSender.ConversationDeleteNotification(ctx, req.OwnerUserID, needDeleteConversationIDs)
|
||||||
|
|
||||||
|
return &pbconversation.DeleteConversationsResp{}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -73,3 +73,12 @@ func (c *ConversationNotificationSender) ConversationUnreadChangeNotification(
|
|||||||
|
|
||||||
c.Notification(ctx, userID, userID, constant.ConversationUnreadNotification, tips)
|
c.Notification(ctx, userID, userID, constant.ConversationUnreadNotification, tips)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ConversationNotificationSender) ConversationDeleteNotification(ctx context.Context, userID string, conversationIDs []string) {
|
||||||
|
tips := &sdkws.ConversationDeleteTips{
|
||||||
|
UserID: userID,
|
||||||
|
ConversationIDs: conversationIDs,
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Notification(ctx, userID, userID, constant.ConversationDeleteNotification, tips)
|
||||||
|
}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (c *conversationServer) GetFullOwnerConversationIDs(ctx context.Context, re
|
|||||||
conversationIDs = nil
|
conversationIDs = nil
|
||||||
}
|
}
|
||||||
return &conversation.GetFullOwnerConversationIDsResp{
|
return &conversation.GetFullOwnerConversationIDsResp{
|
||||||
Version: idHash,
|
Version: uint64(vl.Version),
|
||||||
VersionID: vl.ID.Hex(),
|
VersionID: vl.ID.Hex(),
|
||||||
Equal: req.IdHash == idHash,
|
Equal: req.IdHash == idHash,
|
||||||
ConversationIDs: conversationIDs,
|
ConversationIDs: conversationIDs,
|
||||||
|
|||||||
@@ -472,6 +472,9 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
|
|||||||
g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, req.SendMessage, opUserID, userIDs...)
|
g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, req.SendMessage, opUserID, userIDs...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := g.setMemberJoinSeq(ctx, req.GroupID, req.InvitedUserIDs); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &pbgroup.InviteUserToGroupResp{}, nil
|
return &pbgroup.InviteUserToGroupResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -602,10 +605,6 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
num, err := g.db.FindGroupMemberNum(ctx, req.GroupID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ownerUserIDs, err := g.db.GetGroupRoleLevelMemberIDs(ctx, req.GroupID, constant.GroupOwner)
|
ownerUserIDs, err := g.db.GetGroupRoleLevelMemberIDs(ctx, req.GroupID, constant.GroupOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -617,6 +616,10 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
|
|||||||
if err := g.db.DeleteGroupMember(ctx, group.GroupID, req.KickedUserIDs); err != nil {
|
if err := g.db.DeleteGroupMember(ctx, group.GroupID, req.KickedUserIDs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
num, err := g.db.FindGroupMemberNum(ctx, req.GroupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
tips := &sdkws.MemberKickedTips{
|
tips := &sdkws.MemberKickedTips{
|
||||||
Group: &sdkws.GroupInfo{
|
Group: &sdkws.GroupInfo{
|
||||||
GroupID: group.GroupID,
|
GroupID: group.GroupID,
|
||||||
@@ -626,7 +629,7 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
|
|||||||
FaceURL: group.FaceURL,
|
FaceURL: group.FaceURL,
|
||||||
OwnerUserID: ownerUserID,
|
OwnerUserID: ownerUserID,
|
||||||
CreateTime: group.CreateTime.UnixMilli(),
|
CreateTime: group.CreateTime.UnixMilli(),
|
||||||
MemberCount: num - uint32(len(req.KickedUserIDs)),
|
MemberCount: num,
|
||||||
Ex: group.Ex,
|
Ex: group.Ex,
|
||||||
Status: group.Status,
|
Status: group.Status,
|
||||||
CreatorUserID: group.CreatorUserID,
|
CreatorUserID: group.CreatorUserID,
|
||||||
@@ -905,6 +908,9 @@ func (g *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if err := g.setMemberJoinSeq(ctx, req.GroupID, []string{req.FromUserID}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case constant.GroupResponseRefuse:
|
case constant.GroupResponseRefuse:
|
||||||
g.notification.GroupApplicationRejectedNotification(ctx, req)
|
g.notification.GroupApplicationRejectedNotification(ctx, req)
|
||||||
@@ -967,6 +973,9 @@ func (g *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq)
|
|||||||
if err = g.notification.MemberEnterNotification(ctx, req.GroupID, req.InviterUserID); err != nil {
|
if err = g.notification.MemberEnterNotification(ctx, req.GroupID, req.InviterUserID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if err := g.setMemberJoinSeq(ctx, req.GroupID, []string{req.InviterUserID}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
g.webhookAfterJoinGroup(ctx, &g.config.WebhooksConfig.AfterJoinGroup, req)
|
g.webhookAfterJoinGroup(ctx, &g.config.WebhooksConfig.AfterJoinGroup, req)
|
||||||
|
|
||||||
return &pbgroup.JoinGroupResp{}, nil
|
return &pbgroup.JoinGroupResp{}, nil
|
||||||
@@ -1028,6 +1037,11 @@ func (g *groupServer) deleteMemberAndSetConversationSeq(ctx context.Context, gro
|
|||||||
return g.conversationClient.SetConversationMaxSeq(ctx, conversationID, userIDs, maxSeq)
|
return g.conversationClient.SetConversationMaxSeq(ctx, conversationID, userIDs, maxSeq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *groupServer) setMemberJoinSeq(ctx context.Context, groupID string, userIDs []string) error {
|
||||||
|
conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, groupID)
|
||||||
|
return g.conversationClient.SetConversationMaxSeq(ctx, conversationID, userIDs, 0)
|
||||||
|
}
|
||||||
|
|
||||||
func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInfoReq) (*pbgroup.SetGroupInfoResp, error) {
|
func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInfoReq) (*pbgroup.SetGroupInfoResp, error) {
|
||||||
var opMember *model.GroupMember
|
var opMember *model.GroupMember
|
||||||
if !authverify.IsAdmin(ctx) {
|
if !authverify.IsAdmin(ctx) {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package group
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
|
"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||||
@@ -11,6 +12,7 @@ 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/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const versionSyncLimit = 500
|
const versionSyncLimit = 500
|
||||||
@@ -32,7 +34,7 @@ func (g *groupServer) GetFullGroupMemberUserIDs(ctx context.Context, req *pbgrou
|
|||||||
userIDs = nil
|
userIDs = nil
|
||||||
}
|
}
|
||||||
return &pbgroup.GetFullGroupMemberUserIDsResp{
|
return &pbgroup.GetFullGroupMemberUserIDsResp{
|
||||||
Version: idHash,
|
Version: uint64(vl.Version),
|
||||||
VersionID: vl.ID.Hex(),
|
VersionID: vl.ID.Hex(),
|
||||||
Equal: req.IdHash == idHash,
|
Equal: req.IdHash == idHash,
|
||||||
UserIDs: userIDs,
|
UserIDs: userIDs,
|
||||||
@@ -56,7 +58,7 @@ func (g *groupServer) GetFullJoinGroupIDs(ctx context.Context, req *pbgroup.GetF
|
|||||||
groupIDs = nil
|
groupIDs = nil
|
||||||
}
|
}
|
||||||
return &pbgroup.GetFullJoinGroupIDsResp{
|
return &pbgroup.GetFullJoinGroupIDsResp{
|
||||||
Version: idHash,
|
Version: uint64(vl.Version),
|
||||||
VersionID: vl.ID.Hex(),
|
VersionID: vl.ID.Hex(),
|
||||||
Equal: req.IdHash == idHash,
|
Equal: req.IdHash == idHash,
|
||||||
GroupIDs: groupIDs,
|
GroupIDs: groupIDs,
|
||||||
@@ -170,19 +172,26 @@ func (g *groupServer) GetIncrementalJoinGroup(ctx context.Context, req *pbgroup.
|
|||||||
func (g *groupServer) BatchGetIncrementalGroupMember(ctx context.Context, req *pbgroup.BatchGetIncrementalGroupMemberReq) (*pbgroup.BatchGetIncrementalGroupMemberResp, error) {
|
func (g *groupServer) BatchGetIncrementalGroupMember(ctx context.Context, req *pbgroup.BatchGetIncrementalGroupMemberReq) (*pbgroup.BatchGetIncrementalGroupMemberResp, error) {
|
||||||
var num int
|
var num int
|
||||||
resp := make(map[string]*pbgroup.GetIncrementalGroupMemberResp)
|
resp := make(map[string]*pbgroup.GetIncrementalGroupMemberResp)
|
||||||
|
|
||||||
for _, memberReq := range req.ReqList {
|
for _, memberReq := range req.ReqList {
|
||||||
if _, ok := resp[memberReq.GroupID]; ok {
|
if _, ok := resp[memberReq.GroupID]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
memberResp, err := g.GetIncrementalGroupMember(ctx, memberReq)
|
memberResp, err := g.GetIncrementalGroupMember(ctx, memberReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if errors.Is(err, servererrs.ErrDismissedAlready) {
|
||||||
|
log.ZWarn(ctx, "Failed to get incremental group member", err, "groupID", memberReq.GroupID, "request", memberReq)
|
||||||
|
continue
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp[memberReq.GroupID] = memberResp
|
resp[memberReq.GroupID] = memberResp
|
||||||
num += len(memberResp.Insert) + len(memberResp.Update) + len(memberResp.Delete)
|
num += len(memberResp.Insert) + len(memberResp.Update) + len(memberResp.Delete)
|
||||||
if num >= versionSyncLimit {
|
if num >= versionSyncLimit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pbgroup.BatchGetIncrementalGroupMemberResp{RespList: resp}, nil
|
return &pbgroup.BatchGetIncrementalGroupMemberResp{RespList: resp}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,10 @@ package msg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"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/errs"
|
||||||
|
|
||||||
@@ -28,6 +30,7 @@ import (
|
|||||||
"github.com/openimsdk/protocol/sdkws"
|
"github.com/openimsdk/protocol/sdkws"
|
||||||
"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"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -87,19 +90,19 @@ func (m *msgServer) webhookBeforeSendSingleMsg(ctx context.Context, before *conf
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Move to msgtransfer
|
// Move to msgtransfer
|
||||||
// func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) {
|
func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) {
|
||||||
// if msg.MsgData.ContentType == constant.Typing {
|
if msg.MsgData.ContentType == constant.Typing {
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
// if !filterAfterMsg(msg, after) {
|
if !filterAfterMsg(msg, after) {
|
||||||
// return
|
return
|
||||||
// }
|
}
|
||||||
// cbReq := &cbapi.CallbackAfterSendSingleMsgReq{
|
cbReq := &cbapi.CallbackAfterSendSingleMsgReq{
|
||||||
// CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand),
|
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand),
|
||||||
// RecvID: msg.MsgData.RecvID,
|
RecvID: msg.MsgData.RecvID,
|
||||||
// }
|
}
|
||||||
// m.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendSingleMsgResp{}, after, buildKeyMsgDataQuery(msg.MsgData))
|
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 {
|
||||||
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
|
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
|
||||||
@@ -121,21 +124,20 @@ func (m *msgServer) webhookBeforeSendGroupMsg(ctx context.Context, before *confi
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Move to msgtransfer
|
func (m *msgServer) webhookAfterSendGroupMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) {
|
||||||
// func (m *msgServer) webhookAfterSendGroupMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) {
|
if msg.MsgData.ContentType == constant.Typing {
|
||||||
// if msg.MsgData.ContentType == constant.Typing {
|
return
|
||||||
// return
|
}
|
||||||
// }
|
if !filterAfterMsg(msg, after) {
|
||||||
// if !filterAfterMsg(msg, after) {
|
return
|
||||||
// return
|
}
|
||||||
// }
|
cbReq := &cbapi.CallbackAfterSendGroupMsgReq{
|
||||||
// cbReq := &cbapi.CallbackAfterSendGroupMsgReq{
|
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand),
|
||||||
// CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand),
|
GroupID: msg.MsgData.GroupID,
|
||||||
// GroupID: msg.MsgData.GroupID,
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// m.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendGroupMsgResp{}, after, buildKeyMsgDataQuery(msg.MsgData))
|
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, beforeMsgData **sdkws.MsgData) 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 {
|
||||||
@@ -204,14 +206,14 @@ 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 {
|
func buildKeyMsgDataQuery(msg *sdkws.MsgData) map[string]string {
|
||||||
// keyMsgData := apistruct.KeyMsgData{
|
keyMsgData := apistruct.KeyMsgData{
|
||||||
// SendID: msg.SendID,
|
SendID: msg.SendID,
|
||||||
// RecvID: msg.RecvID,
|
RecvID: msg.RecvID,
|
||||||
// GroupID: msg.GroupID,
|
GroupID: msg.GroupID,
|
||||||
// }
|
}
|
||||||
|
|
||||||
// return map[string]string{
|
return map[string]string{
|
||||||
// webhook.Key: base64.StdEncoding.EncodeToString(stringutil.StructToJsonBytes(keyMsgData)),
|
webhook.Key: base64.StdEncoding.EncodeToString(stringutil.StructToJsonBytes(keyMsgData)),
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||||
"github.com/openimsdk/protocol/constant"
|
"github.com/openimsdk/protocol/constant"
|
||||||
"github.com/openimsdk/protocol/conversation"
|
|
||||||
"github.com/openimsdk/protocol/msg"
|
"github.com/openimsdk/protocol/msg"
|
||||||
"github.com/openimsdk/protocol/sdkws"
|
"github.com/openimsdk/protocol/sdkws"
|
||||||
"github.com/openimsdk/tools/log"
|
"github.com/openimsdk/tools/log"
|
||||||
@@ -74,7 +73,7 @@ func (m *msgServer) DeleteMsgs(ctx context.Context, req *msg.DeleteMsgsReq) (*ms
|
|||||||
if err := m.MsgDatabase.DeleteMsgsPhysicalBySeqs(ctx, req.ConversationID, req.Seqs); err != nil {
|
if err := m.MsgDatabase.DeleteMsgsPhysicalBySeqs(ctx, req.ConversationID, req.Seqs); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
conv, err := m.conversationClient.GetConversationsByConversationID(ctx, req.ConversationID)
|
conv, err := m.conversationClient.GetConversation(ctx, req.ConversationID, req.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -116,14 +115,12 @@ func (m *msgServer) DeleteMsgPhysical(ctx context.Context, req *msg.DeleteMsgPhy
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *msgServer) clearConversation(ctx context.Context, conversationIDs []string, userID string, deleteSyncOpt *msg.DeleteSyncOpt) error {
|
func (m *msgServer) clearConversation(ctx context.Context, conversationIDs []string, userID string, deleteSyncOpt *msg.DeleteSyncOpt) error {
|
||||||
conversations, err := m.conversationClient.GetConversationsByConversationIDs(ctx, conversationIDs)
|
conversations, err := m.conversationClient.GetConversations(ctx, conversationIDs, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var existConversations []*conversation.Conversation
|
|
||||||
var existConversationIDs []string
|
var existConversationIDs []string
|
||||||
for _, conversation := range conversations {
|
for _, conversation := range conversations {
|
||||||
existConversations = append(existConversations, conversation)
|
|
||||||
existConversationIDs = append(existConversationIDs, conversation.ConversationID)
|
existConversationIDs = append(existConversationIDs, conversation.ConversationID)
|
||||||
}
|
}
|
||||||
log.ZDebug(ctx, "ClearConversationsMsg", "existConversationIDs", existConversationIDs)
|
log.ZDebug(ctx, "ClearConversationsMsg", "existConversationIDs", existConversationIDs)
|
||||||
@@ -152,7 +149,7 @@ func (m *msgServer) clearConversation(ctx context.Context, conversationIDs []str
|
|||||||
if err := m.MsgDatabase.SetMinSeqs(ctx, m.getMinSeqs(maxSeqs)); err != nil {
|
if err := m.MsgDatabase.SetMinSeqs(ctx, m.getMinSeqs(maxSeqs)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, conversation := range existConversations {
|
for _, conversation := range conversations {
|
||||||
tips := &sdkws.ClearConversationTips{UserID: userID, ConversationIDs: []string{conversation.ConversationID}}
|
tips := &sdkws.ClearConversationTips{UserID: userID, ConversationIDs: []string{conversation.ConversationID}}
|
||||||
m.notificationSender.NotificationWithSessionType(ctx, userID, m.conversationAndGetRecvID(conversation, userID), constant.ClearConversationNotification, conversation.ConversationType, tips)
|
m.notificationSender.NotificationWithSessionType(ctx, userID, m.conversationAndGetRecvID(conversation, userID), constant.ClearConversationNotification, conversation.ConversationType, tips)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq,
|
|||||||
go m.setConversationAtInfo(ctx, req.MsgData)
|
go m.setConversationAtInfo(ctx, req.MsgData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// m.webhookAfterSendGroupMsg(ctx, &m.config.WebhooksConfig.AfterSendGroupMsg, req)
|
m.webhookAfterSendGroupMsg(ctx, &m.config.WebhooksConfig.AfterSendGroupMsg, req)
|
||||||
|
|
||||||
prommetrics.GroupChatMsgProcessSuccessCounter.Inc()
|
prommetrics.GroupChatMsgProcessSuccessCounter.Inc()
|
||||||
resp = &pbmsg.SendMsgResp{}
|
resp = &pbmsg.SendMsgResp{}
|
||||||
@@ -194,7 +194,7 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// m.webhookAfterSendSingleMsg(ctx, &m.config.WebhooksConfig.AfterSendSingleMsg, req)
|
m.webhookAfterSendSingleMsg(ctx, &m.config.WebhooksConfig.AfterSendSingleMsg, req)
|
||||||
prommetrics.SingleChatMsgProcessSuccessCounter.Inc()
|
prommetrics.SingleChatMsgProcessSuccessCounter.Inc()
|
||||||
return &pbmsg.SendMsgResp{
|
return &pbmsg.SendMsgResp{
|
||||||
ServerMsgID: req.MsgData.ServerMsgID,
|
ServerMsgID: req.MsgData.ServerMsgID,
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ func (s *friendServer) GetFullFriendUserIDs(ctx context.Context, req *relation.G
|
|||||||
userIDs = nil
|
userIDs = nil
|
||||||
}
|
}
|
||||||
return &relation.GetFullFriendUserIDsResp{
|
return &relation.GetFullFriendUserIDsResp{
|
||||||
Version: idHash,
|
Version: uint64(vl.Version),
|
||||||
VersionID: vl.ID.Hex(),
|
VersionID: vl.ID.Hex(),
|
||||||
Equal: req.IdHash == idHash,
|
Equal: req.IdHash == idHash,
|
||||||
UserIDs: userIDs,
|
UserIDs: userIDs,
|
||||||
|
|||||||
+42
-8
@@ -5,9 +5,12 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/openimsdk/gomake/mageutil"
|
"github.com/openimsdk/gomake/mageutil"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/version"
|
||||||
|
"github.com/openimsdk/tools/utils/datautil"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Default = Build
|
var Default = Build
|
||||||
@@ -25,7 +28,6 @@ var (
|
|||||||
customToolsDir = "tools" // tools source code directory, default is "tools"
|
customToolsDir = "tools" // tools source code directory, default is "tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// Build support specifical binary build.
|
// Build support specifical binary build.
|
||||||
//
|
//
|
||||||
// Example: `mage build openim-api openim-rpc-user seq`
|
// Example: `mage build openim-api openim-rpc-user seq`
|
||||||
@@ -35,8 +37,7 @@ func Build() {
|
|||||||
if len(bin) != 0 {
|
if len(bin) != 0 {
|
||||||
bin = bin[1:]
|
bin = bin[1:]
|
||||||
}
|
}
|
||||||
|
mageutil.WithSpinner("Building binaries...", func() { mageutil.Build(bin, nil, nil) })
|
||||||
mageutil.Build(bin, nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildWithCustomConfig() {
|
func BuildWithCustomConfig() {
|
||||||
@@ -53,7 +54,9 @@ func BuildWithCustomConfig() {
|
|||||||
ToolsDir: &customToolsDir,
|
ToolsDir: &customToolsDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
mageutil.Build(bin, config)
|
mageutil.WithSpinner("Building binaries with custom config...", func() {
|
||||||
|
mageutil.Build(bin, config, nil)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start() {
|
func Start() {
|
||||||
@@ -70,7 +73,9 @@ func Start() {
|
|||||||
bin = bin[1:]
|
bin = bin[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
mageutil.StartToolsAndServices(bin, nil)
|
mageutil.WithSpinner("Starting...", func() {
|
||||||
|
mageutil.StartToolsAndServices(bin, nil)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func StartWithCustomConfig() {
|
func StartWithCustomConfig() {
|
||||||
@@ -93,13 +98,42 @@ func StartWithCustomConfig() {
|
|||||||
ConfigDir: &customConfigDir,
|
ConfigDir: &customConfigDir,
|
||||||
}
|
}
|
||||||
|
|
||||||
mageutil.StartToolsAndServices(bin, config)
|
mageutil.WithSpinner("Starting with custom config...", func() {
|
||||||
|
mageutil.StartToolsAndServices(bin, config)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func Stop() {
|
func Stop() {
|
||||||
mageutil.StopAndCheckBinaries()
|
mageutil.WithSpinner("Stopping...", mageutil.StopAndCheckBinaries)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Check() {
|
func Check() {
|
||||||
mageutil.CheckAndReportBinariesStatus()
|
mageutil.WithSpinner("Checking binaries...", mageutil.CheckAndReportBinariesStatus)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Export() {
|
||||||
|
mappingPaths, err := mageutil.GetDefaultExportMappingPaths([]string{
|
||||||
|
"cmd",
|
||||||
|
"internal",
|
||||||
|
"pkg",
|
||||||
|
"test",
|
||||||
|
"tools",
|
||||||
|
"**/*.go",
|
||||||
|
"go.mod",
|
||||||
|
"go.work",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
mageutil.PrintRed("GetDefaultExportMappingPaths failed " + err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
mageutil.WithSpinner("Exporting...", func() {
|
||||||
|
mageutil.ExportMageLauncherArchived(mappingPaths, &mageutil.ExportOptions{
|
||||||
|
ProjectName: datautil.ToPtr(fmt.Sprintf("open-im-server_%s", version.Version)),
|
||||||
|
BuildOpt: &mageutil.BuildOptions{
|
||||||
|
Release: datautil.ToPtr(true),
|
||||||
|
Compress: datautil.ToPtr(true),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,4 +66,5 @@ const (
|
|||||||
CallbackAfterCreateSingleChatConversationsCommand = "callbackAfterCreateSingleChatConversationsCommand"
|
CallbackAfterCreateSingleChatConversationsCommand = "callbackAfterCreateSingleChatConversationsCommand"
|
||||||
CallbackBeforeCreateGroupChatConversationsCommand = "callbackBeforeCreateGroupChatConversationsCommand"
|
CallbackBeforeCreateGroupChatConversationsCommand = "callbackBeforeCreateGroupChatConversationsCommand"
|
||||||
CallbackAfterCreateGroupChatConversationsCommand = "callbackAfterCreateGroupChatConversationsCommand"
|
CallbackAfterCreateGroupChatConversationsCommand = "callbackAfterCreateGroupChatConversationsCommand"
|
||||||
|
CallbackAfterMsgSaveDBCommand = "callbackAfterMsgSaveDBCommand"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -2,42 +2,42 @@ package callbackstruct
|
|||||||
|
|
||||||
type CallbackBeforeCreateSingleChatConversationsReq struct {
|
type CallbackBeforeCreateSingleChatConversationsReq struct {
|
||||||
CallbackCommand `json:"callbackCommand"`
|
CallbackCommand `json:"callbackCommand"`
|
||||||
OwnerUserID string `json:"owner_user_id"`
|
OwnerUserID string `json:"ownerUserId"`
|
||||||
ConversationID string `json:"conversation_id"`
|
ConversationID string `json:"conversationId"`
|
||||||
ConversationType int32 `json:"conversation_type"`
|
ConversationType int32 `json:"conversationType"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"userId"`
|
||||||
RecvMsgOpt int32 `json:"recv_msg_opt"`
|
RecvMsgOpt int32 `json:"recvMsgOpt"`
|
||||||
IsPinned bool `json:"is_pinned"`
|
IsPinned bool `json:"isPinned"`
|
||||||
IsPrivateChat bool `json:"is_private_chat"`
|
IsPrivateChat bool `json:"isPrivateChat"`
|
||||||
BurnDuration int32 `json:"burn_duration"`
|
BurnDuration int32 `json:"burnDuration"`
|
||||||
GroupAtType int32 `json:"group_at_type"`
|
GroupAtType int32 `json:"groupAtType"`
|
||||||
AttachedInfo string `json:"attached_info"`
|
AttachedInfo string `json:"attachedInfo"`
|
||||||
Ex string `json:"ex"`
|
Ex string `json:"ex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallbackBeforeCreateSingleChatConversationsResp struct {
|
type CallbackBeforeCreateSingleChatConversationsResp struct {
|
||||||
CommonCallbackResp
|
CommonCallbackResp
|
||||||
RecvMsgOpt *int32 `json:"recv_msg_opt"`
|
RecvMsgOpt *int32 `json:"recvMsgOpt"`
|
||||||
IsPinned *bool `json:"is_pinned"`
|
IsPinned *bool `json:"isPinned"`
|
||||||
IsPrivateChat *bool `json:"is_private_chat"`
|
IsPrivateChat *bool `json:"isPrivateChat"`
|
||||||
BurnDuration *int32 `json:"burn_duration"`
|
BurnDuration *int32 `json:"burnDuration"`
|
||||||
GroupAtType *int32 `json:"group_at_type"`
|
GroupAtType *int32 `json:"groupAtType"`
|
||||||
AttachedInfo *string `json:"attached_info"`
|
AttachedInfo *string `json:"attachedInfo"`
|
||||||
Ex *string `json:"ex"`
|
Ex *string `json:"ex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallbackAfterCreateSingleChatConversationsReq struct {
|
type CallbackAfterCreateSingleChatConversationsReq struct {
|
||||||
CallbackCommand `json:"callbackCommand"`
|
CallbackCommand `json:"callbackCommand"`
|
||||||
OwnerUserID string `json:"owner_user_id"`
|
OwnerUserID string `json:"ownerUserId"`
|
||||||
ConversationID string `json:"conversation_id"`
|
ConversationID string `json:"conversationId"`
|
||||||
ConversationType int32 `json:"conversation_type"`
|
ConversationType int32 `json:"conversationType"`
|
||||||
UserID string `json:"user_id"`
|
UserID string `json:"userId"`
|
||||||
RecvMsgOpt int32 `json:"recv_msg_opt"`
|
RecvMsgOpt int32 `json:"recvMsgOpt"`
|
||||||
IsPinned bool `json:"is_pinned"`
|
IsPinned bool `json:"isPinned"`
|
||||||
IsPrivateChat bool `json:"is_private_chat"`
|
IsPrivateChat bool `json:"isPrivateChat"`
|
||||||
BurnDuration int32 `json:"burn_duration"`
|
BurnDuration int32 `json:"burnDuration"`
|
||||||
GroupAtType int32 `json:"group_at_type"`
|
GroupAtType int32 `json:"groupAtType"`
|
||||||
AttachedInfo string `json:"attached_info"`
|
AttachedInfo string `json:"attachedInfo"`
|
||||||
Ex string `json:"ex"`
|
Ex string `json:"ex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,42 +47,42 @@ type CallbackAfterCreateSingleChatConversationsResp struct {
|
|||||||
|
|
||||||
type CallbackBeforeCreateGroupChatConversationsReq struct {
|
type CallbackBeforeCreateGroupChatConversationsReq struct {
|
||||||
CallbackCommand `json:"callbackCommand"`
|
CallbackCommand `json:"callbackCommand"`
|
||||||
OwnerUserID string `json:"owner_user_id"`
|
OwnerUserID string `json:"ownerUserId"`
|
||||||
ConversationID string `json:"conversation_id"`
|
ConversationID string `json:"conversationId"`
|
||||||
ConversationType int32 `json:"conversation_type"`
|
ConversationType int32 `json:"conversationType"`
|
||||||
GroupID string `json:"group_id"`
|
GroupID string `json:"groupId"`
|
||||||
RecvMsgOpt int32 `json:"recv_msg_opt"`
|
RecvMsgOpt int32 `json:"recvMsgOpt"`
|
||||||
IsPinned bool `json:"is_pinned"`
|
IsPinned bool `json:"isPinned"`
|
||||||
IsPrivateChat bool `json:"is_private_chat"`
|
IsPrivateChat bool `json:"isPrivateChat"`
|
||||||
BurnDuration int32 `json:"burn_duration"`
|
BurnDuration int32 `json:"burnDuration"`
|
||||||
GroupAtType int32 `json:"group_at_type"`
|
GroupAtType int32 `json:"groupAtType"`
|
||||||
AttachedInfo string `json:"attached_info"`
|
AttachedInfo string `json:"attachedInfo"`
|
||||||
Ex string `json:"ex"`
|
Ex string `json:"ex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallbackBeforeCreateGroupChatConversationsResp struct {
|
type CallbackBeforeCreateGroupChatConversationsResp struct {
|
||||||
CommonCallbackResp
|
CommonCallbackResp
|
||||||
RecvMsgOpt *int32 `json:"recv_msg_opt"`
|
RecvMsgOpt *int32 `json:"recvMsgOpt"`
|
||||||
IsPinned *bool `json:"is_pinned"`
|
IsPinned *bool `json:"isPinned"`
|
||||||
IsPrivateChat *bool `json:"is_private_chat"`
|
IsPrivateChat *bool `json:"isPrivateChat"`
|
||||||
BurnDuration *int32 `json:"burn_duration"`
|
BurnDuration *int32 `json:"burnDuration"`
|
||||||
GroupAtType *int32 `json:"group_at_type"`
|
GroupAtType *int32 `json:"groupAtType"`
|
||||||
AttachedInfo *string `json:"attached_info"`
|
AttachedInfo *string `json:"attachedInfo"`
|
||||||
Ex *string `json:"ex"`
|
Ex *string `json:"ex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CallbackAfterCreateGroupChatConversationsReq struct {
|
type CallbackAfterCreateGroupChatConversationsReq struct {
|
||||||
CallbackCommand `json:"callbackCommand"`
|
CallbackCommand `json:"callbackCommand"`
|
||||||
OwnerUserID string `json:"owner_user_id"`
|
OwnerUserID string `json:"ownerUserId"`
|
||||||
ConversationID string `json:"conversation_id"`
|
ConversationID string `json:"conversationId"`
|
||||||
ConversationType int32 `json:"conversation_type"`
|
ConversationType int32 `json:"conversationType"`
|
||||||
GroupID string `json:"group_id"`
|
GroupID string `json:"groupId"`
|
||||||
RecvMsgOpt int32 `json:"recv_msg_opt"`
|
RecvMsgOpt int32 `json:"recvMsgOpt"`
|
||||||
IsPinned bool `json:"is_pinned"`
|
IsPinned bool `json:"isPinned"`
|
||||||
IsPrivateChat bool `json:"is_private_chat"`
|
IsPrivateChat bool `json:"isPrivateChat"`
|
||||||
BurnDuration int32 `json:"burn_duration"`
|
BurnDuration int32 `json:"burnDuration"`
|
||||||
GroupAtType int32 `json:"group_at_type"`
|
GroupAtType int32 `json:"groupAtType"`
|
||||||
AttachedInfo string `json:"attached_info"`
|
AttachedInfo string `json:"attachedInfo"`
|
||||||
Ex string `json:"ex"`
|
Ex string `json:"ex"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -103,3 +103,13 @@ type CallbackSingleMsgReadReq struct {
|
|||||||
type CallbackSingleMsgReadResp struct {
|
type CallbackSingleMsgReadResp struct {
|
||||||
CommonCallbackResp
|
CommonCallbackResp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CallbackAfterMsgSaveDBReq struct {
|
||||||
|
CommonCallbackReq
|
||||||
|
RecvID string `json:"recvID"`
|
||||||
|
GroupID string `json:"groupID"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CallbackAfterMsgSaveDBResp struct {
|
||||||
|
CommonCallbackResp
|
||||||
|
}
|
||||||
|
|||||||
@@ -81,6 +81,9 @@ func (a *ApiCmd) runE() error {
|
|||||||
}
|
}
|
||||||
return startrpc.Start(
|
return startrpc.Start(
|
||||||
a.ctx, &a.apiConfig.Discovery,
|
a.ctx, &a.apiConfig.Discovery,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
|
// &a.apiConfig.API.RateLimiter,
|
||||||
&prometheus,
|
&prometheus,
|
||||||
a.apiConfig.API.Api.ListenIP, "",
|
a.apiConfig.API.Api.ListenIP, "",
|
||||||
a.apiConfig.API.Prometheus.AutoSetPorts,
|
a.apiConfig.API.Prometheus.AutoSetPorts,
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ func NewAuthRpcCmd() *AuthRpcCmd {
|
|||||||
config.RedisConfigFileName: &authConfig.RedisConfig,
|
config.RedisConfigFileName: &authConfig.RedisConfig,
|
||||||
config.MongodbConfigFileName: &authConfig.MongoConfig,
|
config.MongodbConfigFileName: &authConfig.MongoConfig,
|
||||||
config.ShareFileName: &authConfig.Share,
|
config.ShareFileName: &authConfig.Share,
|
||||||
|
config.LocalCacheConfigFileName: &authConfig.LocalCacheConfig,
|
||||||
config.DiscoveryConfigFilename: &authConfig.Discovery,
|
config.DiscoveryConfigFilename: &authConfig.Discovery,
|
||||||
}
|
}
|
||||||
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
|
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
|
||||||
@@ -56,7 +57,7 @@ func (a *AuthRpcCmd) Exec() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *AuthRpcCmd) runE() error {
|
func (a *AuthRpcCmd) runE() error {
|
||||||
return startrpc.Start(a.ctx, &a.authConfig.Discovery, &a.authConfig.RpcConfig.Prometheus, a.authConfig.RpcConfig.RPC.ListenIP,
|
return startrpc.Start(a.ctx, &a.authConfig.Discovery, &a.authConfig.RpcConfig.CircuitBreaker, &a.authConfig.RpcConfig.RateLimiter, &a.authConfig.RpcConfig.Prometheus, a.authConfig.RpcConfig.RPC.ListenIP,
|
||||||
a.authConfig.RpcConfig.RPC.RegisterIP, a.authConfig.RpcConfig.RPC.AutoSetPorts, a.authConfig.RpcConfig.RPC.Ports,
|
a.authConfig.RpcConfig.RPC.RegisterIP, a.authConfig.RpcConfig.RPC.AutoSetPorts, a.authConfig.RpcConfig.RPC.Ports,
|
||||||
a.Index(), a.authConfig.Discovery.RpcService.Auth, nil, a.authConfig,
|
a.Index(), a.authConfig.Discovery.RpcService.Auth, nil, a.authConfig,
|
||||||
[]string{
|
[]string{
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (a *ConversationRpcCmd) Exec() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ConversationRpcCmd) runE() error {
|
func (a *ConversationRpcCmd) runE() error {
|
||||||
return startrpc.Start(a.ctx, &a.conversationConfig.Discovery, &a.conversationConfig.RpcConfig.Prometheus, a.conversationConfig.RpcConfig.RPC.ListenIP,
|
return startrpc.Start(a.ctx, &a.conversationConfig.Discovery, &a.conversationConfig.RpcConfig.CircuitBreaker, &a.conversationConfig.RpcConfig.RateLimiter, &a.conversationConfig.RpcConfig.Prometheus, a.conversationConfig.RpcConfig.RPC.ListenIP,
|
||||||
a.conversationConfig.RpcConfig.RPC.RegisterIP, a.conversationConfig.RpcConfig.RPC.AutoSetPorts, a.conversationConfig.RpcConfig.RPC.Ports,
|
a.conversationConfig.RpcConfig.RPC.RegisterIP, a.conversationConfig.RpcConfig.RPC.AutoSetPorts, a.conversationConfig.RpcConfig.RPC.Ports,
|
||||||
a.Index(), a.conversationConfig.Discovery.RpcService.Conversation, &a.conversationConfig.NotificationConfig, a.conversationConfig,
|
a.Index(), a.conversationConfig.Discovery.RpcService.Conversation, &a.conversationConfig.NotificationConfig, a.conversationConfig,
|
||||||
[]string{
|
[]string{
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ func (a *CronTaskCmd) runE() error {
|
|||||||
var prometheus config.Prometheus
|
var prometheus config.Prometheus
|
||||||
return startrpc.Start(
|
return startrpc.Start(
|
||||||
a.ctx, &a.cronTaskConfig.Discovery,
|
a.ctx, &a.cronTaskConfig.Discovery,
|
||||||
|
nil,
|
||||||
|
nil,
|
||||||
&prometheus,
|
&prometheus,
|
||||||
"", "",
|
"", "",
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (a *FriendRpcCmd) Exec() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *FriendRpcCmd) runE() error {
|
func (a *FriendRpcCmd) runE() error {
|
||||||
return startrpc.Start(a.ctx, &a.relationConfig.Discovery, &a.relationConfig.RpcConfig.Prometheus, a.relationConfig.RpcConfig.RPC.ListenIP,
|
return startrpc.Start(a.ctx, &a.relationConfig.Discovery, &a.relationConfig.RpcConfig.CircuitBreaker, &a.relationConfig.RpcConfig.RateLimiter, &a.relationConfig.RpcConfig.Prometheus, a.relationConfig.RpcConfig.RPC.ListenIP,
|
||||||
a.relationConfig.RpcConfig.RPC.RegisterIP, a.relationConfig.RpcConfig.RPC.AutoSetPorts, a.relationConfig.RpcConfig.RPC.Ports,
|
a.relationConfig.RpcConfig.RPC.RegisterIP, a.relationConfig.RpcConfig.RPC.AutoSetPorts, a.relationConfig.RpcConfig.RPC.Ports,
|
||||||
a.Index(), a.relationConfig.Discovery.RpcService.Friend, &a.relationConfig.NotificationConfig, a.relationConfig,
|
a.Index(), a.relationConfig.Discovery.RpcService.Friend, &a.relationConfig.NotificationConfig, a.relationConfig,
|
||||||
[]string{
|
[]string{
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (a *GroupRpcCmd) Exec() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *GroupRpcCmd) runE() error {
|
func (a *GroupRpcCmd) runE() error {
|
||||||
return startrpc.Start(a.ctx, &a.groupConfig.Discovery, &a.groupConfig.RpcConfig.Prometheus, a.groupConfig.RpcConfig.RPC.ListenIP,
|
return startrpc.Start(a.ctx, &a.groupConfig.Discovery, &a.groupConfig.RpcConfig.CircuitBreaker, &a.groupConfig.RpcConfig.RateLimiter, &a.groupConfig.RpcConfig.Prometheus, a.groupConfig.RpcConfig.RPC.ListenIP,
|
||||||
a.groupConfig.RpcConfig.RPC.RegisterIP, a.groupConfig.RpcConfig.RPC.AutoSetPorts, a.groupConfig.RpcConfig.RPC.Ports,
|
a.groupConfig.RpcConfig.RPC.RegisterIP, a.groupConfig.RpcConfig.RPC.AutoSetPorts, a.groupConfig.RpcConfig.RPC.Ports,
|
||||||
a.Index(), a.groupConfig.Discovery.RpcService.Group, &a.groupConfig.NotificationConfig, a.groupConfig,
|
a.Index(), a.groupConfig.Discovery.RpcService.Group, &a.groupConfig.NotificationConfig, a.groupConfig,
|
||||||
[]string{
|
[]string{
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (a *MsgRpcCmd) Exec() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *MsgRpcCmd) runE() error {
|
func (a *MsgRpcCmd) runE() error {
|
||||||
return startrpc.Start(a.ctx, &a.msgConfig.Discovery, &a.msgConfig.RpcConfig.Prometheus, a.msgConfig.RpcConfig.RPC.ListenIP,
|
return startrpc.Start(a.ctx, &a.msgConfig.Discovery, &a.msgConfig.RpcConfig.CircuitBreaker, &a.msgConfig.RpcConfig.RateLimiter, &a.msgConfig.RpcConfig.Prometheus, a.msgConfig.RpcConfig.RPC.ListenIP,
|
||||||
a.msgConfig.RpcConfig.RPC.RegisterIP, a.msgConfig.RpcConfig.RPC.AutoSetPorts, a.msgConfig.RpcConfig.RPC.Ports,
|
a.msgConfig.RpcConfig.RPC.RegisterIP, a.msgConfig.RpcConfig.RPC.AutoSetPorts, a.msgConfig.RpcConfig.RPC.Ports,
|
||||||
a.Index(), a.msgConfig.Discovery.RpcService.Msg, &a.msgConfig.NotificationConfig, a.msgConfig,
|
a.Index(), a.msgConfig.Discovery.RpcService.Msg, &a.msgConfig.NotificationConfig, a.msgConfig,
|
||||||
[]string{
|
[]string{
|
||||||
|
|||||||
@@ -61,6 +61,8 @@ func (m *MsgGatewayCmd) runE() error {
|
|||||||
var prometheus config.Prometheus
|
var prometheus config.Prometheus
|
||||||
return startrpc.Start(
|
return startrpc.Start(
|
||||||
m.ctx, &m.msgGatewayConfig.Discovery,
|
m.ctx, &m.msgGatewayConfig.Discovery,
|
||||||
|
&m.msgGatewayConfig.MsgGateway.CircuitBreaker,
|
||||||
|
&m.msgGatewayConfig.MsgGateway.RateLimiter,
|
||||||
&prometheus,
|
&prometheus,
|
||||||
rpc.ListenIP, rpc.RegisterIP,
|
rpc.ListenIP, rpc.RegisterIP,
|
||||||
rpc.AutoSetPorts,
|
rpc.AutoSetPorts,
|
||||||
|
|||||||
@@ -62,6 +62,8 @@ func (m *MsgTransferCmd) runE() error {
|
|||||||
var prometheus config.Prometheus
|
var prometheus config.Prometheus
|
||||||
return startrpc.Start(
|
return startrpc.Start(
|
||||||
m.ctx, &m.msgTransferConfig.Discovery,
|
m.ctx, &m.msgTransferConfig.Discovery,
|
||||||
|
&m.msgTransferConfig.MsgTransfer.CircuitBreaker,
|
||||||
|
&m.msgTransferConfig.MsgTransfer.RateLimiter,
|
||||||
&prometheus,
|
&prometheus,
|
||||||
"", "",
|
"", "",
|
||||||
true,
|
true,
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ func (a *PushRpcCmd) Exec() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *PushRpcCmd) runE() error {
|
func (a *PushRpcCmd) runE() error {
|
||||||
return startrpc.Start(a.ctx, &a.pushConfig.Discovery, &a.pushConfig.RpcConfig.Prometheus, a.pushConfig.RpcConfig.RPC.ListenIP,
|
return startrpc.Start(a.ctx, &a.pushConfig.Discovery, &a.pushConfig.RpcConfig.CircuitBreaker, &a.pushConfig.RpcConfig.RateLimiter, &a.pushConfig.RpcConfig.Prometheus, a.pushConfig.RpcConfig.RPC.ListenIP,
|
||||||
a.pushConfig.RpcConfig.RPC.RegisterIP, a.pushConfig.RpcConfig.RPC.AutoSetPorts, a.pushConfig.RpcConfig.RPC.Ports,
|
a.pushConfig.RpcConfig.RPC.RegisterIP, a.pushConfig.RpcConfig.RPC.AutoSetPorts, a.pushConfig.RpcConfig.RPC.Ports,
|
||||||
a.Index(), a.pushConfig.Discovery.RpcService.Push, &a.pushConfig.NotificationConfig, a.pushConfig,
|
a.Index(), a.pushConfig.Discovery.RpcService.Push, &a.pushConfig.NotificationConfig, a.pushConfig,
|
||||||
[]string{
|
[]string{
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ func (a *ThirdRpcCmd) Exec() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *ThirdRpcCmd) runE() error {
|
func (a *ThirdRpcCmd) runE() error {
|
||||||
return startrpc.Start(a.ctx, &a.thirdConfig.Discovery, &a.thirdConfig.RpcConfig.Prometheus, a.thirdConfig.RpcConfig.RPC.ListenIP,
|
return startrpc.Start(a.ctx, &a.thirdConfig.Discovery, &a.thirdConfig.RpcConfig.CircuitBreaker, &a.thirdConfig.RpcConfig.RateLimiter, &a.thirdConfig.RpcConfig.Prometheus, a.thirdConfig.RpcConfig.RPC.ListenIP,
|
||||||
a.thirdConfig.RpcConfig.RPC.RegisterIP, a.thirdConfig.RpcConfig.RPC.AutoSetPorts, a.thirdConfig.RpcConfig.RPC.Ports,
|
a.thirdConfig.RpcConfig.RPC.RegisterIP, a.thirdConfig.RpcConfig.RPC.AutoSetPorts, a.thirdConfig.RpcConfig.RPC.Ports,
|
||||||
a.Index(), a.thirdConfig.Discovery.RpcService.Third, &a.thirdConfig.NotificationConfig, a.thirdConfig,
|
a.Index(), a.thirdConfig.Discovery.RpcService.Third, &a.thirdConfig.NotificationConfig, a.thirdConfig,
|
||||||
[]string{
|
[]string{
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (a *UserRpcCmd) Exec() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *UserRpcCmd) runE() error {
|
func (a *UserRpcCmd) runE() error {
|
||||||
return startrpc.Start(a.ctx, &a.userConfig.Discovery, &a.userConfig.RpcConfig.Prometheus, a.userConfig.RpcConfig.RPC.ListenIP,
|
return startrpc.Start(a.ctx, &a.userConfig.Discovery, &a.userConfig.RpcConfig.CircuitBreaker, &a.userConfig.RpcConfig.RateLimiter, &a.userConfig.RpcConfig.Prometheus, a.userConfig.RpcConfig.RPC.ListenIP,
|
||||||
a.userConfig.RpcConfig.RPC.RegisterIP, a.userConfig.RpcConfig.RPC.AutoSetPorts, a.userConfig.RpcConfig.RPC.Ports,
|
a.userConfig.RpcConfig.RPC.RegisterIP, a.userConfig.RpcConfig.RPC.AutoSetPorts, a.userConfig.RpcConfig.RPC.Ports,
|
||||||
a.Index(), a.userConfig.Discovery.RpcService.User, &a.userConfig.NotificationConfig, a.userConfig,
|
a.Index(), a.userConfig.Discovery.RpcService.User, &a.userConfig.NotificationConfig, a.userConfig,
|
||||||
[]string{
|
[]string{
|
||||||
|
|||||||
+52
-13
@@ -43,6 +43,7 @@ type CacheConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type LocalCache struct {
|
type LocalCache struct {
|
||||||
|
Auth CacheConfig `yaml:"auth"`
|
||||||
User CacheConfig `yaml:"user"`
|
User CacheConfig `yaml:"user"`
|
||||||
Group CacheConfig `yaml:"group"`
|
Group CacheConfig `yaml:"group"`
|
||||||
Friend CacheConfig `yaml:"friend"`
|
Friend CacheConfig `yaml:"friend"`
|
||||||
@@ -142,6 +143,23 @@ type API struct {
|
|||||||
Ports []int `yaml:"ports"`
|
Ports []int `yaml:"ports"`
|
||||||
GrafanaURL string `yaml:"grafanaURL"`
|
GrafanaURL string `yaml:"grafanaURL"`
|
||||||
} `yaml:"prometheus"`
|
} `yaml:"prometheus"`
|
||||||
|
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RateLimiter struct {
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
Window time.Duration `yaml:"window"`
|
||||||
|
Bucket int `yaml:"bucket"`
|
||||||
|
CPUThreshold int64 `yaml:"cpuThreshold"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CircuitBreaker struct {
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
Window time.Duration `yaml:"window"`
|
||||||
|
Bucket int `yaml:"bucket"`
|
||||||
|
Success float64 `yaml:"success"`
|
||||||
|
Request int64 `yaml:"request"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronTask struct {
|
type CronTask struct {
|
||||||
@@ -216,6 +234,8 @@ type MsgGateway struct {
|
|||||||
WebsocketMaxMsgLen int `yaml:"websocketMaxMsgLen"`
|
WebsocketMaxMsgLen int `yaml:"websocketMaxMsgLen"`
|
||||||
WebsocketTimeout int `yaml:"websocketTimeout"`
|
WebsocketTimeout int `yaml:"websocketTimeout"`
|
||||||
} `yaml:"longConnSvr"`
|
} `yaml:"longConnSvr"`
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MsgTransfer struct {
|
type MsgTransfer struct {
|
||||||
@@ -224,6 +244,8 @@ type MsgTransfer struct {
|
|||||||
AutoSetPorts bool `yaml:"autoSetPorts"`
|
AutoSetPorts bool `yaml:"autoSetPorts"`
|
||||||
Ports []int `yaml:"ports"`
|
Ports []int `yaml:"ports"`
|
||||||
} `yaml:"prometheus"`
|
} `yaml:"prometheus"`
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Push struct {
|
type Push struct {
|
||||||
@@ -254,7 +276,9 @@ type Push struct {
|
|||||||
BadgeCount bool `yaml:"badgeCount"`
|
BadgeCount bool `yaml:"badgeCount"`
|
||||||
Production bool `yaml:"production"`
|
Production bool `yaml:"production"`
|
||||||
} `yaml:"iosPush"`
|
} `yaml:"iosPush"`
|
||||||
FullUserCache bool `yaml:"fullUserCache"`
|
FullUserCache bool `yaml:"fullUserCache"`
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Auth struct {
|
type Auth struct {
|
||||||
@@ -263,28 +287,38 @@ type Auth struct {
|
|||||||
TokenPolicy struct {
|
TokenPolicy struct {
|
||||||
Expire int64 `yaml:"expire"`
|
Expire int64 `yaml:"expire"`
|
||||||
} `yaml:"tokenPolicy"`
|
} `yaml:"tokenPolicy"`
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Conversation struct {
|
type Conversation struct {
|
||||||
RPC RPC `yaml:"rpc"`
|
RPC RPC `yaml:"rpc"`
|
||||||
Prometheus Prometheus `yaml:"prometheus"`
|
Prometheus Prometheus `yaml:"prometheus"`
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Friend struct {
|
type Friend struct {
|
||||||
RPC RPC `yaml:"rpc"`
|
RPC RPC `yaml:"rpc"`
|
||||||
Prometheus Prometheus `yaml:"prometheus"`
|
Prometheus Prometheus `yaml:"prometheus"`
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Group struct {
|
type Group struct {
|
||||||
RPC RPC `yaml:"rpc"`
|
RPC RPC `yaml:"rpc"`
|
||||||
Prometheus Prometheus `yaml:"prometheus"`
|
Prometheus Prometheus `yaml:"prometheus"`
|
||||||
EnableHistoryForNewMembers bool `yaml:"enableHistoryForNewMembers"`
|
EnableHistoryForNewMembers bool `yaml:"enableHistoryForNewMembers"`
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Msg struct {
|
type Msg struct {
|
||||||
RPC RPC `yaml:"rpc"`
|
RPC RPC `yaml:"rpc"`
|
||||||
Prometheus Prometheus `yaml:"prometheus"`
|
Prometheus Prometheus `yaml:"prometheus"`
|
||||||
FriendVerify bool `yaml:"friendVerify"`
|
FriendVerify bool `yaml:"friendVerify"`
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Third struct {
|
type Third struct {
|
||||||
@@ -297,6 +331,8 @@ type Third struct {
|
|||||||
Kodo Kodo `yaml:"kodo"`
|
Kodo Kodo `yaml:"kodo"`
|
||||||
Aws Aws `yaml:"aws"`
|
Aws Aws `yaml:"aws"`
|
||||||
} `yaml:"object"`
|
} `yaml:"object"`
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
|
||||||
}
|
}
|
||||||
type Cos struct {
|
type Cos struct {
|
||||||
BucketURL string `yaml:"bucketURL"`
|
BucketURL string `yaml:"bucketURL"`
|
||||||
@@ -335,8 +371,10 @@ type Aws struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
RPC RPC `yaml:"rpc"`
|
RPC RPC `yaml:"rpc"`
|
||||||
Prometheus Prometheus `yaml:"prometheus"`
|
Prometheus Prometheus `yaml:"prometheus"`
|
||||||
|
RateLimiter RateLimiter `yaml:"rateLimiter"`
|
||||||
|
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type RPC struct {
|
type RPC struct {
|
||||||
@@ -435,6 +473,7 @@ type Webhooks struct {
|
|||||||
BeforeSendGroupMsg BeforeConfig `yaml:"beforeSendGroupMsg"`
|
BeforeSendGroupMsg BeforeConfig `yaml:"beforeSendGroupMsg"`
|
||||||
BeforeMsgModify BeforeConfig `yaml:"beforeMsgModify"`
|
BeforeMsgModify BeforeConfig `yaml:"beforeMsgModify"`
|
||||||
AfterSendGroupMsg AfterConfig `yaml:"afterSendGroupMsg"`
|
AfterSendGroupMsg AfterConfig `yaml:"afterSendGroupMsg"`
|
||||||
|
AfterMsgSaveDB AfterConfig `yaml:"afterMsgSaveDB"`
|
||||||
AfterUserOnline AfterConfig `yaml:"afterUserOnline"`
|
AfterUserOnline AfterConfig `yaml:"afterUserOnline"`
|
||||||
AfterUserOffline AfterConfig `yaml:"afterUserOffline"`
|
AfterUserOffline AfterConfig `yaml:"afterUserOffline"`
|
||||||
AfterUserKickOff AfterConfig `yaml:"afterUserKickOff"`
|
AfterUserKickOff AfterConfig `yaml:"afterUserKickOff"`
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package convert
|
||||||
|
|
||||||
|
func TokenMapDB2Pb(tokenMapDB map[string]int) map[string]int32 {
|
||||||
|
if tokenMapDB == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenMapPB := make(map[string]int32, len(tokenMapDB))
|
||||||
|
for k, v := range tokenMapDB {
|
||||||
|
tokenMapPB[k] = int32(v)
|
||||||
|
}
|
||||||
|
return tokenMapPB
|
||||||
|
}
|
||||||
|
|
||||||
|
func TokenMapPb2DB(tokenMapPB map[string]int32) map[string]int {
|
||||||
|
if tokenMapPB == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenMapDB := make(map[string]int, len(tokenMapPB))
|
||||||
|
for k, v := range tokenMapPB {
|
||||||
|
tokenMapDB[k] = int(v)
|
||||||
|
}
|
||||||
|
return tokenMapDB
|
||||||
|
}
|
||||||
@@ -70,6 +70,7 @@ const (
|
|||||||
BlockedByPeer = 1302 // Blocked by the peer
|
BlockedByPeer = 1302 // Blocked by the peer
|
||||||
NotPeersFriend = 1303 // Not the peer's friend
|
NotPeersFriend = 1303 // Not the peer's friend
|
||||||
RelationshipAlreadyError = 1304 // Already in a friend relationship
|
RelationshipAlreadyError = 1304 // Already in a friend relationship
|
||||||
|
FriendRequestHandled = 1305 // Friend request has already been handled
|
||||||
|
|
||||||
// Message error codes.
|
// Message error codes.
|
||||||
MessageHasReadDisable = 1401
|
MessageHasReadDisable = 1401
|
||||||
|
|||||||
@@ -51,10 +51,11 @@ var (
|
|||||||
|
|
||||||
ErrMessageHasReadDisable = errs.NewCodeError(MessageHasReadDisable, "MessageHasReadDisable")
|
ErrMessageHasReadDisable = errs.NewCodeError(MessageHasReadDisable, "MessageHasReadDisable")
|
||||||
|
|
||||||
ErrCanNotAddYourself = errs.NewCodeError(CanNotAddYourselfError, "CanNotAddYourselfError")
|
ErrCanNotAddYourself = errs.NewCodeError(CanNotAddYourselfError, "CanNotAddYourselfError")
|
||||||
ErrBlockedByPeer = errs.NewCodeError(BlockedByPeer, "BlockedByPeer")
|
ErrBlockedByPeer = errs.NewCodeError(BlockedByPeer, "BlockedByPeer")
|
||||||
ErrNotPeersFriend = errs.NewCodeError(NotPeersFriend, "NotPeersFriend")
|
ErrNotPeersFriend = errs.NewCodeError(NotPeersFriend, "NotPeersFriend")
|
||||||
ErrRelationshipAlready = errs.NewCodeError(RelationshipAlreadyError, "RelationshipAlreadyError")
|
ErrRelationshipAlready = errs.NewCodeError(RelationshipAlreadyError, "RelationshipAlreadyError")
|
||||||
|
ErrFriendRequestHandled = errs.NewCodeError(FriendRequestHandled, "FriendRequestHandled")
|
||||||
|
|
||||||
ErrMutedInGroup = errs.NewCodeError(MutedInGroup, "MutedInGroup")
|
ErrMutedInGroup = errs.NewCodeError(MutedInGroup, "MutedInGroup")
|
||||||
ErrMutedGroup = errs.NewCodeError(MutedGroup, "MutedGroup")
|
ErrMutedGroup = errs.NewCodeError(MutedGroup, "MutedGroup")
|
||||||
|
|||||||
@@ -0,0 +1,107 @@
|
|||||||
|
package startrpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
|
"github.com/openimsdk/tools/stability/circuitbreaker"
|
||||||
|
"github.com/openimsdk/tools/stability/circuitbreaker/sre"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CircuitBreaker struct {
|
||||||
|
Enable bool `yaml:"enable"`
|
||||||
|
Success float64 `yaml:"success"` // success rate threshold (0.0-1.0)
|
||||||
|
Request int64 `yaml:"request"` // request threshold
|
||||||
|
Bucket int `yaml:"bucket"` // number of buckets
|
||||||
|
Window time.Duration `yaml:"window"` // time window for statistics
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCircuitBreaker(config *CircuitBreaker) circuitbreaker.CircuitBreaker {
|
||||||
|
if !config.Enable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return sre.NewSREBraker(
|
||||||
|
sre.WithWindow(config.Window),
|
||||||
|
sre.WithBucket(config.Bucket),
|
||||||
|
sre.WithSuccess(config.Success),
|
||||||
|
sre.WithRequest(config.Request),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnaryCircuitBreakerInterceptor(breaker circuitbreaker.CircuitBreaker) grpc.ServerOption {
|
||||||
|
if breaker == nil {
|
||||||
|
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
|
||||||
|
return handler(ctx, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
|
||||||
|
if err := breaker.Allow(); err != nil {
|
||||||
|
log.ZWarn(ctx, "rpc circuit breaker open", err, "method", info.FullMethod)
|
||||||
|
return nil, status.Error(codes.Unavailable, "service unavailable due to circuit breaker")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err = handler(ctx, req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if st, ok := status.FromError(err); ok {
|
||||||
|
switch st.Code() {
|
||||||
|
case codes.OK:
|
||||||
|
breaker.MarkSuccess()
|
||||||
|
case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied:
|
||||||
|
breaker.MarkSuccess()
|
||||||
|
default:
|
||||||
|
breaker.MarkFailed()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
breaker.MarkFailed()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
breaker.MarkSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
return resp, err
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func StreamCircuitBreakerInterceptor(breaker circuitbreaker.CircuitBreaker) grpc.ServerOption {
|
||||||
|
if breaker == nil {
|
||||||
|
return grpc.ChainStreamInterceptor(func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
return handler(srv, ss)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return grpc.ChainStreamInterceptor(func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
if err := breaker.Allow(); err != nil {
|
||||||
|
log.ZWarn(ss.Context(), "rpc circuit breaker open", err, "method", info.FullMethod)
|
||||||
|
return status.Error(codes.Unavailable, "service unavailable due to circuit breaker")
|
||||||
|
}
|
||||||
|
|
||||||
|
err := handler(srv, ss)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if st, ok := status.FromError(err); ok {
|
||||||
|
switch st.Code() {
|
||||||
|
case codes.OK:
|
||||||
|
breaker.MarkSuccess()
|
||||||
|
case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied:
|
||||||
|
breaker.MarkSuccess()
|
||||||
|
default:
|
||||||
|
breaker.MarkFailed()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
breaker.MarkFailed()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
breaker.MarkSuccess()
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package startrpc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
|
"github.com/openimsdk/tools/stability/ratelimit"
|
||||||
|
"github.com/openimsdk/tools/stability/ratelimit/bbr"
|
||||||
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RateLimiter struct {
|
||||||
|
Enable bool
|
||||||
|
Window time.Duration
|
||||||
|
Bucket int
|
||||||
|
CPUThreshold int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRateLimiter(config *RateLimiter) ratelimit.Limiter {
|
||||||
|
if !config.Enable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return bbr.NewBBRLimiter(
|
||||||
|
bbr.WithWindow(config.Window),
|
||||||
|
bbr.WithBucket(config.Bucket),
|
||||||
|
bbr.WithCPUThreshold(config.CPUThreshold),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UnaryRateLimitInterceptor(limiter ratelimit.Limiter) grpc.ServerOption {
|
||||||
|
if limiter == nil {
|
||||||
|
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
|
||||||
|
return handler(ctx, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
|
||||||
|
done, err := limiter.Allow()
|
||||||
|
if err != nil {
|
||||||
|
log.ZWarn(ctx, "rpc rate limited", err, "method", info.FullMethod)
|
||||||
|
return nil, status.Errorf(codes.ResourceExhausted, "rpc request rate limit exceeded: %v, please try again later", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer done(ratelimit.DoneInfo{})
|
||||||
|
return handler(ctx, req)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func StreamRateLimitInterceptor(limiter ratelimit.Limiter) grpc.ServerOption {
|
||||||
|
if limiter == nil {
|
||||||
|
return grpc.ChainStreamInterceptor(func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
return handler(srv, ss)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return grpc.ChainStreamInterceptor(func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
done, err := limiter.Allow()
|
||||||
|
if err != nil {
|
||||||
|
log.ZWarn(ss.Context(), "rpc rate limited", err, "method", info.FullMethod)
|
||||||
|
return status.Errorf(codes.ResourceExhausted, "rpc request rate limit exceeded: %v, please try again later", err)
|
||||||
|
}
|
||||||
|
defer done(ratelimit.DoneInfo{})
|
||||||
|
|
||||||
|
return handler(srv, ss)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -47,7 +47,7 @@ func init() {
|
|||||||
prommetrics.RegistryAll()
|
prommetrics.RegistryAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start[T any](ctx context.Context, disc *conf.Discovery, prometheusConfig *conf.Prometheus, listenIP,
|
func Start[T any](ctx context.Context, disc *conf.Discovery, circuitBreakerConfig *conf.CircuitBreaker, rateLimiterConfig *conf.RateLimiter, 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.ServiceRegistrar) error,
|
rpcFn func(ctx context.Context, config T, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error,
|
||||||
@@ -84,6 +84,45 @@ func Start[T any](ctx context.Context, disc *conf.Discovery, prometheusConfig *c
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if circuitBreakerConfig != nil && circuitBreakerConfig.Enable {
|
||||||
|
cb := &CircuitBreaker{
|
||||||
|
Enable: circuitBreakerConfig.Enable,
|
||||||
|
Success: circuitBreakerConfig.Success,
|
||||||
|
Request: circuitBreakerConfig.Request,
|
||||||
|
Bucket: circuitBreakerConfig.Bucket,
|
||||||
|
Window: circuitBreakerConfig.Window,
|
||||||
|
}
|
||||||
|
|
||||||
|
breaker := NewCircuitBreaker(cb)
|
||||||
|
|
||||||
|
options = append(options,
|
||||||
|
UnaryCircuitBreakerInterceptor(breaker),
|
||||||
|
StreamCircuitBreakerInterceptor(breaker),
|
||||||
|
)
|
||||||
|
|
||||||
|
log.ZInfo(ctx, "RPC circuit breaker enabled",
|
||||||
|
"service", rpcRegisterName,
|
||||||
|
"window", circuitBreakerConfig.Window,
|
||||||
|
"bucket", circuitBreakerConfig.Bucket,
|
||||||
|
"success", circuitBreakerConfig.Success,
|
||||||
|
"requestThreshold", circuitBreakerConfig.Request)
|
||||||
|
}
|
||||||
|
|
||||||
|
if rateLimiterConfig != nil && rateLimiterConfig.Enable {
|
||||||
|
limiter := NewRateLimiter((*RateLimiter)(rateLimiterConfig))
|
||||||
|
|
||||||
|
options = append(options,
|
||||||
|
UnaryRateLimitInterceptor(limiter),
|
||||||
|
StreamRateLimitInterceptor(limiter),
|
||||||
|
)
|
||||||
|
|
||||||
|
log.ZInfo(ctx, "RPC rate limiter enabled",
|
||||||
|
"service", rpcRegisterName,
|
||||||
|
"window", rateLimiterConfig.Window,
|
||||||
|
"bucket", rateLimiterConfig.Bucket,
|
||||||
|
"cpuThreshold", rateLimiterConfig.CPUThreshold)
|
||||||
|
}
|
||||||
|
|
||||||
registerIP, err := network.GetRpcRegisterIP(registerIP)
|
registerIP, err := network.GetRpcRegisterIP(registerIP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -123,7 +162,7 @@ func Start[T any](ctx context.Context, disc *conf.Discovery, prometheusConfig *c
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
|
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT)
|
||||||
select {
|
select {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
|
|||||||
+2
-1
@@ -16,6 +16,7 @@ package cache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,7 +58,7 @@ type ConversationCache interface {
|
|||||||
GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error)
|
GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error)
|
||||||
DelConversationNotReceiveMessageUserIDs(conversationIDs ...string) ConversationCache
|
DelConversationNotReceiveMessageUserIDs(conversationIDs ...string) ConversationCache
|
||||||
DelConversationNotNotifyMessageUserIDs(userIDs ...string) ConversationCache
|
DelConversationNotNotifyMessageUserIDs(userIDs ...string) ConversationCache
|
||||||
DelConversationPinnedMessageUserIDs(userIDs ...string) ConversationCache
|
DelUserPinnedConversations(userIDs ...string) ConversationCache
|
||||||
DelConversationVersionUserIDs(userIDs ...string) ConversationCache
|
DelConversationVersionUserIDs(userIDs ...string) ConversationCache
|
||||||
|
|
||||||
FindMaxConversationUserVersion(ctx context.Context, userID string) (*relationtb.VersionLog, error)
|
FindMaxConversationUserVersion(ctx context.Context, userID string) (*relationtb.VersionLog, error)
|
||||||
|
|||||||
+1
-1
@@ -24,7 +24,7 @@ var (
|
|||||||
|
|
||||||
func NewMsgCache(cache database.Cache, msgDocDatabase database.Msg) cache.MsgCache {
|
func NewMsgCache(cache database.Cache, msgDocDatabase database.Msg) cache.MsgCache {
|
||||||
initMemMsgCache.Do(func() {
|
initMemMsgCache.Do(func() {
|
||||||
memMsgCache = lru.NewLayLRU[string, *model.MsgInfoModel](1024*8, time.Hour, time.Second*10, localcache.EmptyTarget{}, nil)
|
memMsgCache = lru.NewLazyLRU[string, *model.MsgInfoModel](1024*8, time.Hour, time.Second*10, localcache.EmptyTarget{}, nil)
|
||||||
})
|
})
|
||||||
return &msgCache{
|
return &msgCache{
|
||||||
cache: cache,
|
cache: cache,
|
||||||
|
|||||||
+1
-1
@@ -253,7 +253,7 @@ func (c *ConversationRedisCache) DelConversationNotNotifyMessageUserIDs(userIDs
|
|||||||
return cache
|
return cache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConversationRedisCache) DelConversationPinnedMessageUserIDs(userIDs ...string) cache.ConversationCache {
|
func (c *ConversationRedisCache) DelUserPinnedConversations(userIDs ...string) cache.ConversationCache {
|
||||||
cache := c.CloneConversationCache()
|
cache := c.CloneConversationCache()
|
||||||
for _, userID := range userIDs {
|
for _, userID := range userIDs {
|
||||||
cache.AddKeys(c.getPinnedConversationIDsKey(userID))
|
cache.AddKeys(c.getPinnedConversationIDsKey(userID))
|
||||||
|
|||||||
+73
-5
@@ -2,13 +2,16 @@ package redis
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"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"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||||
"github.com/openimsdk/tools/errs"
|
"github.com/openimsdk/tools/errs"
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
"github.com/openimsdk/tools/utils/datautil"
|
"github.com/openimsdk/tools/utils/datautil"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
)
|
)
|
||||||
@@ -16,16 +19,26 @@ import (
|
|||||||
type tokenCache struct {
|
type tokenCache struct {
|
||||||
rdb redis.UniversalClient
|
rdb redis.UniversalClient
|
||||||
accessExpire time.Duration
|
accessExpire time.Duration
|
||||||
|
localCache *config.LocalCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTokenCacheModel(rdb redis.UniversalClient, accessExpire int64) cache.TokenModel {
|
func NewTokenCacheModel(rdb redis.UniversalClient, localCache *config.LocalCache, accessExpire int64) cache.TokenModel {
|
||||||
c := &tokenCache{rdb: rdb}
|
c := &tokenCache{rdb: rdb, localCache: localCache}
|
||||||
c.accessExpire = c.getExpireTime(accessExpire)
|
c.accessExpire = c.getExpireTime(accessExpire)
|
||||||
return c
|
return c
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tokenCache) SetTokenFlag(ctx context.Context, userID string, platformID int, token string, flag int) error {
|
func (c *tokenCache) SetTokenFlag(ctx context.Context, userID string, platformID int, token string, flag int) error {
|
||||||
return errs.Wrap(c.rdb.HSet(ctx, cachekey.GetTokenKey(userID, platformID), token, flag).Err())
|
key := cachekey.GetTokenKey(userID, platformID)
|
||||||
|
if err := c.rdb.HSet(ctx, key, token, flag).Err(); err != nil {
|
||||||
|
return errs.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.localCache != nil {
|
||||||
|
c.removeLocalTokenCache(ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTokenFlagEx set token and flag with expire time
|
// SetTokenFlagEx set token and flag with expire time
|
||||||
@@ -37,6 +50,11 @@ func (c *tokenCache) SetTokenFlagEx(ctx context.Context, userID string, platform
|
|||||||
if err := c.rdb.Expire(ctx, key, c.accessExpire).Err(); err != nil {
|
if err := c.rdb.Expire(ctx, key, c.accessExpire).Err(); err != nil {
|
||||||
return errs.Wrap(err)
|
return errs.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.localCache != nil {
|
||||||
|
c.removeLocalTokenCache(ctx, key)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +124,17 @@ func (c *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, pla
|
|||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
mm[k] = v
|
mm[k] = v
|
||||||
}
|
}
|
||||||
return errs.Wrap(c.rdb.HSet(ctx, cachekey.GetTokenKey(userID, platformID), mm).Err())
|
|
||||||
|
err := c.rdb.HSet(ctx, cachekey.GetTokenKey(userID, platformID), mm).Err()
|
||||||
|
if err != nil {
|
||||||
|
return errs.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.localCache != nil {
|
||||||
|
c.removeLocalTokenCache(ctx, cachekey.GetTokenKey(userID, platformID))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tokenCache) BatchSetTokenMapByUidPid(ctx context.Context, tokens map[string]map[string]any) error {
|
func (c *tokenCache) BatchSetTokenMapByUidPid(ctx context.Context, tokens map[string]map[string]any) error {
|
||||||
@@ -124,11 +152,23 @@ func (c *tokenCache) BatchSetTokenMapByUidPid(ctx context.Context, tokens map[st
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.localCache != nil {
|
||||||
|
c.removeLocalTokenCache(ctx, keys...)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tokenCache) DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error {
|
func (c *tokenCache) DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error {
|
||||||
return errs.Wrap(c.rdb.HDel(ctx, cachekey.GetTokenKey(userID, platformID), fields...).Err())
|
key := cachekey.GetTokenKey(userID, platformID)
|
||||||
|
if err := c.rdb.HDel(ctx, key, fields...).Err(); err != nil {
|
||||||
|
return errs.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.localCache != nil {
|
||||||
|
c.removeLocalTokenCache(ctx, key)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *tokenCache) getExpireTime(t int64) time.Duration {
|
func (c *tokenCache) getExpireTime(t int64) time.Duration {
|
||||||
@@ -161,6 +201,11 @@ func (c *tokenCache) DeleteTokenByTokenMap(ctx context.Context, userID string, t
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove local cache for the token
|
||||||
|
if c.localCache != nil {
|
||||||
|
c.removeLocalTokenCache(ctx, keys...)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,5 +220,28 @@ func (c *tokenCache) DeleteAndSetTemporary(ctx context.Context, userID string, p
|
|||||||
if err := c.rdb.HDel(ctx, key, fields...).Err(); err != nil {
|
if err := c.rdb.HDel(ctx, key, fields...).Err(); err != nil {
|
||||||
return errs.Wrap(err)
|
return errs.Wrap(err)
|
||||||
}
|
}
|
||||||
|
if c.localCache != nil {
|
||||||
|
c.removeLocalTokenCache(ctx, key)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *tokenCache) removeLocalTokenCache(ctx context.Context, keys ...string) {
|
||||||
|
if len(keys) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
topic := c.localCache.Auth.Topic
|
||||||
|
if topic == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(keys)
|
||||||
|
if err != nil {
|
||||||
|
log.ZWarn(ctx, "keys json marshal failed", err, "topic", topic, "keys", keys)
|
||||||
|
} else {
|
||||||
|
if err := c.rdb.Publish(ctx, topic, string(data)).Err(); err != nil {
|
||||||
|
log.ZWarn(ctx, "redis publish cache delete error", err, "topic", topic, "keys", keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,8 +61,6 @@ type ConversationDatabase interface {
|
|||||||
GetAllConversationIDsNumber(ctx context.Context) (int64, error)
|
GetAllConversationIDsNumber(ctx context.Context) (int64, error)
|
||||||
// PageConversationIDs paginates through conversation IDs based on the specified pagination settings.
|
// PageConversationIDs paginates through conversation IDs based on the specified pagination settings.
|
||||||
PageConversationIDs(ctx context.Context, pagination pagination.Pagination) (conversationIDs []string, err error)
|
PageConversationIDs(ctx context.Context, pagination pagination.Pagination) (conversationIDs []string, err error)
|
||||||
// GetConversationsByConversationID retrieves conversations by their IDs.
|
|
||||||
GetConversationsByConversationID(ctx context.Context, conversationIDs []string) ([]*relationtb.Conversation, error)
|
|
||||||
// GetConversationIDsNeedDestruct fetches conversations that need to be destructed based on specific criteria.
|
// GetConversationIDsNeedDestruct fetches conversations that need to be destructed based on specific criteria.
|
||||||
GetConversationIDsNeedDestruct(ctx context.Context) ([]*relationtb.Conversation, error)
|
GetConversationIDsNeedDestruct(ctx context.Context) ([]*relationtb.Conversation, error)
|
||||||
// GetConversationNotReceiveMessageUserIDs gets user IDs for users in a conversation who have not received messages.
|
// GetConversationNotReceiveMessageUserIDs gets user IDs for users in a conversation who have not received messages.
|
||||||
@@ -78,6 +76,8 @@ type ConversationDatabase interface {
|
|||||||
GetPinnedConversationIDs(ctx context.Context, userID string) ([]string, error)
|
GetPinnedConversationIDs(ctx context.Context, userID string) ([]string, error)
|
||||||
// FindRandConversation finds random conversations based on the specified timestamp and limit.
|
// FindRandConversation finds random conversations based on the specified timestamp and limit.
|
||||||
FindRandConversation(ctx context.Context, ts int64, limit int) ([]*relationtb.Conversation, error)
|
FindRandConversation(ctx context.Context, ts int64, limit int) ([]*relationtb.Conversation, error)
|
||||||
|
|
||||||
|
DeleteUsersConversations(ctx context.Context, userID string, conversationIDs []string) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConversationDatabase(conversation database.Conversation, cache cache.ConversationCache, tx tx.Tx) ConversationDatabase {
|
func NewConversationDatabase(conversation database.Conversation, cache cache.ConversationCache, tx tx.Tx) ConversationDatabase {
|
||||||
@@ -120,7 +120,7 @@ func (c *conversationDatabase) SetUsersConversationFieldTx(ctx context.Context,
|
|||||||
cache = cache.DelConversationNotNotifyMessageUserIDs(userIDs...)
|
cache = cache.DelConversationNotNotifyMessageUserIDs(userIDs...)
|
||||||
}
|
}
|
||||||
if _, ok := fieldMap["is_pinned"]; ok {
|
if _, ok := fieldMap["is_pinned"]; ok {
|
||||||
cache = cache.DelConversationPinnedMessageUserIDs(userIDs...)
|
cache = cache.DelUserPinnedConversations(userIDs...)
|
||||||
}
|
}
|
||||||
cache = cache.DelConversationVersionUserIDs(haveUserIDs...)
|
cache = cache.DelConversationVersionUserIDs(haveUserIDs...)
|
||||||
}
|
}
|
||||||
@@ -172,7 +172,7 @@ func (c *conversationDatabase) UpdateUsersConversationField(ctx context.Context,
|
|||||||
cache = cache.DelConversationNotNotifyMessageUserIDs(userIDs...)
|
cache = cache.DelConversationNotNotifyMessageUserIDs(userIDs...)
|
||||||
}
|
}
|
||||||
if _, ok := args["is_pinned"]; ok {
|
if _, ok := args["is_pinned"]; ok {
|
||||||
cache = cache.DelConversationPinnedMessageUserIDs(userIDs...)
|
cache = cache.DelUserPinnedConversations(userIDs...)
|
||||||
}
|
}
|
||||||
return cache.ChainExecDel(ctx)
|
return cache.ChainExecDel(ctx)
|
||||||
}
|
}
|
||||||
@@ -203,7 +203,7 @@ func (c *conversationDatabase) CreateConversation(ctx context.Context, conversat
|
|||||||
DelUserConversationIDsHash(userIDs...).
|
DelUserConversationIDsHash(userIDs...).
|
||||||
DelConversationVersionUserIDs(userIDs...).
|
DelConversationVersionUserIDs(userIDs...).
|
||||||
DelConversationNotNotifyMessageUserIDs(notNotifyUserIDs...).
|
DelConversationNotNotifyMessageUserIDs(notNotifyUserIDs...).
|
||||||
DelConversationPinnedMessageUserIDs(pinnedUserIDs...).
|
DelUserPinnedConversations(pinnedUserIDs...).
|
||||||
ChainExecDel(ctx)
|
ChainExecDel(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +259,7 @@ func (c *conversationDatabase) SetUserConversations(ctx context.Context, ownerUs
|
|||||||
cache := c.cache.CloneConversationCache()
|
cache := c.cache.CloneConversationCache()
|
||||||
cache = cache.DelConversationVersionUserIDs(ownerUserID).
|
cache = cache.DelConversationVersionUserIDs(ownerUserID).
|
||||||
DelConversationNotNotifyMessageUserIDs(ownerUserID).
|
DelConversationNotNotifyMessageUserIDs(ownerUserID).
|
||||||
DelConversationPinnedMessageUserIDs(ownerUserID)
|
DelUserPinnedConversations(ownerUserID)
|
||||||
|
|
||||||
groupIDs := datautil.Distinct(datautil.Filter(conversations, func(e *relationtb.Conversation) (string, bool) {
|
groupIDs := datautil.Distinct(datautil.Filter(conversations, func(e *relationtb.Conversation) (string, bool) {
|
||||||
return e.GroupID, e.GroupID != ""
|
return e.GroupID, e.GroupID != ""
|
||||||
@@ -373,10 +373,6 @@ func (c *conversationDatabase) PageConversationIDs(ctx context.Context, paginati
|
|||||||
return c.conversationDB.PageConversationIDs(ctx, pagination)
|
return c.conversationDB.PageConversationIDs(ctx, pagination)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conversationDatabase) GetConversationsByConversationID(ctx context.Context, conversationIDs []string) ([]*relationtb.Conversation, error) {
|
|
||||||
return c.conversationDB.GetConversationsByConversationID(ctx, conversationIDs)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *conversationDatabase) GetConversationIDsNeedDestruct(ctx context.Context) ([]*relationtb.Conversation, error) {
|
func (c *conversationDatabase) GetConversationIDsNeedDestruct(ctx context.Context) ([]*relationtb.Conversation, error) {
|
||||||
return c.conversationDB.GetConversationIDsNeedDestruct(ctx)
|
return c.conversationDB.GetConversationIDsNeedDestruct(ctx)
|
||||||
}
|
}
|
||||||
@@ -429,3 +425,21 @@ func (c *conversationDatabase) GetPinnedConversationIDs(ctx context.Context, use
|
|||||||
func (c *conversationDatabase) FindRandConversation(ctx context.Context, ts int64, limit int) ([]*relationtb.Conversation, error) {
|
func (c *conversationDatabase) FindRandConversation(ctx context.Context, ts int64, limit int) ([]*relationtb.Conversation, error) {
|
||||||
return c.conversationDB.FindRandConversation(ctx, ts, limit)
|
return c.conversationDB.FindRandConversation(ctx, ts, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *conversationDatabase) DeleteUsersConversations(ctx context.Context, userID string, conversationIDs []string) (err error) {
|
||||||
|
return c.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||||
|
err = c.conversationDB.DeleteUsersConversations(ctx, userID, conversationIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cache := c.cache.CloneConversationCache()
|
||||||
|
cache = cache.DelConversations(userID, conversationIDs...).
|
||||||
|
DelConversationVersionUserIDs(userID).
|
||||||
|
DelConversationIDs(userID).
|
||||||
|
DelUserConversationIDsHash(userID).
|
||||||
|
DelConversationNotNotifyMessageUserIDs(userID).
|
||||||
|
DelUserPinnedConversations(userID)
|
||||||
|
|
||||||
|
return cache.ChainExecDel(ctx)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||||
"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"
|
||||||
@@ -109,15 +109,13 @@ func (f *friendDatabase) CheckIn(ctx context.Context, userID1, userID2 string) (
|
|||||||
// Retrieve friend IDs of userID1 from the cache
|
// Retrieve friend IDs of userID1 from the cache
|
||||||
userID1FriendIDs, err := f.cache.GetFriendIDs(ctx, userID1)
|
userID1FriendIDs, err := f.cache.GetFriendIDs(ctx, userID1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("error retrieving friend IDs for user %s: %w", userID1, err)
|
return false, false, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieve friend IDs of userID2 from the cache
|
// Retrieve friend IDs of userID2 from the cache
|
||||||
userID2FriendIDs, err := f.cache.GetFriendIDs(ctx, userID2)
|
userID2FriendIDs, err := f.cache.GetFriendIDs(ctx, userID2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("error retrieving friend IDs for user %s: %w", userID2, err)
|
return false, false, err
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if userID2 is in userID1's friend list and vice versa
|
// Check if userID2 is in userID1's friend list and vice versa
|
||||||
@@ -214,12 +212,12 @@ func (f *friendDatabase) RefuseFriendRequest(ctx context.Context, friendRequest
|
|||||||
// Attempt to retrieve the friend request from the database.
|
// Attempt to retrieve the friend request from the database.
|
||||||
fr, err := f.friendRequest.Take(ctx, friendRequest.FromUserID, friendRequest.ToUserID)
|
fr, err := f.friendRequest.Take(ctx, friendRequest.FromUserID, friendRequest.ToUserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to retrieve friend request from %s to %s: %w", friendRequest.FromUserID, friendRequest.ToUserID, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the friend request has already been handled.
|
// Check if the friend request has already been handled.
|
||||||
if fr.HandleResult != 0 {
|
if fr.HandleResult != 0 {
|
||||||
return fmt.Errorf("friend request from %s to %s has already been processed", friendRequest.FromUserID, friendRequest.ToUserID)
|
return servererrs.ErrFriendRequestHandled.WrapMsg("friend request has already been processed", "from", friendRequest.FromUserID, "to", friendRequest.ToUserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the action of refusing the friend request for debugging and auditing purposes.
|
// Log the action of refusing the friend request for debugging and auditing purposes.
|
||||||
@@ -232,7 +230,7 @@ func (f *friendDatabase) RefuseFriendRequest(ctx context.Context, friendRequest
|
|||||||
friendRequest.HandleResult = constant.FriendResponseRefuse
|
friendRequest.HandleResult = constant.FriendResponseRefuse
|
||||||
friendRequest.HandleTime = time.Now()
|
friendRequest.HandleTime = time.Now()
|
||||||
if err := f.friendRequest.Update(ctx, friendRequest); err != nil {
|
if err := f.friendRequest.Update(ctx, friendRequest); err != nil {
|
||||||
return fmt.Errorf("failed to update friend request from %s to %s as refused: %w", friendRequest.FromUserID, friendRequest.ToUserID, err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -350,9 +348,9 @@ func (f *friendDatabase) PageFriendRequestToMe(ctx context.Context, userID strin
|
|||||||
func (f *friendDatabase) FindFriendsWithError(ctx context.Context, ownerUserID string, friendUserIDs []string) (friends []*model.Friend, err error) {
|
func (f *friendDatabase) FindFriendsWithError(ctx context.Context, ownerUserID string, friendUserIDs []string) (friends []*model.Friend, err error) {
|
||||||
friends, err = f.friend.FindFriends(ctx, ownerUserID, friendUserIDs)
|
friends, err = f.friend.FindFriends(ctx, ownerUserID, friendUserIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return nil, err
|
||||||
}
|
}
|
||||||
return
|
return friends, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *friendDatabase) FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error) {
|
func (f *friendDatabase) FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error) {
|
||||||
|
|||||||
@@ -194,7 +194,6 @@ func (g *groupDatabase) CreateGroup(ctx context.Context, groups []*model.Group,
|
|||||||
}
|
}
|
||||||
for _, group := range groups {
|
for _, group := range groups {
|
||||||
c = c.DelGroupsInfo(group.GroupID).
|
c = c.DelGroupsInfo(group.GroupID).
|
||||||
DelGroupMembersHash(group.GroupID).
|
|
||||||
DelGroupMembersHash(group.GroupID).
|
DelGroupMembersHash(group.GroupID).
|
||||||
DelGroupsMemberNum(group.GroupID).
|
DelGroupsMemberNum(group.GroupID).
|
||||||
DelGroupMemberIDs(group.GroupID).
|
DelGroupMemberIDs(group.GroupID).
|
||||||
|
|||||||
@@ -39,9 +39,9 @@ type Conversation interface {
|
|||||||
GetAllConversationIDs(ctx context.Context) ([]string, error)
|
GetAllConversationIDs(ctx context.Context) ([]string, error)
|
||||||
GetAllConversationIDsNumber(ctx context.Context) (int64, error)
|
GetAllConversationIDsNumber(ctx context.Context) (int64, error)
|
||||||
PageConversationIDs(ctx context.Context, pagination pagination.Pagination) (conversationIDs []string, err error)
|
PageConversationIDs(ctx context.Context, pagination pagination.Pagination) (conversationIDs []string, err error)
|
||||||
GetConversationsByConversationID(ctx context.Context, conversationIDs []string) ([]*model.Conversation, error)
|
|
||||||
GetConversationIDsNeedDestruct(ctx context.Context) ([]*model.Conversation, error)
|
GetConversationIDsNeedDestruct(ctx context.Context) ([]*model.Conversation, error)
|
||||||
GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error)
|
GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error)
|
||||||
FindConversationUserVersion(ctx context.Context, userID string, version uint, limit int) (*model.VersionLog, error)
|
FindConversationUserVersion(ctx context.Context, userID string, version uint, limit int) (*model.VersionLog, error)
|
||||||
FindRandConversation(ctx context.Context, ts int64, limit int) ([]*model.Conversation, error)
|
FindRandConversation(ctx context.Context, ts int64, limit int) ([]*model.Conversation, error)
|
||||||
|
DeleteUsersConversations(ctx context.Context, userID string, conversationIDs []string) (err error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,12 @@ func NewConversationMongo(db *mongo.Database) (*ConversationMgo, error) {
|
|||||||
},
|
},
|
||||||
Options: options.Index(),
|
Options: options.Index(),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Keys: bson.D{
|
||||||
|
{Key: "conversation_id", Value: 1},
|
||||||
|
},
|
||||||
|
Options: options.Index().SetUnique(true),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.Wrap(err)
|
return nil, errs.Wrap(err)
|
||||||
@@ -232,10 +238,6 @@ func (c *ConversationMgo) PageConversationIDs(ctx context.Context, pagination pa
|
|||||||
return mongoutil.FindPageOnly[string](ctx, c.coll, bson.M{}, pagination, options.Find().SetProjection(bson.M{"conversation_id": 1}))
|
return mongoutil.FindPageOnly[string](ctx, c.coll, bson.M{}, pagination, options.Find().SetProjection(bson.M{"conversation_id": 1}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConversationMgo) GetConversationsByConversationID(ctx context.Context, conversationIDs []string) ([]*model.Conversation, error) {
|
|
||||||
return mongoutil.Find[*model.Conversation](ctx, c.coll, bson.M{"conversation_id": bson.M{"$in": conversationIDs}})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ConversationMgo) GetConversationIDsNeedDestruct(ctx context.Context) ([]*model.Conversation, error) {
|
func (c *ConversationMgo) GetConversationIDsNeedDestruct(ctx context.Context) ([]*model.Conversation, error) {
|
||||||
// "is_msg_destruct = 1 && msg_destruct_time != 0 && (UNIX_TIMESTAMP(NOW()) > (msg_destruct_time + UNIX_TIMESTAMP(latest_msg_destruct_time)) || latest_msg_destruct_time is NULL)"
|
// "is_msg_destruct = 1 && msg_destruct_time != 0 && (UNIX_TIMESTAMP(NOW()) > (msg_destruct_time + UNIX_TIMESTAMP(latest_msg_destruct_time)) || latest_msg_destruct_time is NULL)"
|
||||||
return mongoutil.Find[*model.Conversation](ctx, c.coll, bson.M{
|
return mongoutil.Find[*model.Conversation](ctx, c.coll, bson.M{
|
||||||
@@ -308,3 +310,20 @@ func (c *ConversationMgo) FindRandConversation(ctx context.Context, ts int64, li
|
|||||||
}
|
}
|
||||||
return mongoutil.Aggregate[*model.Conversation](ctx, c.coll, pipeline)
|
return mongoutil.Aggregate[*model.Conversation](ctx, c.coll, pipeline)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ConversationMgo) DeleteUsersConversations(ctx context.Context, userID string, conversationIDs []string) (err error) {
|
||||||
|
if len(conversationIDs) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return mongoutil.IncrVersion(func() error {
|
||||||
|
err := mongoutil.DeleteMany(ctx, c.coll, bson.M{"owner_user_id": userID, "conversation_id": bson.M{"$in": conversationIDs}})
|
||||||
|
return err
|
||||||
|
}, func() error {
|
||||||
|
for _, conversationID := range conversationIDs {
|
||||||
|
if err := c.version.IncrVersion(ctx, userID, []string{conversationID}, model.VersionStateDelete); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ func (f *FriendRequestMgo) Take(ctx context.Context, fromUserID, toUserID string
|
|||||||
func (f *FriendRequestMgo) GetUnhandledCount(ctx context.Context, userID string, ts int64) (int64, error) {
|
func (f *FriendRequestMgo) GetUnhandledCount(ctx context.Context, userID string, ts int64) (int64, error) {
|
||||||
filter := bson.M{"to_user_id": userID, "handle_result": 0}
|
filter := bson.M{"to_user_id": userID, "handle_result": 0}
|
||||||
if ts != 0 {
|
if ts != 0 {
|
||||||
filter["create_time"] = bson.M{"$gt": time.Unix(ts, 0)}
|
filter["create_time"] = bson.M{"$gt": time.UnixMilli(ts)}
|
||||||
}
|
}
|
||||||
return mongoutil.Count(ctx, f.coll, filter)
|
return mongoutil.Count(ctx, f.coll, filter)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func (g *GroupRequestMgo) GetUnhandledCount(ctx context.Context, groupIDs []stri
|
|||||||
}
|
}
|
||||||
filter := bson.M{"group_id": bson.M{"$in": groupIDs}, "handle_result": 0}
|
filter := bson.M{"group_id": bson.M{"$in": groupIDs}, "handle_result": 0}
|
||||||
if ts != 0 {
|
if ts != 0 {
|
||||||
filter["req_time"] = bson.M{"$gt": time.Unix(ts, 0)}
|
filter["req_time"] = bson.M{"$gt": time.UnixMilli(ts)}
|
||||||
}
|
}
|
||||||
return mongoutil.Count(ctx, g.coll, filter)
|
return mongoutil.Count(ctx, g.coll, filter)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
"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/common/storage/model"
|
||||||
"github.com/openimsdk/protocol/constant"
|
"github.com/openimsdk/protocol/constant"
|
||||||
@@ -14,10 +19,6 @@ import (
|
|||||||
"github.com/openimsdk/tools/errs"
|
"github.com/openimsdk/tools/errs"
|
||||||
"github.com/openimsdk/tools/utils/datautil"
|
"github.com/openimsdk/tools/utils/datautil"
|
||||||
"github.com/openimsdk/tools/utils/jsonutil"
|
"github.com/openimsdk/tools/utils/jsonutil"
|
||||||
"go.mongodb.org/mongo-driver/bson"
|
|
||||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo/options"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewMsgMongo(db *mongo.Database) (database.Msg, error) {
|
func NewMsgMongo(db *mongo.Database) (database.Msg, error) {
|
||||||
@@ -1154,7 +1155,7 @@ func (m *MsgMgo) findBeforeDocSendTime(ctx context.Context, docID string, limit
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
for i := len(res) - 1; i > 0; i-- {
|
for i := len(res) - 1; i >= 0; i-- {
|
||||||
v := res[i]
|
v := res[i]
|
||||||
if v.Msgs != nil && v.Msgs.Msg != nil && v.Msgs.Msg.SendTime > 0 {
|
if v.Msgs != nil && v.Msgs.Msg != nil && v.Msgs.Msg.SendTime > 0 {
|
||||||
return v.Msgs.Msg.Seq, v.Msgs.Msg.SendTime, nil
|
return v.Msgs.Msg.Seq, v.Msgs.Msg.SendTime, nil
|
||||||
@@ -1169,7 +1170,7 @@ func (m *MsgMgo) findBeforeSendTime(ctx context.Context, conversationID string,
|
|||||||
limit := int64(-1)
|
limit := int64(-1)
|
||||||
if first {
|
if first {
|
||||||
first = false
|
first = false
|
||||||
limit = m.model.GetMsgIndex(seq)
|
limit = m.model.GetLimitForSingleDoc(seq)
|
||||||
}
|
}
|
||||||
docID := m.model.BuildDocIDByIndex(conversationID, i)
|
docID := m.model.BuildDocIDByIndex(conversationID, i)
|
||||||
msgSeq, msgSendTime, err := m.findBeforeDocSendTime(ctx, docID, limit)
|
msgSeq, msgSendTime, err := m.findBeforeDocSendTime(ctx, docID, limit)
|
||||||
|
|||||||
@@ -57,8 +57,8 @@ func (s *seqConversationMongo) Malloc(ctx context.Context, conversationID string
|
|||||||
}
|
}
|
||||||
filter := map[string]any{"conversation_id": conversationID}
|
filter := map[string]any{"conversation_id": conversationID}
|
||||||
update := map[string]any{
|
update := map[string]any{
|
||||||
"$inc": map[string]any{"max_seq": size},
|
"$inc": map[string]any{"max_seq": size},
|
||||||
"$set": map[string]any{"min_seq": int64(0)},
|
"$setOnInsert": map[string]any{"min_seq": int64(0)},
|
||||||
}
|
}
|
||||||
opt := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After).SetProjection(map[string]any{"_id": 0, "max_seq": 1})
|
opt := options.FindOneAndUpdate().SetUpsert(true).SetReturnDocument(options.After).SetProjection(map[string]any{"_id": 0, "max_seq": 1})
|
||||||
lastSeq, err := mongoutil.FindOneAndUpdate[int64](ctx, s.coll, filter, update, opt)
|
lastSeq, err := mongoutil.FindOneAndUpdate[int64](ctx, s.coll, filter, update, opt)
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ func (u *UserMgo) TakeNotification(ctx context.Context, level int64) (user []*mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserMgo) TakeGTEAppManagerLevel(ctx context.Context, level int64) (user []*model.User, err error) {
|
func (u *UserMgo) TakeGTEAppManagerLevel(ctx context.Context, level int64) (user []*model.User, err error) {
|
||||||
return mongoutil.Find[*model.User](ctx, u.coll, bson.M{"app_manager_level": bson.M{"$gte": level}})
|
return mongoutil.Find[*model.User](ctx, u.coll, bson.M{"app_manger_level": bson.M{"$gte": level}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (u *UserMgo) TakeByNickname(ctx context.Context, nickname string) (user []*model.User, err error) {
|
func (u *UserMgo) TakeByNickname(ctx context.Context, nickname string) (user []*model.User, err error) {
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
// Copyright © 2024 OpenIM open source community. 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 kafka
|
||||||
|
|
||||||
|
type TLSConfig struct {
|
||||||
|
EnableTLS bool `yaml:"enableTLS"`
|
||||||
|
CACrt string `yaml:"caCrt"`
|
||||||
|
ClientCrt string `yaml:"clientCrt"`
|
||||||
|
ClientKey string `yaml:"clientKey"`
|
||||||
|
ClientKeyPwd string `yaml:"clientKeyPwd"`
|
||||||
|
InsecureSkipVerify bool `yaml:"insecureSkipVerify"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
Password string `yaml:"password"`
|
||||||
|
ProducerAck string `yaml:"producerAck"`
|
||||||
|
CompressType string `yaml:"compressType"`
|
||||||
|
Addr []string `yaml:"addr"`
|
||||||
|
TLS TLSConfig `yaml:"tls"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright © 2023 OpenIM. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package kafka
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/IBM/sarama"
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MConsumerGroup struct {
|
||||||
|
sarama.ConsumerGroup
|
||||||
|
groupID string
|
||||||
|
topics []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMConsumerGroup(conf *Config, groupID string, topics []string, autoCommitEnable bool) (*MConsumerGroup, error) {
|
||||||
|
config, err := BuildConsumerGroupConfig(conf, sarama.OffsetNewest, autoCommitEnable)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
group, err := NewConsumerGroup(config, conf.Addr, groupID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &MConsumerGroup{
|
||||||
|
ConsumerGroup: group,
|
||||||
|
groupID: groupID,
|
||||||
|
topics: topics,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *MConsumerGroup) GetContextFromMsg(cMsg *sarama.ConsumerMessage) context.Context {
|
||||||
|
return GetContextWithMQHeader(cMsg.Headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *MConsumerGroup) RegisterHandleAndConsumer(ctx context.Context, handler sarama.ConsumerGroupHandler) {
|
||||||
|
for {
|
||||||
|
err := mc.ConsumerGroup.Consume(ctx, mc.topics, handler)
|
||||||
|
if errors.Is(err, sarama.ErrClosedConsumerGroup) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.ZWarn(ctx, "consume err", err, "topic", mc.topics, "groupID", mc.groupID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *MConsumerGroup) Close() error {
|
||||||
|
return mc.ConsumerGroup.Close()
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
// Copyright © 2023 OpenIM. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package kafka
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/IBM/sarama"
|
||||||
|
"github.com/openimsdk/tools/errs"
|
||||||
|
"google.golang.org/protobuf/proto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Producer represents a Kafka producer.
|
||||||
|
type Producer struct {
|
||||||
|
addr []string
|
||||||
|
topic string
|
||||||
|
config *sarama.Config
|
||||||
|
producer sarama.SyncProducer
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKafkaProducer(config *sarama.Config, addr []string, topic string) (*Producer, error) {
|
||||||
|
producer, err := NewProducer(config, addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &Producer{
|
||||||
|
addr: addr,
|
||||||
|
topic: topic,
|
||||||
|
config: config,
|
||||||
|
producer: producer,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendMessage sends a message to the Kafka topic configured in the Producer.
|
||||||
|
func (p *Producer) SendMessage(ctx context.Context, key string, msg proto.Message) (int32, int64, error) {
|
||||||
|
// Marshal the protobuf message
|
||||||
|
bMsg, err := proto.Marshal(msg)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, errs.WrapMsg(err, "kafka proto Marshal err")
|
||||||
|
}
|
||||||
|
if len(bMsg) == 0 {
|
||||||
|
return 0, 0, errs.WrapMsg(errEmptyMsg, "kafka proto Marshal err")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare Kafka message
|
||||||
|
kMsg := &sarama.ProducerMessage{
|
||||||
|
Topic: p.topic,
|
||||||
|
Key: sarama.StringEncoder(key),
|
||||||
|
Value: sarama.ByteEncoder(bMsg),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate message key and value
|
||||||
|
if kMsg.Key.Length() == 0 || kMsg.Value.Length() == 0 {
|
||||||
|
return 0, 0, errs.Wrap(errEmptyMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attach context metadata as headers
|
||||||
|
header, err := GetMQHeaderWithContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
kMsg.Headers = header
|
||||||
|
|
||||||
|
// Send the message
|
||||||
|
partition, offset, err := p.producer.SendMessage(kMsg)
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, errs.WrapMsg(err, "p.producer.SendMessage error")
|
||||||
|
}
|
||||||
|
|
||||||
|
return partition, offset, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package kafka
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/IBM/sarama"
|
||||||
|
"github.com/openimsdk/tools/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func BuildConsumerGroupConfig(conf *Config, initial int64, autoCommitEnable bool) (*sarama.Config, error) {
|
||||||
|
kfk := sarama.NewConfig()
|
||||||
|
kfk.Version = sarama.V2_0_0_0
|
||||||
|
kfk.Consumer.Offsets.Initial = initial
|
||||||
|
kfk.Consumer.Offsets.AutoCommit.Enable = autoCommitEnable
|
||||||
|
kfk.Consumer.Return.Errors = false
|
||||||
|
if conf.Username != "" || conf.Password != "" {
|
||||||
|
kfk.Net.SASL.Enable = true
|
||||||
|
kfk.Net.SASL.User = conf.Username
|
||||||
|
kfk.Net.SASL.Password = conf.Password
|
||||||
|
}
|
||||||
|
if conf.TLS.EnableTLS {
|
||||||
|
tls, err := newTLSConfig(conf.TLS.ClientCrt, conf.TLS.ClientKey, conf.TLS.CACrt, []byte(conf.TLS.ClientKeyPwd), conf.TLS.InsecureSkipVerify)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kfk.Net.TLS.Config = tls
|
||||||
|
kfk.Net.TLS.Enable = true
|
||||||
|
}
|
||||||
|
return kfk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewConsumerGroup(conf *sarama.Config, addr []string, groupID string) (sarama.ConsumerGroup, error) {
|
||||||
|
cg, err := sarama.NewConsumerGroup(addr, groupID, conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.WrapMsg(err, "NewConsumerGroup failed", "addr", addr, "groupID", groupID, "conf", *conf)
|
||||||
|
}
|
||||||
|
return cg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func BuildProducerConfig(conf Config) (*sarama.Config, error) {
|
||||||
|
kfk := sarama.NewConfig()
|
||||||
|
kfk.Producer.Return.Successes = true
|
||||||
|
kfk.Producer.Return.Errors = true
|
||||||
|
kfk.Producer.Partitioner = sarama.NewHashPartitioner
|
||||||
|
if conf.Username != "" || conf.Password != "" {
|
||||||
|
kfk.Net.SASL.Enable = true
|
||||||
|
kfk.Net.SASL.User = conf.Username
|
||||||
|
kfk.Net.SASL.Password = conf.Password
|
||||||
|
}
|
||||||
|
switch strings.ToLower(conf.ProducerAck) {
|
||||||
|
case "no_response":
|
||||||
|
kfk.Producer.RequiredAcks = sarama.NoResponse
|
||||||
|
case "wait_for_local":
|
||||||
|
kfk.Producer.RequiredAcks = sarama.WaitForLocal
|
||||||
|
case "wait_for_all":
|
||||||
|
kfk.Producer.RequiredAcks = sarama.WaitForAll
|
||||||
|
default:
|
||||||
|
kfk.Producer.RequiredAcks = sarama.WaitForAll
|
||||||
|
}
|
||||||
|
if conf.CompressType == "" {
|
||||||
|
kfk.Producer.Compression = sarama.CompressionNone
|
||||||
|
} else {
|
||||||
|
if err := kfk.Producer.Compression.UnmarshalText(bytes.ToLower([]byte(conf.CompressType))); err != nil {
|
||||||
|
return nil, errs.WrapMsg(err, "UnmarshalText failed", "compressType", conf.CompressType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if conf.TLS.EnableTLS {
|
||||||
|
tls, err := newTLSConfig(conf.TLS.ClientCrt, conf.TLS.ClientKey, conf.TLS.CACrt, []byte(conf.TLS.ClientKeyPwd), conf.TLS.InsecureSkipVerify)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
kfk.Net.TLS.Config = tls
|
||||||
|
kfk.Net.TLS.Enable = true
|
||||||
|
}
|
||||||
|
return kfk, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProducer(conf *sarama.Config, addr []string) (sarama.SyncProducer, error) {
|
||||||
|
producer, err := sarama.NewSyncProducer(addr, conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.WrapMsg(err, "NewSyncProducer failed", "addr", addr, "conf", *conf)
|
||||||
|
}
|
||||||
|
return producer, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
// Copyright © 2024 OpenIM open source community. 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 kafka
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/openimsdk/tools/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
// decryptPEM decrypts a PEM block using a password.
|
||||||
|
func decryptPEM(data []byte, passphrase []byte) ([]byte, error) {
|
||||||
|
if len(passphrase) == 0 {
|
||||||
|
return data, nil
|
||||||
|
}
|
||||||
|
b, _ := pem.Decode(data)
|
||||||
|
d, err := x509.DecryptPEMBlock(b, passphrase)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.WrapMsg(err, "DecryptPEMBlock failed")
|
||||||
|
}
|
||||||
|
return pem.EncodeToMemory(&pem.Block{
|
||||||
|
Type: b.Type,
|
||||||
|
Bytes: d,
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readEncryptablePEMBlock(path string, pwd []byte) ([]byte, error) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.WrapMsg(err, "ReadFile failed", "path", path)
|
||||||
|
}
|
||||||
|
return decryptPEM(data, pwd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// newTLSConfig setup the TLS config from general config file.
|
||||||
|
func newTLSConfig(clientCertFile, clientKeyFile, caCertFile string, keyPwd []byte, insecureSkipVerify bool) (*tls.Config, error) {
|
||||||
|
var tlsConfig tls.Config
|
||||||
|
if clientCertFile != "" && clientKeyFile != "" {
|
||||||
|
certPEMBlock, err := os.ReadFile(clientCertFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.WrapMsg(err, "ReadFile failed", "clientCertFile", clientCertFile)
|
||||||
|
}
|
||||||
|
keyPEMBlock, err := readEncryptablePEMBlock(clientKeyFile, keyPwd)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.WrapMsg(err, "X509KeyPair failed")
|
||||||
|
}
|
||||||
|
tlsConfig.Certificates = []tls.Certificate{cert}
|
||||||
|
}
|
||||||
|
|
||||||
|
if caCertFile != "" {
|
||||||
|
caCert, err := os.ReadFile(caCertFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.WrapMsg(err, "ReadFile failed", "caCertFile", caCertFile)
|
||||||
|
}
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
if ok := caCertPool.AppendCertsFromPEM(caCert); !ok {
|
||||||
|
return nil, errs.New("AppendCertsFromPEM failed")
|
||||||
|
}
|
||||||
|
tlsConfig.RootCAs = caCertPool
|
||||||
|
}
|
||||||
|
tlsConfig.InsecureSkipVerify = insecureSkipVerify
|
||||||
|
return &tlsConfig, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package kafka
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"github.com/IBM/sarama"
|
||||||
|
"github.com/openimsdk/protocol/constant"
|
||||||
|
"github.com/openimsdk/tools/mcontext"
|
||||||
|
)
|
||||||
|
|
||||||
|
var errEmptyMsg = errors.New("kafka binary msg is empty")
|
||||||
|
|
||||||
|
// GetMQHeaderWithContext extracts message queue headers from the context.
|
||||||
|
func GetMQHeaderWithContext(ctx context.Context) ([]sarama.RecordHeader, error) {
|
||||||
|
operationID, opUserID, platform, connID, err := mcontext.GetCtxInfos(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return []sarama.RecordHeader{
|
||||||
|
{Key: []byte(constant.OperationID), Value: []byte(operationID)},
|
||||||
|
{Key: []byte(constant.OpUserID), Value: []byte(opUserID)},
|
||||||
|
{Key: []byte(constant.OpUserPlatform), Value: []byte(platform)},
|
||||||
|
{Key: []byte(constant.ConnID), Value: []byte(connID)},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetContextWithMQHeader creates a context from message queue headers.
|
||||||
|
func GetContextWithMQHeader(header []*sarama.RecordHeader) context.Context {
|
||||||
|
var values []string
|
||||||
|
for _, recordHeader := range header {
|
||||||
|
values = append(values, string(recordHeader.Value))
|
||||||
|
}
|
||||||
|
return mcontext.WithMustInfoCtx(values) // Attach extracted values to context
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
// Copyright © 2024 OpenIM open source community. 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 kafka
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/IBM/sarama"
|
||||||
|
"github.com/openimsdk/tools/errs"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CheckTopics(ctx context.Context, conf *Config, topics []string) error {
|
||||||
|
kfk, err := BuildConsumerGroupConfig(conf, sarama.OffsetNewest, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cli, err := sarama.NewClient(conf.Addr, kfk)
|
||||||
|
if err != nil {
|
||||||
|
return errs.WrapMsg(err, "NewClient failed", "config: ", fmt.Sprintf("%+v", conf))
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
|
||||||
|
existingTopics, err := cli.Topics()
|
||||||
|
if err != nil {
|
||||||
|
return errs.WrapMsg(err, "Failed to list topics")
|
||||||
|
}
|
||||||
|
|
||||||
|
existingTopicsMap := make(map[string]bool)
|
||||||
|
for _, t := range existingTopics {
|
||||||
|
existingTopicsMap[t] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, topic := range topics {
|
||||||
|
if !existingTopicsMap[topic] {
|
||||||
|
return errs.New("topic not exist", "topic", topic).Wrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckHealth(ctx context.Context, conf *Config) error {
|
||||||
|
kfk, err := BuildConsumerGroupConfig(conf, sarama.OffsetNewest, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cli, err := sarama.NewClient(conf.Addr, kfk)
|
||||||
|
if err != nil {
|
||||||
|
return errs.WrapMsg(err, "NewClient failed", "config: ", fmt.Sprintf("%+v", conf))
|
||||||
|
}
|
||||||
|
defer cli.Close()
|
||||||
|
|
||||||
|
// Get broker list
|
||||||
|
brokers := cli.Brokers()
|
||||||
|
if len(brokers) == 0 {
|
||||||
|
return errs.New("no brokers found").Wrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if all brokers are reachable
|
||||||
|
for _, broker := range brokers {
|
||||||
|
if err := broker.Open(kfk); err != nil {
|
||||||
|
return errs.WrapMsg(err, "failed to open broker", "broker", broker.Addr())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -132,6 +132,10 @@ func (*MsgDocModel) GetMsgIndex(seq int64) int64 {
|
|||||||
return (seq - 1) % singleGocMsgNum
|
return (seq - 1) % singleGocMsgNum
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (*MsgDocModel) GetLimitForSingleDoc(seq int64) int64 {
|
||||||
|
return seq % singleGocMsgNum
|
||||||
|
}
|
||||||
|
|
||||||
func (*MsgDocModel) indexGen(conversationID string, seqSuffix int64) string {
|
func (*MsgDocModel) indexGen(conversationID string, seqSuffix int64) string {
|
||||||
return conversationID + ":" + strconv.FormatInt(seqSuffix, 10)
|
return conversationID + ":" + strconv.FormatInt(seqSuffix, 10)
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-7
@@ -49,7 +49,7 @@ func New[V any](opts ...Option) Cache[V] {
|
|||||||
if opt.expirationEvict {
|
if opt.expirationEvict {
|
||||||
return lru.NewExpirationLRU[string, V](opt.localSlotSize, opt.localSuccessTTL, opt.localFailedTTL, opt.target, c.onEvict)
|
return lru.NewExpirationLRU[string, V](opt.localSlotSize, opt.localSuccessTTL, opt.localFailedTTL, opt.target, c.onEvict)
|
||||||
} else {
|
} else {
|
||||||
return lru.NewLayLRU[string, V](opt.localSlotSize, opt.localSuccessTTL, opt.localFailedTTL, opt.target, c.onEvict)
|
return lru.NewLazyLRU[string, V](opt.localSlotSize, opt.localSuccessTTL, opt.localFailedTTL, opt.target, c.onEvict)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if opt.localSlotNum == 1 {
|
if opt.localSlotNum == 1 {
|
||||||
@@ -72,11 +72,18 @@ type cache[V any] struct {
|
|||||||
|
|
||||||
func (c *cache[V]) onEvict(key string, value V) {
|
func (c *cache[V]) onEvict(key string, value V) {
|
||||||
if c.link != nil {
|
if c.link != nil {
|
||||||
lks := c.link.Del(key)
|
// Do not delete other keys while the underlying LRU still holds its lock;
|
||||||
for k := range lks {
|
// defer linked deletions to avoid re-entering the same slot and deadlocking.
|
||||||
if key != k { // prevent deadlock
|
if lks := c.link.Del(key); len(lks) > 0 {
|
||||||
c.local.Del(k)
|
go c.delLinked(key, lks)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cache[V]) delLinked(src string, keys map[string]struct{}) {
|
||||||
|
for k := range keys {
|
||||||
|
if src != k {
|
||||||
|
c.local.Del(k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,7 +110,7 @@ func (c *cache[V]) Get(ctx context.Context, key string, fetch func(ctx context.C
|
|||||||
func (c *cache[V]) GetLink(ctx context.Context, key string, fetch func(ctx context.Context) (V, error), link ...string) (V, error) {
|
func (c *cache[V]) GetLink(ctx context.Context, key string, fetch func(ctx context.Context) (V, error), link ...string) (V, error) {
|
||||||
if c.local != nil {
|
if c.local != nil {
|
||||||
return c.local.Get(key, func() (V, error) {
|
return c.local.Get(key, func() (V, error) {
|
||||||
if len(link) > 0 {
|
if len(link) > 0 && c.link != nil {
|
||||||
c.link.Link(key, link...)
|
c.link.Link(key, link...)
|
||||||
}
|
}
|
||||||
return fetch(ctx)
|
return fetch(ctx)
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ import (
|
|||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/localcache/lru"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestName(t *testing.T) {
|
func TestName(t *testing.T) {
|
||||||
@@ -91,3 +93,68 @@ func TestName(t *testing.T) {
|
|||||||
t.Log("del", del.Load())
|
t.Log("del", del.Load())
|
||||||
// 137.35s
|
// 137.35s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test deadlock scenario when eviction callback deletes a linked key that hashes to the same slot.
|
||||||
|
func TestCacheEvictDeadlock(t *testing.T) {
|
||||||
|
ctx := context.Background()
|
||||||
|
c := New[string](WithLocalSlotNum(1), WithLocalSlotSize(1), WithLazy())
|
||||||
|
|
||||||
|
if _, err := c.GetLink(ctx, "k1", func(ctx context.Context) (string, error) {
|
||||||
|
return "v1", nil
|
||||||
|
}, "k2"); err != nil {
|
||||||
|
t.Fatalf("seed cache failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
defer close(done)
|
||||||
|
_, _ = c.GetLink(ctx, "k2", func(ctx context.Context) (string, error) {
|
||||||
|
return "v2", nil
|
||||||
|
}, "k1")
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// expected to finish quickly; current implementation deadlocks here.
|
||||||
|
case <-time.After(time.Second):
|
||||||
|
t.Fatal("GetLink deadlocked during eviction of linked key")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExpirationLRUGetBatch(t *testing.T) {
|
||||||
|
l := lru.NewExpirationLRU[string, string](2, time.Minute, time.Second*5, EmptyTarget{}, nil)
|
||||||
|
|
||||||
|
keys := []string{"a", "b"}
|
||||||
|
values, err := l.GetBatch(keys, func(keys []string) (map[string]string, error) {
|
||||||
|
res := make(map[string]string)
|
||||||
|
for _, k := range keys {
|
||||||
|
res[k] = k + "_v"
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if len(values) != len(keys) {
|
||||||
|
t.Fatalf("expected %d values, got %d", len(keys), len(values))
|
||||||
|
}
|
||||||
|
for _, k := range keys {
|
||||||
|
if v, ok := values[k]; !ok || v != k+"_v" {
|
||||||
|
t.Fatalf("unexpected value for %s: %q, ok=%v", k, v, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// second batch should hit cache
|
||||||
|
values, err = l.GetBatch(keys, func(keys []string) (map[string]string, error) {
|
||||||
|
t.Fatalf("should not fetch on cache hit")
|
||||||
|
return nil, nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("unexpected error on cache hit: %v", err)
|
||||||
|
}
|
||||||
|
for _, k := range keys {
|
||||||
|
if v, ok := values[k]; !ok || v != k+"_v" {
|
||||||
|
t.Fatalf("unexpected cached value for %s: %q, ok=%v", k, v, ok)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -15,10 +15,11 @@
|
|||||||
package localcache
|
package localcache
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -52,8 +52,53 @@ type ExpirationLRU[K comparable, V any] struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (x *ExpirationLRU[K, V]) GetBatch(keys []K, fetch func(keys []K) (map[K]V, error)) (map[K]V, error) {
|
func (x *ExpirationLRU[K, V]) GetBatch(keys []K, fetch func(keys []K) (map[K]V, error)) (map[K]V, error) {
|
||||||
//TODO implement me
|
var (
|
||||||
panic("implement me")
|
err error
|
||||||
|
results = make(map[K]V)
|
||||||
|
misses = make([]K, 0, len(keys))
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, key := range keys {
|
||||||
|
x.lock.Lock()
|
||||||
|
v, ok := x.core.Get(key)
|
||||||
|
x.lock.Unlock()
|
||||||
|
if ok {
|
||||||
|
x.target.IncrGetHit()
|
||||||
|
v.lock.RLock()
|
||||||
|
results[key] = v.value
|
||||||
|
if v.err != nil && err == nil {
|
||||||
|
err = v.err
|
||||||
|
}
|
||||||
|
v.lock.RUnlock()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
misses = append(misses, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(misses) == 0 {
|
||||||
|
return results, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchValues, fetchErr := fetch(misses)
|
||||||
|
if fetchErr != nil && err == nil {
|
||||||
|
err = fetchErr
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, val := range fetchValues {
|
||||||
|
results[key] = val
|
||||||
|
if fetchErr != nil {
|
||||||
|
x.target.IncrGetFailed()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
x.target.IncrGetSuccess()
|
||||||
|
item := &expirationLruItem[V]{value: val}
|
||||||
|
x.lock.Lock()
|
||||||
|
x.core.Add(key, item)
|
||||||
|
x.lock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// any keys not returned from fetch remain absent (no cache write)
|
||||||
|
return results, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ExpirationLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
|
func (x *ExpirationLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
|
||||||
|
|||||||
@@ -21,25 +21,25 @@ import (
|
|||||||
"github.com/hashicorp/golang-lru/v2/simplelru"
|
"github.com/hashicorp/golang-lru/v2/simplelru"
|
||||||
)
|
)
|
||||||
|
|
||||||
type layLruItem[V any] struct {
|
type lazyLruItem[V any] struct {
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
expires int64
|
expires int64
|
||||||
err error
|
err error
|
||||||
value V
|
value V
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLayLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) *LayLRU[K, V] {
|
func NewLazyLRU[K comparable, V any](size int, successTTL, failedTTL time.Duration, target Target, onEvict EvictCallback[K, V]) *LazyLRU[K, V] {
|
||||||
var cb simplelru.EvictCallback[K, *layLruItem[V]]
|
var cb simplelru.EvictCallback[K, *lazyLruItem[V]]
|
||||||
if onEvict != nil {
|
if onEvict != nil {
|
||||||
cb = func(key K, value *layLruItem[V]) {
|
cb = func(key K, value *lazyLruItem[V]) {
|
||||||
onEvict(key, value.value)
|
onEvict(key, value.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
core, err := simplelru.NewLRU[K, *layLruItem[V]](size, cb)
|
core, err := simplelru.NewLRU[K, *lazyLruItem[V]](size, cb)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return &LayLRU[K, V]{
|
return &LazyLRU[K, V]{
|
||||||
core: core,
|
core: core,
|
||||||
successTTL: successTTL,
|
successTTL: successTTL,
|
||||||
failedTTL: failedTTL,
|
failedTTL: failedTTL,
|
||||||
@@ -47,15 +47,15 @@ func NewLayLRU[K comparable, V any](size int, successTTL, failedTTL time.Duratio
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type LayLRU[K comparable, V any] struct {
|
type LazyLRU[K comparable, V any] struct {
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
core *simplelru.LRU[K, *layLruItem[V]]
|
core *simplelru.LRU[K, *lazyLruItem[V]]
|
||||||
successTTL time.Duration
|
successTTL time.Duration
|
||||||
failedTTL time.Duration
|
failedTTL time.Duration
|
||||||
target Target
|
target Target
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LayLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
|
func (x *LazyLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
|
||||||
x.lock.Lock()
|
x.lock.Lock()
|
||||||
v, ok := x.core.Get(key)
|
v, ok := x.core.Get(key)
|
||||||
if ok {
|
if ok {
|
||||||
@@ -68,7 +68,7 @@ func (x *LayLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
|
|||||||
return value, err
|
return value, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
v = &layLruItem[V]{}
|
v = &lazyLruItem[V]{}
|
||||||
x.core.Add(key, v)
|
x.core.Add(key, v)
|
||||||
v.lock.Lock()
|
v.lock.Lock()
|
||||||
x.lock.Unlock()
|
x.lock.Unlock()
|
||||||
@@ -88,15 +88,15 @@ func (x *LayLRU[K, V]) Get(key K, fetch func() (V, error)) (V, error) {
|
|||||||
return v.value, v.err
|
return v.value, v.err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LayLRU[K, V]) GetBatch(keys []K, fetch func(keys []K) (map[K]V, error)) (map[K]V, error) {
|
func (x *LazyLRU[K, V]) GetBatch(keys []K, fetch func(keys []K) (map[K]V, error)) (map[K]V, error) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
once sync.Once
|
once sync.Once
|
||||||
)
|
)
|
||||||
|
|
||||||
res := make(map[K]V)
|
res := make(map[K]V)
|
||||||
queries := make([]K, 0)
|
queries := make([]K, 0, len(keys))
|
||||||
setVs := make(map[K]*layLruItem[V])
|
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
x.lock.Lock()
|
x.lock.Lock()
|
||||||
v, ok := x.core.Get(key)
|
v, ok := x.core.Get(key)
|
||||||
@@ -118,14 +118,20 @@ func (x *LayLRU[K, V]) GetBatch(keys []K, fetch func(keys []K) (map[K]V, error))
|
|||||||
}
|
}
|
||||||
queries = append(queries, key)
|
queries = append(queries, key)
|
||||||
}
|
}
|
||||||
values, err1 := fetch(queries)
|
|
||||||
if err1 != nil {
|
if len(queries) == 0 {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
values, fetchErr := fetch(queries)
|
||||||
|
if fetchErr != nil {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
err = err1
|
err = fetchErr
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
for key, val := range values {
|
for key, val := range values {
|
||||||
v := &layLruItem[V]{}
|
v := &lazyLruItem[V]{}
|
||||||
v.value = val
|
v.value = val
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@@ -135,7 +141,7 @@ func (x *LayLRU[K, V]) GetBatch(keys []K, fetch func(keys []K) (map[K]V, error))
|
|||||||
v.expires = time.Now().Add(x.failedTTL).UnixMilli()
|
v.expires = time.Now().Add(x.failedTTL).UnixMilli()
|
||||||
x.target.IncrGetFailed()
|
x.target.IncrGetFailed()
|
||||||
}
|
}
|
||||||
setVs[key] = v
|
|
||||||
x.lock.Lock()
|
x.lock.Lock()
|
||||||
x.core.Add(key, v)
|
x.core.Add(key, v)
|
||||||
x.lock.Unlock()
|
x.lock.Unlock()
|
||||||
@@ -145,29 +151,29 @@ func (x *LayLRU[K, V]) GetBatch(keys []K, fetch func(keys []K) (map[K]V, error))
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
//func (x *LayLRU[K, V]) Has(key K) bool {
|
//func (x *LazyLRU[K, V]) Has(key K) bool {
|
||||||
// x.lock.Lock()
|
// x.lock.Lock()
|
||||||
// defer x.lock.Unlock()
|
// defer x.lock.Unlock()
|
||||||
// return x.core.Contains(key)
|
// return x.core.Contains(key)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
func (x *LayLRU[K, V]) Set(key K, value V) {
|
func (x *LazyLRU[K, V]) Set(key K, value V) {
|
||||||
x.lock.Lock()
|
x.lock.Lock()
|
||||||
defer x.lock.Unlock()
|
defer x.lock.Unlock()
|
||||||
x.core.Add(key, &layLruItem[V]{value: value, expires: time.Now().Add(x.successTTL).UnixMilli()})
|
x.core.Add(key, &lazyLruItem[V]{value: value, expires: time.Now().Add(x.successTTL).UnixMilli()})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LayLRU[K, V]) SetHas(key K, value V) bool {
|
func (x *LazyLRU[K, V]) SetHas(key K, value V) bool {
|
||||||
x.lock.Lock()
|
x.lock.Lock()
|
||||||
defer x.lock.Unlock()
|
defer x.lock.Unlock()
|
||||||
if x.core.Contains(key) {
|
if x.core.Contains(key) {
|
||||||
x.core.Add(key, &layLruItem[V]{value: value, expires: time.Now().Add(x.successTTL).UnixMilli()})
|
x.core.Add(key, &lazyLruItem[V]{value: value, expires: time.Now().Add(x.successTTL).UnixMilli()})
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LayLRU[K, V]) Del(key K) bool {
|
func (x *LazyLRU[K, V]) Del(key K) bool {
|
||||||
x.lock.Lock()
|
x.lock.Lock()
|
||||||
ok := x.core.Remove(key)
|
ok := x.core.Remove(key)
|
||||||
x.lock.Unlock()
|
x.lock.Unlock()
|
||||||
@@ -179,6 +185,6 @@ func (x *LayLRU[K, V]) Del(key K) bool {
|
|||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *LayLRU[K, V]) Stop() {
|
func (x *LazyLRU[K, V]) Stop() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ func GetConversationIDBySessionType(sessionType int, ids ...string) string {
|
|||||||
case constant.ReadGroupChatType:
|
case constant.ReadGroupChatType:
|
||||||
return "sg_" + ids[0] // super group chat
|
return "sg_" + ids[0] // super group chat
|
||||||
case constant.NotificationChatType:
|
case constant.NotificationChatType:
|
||||||
return "sn_" + ids[0] // server notification chat
|
return "sn_" + strings.Join(ids, "_") // server notification chat
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package rpccache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/storage/cache/cachekey"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/localcache"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
|
||||||
|
"github.com/openimsdk/protocol/auth"
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewAuthLocalCache(client *rpcli.AuthClient, localCache *config.LocalCache, cli redis.UniversalClient) *AuthLocalCache {
|
||||||
|
lc := localCache.Auth
|
||||||
|
log.ZDebug(context.Background(), "AuthLocalCache", "topic", lc.Topic, "slotNum", lc.SlotNum, "slotSize", lc.SlotSize, "enable", lc.Enable())
|
||||||
|
x := &AuthLocalCache{
|
||||||
|
client: client,
|
||||||
|
local: localcache.New[[]byte](
|
||||||
|
localcache.WithLocalSlotNum(lc.SlotNum),
|
||||||
|
localcache.WithLocalSlotSize(lc.SlotSize),
|
||||||
|
localcache.WithLinkSlotNum(lc.SlotNum),
|
||||||
|
localcache.WithLocalSuccessTTL(lc.Success()),
|
||||||
|
localcache.WithLocalFailedTTL(lc.Failed()),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
if lc.Enable() {
|
||||||
|
go subscriberRedisDeleteCache(context.Background(), cli, lc.Topic, x.local.DelLocal)
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthLocalCache struct {
|
||||||
|
client *rpcli.AuthClient
|
||||||
|
local localcache.Cache[[]byte]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthLocalCache) GetExistingToken(ctx context.Context, userID string, platformID int) (val map[string]int, err error) {
|
||||||
|
resp, err := a.getExistingToken(ctx, userID, platformID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := convert.TokenMapPb2DB(resp.TokenStates)
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AuthLocalCache) getExistingToken(ctx context.Context, userID string, platformID int) (val *auth.GetExistingTokenResp, err error) {
|
||||||
|
start := time.Now()
|
||||||
|
log.ZDebug(ctx, "AuthLocalCache GetExistingToken req", "userID", userID, "platformID", platformID)
|
||||||
|
defer func() {
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "AuthLocalCache GetExistingToken error", err, "cost", time.Since(start), "userID", userID, "platformID", platformID)
|
||||||
|
} else {
|
||||||
|
log.ZDebug(ctx, "AuthLocalCache GetExistingToken resp", "cost", time.Since(start), "userID", userID, "platformID", platformID, "val", val)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var cache cacheProto[auth.GetExistingTokenResp]
|
||||||
|
|
||||||
|
return cache.Unmarshal(a.local.Get(ctx, cachekey.GetTokenKey(userID, platformID), func(ctx context.Context) ([]byte, error) {
|
||||||
|
log.ZDebug(ctx, "AuthLocalCache GetExistingToken call rpc", "userID", userID, "platformID", platformID)
|
||||||
|
return cache.Marshal(a.client.AuthClient.GetExistingToken(ctx, &auth.GetExistingTokenReq{UserID: userID, PlatformID: int32(platformID)}))
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ func NewOnlineCache(client *rpcli.UserClient, group *GroupLocalCache, rdb redis.
|
|||||||
case false:
|
case false:
|
||||||
log.ZDebug(ctx, "fullUserCache is false")
|
log.ZDebug(ctx, "fullUserCache is false")
|
||||||
x.lruCache = lru.NewSlotLRU(1024, localcache.LRUStringHash, func() lru.LRU[string, []int32] {
|
x.lruCache = lru.NewSlotLRU(1024, localcache.LRUStringHash, func() lru.LRU[string, []int32] {
|
||||||
return lru.NewLayLRU[string, []int32](2048, cachekey.OnlineExpire/2, time.Second*3, localcache.EmptyTarget{}, func(key string, value []int32) {})
|
return lru.NewLazyLRU[string, []int32](2048, cachekey.OnlineExpire/2, time.Second*3, localcache.EmptyTarget{}, func(key string, value []int32) {})
|
||||||
})
|
})
|
||||||
x.CurrentPhase.Store(DoSubscribeOver)
|
x.CurrentPhase.Store(DoSubscribeOver)
|
||||||
x.Cond.Broadcast()
|
x.Cond.Broadcast()
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package rpcli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/openimsdk/protocol/conversation"
|
"github.com/openimsdk/protocol/conversation"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
)
|
)
|
||||||
@@ -30,18 +31,6 @@ func (x *ConversationClient) SetConversations(ctx context.Context, ownerUserIDs
|
|||||||
return ignoreResp(x.ConversationClient.SetConversations(ctx, req))
|
return ignoreResp(x.ConversationClient.SetConversations(ctx, req))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ConversationClient) GetConversationsByConversationIDs(ctx context.Context, conversationIDs []string) ([]*conversation.Conversation, error) {
|
|
||||||
if len(conversationIDs) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
req := &conversation.GetConversationsByConversationIDReq{ConversationIDs: conversationIDs}
|
|
||||||
return extractField(ctx, x.ConversationClient.GetConversationsByConversationID, req, (*conversation.GetConversationsByConversationIDResp).GetConversations)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ConversationClient) GetConversationsByConversationID(ctx context.Context, conversationID string) (*conversation.Conversation, error) {
|
|
||||||
return firstValue(x.GetConversationsByConversationIDs(ctx, []string{conversationID}))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (x *ConversationClient) SetConversationMinSeq(ctx context.Context, conversationID string, ownerUserIDs []string, minSeq int64) error {
|
func (x *ConversationClient) SetConversationMinSeq(ctx context.Context, conversationID string, ownerUserIDs []string, minSeq int64) error {
|
||||||
if len(ownerUserIDs) == 0 {
|
if len(ownerUserIDs) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -0,0 +1,735 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/apistruct"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
|
"github.com/openimsdk/protocol/auth"
|
||||||
|
"github.com/openimsdk/protocol/constant"
|
||||||
|
"github.com/openimsdk/protocol/group"
|
||||||
|
"github.com/openimsdk/protocol/sdkws"
|
||||||
|
pbuser "github.com/openimsdk/protocol/user"
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
|
"github.com/openimsdk/tools/system/program"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 1. Create 100K New Users
|
||||||
|
// 2. Create 100 100K Groups
|
||||||
|
// 3. Create 1000 999 Groups
|
||||||
|
// 4. Send message to 100K Groups every second
|
||||||
|
// 5. Send message to 999 Groups every minute
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Use default userIDs List for testing, need to be created.
|
||||||
|
TestTargetUserList = []string{
|
||||||
|
// "<need-update-it>",
|
||||||
|
}
|
||||||
|
// DefaultGroupID = "<need-update-it>" // Use default group ID for testing, need to be created.
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ApiAddress string
|
||||||
|
|
||||||
|
// API method
|
||||||
|
GetAdminToken = "/auth/get_admin_token"
|
||||||
|
UserCheck = "/user/account_check"
|
||||||
|
CreateUser = "/user/user_register"
|
||||||
|
ImportFriend = "/friend/import_friend"
|
||||||
|
InviteToGroup = "/group/invite_user_to_group"
|
||||||
|
GetGroupMemberInfo = "/group/get_group_members_info"
|
||||||
|
SendMsg = "/msg/send_msg"
|
||||||
|
CreateGroup = "/group/create_group"
|
||||||
|
GetUserToken = "/auth/user_token"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
MaxUser = 100000
|
||||||
|
Max100KGroup = 100
|
||||||
|
Max999Group = 1000
|
||||||
|
MaxInviteUserLimit = 999
|
||||||
|
|
||||||
|
CreateUserTicker = 1 * time.Second
|
||||||
|
CreateGroupTicker = 1 * time.Second
|
||||||
|
Create100KGroupTicker = 1 * time.Second
|
||||||
|
Create999GroupTicker = 1 * time.Second
|
||||||
|
SendMsgTo100KGroupTicker = 1 * time.Second
|
||||||
|
SendMsgTo999GroupTicker = 1 * time.Minute
|
||||||
|
)
|
||||||
|
|
||||||
|
type BaseResp struct {
|
||||||
|
ErrCode int `json:"errCode"`
|
||||||
|
ErrMsg string `json:"errMsg"`
|
||||||
|
Data json.RawMessage `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type StressTest struct {
|
||||||
|
Conf *conf
|
||||||
|
AdminUserID string
|
||||||
|
AdminToken string
|
||||||
|
DefaultGroupID string
|
||||||
|
DefaultUserID string
|
||||||
|
UserCounter int
|
||||||
|
CreateUserCounter int
|
||||||
|
Create100kGroupCounter int
|
||||||
|
Create999GroupCounter int
|
||||||
|
MsgCounter int
|
||||||
|
CreatedUsers []string
|
||||||
|
CreatedGroups []string
|
||||||
|
Mutex sync.Mutex
|
||||||
|
Ctx context.Context
|
||||||
|
Cancel context.CancelFunc
|
||||||
|
HttpClient *http.Client
|
||||||
|
Wg sync.WaitGroup
|
||||||
|
Once sync.Once
|
||||||
|
}
|
||||||
|
|
||||||
|
type conf struct {
|
||||||
|
Share config.Share
|
||||||
|
Api config.API
|
||||||
|
}
|
||||||
|
|
||||||
|
func initConfig(configDir string) (*config.Share, *config.API, error) {
|
||||||
|
var (
|
||||||
|
share = &config.Share{}
|
||||||
|
apiConfig = &config.API{}
|
||||||
|
)
|
||||||
|
|
||||||
|
err := config.Load(configDir, config.ShareFileName, config.EnvPrefixMap[config.ShareFileName], share)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = config.Load(configDir, config.OpenIMAPICfgFileName, config.EnvPrefixMap[config.OpenIMAPICfgFileName], apiConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return share, apiConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post Request
|
||||||
|
func (st *StressTest) PostRequest(ctx context.Context, url string, reqbody any) ([]byte, error) {
|
||||||
|
// Marshal body
|
||||||
|
jsonBody, err := json.Marshal(reqbody)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "Failed to marshal request body", err, "url", url, "reqbody", reqbody)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(jsonBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/json")
|
||||||
|
req.Header.Set("operationID", st.AdminUserID)
|
||||||
|
if st.AdminToken != "" {
|
||||||
|
req.Header.Set("token", st.AdminToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// log.ZInfo(ctx, "Header info is ", "Content-Type", "application/json", "operationID", st.AdminUserID, "token", st.AdminToken)
|
||||||
|
|
||||||
|
resp, err := st.HttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "Failed to send request", err, "url", url, "reqbody", reqbody)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
respBody, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "Failed to read response body", err, "url", url)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseResp BaseResp
|
||||||
|
if err := json.Unmarshal(respBody, &baseResp); err != nil {
|
||||||
|
log.ZError(ctx, "Failed to unmarshal response body", err, "url", url, "respBody", string(respBody))
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if baseResp.ErrCode != 0 {
|
||||||
|
err = fmt.Errorf(baseResp.ErrMsg)
|
||||||
|
log.ZError(ctx, "Failed to send request", err, "url", url, "reqbody", reqbody, "resp", baseResp)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseResp.Data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *StressTest) GetAdminToken(ctx context.Context) (string, error) {
|
||||||
|
req := auth.GetAdminTokenReq{
|
||||||
|
Secret: st.Conf.Share.Secret,
|
||||||
|
UserID: st.AdminUserID,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := st.PostRequest(ctx, ApiAddress+GetAdminToken, &req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &auth.GetAdminTokenResp{}
|
||||||
|
if err := json.Unmarshal(resp, &data); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.Token, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *StressTest) CheckUser(ctx context.Context, userIDs []string) ([]string, error) {
|
||||||
|
req := pbuser.AccountCheckReq{
|
||||||
|
CheckUserIDs: userIDs,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := st.PostRequest(ctx, ApiAddress+UserCheck, &req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &pbuser.AccountCheckResp{}
|
||||||
|
if err := json.Unmarshal(resp, &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
unRegisteredUserIDs := make([]string, 0)
|
||||||
|
|
||||||
|
for _, res := range data.Results {
|
||||||
|
if res.AccountStatus == constant.UnRegistered {
|
||||||
|
unRegisteredUserIDs = append(unRegisteredUserIDs, res.UserID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unRegisteredUserIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *StressTest) CreateUser(ctx context.Context, userID string) (string, error) {
|
||||||
|
user := &sdkws.UserInfo{
|
||||||
|
UserID: userID,
|
||||||
|
Nickname: userID,
|
||||||
|
}
|
||||||
|
|
||||||
|
req := pbuser.UserRegisterReq{
|
||||||
|
Users: []*sdkws.UserInfo{user},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := st.PostRequest(ctx, ApiAddress+CreateUser, &req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
st.UserCounter++
|
||||||
|
return userID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *StressTest) CreateUserBatch(ctx context.Context, userIDs []string) error {
|
||||||
|
// The method can import a large number of users at once.
|
||||||
|
var userList []*sdkws.UserInfo
|
||||||
|
|
||||||
|
defer st.Once.Do(
|
||||||
|
func() {
|
||||||
|
st.DefaultUserID = userIDs[0]
|
||||||
|
fmt.Println("Default Send User Created ID:", st.DefaultUserID)
|
||||||
|
})
|
||||||
|
|
||||||
|
needUserIDs, err := st.CheckUser(ctx, userIDs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, userID := range needUserIDs {
|
||||||
|
user := &sdkws.UserInfo{
|
||||||
|
UserID: userID,
|
||||||
|
Nickname: userID,
|
||||||
|
}
|
||||||
|
userList = append(userList, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
req := pbuser.UserRegisterReq{
|
||||||
|
Users: userList,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = st.PostRequest(ctx, ApiAddress+CreateUser, &req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
st.UserCounter += len(userList)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *StressTest) GetGroupMembersInfo(ctx context.Context, groupID string, userIDs []string) ([]string, error) {
|
||||||
|
needInviteUserIDs := make([]string, 0)
|
||||||
|
|
||||||
|
const maxBatchSize = 500
|
||||||
|
if len(userIDs) > maxBatchSize {
|
||||||
|
for i := 0; i < len(userIDs); i += maxBatchSize {
|
||||||
|
end := min(i+maxBatchSize, len(userIDs))
|
||||||
|
batchUserIDs := userIDs[i:end]
|
||||||
|
|
||||||
|
// log.ZInfo(ctx, "Processing group members batch", "groupID", groupID, "batch", i/maxBatchSize+1,
|
||||||
|
// "batchUserCount", len(batchUserIDs))
|
||||||
|
|
||||||
|
// Process a single batch
|
||||||
|
batchReq := group.GetGroupMembersInfoReq{
|
||||||
|
GroupID: groupID,
|
||||||
|
UserIDs: batchUserIDs,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := st.PostRequest(ctx, ApiAddress+GetGroupMemberInfo, &batchReq)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "Batch query failed", err, "batch", i/maxBatchSize+1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &group.GetGroupMembersInfoResp{}
|
||||||
|
if err := json.Unmarshal(resp, &data); err != nil {
|
||||||
|
log.ZError(ctx, "Failed to parse batch response", err, "batch", i/maxBatchSize+1)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process the batch results
|
||||||
|
existingMembers := make(map[string]bool)
|
||||||
|
for _, member := range data.Members {
|
||||||
|
existingMembers[member.UserID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, userID := range batchUserIDs {
|
||||||
|
if !existingMembers[userID] {
|
||||||
|
needInviteUserIDs = append(needInviteUserIDs, userID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return needInviteUserIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
req := group.GetGroupMembersInfoReq{
|
||||||
|
GroupID: groupID,
|
||||||
|
UserIDs: userIDs,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := st.PostRequest(ctx, ApiAddress+GetGroupMemberInfo, &req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &group.GetGroupMembersInfoResp{}
|
||||||
|
if err := json.Unmarshal(resp, &data); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
existingMembers := make(map[string]bool)
|
||||||
|
for _, member := range data.Members {
|
||||||
|
existingMembers[member.UserID] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, userID := range userIDs {
|
||||||
|
if !existingMembers[userID] {
|
||||||
|
needInviteUserIDs = append(needInviteUserIDs, userID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return needInviteUserIDs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *StressTest) InviteToGroup(ctx context.Context, groupID string, userIDs []string) error {
|
||||||
|
req := group.InviteUserToGroupReq{
|
||||||
|
GroupID: groupID,
|
||||||
|
InvitedUserIDs: userIDs,
|
||||||
|
}
|
||||||
|
_, err := st.PostRequest(ctx, ApiAddress+InviteToGroup, &req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (st *StressTest) SendMsg(ctx context.Context, userID string, groupID string) error {
|
||||||
|
contentObj := map[string]any{
|
||||||
|
// "content": fmt.Sprintf("index %d. The current time is %s", st.MsgCounter, time.Now().Format("2006-01-02 15:04:05.000")),
|
||||||
|
"content": fmt.Sprintf("The current time is %s", time.Now().Format("2006-01-02 15:04:05.000")),
|
||||||
|
}
|
||||||
|
|
||||||
|
req := &apistruct.SendMsgReq{
|
||||||
|
SendMsg: apistruct.SendMsg{
|
||||||
|
SendID: userID,
|
||||||
|
SenderNickname: userID,
|
||||||
|
GroupID: groupID,
|
||||||
|
ContentType: constant.Text,
|
||||||
|
SessionType: constant.ReadGroupChatType,
|
||||||
|
Content: contentObj,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := st.PostRequest(ctx, ApiAddress+SendMsg, &req)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "Failed to send message", err, "userID", userID, "req", &req)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
st.MsgCounter++
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Max userIDs number is 1000
|
||||||
|
func (st *StressTest) CreateGroup(ctx context.Context, groupID string, userID string, userIDsList []string) (string, error) {
|
||||||
|
groupInfo := &sdkws.GroupInfo{
|
||||||
|
GroupID: groupID,
|
||||||
|
GroupName: groupID,
|
||||||
|
GroupType: constant.WorkingGroup,
|
||||||
|
}
|
||||||
|
|
||||||
|
req := group.CreateGroupReq{
|
||||||
|
OwnerUserID: userID,
|
||||||
|
MemberUserIDs: userIDsList,
|
||||||
|
GroupInfo: groupInfo,
|
||||||
|
}
|
||||||
|
|
||||||
|
resp := group.CreateGroupResp{}
|
||||||
|
|
||||||
|
response, err := st.PostRequest(ctx, ApiAddress+CreateGroup, &req)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(response, &resp); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// st.GroupCounter++
|
||||||
|
|
||||||
|
return resp.GroupInfo.GroupID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var configPath string
|
||||||
|
// defaultConfigDir := filepath.Join("..", "..", "..", "..", "..", "config")
|
||||||
|
// flag.StringVar(&configPath, "c", defaultConfigDir, "config path")
|
||||||
|
flag.StringVar(&configPath, "c", "", "config path")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if configPath == "" {
|
||||||
|
_, _ = fmt.Fprintln(os.Stderr, "config path is empty")
|
||||||
|
os.Exit(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" Config Path: %s\n", configPath)
|
||||||
|
|
||||||
|
share, apiConfig, err := initConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
program.ExitWithError(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ApiAddress = fmt.Sprintf("http://%s:%s", "127.0.0.1", fmt.Sprint(apiConfig.Api.Ports[0]))
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
// ch := make(chan struct{})
|
||||||
|
|
||||||
|
st := &StressTest{
|
||||||
|
Conf: &conf{
|
||||||
|
Share: *share,
|
||||||
|
Api: *apiConfig,
|
||||||
|
},
|
||||||
|
AdminUserID: share.IMAdminUser.UserIDs[0],
|
||||||
|
Ctx: ctx,
|
||||||
|
Cancel: cancel,
|
||||||
|
HttpClient: &http.Client{
|
||||||
|
Timeout: 50 * time.Second,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
c := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
<-c
|
||||||
|
fmt.Println("\nReceived stop signal, stopping...")
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
// time.Sleep(5 * time.Second)
|
||||||
|
fmt.Println("Force exit")
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
|
||||||
|
st.Cancel()
|
||||||
|
}()
|
||||||
|
|
||||||
|
token, err := st.GetAdminToken(st.Ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "Get Admin Token failed.", err, "AdminUserID", st.AdminUserID)
|
||||||
|
}
|
||||||
|
|
||||||
|
st.AdminToken = token
|
||||||
|
fmt.Println("Admin Token:", st.AdminToken)
|
||||||
|
fmt.Println("ApiAddress:", ApiAddress)
|
||||||
|
for i := 0; i < MaxUser; i++ {
|
||||||
|
userID := fmt.Sprintf("v2_StressTest_User_%d", i)
|
||||||
|
st.CreatedUsers = append(st.CreatedUsers, userID)
|
||||||
|
st.CreateUserCounter++
|
||||||
|
}
|
||||||
|
|
||||||
|
// err = st.CreateUserBatch(st.Ctx, st.CreatedUsers)
|
||||||
|
// if err != nil {
|
||||||
|
// log.ZError(ctx, "Create user failed.", err)
|
||||||
|
// }
|
||||||
|
|
||||||
|
const batchSize = 1000
|
||||||
|
totalUsers := len(st.CreatedUsers)
|
||||||
|
successCount := 0
|
||||||
|
|
||||||
|
if st.DefaultUserID == "" && len(st.CreatedUsers) > 0 {
|
||||||
|
st.DefaultUserID = st.CreatedUsers[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < totalUsers; i += batchSize {
|
||||||
|
end := min(i+batchSize, totalUsers)
|
||||||
|
|
||||||
|
userBatch := st.CreatedUsers[i:end]
|
||||||
|
log.ZInfo(st.Ctx, "Creating user batch", "batch", i/batchSize+1, "count", len(userBatch))
|
||||||
|
|
||||||
|
err = st.CreateUserBatch(st.Ctx, userBatch)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(st.Ctx, "Batch user creation failed", err, "batch", i/batchSize+1)
|
||||||
|
} else {
|
||||||
|
successCount += len(userBatch)
|
||||||
|
log.ZInfo(st.Ctx, "Batch user creation succeeded", "batch", i/batchSize+1,
|
||||||
|
"progress", fmt.Sprintf("%d/%d", successCount, totalUsers))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute create 100k group
|
||||||
|
st.Wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer st.Wg.Done()
|
||||||
|
|
||||||
|
create100kGroupTicker := time.NewTicker(Create100KGroupTicker)
|
||||||
|
defer create100kGroupTicker.Stop()
|
||||||
|
|
||||||
|
for i := 0; i < Max100KGroup; i++ {
|
||||||
|
select {
|
||||||
|
case <-st.Ctx.Done():
|
||||||
|
log.ZInfo(st.Ctx, "Stop Create 100K Group")
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-create100kGroupTicker.C:
|
||||||
|
// Create 100K groups
|
||||||
|
st.Wg.Add(1)
|
||||||
|
go func(idx int) {
|
||||||
|
defer st.Wg.Done()
|
||||||
|
defer func() {
|
||||||
|
st.Create100kGroupCounter++
|
||||||
|
}()
|
||||||
|
|
||||||
|
groupID := fmt.Sprintf("v2_StressTest_Group_100K_%d", idx)
|
||||||
|
|
||||||
|
if _, err = st.CreateGroup(st.Ctx, groupID, st.DefaultUserID, TestTargetUserList); err != nil {
|
||||||
|
log.ZError(st.Ctx, "Create group failed.", err)
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < MaxUser/MaxInviteUserLimit; i++ {
|
||||||
|
InviteUserIDs := make([]string, 0)
|
||||||
|
// ensure TargetUserList is in group
|
||||||
|
InviteUserIDs = append(InviteUserIDs, TestTargetUserList...)
|
||||||
|
|
||||||
|
startIdx := max(i*MaxInviteUserLimit, 1)
|
||||||
|
endIdx := min((i+1)*MaxInviteUserLimit, MaxUser)
|
||||||
|
|
||||||
|
for j := startIdx; j < endIdx; j++ {
|
||||||
|
userCreatedID := fmt.Sprintf("v2_StressTest_User_%d", j)
|
||||||
|
InviteUserIDs = append(InviteUserIDs, userCreatedID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(InviteUserIDs) == 0 {
|
||||||
|
log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
InviteUserIDs, err := st.GetGroupMembersInfo(ctx, groupID, InviteUserIDs)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(st.Ctx, "GetGroupMembersInfo failed.", err, "groupID", groupID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(InviteUserIDs) == 0 {
|
||||||
|
log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invite To Group
|
||||||
|
if err = st.InviteToGroup(st.Ctx, groupID, InviteUserIDs); err != nil {
|
||||||
|
log.ZError(st.Ctx, "Invite To Group failed.", err, "UserID", InviteUserIDs)
|
||||||
|
continue
|
||||||
|
// os.Exit(1)
|
||||||
|
// return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// create 999 groups
|
||||||
|
st.Wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer st.Wg.Done()
|
||||||
|
|
||||||
|
create999GroupTicker := time.NewTicker(Create999GroupTicker)
|
||||||
|
defer create999GroupTicker.Stop()
|
||||||
|
|
||||||
|
for i := 0; i < Max999Group; i++ {
|
||||||
|
select {
|
||||||
|
case <-st.Ctx.Done():
|
||||||
|
log.ZInfo(st.Ctx, "Stop Create 999 Group")
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-create999GroupTicker.C:
|
||||||
|
// Create 999 groups
|
||||||
|
st.Wg.Add(1)
|
||||||
|
go func(idx int) {
|
||||||
|
defer st.Wg.Done()
|
||||||
|
defer func() {
|
||||||
|
st.Create999GroupCounter++
|
||||||
|
}()
|
||||||
|
|
||||||
|
groupID := fmt.Sprintf("v2_StressTest_Group_1K_%d", idx)
|
||||||
|
|
||||||
|
if _, err = st.CreateGroup(st.Ctx, groupID, st.DefaultUserID, TestTargetUserList); err != nil {
|
||||||
|
log.ZError(st.Ctx, "Create group failed.", err)
|
||||||
|
// continue
|
||||||
|
}
|
||||||
|
for i := 0; i < MaxUser/MaxInviteUserLimit; i++ {
|
||||||
|
InviteUserIDs := make([]string, 0)
|
||||||
|
// ensure TargetUserList is in group
|
||||||
|
InviteUserIDs = append(InviteUserIDs, TestTargetUserList...)
|
||||||
|
|
||||||
|
startIdx := max(i*MaxInviteUserLimit, 1)
|
||||||
|
endIdx := min((i+1)*MaxInviteUserLimit, MaxUser)
|
||||||
|
|
||||||
|
for j := startIdx; j < endIdx; j++ {
|
||||||
|
userCreatedID := fmt.Sprintf("v2_StressTest_User_%d", j)
|
||||||
|
InviteUserIDs = append(InviteUserIDs, userCreatedID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(InviteUserIDs) == 0 {
|
||||||
|
log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
InviteUserIDs, err := st.GetGroupMembersInfo(ctx, groupID, InviteUserIDs)
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(st.Ctx, "GetGroupMembersInfo failed.", err, "groupID", groupID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(InviteUserIDs) == 0 {
|
||||||
|
log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invite To Group
|
||||||
|
if err = st.InviteToGroup(st.Ctx, groupID, InviteUserIDs); err != nil {
|
||||||
|
log.ZError(st.Ctx, "Invite To Group failed.", err, "UserID", InviteUserIDs)
|
||||||
|
continue
|
||||||
|
// os.Exit(1)
|
||||||
|
// return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// Send message to 100K groups
|
||||||
|
st.Wg.Wait()
|
||||||
|
fmt.Println("All groups created successfully, starting to send messages...")
|
||||||
|
log.ZInfo(ctx, "All groups created successfully, starting to send messages...")
|
||||||
|
|
||||||
|
var groups100K []string
|
||||||
|
var groups999 []string
|
||||||
|
|
||||||
|
for i := 0; i < Max100KGroup; i++ {
|
||||||
|
groupID := fmt.Sprintf("v2_StressTest_Group_100K_%d", i)
|
||||||
|
groups100K = append(groups100K, groupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < Max999Group; i++ {
|
||||||
|
groupID := fmt.Sprintf("v2_StressTest_Group_1K_%d", i)
|
||||||
|
groups999 = append(groups999, groupID)
|
||||||
|
}
|
||||||
|
|
||||||
|
send100kGroupLimiter := make(chan struct{}, 20)
|
||||||
|
send999GroupLimiter := make(chan struct{}, 100)
|
||||||
|
|
||||||
|
// execute Send message to 100K groups
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(SendMsgTo100KGroupTicker)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-st.Ctx.Done():
|
||||||
|
log.ZInfo(st.Ctx, "Stop Send Message to 100K Group")
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-ticker.C:
|
||||||
|
// Send message to 100K groups
|
||||||
|
for _, groupID := range groups100K {
|
||||||
|
send100kGroupLimiter <- struct{}{}
|
||||||
|
go func(groupID string) {
|
||||||
|
defer func() { <-send100kGroupLimiter }()
|
||||||
|
if err := st.SendMsg(st.Ctx, st.DefaultUserID, groupID); err != nil {
|
||||||
|
log.ZError(st.Ctx, "Send message to 100K group failed.", err)
|
||||||
|
}
|
||||||
|
}(groupID)
|
||||||
|
}
|
||||||
|
// log.ZInfo(st.Ctx, "Send message to 100K groups successfully.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// execute Send message to 999 groups
|
||||||
|
go func() {
|
||||||
|
ticker := time.NewTicker(SendMsgTo999GroupTicker)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-st.Ctx.Done():
|
||||||
|
log.ZInfo(st.Ctx, "Stop Send Message to 999 Group")
|
||||||
|
return
|
||||||
|
|
||||||
|
case <-ticker.C:
|
||||||
|
// Send message to 999 groups
|
||||||
|
for _, groupID := range groups999 {
|
||||||
|
send999GroupLimiter <- struct{}{}
|
||||||
|
go func(groupID string) {
|
||||||
|
defer func() { <-send999GroupLimiter }()
|
||||||
|
|
||||||
|
if err := st.SendMsg(st.Ctx, st.DefaultUserID, groupID); err != nil {
|
||||||
|
log.ZError(st.Ctx, "Send message to 999 group failed.", err)
|
||||||
|
}
|
||||||
|
}(groupID)
|
||||||
|
}
|
||||||
|
// log.ZInfo(st.Ctx, "Send message to 999 groups successfully.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
<-st.Ctx.Done()
|
||||||
|
fmt.Println("Received signal to exit, shutting down...")
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user