mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-04-30 23:33:06 +08:00
Compare commits
71 Commits
v3.8.1-rc.2
...
v3.8.2
| Author | SHA1 | Date | |
|---|---|---|---|
| c328d39cae | |||
| 7d517970ec | |||
| d8afbb82fc | |||
| 3e220a3519 | |||
| 3a30479b73 | |||
| 687b2ebc07 | |||
| 23966f3155 | |||
| 674b288654 | |||
| a4287309ae | |||
| ce140beddc | |||
| 0e07ad70c3 | |||
| c9e2f7d375 | |||
| 1e749b6217 | |||
| 4698446050 | |||
| 624ae99a12 | |||
| 453c426ab5 | |||
| 0266dc830d | |||
| 9490d8f8ee | |||
| 0d84190ed6 | |||
| 5089568004 | |||
| 9e4cad1815 | |||
| d4d626606b | |||
| 058eeaefd0 | |||
| 0ac6668a50 | |||
| 404a9048e2 | |||
| 7881c8c89a | |||
| eb598ec0e6 | |||
| 625fa77e89 | |||
| f707069089 | |||
| 2bbd1bcfe9 | |||
| e53ae33e39 | |||
| 3914dc1435 | |||
| 047fa33704 | |||
| caf5d5c2f3 | |||
| 7fa2d08636 | |||
| 7b5c18b549 | |||
| 0a565070b8 | |||
| 43bc87ce99 | |||
| c4fe659c69 | |||
| 59c4c7575d | |||
| 49c8440d50 | |||
| a87829c87f | |||
| 93f6c92efa | |||
| da040b1e11 | |||
| 953ed22313 | |||
| 6259a49c61 | |||
| 82845df518 | |||
| 5452741af8 | |||
| 765fa17e7a | |||
| 0892c1c0c3 | |||
| d6b711c7ed | |||
| 2a33b5c666 | |||
| 92ee753805 | |||
| aa1d3119be | |||
| 0ffdc58e48 | |||
| b9217f76ce | |||
| 80a46b329d | |||
| 3472952683 | |||
| d279a5f458 | |||
| 7da87e1e36 | |||
| 51aaf08fa7 | |||
| f6364a4eff | |||
| 031c1cd1e4 | |||
| 6006de4612 | |||
| 7090c99fa5 | |||
| 6add09d476 | |||
| c0eaa1c166 | |||
| 79bf21453a | |||
| 5b2b2c1fc6 | |||
| 67f30199e3 | |||
| 4f0830b1af |
@@ -1,6 +1,5 @@
|
||||
MONGO_IMAGE=mongo:6.0.2
|
||||
MONGO_IMAGE=mongo:7.0
|
||||
REDIS_IMAGE=redis:7.0.0
|
||||
ZOOKEEPER_IMAGE=bitnami/zookeeper:3.8
|
||||
KAFKA_IMAGE=bitnami/kafka:3.5.1
|
||||
MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z
|
||||
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.13
|
||||
@@ -8,12 +7,11 @@ PROMETHEUS_IMAGE=prom/prometheus:v2.45.6
|
||||
ALERTMANAGER_IMAGE=prom/alertmanager:v0.27.0
|
||||
GRAFANA_IMAGE=grafana/grafana:11.0.1
|
||||
|
||||
OPENIM_WEB_FRONT_IMAGE=openim/openim-web-front:release-v3.8.0
|
||||
OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.8.0
|
||||
OPENIM_WEB_FRONT_IMAGE=openim/openim-web-front:release-v3.8.1
|
||||
OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.8.2
|
||||
|
||||
#FRONT_IMAGE: use aliyun images
|
||||
#OPENIM_WEB_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-web-front:release-v3.5.1
|
||||
#OPENIM_ADMIN_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-admin-front:release-v1.7
|
||||
#OPENIM_WEB_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-web-front:release-v3.8.1
|
||||
#OPENIM_ADMIN_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-admin-front:release-v1.8.2
|
||||
|
||||
DATA_DIR=./
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
name: Release Changelog
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
update-changelog:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Run Go Changelog Generator
|
||||
run: |
|
||||
# Run the Go changelog generator, passing the release tag if available
|
||||
if [ "${{ github.event.release.tag_name }}" = "latest" ]; then
|
||||
go run tools/changelog/changelog.go > "${{ github.event.release.tag_name }}-changelog.md"
|
||||
else
|
||||
go run tools/changelog/changelog.go "${{ github.event.release.tag_name }}" > "${{ github.event.release.tag_name }}-changelog.md"
|
||||
fi
|
||||
|
||||
- name: Handle changelog files
|
||||
run: |
|
||||
# Ensure that the CHANGELOG directory exists
|
||||
mkdir -p CHANGELOG
|
||||
|
||||
# Extract Major.Minor version by removing the 'v' prefix from the tag name
|
||||
TAG_NAME=${{ github.event.release.tag_name }}
|
||||
CHANGELOG_VERSION_NUMBER=$(echo "$TAG_NAME" | sed 's/^v//' | grep -oP '^\d+\.\d+')
|
||||
|
||||
# Define the new changelog file path
|
||||
CHANGELOG_FILENAME="CHANGELOG-$CHANGELOG_VERSION_NUMBER.md"
|
||||
CHANGELOG_PATH="CHANGELOG/$CHANGELOG_FILENAME"
|
||||
|
||||
# Check if the changelog file for the current release already exists
|
||||
if [ -f "$CHANGELOG_PATH" ]; then
|
||||
# If the file exists, append the new changelog to the existing one
|
||||
cat "$CHANGELOG_PATH" >> "${TAG_NAME}-changelog.md"
|
||||
# Overwrite the existing changelog with the updated content
|
||||
mv "${TAG_NAME}-changelog.md" "$CHANGELOG_PATH"
|
||||
else
|
||||
# If the changelog file doesn't exist, rename the temp changelog file to the new changelog file
|
||||
mv "${TAG_NAME}-changelog.md" "$CHANGELOG_PATH"
|
||||
|
||||
# Ensure that README.md exists
|
||||
if [ ! -f "CHANGELOG/README.md" ]; then
|
||||
echo -e "# CHANGELOGs\n\n" > CHANGELOG/README.md
|
||||
fi
|
||||
|
||||
# Add the new changelog entry at the top of the README.md
|
||||
if ! grep -q "\[$CHANGELOG_FILENAME\]" CHANGELOG/README.md; then
|
||||
sed -i "3i- [$CHANGELOG_FILENAME](./$CHANGELOG_FILENAME)" CHANGELOG/README.md
|
||||
# Remove the extra newline character added by sed
|
||||
# sed -i '4d' CHANGELOG/README.md
|
||||
fi
|
||||
fi
|
||||
|
||||
- name: Clean up
|
||||
run: |
|
||||
# Remove any temporary files that were created during the process
|
||||
rm -f "${{ github.event.release.tag_name }}-changelog.md"
|
||||
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v7.0.5
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
commit-message: "Update CHANGELOG for release ${{ github.event.release.tag_name }}"
|
||||
title: "Update CHANGELOG for release ${{ github.event.release.tag_name }}"
|
||||
body: "This PR updates the CHANGELOG files for release ${{ github.event.release.tag_name }}"
|
||||
branch: changelog-${{ github.event.release.tag_name }}
|
||||
base: main
|
||||
delete-branch: true
|
||||
labels: changelog
|
||||
@@ -0,0 +1,65 @@
|
||||
name: Cleanup After Milestone PRs Merged
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types:
|
||||
- closed
|
||||
|
||||
jobs:
|
||||
handle_pr:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.2.0
|
||||
|
||||
- name: Get the PR title and extract PR numbers
|
||||
id: extract_pr_numbers
|
||||
run: |
|
||||
# Get the PR title
|
||||
PR_TITLE="${{ github.event.pull_request.title }}"
|
||||
|
||||
echo "PR Title: $PR_TITLE"
|
||||
|
||||
# Extract PR numbers from the title
|
||||
PR_NUMBERS=$(echo "$PR_TITLE" | grep -oE "#[0-9]+" | tr -d '#' | tr '\n' ' ')
|
||||
echo "Extracted PR Numbers: $PR_NUMBERS"
|
||||
|
||||
# Save PR numbers to a file
|
||||
echo "$PR_NUMBERS" > pr_numbers.txt
|
||||
echo "Saved PR Numbers to pr_numbers.txt"
|
||||
|
||||
# Check if the title matches a specific pattern
|
||||
if echo "$PR_TITLE" | grep -qE "^deps: Merge( #[0-9]+)+ PRs into .+"; then
|
||||
echo "proceed=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "proceed=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Use extracted PR numbers and label PRs
|
||||
if: (steps.extract_pr_numbers.outputs.proceed == 'true' || contains(github.event.pull_request.labels.*.name, 'milestone-merge')) && github.event.pull_request.merged == true
|
||||
run: |
|
||||
# Read the previously saved PR numbers
|
||||
PR_NUMBERS=$(cat pr_numbers.txt)
|
||||
echo "Using extracted PR Numbers: $PR_NUMBERS"
|
||||
|
||||
# Loop through each PR number and add label
|
||||
for PR_NUMBER in $PR_NUMBERS; do
|
||||
echo "Adding 'cherry-picked' label to PR #$PR_NUMBER"
|
||||
curl -X POST \
|
||||
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/labels \
|
||||
-d '{"labels":["cherry-picked"]}'
|
||||
done
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Delete branch after PR close
|
||||
if: steps.extract_pr_numbers.outputs.proceed == 'true' || contains(github.event.pull_request.labels.*.name, 'milestone-merge')
|
||||
run: |
|
||||
BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
|
||||
echo "Branch to delete: $BRANCH_NAME"
|
||||
git push origin --delete "$BRANCH_NAME"
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@@ -2,11 +2,7 @@ name: Go Build Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '**/*.md'
|
||||
|
||||
@@ -89,12 +85,71 @@ jobs:
|
||||
mage start
|
||||
mage check
|
||||
|
||||
go-test:
|
||||
name: Benchmark Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
permissions:
|
||||
contents: write
|
||||
env:
|
||||
SDK_DIR: openim-sdk-core
|
||||
CONFIG_PATH: config/notification.yml
|
||||
# pull-requests: write
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest ]
|
||||
go_version: [ "1.22.x" ]
|
||||
|
||||
steps:
|
||||
- name: Checkout Server repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Checkout SDK repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: 'openimsdk/openim-sdk-core'
|
||||
path: ${{ env.SDK_DIR }}
|
||||
|
||||
- name: Set up Go ${{ matrix.go_version }}
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go_version }}
|
||||
|
||||
- name: Get Server dependencies
|
||||
run: |
|
||||
go install github.com/magefile/mage@latest
|
||||
go mod download
|
||||
|
||||
- name: Install yq
|
||||
run: |
|
||||
sudo wget https://github.com/mikefarah/yq/releases/download/v4.34.1/yq_linux_amd64 -O /usr/bin/yq
|
||||
sudo chmod +x /usr/bin/yq
|
||||
|
||||
- name: Modify Server Configuration
|
||||
run: |
|
||||
yq e '.groupCreated.unreadCount = true' -i ${{ env.CONFIG_PATH }}
|
||||
yq e '.friendApplicationApproved.unreadCount = true' -i ${{ env.CONFIG_PATH }}
|
||||
|
||||
- name: Start Server Services
|
||||
run: |
|
||||
docker compose up -d
|
||||
mage build
|
||||
mage start
|
||||
mage check
|
||||
|
||||
- name: Build test SDK core
|
||||
run: |
|
||||
cd ${{ env.SDK_DIR }}
|
||||
go mod tidy
|
||||
cd integration_test
|
||||
mkdir data
|
||||
go run main.go -lgr 0.8 -imf -crg -ckgn -ckcon -sem -ckmsn -u 20 -su 5 -lg 2 -cg 2 -cgm 3 -sm 10 -gm 10 -reg
|
||||
|
||||
dockerfile-test:
|
||||
name: Build and Test Dockerfile
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
go_version: ["1.21"]
|
||||
go_version: ["1.22"]
|
||||
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
|
||||
@@ -0,0 +1,218 @@
|
||||
name: Create Pre-Release PR from Milestone
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
issues: write
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
milestone_name:
|
||||
description: 'Milestone name to collect closed PRs from'
|
||||
required: true
|
||||
default: 'v3.8.2'
|
||||
target_branch:
|
||||
description: 'Target branch to merge the consolidated PR'
|
||||
required: true
|
||||
default: 'pre-release-v3.8.2'
|
||||
|
||||
env:
|
||||
MILESTONE_NAME: ${{ github.event.inputs.milestone_name || 'v3.8.2' }}
|
||||
TARGET_BRANCH: ${{ github.event.inputs.target_branch || 'pre-release-v3.8.2' }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
LABEL_NAME: cherry-picked
|
||||
TEMP_DIR: /tmp # Using /tmp as the temporary directory
|
||||
|
||||
jobs:
|
||||
cherry_pick_milestone_prs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Setup temp directory
|
||||
run: |
|
||||
# Create the temporary directory and initialize necessary files
|
||||
mkdir -p ${{ env.TEMP_DIR }}
|
||||
touch ${{ env.TEMP_DIR }}/pr_numbers.txt
|
||||
touch ${{ env.TEMP_DIR }}/commit_hashes.txt
|
||||
touch ${{ env.TEMP_DIR }}/pr_title.txt
|
||||
touch ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
touch ${{ env.TEMP_DIR }}/created_pr_number.txt
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.BOT_TOKEN }}
|
||||
|
||||
- name: Setup Git User for OpenIM-Robot
|
||||
run: |
|
||||
# Set up Git credentials for the bot
|
||||
git config --global user.email "OpenIM-Robot@users.noreply.github.com"
|
||||
git config --global user.name "OpenIM-Robot"
|
||||
|
||||
- name: Fetch Milestone ID and Filter PR Numbers
|
||||
env:
|
||||
MILESTONE_NAME: ${{ env.MILESTONE_NAME }}
|
||||
run: |
|
||||
# Fetch milestone details and extract milestone ID
|
||||
milestones=$(curl -s -H "Authorization: token $BOT_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/milestones")
|
||||
milestone_id=$(echo "$milestones" | grep -B3 "\"title\": \"$MILESTONE_NAME\"" | grep '"number":' | head -n1 | grep -o '[0-9]\+')
|
||||
if [ -z "$milestone_id" ]; then
|
||||
echo "Milestone '$MILESTONE_NAME' not found. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
echo "Milestone ID: $milestone_id"
|
||||
echo "MILESTONE_ID=$milestone_id" >> $GITHUB_ENV
|
||||
|
||||
# Fetch issues for the milestone
|
||||
issues=$(curl -s -H "Authorization: token $BOT_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/issues?milestone=$milestone_id&state=closed&per_page=100")
|
||||
|
||||
> ${{ env.TEMP_DIR }}/pr_numbers.txt
|
||||
|
||||
# Filter PRs that do not have the 'cherry-picked' label
|
||||
for pr_number in $(echo "$issues" | jq -r '.[] | select(.pull_request != null) | .number'); do
|
||||
labels=$(curl -s -H "Authorization: token $BOT_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/issues/$pr_number/labels" | jq -r '.[].name')
|
||||
|
||||
if ! echo "$labels" | grep -q "${LABEL_NAME}"; then
|
||||
echo "PR #$pr_number does not have the 'cherry-picked' label. Adding to the list."
|
||||
echo "$pr_number" >> ${{ env.TEMP_DIR }}/pr_numbers.txt
|
||||
else
|
||||
echo "PR #$pr_number already has the 'cherry-picked' label. Skipping."
|
||||
fi
|
||||
done
|
||||
|
||||
# Sort the filtered PR numbers
|
||||
sort -n ${{ env.TEMP_DIR }}/pr_numbers.txt -o ${{ env.TEMP_DIR }}/pr_numbers.txt
|
||||
|
||||
echo "Filtered and sorted PR numbers:"
|
||||
cat ${{ env.TEMP_DIR }}/pr_numbers.txt || echo "No closed PR numbers found for milestone."
|
||||
|
||||
- name: Fetch Merge Commits for PRs and Generate Title and Body
|
||||
run: |
|
||||
# Ensure the files are initialized
|
||||
> ${{ env.TEMP_DIR }}/commit_hashes.txt
|
||||
> ${{ env.TEMP_DIR }}/pr_title.txt
|
||||
> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
|
||||
# Write description to the PR body
|
||||
echo "### Description:" >> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
echo "Merging PRs from milestone \`$MILESTONE_NAME\` into target branch \`$TARGET_BRANCH\`." >> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
echo "" >> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
echo "### Need Merge PRs:" >> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
|
||||
pr_numbers_in_title=""
|
||||
|
||||
# Process sorted PR numbers and generate commit hashes
|
||||
for pr_number in $(cat ${{ env.TEMP_DIR }}/pr_numbers.txt); do
|
||||
echo "Processing PR #$pr_number"
|
||||
pr_details=$(curl -s -H "Authorization: token $BOT_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/${{ github.repository }}/pulls/$pr_number")
|
||||
pr_title=$(echo "$pr_details" | jq -r '.title')
|
||||
merge_commit=$(echo "$pr_details" | jq -r '.merge_commit_sha')
|
||||
short_commit_hash=$(echo "$merge_commit" | cut -c 1-7)
|
||||
|
||||
# Append PR details to the body
|
||||
echo "- $pr_title: (#$pr_number) ($short_commit_hash)" >> ${{ env.TEMP_DIR }}/pr_body.txt
|
||||
|
||||
if [ "$merge_commit" != "null" ];then
|
||||
echo "$merge_commit" >> ${{ env.TEMP_DIR }}/commit_hashes.txt
|
||||
echo "#$pr_number" >> ${{ env.TEMP_DIR }}/pr_title.txt
|
||||
pr_numbers_in_title="$pr_numbers_in_title #$pr_number"
|
||||
fi
|
||||
done
|
||||
|
||||
commit_hashes=$(cat ${{ env.TEMP_DIR }}/commit_hashes.txt | tr '\n' ' ')
|
||||
first_commit_hash=$(head -n 1 ${{ env.TEMP_DIR }}/commit_hashes.txt)
|
||||
cherry_pick_branch="cherry-pick-${first_commit_hash:0:7}"
|
||||
echo "COMMIT_HASHES=$commit_hashes" >> $GITHUB_ENV
|
||||
echo "CHERRY_PICK_BRANCH=$cherry_pick_branch" >> $GITHUB_ENV
|
||||
echo "pr_numbers_in_title=$pr_numbers_in_title" >> $GITHUB_ENV
|
||||
|
||||
- name: Pull and Cherry-pick Commits, Then Push
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
|
||||
run: |
|
||||
# Fetch and pull the latest changes from the target branch
|
||||
git fetch origin
|
||||
git checkout $TARGET_BRANCH
|
||||
git pull origin $TARGET_BRANCH
|
||||
|
||||
# Create a new branch for cherry-picking
|
||||
git checkout -b $CHERRY_PICK_BRANCH
|
||||
|
||||
# Cherry-pick the commits and handle conflicts
|
||||
for commit_hash in $COMMIT_HASHES; do
|
||||
echo "Attempting to cherry-pick commit $commit_hash"
|
||||
if ! git cherry-pick "$commit_hash" --strategy=recursive -X theirs; then
|
||||
echo "Conflict detected for $commit_hash. Resolving with incoming changes."
|
||||
conflict_files=$(git diff --name-only --diff-filter=U)
|
||||
echo "Conflicting files:"
|
||||
echo "$conflict_files"
|
||||
|
||||
for file in $conflict_files; do
|
||||
if [ -f "$file" ]; then
|
||||
echo "Resolving conflict for $file"
|
||||
git add "$file"
|
||||
else
|
||||
echo "File $file has been deleted. Skipping."
|
||||
git rm "$file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Conflicts resolved. Continuing cherry-pick."
|
||||
git cherry-pick --continue
|
||||
else
|
||||
echo "Cherry-pick successful for commit $commit_hash."
|
||||
fi
|
||||
done
|
||||
|
||||
# Push the cherry-pick branch to the repository
|
||||
git remote set-url origin "https://${BOT_TOKEN}@github.com/${{ github.repository }}.git"
|
||||
git push origin $CHERRY_PICK_BRANCH --force
|
||||
|
||||
- name: Create Pull Request
|
||||
run: |
|
||||
# Prepare and create the PR
|
||||
pr_title="deps: Merge ${{ env.pr_numbers_in_title }} PRs into $TARGET_BRANCH"
|
||||
pr_body=$(cat ${{ env.TEMP_DIR }}/pr_body.txt)
|
||||
|
||||
echo "Prepared PR title:"
|
||||
echo "$pr_title"
|
||||
echo "Prepared PR body:"
|
||||
echo "$pr_body"
|
||||
|
||||
# Create the PR using the GitHub API
|
||||
response=$(curl -s -X POST -H "Authorization: token $BOT_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
https://api.github.com/repos/${{ github.repository }}/pulls \
|
||||
-d "$(jq -n --arg title "$pr_title" \
|
||||
--arg head "$CHERRY_PICK_BRANCH" \
|
||||
--arg base "$TARGET_BRANCH" \
|
||||
--arg body "$pr_body" \
|
||||
'{title: $title, head: $head, base: $base, body: $body}')")
|
||||
|
||||
pr_number=$(echo "$response" | jq -r '.number')
|
||||
echo "$pr_number" > ${{ env.TEMP_DIR }}/created_pr_number.txt
|
||||
echo "Created PR #$pr_number"
|
||||
|
||||
- name: Add Label to Created Pull Request
|
||||
run: |
|
||||
# Add 'milestone-merge' label to the created PR
|
||||
pr_number=$(cat ${{ env.TEMP_DIR }}/created_pr_number.txt)
|
||||
echo "Adding label to PR #$pr_number"
|
||||
|
||||
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-d '{"labels": ["milestone-merge"]}' \
|
||||
"https://api.github.com/repos/${{ github.repository }}/issues/$pr_number/labels"
|
||||
|
||||
echo "Added 'milestone-merge' label to PR #$pr_number."
|
||||
@@ -4,6 +4,8 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- release-*
|
||||
# tags:
|
||||
# - 'v*'
|
||||
|
||||
release:
|
||||
types: [published]
|
||||
@@ -15,11 +17,8 @@ on:
|
||||
required: true
|
||||
default: "v3.8.0"
|
||||
|
||||
# env:
|
||||
# GO_VERSION: "1.21"
|
||||
|
||||
jobs:
|
||||
publish-docker-images:
|
||||
build-and-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
@@ -28,16 +27,22 @@ jobs:
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and push Docker image
|
||||
- name: Build Docker image
|
||||
id: build
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./main-repo
|
||||
load: true
|
||||
tags: "openim/openim-server:local"
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
- name: Save Docker image to file
|
||||
run: docker save -o image.tar openim/openim-server:local
|
||||
|
||||
- name: Checkout compose repository
|
||||
uses: actions/checkout@v4
|
||||
@@ -63,38 +68,39 @@ jobs:
|
||||
docker compose up -d
|
||||
sleep 60
|
||||
|
||||
- name: Check openim-server health
|
||||
run: |
|
||||
timeout=300
|
||||
interval=30
|
||||
elapsed=0
|
||||
while [[ $elapsed -le $timeout ]]; do
|
||||
if ! docker exec openim-server mage check; then
|
||||
echo "openim-server is not ready, waiting..."
|
||||
sleep $interval
|
||||
elapsed=$(($elapsed + $interval))
|
||||
else
|
||||
echo "Health check successful"
|
||||
exit 0
|
||||
fi
|
||||
done
|
||||
echo "Health check failed after 5 minutes"
|
||||
exit 1
|
||||
# - name: Check openim-server health
|
||||
# run: |
|
||||
# timeout=300
|
||||
# interval=30
|
||||
# elapsed=0
|
||||
# while [[ $elapsed -le $timeout ]]; do
|
||||
# if ! docker exec openim-server mage check; then
|
||||
# echo "openim-server is not ready, waiting..."
|
||||
# sleep $interval
|
||||
# elapsed=$(($elapsed + $interval))
|
||||
# else
|
||||
# echo "Health check successful"
|
||||
# exit 0
|
||||
# fi
|
||||
# done
|
||||
# echo "Health check failed after 5 minutes"
|
||||
# exit 1
|
||||
|
||||
- name: Check openim-chat health
|
||||
if: success()
|
||||
run: |
|
||||
if ! docker exec openim-chat mage check; then
|
||||
echo "openim-chat check failed"
|
||||
exit 1
|
||||
else
|
||||
echo "Health check successful"
|
||||
exit 0
|
||||
fi
|
||||
# - name: Check openim-chat health
|
||||
# if: success()
|
||||
# run: |
|
||||
# if ! docker exec openim-chat mage check; then
|
||||
# echo "openim-chat check failed"
|
||||
# exit 1
|
||||
# else
|
||||
# echo "Health check successful"
|
||||
# exit 0
|
||||
# fi
|
||||
|
||||
- name: Load Docker image from file
|
||||
run: docker load -i image.tar
|
||||
|
||||
- name: Extract metadata for Docker # (tags, labels)
|
||||
if: success()
|
||||
- name: Extract metadata for Docker (tags, labels)
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5.5.1
|
||||
with:
|
||||
@@ -102,18 +108,17 @@ jobs:
|
||||
openim/openim-server
|
||||
ghcr.io/openimsdk/openim-server
|
||||
registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server
|
||||
|
||||
# generate Docker tags based on the following events/attributes
|
||||
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=semver,pattern=release-{{raw}}
|
||||
type=sha
|
||||
type=raw,value=${{ github.event.inputs.tag }}
|
||||
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
@@ -135,7 +140,7 @@ jobs:
|
||||
username: ${{ secrets.ALIREGISTRY_USERNAME }}
|
||||
password: ${{ secrets.ALIREGISTRY_TOKEN }}
|
||||
|
||||
- name: Build and push Docker images
|
||||
- name: Push Docker images
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: ./main-repo
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
name: Update Version File on Release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [created]
|
||||
|
||||
jobs:
|
||||
update-version:
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
TAG_VERSION: ${{ github.event.release.tag_name }}
|
||||
steps:
|
||||
# Step 1: Checkout the original repository's code
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
# Step 2: Set up Git with official account
|
||||
- name: Set up Git
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
# Step 3: Check and delete existing tag
|
||||
- name: Check and delete existing tag
|
||||
run: |
|
||||
if git rev-parse ${{ env.TAG_VERSION }} >/dev/null 2>&1; then
|
||||
git tag -d ${{ env.TAG_VERSION }}
|
||||
git push --delete origin ${{ env.TAG_VERSION }}
|
||||
fi
|
||||
|
||||
# Step 4: Update version file
|
||||
- name: Update version file
|
||||
run: |
|
||||
echo "${{ env.TAG_VERSION }}" > version/version
|
||||
|
||||
# Step 5: Commit and push changes
|
||||
- name: Commit and push changes
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
git add version/version
|
||||
git commit -m "Update version to ${{ env.TAG_VERSION }}"
|
||||
git push origin HEAD:${{ github.ref }}
|
||||
|
||||
# Step 6: Create and push tag
|
||||
- name: Create and push tag
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: |
|
||||
git tag ${{ env.TAG_VERSION }}
|
||||
git push origin ${{ env.TAG_VERSION }}
|
||||
|
||||
# Step 7: Find and Publish Draft Release
|
||||
- name: Find and Publish Draft Release
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
// Get the list of releases
|
||||
const releases = await github.rest.repos.listReleases({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo
|
||||
});
|
||||
|
||||
// Find the draft release where the title and tag_name are the same
|
||||
const draftRelease = releases.data.find(release =>
|
||||
release.draft && release.name === release.tag_name
|
||||
);
|
||||
|
||||
if (draftRelease) {
|
||||
// Publish the draft release using the release_id
|
||||
await github.rest.repos.updateRelease({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
release_id: draftRelease.id, // Use release_id
|
||||
draft: false
|
||||
});
|
||||
|
||||
core.info(`Draft Release ${draftRelease.tag_name} published successfully.`);
|
||||
} else {
|
||||
core.info("No matching draft release found.");
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
# Version logging for OpenIM
|
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_TOC -->
|
||||
|
||||
<!-- END MUNGE: GENERATED_TOC -->
|
||||
|
||||
{{ if .Versions -}}
|
||||
<a name="unreleased"></a>
|
||||
## [Unreleased]
|
||||
|
||||
{{ if .Unreleased.CommitGroups -}}
|
||||
{{ range .Unreleased.CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{ range .Versions }}
|
||||
<a name="{{ .Tag.Name }}"></a>
|
||||
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
|
||||
{{ range .CommitGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Commits -}}
|
||||
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .RevertCommits -}}
|
||||
### Reverts
|
||||
{{ range .RevertCommits -}}
|
||||
- {{ .Revert.Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .MergeCommits -}}
|
||||
### Pull Requests
|
||||
{{ range .MergeCommits -}}
|
||||
- {{ .Header }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .NoteGroups -}}
|
||||
{{ range .NoteGroups -}}
|
||||
### {{ .Title }}
|
||||
{{ range .Notes }}
|
||||
{{ .Body }}
|
||||
{{ end }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
|
||||
{{- if .Versions }}
|
||||
[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
|
||||
{{ range .Versions -}}
|
||||
{{ if .Tag.Previous -}}
|
||||
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
{{ end -}}
|
||||
@@ -1,67 +0,0 @@
|
||||
bin: git
|
||||
style: github
|
||||
template: CHANGELOG.tpl.md
|
||||
info:
|
||||
title: CHANGELOG
|
||||
repository_url: https://github.com/openimsdk/open-im-server
|
||||
options:
|
||||
tag_filter_pattern: '^v'
|
||||
sort: "date"
|
||||
|
||||
commits:
|
||||
filters:
|
||||
Type:
|
||||
- feat
|
||||
- fix
|
||||
- perf
|
||||
- refactor
|
||||
- docs
|
||||
- test
|
||||
- chore
|
||||
- ci
|
||||
- build
|
||||
sort_by: Scope
|
||||
|
||||
commit_groups:
|
||||
group_by: Type
|
||||
sort_by: Title
|
||||
title_order:
|
||||
- feat
|
||||
- fix
|
||||
- perf
|
||||
- refactor
|
||||
- docs
|
||||
- test
|
||||
- chore
|
||||
- ci
|
||||
- build
|
||||
title_maps:
|
||||
feat: Features
|
||||
|
||||
header:
|
||||
pattern: "<regexp>"
|
||||
pattern_maps:
|
||||
- PropName
|
||||
|
||||
issues:
|
||||
prefix:
|
||||
- #
|
||||
|
||||
refs:
|
||||
actions:
|
||||
- Closes
|
||||
- Fixes
|
||||
|
||||
merges:
|
||||
pattern: "^Merge branch '(\\w+)'$"
|
||||
pattern_maps:
|
||||
- Source
|
||||
|
||||
reverts:
|
||||
pattern: "^Revert \"([\\s\\S]*)\"$"
|
||||
pattern_maps:
|
||||
- Header
|
||||
|
||||
notes:
|
||||
keywords:
|
||||
- BREAKING CHANGE
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
# Use Go 1.21 Alpine as the base image for building the application
|
||||
FROM golang:1.21-alpine as builder
|
||||
# Use Go 1.22 Alpine as the base image for building the application
|
||||
FROM golang:1.22-alpine AS builder
|
||||
|
||||
# Define the base directory for the application as an environment variable
|
||||
ENV SERVER_DIR=/openim-server
|
||||
@@ -22,7 +22,7 @@ RUN go install github.com/magefile/mage@v1.15.0
|
||||
RUN mage build
|
||||
|
||||
# Using Alpine Linux with Go environment for the final image
|
||||
FROM golang:1.21-alpine
|
||||
FROM golang:1.22-alpine
|
||||
|
||||
# Install necessary packages, such as bash
|
||||
RUN apk add --no-cache bash
|
||||
|
||||
@@ -53,15 +53,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-cmdutils
|
||||
id: openim-cmdutils
|
||||
@@ -71,15 +64,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-crontask
|
||||
id: openim-crontask
|
||||
@@ -89,15 +75,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-msggateway
|
||||
id: openim-msggateway
|
||||
@@ -107,15 +86,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-msgtransfer
|
||||
id: openim-msgtransfer
|
||||
@@ -125,15 +97,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-push
|
||||
id: openim-push
|
||||
@@ -143,15 +108,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-rpc-auth
|
||||
id: openim-rpc-auth
|
||||
@@ -161,15 +119,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-rpc-conversation
|
||||
id: openim-rpc-conversation
|
||||
@@ -179,15 +130,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-rpc-friend
|
||||
id: openim-rpc-friend
|
||||
@@ -197,15 +141,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-rpc-group
|
||||
id: openim-rpc-group
|
||||
@@ -215,15 +152,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-rpc-msg
|
||||
id: openim-rpc-msg
|
||||
@@ -233,15 +163,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-rpc-third
|
||||
id: openim-rpc-third
|
||||
@@ -251,15 +174,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
- binary: openim-rpc-user
|
||||
id: openim-rpc-user
|
||||
@@ -269,15 +185,8 @@ builds:
|
||||
- windows
|
||||
- linux
|
||||
goarch:
|
||||
- s390x
|
||||
- mips64
|
||||
- mips64le
|
||||
- amd64
|
||||
- ppc64le
|
||||
- arm64
|
||||
goarm:
|
||||
- "6"
|
||||
- "7"
|
||||
|
||||
|
||||
# TODO:Need a script, such as the init - release to help binary to find the right directory
|
||||
|
||||
@@ -15,10 +15,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cmd"
|
||||
"github.com/openimsdk/tools/system/program"
|
||||
_ "net/http/pprof"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -5,9 +5,4 @@ etcd:
|
||||
username: ''
|
||||
password: ''
|
||||
|
||||
zookeeper:
|
||||
schema: openim
|
||||
address: [ localhost:12181 ]
|
||||
username: ''
|
||||
password: ''
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ database: openim_v3
|
||||
username: openIM
|
||||
# Password for database authentication
|
||||
password: openIM123
|
||||
# Authentication source for database authentication, if use root user, set it to admin
|
||||
authSource: openim_v3
|
||||
# Maximum number of connections in the connection pool
|
||||
maxPoolSize: 100
|
||||
# Maximum number of retry attempts for a failed database connection
|
||||
|
||||
+3
-20
@@ -1,20 +1,3 @@
|
||||
# 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.
|
||||
|
||||
# Determines if a message should be sent. If set to false, it triggers a silent sync without a message. If true, it requires triggering a conversation.
|
||||
# For rpc notification, send twice: once as a message and once as a notification.
|
||||
# The options field 'isNotification' indicates if it's a notification.
|
||||
groupCreated:
|
||||
isSendMsg: true
|
||||
# Reliability level of the message sending.
|
||||
@@ -309,9 +292,9 @@ userInfoUpdated:
|
||||
unreadCount: false
|
||||
offlinePush:
|
||||
enable: true
|
||||
title: Remove a blocked user
|
||||
desc: Remove a blocked user
|
||||
ext: Remove a blocked user
|
||||
title: userInfo updated
|
||||
desc: userInfo updated
|
||||
ext: userInfo updated
|
||||
|
||||
userStatusChanged:
|
||||
isSendMsg: false
|
||||
|
||||
@@ -22,6 +22,3 @@ longConnSvr:
|
||||
websocketMaxMsgLen: 4096
|
||||
# WebSocket connection handshake timeout in seconds
|
||||
websocketTimeout: 10
|
||||
|
||||
# 1: For Android, iOS, Windows, Mac, and web platforms, only one instance can be online at a time
|
||||
multiLoginPolicy: 1
|
||||
|
||||
@@ -3,4 +3,4 @@ prometheus:
|
||||
enable: true
|
||||
# List of ports that Prometheus listens on; each port corresponds to an instance of monitoring. Ensure these are managed accordingly
|
||||
# Because four instances have been launched, four ports need to be specified
|
||||
ports: [ 12020, 12021, 12022, 12023, 12024, 12025, 12026, 12027 ]
|
||||
ports: [ 12020, 12021, 12022, 12023, 12024, 12025, 12026, 12027, 12028, 12029, 12030, 12031, 12032, 12033, 12034, 12035 ]
|
||||
|
||||
+16
-16
@@ -10,32 +10,32 @@ prometheus:
|
||||
# Enable or disable Prometheus monitoring
|
||||
enable: true
|
||||
# List of ports that Prometheus listens on; these must match the number of rpc.ports to ensure correct monitoring setup
|
||||
ports: [ 12170, 12171, 12172, 12173, 12174, 12175, 12176, 12177, 12178, 12179, 12180, 12181, 12182, 12183, 12184, 12185 ]
|
||||
ports: [ 12170, 12171, 12172, 12173, 12174, 12175, 12176, 12177, 12178, 12179, 12180, 12182, 12183, 12184, 12185, 12186 ]
|
||||
|
||||
maxConcurrentWorkers: 3
|
||||
#Use geTui for offline push notifications, or choose fcm or jpns; corresponding configuration settings must be specified.
|
||||
#Use geTui for offline push notifications, or choose fcm or jpush; corresponding configuration settings must be specified.
|
||||
enable: geTui
|
||||
geTui:
|
||||
pushUrl: https://restapi.getui.com/v2/$appId
|
||||
masterSecret:
|
||||
appKey:
|
||||
intent:
|
||||
channelID:
|
||||
channelName:
|
||||
masterSecret:
|
||||
appKey:
|
||||
intent:
|
||||
channelID:
|
||||
channelName:
|
||||
fcm:
|
||||
# Prioritize using file paths. If the file path is empty, use URL
|
||||
filePath: # File path is concatenated with the parameters passed in through - c(`mage` default pass in `config/`) and filePath.
|
||||
filePath: # File path is concatenated with the parameters passed in through - c(`mage` default pass in `config/`) and filePath.
|
||||
authURL: # Must start with https or http.
|
||||
jpns:
|
||||
appKey:
|
||||
masterSecret:
|
||||
pushURL:
|
||||
pushIntent:
|
||||
jpush:
|
||||
appKey:
|
||||
masterSecret:
|
||||
pushURL:
|
||||
pushIntent:
|
||||
|
||||
# iOS system push sound and badge count
|
||||
iosPush:
|
||||
pushSound: xxx
|
||||
badgeCount: true
|
||||
production: false
|
||||
pushSound: xxx
|
||||
badgeCount: true
|
||||
production: false
|
||||
|
||||
fullUserCache: true
|
||||
|
||||
@@ -46,7 +46,7 @@ scrape_configs:
|
||||
- job_name: openimserver-openim-push
|
||||
static_configs:
|
||||
- targets: [ internal_ip:12170, internal_ip:12171, internal_ip:12172, internal_ip:12173, internal_ip:12174, internal_ip:12175, internal_ip:12176, internal_ip:12177 ]
|
||||
# - targets: [ internal_ip:12170, internal_ip:12171, internal_ip:12172, internal_ip:12173, internal_ip:12174, internal_ip:12175, internal_ip:12176, internal_ip:12177, internal_ip:12178, internal_ip:12179, internal_ip:12180, internal_ip:12181, internal_ip:12182, internal_ip:12183, internal_ip:12184, internal_ip:12185 ]
|
||||
# - targets: [ internal_ip:12170, internal_ip:12171, internal_ip:12172, internal_ip:12173, internal_ip:12174, internal_ip:12175, internal_ip:12176, internal_ip:12177, internal_ip:12178, internal_ip:12179, internal_ip:12180, internal_ip:12182, internal_ip:12183, internal_ip:12184, internal_ip:12185, internal_ip:12186 ]
|
||||
labels:
|
||||
namespace: default
|
||||
- job_name: openimserver-openim-rpc-auth
|
||||
|
||||
@@ -12,3 +12,7 @@ rpcRegisterName:
|
||||
|
||||
imAdminUserID: [ imAdmin ]
|
||||
|
||||
# 1: For Android, iOS, Windows, Mac, and web platforms, only one instance can be online at a time
|
||||
multiLogin:
|
||||
policy: 1
|
||||
maxNumOneEnd: 30
|
||||
+26
-1
@@ -1,8 +1,18 @@
|
||||
url: webhook://127.0.0.1:10008/callbackExample
|
||||
url: http://127.0.0.1:10006/callbackExample
|
||||
beforeSendSingleMsg:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
# Only the contentType in allowedTypes will send the callback.
|
||||
# Supports two formats: a single type or a range. The range is defined by the lower and upper bounds connected with a hyphen ("-").
|
||||
# e.g. allowedTypes: [1, 100, 200-500, 600-700] means that only contentType within the range
|
||||
# {1, 100} ∪ [200, 500] ∪ [600, 700] will be allowed through the filter.
|
||||
# If not set, all contentType messages will through this filter.
|
||||
allowedTypes: []
|
||||
# Only the contentType not in deniedTypes will send the callback.
|
||||
# Supports two formats, same as allowedTypes.
|
||||
# If not set, all contentType messages will through this filter.
|
||||
deniedTypes: []
|
||||
beforeUpdateUserInfoEx:
|
||||
enable: false
|
||||
timeout: 5
|
||||
@@ -16,17 +26,29 @@ afterSendSingleMsg:
|
||||
# Only the senID/recvID specified in attentionIds will send the callback
|
||||
# if not set, all user messages will be callback
|
||||
attentionIds: []
|
||||
# See beforeSendSingleMsg comment.
|
||||
allowedTypes: []
|
||||
deniedTypes: []
|
||||
beforeSendGroupMsg:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
# See beforeSendSingleMsg comment.
|
||||
allowedTypes: []
|
||||
deniedTypes: []
|
||||
beforeMsgModify:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
# See beforeSendSingleMsg comment.
|
||||
allowedTypes: []
|
||||
deniedTypes: []
|
||||
afterSendGroupMsg:
|
||||
enable: false
|
||||
timeout: 5
|
||||
# See beforeSendSingleMsg comment.
|
||||
allowedTypes: []
|
||||
deniedTypes: []
|
||||
afterUserOnline:
|
||||
enable: false
|
||||
timeout: 5
|
||||
@@ -151,6 +173,9 @@ beforeAddFriendAgree:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
afterAddFriendAgree:
|
||||
enable: false
|
||||
timeout: 5
|
||||
afterDeleteFriend:
|
||||
enable: false
|
||||
timeout: 5
|
||||
|
||||
@@ -240,11 +240,11 @@ push:
|
||||
channelName: ${GETUI_CHANNEL_NAME}
|
||||
fcm:
|
||||
serviceAccount: "${FCM_SERVICE_ACCOUNT}"
|
||||
jpns:
|
||||
appKey: ${JPNS_APP_KEY}
|
||||
masterSecret: ${JPNS_MASTER_SECRET}
|
||||
pushUrl: ${JPNS_PUSH_URL}
|
||||
pushIntent: ${JPNS_PUSH_INTENT}
|
||||
jpush:
|
||||
appKey: ${JPUSH_APP_KEY}
|
||||
masterSecret: ${JPUSH_MASTER_SECRET}
|
||||
pushUrl: ${JPUSH_PUSH_URL}
|
||||
pushIntent: ${JPUSH_PUSH_INTENT}
|
||||
|
||||
# App manager configuration
|
||||
#
|
||||
|
||||
+29
-23
@@ -1,5 +1,3 @@
|
||||
version: '3'
|
||||
|
||||
networks:
|
||||
openim:
|
||||
driver: bridge
|
||||
@@ -10,12 +8,35 @@ services:
|
||||
ports:
|
||||
- "37017:27017"
|
||||
container_name: mongo
|
||||
command: ["/bin/bash", "-c", "/docker-entrypoint-initdb.d/mongo-init.sh; docker-entrypoint.sh mongod --wiredTigerCacheSizeGB 1 --auth"]
|
||||
command: >
|
||||
bash -c '
|
||||
docker-entrypoint.sh mongod --wiredTigerCacheSizeGB $$wiredTigerCacheSizeGB --auth &
|
||||
until mongosh -u $$MONGO_INITDB_ROOT_USERNAME -p $$MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "db.runCommand({ ping: 1 })" &>/dev/null; do
|
||||
echo "Waiting for MongoDB to start..."
|
||||
sleep 1
|
||||
done &&
|
||||
mongosh -u $$MONGO_INITDB_ROOT_USERNAME -p $$MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "
|
||||
db = db.getSiblingDB(\"$$MONGO_INITDB_DATABASE\");
|
||||
if (!db.getUser(\"$$MONGO_OPENIM_USERNAME\")) {
|
||||
db.createUser({
|
||||
user: \"$$MONGO_OPENIM_USERNAME\",
|
||||
pwd: \"$$MONGO_OPENIM_PASSWORD\",
|
||||
roles: [{role: \"readWrite\", db: \"$$MONGO_INITDB_DATABASE\"}]
|
||||
});
|
||||
print(\"User created successfully: \");
|
||||
print(\"Username: $$MONGO_OPENIM_USERNAME\");
|
||||
print(\"Password: $$MONGO_OPENIM_PASSWORD\");
|
||||
print(\"Database: $$MONGO_INITDB_DATABASE\");
|
||||
} else {
|
||||
print(\"User already exists in database: $$MONGO_INITDB_DATABASE, Username: $$MONGO_OPENIM_USERNAME\");
|
||||
}
|
||||
" &&
|
||||
tail -f /dev/null
|
||||
'
|
||||
volumes:
|
||||
- "${DATA_DIR}/components/mongodb/data/db:/data/db"
|
||||
- "${DATA_DIR}/components/mongodb/data/logs:/data/logs"
|
||||
- "${DATA_DIR}/components/mongodb/data/conf:/etc/mongo"
|
||||
- "./scripts/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh:ro"
|
||||
environment:
|
||||
- TZ=Asia/Shanghai
|
||||
- wiredTigerCacheSizeGB=1
|
||||
@@ -45,19 +66,6 @@ services:
|
||||
networks:
|
||||
- openim
|
||||
|
||||
zookeeper:
|
||||
image: "${ZOOKEEPER_IMAGE}"
|
||||
container_name: zookeeper
|
||||
ports:
|
||||
- "12181:2181"
|
||||
environment:
|
||||
#JVMFLAGS: "-Xms32m -Xmx128m"
|
||||
TZ: "Asia/Shanghai"
|
||||
ALLOW_ANONYMOUS_LOGIN: "yes"
|
||||
restart: always
|
||||
networks:
|
||||
- openim
|
||||
|
||||
etcd:
|
||||
image: "${ETCD_IMAGE}"
|
||||
container_name: etcd
|
||||
@@ -86,10 +94,7 @@ services:
|
||||
ports:
|
||||
- "19094:9094"
|
||||
volumes:
|
||||
- ./scripts/create-topic.sh:/opt/bitnami/kafka/create-topic.sh
|
||||
- "${DATA_DIR}/components/kafka:/bitnami/kafka"
|
||||
command: >
|
||||
bash -c "/opt/bitnami/scripts/kafka/run.sh & /opt/bitnami/kafka/create-topic.sh; wait"
|
||||
environment:
|
||||
#KAFKA_HEAP_OPTS: "-Xms128m -Xmx256m"
|
||||
TZ: Asia/Shanghai
|
||||
@@ -100,10 +105,11 @@ services:
|
||||
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:19094
|
||||
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
|
||||
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
||||
KAFKA_NUM_PARTITIONS: 8
|
||||
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true"
|
||||
networks:
|
||||
- openim
|
||||
|
||||
|
||||
minio:
|
||||
image: "${MINIO_IMAGE}"
|
||||
ports:
|
||||
@@ -139,11 +145,12 @@ services:
|
||||
- "11002:80"
|
||||
networks:
|
||||
- openim
|
||||
|
||||
|
||||
# prometheus:
|
||||
# image: ${PROMETHEUS_IMAGE}
|
||||
# container_name: prometheus
|
||||
# restart: always
|
||||
# user: root
|
||||
# volumes:
|
||||
# - ./config/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
# - ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
|
||||
@@ -185,4 +192,3 @@ services:
|
||||
# - ${DATA_DIR:-./}/components/grafana:/var/lib/grafana
|
||||
# networks:
|
||||
# - openim
|
||||
|
||||
|
||||
@@ -474,10 +474,10 @@ This section involves setting up additional configuration variables for Websocke
|
||||
| GETUI_CHANNEL_ID | [User Defined] | GeTui Channel ID |
|
||||
| GETUI_CHANNEL_NAME | [User Defined] | GeTui Channel Name |
|
||||
| FCM_SERVICE_ACCOUNT | "x.json" | FCM Service Account |
|
||||
| JPNS_APP_KEY | [User Defined] | JPNS Application Key |
|
||||
| JPNS_MASTER_SECRET | [User Defined] | JPNS Master Secret |
|
||||
| JPNS_PUSH_URL | [User Defined] | JPNS Push Notification URL |
|
||||
| JPNS_PUSH_INTENT | [User Defined] | JPNS Push Intent |
|
||||
| JPUSH_APP_KEY | [User Defined] | JPUSH Application Key |
|
||||
| JPUSH_MASTER_SECRET | [User Defined] | JPUSH Master Secret |
|
||||
| JPUSH_PUSH_URL | [User Defined] | JPUSH Push Notification URL |
|
||||
| JPUSH_PUSH_INTENT | [User Defined] | JPUSH Push Intent |
|
||||
| IM_ADMIN_USERID | "imAdmin" | IM Administrator ID |
|
||||
| IM_ADMIN_NAME | "imAdmin" | IM Administrator Nickname |
|
||||
| MULTILOGIN_POLICY | "1" | Multi-login Policy |
|
||||
|
||||
@@ -1,24 +1,26 @@
|
||||
module github.com/openimsdk/open-im-server/v3
|
||||
|
||||
go 1.21.2
|
||||
go 1.22.0
|
||||
|
||||
toolchain go1.23.2
|
||||
|
||||
require (
|
||||
firebase.google.com/go v3.13.0+incompatible
|
||||
firebase.google.com/go/v4 v4.14.1
|
||||
github.com/dtm-labs/rockscache v0.1.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
github.com/go-playground/validator/v10 v10.20.0
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1
|
||||
github.com/gorilla/websocket v1.5.1
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||
github.com/mitchellh/mapstructure v1.5.0
|
||||
github.com/openimsdk/protocol v0.0.72-alpha.24
|
||||
github.com/openimsdk/tools v0.0.50-alpha.12
|
||||
github.com/openimsdk/protocol v0.0.72-alpha.54
|
||||
github.com/openimsdk/tools v0.0.50-alpha.32
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/prometheus/client_golang v1.18.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
go.mongodb.org/mongo-driver v1.14.0
|
||||
google.golang.org/api v0.165.0
|
||||
google.golang.org/api v0.170.0
|
||||
google.golang.org/grpc v1.66.2
|
||||
google.golang.org/protobuf v1.34.2
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@@ -47,12 +49,13 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.112.0 // indirect
|
||||
cloud.google.com/go v0.112.1 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
||||
cloud.google.com/go/firestore v1.14.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.5 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.4 // indirect
|
||||
cloud.google.com/go/storage v1.36.0 // indirect
|
||||
cloud.google.com/go/firestore v1.15.0 // indirect
|
||||
cloud.google.com/go/iam v1.1.7 // indirect
|
||||
cloud.google.com/go/longrunning v0.5.5 // indirect
|
||||
cloud.google.com/go/storage v1.40.0 // indirect
|
||||
github.com/MicahParks/keyfunc v1.9.0 // indirect
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.23.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1 // indirect
|
||||
@@ -91,7 +94,7 @@ require (
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
@@ -102,7 +105,7 @@ require (
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/s2a-go v0.1.7 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||
@@ -164,11 +167,11 @@ require (
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.13 // indirect
|
||||
go.etcd.io/etcd/client/v3 v3.5.13 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect
|
||||
go.opentelemetry.io/otel v1.23.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.23.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.23.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 // indirect
|
||||
go.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.7.0 // indirect
|
||||
@@ -178,8 +181,8 @@ require (
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
google.golang.org/appengine v1.6.8 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect
|
||||
google.golang.org/appengine/v2 v2.0.2 // indirect
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||
gorm.io/gorm v1.25.8 // indirect
|
||||
|
||||
@@ -1,21 +1,23 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
cloud.google.com/go v0.112.0 h1:tpFCD7hpHFlQ8yPwT3x+QeXqc2T6+n6T+hmABHfDUSM=
|
||||
cloud.google.com/go v0.112.0/go.mod h1:3jEEVwZ/MHU4djK5t5RHuKOA/GbLddgTdVubX1qnPD4=
|
||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
||||
cloud.google.com/go/firestore v1.14.0 h1:8aLcKnMPoldYU3YHgu4t2exrKhLQkqaXAGqT0ljrFVw=
|
||||
cloud.google.com/go/firestore v1.14.0/go.mod h1:96MVaHLsEhbvkBEdZgfN+AS/GIkco1LRpH9Xp9YZfzQ=
|
||||
cloud.google.com/go/iam v1.1.5 h1:1jTsCu4bcsNsE4iiqNT5SHwrDRCfRmIaaaVFhRveTJI=
|
||||
cloud.google.com/go/iam v1.1.5/go.mod h1:rB6P/Ic3mykPbFio+vo7403drjlgvoWfYpJhMXEbzv8=
|
||||
cloud.google.com/go/longrunning v0.5.4 h1:w8xEcbZodnA2BbW6sVirkkoC+1gP8wS57EUUgGS0GVg=
|
||||
cloud.google.com/go/longrunning v0.5.4/go.mod h1:zqNVncI0BOP8ST6XQD1+VcvuShMmq7+xFSzOL++V0dI=
|
||||
cloud.google.com/go/storage v1.36.0 h1:P0mOkAcaJxhCTvAkMhxMfrTKiNcub4YmmPBtlhAyTr8=
|
||||
cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYEsng2xgOs8=
|
||||
firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4=
|
||||
firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs=
|
||||
cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
|
||||
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
|
||||
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
|
||||
cloud.google.com/go/iam v1.1.7/go.mod h1:J4PMPg8TtyurAUvSmPj8FF3EDgY1SPRZxcUGrn7WXGA=
|
||||
cloud.google.com/go/longrunning v0.5.5 h1:GOE6pZFdSrTb4KAiKnXsJBtlE6mEyaW44oKyMILWnOg=
|
||||
cloud.google.com/go/longrunning v0.5.5/go.mod h1:WV2LAxD8/rg5Z1cNW6FJ/ZpX4E4VnDnoTk0yawPBB7s=
|
||||
cloud.google.com/go/storage v1.40.0 h1:VEpDQV5CJxFmJ6ueWNsKxcr1QAYOXEgxDa+sBbJahPw=
|
||||
cloud.google.com/go/storage v1.40.0/go.mod h1:Rrj7/hKlG87BLqDJYtwR0fbPld8uJPbQ2ucUMY7Ir0g=
|
||||
firebase.google.com/go/v4 v4.14.1 h1:4qiUETaFRWoFGE1XP5VbcEdtPX93Qs+8B/7KvP2825g=
|
||||
firebase.google.com/go/v4 v4.14.1/go.mod h1:fgk2XshgNDEKaioKco+AouiegSI9oTWVqRaBdTTGBoM=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/IBM/sarama v1.43.0 h1:YFFDn8mMI2QL0wOrG0J2sFoVIAFl7hS9JQi2YZsXtJc=
|
||||
github.com/IBM/sarama v1.43.0/go.mod h1:zlE6HEbC/SMQ9mhEYaF7nNLYOUyrs0obySKCckWP9BM=
|
||||
github.com/MicahParks/keyfunc v1.9.0 h1:lhKd5xrFHLNOWrDc4Tyb/Q1AJ4LCzQ48GVJyVIID3+o=
|
||||
github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x95xuVNtM5jw=
|
||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
@@ -78,8 +80,6 @@ github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJ
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b h1:ga8SEFjZ60pxLcmhnThWgvH2wg8376yUJmPhEH4H3kw=
|
||||
github.com/cncf/xds/go v0.0.0-20240423153145-555b57ec207b/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
|
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
|
||||
@@ -107,8 +107,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
|
||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
|
||||
github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w=
|
||||
github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg=
|
||||
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
|
||||
@@ -128,8 +126,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
|
||||
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
|
||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
@@ -159,14 +157,16 @@ github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MG
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
@@ -175,8 +175,6 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W
|
||||
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
|
||||
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||
github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM=
|
||||
@@ -188,7 +186,6 @@ github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||
github.com/google/go-cmp v0.5.0/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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
|
||||
@@ -205,8 +202,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs=
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=
|
||||
github.com/googleapis/gax-go/v2 v2.12.0/go.mod h1:y+aIqrI5eb1YGMVJfuV3185Ts/D7qKpsEkdD5+I6QGU=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA=
|
||||
github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4=
|
||||
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||
github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY=
|
||||
@@ -322,10 +319,10 @@ github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
|
||||
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
|
||||
github.com/openimsdk/gomake v0.0.14-alpha.5 h1:VY9c5x515lTfmdhhPjMvR3BBRrRquAUCFsz7t7vbv7Y=
|
||||
github.com/openimsdk/gomake v0.0.14-alpha.5/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
|
||||
github.com/openimsdk/protocol v0.0.72-alpha.24 h1:1Dl8TEZVXhdmve+ewoLkJa2wbFBIHqPgjvr9u/J66JM=
|
||||
github.com/openimsdk/protocol v0.0.72-alpha.24/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8=
|
||||
github.com/openimsdk/tools v0.0.50-alpha.12 h1:rV3BxgqN+F79vZvdoQ+97Eob8ScsRVEM8D+Wrcl23uo=
|
||||
github.com/openimsdk/tools v0.0.50-alpha.12/go.mod h1:h1cYmfyaVtgFbKmb1Cfsl8XwUOMTt8ubVUQrdGtsUh4=
|
||||
github.com/openimsdk/protocol v0.0.72-alpha.54 h1:opato7N4QjjRq/SHD54bDSVBpOEEDp1VLWVk5Os2A9s=
|
||||
github.com/openimsdk/protocol v0.0.72-alpha.54/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8=
|
||||
github.com/openimsdk/tools v0.0.50-alpha.32 h1:JEsUFHFnaYg230TG+Ke3SUnaA2h44t4kABAzEdv5VZw=
|
||||
github.com/openimsdk/tools v0.0.50-alpha.32/go.mod h1:r5U6RbxcR4xhKb2fhTmKGC9Yt5LcErHBVt3lhXQIHSo=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
@@ -359,8 +356,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
|
||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
@@ -436,18 +433,18 @@ go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd
|
||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
|
||||
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
|
||||
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 h1:UNQQKPfTDe1J81ViolILjTKPr9WetKW6uei2hFgJmFs=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0/go.mod h1:r9vWsPS/3AQItv3OSlEJ/E4mbrhUbbw18meOjArPtKQ=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 h1:sv9kVfal0MK0wBMCOGr+HeJm9v803BkJxGrk2au7j08=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0/go.mod h1:SK2UL73Zy1quvRPonmOmRDiWk1KBV3LyIeeIxcEApWw=
|
||||
go.opentelemetry.io/otel v1.23.0 h1:Df0pqjqExIywbMCMTxkAwzjLZtRf+bBKLbUcpxO2C9E=
|
||||
go.opentelemetry.io/otel v1.23.0/go.mod h1:YCycw9ZeKhcJFrb34iVSkyT0iczq/zYDtZYFufObyB0=
|
||||
go.opentelemetry.io/otel/metric v1.23.0 h1:pazkx7ss4LFVVYSxYew7L5I6qvLXHA0Ap2pwV+9Cnpo=
|
||||
go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms20Jb7Bbp+HiTo=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8=
|
||||
go.opentelemetry.io/otel/sdk v1.21.0/go.mod h1:Nna6Yv7PWTdgJHVRD9hIYywQBRx7pbox6nwBnZIxl/E=
|
||||
go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI=
|
||||
go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg=
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0 h1:jq9TW8u3so/bN+JPT166wjOI6/vQPF6Xe7nMNIltagk=
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.49.0/go.mod h1:p8pYQP+m5XfbZm9fxtSKAbM6oIllS7s2AfxrChvc7iw=
|
||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
|
||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
|
||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
|
||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0 h1:6coWHw9xw7EfClIC/+O31R8IY3/+EiRFHevmHafB2Gw=
|
||||
go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc=
|
||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
|
||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
|
||||
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
|
||||
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8=
|
||||
@@ -492,6 +489,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
|
||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220708220712-1185a9018129/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
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=
|
||||
@@ -554,19 +552,19 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||
google.golang.org/api v0.165.0 h1:zd5d4JIIIaYYsfVy1HzoXYZ9rWCSBxxAglbczzo7Bgc=
|
||||
google.golang.org/api v0.165.0/go.mod h1:2OatzO7ZDQsoS7IFf3rvsE17/TldiU3F/zxFHeqUB5o=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
google.golang.org/api v0.170.0 h1:zMaruDePM88zxZBG+NG8+reALO2rfLhe/JShitLyT48=
|
||||
google.golang.org/api v0.170.0/go.mod h1:/xql9M2btF85xac/VAm4PsLMTLVGUOpq4BE9R8jyNy8=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
|
||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
|
||||
google.golang.org/appengine/v2 v2.0.2 h1:MSqyWy2shDLwG7chbwBJ5uMyw6SNqJzhJHNDwYB0Akk=
|
||||
google.golang.org/appengine/v2 v2.0.2/go.mod h1:PkgRUWz4o1XOvbqtWTkBtCitEJ5Tp4HoVEdMMYQR/8E=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo=
|
||||
google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||
@@ -587,8 +585,6 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -27,8 +27,8 @@ func NewAuthApi(client rpcclient.Auth) AuthApi {
|
||||
return AuthApi(client)
|
||||
}
|
||||
|
||||
func (o *AuthApi) UserToken(c *gin.Context) {
|
||||
a2r.Call(auth.AuthClient.UserToken, o.Client, c)
|
||||
func (o *AuthApi) GetAdminToken(c *gin.Context) {
|
||||
a2r.Call(auth.AuthClient.GetAdminToken, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *AuthApi) GetUserToken(c *gin.Context) {
|
||||
|
||||
@@ -62,3 +62,11 @@ func (o *ConversationApi) GetIncrementalConversation(c *gin.Context) {
|
||||
func (o *ConversationApi) GetOwnerConversation(c *gin.Context) {
|
||||
a2r.Call(conversation.ConversationClient.GetOwnerConversation, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *ConversationApi) GetNotNotifyConversationIDs(c *gin.Context) {
|
||||
a2r.Call(conversation.ConversationClient.GetNotNotifyConversationIDs, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *ConversationApi) GetPinnedConversationIDs(c *gin.Context) {
|
||||
a2r.Call(conversation.ConversationClient.GetPinnedConversationIDs, o.Client, c)
|
||||
}
|
||||
|
||||
@@ -72,6 +72,10 @@ func (o *FriendApi) GetPaginationBlacks(c *gin.Context) {
|
||||
a2r.Call(relation.FriendClient.GetPaginationBlacks, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *FriendApi) GetSpecifiedBlacks(c *gin.Context) {
|
||||
a2r.Call(relation.FriendClient.GetSpecifiedBlacks, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *FriendApi) RemoveBlack(c *gin.Context) {
|
||||
a2r.Call(relation.FriendClient.RemoveBlack, o.Client, c)
|
||||
}
|
||||
|
||||
@@ -67,6 +67,10 @@ func (o *GroupApi) GetGroupUsersReqApplicationList(c *gin.Context) {
|
||||
a2r.Call(group.GroupClient.GetGroupUsersReqApplicationList, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *GroupApi) GetSpecifiedUserGroupRequestInfo(c *gin.Context) {
|
||||
a2r.Call(group.GroupClient.GetSpecifiedUserGroupRequestInfo, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *GroupApi) GetGroupsInfo(c *gin.Context) {
|
||||
a2r.Call(group.GroupClient.GetGroupsInfo, o.Client, c)
|
||||
//a2r.Call(group.GroupClient.GetGroupsInfo, o.Client, c, a2r.NewNilReplaceOption(group.GroupClient.GetGroupsInfo))
|
||||
|
||||
@@ -0,0 +1,262 @@
|
||||
package jssdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/openimsdk/protocol/conversation"
|
||||
"github.com/openimsdk/protocol/group"
|
||||
"github.com/openimsdk/protocol/jssdk"
|
||||
"github.com/openimsdk/protocol/msg"
|
||||
"github.com/openimsdk/protocol/relation"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/protocol/user"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"sort"
|
||||
)
|
||||
|
||||
const (
|
||||
maxGetActiveConversation = 500
|
||||
defaultGetActiveConversation = 100
|
||||
)
|
||||
|
||||
func NewJSSdkApi(user user.UserClient, friend relation.FriendClient, group group.GroupClient, msg msg.MsgClient, conv conversation.ConversationClient) *JSSdk {
|
||||
return &JSSdk{
|
||||
user: user,
|
||||
friend: friend,
|
||||
group: group,
|
||||
msg: msg,
|
||||
conv: conv,
|
||||
}
|
||||
}
|
||||
|
||||
type JSSdk struct {
|
||||
user user.UserClient
|
||||
friend relation.FriendClient
|
||||
group group.GroupClient
|
||||
msg msg.MsgClient
|
||||
conv conversation.ConversationClient
|
||||
}
|
||||
|
||||
func (x *JSSdk) GetActiveConversations(c *gin.Context) {
|
||||
call(c, x.getActiveConversations)
|
||||
}
|
||||
|
||||
func (x *JSSdk) GetConversations(c *gin.Context) {
|
||||
call(c, x.getConversations)
|
||||
}
|
||||
|
||||
func (x *JSSdk) fillConversations(ctx context.Context, conversations []*jssdk.ConversationMsg) error {
|
||||
if len(conversations) == 0 {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
userIDs []string
|
||||
groupIDs []string
|
||||
)
|
||||
for _, c := range conversations {
|
||||
if c.Conversation.GroupID == "" {
|
||||
userIDs = append(userIDs, c.Conversation.UserID)
|
||||
} else {
|
||||
groupIDs = append(groupIDs, c.Conversation.GroupID)
|
||||
}
|
||||
}
|
||||
var (
|
||||
userMap map[string]*sdkws.UserInfo
|
||||
friendMap map[string]*relation.FriendInfoOnly
|
||||
groupMap map[string]*sdkws.GroupInfo
|
||||
)
|
||||
if len(userIDs) > 0 {
|
||||
users, err := field(ctx, x.user.GetDesignateUsers, &user.GetDesignateUsersReq{UserIDs: userIDs}, (*user.GetDesignateUsersResp).GetUsersInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
friends, err := field(ctx, x.friend.GetFriendInfo, &relation.GetFriendInfoReq{OwnerUserID: conversations[0].Conversation.OwnerUserID, FriendUserIDs: userIDs}, (*relation.GetFriendInfoResp).GetFriendInfos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
userMap = datautil.SliceToMap(users, (*sdkws.UserInfo).GetUserID)
|
||||
friendMap = datautil.SliceToMap(friends, (*relation.FriendInfoOnly).GetFriendUserID)
|
||||
}
|
||||
if len(groupIDs) > 0 {
|
||||
resp, err := x.group.GetGroupsInfo(ctx, &group.GetGroupsInfoReq{GroupIDs: groupIDs})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
groupMap = datautil.SliceToMap(resp.GroupInfos, (*sdkws.GroupInfo).GetGroupID)
|
||||
}
|
||||
for _, c := range conversations {
|
||||
if c.Conversation.GroupID == "" {
|
||||
c.User = userMap[c.Conversation.UserID]
|
||||
c.Friend = friendMap[c.Conversation.UserID]
|
||||
} else {
|
||||
c.Group = groupMap[c.Conversation.GroupID]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActiveConversationsReq) (*jssdk.GetActiveConversationsResp, error) {
|
||||
if req.Count <= 0 || req.Count > maxGetActiveConversation {
|
||||
req.Count = defaultGetActiveConversation
|
||||
}
|
||||
req.OwnerUserID = mcontext.GetOpUserID(ctx)
|
||||
conversationIDs, err := field(ctx, x.conv.GetConversationIDs,
|
||||
&conversation.GetConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetConversationIDsResp).GetConversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(conversationIDs) == 0 {
|
||||
return &jssdk.GetActiveConversationsResp{}, nil
|
||||
}
|
||||
readSeq, err := field(ctx, x.msg.GetHasReadSeqs,
|
||||
&msg.GetHasReadSeqsReq{UserID: req.OwnerUserID, ConversationIDs: conversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
activeConversation, err := field(ctx, x.msg.GetActiveConversation,
|
||||
&msg.GetActiveConversationReq{ConversationIDs: conversationIDs}, (*msg.GetActiveConversationResp).GetConversations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(activeConversation) == 0 {
|
||||
return &jssdk.GetActiveConversationsResp{}, nil
|
||||
}
|
||||
sortConversations := sortActiveConversations{
|
||||
Conversation: activeConversation,
|
||||
}
|
||||
if len(activeConversation) > 1 {
|
||||
pinnedConversationIDs, err := field(ctx, x.conv.GetPinnedConversationIDs,
|
||||
&conversation.GetPinnedConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetPinnedConversationIDsResp).GetConversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sortConversations.PinnedConversationIDs = datautil.SliceSet(pinnedConversationIDs)
|
||||
}
|
||||
sort.Sort(&sortConversations)
|
||||
sortList := sortConversations.Top(int(req.Count))
|
||||
conversations, err := field(ctx, x.conv.GetConversations,
|
||||
&conversation.GetConversationsReq{
|
||||
OwnerUserID: req.OwnerUserID,
|
||||
ConversationIDs: datautil.Slice(sortList, func(c *msg.ActiveConversation) string {
|
||||
return c.ConversationID
|
||||
})}, (*conversation.GetConversationsResp).GetConversations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgs, err := field(ctx, x.msg.GetSeqMessage,
|
||||
&msg.GetSeqMessageReq{
|
||||
UserID: req.OwnerUserID,
|
||||
Conversations: datautil.Slice(sortList, func(c *msg.ActiveConversation) *msg.ConversationSeqs {
|
||||
return &msg.ConversationSeqs{
|
||||
ConversationID: c.ConversationID,
|
||||
Seqs: []int64{c.MaxSeq},
|
||||
}
|
||||
}),
|
||||
}, (*msg.GetSeqMessageResp).GetMsgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string {
|
||||
return c.ConversationID
|
||||
})
|
||||
resp := make([]*jssdk.ConversationMsg, 0, len(sortList))
|
||||
for _, c := range sortList {
|
||||
conv, ok := conversationMap[c.ConversationID]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
var lastMsg *sdkws.MsgData
|
||||
if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
|
||||
lastMsg = msgList.Msgs[0]
|
||||
}
|
||||
resp = append(resp, &jssdk.ConversationMsg{
|
||||
Conversation: conv,
|
||||
LastMsg: lastMsg,
|
||||
MaxSeq: c.MaxSeq,
|
||||
ReadSeq: readSeq[c.ConversationID],
|
||||
})
|
||||
}
|
||||
if err := x.fillConversations(ctx, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var unreadCount int64
|
||||
for _, c := range activeConversation {
|
||||
count := c.MaxSeq - readSeq[c.ConversationID]
|
||||
if count > 0 {
|
||||
unreadCount += count
|
||||
}
|
||||
}
|
||||
return &jssdk.GetActiveConversationsResp{
|
||||
Conversations: resp,
|
||||
UnreadCount: unreadCount,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (x *JSSdk) getConversations(ctx context.Context, req *jssdk.GetConversationsReq) (*jssdk.GetConversationsResp, error) {
|
||||
req.OwnerUserID = mcontext.GetOpUserID(ctx)
|
||||
conversations, err := field(ctx, x.conv.GetConversations, &conversation.GetConversationsReq{OwnerUserID: req.OwnerUserID, ConversationIDs: req.ConversationIDs}, (*conversation.GetConversationsResp).GetConversations)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(conversations) == 0 {
|
||||
return &jssdk.GetConversationsResp{}, nil
|
||||
}
|
||||
req.ConversationIDs = datautil.Slice(conversations, func(c *conversation.Conversation) string {
|
||||
return c.ConversationID
|
||||
})
|
||||
maxSeqs, err := field(ctx, x.msg.GetMaxSeqs,
|
||||
&msg.GetMaxSeqsReq{ConversationIDs: req.ConversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
readSeqs, err := field(ctx, x.msg.GetHasReadSeqs,
|
||||
&msg.GetHasReadSeqsReq{UserID: req.OwnerUserID, ConversationIDs: req.ConversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversationSeqs := make([]*msg.ConversationSeqs, 0, len(conversations))
|
||||
for _, c := range conversations {
|
||||
if seq := maxSeqs[c.ConversationID]; seq > 0 {
|
||||
conversationSeqs = append(conversationSeqs, &msg.ConversationSeqs{
|
||||
ConversationID: c.ConversationID,
|
||||
Seqs: []int64{seq},
|
||||
})
|
||||
}
|
||||
}
|
||||
var msgs map[string]*sdkws.PullMsgs
|
||||
if len(conversationSeqs) > 0 {
|
||||
msgs, err = field(ctx, x.msg.GetSeqMessage,
|
||||
&msg.GetSeqMessageReq{UserID: req.OwnerUserID, Conversations: conversationSeqs}, (*msg.GetSeqMessageResp).GetMsgs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
resp := make([]*jssdk.ConversationMsg, 0, len(conversations))
|
||||
for _, c := range conversations {
|
||||
var lastMsg *sdkws.MsgData
|
||||
if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
|
||||
lastMsg = msgList.Msgs[0]
|
||||
}
|
||||
resp = append(resp, &jssdk.ConversationMsg{
|
||||
Conversation: c,
|
||||
LastMsg: lastMsg,
|
||||
MaxSeq: maxSeqs[c.ConversationID],
|
||||
ReadSeq: readSeqs[c.ConversationID],
|
||||
})
|
||||
}
|
||||
if err := x.fillConversations(ctx, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var unreadCount int64
|
||||
for conversationID, maxSeq := range maxSeqs {
|
||||
count := maxSeq - readSeqs[conversationID]
|
||||
if count > 0 {
|
||||
unreadCount += count
|
||||
}
|
||||
}
|
||||
return &jssdk.GetConversationsResp{
|
||||
Conversations: resp,
|
||||
UnreadCount: unreadCount,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package jssdk
|
||||
|
||||
import "github.com/openimsdk/protocol/msg"
|
||||
|
||||
type sortActiveConversations struct {
|
||||
Conversation []*msg.ActiveConversation
|
||||
PinnedConversationIDs map[string]struct{}
|
||||
}
|
||||
|
||||
func (s sortActiveConversations) Top(limit int) []*msg.ActiveConversation {
|
||||
if limit > 0 && len(s.Conversation) > limit {
|
||||
return s.Conversation[:limit]
|
||||
}
|
||||
return s.Conversation
|
||||
}
|
||||
|
||||
func (s sortActiveConversations) Len() int {
|
||||
return len(s.Conversation)
|
||||
}
|
||||
|
||||
func (s sortActiveConversations) Less(i, j int) bool {
|
||||
iv, jv := s.Conversation[i], s.Conversation[j]
|
||||
_, ip := s.PinnedConversationIDs[iv.ConversationID]
|
||||
_, jp := s.PinnedConversationIDs[jv.ConversationID]
|
||||
if ip != jp {
|
||||
return ip
|
||||
}
|
||||
return iv.LastTime > jv.LastTime
|
||||
}
|
||||
|
||||
func (s sortActiveConversations) Swap(i, j int) {
|
||||
s.Conversation[i], s.Conversation[j] = s.Conversation[j], s.Conversation[i]
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package jssdk
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/openimsdk/tools/a2r"
|
||||
"github.com/openimsdk/tools/apiresp"
|
||||
"github.com/openimsdk/tools/checker"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func field[A, B, C any](ctx context.Context, fn func(ctx context.Context, req *A, opts ...grpc.CallOption) (*B, error), req *A, get func(*B) C) (C, error) {
|
||||
resp, err := fn(ctx, req)
|
||||
if err != nil {
|
||||
var c C
|
||||
return c, err
|
||||
}
|
||||
return get(resp), nil
|
||||
}
|
||||
|
||||
func call[A, B any](c *gin.Context, fn func(ctx context.Context, req *A) (*B, error)) {
|
||||
var isJSON bool
|
||||
switch contentType := c.GetHeader("Content-Type"); {
|
||||
case contentType == "":
|
||||
isJSON = true
|
||||
case strings.Contains(contentType, "application/json"):
|
||||
isJSON = true
|
||||
case strings.Contains(contentType, "application/protobuf"):
|
||||
case strings.Contains(contentType, "application/x-protobuf"):
|
||||
default:
|
||||
apiresp.GinError(c, errs.ErrArgs.WrapMsg("unsupported content type"))
|
||||
return
|
||||
}
|
||||
var req *A
|
||||
if isJSON {
|
||||
var err error
|
||||
req, err = a2r.ParseRequest[A](c)
|
||||
if err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
body, err := io.ReadAll(c.Request.Body)
|
||||
if err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
req = new(A)
|
||||
if err := proto.Unmarshal(body, any(req).(proto.Message)); err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
if err := checker.Validate(&req); err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
resp, err := fn(c, req)
|
||||
if err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
if isJSON {
|
||||
apiresp.GinSuccess(c, resp)
|
||||
return
|
||||
}
|
||||
body, err := proto.Marshal(any(resp).(proto.Message))
|
||||
if err != nil {
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
apiresp.GinSuccess(c, body)
|
||||
}
|
||||
+16
-4
@@ -2,6 +2,9 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/internal/api/jssdk"
|
||||
|
||||
"github.com/gin-contrib/gzip"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
@@ -71,9 +74,10 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
|
||||
case BestSpeed:
|
||||
r.Use(gzip.Gzip(gzip.BestSpeed))
|
||||
}
|
||||
r.Use(prommetricsGin(), gin.Recovery(), mw.CorsHandler(), mw.GinParseOperationID(), GinParseToken(authRpc))
|
||||
r.Use(prommetricsGin(), gin.RecoveryWithWriter(gin.DefaultErrorWriter, mw.GinPanicErr), mw.CorsHandler(), mw.GinParseOperationID(), GinParseToken(authRpc))
|
||||
u := NewUserApi(*userRpc)
|
||||
m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID)
|
||||
j := jssdk.NewJSSdkApi(userRpc.Client, friendRpc.Client, groupRpc.Client, messageRpc.Client, conversationRpc.Client)
|
||||
userRouterGroup := r.Group("/user")
|
||||
{
|
||||
userRouterGroup.POST("/user_register", u.UserRegister)
|
||||
@@ -115,6 +119,7 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
|
||||
friendRouterGroup.POST("/set_friend_remark", f.SetFriendRemark)
|
||||
friendRouterGroup.POST("/add_black", f.AddBlack)
|
||||
friendRouterGroup.POST("/get_black_list", f.GetPaginationBlacks)
|
||||
friendRouterGroup.POST("/get_specified_blacks", f.GetSpecifiedBlacks)
|
||||
friendRouterGroup.POST("/remove_black", f.RemoveBlack)
|
||||
friendRouterGroup.POST("/get_incremental_blacks", f.GetIncrementalBlacks)
|
||||
friendRouterGroup.POST("/import_friend", f.ImportFriends)
|
||||
@@ -138,6 +143,7 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
|
||||
groupRouterGroup.POST("/get_recv_group_applicationList", g.GetRecvGroupApplicationList)
|
||||
groupRouterGroup.POST("/get_user_req_group_applicationList", g.GetUserReqGroupApplicationList)
|
||||
groupRouterGroup.POST("/get_group_users_req_application_list", g.GetGroupUsersReqApplicationList)
|
||||
groupRouterGroup.POST("/get_specified_user_group_request_info", g.GetSpecifiedUserGroupRequestInfo)
|
||||
groupRouterGroup.POST("/get_groups_info", g.GetGroupsInfo)
|
||||
groupRouterGroup.POST("/kick_group", g.KickGroupMember)
|
||||
groupRouterGroup.POST("/get_group_members_info", g.GetGroupMembersInfo)
|
||||
@@ -163,7 +169,7 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
|
||||
authRouterGroup := r.Group("/auth")
|
||||
{
|
||||
a := NewAuthApi(*authRpc)
|
||||
authRouterGroup.POST("/user_token", a.UserToken)
|
||||
authRouterGroup.POST("/get_admin_token", a.GetAdminToken)
|
||||
authRouterGroup.POST("/get_user_token", a.GetUserToken)
|
||||
authRouterGroup.POST("/parse_token", a.ParseToken)
|
||||
authRouterGroup.POST("/force_logout", a.ForceLogout)
|
||||
@@ -230,6 +236,8 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
|
||||
conversationGroup.POST("/get_full_conversation_ids", c.GetFullOwnerConversationIDs)
|
||||
conversationGroup.POST("/get_incremental_conversations", c.GetIncrementalConversation)
|
||||
conversationGroup.POST("/get_owner_conversation", c.GetOwnerConversation)
|
||||
conversationGroup.POST("/get_not_notify_conversation_ids", c.GetNotNotifyConversationIDs)
|
||||
conversationGroup.POST("/get_pinned_conversation_ids", c.GetPinnedConversationIDs)
|
||||
}
|
||||
|
||||
statisticsGroup := r.Group("/statistics")
|
||||
@@ -239,6 +247,11 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
|
||||
statisticsGroup.POST("/group/create", g.GroupCreateCount)
|
||||
statisticsGroup.POST("/group/active", m.GetActiveGroup)
|
||||
}
|
||||
|
||||
jssdk := r.Group("/jssdk")
|
||||
jssdk.POST("/get_conversations", j.GetConversations)
|
||||
jssdk.POST("/get_active_conversations", j.GetActiveConversations)
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
@@ -275,7 +288,6 @@ func GinParseToken(authRPC *rpcclient.Auth) gin.HandlerFunc {
|
||||
|
||||
// Whitelist api not parse token
|
||||
var Whitelist = []string{
|
||||
"/user/user_register",
|
||||
"/auth/user_token",
|
||||
"/auth/get_admin_token",
|
||||
"/auth/parse_token",
|
||||
}
|
||||
|
||||
@@ -107,14 +107,14 @@ func (u *UserApi) GetUsersOnlineStatus(c *gin.Context) {
|
||||
if v2.UserID == v1 {
|
||||
flag = true
|
||||
res.UserID = v1
|
||||
res.Status = constant.OnlineStatus
|
||||
res.Status = constant.Online
|
||||
res.DetailPlatformStatus = append(res.DetailPlatformStatus, v2.DetailPlatformStatus...)
|
||||
break
|
||||
}
|
||||
}
|
||||
if !flag {
|
||||
res.UserID = v1
|
||||
res.Status = constant.OfflineStatus
|
||||
res.Status = constant.Offline
|
||||
}
|
||||
respResult = append(respResult, res)
|
||||
}
|
||||
@@ -153,26 +153,26 @@ func (u *UserApi) GetUsersOnlineTokenDetail(c *gin.Context) {
|
||||
}
|
||||
|
||||
for _, v1 := range req.UserIDs {
|
||||
m := make(map[string][]string, 10)
|
||||
m := make(map[int32][]string, 10)
|
||||
flag = false
|
||||
temp := new(msggateway.SingleDetail)
|
||||
for _, v2 := range wsResult {
|
||||
if v2.UserID == v1 {
|
||||
flag = true
|
||||
temp.UserID = v1
|
||||
temp.Status = constant.OnlineStatus
|
||||
temp.Status = constant.Online
|
||||
for _, status := range v2.DetailPlatformStatus {
|
||||
if v, ok := m[status.Platform]; ok {
|
||||
m[status.Platform] = append(v, status.Token)
|
||||
if v, ok := m[status.PlatformID]; ok {
|
||||
m[status.PlatformID] = append(v, status.Token)
|
||||
} else {
|
||||
m[status.Platform] = []string{status.Token}
|
||||
m[status.PlatformID] = []string{status.Token}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
for p, tokens := range m {
|
||||
t := new(msggateway.SinglePlatformToken)
|
||||
t.Platform = p
|
||||
t.PlatformID = p
|
||||
t.Token = tokens
|
||||
t.Total = int32(len(tokens))
|
||||
temp.SinglePlatformToken = append(temp.SinglePlatformToken, t)
|
||||
|
||||
@@ -16,7 +16,9 @@ package msggateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/openimsdk/tools/mw"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
@@ -69,6 +71,8 @@ type Client struct {
|
||||
IsCompress bool `json:"isCompress"`
|
||||
UserID string `json:"userID"`
|
||||
IsBackground bool `json:"isBackground"`
|
||||
SDKType string `json:"sdkType"`
|
||||
Encoder Encoder
|
||||
ctx *UserConnContext
|
||||
longConnServer LongConnServer
|
||||
closed atomic.Bool
|
||||
@@ -94,11 +98,17 @@ func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer
|
||||
c.closed.Store(false)
|
||||
c.closedErr = nil
|
||||
c.token = ctx.GetToken()
|
||||
c.SDKType = ctx.GetSDKType()
|
||||
c.hbCtx, c.hbCancel = context.WithCancel(c.ctx)
|
||||
c.subLock = new(sync.Mutex)
|
||||
if c.subUserIDs != nil {
|
||||
clear(c.subUserIDs)
|
||||
}
|
||||
if c.SDKType == GoSDK {
|
||||
c.Encoder = NewGobEncoder()
|
||||
} else {
|
||||
c.Encoder = NewJsonEncoder()
|
||||
}
|
||||
c.subUserIDs = make(map[string]struct{})
|
||||
}
|
||||
|
||||
@@ -159,9 +169,12 @@ func (c *Client) readMessage() {
|
||||
return
|
||||
}
|
||||
case MessageText:
|
||||
c.closedErr = ErrNotSupportMessageProtocol
|
||||
return
|
||||
|
||||
_ = 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)
|
||||
@@ -188,7 +201,7 @@ func (c *Client) handleMessage(message []byte) error {
|
||||
var binaryReq = getReq()
|
||||
defer freeReq(binaryReq)
|
||||
|
||||
err := c.longConnServer.Decode(message, binaryReq)
|
||||
err := c.Encoder.Decode(message, binaryReq)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -335,7 +348,7 @@ func (c *Client) writeBinaryMsg(resp Resp) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
encodedBuf, err := c.longConnServer.Encode(resp)
|
||||
encodedBuf, err := c.Encoder.Encode(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -363,6 +376,11 @@ func (c *Client) writeBinaryMsg(resp Resp) error {
|
||||
func (c *Client) activeHeartbeat(ctx context.Context) {
|
||||
if c.PlatformID == constant.WebPlatformID {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
mw.PanicStackToLog(ctx, r)
|
||||
}
|
||||
}()
|
||||
log.ZDebug(ctx, "server initiative send heartbeat start.")
|
||||
ticker := time.NewTicker(pingPeriod)
|
||||
defer ticker.Stop()
|
||||
@@ -419,3 +437,28 @@ func (c *Client) writePongMsg(appData string) error {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,12 @@ const (
|
||||
GzipCompressionProtocol = "gzip"
|
||||
BackgroundStatus = "isBackground"
|
||||
SendResponse = "isMsgResp"
|
||||
SDKType = "sdkType"
|
||||
)
|
||||
|
||||
const (
|
||||
GoSDK = "go"
|
||||
JsSDK = "js"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -153,6 +153,14 @@ func (c *UserConnContext) GetCompression() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *UserConnContext) GetSDKType() string {
|
||||
sdkType := c.Req.URL.Query().Get(SDKType)
|
||||
if sdkType == "" {
|
||||
sdkType = GoSDK
|
||||
}
|
||||
return sdkType
|
||||
}
|
||||
|
||||
func (c *UserConnContext) ShouldSendResp() bool {
|
||||
errResp, exists := c.Query(SendResponse)
|
||||
if exists {
|
||||
@@ -193,7 +201,11 @@ func (c *UserConnContext) ParseEssentialArgs() error {
|
||||
_, 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
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package msggateway
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/openimsdk/tools/errs"
|
||||
)
|
||||
@@ -28,12 +29,12 @@ type Encoder interface {
|
||||
|
||||
type GobEncoder struct{}
|
||||
|
||||
func NewGobEncoder() *GobEncoder {
|
||||
return &GobEncoder{}
|
||||
func NewGobEncoder() Encoder {
|
||||
return GobEncoder{}
|
||||
}
|
||||
|
||||
func (g *GobEncoder) Encode(data any) ([]byte, error) {
|
||||
buff := bytes.Buffer{}
|
||||
func (g GobEncoder) Encode(data any) ([]byte, error) {
|
||||
var buff bytes.Buffer
|
||||
enc := gob.NewEncoder(&buff)
|
||||
if err := enc.Encode(data); err != nil {
|
||||
return nil, errs.WrapMsg(err, "GobEncoder.Encode failed", "action", "encode")
|
||||
@@ -41,7 +42,7 @@ func (g *GobEncoder) Encode(data any) ([]byte, error) {
|
||||
return buff.Bytes(), nil
|
||||
}
|
||||
|
||||
func (g *GobEncoder) Decode(encodeData []byte, decodeData any) error {
|
||||
func (g GobEncoder) Decode(encodeData []byte, decodeData any) error {
|
||||
buff := bytes.NewBuffer(encodeData)
|
||||
dec := gob.NewDecoder(buff)
|
||||
if err := dec.Decode(decodeData); err != nil {
|
||||
@@ -49,3 +50,25 @@ func (g *GobEncoder) Decode(encodeData []byte, decodeData any) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type JsonEncoder struct{}
|
||||
|
||||
func NewJsonEncoder() Encoder {
|
||||
return JsonEncoder{}
|
||||
}
|
||||
|
||||
func (g JsonEncoder) Encode(data any) ([]byte, error) {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, errs.New("JsonEncoder.Encode failed", "action", "encode")
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func (g JsonEncoder) Decode(encodeData []byte, decodeData any) error {
|
||||
err := json.Unmarshal(encodeData, decodeData)
|
||||
if err != nil {
|
||||
return errs.New("JsonEncoder.Decode failed", "action", "decode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@ package msggateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
|
||||
@@ -30,7 +32,6 @@ import (
|
||||
"github.com/openimsdk/tools/mq/memamq"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"google.golang.org/grpc"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
func (s *Server) InitServer(ctx context.Context, config *Config, disCov discovery.SvcDiscoveryRegistry, server *grpc.Server) error {
|
||||
@@ -82,17 +83,11 @@ func NewServer(rpcPort int, longConnServer LongConnServer, conf *Config, ready f
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Server) OnlinePushMsg(
|
||||
context context.Context,
|
||||
req *msggateway.OnlinePushMsgReq,
|
||||
) (*msggateway.OnlinePushMsgResp, error) {
|
||||
func (s *Server) OnlinePushMsg(context context.Context, req *msggateway.OnlinePushMsgReq) (*msggateway.OnlinePushMsgResp, error) {
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (s *Server) GetUsersOnlineStatus(
|
||||
ctx context.Context,
|
||||
req *msggateway.GetUsersOnlineStatusReq,
|
||||
) (*msggateway.GetUsersOnlineStatusResp, error) {
|
||||
func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUsersOnlineStatusReq) (*msggateway.GetUsersOnlineStatusResp, error) {
|
||||
if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("only app manager")
|
||||
}
|
||||
@@ -111,15 +106,14 @@ func (s *Server) GetUsersOnlineStatus(
|
||||
}
|
||||
|
||||
ps := new(msggateway.GetUsersOnlineStatusResp_SuccessDetail)
|
||||
ps.Platform = constant.PlatformIDToName(client.PlatformID)
|
||||
ps.Status = constant.OnlineStatus
|
||||
ps.PlatformID = int32(client.PlatformID)
|
||||
ps.ConnID = client.ctx.GetConnID()
|
||||
ps.Token = client.token
|
||||
ps.IsBackground = client.IsBackground
|
||||
uresp.Status = constant.OnlineStatus
|
||||
uresp.Status = constant.Online
|
||||
uresp.DetailPlatformStatus = append(uresp.DetailPlatformStatus, ps)
|
||||
}
|
||||
if uresp.Status == constant.OnlineStatus {
|
||||
if uresp.Status == constant.Online {
|
||||
resp.SuccessResult = append(resp.SuccessResult, uresp)
|
||||
}
|
||||
}
|
||||
@@ -155,6 +149,7 @@ func (s *Server) pushToUser(ctx context.Context, userID string, msgData *sdkws.M
|
||||
(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 {
|
||||
@@ -220,10 +215,7 @@ func (s *Server) SuperGroupOnlineBatchPushOneMsg(ctx context.Context, req *msgga
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) KickUserOffline(
|
||||
ctx context.Context,
|
||||
req *msggateway.KickUserOfflineReq,
|
||||
) (*msggateway.KickUserOfflineResp, error) {
|
||||
func (s *Server) KickUserOffline(ctx context.Context, req *msggateway.KickUserOfflineReq) (*msggateway.KickUserOfflineResp, error) {
|
||||
for _, v := range req.KickUserIDList {
|
||||
clients, _, ok := s.LongConnServer.GetUserPlatformCons(v, int(req.PlatformID))
|
||||
if !ok {
|
||||
|
||||
@@ -16,6 +16,7 @@ package msggateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"sync"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
@@ -31,6 +32,16 @@ import (
|
||||
"github.com/openimsdk/tools/utils/jsonutil"
|
||||
)
|
||||
|
||||
const (
|
||||
TextPing = "ping"
|
||||
TextPong = "pong"
|
||||
)
|
||||
|
||||
type TextMessage struct {
|
||||
Type string `json:"type"`
|
||||
Body json.RawMessage `json:"body"`
|
||||
}
|
||||
|
||||
type Req struct {
|
||||
ReqIdentifier int32 `json:"reqIdentifier" validate:"required"`
|
||||
Token string `json:"token"`
|
||||
|
||||
@@ -90,6 +90,19 @@ func (ws *WsServer) ChangeOnlineStatus(concurrent int) {
|
||||
if _, err := ws.userClient.Client.SetUserOnlineStatus(ctx, req); err != nil {
|
||||
log.ZError(ctx, "update user online status", err)
|
||||
}
|
||||
for _, ss := range req.Status {
|
||||
for _, online := range ss.Online {
|
||||
client, _, _ := ws.clients.Get(ss.UserID, int(online))
|
||||
back := false
|
||||
if len(client) > 0 {
|
||||
back = client[0].IsBackground
|
||||
}
|
||||
ws.webhookAfterUserOnline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOnline, ss.UserID, int(online), back, ss.ConnID)
|
||||
}
|
||||
for _, offline := range ss.Offline {
|
||||
ws.webhookAfterUserOffline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOffline, ss.UserID, int(offline), ss.ConnID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < concurrent; i++ {
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// 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 (
|
||||
@@ -51,7 +37,6 @@ type LongConnServer interface {
|
||||
SetKickHandlerInfo(i *kickHandler)
|
||||
SubUserOnlineStatus(ctx context.Context, client *Client, data *Req) ([]byte, error)
|
||||
Compressor
|
||||
Encoder
|
||||
MessageHandler
|
||||
}
|
||||
|
||||
@@ -75,7 +60,7 @@ type WsServer struct {
|
||||
authClient *rpcclient.Auth
|
||||
disCov discovery.SvcDiscoveryRegistry
|
||||
Compressor
|
||||
Encoder
|
||||
//Encoder
|
||||
MessageHandler
|
||||
webhookClient *webhook.Client
|
||||
}
|
||||
@@ -149,7 +134,6 @@ func NewWsServer(msgGatewayConfig *Config, opts ...Option) *WsServer {
|
||||
clients: newUserMap(),
|
||||
subscription: newSubscription(),
|
||||
Compressor: NewGzipCompressor(),
|
||||
Encoder: NewGobEncoder(),
|
||||
webhookClient: webhook.NewWebhookClient(msgGatewayConfig.WebhooksConfig.URL),
|
||||
}
|
||||
}
|
||||
@@ -212,7 +196,6 @@ func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *C
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
wg := errgroup.Group{}
|
||||
wg.SetLimit(concurrentRequest)
|
||||
|
||||
@@ -293,14 +276,7 @@ func (ws *WsServer) registerClient(client *Client) {
|
||||
|
||||
wg.Wait()
|
||||
|
||||
log.ZDebug(
|
||||
client.ctx,
|
||||
"user online",
|
||||
"online user Num",
|
||||
ws.onlineUserNum.Load(),
|
||||
"online user conn Num",
|
||||
ws.onlineUserConnNum.Load(),
|
||||
)
|
||||
log.ZDebug(client.ctx, "user online", "online user Num", ws.onlineUserNum.Load(), "online user conn Num", ws.onlineUserConnNum.Load())
|
||||
}
|
||||
|
||||
func getRemoteAdders(client []*Client) string {
|
||||
@@ -321,7 +297,26 @@ func (ws *WsServer) KickUserConn(client *Client) error {
|
||||
}
|
||||
|
||||
func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Client, newClient *Client) {
|
||||
switch ws.msgGatewayConfig.MsgGateway.MultiLoginPolicy {
|
||||
kickTokenFunc := func(kickClients []*Client) {
|
||||
var kickTokens []string
|
||||
ws.clients.DeleteClients(newClient.UserID, kickClients)
|
||||
for _, c := range kickClients {
|
||||
kickTokens = append(kickTokens, c.token)
|
||||
err := c.KickOnlineMessage()
|
||||
if err != nil {
|
||||
log.ZWarn(c.ctx, "KickOnlineMessage", err)
|
||||
}
|
||||
}
|
||||
ctx := mcontext.WithMustInfoCtx(
|
||||
[]string{newClient.ctx.GetOperationID(), newClient.ctx.GetUserID(),
|
||||
constant.PlatformIDToName(newClient.PlatformID), newClient.ctx.GetConnID()},
|
||||
)
|
||||
if _, err := ws.authClient.KickTokens(ctx, kickTokens); err != nil {
|
||||
log.ZWarn(newClient.ctx, "kickTokens err", err)
|
||||
}
|
||||
}
|
||||
|
||||
switch ws.msgGatewayConfig.Share.MultiLogin.Policy {
|
||||
case constant.DefalutNotKick:
|
||||
case constant.PCAndOther:
|
||||
if constant.PlatformIDToClass(newClient.PlatformID) == constant.TerminalPC {
|
||||
@@ -347,6 +342,20 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
|
||||
log.ZWarn(newClient.ctx, "InvalidateToken err", err, "userID", newClient.UserID,
|
||||
"platformID", newClient.PlatformID)
|
||||
}
|
||||
case constant.AllLoginButSameClassKick:
|
||||
clients, ok := ws.clients.GetAll(newClient.UserID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var (
|
||||
kickClients []*Client
|
||||
)
|
||||
for _, client := range clients {
|
||||
if constant.PlatformIDToClass(client.PlatformID) == constant.PlatformIDToClass(newClient.PlatformID) {
|
||||
kickClients = append(kickClients, client)
|
||||
}
|
||||
}
|
||||
kickTokenFunc(kickClients)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -128,6 +128,7 @@ func (m *MsgTransfer) Start(index int, config *Config) error {
|
||||
|
||||
go m.historyCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyCH)
|
||||
go m.historyMongoCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyMongoCH)
|
||||
go m.historyCH.HandleUserHasReadSeqMessages(m.ctx)
|
||||
err := m.historyCH.redisMessageBatches.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -135,6 +136,11 @@ func (m *MsgTransfer) Start(index int, config *Config) error {
|
||||
|
||||
if config.MsgTransfer.Prometheus.Enable {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
mw.PanicStackToLog(m.ctx, r)
|
||||
}
|
||||
}()
|
||||
prometheusPort, err := datautil.GetElemByIndex(config.MsgTransfer.Prometheus.Ports, index)
|
||||
if err != nil {
|
||||
netErr = err
|
||||
@@ -157,12 +163,14 @@ func (m *MsgTransfer) Start(index int, config *Config) error {
|
||||
// graceful close kafka client.
|
||||
m.cancel()
|
||||
m.historyCH.redisMessageBatches.Close()
|
||||
m.historyCH.Close()
|
||||
m.historyCH.historyConsumerGroup.Close()
|
||||
m.historyMongoCH.historyConsumerGroup.Close()
|
||||
return nil
|
||||
case <-netDone:
|
||||
m.cancel()
|
||||
m.historyCH.redisMessageBatches.Close()
|
||||
m.historyCH.Close()
|
||||
m.historyCH.historyConsumerGroup.Close()
|
||||
m.historyMongoCH.historyConsumerGroup.Close()
|
||||
close(netDone)
|
||||
|
||||
@@ -18,8 +18,11 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
"github.com/openimsdk/tools/mw"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
@@ -40,11 +43,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
size = 500
|
||||
mainDataBuffer = 500
|
||||
subChanBuffer = 50
|
||||
worker = 50
|
||||
interval = 100 * time.Millisecond
|
||||
size = 500
|
||||
mainDataBuffer = 500
|
||||
subChanBuffer = 50
|
||||
worker = 50
|
||||
interval = 100 * time.Millisecond
|
||||
hasReadChanBuffer = 1000
|
||||
)
|
||||
|
||||
type ContextMsg struct {
|
||||
@@ -52,14 +56,23 @@ type ContextMsg struct {
|
||||
ctx context.Context
|
||||
}
|
||||
|
||||
// This structure is used for asynchronously writing the sender’s read sequence (seq) regarding a message into MongoDB.
|
||||
// For example, if the sender sends a message with a seq of 10, then their own read seq for this conversation should be set to 10.
|
||||
type userHasReadSeq struct {
|
||||
conversationID string
|
||||
userHasReadMap map[string]int64
|
||||
}
|
||||
|
||||
type OnlineHistoryRedisConsumerHandler struct {
|
||||
historyConsumerGroup *kafka.MConsumerGroup
|
||||
|
||||
redisMessageBatches *batcher.Batcher[sarama.ConsumerMessage]
|
||||
|
||||
msgTransferDatabase controller.MsgTransferDatabase
|
||||
conversationRpcClient *rpcclient.ConversationRpcClient
|
||||
groupRpcClient *rpcclient.GroupRpcClient
|
||||
msgTransferDatabase controller.MsgTransferDatabase
|
||||
conversationRpcClient *rpcclient.ConversationRpcClient
|
||||
groupRpcClient *rpcclient.GroupRpcClient
|
||||
conversationUserHasReadChan chan *userHasReadSeq
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewOnlineHistoryRedisConsumerHandler(kafkaConf *config.Kafka, database controller.MsgTransferDatabase,
|
||||
@@ -70,6 +83,8 @@ func NewOnlineHistoryRedisConsumerHandler(kafkaConf *config.Kafka, database cont
|
||||
}
|
||||
var och OnlineHistoryRedisConsumerHandler
|
||||
och.msgTransferDatabase = database
|
||||
och.conversationUserHasReadChan = make(chan *userHasReadSeq, hasReadChanBuffer)
|
||||
och.wg.Add(1)
|
||||
|
||||
b := batcher.New[sarama.ConsumerMessage](
|
||||
batcher.WithSize(size),
|
||||
@@ -115,25 +130,25 @@ func (och *OnlineHistoryRedisConsumerHandler) do(ctx context.Context, channelID
|
||||
}
|
||||
|
||||
func (och *OnlineHistoryRedisConsumerHandler) doSetReadSeq(ctx context.Context, msgs []*ContextMsg) {
|
||||
type seqKey struct {
|
||||
conversationID string
|
||||
userID string
|
||||
}
|
||||
var readSeq map[seqKey]int64
|
||||
|
||||
var conversationID string
|
||||
var userSeqMap map[string]int64
|
||||
for _, msg := range msgs {
|
||||
if msg.message.ContentType != constant.HasReadReceipt {
|
||||
continue
|
||||
}
|
||||
var elem sdkws.NotificationElem
|
||||
if err := json.Unmarshal(msg.message.Content, &elem); err != nil {
|
||||
log.ZError(ctx, "handlerConversationRead Unmarshal NotificationElem msg err", err, "msg", msg)
|
||||
log.ZWarn(ctx, "handlerConversationRead Unmarshal NotificationElem msg err", err, "msg", msg)
|
||||
continue
|
||||
}
|
||||
var tips sdkws.MarkAsReadTips
|
||||
if err := json.Unmarshal([]byte(elem.Detail), &tips); err != nil {
|
||||
log.ZError(ctx, "handlerConversationRead Unmarshal MarkAsReadTips msg err", err, "msg", msg)
|
||||
log.ZWarn(ctx, "handlerConversationRead Unmarshal MarkAsReadTips msg err", err, "msg", msg)
|
||||
continue
|
||||
}
|
||||
//The conversation ID for each batch of messages processed by the batcher is the same.
|
||||
conversationID = tips.ConversationID
|
||||
if len(tips.Seqs) > 0 {
|
||||
for _, seq := range tips.Seqs {
|
||||
if tips.HasReadSeq < seq {
|
||||
@@ -146,26 +161,25 @@ func (och *OnlineHistoryRedisConsumerHandler) doSetReadSeq(ctx context.Context,
|
||||
if tips.HasReadSeq < 0 {
|
||||
continue
|
||||
}
|
||||
if readSeq == nil {
|
||||
readSeq = make(map[seqKey]int64)
|
||||
if userSeqMap == nil {
|
||||
userSeqMap = make(map[string]int64)
|
||||
}
|
||||
key := seqKey{
|
||||
conversationID: tips.ConversationID,
|
||||
userID: tips.MarkAsReadUserID,
|
||||
}
|
||||
if readSeq[key] > tips.HasReadSeq {
|
||||
|
||||
if userSeqMap[tips.MarkAsReadUserID] > tips.HasReadSeq {
|
||||
continue
|
||||
}
|
||||
readSeq[key] = tips.HasReadSeq
|
||||
userSeqMap[tips.MarkAsReadUserID] = tips.HasReadSeq
|
||||
}
|
||||
if readSeq == nil {
|
||||
if userSeqMap == nil {
|
||||
return
|
||||
}
|
||||
for key, seq := range readSeq {
|
||||
if err := och.msgTransferDatabase.SetHasReadSeqToDB(ctx, key.userID, key.conversationID, seq); err != nil {
|
||||
log.ZError(ctx, "set read seq to db error", err, "userID", key.userID, "conversationID", key.conversationID, "seq", seq)
|
||||
}
|
||||
if len(conversationID) == 0 {
|
||||
log.ZWarn(ctx, "conversation err", nil, "conversationID", conversationID)
|
||||
}
|
||||
if err := och.msgTransferDatabase.SetHasReadSeqToDB(ctx, conversationID, userSeqMap); err != nil {
|
||||
log.ZWarn(ctx, "set read seq to db error", err, "conversationID", conversationID, "userSeqMap", userSeqMap)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (och *OnlineHistoryRedisConsumerHandler) parseConsumerMessages(ctx context.Context, consumerMessages []*sarama.ConsumerMessage) []*ContextMsg {
|
||||
@@ -250,12 +264,21 @@ func (och *OnlineHistoryRedisConsumerHandler) handleMsg(ctx context.Context, key
|
||||
}
|
||||
if len(storageMessageList) > 0 {
|
||||
msg := storageMessageList[0]
|
||||
lastSeq, isNewConversation, err := och.msgTransferDatabase.BatchInsertChat2Cache(ctx, conversationID, storageMessageList)
|
||||
lastSeq, isNewConversation, userSeqMap, err := och.msgTransferDatabase.BatchInsertChat2Cache(ctx, conversationID, storageMessageList)
|
||||
if err != nil && !errors.Is(errs.Unwrap(err), redis.Nil) {
|
||||
log.ZError(ctx, "batch data insert to redis err", err, "storageMsgList", storageMessageList)
|
||||
log.ZWarn(ctx, "batch data insert to redis err", err, "storageMsgList", storageMessageList)
|
||||
return
|
||||
}
|
||||
log.ZInfo(ctx, "BatchInsertChat2Cache end")
|
||||
err = och.msgTransferDatabase.SetHasReadSeqs(ctx, conversationID, userSeqMap)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "SetHasReadSeqs error", err, "userSeqMap", userSeqMap, "conversationID", conversationID)
|
||||
prommetrics.SeqSetFailedCounter.Inc()
|
||||
}
|
||||
och.conversationUserHasReadChan <- &userHasReadSeq{
|
||||
conversationID: conversationID,
|
||||
userHasReadMap: userSeqMap,
|
||||
}
|
||||
|
||||
if isNewConversation {
|
||||
switch msg.SessionType {
|
||||
@@ -308,7 +331,7 @@ func (och *OnlineHistoryRedisConsumerHandler) handleNotification(ctx context.Con
|
||||
storageMessageList = append(storageMessageList, msg.message)
|
||||
}
|
||||
if len(storageMessageList) > 0 {
|
||||
lastSeq, _, err := och.msgTransferDatabase.BatchInsertChat2Cache(ctx, conversationID, storageMessageList)
|
||||
lastSeq, _, _, err := och.msgTransferDatabase.BatchInsertChat2Cache(ctx, conversationID, storageMessageList)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "notification batch insert to redis error", err, "conversationID", conversationID,
|
||||
"storageList", storageMessageList)
|
||||
@@ -323,6 +346,27 @@ func (och *OnlineHistoryRedisConsumerHandler) handleNotification(ctx context.Con
|
||||
och.toPushTopic(ctx, key, conversationID, storageList)
|
||||
}
|
||||
}
|
||||
func (och *OnlineHistoryRedisConsumerHandler) HandleUserHasReadSeqMessages(ctx context.Context) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
mw.PanicStackToLog(ctx, r)
|
||||
}
|
||||
}()
|
||||
|
||||
defer och.wg.Done()
|
||||
|
||||
for msg := range och.conversationUserHasReadChan {
|
||||
if err := och.msgTransferDatabase.SetHasReadSeqToDB(ctx, msg.conversationID, msg.userHasReadMap); err != nil {
|
||||
log.ZWarn(ctx, "set read seq to db error", err, "conversationID", msg.conversationID, "userSeqMap", msg.userHasReadMap)
|
||||
}
|
||||
}
|
||||
|
||||
log.ZInfo(ctx, "Channel closed, exiting handleUserHasReadSeqMessages")
|
||||
}
|
||||
func (och *OnlineHistoryRedisConsumerHandler) Close() {
|
||||
close(och.conversationUserHasReadChan)
|
||||
och.wg.Wait()
|
||||
}
|
||||
|
||||
func (och *OnlineHistoryRedisConsumerHandler) toPushTopic(ctx context.Context, key, conversationID string, msgs []*ContextMsg) {
|
||||
for _, v := range msgs {
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package push
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestName(t *testing.T) {
|
||||
var c ConsumerHandler
|
||||
c.readCh = make(chan *sdkws.MarkAsReadTips)
|
||||
|
||||
go c.loopRead()
|
||||
|
||||
go func() {
|
||||
for i := 0; ; i++ {
|
||||
seq := int64(i + 1)
|
||||
if seq%3 == 0 {
|
||||
seq = 1
|
||||
}
|
||||
c.readCh <- &sdkws.MarkAsReadTips{
|
||||
ConversationID: "c100",
|
||||
MarkAsReadUserID: "u100",
|
||||
HasReadSeq: seq,
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
select {}
|
||||
}
|
||||
@@ -24,7 +24,6 @@ import (
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
)
|
||||
|
||||
func (c *ConsumerHandler) webhookBeforeOfflinePush(ctx context.Context, before *config.BeforeConfig, userIDs []string, msg *sdkws.MsgData, offlinePushUserIDs *[]string) error {
|
||||
@@ -70,7 +69,7 @@ func (c *ConsumerHandler) webhookBeforeOfflinePush(ctx context.Context, before *
|
||||
|
||||
func (c *ConsumerHandler) webhookBeforeOnlinePush(ctx context.Context, before *config.BeforeConfig, userIDs []string, msg *sdkws.MsgData) error {
|
||||
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
|
||||
if datautil.Contain(msg.SendID, userIDs...) || msg.ContentType == constant.Typing {
|
||||
if msg.ContentType == constant.Typing {
|
||||
return nil
|
||||
}
|
||||
req := callbackstruct.CallbackBeforePushReq{
|
||||
|
||||
@@ -29,5 +29,6 @@ type Dummy struct {
|
||||
|
||||
func (d *Dummy) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error {
|
||||
log.ZDebug(ctx, "dummy push")
|
||||
log.ZWarn(ctx, "Dummy push", nil, "ps", "The offline push is not configured. To configure it, please go to config/openim-push.yml.")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
firebase "firebase.google.com/go"
|
||||
"firebase.google.com/go/messaging"
|
||||
firebase "firebase.google.com/go/v4"
|
||||
"firebase.google.com/go/v4/messaging"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
@@ -99,7 +99,7 @@ func (f *Fcm) Push(ctx context.Context, userIDs []string, title, content string,
|
||||
apns := &messaging.APNSConfig{Payload: &messaging.APNSPayload{Aps: &messaging.Aps{Sound: opts.IOSPushSound}}}
|
||||
messageCount := len(messages)
|
||||
if messageCount >= SinglePushCountLimit {
|
||||
response, err := f.fcmMsgCli.SendAll(ctx, messages)
|
||||
response, err := f.fcmMsgCli.SendEach(ctx, messages)
|
||||
if err != nil {
|
||||
Fail = Fail + messageCount
|
||||
// Record push error
|
||||
@@ -154,7 +154,7 @@ func (f *Fcm) Push(ctx context.Context, userIDs []string, title, content string,
|
||||
}
|
||||
messageCount := len(messages)
|
||||
if messageCount > 0 {
|
||||
response, err := f.fcmMsgCli.SendAll(ctx, messages)
|
||||
response, err := f.fcmMsgCli.SendEach(ctx, messages)
|
||||
if err != nil {
|
||||
Fail = Fail + messageCount
|
||||
} else {
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package body
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
)
|
||||
|
||||
@@ -26,38 +27,44 @@ type Notification struct {
|
||||
|
||||
type Android struct {
|
||||
Alert string `json:"alert,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Intent struct {
|
||||
URL string `json:"url,omitempty"`
|
||||
} `json:"intent,omitempty"`
|
||||
Extras Extras `json:"extras"`
|
||||
Extras map[string]string `json:"extras,omitempty"`
|
||||
}
|
||||
type Ios struct {
|
||||
Alert string `json:"alert,omitempty"`
|
||||
Sound string `json:"sound,omitempty"`
|
||||
Badge string `json:"badge,omitempty"`
|
||||
Extras Extras `json:"extras"`
|
||||
MutableContent bool `json:"mutable-content"`
|
||||
Alert IosAlert `json:"alert,omitempty"`
|
||||
Sound string `json:"sound,omitempty"`
|
||||
Badge string `json:"badge,omitempty"`
|
||||
Extras map[string]string `json:"extras,omitempty"`
|
||||
MutableContent bool `json:"mutable-content"`
|
||||
}
|
||||
|
||||
type Extras struct {
|
||||
ClientMsgID string `json:"clientMsgID"`
|
||||
type IosAlert struct {
|
||||
Title string `json:"title,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Notification) SetAlert(alert string) {
|
||||
func (n *Notification) SetAlert(alert string, title string, opts *options.Opts) {
|
||||
n.Alert = alert
|
||||
n.Android.Alert = alert
|
||||
n.IOS.Alert = alert
|
||||
n.IOS.Sound = "default"
|
||||
n.IOS.Badge = "+1"
|
||||
n.Android.Title = title
|
||||
n.IOS.Alert.Body = alert
|
||||
n.IOS.Alert.Title = title
|
||||
n.IOS.Sound = opts.IOSPushSound
|
||||
if opts.IOSBadgeCount {
|
||||
n.IOS.Badge = "+1"
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Notification) SetExtras(extras Extras) {
|
||||
func (n *Notification) SetExtras(extras map[string]string) {
|
||||
n.IOS.Extras = extras
|
||||
n.Android.Extras = extras
|
||||
}
|
||||
|
||||
func (n *Notification) SetAndroidIntent(pushConf *config.Push) {
|
||||
n.Android.Intent.URL = pushConf.JPNS.PushIntent
|
||||
n.Android.Intent.URL = pushConf.JPush.PushIntent
|
||||
}
|
||||
|
||||
func (n *Notification) IOSEnableMutableContent() {
|
||||
|
||||
@@ -18,9 +18,9 @@ import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/jpush/body"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/tools/utils/httputil"
|
||||
)
|
||||
@@ -57,17 +57,23 @@ func (j *JPush) Push(ctx context.Context, userIDs []string, title, content strin
|
||||
var au body.Audience
|
||||
au.SetAlias(userIDs)
|
||||
var no body.Notification
|
||||
var extras body.Extras
|
||||
extras := make(map[string]string)
|
||||
extras["ex"] = opts.Ex
|
||||
if opts.Signal.ClientMsgID != "" {
|
||||
extras.ClientMsgID = opts.Signal.ClientMsgID
|
||||
extras["ClientMsgID"] = opts.Signal.ClientMsgID
|
||||
}
|
||||
no.IOSEnableMutableContent()
|
||||
no.SetExtras(extras)
|
||||
no.SetAlert(title)
|
||||
no.SetAlert(content, title, opts)
|
||||
no.SetAndroidIntent(j.pushConf)
|
||||
|
||||
var msg body.Message
|
||||
msg.SetMsgContent(content)
|
||||
msg.SetTitle(title)
|
||||
if opts.Signal.ClientMsgID != "" {
|
||||
msg.SetExtras("ClientMsgID", opts.Signal.ClientMsgID)
|
||||
}
|
||||
msg.SetExtras("ex", opts.Ex)
|
||||
var opt body.Options
|
||||
opt.SetApnsProduction(j.pushConf.IOSPush.Production)
|
||||
var pushObj body.PushObj
|
||||
@@ -76,19 +82,26 @@ func (j *JPush) Push(ctx context.Context, userIDs []string, title, content strin
|
||||
pushObj.SetNotification(&no)
|
||||
pushObj.SetMessage(&msg)
|
||||
pushObj.SetOptions(&opt)
|
||||
var resp any
|
||||
return j.request(ctx, pushObj, resp, 5)
|
||||
var resp map[string]any
|
||||
return j.request(ctx, pushObj, &resp, 5)
|
||||
}
|
||||
|
||||
func (j *JPush) request(ctx context.Context, po body.PushObj, resp any, timeout int) error {
|
||||
return j.httpClient.PostReturn(
|
||||
func (j *JPush) request(ctx context.Context, po body.PushObj, resp *map[string]any, timeout int) error {
|
||||
err := j.httpClient.PostReturn(
|
||||
ctx,
|
||||
j.pushConf.JPNS.PushURL,
|
||||
j.pushConf.JPush.PushURL,
|
||||
map[string]string{
|
||||
"Authorization": j.getAuthorization(j.pushConf.JPNS.AppKey, j.pushConf.JPNS.MasterSecret),
|
||||
"Authorization": j.getAuthorization(j.pushConf.JPush.AppKey, j.pushConf.JPush.MasterSecret),
|
||||
},
|
||||
po,
|
||||
resp,
|
||||
timeout,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (*resp)["sendno"] != "0" {
|
||||
return fmt.Errorf("jpush push failed %v", resp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -23,10 +23,11 @@ import (
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
geTUI = "geTui"
|
||||
geTUI = "getui"
|
||||
firebase = "fcm"
|
||||
jPush = "jpush"
|
||||
)
|
||||
@@ -38,6 +39,7 @@ type OfflinePusher interface {
|
||||
|
||||
func NewOfflinePusher(pushConf *config.Push, cache cache.ThirdCache, fcmConfigPath string) (OfflinePusher, error) {
|
||||
var offlinePusher OfflinePusher
|
||||
pushConf.Enable = strings.ToLower(pushConf.Enable)
|
||||
switch pushConf.Enable {
|
||||
case geTUI:
|
||||
offlinePusher = getui.NewClient(pushConf, cache)
|
||||
|
||||
@@ -55,6 +55,9 @@ func (o *OfflinePushConsumerHandler) handleMsg2OfflinePush(ctx context.Context,
|
||||
log.ZError(ctx, "offline push msg is empty", errs.New("offlinePushMsg is empty"), "userIDs", offlinePushMsg.UserIDs, "msg", offlinePushMsg.MsgData)
|
||||
return
|
||||
}
|
||||
if offlinePushMsg.MsgData.Status == constant.MsgStatusSending {
|
||||
offlinePushMsg.MsgData.Status = constant.MsgStatusSendSuccess
|
||||
}
|
||||
log.ZInfo(ctx, "receive to OfflinePush MQ", "userIDs", offlinePushMsg.UserIDs, "msg", offlinePushMsg.MsgData)
|
||||
|
||||
err := o.offlinePushMsg(ctx, offlinePushMsg.MsgData, offlinePushMsg.UserIDs)
|
||||
@@ -70,7 +73,7 @@ func (o *OfflinePushConsumerHandler) getOfflinePushInfos(msg *sdkws.MsgData) (ti
|
||||
IsAtSelf bool `json:"isAtSelf"`
|
||||
}
|
||||
|
||||
opts = &options.Opts{Signal: &options.Signal{}}
|
||||
opts = &options.Opts{Signal: &options.Signal{ClientMsgID: msg.ClientMsgID}}
|
||||
if msg.OfflinePushInfo != nil {
|
||||
opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount
|
||||
opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound
|
||||
|
||||
@@ -4,6 +4,10 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
|
||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
||||
@@ -27,9 +31,6 @@ import (
|
||||
"github.com/openimsdk/tools/utils/timeutil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
type ConsumerHandler struct {
|
||||
@@ -165,17 +166,21 @@ func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *
|
||||
return nil
|
||||
}
|
||||
}
|
||||
offlinePushUserID := []string{msg.RecvID}
|
||||
needOfflinePushUserID := []string{msg.RecvID}
|
||||
var offlinePushUserID []string
|
||||
|
||||
//receiver offline push
|
||||
if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush,
|
||||
offlinePushUserID, msg, nil); err != nil {
|
||||
if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, needOfflinePushUserID, msg, &offlinePushUserID); err != nil {
|
||||
return err
|
||||
}
|
||||
log.ZInfo(ctx, "webhookBeforeOfflinePush end")
|
||||
err = c.offlinePushMsg(ctx, msg, offlinePushUserID)
|
||||
|
||||
if len(offlinePushUserID) > 0 {
|
||||
needOfflinePushUserID = offlinePushUserID
|
||||
}
|
||||
err = c.offlinePushMsg(ctx, msg, needOfflinePushUserID)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "offlinePushMsg failed", err, "offlinePushUserID", offlinePushUserID, "msg", msg)
|
||||
log.ZWarn(ctx, "offlinePushMsg failed", err, "needOfflinePushUserID", needOfflinePushUserID, "msg", msg)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -194,6 +199,9 @@ func (c *ConsumerHandler) shouldPushOffline(_ context.Context, msg *sdkws.MsgDat
|
||||
}
|
||||
|
||||
func (c *ConsumerHandler) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) ([]*msggateway.SingleMsgToUserResults, error) {
|
||||
if msg != nil && msg.Status == constant.MsgStatusSending {
|
||||
msg.Status = constant.MsgStatusSendSuccess
|
||||
}
|
||||
onlineUserIDs, offlineUserIDs, err := c.onlineCache.GetUsersOnline(ctx, pushToUserIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -332,6 +340,7 @@ func (c *ConsumerHandler) groupMessagesHandler(ctx context.Context, groupID stri
|
||||
func (c *ConsumerHandler) offlinePushMsg(ctx context.Context, msg *sdkws.MsgData, offlinePushUserIDs []string) error {
|
||||
title, content, opts, err := c.getOfflinePushInfos(msg)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "getOfflinePushInfos failed", err, "msg", msg)
|
||||
return err
|
||||
}
|
||||
err = c.offlinePusher.Push(ctx, offlinePushUserIDs, title, content, opts)
|
||||
@@ -361,7 +370,7 @@ func (c *ConsumerHandler) getOfflinePushInfos(msg *sdkws.MsgData) (title, conten
|
||||
IsAtSelf bool `json:"isAtSelf"`
|
||||
}
|
||||
|
||||
opts = &options.Opts{Signal: &options.Signal{}}
|
||||
opts = &options.Opts{Signal: &options.Signal{ClientMsgID: msg.ClientMsgID}}
|
||||
if msg.OfflinePushInfo != nil {
|
||||
opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount
|
||||
opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound
|
||||
|
||||
@@ -16,10 +16,12 @@ package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
||||
"github.com/openimsdk/tools/db/redisutil"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
@@ -64,24 +66,34 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
||||
redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire),
|
||||
config.Share.Secret,
|
||||
config.RpcConfig.TokenPolicy.Expire,
|
||||
config.Share.MultiLogin,
|
||||
config.Share.IMAdminUserID,
|
||||
),
|
||||
config: config,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *authServer) UserToken(ctx context.Context, req *pbauth.UserTokenReq) (*pbauth.UserTokenResp, error) {
|
||||
resp := pbauth.UserTokenResp{}
|
||||
func (s *authServer) GetAdminToken(ctx context.Context, req *pbauth.GetAdminTokenReq) (*pbauth.GetAdminTokenResp, error) {
|
||||
resp := pbauth.GetAdminTokenResp{}
|
||||
if req.Secret != s.config.Share.Secret {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("secret invalid")
|
||||
}
|
||||
|
||||
if !datautil.Contain(req.UserID, s.config.Share.IMAdminUserID...) {
|
||||
return nil, errs.ErrArgs.WrapMsg("userID is error.", "userID", req.UserID, "adminUserID", s.config.Share.IMAdminUserID)
|
||||
|
||||
}
|
||||
|
||||
if _, err := s.userRpcClient.GetUserInfo(ctx, req.UserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token, err := s.authDatabase.CreateToken(ctx, req.UserID, int(req.PlatformID))
|
||||
|
||||
token, err := s.authDatabase.CreateToken(ctx, req.UserID, int(constant.AdminPlatformID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
prommetrics.UserLoginCounter.Inc()
|
||||
resp.Token = token
|
||||
resp.ExpireTimeSeconds = s.config.RpcConfig.TokenPolicy.Expire * 24 * 60 * 60
|
||||
@@ -92,6 +104,11 @@ func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenR
|
||||
if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.PlatformID == constant.AdminPlatformID {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("platformID invalid. platformID must not be adminPlatformID")
|
||||
}
|
||||
|
||||
resp := pbauth.GetUserTokenResp{}
|
||||
|
||||
if authverify.IsManagerUserID(req.UserID, s.config.Share.IMAdminUserID) {
|
||||
@@ -114,6 +131,10 @@ func (s *authServer) parseToken(ctx context.Context, tokensString string) (claim
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
isAdmin := authverify.IsManagerUserID(claims.UserID, s.config.Share.IMAdminUserID)
|
||||
if isAdmin {
|
||||
return claims, nil
|
||||
}
|
||||
m, err := s.authDatabase.GetTokensWithoutError(ctx, claims.UserID, claims.PlatformID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -175,7 +196,7 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID
|
||||
}
|
||||
|
||||
m, err := s.authDatabase.GetTokensWithoutError(ctx, userID, int(platformID))
|
||||
if err != nil && err != redis.Nil {
|
||||
if err != nil && errors.Is(err, redis.Nil) {
|
||||
return err
|
||||
}
|
||||
for k := range m {
|
||||
@@ -193,7 +214,7 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID
|
||||
|
||||
func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.InvalidateTokenReq) (*pbauth.InvalidateTokenResp, error) {
|
||||
m, err := s.authDatabase.GetTokensWithoutError(ctx, req.UserID, int(req.PlatformID))
|
||||
if err != nil && err != redis.Nil {
|
||||
if err != nil && errors.Is(err, redis.Nil) {
|
||||
return nil, err
|
||||
}
|
||||
if m == nil {
|
||||
@@ -215,3 +236,10 @@ func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.Invalidate
|
||||
}
|
||||
return &pbauth.InvalidateTokenResp{}, nil
|
||||
}
|
||||
|
||||
func (s *authServer) KickTokens(ctx context.Context, req *pbauth.KickTokensReq) (*pbauth.KickTokensResp, error) {
|
||||
if err := s.authDatabase.BatchSetTokenMapByUidPid(ctx, req.Tokens); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbauth.KickTokensResp{}, nil
|
||||
}
|
||||
|
||||
+26
-2
@@ -261,27 +261,35 @@ func (c *conversationServer) SetConversations(ctx context.Context, req *pbconver
|
||||
|
||||
setConversationFieldsFunc := func() {
|
||||
if req.Conversation.RecvMsgOpt != nil {
|
||||
conversation.RecvMsgOpt = req.Conversation.RecvMsgOpt.Value
|
||||
m["recv_msg_opt"] = req.Conversation.RecvMsgOpt.Value
|
||||
}
|
||||
if req.Conversation.AttachedInfo != nil {
|
||||
conversation.AttachedInfo = req.Conversation.AttachedInfo.Value
|
||||
m["attached_info"] = req.Conversation.AttachedInfo.Value
|
||||
}
|
||||
if req.Conversation.Ex != nil {
|
||||
conversation.Ex = req.Conversation.Ex.Value
|
||||
m["ex"] = req.Conversation.Ex.Value
|
||||
}
|
||||
if req.Conversation.IsPinned != nil {
|
||||
conversation.IsPinned = req.Conversation.IsPinned.Value
|
||||
m["is_pinned"] = req.Conversation.IsPinned.Value
|
||||
}
|
||||
if req.Conversation.GroupAtType != nil {
|
||||
conversation.GroupAtType = req.Conversation.GroupAtType.Value
|
||||
m["group_at_type"] = req.Conversation.GroupAtType.Value
|
||||
}
|
||||
if req.Conversation.MsgDestructTime != nil {
|
||||
conversation.MsgDestructTime = req.Conversation.MsgDestructTime.Value
|
||||
m["msg_destruct_time"] = req.Conversation.MsgDestructTime.Value
|
||||
}
|
||||
if req.Conversation.MsgDestructTime != nil {
|
||||
m["msg_destruct_time"] = req.Conversation.MsgDestructTime.Value
|
||||
if req.Conversation.IsMsgDestruct != nil {
|
||||
conversation.IsMsgDestruct = req.Conversation.IsMsgDestruct.Value
|
||||
m["is_msg_destruct"] = req.Conversation.IsMsgDestruct.Value
|
||||
}
|
||||
if req.Conversation.BurnDuration != nil {
|
||||
conversation.BurnDuration = req.Conversation.BurnDuration.Value
|
||||
m["burn_duration"] = req.Conversation.BurnDuration.Value
|
||||
}
|
||||
}
|
||||
@@ -710,3 +718,19 @@ func (c *conversationServer) GetConversationsNeedDestructMsgs(ctx context.Contex
|
||||
|
||||
return &pbconversation.GetConversationsNeedDestructMsgsResp{Conversations: convert.ConversationsDB2Pb(temp)}, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, req *pbconversation.GetNotNotifyConversationIDsReq) (*pbconversation.GetNotNotifyConversationIDsResp, error) {
|
||||
conversationIDs, err := c.conversationDatabase.GetNotNotifyConversationIDs(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbconversation.GetNotNotifyConversationIDsResp{ConversationIDs: conversationIDs}, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetPinnedConversationIDs(ctx context.Context, req *pbconversation.GetPinnedConversationIDsReq) (*pbconversation.GetPinnedConversationIDsResp, error) {
|
||||
conversationIDs, err := c.conversationDatabase.GetPinnedConversationIDs(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbconversation.GetPinnedConversationIDsResp{ConversationIDs: conversationIDs}, nil
|
||||
}
|
||||
@@ -218,6 +218,7 @@ func (s *groupServer) webhookAfterKickGroupMember(ctx context.Context, after *co
|
||||
CallbackCommand: callbackstruct.CallbackAfterKickGroupCommand,
|
||||
GroupID: req.GroupID,
|
||||
KickedUserIDs: req.KickedUserIDs,
|
||||
Reason: req.Reason,
|
||||
}
|
||||
s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackKillGroupMemberResp{}, after)
|
||||
}
|
||||
@@ -373,7 +374,7 @@ func (s *groupServer) webhookBeforeSetGroupInfoEx(ctx context.Context, before *c
|
||||
if req.Ex != nil {
|
||||
cbReq.Ex = req.Ex
|
||||
}
|
||||
log.ZDebug(ctx, "debug CallbackBeforeSetGroupInfoEX", "ex", cbReq.Ex)
|
||||
log.ZDebug(ctx, "debug CallbackBeforeSetGroupInfoEx", "ex", cbReq.Ex)
|
||||
|
||||
if req.NeedVerification != nil {
|
||||
cbReq.NeedVerification = req.NeedVerification
|
||||
|
||||
@@ -58,8 +58,12 @@ func UpdateGroupInfoMap(ctx context.Context, group *sdkws.GroupInfoForSet) map[s
|
||||
func UpdateGroupInfoExMap(ctx context.Context, group *pbgroup.SetGroupInfoExReq) (map[string]any, error) {
|
||||
m := make(map[string]any)
|
||||
|
||||
if group.GroupName != nil && group.GroupName.Value != "" {
|
||||
return nil, errs.ErrArgs.WrapMsg("group name is empty")
|
||||
if group.GroupName != nil {
|
||||
if group.GroupName.Value != "" {
|
||||
m["group_name"] = group.GroupName.Value
|
||||
} else {
|
||||
return nil, errs.ErrArgs.WrapMsg("group name is empty")
|
||||
}
|
||||
}
|
||||
if group.Notification != nil {
|
||||
m["notification"] = group.Notification.Value
|
||||
|
||||
+167
-33
@@ -167,11 +167,11 @@ func (g *groupServer) CheckGroupAdmin(ctx context.Context, groupID string) error
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *groupServer) GetPublicUserInfoMap(ctx context.Context, userIDs []string, complete bool) (map[string]*sdkws.PublicUserInfo, error) {
|
||||
func (g *groupServer) GetPublicUserInfoMap(ctx context.Context, userIDs []string) (map[string]*sdkws.PublicUserInfo, error) {
|
||||
if len(userIDs) == 0 {
|
||||
return map[string]*sdkws.PublicUserInfo{}, nil
|
||||
}
|
||||
users, err := g.user.GetPublicUserInfos(ctx, userIDs, complete)
|
||||
users, err := g.user.GetPublicUserInfos(ctx, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -465,7 +465,7 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = g.notification.MemberEnterNotification(ctx, req.GroupID, req.InvitedUserIDs...); err != nil {
|
||||
if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, opUserID, req.InvitedUserIDs...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbgroup.InviteUserToGroupResp{}, nil
|
||||
@@ -696,7 +696,7 @@ func (g *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup.
|
||||
userIDs = append(userIDs, gr.UserID)
|
||||
}
|
||||
userIDs = datautil.Distinct(userIDs)
|
||||
userMap, err := g.user.GetPublicUserInfoMap(ctx, userIDs, true)
|
||||
userMap, err := g.user.GetPublicUserInfoMap(ctx, userIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1026,7 +1026,7 @@ func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
|
||||
}
|
||||
num := len(update)
|
||||
if req.GroupInfoForSet.Notification != "" {
|
||||
num--
|
||||
num -= 3
|
||||
func() {
|
||||
conversation := &pbconversation.ConversationReq{
|
||||
ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupInfoForSet.GroupID),
|
||||
@@ -1133,8 +1133,9 @@ func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupI
|
||||
}
|
||||
|
||||
num := len(updatedData)
|
||||
|
||||
if req.Notification != nil {
|
||||
num--
|
||||
num -= 3
|
||||
|
||||
if req.Notification.Value != "" {
|
||||
func() {
|
||||
@@ -1180,36 +1181,53 @@ func (g *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if group.Status == constant.GroupStatusDismissed {
|
||||
return nil, servererrs.ErrDismissedAlready.Wrap()
|
||||
}
|
||||
|
||||
if req.OldOwnerUserID == req.NewOwnerUserID {
|
||||
return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID == NewOwnerUserID")
|
||||
}
|
||||
|
||||
members, err := g.db.FindGroupMembers(ctx, req.GroupID, []string{req.OldOwnerUserID, req.NewOwnerUserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := g.PopulateGroupMember(ctx, members...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
memberMap := datautil.SliceToMap(members, func(e *model.GroupMember) string { return e.UserID })
|
||||
if ids := datautil.Single([]string{req.OldOwnerUserID, req.NewOwnerUserID}, datautil.Keys(memberMap)); len(ids) > 0 {
|
||||
return nil, errs.ErrArgs.WrapMsg("user not in group " + strings.Join(ids, ","))
|
||||
}
|
||||
|
||||
oldOwner := memberMap[req.OldOwnerUserID]
|
||||
if oldOwner == nil {
|
||||
return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID not in group " + req.NewOwnerUserID)
|
||||
}
|
||||
|
||||
newOwner := memberMap[req.NewOwnerUserID]
|
||||
if newOwner == nil {
|
||||
return nil, errs.ErrArgs.WrapMsg("NewOwnerUser not in group " + req.NewOwnerUserID)
|
||||
}
|
||||
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
if !(mcontext.GetOpUserID(ctx) == oldOwner.UserID && oldOwner.RoleLevel == constant.GroupOwner) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("no permission transfer group owner")
|
||||
}
|
||||
}
|
||||
|
||||
if newOwner.MuteEndTime.After(time.Now()) {
|
||||
if _, err := g.CancelMuteGroupMember(ctx, &pbgroup.CancelMuteGroupMemberReq{
|
||||
GroupID: group.GroupID,
|
||||
UserID: req.NewOwnerUserID}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if err := g.db.TransferGroupOwner(ctx, req.GroupID, req.OldOwnerUserID, req.NewOwnerUserID, newOwner.RoleLevel); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1217,6 +1235,7 @@ func (g *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
|
||||
g.webhookAfterTransferGroupOwner(ctx, &g.config.WebhooksConfig.AfterTransferGroupOwner, req)
|
||||
|
||||
g.notification.GroupOwnerTransferredNotification(ctx, req)
|
||||
|
||||
return &pbgroup.TransferGroupOwnerResp{}, nil
|
||||
}
|
||||
|
||||
@@ -1425,32 +1444,38 @@ func (g *groupServer) CancelMuteGroupMember(ctx context.Context, req *pbgroup.Ca
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := g.PopulateGroupMember(ctx, member); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||
opMember, err := g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch member.RoleLevel {
|
||||
case constant.GroupOwner:
|
||||
return nil, errs.ErrNoPermission.WrapMsg("set group owner mute")
|
||||
return nil, errs.ErrNoPermission.WrapMsg("Can not set group owner unmute")
|
||||
case constant.GroupAdmin:
|
||||
if opMember.RoleLevel != constant.GroupOwner {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("set group admin mute")
|
||||
return nil, errs.ErrNoPermission.WrapMsg("Can not set group admin unmute")
|
||||
}
|
||||
case constant.GroupOrdinaryUsers:
|
||||
if !(opMember.RoleLevel == constant.GroupAdmin || opMember.RoleLevel == constant.GroupOwner) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("set group ordinary users mute")
|
||||
return nil, errs.ErrNoPermission.WrapMsg("Can not set group ordinary users unmute")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data := UpdateGroupMemberMutedTimeMap(time.Unix(0, 0))
|
||||
if err := g.db.UpdateGroupMember(ctx, member.GroupID, member.UserID, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
g.notification.GroupMemberCancelMutedNotification(ctx, req.GroupID, req.UserID)
|
||||
|
||||
return &pbgroup.CancelMuteGroupMemberResp{}, nil
|
||||
}
|
||||
|
||||
@@ -1485,9 +1510,6 @@ func (g *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
|
||||
return nil, errs.ErrNoPermission.WrapMsg("no op user id")
|
||||
}
|
||||
isAppManagerUid := authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID)
|
||||
for i := range req.Members {
|
||||
req.Members[i].FaceURL = nil
|
||||
}
|
||||
groupMembers := make(map[string][]*pbgroup.SetGroupMemberInfo)
|
||||
for i, member := range req.Members {
|
||||
if member.RoleLevel != nil {
|
||||
@@ -1529,29 +1551,61 @@ func (g *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
|
||||
case 0:
|
||||
if !isAppManagerUid {
|
||||
roleLevel := dbMembers[opUserIndex].RoleLevel
|
||||
if roleLevel != constant.GroupOwner {
|
||||
switch roleLevel {
|
||||
case constant.GroupAdmin:
|
||||
for _, member := range dbMembers {
|
||||
if member.RoleLevel == constant.GroupOwner {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("admin can not change group owner")
|
||||
}
|
||||
if member.RoleLevel == constant.GroupAdmin && member.UserID != opUserID {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("admin can not change other group admin")
|
||||
}
|
||||
var (
|
||||
dbSelf = &model.GroupMember{}
|
||||
reqSelf *pbgroup.SetGroupMemberInfo
|
||||
)
|
||||
switch roleLevel {
|
||||
case constant.GroupOwner:
|
||||
for _, member := range dbMembers {
|
||||
if member.UserID == opUserID {
|
||||
dbSelf = member
|
||||
break
|
||||
}
|
||||
case constant.GroupOrdinaryUsers:
|
||||
for _, member := range dbMembers {
|
||||
if !(member.RoleLevel == constant.GroupOrdinaryUsers && member.UserID == opUserID) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("ordinary users can not change other role level")
|
||||
}
|
||||
}
|
||||
case constant.GroupAdmin:
|
||||
for _, member := range dbMembers {
|
||||
if member.UserID == opUserID {
|
||||
dbSelf = member
|
||||
}
|
||||
default:
|
||||
for _, member := range dbMembers {
|
||||
if member.RoleLevel >= roleLevel {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("can not change higher role level")
|
||||
}
|
||||
if member.RoleLevel == constant.GroupOwner {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("admin can not change group owner")
|
||||
}
|
||||
if member.RoleLevel == constant.GroupAdmin && member.UserID != opUserID {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("admin can not change other group admin")
|
||||
}
|
||||
}
|
||||
case constant.GroupOrdinaryUsers:
|
||||
for _, member := range dbMembers {
|
||||
if member.UserID == opUserID {
|
||||
dbSelf = member
|
||||
}
|
||||
if !(member.RoleLevel == constant.GroupOrdinaryUsers && member.UserID == opUserID) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("ordinary users can not change other role level")
|
||||
}
|
||||
}
|
||||
default:
|
||||
for _, member := range dbMembers {
|
||||
if member.UserID == opUserID {
|
||||
dbSelf = member
|
||||
}
|
||||
if member.RoleLevel >= roleLevel {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("can not change higher role level")
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, member := range req.Members {
|
||||
if member.UserID == opUserID {
|
||||
reqSelf = member
|
||||
break
|
||||
}
|
||||
}
|
||||
if reqSelf != nil && reqSelf.RoleLevel != nil {
|
||||
if reqSelf.RoleLevel.GetValue() > dbSelf.RoleLevel {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("can not improve role level by self")
|
||||
}
|
||||
if roleLevel == constant.GroupOwner {
|
||||
return nil, errs.ErrArgs.WrapMsg("group owner can not change own role level") // Prevent the absence of a group owner
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1685,36 +1739,51 @@ func (g *groupServer) GetGroupUsersReqApplicationList(ctx context.Context, req *
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(requests) == 0 {
|
||||
return &pbgroup.GetGroupUsersReqApplicationListResp{}, nil
|
||||
}
|
||||
|
||||
groupIDs := datautil.Distinct(datautil.Slice(requests, func(e *model.GroupRequest) string {
|
||||
return e.GroupID
|
||||
}))
|
||||
|
||||
groups, err := g.db.FindGroup(ctx, groupIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupMap := datautil.SliceToMap(groups, func(e *model.Group) string {
|
||||
return e.GroupID
|
||||
})
|
||||
|
||||
if ids := datautil.Single(groupIDs, datautil.Keys(groupMap)); len(ids) > 0 {
|
||||
return nil, servererrs.ErrGroupIDNotFound.WrapMsg(strings.Join(ids, ","))
|
||||
}
|
||||
|
||||
userMap, err := g.user.GetPublicUserInfoMap(ctx, req.UserIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
owners, err := g.db.FindGroupsOwner(ctx, groupIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := g.PopulateGroupMember(ctx, owners...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ownerMap := datautil.SliceToMap(owners, func(e *model.GroupMember) string {
|
||||
return e.GroupID
|
||||
})
|
||||
|
||||
groupMemberNum, err := g.db.MapGroupMemberNum(ctx, groupIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbgroup.GetGroupUsersReqApplicationListResp{
|
||||
Total: int64(len(requests)),
|
||||
GroupRequests: datautil.Slice(requests, func(e *model.GroupRequest) *sdkws.GroupRequest {
|
||||
@@ -1722,7 +1791,72 @@ func (g *groupServer) GetGroupUsersReqApplicationList(ctx context.Context, req *
|
||||
if owner, ok := ownerMap[e.GroupID]; ok {
|
||||
ownerUserID = owner.UserID
|
||||
}
|
||||
return convert.Db2PbGroupRequest(e, nil, convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerUserID, groupMemberNum[e.GroupID]))
|
||||
|
||||
var userInfo *sdkws.PublicUserInfo
|
||||
if user, ok := userMap[e.UserID]; !ok {
|
||||
userInfo = user
|
||||
}
|
||||
|
||||
return convert.Db2PbGroupRequest(e, userInfo, convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerUserID, groupMemberNum[e.GroupID]))
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (g *groupServer) GetSpecifiedUserGroupRequestInfo(ctx context.Context, req *pbgroup.GetSpecifiedUserGroupRequestInfoReq) (*pbgroup.GetSpecifiedUserGroupRequestInfoResp, error) {
|
||||
opUserID := mcontext.GetOpUserID(ctx)
|
||||
|
||||
owners, err := g.db.FindGroupsOwner(ctx, []string{req.GroupID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.UserID != opUserID {
|
||||
adminIDs, err := g.db.GetGroupRoleLevelMemberIDs(ctx, req.GroupID, constant.GroupAdmin)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
adminIDs = append(adminIDs, owners[0].UserID)
|
||||
adminIDs = append(adminIDs, g.config.Share.IMAdminUserID...)
|
||||
|
||||
if !datautil.Contain(opUserID, adminIDs...) {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("opUser no permission")
|
||||
}
|
||||
}
|
||||
|
||||
requests, err := g.db.FindGroupRequests(ctx, req.GroupID, []string{req.UserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(requests) == 0 {
|
||||
return &pbgroup.GetSpecifiedUserGroupRequestInfoResp{}, nil
|
||||
}
|
||||
|
||||
groups, err := g.db.FindGroup(ctx, []string{req.GroupID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
userInfos, err := g.user.GetPublicUserInfos(ctx, []string{req.UserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
groupMemberNum, err := g.db.MapGroupMemberNum(ctx, []string{req.GroupID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := &pbgroup.GetSpecifiedUserGroupRequestInfoResp{
|
||||
GroupRequests: make([]*sdkws.GroupRequest, 0, len(requests)),
|
||||
}
|
||||
|
||||
for _, request := range requests {
|
||||
resp.GroupRequests = append(resp.GroupRequests, convert.Db2PbGroupRequest(request, userInfos[0], convert.Db2PbGroupInfo(groups[0], owners[0].UserID, groupMemberNum[groups[0].GroupID])))
|
||||
}
|
||||
|
||||
resp.Total = uint32(len(requests))
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -38,6 +38,7 @@ import (
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GroupApplicationReceiver
|
||||
@@ -572,8 +573,51 @@ func (g *GroupNotificationSender) GroupApplicationAgreeMemberEnterNotification(c
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GroupNotificationSender) MemberEnterNotification(ctx context.Context, groupID string, entrantUserID ...string) error {
|
||||
return g.GroupApplicationAgreeMemberEnterNotification(ctx, groupID, "", entrantUserID...)
|
||||
func (g *GroupNotificationSender) MemberEnterNotification(ctx context.Context, groupID string, entrantUserID string) error {
|
||||
var err error
|
||||
defer func() {
|
||||
if err != nil {
|
||||
log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err)
|
||||
}
|
||||
}()
|
||||
|
||||
if !g.config.RpcConfig.EnableHistoryForNewMembers {
|
||||
conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, groupID)
|
||||
maxSeq, err := g.msgRpcClient.GetConversationMaxSeq(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = g.msgRpcClient.SetUserConversationsMinSeq(ctx, &msg.SetUserConversationsMinSeqReq{
|
||||
UserIDs: []string{entrantUserID},
|
||||
ConversationID: conversationID,
|
||||
Seq: maxSeq,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := g.conversationRpcClient.GroupChatFirstCreateConversation(ctx, groupID, []string{entrantUserID}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var group *sdkws.GroupInfo
|
||||
group, err = g.getGroupInfo(ctx, groupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
user, err := g.getGroupMember(ctx, groupID, entrantUserID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tips := &sdkws.MemberEnterTips{
|
||||
Group: group,
|
||||
EntrantUser: user,
|
||||
OperationTime: time.Now().UnixMilli(),
|
||||
}
|
||||
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
|
||||
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberEnterNotification, tips)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *GroupNotificationSender) GroupDismissedNotification(ctx context.Context, tips *sdkws.GroupDismissedTips) {
|
||||
|
||||
+22
-11
@@ -16,6 +16,7 @@ package msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
@@ -55,7 +56,7 @@ func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *m
|
||||
conversationMaxSeqMap[conversation.ConversationID] = conversation.MaxSeq
|
||||
}
|
||||
}
|
||||
maxSeqs, err := m.MsgDatabase.GetMaxSeqs(ctx, conversationIDs)
|
||||
maxSeqs, err := m.MsgDatabase.GetMaxSeqsWithTime(ctx, conversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -63,7 +64,8 @@ func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *m
|
||||
for conversationID, maxSeq := range maxSeqs {
|
||||
resp.Seqs[conversationID] = &msg.Seqs{
|
||||
HasReadSeq: hasReadSeqs[conversationID],
|
||||
MaxSeq: maxSeq,
|
||||
MaxSeq: maxSeq.Seq,
|
||||
MaxSeqTime: maxSeq.Time,
|
||||
}
|
||||
if v, ok := conversationMaxSeqMap[conversationID]; ok {
|
||||
resp.Seqs[conversationID].MaxSeq = v
|
||||
@@ -107,7 +109,7 @@ func (m *msgServer) MarkMsgsAsRead(ctx context.Context, req *msg.MarkMsgsAsReadR
|
||||
return nil, err
|
||||
}
|
||||
currentHasReadSeq, err := m.MsgDatabase.GetHasReadSeq(ctx, req.UserID, req.ConversationID)
|
||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
||||
if err != nil && !errors.Is(err, redis.Nil) {
|
||||
return nil, err
|
||||
}
|
||||
if hasReadSeq > currentHasReadSeq {
|
||||
@@ -135,7 +137,7 @@ func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkCon
|
||||
return nil, err
|
||||
}
|
||||
hasReadSeq, err := m.MsgDatabase.GetHasReadSeq(ctx, req.UserID, req.ConversationID)
|
||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
||||
if err != nil && errors.Is(err, redis.Nil) {
|
||||
return nil, err
|
||||
}
|
||||
var seqs []int64
|
||||
@@ -179,14 +181,23 @@ func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkCon
|
||||
req.UserID, seqs, hasReadSeq)
|
||||
}
|
||||
|
||||
reqCall := &cbapi.CallbackGroupMsgReadReq{
|
||||
SendID: conversation.OwnerUserID,
|
||||
ReceiveID: req.UserID,
|
||||
UnreadMsgNum: req.HasReadSeq,
|
||||
ContentType: int64(conversation.ConversationType),
|
||||
if conversation.ConversationType == constant.SingleChatType {
|
||||
reqCall := &cbapi.CallbackSingleMsgReadReq{
|
||||
ConversationID: conversation.ConversationID,
|
||||
UserID: conversation.OwnerUserID,
|
||||
Seqs: req.Seqs,
|
||||
ContentType: conversation.ConversationType,
|
||||
}
|
||||
m.webhookAfterSingleMsgRead(ctx, &m.config.WebhooksConfig.AfterSingleMsgRead, reqCall)
|
||||
} else if conversation.ConversationType == constant.ReadGroupChatType {
|
||||
reqCall := &cbapi.CallbackGroupMsgReadReq{
|
||||
SendID: conversation.OwnerUserID,
|
||||
ReceiveID: req.UserID,
|
||||
UnreadMsgNum: req.HasReadSeq,
|
||||
ContentType: int64(conversation.ConversationType),
|
||||
}
|
||||
m.webhookAfterGroupMsgRead(ctx, &m.config.WebhooksConfig.AfterGroupMsgRead, reqCall)
|
||||
}
|
||||
|
||||
m.webhookAfterGroupMsgRead(ctx, &m.config.WebhooksConfig.AfterGroupMsgRead, reqCall)
|
||||
return &msg.MarkConversationAsReadResp{}, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,9 @@ func (m *msgServer) webhookBeforeSendSingleMsg(ctx context.Context, before *conf
|
||||
if msg.MsgData.ContentType == constant.Typing {
|
||||
return nil
|
||||
}
|
||||
if !filterBeforeMsg(msg, before) {
|
||||
return nil
|
||||
}
|
||||
cbReq := &cbapi.CallbackBeforeSendSingleMsgReq{
|
||||
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeSendSingleMsgCommand),
|
||||
RecvID: msg.MsgData.RecvID,
|
||||
@@ -84,9 +87,7 @@ func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config
|
||||
if msg.MsgData.ContentType == constant.Typing {
|
||||
return
|
||||
}
|
||||
// According to the attentionIds configuration, only some users are sent
|
||||
attentionIds := after.AttentionIds
|
||||
if attentionIds != nil && !datautil.Contain(msg.MsgData.RecvID, attentionIds...) && !datautil.Contain(msg.MsgData.SendID, attentionIds...) {
|
||||
if !filterAfterMsg(msg, after) {
|
||||
return
|
||||
}
|
||||
cbReq := &cbapi.CallbackAfterSendSingleMsgReq{
|
||||
@@ -98,6 +99,9 @@ func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config
|
||||
|
||||
func (m *msgServer) webhookBeforeSendGroupMsg(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error {
|
||||
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
|
||||
if !filterBeforeMsg(msg, before) {
|
||||
return nil
|
||||
}
|
||||
if msg.MsgData.ContentType == constant.Typing {
|
||||
return nil
|
||||
}
|
||||
@@ -117,6 +121,9 @@ func (m *msgServer) webhookAfterSendGroupMsg(ctx context.Context, after *config.
|
||||
if msg.MsgData.ContentType == constant.Typing {
|
||||
return
|
||||
}
|
||||
if !filterAfterMsg(msg, after) {
|
||||
return
|
||||
}
|
||||
cbReq := &cbapi.CallbackAfterSendGroupMsgReq{
|
||||
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand),
|
||||
GroupID: msg.MsgData.GroupID,
|
||||
@@ -129,6 +136,9 @@ func (m *msgServer) webhookBeforeMsgModify(ctx context.Context, before *config.B
|
||||
if msg.MsgData.ContentType != constant.Text {
|
||||
return nil
|
||||
}
|
||||
if !filterBeforeMsg(msg, before) {
|
||||
return nil
|
||||
}
|
||||
cbReq := &cbapi.CallbackMsgModifyCommandReq{
|
||||
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeMsgModifyCommand),
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package msg
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
pbchat "github.com/openimsdk/protocol/msg"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
separator = "-"
|
||||
)
|
||||
|
||||
func filterAfterMsg(msg *pbchat.SendMsgReq, after *config.AfterConfig) bool {
|
||||
return filterMsg(msg, after.AttentionIds, after.AllowedTypes, after.DeniedTypes)
|
||||
}
|
||||
|
||||
func filterBeforeMsg(msg *pbchat.SendMsgReq, before *config.BeforeConfig) bool {
|
||||
return filterMsg(msg, nil, before.AllowedTypes, before.DeniedTypes)
|
||||
}
|
||||
|
||||
func filterMsg(msg *pbchat.SendMsgReq, attentionIds, allowedTypes, deniedTypes []string) bool {
|
||||
// According to the attentionIds configuration, only some users are sent
|
||||
if len(attentionIds) != 0 && !datautil.Contains([]string{msg.MsgData.SendID, msg.MsgData.RecvID}, attentionIds...) {
|
||||
return false
|
||||
}
|
||||
if len(allowedTypes) != 0 && !isInInterval(msg.MsgData.ContentType, allowedTypes) {
|
||||
return false
|
||||
}
|
||||
if len(deniedTypes) != 0 && isInInterval(msg.MsgData.ContentType, deniedTypes) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isInInterval(contentType int32, interval []string) bool {
|
||||
for _, v := range interval {
|
||||
if strings.Contains(v, separator) {
|
||||
// is interval
|
||||
bounds := strings.Split(v, separator)
|
||||
if len(bounds) != 2 {
|
||||
continue
|
||||
}
|
||||
bottom, err := strconv.Atoi(bounds[0])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
top, err := strconv.Atoi(bounds[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if datautil.BetweenEq(int(contentType), bottom, top) {
|
||||
return true
|
||||
}
|
||||
} else {
|
||||
iv, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if int(contentType) == iv {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -16,6 +16,7 @@ package msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/tools/mw"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||
@@ -29,7 +30,6 @@ import (
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
)
|
||||
|
||||
func (m *msgServer) SendMsg(ctx context.Context, req *pbmsg.SendMsgReq) (*pbmsg.SendMsgResp, error) {
|
||||
@@ -79,14 +79,25 @@ func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq)
|
||||
}
|
||||
|
||||
func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgData) {
|
||||
|
||||
log.ZDebug(nctx, "setConversationAtInfo", "msg", msg)
|
||||
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
mw.PanicStackToLog(nctx, r)
|
||||
}
|
||||
}()
|
||||
|
||||
ctx := mcontext.NewCtx("@@@" + mcontext.GetOperationID(nctx))
|
||||
|
||||
var atUserID []string
|
||||
|
||||
conversation := &pbconversation.ConversationReq{
|
||||
ConversationID: msgprocessor.GetConversationIDByMsg(msg),
|
||||
ConversationType: msg.SessionType,
|
||||
GroupID: msg.GroupID,
|
||||
}
|
||||
|
||||
tagAll := datautil.Contain(constant.AtAllString, msg.AtUserIDList...)
|
||||
if tagAll {
|
||||
memberUserIDList, err := m.GroupLocalCache.GetGroupMemberIDs(ctx, msg.GroupID)
|
||||
@@ -94,25 +105,35 @@ func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgDa
|
||||
log.ZWarn(ctx, "GetGroupMemberIDs", err)
|
||||
return
|
||||
}
|
||||
atUserID = stringutil.DifferenceString([]string{constant.AtAllString}, msg.AtUserIDList)
|
||||
|
||||
memberUserIDList = datautil.DeleteElems(memberUserIDList, msg.SendID)
|
||||
|
||||
atUserID = datautil.Single([]string{constant.AtAllString}, msg.AtUserIDList)
|
||||
|
||||
if len(atUserID) == 0 { // just @everyone
|
||||
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll}
|
||||
} else { // @Everyone and @other people
|
||||
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAllAtMe}
|
||||
|
||||
err = m.Conversation.SetConversations(ctx, atUserID, conversation)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "SetConversations", err, "userID", atUserID, "conversation", conversation)
|
||||
}
|
||||
memberUserIDList = stringutil.DifferenceString(atUserID, memberUserIDList)
|
||||
|
||||
memberUserIDList = datautil.Single(atUserID, memberUserIDList)
|
||||
}
|
||||
|
||||
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll}
|
||||
|
||||
err = m.Conversation.SetConversations(ctx, memberUserIDList, conversation)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "SetConversations", err, "userID", memberUserIDList, "conversation", conversation)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtMe}
|
||||
|
||||
err := m.Conversation.SetConversations(ctx, msg.AtUserIDList, conversation)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "SetConversations", err, msg.AtUserIDList, conversation)
|
||||
@@ -153,9 +174,6 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq
|
||||
prommetrics.SingleChatMsgProcessFailedCounter.Inc()
|
||||
return nil, nil
|
||||
} else {
|
||||
if err = m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
+26
-4
@@ -16,15 +16,15 @@ package msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
"errors"
|
||||
pbmsg "github.com/openimsdk/protocol/msg"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func (m *msgServer) GetConversationMaxSeq(ctx context.Context, req *pbmsg.GetConversationMaxSeqReq) (*pbmsg.GetConversationMaxSeqResp, error) {
|
||||
maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID)
|
||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
||||
if err != nil && !errors.Is(err, redis.Nil) {
|
||||
return nil, err
|
||||
}
|
||||
return &pbmsg.GetConversationMaxSeqResp{MaxSeq: maxSeq}, nil
|
||||
@@ -62,3 +62,25 @@ func (m *msgServer) SetUserConversationsMinSeq(ctx context.Context, req *pbmsg.S
|
||||
}
|
||||
return &pbmsg.SetUserConversationsMinSeqResp{}, nil
|
||||
}
|
||||
|
||||
func (m *msgServer) GetActiveConversation(ctx context.Context, req *pbmsg.GetActiveConversationReq) (*pbmsg.GetActiveConversationResp, error) {
|
||||
res, err := m.MsgDatabase.GetCacheMaxSeqWithTime(ctx, req.ConversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversations := make([]*pbmsg.ActiveConversation, 0, len(res))
|
||||
for conversationID, val := range res {
|
||||
conversations = append(conversations, &pbmsg.ActiveConversation{
|
||||
MaxSeq: val.Seq,
|
||||
LastTime: val.Time,
|
||||
ConversationID: conversationID,
|
||||
})
|
||||
}
|
||||
if req.Limit > 0 {
|
||||
sort.Sort(activeConversations(conversations))
|
||||
if len(conversations) > int(req.Limit) {
|
||||
conversations = conversations[:req.Limit]
|
||||
}
|
||||
}
|
||||
return &pbmsg.GetActiveConversationResp{Conversations: conversations}, nil
|
||||
}
|
||||
|
||||
@@ -139,3 +139,11 @@ func (m *msgServer) conversationAndGetRecvID(conversation *conversation.Conversa
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (m *msgServer) AppendStreamMsg(ctx context.Context, req *msg.AppendStreamMsgReq) (*msg.AppendStreamMsgResp, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (m *msgServer) GetStreamMsg(ctx context.Context, req *msg.GetStreamMsgReq) (*msg.GetStreamMsgResp, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
package msg
|
||||
|
||||
import (
|
||||
"github.com/openimsdk/protocol/msg"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"go.mongodb.org/mongo-driver/mongo"
|
||||
@@ -28,3 +29,63 @@ func IsNotFound(err error) bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type activeConversations []*msg.ActiveConversation
|
||||
|
||||
func (s activeConversations) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s activeConversations) Less(i, j int) bool {
|
||||
return s[i].LastTime > s[j].LastTime
|
||||
}
|
||||
|
||||
func (s activeConversations) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
//type seqTime struct {
|
||||
// ConversationID string
|
||||
// Seq int64
|
||||
// Time int64
|
||||
// Unread int64
|
||||
// Pinned bool
|
||||
//}
|
||||
//
|
||||
//func (s seqTime) String() string {
|
||||
// return fmt.Sprintf("<Time_%d,Unread_%d,Pinned_%t>", s.Time, s.Unread, s.Pinned)
|
||||
//}
|
||||
//
|
||||
//type seqTimes []seqTime
|
||||
//
|
||||
//func (s seqTimes) Len() int {
|
||||
// return len(s)
|
||||
//}
|
||||
//
|
||||
//// Less sticky priority, unread priority, time descending
|
||||
//func (s seqTimes) Less(i, j int) bool {
|
||||
// iv, jv := s[i], s[j]
|
||||
// if iv.Pinned && (!jv.Pinned) {
|
||||
// return true
|
||||
// }
|
||||
// if jv.Pinned && (!iv.Pinned) {
|
||||
// return false
|
||||
// }
|
||||
// if iv.Unread > 0 && jv.Unread == 0 {
|
||||
// return true
|
||||
// }
|
||||
// if jv.Unread > 0 && iv.Unread == 0 {
|
||||
// return false
|
||||
// }
|
||||
// return iv.Time > jv.Time
|
||||
//}
|
||||
//
|
||||
//func (s seqTimes) Swap(i, j int) {
|
||||
// s[i], s[j] = s[j], s[i]
|
||||
//}
|
||||
//
|
||||
//type conversationStatus struct {
|
||||
// ConversationID string
|
||||
// Pinned bool
|
||||
// Recv bool
|
||||
//}
|
||||
|
||||
@@ -59,6 +59,9 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe
|
||||
data.MsgData.ContentType >= constant.NotificationBegin {
|
||||
return nil
|
||||
}
|
||||
if err := m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, data); err != nil {
|
||||
return err
|
||||
}
|
||||
black, err := m.FriendLocalCache.IsBlack(ctx, data.MsgData.SendID, data.MsgData.RecvID)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -23,13 +23,17 @@ import (
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
|
||||
"github.com/openimsdk/protocol/relation"
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/mcontext"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
)
|
||||
|
||||
func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *relation.GetPaginationBlacksReq) (resp *relation.GetPaginationBlacksResp, err error) {
|
||||
if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
|
||||
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total, blacks, err := s.blackDatabase.FindOwnerBlacks(ctx, req.UserID, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -55,7 +59,7 @@ func (s *friendServer) IsBlack(ctx context.Context, req *relation.IsBlackReq) (*
|
||||
}
|
||||
|
||||
func (s *friendServer) RemoveBlack(ctx context.Context, req *relation.RemoveBlackReq) (*relation.RemoveBlackResp, error) {
|
||||
if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil {
|
||||
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -64,6 +68,7 @@ func (s *friendServer) RemoveBlack(ctx context.Context, req *relation.RemoveBlac
|
||||
}
|
||||
|
||||
s.notificationSender.BlackDeletedNotification(ctx, req)
|
||||
s.webhookAfterRemoveBlack(ctx, &s.config.WebhooksConfig.AfterRemoveBlack, req)
|
||||
|
||||
return &relation.RemoveBlackResp{}, nil
|
||||
}
|
||||
@@ -72,6 +77,11 @@ func (s *friendServer) AddBlack(ctx context.Context, req *relation.AddBlackReq)
|
||||
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.webhookBeforeAddBlack(ctx, &s.config.WebhooksConfig.BeforeAddBlack, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err := s.userRpcClient.GetUsersInfo(ctx, []string{req.OwnerUserID, req.BlackUserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -90,3 +100,53 @@ func (s *friendServer) AddBlack(ctx context.Context, req *relation.AddBlackReq)
|
||||
s.notificationSender.BlackAddedNotification(ctx, req)
|
||||
return &relation.AddBlackResp{}, nil
|
||||
}
|
||||
|
||||
func (s *friendServer) GetSpecifiedBlacks(ctx context.Context, req *relation.GetSpecifiedBlacksReq) (*relation.GetSpecifiedBlacksResp, error) {
|
||||
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(req.UserIDList) == 0 {
|
||||
return nil, errs.ErrArgs.WrapMsg("userIDList is empty")
|
||||
}
|
||||
|
||||
if datautil.Duplicate(req.UserIDList) {
|
||||
return nil, errs.ErrArgs.WrapMsg("userIDList repeated")
|
||||
}
|
||||
|
||||
userMap, err := s.userRpcClient.GetPublicUserInfoMap(ctx, req.UserIDList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blacks, err := s.blackDatabase.FindBlackInfos(ctx, req.OwnerUserID, req.UserIDList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blackMap := datautil.SliceToMap(blacks, func(e *model.Black) string {
|
||||
return e.BlockUserID
|
||||
})
|
||||
|
||||
resp := &relation.GetSpecifiedBlacksResp{
|
||||
Blacks: make([]*sdkws.BlackInfo, 0, len(req.UserIDList)),
|
||||
}
|
||||
|
||||
for _, userID := range req.UserIDList {
|
||||
if black := blackMap[userID]; black != nil {
|
||||
resp.Blacks = append(resp.Blacks,
|
||||
&sdkws.BlackInfo{
|
||||
OwnerUserID: black.OwnerUserID,
|
||||
CreateTime: black.CreateTime.UnixMilli(),
|
||||
BlackUserInfo: userMap[userID],
|
||||
AddSource: black.AddSource,
|
||||
OperatorUserID: black.OperatorUserID,
|
||||
Ex: black.Ex,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
resp.Total = int32(len(resp.Blacks))
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -138,6 +138,18 @@ func (s *friendServer) webhookBeforeAddFriendAgree(ctx context.Context, before *
|
||||
})
|
||||
}
|
||||
|
||||
func (s *friendServer) webhookAfterAddFriendAgree(ctx context.Context, after *config.AfterConfig, req *relation.RespondFriendApplyReq) {
|
||||
cbReq := &cbapi.CallbackAfterAddFriendAgreeReq{
|
||||
CallbackCommand: cbapi.CallbackAfterAddFriendAgreeCommand,
|
||||
FromUserID: req.FromUserID,
|
||||
ToUserID: req.ToUserID,
|
||||
HandleMsg: req.HandleMsg,
|
||||
HandleResult: req.HandleResult,
|
||||
}
|
||||
resp := &cbapi.CallbackAfterAddFriendAgreeResp{}
|
||||
s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after)
|
||||
}
|
||||
|
||||
func (s *friendServer) webhookBeforeImportFriends(ctx context.Context, before *config.BeforeConfig, req *relation.ImportFriendReq) error {
|
||||
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
|
||||
cbReq := &cbapi.CallbackBeforeImportFriendsReq{
|
||||
|
||||
@@ -212,6 +212,7 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *relation.Res
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.webhookAfterAddFriendAgree(ctx, &s.config.WebhooksConfig.AfterAddFriendAgree, req)
|
||||
s.notificationSender.FriendApplicationAgreedNotification(ctx, req)
|
||||
return resp, nil
|
||||
}
|
||||
@@ -228,20 +229,23 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *relation.Res
|
||||
|
||||
// ok.
|
||||
func (s *friendServer) DeleteFriend(ctx context.Context, req *relation.DeleteFriendReq) (resp *relation.DeleteFriendResp, err error) {
|
||||
resp = &relation.DeleteFriendResp{}
|
||||
if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil {
|
||||
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = s.db.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.db.Delete(ctx, req.OwnerUserID, []string{req.FriendUserID}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.notificationSender.FriendDeletedNotification(ctx, req)
|
||||
s.webhookAfterDeleteFriend(ctx, &s.config.WebhooksConfig.AfterDeleteFriend, req)
|
||||
return resp, nil
|
||||
|
||||
return &relation.DeleteFriendResp{}, nil
|
||||
}
|
||||
|
||||
// ok.
|
||||
@@ -249,23 +253,34 @@ func (s *friendServer) SetFriendRemark(ctx context.Context, req *relation.SetFri
|
||||
if err = s.webhookBeforeSetFriendRemark(ctx, &s.config.WebhooksConfig.BeforeSetFriendRemark, req); err != nil && err != servererrs.ErrCallbackContinue {
|
||||
return nil, err
|
||||
}
|
||||
resp = &relation.SetFriendRemarkResp{}
|
||||
if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil {
|
||||
|
||||
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = s.db.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.db.UpdateRemark(ctx, req.OwnerUserID, req.FriendUserID, req.Remark); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.webhookAfterSetFriendRemark(ctx, &s.config.WebhooksConfig.AfterSetFriendRemark, req)
|
||||
s.notificationSender.FriendRemarkSetNotification(ctx, req.OwnerUserID, req.FriendUserID)
|
||||
return resp, nil
|
||||
|
||||
return &relation.SetFriendRemarkResp{}, nil
|
||||
}
|
||||
|
||||
func (s *friendServer) GetFriendInfo(ctx context.Context, req *relation.GetFriendInfoReq) (*relation.GetFriendInfoResp, error) {
|
||||
friends, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &relation.GetFriendInfoResp{FriendInfos: convert.FriendOnlyDB2PbOnly(friends)}, nil
|
||||
}
|
||||
|
||||
// ok.
|
||||
func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *relation.GetDesignatedFriendsReq) (resp *relation.GetDesignatedFriendsResp, err error) {
|
||||
resp = &relation.GetDesignatedFriendsResp{}
|
||||
if datautil.Duplicate(req.FriendUserIDs) {
|
||||
@@ -309,7 +324,7 @@ func (s *friendServer) GetDesignatedFriendsApply(ctx context.Context,
|
||||
|
||||
// Get received friend requests (i.e., those initiated by others).
|
||||
func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *relation.GetPaginationFriendsApplyToReq) (resp *relation.GetPaginationFriendsApplyToResp, err error) {
|
||||
if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
|
||||
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -331,18 +346,23 @@ func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *rel
|
||||
|
||||
func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *relation.GetPaginationFriendsApplyFromReq) (resp *relation.GetPaginationFriendsApplyFromResp, err error) {
|
||||
resp = &relation.GetPaginationFriendsApplyFromResp{}
|
||||
if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
|
||||
|
||||
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total, friendRequests, err := s.db.PageFriendRequestFromMe(ctx, req.UserID, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.userRpcClient.GetUsersInfoMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Total = int32(total)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -357,31 +377,37 @@ func (s *friendServer) IsFriend(ctx context.Context, req *relation.IsFriendReq)
|
||||
}
|
||||
|
||||
func (s *friendServer) GetPaginationFriends(ctx context.Context, req *relation.GetPaginationFriendsReq) (resp *relation.GetPaginationFriendsResp, err error) {
|
||||
if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
|
||||
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
total, friends, err := s.db.PageOwnerFriends(ctx, req.UserID, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp = &relation.GetPaginationFriendsResp{}
|
||||
resp.FriendsInfo, err = convert.FriendsDB2Pb(ctx, friends, s.userRpcClient.GetUsersInfoMap)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.Total = int32(total)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *friendServer) GetFriendIDs(ctx context.Context, req *relation.GetFriendIDsReq) (resp *relation.GetFriendIDsResp, err error) {
|
||||
if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
|
||||
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp = &relation.GetFriendIDsResp{}
|
||||
resp.FriendIDs, err = s.db.FindFriendUserIDs(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -389,35 +415,45 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *relatio
|
||||
if len(req.UserIDList) == 0 {
|
||||
return nil, errs.ErrArgs.WrapMsg("userIDList is empty")
|
||||
}
|
||||
|
||||
if datautil.Duplicate(req.UserIDList) {
|
||||
return nil, errs.ErrArgs.WrapMsg("userIDList repeated")
|
||||
}
|
||||
|
||||
userMap, err := s.userRpcClient.GetUsersInfoMap(ctx, req.UserIDList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
friends, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.UserIDList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blacks, err := s.blackDatabase.FindBlackInfos(ctx, req.OwnerUserID, req.UserIDList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
friendMap := datautil.SliceToMap(friends, func(e *model.Friend) string {
|
||||
return e.FriendUserID
|
||||
})
|
||||
|
||||
blackMap := datautil.SliceToMap(blacks, func(e *model.Black) string {
|
||||
return e.BlockUserID
|
||||
})
|
||||
|
||||
resp := &relation.GetSpecifiedFriendsInfoResp{
|
||||
Infos: make([]*relation.GetSpecifiedFriendsInfoInfo, 0, len(req.UserIDList)),
|
||||
}
|
||||
|
||||
for _, userID := range req.UserIDList {
|
||||
user := userMap[userID]
|
||||
|
||||
if user == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var friendInfo *sdkws.FriendInfo
|
||||
if friend := friendMap[userID]; friend != nil {
|
||||
friendInfo = &sdkws.FriendInfo{
|
||||
@@ -430,6 +466,7 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *relatio
|
||||
IsPinned: friend.IsPinned,
|
||||
}
|
||||
}
|
||||
|
||||
var blackInfo *sdkws.BlackInfo
|
||||
if black := blackMap[userID]; black != nil {
|
||||
blackInfo = &sdkws.BlackInfo{
|
||||
@@ -440,12 +477,14 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *relatio
|
||||
Ex: black.Ex,
|
||||
}
|
||||
}
|
||||
|
||||
resp.Infos = append(resp.Infos, &relation.GetSpecifiedFriendsInfoInfo{
|
||||
UserInfo: user,
|
||||
FriendInfo: friendInfo,
|
||||
BlackInfo: blackInfo,
|
||||
})
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Select the oss method according to the profile policy
|
||||
enable := config.RpcConfig.Object.Enable
|
||||
var (
|
||||
|
||||
@@ -82,3 +82,11 @@ func checkValidObjectName(objectName string) error {
|
||||
func (t *thirdServer) IsManagerUserID(opUserID string) bool {
|
||||
return authverify.IsManagerUserID(opUserID, t.config.Share.IMAdminUserID)
|
||||
}
|
||||
|
||||
func putUpdate[T any](update map[string]any, name string, val interface{ GetValuePtr() *T }) {
|
||||
ptrVal := val.GetValuePtr()
|
||||
if ptrVal == nil {
|
||||
return
|
||||
}
|
||||
update[name] = *ptrVal
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
|
||||
@@ -88,7 +89,6 @@ func (s *userServer) webhookBeforeUserRegister(ctx context.Context, before *conf
|
||||
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
|
||||
cbReq := &cbapi.CallbackBeforeUserRegisterReq{
|
||||
CallbackCommand: cbapi.CallbackBeforeUserRegisterCommand,
|
||||
Secret: req.Secret,
|
||||
Users: req.Users,
|
||||
}
|
||||
|
||||
@@ -108,7 +108,6 @@ func (s *userServer) webhookBeforeUserRegister(ctx context.Context, before *conf
|
||||
func (s *userServer) webhookAfterUserRegister(ctx context.Context, after *config.AfterConfig, req *pbuser.UserRegisterReq) {
|
||||
cbReq := &cbapi.CallbackAfterUserRegisterReq{
|
||||
CallbackCommand: cbapi.CallbackAfterUserRegisterCommand,
|
||||
Secret: req.Secret,
|
||||
Users: req.Users,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
@@ -61,7 +62,7 @@ func (s *userServer) SetUserStatus(ctx context.Context, req *pbuser.SetUserStatu
|
||||
case constant.Online:
|
||||
online = []int32{req.PlatformID}
|
||||
case constant.Offline:
|
||||
online = []int32{req.PlatformID}
|
||||
offline = []int32{req.PlatformID}
|
||||
}
|
||||
if err := s.online.SetUserOnline(ctx, req.UserID, online, offline); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -47,7 +47,6 @@ import (
|
||||
"github.com/openimsdk/tools/db/pagination"
|
||||
registry "github.com/openimsdk/tools/discovery"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
@@ -117,18 +116,17 @@ func Start(ctx context.Context, config *Config, client registry.SvcDiscoveryRegi
|
||||
|
||||
func (s *userServer) GetDesignateUsers(ctx context.Context, req *pbuser.GetDesignateUsersReq) (resp *pbuser.GetDesignateUsersResp, err error) {
|
||||
resp = &pbuser.GetDesignateUsersResp{}
|
||||
users, err := s.db.FindWithError(ctx, req.UserIDs)
|
||||
users, err := s.db.Find(ctx, req.UserIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp.UsersInfo = convert.UsersDB2Pb(users)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// deprecated:
|
||||
|
||||
//UpdateUserInfo
|
||||
|
||||
// UpdateUserInfo
|
||||
func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInfoReq) (resp *pbuser.UpdateUserInfoResp, err error) {
|
||||
resp = &pbuser.UpdateUserInfoResp{}
|
||||
err = authverify.CheckAccessV3(ctx, req.UserInfo.UserID, s.config.Share.IMAdminUserID)
|
||||
@@ -263,10 +261,11 @@ func (s *userServer) UserRegister(ctx context.Context, req *pbuser.UserRegisterR
|
||||
if len(req.Users) == 0 {
|
||||
return nil, errs.ErrArgs.WrapMsg("users is empty")
|
||||
}
|
||||
if req.Secret != s.config.Share.Secret {
|
||||
log.ZDebug(ctx, "UserRegister", s.config.Share.Secret, req.Secret)
|
||||
return nil, errs.ErrNoPermission.WrapMsg("secret invalid")
|
||||
|
||||
if err = authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if datautil.DuplicateAny(req.Users, func(e *sdkws.UserInfo) string { return e.UserID }) {
|
||||
return nil, errs.ErrArgs.WrapMsg("userID repeated")
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ const (
|
||||
CallbackBeforeAddBlackCommand = "callbackBeforeAddBlackCommand"
|
||||
CallbackAfterAddFriendCommand = "callbackAfterAddFriendCommand"
|
||||
CallbackBeforeAddFriendAgreeCommand = "callbackBeforeAddFriendAgreeCommand"
|
||||
CallbackAfterAddFriendAgreeCommand = "callbackAfterAddFriendAgreeCommand"
|
||||
CallbackAfterDeleteFriendCommand = "callbackAfterDeleteFriendCommand"
|
||||
CallbackBeforeImportFriendsCommand = "callbackBeforeImportFriendsCommand"
|
||||
CallbackAfterImportFriendsCommand = "callbackAfterImportFriendsCommand"
|
||||
|
||||
@@ -90,6 +90,18 @@ type CallbackBeforeAddFriendAgreeResp struct {
|
||||
CommonCallbackResp
|
||||
}
|
||||
|
||||
type CallbackAfterAddFriendAgreeReq struct {
|
||||
CallbackCommand `json:"callbackCommand"`
|
||||
FromUserID string `json:"fromUserID" `
|
||||
ToUserID string `json:"blackUserID"`
|
||||
HandleResult int32 `json:"HandleResult"`
|
||||
HandleMsg string `json:"HandleMsg"`
|
||||
}
|
||||
|
||||
type CallbackAfterAddFriendAgreeResp struct {
|
||||
CommonCallbackResp
|
||||
}
|
||||
|
||||
type CallbackAfterDeleteFriendReq struct {
|
||||
CallbackCommand `json:"callbackCommand"`
|
||||
OwnerUserID string `json:"ownerUserID" `
|
||||
|
||||
@@ -72,7 +72,6 @@ type CallbackAfterUpdateUserInfoExResp struct {
|
||||
|
||||
type CallbackBeforeUserRegisterReq struct {
|
||||
CallbackCommand `json:"callbackCommand"`
|
||||
Secret string `json:"secret"`
|
||||
Users []*sdkws.UserInfo `json:"users"`
|
||||
}
|
||||
|
||||
@@ -83,7 +82,6 @@ type CallbackBeforeUserRegisterResp struct {
|
||||
|
||||
type CallbackAfterUserRegisterReq struct {
|
||||
CallbackCommand `json:"callbackCommand"`
|
||||
Secret string `json:"secret"`
|
||||
Users []*sdkws.UserInfo `json:"users"`
|
||||
}
|
||||
|
||||
|
||||
@@ -15,13 +15,14 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
|
||||
"github.com/openimsdk/protocol/auth"
|
||||
"github.com/openimsdk/tools/apiresp"
|
||||
"github.com/openimsdk/tools/utils/jsonutil"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||
"math"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// MockRootCmd is a mock type for the RootCmd type
|
||||
@@ -39,7 +40,7 @@ func TestName(t *testing.T) {
|
||||
ErrCode: 1234,
|
||||
ErrMsg: "test",
|
||||
ErrDlt: "4567",
|
||||
Data: &auth.UserTokenResp{
|
||||
Data: &auth.GetUserTokenResp{
|
||||
Token: "1234567",
|
||||
ExpireTimeSeconds: math.MaxInt64,
|
||||
},
|
||||
@@ -51,7 +52,7 @@ func TestName(t *testing.T) {
|
||||
t.Log(string(data))
|
||||
|
||||
var rReso apiresp.ApiResponse
|
||||
rReso.Data = &auth.UserTokenResp{}
|
||||
rReso.Data = &auth.GetUserTokenResp{}
|
||||
|
||||
if err := jsonutil.JsonUnmarshal(data, &rReso); err != nil {
|
||||
panic(err)
|
||||
|
||||
@@ -69,6 +69,7 @@ type Mongo struct {
|
||||
Database string `mapstructure:"database"`
|
||||
Username string `mapstructure:"username"`
|
||||
Password string `mapstructure:"password"`
|
||||
AuthSource string `mapstructure:"authSource"`
|
||||
MaxPoolSize int `mapstructure:"maxPoolSize"`
|
||||
MaxRetry int `mapstructure:"maxRetry"`
|
||||
}
|
||||
@@ -185,7 +186,6 @@ type MsgGateway struct {
|
||||
WebsocketMaxMsgLen int `mapstructure:"websocketMaxMsgLen"`
|
||||
WebsocketTimeout int `mapstructure:"websocketTimeout"`
|
||||
} `mapstructure:"longConnSvr"`
|
||||
MultiLoginPolicy int `mapstructure:"multiLoginPolicy"`
|
||||
}
|
||||
|
||||
type MsgTransfer struct {
|
||||
@@ -213,12 +213,12 @@ type Push struct {
|
||||
FilePath string `mapstructure:"filePath"`
|
||||
AuthURL string `mapstructure:"authURL"`
|
||||
} `mapstructure:"fcm"`
|
||||
JPNS struct {
|
||||
JPush struct {
|
||||
AppKey string `mapstructure:"appKey"`
|
||||
MasterSecret string `mapstructure:"masterSecret"`
|
||||
PushURL string `mapstructure:"pushURL"`
|
||||
PushIntent string `mapstructure:"pushIntent"`
|
||||
} `mapstructure:"jpns"`
|
||||
} `mapstructure:"jpush"`
|
||||
IOSPush struct {
|
||||
PushSound string `mapstructure:"pushSound"`
|
||||
BadgeCount bool `mapstructure:"badgeCount"`
|
||||
@@ -346,22 +346,33 @@ type Redis struct {
|
||||
}
|
||||
|
||||
type BeforeConfig struct {
|
||||
Enable bool `mapstructure:"enable"`
|
||||
Timeout int `mapstructure:"timeout"`
|
||||
FailedContinue bool `mapstructure:"failedContinue"`
|
||||
Enable bool `mapstructure:"enable"`
|
||||
Timeout int `mapstructure:"timeout"`
|
||||
FailedContinue bool `mapstructure:"failedContinue"`
|
||||
AllowedTypes []string `mapstructure:"allowedTypes"`
|
||||
DeniedTypes []string `mapstructure:"deniedTypes"`
|
||||
}
|
||||
|
||||
type AfterConfig struct {
|
||||
Enable bool `mapstructure:"enable"`
|
||||
Timeout int `mapstructure:"timeout"`
|
||||
AttentionIds []string `mapstructure:"attentionIds"`
|
||||
AllowedTypes []string `mapstructure:"allowedTypes"`
|
||||
DeniedTypes []string `mapstructure:"deniedTypes"`
|
||||
}
|
||||
|
||||
type Share struct {
|
||||
Secret string `mapstructure:"secret"`
|
||||
RpcRegisterName RpcRegisterName `mapstructure:"rpcRegisterName"`
|
||||
IMAdminUserID []string `mapstructure:"imAdminUserID"`
|
||||
MultiLogin MultiLogin `mapstructure:"multiLogin"`
|
||||
}
|
||||
|
||||
type MultiLogin struct {
|
||||
Policy int `mapstructure:"policy"`
|
||||
MaxNumOneEnd int `mapstructure:"maxNumOneEnd"`
|
||||
}
|
||||
|
||||
type RpcRegisterName struct {
|
||||
User string `mapstructure:"user"`
|
||||
Friend string `mapstructure:"friend"`
|
||||
@@ -434,6 +445,7 @@ type Webhooks struct {
|
||||
BeforeAddBlack BeforeConfig `mapstructure:"beforeAddBlack"`
|
||||
AfterAddFriend AfterConfig `mapstructure:"afterAddFriend"`
|
||||
BeforeAddFriendAgree BeforeConfig `mapstructure:"beforeAddFriendAgree"`
|
||||
AfterAddFriendAgree AfterConfig `mapstructure:"afterAddFriendAgree"`
|
||||
AfterDeleteFriend AfterConfig `mapstructure:"afterDeleteFriend"`
|
||||
BeforeImportFriends BeforeConfig `mapstructure:"beforeImportFriends"`
|
||||
AfterImportFriends AfterConfig `mapstructure:"afterImportFriends"`
|
||||
@@ -467,6 +479,7 @@ func (m *Mongo) Build() *mongoutil.Config {
|
||||
Database: m.Database,
|
||||
Username: m.Username,
|
||||
Password: m.Password,
|
||||
AuthSource: m.AuthSource,
|
||||
MaxPoolSize: m.MaxPoolSize,
|
||||
MaxRetry: m.MaxRetry,
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"github.com/openimsdk/protocol/relation"
|
||||
|
||||
"github.com/openimsdk/protocol/sdkws"
|
||||
"github.com/openimsdk/tools/utils/datautil"
|
||||
@@ -35,9 +36,7 @@ func FriendPb2DB(friend *sdkws.FriendInfo) *model.Friend {
|
||||
return dbFriend
|
||||
}
|
||||
|
||||
func FriendDB2Pb(ctx context.Context, friendDB *model.Friend,
|
||||
getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error),
|
||||
) (*sdkws.FriendInfo, error) {
|
||||
func FriendDB2Pb(ctx context.Context, friendDB *model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (*sdkws.FriendInfo, error) {
|
||||
users, err := getUsers(ctx, []string{friendDB.FriendUserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -53,11 +52,7 @@ func FriendDB2Pb(ctx context.Context, friendDB *model.Friend,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func FriendsDB2Pb(
|
||||
ctx context.Context,
|
||||
friendsDB []*model.Friend,
|
||||
getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error),
|
||||
) (friendsPb []*sdkws.FriendInfo, err error) {
|
||||
func FriendsDB2Pb(ctx context.Context, friendsDB []*model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (friendsPb []*sdkws.FriendInfo, err error) {
|
||||
if len(friendsDB) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -86,7 +81,21 @@ func FriendsDB2Pb(
|
||||
friendsPb = append(friendsPb, friendPb)
|
||||
}
|
||||
return friendsPb, nil
|
||||
}
|
||||
|
||||
func FriendOnlyDB2PbOnly(friendsDB []*model.Friend) []*relation.FriendInfoOnly {
|
||||
return datautil.Slice(friendsDB, func(f *model.Friend) *relation.FriendInfoOnly {
|
||||
return &relation.FriendInfoOnly{
|
||||
OwnerUserID: f.OwnerUserID,
|
||||
FriendUserID: f.FriendUserID,
|
||||
Remark: f.Remark,
|
||||
CreateTime: f.CreateTime.UnixMilli(),
|
||||
AddSource: f.AddSource,
|
||||
OperatorUserID: f.OperatorUserID,
|
||||
Ex: f.Ex,
|
||||
IsPinned: f.IsPinned,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func FriendRequestDB2Pb(ctx context.Context, friendRequests []*model.FriendRequest, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) ([]*sdkws.FriendRequest, error) {
|
||||
|
||||
@@ -17,6 +17,8 @@ package cachekey
|
||||
const (
|
||||
ConversationKey = "CONVERSATION:"
|
||||
ConversationIDsKey = "CONVERSATION_IDS:"
|
||||
NotNotifyConversationIDsKey = "NOT_NOTIFY_CONVERSATION_IDS:"
|
||||
PinnedConversationIDsKey = "PINNED_CONVERSATION_IDS:"
|
||||
ConversationIDsHashKey = "CONVERSATION_IDS_HASH:"
|
||||
ConversationHasReadSeqKey = "CONVERSATION_HAS_READ_SEQ:"
|
||||
RecvMsgOptKey = "RECV_MSG_OPT:"
|
||||
@@ -34,6 +36,14 @@ func GetConversationIDsKey(ownerUserID string) string {
|
||||
return ConversationIDsKey + ownerUserID
|
||||
}
|
||||
|
||||
func GetNotNotifyConversationIDsKey(ownerUserID string) string {
|
||||
return NotNotifyConversationIDsKey + ownerUserID
|
||||
}
|
||||
|
||||
func GetPinnedConversationIDs(ownerUserID string) string {
|
||||
return PinnedConversationIDsKey + ownerUserID
|
||||
}
|
||||
|
||||
func GetSuperGroupRecvNotNotifyUserIDsKey(groupID string) string {
|
||||
return SuperGroupRecvMsgNotNotifyUserIDsKey + groupID
|
||||
}
|
||||
|
||||
+11
-10
@@ -20,16 +20,17 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
groupExpireTime = time.Second * 60 * 60 * 12
|
||||
GroupInfoKey = "GROUP_INFO:"
|
||||
GroupMemberIDsKey = "GROUP_MEMBER_IDS:"
|
||||
GroupMembersHashKey = "GROUP_MEMBERS_HASH2:"
|
||||
GroupMemberInfoKey = "GROUP_MEMBER_INFO:"
|
||||
JoinedGroupsKey = "JOIN_GROUPS_KEY:"
|
||||
GroupMemberNumKey = "GROUP_MEMBER_NUM_CACHE:"
|
||||
GroupRoleLevelMemberIDsKey = "GROUP_ROLE_LEVEL_MEMBER_IDS:"
|
||||
GroupMemberMaxVersionKey = "GROUP_MEMBER_MAX_VERSION:"
|
||||
GroupJoinMaxVersionKey = "GROUP_JOIN_MAX_VERSION:"
|
||||
groupExpireTime = time.Second * 60 * 60 * 12
|
||||
GroupInfoKey = "GROUP_INFO:"
|
||||
GroupMemberIDsKey = "GROUP_MEMBER_IDS:"
|
||||
GroupMembersHashKey = "GROUP_MEMBERS_HASH2:"
|
||||
GroupMemberInfoKey = "GROUP_MEMBER_INFO:"
|
||||
JoinedGroupsKey = "JOIN_GROUPS_KEY:"
|
||||
GroupMemberNumKey = "GROUP_MEMBER_NUM_CACHE:"
|
||||
GroupRoleLevelMemberIDsKey = "GROUP_ROLE_LEVEL_MEMBER_IDS:"
|
||||
GroupAdminLevelMemberIDsKey = "GROUP_ADMIN_LEVEL_MEMBER_IDS:"
|
||||
GroupMemberMaxVersionKey = "GROUP_MEMBER_MAX_VERSION:"
|
||||
GroupJoinMaxVersionKey = "GROUP_JOIN_MAX_VERSION:"
|
||||
)
|
||||
|
||||
func GetGroupInfoKey(groupID string) string {
|
||||
|
||||
+18
-1
@@ -1,6 +1,9 @@
|
||||
package cachekey
|
||||
|
||||
import "github.com/openimsdk/protocol/constant"
|
||||
import (
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
UidPidToken = "UID_PID_TOKEN_STATUS:"
|
||||
@@ -9,3 +12,17 @@ const (
|
||||
func GetTokenKey(userID string, platformID int) string {
|
||||
return UidPidToken + userID + ":" + constant.PlatformIDToName(platformID)
|
||||
}
|
||||
|
||||
func GetAllPlatformTokenKey(userID string) []string {
|
||||
res := make([]string, len(constant.PlatformID2Name))
|
||||
for k := range constant.PlatformID2Name {
|
||||
res[k-1] = GetTokenKey(userID, k)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func GetPlatformIDByTokenKey(key string) int {
|
||||
splitKey := strings.Split(key, ":")
|
||||
platform := splitKey[len(splitKey)-1]
|
||||
return constant.PlatformNameToID(platform)
|
||||
}
|
||||
|
||||
+4
-1
@@ -25,6 +25,8 @@ type ConversationCache interface {
|
||||
CloneConversationCache() ConversationCache
|
||||
// get user's conversationIDs from msgCache
|
||||
GetUserConversationIDs(ctx context.Context, ownerUserID string) ([]string, error)
|
||||
GetUserNotNotifyConversationIDs(ctx context.Context, userID string) ([]string, error)
|
||||
GetPinnedConversationIDs(ctx context.Context, userID string) ([]string, error)
|
||||
DelConversationIDs(userIDs ...string) ConversationCache
|
||||
|
||||
GetUserConversationIDsHash(ctx context.Context, ownerUserID string) (hash uint64, err error)
|
||||
@@ -54,7 +56,8 @@ type ConversationCache interface {
|
||||
|
||||
GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error)
|
||||
DelConversationNotReceiveMessageUserIDs(conversationIDs ...string) ConversationCache
|
||||
|
||||
DelConversationNotNotifyMessageUserIDs(userIDs ...string) ConversationCache
|
||||
DelConversationPinnedMessageUserIDs(userIDs ...string) ConversationCache
|
||||
DelConversationVersionUserIDs(userIDs ...string) ConversationCache
|
||||
|
||||
FindMaxConversationUserVersion(ctx context.Context, userID string) (*relationtb.VersionLog, error)
|
||||
|
||||
Vendored
+1
@@ -16,6 +16,7 @@ package cache
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/common"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
)
|
||||
|
||||
+37
-1
@@ -38,7 +38,7 @@ const (
|
||||
func NewConversationRedis(rdb redis.UniversalClient, localCache *config.LocalCache, opts *rockscache.Options, db database.Conversation) cache.ConversationCache {
|
||||
batchHandler := NewBatchDeleterRedis(rdb, opts, []string{localCache.Conversation.Topic})
|
||||
c := localCache.Conversation
|
||||
log.ZDebug(context.Background(), "black local cache init", "Topic", c.Topic, "SlotNum", c.SlotNum, "SlotSize", c.SlotSize, "enable", c.Enable())
|
||||
log.ZDebug(context.Background(), "conversation local cache init", "Topic", c.Topic, "SlotNum", c.SlotNum, "SlotSize", c.SlotSize, "enable", c.Enable())
|
||||
return &ConversationRedisCache{
|
||||
BatchDeleter: batchHandler,
|
||||
rcClient: rockscache.NewClient(rdb, *opts),
|
||||
@@ -71,6 +71,14 @@ func (c *ConversationRedisCache) getConversationIDsKey(ownerUserID string) strin
|
||||
return cachekey.GetConversationIDsKey(ownerUserID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getNotNotifyConversationIDsKey(ownerUserID string) string {
|
||||
return cachekey.GetNotNotifyConversationIDsKey(ownerUserID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getPinnedConversationIDsKey(ownerUserID string) string {
|
||||
return cachekey.GetPinnedConversationIDs(ownerUserID)
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) getSuperGroupRecvNotNotifyUserIDsKey(groupID string) string {
|
||||
return cachekey.GetSuperGroupRecvNotNotifyUserIDsKey(groupID)
|
||||
}
|
||||
@@ -105,6 +113,18 @@ func (c *ConversationRedisCache) GetUserConversationIDs(ctx context.Context, own
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) GetUserNotNotifyConversationIDs(ctx context.Context, userID string) ([]string, error) {
|
||||
return getCache(ctx, c.rcClient, c.getNotNotifyConversationIDsKey(userID), c.expireTime, func(ctx context.Context) ([]string, error) {
|
||||
return c.conversationDB.FindUserIDAllNotNotifyConversationID(ctx, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) GetPinnedConversationIDs(ctx context.Context, userID string) ([]string, error) {
|
||||
return getCache(ctx, c.rcClient, c.getPinnedConversationIDsKey(userID), c.expireTime, func(ctx context.Context) ([]string, error) {
|
||||
return c.conversationDB.FindUserIDAllPinnedConversationID(ctx, userID)
|
||||
})
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelConversationIDs(userIDs ...string) cache.ConversationCache {
|
||||
keys := make([]string, 0, len(userIDs))
|
||||
for _, userID := range userIDs {
|
||||
@@ -242,6 +262,22 @@ func (c *ConversationRedisCache) DelConversationNotReceiveMessageUserIDs(convers
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelConversationNotNotifyMessageUserIDs(userIDs ...string) cache.ConversationCache {
|
||||
cache := c.CloneConversationCache()
|
||||
for _, userID := range userIDs {
|
||||
cache.AddKeys(c.getNotNotifyConversationIDsKey(userID))
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelConversationPinnedMessageUserIDs(userIDs ...string) cache.ConversationCache {
|
||||
cache := c.CloneConversationCache()
|
||||
for _, userID := range userIDs {
|
||||
cache.AddKeys(c.getPinnedConversationIDsKey(userID))
|
||||
}
|
||||
return cache
|
||||
}
|
||||
|
||||
func (c *ConversationRedisCache) DelConversationVersionUserIDs(userIDs ...string) cache.ConversationCache {
|
||||
cache := c.CloneConversationCache()
|
||||
for _, userID := range userIDs {
|
||||
|
||||
-14
@@ -1,17 +1,3 @@
|
||||
// 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 redis
|
||||
|
||||
import (
|
||||
|
||||
+207
-21
@@ -12,6 +12,7 @@ import (
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -57,6 +58,14 @@ func (s *seqConversationCacheRedis) getSingleMaxSeq(ctx context.Context, convers
|
||||
return map[string]int64{conversationID: seq}, nil
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) getSingleMaxSeqWithTime(ctx context.Context, conversationID string) (map[string]database.SeqTime, error) {
|
||||
seq, err := s.GetMaxSeqWithTime(ctx, conversationID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return map[string]database.SeqTime{conversationID: seq}, nil
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) batchGetMaxSeq(ctx context.Context, keys []string, keyConversationID map[string]string, seqs map[string]int64) error {
|
||||
result := make([]*redis.StringCmd, len(keys))
|
||||
pipe := s.rdb.Pipeline()
|
||||
@@ -88,6 +97,46 @@ func (s *seqConversationCacheRedis) batchGetMaxSeq(ctx context.Context, keys []s
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) batchGetMaxSeqWithTime(ctx context.Context, keys []string, keyConversationID map[string]string, seqs map[string]database.SeqTime) error {
|
||||
result := make([]*redis.SliceCmd, len(keys))
|
||||
pipe := s.rdb.Pipeline()
|
||||
for i, key := range keys {
|
||||
result[i] = pipe.HMGet(ctx, key, "CURR", "TIME")
|
||||
}
|
||||
if _, err := pipe.Exec(ctx); err != nil && !errors.Is(err, redis.Nil) {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
var notFoundKey []string
|
||||
for i, r := range result {
|
||||
val, err := r.Result()
|
||||
if len(val) != 2 {
|
||||
return errs.WrapMsg(err, "batchGetMaxSeqWithTime invalid result", "key", keys[i], "res", val)
|
||||
}
|
||||
if val[0] == nil {
|
||||
notFoundKey = append(notFoundKey, keys[i])
|
||||
continue
|
||||
}
|
||||
seq, err := s.parseInt64(val[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mill, err := s.parseInt64(val[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seqs[keyConversationID[keys[i]]] = database.SeqTime{Seq: seq, Time: mill}
|
||||
}
|
||||
for _, key := range notFoundKey {
|
||||
conversationID := keyConversationID[key]
|
||||
seq, err := s.GetMaxSeqWithTime(ctx, conversationID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
seqs[conversationID] = seq
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) {
|
||||
switch len(conversationIDs) {
|
||||
case 0:
|
||||
@@ -121,11 +170,44 @@ func (s *seqConversationCacheRedis) GetMaxSeqs(ctx context.Context, conversation
|
||||
return seqs, nil
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) GetMaxSeqsWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error) {
|
||||
switch len(conversationIDs) {
|
||||
case 0:
|
||||
return map[string]database.SeqTime{}, nil
|
||||
case 1:
|
||||
return s.getSingleMaxSeqWithTime(ctx, conversationIDs[0])
|
||||
}
|
||||
keys := make([]string, 0, len(conversationIDs))
|
||||
keyConversationID := make(map[string]string, len(conversationIDs))
|
||||
for _, conversationID := range conversationIDs {
|
||||
key := s.getSeqMallocKey(conversationID)
|
||||
if _, ok := keyConversationID[key]; ok {
|
||||
continue
|
||||
}
|
||||
keys = append(keys, key)
|
||||
keyConversationID[key] = conversationID
|
||||
}
|
||||
if len(keys) == 1 {
|
||||
return s.getSingleMaxSeqWithTime(ctx, conversationIDs[0])
|
||||
}
|
||||
slotKeys, err := groupKeysBySlot(ctx, s.rdb, keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
seqs := make(map[string]database.SeqTime, len(conversationIDs))
|
||||
for _, keys := range slotKeys {
|
||||
if err := s.batchGetMaxSeqWithTime(ctx, keys, keyConversationID, seqs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return seqs, nil
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) getSeqMallocKey(conversationID string) string {
|
||||
return cachekey.GetMallocSeqKey(conversationID)
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) setSeq(ctx context.Context, key string, owner int64, currSeq int64, lastSeq int64) (int64, error) {
|
||||
func (s *seqConversationCacheRedis) setSeq(ctx context.Context, key string, owner int64, currSeq int64, lastSeq int64, mill int64) (int64, error) {
|
||||
if lastSeq < currSeq {
|
||||
return 0, errs.New("lastSeq must be greater than currSeq")
|
||||
}
|
||||
@@ -138,8 +220,9 @@ local lockValue = ARGV[1]
|
||||
local dataSecond = ARGV[2]
|
||||
local curr_seq = tonumber(ARGV[3])
|
||||
local last_seq = tonumber(ARGV[4])
|
||||
local mallocTime = ARGV[5]
|
||||
if redis.call("EXISTS", key) == 0 then
|
||||
redis.call("HSET", key, "CURR", curr_seq, "LAST", last_seq)
|
||||
redis.call("HSET", key, "CURR", curr_seq, "LAST", last_seq, "TIME", mallocTime)
|
||||
redis.call("EXPIRE", key, dataSecond)
|
||||
return 1
|
||||
end
|
||||
@@ -147,11 +230,11 @@ if redis.call("HGET", key, "LOCK") ~= lockValue then
|
||||
return 2
|
||||
end
|
||||
redis.call("HDEL", key, "LOCK")
|
||||
redis.call("HSET", key, "CURR", curr_seq, "LAST", last_seq)
|
||||
redis.call("HSET", key, "CURR", curr_seq, "LAST", last_seq, "TIME", mallocTime)
|
||||
redis.call("EXPIRE", key, dataSecond)
|
||||
return 0
|
||||
`
|
||||
result, err := s.rdb.Eval(ctx, script, []string{key}, owner, int64(s.dataTime/time.Second), currSeq, lastSeq).Int64()
|
||||
result, err := s.rdb.Eval(ctx, script, []string{key}, owner, int64(s.dataTime/time.Second), currSeq, lastSeq, mill).Int64()
|
||||
if err != nil {
|
||||
return 0, errs.Wrap(err)
|
||||
}
|
||||
@@ -169,6 +252,7 @@ local key = KEYS[1]
|
||||
local size = tonumber(ARGV[1])
|
||||
local lockSecond = ARGV[2]
|
||||
local dataSecond = ARGV[3]
|
||||
local mallocTime = ARGV[4]
|
||||
local result = {}
|
||||
if redis.call("EXISTS", key) == 0 then
|
||||
local lockValue = math.random(0, 999999999)
|
||||
@@ -176,6 +260,7 @@ if redis.call("EXISTS", key) == 0 then
|
||||
redis.call("EXPIRE", key, lockSecond)
|
||||
table.insert(result, 1)
|
||||
table.insert(result, lockValue)
|
||||
table.insert(result, mallocTime)
|
||||
return result
|
||||
end
|
||||
if redis.call("HEXISTS", key, "LOCK") == 1 then
|
||||
@@ -189,6 +274,12 @@ if size == 0 then
|
||||
table.insert(result, 0)
|
||||
table.insert(result, curr_seq)
|
||||
table.insert(result, last_seq)
|
||||
local setTime = redis.call("HGET", key, "TIME")
|
||||
if setTime then
|
||||
table.insert(result, setTime)
|
||||
else
|
||||
table.insert(result, 0)
|
||||
end
|
||||
return result
|
||||
end
|
||||
local max_seq = curr_seq + size
|
||||
@@ -196,21 +287,25 @@ if max_seq > last_seq then
|
||||
local lockValue = math.random(0, 999999999)
|
||||
redis.call("HSET", key, "LOCK", lockValue)
|
||||
redis.call("HSET", key, "CURR", last_seq)
|
||||
redis.call("HSET", key, "TIME", mallocTime)
|
||||
redis.call("EXPIRE", key, lockSecond)
|
||||
table.insert(result, 3)
|
||||
table.insert(result, curr_seq)
|
||||
table.insert(result, last_seq)
|
||||
table.insert(result, lockValue)
|
||||
table.insert(result, mallocTime)
|
||||
return result
|
||||
end
|
||||
redis.call("HSET", key, "CURR", max_seq)
|
||||
redis.call("HSET", key, "TIME", ARGV[4])
|
||||
redis.call("EXPIRE", key, dataSecond)
|
||||
table.insert(result, 0)
|
||||
table.insert(result, curr_seq)
|
||||
table.insert(result, last_seq)
|
||||
table.insert(result, mallocTime)
|
||||
return result
|
||||
`
|
||||
result, err := s.rdb.Eval(ctx, script, []string{key}, size, int64(s.lockTime/time.Second), int64(s.dataTime/time.Second)).Int64Slice()
|
||||
result, err := s.rdb.Eval(ctx, script, []string{key}, size, int64(s.lockTime/time.Second), int64(s.dataTime/time.Second), time.Now().UnixMilli()).Int64Slice()
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
@@ -228,9 +323,9 @@ func (s *seqConversationCacheRedis) wait(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) setSeqRetry(ctx context.Context, key string, owner int64, currSeq int64, lastSeq int64) {
|
||||
func (s *seqConversationCacheRedis) setSeqRetry(ctx context.Context, key string, owner int64, currSeq int64, lastSeq int64, mill int64) {
|
||||
for i := 0; i < 10; i++ {
|
||||
state, err := s.setSeq(ctx, key, owner, currSeq, lastSeq)
|
||||
state, err := s.setSeq(ctx, key, owner, currSeq, lastSeq, mill)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "set seq cache failed", err, "key", key, "owner", owner, "currSeq", currSeq, "lastSeq", lastSeq, "count", i+1)
|
||||
if err := s.wait(ctx); err != nil {
|
||||
@@ -267,60 +362,74 @@ func (s *seqConversationCacheRedis) getMallocSize(conversationID string, size in
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) Malloc(ctx context.Context, conversationID string, size int64) (int64, error) {
|
||||
seq, _, err := s.mallocTime(ctx, conversationID, size)
|
||||
return seq, err
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) mallocTime(ctx context.Context, conversationID string, size int64) (int64, int64, error) {
|
||||
if size < 0 {
|
||||
return 0, errs.New("size must be greater than 0")
|
||||
return 0, 0, errs.New("size must be greater than 0")
|
||||
}
|
||||
key := s.getSeqMallocKey(conversationID)
|
||||
for i := 0; i < 10; i++ {
|
||||
states, err := s.malloc(ctx, key, size)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, 0, err
|
||||
}
|
||||
switch states[0] {
|
||||
case 0: // success
|
||||
return states[1], nil
|
||||
return states[1], states[3], nil
|
||||
case 1: // not found
|
||||
mallocSize := s.getMallocSize(conversationID, size)
|
||||
seq, err := s.mgo.Malloc(ctx, conversationID, mallocSize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, 0, err
|
||||
}
|
||||
s.setSeqRetry(ctx, key, states[1], seq+size, seq+mallocSize)
|
||||
return seq, nil
|
||||
s.setSeqRetry(ctx, key, states[1], seq+size, seq+mallocSize, states[2])
|
||||
return seq, 0, nil
|
||||
case 2: // locked
|
||||
if err := s.wait(ctx); err != nil {
|
||||
return 0, err
|
||||
return 0, 0, err
|
||||
}
|
||||
continue
|
||||
case 3: // exceeded cache max value
|
||||
currSeq := states[1]
|
||||
lastSeq := states[2]
|
||||
mill := states[4]
|
||||
mallocSize := s.getMallocSize(conversationID, size)
|
||||
seq, err := s.mgo.Malloc(ctx, conversationID, mallocSize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
return 0, 0, err
|
||||
}
|
||||
if lastSeq == seq {
|
||||
s.setSeqRetry(ctx, key, states[3], currSeq+size, seq+mallocSize)
|
||||
return currSeq, nil
|
||||
s.setSeqRetry(ctx, key, states[3], currSeq+size, seq+mallocSize, mill)
|
||||
return currSeq, states[4], nil
|
||||
} else {
|
||||
log.ZWarn(ctx, "malloc seq not equal cache last seq", nil, "conversationID", conversationID, "currSeq", currSeq, "lastSeq", lastSeq, "mallocSeq", seq)
|
||||
s.setSeqRetry(ctx, key, states[3], seq+size, seq+mallocSize)
|
||||
return seq, nil
|
||||
s.setSeqRetry(ctx, key, states[3], seq+size, seq+mallocSize, mill)
|
||||
return seq, mill, nil
|
||||
}
|
||||
default:
|
||||
log.ZError(ctx, "malloc seq unknown state", nil, "state", states[0], "conversationID", conversationID, "size", size)
|
||||
return 0, errs.New(fmt.Sprintf("unknown state: %d", states[0]))
|
||||
return 0, 0, errs.New(fmt.Sprintf("unknown state: %d", states[0]))
|
||||
}
|
||||
}
|
||||
log.ZError(ctx, "malloc seq retrying still failed", nil, "conversationID", conversationID, "size", size)
|
||||
return 0, errs.New("malloc seq waiting for lock timeout", "conversationID", conversationID, "size", size)
|
||||
return 0, 0, errs.New("malloc seq waiting for lock timeout", "conversationID", conversationID, "size", size)
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
|
||||
return s.Malloc(ctx, conversationID, 0)
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) GetMaxSeqWithTime(ctx context.Context, conversationID string) (database.SeqTime, error) {
|
||||
seq, mill, err := s.mallocTime(ctx, conversationID, 0)
|
||||
if err != nil {
|
||||
return database.SeqTime{}, err
|
||||
}
|
||||
return database.SeqTime{Seq: seq, Time: mill}, nil
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) SetMinSeqs(ctx context.Context, seqs map[string]int64) error {
|
||||
keys := make([]string, 0, len(seqs))
|
||||
for conversationID, seq := range seqs {
|
||||
@@ -331,3 +440,80 @@ func (s *seqConversationCacheRedis) SetMinSeqs(ctx context.Context, seqs map[str
|
||||
}
|
||||
return DeleteCacheBySlot(ctx, s.rocks, keys)
|
||||
}
|
||||
|
||||
// GetCacheMaxSeqWithTime only get the existing cache, if there is no cache, no cache will be generated
|
||||
func (s *seqConversationCacheRedis) GetCacheMaxSeqWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error) {
|
||||
if len(conversationIDs) == 0 {
|
||||
return map[string]database.SeqTime{}, nil
|
||||
}
|
||||
key2conversationID := make(map[string]string)
|
||||
keys := make([]string, 0, len(conversationIDs))
|
||||
for _, conversationID := range conversationIDs {
|
||||
key := s.getSeqMallocKey(conversationID)
|
||||
if _, ok := key2conversationID[key]; ok {
|
||||
continue
|
||||
}
|
||||
key2conversationID[key] = conversationID
|
||||
keys = append(keys, key)
|
||||
}
|
||||
slotKeys, err := groupKeysBySlot(ctx, s.rdb, keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := make(map[string]database.SeqTime)
|
||||
for _, keys := range slotKeys {
|
||||
if len(keys) == 0 {
|
||||
continue
|
||||
}
|
||||
pipe := s.rdb.Pipeline()
|
||||
cmds := make([]*redis.SliceCmd, 0, len(keys))
|
||||
for _, key := range keys {
|
||||
cmds = append(cmds, pipe.HMGet(ctx, key, "CURR", "TIME"))
|
||||
}
|
||||
if _, err := pipe.Exec(ctx); err != nil {
|
||||
return nil, errs.Wrap(err)
|
||||
}
|
||||
for i, cmd := range cmds {
|
||||
val, err := cmd.Result()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(val) != 2 {
|
||||
return nil, errs.WrapMsg(err, "GetCacheMaxSeqWithTime invalid result", "key", keys[i], "res", val)
|
||||
}
|
||||
if val[0] == nil {
|
||||
continue
|
||||
}
|
||||
seq, err := s.parseInt64(val[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mill, err := s.parseInt64(val[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
conversationID := key2conversationID[keys[i]]
|
||||
res[conversationID] = database.SeqTime{Seq: seq, Time: mill}
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (s *seqConversationCacheRedis) parseInt64(val any) (int64, error) {
|
||||
switch v := val.(type) {
|
||||
case nil:
|
||||
return 0, nil
|
||||
case int:
|
||||
return int64(v), nil
|
||||
case int64:
|
||||
return v, nil
|
||||
case string:
|
||||
res, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil {
|
||||
return 0, errs.WrapMsg(err, "invalid string not int64", "value", v)
|
||||
}
|
||||
return res, nil
|
||||
default:
|
||||
return 0, errs.New("invalid result not int64", "resType", fmt.Sprintf("%T", v), "value", v)
|
||||
}
|
||||
}
|
||||
|
||||
+36
-2
@@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func newTestSeq() *seqConversationCacheRedis {
|
||||
mgocli, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://openIM:openIM123@172.16.8.48:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second))
|
||||
mgocli, err := mongo.Connect(context.Background(), options.Client().ApplyURI("mongodb://openIM:openIM123@127.0.0.1:37017/openim_v3?maxPoolSize=100").SetConnectTimeout(5*time.Second))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -23,7 +23,7 @@ func newTestSeq() *seqConversationCacheRedis {
|
||||
panic(err)
|
||||
}
|
||||
opt := &redis.Options{
|
||||
Addr: "172.16.8.48:16379",
|
||||
Addr: "127.0.0.1:16379",
|
||||
Password: "openIM123",
|
||||
DB: 1,
|
||||
}
|
||||
@@ -107,3 +107,37 @@ func TestMinSeq(t *testing.T) {
|
||||
ts := newTestSeq()
|
||||
t.Log(ts.GetMinSeq(context.Background(), "10000000"))
|
||||
}
|
||||
|
||||
func TestMalloc(t *testing.T) {
|
||||
ts := newTestSeq()
|
||||
t.Log(ts.mallocTime(context.Background(), "10000000", 100))
|
||||
}
|
||||
|
||||
func TestHMGET(t *testing.T) {
|
||||
ts := newTestSeq()
|
||||
res, err := ts.GetCacheMaxSeqWithTime(context.Background(), []string{"10000000", "123456"})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
t.Log(res)
|
||||
}
|
||||
|
||||
func TestGetMaxSeqWithTime(t *testing.T) {
|
||||
ts := newTestSeq()
|
||||
t.Log(ts.GetMaxSeqWithTime(context.Background(), "10000000"))
|
||||
}
|
||||
|
||||
func TestGetMaxSeqWithTime1(t *testing.T) {
|
||||
ts := newTestSeq()
|
||||
t.Log(ts.GetMaxSeqsWithTime(context.Background(), []string{"10000000", "12345", "111"}))
|
||||
}
|
||||
|
||||
//
|
||||
//func TestHMGET(t *testing.T) {
|
||||
// ts := newTestSeq()
|
||||
// res, err := ts.rdb.HMGet(context.Background(), "MALLOC_SEQ:1", "CURR", "TIME1").Result()
|
||||
// if err != nil {
|
||||
// panic(err)
|
||||
// }
|
||||
// t.Log(res)
|
||||
//}
|
||||
|
||||
+56
-17
@@ -1,17 +1,3 @@
|
||||
// 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 redis
|
||||
|
||||
import (
|
||||
@@ -19,8 +5,9 @@ import (
|
||||
"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/tools/errs"
|
||||
"github.com/openimsdk/tools/utils/stringutil"
|
||||
"github.com/redis/go-redis/v9"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -58,12 +45,52 @@ func (c *tokenCache) GetTokensWithoutError(ctx context.Context, userID string, p
|
||||
}
|
||||
mm := make(map[string]int)
|
||||
for k, v := range m {
|
||||
mm[k] = stringutil.StringToInt(v)
|
||||
state, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return nil, errs.WrapMsg(err, "redis token value is not int", "value", v, "userID", userID, "platformID", platformID)
|
||||
}
|
||||
mm[k] = state
|
||||
}
|
||||
|
||||
return mm, nil
|
||||
}
|
||||
|
||||
func (c *tokenCache) GetAllTokensWithoutError(ctx context.Context, userID string) (map[int]map[string]int, error) {
|
||||
var (
|
||||
res = make(map[int]map[string]int)
|
||||
resLock = sync.Mutex{}
|
||||
)
|
||||
|
||||
keys := cachekey.GetAllPlatformTokenKey(userID)
|
||||
if err := ProcessKeysBySlot(ctx, c.rdb, keys, func(ctx context.Context, slot int64, keys []string) error {
|
||||
pipe := c.rdb.Pipeline()
|
||||
mapRes := make([]*redis.MapStringStringCmd, len(keys))
|
||||
for i, key := range keys {
|
||||
mapRes[i] = pipe.HGetAll(ctx, key)
|
||||
}
|
||||
_, err := pipe.Exec(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, m := range mapRes {
|
||||
mm := make(map[string]int)
|
||||
for k, v := range m.Val() {
|
||||
state, err := strconv.Atoi(v)
|
||||
if err != nil {
|
||||
return errs.WrapMsg(err, "redis token value is not int", "value", v, "userID", userID)
|
||||
}
|
||||
mm[k] = state
|
||||
}
|
||||
resLock.Lock()
|
||||
res[cachekey.GetPlatformIDByTokenKey(keys[i])] = mm
|
||||
resLock.Unlock()
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error {
|
||||
mm := make(map[string]any)
|
||||
for k, v := range m {
|
||||
@@ -72,6 +99,18 @@ func (c *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, pla
|
||||
return errs.Wrap(c.rdb.HSet(ctx, cachekey.GetTokenKey(userID, platformID), mm).Err())
|
||||
}
|
||||
|
||||
func (c *tokenCache) BatchSetTokenMapByUidPid(ctx context.Context, tokens map[string]map[string]int) error {
|
||||
pipe := c.rdb.Pipeline()
|
||||
for k, v := range tokens {
|
||||
pipe.HSet(ctx, k, v)
|
||||
}
|
||||
_, err := pipe.Exec(ctx)
|
||||
if err != nil {
|
||||
return errs.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,6 +1,9 @@
|
||||
package cache
|
||||
|
||||
import "context"
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
)
|
||||
|
||||
type SeqConversationCache interface {
|
||||
Malloc(ctx context.Context, conversationID string, size int64) (int64, error)
|
||||
@@ -9,4 +12,7 @@ type SeqConversationCache interface {
|
||||
GetMinSeq(ctx context.Context, conversationID string) (int64, error)
|
||||
GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error)
|
||||
SetMinSeqs(ctx context.Context, seqs map[string]int64) error
|
||||
GetCacheMaxSeqWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error)
|
||||
GetMaxSeqsWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error)
|
||||
GetMaxSeqWithTime(ctx context.Context, conversationID string) (database.SeqTime, error)
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -9,6 +9,8 @@ type TokenModel interface {
|
||||
// SetTokenFlagEx set token and flag with expire time
|
||||
SetTokenFlagEx(ctx context.Context, userID string, platformID int, token string, flag int) error
|
||||
GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error)
|
||||
GetAllTokensWithoutError(ctx context.Context, userID string) (map[int]map[string]int, error)
|
||||
SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error
|
||||
BatchSetTokenMapByUidPid(ctx context.Context, tokens map[string]map[string]int) error
|
||||
DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error
|
||||
}
|
||||
|
||||
@@ -1,17 +1,3 @@
|
||||
// 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 controller
|
||||
|
||||
import (
|
||||
@@ -19,9 +5,12 @@ import (
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
"github.com/openimsdk/tools/errs"
|
||||
"github.com/openimsdk/tools/log"
|
||||
"github.com/openimsdk/tools/tokenverify"
|
||||
)
|
||||
|
||||
@@ -31,17 +20,30 @@ type AuthDatabase interface {
|
||||
// Create token
|
||||
CreateToken(ctx context.Context, userID string, platformID int) (string, error)
|
||||
|
||||
BatchSetTokenMapByUidPid(ctx context.Context, tokens []string) error
|
||||
|
||||
SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error
|
||||
}
|
||||
|
||||
type multiLoginConfig struct {
|
||||
Policy int
|
||||
MaxNumOneEnd int
|
||||
}
|
||||
|
||||
type authDatabase struct {
|
||||
cache cache.TokenModel
|
||||
accessSecret string
|
||||
accessExpire int64
|
||||
multiLogin multiLoginConfig
|
||||
adminUserIDs []string
|
||||
}
|
||||
|
||||
func NewAuthDatabase(cache cache.TokenModel, accessSecret string, accessExpire int64) AuthDatabase {
|
||||
return &authDatabase{cache: cache, accessSecret: accessSecret, accessExpire: accessExpire}
|
||||
func NewAuthDatabase(cache cache.TokenModel, accessSecret string, accessExpire int64, multiLogin config.MultiLogin, adminUserIDs []string) AuthDatabase {
|
||||
return &authDatabase{cache: cache, accessSecret: accessSecret, accessExpire: accessExpire, multiLogin: multiLoginConfig{
|
||||
Policy: multiLogin.Policy,
|
||||
MaxNumOneEnd: multiLogin.MaxNumOneEnd,
|
||||
}, adminUserIDs: adminUserIDs,
|
||||
}
|
||||
}
|
||||
|
||||
// If the result is empty.
|
||||
@@ -53,24 +55,57 @@ func (a *authDatabase) SetTokenMapByUidPid(ctx context.Context, userID string, p
|
||||
return a.cache.SetTokenMapByUidPid(ctx, userID, platformID, m)
|
||||
}
|
||||
|
||||
// Create Token.
|
||||
func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformID int) (string, error) {
|
||||
tokens, err := a.cache.GetTokensWithoutError(ctx, userID, platformID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var deleteTokenKey []string
|
||||
for k, v := range tokens {
|
||||
_, err = tokenverify.GetClaimFromToken(k, authverify.Secret(a.accessSecret))
|
||||
if err != nil || v != constant.NormalToken {
|
||||
deleteTokenKey = append(deleteTokenKey, k)
|
||||
func (a *authDatabase) BatchSetTokenMapByUidPid(ctx context.Context, tokens []string) error {
|
||||
setMap := make(map[string]map[string]int)
|
||||
for _, token := range tokens {
|
||||
claims, err := tokenverify.GetClaimFromToken(token, authverify.Secret(a.accessSecret))
|
||||
key := cachekey.GetTokenKey(claims.UserID, claims.PlatformID)
|
||||
if err != nil {
|
||||
continue
|
||||
} else {
|
||||
if v, ok := setMap[key]; ok {
|
||||
v[token] = constant.KickedToken
|
||||
} else {
|
||||
setMap[key] = map[string]int{
|
||||
token: constant.KickedToken,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(deleteTokenKey) != 0 {
|
||||
err = a.cache.DeleteTokenByUidPid(ctx, userID, platformID, deleteTokenKey)
|
||||
if err := a.cache.BatchSetTokenMapByUidPid(ctx, setMap); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create Token.
|
||||
func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformID int) (string, error) {
|
||||
isAdmin := authverify.IsManagerUserID(userID, a.adminUserIDs)
|
||||
if !isAdmin {
|
||||
tokens, err := a.cache.GetAllTokensWithoutError(ctx, userID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
deleteTokenKey, kickedTokenKey, err := a.checkToken(ctx, tokens, platformID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(deleteTokenKey) != 0 {
|
||||
err = a.cache.DeleteTokenByUidPid(ctx, userID, platformID, deleteTokenKey)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if len(kickedTokenKey) != 0 {
|
||||
for _, k := range kickedTokenKey {
|
||||
err := a.cache.SetTokenFlagEx(ctx, userID, platformID, k, constant.KickedToken)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
log.ZDebug(ctx, "kicked token in create token", "token", k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
claims := tokenverify.BuildClaims(userID, platformID, a.accessExpire)
|
||||
@@ -80,8 +115,124 @@ func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformI
|
||||
return "", errs.WrapMsg(err, "token.SignedString")
|
||||
}
|
||||
|
||||
if err = a.cache.SetTokenFlagEx(ctx, userID, platformID, tokenString, constant.NormalToken); err != nil {
|
||||
return "", err
|
||||
if !isAdmin {
|
||||
if err = a.cache.SetTokenFlagEx(ctx, userID, platformID, tokenString, constant.NormalToken); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return tokenString, nil
|
||||
}
|
||||
|
||||
func (a *authDatabase) checkToken(ctx context.Context, tokens map[int]map[string]int, platformID int) ([]string, []string, error) {
|
||||
// todo: Move the logic for handling old data to another location.
|
||||
var (
|
||||
loginTokenMap = make(map[int][]string) // The length of the value of the map must be greater than 0
|
||||
deleteToken = make([]string, 0)
|
||||
kickToken = make([]string, 0)
|
||||
adminToken = make([]string, 0)
|
||||
unkickTerminal = ""
|
||||
)
|
||||
|
||||
for plfID, tks := range tokens {
|
||||
for k, v := range tks {
|
||||
_, err := tokenverify.GetClaimFromToken(k, authverify.Secret(a.accessSecret))
|
||||
if err != nil || v != constant.NormalToken {
|
||||
deleteToken = append(deleteToken, k)
|
||||
} else {
|
||||
if plfID != constant.AdminPlatformID {
|
||||
loginTokenMap[plfID] = append(loginTokenMap[plfID], k)
|
||||
} else {
|
||||
adminToken = append(adminToken, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
switch a.multiLogin.Policy {
|
||||
case constant.DefalutNotKick:
|
||||
for plt, ts := range loginTokenMap {
|
||||
l := len(ts)
|
||||
if platformID == plt {
|
||||
l++
|
||||
}
|
||||
limit := a.multiLogin.MaxNumOneEnd
|
||||
if l > limit {
|
||||
kickToken = append(kickToken, ts[:l-limit]...)
|
||||
}
|
||||
}
|
||||
case constant.AllLoginButSameTermKick:
|
||||
for plt, ts := range loginTokenMap {
|
||||
kickToken = append(kickToken, ts[:len(ts)-1]...)
|
||||
if plt == platformID {
|
||||
kickToken = append(kickToken, ts[len(ts)-1])
|
||||
}
|
||||
}
|
||||
case constant.PCAndOther:
|
||||
unkickTerminal = constant.TerminalPC
|
||||
if constant.PlatformIDToClass(platformID) != unkickTerminal {
|
||||
for plt, ts := range loginTokenMap {
|
||||
if constant.PlatformIDToClass(plt) != unkickTerminal {
|
||||
kickToken = append(kickToken, ts...)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var (
|
||||
preKick []string
|
||||
isReserve = true
|
||||
)
|
||||
for plt, ts := range loginTokenMap {
|
||||
if constant.PlatformIDToClass(plt) != unkickTerminal {
|
||||
// Keep a token from another end
|
||||
if isReserve {
|
||||
isReserve = false
|
||||
kickToken = append(kickToken, ts[:len(ts)-1]...)
|
||||
preKick = append(preKick, ts[len(ts)-1])
|
||||
continue
|
||||
} else {
|
||||
// Prioritize keeping Android
|
||||
if plt == constant.AndroidPlatformID {
|
||||
kickToken = append(kickToken, preKick...)
|
||||
kickToken = append(kickToken, ts[:len(ts)-1]...)
|
||||
} else {
|
||||
kickToken = append(kickToken, ts...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case constant.AllLoginButSameClassKick:
|
||||
var (
|
||||
reserved = make(map[string]struct{})
|
||||
)
|
||||
|
||||
for plt, ts := range loginTokenMap {
|
||||
if constant.PlatformIDToClass(plt) == constant.PlatformIDToClass(platformID) {
|
||||
kickToken = append(kickToken, ts...)
|
||||
} else {
|
||||
if _, ok := reserved[constant.PlatformIDToClass(plt)]; !ok {
|
||||
reserved[constant.PlatformIDToClass(plt)] = struct{}{}
|
||||
kickToken = append(kickToken, ts[:len(ts)-1]...)
|
||||
continue
|
||||
} else {
|
||||
kickToken = append(kickToken, ts...)
|
||||
}
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, nil, errs.New("unknown multiLogin policy").Wrap()
|
||||
}
|
||||
|
||||
//var adminTokenMaxNum = a.multiLogin.MaxNumOneEnd
|
||||
//if a.multiLogin.Policy == constant.Customize {
|
||||
// adminTokenMaxNum = a.multiLogin.CustomizeLoginNum[constant.AdminPlatformID]
|
||||
//}
|
||||
//l := len(adminToken)
|
||||
//if platformID == constant.AdminPlatformID {
|
||||
// l++
|
||||
//}
|
||||
//if l > adminTokenMaxNum {
|
||||
// kickToken = append(kickToken, adminToken[:l-adminTokenMaxNum]...)
|
||||
//}
|
||||
return deleteToken, kickToken, nil
|
||||
}
|
||||
|
||||
@@ -16,9 +16,10 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||
@@ -69,6 +70,10 @@ type ConversationDatabase interface {
|
||||
FindConversationUserVersion(ctx context.Context, userID string, version uint, limit int) (*relationtb.VersionLog, error)
|
||||
FindMaxConversationUserVersionCache(ctx context.Context, userID string) (*relationtb.VersionLog, error)
|
||||
GetOwnerConversation(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (int64, []*relationtb.Conversation, error)
|
||||
// GetNotNotifyConversationIDs gets not notify conversationIDs by userID
|
||||
GetNotNotifyConversationIDs(ctx context.Context, userID string) ([]string, error)
|
||||
// GetPinnedConversationIDs gets pinned conversationIDs by userID
|
||||
GetPinnedConversationIDs(ctx context.Context, userID string) ([]string, error)
|
||||
}
|
||||
|
||||
func NewConversationDatabase(conversation database.Conversation, cache cache.ConversationCache, tx tx.Tx) ConversationDatabase {
|
||||
@@ -108,6 +113,10 @@ func (c *conversationDatabase) SetUsersConversationFieldTx(ctx context.Context,
|
||||
}
|
||||
if _, ok := fieldMap["recv_msg_opt"]; ok {
|
||||
cache = cache.DelConversationNotReceiveMessageUserIDs(conversation.ConversationID)
|
||||
cache = cache.DelConversationNotNotifyMessageUserIDs(userIDs...)
|
||||
}
|
||||
if _, ok := fieldMap["is_pinned"]; ok {
|
||||
cache = cache.DelConversationPinnedMessageUserIDs(userIDs...)
|
||||
}
|
||||
cache = cache.DelConversationVersionUserIDs(haveUserIDs...)
|
||||
}
|
||||
@@ -144,6 +153,10 @@ func (c *conversationDatabase) UpdateUsersConversationField(ctx context.Context,
|
||||
cache = cache.DelUsersConversation(conversationID, userIDs...).DelConversationVersionUserIDs(userIDs...)
|
||||
if _, ok := args["recv_msg_opt"]; ok {
|
||||
cache = cache.DelConversationNotReceiveMessageUserIDs(conversationID)
|
||||
cache = cache.DelConversationNotNotifyMessageUserIDs(userIDs...)
|
||||
}
|
||||
if _, ok := args["is_pinned"]; ok {
|
||||
cache = cache.DelConversationPinnedMessageUserIDs(userIDs...)
|
||||
}
|
||||
return cache.ChainExecDel(ctx)
|
||||
}
|
||||
@@ -152,21 +165,37 @@ func (c *conversationDatabase) CreateConversation(ctx context.Context, conversat
|
||||
if err := c.conversationDB.Create(ctx, conversations); err != nil {
|
||||
return err
|
||||
}
|
||||
var userIDs []string
|
||||
var (
|
||||
userIDs []string
|
||||
notNotifyUserIDs []string
|
||||
pinnedUserIDs []string
|
||||
)
|
||||
|
||||
cache := c.cache.CloneConversationCache()
|
||||
for _, conversation := range conversations {
|
||||
cache = cache.DelConversations(conversation.OwnerUserID, conversation.ConversationID)
|
||||
cache = cache.DelConversationNotReceiveMessageUserIDs(conversation.ConversationID)
|
||||
userIDs = append(userIDs, conversation.OwnerUserID)
|
||||
if conversation.RecvMsgOpt == constant.ReceiveNotNotifyMessage {
|
||||
notNotifyUserIDs = append(notNotifyUserIDs, conversation.OwnerUserID)
|
||||
}
|
||||
if conversation.IsPinned == true {
|
||||
pinnedUserIDs = append(pinnedUserIDs, conversation.OwnerUserID)
|
||||
}
|
||||
}
|
||||
return cache.DelConversationIDs(userIDs...).DelUserConversationIDsHash(userIDs...).DelConversationVersionUserIDs(userIDs...).ChainExecDel(ctx)
|
||||
return cache.DelConversationIDs(userIDs...).
|
||||
DelUserConversationIDsHash(userIDs...).
|
||||
DelConversationVersionUserIDs(userIDs...).
|
||||
DelConversationNotNotifyMessageUserIDs(notNotifyUserIDs...).
|
||||
DelConversationPinnedMessageUserIDs(pinnedUserIDs...).
|
||||
ChainExecDel(ctx)
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) SyncPeerUserPrivateConversationTx(ctx context.Context, conversations []*relationtb.Conversation) error {
|
||||
return c.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
cache := c.cache.CloneConversationCache()
|
||||
for _, conversation := range conversations {
|
||||
cache = cache.DelConversationVersionUserIDs(conversation.OwnerUserID)
|
||||
cache = cache.DelConversationVersionUserIDs(conversation.OwnerUserID, conversation.UserID)
|
||||
for _, v := range [][2]string{{conversation.OwnerUserID, conversation.UserID}, {conversation.UserID, conversation.OwnerUserID}} {
|
||||
ownerUserID := v[0]
|
||||
userID := v[1]
|
||||
@@ -212,7 +241,10 @@ func (c *conversationDatabase) GetUserAllConversation(ctx context.Context, owner
|
||||
func (c *conversationDatabase) SetUserConversations(ctx context.Context, ownerUserID string, conversations []*relationtb.Conversation) error {
|
||||
return c.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
cache := c.cache.CloneConversationCache()
|
||||
cache = cache.DelConversationVersionUserIDs(ownerUserID)
|
||||
cache = cache.DelConversationVersionUserIDs(ownerUserID).
|
||||
DelConversationNotNotifyMessageUserIDs(ownerUserID).
|
||||
DelConversationPinnedMessageUserIDs(ownerUserID)
|
||||
|
||||
groupIDs := datautil.Distinct(datautil.Filter(conversations, func(e *relationtb.Conversation) (string, bool) {
|
||||
return e.GroupID, e.GroupID != ""
|
||||
}))
|
||||
@@ -353,3 +385,19 @@ func (c *conversationDatabase) GetOwnerConversation(ctx context.Context, ownerUs
|
||||
}
|
||||
return int64(len(conversationIDs)), conversations, nil
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetNotNotifyConversationIDs(ctx context.Context, userID string) ([]string, error) {
|
||||
conversationIDs, err := c.cache.GetUserNotNotifyConversationIDs(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conversationIDs, nil
|
||||
}
|
||||
|
||||
func (c *conversationDatabase) GetPinnedConversationIDs(ctx context.Context, userID string) ([]string, error) {
|
||||
conversationIDs, err := c.cache.GetPinnedConversationIDs(ctx, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conversationIDs, nil
|
||||
}
|
||||
|
||||
@@ -74,6 +74,10 @@ type CommonMsgDatabase interface {
|
||||
GetHasReadSeq(ctx context.Context, userID string, conversationID string) (int64, error)
|
||||
UserSetHasReadSeqs(ctx context.Context, userID string, hasReadSeqs map[string]int64) error
|
||||
|
||||
GetMaxSeqsWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error)
|
||||
GetMaxSeqWithTime(ctx context.Context, conversationID string) (database.SeqTime, error)
|
||||
GetCacheMaxSeqWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error)
|
||||
|
||||
//GetMongoMaxAndMinSeq(ctx context.Context, conversationID string) (minSeqMongo, maxSeqMongo int64, err error)
|
||||
//GetConversationMinMaxSeqInMongoAndCache(ctx context.Context, conversationID string) (minSeqMongo, maxSeqMongo, minSeqCache, maxSeqCache int64, err error)
|
||||
SetSendMsgStatus(ctx context.Context, id string, status int32) error
|
||||
@@ -368,7 +372,7 @@ func (db *commonMsgDatabase) getMsgBySeqsRange(ctx context.Context, userID strin
|
||||
// This ensures that their message retrieval starts from the point they joined.
|
||||
func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID string, conversationID string, begin, end, num, userMaxSeq int64) (int64, int64, []*sdkws.MsgData, error) {
|
||||
userMinSeq, err := db.seqUser.GetUserMinSeq(ctx, conversationID, userID)
|
||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
||||
if err != nil && !errors.Is(err, redis.Nil) {
|
||||
return 0, 0, nil, err
|
||||
}
|
||||
minSeq, err := db.seqConversation.GetMinSeq(ctx, conversationID)
|
||||
@@ -439,6 +443,11 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
|
||||
return 0, 0, nil, err
|
||||
}
|
||||
successMsgs = append(mongoMsgs, successMsgs...)
|
||||
|
||||
//_, err = db.msg.SetMessagesToCache(ctx, conversationID, mongoMsgs)
|
||||
//if err != nil {
|
||||
// return 0, 0, nil, err
|
||||
//}
|
||||
}
|
||||
|
||||
return minSeq, maxSeq, successMsgs, nil
|
||||
@@ -446,7 +455,7 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
|
||||
|
||||
func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, conversationID string, seqs []int64) (int64, int64, []*sdkws.MsgData, error) {
|
||||
userMinSeq, err := db.seqUser.GetUserMinSeq(ctx, conversationID, userID)
|
||||
if err != nil && errs.Unwrap(err) != redis.Nil {
|
||||
if err != nil {
|
||||
return 0, 0, nil, err
|
||||
}
|
||||
minSeq, err := db.seqConversation.GetMinSeq(ctx, conversationID)
|
||||
@@ -457,18 +466,31 @@ func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, co
|
||||
if err != nil {
|
||||
return 0, 0, nil, err
|
||||
}
|
||||
if userMinSeq < minSeq {
|
||||
userMaxSeq, err := db.seqUser.GetUserMaxSeq(ctx, conversationID, userID)
|
||||
if err != nil {
|
||||
return 0, 0, nil, err
|
||||
}
|
||||
if userMinSeq > minSeq {
|
||||
minSeq = userMinSeq
|
||||
}
|
||||
var newSeqs []int64
|
||||
if userMaxSeq > 0 && userMaxSeq < maxSeq {
|
||||
maxSeq = userMaxSeq
|
||||
}
|
||||
newSeqs := make([]int64, 0, len(seqs))
|
||||
for _, seq := range seqs {
|
||||
if seq <= 0 {
|
||||
continue
|
||||
}
|
||||
if seq >= minSeq && seq <= maxSeq {
|
||||
newSeqs = append(newSeqs, seq)
|
||||
}
|
||||
}
|
||||
if len(newSeqs) == 0 {
|
||||
return minSeq, maxSeq, nil, nil
|
||||
}
|
||||
successMsgs, failedSeqs, err := db.msg.GetMessagesBySeq(ctx, conversationID, newSeqs)
|
||||
if err != nil {
|
||||
if err != redis.Nil {
|
||||
if errors.Is(err, redis.Nil) {
|
||||
log.ZError(ctx, "get message from redis exception", err, "failedSeqs", failedSeqs, "conversationID", conversationID)
|
||||
}
|
||||
}
|
||||
@@ -483,6 +505,11 @@ func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, co
|
||||
}
|
||||
|
||||
successMsgs = append(successMsgs, mongoMsgs...)
|
||||
|
||||
//_, err = db.msg.SetMessagesToCache(ctx, conversationID, mongoMsgs)
|
||||
//if err != nil {
|
||||
// return 0, 0, nil, err
|
||||
//}
|
||||
}
|
||||
return minSeq, maxSeq, successMsgs, nil
|
||||
}
|
||||
@@ -853,3 +880,16 @@ func (db *commonMsgDatabase) setMinSeq(ctx context.Context, conversationID strin
|
||||
func (db *commonMsgDatabase) GetDocIDs(ctx context.Context) ([]string, error) {
|
||||
return db.msgDocDatabase.GetDocIDs(ctx)
|
||||
}
|
||||
|
||||
func (db *commonMsgDatabase) GetCacheMaxSeqWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error) {
|
||||
return db.seqConversation.GetCacheMaxSeqWithTime(ctx, conversationIDs)
|
||||
}
|
||||
|
||||
func (db *commonMsgDatabase) GetMaxSeqWithTime(ctx context.Context, conversationID string) (database.SeqTime, error) {
|
||||
return db.seqConversation.GetMaxSeqWithTime(ctx, conversationID)
|
||||
}
|
||||
|
||||
func (db *commonMsgDatabase) GetMaxSeqsWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error) {
|
||||
// todo: only the time in the redis cache will be taken, not the message time
|
||||
return db.seqConversation.GetMaxSeqsWithTime(ctx, conversationIDs)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package controller
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/openimsdk/protocol/constant"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||
@@ -23,8 +24,11 @@ type MsgTransferDatabase interface {
|
||||
DeleteMessagesFromCache(ctx context.Context, conversationID string, seqs []int64) error
|
||||
|
||||
// BatchInsertChat2Cache increments the sequence number and then batch inserts messages into the cache.
|
||||
BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNewConversation bool, err error)
|
||||
SetHasReadSeqToDB(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error
|
||||
BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNewConversation bool, userHasReadMap map[string]int64, err error)
|
||||
|
||||
SetHasReadSeqs(ctx context.Context, conversationID string, userSeqMap map[string]int64) error
|
||||
|
||||
SetHasReadSeqToDB(ctx context.Context, conversationID string, userSeqMap map[string]int64) error
|
||||
|
||||
// to mq
|
||||
MsgToPushMQ(ctx context.Context, key, conversationID string, msg2mq *sdkws.MsgData) (int32, int64, error)
|
||||
@@ -83,6 +87,9 @@ func (db *msgTransferDatabase) BatchInsertChat2DB(ctx context.Context, conversat
|
||||
IOSBadgeCount: msg.OfflinePushInfo.IOSBadgeCount,
|
||||
}
|
||||
}
|
||||
if msg.Status == constant.MsgStatusSending {
|
||||
msg.Status = constant.MsgStatusSendSuccess
|
||||
}
|
||||
msgs[i] = &model.MsgDataModel{
|
||||
SendID: msg.SendID,
|
||||
RecvID: msg.RecvID,
|
||||
@@ -215,18 +222,18 @@ func (db *msgTransferDatabase) DeleteMessagesFromCache(ctx context.Context, conv
|
||||
return db.msg.DeleteMessagesFromCache(ctx, conversationID, seqs)
|
||||
}
|
||||
|
||||
func (db *msgTransferDatabase) BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNew bool, err error) {
|
||||
func (db *msgTransferDatabase) BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNew bool, userHasReadMap map[string]int64, err error) {
|
||||
lenList := len(msgs)
|
||||
if int64(lenList) > db.msgTable.GetSingleGocMsgNum() {
|
||||
return 0, false, errs.New("message count exceeds limit", "limit", db.msgTable.GetSingleGocMsgNum()).Wrap()
|
||||
return 0, false, nil, errs.New("message count exceeds limit", "limit", db.msgTable.GetSingleGocMsgNum()).Wrap()
|
||||
}
|
||||
if lenList < 1 {
|
||||
return 0, false, errs.New("no messages to insert", "minCount", 1).Wrap()
|
||||
return 0, false, nil, errs.New("no messages to insert", "minCount", 1).Wrap()
|
||||
}
|
||||
currentMaxSeq, err := db.seqConversation.Malloc(ctx, conversationID, int64(len(msgs)))
|
||||
if err != nil {
|
||||
log.ZError(ctx, "storage.seq.Malloc", err)
|
||||
return 0, false, err
|
||||
return 0, false, nil, err
|
||||
}
|
||||
isNew = currentMaxSeq == 0
|
||||
lastMaxSeq := currentMaxSeq
|
||||
@@ -244,15 +251,10 @@ func (db *msgTransferDatabase) BatchInsertChat2Cache(ctx context.Context, conver
|
||||
} else {
|
||||
prommetrics.MsgInsertRedisSuccessCounter.Inc()
|
||||
}
|
||||
err = db.setHasReadSeqs(ctx, conversationID, userSeqMap)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "SetHasReadSeqs error", err, "userSeqMap", userSeqMap, "conversationID", conversationID)
|
||||
prommetrics.SeqSetFailedCounter.Inc()
|
||||
}
|
||||
return lastMaxSeq, isNew, errs.Wrap(err)
|
||||
return lastMaxSeq, isNew, userSeqMap, errs.Wrap(err)
|
||||
}
|
||||
|
||||
func (db *msgTransferDatabase) setHasReadSeqs(ctx context.Context, conversationID string, userSeqMap map[string]int64) error {
|
||||
func (db *msgTransferDatabase) SetHasReadSeqs(ctx context.Context, conversationID string, userSeqMap map[string]int64) error {
|
||||
for userID, seq := range userSeqMap {
|
||||
if err := db.seqUser.SetUserReadSeq(ctx, conversationID, userID, seq); err != nil {
|
||||
return err
|
||||
@@ -261,8 +263,13 @@ func (db *msgTransferDatabase) setHasReadSeqs(ctx context.Context, conversationI
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *msgTransferDatabase) SetHasReadSeqToDB(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error {
|
||||
return db.seqUser.SetUserReadSeqToDB(ctx, conversationID, userID, hasReadSeq)
|
||||
func (db *msgTransferDatabase) SetHasReadSeqToDB(ctx context.Context, conversationID string, userSeqMap map[string]int64) error {
|
||||
for userID, seq := range userSeqMap {
|
||||
if err := db.seqUser.SetUserReadSeqToDB(ctx, conversationID, userID, seq); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *msgTransferDatabase) MsgToPushMQ(ctx context.Context, key, conversationID string, msg2mq *sdkws.MsgData) (int32, int64, error) {
|
||||
|
||||
@@ -27,6 +27,8 @@ type Conversation interface {
|
||||
Find(ctx context.Context, ownerUserID string, conversationIDs []string) (conversations []*model.Conversation, err error)
|
||||
FindUserID(ctx context.Context, userIDs []string, conversationIDs []string) ([]string, error)
|
||||
FindUserIDAllConversationID(ctx context.Context, userID string) ([]string, error)
|
||||
FindUserIDAllNotNotifyConversationID(ctx context.Context, userID string) ([]string, error)
|
||||
FindUserIDAllPinnedConversationID(ctx context.Context, userID string) ([]string, error)
|
||||
Take(ctx context.Context, userID, conversationID string) (conversation *model.Conversation, err error)
|
||||
FindConversationID(ctx context.Context, userID string, conversationIDs []string) (existConversationID []string, err error)
|
||||
FindUserIDAllConversations(ctx context.Context, userID string) (conversations []*model.Conversation, err error)
|
||||
|
||||
@@ -124,6 +124,20 @@ func (c *ConversationMgo) FindUserIDAllConversationID(ctx context.Context, userI
|
||||
return mongoutil.Find[string](ctx, c.coll, bson.M{"owner_user_id": userID}, options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1}))
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) FindUserIDAllNotNotifyConversationID(ctx context.Context, userID string) ([]string, error) {
|
||||
return mongoutil.Find[string](ctx, c.coll, bson.M{
|
||||
"owner_user_id": userID,
|
||||
"recv_msg_opt": constant.ReceiveNotNotifyMessage,
|
||||
}, options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1}))
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) FindUserIDAllPinnedConversationID(ctx context.Context, userID string) ([]string, error) {
|
||||
return mongoutil.Find[string](ctx, c.coll, bson.M{
|
||||
"owner_user_id": userID,
|
||||
"is_pinned": true,
|
||||
}, options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1}))
|
||||
}
|
||||
|
||||
func (c *ConversationMgo) Take(ctx context.Context, userID, conversationID string) (conversation *model.Conversation, err error) {
|
||||
return mongoutil.FindOne[*model.Conversation](ctx, c.coll, bson.M{"owner_user_id": userID, "conversation_id": conversationID})
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user