mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-05-11 12:36:00 +08:00
Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15762172fe | |||
| 716e06f3ad | |||
| 3fe2053d4f | |||
| 5430bc4569 | |||
| 01c0d9ca89 | |||
| 3c6fbabded | |||
| 7f471c44bf | |||
| f3a78260a8 | |||
| be4061da85 | |||
| c62945ed05 | |||
| 7d2fd64429 | |||
| 1f91c756a4 | |||
| 573b400af6 | |||
| 22820fa189 | |||
| 645a5925bd | |||
| 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 |
@@ -1,6 +1,5 @@
|
|||||||
MONGO_IMAGE=mongo:6.0.2
|
MONGO_IMAGE=mongo:7.0
|
||||||
REDIS_IMAGE=redis:7.0.0
|
REDIS_IMAGE=redis:7.0.0
|
||||||
ZOOKEEPER_IMAGE=bitnami/zookeeper:3.8
|
|
||||||
KAFKA_IMAGE=bitnami/kafka:3.5.1
|
KAFKA_IMAGE=bitnami/kafka:3.5.1
|
||||||
MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z
|
MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z
|
||||||
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.13
|
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.13
|
||||||
@@ -9,11 +8,10 @@ ALERTMANAGER_IMAGE=prom/alertmanager:v0.27.0
|
|||||||
GRAFANA_IMAGE=grafana/grafana:11.0.1
|
GRAFANA_IMAGE=grafana/grafana:11.0.1
|
||||||
|
|
||||||
OPENIM_WEB_FRONT_IMAGE=openim/openim-web-front:release-v3.8.1
|
OPENIM_WEB_FRONT_IMAGE=openim/openim-web-front:release-v3.8.1
|
||||||
OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.8.2
|
OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.8.3
|
||||||
|
|
||||||
#FRONT_IMAGE: use aliyun images
|
#FRONT_IMAGE: use aliyun images
|
||||||
#OPENIM_WEB_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-web-front:release-v3.8.1
|
#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
|
#OPENIM_ADMIN_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-admin-front:release-v1.8.3
|
||||||
|
|
||||||
DATA_DIR=./
|
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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- '**/*.md'
|
- '**/*.md'
|
||||||
|
|
||||||
@@ -153,7 +149,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go_version: ["1.21"]
|
go_version: ["1.22"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Repository
|
- 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."
|
||||||
@@ -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
|
|
||||||
+4
-4
@@ -1,5 +1,5 @@
|
|||||||
# Use Go 1.21 Alpine as the base image for building the application
|
# Use Go 1.22 Alpine as the base image for building the application
|
||||||
FROM golang:1.21-alpine AS builder
|
FROM golang:1.22-alpine AS builder
|
||||||
|
|
||||||
# Define the base directory for the application as an environment variable
|
# Define the base directory for the application as an environment variable
|
||||||
ENV SERVER_DIR=/openim-server
|
ENV SERVER_DIR=/openim-server
|
||||||
@@ -8,7 +8,7 @@ ENV SERVER_DIR=/openim-server
|
|||||||
WORKDIR $SERVER_DIR
|
WORKDIR $SERVER_DIR
|
||||||
|
|
||||||
# Set the Go proxy to improve dependency resolution speed
|
# Set the Go proxy to improve dependency resolution speed
|
||||||
ENV GOPROXY=https://goproxy.io,direct
|
# ENV GOPROXY=https://goproxy.io,direct
|
||||||
|
|
||||||
# Copy all files from the current directory into the container
|
# Copy all files from the current directory into the container
|
||||||
COPY . .
|
COPY . .
|
||||||
@@ -22,7 +22,7 @@ RUN go install github.com/magefile/mage@v1.15.0
|
|||||||
RUN mage build
|
RUN mage build
|
||||||
|
|
||||||
# Using Alpine Linux with Go environment for the final image
|
# 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
|
# Install necessary packages, such as bash
|
||||||
RUN apk add --no-cache bash
|
RUN apk add --no-cache bash
|
||||||
|
|||||||
@@ -53,15 +53,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-cmdutils
|
- binary: openim-cmdutils
|
||||||
id: openim-cmdutils
|
id: openim-cmdutils
|
||||||
@@ -71,15 +64,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-crontask
|
- binary: openim-crontask
|
||||||
id: openim-crontask
|
id: openim-crontask
|
||||||
@@ -89,15 +75,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-msggateway
|
- binary: openim-msggateway
|
||||||
id: openim-msggateway
|
id: openim-msggateway
|
||||||
@@ -107,15 +86,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-msgtransfer
|
- binary: openim-msgtransfer
|
||||||
id: openim-msgtransfer
|
id: openim-msgtransfer
|
||||||
@@ -125,15 +97,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-push
|
- binary: openim-push
|
||||||
id: openim-push
|
id: openim-push
|
||||||
@@ -143,15 +108,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-rpc-auth
|
- binary: openim-rpc-auth
|
||||||
id: openim-rpc-auth
|
id: openim-rpc-auth
|
||||||
@@ -161,15 +119,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-rpc-conversation
|
- binary: openim-rpc-conversation
|
||||||
id: openim-rpc-conversation
|
id: openim-rpc-conversation
|
||||||
@@ -179,15 +130,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-rpc-friend
|
- binary: openim-rpc-friend
|
||||||
id: openim-rpc-friend
|
id: openim-rpc-friend
|
||||||
@@ -197,15 +141,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-rpc-group
|
- binary: openim-rpc-group
|
||||||
id: openim-rpc-group
|
id: openim-rpc-group
|
||||||
@@ -215,15 +152,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-rpc-msg
|
- binary: openim-rpc-msg
|
||||||
id: openim-rpc-msg
|
id: openim-rpc-msg
|
||||||
@@ -233,15 +163,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-rpc-third
|
- binary: openim-rpc-third
|
||||||
id: openim-rpc-third
|
id: openim-rpc-third
|
||||||
@@ -251,15 +174,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
- binary: openim-rpc-user
|
- binary: openim-rpc-user
|
||||||
id: openim-rpc-user
|
id: openim-rpc-user
|
||||||
@@ -269,15 +185,8 @@ builds:
|
|||||||
- windows
|
- windows
|
||||||
- linux
|
- linux
|
||||||
goarch:
|
goarch:
|
||||||
- s390x
|
|
||||||
- mips64
|
|
||||||
- mips64le
|
|
||||||
- amd64
|
- amd64
|
||||||
- ppc64le
|
|
||||||
- arm64
|
- arm64
|
||||||
goarm:
|
|
||||||
- "6"
|
|
||||||
- "7"
|
|
||||||
|
|
||||||
|
|
||||||
# TODO:Need a script, such as the init - release to help binary to find the right directory
|
# TODO:Need a script, such as the init - release to help binary to find the right directory
|
||||||
|
|||||||
@@ -15,10 +15,9 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
_ "net/http/pprof"
|
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/cmd"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/cmd"
|
||||||
"github.com/openimsdk/tools/system/program"
|
"github.com/openimsdk/tools/system/program"
|
||||||
|
_ "net/http/pprof"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -5,9 +5,4 @@ etcd:
|
|||||||
username: ''
|
username: ''
|
||||||
password: ''
|
password: ''
|
||||||
|
|
||||||
zookeeper:
|
|
||||||
schema: openim
|
|
||||||
address: [ localhost:12181 ]
|
|
||||||
username: ''
|
|
||||||
password: ''
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ database: openim_v3
|
|||||||
username: openIM
|
username: openIM
|
||||||
# Password for database authentication
|
# Password for database authentication
|
||||||
password: openIM123
|
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
|
# Maximum number of connections in the connection pool
|
||||||
maxPoolSize: 100
|
maxPoolSize: 100
|
||||||
# Maximum number of retry attempts for a failed database connection
|
# 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:
|
groupCreated:
|
||||||
isSendMsg: true
|
isSendMsg: true
|
||||||
# Reliability level of the message sending.
|
# Reliability level of the message sending.
|
||||||
@@ -309,9 +292,9 @@ userInfoUpdated:
|
|||||||
unreadCount: false
|
unreadCount: false
|
||||||
offlinePush:
|
offlinePush:
|
||||||
enable: true
|
enable: true
|
||||||
title: Remove a blocked user
|
title: userInfo updated
|
||||||
desc: Remove a blocked user
|
desc: userInfo updated
|
||||||
ext: Remove a blocked user
|
ext: userInfo updated
|
||||||
|
|
||||||
userStatusChanged:
|
userStatusChanged:
|
||||||
isSendMsg: false
|
isSendMsg: false
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
cronExecuteTime: 0 2 * * *
|
cronExecuteTime: 0 2 * * *
|
||||||
retainChatRecords: 365
|
retainChatRecords: 365
|
||||||
fileExpireTime: 90
|
fileExpireTime: 180
|
||||||
|
deleteObjectType: ["msg-picture","msg-file", "msg-voice","msg-video","msg-video-snapshot","sdklog"]
|
||||||
@@ -22,5 +22,3 @@ longConnSvr:
|
|||||||
websocketMaxMsgLen: 4096
|
websocketMaxMsgLen: 4096
|
||||||
# WebSocket connection handshake timeout in seconds
|
# WebSocket connection handshake timeout in seconds
|
||||||
websocketTimeout: 10
|
websocketTimeout: 10
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+15
-15
@@ -13,29 +13,29 @@ prometheus:
|
|||||||
ports: [ 12170, 12171, 12172, 12173, 12174, 12175, 12176, 12177, 12178, 12179, 12180, 12182, 12183, 12184, 12185, 12186 ]
|
ports: [ 12170, 12171, 12172, 12173, 12174, 12175, 12176, 12177, 12178, 12179, 12180, 12182, 12183, 12184, 12185, 12186 ]
|
||||||
|
|
||||||
maxConcurrentWorkers: 3
|
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
|
enable: geTui
|
||||||
geTui:
|
geTui:
|
||||||
pushUrl: https://restapi.getui.com/v2/$appId
|
pushUrl: https://restapi.getui.com/v2/$appId
|
||||||
masterSecret:
|
masterSecret:
|
||||||
appKey:
|
appKey:
|
||||||
intent:
|
intent:
|
||||||
channelID:
|
channelID:
|
||||||
channelName:
|
channelName:
|
||||||
fcm:
|
fcm:
|
||||||
# Prioritize using file paths. If the file path is empty, use URL
|
# 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.
|
authURL: # Must start with https or http.
|
||||||
jpns:
|
jpush:
|
||||||
appKey:
|
appKey:
|
||||||
masterSecret:
|
masterSecret:
|
||||||
pushURL:
|
pushURL:
|
||||||
pushIntent:
|
pushIntent:
|
||||||
|
|
||||||
# iOS system push sound and badge count
|
# iOS system push sound and badge count
|
||||||
iosPush:
|
iosPush:
|
||||||
pushSound: xxx
|
pushSound: xxx
|
||||||
badgeCount: true
|
badgeCount: true
|
||||||
production: false
|
production: false
|
||||||
|
|
||||||
fullUserCache: true
|
fullUserCache: true
|
||||||
|
|||||||
+3
-1
@@ -13,4 +13,6 @@ rpcRegisterName:
|
|||||||
imAdminUserID: [ imAdmin ]
|
imAdminUserID: [ imAdmin ]
|
||||||
|
|
||||||
# 1: For Android, iOS, Windows, Mac, and web platforms, only one instance can be online at a time
|
# 1: For Android, iOS, Windows, Mac, and web platforms, only one instance can be online at a time
|
||||||
multiLoginPolicy: 1
|
multiLogin:
|
||||||
|
policy: 1
|
||||||
|
maxNumOneEnd: 30
|
||||||
@@ -240,11 +240,11 @@ push:
|
|||||||
channelName: ${GETUI_CHANNEL_NAME}
|
channelName: ${GETUI_CHANNEL_NAME}
|
||||||
fcm:
|
fcm:
|
||||||
serviceAccount: "${FCM_SERVICE_ACCOUNT}"
|
serviceAccount: "${FCM_SERVICE_ACCOUNT}"
|
||||||
jpns:
|
jpush:
|
||||||
appKey: ${JPNS_APP_KEY}
|
appKey: ${JPUSH_APP_KEY}
|
||||||
masterSecret: ${JPNS_MASTER_SECRET}
|
masterSecret: ${JPUSH_MASTER_SECRET}
|
||||||
pushUrl: ${JPNS_PUSH_URL}
|
pushUrl: ${JPUSH_PUSH_URL}
|
||||||
pushIntent: ${JPNS_PUSH_INTENT}
|
pushIntent: ${JPUSH_PUSH_INTENT}
|
||||||
|
|
||||||
# App manager configuration
|
# App manager configuration
|
||||||
#
|
#
|
||||||
|
|||||||
+29
-21
@@ -8,12 +8,35 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "37017:27017"
|
- "37017:27017"
|
||||||
container_name: mongo
|
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:
|
volumes:
|
||||||
- "${DATA_DIR}/components/mongodb/data/db:/data/db"
|
- "${DATA_DIR}/components/mongodb/data/db:/data/db"
|
||||||
- "${DATA_DIR}/components/mongodb/data/logs:/data/logs"
|
- "${DATA_DIR}/components/mongodb/data/logs:/data/logs"
|
||||||
- "${DATA_DIR}/components/mongodb/data/conf:/etc/mongo"
|
- "${DATA_DIR}/components/mongodb/data/conf:/etc/mongo"
|
||||||
- "./scripts/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh:ro"
|
|
||||||
environment:
|
environment:
|
||||||
- TZ=Asia/Shanghai
|
- TZ=Asia/Shanghai
|
||||||
- wiredTigerCacheSizeGB=1
|
- wiredTigerCacheSizeGB=1
|
||||||
@@ -43,19 +66,6 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- openim
|
- 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:
|
etcd:
|
||||||
image: "${ETCD_IMAGE}"
|
image: "${ETCD_IMAGE}"
|
||||||
container_name: etcd
|
container_name: etcd
|
||||||
@@ -84,10 +94,7 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "19094:9094"
|
- "19094:9094"
|
||||||
volumes:
|
volumes:
|
||||||
- ./scripts/create-topic.sh:/opt/bitnami/kafka/create-topic.sh
|
|
||||||
- "${DATA_DIR}/components/kafka:/bitnami/kafka"
|
- "${DATA_DIR}/components/kafka:/bitnami/kafka"
|
||||||
command: >
|
|
||||||
bash -c "/opt/bitnami/scripts/kafka/run.sh & /opt/bitnami/kafka/create-topic.sh; wait"
|
|
||||||
environment:
|
environment:
|
||||||
#KAFKA_HEAP_OPTS: "-Xms128m -Xmx256m"
|
#KAFKA_HEAP_OPTS: "-Xms128m -Xmx256m"
|
||||||
TZ: Asia/Shanghai
|
TZ: Asia/Shanghai
|
||||||
@@ -98,10 +105,11 @@ services:
|
|||||||
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:19094
|
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:19094
|
||||||
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
|
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
|
||||||
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
|
||||||
|
KAFKA_NUM_PARTITIONS: 8
|
||||||
|
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true"
|
||||||
networks:
|
networks:
|
||||||
- openim
|
- openim
|
||||||
|
|
||||||
|
|
||||||
minio:
|
minio:
|
||||||
image: "${MINIO_IMAGE}"
|
image: "${MINIO_IMAGE}"
|
||||||
ports:
|
ports:
|
||||||
@@ -137,11 +145,12 @@ services:
|
|||||||
- "11002:80"
|
- "11002:80"
|
||||||
networks:
|
networks:
|
||||||
- openim
|
- openim
|
||||||
|
|
||||||
# prometheus:
|
# prometheus:
|
||||||
# image: ${PROMETHEUS_IMAGE}
|
# image: ${PROMETHEUS_IMAGE}
|
||||||
# container_name: prometheus
|
# container_name: prometheus
|
||||||
# restart: always
|
# restart: always
|
||||||
|
# user: root
|
||||||
# volumes:
|
# volumes:
|
||||||
# - ./config/prometheus.yml:/etc/prometheus/prometheus.yml
|
# - ./config/prometheus.yml:/etc/prometheus/prometheus.yml
|
||||||
# - ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
|
# - ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
|
||||||
@@ -183,4 +192,3 @@ services:
|
|||||||
# - ${DATA_DIR:-./}/components/grafana:/var/lib/grafana
|
# - ${DATA_DIR:-./}/components/grafana:/var/lib/grafana
|
||||||
# networks:
|
# networks:
|
||||||
# - openim
|
# - 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_ID | [User Defined] | GeTui Channel ID |
|
||||||
| GETUI_CHANNEL_NAME | [User Defined] | GeTui Channel Name |
|
| GETUI_CHANNEL_NAME | [User Defined] | GeTui Channel Name |
|
||||||
| FCM_SERVICE_ACCOUNT | "x.json" | FCM Service Account |
|
| FCM_SERVICE_ACCOUNT | "x.json" | FCM Service Account |
|
||||||
| JPNS_APP_KEY | [User Defined] | JPNS Application Key |
|
| JPUSH_APP_KEY | [User Defined] | JPUSH Application Key |
|
||||||
| JPNS_MASTER_SECRET | [User Defined] | JPNS Master Secret |
|
| JPUSH_MASTER_SECRET | [User Defined] | JPUSH Master Secret |
|
||||||
| JPNS_PUSH_URL | [User Defined] | JPNS Push Notification URL |
|
| JPUSH_PUSH_URL | [User Defined] | JPUSH Push Notification URL |
|
||||||
| JPNS_PUSH_INTENT | [User Defined] | JPNS Push Intent |
|
| JPUSH_PUSH_INTENT | [User Defined] | JPUSH Push Intent |
|
||||||
| IM_ADMIN_USERID | "imAdmin" | IM Administrator ID |
|
| IM_ADMIN_USERID | "imAdmin" | IM Administrator ID |
|
||||||
| IM_ADMIN_NAME | "imAdmin" | IM Administrator Nickname |
|
| IM_ADMIN_NAME | "imAdmin" | IM Administrator Nickname |
|
||||||
| MULTILOGIN_POLICY | "1" | Multi-login Policy |
|
| MULTILOGIN_POLICY | "1" | Multi-login Policy |
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
module github.com/openimsdk/open-im-server/v3
|
module github.com/openimsdk/open-im-server/v3
|
||||||
|
|
||||||
go 1.21.2
|
go 1.22.7
|
||||||
|
|
||||||
|
toolchain go1.23.2
|
||||||
|
|
||||||
require (
|
require (
|
||||||
firebase.google.com/go/v4 v4.14.1
|
firebase.google.com/go/v4 v4.14.1
|
||||||
@@ -8,19 +10,19 @@ require (
|
|||||||
github.com/gin-gonic/gin v1.9.1
|
github.com/gin-gonic/gin v1.9.1
|
||||||
github.com/go-playground/validator/v10 v10.20.0
|
github.com/go-playground/validator/v10 v10.20.0
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
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/gorilla/websocket v1.5.1
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
github.com/openimsdk/protocol v0.0.72
|
github.com/openimsdk/protocol v0.0.72-alpha.63
|
||||||
github.com/openimsdk/tools v0.0.50-alpha.16
|
github.com/openimsdk/tools v0.0.50-alpha.50
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
github.com/prometheus/client_golang v1.18.0
|
github.com/prometheus/client_golang v1.18.0
|
||||||
github.com/stretchr/testify v1.9.0
|
github.com/stretchr/testify v1.9.0
|
||||||
go.mongodb.org/mongo-driver v1.14.0
|
go.mongodb.org/mongo-driver v1.14.0
|
||||||
google.golang.org/api v0.170.0
|
google.golang.org/api v0.170.0
|
||||||
google.golang.org/grpc v1.66.2
|
google.golang.org/grpc v1.68.0
|
||||||
google.golang.org/protobuf v1.34.2
|
google.golang.org/protobuf v1.35.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -48,31 +50,31 @@ require (
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.112.1 // indirect
|
cloud.google.com/go v0.112.1 // indirect
|
||||||
cloud.google.com/go/compute/metadata v0.3.0 // indirect
|
cloud.google.com/go/compute/metadata v0.5.0 // indirect
|
||||||
cloud.google.com/go/firestore v1.15.0 // indirect
|
cloud.google.com/go/firestore v1.15.0 // indirect
|
||||||
cloud.google.com/go/iam v1.1.7 // indirect
|
cloud.google.com/go/iam v1.1.7 // indirect
|
||||||
cloud.google.com/go/longrunning v0.5.5 // indirect
|
cloud.google.com/go/longrunning v0.5.5 // indirect
|
||||||
cloud.google.com/go/storage v1.40.0 // indirect
|
cloud.google.com/go/storage v1.40.0 // indirect
|
||||||
github.com/MicahParks/keyfunc v1.9.0 // indirect
|
github.com/MicahParks/keyfunc v1.9.0 // indirect
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
|
||||||
github.com/aws/aws-sdk-go-v2 v1.23.1 // indirect
|
github.com/aws/aws-sdk-go-v2 v1.32.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.25.4 // indirect
|
github.com/aws/aws-sdk-go-v2/config v1.28.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.3 // indirect
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.46 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.4 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.4 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.43.1 // indirect
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.43.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 // indirect
|
||||||
github.com/aws/smithy-go v1.17.0 // indirect
|
github.com/aws/smithy-go v1.22.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/bytedance/sonic v1.11.6 // indirect
|
github.com/bytedance/sonic v1.11.6 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||||
@@ -92,7 +94,7 @@ require (
|
|||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.1 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
@@ -175,13 +177,13 @@ require (
|
|||||||
golang.org/x/arch v0.7.0 // indirect
|
golang.org/x/arch v0.7.0 // indirect
|
||||||
golang.org/x/image v0.15.0 // indirect
|
golang.org/x/image v0.15.0 // indirect
|
||||||
golang.org/x/net v0.29.0 // indirect
|
golang.org/x/net v0.29.0 // indirect
|
||||||
golang.org/x/oauth2 v0.21.0 // indirect
|
golang.org/x/oauth2 v0.23.0 // indirect
|
||||||
golang.org/x/sys v0.25.0 // indirect
|
golang.org/x/sys v0.25.0 // indirect
|
||||||
golang.org/x/text v0.18.0 // indirect
|
golang.org/x/text v0.18.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
google.golang.org/appengine/v2 v2.0.2 // indirect
|
google.golang.org/appengine/v2 v2.0.2 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect
|
||||||
gorm.io/gorm v1.25.8 // indirect
|
gorm.io/gorm v1.25.8 // indirect
|
||||||
stathat.com/c/consistent v1.0.0 // indirect
|
stathat.com/c/consistent v1.0.0 // indirect
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM=
|
||||||
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4=
|
||||||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
|
cloud.google.com/go/compute/metadata v0.5.0 h1:Zr0eK8JbFv6+Wi4ilXAR8FJ3wyNdpxHKJNPos6LTZOY=
|
||||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
|
cloud.google.com/go/compute/metadata v0.5.0/go.mod h1:aHnloV2TPI38yx4s9+wAZhHykWvVCfu7hQbF+9CWoiY=
|
||||||
cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
|
cloud.google.com/go/firestore v1.15.0 h1:/k8ppuWOtNuDHt2tsRV42yI21uaGnKDEQnRFeBpbFF8=
|
||||||
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
|
cloud.google.com/go/firestore v1.15.0/go.mod h1:GWOxFXcv8GZUtYpWHw/w6IuYNux/BtmeVTMmjrm4yhk=
|
||||||
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
|
cloud.google.com/go/iam v1.1.7 h1:z4VHOhwKLF/+UYXAJDFwGtNF0b6gjsW1Pk9Ml0U/IoM=
|
||||||
@@ -21,42 +21,42 @@ github.com/MicahParks/keyfunc v1.9.0/go.mod h1:IdnCilugA0O/99dW+/MkvlyrsX8+L8+x9
|
|||||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
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=
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.23.1 h1:qXaFsOOMA+HsZtX8WoCa+gJnbyW7qyFFBlPqvTSzbaI=
|
github.com/aws/aws-sdk-go-v2 v1.32.5 h1:U8vdWJuY7ruAkzaOdD7guwJjD06YSKmnKCJs7s3IkIo=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.23.1/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA=
|
github.com/aws/aws-sdk-go-v2 v1.32.5/go.mod h1:P5WJBrYqqbWVaOxgH0X/FYYD47/nooaPOZPlQdmiN2U=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1 h1:ZY3108YtBNq96jNZTICHxN1gSBSbnvIdYwwqnvCV4Mc=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1 h1:ZY3108YtBNq96jNZTICHxN1gSBSbnvIdYwwqnvCV4Mc=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1/go.mod h1:t8PYl/6LzdAqsU4/9tz28V/kU+asFePvpOMkdul0gEQ=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1/go.mod h1:t8PYl/6LzdAqsU4/9tz28V/kU+asFePvpOMkdul0gEQ=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.25.4 h1:r+X1x8QI6FEPdJDWCNBDZHyAcyFwSjHN8q8uuus+Axs=
|
github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.25.4/go.mod h1:8GTjImECskr7D88P/Nn9uM4M4rLY9i77hLJZgkZEWV8=
|
github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.3 h1:8PeI2krzzjDJ5etmgaMiD1JswsrLrWvKKu/uBUtNy1g=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.16.3/go.mod h1:Kdh/okh+//vQ/AjEt81CjvkTo64+/zIE4OewP7RpfXk=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 h1:KehRNiVzIfAcj6gw98zotVbb/K67taJE0fkfgM6vzqU=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5/go.mod h1:VhnExhw6uXy9QzetvpXDolo1/hjhx4u9qukBGkuUwjs=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 h1:LAm3Ycm9HJfbSCd5I+wqC2S9Ej7FPrgr5CQoOljJZcE=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24 h1:4usbeaes3yJnCFC7kfeyhkdkPtoRYPa/hTmCqMpKpLI=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4/go.mod h1:xEhvbJcyUf/31yfGSQBe01fukXwXJ0gxDp7rLfymWE0=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.24/go.mod h1:5CI1JemjVwde8m2WG3cz23qHKPOxbpkq0HaoreEgLIY=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 h1:4GV0kKZzUxiWxSVpn/9gwR0g21NF1Jsyduzo9rHgC/Q=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24 h1:N1zsICrQglfzaBnrfM0Ys00860C+QFwu6u/5+LomP+o=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4/go.mod h1:dYvTNAggxDZy6y1AF7YDwXsPuHFy/VNEpEI/2dWK9IU=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.24/go.mod h1:dCn9HbJ8+K31i8IQ8EWmWj0EiIk0+vKiHNMxTTYveAg=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.4 h1:40Q4X5ebZruRtknEZH/bg91sT5pR853F7/1X9QRbI54=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.4 h1:40Q4X5ebZruRtknEZH/bg91sT5pR853F7/1X9QRbI54=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.4/go.mod h1:u77N7eEECzUv7F0xl2gcfK/vzc8wcjWobpy+DcrLJ5E=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.4/go.mod h1:u77N7eEECzUv7F0xl2gcfK/vzc8wcjWobpy+DcrLJ5E=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 h1:rpkF4n0CyFcrJUG/rNNohoTmhtWlFTRI4BsZOh9PvLs=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1 h1:iXtILhvDxB6kPvEXgsDhGaZCSC6LQET5ZHSdJozeI0Y=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1/go.mod h1:l9ymW25HOqymeU2m1gbUQ3rUIsTwKs8gYHXkqDQUhiI=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.1/go.mod h1:9nu0fVANtYiAePIBh2/pFUSwtJ402hLnp854CNoDOeE=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.4 h1:6DRKQc+9cChgzL5gplRGusI5dBGeiEod4m/pmGbcX48=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.4 h1:6DRKQc+9cChgzL5gplRGusI5dBGeiEod4m/pmGbcX48=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.4/go.mod h1:s8ORvrW4g4v7IvYKIAoBg17w3GQ+XuwXDXYrQ5SkzU0=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.4/go.mod h1:s8ORvrW4g4v7IvYKIAoBg17w3GQ+XuwXDXYrQ5SkzU0=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 h1:rdovz3rEu0vZKbzoMYPTehp0E8veoE9AyfzqCr5Eeao=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5 h1:wtpJ4zcwrSbwhECWQoI/g6WM9zqCcSpHDJIWSbMLOu4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4/go.mod h1:aYCGNjyUCUelhofxlZyj63srdxWUSsBSGg5l6MCuXuE=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.5/go.mod h1:qu/W9HXQbbQ4+1+JcZp0ZNPV31ym537ZJN+fiS7Ti8E=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.4 h1:o3DcfCxGDIT20pTbVKVhp3vWXOj/VvgazNJvumWeYW0=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.4 h1:o3DcfCxGDIT20pTbVKVhp3vWXOj/VvgazNJvumWeYW0=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.4/go.mod h1:Uy0KVOxuTK2ne+/PKQ+VvEeWmjMMksE17k/2RK/r5oM=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.4/go.mod h1:Uy0KVOxuTK2ne+/PKQ+VvEeWmjMMksE17k/2RK/r5oM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.43.1 h1:1w11lfXOa8HoHoSlNtt4mqv/N3HmDOa+OnUH3Y9DHm8=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.43.1 h1:1w11lfXOa8HoHoSlNtt4mqv/N3HmDOa+OnUH3Y9DHm8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.43.1/go.mod h1:dqJ5JBL0clzgHriH35Amx3LRFY6wNIPUX7QO/BerSBo=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.43.1/go.mod h1:dqJ5JBL0clzgHriH35Amx3LRFY6wNIPUX7QO/BerSBo=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 h1:CdsSOGlFF3Pn+koXOIpTtvX7st0IuGsZ8kJqcWMlX54=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.17.3/go.mod h1:oA6VjNsLll2eVuUoF2D+CMyORgNzPEW/3PyUdq6WQjI=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 h1:cbRqFTVnJV+KRpwFl76GJdIZJKKCdTPnjUZ7uWh3pIU=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1/go.mod h1:hHL974p5auvXlZPIjJTblXJpbkfK4klBczlsEaMCGVY=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 h1:yEvZ4neOQ/KpUqyR+X0ycUTW/kVRNR4nDZ38wStHGAA=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1 h1:6SZUVRQNvExYlMLbHdlKB48x0fLbc2iVROyaNEwBHbU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.25.4/go.mod h1:feTnm2Tk/pJxdX+eooEsxvlvTWBvDm6CasRZ+JOs2IY=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.33.1/go.mod h1:GqWyYCwLXnlUB1lOAXQyNSPqPLQJvmo8J0DWBzp9mtg=
|
||||||
github.com/aws/smithy-go v1.17.0 h1:wWJD7LX6PBV6etBUwO0zElG0nWN9rUhp0WdYeHSHAaI=
|
github.com/aws/smithy-go v1.22.1 h1:/HPHZQ0g7f4eUeK6HKglFz8uwVfZKgoI25rb/J+dnro=
|
||||||
github.com/aws/smithy-go v1.17.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
|
github.com/aws/smithy-go v1.22.1/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg=
|
||||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||||
@@ -126,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 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
|
||||||
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
|
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.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.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||||
@@ -158,8 +158,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
|
|||||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-jwt/jwt/v4 v4.4.2/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.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
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/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-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 h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
|
||||||
@@ -319,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/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 h1:VY9c5x515lTfmdhhPjMvR3BBRrRquAUCFsz7t7vbv7Y=
|
||||||
github.com/openimsdk/gomake v0.0.14-alpha.5/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
|
github.com/openimsdk/gomake v0.0.14-alpha.5/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
|
||||||
github.com/openimsdk/protocol v0.0.72 h1:K+vslwaR7lDXyBzb07UuEQITaqsgighz7NyXVIWsu6A=
|
github.com/openimsdk/protocol v0.0.72-alpha.63 h1:IyPBibEvwBtTmD8DSrlqcekfEXe74k4+KeeHsgdhGh0=
|
||||||
github.com/openimsdk/protocol v0.0.72/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8=
|
github.com/openimsdk/protocol v0.0.72-alpha.63/go.mod h1:Iet+piS/jaS+kWWyj6EEr36mk4ISzIRYjoMSVA4dq2M=
|
||||||
github.com/openimsdk/tools v0.0.50-alpha.16 h1:bC1AQvJMuOHtZm8LZRvN8L5mH1Ws2VYdL+TLTs1iGSc=
|
github.com/openimsdk/tools v0.0.50-alpha.50 h1:+naDlvHcqJDj2NsCGnQd1LLQOET5IRPbrtmWbM/o7JQ=
|
||||||
github.com/openimsdk/tools v0.0.50-alpha.16/go.mod h1:h1cYmfyaVtgFbKmb1Cfsl8XwUOMTt8ubVUQrdGtsUh4=
|
github.com/openimsdk/tools v0.0.50-alpha.50/go.mod h1:muCtxguNJv8lFwLei27UASu2Nvg4ERSeN0R4K5tivk0=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
|
||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||||
@@ -356,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/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
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 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
|
||||||
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
@@ -497,8 +497,8 @@ golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
|||||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.21.0 h1:tsimM75w1tF/uws5rbeHzIWxEqElMehnc+iW793zsZs=
|
golang.org/x/oauth2 v0.23.0 h1:PbgcYx2W7i4LvjJWEbf0ngHV6qJYr86PkAV3bXdLEbs=
|
||||||
golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.23.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
@@ -565,8 +565,8 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98
|
|||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9 h1:9+tzLLstTlPTRyJTh+ah5wIMsBW5c4tQwGTN3thOW9Y=
|
||||||
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
google.golang.org/genproto v0.0.0-20240213162025-012b6fc9bca9/go.mod h1:mqHbVIp48Muh7Ywss/AD6I5kNVKZMmAa/QEW58Gxp2s=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
@@ -574,8 +574,8 @@ google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac
|
|||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
||||||
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.66.2 h1:3QdXkuq3Bkh7w+ywLdLvM56cmGvQHUMZpiCzt6Rqaoo=
|
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||||
google.golang.org/grpc v1.66.2/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@@ -585,8 +585,8 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2
|
|||||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
|
|||||||
+90
-32
@@ -1,11 +1,15 @@
|
|||||||
package jssdk
|
package jssdk
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/openimsdk/protocol/conversation"
|
"github.com/openimsdk/protocol/conversation"
|
||||||
|
"github.com/openimsdk/protocol/group"
|
||||||
|
"github.com/openimsdk/protocol/jssdk"
|
||||||
"github.com/openimsdk/protocol/msg"
|
"github.com/openimsdk/protocol/msg"
|
||||||
|
"github.com/openimsdk/protocol/relation"
|
||||||
"github.com/openimsdk/protocol/sdkws"
|
"github.com/openimsdk/protocol/sdkws"
|
||||||
"github.com/openimsdk/tools/a2r"
|
"github.com/openimsdk/protocol/user"
|
||||||
"github.com/openimsdk/tools/mcontext"
|
"github.com/openimsdk/tools/mcontext"
|
||||||
"github.com/openimsdk/tools/utils/datautil"
|
"github.com/openimsdk/tools/utils/datautil"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -16,16 +20,22 @@ const (
|
|||||||
defaultGetActiveConversation = 100
|
defaultGetActiveConversation = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewJSSdkApi(msg msg.MsgClient, conv conversation.ConversationClient) *JSSdk {
|
func NewJSSdkApi(user user.UserClient, friend relation.FriendClient, group group.GroupClient, msg msg.MsgClient, conv conversation.ConversationClient) *JSSdk {
|
||||||
return &JSSdk{
|
return &JSSdk{
|
||||||
msg: msg,
|
user: user,
|
||||||
conv: conv,
|
friend: friend,
|
||||||
|
group: group,
|
||||||
|
msg: msg,
|
||||||
|
conv: conv,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type JSSdk struct {
|
type JSSdk struct {
|
||||||
msg msg.MsgClient
|
user user.UserClient
|
||||||
conv conversation.ConversationClient
|
friend relation.FriendClient
|
||||||
|
group group.GroupClient
|
||||||
|
msg msg.MsgClient
|
||||||
|
conv conversation.ConversationClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *JSSdk) GetActiveConversations(c *gin.Context) {
|
func (x *JSSdk) GetActiveConversations(c *gin.Context) {
|
||||||
@@ -36,25 +46,71 @@ func (x *JSSdk) GetConversations(c *gin.Context) {
|
|||||||
call(c, x.getConversations)
|
call(c, x.getConversations)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, error) {
|
func (x *JSSdk) fillConversations(ctx context.Context, conversations []*jssdk.ConversationMsg) error {
|
||||||
req, err := a2r.ParseRequest[ActiveConversationsReq](ctx)
|
if len(conversations) == 0 {
|
||||||
if err != nil {
|
return nil
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
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 {
|
if req.Count <= 0 || req.Count > maxGetActiveConversation {
|
||||||
req.Count = defaultGetActiveConversation
|
req.Count = defaultGetActiveConversation
|
||||||
}
|
}
|
||||||
opUserID := mcontext.GetOpUserID(ctx)
|
req.OwnerUserID = mcontext.GetOpUserID(ctx)
|
||||||
conversationIDs, err := field(ctx, x.conv.GetConversationIDs,
|
conversationIDs, err := field(ctx, x.conv.GetConversationIDs,
|
||||||
&conversation.GetConversationIDsReq{UserID: opUserID}, (*conversation.GetConversationIDsResp).GetConversationIDs)
|
&conversation.GetConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetConversationIDsResp).GetConversationIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(conversationIDs) == 0 {
|
if len(conversationIDs) == 0 {
|
||||||
return &ConversationsResp{}, nil
|
return &jssdk.GetActiveConversationsResp{}, nil
|
||||||
}
|
}
|
||||||
readSeq, err := field(ctx, x.msg.GetHasReadSeqs,
|
readSeq, err := field(ctx, x.msg.GetHasReadSeqs,
|
||||||
&msg.GetHasReadSeqsReq{UserID: opUserID, ConversationIDs: conversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
|
&msg.GetHasReadSeqsReq{UserID: req.OwnerUserID, ConversationIDs: conversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -64,24 +120,24 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(activeConversation) == 0 {
|
if len(activeConversation) == 0 {
|
||||||
return &ConversationsResp{}, nil
|
return &jssdk.GetActiveConversationsResp{}, nil
|
||||||
}
|
}
|
||||||
sortConversations := sortActiveConversations{
|
sortConversations := sortActiveConversations{
|
||||||
Conversation: activeConversation,
|
Conversation: activeConversation,
|
||||||
}
|
}
|
||||||
if len(activeConversation) > 1 {
|
if len(activeConversation) > 1 {
|
||||||
pinnedConversationIDs, err := field(ctx, x.conv.GetPinnedConversationIDs,
|
pinnedConversationIDs, err := field(ctx, x.conv.GetPinnedConversationIDs,
|
||||||
&conversation.GetPinnedConversationIDsReq{UserID: opUserID}, (*conversation.GetPinnedConversationIDsResp).GetConversationIDs)
|
&conversation.GetPinnedConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetPinnedConversationIDsResp).GetConversationIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
sortConversations.PinnedConversationIDs = datautil.SliceSet(pinnedConversationIDs)
|
sortConversations.PinnedConversationIDs = datautil.SliceSet(pinnedConversationIDs)
|
||||||
}
|
}
|
||||||
sort.Sort(&sortConversations)
|
sort.Sort(&sortConversations)
|
||||||
sortList := sortConversations.Top(req.Count)
|
sortList := sortConversations.Top(int(req.Count))
|
||||||
conversations, err := field(ctx, x.conv.GetConversations,
|
conversations, err := field(ctx, x.conv.GetConversations,
|
||||||
&conversation.GetConversationsReq{
|
&conversation.GetConversationsReq{
|
||||||
OwnerUserID: opUserID,
|
OwnerUserID: req.OwnerUserID,
|
||||||
ConversationIDs: datautil.Slice(sortList, func(c *msg.ActiveConversation) string {
|
ConversationIDs: datautil.Slice(sortList, func(c *msg.ActiveConversation) string {
|
||||||
return c.ConversationID
|
return c.ConversationID
|
||||||
})}, (*conversation.GetConversationsResp).GetConversations)
|
})}, (*conversation.GetConversationsResp).GetConversations)
|
||||||
@@ -90,7 +146,7 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
|
|||||||
}
|
}
|
||||||
msgs, err := field(ctx, x.msg.GetSeqMessage,
|
msgs, err := field(ctx, x.msg.GetSeqMessage,
|
||||||
&msg.GetSeqMessageReq{
|
&msg.GetSeqMessageReq{
|
||||||
UserID: opUserID,
|
UserID: req.OwnerUserID,
|
||||||
Conversations: datautil.Slice(sortList, func(c *msg.ActiveConversation) *msg.ConversationSeqs {
|
Conversations: datautil.Slice(sortList, func(c *msg.ActiveConversation) *msg.ConversationSeqs {
|
||||||
return &msg.ConversationSeqs{
|
return &msg.ConversationSeqs{
|
||||||
ConversationID: c.ConversationID,
|
ConversationID: c.ConversationID,
|
||||||
@@ -104,7 +160,7 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
|
|||||||
conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string {
|
conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string {
|
||||||
return c.ConversationID
|
return c.ConversationID
|
||||||
})
|
})
|
||||||
resp := make([]ConversationMsg, 0, len(sortList))
|
resp := make([]*jssdk.ConversationMsg, 0, len(sortList))
|
||||||
for _, c := range sortList {
|
for _, c := range sortList {
|
||||||
conv, ok := conversationMap[c.ConversationID]
|
conv, ok := conversationMap[c.ConversationID]
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -114,13 +170,16 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
|
|||||||
if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
|
if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
|
||||||
lastMsg = msgList.Msgs[0]
|
lastMsg = msgList.Msgs[0]
|
||||||
}
|
}
|
||||||
resp = append(resp, ConversationMsg{
|
resp = append(resp, &jssdk.ConversationMsg{
|
||||||
Conversation: conv,
|
Conversation: conv,
|
||||||
LastMsg: lastMsg,
|
LastMsg: lastMsg,
|
||||||
MaxSeq: c.MaxSeq,
|
MaxSeq: c.MaxSeq,
|
||||||
ReadSeq: readSeq[c.ConversationID],
|
ReadSeq: readSeq[c.ConversationID],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if err := x.fillConversations(ctx, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var unreadCount int64
|
var unreadCount int64
|
||||||
for _, c := range activeConversation {
|
for _, c := range activeConversation {
|
||||||
count := c.MaxSeq - readSeq[c.ConversationID]
|
count := c.MaxSeq - readSeq[c.ConversationID]
|
||||||
@@ -128,24 +187,20 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
|
|||||||
unreadCount += count
|
unreadCount += count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ConversationsResp{
|
return &jssdk.GetActiveConversationsResp{
|
||||||
Conversations: resp,
|
Conversations: resp,
|
||||||
UnreadCount: unreadCount,
|
UnreadCount: unreadCount,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) {
|
func (x *JSSdk) getConversations(ctx context.Context, req *jssdk.GetConversationsReq) (*jssdk.GetConversationsResp, error) {
|
||||||
req, err := a2r.ParseRequest[conversation.GetConversationsReq](ctx)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
req.OwnerUserID = mcontext.GetOpUserID(ctx)
|
req.OwnerUserID = mcontext.GetOpUserID(ctx)
|
||||||
conversations, err := field(ctx, x.conv.GetConversations, req, (*conversation.GetConversationsResp).GetConversations)
|
conversations, err := field(ctx, x.conv.GetConversations, &conversation.GetConversationsReq{OwnerUserID: req.OwnerUserID, ConversationIDs: req.ConversationIDs}, (*conversation.GetConversationsResp).GetConversations)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(conversations) == 0 {
|
if len(conversations) == 0 {
|
||||||
return &ConversationsResp{}, nil
|
return &jssdk.GetConversationsResp{}, nil
|
||||||
}
|
}
|
||||||
req.ConversationIDs = datautil.Slice(conversations, func(c *conversation.Conversation) string {
|
req.ConversationIDs = datautil.Slice(conversations, func(c *conversation.Conversation) string {
|
||||||
return c.ConversationID
|
return c.ConversationID
|
||||||
@@ -177,19 +232,22 @@ func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp := make([]ConversationMsg, 0, len(conversations))
|
resp := make([]*jssdk.ConversationMsg, 0, len(conversations))
|
||||||
for _, c := range conversations {
|
for _, c := range conversations {
|
||||||
var lastMsg *sdkws.MsgData
|
var lastMsg *sdkws.MsgData
|
||||||
if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
|
if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
|
||||||
lastMsg = msgList.Msgs[0]
|
lastMsg = msgList.Msgs[0]
|
||||||
}
|
}
|
||||||
resp = append(resp, ConversationMsg{
|
resp = append(resp, &jssdk.ConversationMsg{
|
||||||
Conversation: c,
|
Conversation: c,
|
||||||
LastMsg: lastMsg,
|
LastMsg: lastMsg,
|
||||||
MaxSeq: maxSeqs[c.ConversationID],
|
MaxSeq: maxSeqs[c.ConversationID],
|
||||||
ReadSeq: readSeqs[c.ConversationID],
|
ReadSeq: readSeqs[c.ConversationID],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
if err := x.fillConversations(ctx, resp); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
var unreadCount int64
|
var unreadCount int64
|
||||||
for conversationID, maxSeq := range maxSeqs {
|
for conversationID, maxSeq := range maxSeqs {
|
||||||
count := maxSeq - readSeqs[conversationID]
|
count := maxSeq - readSeqs[conversationID]
|
||||||
@@ -197,7 +255,7 @@ func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) {
|
|||||||
unreadCount += count
|
unreadCount += count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &ConversationsResp{
|
return &jssdk.GetConversationsResp{
|
||||||
Conversations: resp,
|
Conversations: resp,
|
||||||
UnreadCount: unreadCount,
|
UnreadCount: unreadCount,
|
||||||
}, nil
|
}, nil
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package jssdk
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/openimsdk/protocol/conversation"
|
|
||||||
"github.com/openimsdk/protocol/sdkws"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ActiveConversationsReq struct {
|
|
||||||
Count int `json:"count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConversationMsg struct {
|
|
||||||
Conversation *conversation.Conversation `json:"conversation"`
|
|
||||||
LastMsg *sdkws.MsgData `json:"lastMsg"`
|
|
||||||
MaxSeq int64 `json:"maxSeq"`
|
|
||||||
ReadSeq int64 `json:"readSeq"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConversationsResp struct {
|
|
||||||
UnreadCount int64 `json:"unreadCount"`
|
|
||||||
Conversations []ConversationMsg `json:"conversations"`
|
|
||||||
}
|
|
||||||
@@ -3,8 +3,14 @@ package jssdk
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
"github.com/openimsdk/tools/a2r"
|
||||||
"github.com/openimsdk/tools/apiresp"
|
"github.com/openimsdk/tools/apiresp"
|
||||||
|
"github.com/openimsdk/tools/checker"
|
||||||
|
"github.com/openimsdk/tools/errs"
|
||||||
"google.golang.org/grpc"
|
"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) {
|
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) {
|
||||||
@@ -16,11 +22,56 @@ func field[A, B, C any](ctx context.Context, fn func(ctx context.Context, req *A
|
|||||||
return get(resp), nil
|
return get(resp), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func call[R any](c *gin.Context, fn func(ctx *gin.Context) (R, error)) {
|
func call[A, B any](c *gin.Context, fn func(ctx context.Context, req *A) (*B, error)) {
|
||||||
resp, err := fn(c)
|
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 {
|
if err != nil {
|
||||||
apiresp.GinError(c, err)
|
apiresp.GinError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
apiresp.GinSuccess(c, resp)
|
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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/openimsdk/protocol/msg"
|
|
||||||
"sort"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestName(t *testing.T) {
|
|
||||||
val := sortActiveConversations{
|
|
||||||
Conversation: []*msg.ActiveConversation{
|
|
||||||
{
|
|
||||||
ConversationID: "100",
|
|
||||||
LastTime: 100,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ConversationID: "200",
|
|
||||||
LastTime: 200,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ConversationID: "300",
|
|
||||||
LastTime: 300,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
ConversationID: "400",
|
|
||||||
LastTime: 400,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
//PinnedConversationIDs: map[string]struct{}{
|
|
||||||
// "100": {},
|
|
||||||
// "300": {},
|
|
||||||
//},
|
|
||||||
}
|
|
||||||
sort.Sort(&val)
|
|
||||||
t.Log(val)
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -74,10 +74,10 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
|
|||||||
case BestSpeed:
|
case BestSpeed:
|
||||||
r.Use(gzip.Gzip(gzip.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)
|
u := NewUserApi(*userRpc)
|
||||||
m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID)
|
m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID)
|
||||||
j := jssdk.NewJSSdkApi(messageRpc.Client, conversationRpc.Client)
|
j := jssdk.NewJSSdkApi(userRpc.Client, friendRpc.Client, groupRpc.Client, messageRpc.Client, conversationRpc.Client)
|
||||||
userRouterGroup := r.Group("/user")
|
userRouterGroup := r.Group("/user")
|
||||||
{
|
{
|
||||||
userRouterGroup.POST("/user_register", u.UserRegister)
|
userRouterGroup.POST("/user_register", u.UserRegister)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package msggateway
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"sync"
|
"sync"
|
||||||
@@ -69,6 +70,8 @@ type Client struct {
|
|||||||
IsCompress bool `json:"isCompress"`
|
IsCompress bool `json:"isCompress"`
|
||||||
UserID string `json:"userID"`
|
UserID string `json:"userID"`
|
||||||
IsBackground bool `json:"isBackground"`
|
IsBackground bool `json:"isBackground"`
|
||||||
|
SDKType string `json:"sdkType"`
|
||||||
|
Encoder Encoder
|
||||||
ctx *UserConnContext
|
ctx *UserConnContext
|
||||||
longConnServer LongConnServer
|
longConnServer LongConnServer
|
||||||
closed atomic.Bool
|
closed atomic.Bool
|
||||||
@@ -94,11 +97,17 @@ func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer
|
|||||||
c.closed.Store(false)
|
c.closed.Store(false)
|
||||||
c.closedErr = nil
|
c.closedErr = nil
|
||||||
c.token = ctx.GetToken()
|
c.token = ctx.GetToken()
|
||||||
|
c.SDKType = ctx.GetSDKType()
|
||||||
c.hbCtx, c.hbCancel = context.WithCancel(c.ctx)
|
c.hbCtx, c.hbCancel = context.WithCancel(c.ctx)
|
||||||
c.subLock = new(sync.Mutex)
|
c.subLock = new(sync.Mutex)
|
||||||
if c.subUserIDs != nil {
|
if c.subUserIDs != nil {
|
||||||
clear(c.subUserIDs)
|
clear(c.subUserIDs)
|
||||||
}
|
}
|
||||||
|
if c.SDKType == GoSDK {
|
||||||
|
c.Encoder = NewGobEncoder()
|
||||||
|
} else {
|
||||||
|
c.Encoder = NewJsonEncoder()
|
||||||
|
}
|
||||||
c.subUserIDs = make(map[string]struct{})
|
c.subUserIDs = make(map[string]struct{})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,9 +168,12 @@ func (c *Client) readMessage() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
case MessageText:
|
case MessageText:
|
||||||
c.closedErr = ErrNotSupportMessageProtocol
|
_ = c.conn.SetReadDeadline(pongWait)
|
||||||
return
|
parseDataErr := c.handlerTextMessage(message)
|
||||||
|
if parseDataErr != nil {
|
||||||
|
c.closedErr = parseDataErr
|
||||||
|
return
|
||||||
|
}
|
||||||
case PingMessage:
|
case PingMessage:
|
||||||
err := c.writePongMsg("")
|
err := c.writePongMsg("")
|
||||||
log.ZError(c.ctx, "writePongMsg", err)
|
log.ZError(c.ctx, "writePongMsg", err)
|
||||||
@@ -188,7 +200,7 @@ func (c *Client) handleMessage(message []byte) error {
|
|||||||
var binaryReq = getReq()
|
var binaryReq = getReq()
|
||||||
defer freeReq(binaryReq)
|
defer freeReq(binaryReq)
|
||||||
|
|
||||||
err := c.longConnServer.Decode(message, binaryReq)
|
err := c.Encoder.Decode(message, binaryReq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -335,7 +347,7 @@ func (c *Client) writeBinaryMsg(resp Resp) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
encodedBuf, err := c.longConnServer.Encode(resp)
|
encodedBuf, err := c.Encoder.Encode(resp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -363,6 +375,11 @@ func (c *Client) writeBinaryMsg(resp Resp) error {
|
|||||||
func (c *Client) activeHeartbeat(ctx context.Context) {
|
func (c *Client) activeHeartbeat(ctx context.Context) {
|
||||||
if c.PlatformID == constant.WebPlatformID {
|
if c.PlatformID == constant.WebPlatformID {
|
||||||
go func() {
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.ZPanic(ctx, "activeHeartbeat Panic", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
log.ZDebug(ctx, "server initiative send heartbeat start.")
|
log.ZDebug(ctx, "server initiative send heartbeat start.")
|
||||||
ticker := time.NewTicker(pingPeriod)
|
ticker := time.NewTicker(pingPeriod)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
@@ -419,3 +436,28 @@ func (c *Client) writePongMsg(appData string) error {
|
|||||||
|
|
||||||
return errs.Wrap(err)
|
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"
|
GzipCompressionProtocol = "gzip"
|
||||||
BackgroundStatus = "isBackground"
|
BackgroundStatus = "isBackground"
|
||||||
SendResponse = "isMsgResp"
|
SendResponse = "isMsgResp"
|
||||||
|
SDKType = "sdkType"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
GoSDK = "go"
|
||||||
|
JsSDK = "js"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -153,6 +153,14 @@ func (c *UserConnContext) GetCompression() bool {
|
|||||||
return false
|
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 {
|
func (c *UserConnContext) ShouldSendResp() bool {
|
||||||
errResp, exists := c.Query(SendResponse)
|
errResp, exists := c.Query(SendResponse)
|
||||||
if exists {
|
if exists {
|
||||||
@@ -193,7 +201,11 @@ func (c *UserConnContext) ParseEssentialArgs() error {
|
|||||||
_, err := strconv.Atoi(platformIDStr)
|
_, err := strconv.Atoi(platformIDStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return servererrs.ErrConnArgsErr.WrapMsg("platformID is not int")
|
return servererrs.ErrConnArgsErr.WrapMsg("platformID is not int")
|
||||||
|
}
|
||||||
|
switch sdkType, _ := c.Query(SDKType); sdkType {
|
||||||
|
case "", GoSDK, JsSDK:
|
||||||
|
default:
|
||||||
|
return servererrs.ErrConnArgsErr.WrapMsg("sdkType is not go or js")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package msggateway
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
"github.com/openimsdk/tools/errs"
|
"github.com/openimsdk/tools/errs"
|
||||||
)
|
)
|
||||||
@@ -28,12 +29,12 @@ type Encoder interface {
|
|||||||
|
|
||||||
type GobEncoder struct{}
|
type GobEncoder struct{}
|
||||||
|
|
||||||
func NewGobEncoder() *GobEncoder {
|
func NewGobEncoder() Encoder {
|
||||||
return &GobEncoder{}
|
return GobEncoder{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GobEncoder) Encode(data any) ([]byte, error) {
|
func (g GobEncoder) Encode(data any) ([]byte, error) {
|
||||||
buff := bytes.Buffer{}
|
var buff bytes.Buffer
|
||||||
enc := gob.NewEncoder(&buff)
|
enc := gob.NewEncoder(&buff)
|
||||||
if err := enc.Encode(data); err != nil {
|
if err := enc.Encode(data); err != nil {
|
||||||
return nil, errs.WrapMsg(err, "GobEncoder.Encode failed", "action", "encode")
|
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
|
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)
|
buff := bytes.NewBuffer(encodeData)
|
||||||
dec := gob.NewDecoder(buff)
|
dec := gob.NewDecoder(buff)
|
||||||
if err := dec.Decode(decodeData); err != nil {
|
if err := dec.Decode(decodeData); err != nil {
|
||||||
@@ -49,3 +50,25 @@ func (g *GobEncoder) Decode(encodeData []byte, decodeData any) error {
|
|||||||
}
|
}
|
||||||
return nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ func (s *Server) Start(ctx context.Context, index int, conf *Config) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
|
msggateway.UnimplementedMsgGatewayServer
|
||||||
rpcPort int
|
rpcPort int
|
||||||
LongConnServer LongConnServer
|
LongConnServer LongConnServer
|
||||||
config *Config
|
config *Config
|
||||||
@@ -83,17 +84,11 @@ func NewServer(rpcPort int, longConnServer LongConnServer, conf *Config, ready f
|
|||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) OnlinePushMsg(
|
func (s *Server) OnlinePushMsg(context context.Context, req *msggateway.OnlinePushMsgReq) (*msggateway.OnlinePushMsgResp, error) {
|
||||||
context context.Context,
|
|
||||||
req *msggateway.OnlinePushMsgReq,
|
|
||||||
) (*msggateway.OnlinePushMsgResp, error) {
|
|
||||||
panic("implement me")
|
panic("implement me")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) GetUsersOnlineStatus(
|
func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUsersOnlineStatusReq) (*msggateway.GetUsersOnlineStatusResp, error) {
|
||||||
ctx context.Context,
|
|
||||||
req *msggateway.GetUsersOnlineStatusReq,
|
|
||||||
) (*msggateway.GetUsersOnlineStatusResp, error) {
|
|
||||||
if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) {
|
if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) {
|
||||||
return nil, errs.ErrNoPermission.WrapMsg("only app manager")
|
return nil, errs.ErrNoPermission.WrapMsg("only app manager")
|
||||||
}
|
}
|
||||||
@@ -155,6 +150,7 @@ func (s *Server) pushToUser(ctx context.Context, userID string, msgData *sdkws.M
|
|||||||
(client.IsBackground && client.PlatformID != constant.IOSPlatformID) {
|
(client.IsBackground && client.PlatformID != constant.IOSPlatformID) {
|
||||||
err := client.PushMessage(ctx, msgData)
|
err := client.PushMessage(ctx, msgData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.ZWarn(ctx, "online push msg failed", err, "userID", userID, "platformID", client.PlatformID)
|
||||||
userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code())
|
userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code())
|
||||||
} else {
|
} else {
|
||||||
if _, ok := s.pushTerminal[client.PlatformID]; ok {
|
if _, ok := s.pushTerminal[client.PlatformID]; ok {
|
||||||
@@ -220,10 +216,7 @@ func (s *Server) SuperGroupOnlineBatchPushOneMsg(ctx context.Context, req *msgga
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) KickUserOffline(
|
func (s *Server) KickUserOffline(ctx context.Context, req *msggateway.KickUserOfflineReq) (*msggateway.KickUserOfflineResp, error) {
|
||||||
ctx context.Context,
|
|
||||||
req *msggateway.KickUserOfflineReq,
|
|
||||||
) (*msggateway.KickUserOfflineResp, error) {
|
|
||||||
for _, v := range req.KickUserIDList {
|
for _, v := range req.KickUserIDList {
|
||||||
clients, _, ok := s.LongConnServer.GetUserPlatformCons(v, int(req.PlatformID))
|
clients, _, ok := s.LongConnServer.GetUserPlatformCons(v, int(req.PlatformID))
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package msggateway
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
@@ -31,6 +32,16 @@ import (
|
|||||||
"github.com/openimsdk/tools/utils/jsonutil"
|
"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 {
|
type Req struct {
|
||||||
ReqIdentifier int32 `json:"reqIdentifier" validate:"required"`
|
ReqIdentifier int32 `json:"reqIdentifier" validate:"required"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
|
|||||||
@@ -90,6 +90,19 @@ func (ws *WsServer) ChangeOnlineStatus(concurrent int) {
|
|||||||
if _, err := ws.userClient.Client.SetUserOnlineStatus(ctx, req); err != nil {
|
if _, err := ws.userClient.Client.SetUserOnlineStatus(ctx, req); err != nil {
|
||||||
log.ZError(ctx, "update user online status", err)
|
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++ {
|
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
|
package msggateway
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -51,7 +37,6 @@ type LongConnServer interface {
|
|||||||
SetKickHandlerInfo(i *kickHandler)
|
SetKickHandlerInfo(i *kickHandler)
|
||||||
SubUserOnlineStatus(ctx context.Context, client *Client, data *Req) ([]byte, error)
|
SubUserOnlineStatus(ctx context.Context, client *Client, data *Req) ([]byte, error)
|
||||||
Compressor
|
Compressor
|
||||||
Encoder
|
|
||||||
MessageHandler
|
MessageHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +60,7 @@ type WsServer struct {
|
|||||||
authClient *rpcclient.Auth
|
authClient *rpcclient.Auth
|
||||||
disCov discovery.SvcDiscoveryRegistry
|
disCov discovery.SvcDiscoveryRegistry
|
||||||
Compressor
|
Compressor
|
||||||
Encoder
|
//Encoder
|
||||||
MessageHandler
|
MessageHandler
|
||||||
webhookClient *webhook.Client
|
webhookClient *webhook.Client
|
||||||
}
|
}
|
||||||
@@ -149,7 +134,6 @@ func NewWsServer(msgGatewayConfig *Config, opts ...Option) *WsServer {
|
|||||||
clients: newUserMap(),
|
clients: newUserMap(),
|
||||||
subscription: newSubscription(),
|
subscription: newSubscription(),
|
||||||
Compressor: NewGzipCompressor(),
|
Compressor: NewGzipCompressor(),
|
||||||
Encoder: NewGobEncoder(),
|
|
||||||
webhookClient: webhook.NewWebhookClient(msgGatewayConfig.WebhooksConfig.URL),
|
webhookClient: webhook.NewWebhookClient(msgGatewayConfig.WebhooksConfig.URL),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -212,7 +196,6 @@ func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *C
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
wg := errgroup.Group{}
|
wg := errgroup.Group{}
|
||||||
wg.SetLimit(concurrentRequest)
|
wg.SetLimit(concurrentRequest)
|
||||||
|
|
||||||
@@ -293,14 +276,7 @@ func (ws *WsServer) registerClient(client *Client) {
|
|||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
log.ZDebug(
|
log.ZDebug(client.ctx, "user online", "online user Num", ws.onlineUserNum.Load(), "online user conn Num", ws.onlineUserConnNum.Load())
|
||||||
client.ctx,
|
|
||||||
"user online",
|
|
||||||
"online user Num",
|
|
||||||
ws.onlineUserNum.Load(),
|
|
||||||
"online user conn Num",
|
|
||||||
ws.onlineUserConnNum.Load(),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRemoteAdders(client []*Client) string {
|
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) {
|
func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Client, newClient *Client) {
|
||||||
switch ws.msgGatewayConfig.Share.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.DefalutNotKick:
|
||||||
case constant.PCAndOther:
|
case constant.PCAndOther:
|
||||||
if constant.PlatformIDToClass(newClient.PlatformID) == constant.TerminalPC {
|
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,
|
log.ZWarn(newClient.ctx, "InvalidateToken err", err, "userID", newClient.UserID,
|
||||||
"platformID", newClient.PlatformID)
|
"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.historyCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyCH)
|
||||||
go m.historyMongoCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyMongoCH)
|
go m.historyMongoCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyMongoCH)
|
||||||
|
go m.historyCH.HandleUserHasReadSeqMessages(m.ctx)
|
||||||
err := m.historyCH.redisMessageBatches.Start()
|
err := m.historyCH.redisMessageBatches.Start()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -135,6 +136,11 @@ func (m *MsgTransfer) Start(index int, config *Config) error {
|
|||||||
|
|
||||||
if config.MsgTransfer.Prometheus.Enable {
|
if config.MsgTransfer.Prometheus.Enable {
|
||||||
go func() {
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.ZPanic(m.ctx, "MsgTransfer Start Panic", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
prometheusPort, err := datautil.GetElemByIndex(config.MsgTransfer.Prometheus.Ports, index)
|
prometheusPort, err := datautil.GetElemByIndex(config.MsgTransfer.Prometheus.Ports, index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
netErr = err
|
netErr = err
|
||||||
@@ -157,12 +163,14 @@ func (m *MsgTransfer) Start(index int, config *Config) error {
|
|||||||
// graceful close kafka client.
|
// graceful close kafka client.
|
||||||
m.cancel()
|
m.cancel()
|
||||||
m.historyCH.redisMessageBatches.Close()
|
m.historyCH.redisMessageBatches.Close()
|
||||||
|
m.historyCH.Close()
|
||||||
m.historyCH.historyConsumerGroup.Close()
|
m.historyCH.historyConsumerGroup.Close()
|
||||||
m.historyMongoCH.historyConsumerGroup.Close()
|
m.historyMongoCH.historyConsumerGroup.Close()
|
||||||
return nil
|
return nil
|
||||||
case <-netDone:
|
case <-netDone:
|
||||||
m.cancel()
|
m.cancel()
|
||||||
m.historyCH.redisMessageBatches.Close()
|
m.historyCH.redisMessageBatches.Close()
|
||||||
|
m.historyCH.Close()
|
||||||
m.historyCH.historyConsumerGroup.Close()
|
m.historyCH.historyConsumerGroup.Close()
|
||||||
m.historyMongoCH.historyConsumerGroup.Close()
|
m.historyMongoCH.historyConsumerGroup.Close()
|
||||||
close(netDone)
|
close(netDone)
|
||||||
|
|||||||
@@ -20,8 +20,11 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
||||||
|
|
||||||
"github.com/IBM/sarama"
|
"github.com/IBM/sarama"
|
||||||
"github.com/go-redis/redis"
|
"github.com/go-redis/redis"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
@@ -40,11 +43,12 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
size = 500
|
size = 500
|
||||||
mainDataBuffer = 500
|
mainDataBuffer = 500
|
||||||
subChanBuffer = 50
|
subChanBuffer = 50
|
||||||
worker = 50
|
worker = 50
|
||||||
interval = 100 * time.Millisecond
|
interval = 100 * time.Millisecond
|
||||||
|
hasReadChanBuffer = 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContextMsg struct {
|
type ContextMsg struct {
|
||||||
@@ -52,14 +56,23 @@ type ContextMsg struct {
|
|||||||
ctx context.Context
|
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 {
|
type OnlineHistoryRedisConsumerHandler struct {
|
||||||
historyConsumerGroup *kafka.MConsumerGroup
|
historyConsumerGroup *kafka.MConsumerGroup
|
||||||
|
|
||||||
redisMessageBatches *batcher.Batcher[sarama.ConsumerMessage]
|
redisMessageBatches *batcher.Batcher[sarama.ConsumerMessage]
|
||||||
|
|
||||||
msgTransferDatabase controller.MsgTransferDatabase
|
msgTransferDatabase controller.MsgTransferDatabase
|
||||||
conversationRpcClient *rpcclient.ConversationRpcClient
|
conversationRpcClient *rpcclient.ConversationRpcClient
|
||||||
groupRpcClient *rpcclient.GroupRpcClient
|
groupRpcClient *rpcclient.GroupRpcClient
|
||||||
|
conversationUserHasReadChan chan *userHasReadSeq
|
||||||
|
wg sync.WaitGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOnlineHistoryRedisConsumerHandler(kafkaConf *config.Kafka, database controller.MsgTransferDatabase,
|
func NewOnlineHistoryRedisConsumerHandler(kafkaConf *config.Kafka, database controller.MsgTransferDatabase,
|
||||||
@@ -70,6 +83,8 @@ func NewOnlineHistoryRedisConsumerHandler(kafkaConf *config.Kafka, database cont
|
|||||||
}
|
}
|
||||||
var och OnlineHistoryRedisConsumerHandler
|
var och OnlineHistoryRedisConsumerHandler
|
||||||
och.msgTransferDatabase = database
|
och.msgTransferDatabase = database
|
||||||
|
och.conversationUserHasReadChan = make(chan *userHasReadSeq, hasReadChanBuffer)
|
||||||
|
och.wg.Add(1)
|
||||||
|
|
||||||
b := batcher.New[sarama.ConsumerMessage](
|
b := batcher.New[sarama.ConsumerMessage](
|
||||||
batcher.WithSize(size),
|
batcher.WithSize(size),
|
||||||
@@ -115,25 +130,25 @@ func (och *OnlineHistoryRedisConsumerHandler) do(ctx context.Context, channelID
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (och *OnlineHistoryRedisConsumerHandler) doSetReadSeq(ctx context.Context, msgs []*ContextMsg) {
|
func (och *OnlineHistoryRedisConsumerHandler) doSetReadSeq(ctx context.Context, msgs []*ContextMsg) {
|
||||||
type seqKey struct {
|
|
||||||
conversationID string
|
var conversationID string
|
||||||
userID string
|
var userSeqMap map[string]int64
|
||||||
}
|
|
||||||
var readSeq map[seqKey]int64
|
|
||||||
for _, msg := range msgs {
|
for _, msg := range msgs {
|
||||||
if msg.message.ContentType != constant.HasReadReceipt {
|
if msg.message.ContentType != constant.HasReadReceipt {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var elem sdkws.NotificationElem
|
var elem sdkws.NotificationElem
|
||||||
if err := json.Unmarshal(msg.message.Content, &elem); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
var tips sdkws.MarkAsReadTips
|
var tips sdkws.MarkAsReadTips
|
||||||
if err := json.Unmarshal([]byte(elem.Detail), &tips); err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
//The conversation ID for each batch of messages processed by the batcher is the same.
|
||||||
|
conversationID = tips.ConversationID
|
||||||
if len(tips.Seqs) > 0 {
|
if len(tips.Seqs) > 0 {
|
||||||
for _, seq := range tips.Seqs {
|
for _, seq := range tips.Seqs {
|
||||||
if tips.HasReadSeq < seq {
|
if tips.HasReadSeq < seq {
|
||||||
@@ -146,26 +161,25 @@ func (och *OnlineHistoryRedisConsumerHandler) doSetReadSeq(ctx context.Context,
|
|||||||
if tips.HasReadSeq < 0 {
|
if tips.HasReadSeq < 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if readSeq == nil {
|
if userSeqMap == nil {
|
||||||
readSeq = make(map[seqKey]int64)
|
userSeqMap = make(map[string]int64)
|
||||||
}
|
}
|
||||||
key := seqKey{
|
|
||||||
conversationID: tips.ConversationID,
|
if userSeqMap[tips.MarkAsReadUserID] > tips.HasReadSeq {
|
||||||
userID: tips.MarkAsReadUserID,
|
|
||||||
}
|
|
||||||
if readSeq[key] > tips.HasReadSeq {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
readSeq[key] = tips.HasReadSeq
|
userSeqMap[tips.MarkAsReadUserID] = tips.HasReadSeq
|
||||||
}
|
}
|
||||||
if readSeq == nil {
|
if userSeqMap == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for key, seq := range readSeq {
|
if len(conversationID) == 0 {
|
||||||
if err := och.msgTransferDatabase.SetHasReadSeqToDB(ctx, key.userID, key.conversationID, seq); err != nil {
|
log.ZWarn(ctx, "conversation err", nil, "conversationID", conversationID)
|
||||||
log.ZError(ctx, "set read seq to db error", err, "userID", key.userID, "conversationID", key.conversationID, "seq", seq)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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 {
|
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 {
|
if len(storageMessageList) > 0 {
|
||||||
msg := 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) {
|
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
|
return
|
||||||
}
|
}
|
||||||
log.ZInfo(ctx, "BatchInsertChat2Cache end")
|
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 {
|
if isNewConversation {
|
||||||
switch msg.SessionType {
|
switch msg.SessionType {
|
||||||
@@ -308,7 +331,7 @@ func (och *OnlineHistoryRedisConsumerHandler) handleNotification(ctx context.Con
|
|||||||
storageMessageList = append(storageMessageList, msg.message)
|
storageMessageList = append(storageMessageList, msg.message)
|
||||||
}
|
}
|
||||||
if len(storageMessageList) > 0 {
|
if len(storageMessageList) > 0 {
|
||||||
lastSeq, _, err := och.msgTransferDatabase.BatchInsertChat2Cache(ctx, conversationID, storageMessageList)
|
lastSeq, _, _, err := och.msgTransferDatabase.BatchInsertChat2Cache(ctx, conversationID, storageMessageList)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZError(ctx, "notification batch insert to redis error", err, "conversationID", conversationID,
|
log.ZError(ctx, "notification batch insert to redis error", err, "conversationID", conversationID,
|
||||||
"storageList", storageMessageList)
|
"storageList", storageMessageList)
|
||||||
@@ -323,6 +346,27 @@ func (och *OnlineHistoryRedisConsumerHandler) handleNotification(ctx context.Con
|
|||||||
och.toPushTopic(ctx, key, conversationID, storageList)
|
och.toPushTopic(ctx, key, conversationID, storageList)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func (och *OnlineHistoryRedisConsumerHandler) HandleUserHasReadSeqMessages(ctx context.Context) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.ZPanic(ctx, "HandleUserHasReadSeqMessages Panic", 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) {
|
func (och *OnlineHistoryRedisConsumerHandler) toPushTopic(ctx context.Context, key, conversationID string, msgs []*ContextMsg) {
|
||||||
for _, v := range msgs {
|
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/constant"
|
||||||
"github.com/openimsdk/protocol/sdkws"
|
"github.com/openimsdk/protocol/sdkws"
|
||||||
"github.com/openimsdk/tools/mcontext"
|
"github.com/openimsdk/tools/mcontext"
|
||||||
"github.com/openimsdk/tools/utils/datautil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *ConsumerHandler) webhookBeforeOfflinePush(ctx context.Context, before *config.BeforeConfig, userIDs []string, msg *sdkws.MsgData, offlinePushUserIDs *[]string) error {
|
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 {
|
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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
req := callbackstruct.CallbackBeforePushReq{
|
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 {
|
func (d *Dummy) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error {
|
||||||
log.ZDebug(ctx, "dummy push")
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
package body
|
package body
|
||||||
|
|
||||||
import (
|
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/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,38 +27,44 @@ type Notification struct {
|
|||||||
|
|
||||||
type Android struct {
|
type Android struct {
|
||||||
Alert string `json:"alert,omitempty"`
|
Alert string `json:"alert,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
Intent struct {
|
Intent struct {
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
} `json:"intent,omitempty"`
|
} `json:"intent,omitempty"`
|
||||||
Extras Extras `json:"extras"`
|
Extras map[string]string `json:"extras,omitempty"`
|
||||||
}
|
}
|
||||||
type Ios struct {
|
type Ios struct {
|
||||||
Alert string `json:"alert,omitempty"`
|
Alert IosAlert `json:"alert,omitempty"`
|
||||||
Sound string `json:"sound,omitempty"`
|
Sound string `json:"sound,omitempty"`
|
||||||
Badge string `json:"badge,omitempty"`
|
Badge string `json:"badge,omitempty"`
|
||||||
Extras Extras `json:"extras"`
|
Extras map[string]string `json:"extras,omitempty"`
|
||||||
MutableContent bool `json:"mutable-content"`
|
MutableContent bool `json:"mutable-content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Extras struct {
|
type IosAlert struct {
|
||||||
ClientMsgID string `json:"clientMsgID"`
|
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.Alert = alert
|
||||||
n.Android.Alert = alert
|
n.Android.Alert = alert
|
||||||
n.IOS.Alert = alert
|
n.Android.Title = title
|
||||||
n.IOS.Sound = "default"
|
n.IOS.Alert.Body = alert
|
||||||
n.IOS.Badge = "+1"
|
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.IOS.Extras = extras
|
||||||
n.Android.Extras = extras
|
n.Android.Extras = extras
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Notification) SetAndroidIntent(pushConf *config.Push) {
|
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() {
|
func (n *Notification) IOSEnableMutableContent() {
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"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/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/open-im-server/v3/pkg/common/config"
|
||||||
"github.com/openimsdk/tools/utils/httputil"
|
"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
|
var au body.Audience
|
||||||
au.SetAlias(userIDs)
|
au.SetAlias(userIDs)
|
||||||
var no body.Notification
|
var no body.Notification
|
||||||
var extras body.Extras
|
extras := make(map[string]string)
|
||||||
|
extras["ex"] = opts.Ex
|
||||||
if opts.Signal.ClientMsgID != "" {
|
if opts.Signal.ClientMsgID != "" {
|
||||||
extras.ClientMsgID = opts.Signal.ClientMsgID
|
extras["ClientMsgID"] = opts.Signal.ClientMsgID
|
||||||
}
|
}
|
||||||
no.IOSEnableMutableContent()
|
no.IOSEnableMutableContent()
|
||||||
no.SetExtras(extras)
|
no.SetExtras(extras)
|
||||||
no.SetAlert(title)
|
no.SetAlert(content, title, opts)
|
||||||
no.SetAndroidIntent(j.pushConf)
|
no.SetAndroidIntent(j.pushConf)
|
||||||
|
|
||||||
var msg body.Message
|
var msg body.Message
|
||||||
msg.SetMsgContent(content)
|
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
|
var opt body.Options
|
||||||
opt.SetApnsProduction(j.pushConf.IOSPush.Production)
|
opt.SetApnsProduction(j.pushConf.IOSPush.Production)
|
||||||
var pushObj body.PushObj
|
var pushObj body.PushObj
|
||||||
@@ -76,19 +82,26 @@ func (j *JPush) Push(ctx context.Context, userIDs []string, title, content strin
|
|||||||
pushObj.SetNotification(&no)
|
pushObj.SetNotification(&no)
|
||||||
pushObj.SetMessage(&msg)
|
pushObj.SetMessage(&msg)
|
||||||
pushObj.SetOptions(&opt)
|
pushObj.SetOptions(&opt)
|
||||||
var resp any
|
var resp map[string]any
|
||||||
return j.request(ctx, pushObj, resp, 5)
|
return j.request(ctx, pushObj, &resp, 5)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (j *JPush) request(ctx context.Context, po body.PushObj, resp any, timeout int) error {
|
func (j *JPush) request(ctx context.Context, po body.PushObj, resp *map[string]any, timeout int) error {
|
||||||
return j.httpClient.PostReturn(
|
err := j.httpClient.PostReturn(
|
||||||
ctx,
|
ctx,
|
||||||
j.pushConf.JPNS.PushURL,
|
j.pushConf.JPush.PushURL,
|
||||||
map[string]string{
|
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,
|
po,
|
||||||
resp,
|
resp,
|
||||||
timeout,
|
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/internal/push/offlinepush/options"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
geTUI = "geTui"
|
geTUI = "getui"
|
||||||
firebase = "fcm"
|
firebase = "fcm"
|
||||||
jPush = "jpush"
|
jPush = "jpush"
|
||||||
)
|
)
|
||||||
@@ -38,6 +39,7 @@ type OfflinePusher interface {
|
|||||||
|
|
||||||
func NewOfflinePusher(pushConf *config.Push, cache cache.ThirdCache, fcmConfigPath string) (OfflinePusher, error) {
|
func NewOfflinePusher(pushConf *config.Push, cache cache.ThirdCache, fcmConfigPath string) (OfflinePusher, error) {
|
||||||
var offlinePusher OfflinePusher
|
var offlinePusher OfflinePusher
|
||||||
|
pushConf.Enable = strings.ToLower(pushConf.Enable)
|
||||||
switch pushConf.Enable {
|
switch pushConf.Enable {
|
||||||
case geTUI:
|
case geTUI:
|
||||||
offlinePusher = getui.NewClient(pushConf, cache)
|
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)
|
log.ZError(ctx, "offline push msg is empty", errs.New("offlinePushMsg is empty"), "userIDs", offlinePushMsg.UserIDs, "msg", offlinePushMsg.MsgData)
|
||||||
return
|
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)
|
log.ZInfo(ctx, "receive to OfflinePush MQ", "userIDs", offlinePushMsg.UserIDs, "msg", offlinePushMsg.MsgData)
|
||||||
|
|
||||||
err := o.offlinePushMsg(ctx, offlinePushMsg.MsgData, offlinePushMsg.UserIDs)
|
err := o.offlinePushMsg(ctx, offlinePushMsg.MsgData, offlinePushMsg.UserIDs)
|
||||||
@@ -70,7 +73,7 @@ func (o *OfflinePushConsumerHandler) getOfflinePushInfos(msg *sdkws.MsgData) (ti
|
|||||||
IsAtSelf bool `json:"isAtSelf"`
|
IsAtSelf bool `json:"isAtSelf"`
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = &options.Opts{Signal: &options.Signal{}}
|
opts = &options.Opts{Signal: &options.Signal{ClientMsgID: msg.ClientMsgID}}
|
||||||
if msg.OfflinePushInfo != nil {
|
if msg.OfflinePushInfo != nil {
|
||||||
opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount
|
opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount
|
||||||
opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound
|
opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type pushServer struct {
|
type pushServer struct {
|
||||||
|
pbpush.UnimplementedPushMsgServiceServer
|
||||||
database controller.PushDatabase
|
database controller.PushDatabase
|
||||||
disCov discovery.SvcDiscoveryRegistry
|
disCov discovery.SvcDiscoveryRegistry
|
||||||
offlinePusher offlinepush.OfflinePusher
|
offlinePusher offlinepush.OfflinePusher
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
|
||||||
|
"math/rand"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/IBM/sarama"
|
"github.com/IBM/sarama"
|
||||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
|
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
|
||||||
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
|
||||||
@@ -27,9 +31,6 @@ import (
|
|||||||
"github.com/openimsdk/tools/utils/timeutil"
|
"github.com/openimsdk/tools/utils/timeutil"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type ConsumerHandler struct {
|
type ConsumerHandler struct {
|
||||||
@@ -165,17 +166,21 @@ func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
offlinePushUserID := []string{msg.RecvID}
|
needOfflinePushUserID := []string{msg.RecvID}
|
||||||
|
var offlinePushUserID []string
|
||||||
|
|
||||||
//receiver offline push
|
//receiver offline push
|
||||||
if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush,
|
if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, needOfflinePushUserID, msg, &offlinePushUserID); err != nil {
|
||||||
offlinePushUserID, msg, nil); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.ZInfo(ctx, "webhookBeforeOfflinePush end")
|
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 {
|
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
|
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) {
|
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)
|
onlineUserIDs, offlineUserIDs, err := c.onlineCache.GetUsersOnline(ctx, pushToUserIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
func (c *ConsumerHandler) offlinePushMsg(ctx context.Context, msg *sdkws.MsgData, offlinePushUserIDs []string) error {
|
||||||
title, content, opts, err := c.getOfflinePushInfos(msg)
|
title, content, opts, err := c.getOfflinePushInfos(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.ZError(ctx, "getOfflinePushInfos failed", err, "msg", msg)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = c.offlinePusher.Push(ctx, offlinePushUserIDs, title, content, opts)
|
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"`
|
IsAtSelf bool `json:"isAtSelf"`
|
||||||
}
|
}
|
||||||
|
|
||||||
opts = &options.Opts{Signal: &options.Signal{}}
|
opts = &options.Opts{Signal: &options.Signal{ClientMsgID: msg.ClientMsgID}}
|
||||||
if msg.OfflinePushInfo != nil {
|
if msg.OfflinePushInfo != nil {
|
||||||
opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount
|
opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount
|
||||||
opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound
|
opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound
|
||||||
@@ -405,6 +414,7 @@ func (c *ConsumerHandler) DeleteMemberAndSetConversationSeq(ctx context.Context,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.conversationRpcClient.SetConversationMaxSeq(ctx, userIDs, conversationID, maxSeq)
|
return c.conversationRpcClient.SetConversationMaxSeq(ctx, userIDs, conversationID, maxSeq)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package auth
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
||||||
@@ -39,6 +40,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type authServer struct {
|
type authServer struct {
|
||||||
|
pbauth.UnimplementedAuthServer
|
||||||
authDatabase controller.AuthDatabase
|
authDatabase controller.AuthDatabase
|
||||||
userRpcClient *rpcclient.UserRpcClient
|
userRpcClient *rpcclient.UserRpcClient
|
||||||
RegisterCenter discovery.SvcDiscoveryRegistry
|
RegisterCenter discovery.SvcDiscoveryRegistry
|
||||||
@@ -65,7 +67,8 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
|||||||
redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire),
|
redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire),
|
||||||
config.Share.Secret,
|
config.Share.Secret,
|
||||||
config.RpcConfig.TokenPolicy.Expire,
|
config.RpcConfig.TokenPolicy.Expire,
|
||||||
config.Share.MultiLoginPolicy,
|
config.Share.MultiLogin,
|
||||||
|
config.Share.IMAdminUserID,
|
||||||
),
|
),
|
||||||
config: config,
|
config: config,
|
||||||
})
|
})
|
||||||
@@ -129,6 +132,10 @@ func (s *authServer) parseToken(ctx context.Context, tokensString string) (claim
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.Wrap(err)
|
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)
|
m, err := s.authDatabase.GetTokensWithoutError(ctx, claims.UserID, claims.PlatformID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -190,7 +197,7 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID
|
|||||||
}
|
}
|
||||||
|
|
||||||
m, err := s.authDatabase.GetTokensWithoutError(ctx, userID, int(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
|
return err
|
||||||
}
|
}
|
||||||
for k := range m {
|
for k := range m {
|
||||||
@@ -208,7 +215,7 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID
|
|||||||
|
|
||||||
func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.InvalidateTokenReq) (*pbauth.InvalidateTokenResp, error) {
|
func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.InvalidateTokenReq) (*pbauth.InvalidateTokenResp, error) {
|
||||||
m, err := s.authDatabase.GetTokensWithoutError(ctx, req.UserID, int(req.PlatformID))
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if m == nil {
|
if m == nil {
|
||||||
@@ -230,3 +237,10 @@ func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.Invalidate
|
|||||||
}
|
}
|
||||||
return &pbauth.InvalidateTokenResp{}, nil
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ package conversation
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||||
|
pbmsg "github.com/openimsdk/protocol/msg"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -43,6 +45,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type conversationServer struct {
|
type conversationServer struct {
|
||||||
|
pbconversation.UnimplementedConversationServer
|
||||||
msgRpcClient *rpcclient.MessageRpcClient
|
msgRpcClient *rpcclient.MessageRpcClient
|
||||||
user *rpcclient.UserRpcClient
|
user *rpcclient.UserRpcClient
|
||||||
groupRpcClient *rpcclient.GroupRpcClient
|
groupRpcClient *rpcclient.GroupRpcClient
|
||||||
@@ -261,27 +264,35 @@ func (c *conversationServer) SetConversations(ctx context.Context, req *pbconver
|
|||||||
|
|
||||||
setConversationFieldsFunc := func() {
|
setConversationFieldsFunc := func() {
|
||||||
if req.Conversation.RecvMsgOpt != nil {
|
if req.Conversation.RecvMsgOpt != nil {
|
||||||
|
conversation.RecvMsgOpt = req.Conversation.RecvMsgOpt.Value
|
||||||
m["recv_msg_opt"] = req.Conversation.RecvMsgOpt.Value
|
m["recv_msg_opt"] = req.Conversation.RecvMsgOpt.Value
|
||||||
}
|
}
|
||||||
if req.Conversation.AttachedInfo != nil {
|
if req.Conversation.AttachedInfo != nil {
|
||||||
|
conversation.AttachedInfo = req.Conversation.AttachedInfo.Value
|
||||||
m["attached_info"] = req.Conversation.AttachedInfo.Value
|
m["attached_info"] = req.Conversation.AttachedInfo.Value
|
||||||
}
|
}
|
||||||
if req.Conversation.Ex != nil {
|
if req.Conversation.Ex != nil {
|
||||||
|
conversation.Ex = req.Conversation.Ex.Value
|
||||||
m["ex"] = req.Conversation.Ex.Value
|
m["ex"] = req.Conversation.Ex.Value
|
||||||
}
|
}
|
||||||
if req.Conversation.IsPinned != nil {
|
if req.Conversation.IsPinned != nil {
|
||||||
|
conversation.IsPinned = req.Conversation.IsPinned.Value
|
||||||
m["is_pinned"] = req.Conversation.IsPinned.Value
|
m["is_pinned"] = req.Conversation.IsPinned.Value
|
||||||
}
|
}
|
||||||
if req.Conversation.GroupAtType != nil {
|
if req.Conversation.GroupAtType != nil {
|
||||||
|
conversation.GroupAtType = req.Conversation.GroupAtType.Value
|
||||||
m["group_at_type"] = req.Conversation.GroupAtType.Value
|
m["group_at_type"] = req.Conversation.GroupAtType.Value
|
||||||
}
|
}
|
||||||
if req.Conversation.MsgDestructTime != nil {
|
if req.Conversation.MsgDestructTime != nil {
|
||||||
|
conversation.MsgDestructTime = req.Conversation.MsgDestructTime.Value
|
||||||
m["msg_destruct_time"] = req.Conversation.MsgDestructTime.Value
|
m["msg_destruct_time"] = req.Conversation.MsgDestructTime.Value
|
||||||
}
|
}
|
||||||
if req.Conversation.IsMsgDestruct != nil {
|
if req.Conversation.IsMsgDestruct != nil {
|
||||||
|
conversation.IsMsgDestruct = req.Conversation.IsMsgDestruct.Value
|
||||||
m["is_msg_destruct"] = req.Conversation.IsMsgDestruct.Value
|
m["is_msg_destruct"] = req.Conversation.IsMsgDestruct.Value
|
||||||
}
|
}
|
||||||
if req.Conversation.BurnDuration != nil {
|
if req.Conversation.BurnDuration != nil {
|
||||||
|
conversation.BurnDuration = req.Conversation.BurnDuration.Value
|
||||||
m["burn_duration"] = req.Conversation.BurnDuration.Value
|
m["burn_duration"] = req.Conversation.BurnDuration.Value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -424,22 +435,38 @@ func (c *conversationServer) CreateGroupChatConversations(ctx context.Context, r
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID)
|
||||||
|
if _, err := c.msgRpcClient.Client.SetUserConversationMaxSeq(ctx, &pbmsg.SetUserConversationMaxSeqReq{ConversationID: conversationID, OwnerUserID: req.UserIDs, MaxSeq: 0}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return &pbconversation.CreateGroupChatConversationsResp{}, nil
|
return &pbconversation.CreateGroupChatConversationsResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conversationServer) SetConversationMaxSeq(ctx context.Context, req *pbconversation.SetConversationMaxSeqReq) (*pbconversation.SetConversationMaxSeqResp, error) {
|
func (c *conversationServer) SetConversationMaxSeq(ctx context.Context, req *pbconversation.SetConversationMaxSeqReq) (*pbconversation.SetConversationMaxSeqResp, error) {
|
||||||
|
if _, err := c.msgRpcClient.Client.SetUserConversationMaxSeq(ctx, &pbmsg.SetUserConversationMaxSeqReq{ConversationID: req.ConversationID, OwnerUserID: req.OwnerUserID, MaxSeq: req.MaxSeq}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := c.conversationDatabase.UpdateUsersConversationField(ctx, req.OwnerUserID, req.ConversationID,
|
if err := c.conversationDatabase.UpdateUsersConversationField(ctx, req.OwnerUserID, req.ConversationID,
|
||||||
map[string]any{"max_seq": req.MaxSeq}); err != nil {
|
map[string]any{"max_seq": req.MaxSeq}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, userID := range req.OwnerUserID {
|
||||||
|
c.conversationNotificationSender.ConversationChangeNotification(ctx, userID, []string{req.ConversationID})
|
||||||
|
}
|
||||||
return &pbconversation.SetConversationMaxSeqResp{}, nil
|
return &pbconversation.SetConversationMaxSeqResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conversationServer) SetConversationMinSeq(ctx context.Context, req *pbconversation.SetConversationMinSeqReq) (*pbconversation.SetConversationMinSeqResp, error) {
|
func (c *conversationServer) SetConversationMinSeq(ctx context.Context, req *pbconversation.SetConversationMinSeqReq) (*pbconversation.SetConversationMinSeqResp, error) {
|
||||||
|
if _, err := c.msgRpcClient.Client.SetUserConversationMinSeq(ctx, &pbmsg.SetUserConversationMinSeqReq{ConversationID: req.ConversationID, OwnerUserID: req.OwnerUserID, MinSeq: req.MinSeq}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if err := c.conversationDatabase.UpdateUsersConversationField(ctx, req.OwnerUserID, req.ConversationID,
|
if err := c.conversationDatabase.UpdateUsersConversationField(ctx, req.OwnerUserID, req.ConversationID,
|
||||||
map[string]any{"min_seq": req.MinSeq}); err != nil {
|
map[string]any{"min_seq": req.MinSeq}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
for _, userID := range req.OwnerUserID {
|
||||||
|
c.conversationNotificationSender.ConversationChangeNotification(ctx, userID, []string{req.ConversationID})
|
||||||
|
}
|
||||||
return &pbconversation.SetConversationMinSeqResp{}, nil
|
return &pbconversation.SetConversationMinSeqResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -661,7 +688,7 @@ func (c *conversationServer) GetOwnerConversation(ctx context.Context, req *pbco
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conversationServer) GetConversationsNeedDestructMsgs(ctx context.Context, _ *pbconversation.GetConversationsNeedDestructMsgsReq) (*pbconversation.GetConversationsNeedDestructMsgsResp, error) {
|
func (c *conversationServer) GetConversationsNeedClearMsg(ctx context.Context, _ *pbconversation.GetConversationsNeedClearMsgReq) (*pbconversation.GetConversationsNeedClearMsgResp, error) {
|
||||||
num, err := c.conversationDatabase.GetAllConversationIDsNumber(ctx)
|
num, err := c.conversationDatabase.GetAllConversationIDsNumber(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZError(ctx, "GetAllConversationIDsNumber failed", err)
|
log.ZError(ctx, "GetAllConversationIDsNumber failed", err)
|
||||||
@@ -685,7 +712,7 @@ func (c *conversationServer) GetConversationsNeedDestructMsgs(ctx context.Contex
|
|||||||
|
|
||||||
conversationIDs, err := c.conversationDatabase.PageConversationIDs(ctx, pagination)
|
conversationIDs, err := c.conversationDatabase.PageConversationIDs(ctx, pagination)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// log.ZError(ctx, "PageConversationIDs failed", err, "pageNumber", pageNumber)
|
log.ZError(ctx, "PageConversationIDs failed", err, "pageNumber", pageNumber)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,7 +735,7 @@ func (c *conversationServer) GetConversationsNeedDestructMsgs(ctx context.Contex
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pbconversation.GetConversationsNeedDestructMsgsResp{Conversations: convert.ConversationsDB2Pb(temp)}, nil
|
return &pbconversation.GetConversationsNeedClearMsgResp{Conversations: convert.ConversationsDB2Pb(temp)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, req *pbconversation.GetNotNotifyConversationIDsReq) (*pbconversation.GetNotNotifyConversationIDsResp, error) {
|
func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, req *pbconversation.GetNotNotifyConversationIDsReq) (*pbconversation.GetNotNotifyConversationIDsResp, error) {
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ func UpdateGroupMemberMap(req *pbgroup.SetGroupMemberInfo) map[string]any {
|
|||||||
m["nickname"] = req.Nickname.Value
|
m["nickname"] = req.Nickname.Value
|
||||||
}
|
}
|
||||||
if req.FaceURL != nil {
|
if req.FaceURL != nil {
|
||||||
m["user_group_face_url"] = req.FaceURL.Value
|
m["face_url"] = req.FaceURL.Value
|
||||||
}
|
}
|
||||||
if req.RoleLevel != nil {
|
if req.RoleLevel != nil {
|
||||||
m["role_level"] = req.RoleLevel.Value
|
m["role_level"] = req.RoleLevel.Value
|
||||||
|
|||||||
+86
-31
@@ -57,6 +57,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type groupServer struct {
|
type groupServer struct {
|
||||||
|
pbgroup.UnimplementedGroupServer
|
||||||
db controller.GroupDatabase
|
db controller.GroupDatabase
|
||||||
user rpcclient.UserRpcClient
|
user rpcclient.UserRpcClient
|
||||||
notification *GroupNotificationSender
|
notification *GroupNotificationSender
|
||||||
@@ -465,7 +466,7 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
|
|||||||
return nil, err
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &pbgroup.InviteUserToGroupResp{}, nil
|
return &pbgroup.InviteUserToGroupResp{}, nil
|
||||||
@@ -1026,7 +1027,7 @@ func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
|
|||||||
}
|
}
|
||||||
num := len(update)
|
num := len(update)
|
||||||
if req.GroupInfoForSet.Notification != "" {
|
if req.GroupInfoForSet.Notification != "" {
|
||||||
num--
|
num -= 3
|
||||||
func() {
|
func() {
|
||||||
conversation := &pbconversation.ConversationReq{
|
conversation := &pbconversation.ConversationReq{
|
||||||
ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupInfoForSet.GroupID),
|
ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupInfoForSet.GroupID),
|
||||||
@@ -1133,8 +1134,9 @@ func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupI
|
|||||||
}
|
}
|
||||||
|
|
||||||
num := len(updatedData)
|
num := len(updatedData)
|
||||||
|
|
||||||
if req.Notification != nil {
|
if req.Notification != nil {
|
||||||
num--
|
num -= 3
|
||||||
|
|
||||||
if req.Notification.Value != "" {
|
if req.Notification.Value != "" {
|
||||||
func() {
|
func() {
|
||||||
@@ -1180,36 +1182,53 @@ func (g *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.Status == constant.GroupStatusDismissed {
|
if group.Status == constant.GroupStatusDismissed {
|
||||||
return nil, servererrs.ErrDismissedAlready.Wrap()
|
return nil, servererrs.ErrDismissedAlready.Wrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
if req.OldOwnerUserID == req.NewOwnerUserID {
|
if req.OldOwnerUserID == req.NewOwnerUserID {
|
||||||
return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID == NewOwnerUserID")
|
return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID == NewOwnerUserID")
|
||||||
}
|
}
|
||||||
|
|
||||||
members, err := g.db.FindGroupMembers(ctx, req.GroupID, []string{req.OldOwnerUserID, req.NewOwnerUserID})
|
members, err := g.db.FindGroupMembers(ctx, req.GroupID, []string{req.OldOwnerUserID, req.NewOwnerUserID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.PopulateGroupMember(ctx, members...); err != nil {
|
if err := g.PopulateGroupMember(ctx, members...); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
memberMap := datautil.SliceToMap(members, func(e *model.GroupMember) string { return e.UserID })
|
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 {
|
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, ","))
|
return nil, errs.ErrArgs.WrapMsg("user not in group " + strings.Join(ids, ","))
|
||||||
}
|
}
|
||||||
|
|
||||||
oldOwner := memberMap[req.OldOwnerUserID]
|
oldOwner := memberMap[req.OldOwnerUserID]
|
||||||
if oldOwner == nil {
|
if oldOwner == nil {
|
||||||
return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID not in group " + req.NewOwnerUserID)
|
return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID not in group " + req.NewOwnerUserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
newOwner := memberMap[req.NewOwnerUserID]
|
newOwner := memberMap[req.NewOwnerUserID]
|
||||||
if newOwner == nil {
|
if newOwner == nil {
|
||||||
return nil, errs.ErrArgs.WrapMsg("NewOwnerUser not in group " + req.NewOwnerUserID)
|
return nil, errs.ErrArgs.WrapMsg("NewOwnerUser not in group " + req.NewOwnerUserID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||||
if !(mcontext.GetOpUserID(ctx) == oldOwner.UserID && oldOwner.RoleLevel == constant.GroupOwner) {
|
if !(mcontext.GetOpUserID(ctx) == oldOwner.UserID && oldOwner.RoleLevel == constant.GroupOwner) {
|
||||||
return nil, errs.ErrNoPermission.WrapMsg("no permission transfer group owner")
|
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 {
|
if err := g.db.TransferGroupOwner(ctx, req.GroupID, req.OldOwnerUserID, req.NewOwnerUserID, newOwner.RoleLevel); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -1217,6 +1236,7 @@ func (g *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
|
|||||||
g.webhookAfterTransferGroupOwner(ctx, &g.config.WebhooksConfig.AfterTransferGroupOwner, req)
|
g.webhookAfterTransferGroupOwner(ctx, &g.config.WebhooksConfig.AfterTransferGroupOwner, req)
|
||||||
|
|
||||||
g.notification.GroupOwnerTransferredNotification(ctx, req)
|
g.notification.GroupOwnerTransferredNotification(ctx, req)
|
||||||
|
|
||||||
return &pbgroup.TransferGroupOwnerResp{}, nil
|
return &pbgroup.TransferGroupOwnerResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1425,32 +1445,38 @@ func (g *groupServer) CancelMuteGroupMember(ctx context.Context, req *pbgroup.Ca
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := g.PopulateGroupMember(ctx, member); err != nil {
|
if err := g.PopulateGroupMember(ctx, member); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
|
||||||
opMember, err := g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx))
|
opMember, err := g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
switch member.RoleLevel {
|
switch member.RoleLevel {
|
||||||
case constant.GroupOwner:
|
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:
|
case constant.GroupAdmin:
|
||||||
if opMember.RoleLevel != constant.GroupOwner {
|
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:
|
case constant.GroupOrdinaryUsers:
|
||||||
if !(opMember.RoleLevel == constant.GroupAdmin || opMember.RoleLevel == constant.GroupOwner) {
|
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))
|
data := UpdateGroupMemberMutedTimeMap(time.Unix(0, 0))
|
||||||
if err := g.db.UpdateGroupMember(ctx, member.GroupID, member.UserID, data); err != nil {
|
if err := g.db.UpdateGroupMember(ctx, member.GroupID, member.UserID, data); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
g.notification.GroupMemberCancelMutedNotification(ctx, req.GroupID, req.UserID)
|
g.notification.GroupMemberCancelMutedNotification(ctx, req.GroupID, req.UserID)
|
||||||
|
|
||||||
return &pbgroup.CancelMuteGroupMemberResp{}, nil
|
return &pbgroup.CancelMuteGroupMemberResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1485,9 +1511,6 @@ func (g *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
|
|||||||
return nil, errs.ErrNoPermission.WrapMsg("no op user id")
|
return nil, errs.ErrNoPermission.WrapMsg("no op user id")
|
||||||
}
|
}
|
||||||
isAppManagerUid := authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID)
|
isAppManagerUid := authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID)
|
||||||
for i := range req.Members {
|
|
||||||
req.Members[i].FaceURL = nil
|
|
||||||
}
|
|
||||||
groupMembers := make(map[string][]*pbgroup.SetGroupMemberInfo)
|
groupMembers := make(map[string][]*pbgroup.SetGroupMemberInfo)
|
||||||
for i, member := range req.Members {
|
for i, member := range req.Members {
|
||||||
if member.RoleLevel != nil {
|
if member.RoleLevel != nil {
|
||||||
@@ -1529,29 +1552,61 @@ func (g *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
|
|||||||
case 0:
|
case 0:
|
||||||
if !isAppManagerUid {
|
if !isAppManagerUid {
|
||||||
roleLevel := dbMembers[opUserIndex].RoleLevel
|
roleLevel := dbMembers[opUserIndex].RoleLevel
|
||||||
if roleLevel != constant.GroupOwner {
|
var (
|
||||||
switch roleLevel {
|
dbSelf = &model.GroupMember{}
|
||||||
case constant.GroupAdmin:
|
reqSelf *pbgroup.SetGroupMemberInfo
|
||||||
for _, member := range dbMembers {
|
)
|
||||||
if member.RoleLevel == constant.GroupOwner {
|
switch roleLevel {
|
||||||
return nil, errs.ErrNoPermission.WrapMsg("admin can not change group owner")
|
case constant.GroupOwner:
|
||||||
}
|
for _, member := range dbMembers {
|
||||||
if member.RoleLevel == constant.GroupAdmin && member.UserID != opUserID {
|
if member.UserID == opUserID {
|
||||||
return nil, errs.ErrNoPermission.WrapMsg("admin can not change other group admin")
|
dbSelf = member
|
||||||
}
|
break
|
||||||
}
|
}
|
||||||
case constant.GroupOrdinaryUsers:
|
}
|
||||||
for _, member := range dbMembers {
|
case constant.GroupAdmin:
|
||||||
if !(member.RoleLevel == constant.GroupOrdinaryUsers && member.UserID == opUserID) {
|
for _, member := range dbMembers {
|
||||||
return nil, errs.ErrNoPermission.WrapMsg("ordinary users can not change other role level")
|
if member.UserID == opUserID {
|
||||||
}
|
dbSelf = member
|
||||||
}
|
}
|
||||||
default:
|
if member.RoleLevel == constant.GroupOwner {
|
||||||
for _, member := range dbMembers {
|
return nil, errs.ErrNoPermission.WrapMsg("admin can not change group owner")
|
||||||
if member.RoleLevel >= roleLevel {
|
|
||||||
return nil, errs.ErrNoPermission.WrapMsg("can not change higher role level")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1757,7 +1812,6 @@ func (g *groupServer) GetSpecifiedUserGroupRequestInfo(ctx context.Context, req
|
|||||||
}
|
}
|
||||||
|
|
||||||
if req.UserID != opUserID {
|
if req.UserID != opUserID {
|
||||||
req.UserID = mcontext.GetOpUserID(ctx)
|
|
||||||
adminIDs, err := g.db.GetGroupRoleLevelMemberIDs(ctx, req.GroupID, constant.GroupAdmin)
|
adminIDs, err := g.db.GetGroupRoleLevelMemberIDs(ctx, req.GroupID, constant.GroupAdmin)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -1766,10 +1820,11 @@ func (g *groupServer) GetSpecifiedUserGroupRequestInfo(ctx context.Context, req
|
|||||||
adminIDs = append(adminIDs, owners[0].UserID)
|
adminIDs = append(adminIDs, owners[0].UserID)
|
||||||
adminIDs = append(adminIDs, g.config.Share.IMAdminUserID...)
|
adminIDs = append(adminIDs, g.config.Share.IMAdminUserID...)
|
||||||
|
|
||||||
if !datautil.Contain(req.UserID, adminIDs...) {
|
if !datautil.Contain(opUserID, adminIDs...) {
|
||||||
return nil, errs.ErrNoPermission.WrapMsg("opUser no permission")
|
return nil, errs.ErrNoPermission.WrapMsg("opUser no permission")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
requests, err := g.db.FindGroupRequests(ctx, req.GroupID, []string{req.UserID})
|
requests, err := g.db.FindGroupRequests(ctx, req.GroupID, []string{req.UserID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import (
|
|||||||
"github.com/openimsdk/tools/utils/datautil"
|
"github.com/openimsdk/tools/utils/datautil"
|
||||||
"github.com/openimsdk/tools/utils/stringutil"
|
"github.com/openimsdk/tools/utils/stringutil"
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GroupApplicationReceiver
|
// GroupApplicationReceiver
|
||||||
@@ -572,8 +573,51 @@ func (g *GroupNotificationSender) GroupApplicationAgreeMemberEnterNotification(c
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GroupNotificationSender) MemberEnterNotification(ctx context.Context, groupID string, entrantUserID ...string) error {
|
func (g *GroupNotificationSender) MemberEnterNotification(ctx context.Context, groupID string, entrantUserID string) error {
|
||||||
return g.GroupApplicationAgreeMemberEnterNotification(ctx, groupID, "", entrantUserID...)
|
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) {
|
func (g *GroupNotificationSender) GroupDismissedNotification(ctx context.Context, tips *sdkws.GroupDismissedTips) {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package msg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
|
|
||||||
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||||||
"github.com/openimsdk/protocol/constant"
|
"github.com/openimsdk/protocol/constant"
|
||||||
@@ -108,7 +109,7 @@ func (m *msgServer) MarkMsgsAsRead(ctx context.Context, req *msg.MarkMsgsAsReadR
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
currentHasReadSeq, err := m.MsgDatabase.GetHasReadSeq(ctx, req.UserID, req.ConversationID)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
if hasReadSeq > currentHasReadSeq {
|
if hasReadSeq > currentHasReadSeq {
|
||||||
@@ -136,7 +137,7 @@ func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkCon
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
hasReadSeq, err := m.MsgDatabase.GetHasReadSeq(ctx, req.UserID, req.ConversationID)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
var seqs []int64
|
var seqs []int64
|
||||||
@@ -180,14 +181,23 @@ func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkCon
|
|||||||
req.UserID, seqs, hasReadSeq)
|
req.UserID, seqs, hasReadSeq)
|
||||||
}
|
}
|
||||||
|
|
||||||
reqCall := &cbapi.CallbackGroupMsgReadReq{
|
if conversation.ConversationType == constant.SingleChatType {
|
||||||
SendID: conversation.OwnerUserID,
|
reqCall := &cbapi.CallbackSingleMsgReadReq{
|
||||||
ReceiveID: req.UserID,
|
ConversationID: conversation.ConversationID,
|
||||||
UnreadMsgNum: req.HasReadSeq,
|
UserID: conversation.OwnerUserID,
|
||||||
ContentType: int64(conversation.ConversationType),
|
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
|
return &msg.MarkConversationAsReadResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-12
@@ -12,13 +12,14 @@ import (
|
|||||||
"github.com/openimsdk/tools/errs"
|
"github.com/openimsdk/tools/errs"
|
||||||
"github.com/openimsdk/tools/log"
|
"github.com/openimsdk/tools/log"
|
||||||
"github.com/openimsdk/tools/mcontext"
|
"github.com/openimsdk/tools/mcontext"
|
||||||
|
"github.com/openimsdk/tools/utils/datautil"
|
||||||
"github.com/openimsdk/tools/utils/idutil"
|
"github.com/openimsdk/tools/utils/idutil"
|
||||||
"github.com/openimsdk/tools/utils/stringutil"
|
"github.com/openimsdk/tools/utils/stringutil"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// hard delete in Database.
|
// hard delete in Database.
|
||||||
func (m *msgServer) ClearMsg(ctx context.Context, req *msg.ClearMsgReq) (_ *msg.ClearMsgResp, err error) {
|
func (m *msgServer) DestructMsgs(ctx context.Context, req *msg.DestructMsgsReq) (_ *msg.DestructMsgsResp, err error) {
|
||||||
if err := authverify.CheckAdmin(ctx, m.config.Share.IMAdminUserID); err != nil {
|
if err := authverify.CheckAdmin(ctx, m.config.Share.IMAdminUserID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -26,18 +27,19 @@ func (m *msgServer) ClearMsg(ctx context.Context, req *msg.ClearMsgReq) (_ *msg.
|
|||||||
return nil, errs.ErrArgs.WrapMsg("request millisecond timestamp error")
|
return nil, errs.ErrArgs.WrapMsg("request millisecond timestamp error")
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
docNum int
|
docNum int
|
||||||
msgNum int
|
msgNum int
|
||||||
start = time.Now()
|
start = time.Now()
|
||||||
|
getLimit = 5000
|
||||||
)
|
)
|
||||||
|
|
||||||
clearMsg := func(ctx context.Context) (bool, error) {
|
destructMsg := func(ctx context.Context) (bool, error) {
|
||||||
docIDs, err := m.MsgDatabase.GetDocIDs(ctx)
|
docIDs, err := m.MsgDatabase.GetDocIDs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
msgs, err := m.MsgDatabase.GetBeforeMsg(ctx, req.Timestamp, docIDs, 5000)
|
msgs, err := m.MsgDatabase.GetBeforeMsg(ctx, req.Timestamp, docIDs, getLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@@ -61,7 +63,7 @@ func (m *msgServer) ClearMsg(ctx context.Context, req *msg.ClearMsgReq) (_ *msg.
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = clearMsg(ctx)
|
_, err = destructMsg(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZError(ctx, "clear msg failed", err, "docNum", docNum, "msgNum", msgNum, "cost", time.Since(start))
|
log.ZError(ctx, "clear msg failed", err, "docNum", docNum, "msgNum", msgNum, "cost", time.Since(start))
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -69,11 +71,11 @@ func (m *msgServer) ClearMsg(ctx context.Context, req *msg.ClearMsgReq) (_ *msg.
|
|||||||
|
|
||||||
log.ZDebug(ctx, "clearing message", "docNum", docNum, "msgNum", msgNum, "cost", time.Since(start))
|
log.ZDebug(ctx, "clearing message", "docNum", docNum, "msgNum", msgNum, "cost", time.Since(start))
|
||||||
|
|
||||||
return &msg.ClearMsgResp{}, nil
|
return &msg.DestructMsgsResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// soft delete for self
|
// soft delete for user self
|
||||||
func (m *msgServer) DestructMsgs(ctx context.Context, req *msg.DestructMsgsReq) (_ *msg.DestructMsgsResp, err error) {
|
func (m *msgServer) ClearMsg(ctx context.Context, req *msg.ClearMsgReq) (_ *msg.ClearMsgResp, err error) {
|
||||||
temp := convert.ConversationsPb2DB(req.Conversations)
|
temp := convert.ConversationsPb2DB(req.Conversations)
|
||||||
|
|
||||||
batchNum := 100
|
batchNum := 100
|
||||||
@@ -93,22 +95,31 @@ func (m *msgServer) DestructMsgs(ctx context.Context, req *msg.DestructMsgsReq)
|
|||||||
"msgDestructTime", conversation.MsgDestructTime,
|
"msgDestructTime", conversation.MsgDestructTime,
|
||||||
"lastMsgDestructTime", conversation.LatestMsgDestructTime)
|
"lastMsgDestructTime", conversation.LatestMsgDestructTime)
|
||||||
|
|
||||||
seqs, err := m.MsgDatabase.UserMsgsDestruct(handleCtx, conversation.OwnerUserID, conversation.ConversationID, conversation.MsgDestructTime, conversation.LatestMsgDestructTime)
|
seqs, err := m.MsgDatabase.ClearUserMsgs(handleCtx, conversation.OwnerUserID, conversation.ConversationID, conversation.MsgDestructTime, conversation.LatestMsgDestructTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZError(handleCtx, "user msg destruct failed", err, "conversationID", conversation.ConversationID, "ownerUserID", conversation.OwnerUserID)
|
log.ZError(handleCtx, "user msg destruct failed", err, "conversationID", conversation.ConversationID, "ownerUserID", conversation.OwnerUserID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(seqs) > 0 {
|
if len(seqs) > 0 {
|
||||||
|
minseq := datautil.Max(seqs...)
|
||||||
|
|
||||||
|
// update
|
||||||
if err := m.Conversation.UpdateConversation(handleCtx,
|
if err := m.Conversation.UpdateConversation(handleCtx,
|
||||||
&pbconversation.UpdateConversationReq{
|
&pbconversation.UpdateConversationReq{
|
||||||
UserIDs: []string{conversation.OwnerUserID},
|
UserIDs: []string{conversation.OwnerUserID},
|
||||||
ConversationID: conversation.ConversationID,
|
ConversationID: conversation.ConversationID,
|
||||||
LatestMsgDestructTime: wrapperspb.Int64(time.Now().UnixMilli())}); err != nil {
|
LatestMsgDestructTime: wrapperspb.Int64(time.Now().UnixMilli()),
|
||||||
|
MinSeq: wrapperspb.Int64(minseq),
|
||||||
|
}); err != nil {
|
||||||
log.ZError(handleCtx, "updateUsersConversationField failed", err, "conversationID", conversation.ConversationID, "ownerUserID", conversation.OwnerUserID)
|
log.ZError(handleCtx, "updateUsersConversationField failed", err, "conversationID", conversation.ConversationID, "ownerUserID", conversation.OwnerUserID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := m.Conversation.SetConversationMinSeq(handleCtx, []string{conversation.OwnerUserID}, conversation.ConversationID, minseq); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// if you need Notify SDK client userseq is update.
|
// if you need Notify SDK client userseq is update.
|
||||||
// m.msgNotificationSender.UserDeleteMsgsNotification(handleCtx, conversation.OwnerUserID, conversation.ConversationID, seqs)
|
// m.msgNotificationSender.UserDeleteMsgsNotification(handleCtx, conversation.OwnerUserID, conversation.ConversationID, seqs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,9 +138,16 @@ func (m *msgServer) clearConversation(ctx context.Context, conversationIDs []str
|
|||||||
}
|
}
|
||||||
isSyncSelf, isSyncOther := m.validateDeleteSyncOpt(deleteSyncOpt)
|
isSyncSelf, isSyncOther := m.validateDeleteSyncOpt(deleteSyncOpt)
|
||||||
if !isSyncOther {
|
if !isSyncOther {
|
||||||
if err := m.MsgDatabase.SetUserConversationsMinSeqs(ctx, userID, m.getMinSeqs(maxSeqs)); err != nil {
|
setSeqs := m.getMinSeqs(maxSeqs)
|
||||||
|
if err := m.MsgDatabase.SetUserConversationsMinSeqs(ctx, userID, setSeqs); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
ownerUserIDs := []string{userID}
|
||||||
|
for conversationID, seq := range setSeqs {
|
||||||
|
if err := m.Conversation.SetConversationMinSeq(ctx, ownerUserIDs, conversationID, seq); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
// notification 2 self
|
// notification 2 self
|
||||||
if isSyncSelf {
|
if isSyncSelf {
|
||||||
tips := &sdkws.ClearConversationTips{UserID: userID, ConversationIDs: existConversationIDs}
|
tips := &sdkws.ClearConversationTips{UserID: userID, ConversationIDs: existConversationIDs}
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ import (
|
|||||||
"github.com/openimsdk/tools/log"
|
"github.com/openimsdk/tools/log"
|
||||||
"github.com/openimsdk/tools/mcontext"
|
"github.com/openimsdk/tools/mcontext"
|
||||||
"github.com/openimsdk/tools/utils/datautil"
|
"github.com/openimsdk/tools/utils/datautil"
|
||||||
"github.com/openimsdk/tools/utils/stringutil"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *msgServer) SendMsg(ctx context.Context, req *pbmsg.SendMsgReq) (*pbmsg.SendMsgResp, error) {
|
func (m *msgServer) SendMsg(ctx context.Context, req *pbmsg.SendMsgReq) (*pbmsg.SendMsgResp, error) {
|
||||||
@@ -79,14 +78,25 @@ func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgData) {
|
func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgData) {
|
||||||
|
|
||||||
log.ZDebug(nctx, "setConversationAtInfo", "msg", msg)
|
log.ZDebug(nctx, "setConversationAtInfo", "msg", msg)
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.ZPanic(nctx, "setConversationAtInfo Panic", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
ctx := mcontext.NewCtx("@@@" + mcontext.GetOperationID(nctx))
|
ctx := mcontext.NewCtx("@@@" + mcontext.GetOperationID(nctx))
|
||||||
|
|
||||||
var atUserID []string
|
var atUserID []string
|
||||||
|
|
||||||
conversation := &pbconversation.ConversationReq{
|
conversation := &pbconversation.ConversationReq{
|
||||||
ConversationID: msgprocessor.GetConversationIDByMsg(msg),
|
ConversationID: msgprocessor.GetConversationIDByMsg(msg),
|
||||||
ConversationType: msg.SessionType,
|
ConversationType: msg.SessionType,
|
||||||
GroupID: msg.GroupID,
|
GroupID: msg.GroupID,
|
||||||
}
|
}
|
||||||
|
|
||||||
tagAll := datautil.Contain(constant.AtAllString, msg.AtUserIDList...)
|
tagAll := datautil.Contain(constant.AtAllString, msg.AtUserIDList...)
|
||||||
if tagAll {
|
if tagAll {
|
||||||
memberUserIDList, err := m.GroupLocalCache.GetGroupMemberIDs(ctx, msg.GroupID)
|
memberUserIDList, err := m.GroupLocalCache.GetGroupMemberIDs(ctx, msg.GroupID)
|
||||||
@@ -94,25 +104,35 @@ func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgDa
|
|||||||
log.ZWarn(ctx, "GetGroupMemberIDs", err)
|
log.ZWarn(ctx, "GetGroupMemberIDs", err)
|
||||||
return
|
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
|
if len(atUserID) == 0 { // just @everyone
|
||||||
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll}
|
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll}
|
||||||
} else { // @Everyone and @other people
|
} else { // @Everyone and @other people
|
||||||
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAllAtMe}
|
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAllAtMe}
|
||||||
|
|
||||||
err = m.Conversation.SetConversations(ctx, atUserID, conversation)
|
err = m.Conversation.SetConversations(ctx, atUserID, conversation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZWarn(ctx, "SetConversations", err, "userID", atUserID, "conversation", conversation)
|
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}
|
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll}
|
||||||
|
|
||||||
err = m.Conversation.SetConversations(ctx, memberUserIDList, conversation)
|
err = m.Conversation.SetConversations(ctx, memberUserIDList, conversation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZWarn(ctx, "SetConversations", err, "userID", memberUserIDList, "conversation", conversation)
|
log.ZWarn(ctx, "SetConversations", err, "userID", memberUserIDList, "conversation", conversation)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtMe}
|
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtMe}
|
||||||
|
|
||||||
err := m.Conversation.SetConversations(ctx, msg.AtUserIDList, conversation)
|
err := m.Conversation.SetConversations(ctx, msg.AtUserIDList, conversation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZWarn(ctx, "SetConversations", err, msg.AtUserIDList, conversation)
|
log.ZWarn(ctx, "SetConversations", err, msg.AtUserIDList, conversation)
|
||||||
@@ -153,9 +173,6 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq
|
|||||||
prommetrics.SingleChatMsgProcessFailedCounter.Inc()
|
prommetrics.SingleChatMsgProcessFailedCounter.Inc()
|
||||||
return nil, nil
|
return nil, nil
|
||||||
} else {
|
} 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 {
|
if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-2
@@ -16,15 +16,15 @@ package msg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
pbmsg "github.com/openimsdk/protocol/msg"
|
pbmsg "github.com/openimsdk/protocol/msg"
|
||||||
"github.com/openimsdk/tools/errs"
|
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"sort"
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (m *msgServer) GetConversationMaxSeq(ctx context.Context, req *pbmsg.GetConversationMaxSeqReq) (*pbmsg.GetConversationMaxSeqResp, error) {
|
func (m *msgServer) GetConversationMaxSeq(ctx context.Context, req *pbmsg.GetConversationMaxSeqReq) (*pbmsg.GetConversationMaxSeqResp, error) {
|
||||||
maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID)
|
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 nil, err
|
||||||
}
|
}
|
||||||
return &pbmsg.GetConversationMaxSeqResp{MaxSeq: maxSeq}, nil
|
return &pbmsg.GetConversationMaxSeqResp{MaxSeq: maxSeq}, nil
|
||||||
@@ -84,3 +84,21 @@ func (m *msgServer) GetActiveConversation(ctx context.Context, req *pbmsg.GetAct
|
|||||||
}
|
}
|
||||||
return &pbmsg.GetActiveConversationResp{Conversations: conversations}, nil
|
return &pbmsg.GetActiveConversationResp{Conversations: conversations}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *msgServer) SetUserConversationMaxSeq(ctx context.Context, req *pbmsg.SetUserConversationMaxSeqReq) (*pbmsg.SetUserConversationMaxSeqResp, error) {
|
||||||
|
for _, userID := range req.OwnerUserID {
|
||||||
|
if err := m.MsgDatabase.SetUserConversationsMaxSeq(ctx, req.ConversationID, userID, req.MaxSeq); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &pbmsg.SetUserConversationMaxSeqResp{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *msgServer) SetUserConversationMinSeq(ctx context.Context, req *pbmsg.SetUserConversationMinSeqReq) (*pbmsg.SetUserConversationMinSeqResp, error) {
|
||||||
|
for _, userID := range req.OwnerUserID {
|
||||||
|
if err := m.MsgDatabase.SetUserConversationsMinSeq(ctx, req.ConversationID, userID, req.MinSeq); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &pbmsg.SetUserConversationMinSeqResp{}, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ package msg
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
|
||||||
@@ -54,6 +53,7 @@ type (
|
|||||||
msgNotificationSender *MsgNotificationSender // RPC client for sending msg notifications.
|
msgNotificationSender *MsgNotificationSender // RPC client for sending msg notifications.
|
||||||
config *Config // Global configuration settings.
|
config *Config // Global configuration settings.
|
||||||
webhookClient *webhook.Client
|
webhookClient *webhook.Client
|
||||||
|
msg.UnimplementedMsgServer
|
||||||
}
|
}
|
||||||
|
|
||||||
Config struct {
|
Config struct {
|
||||||
@@ -139,3 +139,11 @@ func (m *msgServer) conversationAndGetRecvID(conversation *conversation.Conversa
|
|||||||
}
|
}
|
||||||
return ""
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func (m *msgServer) GetSeqMessage(ctx context.Context, req *msg.GetSeqMessageReq
|
|||||||
NotificationMsgs: make(map[string]*sdkws.PullMsgs),
|
NotificationMsgs: make(map[string]*sdkws.PullMsgs),
|
||||||
}
|
}
|
||||||
for _, conv := range req.Conversations {
|
for _, conv := range req.Conversations {
|
||||||
_, _, msgs, err := m.MsgDatabase.GetMsgBySeqs(ctx, req.UserID, conv.ConversationID, conv.Seqs)
|
isEnd, endSeq, msgs, err := m.MsgDatabase.GetMessagesBySeqWithBounds(ctx, req.UserID, conv.ConversationID, conv.Seqs, req.GetOrder())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -111,6 +111,8 @@ func (m *msgServer) GetSeqMessage(ctx context.Context, req *msg.GetSeqMessageReq
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pullMsgs.Msgs = append(pullMsgs.Msgs, msgs...)
|
pullMsgs.Msgs = append(pullMsgs.Msgs, msgs...)
|
||||||
|
pullMsgs.IsEnd = isEnd
|
||||||
|
pullMsgs.EndSeq = endSeq
|
||||||
}
|
}
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,6 +59,9 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe
|
|||||||
data.MsgData.ContentType >= constant.NotificationBegin {
|
data.MsgData.ContentType >= constant.NotificationBegin {
|
||||||
return nil
|
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)
|
black, err := m.FriendLocalCache.IsBlack(ctx, data.MsgData.SendID, data.MsgData.RecvID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type friendServer struct {
|
type friendServer struct {
|
||||||
|
relation.UnimplementedFriendServer
|
||||||
db controller.FriendDatabase
|
db controller.FriendDatabase
|
||||||
blackDatabase controller.BlackDatabase
|
blackDatabase controller.BlackDatabase
|
||||||
userRpcClient *rpcclient.UserRpcClient
|
userRpcClient *rpcclient.UserRpcClient
|
||||||
@@ -273,7 +274,14 @@ func (s *friendServer) SetFriendRemark(ctx context.Context, req *relation.SetFri
|
|||||||
return &relation.SetFriendRemarkResp{}, nil
|
return &relation.SetFriendRemarkResp{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ok.
|
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
|
||||||
|
}
|
||||||
|
|
||||||
func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *relation.GetDesignatedFriendsReq) (resp *relation.GetDesignatedFriendsResp, err error) {
|
func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *relation.GetDesignatedFriendsReq) (resp *relation.GetDesignatedFriendsResp, err error) {
|
||||||
resp = &relation.GetDesignatedFriendsResp{}
|
resp = &relation.GetDesignatedFriendsResp{}
|
||||||
if datautil.Duplicate(req.FriendUserIDs) {
|
if datautil.Duplicate(req.FriendUserIDs) {
|
||||||
|
|||||||
@@ -50,14 +50,14 @@ func (t *thirdServer) UploadLogs(ctx context.Context, req *third.UploadLogsReq)
|
|||||||
platform := constant.PlatformID2Name[int(req.Platform)]
|
platform := constant.PlatformID2Name[int(req.Platform)]
|
||||||
for _, fileURL := range req.FileURLs {
|
for _, fileURL := range req.FileURLs {
|
||||||
log := relationtb.Log{
|
log := relationtb.Log{
|
||||||
Platform: platform,
|
Platform: platform,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
CreateTime: time.Now(),
|
CreateTime: time.Now(),
|
||||||
Url: fileURL.URL,
|
Url: fileURL.URL,
|
||||||
FileName: fileURL.Filename,
|
FileName: fileURL.Filename,
|
||||||
SystemType: req.SystemType,
|
AppFramework: req.AppFramework,
|
||||||
Version: req.Version,
|
Version: req.Version,
|
||||||
Ex: req.Ex,
|
Ex: req.Ex,
|
||||||
}
|
}
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
id := genLogID()
|
id := genLogID()
|
||||||
|
|||||||
+66
-29
@@ -290,48 +290,85 @@ func (t *thirdServer) apiAddress(prefix, name string) string {
|
|||||||
func (t *thirdServer) DeleteOutdatedData(ctx context.Context, req *third.DeleteOutdatedDataReq) (*third.DeleteOutdatedDataResp, error) {
|
func (t *thirdServer) DeleteOutdatedData(ctx context.Context, req *third.DeleteOutdatedDataReq) (*third.DeleteOutdatedDataResp, error) {
|
||||||
var conf config.Third
|
var conf config.Third
|
||||||
expireTime := time.UnixMilli(req.ExpireTime)
|
expireTime := time.UnixMilli(req.ExpireTime)
|
||||||
var deltotal int
|
|
||||||
findPagination := &sdkws.RequestPagination{
|
findPagination := &sdkws.RequestPagination{
|
||||||
PageNumber: 1,
|
PageNumber: 1,
|
||||||
ShowNumber: 1000,
|
ShowNumber: 500,
|
||||||
}
|
}
|
||||||
for {
|
|
||||||
total, models, err := t.s3dataBase.FindByExpires(ctx, expireTime, findPagination)
|
// Find all expired data in S3 database
|
||||||
|
total, models, err := t.s3dataBase.FindNeedDeleteObjectByDB(ctx, expireTime, req.ObjectGroup, findPagination)
|
||||||
|
if err != nil && errs.Unwrap(err) != mongo.ErrNoDocuments {
|
||||||
|
return nil, errs.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if total == 0 {
|
||||||
|
log.ZDebug(ctx, "Not have OutdatedData", "delete Total", total)
|
||||||
|
return &third.DeleteOutdatedDataResp{Count: int32(total)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
needDelObjectKeys := make([]string, len(models))
|
||||||
|
for _, model := range models {
|
||||||
|
needDelObjectKeys = append(needDelObjectKeys, model.Key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove duplicate keys, have the same key use in different models
|
||||||
|
needDelObjectKeys = datautil.Distinct(needDelObjectKeys)
|
||||||
|
|
||||||
|
for _, key := range needDelObjectKeys {
|
||||||
|
// Find all models by key
|
||||||
|
keyModels, err := t.s3dataBase.FindModelsByKey(ctx, key)
|
||||||
if err != nil && errs.Unwrap(err) != mongo.ErrNoDocuments {
|
if err != nil && errs.Unwrap(err) != mongo.ErrNoDocuments {
|
||||||
return nil, errs.Wrap(err)
|
return nil, errs.Wrap(err)
|
||||||
}
|
}
|
||||||
needDelObjectKeys := make([]string, 0)
|
|
||||||
for _, model := range models {
|
|
||||||
needDelObjectKeys = append(needDelObjectKeys, model.Key)
|
|
||||||
}
|
|
||||||
|
|
||||||
needDelObjectKeys = datautil.Distinct(needDelObjectKeys)
|
// check keyModels, if all keyModels.
|
||||||
for _, key := range needDelObjectKeys {
|
needDelKey := true // Default can delete
|
||||||
count, err := t.s3dataBase.FindNotDelByS3(ctx, key, expireTime)
|
for _, keymodel := range keyModels {
|
||||||
if err != nil && errs.Unwrap(err) != mongo.ErrNoDocuments {
|
// If group is empty or CreateTime is after expireTime, can't delete this key
|
||||||
return nil, errs.Wrap(err)
|
if keymodel.Group == "" || keymodel.CreateTime.After(expireTime) {
|
||||||
}
|
needDelKey = false
|
||||||
if int(count) < 1 && t.minio != nil {
|
break
|
||||||
thumbnailKey, _ := t.getMinioImageThumbnailKey(ctx, key)
|
|
||||||
|
|
||||||
t.s3dataBase.DeleteObject(ctx, thumbnailKey)
|
|
||||||
t.s3dataBase.DelS3Key(ctx, conf.Object.Enable, needDelObjectKeys...)
|
|
||||||
t.s3dataBase.DeleteObject(ctx, key)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, model := range models {
|
|
||||||
err := t.s3dataBase.DeleteSpecifiedData(ctx, model.Engine, model.Name)
|
// If this object is not referenced by not expire data, delete it
|
||||||
|
if needDelKey && t.minio != nil {
|
||||||
|
// If have a thumbnail, delete it
|
||||||
|
thumbnailKey, _ := t.getMinioImageThumbnailKey(ctx, key)
|
||||||
|
if thumbnailKey != "" {
|
||||||
|
err := t.s3dataBase.DeleteObject(ctx, thumbnailKey)
|
||||||
|
if err != nil {
|
||||||
|
log.ZWarn(ctx, "Delete thumbnail object is error:", errs.Wrap(err), "thumbnailKey", thumbnailKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete object
|
||||||
|
err = t.s3dataBase.DeleteObject(ctx, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.Wrap(err)
|
log.ZWarn(ctx, "Delete object is error", errs.Wrap(err), "object key", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete cache key
|
||||||
|
err = t.s3dataBase.DelS3Key(ctx, conf.Object.Enable, key)
|
||||||
|
if err != nil {
|
||||||
|
log.ZWarn(ctx, "Delete cache key is error:", errs.Wrap(err), "cache S3 key:", key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if total < int64(findPagination.ShowNumber) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
deltotal += int(total)
|
|
||||||
}
|
}
|
||||||
log.ZDebug(ctx, "DeleteOutdatedData", "delete Total", deltotal)
|
|
||||||
return &third.DeleteOutdatedDataResp{}, nil
|
// handle delete data in S3 database
|
||||||
|
for _, model := range models {
|
||||||
|
// Delete all expired data row in S3 database
|
||||||
|
err := t.s3dataBase.DeleteSpecifiedData(ctx, model.Engine, model.Name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Wrap(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.ZDebug(ctx, "DeleteOutdatedData", "delete Total", total)
|
||||||
|
|
||||||
|
return &third.DeleteOutdatedDataResp{Count: int32(total)}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type FormDataMate struct {
|
type FormDataMate struct {
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type thirdServer struct {
|
type thirdServer struct {
|
||||||
|
third.UnimplementedThirdServer
|
||||||
thirdDatabase controller.ThirdDatabase
|
thirdDatabase controller.ThirdDatabase
|
||||||
s3dataBase controller.S3Database
|
s3dataBase controller.S3Database
|
||||||
userRpcClient rpcclient.UserRpcClient
|
userRpcClient rpcclient.UserRpcClient
|
||||||
@@ -74,6 +75,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select the oss method according to the profile policy
|
// Select the oss method according to the profile policy
|
||||||
enable := config.RpcConfig.Object.Enable
|
enable := config.RpcConfig.Object.Enable
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -82,3 +82,11 @@ func checkValidObjectName(objectName string) error {
|
|||||||
func (t *thirdServer) IsManagerUserID(opUserID string) bool {
|
func (t *thirdServer) IsManagerUserID(opUserID string) bool {
|
||||||
return authverify.IsManagerUserID(opUserID, t.config.Share.IMAdminUserID)
|
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
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type userServer struct {
|
type userServer struct {
|
||||||
|
pbuser.UnimplementedUserServer
|
||||||
online cache.OnlineCache
|
online cache.OnlineCache
|
||||||
db controller.UserDatabase
|
db controller.UserDatabase
|
||||||
friendNotificationSender *relation.FriendNotificationSender
|
friendNotificationSender *relation.FriendNotificationSender
|
||||||
@@ -116,18 +117,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) {
|
func (s *userServer) GetDesignateUsers(ctx context.Context, req *pbuser.GetDesignateUsersReq) (resp *pbuser.GetDesignateUsersResp, err error) {
|
||||||
resp = &pbuser.GetDesignateUsersResp{}
|
resp = &pbuser.GetDesignateUsersResp{}
|
||||||
users, err := s.db.FindWithError(ctx, req.UserIDs)
|
users, err := s.db.Find(ctx, req.UserIDs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp.UsersInfo = convert.UsersDB2Pb(users)
|
resp.UsersInfo = convert.UsersDB2Pb(users)
|
||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprecated:
|
// deprecated:
|
||||||
|
// UpdateUserInfo
|
||||||
//UpdateUserInfo
|
|
||||||
|
|
||||||
func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInfoReq) (resp *pbuser.UpdateUserInfoResp, err error) {
|
func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInfoReq) (resp *pbuser.UpdateUserInfoResp, err error) {
|
||||||
resp = &pbuser.UpdateUserInfoResp{}
|
resp = &pbuser.UpdateUserInfoResp{}
|
||||||
err = authverify.CheckAccessV3(ctx, req.UserInfo.UserID, s.config.Share.IMAdminUserID)
|
err = authverify.CheckAccessV3(ctx, req.UserInfo.UserID, s.config.Share.IMAdminUserID)
|
||||||
|
|||||||
+56
-38
@@ -24,6 +24,7 @@ import (
|
|||||||
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister"
|
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister"
|
||||||
pbconversation "github.com/openimsdk/protocol/conversation"
|
pbconversation "github.com/openimsdk/protocol/conversation"
|
||||||
"github.com/openimsdk/protocol/msg"
|
"github.com/openimsdk/protocol/msg"
|
||||||
|
"github.com/openimsdk/protocol/third"
|
||||||
|
|
||||||
"github.com/openimsdk/tools/mcontext"
|
"github.com/openimsdk/tools/mcontext"
|
||||||
"github.com/openimsdk/tools/mw"
|
"github.com/openimsdk/tools/mw"
|
||||||
@@ -58,10 +59,10 @@ func Start(ctx context.Context, config *CronTaskConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// thirdConn, err := client.GetConn(ctx, config.Share.RpcRegisterName.Third)
|
thirdConn, err := client.GetConn(ctx, config.Share.RpcRegisterName.Third)
|
||||||
// if err != nil {
|
if err != nil {
|
||||||
// return err
|
return err
|
||||||
// }
|
}
|
||||||
|
|
||||||
conversationConn, err := client.GetConn(ctx, config.Share.RpcRegisterName.Conversation)
|
conversationConn, err := client.GetConn(ctx, config.Share.RpcRegisterName.Conversation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -70,66 +71,83 @@ func Start(ctx context.Context, config *CronTaskConfig) error {
|
|||||||
|
|
||||||
msgClient := msg.NewMsgClient(msgConn)
|
msgClient := msg.NewMsgClient(msgConn)
|
||||||
conversationClient := pbconversation.NewConversationClient(conversationConn)
|
conversationClient := pbconversation.NewConversationClient(conversationConn)
|
||||||
// thirdClient := third.NewThirdClient(thirdConn)
|
thirdClient := third.NewThirdClient(thirdConn)
|
||||||
|
|
||||||
crontab := cron.New()
|
crontab := cron.New()
|
||||||
|
|
||||||
// scheduled hard delete outdated Msgs in specific time.
|
// scheduled hard delete outdated Msgs in specific time.
|
||||||
clearMsgFunc := func() {
|
destructMsgsFunc := func() {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
deltime := now.Add(-time.Hour * 24 * time.Duration(config.CronTask.RetainChatRecords))
|
deltime := now.Add(-time.Hour * 24 * time.Duration(config.CronTask.RetainChatRecords))
|
||||||
ctx := mcontext.SetOperationID(ctx, fmt.Sprintf("cron_%d_%d", os.Getpid(), deltime.UnixMilli()))
|
ctx := mcontext.SetOperationID(ctx, fmt.Sprintf("cron_%d_%d", os.Getpid(), deltime.UnixMilli()))
|
||||||
log.ZDebug(ctx, "clear chat records", "deltime", deltime, "timestamp", deltime.UnixMilli())
|
log.ZDebug(ctx, "Destruct chat records", "deltime", deltime, "timestamp", deltime.UnixMilli())
|
||||||
|
|
||||||
if _, err := msgClient.ClearMsg(ctx, &msg.ClearMsgReq{Timestamp: deltime.UnixMilli()}); err != nil {
|
if _, err := msgClient.DestructMsgs(ctx, &msg.DestructMsgsReq{Timestamp: deltime.UnixMilli()}); err != nil {
|
||||||
log.ZError(ctx, "cron clear chat records failed", err, "deltime", deltime, "cont", time.Since(now))
|
log.ZError(ctx, "cron destruct chat records failed", err, "deltime", deltime, "cont", time.Since(now))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
log.ZDebug(ctx, "cron clear chat records success", "deltime", deltime, "cont", time.Since(now))
|
log.ZDebug(ctx, "cron destruct chat records success", "deltime", deltime, "cont", time.Since(now))
|
||||||
|
}
|
||||||
|
if _, err := crontab.AddFunc(config.CronTask.CronExecuteTime, destructMsgsFunc); err != nil {
|
||||||
|
return errs.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scheduled soft delete outdated Msgs in specific time when user set `is_msg_destruct` feature.
|
||||||
|
clearMsgFunc := func() {
|
||||||
|
now := time.Now()
|
||||||
|
ctx := mcontext.SetOperationID(ctx, fmt.Sprintf("cron_%d_%d", os.Getpid(), now.UnixMilli()))
|
||||||
|
log.ZDebug(ctx, "clear msg cron start", "now", now)
|
||||||
|
|
||||||
|
conversations, err := conversationClient.GetConversationsNeedClearMsg(ctx, &pbconversation.GetConversationsNeedClearMsgReq{})
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "Get conversation need Destruct msgs failed.", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = msgClient.ClearMsg(ctx, &msg.ClearMsgReq{Conversations: conversations.Conversations})
|
||||||
|
if err != nil {
|
||||||
|
log.ZError(ctx, "Clear Msg failed.", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.ZDebug(ctx, "clear msg cron task completed", "cont", time.Since(now))
|
||||||
}
|
}
|
||||||
if _, err := crontab.AddFunc(config.CronTask.CronExecuteTime, clearMsgFunc); err != nil {
|
if _, err := crontab.AddFunc(config.CronTask.CronExecuteTime, clearMsgFunc); err != nil {
|
||||||
return errs.Wrap(err)
|
return errs.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// scheduled soft delete outdated Msgs in specific time when user set `is_msg_destruct` feature.
|
// scheduled delete outdated file Objects and their datas in specific time.
|
||||||
msgDestructFunc := func() {
|
deleteObjectFunc := func() {
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
ctx := mcontext.SetOperationID(ctx, fmt.Sprintf("cron_%d_%d", os.Getpid(), now.UnixMilli()))
|
executeNum := 5
|
||||||
log.ZDebug(ctx, "msg destruct cron start", "now", now)
|
// number of pagination. if need modify, need update value in third.DeleteOutdatedData
|
||||||
|
pageShowNumber := 500
|
||||||
|
deleteTime := now.Add(-time.Hour * 24 * time.Duration(config.CronTask.FileExpireTime))
|
||||||
|
ctx := mcontext.SetOperationID(ctx, fmt.Sprintf("cron_%d_%d", os.Getpid(), deleteTime.UnixMilli()))
|
||||||
|
log.ZDebug(ctx, "deleteoutDatedData", "deletetime", deleteTime, "timestamp", deleteTime.UnixMilli())
|
||||||
|
|
||||||
conversations, err := conversationClient.GetConversationsNeedDestructMsgs(ctx, &pbconversation.GetConversationsNeedDestructMsgsReq{})
|
if len(config.CronTask.DeleteObjectType) == 0 {
|
||||||
if err != nil {
|
log.ZDebug(ctx, "cron deleteoutDatedData not type need delete", "deletetime", deleteTime, "DeleteObjectType", config.CronTask.DeleteObjectType, "cont", time.Since(now))
|
||||||
log.ZError(ctx, "Get conversation need Destruct msgs failed.", err)
|
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
_, err := msgClient.DestructMsgs(ctx, &msg.DestructMsgsReq{Conversations: conversations.Conversations})
|
|
||||||
|
for i := 0; i < executeNum; i++ {
|
||||||
|
resp, err := thirdClient.DeleteOutdatedData(ctx, &third.DeleteOutdatedDataReq{ExpireTime: deleteTime.UnixMilli(), ObjectGroup: config.CronTask.DeleteObjectType})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZError(ctx, "Destruct Msgs failed.", err)
|
log.ZError(ctx, "cron deleteoutDatedData failed", err, "deleteTime", deleteTime, "cont", time.Since(now))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if resp.Count == 0 || resp.Count < int32(pageShowNumber) {
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
log.ZDebug(ctx, "msg destruct cron task completed", "cont", time.Since(now))
|
|
||||||
|
log.ZDebug(ctx, "cron deleteoutDatedData success", "deltime", deleteTime, "cont", time.Since(now))
|
||||||
}
|
}
|
||||||
if _, err := crontab.AddFunc(config.CronTask.CronExecuteTime, msgDestructFunc); err != nil {
|
if _, err := crontab.AddFunc(config.CronTask.CronExecuteTime, deleteObjectFunc); err != nil {
|
||||||
return errs.Wrap(err)
|
return errs.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// // scheduled delete outdated file Objects and their datas in specific time.
|
|
||||||
// deleteObjectFunc := func() {
|
|
||||||
// now := time.Now()
|
|
||||||
// deleteTime := now.Add(-time.Hour * 24 * time.Duration(config.CronTask.FileExpireTime))
|
|
||||||
// ctx := mcontext.SetOperationID(ctx, fmt.Sprintf("cron_%d_%d", os.Getpid(), deleteTime.UnixMilli()))
|
|
||||||
// log.ZDebug(ctx, "deleteoutDatedData ", "deletetime", deleteTime, "timestamp", deleteTime.UnixMilli())
|
|
||||||
// if _, err := thirdClient.DeleteOutdatedData(ctx, &third.DeleteOutdatedDataReq{ExpireTime: deleteTime.UnixMilli()}); err != nil {
|
|
||||||
// log.ZError(ctx, "cron deleteoutDatedData failed", err, "deleteTime", deleteTime, "cont", time.Since(now))
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
// log.ZDebug(ctx, "cron deleteoutDatedData success", "deltime", deleteTime, "cont", time.Since(now))
|
|
||||||
// }
|
|
||||||
// if _, err := crontab.AddFunc(config.CronTask.CronExecuteTime, deleteObjectFunc); err != nil {
|
|
||||||
// return errs.Wrap(err)
|
|
||||||
// }
|
|
||||||
|
|
||||||
log.ZDebug(ctx, "start cron task", "CronExecuteTime", config.CronTask.CronExecuteTime)
|
log.ZDebug(ctx, "start cron task", "CronExecuteTime", config.CronTask.CronExecuteTime)
|
||||||
crontab.Start()
|
crontab.Start()
|
||||||
<-ctx.Done()
|
<-ctx.Done()
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ type Mongo struct {
|
|||||||
Database string `mapstructure:"database"`
|
Database string `mapstructure:"database"`
|
||||||
Username string `mapstructure:"username"`
|
Username string `mapstructure:"username"`
|
||||||
Password string `mapstructure:"password"`
|
Password string `mapstructure:"password"`
|
||||||
|
AuthSource string `mapstructure:"authSource"`
|
||||||
MaxPoolSize int `mapstructure:"maxPoolSize"`
|
MaxPoolSize int `mapstructure:"maxPoolSize"`
|
||||||
MaxRetry int `mapstructure:"maxRetry"`
|
MaxRetry int `mapstructure:"maxRetry"`
|
||||||
}
|
}
|
||||||
@@ -112,9 +113,10 @@ type API struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CronTask struct {
|
type CronTask struct {
|
||||||
CronExecuteTime string `mapstructure:"cronExecuteTime"`
|
CronExecuteTime string `mapstructure:"cronExecuteTime"`
|
||||||
RetainChatRecords int `mapstructure:"retainChatRecords"`
|
RetainChatRecords int `mapstructure:"retainChatRecords"`
|
||||||
FileExpireTime int `mapstructure:"fileExpireTime"`
|
FileExpireTime int `mapstructure:"fileExpireTime"`
|
||||||
|
DeleteObjectType []string `mapstructure:"deleteObjectType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type OfflinePushConfig struct {
|
type OfflinePushConfig struct {
|
||||||
@@ -212,12 +214,12 @@ type Push struct {
|
|||||||
FilePath string `mapstructure:"filePath"`
|
FilePath string `mapstructure:"filePath"`
|
||||||
AuthURL string `mapstructure:"authURL"`
|
AuthURL string `mapstructure:"authURL"`
|
||||||
} `mapstructure:"fcm"`
|
} `mapstructure:"fcm"`
|
||||||
JPNS struct {
|
JPush struct {
|
||||||
AppKey string `mapstructure:"appKey"`
|
AppKey string `mapstructure:"appKey"`
|
||||||
MasterSecret string `mapstructure:"masterSecret"`
|
MasterSecret string `mapstructure:"masterSecret"`
|
||||||
PushURL string `mapstructure:"pushURL"`
|
PushURL string `mapstructure:"pushURL"`
|
||||||
PushIntent string `mapstructure:"pushIntent"`
|
PushIntent string `mapstructure:"pushIntent"`
|
||||||
} `mapstructure:"jpns"`
|
} `mapstructure:"jpush"`
|
||||||
IOSPush struct {
|
IOSPush struct {
|
||||||
PushSound string `mapstructure:"pushSound"`
|
PushSound string `mapstructure:"pushSound"`
|
||||||
BadgeCount bool `mapstructure:"badgeCount"`
|
BadgeCount bool `mapstructure:"badgeCount"`
|
||||||
@@ -361,11 +363,17 @@ type AfterConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Share struct {
|
type Share struct {
|
||||||
Secret string `mapstructure:"secret"`
|
Secret string `mapstructure:"secret"`
|
||||||
RpcRegisterName RpcRegisterName `mapstructure:"rpcRegisterName"`
|
RpcRegisterName RpcRegisterName `mapstructure:"rpcRegisterName"`
|
||||||
IMAdminUserID []string `mapstructure:"imAdminUserID"`
|
IMAdminUserID []string `mapstructure:"imAdminUserID"`
|
||||||
MultiLoginPolicy int `mapstructure:"multiLoginPolicy"`
|
MultiLogin MultiLogin `mapstructure:"multiLogin"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type MultiLogin struct {
|
||||||
|
Policy int `mapstructure:"policy"`
|
||||||
|
MaxNumOneEnd int `mapstructure:"maxNumOneEnd"`
|
||||||
|
}
|
||||||
|
|
||||||
type RpcRegisterName struct {
|
type RpcRegisterName struct {
|
||||||
User string `mapstructure:"user"`
|
User string `mapstructure:"user"`
|
||||||
Friend string `mapstructure:"friend"`
|
Friend string `mapstructure:"friend"`
|
||||||
@@ -472,6 +480,7 @@ func (m *Mongo) Build() *mongoutil.Config {
|
|||||||
Database: m.Database,
|
Database: m.Database,
|
||||||
Username: m.Username,
|
Username: m.Username,
|
||||||
Password: m.Password,
|
Password: m.Password,
|
||||||
|
AuthSource: m.AuthSource,
|
||||||
MaxPoolSize: m.MaxPoolSize,
|
MaxPoolSize: m.MaxPoolSize,
|
||||||
MaxRetry: m.MaxRetry,
|
MaxRetry: m.MaxRetry,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||||
|
"github.com/openimsdk/protocol/relation"
|
||||||
|
|
||||||
"github.com/openimsdk/protocol/sdkws"
|
"github.com/openimsdk/protocol/sdkws"
|
||||||
"github.com/openimsdk/tools/utils/datautil"
|
"github.com/openimsdk/tools/utils/datautil"
|
||||||
@@ -35,9 +36,7 @@ func FriendPb2DB(friend *sdkws.FriendInfo) *model.Friend {
|
|||||||
return dbFriend
|
return dbFriend
|
||||||
}
|
}
|
||||||
|
|
||||||
func FriendDB2Pb(ctx context.Context, friendDB *model.Friend,
|
func FriendDB2Pb(ctx context.Context, friendDB *model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (*sdkws.FriendInfo, error) {
|
||||||
getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error),
|
|
||||||
) (*sdkws.FriendInfo, error) {
|
|
||||||
users, err := getUsers(ctx, []string{friendDB.FriendUserID})
|
users, err := getUsers(ctx, []string{friendDB.FriendUserID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -53,11 +52,7 @@ func FriendDB2Pb(ctx context.Context, friendDB *model.Friend,
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func FriendsDB2Pb(
|
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) {
|
||||||
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 {
|
if len(friendsDB) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -86,7 +81,21 @@ func FriendsDB2Pb(
|
|||||||
friendsPb = append(friendsPb, friendPb)
|
friendsPb = append(friendsPb, friendPb)
|
||||||
}
|
}
|
||||||
return friendsPb, nil
|
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) {
|
func FriendRequestDB2Pb(ctx context.Context, friendRequests []*model.FriendRequest, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) ([]*sdkws.FriendRequest, error) {
|
||||||
|
|||||||
+18
-1
@@ -1,6 +1,9 @@
|
|||||||
package cachekey
|
package cachekey
|
||||||
|
|
||||||
import "github.com/openimsdk/protocol/constant"
|
import (
|
||||||
|
"github.com/openimsdk/protocol/constant"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UidPidToken = "UID_PID_TOKEN_STATUS:"
|
UidPidToken = "UID_PID_TOKEN_STATUS:"
|
||||||
@@ -9,3 +12,17 @@ const (
|
|||||||
func GetTokenKey(userID string, platformID int) string {
|
func GetTokenKey(userID string, platformID int) string {
|
||||||
return UidPidToken + userID + ":" + constant.PlatformIDToName(platformID)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -38,7 +38,7 @@ const (
|
|||||||
func NewConversationRedis(rdb redis.UniversalClient, localCache *config.LocalCache, opts *rockscache.Options, db database.Conversation) cache.ConversationCache {
|
func NewConversationRedis(rdb redis.UniversalClient, localCache *config.LocalCache, opts *rockscache.Options, db database.Conversation) cache.ConversationCache {
|
||||||
batchHandler := NewBatchDeleterRedis(rdb, opts, []string{localCache.Conversation.Topic})
|
batchHandler := NewBatchDeleterRedis(rdb, opts, []string{localCache.Conversation.Topic})
|
||||||
c := localCache.Conversation
|
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{
|
return &ConversationRedisCache{
|
||||||
BatchDeleter: batchHandler,
|
BatchDeleter: batchHandler,
|
||||||
rcClient: rockscache.NewClient(rdb, *opts),
|
rcClient: rockscache.NewClient(rdb, *opts),
|
||||||
|
|||||||
-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
|
package redis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
+50
-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
|
package redis
|
||||||
|
|
||||||
import (
|
import (
|
||||||
@@ -21,6 +7,7 @@ import (
|
|||||||
"github.com/openimsdk/tools/errs"
|
"github.com/openimsdk/tools/errs"
|
||||||
"github.com/redis/go-redis/v9"
|
"github.com/redis/go-redis/v9"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -67,6 +54,43 @@ func (c *tokenCache) GetTokensWithoutError(ctx context.Context, userID string, p
|
|||||||
return mm, nil
|
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 {
|
func (c *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error {
|
||||||
mm := make(map[string]any)
|
mm := make(map[string]any)
|
||||||
for k, v := range m {
|
for k, v := range m {
|
||||||
@@ -75,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())
|
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 {
|
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())
|
return errs.Wrap(c.rdb.HDel(ctx, cachekey.GetTokenKey(userID, platformID), fields...).Err())
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+2
@@ -9,6 +9,8 @@ type TokenModel interface {
|
|||||||
// SetTokenFlagEx set token and flag with expire time
|
// SetTokenFlagEx set token and flag with expire time
|
||||||
SetTokenFlagEx(ctx context.Context, userID string, platformID int, token string, flag int) error
|
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)
|
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
|
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
|
DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,16 @@
|
|||||||
// 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
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/openimsdk/tools/log"
|
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v4"
|
"github.com/golang-jwt/jwt/v4"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||||
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||||
"github.com/openimsdk/protocol/constant"
|
"github.com/openimsdk/protocol/constant"
|
||||||
"github.com/openimsdk/tools/errs"
|
"github.com/openimsdk/tools/errs"
|
||||||
|
"github.com/openimsdk/tools/log"
|
||||||
"github.com/openimsdk/tools/tokenverify"
|
"github.com/openimsdk/tools/tokenverify"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,18 +20,30 @@ type AuthDatabase interface {
|
|||||||
// Create token
|
// Create token
|
||||||
CreateToken(ctx context.Context, userID string, platformID int) (string, error)
|
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
|
SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type authDatabase struct {
|
type multiLoginConfig struct {
|
||||||
cache cache.TokenModel
|
Policy int
|
||||||
accessSecret string
|
MaxNumOneEnd int
|
||||||
accessExpire int64
|
|
||||||
multiLoginPolicy int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthDatabase(cache cache.TokenModel, accessSecret string, accessExpire int64, policy int) AuthDatabase {
|
type authDatabase struct {
|
||||||
return &authDatabase{cache: cache, accessSecret: accessSecret, accessExpire: accessExpire, multiLoginPolicy: policy}
|
cache cache.TokenModel
|
||||||
|
accessSecret string
|
||||||
|
accessExpire int64
|
||||||
|
multiLogin multiLoginConfig
|
||||||
|
adminUserIDs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
// If the result is empty.
|
||||||
@@ -55,46 +55,56 @@ func (a *authDatabase) SetTokenMapByUidPid(ctx context.Context, userID string, p
|
|||||||
return a.cache.SetTokenMapByUidPid(ctx, userID, platformID, m)
|
return a.cache.SetTokenMapByUidPid(ctx, userID, platformID, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Token.
|
func (a *authDatabase) BatchSetTokenMapByUidPid(ctx context.Context, tokens []string) error {
|
||||||
func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformID int) (string, error) {
|
setMap := make(map[string]map[string]int)
|
||||||
// todo: get all platform token
|
for _, token := range tokens {
|
||||||
tokens, err := a.cache.GetTokensWithoutError(ctx, userID, platformID)
|
claims, err := tokenverify.GetClaimFromToken(token, authverify.Secret(a.accessSecret))
|
||||||
if err != nil {
|
key := cachekey.GetTokenKey(claims.UserID, claims.PlatformID)
|
||||||
return "", err
|
if err != nil {
|
||||||
}
|
continue
|
||||||
var deleteTokenKey []string
|
} else {
|
||||||
var kickedTokenKey []string
|
if v, ok := setMap[key]; ok {
|
||||||
for k, v := range tokens {
|
v[token] = constant.KickedToken
|
||||||
t, err := tokenverify.GetClaimFromToken(k, authverify.Secret(a.accessSecret))
|
} else {
|
||||||
if err != nil || v != constant.NormalToken {
|
setMap[key] = map[string]int{
|
||||||
deleteTokenKey = append(deleteTokenKey, k)
|
token: constant.KickedToken,
|
||||||
} else if a.checkKickToken(ctx, platformID, t) {
|
}
|
||||||
kickedTokenKey = append(kickedTokenKey, k)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(deleteTokenKey) != 0 {
|
if err := a.cache.BatchSetTokenMapByUidPid(ctx, setMap); err != nil {
|
||||||
err = a.cache.DeleteTokenByUidPid(ctx, userID, platformID, deleteTokenKey)
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const adminTokenMaxNum = 30
|
deleteTokenKey, kickedTokenKey, err := a.checkToken(ctx, tokens, platformID)
|
||||||
if platformID == constant.AdminPlatformID {
|
if err != nil {
|
||||||
if len(kickedTokenKey) > adminTokenMaxNum {
|
return "", err
|
||||||
kickedTokenKey = kickedTokenKey[:len(kickedTokenKey)-adminTokenMaxNum]
|
|
||||||
} else {
|
|
||||||
kickedTokenKey = nil
|
|
||||||
}
|
}
|
||||||
}
|
if len(deleteTokenKey) != 0 {
|
||||||
|
err = a.cache.DeleteTokenByUidPid(ctx, userID, platformID, deleteTokenKey)
|
||||||
if len(kickedTokenKey) != 0 {
|
|
||||||
for _, k := range kickedTokenKey {
|
|
||||||
err := a.cache.SetTokenFlagEx(ctx, userID, platformID, k, constant.KickedToken)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
log.ZDebug(ctx, "kicked token in create token", "token", k)
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -105,28 +115,124 @@ func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformI
|
|||||||
return "", errs.WrapMsg(err, "token.SignedString")
|
return "", errs.WrapMsg(err, "token.SignedString")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = a.cache.SetTokenFlagEx(ctx, userID, platformID, tokenString, constant.NormalToken); err != nil {
|
if !isAdmin {
|
||||||
return "", err
|
if err = a.cache.SetTokenFlagEx(ctx, userID, platformID, tokenString, constant.NormalToken); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tokenString, nil
|
return tokenString, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *authDatabase) checkKickToken(ctx context.Context, platformID int, token *tokenverify.Claims) bool {
|
func (a *authDatabase) checkToken(ctx context.Context, tokens map[int]map[string]int, platformID int) ([]string, []string, error) {
|
||||||
switch a.multiLoginPolicy {
|
// todo: Move the logic for handling old data to another location.
|
||||||
case constant.DefalutNotKick:
|
var (
|
||||||
return false
|
loginTokenMap = make(map[int][]string) // The length of the value of the map must be greater than 0
|
||||||
case constant.PCAndOther:
|
deleteToken = make([]string, 0)
|
||||||
if constant.PlatformIDToClass(platformID) == constant.TerminalPC ||
|
kickToken = make([]string, 0)
|
||||||
constant.PlatformIDToClass(token.PlatformID) == constant.TerminalPC {
|
adminToken = make([]string, 0)
|
||||||
return false
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
case constant.AllLoginButSameTermKick:
|
|
||||||
if platformID == token.PlatformID {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||||
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
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/common/storage/cache"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
|
||||||
@@ -178,7 +179,7 @@ func (c *conversationDatabase) CreateConversation(ctx context.Context, conversat
|
|||||||
if conversation.RecvMsgOpt == constant.ReceiveNotNotifyMessage {
|
if conversation.RecvMsgOpt == constant.ReceiveNotNotifyMessage {
|
||||||
notNotifyUserIDs = append(notNotifyUserIDs, conversation.OwnerUserID)
|
notNotifyUserIDs = append(notNotifyUserIDs, conversation.OwnerUserID)
|
||||||
}
|
}
|
||||||
if conversation.IsPinned == true {
|
if conversation.IsPinned {
|
||||||
pinnedUserIDs = append(pinnedUserIDs, conversation.OwnerUserID)
|
pinnedUserIDs = append(pinnedUserIDs, conversation.OwnerUserID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,7 +195,7 @@ func (c *conversationDatabase) SyncPeerUserPrivateConversationTx(ctx context.Con
|
|||||||
return c.tx.Transaction(ctx, func(ctx context.Context) error {
|
return c.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||||
cache := c.cache.CloneConversationCache()
|
cache := c.cache.CloneConversationCache()
|
||||||
for _, conversation := range conversations {
|
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}} {
|
for _, v := range [][2]string{{conversation.OwnerUserID, conversation.UserID}, {conversation.UserID, conversation.OwnerUserID}} {
|
||||||
ownerUserID := v[0]
|
ownerUserID := v[0]
|
||||||
userID := v[1]
|
userID := v[1]
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ import (
|
|||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
|
||||||
@@ -35,8 +38,6 @@ import (
|
|||||||
"github.com/openimsdk/tools/mq/kafka"
|
"github.com/openimsdk/tools/mq/kafka"
|
||||||
"github.com/openimsdk/tools/utils/datautil"
|
"github.com/openimsdk/tools/utils/datautil"
|
||||||
"github.com/openimsdk/tools/utils/timeutil"
|
"github.com/openimsdk/tools/utils/timeutil"
|
||||||
"github.com/redis/go-redis/v9"
|
|
||||||
"go.mongodb.org/mongo-driver/mongo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -56,9 +57,10 @@ type CommonMsgDatabase interface {
|
|||||||
GetMsgBySeqs(ctx context.Context, userID string, conversationID string, seqs []int64) (minSeq int64, maxSeq int64, seqMsg []*sdkws.MsgData, err error)
|
GetMsgBySeqs(ctx context.Context, userID string, conversationID string, seqs []int64) (minSeq int64, maxSeq int64, seqMsg []*sdkws.MsgData, err error)
|
||||||
// DeleteConversationMsgsAndSetMinSeq deletes conversation messages and resets the minimum sequence number. If `remainTime` is 0, all messages are deleted (this method does not delete Redis
|
// DeleteConversationMsgsAndSetMinSeq deletes conversation messages and resets the minimum sequence number. If `remainTime` is 0, all messages are deleted (this method does not delete Redis
|
||||||
// cache).
|
// cache).
|
||||||
|
GetMessagesBySeqWithBounds(ctx context.Context, userID string, conversationID string, seqs []int64, pullOrder sdkws.PullOrder) (bool, int64, []*sdkws.MsgData, error)
|
||||||
DeleteConversationMsgsAndSetMinSeq(ctx context.Context, conversationID string, remainTime int64) error
|
DeleteConversationMsgsAndSetMinSeq(ctx context.Context, conversationID string, remainTime int64) error
|
||||||
// UserMsgsDestruct marks messages for deletion based on destruct time and returns a list of sequence numbers for marked messages.
|
// ClearUserMsgs marks messages for deletion based on clear time and returns a list of sequence numbers for marked messages.
|
||||||
UserMsgsDestruct(ctx context.Context, userID string, conversationID string, destructTime int64, lastMsgDestructTime time.Time) (seqs []int64, err error)
|
ClearUserMsgs(ctx context.Context, userID string, conversationID string, clearTime int64, lastMsgClearTime time.Time) (seqs []int64, err error)
|
||||||
// DeleteUserMsgsBySeqs allows a user to delete messages based on sequence numbers.
|
// DeleteUserMsgsBySeqs allows a user to delete messages based on sequence numbers.
|
||||||
DeleteUserMsgsBySeqs(ctx context.Context, userID string, conversationID string, seqs []int64) error
|
DeleteUserMsgsBySeqs(ctx context.Context, userID string, conversationID string, seqs []int64) error
|
||||||
// DeleteMsgsPhysicalBySeqs physically deletes messages by emptying them based on sequence numbers.
|
// DeleteMsgsPhysicalBySeqs physically deletes messages by emptying them based on sequence numbers.
|
||||||
@@ -92,11 +94,14 @@ type CommonMsgDatabase interface {
|
|||||||
RangeGroupSendCount(ctx context.Context, start time.Time, end time.Time, ase bool, pageNumber int32, showNumber int32) (msgCount int64, userCount int64, groups []*model.GroupCount, dateCount map[string]int64, err error)
|
RangeGroupSendCount(ctx context.Context, start time.Time, end time.Time, ase bool, pageNumber int32, showNumber int32) (msgCount int64, userCount int64, groups []*model.GroupCount, dateCount map[string]int64, err error)
|
||||||
ConvertMsgsDocLen(ctx context.Context, conversationIDs []string)
|
ConvertMsgsDocLen(ctx context.Context, conversationIDs []string)
|
||||||
|
|
||||||
// clear msg
|
// get Msg when destruct msg before
|
||||||
GetBeforeMsg(ctx context.Context, ts int64, docIds []string, limit int) ([]*model.MsgDocModel, error)
|
GetBeforeMsg(ctx context.Context, ts int64, docIds []string, limit int) ([]*model.MsgDocModel, error)
|
||||||
DeleteDocMsgBefore(ctx context.Context, ts int64, doc *model.MsgDocModel) ([]int, error)
|
DeleteDocMsgBefore(ctx context.Context, ts int64, doc *model.MsgDocModel) ([]int, error)
|
||||||
|
|
||||||
GetDocIDs(ctx context.Context) ([]string, error)
|
GetDocIDs(ctx context.Context) ([]string, error)
|
||||||
|
|
||||||
|
SetUserConversationsMaxSeq(ctx context.Context, conversationID string, userID string, seq int64) error
|
||||||
|
SetUserConversationsMinSeq(ctx context.Context, conversationID string, userID string, seq int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCommonMsgDatabase(msgDocModel database.Msg, msg cache.MsgCache, seqUser cache.SeqUser, seqConversation cache.SeqConversationCache, kafkaConf *config.Kafka) (CommonMsgDatabase, error) {
|
func NewCommonMsgDatabase(msgDocModel database.Msg, msg cache.MsgCache, seqUser cache.SeqUser, seqConversation cache.SeqConversationCache, kafkaConf *config.Kafka) (CommonMsgDatabase, error) {
|
||||||
@@ -372,7 +377,7 @@ func (db *commonMsgDatabase) getMsgBySeqsRange(ctx context.Context, userID strin
|
|||||||
// This ensures that their message retrieval starts from the point they joined.
|
// 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) {
|
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)
|
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
|
return 0, 0, nil, err
|
||||||
}
|
}
|
||||||
minSeq, err := db.seqConversation.GetMinSeq(ctx, conversationID)
|
minSeq, err := db.seqConversation.GetMinSeq(ctx, conversationID)
|
||||||
@@ -443,6 +448,11 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
|
|||||||
return 0, 0, nil, err
|
return 0, 0, nil, err
|
||||||
}
|
}
|
||||||
successMsgs = append(mongoMsgs, successMsgs...)
|
successMsgs = append(mongoMsgs, successMsgs...)
|
||||||
|
|
||||||
|
//_, err = db.msg.SetMessagesToCache(ctx, conversationID, mongoMsgs)
|
||||||
|
//if err != nil {
|
||||||
|
// return 0, 0, nil, err
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
return minSeq, maxSeq, successMsgs, nil
|
return minSeq, maxSeq, successMsgs, nil
|
||||||
@@ -485,8 +495,8 @@ func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, co
|
|||||||
}
|
}
|
||||||
successMsgs, failedSeqs, err := db.msg.GetMessagesBySeq(ctx, conversationID, newSeqs)
|
successMsgs, failedSeqs, err := db.msg.GetMessagesBySeq(ctx, conversationID, newSeqs)
|
||||||
if err != nil {
|
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)
|
log.ZWarn(ctx, "get message from redis exception", err, "failedSeqs", failedSeqs, "conversationID", conversationID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.ZDebug(ctx, "db.seq.GetMessagesBySeq", "userID", userID, "conversationID", conversationID, "seqs",
|
log.ZDebug(ctx, "db.seq.GetMessagesBySeq", "userID", userID, "conversationID", conversationID, "seqs",
|
||||||
@@ -500,10 +510,90 @@ func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, co
|
|||||||
}
|
}
|
||||||
|
|
||||||
successMsgs = append(successMsgs, mongoMsgs...)
|
successMsgs = append(successMsgs, mongoMsgs...)
|
||||||
|
|
||||||
|
//_, err = db.msg.SetMessagesToCache(ctx, conversationID, mongoMsgs)
|
||||||
|
//if err != nil {
|
||||||
|
// return 0, 0, nil, err
|
||||||
|
//}
|
||||||
}
|
}
|
||||||
return minSeq, maxSeq, successMsgs, nil
|
return minSeq, maxSeq, successMsgs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *commonMsgDatabase) GetMessagesBySeqWithBounds(ctx context.Context, userID string, conversationID string, seqs []int64, pullOrder sdkws.PullOrder) (bool, int64, []*sdkws.MsgData, error) {
|
||||||
|
var endSeq int64
|
||||||
|
var isEnd bool
|
||||||
|
userMinSeq, err := db.seqUser.GetUserMinSeq(ctx, conversationID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, nil, err
|
||||||
|
}
|
||||||
|
minSeq, err := db.seqConversation.GetMinSeq(ctx, conversationID)
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, nil, err
|
||||||
|
}
|
||||||
|
maxSeq, err := db.seqConversation.GetMaxSeq(ctx, conversationID)
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, nil, err
|
||||||
|
}
|
||||||
|
userMaxSeq, err := db.seqUser.GetUserMaxSeq(ctx, conversationID, userID)
|
||||||
|
if err != nil {
|
||||||
|
return false, 0, nil, err
|
||||||
|
}
|
||||||
|
if userMinSeq > minSeq {
|
||||||
|
minSeq = userMinSeq
|
||||||
|
}
|
||||||
|
if userMaxSeq > 0 && userMaxSeq < maxSeq {
|
||||||
|
maxSeq = userMaxSeq
|
||||||
|
}
|
||||||
|
newSeqs := make([]int64, 0, len(seqs))
|
||||||
|
for _, seq := range seqs {
|
||||||
|
if seq <= 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// The normal range and can fetch messages
|
||||||
|
if seq >= minSeq && seq <= maxSeq {
|
||||||
|
newSeqs = append(newSeqs, seq)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// If the requested seq is smaller than the minimum seq and the pull order is descending (pulling older messages)
|
||||||
|
if seq < minSeq && pullOrder == sdkws.PullOrder_PullOrderDesc {
|
||||||
|
isEnd = true
|
||||||
|
endSeq = minSeq
|
||||||
|
}
|
||||||
|
// If the requested seq is larger than the maximum seq and the pull order is ascending (pulling newer messages)
|
||||||
|
if seq > maxSeq && pullOrder == sdkws.PullOrder_PullOrderAsc {
|
||||||
|
isEnd = true
|
||||||
|
endSeq = maxSeq
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(newSeqs) == 0 {
|
||||||
|
return isEnd, endSeq, nil, nil
|
||||||
|
}
|
||||||
|
successMsgs, failedSeqs, err := db.msg.GetMessagesBySeq(ctx, conversationID, newSeqs)
|
||||||
|
if err != nil {
|
||||||
|
if !errors.Is(err, redis.Nil) {
|
||||||
|
log.ZWarn(ctx, "get message from redis exception", err, "failedSeqs", failedSeqs, "conversationID", conversationID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.ZDebug(ctx, "db.seq.GetMessagesBySeq", "userID", userID, "conversationID", conversationID, "seqs",
|
||||||
|
seqs, "len(successMsgs)", len(successMsgs), "failedSeqs", failedSeqs)
|
||||||
|
|
||||||
|
if len(failedSeqs) > 0 {
|
||||||
|
mongoMsgs, err := db.getMsgBySeqs(ctx, userID, conversationID, failedSeqs)
|
||||||
|
if err != nil {
|
||||||
|
|
||||||
|
return false, 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
successMsgs = append(successMsgs, mongoMsgs...)
|
||||||
|
|
||||||
|
//_, err = db.msg.SetMessagesToCache(ctx, conversationID, mongoMsgs)
|
||||||
|
//if err != nil {
|
||||||
|
// return 0, 0, nil, err
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
return isEnd, endSeq, successMsgs, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (db *commonMsgDatabase) DeleteConversationMsgsAndSetMinSeq(ctx context.Context, conversationID string, remainTime int64) error {
|
func (db *commonMsgDatabase) DeleteConversationMsgsAndSetMinSeq(ctx context.Context, conversationID string, remainTime int64) error {
|
||||||
var delStruct delMsgRecursionStruct
|
var delStruct delMsgRecursionStruct
|
||||||
var skip int64
|
var skip int64
|
||||||
@@ -518,10 +608,10 @@ func (db *commonMsgDatabase) DeleteConversationMsgsAndSetMinSeq(ctx context.Cont
|
|||||||
return db.seqConversation.SetMinSeq(ctx, conversationID, minSeq)
|
return db.seqConversation.SetMinSeq(ctx, conversationID, minSeq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *commonMsgDatabase) UserMsgsDestruct(ctx context.Context, userID string, conversationID string, destructTime int64, lastMsgDestructTime time.Time) (seqs []int64, err error) {
|
func (db *commonMsgDatabase) ClearUserMsgs(ctx context.Context, userID string, conversationID string, clearTime int64, lastMsgClearTime time.Time) (seqs []int64, err error) {
|
||||||
var index int64
|
var index int64
|
||||||
for {
|
for {
|
||||||
// from oldest 2 newest
|
// from oldest 2 newest, ASC
|
||||||
msgDocModel, err := db.msgDocDatabase.GetMsgDocModelByIndex(ctx, conversationID, index, 1)
|
msgDocModel, err := db.msgDocDatabase.GetMsgDocModelByIndex(ctx, conversationID, index, 1)
|
||||||
if err != nil || msgDocModel.DocID == "" {
|
if err != nil || msgDocModel.DocID == "" {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -534,15 +624,19 @@ func (db *commonMsgDatabase) UserMsgsDestruct(ctx context.Context, userID string
|
|||||||
// If an error is reported, or the error cannot be obtained, it is physically deleted and seq delMongoMsgsPhysical(delStruct.delDocIDList) is returned to end the recursion
|
// If an error is reported, or the error cannot be obtained, it is physically deleted and seq delMongoMsgsPhysical(delStruct.delDocIDList) is returned to end the recursion
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
index++
|
index++
|
||||||
// && msgDocModel.Msg[0].Msg.SendTime > lastMsgDestructTime.UnixMilli()
|
|
||||||
|
// && msgDocModel.Msg[0].Msg.SendTime > lastMsgClearTime.UnixMilli()
|
||||||
if len(msgDocModel.Msg) > 0 {
|
if len(msgDocModel.Msg) > 0 {
|
||||||
i := 0
|
i := 0
|
||||||
var over bool
|
var over bool
|
||||||
for _, msg := range msgDocModel.Msg {
|
for _, msg := range msgDocModel.Msg {
|
||||||
i++
|
i++
|
||||||
if msg != nil && msg.Msg != nil && msg.Msg.SendTime+destructTime*1000 <= time.Now().UnixMilli() {
|
// over clear time, need to clear
|
||||||
if msg.Msg.SendTime+destructTime*1000 > lastMsgDestructTime.UnixMilli() && !datautil.Contain(userID, msg.DelList...) {
|
if msg != nil && msg.Msg != nil && msg.Msg.SendTime+clearTime*1000 <= time.Now().UnixMilli() {
|
||||||
|
// if msg is not in del list, add to del list
|
||||||
|
if msg.Msg.SendTime+clearTime*1000 > lastMsgClearTime.UnixMilli() && !datautil.Contain(userID, msg.DelList...) {
|
||||||
seqs = append(seqs, msg.Msg.Seq)
|
seqs = append(seqs, msg.Msg.Seq)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -557,13 +651,18 @@ func (db *commonMsgDatabase) UserMsgsDestruct(ctx context.Context, userID string
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.ZDebug(ctx, "UserMsgsDestruct", "conversationID", conversationID, "userID", userID, "seqs", seqs)
|
log.ZDebug(ctx, "ClearUserMsgs", "conversationID", conversationID, "userID", userID, "seqs", seqs)
|
||||||
|
|
||||||
|
// have msg need to destruct
|
||||||
if len(seqs) > 0 {
|
if len(seqs) > 0 {
|
||||||
userMinSeq := seqs[len(seqs)-1] + 1
|
// update min seq to clear after
|
||||||
currentUserMinSeq, err := db.seqUser.GetUserMinSeq(ctx, conversationID, userID)
|
userMinSeq := seqs[len(seqs)-1] + 1 // user min seq when clear after
|
||||||
|
currentUserMinSeq, err := db.seqUser.GetUserMinSeq(ctx, conversationID, userID) // user min seq when clear before
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if before < after, update min seq
|
||||||
if currentUserMinSeq < userMinSeq {
|
if currentUserMinSeq < userMinSeq {
|
||||||
if err := db.seqUser.SetUserMinSeq(ctx, conversationID, userID, userMinSeq); err != nil {
|
if err := db.seqUser.SetUserMinSeq(ctx, conversationID, userID, userMinSeq); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -683,6 +782,14 @@ func (db *commonMsgDatabase) SetUserConversationsMinSeqs(ctx context.Context, us
|
|||||||
return db.seqUser.SetUserMinSeqs(ctx, userID, seqs)
|
return db.seqUser.SetUserMinSeqs(ctx, userID, seqs)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *commonMsgDatabase) SetUserConversationsMaxSeq(ctx context.Context, conversationID string, userID string, seq int64) error {
|
||||||
|
return db.seqUser.SetUserMaxSeq(ctx, conversationID, userID, seq)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *commonMsgDatabase) SetUserConversationsMinSeq(ctx context.Context, conversationID string, userID string, seq int64) error {
|
||||||
|
return db.seqUser.SetUserMinSeq(ctx, conversationID, userID, seq)
|
||||||
|
}
|
||||||
|
|
||||||
func (db *commonMsgDatabase) UserSetHasReadSeqs(ctx context.Context, userID string, hasReadSeqs map[string]int64) error {
|
func (db *commonMsgDatabase) UserSetHasReadSeqs(ctx context.Context, userID string, hasReadSeqs map[string]int64) error {
|
||||||
return db.seqUser.SetUserReadSeqs(ctx, userID, hasReadSeqs)
|
return db.seqUser.SetUserReadSeqs(ctx, userID, hasReadSeqs)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package controller
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"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/config"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
|
"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
|
DeleteMessagesFromCache(ctx context.Context, conversationID string, seqs []int64) error
|
||||||
|
|
||||||
// BatchInsertChat2Cache increments the sequence number and then batch inserts messages into the cache.
|
// 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)
|
BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNewConversation bool, userHasReadMap map[string]int64, err error)
|
||||||
SetHasReadSeqToDB(ctx context.Context, userID string, conversationID string, hasReadSeq int64) 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
|
// to mq
|
||||||
MsgToPushMQ(ctx context.Context, key, conversationID string, msg2mq *sdkws.MsgData) (int32, int64, error)
|
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,
|
IOSBadgeCount: msg.OfflinePushInfo.IOSBadgeCount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if msg.Status == constant.MsgStatusSending {
|
||||||
|
msg.Status = constant.MsgStatusSendSuccess
|
||||||
|
}
|
||||||
msgs[i] = &model.MsgDataModel{
|
msgs[i] = &model.MsgDataModel{
|
||||||
SendID: msg.SendID,
|
SendID: msg.SendID,
|
||||||
RecvID: msg.RecvID,
|
RecvID: msg.RecvID,
|
||||||
@@ -215,18 +222,18 @@ func (db *msgTransferDatabase) DeleteMessagesFromCache(ctx context.Context, conv
|
|||||||
return db.msg.DeleteMessagesFromCache(ctx, conversationID, seqs)
|
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)
|
lenList := len(msgs)
|
||||||
if int64(lenList) > db.msgTable.GetSingleGocMsgNum() {
|
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 {
|
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)))
|
currentMaxSeq, err := db.seqConversation.Malloc(ctx, conversationID, int64(len(msgs)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.ZError(ctx, "storage.seq.Malloc", err)
|
log.ZError(ctx, "storage.seq.Malloc", err)
|
||||||
return 0, false, err
|
return 0, false, nil, err
|
||||||
}
|
}
|
||||||
isNew = currentMaxSeq == 0
|
isNew = currentMaxSeq == 0
|
||||||
lastMaxSeq := currentMaxSeq
|
lastMaxSeq := currentMaxSeq
|
||||||
@@ -244,15 +251,10 @@ func (db *msgTransferDatabase) BatchInsertChat2Cache(ctx context.Context, conver
|
|||||||
} else {
|
} else {
|
||||||
prommetrics.MsgInsertRedisSuccessCounter.Inc()
|
prommetrics.MsgInsertRedisSuccessCounter.Inc()
|
||||||
}
|
}
|
||||||
err = db.setHasReadSeqs(ctx, conversationID, userSeqMap)
|
return lastMaxSeq, isNew, userSeqMap, errs.Wrap(err)
|
||||||
if err != nil {
|
|
||||||
log.ZError(ctx, "SetHasReadSeqs error", err, "userSeqMap", userSeqMap, "conversationID", conversationID)
|
|
||||||
prommetrics.SeqSetFailedCounter.Inc()
|
|
||||||
}
|
|
||||||
return lastMaxSeq, isNew, 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 {
|
for userID, seq := range userSeqMap {
|
||||||
if err := db.seqUser.SetUserReadSeq(ctx, conversationID, userID, seq); err != nil {
|
if err := db.seqUser.SetUserReadSeq(ctx, conversationID, userID, seq); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -261,8 +263,13 @@ func (db *msgTransferDatabase) setHasReadSeqs(ctx context.Context, conversationI
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *msgTransferDatabase) SetHasReadSeqToDB(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error {
|
func (db *msgTransferDatabase) SetHasReadSeqToDB(ctx context.Context, conversationID string, userSeqMap map[string]int64) error {
|
||||||
return db.seqUser.SetUserReadSeqToDB(ctx, conversationID, userID, hasReadSeq)
|
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) {
|
func (db *msgTransferDatabase) MsgToPushMQ(ctx context.Context, key, conversationID string, msg2mq *sdkws.MsgData) (int32, int64, error) {
|
||||||
|
|||||||
@@ -40,10 +40,10 @@ type S3Database interface {
|
|||||||
SetObject(ctx context.Context, info *model.Object) error
|
SetObject(ctx context.Context, info *model.Object) error
|
||||||
StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error)
|
StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error)
|
||||||
FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error)
|
FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error)
|
||||||
FindByExpires(ctx context.Context, duration time.Time, pagination pagination.Pagination) (total int64, objects []*model.Object, err error)
|
FindNeedDeleteObjectByDB(ctx context.Context, duration time.Time, needDelType []string, pagination pagination.Pagination) (total int64, objects []*model.Object, err error)
|
||||||
DeleteObject(ctx context.Context, name string) error
|
DeleteObject(ctx context.Context, name string) error
|
||||||
DeleteSpecifiedData(ctx context.Context, engine string, name string) error
|
DeleteSpecifiedData(ctx context.Context, engine string, name string) error
|
||||||
FindNotDelByS3(ctx context.Context, key string, duration time.Time) (int64, error)
|
FindModelsByKey(ctx context.Context, key string) (objects []*model.Object, err error)
|
||||||
DelS3Key(ctx context.Context, engine string, keys ...string) error
|
DelS3Key(ctx context.Context, engine string, keys ...string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,9 +120,8 @@ func (s *s3Database) StatObject(ctx context.Context, name string) (*s3.ObjectInf
|
|||||||
func (s *s3Database) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) {
|
func (s *s3Database) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) {
|
||||||
return s.s3.FormData(ctx, name, size, contentType, duration)
|
return s.s3.FormData(ctx, name, size, contentType, duration)
|
||||||
}
|
}
|
||||||
func (s *s3Database) FindByExpires(ctx context.Context, duration time.Time, pagination pagination.Pagination) (total int64, objects []*model.Object, err error) {
|
func (s *s3Database) FindNeedDeleteObjectByDB(ctx context.Context, duration time.Time, needDelType []string, pagination pagination.Pagination) (total int64, objects []*model.Object, err error) {
|
||||||
|
return s.db.FindNeedDeleteObjectByDB(ctx, duration, needDelType, pagination)
|
||||||
return s.db.FindByExpires(ctx, duration, pagination)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *s3Database) DeleteObject(ctx context.Context, name string) error {
|
func (s *s3Database) DeleteObject(ctx context.Context, name string) error {
|
||||||
@@ -132,8 +131,8 @@ func (s *s3Database) DeleteSpecifiedData(ctx context.Context, engine string, nam
|
|||||||
return s.db.Delete(ctx, engine, name)
|
return s.db.Delete(ctx, engine, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *s3Database) FindNotDelByS3(ctx context.Context, key string, duration time.Time) (int64, error) {
|
func (s *s3Database) FindModelsByKey(ctx context.Context, key string) (objects []*model.Object, err error) {
|
||||||
return s.db.FindNotDelByS3(ctx, key, duration)
|
return s.db.FindModelsByKey(ctx, key)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *s3Database) DelS3Key(ctx context.Context, engine string, keys ...string) error {
|
func (s *s3Database) DelS3Key(ctx context.Context, engine string, keys ...string) error {
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package database
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||||
"github.com/openimsdk/tools/db/pagination"
|
"github.com/openimsdk/tools/db/pagination"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ package mgo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/openimsdk/protocol/constant"
|
"github.com/openimsdk/protocol/constant"
|
||||||
"github.com/openimsdk/tools/db/mongoutil"
|
"github.com/openimsdk/tools/db/mongoutil"
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ func (g *GroupMgo) Take(ctx context.Context, groupID string) (group *model.Group
|
|||||||
|
|
||||||
func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*model.Group, err error) {
|
func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*model.Group, err error) {
|
||||||
// Define the sorting options
|
// Define the sorting options
|
||||||
opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}})
|
opts := options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}})
|
||||||
|
|
||||||
// Perform the search with pagination and sorting
|
// Perform the search with pagination and sorting
|
||||||
return mongoutil.FindPage[*model.Group](ctx, g.coll, bson.M{
|
return mongoutil.FindPage[*model.Group](ctx, g.coll, bson.M{
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ type GroupMemberMgo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *GroupMemberMgo) memberSort() any {
|
func (g *GroupMemberMgo) memberSort() any {
|
||||||
return bson.D{{"role_level", -1}, {"create_time", 1}}
|
return bson.D{{Key: "role_level", Value: -1}, {Key: "create_time", Value: 1}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GroupMemberMgo) Create(ctx context.Context, groupMembers []*model.GroupMember) (err error) {
|
func (g *GroupMemberMgo) Create(ctx context.Context, groupMembers []*model.GroupMember) (err error) {
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ import (
|
|||||||
|
|
||||||
func NewS3Mongo(db *mongo.Database) (database.ObjectInfo, error) {
|
func NewS3Mongo(db *mongo.Database) (database.ObjectInfo, error) {
|
||||||
coll := db.Collection(database.ObjectName)
|
coll := db.Collection(database.ObjectName)
|
||||||
|
|
||||||
|
// Create index for name
|
||||||
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
_, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||||
Keys: bson.D{
|
Keys: bson.D{
|
||||||
{Key: "name", Value: 1},
|
{Key: "name", Value: 1},
|
||||||
@@ -40,6 +42,27 @@ func NewS3Mongo(db *mongo.Database) (database.ObjectInfo, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errs.Wrap(err)
|
return nil, errs.Wrap(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create index for create_time
|
||||||
|
_, err = coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||||
|
Keys: bson.D{
|
||||||
|
{Key: "create_time", Value: 1},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create index for key
|
||||||
|
_, err = coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{
|
||||||
|
Keys: bson.D{
|
||||||
|
{Key: "key", Value: 1},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errs.Wrap(err)
|
||||||
|
}
|
||||||
|
|
||||||
return &S3Mongo{coll: coll}, nil
|
return &S3Mongo{coll: coll}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -71,14 +94,18 @@ func (o *S3Mongo) Take(ctx context.Context, engine string, name string) (*model.
|
|||||||
func (o *S3Mongo) Delete(ctx context.Context, engine string, name string) error {
|
func (o *S3Mongo) Delete(ctx context.Context, engine string, name string) error {
|
||||||
return mongoutil.DeleteOne(ctx, o.coll, bson.M{"name": name, "engine": engine})
|
return mongoutil.DeleteOne(ctx, o.coll, bson.M{"name": name, "engine": engine})
|
||||||
}
|
}
|
||||||
func (o *S3Mongo) FindByExpires(ctx context.Context, duration time.Time, pagination pagination.Pagination) (total int64, objects []*model.Object, err error) {
|
|
||||||
|
// Find Expires object
|
||||||
|
func (o *S3Mongo) FindNeedDeleteObjectByDB(ctx context.Context, duration time.Time, needDelType []string, pagination pagination.Pagination) (total int64, objects []*model.Object, err error) {
|
||||||
return mongoutil.FindPage[*model.Object](ctx, o.coll, bson.M{
|
return mongoutil.FindPage[*model.Object](ctx, o.coll, bson.M{
|
||||||
"create_time": bson.M{"$lt": duration},
|
"create_time": bson.M{"$lt": duration},
|
||||||
|
"group": bson.M{"$in": needDelType},
|
||||||
}, pagination)
|
}, pagination)
|
||||||
}
|
}
|
||||||
func (o *S3Mongo) FindNotDelByS3(ctx context.Context, key string, duration time.Time) (int64, error) {
|
|
||||||
return mongoutil.Count(ctx, o.coll, bson.M{
|
// Find object by key
|
||||||
"key": key,
|
func (o *S3Mongo) FindModelsByKey(ctx context.Context, key string) (objects []*model.Object, err error) {
|
||||||
"create_time": bson.M{"$gt": duration},
|
return mongoutil.Find[*model.Object](ctx, o.coll, bson.M{
|
||||||
|
"key": key,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,6 +26,6 @@ type ObjectInfo interface {
|
|||||||
SetObject(ctx context.Context, obj *model.Object) error
|
SetObject(ctx context.Context, obj *model.Object) error
|
||||||
Take(ctx context.Context, engine string, name string) (*model.Object, error)
|
Take(ctx context.Context, engine string, name string) (*model.Object, error)
|
||||||
Delete(ctx context.Context, engine string, name string) error
|
Delete(ctx context.Context, engine string, name string) error
|
||||||
FindByExpires(ctx context.Context, duration time.Time, pagination pagination.Pagination) (total int64, objects []*model.Object, err error)
|
FindNeedDeleteObjectByDB(ctx context.Context, duration time.Time, needDelType []string, pagination pagination.Pagination) (total int64, objects []*model.Object, err error)
|
||||||
FindNotDelByS3(ctx context.Context, key string, duration time.Time) (int64, error)
|
FindModelsByKey(ctx context.Context, key string) (objects []*model.Object, err error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Application struct {
|
||||||
|
ID primitive.ObjectID `bson:"_id"`
|
||||||
|
Platform string `bson:"platform"`
|
||||||
|
Hot bool `bson:"hot"`
|
||||||
|
Version string `bson:"version"`
|
||||||
|
Url string `bson:"url"`
|
||||||
|
Text string `bson:"text"`
|
||||||
|
Force bool `bson:"force"`
|
||||||
|
Latest bool `bson:"latest"`
|
||||||
|
CreateTime time.Time `bson:"create_time"`
|
||||||
|
}
|
||||||
@@ -19,13 +19,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Log struct {
|
type Log struct {
|
||||||
LogID string `bson:"log_id"`
|
LogID string `bson:"log_id"`
|
||||||
Platform string `bson:"platform"`
|
Platform string `bson:"platform"`
|
||||||
UserID string `bson:"user_id"`
|
UserID string `bson:"user_id"`
|
||||||
CreateTime time.Time `bson:"create_time"`
|
CreateTime time.Time `bson:"create_time"`
|
||||||
Url string `bson:"url"`
|
Url string `bson:"url"`
|
||||||
FileName string `bson:"file_name"`
|
FileName string `bson:"file_name"`
|
||||||
SystemType string `bson:"system_type"`
|
SystemType string `bson:"system_type"`
|
||||||
Version string `bson:"version"`
|
AppFramework string `bson:"app_framework"`
|
||||||
Ex string `bson:"ex"`
|
Version string `bson:"version"`
|
||||||
|
Ex string `bson:"ex"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func subscriberRedisDeleteCache(ctx context.Context, client redis.UniversalClient, channel string, del func(ctx context.Context, key ...string)) {
|
func subscriberRedisDeleteCache(ctx context.Context, client redis.UniversalClient, channel string, del func(ctx context.Context, key ...string)) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
log.ZPanic(ctx, "subscriberRedisDeleteCache Panic", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
for message := range client.Subscribe(ctx, channel).Channel() {
|
for message := range client.Subscribe(ctx, channel).Channel() {
|
||||||
log.ZDebug(ctx, "subscriberRedisDeleteCache", "channel", channel, "payload", message.Payload)
|
log.ZDebug(ctx, "subscriberRedisDeleteCache", "channel", channel, "payload", message.Payload)
|
||||||
var keys []string
|
var keys []string
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ package rpccache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
|
||||||
"github.com/openimsdk/open-im-server/v3/pkg/localcache"
|
"github.com/openimsdk/open-im-server/v3/pkg/localcache"
|
||||||
@@ -97,6 +98,7 @@ func (u *UserLocalCache) GetUsersInfo(ctx context.Context, userIDs []string) ([]
|
|||||||
user, err := u.GetUserInfo(ctx, userID)
|
user, err := u.GetUserInfo(ctx, userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errs.ErrRecordNotFound.Is(err) {
|
if errs.ErrRecordNotFound.Is(err) {
|
||||||
|
log.ZWarn(ctx, "User info notFound", err, "userID", userID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+16
-5
@@ -16,8 +16,8 @@ package rpcclient
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/openimsdk/protocol/auth"
|
"github.com/openimsdk/protocol/auth"
|
||||||
pbAuth "github.com/openimsdk/protocol/auth"
|
|
||||||
"github.com/openimsdk/tools/discovery"
|
"github.com/openimsdk/tools/discovery"
|
||||||
"github.com/openimsdk/tools/system/program"
|
"github.com/openimsdk/tools/system/program"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -38,8 +38,8 @@ type Auth struct {
|
|||||||
discov discovery.SvcDiscoveryRegistry
|
discov discovery.SvcDiscoveryRegistry
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) ParseToken(ctx context.Context, token string) (*pbAuth.ParseTokenResp, error) {
|
func (a *Auth) ParseToken(ctx context.Context, token string) (*auth.ParseTokenResp, error) {
|
||||||
req := pbAuth.ParseTokenReq{
|
req := auth.ParseTokenReq{
|
||||||
Token: token,
|
Token: token,
|
||||||
}
|
}
|
||||||
resp, err := a.Client.ParseToken(ctx, &req)
|
resp, err := a.Client.ParseToken(ctx, &req)
|
||||||
@@ -49,8 +49,8 @@ func (a *Auth) ParseToken(ctx context.Context, token string) (*pbAuth.ParseToken
|
|||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Auth) InvalidateToken(ctx context.Context, preservedToken, userID string, platformID int) (*pbAuth.InvalidateTokenResp, error) {
|
func (a *Auth) InvalidateToken(ctx context.Context, preservedToken, userID string, platformID int) (*auth.InvalidateTokenResp, error) {
|
||||||
req := pbAuth.InvalidateTokenReq{
|
req := auth.InvalidateTokenReq{
|
||||||
PreservedToken: preservedToken,
|
PreservedToken: preservedToken,
|
||||||
UserID: userID,
|
UserID: userID,
|
||||||
PlatformID: int32(platformID),
|
PlatformID: int32(platformID),
|
||||||
@@ -61,3 +61,14 @@ func (a *Auth) InvalidateToken(ctx context.Context, preservedToken, userID strin
|
|||||||
}
|
}
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *Auth) KickTokens(ctx context.Context, tokens []string) (*auth.KickTokensResp, error) {
|
||||||
|
req := auth.KickTokensReq{
|
||||||
|
Tokens: tokens,
|
||||||
|
}
|
||||||
|
resp, err := a.Client.KickTokens(ctx, &req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|||||||
@@ -152,8 +152,8 @@ func (c *ConversationRpcClient) GetConversationNotReceiveMessageUserIDs(ctx cont
|
|||||||
return resp.UserIDs, nil
|
return resp.UserIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ConversationRpcClient) GetConversationsNeedDestructMsgs(ctx context.Context) ([]*pbconversation.Conversation, error) {
|
func (c *ConversationRpcClient) GetConversationsNeedClearMsg(ctx context.Context) ([]*pbconversation.Conversation, error) {
|
||||||
resp, err := c.Client.GetConversationsNeedDestructMsgs(ctx, &pbconversation.GetConversationsNeedDestructMsgsReq{})
|
resp, err := c.Client.GetConversationsNeedClearMsg(ctx, &pbconversation.GetConversationsNeedClearMsgReq{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -244,8 +244,8 @@ func (m *MessageRpcClient) GetConversationMaxSeq(ctx context.Context, conversati
|
|||||||
return resp.MaxSeq, nil
|
return resp.MaxSeq, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MessageRpcClient) ClearMsg(ctx context.Context, ts int64) error {
|
func (m *MessageRpcClient) DestructMsgs(ctx context.Context, ts int64) error {
|
||||||
_, err := m.Client.ClearMsg(ctx, &msg.ClearMsgReq{Timestamp: ts})
|
_, err := m.Client.DestructMsgs(ctx, &msg.DestructMsgsReq{Timestamp: ts})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,55 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
# Wait for Kafka to be ready
|
|
||||||
|
|
||||||
KAFKA_SERVER=localhost:9092
|
|
||||||
|
|
||||||
MAX_ATTEMPTS=300
|
|
||||||
attempt_num=1
|
|
||||||
|
|
||||||
echo "Waiting for Kafka to be ready..."
|
|
||||||
|
|
||||||
until /opt/bitnami/kafka/bin/kafka-topics.sh --list --bootstrap-server $KAFKA_SERVER; do
|
|
||||||
echo "Attempt $attempt_num of $MAX_ATTEMPTS: Kafka not ready yet..."
|
|
||||||
if [ $attempt_num -eq $MAX_ATTEMPTS ]; then
|
|
||||||
echo "Kafka not ready after $MAX_ATTEMPTS attempts, exiting"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
attempt_num=$((attempt_num+1))
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "Kafka is ready. Creating topics..."
|
|
||||||
|
|
||||||
|
|
||||||
topics=("toRedis" "toMongo" "toPush" "toOfflinePush")
|
|
||||||
partitions=8
|
|
||||||
replicationFactor=1
|
|
||||||
|
|
||||||
for topic in "${topics[@]}"; do
|
|
||||||
if /opt/bitnami/kafka/bin/kafka-topics.sh --create \
|
|
||||||
--bootstrap-server $KAFKA_SERVER \
|
|
||||||
--replication-factor $replicationFactor \
|
|
||||||
--partitions $partitions \
|
|
||||||
--topic $topic
|
|
||||||
then
|
|
||||||
echo "Topic $topic created."
|
|
||||||
else
|
|
||||||
echo "Failed to create topic $topic."
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
echo "All topics created."
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
# Copyright © 2023 OpenIM. All rights reserved.
|
|
||||||
#
|
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
# you may not use this file except in compliance with the License.
|
|
||||||
# You may obtain a copy of the License at
|
|
||||||
#
|
|
||||||
# http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
#
|
|
||||||
# Unless required by applicable law or agreed to in writing, software
|
|
||||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
# See the License for the specific language governing permissions and
|
|
||||||
# limitations under the License.
|
|
||||||
mongosh <<EOF
|
|
||||||
var maxRetries = 300;
|
|
||||||
var connected = false;
|
|
||||||
var rootUsername = '$MONGO_INITDB_ROOT_USERNAME';
|
|
||||||
var rootPassword = '$MONGO_INITDB_ROOT_PASSWORD';
|
|
||||||
var dbName = '$MONGO_INITDB_DATABASE';
|
|
||||||
var openimUsername = '$MONGO_OPENIM_USERNAME';
|
|
||||||
var openimPassword = '$MONGO_OPENIM_PASSWORD';
|
|
||||||
|
|
||||||
while (!connected && maxRetries > 0) {
|
|
||||||
try {
|
|
||||||
db = connect('mongodb://127.0.0.1:27017/admin');
|
|
||||||
var authResult = db.auth(rootUsername, rootPassword);
|
|
||||||
if (authResult) {
|
|
||||||
print('Authentication successful for root user: ' + rootUsername);
|
|
||||||
connected = true;
|
|
||||||
} else {
|
|
||||||
print('Authentication failed for root user: ' + rootUsername + ' with password: ' + rootPassword);
|
|
||||||
quit(1);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
maxRetries--;
|
|
||||||
print('Connection failed, retrying... Remaining attempts: ' + maxRetries);
|
|
||||||
sleep(1000); // Sleep for 1 second
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connected) {
|
|
||||||
db = db.getSiblingDB(dbName);
|
|
||||||
var createUserResult = db.createUser({
|
|
||||||
user: openimUsername,
|
|
||||||
pwd: openimPassword,
|
|
||||||
roles: [{
|
|
||||||
role: 'readWrite',
|
|
||||||
db: dbName
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (createUserResult.ok == 1) {
|
|
||||||
print('User creation successful. User: ' + openimUsername + ', Database: ' + dbName);
|
|
||||||
} else {
|
|
||||||
print('User creation failed for user: ' + openimUsername + ' in database: ' + dbName);
|
|
||||||
quit(1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
print('Failed to connect to MongoDB after 300 retries.');
|
|
||||||
quit(1);
|
|
||||||
}
|
|
||||||
EOF
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,198 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// You can specify a tag as a command line argument to generate the changelog for a specific version.
|
||||||
|
// Example: go run tools/changelog/changelog.go v0.0.33
|
||||||
|
// If no tag is provided, the latest release will be used.
|
||||||
|
|
||||||
|
// Setting repo owner and repo name by generate changelog
|
||||||
|
const (
|
||||||
|
repoOwner = "openimsdk"
|
||||||
|
repoName = "open-im-server"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GitHubRepo struct represents the repo details.
|
||||||
|
type GitHubRepo struct {
|
||||||
|
Owner string
|
||||||
|
Repo string
|
||||||
|
FullChangelog string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseData represents the JSON structure for release data.
|
||||||
|
type ReleaseData struct {
|
||||||
|
TagName string `json:"tag_name"`
|
||||||
|
Body string `json:"body"`
|
||||||
|
HtmlUrl string `json:"html_url"`
|
||||||
|
Published string `json:"published_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to classify and format release notes.
|
||||||
|
func (g *GitHubRepo) classifyReleaseNotes(body string) map[string][]string {
|
||||||
|
result := map[string][]string{
|
||||||
|
"feat": {},
|
||||||
|
"fix": {},
|
||||||
|
"chore": {},
|
||||||
|
"refactor": {},
|
||||||
|
"build": {},
|
||||||
|
"other": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regular expression to extract PR number and URL (case insensitive)
|
||||||
|
rePR := regexp.MustCompile(`(?i)in (https://github\.com/[^\s]+/pull/(\d+))`)
|
||||||
|
|
||||||
|
// Split the body into individual lines.
|
||||||
|
lines := strings.Split(body, "\n")
|
||||||
|
|
||||||
|
for _, line := range lines {
|
||||||
|
// Skip lines that contain "deps: Merge"
|
||||||
|
if strings.Contains(strings.ToLower(line), "deps: merge #") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use a regular expression to extract Full Changelog link and its title (case insensitive).
|
||||||
|
if strings.Contains(strings.ToLower(line), "**full changelog**") {
|
||||||
|
matches := regexp.MustCompile(`(?i)\*\*full changelog\*\*: (https://github\.com/[^\s]+/compare/([^\s]+))`).FindStringSubmatch(line)
|
||||||
|
if len(matches) > 2 {
|
||||||
|
// Format the Full Changelog link with title
|
||||||
|
g.FullChangelog = fmt.Sprintf("[%s](%s)", matches[2], matches[1])
|
||||||
|
}
|
||||||
|
continue // Skip further processing for this line.
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.HasPrefix(line, "*") {
|
||||||
|
var category string
|
||||||
|
|
||||||
|
// Use strings.ToLower to make the matching case insensitive
|
||||||
|
lowerLine := strings.ToLower(line)
|
||||||
|
|
||||||
|
// Determine the category based on the prefix (case insensitive).
|
||||||
|
if strings.HasPrefix(lowerLine, "* feat") {
|
||||||
|
category = "feat"
|
||||||
|
} else if strings.HasPrefix(lowerLine, "* fix") {
|
||||||
|
category = "fix"
|
||||||
|
} else if strings.HasPrefix(lowerLine, "* chore") {
|
||||||
|
category = "chore"
|
||||||
|
} else if strings.HasPrefix(lowerLine, "* refactor") {
|
||||||
|
category = "refactor"
|
||||||
|
} else if strings.HasPrefix(lowerLine, "* build") {
|
||||||
|
category = "build"
|
||||||
|
} else {
|
||||||
|
category = "other"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract PR number and URL (case insensitive)
|
||||||
|
matches := rePR.FindStringSubmatch(line)
|
||||||
|
if len(matches) == 3 {
|
||||||
|
prURL := matches[1]
|
||||||
|
prNumber := matches[2]
|
||||||
|
// Format the line with the PR link and use original content for the final result
|
||||||
|
formattedLine := fmt.Sprintf("* %s [#%s](%s)", strings.Split(line, " by ")[0][2:], prNumber, prURL)
|
||||||
|
result[category] = append(result[category], formattedLine)
|
||||||
|
} else {
|
||||||
|
// If no PR link is found, just add the line as is
|
||||||
|
result[category] = append(result[category], line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to generate the final changelog.
|
||||||
|
func (g *GitHubRepo) generateChangelog(tag, date, htmlURL, body string) string {
|
||||||
|
sections := g.classifyReleaseNotes(body)
|
||||||
|
|
||||||
|
// Convert ISO 8601 date to simpler format (YYYY-MM-DD)
|
||||||
|
formattedDate := date[:10]
|
||||||
|
|
||||||
|
// Changelog header with tag, date, and links.
|
||||||
|
changelog := fmt.Sprintf("## [%s](%s) \t(%s)\n\n", tag, htmlURL, formattedDate)
|
||||||
|
|
||||||
|
if len(sections["feat"]) > 0 {
|
||||||
|
changelog += "### New Features\n" + strings.Join(sections["feat"], "\n") + "\n\n"
|
||||||
|
}
|
||||||
|
if len(sections["fix"]) > 0 {
|
||||||
|
changelog += "### Bug Fixes\n" + strings.Join(sections["fix"], "\n") + "\n\n"
|
||||||
|
}
|
||||||
|
if len(sections["chore"]) > 0 {
|
||||||
|
changelog += "### Chores\n" + strings.Join(sections["chore"], "\n") + "\n\n"
|
||||||
|
}
|
||||||
|
if len(sections["refactor"]) > 0 {
|
||||||
|
changelog += "### Refactors\n" + strings.Join(sections["refactor"], "\n") + "\n\n"
|
||||||
|
}
|
||||||
|
if len(sections["build"]) > 0 {
|
||||||
|
changelog += "### Builds\n" + strings.Join(sections["build"], "\n") + "\n\n"
|
||||||
|
}
|
||||||
|
if len(sections["other"]) > 0 {
|
||||||
|
changelog += "### Others\n" + strings.Join(sections["other"], "\n") + "\n\n"
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.FullChangelog != "" {
|
||||||
|
changelog += fmt.Sprintf("**Full Changelog**: %s\n", g.FullChangelog)
|
||||||
|
}
|
||||||
|
|
||||||
|
return changelog
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method to fetch release data from GitHub API.
|
||||||
|
func (g *GitHubRepo) fetchReleaseData(version string) (*ReleaseData, error) {
|
||||||
|
var apiURL string
|
||||||
|
|
||||||
|
if version == "" {
|
||||||
|
// Fetch the latest release.
|
||||||
|
apiURL = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", g.Owner, g.Repo)
|
||||||
|
} else {
|
||||||
|
// Fetch a specific version.
|
||||||
|
apiURL = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", g.Owner, g.Repo, version)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := http.Get(apiURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var releaseData ReleaseData
|
||||||
|
err = json.Unmarshal(body, &releaseData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &releaseData, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
repo := &GitHubRepo{Owner: repoOwner, Repo: repoName}
|
||||||
|
|
||||||
|
// Get the version from command line arguments, if provided
|
||||||
|
var version string // Default is use latest
|
||||||
|
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
version = os.Args[1] // Use the provided version
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch release data (either for latest or specific version)
|
||||||
|
releaseData, err := repo.fetchReleaseData(version)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Error fetching release data:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate and print the formatted changelog
|
||||||
|
changelog := repo.generateChangelog(releaseData.TagName, releaseData.Published, releaseData.HtmlUrl, releaseData.Body)
|
||||||
|
fmt.Println(changelog)
|
||||||
|
}
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
// Copyright © 2023 OpenIM. All rights reserved.
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"regexp"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
mergeRequest = regexp.MustCompile(`Merge pull request #([\d]+)`)
|
|
||||||
webconsoleBump = regexp.MustCompile(regexp.QuoteMeta("bump(github.com/openshift/origin-web-console): ") + `([\w]+)`)
|
|
||||||
upstreamKube = regexp.MustCompile(`^UPSTREAM: (\d+)+:(.+)`)
|
|
||||||
upstreamRepo = regexp.MustCompile(`^UPSTREAM: ([\w/-]+): (\d+)+:(.+)`)
|
|
||||||
prefix = regexp.MustCompile(`^[\w-]: `)
|
|
||||||
|
|
||||||
assignments = []prefixAssignment{
|
|
||||||
{"cluster up", "cluster"},
|
|
||||||
{" pv ", "storage"},
|
|
||||||
{"haproxy", "router"},
|
|
||||||
{"router", "router"},
|
|
||||||
{"route", "route"},
|
|
||||||
{"authoriz", "auth"},
|
|
||||||
{"rbac", "auth"},
|
|
||||||
{"authent", "auth"},
|
|
||||||
{"reconcil", "auth"},
|
|
||||||
{"auth", "auth"},
|
|
||||||
{"role", "auth"},
|
|
||||||
{" dc ", "deploy"},
|
|
||||||
{"deployment", "deploy"},
|
|
||||||
{"rolling", "deploy"},
|
|
||||||
{"security context constr", "security"},
|
|
||||||
{"scc", "security"},
|
|
||||||
{"pipeline", "build"},
|
|
||||||
{"build", "build"},
|
|
||||||
{"registry", "registry"},
|
|
||||||
{"registries", "image"},
|
|
||||||
{"image", "image"},
|
|
||||||
{" arp ", "network"},
|
|
||||||
{" cni ", "network"},
|
|
||||||
{"egress", "network"},
|
|
||||||
{"network", "network"},
|
|
||||||
{"oc ", "cli"},
|
|
||||||
{"template", "template"},
|
|
||||||
{"etcd", "server"},
|
|
||||||
{"pod", "node"},
|
|
||||||
{"scripts/", "hack"},
|
|
||||||
{"e2e", "test"},
|
|
||||||
{"integration", "test"},
|
|
||||||
{"cluster", "cluster"},
|
|
||||||
{"master", "server"},
|
|
||||||
{"packages", "hack"},
|
|
||||||
{"api", "server"},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type prefixAssignment struct {
|
|
||||||
term string
|
|
||||||
prefix string
|
|
||||||
}
|
|
||||||
|
|
||||||
type commit struct {
|
|
||||||
short string
|
|
||||||
parents []string
|
|
||||||
message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func contains(arr []string, value string) bool {
|
|
||||||
for _, s := range arr {
|
|
||||||
if s == value {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
if len(os.Args) != 3 {
|
|
||||||
log.Fatalf("Must specify two arguments, FROM and TO")
|
|
||||||
}
|
|
||||||
from := os.Args[1]
|
|
||||||
to := os.Args[2]
|
|
||||||
|
|
||||||
out, err := exec.Command("git", "log", "--topo-order", "--pretty=tformat:%h %p|%s", "--reverse", fmt.Sprintf("%s..%s", from, to)).CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hide := make(map[string]struct{})
|
|
||||||
var apiChanges []string
|
|
||||||
var webconsole []string
|
|
||||||
var commits []commit
|
|
||||||
var upstreams []commit
|
|
||||||
var bumps []commit
|
|
||||||
for _, line := range strings.Split(string(out), "\n") {
|
|
||||||
if len(strings.TrimSpace(line)) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
parts := strings.SplitN(line, "|", 2)
|
|
||||||
hashes := strings.Split(parts[0], " ")
|
|
||||||
c := commit{short: hashes[0], parents: hashes[1:], message: parts[1]}
|
|
||||||
|
|
||||||
if strings.HasPrefix(c.message, "UPSTREAM: ") {
|
|
||||||
hide[c.short] = struct{}{}
|
|
||||||
upstreams = append(upstreams, c)
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(c.message, "bump(") {
|
|
||||||
hide[c.short] = struct{}{}
|
|
||||||
bumps = append(bumps, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.parents) == 1 {
|
|
||||||
commits = append(commits, c)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := mergeRequest.FindStringSubmatch(line)
|
|
||||||
if len(matches) == 0 {
|
|
||||||
// this may have been a human pressing the merge button, we'll just record this as a direct push
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// split the accumulated commits into any that are force merges (assumed to be the initial set due
|
|
||||||
// to --topo-order) from the PR commits as soon as we see any of our merge parents. Then print
|
|
||||||
// any of the force merges
|
|
||||||
var first int
|
|
||||||
for i := range commits {
|
|
||||||
first = i
|
|
||||||
if contains(c.parents, commits[i].short) {
|
|
||||||
first++
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
individual := commits[:first]
|
|
||||||
merged := commits[first:]
|
|
||||||
for _, commit := range individual {
|
|
||||||
if len(commit.parents) > 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if _, ok := hide[commit.short]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Printf("force-merge: %s %s\n", commit.message, commit.short)
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to find either the PR title or the first commit title from the merge commit
|
|
||||||
out, err := exec.Command("git", "show", "--pretty=tformat:%b", c.short).CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
var message string
|
|
||||||
para := strings.Split(string(out), "\n\n")
|
|
||||||
if len(para) > 0 && strings.HasPrefix(para[0], "Automatic merge from submit-queue") {
|
|
||||||
para = para[1:]
|
|
||||||
}
|
|
||||||
// this is no longer necessary with the submit queue in place
|
|
||||||
if len(para) > 0 && strings.HasPrefix(para[0], "Merged by ") {
|
|
||||||
para = para[1:]
|
|
||||||
}
|
|
||||||
// post submit-queue, the merge bot will add the PR title, which is usually pretty good
|
|
||||||
if len(para) > 0 {
|
|
||||||
message = strings.Split(para[0], "\n")[0]
|
|
||||||
}
|
|
||||||
if len(message) == 0 && len(merged) > 0 {
|
|
||||||
message = merged[0].message
|
|
||||||
}
|
|
||||||
if len(message) > 0 && len(merged) == 1 && message == merged[0].message {
|
|
||||||
merged = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to calculate a prefix based on the diff
|
|
||||||
if len(message) > 0 && !prefix.MatchString(message) {
|
|
||||||
prefix, ok := findPrefixFor(message, merged)
|
|
||||||
if ok {
|
|
||||||
message = prefix + ": " + message
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// github merge
|
|
||||||
|
|
||||||
// has api changes
|
|
||||||
display := fmt.Sprintf("%s [\\#%s](https://github.com/openimsdk/Open-IM-Server/pull/%s)", message, matches[1], matches[1])
|
|
||||||
if hasFileChanges(c.short, "pkg/apistruct/") {
|
|
||||||
apiChanges = append(apiChanges, display)
|
|
||||||
}
|
|
||||||
|
|
||||||
var filtered []commit
|
|
||||||
for _, commit := range merged {
|
|
||||||
if _, ok := hide[commit.short]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
filtered = append(filtered, commit)
|
|
||||||
}
|
|
||||||
if len(filtered) > 0 {
|
|
||||||
fmt.Printf("- %s\n", display)
|
|
||||||
for _, commit := range filtered {
|
|
||||||
fmt.Printf(" - %s (%s)\n", commit.message, commit.short)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// stick the merge commit in at the beginning of the next list so we can anchor the previous parent
|
|
||||||
commits = []commit{c}
|
|
||||||
}
|
|
||||||
|
|
||||||
// chunk the bumps
|
|
||||||
var lines []string
|
|
||||||
for _, commit := range bumps {
|
|
||||||
if m := webconsoleBump.FindStringSubmatch(commit.message); len(m) > 0 {
|
|
||||||
webconsole = append(webconsole, m[1])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lines = append(lines, commit.message)
|
|
||||||
}
|
|
||||||
lines = sortAndUniq(lines)
|
|
||||||
for _, line := range lines {
|
|
||||||
fmt.Printf("- %s\n", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
// chunk the upstreams
|
|
||||||
lines = nil
|
|
||||||
for _, commit := range upstreams {
|
|
||||||
lines = append(lines, commit.message)
|
|
||||||
}
|
|
||||||
lines = sortAndUniq(lines)
|
|
||||||
for _, line := range lines {
|
|
||||||
fmt.Printf("- %s\n", upstreamLinkify(line))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(webconsole) > 0 {
|
|
||||||
fmt.Printf("- web: from %s^..%s\n", webconsole[0], webconsole[len(webconsole)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, apiChange := range apiChanges {
|
|
||||||
fmt.Printf(" - %s\n", apiChange)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findPrefixFor(message string, commits []commit) (string, bool) {
|
|
||||||
message = strings.ToLower(message)
|
|
||||||
for _, m := range assignments {
|
|
||||||
if strings.Contains(message, m.term) {
|
|
||||||
return m.prefix, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, c := range commits {
|
|
||||||
if prefix, ok := findPrefixFor(c.message, nil); ok {
|
|
||||||
return prefix, ok
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "", false
|
|
||||||
}
|
|
||||||
|
|
||||||
func hasFileChanges(commit string, prefixes ...string) bool {
|
|
||||||
out, err := exec.Command("git", "diff", "--name-only", fmt.Sprintf("%s^..%s", commit, commit)).CombinedOutput()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, file := range strings.Split(string(out), "\n") {
|
|
||||||
for _, prefix := range prefixes {
|
|
||||||
if strings.HasPrefix(file, prefix) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func sortAndUniq(lines []string) []string {
|
|
||||||
sort.Strings(lines)
|
|
||||||
out := make([]string, 0, len(lines))
|
|
||||||
last := ""
|
|
||||||
for _, s := range lines {
|
|
||||||
if last == s {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
last = s
|
|
||||||
out = append(out, s)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func upstreamLinkify(line string) string {
|
|
||||||
if m := upstreamKube.FindStringSubmatch(line); len(m) > 0 {
|
|
||||||
return fmt.Sprintf("UPSTREAM: [#%s](https://github.com/openimsdk/open-im-server/pull/%s):%s", m[1], m[1], m[2])
|
|
||||||
}
|
|
||||||
if m := upstreamRepo.FindStringSubmatch(line); len(m) > 0 {
|
|
||||||
return fmt.Sprintf("UPSTREAM: [%s#%s](https://github.com/%s/pull/%s):%s", m[1], m[2], m[1], m[2], m[3])
|
|
||||||
}
|
|
||||||
return line
|
|
||||||
}
|
|
||||||
@@ -66,7 +66,7 @@ func CheckMinIO(ctx context.Context, config *config.Minio) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CheckKafka(ctx context.Context, conf *config.Kafka) error {
|
func CheckKafka(ctx context.Context, conf *config.Kafka) error {
|
||||||
return kafka.Check(ctx, conf.Build(), []string{conf.ToMongoTopic, conf.ToRedisTopic, conf.ToPushTopic, conf.ToOfflinePushTopic})
|
return kafka.CheckHealth(ctx, conf.Build())
|
||||||
}
|
}
|
||||||
|
|
||||||
func initConfig(configDir string) (*config.Mongo, *config.Redis, *config.Kafka, *config.Minio, *config.Discovery, error) {
|
func initConfig(configDir string) (*config.Mongo, *config.Redis, *config.Kafka, *config.Minio, *config.Discovery, error) {
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
3.8.1
|
v3.8.3-alpha.1
|
||||||
|
|||||||
Reference in New Issue
Block a user