parent
e8ccae6349
commit
88b8043224
@ -0,0 +1,23 @@ |
||||
# Copyright © 2023 OpenIM SDK. 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. |
||||
|
||||
coverage: |
||||
status: |
||||
project: |
||||
default: false # disable the default status that measures entire project |
||||
pkg: # declare a new status context "pkg" |
||||
paths: |
||||
- pkg/* # only include coverage in "pkg/" folder |
||||
informational: true # Always pass check |
||||
patch: off # disable the commit only checks |
@ -0,0 +1,43 @@ |
||||
# Copyright © 2023 OpenIM SDK. 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. |
||||
|
||||
# Refer to Kubernetes for size/* Settings |
||||
# https://github.com/Kubernetes/Kubernetes |
||||
XS: |
||||
name: size/XS |
||||
lines: 0 |
||||
color: 3CBF00 |
||||
S: |
||||
name: size/S |
||||
lines: 10 |
||||
color: 5D9801 |
||||
M: |
||||
name: size/M |
||||
lines: 30 |
||||
color: 7F7203 |
||||
L: |
||||
name: size/L |
||||
lines: 100 |
||||
color: A14C05 |
||||
XL: |
||||
name: size/XL |
||||
lines: 500 |
||||
color: C32607 |
||||
XXL: |
||||
name: size/XXL |
||||
lines: 1000 |
||||
color: E50009 |
||||
comment: | |
||||
# Whoa! Easy there, Partner! |
||||
This PR is too big. Please break it up into smaller PRs. |
@ -0,0 +1,53 @@ |
||||
# Copyright © 2023 OpenIM SDK. 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. |
||||
|
||||
name: Invite users to join our group |
||||
on: |
||||
issue_comment: |
||||
types: |
||||
- created |
||||
jobs: |
||||
issue_comment: |
||||
name: Invite users to join our group |
||||
if: ${{ github.event.comment.body == '/invite' || github.event.comment.body == '/close' || github.event.comment.body == '/comment' }} |
||||
runs-on: ubuntu-latest |
||||
permissions: |
||||
issues: write |
||||
steps: |
||||
|
||||
- name: Invite user to join our group |
||||
uses: peter-evans/create-or-update-comment@v1 |
||||
with: |
||||
token: ${{ secrets.BOT_GITHUB_TOKEN }} |
||||
issue-number: ${{ github.event.issue.number }} |
||||
body: | |
||||
We value close connections with our users, developers, and contributors here at Open-IM-Server. With a large community and maintainer team, we're always here to help and support you. Whether you're looking to join our community or have any questions or suggestions, we welcome you to get in touch with us. |
||||
|
||||
Our most recommended /root/workspaces/openim/Open-IM-Server/docs copyway to get in touch is through [Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg). Even if you're in China, Slack is usually not blocked by firewalls, making it an easy way to connect with us. Our Slack community is the ideal place to discuss and share ideas and suggestions with other users and developers of Open-IM-Server. You can ask technical questions, seek help, or share your experiences with other users of Open-IM-Server. |
||||
|
||||
In addition to Slack, we also offer the following ways to get in touch: |
||||
|
||||
+ <a href="https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg" target="_blank"><img src="https://img.shields.io/badge/Slack-OpenIM%2B-blueviolet?logo=slack&logoColor=white"></a> We also have Slack channels for you to communicate and discuss. To join, visit https://slack.com/ and join our [👀 Open-IM-Server slack](https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg) team channel. |
||||
+ <a href="https://mail.google.com/mail/u/0/?fs=1&tf=cm&to=winxu81@gmail.com" target="_blank"><img src="https://img.shields.io/badge/gmail-%40OOpenIMSDKCore?style=social&logo=gmail"></a> Get in touch with us on [Gmail](https://mail.google.com/mail/u/0/?fs=1&tf=cm&to=winxu81@gmail.com). If you have any questions or issues that need resolving, or any suggestions and feedback for our open source projects, please feel free to contact us via email. |
||||
+ <a href="https://doc.rentsoft.cn/" target="_blank"><img src="https://img.shields.io/badge/%E5%8D%9A%E5%AE%A2-%40OpenIMSDKCore-blue?style=social&logo=Octopus%20Deploy"></a> Read our [blog](https://doc.rentsoft.cn/). Our blog is a great place to stay up-to-date with Open-IM-Server projects and trends. On the blog, we share our latest developments, tech trends, and other interesting information. |
||||
+ <a href="https://github.com/openimsdk/OpenIM-Docs/blob/main/docs/images/WechatIMG20.jpeg" target="_blank"><img src="https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1-OpenIMSDKCore-brightgreen?logo=wechat&style=flat-square"></a> Add [Wechat](https://github.com/openimsdk/OpenIM-Docs/blob/main/docs/images/WechatIMG20.jpeg) and indicate that you are a user or developer of Open-IM-Server. We will process your request as soon as possible. |
||||
|
||||
- name: Close Issue |
||||
uses: peter-evans/close-issue@v3 |
||||
with: |
||||
token: ${{ secrets.BOT_GITHUB_TOKEN }} |
||||
issue-number: ${{ github.event.issue.number }} |
||||
comment: 🤖 Auto-closing issue, if you still need help please reopen the issue or ask for help in the community above |
||||
labels: | |
||||
triage/accepted |
@ -0,0 +1,60 @@ |
||||
# Copyright © 2023 OpenIM SDK. 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. |
||||
|
||||
# name: Check-Coverage |
||||
|
||||
|
||||
# on: |
||||
# workflow_dispatch: |
||||
# push: |
||||
# branches: [ "main" ] |
||||
# paths-ignore: |
||||
# - "docs/**" |
||||
# - "**/*.md" |
||||
# - "**/*.yaml" |
||||
# - "CONTRIBUTORS" |
||||
# - "CHANGELOG/**" |
||||
# pull_request: |
||||
# branches: [ "*" ] |
||||
# paths-ignore: |
||||
# - "docs/**" |
||||
# - "**/*.md" |
||||
# - "**/*.yaml" |
||||
# - "CONTRIBUTORS" |
||||
# - "CHANGELOG/**" |
||||
# env: |
||||
# # Common versions |
||||
# GO_VERSION: "1.20" |
||||
|
||||
# jobs: |
||||
# coverage: |
||||
# runs-on: ubuntu-20.04 |
||||
# steps: |
||||
# - name: Checkout |
||||
# uses: actions/checkout@v3 |
||||
|
||||
# - name: Setup Golang with cache |
||||
# uses: magnetikonline/action-golang-cache@v3 |
||||
# with: |
||||
# go-version: ${{ env.GO_VERSION }} |
||||
# token: ${{ secrets.BOT_GITHUB_TOKEN }} |
||||
|
||||
# - name: Install Dependencies |
||||
# run: sudo apt update && sudo apt install -y libgpgme-dev libbtrfs-dev libdevmapper-dev |
||||
|
||||
# - name: Run Cover |
||||
# run: make cover |
||||
|
||||
# - name: Upload Coverage to Codecov |
||||
# uses: codecov/codecov-action@v3 |
@ -0,0 +1,15 @@ |
||||
# Copyright © 2023 OpenIM SDK. 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. |
||||
|
||||
# |
@ -0,0 +1,44 @@ |
||||
# Copyright © 2023 OpenIM SDK. 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. |
||||
|
||||
# name: Run gosec |
||||
|
||||
# # gosec is a source code security audit tool for the Go language. It performs a static |
||||
# # analysis of the Go code, looking for potential security problems. The main functions of gosec are: |
||||
# # 1. Find common security vulnerabilities, such as SQL injection, command injection, and cross-site scripting (XSS). |
||||
# # 2. Audit codes according to common security standards and find non-standard codes. |
||||
# # 3. Assist the Go language engineer to write safe and reliable code. |
||||
|
||||
# on: |
||||
# push: |
||||
# branches: "*" |
||||
# pull_request: |
||||
# branches: "*" |
||||
# paths-ignore: |
||||
# - '*.md' |
||||
# - '*.yml' |
||||
# - '.github' |
||||
|
||||
# jobs: |
||||
# golang-security-action: |
||||
# runs-on: ubuntu-latest |
||||
# env: |
||||
# GO111MODULE: on |
||||
# steps: |
||||
# - name: Check out code |
||||
# uses: actions/checkout@v3 |
||||
# - name: Run Gosec Security Scanner |
||||
# uses: securego/gosec@master |
||||
# with: |
||||
# args: ./... |
@ -0,0 +1,31 @@ |
||||
# Copyright © 2023 OpenIM SDK. 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. |
||||
|
||||
name: 'issue translator' |
||||
on: |
||||
issue_comment: |
||||
types: [created] |
||||
issues: |
||||
types: [opened] |
||||
|
||||
jobs: |
||||
build: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: usthe/issues-translate-action@v2.7 |
||||
with: |
||||
# it is not necessary to decide whether you need to modify the issue header content |
||||
IS_MODIFY_TITLE: true |
||||
BOT_GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} |
||||
# Required, input your bot github token |
@ -0,0 +1,61 @@ |
||||
# Copyright © 2023 OpenIM SDK. 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. |
||||
|
||||
# name: Github Rebot for Link check error |
||||
|
||||
# on: |
||||
# pull_request: |
||||
# branches: [ main ] |
||||
# paths: |
||||
# - '**.md' |
||||
# - 'docs/**' |
||||
# - '.lycheeignore' |
||||
# push: |
||||
# branches: [ main ] |
||||
|
||||
# schedule: |
||||
# - cron: '0 11 * * *' |
||||
|
||||
# jobs: |
||||
# linkChecker: |
||||
# runs-on: ubuntu-latest |
||||
# steps: |
||||
# - uses: actions/checkout@v3 |
||||
|
||||
# - name: Link Checker |
||||
# id: lychee |
||||
# uses: lycheeverse/lychee-action@v1.7.0 |
||||
# with: |
||||
# # For parameter description, see https://github.com/lycheeverse/lychee#commandline-parameters |
||||
# # Actions Link address -> https://github.com/lycheeverse/lychee-action |
||||
# # -E, --exclude-all-private Exclude all private IPs from checking. |
||||
# # -i, --insecure Proceed for server connections considered insecure (invalid TLS) |
||||
# # -n, --no-progress Do not show progress bar. |
||||
# # -t, --timeout <timeout> Website timeout in seconds from connect to response finished [default:20] |
||||
# # --max-concurrency <max-concurrency> Maximum number of concurrent network requests [default: 128] |
||||
# # -a --accept <accept> Comma-separated list of accepted status codes for valid links |
||||
# # docs/.vitepress/dist the site directory to check |
||||
# # ./*.md all markdown files in the root directory |
||||
# args: --verbose -E -i --no-progress --exclude-path './CHANGELOG' './**/*.md' |
||||
# env: |
||||
# GITHUB_TOKEN: ${{secrets.GH_PAT}} |
||||
|
||||
# - name: Create Issue From File |
||||
# if: env.lychee_exit_code != 0 |
||||
# uses: peter-evans/create-issue-from-file@v4 |
||||
# with: |
||||
# title: Bug reports for links in OpenIM docs |
||||
# content-filepath: ./lychee/out.md |
||||
# labels: kind/documentation, triage/unresolved, report |
||||
# token: ${{ secrets.BOT_GITHUB_TOKEN }} |
@ -0,0 +1,52 @@ |
||||
# Copyright © 2023 OpenIM open source community. All rights reserved. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
name: 'OpenIM Commit Action' |
||||
|
||||
on: |
||||
push: |
||||
branches: |
||||
- main |
||||
|
||||
jobs: |
||||
opencommit: |
||||
timeout-minutes: 10 |
||||
name: OpenCommit |
||||
runs-on: ubuntu-latest |
||||
permissions: write-all |
||||
steps: |
||||
- name: Setup Node.js Environment |
||||
uses: actions/setup-node@v2 |
||||
with: |
||||
node-version: '16' |
||||
- uses: actions/checkout@v3 |
||||
with: |
||||
fetch-depth: 0 |
||||
- uses: di-sukharev/opencommit@github-action-v1.0.4 |
||||
with: |
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
||||
|
||||
env: |
||||
# set openAI api key in repo actions secrets, |
||||
# for openAI keys go to: https://platform.openai.com/account/api-keys |
||||
# for repo secret go to: https://github.com/kuebcub/settings/secrets/actions |
||||
OCO_OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} |
||||
|
||||
# customization |
||||
OCO_OPENAI_MAX_TOKENS: 500 |
||||
OCO_OPENAI_BASE_PATH: '' |
||||
OCO_DESCRIPTION: false |
||||
OCO_EMOJI: false |
||||
OCO_MODEL: gpt-3.5-turbo |
||||
OCO_LANGUAGE: en |
@ -0,0 +1,144 @@ |
||||
# Copyright © 2023 OpenIM open source community. All rights reserved. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
name: OpenIM CI Aotu Build and Install |
||||
|
||||
on: |
||||
push: |
||||
branches: |
||||
- main |
||||
paths-ignore: |
||||
- "docs/**" |
||||
- "README.md" |
||||
- "README_zh-CN.md" |
||||
- "CONTRIBUTING.md" |
||||
pull_request: |
||||
branches: |
||||
- main |
||||
paths-ignore: |
||||
- "README.md" |
||||
- "README_zh-CN.md" |
||||
- "CONTRIBUTING.md" |
||||
- "docs/**" |
||||
|
||||
env: |
||||
GO_VERSION: "1.19" |
||||
GOLANGCI_VERSION: "v1.50.1" |
||||
|
||||
jobs: |
||||
openim: |
||||
name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }} |
||||
runs-on: ${{ matrix.os }} |
||||
permissions: |
||||
# Give the default GITHUB_TOKEN write permission to commit and push the changed files back to the repository. |
||||
contents: write |
||||
environment: |
||||
name: openim |
||||
|
||||
strategy: |
||||
matrix: |
||||
go_version: ["1.18","1.19","1.20","1.21"] |
||||
os: [ubuntu-latest] |
||||
|
||||
steps: |
||||
- name: Set up Go ${{ matrix.go_version }} |
||||
uses: actions/setup-go@v2 |
||||
with: |
||||
go-version: ${{ matrix.go_version }} |
||||
id: go |
||||
|
||||
- name: Check out code into the Go module directory |
||||
uses: actions/checkout@v3 |
||||
|
||||
- name: Install Task |
||||
uses: arduino/setup-task@v1 |
||||
with: |
||||
version: 2.x |
||||
|
||||
- name: Run go format |
||||
run: | |
||||
sudo make format |
||||
echo "Run go format successfully" |
||||
continue-on-error: true |
||||
|
||||
- name: Generate all necessary files, such as error code files |
||||
run: | |
||||
make generate |
||||
echo "Generate all necessary files successfully" |
||||
continue-on-error: true |
||||
|
||||
- name: Run unit test and get test coverage |
||||
run: | |
||||
make cover |
||||
echo "Run unit test and get test coverage successfully" |
||||
continue-on-error: true |
||||
|
||||
- name: Clean all build |
||||
run: | |
||||
sudo make clean |
||||
echo "Clean all build successfully" |
||||
|
||||
- name: Build source code for host platform |
||||
run: | |
||||
sudo make build |
||||
echo "Build source code for host platform successfully" |
||||
|
||||
- name: Build wasm source code |
||||
run: | |
||||
sudo make build-wasm |
||||
echo "Build wasm source code successfully" |
||||
|
||||
- name: OpenIM verify copyright |
||||
run: | |
||||
sudo make verify-copyright |
||||
sudo make add-copyright |
||||
echo "OpenIM verify successfully" |
||||
continue-on-error: true |
||||
|
||||
- name: push OpenIM |
||||
uses: stefanzweifel/git-auto-commit-action@v4 |
||||
with: |
||||
commit_message: "cicd: robot automated Change" |
||||
# commit_options: '--no-verify --signoff' |
||||
branch: main |
||||
# create_branch: true |
||||
# # Optional commit user and author settings |
||||
# commit_user_name: kubbot # defaults to "github-actions[bot]" |
||||
# commit_user_email: 3293172751ysy@gmail.com # defaults to "41898282+github-actions[bot]@users.noreply.github.com" |
||||
# commit_author: Kubbot # defaults to author of the commit that triggered the run |
||||
continue-on-error: true |
||||
|
||||
- name: Set Current Directory |
||||
id: set_directory |
||||
run: | |
||||
echo "::set-output name=directory::$(pwd)" |
||||
continue-on-error: true |
||||
|
||||
- name: Collect Test Coverage File |
||||
id: collect_coverage |
||||
run: | |
||||
cd ${{ steps.set_directory.outputs.directory }} |
||||
make cover |
||||
echo "::set-output name=coverage_file::./_output/tmp/coverage.out" |
||||
continue-on-error: true |
||||
|
||||
- name: Display Test Coverage |
||||
run: | |
||||
echo "Test Coverage:" |
||||
cat ${{ steps.collect_coverage.outputs.coverage_file }} |
||||
continue-on-error: true |
||||
|
||||
- name: Set up Docker Buildx |
||||
uses: docker/setup-buildx-action@v1 |
||||
continue-on-error: true |
@ -0,0 +1,82 @@ |
||||
# Copyright © 2023 OpenIM open source community. All rights reserved. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
name: OpenIM OpenIM Core release |
||||
|
||||
on: |
||||
push: |
||||
# run only against tags |
||||
tags: |
||||
- '*' |
||||
|
||||
permissions: |
||||
contents: write |
||||
packages: write |
||||
issues: write |
||||
|
||||
|
||||
jobs: |
||||
goreleaser: |
||||
runs-on: ubuntu-latest |
||||
steps: |
||||
- uses: actions/checkout@v3 |
||||
with: |
||||
fetch-depth: 0 |
||||
- run: git fetch --force --tags |
||||
- uses: actions/setup-go@v4 |
||||
with: |
||||
go-version: stable |
||||
# More assembly might be required: Docker logins, GPG, etc. It all depends |
||||
# on your needs. |
||||
- uses: goreleaser/goreleaser-action@v4 |
||||
with: |
||||
# either 'goreleaser' (default) or 'goreleaser-pro': |
||||
distribution: goreleaser |
||||
version: latest |
||||
workdir: . |
||||
args: release --clean --clean --release-footer-tmpl=scripts/template/footer.md.tmpl --release-header-tmpl=scripts/template/head.md.tmpl |
||||
env: |
||||
USERNAME: ${{ github.repository_owner }} |
||||
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} |
||||
FURY_TOKEN: ${{ secrets.FURY_TOKEN }} |
||||
# Your GoReleaser Pro key, if you are using the 'goreleaser-pro' |
||||
# distribution: |
||||
# GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} |
||||
|
||||
goreleaser-check-pkgs: |
||||
runs-on: ubuntu-latest |
||||
env: |
||||
DOCKER_CLI_EXPERIMENTAL: "enabled" |
||||
needs: [ goreleaser ] |
||||
if: github.ref == 'refs/heads/main' |
||||
strategy: |
||||
matrix: |
||||
format: [ deb, rpm, apk ] |
||||
steps: |
||||
- uses: actions/checkout@v3 # v3 |
||||
with: |
||||
fetch-depth: 0 |
||||
- uses: arduino/setup-task@e26d8975574116b0097a1161e0fe16ba75d84c1c # v1 |
||||
with: |
||||
version: 3.x |
||||
repo-token: ${{ secrets.GITHUB_TOKEN }} |
||||
- uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2 |
||||
- uses: actions/cache@88522ab9f39a2ea568f7027eddc7d8d8bc9d59c8 # v3 |
||||
with: |
||||
path: | |
||||
./_output/dist/*.deb |
||||
./_output/dist/*.rpm |
||||
./_output/dist/*.apk |
||||
key: ${{ github.ref }} |
||||
- run: task goreleaser:test:${{ matrix.format }} |
@ -0,0 +1,48 @@ |
||||
# Copyright © 2023 OpenIM SDK. 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. |
||||
|
||||
# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. |
||||
# |
||||
# You can adjust the behavior by modifying this file. |
||||
# For more information, see: |
||||
# https://github.com/actions/stale |
||||
name: Mark stale issues and pull requests |
||||
|
||||
on: |
||||
schedule: |
||||
- cron: '0 8 * * *' |
||||
|
||||
jobs: |
||||
stale: |
||||
|
||||
runs-on: ubuntu-latest |
||||
permissions: |
||||
issues: write |
||||
pull-requests: write |
||||
|
||||
steps: |
||||
- uses: actions/stale@v5 |
||||
with: |
||||
repo-token: ${{ secrets.BOT_GITHUB_TOKEN }} |
||||
days-before-stale: 60 |
||||
days-before-close: 7 |
||||
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days.' |
||||
stale-pr-message: 'This issue is stale because it has been open 60 days with no activity.' |
||||
close-issue-message: 'This issue was closed because it has been stalled for 7 days with no activity.' |
||||
close-pr-message: 'This PR was closed because it has been stalled for 7 days with no activity. You can reopen it if you want.' |
||||
stale-pr-label: lifecycle/stale |
||||
stale-issue-label: lifecycle/stale |
||||
exempt-issue-labels: 'openim' |
||||
exempt-pr-labels: 'openim' |
||||
exempt-draft-pr: true |
@ -0,0 +1,172 @@ |
||||
logs |
||||
.devcontainer |
||||
components |
||||
logs |
||||
out-test |
||||
*.db |
||||
|
||||
### Backup ### |
||||
*.bak |
||||
*.gho |
||||
*.ori |
||||
*.orig |
||||
*.tmp |
||||
|
||||
### deploy dir ### |
||||
deploy/open_im_api |
||||
deploy/open_im_msg_gateway |
||||
deploy/open_im_msg_transfer |
||||
deploy/open_im_push |
||||
deploy/open_im_rpc_user |
||||
deploy/open_im_rpc_friend |
||||
deploy/open_im_rpc_group |
||||
deploy/open_im_rpc_msg |
||||
deploy/open_im_rpc_auth |
||||
deploy/Open-IM-SDK-Core |
||||
|
||||
### Git ### |
||||
# Created by git for backups. To disable backups in Git: |
||||
# $ git config --global mergetool.keepBackup false |
||||
|
||||
# Created by git when using merge tools for conflicts |
||||
*.BACKUP.* |
||||
*.BASE.* |
||||
*.LOCAL.* |
||||
*.REMOTE.* |
||||
*_BACKUP_*.txt |
||||
*_BASE_*.txt |
||||
*_LOCAL_*.txt |
||||
*_REMOTE_*.txt |
||||
|
||||
### Go ### |
||||
# Binaries for programs and plugins |
||||
*.exe |
||||
*.exe~ |
||||
*.dll |
||||
*.so |
||||
*.dylib |
||||
|
||||
# Test binary, built with `go test -c` |
||||
*.test |
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE |
||||
*.out |
||||
|
||||
# Dependency directories (remove the comment below to include it) |
||||
vendor/ |
||||
bin/ |
||||
tools/ |
||||
tmp/ |
||||
|
||||
|
||||
### vscode ### |
||||
.vscode/* |
||||
!.vscode/settings.json |
||||
!.vscode/tasks.json |
||||
!.vscode/launch.json |
||||
!.vscode/extensions.json |
||||
*.code-workspace |
||||
|
||||
### JetBrains ### |
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider |
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 |
||||
.idea |
||||
|
||||
# User-specific stuff |
||||
.idea/**/workspace.xml |
||||
.idea/**/tasks.xml |
||||
.idea/**/usage.statistics.xml |
||||
.idea/**/dictionaries |
||||
.idea/**/shelf |
||||
|
||||
# Generated files |
||||
.idea/**/contentModel.xml |
||||
|
||||
# Sensitive or high-churn files |
||||
.idea/**/dataSources/ |
||||
.idea/**/dataSources.ids |
||||
.idea/**/dataSources.local.xml |
||||
.idea/**/sqlDataSources.xml |
||||
.idea/**/dynamic.xml |
||||
.idea/**/uiDesigner.xml |
||||
.idea/**/dbnavigator.xml |
||||
|
||||
# Gradle |
||||
.idea/**/gradle.xml |
||||
.idea/**/libraries |
||||
|
||||
# Gradle and Maven with auto-import |
||||
# When using Gradle or Maven with auto-import, you should exclude module files, |
||||
# since they will be recreated, and may cause churn. Uncomment if using |
||||
# auto-import. |
||||
# .idea/artifacts |
||||
# .idea/compiler.xml |
||||
# .idea/jarRepositories.xml |
||||
# .idea/modules.xml |
||||
# .idea/*.iml |
||||
# .idea/modules |
||||
# *.iml |
||||
# *.ipr |
||||
|
||||
# CMake |
||||
cmake-build-*/ |
||||
|
||||
# Mongo Explorer plugin |
||||
.idea/**/mongoSettings.xml |
||||
|
||||
# File-based project format |
||||
*.iws |
||||
|
||||
# IntelliJ |
||||
out/ |
||||
|
||||
# mpeltonen/sbt-idea plugin |
||||
.idea_modules/ |
||||
|
||||
# JIRA plugin |
||||
atlassian-ide-plugin.xml |
||||
|
||||
# Cursive Clojure plugin |
||||
.idea/replstate.xml |
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ) |
||||
com_crashlytics_export_strings.xml |
||||
crashlytics.properties |
||||
crashlytics-build.properties |
||||
fabric.properties |
||||
|
||||
# Editor-based Rest Client |
||||
.idea/httpRequests |
||||
|
||||
# Android studio 3.1+ serialized cache file |
||||
.idea/caches/build_file_checksums.ser |
||||
|
||||
### JetBrains Patch ### |
||||
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 |
||||
|
||||
# *.iml |
||||
# modules.xml |
||||
# .idea/misc.xml |
||||
# *.ipr |
||||
|
||||
# Sonarlint plugin |
||||
# https://plugins.jetbrains.com/plugin/7973-sonarlint |
||||
.idea/**/sonarlint/ |
||||
|
||||
# SonarQube Plugin |
||||
# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin |
||||
.idea/**/sonarIssues.xml |
||||
|
||||
# Markdown Navigator plugin |
||||
# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced |
||||
.idea/**/markdown-navigator.xml |
||||
.idea/**/markdown-navigator-enh.xml |
||||
.idea/**/markdown-navigator/ |
||||
|
||||
# Cache file creation bug |
||||
# See https://youtrack.jetbrains.com/issue/JBR-2257 |
||||
.idea/$CACHE_FILE$ |
||||
|
||||
# CodeStream plugin |
||||
# https://plugins.jetbrains.com/plugin/12206-codestream |
||||
.idea/codestream.xml |
@ -0,0 +1,934 @@ |
||||
# Copyright © 2023 OpenIMSDK open source community. All rights reserved. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
# This file contains all available configuration options |
||||
# with their default values. |
||||
|
||||
# options for analysis running |
||||
run: |
||||
# default concurrency is a available CPU number |
||||
concurrency: 4 |
||||
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m |
||||
timeout: 5m |
||||
|
||||
# exit code when at least one issue was found, default is 1 |
||||
issues-exit-code: 1 |
||||
|
||||
# include test files or not, default is true |
||||
tests: true |
||||
|
||||
# list of build tags, all linters use it. Default is empty list. |
||||
build-tags: |
||||
- mytag |
||||
|
||||
# which dirs to skip: issues from them won't be reported; |
||||
# can use regexp here: generated.*, regexp is applied on full path; |
||||
# default value is empty list, but default dirs are skipped independently |
||||
# from this option's value (see skip-dirs-use-default). |
||||
# "/" will be replaced by current OS file path separator to properly work |
||||
# on Windows. |
||||
skip-dirs: |
||||
- util |
||||
- .*~ |
||||
- api/swagger/docs |
||||
- server/docs |
||||
|
||||
# default is true. Enables skipping of directories: |
||||
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ |
||||
skip-dirs-use-default: true |
||||
|
||||
# which files to skip: they will be analyzed, but issues from them |
||||
# won't be reported. Default value is empty list, but there is |
||||
# no need to include all autogenerated files, we confidently recognize |
||||
# autogenerated files. If it's not please let us know. |
||||
# "/" will be replaced by current OS file path separator to properly work |
||||
# on Windows. |
||||
skip-files: |
||||
- ".*\\.my\\.go$" |
||||
- _test.go |
||||
|
||||
# by default isn't set. If set we pass it to "go list -mod={option}". From "go help modules": |
||||
# If invoked with -mod=readonly, the go command is disallowed from the implicit |
||||
# automatic updating of go.mod described above. Instead, it fails when any changes |
||||
# to go.mod are needed. This setting is most useful to check that go.mod does |
||||
# not need updates, such as in a continuous integration and testing system. |
||||
# If invoked with -mod=vendor, the go command assumes that the vendor |
||||
# directory holds the correct copies of dependencies and ignores |
||||
# the dependency descriptions in go.mod. |
||||
#modules-download-mode: release|readonly|vendor |
||||
|
||||
# Allow multiple parallel golangci-lint instances running. |
||||
# If false (default) - golangci-lint acquires file lock on start. |
||||
allow-parallel-runners: true |
||||
|
||||
|
||||
# output configuration options |
||||
output: |
||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" |
||||
format: colored-line-number |
||||
|
||||
# print lines of code with issue, default is true |
||||
print-issued-lines: true |
||||
|
||||
# print linter name in the end of issue text, default is true |
||||
print-linter-name: true |
||||
|
||||
# make issues output unique by line, default is true |
||||
uniq-by-line: true |
||||
|
||||
# add a prefix to the output file references; default is no prefix |
||||
path-prefix: "" |
||||
|
||||
# sorts results by: filepath, line and column |
||||
sort-results: true |
||||
|
||||
# all available settings of specific linters |
||||
linters-settings: |
||||
bidichk: |
||||
# The following configurations check for all mentioned invisible unicode |
||||
# runes. It can be omitted because all runes are enabled by default. |
||||
left-to-right-embedding: true |
||||
right-to-left-embedding: true |
||||
pop-directional-formatting: true |
||||
left-to-right-override: true |
||||
right-to-left-override: true |
||||
left-to-right-isolate: true |
||||
right-to-left-isolate: true |
||||
first-strong-isolate: true |
||||
pop-directional-isolate: true |
||||
dogsled: |
||||
# checks assignments with too many blank identifiers; default is 2 |
||||
max-blank-identifiers: 2 |
||||
dupl: |
||||
# tokens count to trigger issue, 150 by default |
||||
threshold: 100 |
||||
errcheck: |
||||
# report about not checking of errors in type assertions: `a := b.(MyStruct)`; |
||||
# default is false: such cases aren't reported by default. |
||||
check-type-assertions: false |
||||
|
||||
# report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`; |
||||
# default is false: such cases aren't reported by default. |
||||
check-blank: false |
||||
|
||||
# [deprecated] comma-separated list of pairs of the form pkg:regex |
||||
# the regex is used to ignore names within pkg. (default "fmt:.*"). |
||||
# see https://github.com/kisielk/errcheck#the-deprecated-method for details |
||||
#ignore: GenMarkdownTree,os:.*,BindPFlags,WriteTo,Help |
||||
#ignore: (os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*print(f|ln)?|os\.(Un)?Setenv |
||||
|
||||
# path to a file containing a list of functions to exclude from checking |
||||
# see https://github.com/kisielk/errcheck#excluding-functions for details |
||||
#exclude: errcheck.txt |
||||
|
||||
errorlint: |
||||
# Check whether fmt.Errorf uses the %w verb for formatting errors. See the readme for caveats |
||||
errorf: true |
||||
# Check for plain type assertions and type switches |
||||
asserts: true |
||||
# Check for plain error comparisons |
||||
comparison: true |
||||
|
||||
exhaustive: |
||||
# check switch statements in generated files also |
||||
check-generated: false |
||||
# indicates that switch statements are to be considered exhaustive if a |
||||
# 'default' case is present, even if all enum members aren't listed in the |
||||
# switch |
||||
default-signifies-exhaustive: false |
||||
# enum members matching the supplied regex do not have to be listed in |
||||
# switch statements to satisfy exhaustiveness |
||||
ignore-enum-members: "" |
||||
# consider enums only in package scopes, not in inner scopes |
||||
package-scope-only: false |
||||
exhaustivestruct: |
||||
struct-patterns: |
||||
- '*.Test' |
||||
- '*.Test2' |
||||
- '*.Embedded' |
||||
- '*.External' |
||||
|
||||
# forbidigo: |
||||
# # Forbid the following identifiers (identifiers are written using regexp): |
||||
# forbid: |
||||
# - ^print.*$ |
||||
# - 'fmt\.Print.*' |
||||
# - fmt.Println.* # too much log noise |
||||
# - ginkgo\\.F.* # these are used just for local development |
||||
# # Exclude godoc examples from forbidigo checks. Default is true. |
||||
# exclude_godoc_examples: false |
||||
funlen: |
||||
lines: 150 |
||||
statements: 50 |
||||
gci: |
||||
# put imports beginning with prefix after 3rd-party packages; |
||||
# only support one prefix |
||||
# if not set, use goimports.local-prefixes |
||||
prefix: github.com/openimsdk/openim-sdk-core |
||||
gocognit: |
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20) |
||||
min-complexity: 30 |
||||
goconst: |
||||
# minimal length of string constant, 3 by default |
||||
min-len: 3 |
||||
# minimal occurrences count to trigger, 3 by default |
||||
min-occurrences: 3 |
||||
# ignore test files, false by default |
||||
ignore-tests: false |
||||
# look for existing constants matching the values, true by default |
||||
match-constant: true |
||||
# search also for duplicated numbers, false by default |
||||
numbers: false |
||||
# minimum value, only works with goconst.numbers, 3 by default |
||||
min: 3 |
||||
# maximum value, only works with goconst.numbers, 3 by default |
||||
max: 3 |
||||
# ignore when constant is not used as function argument, true by default |
||||
ignore-calls: true |
||||
|
||||
gocritic: |
||||
# Which checks should be enabled; can't be combined with 'disabled-checks'; |
||||
# See https://go-critic.github.io/overview#checks-overview |
||||
# To check which checks are enabled run `GL_DEBUG=gocritic golangci-lint run` |
||||
# By default list of stable checks is used. |
||||
enabled-checks: |
||||
#- rangeValCopy |
||||
- nestingreduce |
||||
- truncatecmp |
||||
- unnamedresult |
||||
- ruleguard |
||||
|
||||
# Which checks should be disabled; can't be combined with 'enabled-checks'; default is empty |
||||
disabled-checks: |
||||
- regexpMust |
||||
- ifElseChain |
||||
#- exitAfterDefer |
||||
|
||||
# Enable multiple checks by tags, run `GL_DEBUG=gocritic golangci-lint run` to see all tags and checks. |
||||
# Empty list by default. See https://github.com/go-critic/go-critic#usage -> section "Tags". |
||||
enabled-tags: |
||||
- performance |
||||
disabled-tags: |
||||
- experimental |
||||
|
||||
# Settings passed to gocritic. |
||||
# The settings key is the name of a supported gocritic checker. |
||||
# The list of supported checkers can be find in https://go-critic.github.io/overview. |
||||
settings: |
||||
captLocal: # must be valid enabled check name |
||||
# whether to restrict checker to params only (default true) |
||||
paramsOnly: true |
||||
elseif: |
||||
# whether to skip balanced if-else pairs (default true) |
||||
skipBalanced: true |
||||
hugeParam: |
||||
# size in bytes that makes the warning trigger (default 80) |
||||
sizeThreshold: 80 |
||||
nestingReduce: |
||||
# min number of statements inside a branch to trigger a warning (default 5) |
||||
bodyWidth: 5 |
||||
rangeExprCopy: |
||||
# size in bytes that makes the warning trigger (default 512) |
||||
sizeThreshold: 512 |
||||
# whether to check test functions (default true) |
||||
skipTestFuncs: true |
||||
rangeValCopy: |
||||
# size in bytes that makes the warning trigger (default 128) |
||||
sizeThreshold: 32 |
||||
# whether to check test functions (default true) |
||||
skipTestFuncs: true |
||||
ruleguard: |
||||
# path to a gorules file for the ruleguard checker |
||||
rules: '' |
||||
truncateCmp: |
||||
# whether to skip int/uint/uintptr types (default true) |
||||
skipArchDependent: true |
||||
underef: |
||||
# whether to skip (*x).method() calls where x is a pointer receiver (default true) |
||||
skipRecvDeref: true |
||||
unnamedResult: |
||||
# whether to check exported functions |
||||
checkExported: true |
||||
gocyclo: |
||||
# minimal code complexity to report, 30 by default (but we recommend 10-20) |
||||
min-complexity: 30 |
||||
cyclop: |
||||
# the maximal code complexity to report |
||||
max-complexity: 50 |
||||
# the maximal average package complexity. If it's higher than 0.0 (float) the check is enabled (default 0.0) |
||||
package-average: 0.0 |
||||
# should ignore tests (default false) |
||||
skip-tests: false |
||||
godot: |
||||
# comments to be checked: `declarations`, `toplevel`, or `all` |
||||
scope: declarations |
||||
# list of regexps for excluding particular comment lines from check |
||||
exclude: |
||||
# example: exclude comments which contain numbers |
||||
# - '[0-9]+' |
||||
# check that each sentence starts with a capital letter |
||||
capital: false |
||||
godox: |
||||
# report any comments starting with keywords, this is useful for TODO or FIXME comments that |
||||
# might be left in the code accidentally and should be resolved before merging |
||||
keywords: # default keywords are TODO, BUG, and FIXME, these can be overwritten by this setting |
||||
#- TODO |
||||
- BUG |
||||
- FIXME |
||||
#- NOTE |
||||
- OPTIMIZE # marks code that should be optimized before merging |
||||
- HACK # marks hack-arounds that should be removed before merging |
||||
gofmt: |
||||
# simplify code: gofmt with `-s` option, true by default |
||||
simplify: true |
||||
|
||||
gofumpt: |
||||
# Select the Go version to target. The default is `1.18`. |
||||
lang-version: "1.20" |
||||
|
||||
# Choose whether or not to use the extra rules that are disabled |
||||
# by default |
||||
extra-rules: false |
||||
|
||||
goheader: |
||||
values: |
||||
const: |
||||
# define here const type values in format k:v, for example: |
||||
# COMPANY: MY COMPANY |
||||
regexp: |
||||
# define here regexp type values, for example |
||||
# AUTHOR: .*@mycompany\.com |
||||
template: # |- |
||||
# put here copyright header template for source code files, for example: |
||||
# Note: {{ YEAR }} is a builtin value that returns the year relative to the current machine time. |
||||
# |
||||
# {{ AUTHOR }} {{ COMPANY }} {{ YEAR }} |
||||
# SPDX-License-Identifier: Apache-2.0 |
||||
|
||||
# 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. |
||||
template-path: |
||||
# also as alternative of directive 'template' you may put the path to file with the template source |
||||
goimports: |
||||
# put imports beginning with prefix after 3rd-party packages; |
||||
# it's a comma-separated list of prefixes |
||||
local-prefixes: github.com/openimsdk/openim-sdk-core |
||||
golint: |
||||
# minimal confidence for issues, default is 0.8 |
||||
min-confidence: 0.9 |
||||
gomnd: |
||||
settings: |
||||
mnd: |
||||
# the list of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. |
||||
checks: argument,case,condition,operation,return,assign |
||||
# ignored-numbers: 1000 |
||||
# ignored-files: magic_.*.go |
||||
# ignored-functions: math.* |
||||
gomoddirectives: |
||||
# Allow local `replace` directives. Default is false. |
||||
replace-local: true |
||||
# List of allowed `replace` directives. Default is empty. |
||||
replace-allow-list: |
||||
- google.golang.org/grpc |
||||
|
||||
# Allow to not explain why the version has been retracted in the `retract` directives. Default is false. |
||||
retract-allow-no-explanation: false |
||||
# Forbid the use of the `exclude` directives. Default is false. |
||||
exclude-forbidden: false |
||||
gomodguard: |
||||
allowed: |
||||
modules: |
||||
- gorm.io/gen # List of allowed modules |
||||
- gorm.io/gorm |
||||
- gorm.io/driver/mysql |
||||
- k8s.io/klog |
||||
# - gopkg.in/yaml.v2 |
||||
domains: # List of allowed module domains |
||||
- google.golang.org |
||||
- gopkg.in |
||||
- golang.org |
||||
- github.com |
||||
- go.uber.org |
||||
- go.etcd.io |
||||
blocked: |
||||
versions: |
||||
- github.com/MakeNowJust/heredoc: |
||||
version: "> 2.0.9" |
||||
reason: "use the latest version" |
||||
local_replace_directives: false # Set to true to raise lint issues for packages that are loaded from a local path via replace directive |
||||
|
||||
gosec: |
||||
# To select a subset of rules to run. |
||||
# Available rules: https://github.com/securego/gosec#available-rules |
||||
includes: |
||||
- G401 |
||||
- G306 |
||||
- G101 |
||||
# To specify a set of rules to explicitly exclude. |
||||
# Available rules: https://github.com/securego/gosec#available-rules |
||||
excludes: |
||||
- G204 |
||||
# Exclude generated files |
||||
exclude-generated: true |
||||
# Filter out the issues with a lower severity than the given value. Valid options are: low, medium, high. |
||||
severity: "low" |
||||
# Filter out the issues with a lower confidence than the given value. Valid options are: low, medium, high. |
||||
confidence: "low" |
||||
# To specify the configuration of rules. |
||||
# The configuration of rules is not fully documented by gosec: |
||||
# https://github.com/securego/gosec#configuration |
||||
# https://github.com/securego/gosec/blob/569328eade2ccbad4ce2d0f21ee158ab5356a5cf/rules/rulelist.go#L60-L102 |
||||
config: |
||||
G306: "0600" |
||||
G101: |
||||
pattern: "(?i)example" |
||||
ignore_entropy: false |
||||
entropy_threshold: "80.0" |
||||
per_char_threshold: "3.0" |
||||
truncate: "32" |
||||
|
||||
gosimple: |
||||
# Select the Go version to target. The default is '1.13'. |
||||
go: "1.20" |
||||
# https://staticcheck.io/docs/options#checks |
||||
checks: [ "all" ] |
||||
|
||||
govet: |
||||
# report about shadowed variables |
||||
check-shadowing: true |
||||
|
||||
# settings per analyzer |
||||
settings: |
||||
printf: # analyzer name, run `go tool vet help` to see all analyzers |
||||
funcs: # run `go tool vet help printf` to see available settings for `printf` analyzer |
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof |
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf |
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf |
||||
- (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf |
||||
|
||||
# enable or disable analyzers by name |
||||
enable: |
||||
- atomicalign |
||||
enable-all: false |
||||
disable: |
||||
- shadow |
||||
disable-all: false |
||||
# depguard: |
||||
# list-type: blacklist |
||||
# include-go-root: false |
||||
# packages: |
||||
# - github.com/Sirupsen/logrus |
||||
# packages-with-error-message: |
||||
# # specify an error message to output when a blacklisted package is used |
||||
# - github.com/Sirupsen/logrus: "logging is allowed only by logutils.Log" |
||||
ifshort: |
||||
# Maximum length of variable declaration measured in number of lines, after which linter won't suggest using short syntax. |
||||
# Has higher priority than max-decl-chars. |
||||
max-decl-lines: 1 |
||||
# Maximum length of variable declaration measured in number of characters, after which linter won't suggest using short syntax. |
||||
max-decl-chars: 30 |
||||
|
||||
importas: |
||||
# if set to `true`, force to use alias. |
||||
no-unaliased: true |
||||
# List of aliases |
||||
alias: |
||||
# using `servingv1` alias for `knative.dev/serving/pkg/apis/serving/v1` package |
||||
- pkg: knative.dev/serving/pkg/apis/serving/v1 |
||||
alias: servingv1 |
||||
# using `autoscalingv1alpha1` alias for `knative.dev/serving/pkg/apis/autoscaling/v1alpha1` package |
||||
- pkg: knative.dev/serving/pkg/apis/autoscaling/v1alpha1 |
||||
alias: autoscalingv1alpha1 |
||||
# You can specify the package path by regular expression, |
||||
# and alias by regular expression expansion syntax like below. |
||||
# see https://github.com/julz/importas#use-regular-expression for details |
||||
- pkg: knative.dev/serving/pkg/apis/(\w+)/(v[\w\d]+) |
||||
alias: $1$2 |
||||
# using `jwt` alias for `github.com/appleboy/gin-jwt/v2` package |
||||
jwt: github.com/appleboy/gin-jwt/v2 |
||||
|
||||
ireturn: |
||||
# ireturn allows using `allow` and `reject` settings at the same time. |
||||
# Both settings are lists of the keywords and regular expressions matched to interface or package names. |
||||
# keywords: |
||||
# - `empty` for `interface{}` |
||||
# - `error` for errors |
||||
# - `stdlib` for standard library |
||||
# - `anon` for anonymous interfaces |
||||
|
||||
# By default, it allows using errors, empty interfaces, anonymous interfaces, |
||||
# and interfaces provided by the standard library. |
||||
allow: |
||||
- anon |
||||
- error |
||||
- empty |
||||
- stdlib |
||||
# You can specify idiomatic endings for interface |
||||
- (or|er)$ |
||||
|
||||
# Reject patterns |
||||
reject: |
||||
- github.com\/user\/package\/v4\.Type |
||||
|
||||
lll: |
||||
# max line length, lines longer will be reported. Default is 120. |
||||
# '\t' is counted as 1 character by default, and can be changed with the tab-width option |
||||
line-length: 240 |
||||
# tab width in spaces. Default to 1. |
||||
tab-width: 4 |
||||
maligned: |
||||
# print struct with more effective memory layout or not, false by default |
||||
suggest-new: true |
||||
misspell: |
||||
# Correct spellings using locale preferences for US or UK. |
||||
# Default is to use a neutral variety of English. |
||||
# Setting locale to US will correct the British spelling of 'colour' to 'color'. |
||||
locale: US |
||||
ignore-words: |
||||
- someword |
||||
nakedret: |
||||
# make an issue if func has more lines of code than this setting and it has naked returns; default is 30 |
||||
max-func-lines: 30 |
||||
|
||||
nestif: |
||||
# minimal complexity of if statements to report, 5 by default |
||||
min-complexity: 4 |
||||
|
||||
nilnil: |
||||
# By default, nilnil checks all returned types below. |
||||
checked-types: |
||||
- ptr |
||||
- func |
||||
- iface |
||||
- map |
||||
- chan |
||||
|
||||
nlreturn: |
||||
# size of the block (including return statement that is still "OK") |
||||
# so no return split required. |
||||
block-size: 1 |
||||
|
||||
nolintlint: |
||||
# Disable to ensure that all nolint directives actually have an effect. Default is true. |
||||
allow-unused: false |
||||
# Disable to ensure that nolint directives don't have a leading space. Default is true. |
||||
allow-leading-space: true |
||||
# Exclude following linters from requiring an explanation. Default is []. |
||||
allow-no-explanation: [ ] |
||||
# Enable to require an explanation of nonzero length after each nolint directive. Default is false. |
||||
require-explanation: false |
||||
# Enable to require nolint directives to mention the specific linter being suppressed. Default is false. |
||||
require-specific: true |
||||
|
||||
prealloc: |
||||
# XXX: we don't recommend using this linter before doing performance profiling. |
||||
# For most programs usage of prealloc will be a premature optimization. |
||||
|
||||
# Report preallocation suggestions only on simple loops that have no returns/breaks/continues/gotos in them. |
||||
# True by default. |
||||
simple: true |
||||
range-loops: true # Report preallocation suggestions on range loops, true by default |
||||
for-loops: false # Report preallocation suggestions on for loops, false by default |
||||
|
||||
promlinter: |
||||
# Promlinter cannot infer all metrics name in static analysis. |
||||
# Enable strict mode will also include the errors caused by failing to parse the args. |
||||
strict: false |
||||
# Please refer to https://github.com/yeya24/promlinter#usage for detailed usage. |
||||
disabled-linters: |
||||
# - "Help" |
||||
# - "MetricUnits" |
||||
# - "Counter" |
||||
# - "HistogramSummaryReserved" |
||||
# - "MetricTypeInName" |
||||
# - "ReservedChars" |
||||
# - "CamelCase" |
||||
# - "lintUnitAbbreviations" |
||||
|
||||
predeclared: |
||||
# comma-separated list of predeclared identifiers to not report on |
||||
ignore: "" |
||||
# include method names and field names (i.e., qualified names) in checks |
||||
q: false |
||||
rowserrcheck: |
||||
packages: |
||||
- github.com/jmoiron/sqlx |
||||
revive: |
||||
# see https://github.com/mgechev/revive#available-rules for details. |
||||
ignore-generated-header: true |
||||
severity: warning |
||||
rules: |
||||
- name: indent-error-flow |
||||
severity: warning |
||||
staticcheck: |
||||
# Select the Go version to target. The default is '1.13'. |
||||
go: "1.16" |
||||
# https://staticcheck.io/docs/options#checks |
||||
checks: [ "all" ] |
||||
|
||||
stylecheck: |
||||
# Select the Go version to target. The default is '1.13'. |
||||
go: "1.16" |
||||
|
||||
# https://staticcheck.io/docs/options#checks |
||||
checks: [ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022" ] |
||||
# https://staticcheck.io/docs/options#dot_import_whitelist |
||||
dot-import-whitelist: |
||||
- fmt |
||||
# https://staticcheck.io/docs/options#initialisms |
||||
initialisms: [ "ACL", "API", "ASCII", "CPU", "CSS", "DNS", "EOF", "GUID", "HTML", "HTTP", "HTTPS", "ID", "IP", "JSON", "QPS", "RAM", "RPC", "SLA", "SMTP", "SQL", "SSH", "TCP", "TLS", "TTL", "UDP", "UI", "GID", "UID", "UUID", "URI", "URL", "UTF8", "VM", "XML", "XMPP", "XSRF", "XSS" ] |
||||
# https://staticcheck.io/docs/options#http_status_code_whitelist |
||||
http-status-code-whitelist: [ "200", "400", "404", "500" ] |
||||
|
||||
|
||||
tagliatelle: |
||||
# check the struck tag name case |
||||
case: |
||||
# use the struct field name to check the name of the struct tag |
||||
use-field-name: true |
||||
rules: |
||||
# any struct tag type can be used. |
||||
# support string case: `camel`, `pascal`, `kebab`, `snake`, `goCamel`, `goPascal`, `goKebab`, `goSnake`, `upper`, `lower` |
||||
json: camel |
||||
yaml: camel |
||||
xml: camel |
||||
bson: camel |
||||
avro: snake |
||||
mapstructure: kebab |
||||
|
||||
testpackage: |
||||
# regexp pattern to skip files |
||||
skip-regexp: (id|export|internal)_test\.go |
||||
thelper: |
||||
# The following configurations enable all checks. It can be omitted because all checks are enabled by default. |
||||
# You can enable only required checks deleting unnecessary checks. |
||||
test: |
||||
first: true |
||||
name: true |
||||
begin: true |
||||
benchmark: |
||||
first: true |
||||
name: true |
||||
begin: true |
||||
tb: |
||||
first: true |
||||
name: true |
||||
begin: true |
||||
|
||||
tenv: |
||||
# The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures. |
||||
# By default, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked. |
||||
all: false |
||||
|
||||
unparam: |
||||
# Inspect exported functions, default is false. Set to true if no external program/library imports your code. |
||||
# XXX: if you enable this setting, unparam will report a lot of false-positives in text editors: |
||||
# if it's called for subdir of a project it can't find external interfaces. All text editor integrations |
||||
# with golangci-lint call it on a directory with the changed file. |
||||
check-exported: false |
||||
unused: |
||||
# treat code as a program (not a library) and report unused exported identifiers; default is false. |
||||
# XXX: if you enable this setting, unused will report a lot of false-positives in text editors: |
||||
# if it's called for subdir of a project it can't find funcs usages. All text editor integrations |
||||
# with golangci-lint call it on a directory with the changed file. |
||||
check-exported: false |
||||
whitespace: |
||||
multi-if: false # Enforces newlines (or comments) after every multi-line if statement |
||||
multi-func: false # Enforces newlines (or comments) after every multi-line function signature |
||||
|
||||
wrapcheck: |
||||
# An array of strings that specify substrings of signatures to ignore. |
||||
# If this set, it will override the default set of ignored signatures. |
||||
# See https://github.com/tomarrell/wrapcheck#configuration for more information. |
||||
ignoreSigs: |
||||
- .Errorf( |
||||
- errors.New( |
||||
- errors.Unwrap( |
||||
- .Wrap( |
||||
- .Wrapf( |
||||
- .WithMessage( |
||||
- .WithMessagef( |
||||
- .WithStack( |
||||
ignorePackageGlobs: |
||||
- encoding/* |
||||
- github.com/pkg/* |
||||
|
||||
wsl: |
||||
# If true append is only allowed to be cuddled if appending value is |
||||
# matching variables, fields or types on line above. Default is true. |
||||
strict-append: true |
||||
# Allow calls and assignments to be cuddled as long as the lines have any |
||||
# matching variables, fields or types. Default is true. |
||||
allow-assign-and-call: true |
||||
# Allow assignments to be cuddled with anything. Default is false. |
||||
allow-assign-and-anything: false |
||||
# Allow multiline assignments to be cuddled. Default is true. |
||||
allow-multiline-assign: true |
||||
# Allow declarations (var) to be cuddled. |
||||
allow-cuddle-declarations: false |
||||
# Allow trailing comments in ending of blocks |
||||
allow-trailing-comment: false |
||||
# Force newlines in end of case at this limit (0 = never). |
||||
force-case-trailing-whitespace: 0 |
||||
# Force cuddling of err checks with err var assignment |
||||
force-err-cuddling: false |
||||
# Allow leading comments to be separated with empty liens |
||||
allow-separated-leading-comment: false |
||||
makezero: |
||||
# Allow only slices initialized with a length of zero. Default is false. |
||||
always: false |
||||
|
||||
|
||||
# The custom section can be used to define linter plugins to be loaded at runtime. See README doc |
||||
# for more info. |
||||
#custom: |
||||
# Each custom linter should have a unique name. |
||||
#example: |
||||
# The path to the plugin *.so. Can be absolute or local. Required for each custom linter |
||||
#path: /path/to/example.so |
||||
# The description of the linter. Optional, just for documentation purposes. |
||||
#description: This is an example usage of a plugin linter. |
||||
# Intended to point to the repo location of the linter. Optional, just for documentation purposes. |
||||
#original-url: github.com/golangci/example-linter |
||||
|
||||
linters: |
||||
# please, do not use `enable-all`: it's deprecated and will be removed soon. |
||||
# inverted configuration with `enable-all` and `disable` is not scalable during updates of golangci-lint |
||||
# enable-all: true |
||||
disable-all: true |
||||
enable: |
||||
- typecheck |
||||
- asciicheck |
||||
- bodyclose |
||||
- cyclop |
||||
- deadcode |
||||
# - depguard |
||||
- dogsled |
||||
- dupl |
||||
- durationcheck |
||||
- errcheck |
||||
- errorlint |
||||
- exhaustive |
||||
- exportloopref |
||||
# - forbidigo |
||||
- funlen |
||||
# - gci |
||||
# - gochecknoinits |
||||
- gocognit |
||||
- goconst |
||||
- gocyclo |
||||
- godot |
||||
- godox |
||||
- gofmt |
||||
- gofumpt |
||||
- goheader |
||||
- goimports |
||||
- gomoddirectives |
||||
- gomodguard |
||||
- goprintffuncname |
||||
- gosec |
||||
- gosimple |
||||
- govet |
||||
- ifshort |
||||
- importas |
||||
- ineffassign |
||||
- lll |
||||
- makezero |
||||
- misspell |
||||
- nakedret |
||||
- nestif |
||||
- nilerr |
||||
- nlreturn |
||||
- noctx |
||||
- nolintlint |
||||
- paralleltest |
||||
- prealloc |
||||
- predeclared |
||||
- promlinter |
||||
- revive |
||||
- rowserrcheck |
||||
- sqlclosecheck |
||||
- staticcheck |
||||
- structcheck |
||||
- stylecheck |
||||
- thelper |
||||
- tparallel |
||||
- unconvert |
||||
- unparam |
||||
- unused |
||||
- varcheck |
||||
- wastedassign |
||||
- whitespace |
||||
- bidichk |
||||
- wastedassign |
||||
- golint |
||||
- execinquery |
||||
- nosprintfhostport |
||||
- grouper |
||||
- decorder |
||||
- errchkjson |
||||
- maintidx |
||||
#- containedctx |
||||
#- tagliatelle |
||||
#- nonamedreturns |
||||
#- nilnil |
||||
#- tenv |
||||
#- varnamelen |
||||
#- contextcheck |
||||
#- errname |
||||
#- ForceTypeAssert |
||||
#- nilassign |
||||
fast: false |
||||
|
||||
issues: |
||||
# List of regexps of issue texts to exclude, empty list by default. |
||||
# But independently from this option we use default exclude patterns, |
||||
# it can be disabled by `exclude-use-default: false`. To list all |
||||
# excluded by default patterns execute `golangci-lint run --help` |
||||
exclude: |
||||
- tools/.* |
||||
- test/.* |
||||
- third_party/.* |
||||
|
||||
# Excluding configuration per-path, per-linter, per-text and per-source |
||||
exclude-rules: |
||||
- linters: |
||||
- golint |
||||
path: (internal/api/.*)\.go # exclude golint for internal/api/... files |
||||
|
||||
- linters: |
||||
- revive |
||||
path: (log/.*)\.go |
||||
|
||||
- linters: |
||||
- wrapcheck |
||||
path: (cmd/.*|pkg/.*)\.go |
||||
|
||||
- linters: |
||||
- typecheck |
||||
#path: (pkg/storage/.*)\.go |
||||
path: (internal/.*|pkg/.*)\.go |
||||
|
||||
- path: (cmd/.*|test/.*|tools/.*|internal/pump/pumps/.*)\.go |
||||
linters: |
||||
- forbidigo |
||||
|
||||
- path: (cmd/[a-z]*/.*|store/.*)\.go |
||||
linters: |
||||
- dupl |
||||
|
||||
- linters: |
||||
- gocritic |
||||
text: (hugeParam:|rangeValCopy:) |
||||
|
||||
- path: (cmd/[a-z]*/.*)\.go |
||||
linters: |
||||
- lll |
||||
|
||||
- path: (validator/.*|code/.*|validator/.*|watcher/watcher/.*) |
||||
linters: |
||||
- gochecknoinits |
||||
|
||||
- path: (internal/.*/options|internal/pump|pkg/log/options.go|internal/authzserver|tools/) |
||||
linters: |
||||
- tagliatelle |
||||
|
||||
- path: (pkg/app/.*)\.go |
||||
linters: |
||||
- deadcode |
||||
- unused |
||||
- varcheck |
||||
- forbidigo |
||||
|
||||
# Exclude some staticcheck messages |
||||
- linters: |
||||
- staticcheck |
||||
text: "SA9003:" |
||||
|
||||
# Exclude lll issues for long lines with go:generate |
||||
- linters: |
||||
- lll |
||||
source: "^//go:generate " |
||||
|
||||
# Independently from option `exclude` we use default exclude patterns, |
||||
# it can be disabled by this option. To list all |
||||
# excluded by default patterns execute `golangci-lint run --help`. |
||||
# Default value for this option is true. |
||||
exclude-use-default: true |
||||
|
||||
# The default value is false. If set to true exclude and exclude-rules |
||||
# regular expressions become case sensitive. |
||||
exclude-case-sensitive: false |
||||
|
||||
# The list of ids of default excludes to include or disable. By default it's empty. |
||||
include: |
||||
- EXC0002 # disable excluding of issues about comments from golint |
||||
|
||||
# Maximum issues count per one linter. Set to 0 to disable. Default is 50. |
||||
max-issues-per-linter: 0 |
||||
|
||||
# Maximum count of issues with the same text. Set to 0 to disable. Default is 3. |
||||
max-same-issues: 0 |
||||
|
||||
# Show only new issues: if there are unstaged changes or untracked files, |
||||
# only those changes are analyzed, else only changes in HEAD~ are analyzed. |
||||
# It's a super-useful option for integration of golangci-lint into existing |
||||
# large codebase. It's not practical to fix all existing issues at the moment |
||||
# of integration: much better don't allow issues in new code. |
||||
# Default is false. |
||||
new: false |
||||
|
||||
# Show only new issues created after git revision `REV` |
||||
# new-from-rev: REV |
||||
|
||||
# Show only new issues created in git patch with set file path. |
||||
#new-from-patch: path/to/patch/file |
||||
|
||||
# Fix found issues (if it's supported by the linter) |
||||
fix: true |
||||
|
||||
severity: |
||||
# Default value is empty string. |
||||
# Set the default severity for issues. If severity rules are defined and the issues |
||||
# do not match or no severity is provided to the rule this will be the default |
||||
# severity applied. Severities should match the supported severity names of the |
||||
# selected out format. |
||||
# - Code climate: https://docs.codeclimate.com/docs/issues#issue-severity |
||||
# - Checkstyle: https://checkstyle.sourceforge.io/property_types.html#severity |
||||
# - Github: https://help.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-an-error-message |
||||
default-severity: error |
||||
|
||||
# The default value is false. |
||||
# If set to true severity-rules regular expressions become case sensitive. |
||||
case-sensitive: false |
||||
|
||||
# Default value is empty list. |
||||
# When a list of severity rules are provided, severity information will be added to lint |
||||
# issues. Severity rules have the same filtering capability as exclude rules except you |
||||
# are allowed to specify one matcher per severity rule. |
||||
# Only affects out formats that support setting severity information. |
||||
rules: |
||||
- linters: |
||||
- dupl |
||||
severity: info |
@ -0,0 +1,223 @@ |
||||
# Copyright © 2023 OpenIM open source community. All rights reserved. |
||||
# |
||||
# Licensed under the Apache License, Version 2.0 (the "License"); |
||||
# you may not use this file except in compliance with the License. |
||||
# You may obtain a copy of the License at |
||||
# |
||||
# http://www.apache.org/licenses/LICENSE-2.0 |
||||
# |
||||
# Unless required by applicable law or agreed to in writing, software |
||||
# distributed under the License is distributed on an "AS IS" BASIS, |
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
# See the License for the specific language governing permissions and |
||||
# limitations under the License. |
||||
|
||||
# This is an example .goreleaser.yml file with some sensible defaults. |
||||
# Make sure to check the documentation at https://goreleaser.com |
||||
before: |
||||
hooks: |
||||
# You may remove this if you don't use go modules. |
||||
- go mod tidy |
||||
# you may remove this if you don't need go generate |
||||
- go generate ./... |
||||
|
||||
|
||||
|
||||
git: |
||||
# What should be used to sort tags when gathering the current and previous |
||||
# tags if there are more than one tag in the same commit. |
||||
# |
||||
# Default: '-version:refname' |
||||
tag_sort: -version:creatordate |
||||
|
||||
# What should be used to specify prerelease suffix while sorting tags when gathering |
||||
# the current and previous tags if there are more than one tag in the same commit. |
||||
# |
||||
# Since: v1.17 |
||||
prerelease_suffix: "-" |
||||
|
||||
# Tags to be ignored by GoReleaser. |
||||
# This means that GoReleaser will not pick up tags that match any of the |
||||
# provided values as either previous or current tags. |
||||
# |
||||
# Templates: allowed. |
||||
# Since: v1.21. |
||||
ignore_tags: |
||||
- nightly |
||||
# - "{{.Env.IGNORE_TAG}}" |
||||
|
||||
report_sizes: true |
||||
|
||||
builds: |
||||
- binary: openim-sdk-core |
||||
id: openim-sdk-core |
||||
main: ./cmd/main.go |
||||
goos: |
||||
- linux |
||||
goarch: |
||||
- amd64 |
||||
- arm64 |
||||
goarm: |
||||
- "6" |
||||
- "7" |
||||
- id: openIM.wasm |
||||
main: wasm/cmd/main.go # 指定 wasm 主文件路径 |
||||
binary: openIM.wasm |
||||
ldflags: "-s -w" |
||||
goos: |
||||
- js |
||||
goarch: |
||||
- wasm |
||||
|
||||
archives: |
||||
- format: tar.gz |
||||
# this name template makes the OS and Arch compatible with the results of uname. |
||||
name_template: >- |
||||
{{ .ProjectName }}_ |
||||
{{- title .Os }}_ |
||||
{{- if eq .Arch "amd64" }}x86_64 |
||||
{{- else if eq .Arch "386" }}i386 |
||||
{{- else }}{{ .Arch }}{{ end }} |
||||
{{- if .Arm }}v{{ .Arm }}{{ end }} |
||||
# use zip for windows archives |
||||
files: |
||||
- LICENSE |
||||
- README.md |
||||
# a more complete example, check the globbing deep dive below |
||||
- src: "*.md" |
||||
dst: docs |
||||
|
||||
# Strip parent folders when adding files to the archive. |
||||
strip_parent: true |
||||
|
||||
# File info. |
||||
# Not all fields are supported by all formats available formats. |
||||
# |
||||
# Default: copied from the source file |
||||
info: |
||||
# Templates: allowed (since v1.14) |
||||
owner: root |
||||
|
||||
# Templates: allowed (since v1.14) |
||||
group: root |
||||
|
||||
# Must be in time.RFC3339Nano format. |
||||
# |
||||
# Templates: allowed (since v1.14) |
||||
mtime: "{{ .CommitDate }}" |
||||
|
||||
# File mode. |
||||
mode: 0644 |
||||
|
||||
format_overrides: |
||||
- goos: windows |
||||
format: zip |
||||
|
||||
changelog: |
||||
sort: asc |
||||
use: github |
||||
filters: |
||||
exclude: |
||||
- "^test:" |
||||
- "^chore" |
||||
- "merge conflict" |
||||
- Merge pull request |
||||
- Merge remote-tracking branch |
||||
- Merge branch |
||||
- go mod tidy |
||||
groups: |
||||
- title: Dependency updates |
||||
regexp: '^.*?(feat|fix)\(deps\)!?:.+$' |
||||
order: 300 |
||||
- title: "New Features" |
||||
regexp: '^.*?feat(\([[:word:]]+\))??!?:.+$' |
||||
order: 100 |
||||
- title: "Security updates" |
||||
regexp: '^.*?sec(\([[:word:]]+\))??!?:.+$' |
||||
order: 150 |
||||
- title: "Bug fixes" |
||||
regexp: '^.*?fix(\([[:word:]]+\))??!?:.+$' |
||||
order: 200 |
||||
- title: "Documentation updates" |
||||
regexp: ^.*?doc(\([[:word:]]+\))??!?:.+$ |
||||
order: 400 |
||||
- title: "Build process updates" |
||||
regexp: ^.*?build(\([[:word:]]+\))??!?:.+$ |
||||
order: 400 |
||||
- title: Other work |
||||
order: 9999 |
||||
|
||||
|
||||
nfpms: |
||||
- id: packages |
||||
builds: |
||||
- openim-sdk-core |
||||
- openIM.wasm |
||||
# Your app's vendor. |
||||
vendor: OpenIMSDK |
||||
homepage: https://github.com/openimsdk/openim-sdk-core |
||||
maintainer: kubbot <https://github.com/kubbot> |
||||
description: |- |
||||
Auto sync github labels |
||||
kubbot && openimbot |
||||
license: Apache-2.0 |
||||
formats: |
||||
- apk |
||||
- deb |
||||
- rpm |
||||
- termux.deb # Since: v1.11 |
||||
- archlinux # Since: v1.13 |
||||
dependencies: |
||||
- git |
||||
recommends: |
||||
- golang |
||||
|
||||
|
||||
# The lines beneath this are called `modelines`. See `:help modeline` |
||||
# Feel free to remove those if you don't want/use them. |
||||
# yaml-language-server: $schema=https://goreleaser.com/static/schema.json |
||||
# vim: set ts=2 sw=2 tw=0 fo=cnqoj |
||||
|
||||
# Default: './dist' |
||||
dist: ./_output/dist |
||||
|
||||
# .goreleaser.yaml |
||||
milestones: |
||||
# You can have multiple milestone configs |
||||
- |
||||
# Repository for the milestone |
||||
# Default is extracted from the origin remote URL |
||||
repo: |
||||
owner: user |
||||
name: repo |
||||
|
||||
# Whether to close the milestone |
||||
close: true |
||||
|
||||
# Fail release on errors, such as missing milestone. |
||||
fail_on_error: false |
||||
|
||||
# Name of the milestone |
||||
# |
||||
# Default: '{{ .Tag }}' |
||||
name_template: "Current Release" |
||||
|
||||
# publishers: |
||||
# - name: "fury.io" |
||||
# ids: |
||||
# - packages |
||||
# dir: "{{ dir .ArtifactPath }}" |
||||
# cmd: | |
||||
# bash -c ' |
||||
# if [[ "{{ .Tag }}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then |
||||
# curl -F package=@{{ .ArtifactName }} https://{{ .Env.FURY_TOKEN }}@push.fury.io/{{ .Env.USERNAME }}/ |
||||
# else |
||||
# echo "Skipping deployment: Non-production release detected" |
||||
# fi' |
||||
|
||||
checksum: |
||||
name_template: "{{ .ProjectName }}_checksums.txt" |
||||
algorithm: sha256 |
||||
|
||||
release: |
||||
prerelease: auto |
@ -0,0 +1,62 @@ |
||||
# 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 -}} |
@ -0,0 +1,81 @@ |
||||
# Copyright © 2023 OpenIM SDK. 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. |
||||
|
||||
bin: git |
||||
style: github |
||||
template: CHANGELOG.tpl.md |
||||
info: |
||||
title: CHANGELOG |
||||
repository_url: https://github.com/openimsdk/openim-sdk-core |
||||
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 |
@ -0,0 +1,42 @@ |
||||
# Version logging for OpenIM |
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_TOC --> |
||||
|
||||
<!-- END MUNGE: GENERATED_TOC --> |
||||
|
||||
<a name="unreleased"></a> |
||||
## [Unreleased] |
||||
|
||||
|
||||
<a name="v1.0.7"></a> |
||||
## [v1.0.7] - 2021-12-10 |
||||
|
||||
<a name="v1.0.6"></a> |
||||
## [v1.0.6] - 2021-12-03 |
||||
|
||||
<a name="v1.0.5"></a> |
||||
## [v1.0.5] - 2021-11-25 |
||||
|
||||
<a name="v1.0.4"></a> |
||||
## [v1.0.4] - 2021-11-12 |
||||
|
||||
<a name="v1.0.3"></a> |
||||
## [v1.0.3] - 2021-11-05 |
||||
|
||||
<a name="v1.0.2"></a> |
||||
## [v1.0.2] - 2021-11-04 |
||||
|
||||
<a name="v1.0.1"></a> |
||||
## [v1.0.1] - 2021-11-03 |
||||
|
||||
<a name="v1.0.0"></a> |
||||
## v1.0.0 - 2021-10-28 |
||||
|
||||
[Unreleased]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.7...HEAD |
||||
[v1.0.7]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.6...v1.0.7 |
||||
[v1.0.6]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.5...v1.0.6 |
||||
[v1.0.5]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.4...v1.0.5 |
||||
[v1.0.4]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.3...v1.0.4 |
||||
[v1.0.3]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.2...v1.0.3 |
||||
[v1.0.2]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.1...v1.0.2 |
||||
[v1.0.1]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.0...v1.0.1 |
@ -0,0 +1,92 @@ |
||||
# Version logging for OpenIM |
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_TOC --> |
||||
|
||||
<!-- END MUNGE: GENERATED_TOC --> |
||||
|
||||
<a name="unreleased"></a> |
||||
## [Unreleased] |
||||
|
||||
|
||||
<a name="v2.3.3"></a> |
||||
## [v2.3.3] - 2022-09-16 |
||||
|
||||
<a name="v2.3.2"></a> |
||||
## [v2.3.2] - 2022-09-09 |
||||
|
||||
<a name="v2.3.0-rc0"></a> |
||||
## [v2.3.0-rc0] - 2022-07-15 |
||||
|
||||
<a name="v2.2.0"></a> |
||||
## [v2.2.0] - 2022-07-01 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
<a name="v2.1.0"></a> |
||||
## [v2.1.0] - 2022-06-17 |
||||
|
||||
<a name="v2.0.9"></a> |
||||
## [v2.0.9] - 2022-05-11 |
||||
|
||||
<a name="v2.0.8"></a> |
||||
## [v2.0.8] - 2022-04-29 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
<a name="v2.0.7"></a> |
||||
## [v2.0.7] - 2022-04-22 |
||||
|
||||
<a name="v2.0.6"></a> |
||||
## [v2.0.6] - 2022-04-08 |
||||
|
||||
<a name="v2.0.5"></a> |
||||
## [v2.0.5] - 2022-04-01 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
<a name="v2.0.4"></a> |
||||
## [v2.0.4] - 2022-03-25 |
||||
|
||||
<a name="v2.0.3"></a> |
||||
## [v2.0.3] - 2022-03-18 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
<a name="v2.0.2"></a> |
||||
## [v2.0.2] - 2022-02-24 |
||||
|
||||
<a name="v2.0.1"></a> |
||||
## [v2.0.1] - 2022-02-24 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
<a name="v2.0.0"></a> |
||||
## v2.0.0 - 2022-02-23 |
||||
|
||||
[Unreleased]: https://github.com/openimsdk/openim-sdk-core/compare/v2.3.3...HEAD |
||||
[v2.3.3]: https://github.com/openimsdk/openim-sdk-core/compare/v2.3.2...v2.3.3 |
||||
[v2.3.2]: https://github.com/openimsdk/openim-sdk-core/compare/v2.3.0-rc0...v2.3.2 |
||||
[v2.3.0-rc0]: https://github.com/openimsdk/openim-sdk-core/compare/v2.2.0...v2.3.0-rc0 |
||||
[v2.2.0]: https://github.com/openimsdk/openim-sdk-core/compare/v2.1.0...v2.2.0 |
||||
[v2.1.0]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.9...v2.1.0 |
||||
[v2.0.9]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.8...v2.0.9 |
||||
[v2.0.8]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.7...v2.0.8 |
||||
[v2.0.7]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.6...v2.0.7 |
||||
[v2.0.6]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.5...v2.0.6 |
||||
[v2.0.5]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.4...v2.0.5 |
||||
[v2.0.4]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.3...v2.0.4 |
||||
[v2.0.3]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.2...v2.0.3 |
||||
[v2.0.2]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.1...v2.0.2 |
||||
[v2.0.1]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.0...v2.0.1 |
@ -0,0 +1,23 @@ |
||||
# Version logging for OpenIM |
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_TOC --> |
||||
|
||||
<!-- END MUNGE: GENERATED_TOC --> |
||||
|
||||
<a name="unreleased"></a> |
||||
## [Unreleased] |
||||
|
||||
|
||||
<a name="v2.1.0"></a> |
||||
## v2.1.0 - 2022-06-17 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
[Unreleased]: https://github.com/openimsdk/openim-sdk-core/compare/v2.1.0...HEAD |
@ -0,0 +1,24 @@ |
||||
# Version logging for OpenIM |
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_TOC --> |
||||
|
||||
<!-- END MUNGE: GENERATED_TOC --> |
||||
|
||||
<a name="unreleased"></a> |
||||
## [Unreleased] |
||||
|
||||
|
||||
<a name="v2.2.0"></a> |
||||
## v2.2.0 - 2022-07-01 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
[Unreleased]: https://github.com/openimsdk/openim-sdk-core/compare/v2.2.0...HEAD |
@ -0,0 +1,32 @@ |
||||
# Version logging for OpenIM |
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_TOC --> |
||||
|
||||
<!-- END MUNGE: GENERATED_TOC --> |
||||
|
||||
<a name="unreleased"></a> |
||||
## [Unreleased] |
||||
|
||||
|
||||
<a name="v2.3.3"></a> |
||||
## [v2.3.3] - 2022-09-16 |
||||
|
||||
<a name="v2.3.2"></a> |
||||
## [v2.3.2] - 2022-09-09 |
||||
|
||||
<a name="v2.3.0-rc0"></a> |
||||
## v2.3.0-rc0 - 2022-07-15 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
[Unreleased]: https://github.com/openimsdk/openim-sdk-core/compare/v2.3.3...HEAD |
||||
[v2.3.3]: https://github.com/openimsdk/openim-sdk-core/compare/v2.3.2...v2.3.3 |
||||
[v2.3.2]: https://github.com/openimsdk/openim-sdk-core/compare/v2.3.0-rc0...v2.3.2 |
@ -0,0 +1,52 @@ |
||||
# Version logging for OpenIM |
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_TOC --> |
||||
|
||||
- [Version logging for OpenIM](#version-logging-for-openim) |
||||
- [Unreleased](#unreleased) |
||||
- [v2.9.0+1.839643f - 2023-07-07](#v2901839643f---2023-07-07) |
||||
- [v2.9.0+2.35f07fe - 2023-07-06](#v290235f07fe---2023-07-06) |
||||
- [v2.9.0+1.b5072b1 - 2023-07-05](#v2901b5072b1---2023-07-05) |
||||
- [v2.9.0+3.2667a3a - 2023-07-05](#v29032667a3a---2023-07-05) |
||||
- [v2.9.0+7.04818ca - 2023-07-05](#v290704818ca---2023-07-05) |
||||
- [v2.9.0 - 2023-07-04](#v290---2023-07-04) |
||||
- [Reverts](#reverts) |
||||
- [Pull Requests](#pull-requests) |
||||
|
||||
|
||||
<!-- END MUNGE: GENERATED_TOC --> |
||||
|
||||
<a name="unreleased"></a> |
||||
## [Unreleased] |
||||
|
||||
|
||||
<a name="v2.9.0+1.839643f"></a> |
||||
## [v2.9.0+1.839643f] - 2023-07-07 |
||||
|
||||
<a name="v2.9.0+2.35f07fe"></a> |
||||
## [v2.9.0+2.35f07fe] - 2023-07-06 |
||||
|
||||
<a name="v2.9.0+1.b5072b1"></a> |
||||
## [v2.9.0+1.b5072b1] - 2023-07-05 |
||||
|
||||
<a name="v2.9.0+3.2667a3a"></a> |
||||
## [v2.9.0+3.2667a3a] - 2023-07-05 |
||||
|
||||
<a name="v2.9.0+7.04818ca"></a> |
||||
## [v2.9.0+7.04818ca] - 2023-07-05 |
||||
|
||||
<a name="v2.9.0"></a> |
||||
## v2.9.0 - 2023-07-04 |
||||
### Reverts |
||||
- update etcd to v3.5.2 ([#206](https://github.com/openimsdk/Open-IM-Server/issues/206)) |
||||
|
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
[Unreleased]: https://github.com/openimsdk/Open-IM-Server/compare/v2.9.0+1.839643f...HEAD |
||||
[v2.9.0+1.839643f]: https://github.com/openimsdk/Open-IM-Server/compare/v2.9.0+2.35f07fe...v2.9.0+1.839643f |
||||
[v2.9.0+2.35f07fe]: https://github.com/openimsdk/Open-IM-Server/compare/v2.9.0+1.b5072b1...v2.9.0+2.35f07fe |
||||
[v2.9.0+1.b5072b1]: https://github.com/openimsdk/Open-IM-Server/compare/v2.9.0+3.2667a3a...v2.9.0+1.b5072b1 |
||||
[v2.9.0+3.2667a3a]: https://github.com/openimsdk/Open-IM-Server/compare/v2.9.0+7.04818ca...v2.9.0+3.2667a3a |
||||
[v2.9.0+7.04818ca]: https://github.com/openimsdk/Open-IM-Server/compare/v2.9.0...v2.9.0+7.04818ca |
@ -0,0 +1,128 @@ |
||||
# Version logging for OpenIM |
||||
|
||||
<!-- BEGIN MUNGE: GENERATED_TOC --> |
||||
|
||||
<!-- END MUNGE: GENERATED_TOC --> |
||||
|
||||
<a name="unreleased"></a> |
||||
## [Unreleased] |
||||
|
||||
|
||||
<a name="v3.0.0-rc.1"></a> |
||||
## [v3.0.0-rc.1] - 2023-07-13 |
||||
|
||||
<a name="v2.3.3"></a> |
||||
## [v2.3.3] - 2022-09-16 |
||||
|
||||
<a name="v2.3.2"></a> |
||||
## [v2.3.2] - 2022-09-09 |
||||
|
||||
<a name="v2.3.0-rc0"></a> |
||||
## [v2.3.0-rc0] - 2022-07-15 |
||||
|
||||
<a name="v2.2.0"></a> |
||||
## [v2.2.0] - 2022-07-01 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
<a name="v2.1.0"></a> |
||||
## [v2.1.0] - 2022-06-17 |
||||
|
||||
<a name="v2.0.9"></a> |
||||
## [v2.0.9] - 2022-05-11 |
||||
|
||||
<a name="v2.0.8"></a> |
||||
## [v2.0.8] - 2022-04-29 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
<a name="v2.0.7"></a> |
||||
## [v2.0.7] - 2022-04-22 |
||||
|
||||
<a name="v2.0.6"></a> |
||||
## [v2.0.6] - 2022-04-08 |
||||
|
||||
<a name="v2.0.5"></a> |
||||
## [v2.0.5] - 2022-04-01 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
<a name="v2.0.4"></a> |
||||
## [v2.0.4] - 2022-03-25 |
||||
|
||||
<a name="v2.0.3"></a> |
||||
## [v2.0.3] - 2022-03-18 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
<a name="v2.0.2"></a> |
||||
## [v2.0.2] - 2022-02-24 |
||||
|
||||
<a name="v2.0.1"></a> |
||||
## [v2.0.1] - 2022-02-24 |
||||
### Pull Requests |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
- Merge branch 'tuoyun' |
||||
|
||||
|
||||
<a name="v2.0.0"></a> |
||||
## [v2.0.0] - 2022-02-23 |
||||
|
||||
<a name="v1.0.7"></a> |
||||
## [v1.0.7] - 2021-12-10 |
||||
|
||||
<a name="v1.0.6"></a> |
||||
## [v1.0.6] - 2021-12-03 |
||||
|
||||
<a name="v1.0.5"></a> |
||||
## [v1.0.5] - 2021-11-25 |
||||
|
||||
<a name="v1.0.4"></a> |
||||
## [v1.0.4] - 2021-11-12 |
||||
|
||||
<a name="v1.0.3"></a> |
||||
## [v1.0.3] - 2021-11-05 |
||||
|
||||
<a name="v1.0.2"></a> |
||||
## [v1.0.2] - 2021-11-04 |
||||
|
||||
<a name="v1.0.1"></a> |
||||
## [v1.0.1] - 2021-11-03 |
||||
|
||||
<a name="v1.0.0"></a> |
||||
## v1.0.0 - 2021-10-28 |
||||
|
||||
[Unreleased]: https://github.com/openimsdk/openim-sdk-core/compare/v3.0.0-rc.1...HEAD |
||||
[v3.0.0-rc.1]: https://github.com/openimsdk/openim-sdk-core/compare/v2.3.3...v3.0.0-rc.1 |
||||
[v2.3.3]: https://github.com/openimsdk/openim-sdk-core/compare/v2.3.2...v2.3.3 |
||||
[v2.3.2]: https://github.com/openimsdk/openim-sdk-core/compare/v2.3.0-rc0...v2.3.2 |
||||
[v2.3.0-rc0]: https://github.com/openimsdk/openim-sdk-core/compare/v2.2.0...v2.3.0-rc0 |
||||
[v2.2.0]: https://github.com/openimsdk/openim-sdk-core/compare/v2.1.0...v2.2.0 |
||||
[v2.1.0]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.9...v2.1.0 |
||||
[v2.0.9]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.8...v2.0.9 |
||||
[v2.0.8]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.7...v2.0.8 |
||||
[v2.0.7]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.6...v2.0.7 |
||||
[v2.0.6]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.5...v2.0.6 |
||||
[v2.0.5]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.4...v2.0.5 |
||||
[v2.0.4]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.3...v2.0.4 |
||||
[v2.0.3]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.2...v2.0.3 |
||||
[v2.0.2]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.1...v2.0.2 |
||||
[v2.0.1]: https://github.com/openimsdk/openim-sdk-core/compare/v2.0.0...v2.0.1 |
||||
[v2.0.0]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.7...v2.0.0 |
||||
[v1.0.7]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.6...v1.0.7 |
||||
[v1.0.6]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.5...v1.0.6 |
||||
[v1.0.5]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.4...v1.0.5 |
||||
[v1.0.4]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.3...v1.0.4 |
||||
[v1.0.3]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.2...v1.0.3 |
||||
[v1.0.2]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.1...v1.0.2 |
||||
[v1.0.1]: https://github.com/openimsdk/openim-sdk-core/compare/v1.0.0...v1.0.1 |
@ -0,0 +1,104 @@ |
||||
# Changelog |
||||
|
||||
- [Changelog](#changelog) |
||||
- [command](#command) |
||||
- [create next tag](#create-next-tag) |
||||
- [Release version logs](#release-version-logs) |
||||
- [Introduction](#introduction) |
||||
- [Naming Format](#naming-format) |
||||
- [Examples](#examples) |
||||
- [Version Modifiers](#version-modifiers) |
||||
- [Versioning Strategy](#versioning-strategy) |
||||
|
||||
|
||||
All notable changes to this project will be documented in this file. |
||||
|
||||
+ [https://github.com/openimsdk/Open-IM-Server/releases](https://github.com/openimsdk/Open-IM-Server/releases) |
||||
|
||||
## command |
||||
|
||||
```bash |
||||
git-chglog --tag-filter-pattern 'v2.0.*' -o CHANGELOG-2.0.md |
||||
``` |
||||
|
||||
## create next tag |
||||
|
||||
```bash |
||||
git-chglog --next-tag 2.0.0 -o CHANGELOG.md |
||||
git commit -am "release 2.0.0" |
||||
git tag 2.0.0 |
||||
``` |
||||
|
||||
| Query | Description | Example | |
||||
| -------------- | ---------------------------------------------- | --------------------------- | |
||||
| `<old>..<new>` | Commit contained in `<new>` tags from `<old>`. | `$ git-chglog 1.0.0..2.0.0` | |
||||
| `<name>..` | Commit from the `<name>` to the latest tag. | `$ git-chglog 1.0.0..` | |
||||
| `..<name>` | Commit from the oldest tag to `<name>`. | `$ git-chglog ..2.0.0` | |
||||
| `<name>` | Commit contained in `<name>`. | `$ git-chglog 1.0.0` | |
||||
|
||||
|
||||
## Release version logs |
||||
|
||||
+ [OpenIM CHANGELOG-V1.0](CHANGELOG-1.0.md) |
||||
+ [OpenIM CHANGELOG-V2.0](CHANGELOG-2.0.md) |
||||
+ [OpenIM CHANGELOG-V2.1](CHANGELOG-2.1.md) |
||||
+ [OpenIM CHANGELOG-V2.2](CHANGELOG-2.2.md) |
||||
+ [OpenIM CHANGELOG-V2.3](CHANGELOG-2.3.md) |
||||
+ [OpenIM CHANGELOG-V2.9](CHANGELOG-2.9.md) |
||||
+ [OpenIM CHANGELOG-V3.0](CHANGELOG-3.0.md) |
||||
|
||||
## Introduction |
||||
|
||||
In both the open-source and closed-source software development communities, it is important to follow a consistent and understandable versioning scheme for software projects. This ensures clear communication of changes, compatibility, and stability across different releases. One widely adopted naming convention is the Semantic Versioning 2.0.0. |
||||
|
||||
## Naming Format |
||||
|
||||
The most common format for version numbers is as follows: |
||||
|
||||
``` |
||||
major.minor[.patch[.build]] |
||||
``` |
||||
|
||||
Let's take a closer look at each component: |
||||
|
||||
1. **Major Version**: This is the first number in the versioning scheme and indicates significant changes that may not be backward compatible (specific to each project). |
||||
2. **Minor Version**: The second number signifies the addition of new features while maintaining backward compatibility. |
||||
3. **Patch Version**: The third number represents bug fixes or code optimizations without introducing new features. It is generally backward compatible. |
||||
4. **Build Version**: Typically an automatically generated number that increments with each code commit. |
||||
|
||||
## Examples |
||||
|
||||
Here are a few examples to illustrate the versioning scheme: |
||||
|
||||
1. `1.0` |
||||
2. `2.14.0.1478` |
||||
3. `3.2.1 build-354` |
||||
|
||||
## Version Modifiers |
||||
|
||||
Apart from the version numbers, there are also version modifiers used to indicate specific stages or statuses of a release. Some commonly used version modifiers include: |
||||
|
||||
- **alpha**: An internal testing version with numerous known bugs. It is primarily used for communication among developers. |
||||
- **beta**: A testing version released to enthusiastic users for feedback and bug detection. |
||||
- **rc (release candidate)**: The final testing version before the official release. |
||||
- **ga (general availability)**: The initial stable release for public distribution. |
||||
- **r/release** (or no modifier at all): The final released version intended for general users. |
||||
- **lts (long-term support)**: Designates a version that will receive extended maintenance and bug fixes for a specified number of years. |
||||
|
||||
## Versioning Strategy |
||||
|
||||
To effectively manage version numbers, the following strategies are commonly employed: |
||||
|
||||
- The initial version of a project can be either `0.1` or `1.0`. |
||||
- When fixing bugs, the patch version is incremented by 1. |
||||
- When adding new features, the minor version is incremented by 1, and the patch version is reset to 0. |
||||
- In the case of significant modifications, the major version is incremented by 1. |
||||
- The build version is usually automatically generated by the compilation process and follows a defined format. It does not require manual control. |
||||
|
||||
By adhering to these strategies and guidelines, developers can maintain consistency and clarity in versioning their software projects. This enables users and collaborators to understand the nature of changes between different releases and ensure compatibility with their systems. |
||||
|
||||
(Note: Markdown formatting has been used to structure this article. Markdown is a lightweight markup language used to format text on platforms like GitHub.) |
||||
|
||||
------ |
||||
|
||||
**Note**: The above article is based on the given content and aims to provide a Markdown-formatted English article explaining the naming conventions for software project versions, specifically focusing on the Semantic Versioning 2.0.0. |
@ -0,0 +1,201 @@ |
||||
Apache License |
||||
Version 2.0, January 2004 |
||||
http://www.apache.org/licenses/ |
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||
|
||||
1. Definitions. |
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction, |
||||
and distribution as defined by Sections 1 through 9 of this document. |
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by |
||||
the copyright owner that is granting the License. |
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all |
||||
other entities that control, are controlled by, or are under common |
||||
control with that entity. For the purposes of this definition, |
||||
"control" means (i) the power, direct or indirect, to cause the |
||||
direction or management of such entity, whether by contract or |
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||
outstanding shares, or (iii) beneficial ownership of such entity. |
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity |
||||
exercising permissions granted by this License. |
||||
|
||||
"Source" form shall mean the preferred form for making modifications, |
||||
including but not limited to software source code, documentation |
||||
source, and configuration files. |
||||
|
||||
"Object" form shall mean any form resulting from mechanical |
||||
transformation or translation of a Source form, including but |
||||
not limited to compiled object code, generated documentation, |
||||
and conversions to other media types. |
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or |
||||
Object form, made available under the License, as indicated by a |
||||
copyright notice that is included in or attached to the work |
||||
(an example is provided in the Appendix below). |
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object |
||||
form, that is based on (or derived from) the Work and for which the |
||||
editorial revisions, annotations, elaborations, or other modifications |
||||
represent, as a whole, an original work of authorship. For the purposes |
||||
of this License, Derivative Works shall not include works that remain |
||||
separable from, or merely link (or bind by name) to the interfaces of, |
||||
the Work and Derivative Works thereof. |
||||
|
||||
"Contribution" shall mean any work of authorship, including |
||||
the original version of the Work and any modifications or additions |
||||
to that Work or Derivative Works thereof, that is intentionally |
||||
submitted to Licensor for inclusion in the Work by the copyright owner |
||||
or by an individual or Legal Entity authorized to submit on behalf of |
||||
the copyright owner. For the purposes of this definition, "submitted" |
||||
means any form of electronic, verbal, or written communication sent |
||||
to the Licensor or its representatives, including but not limited to |
||||
communication on electronic mailing lists, source code control systems, |
||||
and issue tracking systems that are managed by, or on behalf of, the |
||||
Licensor for the purpose of discussing and improving the Work, but |
||||
excluding communication that is conspicuously marked or otherwise |
||||
designated in writing by the copyright owner as "Not a Contribution." |
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||
on behalf of whom a Contribution has been received by Licensor and |
||||
subsequently incorporated within the Work. |
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
copyright license to reproduce, prepare Derivative Works of, |
||||
publicly display, publicly perform, sublicense, and distribute the |
||||
Work and such Derivative Works in Source or Object form. |
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of |
||||
this License, each Contributor hereby grants to You a perpetual, |
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||
(except as stated in this section) patent license to make, have made, |
||||
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||
where such license applies only to those patent claims licensable |
||||
by such Contributor that are necessarily infringed by their |
||||
Contribution(s) alone or by combination of their Contribution(s) |
||||
with the Work to which such Contribution(s) was submitted. If You |
||||
institute patent litigation against any entity (including a |
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||
or a Contribution incorporated within the Work constitutes direct |
||||
or contributory patent infringement, then any patent licenses |
||||
granted to You under this License for that Work shall terminate |
||||
as of the date such litigation is filed. |
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the |
||||
Work or Derivative Works thereof in any medium, with or without |
||||
modifications, and in Source or Object form, provided that You |
||||
meet the following conditions: |
||||
|
||||
(a) You must give any other recipients of the Work or |
||||
Derivative Works a copy of this License; and |
||||
|
||||
(b) You must cause any modified files to carry prominent notices |
||||
stating that You changed the files; and |
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works |
||||
that You distribute, all copyright, patent, trademark, and |
||||
attribution notices from the Source form of the Work, |
||||
excluding those notices that do not pertain to any part of |
||||
the Derivative Works; and |
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its |
||||
distribution, then any Derivative Works that You distribute must |
||||
include a readable copy of the attribution notices contained |
||||
within such NOTICE file, excluding those notices that do not |
||||
pertain to any part of the Derivative Works, in at least one |
||||
of the following places: within a NOTICE text file distributed |
||||
as part of the Derivative Works; within the Source form or |
||||
documentation, if provided along with the Derivative Works; or, |
||||
within a display generated by the Derivative Works, if and |
||||
wherever such third-party notices normally appear. The contents |
||||
of the NOTICE file are for informational purposes only and |
||||
do not modify the License. You may add Your own attribution |
||||
notices within Derivative Works that You distribute, alongside |
||||
or as an addendum to the NOTICE text from the Work, provided |
||||
that such additional attribution notices cannot be construed |
||||
as modifying the License. |
||||
|
||||
You may add Your own copyright statement to Your modifications and |
||||
may provide additional or different license terms and conditions |
||||
for use, reproduction, or distribution of Your modifications, or |
||||
for any such Derivative Works as a whole, provided Your use, |
||||
reproduction, and distribution of the Work otherwise complies with |
||||
the conditions stated in this License. |
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||
any Contribution intentionally submitted for inclusion in the Work |
||||
by You to the Licensor shall be under the terms and conditions of |
||||
this License, without any additional terms or conditions. |
||||
Notwithstanding the above, nothing herein shall supersede or modify |
||||
the terms of any separate license agreement you may have executed |
||||
with Licensor regarding such Contributions. |
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade |
||||
names, trademarks, service marks, or product names of the Licensor, |
||||
except as required for reasonable and customary use in describing the |
||||
origin of the Work and reproducing the content of the NOTICE file. |
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or |
||||
agreed to in writing, Licensor provides the Work (and each |
||||
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||
implied, including, without limitation, any warranties or conditions |
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||
appropriateness of using or redistributing the Work and assume any |
||||
risks associated with Your exercise of permissions under this License. |
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory, |
||||
whether in tort (including negligence), contract, or otherwise, |
||||
unless required by applicable law (such as deliberate and grossly |
||||
negligent acts) or agreed to in writing, shall any Contributor be |
||||
liable to You for damages, including any direct, indirect, special, |
||||
incidental, or consequential damages of any character arising as a |
||||
result of this License or out of the use or inability to use the |
||||
Work (including but not limited to damages for loss of goodwill, |
||||
work stoppage, computer failure or malfunction, or any and all |
||||
other commercial damages or losses), even if such Contributor |
||||
has been advised of the possibility of such damages. |
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing |
||||
the Work or Derivative Works thereof, You may choose to offer, |
||||
and charge a fee for, acceptance of support, warranty, indemnity, |
||||
or other liability obligations and/or rights consistent with this |
||||
License. However, in accepting such obligations, You may act only |
||||
on Your own behalf and on Your sole responsibility, not on behalf |
||||
of any other Contributor, and only if You agree to indemnify, |
||||
defend, and hold each Contributor harmless for any liability |
||||
incurred by, or claims asserted against, such Contributor by reason |
||||
of your accepting any such warranty or additional liability. |
||||
|
||||
END OF TERMS AND CONDITIONS |
||||
|
||||
APPENDIX: How to apply the Apache License to your work. |
||||
|
||||
To apply the Apache License to your work, attach the following |
||||
boilerplate notice, with the fields enclosed by brackets "[]" |
||||
replaced with your own identifying information. (Don't include |
||||
the brackets!) The text should be enclosed in the appropriate |
||||
comment syntax for the file format. We also recommend that a |
||||
file or class name and description of purpose be included on the |
||||
same "printed page" as the copyright notice for easier |
||||
identification within third-party archives. |
||||
|
||||
Copyright [yyyy] [name of copyright owner] |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
@ -0,0 +1,515 @@ |
||||
# ==============================================================================
|
||||
# define the default goal
|
||||
#
|
||||
|
||||
ROOT_PACKAGE=github.com/openimsdk/Open-IM-SDK-Core
|
||||
|
||||
# Copyright 2023 OpenIM. All rights reserved.
|
||||
# Use of this source code is governed by a MIT style
|
||||
# license that can be found in the LICENSE file.
|
||||
|
||||
###################################=> common commands <=#############################################
|
||||
# ========================== Capture Environment ===============================
|
||||
# get the repo root and output path
|
||||
ROOT_PACKAGE=github.com/OpenIM/chat
|
||||
OUT_DIR=$(REPO_ROOT)/_output
|
||||
# ==============================================================================
|
||||
|
||||
# define the default goal
|
||||
#
|
||||
|
||||
SHELL := /bin/bash
|
||||
DIRS=$(shell ls)
|
||||
GO=go
|
||||
|
||||
.DEFAULT_GOAL := help
|
||||
|
||||
# include the common makefile
|
||||
COMMON_SELF_DIR := $(dir $(lastword $(MAKEFILE_LIST)))
|
||||
# ROOT_DIR: root directory of the code base
|
||||
ifeq ($(origin ROOT_DIR),undefined) |
||||
ROOT_DIR := $(abspath $(shell cd $(COMMON_SELF_DIR)/. && pwd -P))
|
||||
endif |
||||
# OUTPUT_DIR: The directory where the build output is stored.
|
||||
ifeq ($(origin OUTPUT_DIR),undefined) |
||||
OUTPUT_DIR := $(ROOT_DIR)/_output
|
||||
$(shell mkdir -p $(OUTPUT_DIR)) |
||||
endif |
||||
|
||||
# BIN_DIR: The directory where the build output is stored.
|
||||
ifeq ($(origin BIN_DIR),undefined) |
||||
BIN_DIR := $(OUTPUT_DIR)/bin
|
||||
$(shell mkdir -p $(BIN_DIR)) |
||||
endif |
||||
|
||||
ifeq ($(origin TOOLS_DIR),undefined) |
||||
TOOLS_DIR := $(OUTPUT_DIR)/tools
|
||||
$(shell mkdir -p $(TOOLS_DIR)) |
||||
endif |
||||
|
||||
ifeq ($(origin TMP_DIR),undefined) |
||||
TMP_DIR := $(OUTPUT_DIR)/tmp
|
||||
$(shell mkdir -p $(TMP_DIR)) |
||||
endif |
||||
|
||||
ifeq ($(origin VERSION), undefined) |
||||
VERSION := $(shell git describe --tags --always --match="v*" --dirty | sed 's/-/./g') #v2.3.3.631.g00abdc9b.dirty
|
||||
endif |
||||
|
||||
# Check if the tree is dirty. default to dirty(maybe u should commit?)
|
||||
GIT_TREE_STATE:="dirty"
|
||||
ifeq (, $(shell git status --porcelain 2>/dev/null)) |
||||
GIT_TREE_STATE="clean"
|
||||
endif |
||||
GIT_COMMIT:=$(shell git rev-parse HEAD)
|
||||
|
||||
IMG ?= openim_chat:latest
|
||||
|
||||
BUILDFILE = "./main.go"
|
||||
BUILDAPP = "$(OUTPUT_DIR)/"
|
||||
|
||||
# Define the directory you want to copyright
|
||||
CODE_DIRS := $(ROOT_DIR)/ #$(ROOT_DIR)/pkg $(ROOT_DIR)/core $(ROOT_DIR)/integrationtest $(ROOT_DIR)/lib $(ROOT_DIR)/mock $(ROOT_DIR)/db $(ROOT_DIR)/openapi
|
||||
FINDS := find $(CODE_DIRS)
|
||||
|
||||
ifndef V |
||||
MAKEFLAGS += --no-print-directory
|
||||
endif |
||||
|
||||
# The OS must be linux when building docker images
|
||||
# !WARNING: linux_mips64 linux_mips64le
|
||||
PLATFORMS ?= linux_s390x darwin_amd64 windows_amd64 linux_amd64 linux_arm64 linux_ppc64le
|
||||
# The OS can be linux/windows/darwin when building binaries
|
||||
# PLATFORMS ?= darwin_amd64 windows_amd64 linux_amd64 linux_arm64
|
||||
|
||||
# Set a specific PLATFORM
|
||||
ifeq ($(origin PLATFORM), undefined) |
||||
ifeq ($(origin GOOS), undefined)
|
||||
GOOS := $(shell go env GOOS)
|
||||
endif
|
||||
ifeq ($(origin GOARCH), undefined)
|
||||
GOARCH := $(shell go env GOARCH)
|
||||
endif
|
||||
PLATFORM := $(GOOS)_$(GOARCH)
|
||||
# Use linux as the default OS when building images
|
||||
IMAGE_PLAT := linux_$(GOARCH)
|
||||
else |
||||
GOOS := $(word 1, $(subst _, ,$(PLATFORM)))
|
||||
GOARCH := $(word 2, $(subst _, ,$(PLATFORM)))
|
||||
IMAGE_PLAT := $(PLATFORM)
|
||||
endif |
||||
|
||||
# Copy githook scripts when execute makefile
|
||||
# TODO! GIT_FILE_SIZE_LIMIT=42000000 git commit -m "This commit is allowed file sizes up to 42MB"
|
||||
COPY_GITHOOK:=$(shell cp -f scripts/githooks/* .git/hooks/; chmod +x .git/hooks/*)
|
||||
|
||||
# Linux command settings
|
||||
FIND := find . ! -path './image/*' ! -path './vendor/*' ! -path './bin/*'
|
||||
XARGS := xargs -r
|
||||
|
||||
# ==============================================================================
|
||||
# TODO: License selection
|
||||
# LICENSE_TEMPLATE ?= $(ROOT_DIR)/scripts/LICENSE/license_templates.txt # MIT License
|
||||
LICENSE_TEMPLATE ?= $(ROOT_DIR)/scripts/LICENSE/LICENSE_TEMPLATES # Apache License
|
||||
|
||||
# COMMA: Concatenate multiple strings to form a list of strings
|
||||
COMMA := ,
|
||||
# SPACE: Used to separate strings
|
||||
SPACE :=
|
||||
# SPACE: Replace multiple consecutive Spaces with a single space
|
||||
SPACE +=
|
||||
|
||||
# ==============================================================================
|
||||
# Build definition
|
||||
|
||||
GO_SUPPORTED_VERSIONS ?= 1.18|1.19|1.20|1.21
|
||||
GO_LDFLAGS += -X $(VERSION_PACKAGE).GitVersion=$(VERSION) \
|
||||
-X $(VERSION_PACKAGE).GitCommit=$(GIT_COMMIT) \
|
||||
-X $(VERSION_PACKAGE).GitTreeState=$(GIT_TREE_STATE) \
|
||||
-X $(VERSION_PACKAGE).BuildDate=$(shell date -u +'%Y-%m-%dT%H:%M:%SZ')
|
||||
ifneq ($(DLV),) |
||||
GO_BUILD_FLAGS += -gcflags "all=-N -l"
|
||||
LDFLAGS = ""
|
||||
endif |
||||
GO_BUILD_FLAGS += -ldflags "$(GO_LDFLAGS)"
|
||||
|
||||
ifeq ($(GOOS),windows) |
||||
GO_OUT_EXT := .exe
|
||||
endif |
||||
|
||||
ifeq ($(ROOT_PACKAGE),) |
||||
$(error the variable ROOT_PACKAGE must be set prior to including golang.mk)
|
||||
endif |
||||
|
||||
GOPATH := $(shell go env GOPATH)
|
||||
ifeq ($(origin GOBIN), undefined) |
||||
GOBIN := $(GOPATH)/bin
|
||||
endif |
||||
|
||||
COMMANDS ?= $(filter-out %.md, $(wildcard ${ROOT_DIR}/cmd/*))
|
||||
BINS ?= $(foreach cmd,${COMMANDS},$(notdir ${cmd}))
|
||||
|
||||
ifeq (${COMMANDS},) |
||||
$(error Could not determine COMMANDS, set ROOT_DIR or run in source dir)
|
||||
endif |
||||
ifeq (${BINS},) |
||||
$(error Could not determine BINS, set ROOT_DIR or run in source dir)
|
||||
endif |
||||
|
||||
EXCLUDE_TESTS=github.com/openimsdk/openim-sdk-core/test
|
||||
|
||||
# ==============================================================================
|
||||
# Build
|
||||
|
||||
## all: Build all the necessary targets.
|
||||
.PHONY: all |
||||
all: copyright-verify build # tidy lint cover
|
||||
|
||||
# Define available OS and ARCH
|
||||
OSES = linux
|
||||
ARCHS = amd64 arm64
|
||||
|
||||
ifeq ($(ARCH),arm64) |
||||
export CC=aarch64-linux-gnu-gcc
|
||||
export CXX=aarch64-linux-gnu-g++
|
||||
endif |
||||
|
||||
# Set default OS and ARCH (e.g., current platform)
|
||||
OS ?= $(shell go env GOOS)
|
||||
ARCH ?= $(shell go env GOARCH)
|
||||
BIN_DIR ?= ./_output/bin
|
||||
TARGET ?= ./cmd/main.go
|
||||
|
||||
## build: Build for current platform by default
|
||||
.PHONY: build |
||||
build: |
||||
@echo "===========> Building for $(OS)/$(ARCH)"
|
||||
@CGO_ENABLED=1 GOOS=$(OS) GOARCH=$(ARCH) go build -o $(BIN_DIR)/openim-sdk-core-$(OS)-$(ARCH) $(TARGET)
|
||||
|
||||
# sudo apt-get install gcc-aarch64-linux-gnu
|
||||
## build-multiple: Build for all supported platforms
|
||||
.PHONY: build-multiple |
||||
build-multiple: |
||||
@for os in $(OSES); do \
|
||||
for arch in $(ARCHS); do \
|
||||
$(MAKE) build OS=$$os ARCH=$$arch; \
|
||||
done \
|
||||
done
|
||||
|
||||
|
||||
.PHONY: build-wasm |
||||
build-wasm: |
||||
GOOS=js GOARCH=wasm go build -trimpath -ldflags "-s -w" -o ${BIN_DIR}/openIM.wasm wasm/cmd/main.go
|
||||
|
||||
## install: Install the binary to the BIN_DIR
|
||||
.PHONY: install |
||||
install: build |
||||
mv ${BINARY_NAME} ${BIN_DIR}
|
||||
|
||||
## reset_remote_branch: Reset the remote branch
|
||||
.PHONY: reset_remote_branch |
||||
reset_remote_branch: |
||||
remote_branch=$(shell git rev-parse --abbrev-ref --symbolic-full-name @{u})
|
||||
git reset --hard $(remote_branch)
|
||||
git pull $(remote_branch)
|
||||
|
||||
## ios: Build the iOS framework
|
||||
.PHONY: ios |
||||
ios: |
||||
go get golang.org/x/mobile
|
||||
rm -rf build/ open_im_sdk/t_friend_sdk.go open_im_sdk/t_group_sdk.go open_im_sdk/ws_wrapper/
|
||||
GOARCH=arm64 gomobile bind -v -trimpath -ldflags "-s -w" -o build/OpenIMCore.xcframework -target=ios ./open_im_sdk/ ./open_im_sdk_callback/
|
||||
|
||||
## android: Build the Android library
|
||||
# Note: to build an AAR on Windows, gomobile, Android Studio, and the NDK must be installed.
|
||||
# The NDK version tested by the OpenIM team was r20b.
|
||||
# To build an AAR on Mac, gomobile, Android Studio, and the NDK version 20.0.5594570 must be installed.
|
||||
.PHONY: android |
||||
android: |
||||
go get golang.org/x/mobile/bind
|
||||
GOARCH=amd64 gomobile bind -v -trimpath -ldflags="-s -w" -o ./open_im_sdk.aar -target=android ./open_im_sdk/ ./open_im_sdk_callback/
|
||||
|
||||
# Targets
|
||||
.PHONY: release |
||||
release: release.verify release.ensure-tag |
||||
@scripts/release.sh
|
||||
|
||||
.PHONY: install.gsemver |
||||
release.verify: install.git-chglog install.github-release install.coscmd |
||||
|
||||
.PHONY: release.tag |
||||
release.tag: install.gsemver release.ensure-tag |
||||
@git push origin `git describe --tags --abbrev=0`
|
||||
|
||||
.PHONY: release.ensure-tag |
||||
release.ensure-tag: install.gsemver |
||||
@scripts/ensure_tag.sh
|
||||
|
||||
## tidy: tidy go.mod
|
||||
.PHONY: tidy |
||||
tidy: |
||||
@$(GO) mod tidy
|
||||
|
||||
## style: Code style -> fmt,vet,lint
|
||||
.PHONY: style |
||||
style: fmt vet lint |
||||
|
||||
## fmt: Run go fmt against code.
|
||||
.PHONY: fmt |
||||
fmt: |
||||
@$(GO) fmt ./...
|
||||
|
||||
## vet: Run go vet against code.
|
||||
.PHONY: vet |
||||
vet: |
||||
@$(GO) vet ./...
|
||||
|
||||
## generate: Run go generate against code.
|
||||
.PHONY: generate |
||||
generate: |
||||
@$(GO) generate ./...
|
||||
|
||||
## lint: Run go lint against code.
|
||||
.PHONY: lint |
||||
lint: tools.verify.golangci-lint |
||||
@echo "===========> Run golangci to lint source codes"
|
||||
@$(TOOLS_DIR)/golangci-lint run -c $(ROOT_DIR)/.golangci.yml $(ROOT_DIR)/...
|
||||
|
||||
## test: Run unit test
|
||||
.PHONY: test |
||||
test: |
||||
@$(GO) test ./...
|
||||
|
||||
## cover: Run unit test with coverage.
|
||||
.PHONY: cover |
||||
cover: test |
||||
@$(GO) test -cover
|
||||
|
||||
## docker-build: Build docker image with the manager.
|
||||
.PHONY: docker-build |
||||
docker-build: |
||||
docker build -t ${IMG} .
|
||||
|
||||
## docker-push: Push docker image with the manager.
|
||||
.PHONY: docker-push |
||||
docker-push: |
||||
docker push ${IMG}
|
||||
|
||||
## docker-buildx-push: Push docker image with the manager using buildx.
|
||||
.PHONY: docker-buildx-push |
||||
docker-buildx-push: |
||||
docker buildx build --platform linux/arm64,linux/amd64 -t ${IMG} . --push
|
||||
|
||||
## copyright-verify: Validate boilerplate headers for assign files.
|
||||
.PHONY: copyright-verify |
||||
copyright-verify: tools.verify.addlicense copyright-add |
||||
@echo "===========> Validate boilerplate headers for assign files starting in the $(ROOT_DIR) directory"
|
||||
@$(TOOLS_DIR)/addlicense -v -check -ignore **/test/** -f $(LICENSE_TEMPLATE) $(CODE_DIRS)
|
||||
@echo "===========> End of boilerplate headers check..."
|
||||
|
||||
## copyright-add: Add the boilerplate headers for all files.
|
||||
.PHONY: copyright-add |
||||
copyright-add: tools.verify.addlicense |
||||
@echo "===========> Adding $(LICENSE_TEMPLATE) the boilerplate headers for all files"
|
||||
@$(TOOLS_DIR)/addlicense -y $(shell date +"%Y") -v -c "OpenIM open source community." -f $(LICENSE_TEMPLATE) $(CODE_DIRS)
|
||||
@echo "===========> End the copyright is added..."
|
||||
|
||||
## clean: Clean all builds.
|
||||
.PHONY: clean |
||||
clean: |
||||
@echo "===========> Cleaning all builds TMP_DIR($(TMP_DIR)) AND BIN_DIR($(BIN_DIR))"
|
||||
@-rm -vrf $(TMP_DIR) $(BIN_DIR)
|
||||
@echo "===========> End clean..."
|
||||
|
||||
## help: Show this help info.
|
||||
.PHONY: help |
||||
help: Makefile |
||||
@printf "\n\033[1mUsage: make <TARGETS> ...\033[0m\n\n\\033[1mTargets:\\033[0m\n\n"
|
||||
@sed -n 's/^##//p' $< | awk -F':' '{printf "\033[36m%-28s\033[0m %s\n", $$1, $$2}' | sed -e 's/^/ /'
|
||||
|
||||
######################################=> common tools<= ############################################
|
||||
# tools
|
||||
|
||||
BUILD_TOOLS ?= go-gitlint golangci-lint goimports addlicense deepcopy-gen conversion-gen ginkgo go-junit-report
|
||||
|
||||
## tools.verify.%: Check if a tool is installed and install it
|
||||
.PHONY: tools.verify.% |
||||
tools.verify.%: |
||||
@echo "===========> Verifying $* is installed"
|
||||
@if [ ! -f $(TOOLS_DIR)/$* ]; then GOBIN=$(TOOLS_DIR) $(MAKE) tools.install.$*; fi
|
||||
@echo "===========> $* is install in $(TOOLS_DIR)/$*"
|
||||
|
||||
# tools: Install a must tools
|
||||
.PHONY: tools |
||||
tools: $(addprefix tools.verify., $(BUILD_TOOLS)) |
||||
|
||||
# tools.install.%: Install a single tool in $GOBIN/
|
||||
.PHONY: tools.install.% |
||||
tools.install.%: |
||||
@echo "===========> Installing $,The default installation path is $(GOBIN)/$*"
|
||||
@$(MAKE) install.$*
|
||||
|
||||
.PHONY: install.golangci-lint |
||||
install.golangci-lint: |
||||
@$(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@latest
|
||||
|
||||
.PHONY: install.goimports |
||||
install.goimports: |
||||
@$(GO) install golang.org/x/tools/cmd/goimports@latest
|
||||
|
||||
.PHONY: install.addlicense |
||||
install.addlicense: |
||||
@$(GO) install github.com/google/addlicense@latest
|
||||
|
||||
.PHONY: install.deepcopy-gen |
||||
install.deepcopy-gen: |
||||
@$(GO) install k8s.io/code-generator/cmd/deepcopy-gen@latest
|
||||
|
||||
.PHONY: install.conversion-gen |
||||
install.conversion-gen: |
||||
@$(GO) install k8s.io/code-generator/cmd/conversion-gen@latest
|
||||
|
||||
.PHONY: install.ginkgo |
||||
install.ginkgo: |
||||
@$(GO) install github.com/onsi/ginkgo/ginkgo@v1.16.2
|
||||
|
||||
.PHONY: install.go-gitlint |
||||
# wget -P _output/tools/ https://openim-1306374445.cos.ap-guangzhou.myqcloud.com/openim/tools/go-gitlint
|
||||
# go install github.com/antham/go-gitlint/cmd/gitlint@latest
|
||||
install.go-gitlint: |
||||
@wget -q https://openim-1306374445.cos.ap-guangzhou.myqcloud.com/openim/tools/go-gitlint -O ${TOOLS_DIR}/go-gitlint
|
||||
@chmod +x ${TOOLS_DIR}/go-gitlint
|
||||
|
||||
.PHONY: install.go-junit-report |
||||
install.go-junit-report: |
||||
@$(GO) install github.com/jstemmer/go-junit-report@latest
|
||||
|
||||
# ==============================================================================
|
||||
# Tools that might be used include go gvm, cos
|
||||
#
|
||||
|
||||
## install.kube-score: Install kube-score, used to check kubernetes yaml files
|
||||
.PHONY: install.kube-score |
||||
install.kube-score: |
||||
@$(GO) install github.com/zegl/kube-score/cmd/kube-score@latest
|
||||
|
||||
## install.kubeconform: Install kubeconform, used to check kubernetes yaml files
|
||||
.PHONY: install.kubeconform |
||||
install.kubeconform: |
||||
@$(GO) install github.com/yannh/kubeconform/cmd/kubeconform@latest
|
||||
|
||||
## install.gsemver: Install gsemver, used to generate semver
|
||||
.PHONY: install.gsemver |
||||
install.gsemver: |
||||
@$(GO) install github.com/arnaud-deprez/gsemver@latest
|
||||
|
||||
## install.git-chglog: Install git-chglog, used to generate changelog
|
||||
.PHONY: install.git-chglog |
||||
install.git-chglog: |
||||
@$(GO) install github.com/git-chglog/git-chglog/cmd/git-chglog@latest
|
||||
|
||||
## install.github-release: Install github-release, used to create github release
|
||||
.PHONY: install.github-release |
||||
install.github-release: |
||||
@$(GO) install github.com/github-release/github-release@latest
|
||||
|
||||
## install.coscli: Install coscli, used to upload files to cos
|
||||
# example: ./coscli cp/sync -r /root/workspaces/kubecub/chat/ cos://kubecub-1306374445/code/ -e cos.ap-hongkong.myqcloud.com
|
||||
# https://cloud.tencent.com/document/product/436/71763
|
||||
# kubecub/*
|
||||
# - code/
|
||||
# - docs/
|
||||
# - images/
|
||||
# - scripts/
|
||||
.PHONY: install.coscli |
||||
install.coscli: |
||||
@wget -q https://github.com/tencentyun/coscli/releases/download/v0.13.0-beta/coscli-linux -O ${TOOLS_DIR}/coscli
|
||||
@chmod +x ${TOOLS_DIR}/coscli
|
||||
|
||||
## install.coscmd: Install coscmd, used to upload files to cos
|
||||
.PHONY: install.coscmd |
||||
install.coscmd: |
||||
@if which pip &>/dev/null; then pip install coscmd; else pip3 install coscmd; fi
|
||||
|
||||
## install.delve: Install delve, used to debug go program
|
||||
.PHONY: install.delve |
||||
install.delve: |
||||
@$(GO) install github.com/go-delve/delve/cmd/dlv@latest
|
||||
|
||||
## install.air: Install air, used to hot reload go program
|
||||
.PHONY: install.air |
||||
install.air: |
||||
@$(GO) install github.com/cosmtrek/air@latest
|
||||
|
||||
## install.gvm: Install gvm, gvm is a Go version manager, built on top of the official go tool.
|
||||
.PHONY: install.gvm |
||||
install.gvm: |
||||
@echo "===========> Installing gvm,The default installation path is ~/.gvm/scripts/gvm"
|
||||
@bash < <(curl -s -S -L https://raw.gitee.com/moovweb/gvm/master/binscripts/gvm-installer)
|
||||
@$(shell source /root/.gvm/scripts/gvm)
|
||||
|
||||
## install.golines: Install golines, used to format long lines
|
||||
.PHONY: install.golines |
||||
install.golines: |
||||
@$(GO) install github.com/segmentio/golines@latest
|
||||
|
||||
## install.go-mod-outdated: Install go-mod-outdated, used to check outdated dependencies
|
||||
.PHONY: install.go-mod-outdated |
||||
install.go-mod-outdated: |
||||
@$(GO) install github.com/psampaz/go-mod-outdated@latest
|
||||
|
||||
## install.mockgen: Install mockgen, used to generate mock functions
|
||||
.PHONY: install.mockgen |
||||
install.mockgen: |
||||
@$(GO) install github.com/golang/mock/mockgen@latest
|
||||
|
||||
## install.gotests: Install gotests, used to generate test functions
|
||||
.PHONY: install.gotests |
||||
install.gotests: |
||||
@$(GO) install github.com/cweill/gotests/gotests@latest
|
||||
|
||||
## install.protoc-gen-go: Install protoc-gen-go, used to generate go source files from protobuf files
|
||||
.PHONY: install.protoc-gen-go |
||||
install.protoc-gen-go: |
||||
@$(GO) install github.com/golang/protobuf/protoc-gen-go@latest
|
||||
|
||||
## install.cfssl: Install cfssl, used to generate certificates
|
||||
.PHONY: install.cfssl |
||||
install.cfssl: |
||||
@$(ROOT_DIR)/scripts/install/install.sh OpenIM::install::install_cfssl
|
||||
|
||||
## install.depth: Install depth, used to check dependency tree
|
||||
.PHONY: install.depth |
||||
install.depth: |
||||
@$(GO) install github.com/KyleBanks/depth/cmd/depth@latest
|
||||
|
||||
## install.go-callvis: Install go-callvis, used to visualize call graph
|
||||
.PHONY: install.go-callvis |
||||
install.go-callvis: |
||||
@$(GO) install github.com/ofabry/go-callvis@latest
|
||||
|
||||
## install.gothanks: Install gothanks, used to thank go dependencies
|
||||
.PHONY: install.gothanks |
||||
install.gothanks: |
||||
@$(GO) install github.com/psampaz/gothanks@latest
|
||||
|
||||
## install.richgo: Install richgo
|
||||
.PHONY: install.richgo |
||||
install.richgo: |
||||
@$(GO) install github.com/kyoh86/richgo@latest
|
||||
|
||||
## install.rts: Install rts
|
||||
.PHONY: install.rts |
||||
install.rts: |
||||
@$(GO) install github.com/galeone/rts/cmd/rts@latest
|
||||
|
||||
|
||||
## install.gomobile: Install gomobile
|
||||
.PHONY: install.gomobile |
||||
install.gomobile: |
||||
@$(GO) install golang.org/x/mobile/cmd/gomobile@latest
|
||||
|
||||
## install.gobind: Install gobind
|
||||
.PHONY: install.gobind |
||||
install.gobind: |
||||
@$(GO) install golang.org/x/mobile/cmd/gobind@latest
|
@ -0,0 +1,234 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 ( |
||||
"context" |
||||
"encoding/json" |
||||
"errors" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/network" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/server_api_params" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/openim-sdk-core/v3/test" |
||||
"github.com/openimsdk/tools/log" |
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
//APIADDR = "http://43.155.69.205:10002"
|
||||
//WSADDR = "ws://43.155.69.205:10001"
|
||||
//APIADDR = "https://chat-api-dev.opencord.so"
|
||||
//WSADDR = "wss://chat-ws-dev.opencord.so"
|
||||
APIADDR = "http://14.29.168.56:10002" |
||||
WSADDR = "ws://14.29.168.56:10001" |
||||
//APIADDR = "http://113.108.8.93:10002"
|
||||
//WSADDR = "ws://113.108.8.93:10001"
|
||||
REGISTERADDR = APIADDR + "/user_register" |
||||
ACCOUNTCHECK = APIADDR + "/manager/account_check" |
||||
TOKENADDR = APIADDR + "/auth/user_token" |
||||
SECRET = "openIM123" |
||||
//SECRET = "4zbF9Y6Fs1QJ0hsmpC3B676txZcCnjcZ"
|
||||
SENDINTERVAL = 20 |
||||
) |
||||
var ctx context.Context |
||||
|
||||
const PlatformID = 3 |
||||
|
||||
type ResToken struct { |
||||
Data struct { |
||||
ExpiredTime int64 `json:"expiredTime"` |
||||
Token string `json:"token"` |
||||
Uid string `json:"uid"` |
||||
} |
||||
ErrCode int `json:"errCode"` |
||||
ErrMsg string `json:"errMsg"` |
||||
} |
||||
|
||||
func ggetToken(uid string) string { |
||||
url := TOKENADDR |
||||
var req server_api_params.UserTokenReq |
||||
req.Platform = PlatformID |
||||
req.UserID = uid |
||||
req.Secret = SECRET |
||||
req.OperationID = utils.OperationIDGenerator() |
||||
r, err := network.Post2Api(url, req, "a") |
||||
if err != nil { |
||||
log.ZError(ctx, "Post2Api failed ", errors.New("Post2Api failed "), "operationID", req.OperationID, "url", url, "req", req) |
||||
return "" |
||||
} |
||||
var stcResp ResToken |
||||
err = json.Unmarshal(r, &stcResp) |
||||
if stcResp.ErrCode != 0 { |
||||
log.ZError(ctx, "ErrCode failed ", errors.New("ErrCode failed "), "operationID", req.OperationID, |
||||
"errorCode", stcResp.ErrCode, "errMsg", stcResp.ErrMsg, "url", url, "req", req) |
||||
return "" |
||||
} |
||||
log.ZInfo(ctx, "get token: ", "operationID", req.OperationID, "token", stcResp.Data.Token) |
||||
return stcResp.Data.Token |
||||
} |
||||
|
||||
func gRunGetToken(strMyUid string) string { |
||||
var token string |
||||
for true { |
||||
token = ggetToken(strMyUid) |
||||
if token == "" { |
||||
time.Sleep(time.Duration(100) * time.Millisecond) |
||||
continue |
||||
} else { |
||||
break |
||||
} |
||||
} |
||||
return token |
||||
} |
||||
func main() { |
||||
uid := "1695766238" |
||||
//Gordon
|
||||
//uid:="1554321956297519104"
|
||||
//Gordon2
|
||||
//uid := "1583984945064968192"
|
||||
//uid := "3734595565"
|
||||
tokenx := gRunGetToken(uid) |
||||
//tokenx := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVSUQiOiI3MDcwMDgxNTMiLCJQbGF0Zm9ybSI6IkFuZHJvaWQiLCJleHAiOjE5NjY0MTJ1XjJZGWj5fB3mqC7p6ytxSarvxZfsABwIjoxNjUxMDU1MDU2fQ.aWvmJ_sQxXmT5nKwiM5QsF9-tfkldzOYZtRD3nrUuko"
|
||||
test.InOutDoTest(uid, tokenx, WSADDR, APIADDR) |
||||
time.Sleep(time.Second * 30) |
||||
// test.DoTestSendMsg2("7789", "7788")
|
||||
//test.DoTestGetAdvancedHistoryMessageList()
|
||||
//test.DoTestGetSelfUserInfo()
|
||||
//test.DoTestSendMsg2GroupWithMessage(uid, "1623878302774460418", "2")
|
||||
//test.DoTestAddMessageReactionExtensions(1,"special handle")
|
||||
//time.Sleep(time.Second*5)
|
||||
//test.DoTestAddMessageReactionExtensions(2,"special handle")
|
||||
//time.Sleep(time.Second*5)
|
||||
//test.DoTestGetMessageListReactionExtensions("special handle")
|
||||
//test.DoTestSetAppBadge()
|
||||
//test.DoTestSearchLocalMessages()
|
||||
//test.DoTestGetAdvancedHistoryMessageList()
|
||||
println("start") |
||||
//test.DoTestGetUserInDepartment()
|
||||
//test.DoTestGetDepartmentMemberAndSubDepartment()
|
||||
//test.DoTestDeleteAllMsgFromLocalAndSvr()
|
||||
// test.DoTestGetDepartmentMemberAndSubDepartment()
|
||||
//test.DotestUploadFile()
|
||||
//test.DotestMinio()
|
||||
//test.DotestSearchFriends()
|
||||
//if *senderNum == 0 {
|
||||
// test.RegisterAccounts(*onlineNum)
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//test.OnlineTest(*onlineNum)
|
||||
////test.TestSendCostTime()
|
||||
//test.ReliabilityTest(*singleSenderMsgNum, *intervalTime, 10, *senderNum)
|
||||
//test.DoTestSearchLocalMessages()
|
||||
//println("start")
|
||||
//test.DoTestSendImageMsg(strMyUidx, "17726378428")
|
||||
//test.DoTestSearchGroups()
|
||||
//test.DoTestGetHistoryMessage("")
|
||||
//test.DoTestGetHistoryMessageReverse("")
|
||||
//test.DoTestInviteInGroup()
|
||||
//test.DoTestCancel()
|
||||
//test.DoTestSendMsg2(strMyUidx, friendID)
|
||||
//test.DoTestGetAllConversation()
|
||||
|
||||
//test.DoTestGetOneConversation("17726378428")
|
||||
//test.DoTestGetConversations(`["single_17726378428"]`)
|
||||
//test.DoTestGetConversationListSplit()
|
||||
//test.DoTestGetConversationRecvMessageOpt(`["single_17726378428"]`)
|
||||
|
||||
//set batch
|
||||
//test.DoTestSetConversationRecvMessageOpt([]string{"single_17726378428"}, constant.NotReceiveMessage)
|
||||
//set one
|
||||
////set batch
|
||||
//test.DoTestSetConversationRecvMessageOpt([]string{"single_17726378428"}, constant.ReceiveMessage)
|
||||
////set one
|
||||
//test.DoTestSetConversationPinned("single_17726378428", false)
|
||||
//test.DoTestSetOneConversationRecvMessageOpt("single_17726378428", constant.NotReceiveMessage)
|
||||
//test.DoTestSetOneConversationPrivateChat("single_17726378428", false)
|
||||
//test.DoTestReject()
|
||||
//test.DoTestAccept()
|
||||
//test.DoTestMarkGroupMessageAsRead()
|
||||
//test.DoTestGetGroupHistoryMessage()
|
||||
//test.DoTestGetHistoryMessage("17396220460")
|
||||
time.Sleep(250000 * time.Millisecond) |
||||
//b := utils.GetCurrentTimestampBySecond()
|
||||
i := 0 |
||||
for { |
||||
//test.DoTestSendMsg2Group(strMyUidx, "42c9f515cb84ee0e82b3f3ce71eb14d6", i)
|
||||
i++ |
||||
time.Sleep(250 * time.Millisecond) |
||||
//if i == 100 {
|
||||
// break
|
||||
//}
|
||||
//log.Warn("", "10 * time.Millisecond ###################waiting... msg: ", i)
|
||||
} |
||||
//
|
||||
//log.Warn("", "cost time: ", utils.GetCurrentTimestampBySecond()-b)
|
||||
//return
|
||||
//i = 0
|
||||
//for {
|
||||
// //test.DoTestSendMsg2Group(strMyUidx, "42c9f515cb84ee0e82b3f3ce71eb14d6", i)
|
||||
// i++
|
||||
// time.Sleep(1000 * time.Millisecond)
|
||||
// if i == 10 {
|
||||
// break
|
||||
// }
|
||||
// log.Warn("", "1000 * time.Millisecond ###################waiting... msg: ", i)
|
||||
//}
|
||||
//
|
||||
//i = 0
|
||||
//for {
|
||||
// test.DoTestSendMsg2Group(strMyUidx, "42c9f515cb84ee0e82b3f3ce71eb14d6", i)
|
||||
// i++
|
||||
// time.Sleep(10000 * time.Millisecond)
|
||||
// log.Warn("", "10000 * time.Millisecond ###################waiting... msg: ", i)
|
||||
//}
|
||||
|
||||
//reliabilityTest()
|
||||
// test.PressTest(testClientNum, intervalSleep, imIP)
|
||||
} |
||||
|
||||
//
|
||||
//funcation main() {
|
||||
// testClientNum := 100
|
||||
// intervalSleep := 2
|
||||
// imIP := "43.128.5.63"
|
||||
|
||||
//
|
||||
// msgNum := 1000
|
||||
// test.ReliabilityTest(msgNum, intervalSleep, imIP)
|
||||
// for i := 0; i < 6; i++ {
|
||||
// test.Msgwg.Wait()
|
||||
// }
|
||||
//
|
||||
// for {
|
||||
//
|
||||
// if test.CheckReliabilityResult() {
|
||||
// log.Warn("CheckReliabilityResult ok, again")
|
||||
//
|
||||
// } else {
|
||||
// log.Warn("CheckReliabilityResult failed , wait.... ")
|
||||
// }
|
||||
//
|
||||
// time.Sleep(time.Duration(10) * time.Second)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
//funcation printCallerNameAndLine() string {
|
||||
// pc, _, line, _ := runtime.Caller(2)
|
||||
// return runtime.FuncForPC(pc).Name() + "()@" + strconv.Itoa(line) + ": "
|
||||
//}
|
||||
|
||||
// myuid, maxuid, msgnum
|
@ -0,0 +1,61 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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" |
||||
"github.com/openimsdk/openim-sdk-core/v3/test" |
||||
"time" |
||||
) |
||||
|
||||
func main() { |
||||
APIADDR := "http://59.36.173.89:10002" |
||||
WSADDR := "ws://59.36.173.89:10001" |
||||
REGISTERADDR := APIADDR + "/user_register" |
||||
ACCOUNTCHECK := APIADDR + "/manager/account_check" |
||||
TOKENADDR := APIADDR + "/auth/user_token" |
||||
SECRET := "openIM123" |
||||
SENDINTERVAL := 20 |
||||
test.REGISTERADDR = REGISTERADDR |
||||
test.TOKENADDR = TOKENADDR |
||||
test.SECRET = SECRET |
||||
test.SENDINTERVAL = SENDINTERVAL |
||||
test.WSADDR = WSADDR |
||||
test.ACCOUNTCHECK = ACCOUNTCHECK |
||||
strMyUidx := "5284951719" |
||||
|
||||
tokenx := test.RunGetToken(strMyUidx) |
||||
fmt.Println(tokenx) |
||||
test.InOutDoTest(strMyUidx, tokenx, WSADDR, APIADDR) |
||||
time.Sleep(time.Second * 10) |
||||
// test.DoTestGetUsersInfo()
|
||||
// test.DoTestSetMsgDestructTime("sg_1012596513")
|
||||
// test.DoTestRevoke()
|
||||
// test.DotestDeleteFriend("8303492153")
|
||||
// test.TestMarkGroupMessageAsRead()
|
||||
// test.DoTestRevoke()
|
||||
// time.Sleep(time.Second * 5)
|
||||
// test.DoTestAddToBlackList("9169012630")
|
||||
// test.DoTestDeleteFromBlackList("9169012630")
|
||||
// test.DotestDeleteFriend("9169012630")
|
||||
// test.DoTestSetConversationPinned("si_2456093263_9169012630", true)
|
||||
// test.DoTestSetOneConversationRecvMessageOpt("si_2456093263_9169012630", 2)
|
||||
// test.DoTestGetConversationRecvMessageOpt("si_2456093263_9169012630")
|
||||
// test.DoTestDeleteConversationMsgFromLocalAndSvr("sg_537415520")
|
||||
for { |
||||
time.Sleep(10000 * time.Millisecond) |
||||
} |
||||
|
||||
} |
@ -0,0 +1,25 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 |
||||
|
||||
func main() { |
||||
// var onlineNum *int //Number of online users
|
||||
// onlineNum = flag.Int("on", 10, "online num")
|
||||
// flag.Parse()
|
||||
// log.Warn("", "online test start, online num: ", *onlineNum)
|
||||
// test.OnlineTest(*onlineNum)
|
||||
// log.Warn("", "online test finish, online num: ", *onlineNum)
|
||||
// select {}
|
||||
} |
@ -0,0 +1,67 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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" |
||||
"go/ast" |
||||
"go/parser" |
||||
"go/token" |
||||
"go/types" |
||||
) |
||||
|
||||
func main() { |
||||
|
||||
//filePath, err := filepath.Abs(".\test.go")
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
// 解析Go文件
|
||||
fset := token.NewFileSet() |
||||
node, err := parser.ParseFile(fset, "D:\\Goland\\workspace\\Open-IM-SDK-Core\\main\\test.go", nil, parser.AllErrors) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
//myImporter := importer.Default()
|
||||
// 创建类型检查器
|
||||
//conf := types.Config{Importer: myImporter}
|
||||
info := &types.Info{ |
||||
Defs: make(map[*ast.Ident]types.Object), |
||||
} |
||||
//// 类型检查
|
||||
//_, err = conf.Check("", fset, []*ast.File{node}, info)
|
||||
//if err != nil {
|
||||
// panic(err)
|
||||
//}
|
||||
// 遍历文件中所有函数
|
||||
|
||||
fn := func(pkg *types.Package) string { |
||||
return pkg.Name() |
||||
} |
||||
for _, decl := range node.Decls { |
||||
if f, ok := decl.(*ast.FuncDecl); ok { |
||||
// 打印函数名
|
||||
fmt.Println("Function Name: ", f.Name.Name) |
||||
// 打印参数名和类型
|
||||
for _, param := range f.Type.Params.List { |
||||
for _, name := range param.Names { |
||||
obj := info.ObjectOf(name) |
||||
typ := obj.Type() |
||||
fmt.Printf("Parameter Name: %s, Type: %s\n", name.Name, types.TypeString(typ, fn)) |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,38 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 ( |
||||
"errors" |
||||
"flag" |
||||
"github.com/openimsdk/openim-sdk-core/v3/test" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func main() { |
||||
var senderNum *int //Number of users sending messages
|
||||
var singleSenderMsgNum *int //Number of single user send messages
|
||||
var intervalTime *int //Sending time interval, in millisecond
|
||||
senderNum = flag.Int("sn", 100, "sender num") |
||||
singleSenderMsgNum = flag.Int("mn", 1000, "single sender msg num") |
||||
intervalTime = flag.Int("t", 0, "interval time mill second") |
||||
flag.Parse() |
||||
// test.InitMgr(*senderNum)
|
||||
|
||||
log.ZInfo(ctx, "logLevel", uint32(test.LogLevel)) |
||||
log.ZWarn(ctx, "press test begin ", errors.New(""), "sender num", *senderNum, " single sender msg num", *singleSenderMsgNum, " send msg total num ", *senderNum**singleSenderMsgNum) |
||||
test.PressTest(*singleSenderMsgNum, *intervalTime, *senderNum) |
||||
select {} |
||||
} |
@ -0,0 +1,38 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 ( |
||||
"errors" |
||||
"flag" |
||||
"github.com/openimsdk/openim-sdk-core/v3/test" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func main() { |
||||
var senderNum *int //Number of users sending messages
|
||||
var singleSenderMsgNum *int //Number of single user send messages
|
||||
var intervalTime *int //Sending time interval, in millisecond
|
||||
|
||||
senderNum = flag.Int("sn", 200, "sender num") |
||||
singleSenderMsgNum = flag.Int("mn", 100, "single sender msg num") |
||||
intervalTime = flag.Int("t", 10, "interval time mill second") |
||||
flag.Parse() |
||||
test.InitMgr(*senderNum) |
||||
log.ZInfo(ctx, "logName", test.LogName, "logLevel", uint32(test.LogLevel)) |
||||
log.ZWarn(ctx, "reliability test start ", errors.New(""), "sender num", *senderNum, " single sender msg num", *singleSenderMsgNum, " send msg total num ", *senderNum**singleSenderMsgNum) |
||||
|
||||
test.ReliabilityTest(*singleSenderMsgNum, *intervalTime, 10, *senderNum) |
||||
} |
@ -0,0 +1,192 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 ( |
||||
"errors" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/openim-sdk-core/v3/test" |
||||
"github.com/openimsdk/tools/log" |
||||
"time" |
||||
) |
||||
|
||||
var allDB []*db.DataBase |
||||
|
||||
//funcation TestDB(loginUserID string) {
|
||||
// operationID := utils.OperationIDGenerator()
|
||||
// dbUser, err := db.NewDataBase(loginUserID, "/data/test/Open-IM-Server/db/sdk/", operationID)
|
||||
// if err != nil {
|
||||
// log.Error(operationID, "NewDataBase failed ", err.Error(), loginUserID)
|
||||
// return
|
||||
// }
|
||||
// conversationList, err := dbUser.GetAllConversationList()
|
||||
// if err != nil {
|
||||
// log.Error(operationID, "GetAllConversationList failed ", err.Error())
|
||||
// }
|
||||
// log.Info(operationID, "GetAllConversationList len: ", len(conversationList))
|
||||
//
|
||||
// groupIDList, err := dbUser.GetJoinedGroupList()
|
||||
// if err != nil {
|
||||
// log.Error(operationID, "GetJoinedGroupList failed ", err.Error())
|
||||
// }
|
||||
// log.Info(operationID, "GetJoinedGroupList len: ", len(groupIDList))
|
||||
//
|
||||
// groupMemberList, err := dbUser.GetAllGroupMemberList()
|
||||
// if err != nil {
|
||||
// log.Error(operationID, "GetAllGroupMemberList failed ", err.Error())
|
||||
// }
|
||||
// log.Info(operationID, "GetAllGroupMemberList len: ", len(groupMemberList))
|
||||
// //GetAllMessageForTest
|
||||
// msgList, err := dbUser.GetAllMessageForTest()
|
||||
// if err != nil {
|
||||
// log.Error(operationID, "GetAllMessageForTest failed ", err.Error())
|
||||
// }
|
||||
// log.Info(operationID, "GetAllMessageForTest len: ", len(msgList))
|
||||
// allDB = append(allDB, dbUser)
|
||||
//
|
||||
// dbUser.CloseDB(operationID)
|
||||
// log.Info(operationID, "close db finished ")
|
||||
//
|
||||
//}
|
||||
|
||||
func main() { |
||||
//var userIDList []string
|
||||
//f, err := os.Open("/data/test/Open-IM-Server/db/sdk")
|
||||
//if err != nil {
|
||||
// log.Error("", "open failed ", err.Error())
|
||||
// return
|
||||
//}
|
||||
//files, err := f.Readdir(-1)
|
||||
//f.Close()
|
||||
//if err != nil {
|
||||
// log.Error("", "Readdir failed ", err.Error())
|
||||
// return
|
||||
//}
|
||||
//
|
||||
//for _, file := range files {
|
||||
// begin := strings.Index(file.Name(), "OpenIM_v2_")
|
||||
// end := strings.Index(file.Name(), ".db")
|
||||
// userID := file.Name()[begin+len("OpenIM_v2_") : end]
|
||||
// // OpenIM_v2_3380999461.db
|
||||
// log.Info("", "file name: ", file.Name(), userID)
|
||||
// TestDB(userID)
|
||||
//}
|
||||
//log.Info("", "files: ", len(allDB))
|
||||
////for _, v := range allDB {
|
||||
//// v.CloseDB("aa")
|
||||
////}
|
||||
//
|
||||
//log.Info("", "gc begin ")
|
||||
//runtime.GC()
|
||||
//log.Info("", "gc end ")
|
||||
//time.Sleep(100000 * time.Second)
|
||||
//return
|
||||
strMyUidx := "3370431052" |
||||
tokenx := test.RunGetToken(strMyUidx) |
||||
//tokenx := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVSUQiOiI3MDcwMDgxNTMiLCJQbGF0Zm9ybSI6IkFuZHJvaWQiLCJleHAiOjE5NjY0MTJ1XjJZGWj5fB3mqC7p6ytxSarvxZfsABwIjoxNjUxMDU1MDU2fQ.aWvmJ_sQxXmT5nKwiM5QsF9-tfkldzOYZtRD3nrUuko"
|
||||
//go funcation() {
|
||||
// time.Sleep(2 * time.Second)
|
||||
// test.InOutLogou()
|
||||
//}()
|
||||
|
||||
test.InOutDoTest(strMyUidx, tokenx, test.WSADDR, test.APIADDR) |
||||
// test.InOutDoTest(strMyUidx, tokenx, test.WSADDR, test.APIADDR)
|
||||
|
||||
// time.Sleep(5 * time.Second)
|
||||
// test.SetListenerAndLogin(strMyUidx, tokenx)
|
||||
//test.DoTestSetGroupMemberInfo("1104164664", "3188816039", "set ex")
|
||||
|
||||
// test.DotestGetGroupMemberList()
|
||||
//time.Sleep(100000 * time.Second)
|
||||
// test.DoTestCreateGroup()
|
||||
|
||||
// test.DoTestJoinGroup()
|
||||
// test.DoTestGetGroupsInfo()
|
||||
// test.DoTestDeleteAllMsgFromLocalAndSvr()
|
||||
|
||||
// println("token ", tokenx)
|
||||
time.Sleep(100000 * time.Second) |
||||
b := utils.GetCurrentTimestampBySecond() |
||||
i := 0 |
||||
for { |
||||
test.DoTestSendMsg2c2c(strMyUidx, "3380999461", i) |
||||
i++ |
||||
time.Sleep(100 * time.Millisecond) |
||||
if i == 10000 { |
||||
break |
||||
} |
||||
log.ZWarn(ctx, "", errors.New(""), "10 * time.Millisecond ###################waiting... msg: ", i) |
||||
} |
||||
|
||||
//log.Warn("", "cost time: ", utils.GetCurrentTimestampBySecond()-b)
|
||||
time.Sleep(100000 * time.Second) |
||||
return |
||||
i = 0 |
||||
for { |
||||
test.DoTestSendMsg2Group(strMyUidx, "42c9f515cb84ee0e82b3f3ce71eb14d6", i) |
||||
i++ |
||||
time.Sleep(1000 * time.Millisecond) |
||||
if i == 10 { |
||||
break |
||||
} |
||||
log.ZWarn(ctx, "", errors.New(""), "1000 * time.Millisecond ###################waiting... msg: ", i) |
||||
} |
||||
|
||||
i = 0 |
||||
for { |
||||
test.DoTestSendMsg2Group(strMyUidx, "42c9f515cb84ee0e82b3f3ce71eb14d6", i) |
||||
i++ |
||||
time.Sleep(10000 * time.Millisecond) |
||||
log.ZWarn(ctx, "", errors.New(""), "10000 * time.Millisecond ###################waiting... msg: ", i) |
||||
} |
||||
|
||||
//reliabilityTest()
|
||||
// test.PressTest(testClientNum, intervalSleep, imIP)
|
||||
} |
||||
|
||||
//
|
||||
//funcation main() {
|
||||
// testClientNum := 100
|
||||
// intervalSleep := 2
|
||||
// imIP := "43.128.5.63"
|
||||
|
||||
//
|
||||
// msgNum := 1000
|
||||
// test.ReliabilityTest(msgNum, intervalSleep, imIP)
|
||||
// for i := 0; i < 6; i++ {
|
||||
// test.Msgwg.Wait()
|
||||
// }
|
||||
//
|
||||
// for {
|
||||
//
|
||||
// if test.CheckReliabilityResult() {
|
||||
// log.Warn("CheckReliabilityResult ok, again")
|
||||
//
|
||||
// } else {
|
||||
// log.Warn("CheckReliabilityResult failed , wait.... ")
|
||||
// }
|
||||
//
|
||||
// time.Sleep(time.Duration(10) * time.Second)
|
||||
// }
|
||||
//
|
||||
//}
|
||||
|
||||
//funcation printCallerNameAndLine() string {
|
||||
// pc, _, line, _ := runtime.Caller(2)
|
||||
// return runtime.FuncForPC(pc).Name() + "()@" + strconv.Itoa(line) + ": "
|
||||
//}
|
||||
|
||||
// myuid, maxuid, msgnum
|
@ -0,0 +1,74 @@ |
||||
docs/.generated_docs |
||||
docs/guide/en-US/cmd/openim/openim_color.md |
||||
docs/guide/en-US/cmd/openim/openim_completion.md |
||||
docs/guide/en-US/cmd/openim/openim_info.md |
||||
docs/guide/en-US/cmd/openim/openim_jwt.md |
||||
docs/guide/en-US/cmd/openim/openim_jwt_show.md |
||||
docs/guide/en-US/cmd/openim/openim_jwt_sign.md |
||||
docs/guide/en-US/cmd/openim/openim_jwt_verify.md |
||||
docs/guide/en-US/cmd/openim/openim_new.md |
||||
docs/guide/en-US/cmd/openim/openim_options.md |
||||
docs/guide/en-US/cmd/openim/openim_policy.md |
||||
docs/guide/en-US/cmd/openim/openim_policy_create.md |
||||
docs/guide/en-US/cmd/openim/openim_policy_delete.md |
||||
docs/guide/en-US/cmd/openim/openim_policy_get.md |
||||
docs/guide/en-US/cmd/openim/openim_policy_list.md |
||||
docs/guide/en-US/cmd/openim/openim_policy_update.md |
||||
docs/guide/en-US/cmd/openim/openim_secret.md |
||||
docs/guide/en-US/cmd/openim/openim_secret_create.md |
||||
docs/guide/en-US/cmd/openim/openim_secret_delete.md |
||||
docs/guide/en-US/cmd/openim/openim_secret_get.md |
||||
docs/guide/en-US/cmd/openim/openim_secret_list.md |
||||
docs/guide/en-US/cmd/openim/openim_secret_update.md |
||||
docs/guide/en-US/cmd/openim/openim_set.md |
||||
docs/guide/en-US/cmd/openim/openim-rpc-user.md |
||||
docs/guide/en-US/cmd/openim/openim-rpc-user_create.md |
||||
docs/guide/en-US/cmd/openim/openim-rpc-user_delete.md |
||||
docs/guide/en-US/cmd/openim/openim-rpc-user_get.md |
||||
docs/guide/en-US/cmd/openim/openim-rpc-user_list.md |
||||
docs/guide/en-US/cmd/openim/openim-rpc-user_update.md |
||||
docs/guide/en-US/cmd/openim/openim_validate.md |
||||
docs/guide/en-US/cmd/openim/openim_version.md |
||||
docs/guide/en-US/yaml/openim/openim.yaml |
||||
docs/guide/en-US/yaml/openim/openim_color.yaml |
||||
docs/guide/en-US/yaml/openim/openim_completion.yaml |
||||
docs/guide/en-US/yaml/openim/openim_info.yaml |
||||
docs/guide/en-US/yaml/openim/openim_jwt.yaml |
||||
docs/guide/en-US/yaml/openim/openim_new.yaml |
||||
docs/guide/en-US/yaml/openim/openim_options.yaml |
||||
docs/guide/en-US/yaml/openim/openim_policy.yaml |
||||
docs/guide/en-US/yaml/openim/openim_secret.yaml |
||||
docs/guide/en-US/yaml/openim/openim_set.yaml |
||||
docs/guide/en-US/yaml/openim/openim-rpc-user.yaml |
||||
docs/guide/en-US/yaml/openim/openim_validate.yaml |
||||
docs/guide/en-US/yaml/openim/openim_version.yaml |
||||
docs/man/man1/openim-completion.1 |
||||
docs/man/man1/openim-info.1 |
||||
docs/man/man1/openim-jwt-show.1 |
||||
docs/man/man1/openim-jwt-sign.1 |
||||
docs/man/man1/openim-jwt-verify.1 |
||||
docs/man/man1/openim-jwt.1 |
||||
docs/man/man1/openim-new.1 |
||||
docs/man/man1/openim-options.1 |
||||
docs/man/man1/openim-policy-create.1 |
||||
docs/man/man1/openim-policy-delete.1 |
||||
docs/man/man1/openim-policy-get.1 |
||||
docs/man/man1/openim-policy-list.1 |
||||
docs/man/man1/openim-policy-update.1 |
||||
docs/man/man1/openim-policy.1 |
||||
docs/man/man1/openim-secret-create.1 |
||||
docs/man/man1/openim-secret-delete.1 |
||||
docs/man/man1/openim-secret-get.1 |
||||
docs/man/man1/openim-secret-list.1 |
||||
docs/man/man1/openim-secret-update.1 |
||||
docs/man/man1/openim-secret.1 |
||||
docs/man/man1/openim-set.1 |
||||
docs/man/man1/openim-user-create.1 |
||||
docs/man/man1/openim-user-delete.1 |
||||
docs/man/man1/openim-user-get.1 |
||||
docs/man/man1/openim-user-list.1 |
||||
docs/man/man1/openim-user-update.1 |
||||
docs/man/man1/openim-user.1 |
||||
docs/man/man1/openim-validate.1 |
||||
docs/man/man1/openim-version.1 |
||||
docs/man/man1/openim.1 |
@ -0,0 +1 @@ |
||||
* @openimsdk/go-code-review |
@ -0,0 +1,38 @@ |
||||
# Code conventions |
||||
|
||||
- [Code conventions](#code-conventions) |
||||
- [POSIX shell](#posix-shell) |
||||
- [Go](#go) |
||||
- [Directory and file conventions](#directory-and-file-conventions) |
||||
- [Testing conventions](#testing-conventions) |
||||
|
||||
## POSIX shell |
||||
|
||||
- [Style guide](https://google.github.io/styleguide/shell.xml) |
||||
|
||||
## Go |
||||
|
||||
- [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) |
||||
- [Effective Go](https://golang.org/doc/effective_go.html) |
||||
- Know and avoid [Go landmines](https://gist.github.com/lavalamp/4bd23295a9f32706a48f) |
||||
- Comment your code. |
||||
- [Go's commenting conventions](http://blog.golang.org/godoc-documenting-go-code) |
||||
- If reviewers ask questions about why the code is the way it is, that's a sign that comments might be helpful. |
||||
- Command-line flags should use dashes, not underscores |
||||
- Naming |
||||
- Please consider package name when selecting an interface name, and avoid redundancy. For example, `storage.Interface` is better than `storage.StorageInterface`. |
||||
- Do not use uppercase characters, underscores, or dashes in package names. |
||||
- Please consider parent directory name when choosing a package name. For example, `pkg/controllers/autoscaler/foo.go` should say `package autoscaler` not `package autoscalercontroller`. |
||||
- Unless there's a good reason, the `package foo` line should match the name of the directory in which the `.go` file exists. |
||||
- Importers can use a different name if they need to disambiguate. |
||||
|
||||
## Directory and file conventions |
||||
|
||||
- Avoid general utility packages. Packages called "util" are suspect. Instead, derive a name that describes your desired function. For example, the utility functions dealing with waiting for operations are in the `wait` package and include functionality like `Poll`. The full name is `wait.Poll`. |
||||
- All filenames should be lowercase. |
||||
- All source files and directories should use underscores, not dashes. |
||||
- Package directories should generally avoid using separators as much as possible. When package names are multiple words, they usually should be in nested subdirectories. |
||||
|
||||
## Testing conventions |
||||
|
||||
Please refer to [TESTING.md](../../tests/TESTING.md) document. |
@ -0,0 +1,80 @@ |
||||
# Development Guide |
||||
|
||||
Since OpenIM is written in Go, it is fair to assume that the Go tools are all one needs to contribute to this project. Unfortunately, there is a point where this no longer holds true when required to test or build local changes. This document elaborates on the required tooling for OpenIM development. |
||||
|
||||
- [Development Guide](#development-guide) |
||||
- [Non-Linux environment prerequisites](#non-linux-environment-prerequisites) |
||||
- [Windows Setup](#windows-setup) |
||||
- [macOS Setup](#macos-setup) |
||||
- [Installing Required Software](#installing-required-software) |
||||
- [Go](#go) |
||||
- [Docker](#docker) |
||||
- [Vagrant](#vagrant) |
||||
- [Cloning, Building and Testing OpenIM](#cloning-building-and-testing-openim) |
||||
- [Dependency management](#dependency-management) |
||||
|
||||
## Non-Linux environment prerequisites |
||||
|
||||
All the test and build scripts within this repository were created to be run on GNU Linux development environments. Due to this, it is suggested to use the virtual machine defined on this repository's [Vagrantfile](../../Vagrantfile) to use them. |
||||
|
||||
Either way, if one still wants to build and test OpenIM on non-Linux environments, specific setups are to be followed. |
||||
|
||||
### Windows Setup |
||||
|
||||
To build OpenIM on Windows is only possible for versions that support Windows Subsystem for Linux (WSL). If the development environment in question has Windows 10, Version 2004, Build 19041 or higher, [follow these instructions to install WSL2](https://docs.microsoft.com/en-us/windows/wsl/install-win10); otherwise, use a Linux Virtual machine instead. |
||||
|
||||
### macOS Setup |
||||
|
||||
The shell scripts in charge of the build and test processes rely on GNU utils (i.e. `sed`), [which slightly differ on macOS](https://unix.stackexchange.com/a/79357), meaning that one must make some adjustments before using them. |
||||
|
||||
First, install the GNU utils: |
||||
|
||||
```sh |
||||
brew install coreutils findutils gawk gnu-sed gnu-tar grep make |
||||
``` |
||||
|
||||
Then update the shell init script (i.e. `.bashrc`) to prepend the GNU Utils to the `$PATH` variable |
||||
|
||||
```sh |
||||
GNUBINS="$(find /usr/local/opt -type d -follow -name gnubin -print)" |
||||
|
||||
for bindir in ${GNUBINS[@]}; do |
||||
PATH=$bindir:$PATH |
||||
done |
||||
|
||||
export PATH |
||||
``` |
||||
|
||||
## Installing Required Software |
||||
|
||||
### Go |
||||
|
||||
It is well known that OpenIM is written in [Go](http://golang.org). Please follow the [Go Getting Started guide](https://golang.org/doc/install) to install and set up the Go tools used to compile and run the test batteries. |
||||
|
||||
| OpenIM | requires Go | |
||||
|----------------|-------------| |
||||
| 2.24 - 3.00 | 1.15 + | |
||||
| 3.30 + | 1.18 + | |
||||
|
||||
### Docker |
||||
|
||||
OpenIM build and test processes development require Docker to run certain steps. [Follow the Docker website instructions to install Docker](https://docs.docker.com/get-docker/) in the development environment. |
||||
|
||||
### Vagrant |
||||
|
||||
As described in the [Testing documentation](../../tests/TESTING.md), all the smoke tests are run in virtual machines managed by Vagrant. To install Vagrant in the development environment, [follow the instructions from the Hashicorp website](https://www.vagrantup.com/downloads), alongside any of the following hypervisors: |
||||
|
||||
- [VirtualBox](https://www.virtualbox.org/) |
||||
- [libvirt](https://libvirt.org/) and the [vagrant-libvirt plugin](https://github.com/vagrant-libvirt/vagrant-libvirt#installation) |
||||
|
||||
## Cloning, Building and Testing OpenIM |
||||
|
||||
These topics already have been addressed on their respective documents: |
||||
|
||||
- [Git Workflow](./git-workflow.md) |
||||
- [Building](../../BUILDING.md) |
||||
- [Testing](../../tests/TESTING.md) |
||||
|
||||
## Dependency management |
||||
|
||||
OpenIM uses [go modules](https://github.com/golang/go/wiki/Modules) to manage dependencies. |
@ -0,0 +1,102 @@ |
||||
# Git workflows |
||||
|
||||
This document is an overview of OpenIM git workflow. It includes conventions, tips, and how to maintain good repository hygiene. |
||||
|
||||
- [Git workflows](#git-workflows) |
||||
- [Branching model](#branching-model) |
||||
- [Branch naming conventions](#branch-naming-conventions) |
||||
- [Backport policy](#backport-policy) |
||||
- [Git operations](#git-operations) |
||||
- [Setting up](#setting-up) |
||||
- [Branching out](#branching-out) |
||||
- [Keeping local branches in sync](#keeping-local-branches-in-sync) |
||||
- [Pushing changes](#pushing-changes) |
||||
|
||||
## Branching model |
||||
|
||||
OpenIM project uses the [GitHub flow](https://docs.github.com/en/get-started/quickstart/github-flow) as its branching model, where most of the changes come from repositories forks instead of branches within the same one. |
||||
|
||||
### Branch naming conventions |
||||
|
||||
Every forked repository works independently, meaning that any contributor can create branches with the name they see fit. However, it is worth noting that OpenIM mirrors [OpenIM version skew policy](https://github.com/openimsdk/openim-sdk-core/releases) by maintaining release branches for the most recent three minor releases. The only exception is that the main branch mirrors the latest OpenIM release (3.10) instead of using a `release-` prefixed one. |
||||
|
||||
```text |
||||
main -------------------------------------------. (OpenIM 3.10) |
||||
release-3.0.0 \---------------|---------------. (OpenIM 3.00) |
||||
release-2.4.0 \---------------. (OpenIM 2.40) |
||||
``` |
||||
|
||||
|
||||
### Backport policy |
||||
|
||||
All new work happens on the main branch, which means that for most cases, one should branch out from there and create the pull request against it. If the change involves adding a feature or patching OpenIM, the maintainers will backport it into the supported release branches. |
||||
|
||||
## Git operations |
||||
|
||||
There are everyday tasks related to git that every contributor needs to perform, and this section elaborates on them. |
||||
|
||||
### Setting up |
||||
|
||||
Creating a OpenIM fork, cloning it, and setting its upstream remote can be summarized on: |
||||
|
||||
1. Visit <https://github.com/openimsdk/openim-sdk-core> |
||||
2. Click the `Fork` button (top right) to establish a cloud-based fork |
||||
3. Clone fork to local storage |
||||
4. Add to your fork OpenIM remote as upstream |
||||
|
||||
Once cloned, in code it would look this way: |
||||
|
||||
```sh |
||||
## Clone fork to local storage |
||||
export user="your github profile name" |
||||
git clone https://github.com/$user/OpenIM.git |
||||
# or: git clone git@github.com:$user/OpenIM.git |
||||
|
||||
## Add OpenIM as upstream to your fork |
||||
cd OpenIM |
||||
git remote add upstream https://github.com/openimsdk/openim-sdk-core.git |
||||
# or: git remote add upstream git@github.com:OpenIMSDK/openim-sdk-core.git |
||||
|
||||
## Ensure to never push to upstream directly |
||||
git remote set-url --push upstream no_push |
||||
|
||||
## Confirm that your remotes make sense: |
||||
git remote -v |
||||
``` |
||||
|
||||
### Branching out |
||||
|
||||
Every time one wants to work on a new OpenIM feature, we do: |
||||
|
||||
1. Get local main branch up to date |
||||
2. Create a new branch from the main one (i.e.: myfeature branch ) |
||||
|
||||
In code it would look this way: |
||||
|
||||
```sh |
||||
## Get local main up to date |
||||
# Assuming the OpenIM clone is the current working directory |
||||
git fetch upstream |
||||
git checkout main |
||||
git rebase upstream/main |
||||
|
||||
## Create a new branch from main |
||||
git checkout -b myfeature |
||||
``` |
||||
|
||||
### Keeping local branches in sync |
||||
|
||||
Either when branching out from main or a release one, keep in mind it is worth checking if any change has been pushed upstream by doing: |
||||
|
||||
```sh |
||||
git fetch upstream |
||||
git rebase upstream/main |
||||
``` |
||||
|
||||
It is suggested to `fetch` then `rebase` instead of `pull` since the latter does a merge, which leaves merge commits. For this, one can consider changing the local repository configuration by doing `git config branch.autoSetupRebase always` to change the behavior of `git pull`, or another non-merge option such as `git pull --rebase`. |
||||
|
||||
### Pushing changes |
||||
|
||||
For commit messages and signatures please refer to the [CONTRIBUTING.md](../../CONTRIBUTING.md) document. |
||||
|
||||
Nobody should push directly to upstream, even if one has such contributor access; instead, prefer [Github's pull request](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests) mechanism to contribute back into OpenIM. For expectations and guidelines about pull requests, consult the [CONTRIBUTING.md](../../CONTRIBUTING.md) document. |
@ -0,0 +1,43 @@ |
||||
module github.com/openimsdk/openim-sdk-core/v3 |
||||
|
||||
go 1.21 |
||||
|
||||
require ( |
||||
github.com/golang/protobuf v1.5.4 |
||||
github.com/gorilla/websocket v1.4.2 |
||||
github.com/jinzhu/copier v0.4.0 |
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect |
||||
github.com/pkg/errors v0.9.1 |
||||
google.golang.org/protobuf v1.33.0 // indirect |
||||
gorm.io/driver/sqlite v1.5.5 |
||||
nhooyr.io/websocket v1.8.10 |
||||
) |
||||
|
||||
require golang.org/x/net v0.22.0 |
||||
|
||||
require ( |
||||
github.com/google/go-cmp v0.6.0 |
||||
github.com/openimsdk/protocol v0.0.69-alpha.16 |
||||
github.com/openimsdk/tools v0.0.49-alpha.12 |
||||
github.com/patrickmn/go-cache v2.1.0+incompatible |
||||
go.etcd.io/etcd/api/v3 v3.5.13 |
||||
golang.org/x/image v0.15.0 |
||||
gorm.io/gorm v1.25.10 |
||||
) |
||||
|
||||
require ( |
||||
github.com/coreos/go-semver v0.3.0 // indirect |
||||
github.com/jinzhu/inflection v1.0.0 // indirect |
||||
github.com/jinzhu/now v1.1.5 // indirect |
||||
github.com/lestrrat-go/strftime v1.0.6 // indirect |
||||
github.com/mattn/go-sqlite3 v1.14.22 // indirect |
||||
go.uber.org/atomic v1.7.0 // indirect |
||||
go.uber.org/multierr v1.6.0 // indirect |
||||
go.uber.org/zap v1.24.0 // indirect |
||||
golang.org/x/sys v0.18.0 // indirect |
||||
golang.org/x/text v0.14.0 // indirect |
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect |
||||
google.golang.org/grpc v1.62.1 // indirect |
||||
) |
||||
|
||||
//replace github.com/openimsdk/protocol => /Users/chao/Desktop/project/protocol |
@ -0,0 +1,77 @@ |
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= |
||||
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= |
||||
github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= |
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= |
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= |
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= |
||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= |
||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= |
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= |
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= |
||||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= |
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= |
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= |
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= |
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= |
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= |
||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= |
||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= |
||||
github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= |
||||
github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= |
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= |
||||
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= |
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkLibYKgg+SwmyFU9dF2hn6MdTj4= |
||||
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible/go.mod h1:ZQnN8lSECaebrkQytbHj4xNgtg8CR7RYXnPok8e0EHA= |
||||
github.com/lestrrat-go/strftime v1.0.6 h1:CFGsDEt1pOpFNU+TJB0nhz9jl+K0hZSLE205AhTIGQQ= |
||||
github.com/lestrrat-go/strftime v1.0.6/go.mod h1:f7jQKgV5nnJpYgdEasS+/y7EsTb8ykN2z68n3TtcTaw= |
||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= |
||||
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= |
||||
github.com/openimsdk/protocol v0.0.69-alpha.16 h1:ciSqm2rjBdpScpkQm3wPjAFv0YbIRp8MITRkDZWVv6c= |
||||
github.com/openimsdk/protocol v0.0.69-alpha.16/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8= |
||||
github.com/openimsdk/tools v0.0.49-alpha.12 h1:vsr63W1kHW1dEw9yelMhmr72WmsrjKfs2vXww3upfWI= |
||||
github.com/openimsdk/tools v0.0.49-alpha.12/go.mod h1:g7mkHXYUPi0/8aAX8VPMHpnb3hqdV69Jph+bXOGvvNM= |
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= |
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= |
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= |
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= |
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= |
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= |
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= |
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= |
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= |
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= |
||||
go.etcd.io/etcd/api/v3 v3.5.13 h1:8WXU2/NBge6AUF1K1gOexB6e07NgsN1hXK0rSTtgSp4= |
||||
go.etcd.io/etcd/api/v3 v3.5.13/go.mod h1:gBqlqkcMMZMVTMm4NDZloEVJzxQOQIls8splbqBDa0c= |
||||
go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= |
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= |
||||
go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= |
||||
go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= |
||||
go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= |
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= |
||||
go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= |
||||
go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= |
||||
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= |
||||
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= |
||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= |
||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= |
||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= |
||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= |
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= |
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc= |
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY= |
||||
google.golang.org/grpc v1.62.1 h1:B4n+nfKzOICUXMgyrNd19h/I9oH0L1pizfk1d4zSgTk= |
||||
google.golang.org/grpc v1.62.1/go.mod h1:IWTG0VlJLCh1SkC58F7np9ka9mx/WNkjl4PGJaiq+QE= |
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= |
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= |
||||
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= |
||||
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= |
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= |
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= |
||||
gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= |
||||
gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= |
||||
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s= |
||||
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= |
||||
nhooyr.io/websocket v1.8.10 h1:mv4p+MnGrLDcPlBoWsvPP7XCzTYMXP9F9eIGoKbgx7Q= |
||||
nhooyr.io/websocket v1.8.10/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c= |
@ -0,0 +1,49 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 business |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/openimsdk/openim-sdk-core/v3/open_im_sdk_callback" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
|
||||
"github.com/openimsdk/protocol/sdkws" |
||||
|
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
type Business struct { |
||||
listener func() open_im_sdk_callback.OnCustomBusinessListener |
||||
db db_interface.DataBase |
||||
} |
||||
|
||||
func NewBusiness(db db_interface.DataBase) *Business { |
||||
return &Business{ |
||||
db: db, |
||||
} |
||||
} |
||||
|
||||
func (b *Business) DoNotification(ctx context.Context, msg *sdkws.MsgData) { |
||||
var n sdk_struct.NotificationElem |
||||
err := utils.JsonStringToStruct(string(msg.Content), &n) |
||||
if err != nil { |
||||
log.ZError(ctx, "unmarshal failed", err, "msg", msg) |
||||
return |
||||
|
||||
} |
||||
b.listener().OnRecvCustomBusinessMessage(n.Detail) |
||||
} |
@ -0,0 +1,23 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 business |
||||
|
||||
import ( |
||||
"github.com/openimsdk/openim-sdk-core/v3/open_im_sdk_callback" |
||||
) |
||||
|
||||
func (w *Business) SetListener(listener func() open_im_sdk_callback.OnCustomBusinessListener) { |
||||
w.listener = listener |
||||
} |
@ -0,0 +1,72 @@ |
||||
package cache |
||||
|
||||
import "sync" |
||||
|
||||
// Cache is a Generic sync.Map structure.
|
||||
type Cache[K comparable, V any] struct { |
||||
m sync.Map |
||||
} |
||||
|
||||
func NewCache[K comparable, V any]() *Cache[K, V] { |
||||
return &Cache[K, V]{} |
||||
} |
||||
|
||||
// Load returns the value stored in the map for a key, or nil if no value is present.
|
||||
func (c *Cache[K, V]) Load(key K) (value V, ok bool) { |
||||
rawValue, ok := c.m.Load(key) |
||||
if !ok { |
||||
return |
||||
} |
||||
return rawValue.(V), ok |
||||
} |
||||
|
||||
// Store sets the value for a key.
|
||||
func (c *Cache[K, V]) Store(key K, value V) { |
||||
c.m.Store(key, value) |
||||
} |
||||
|
||||
// StoreAll sets all value by f's key.
|
||||
func (c *Cache[K, V]) StoreAll(f func(value V) K, values []V) { |
||||
for _, v := range values { |
||||
c.m.Store(f(v), v) |
||||
} |
||||
} |
||||
|
||||
// LoadOrStore returns the existing value for the key if present.
|
||||
func (c *Cache[K, V]) LoadOrStore(key K, value V) (actual V, loaded bool) { |
||||
rawValue, loaded := c.m.LoadOrStore(key, value) |
||||
return rawValue.(V), loaded |
||||
} |
||||
|
||||
// Delete deletes the value for a key.
|
||||
func (c *Cache[K, V]) Delete(key K) { |
||||
c.m.Delete(key) |
||||
} |
||||
|
||||
// DeleteAll deletes all values.
|
||||
func (c *Cache[K, V]) DeleteAll() { |
||||
c.m.Range(func(key, value interface{}) bool { |
||||
c.m.Delete(key) |
||||
return true |
||||
}) |
||||
} |
||||
|
||||
// RangeAll returns all values in the map.
|
||||
func (c *Cache[K, V]) RangeAll() (values []V) { |
||||
c.m.Range(func(rawKey, rawValue interface{}) bool { |
||||
values = append(values, rawValue.(V)) |
||||
return true |
||||
}) |
||||
return values |
||||
} |
||||
|
||||
// RangeCon returns values in the map that satisfy condition f.
|
||||
func (c *Cache[K, V]) RangeCon(f func(key K, value V) bool) (values []V) { |
||||
c.m.Range(func(rawKey, rawValue interface{}) bool { |
||||
if f(rawKey.(K), rawValue.(V)) { |
||||
values = append(values, rawValue.(V)) |
||||
} |
||||
return true |
||||
}) |
||||
return values |
||||
} |
@ -0,0 +1,33 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 common |
||||
|
||||
import ( |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
|
||||
"github.com/golang/protobuf/proto" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
) |
||||
|
||||
func UnmarshalTips(msg *sdkws.MsgData, detail proto.Message) error { |
||||
var tips sdkws.TipsComm |
||||
if err := proto.Unmarshal(msg.Content, &tips); err != nil { |
||||
return utils.Wrap(err, "") |
||||
} |
||||
if err := proto.Unmarshal(tips.Detail, detail); err != nil { |
||||
return utils.Wrap(err, "") |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,33 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 common |
||||
|
||||
import ( |
||||
"bytes" |
||||
_ "image/gif" |
||||
_ "image/jpeg" |
||||
_ "image/png" |
||||
) |
||||
|
||||
type ObjectStorage interface { |
||||
UploadImage(filePath string, onProgressFun func(int)) (string, string, error) |
||||
UploadSound(filePath string, onProgressFun func(int)) (string, string, error) |
||||
UploadFile(filePath string, onProgressFun func(int)) (string, string, error) |
||||
UploadVideo(videoPath, snapshotPath string, onProgressFun func(int)) (string, string, string, string, error) |
||||
UploadImageByBuffer(buffer *bytes.Buffer, size int64, imageType string, onProgressFun func(int)) (string, string, error) |
||||
UploadSoundByBuffer(buffer *bytes.Buffer, size int64, fileType string, onProgressFun func(int)) (string, string, error) |
||||
UploadFileByBuffer(buffer *bytes.Buffer, size int64, fileType string, onProgressFun func(int)) (string, string, error) |
||||
UploadVideoByBuffer(videoBuffer, snapshotBuffer *bytes.Buffer, videoSize, snapshotSize int64, videoType, snapshotType string, onProgressFun func(int)) (string, string, string, string, error) |
||||
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,714 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
"reflect" |
||||
"runtime" |
||||
"time" |
||||
|
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func (c *Conversation) Work(c2v common.Cmd2Value) { |
||||
log.ZDebug(c2v.Ctx, "NotificationCmd start", "cmd", c2v.Cmd, "value", c2v.Value) |
||||
defer log.ZDebug(c2v.Ctx, "NotificationCmd end", "cmd", c2v.Cmd, "value", c2v.Value) |
||||
switch c2v.Cmd { |
||||
case constant.CmdNewMsgCome: |
||||
c.doMsgNew(c2v) |
||||
case constant.CmdSuperGroupMsgCome: |
||||
case constant.CmdUpdateConversation: |
||||
c.doUpdateConversation(c2v) |
||||
case constant.CmdUpdateMessage: |
||||
c.doUpdateMessage(c2v) |
||||
case constant.CmSyncReactionExtensions: |
||||
case constant.CmdNotification: |
||||
c.doNotificationNew(c2v) |
||||
} |
||||
} |
||||
|
||||
func (c *Conversation) doNotificationNew(c2v common.Cmd2Value) { |
||||
ctx := c2v.Ctx |
||||
allMsg := c2v.Value.(sdk_struct.CmdNewMsgComeToConversation).Msgs |
||||
syncFlag := c2v.Value.(sdk_struct.CmdNewMsgComeToConversation).SyncFlag |
||||
switch syncFlag { |
||||
case constant.MsgSyncBegin: |
||||
c.startTime = time.Now() |
||||
c.ConversationListener().OnSyncServerStart() |
||||
if err := c.SyncAllConversationHashReadSeqs(ctx); err != nil { |
||||
log.ZError(ctx, "SyncConversationHashReadSeqs err", err) |
||||
} |
||||
//clear SubscriptionStatusMap
|
||||
c.user.OnlineStatusCache.DeleteAll() |
||||
for _, syncFunc := range []func(c context.Context) error{ |
||||
c.user.SyncLoginUserInfo, |
||||
c.friend.SyncAllBlackList, c.friend.SyncAllFriendApplication, c.friend.SyncAllSelfFriendApplication, |
||||
c.group.SyncAllAdminGroupApplication, c.group.SyncAllSelfGroupApplication, c.user.SyncAllCommand, |
||||
} { |
||||
go func(syncFunc func(c context.Context) error) { |
||||
_ = syncFunc(ctx) |
||||
}(syncFunc) |
||||
} |
||||
|
||||
syncFunctions := []func(c context.Context) error{ |
||||
c.group.SyncAllJoinedGroupsAndMembers, c.friend.IncrSyncFriends, |
||||
} |
||||
|
||||
for _, syncFunc := range syncFunctions { |
||||
funcName := runtime.FuncForPC(reflect.ValueOf(syncFunc).Pointer()).Name() |
||||
startTime := time.Now() |
||||
err := syncFunc(ctx) |
||||
duration := time.Since(startTime) |
||||
if err != nil { |
||||
log.ZWarn(ctx, fmt.Sprintf("%s sync err", funcName), err, "duration", duration) |
||||
} else { |
||||
log.ZDebug(ctx, fmt.Sprintf("%s completed successfully", funcName), "duration", duration) |
||||
} |
||||
} |
||||
case constant.MsgSyncFailed: |
||||
c.ConversationListener().OnSyncServerFailed() |
||||
case constant.MsgSyncEnd: |
||||
log.ZDebug(ctx, "MsgSyncEnd", "time", time.Since(c.startTime).Milliseconds()) |
||||
defer c.ConversationListener().OnSyncServerFinish() |
||||
go c.SyncAllConversations(ctx) |
||||
} |
||||
|
||||
for conversationID, msgs := range allMsg { |
||||
log.ZDebug(ctx, "notification handling", "conversationID", conversationID, "msgs", msgs) |
||||
if len(msgs.Msgs) != 0 { |
||||
lastMsg := msgs.Msgs[len(msgs.Msgs)-1] |
||||
log.ZDebug(ctx, "SetNotificationSeq", "conversationID", conversationID, "seq", lastMsg.Seq) |
||||
if lastMsg.Seq != 0 { |
||||
if err := c.db.SetNotificationSeq(ctx, conversationID, lastMsg.Seq); err != nil { |
||||
log.ZError(ctx, "SetNotificationSeq err", err, "conversationID", conversationID, "lastMsg", lastMsg) |
||||
} |
||||
} |
||||
} |
||||
for _, v := range msgs.Msgs { |
||||
switch { |
||||
case v.ContentType == constant.ConversationChangeNotification: |
||||
c.DoConversationChangedNotification(ctx, v) |
||||
case v.ContentType == constant.ConversationPrivateChatNotification: |
||||
c.DoConversationIsPrivateChangedNotification(ctx, v) |
||||
case v.ContentType == constant.ConversationUnreadNotification: |
||||
var tips sdkws.ConversationHasReadTips |
||||
_ = json.Unmarshal(v.Content, &tips) |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: tips.ConversationID, Action: constant.UnreadCountSetZero}}) |
||||
c.db.DeleteConversationUnreadMessageList(ctx, tips.ConversationID, tips.UnreadCountTime) |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: []string{tips.ConversationID}}}) |
||||
continue |
||||
case v.ContentType == constant.BusinessNotification: |
||||
c.business.DoNotification(ctx, v) |
||||
continue |
||||
case v.ContentType == constant.RevokeNotification: |
||||
c.doRevokeMsg(ctx, v) |
||||
case v.ContentType == constant.ClearConversationNotification: |
||||
c.doClearConversations(ctx, v) |
||||
case v.ContentType == constant.DeleteMsgsNotification: |
||||
c.doDeleteMsgs(ctx, v) |
||||
case v.ContentType == constant.HasReadReceipt: |
||||
c.doReadDrawing(ctx, v) |
||||
} |
||||
|
||||
switch v.SessionType { |
||||
case constant.SingleChatType: |
||||
if v.ContentType > constant.FriendNotificationBegin && v.ContentType < constant.FriendNotificationEnd { |
||||
c.friend.DoNotification(ctx, v) |
||||
} else if v.ContentType > constant.UserNotificationBegin && v.ContentType < constant.UserNotificationEnd { |
||||
c.user.DoNotification(ctx, v) |
||||
} else if datautil.Contain(v.ContentType, constant.GroupApplicationRejectedNotification, constant.GroupApplicationAcceptedNotification, constant.JoinGroupApplicationNotification) { |
||||
c.group.DoNotification(ctx, v) |
||||
} else if v.ContentType > constant.SignalingNotificationBegin && v.ContentType < constant.SignalingNotificationEnd { |
||||
|
||||
continue |
||||
} |
||||
case constant.GroupChatType, constant.SuperGroupChatType: |
||||
if v.ContentType > constant.GroupNotificationBegin && v.ContentType < constant.GroupNotificationEnd { |
||||
c.group.DoNotification(ctx, v) |
||||
} else if v.ContentType > constant.SignalingNotificationBegin && v.ContentType < constant.SignalingNotificationEnd { |
||||
continue |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
func (c *Conversation) doDeleteConversation(c2v common.Cmd2Value) { |
||||
node := c2v.Value.(common.DeleteConNode) |
||||
ctx := c2v.Ctx |
||||
// Mark messages related to this conversation for deletion
|
||||
err := c.db.UpdateMessageStatusBySourceID(context.Background(), node.SourceID, constant.MsgStatusHasDeleted, int32(node.SessionType)) |
||||
if err != nil { |
||||
log.ZError(ctx, "setMessageStatusBySourceID", err) |
||||
return |
||||
} |
||||
// Reset the session information, empty session
|
||||
err = c.db.ResetConversation(ctx, node.ConversationID) |
||||
if err != nil { |
||||
log.ZError(ctx, "ResetConversation err:", err) |
||||
} |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{"", constant.TotalUnreadMessageChanged, ""}}) |
||||
} |
||||
|
||||
func (c *Conversation) getConversationLatestMsgClientID(latestMsg string) string { |
||||
msg := &sdk_struct.MsgStruct{} |
||||
if err := json.Unmarshal([]byte(latestMsg), msg); err != nil { |
||||
log.ZError(context.Background(), "getConversationLatestMsgClientID", err, "latestMsg", latestMsg) |
||||
} |
||||
return msg.ClientMsgID |
||||
} |
||||
|
||||
func (c *Conversation) doUpdateConversation(c2v common.Cmd2Value) { |
||||
ctx := c2v.Ctx |
||||
node := c2v.Value.(common.UpdateConNode) |
||||
switch node.Action { |
||||
case constant.AddConOrUpLatMsg: |
||||
var list []*model_struct.LocalConversation |
||||
lc := node.Args.(model_struct.LocalConversation) |
||||
oc, err := c.db.GetConversation(ctx, lc.ConversationID) |
||||
if err == nil { |
||||
// log.Info("this is old conversation", *oc)
|
||||
if lc.LatestMsgSendTime >= oc.LatestMsgSendTime || c.getConversationLatestMsgClientID(lc.LatestMsg) == c.getConversationLatestMsgClientID(oc.LatestMsg) { // The session update of asynchronous messages is subject to the latest sending time
|
||||
err := c.db.UpdateColumnsConversation(ctx, node.ConID, map[string]interface{}{"latest_msg_send_time": lc.LatestMsgSendTime, "latest_msg": lc.LatestMsg}) |
||||
if err != nil { |
||||
log.ZError(ctx, "updateConversationLatestMsgModel", err, "conversationID", node.ConID) |
||||
} else { |
||||
oc.LatestMsgSendTime = lc.LatestMsgSendTime |
||||
oc.LatestMsg = lc.LatestMsg |
||||
list = append(list, oc) |
||||
c.ConversationListener().OnConversationChanged(utils.StructToJsonString(list)) |
||||
} |
||||
} |
||||
} else { |
||||
// log.Info("this is new conversation", lc)
|
||||
err4 := c.db.InsertConversation(ctx, &lc) |
||||
if err4 != nil { |
||||
// log.Error("internal", "insert new conversation err:", err4.Error())
|
||||
} else { |
||||
list = append(list, &lc) |
||||
c.ConversationListener().OnNewConversation(utils.StructToJsonString(list)) |
||||
} |
||||
} |
||||
|
||||
case constant.UnreadCountSetZero: |
||||
if err := c.db.UpdateColumnsConversation(ctx, node.ConID, map[string]interface{}{"unread_count": 0}); err != nil { |
||||
log.ZError(ctx, "updateConversationUnreadCountModel err", err, "conversationID", node.ConID) |
||||
} else { |
||||
totalUnreadCount, err := c.db.GetTotalUnreadMsgCountDB(ctx) |
||||
if err == nil { |
||||
c.ConversationListener().OnTotalUnreadMessageCountChanged(totalUnreadCount) |
||||
} else { |
||||
log.ZError(ctx, "getTotalUnreadMsgCountDB err", err) |
||||
} |
||||
|
||||
} |
||||
// case ConChange:
|
||||
// err, list := u.getAllConversationListModel()
|
||||
// if err != nil {
|
||||
// sdkLog("getAllConversationListModel database err:", err.Error())
|
||||
// } else {
|
||||
// if list == nil {
|
||||
// u.ConversationListenerx.OnConversationChanged(structToJsonString([]ConversationStruct{}))
|
||||
// } else {
|
||||
// u.ConversationListenerx.OnConversationChanged(structToJsonString(list))
|
||||
//
|
||||
// }
|
||||
// }
|
||||
case constant.IncrUnread: |
||||
err := c.db.IncrConversationUnreadCount(ctx, node.ConID) |
||||
if err != nil { |
||||
// log.Error("internal", "incrConversationUnreadCount database err:", err.Error())
|
||||
return |
||||
} |
||||
case constant.TotalUnreadMessageChanged: |
||||
totalUnreadCount, err := c.db.GetTotalUnreadMsgCountDB(ctx) |
||||
if err != nil { |
||||
// log.Error("internal", "TotalUnreadMessageChanged database err:", err.Error())
|
||||
} else { |
||||
c.ConversationListener().OnTotalUnreadMessageCountChanged(totalUnreadCount) |
||||
} |
||||
case constant.UpdateConFaceUrlAndNickName: |
||||
var lc model_struct.LocalConversation |
||||
st := node.Args.(common.SourceIDAndSessionType) |
||||
log.ZInfo(ctx, "UpdateConFaceUrlAndNickName", "st", st) |
||||
switch st.SessionType { |
||||
case constant.SingleChatType: |
||||
lc.UserID = st.SourceID |
||||
lc.ConversationID = c.getConversationIDBySessionType(st.SourceID, constant.SingleChatType) |
||||
lc.ConversationType = constant.SingleChatType |
||||
case constant.SuperGroupChatType: |
||||
conversationID, conversationType, err := c.getConversationTypeByGroupID(ctx, st.SourceID) |
||||
if err != nil { |
||||
return |
||||
} |
||||
lc.GroupID = st.SourceID |
||||
lc.ConversationID = conversationID |
||||
lc.ConversationType = conversationType |
||||
case constant.NotificationChatType: |
||||
lc.UserID = st.SourceID |
||||
lc.ConversationID = c.getConversationIDBySessionType(st.SourceID, constant.NotificationChatType) |
||||
lc.ConversationType = constant.NotificationChatType |
||||
default: |
||||
log.ZError(ctx, "not support sessionType", nil, "sessionType", st.SessionType) |
||||
return |
||||
} |
||||
lc.ShowName = st.Nickname |
||||
lc.FaceURL = st.FaceURL |
||||
err := c.db.UpdateConversation(ctx, &lc) |
||||
if err != nil { |
||||
// log.Error("internal", "setConversationFaceUrlAndNickName database err:", err.Error())
|
||||
return |
||||
} |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: lc.ConversationID, Action: constant.ConChange, Args: []string{lc.ConversationID}}}) |
||||
|
||||
case constant.UpdateLatestMessageChange: |
||||
conversationID := node.ConID |
||||
var latestMsg sdk_struct.MsgStruct |
||||
l, err := c.db.GetConversation(ctx, conversationID) |
||||
if err != nil { |
||||
log.ZError(ctx, "getConversationLatestMsgModel err", err, "conversationID", conversationID) |
||||
} else { |
||||
err := json.Unmarshal([]byte(l.LatestMsg), &latestMsg) |
||||
if err != nil { |
||||
log.ZError(ctx, "latestMsg,Unmarshal err", err) |
||||
} else { |
||||
latestMsg.IsRead = true |
||||
newLatestMessage := utils.StructToJsonString(latestMsg) |
||||
err = c.db.UpdateColumnsConversation(ctx, node.ConID, map[string]interface{}{"latest_msg_send_time": latestMsg.SendTime, "latest_msg": newLatestMessage}) |
||||
if err != nil { |
||||
log.ZError(ctx, "updateConversationLatestMsgModel err", err) |
||||
} |
||||
} |
||||
} |
||||
case constant.ConChange: |
||||
conversationIDs := node.Args.([]string) |
||||
conversations, err := c.db.GetMultipleConversationDB(ctx, conversationIDs) |
||||
if err != nil { |
||||
log.ZError(ctx, "getMultipleConversationModel err", err) |
||||
} else { |
||||
var newCList []*model_struct.LocalConversation |
||||
for _, v := range conversations { |
||||
if v.LatestMsgSendTime != 0 { |
||||
newCList = append(newCList, v) |
||||
} |
||||
} |
||||
c.ConversationListener().OnConversationChanged(utils.StructToJsonStringDefault(newCList)) |
||||
} |
||||
case constant.NewCon: |
||||
cidList := node.Args.([]string) |
||||
cLists, err := c.db.GetMultipleConversationDB(ctx, cidList) |
||||
if err != nil { |
||||
// log.Error("internal", "getMultipleConversationModel err :", err.Error())
|
||||
} else { |
||||
if cLists != nil { |
||||
// log.Info("internal", "getMultipleConversationModel success :", cLists)
|
||||
c.ConversationListener().OnNewConversation(utils.StructToJsonString(cLists)) |
||||
} |
||||
} |
||||
case constant.ConChangeDirect: |
||||
cidList := node.Args.(string) |
||||
c.ConversationListener().OnConversationChanged(cidList) |
||||
|
||||
case constant.NewConDirect: |
||||
cidList := node.Args.(string) |
||||
// log.Debug("internal", "NewConversation", cidList)
|
||||
c.ConversationListener().OnNewConversation(cidList) |
||||
|
||||
case constant.ConversationLatestMsgHasRead: |
||||
hasReadMsgList := node.Args.(map[string][]string) |
||||
var result []*model_struct.LocalConversation |
||||
var latestMsg sdk_struct.MsgStruct |
||||
var lc model_struct.LocalConversation |
||||
for conversationID, msgIDList := range hasReadMsgList { |
||||
LocalConversation, err := c.db.GetConversation(ctx, conversationID) |
||||
if err != nil { |
||||
// log.Error("internal", "get conversation err", err.Error(), conversationID)
|
||||
continue |
||||
} |
||||
err = utils.JsonStringToStruct(LocalConversation.LatestMsg, &latestMsg) |
||||
if err != nil { |
||||
// log.Error("internal", "JsonStringToStruct err", err.Error(), conversationID)
|
||||
continue |
||||
} |
||||
if utils.IsContain(latestMsg.ClientMsgID, msgIDList) { |
||||
latestMsg.IsRead = true |
||||
lc.ConversationID = conversationID |
||||
lc.LatestMsg = utils.StructToJsonString(latestMsg) |
||||
LocalConversation.LatestMsg = utils.StructToJsonString(latestMsg) |
||||
err := c.db.UpdateConversation(ctx, &lc) |
||||
if err != nil { |
||||
// log.Error("internal", "UpdateConversation database err:", err.Error())
|
||||
continue |
||||
} else { |
||||
result = append(result, LocalConversation) |
||||
} |
||||
} |
||||
} |
||||
if result != nil { |
||||
// log.Info("internal", "getMultipleConversationModel success :", result)
|
||||
c.ConversationListener().OnNewConversation(utils.StructToJsonString(result)) |
||||
} |
||||
case constant.SyncConversation: |
||||
|
||||
} |
||||
} |
||||
|
||||
func (c *Conversation) doUpdateMessage(c2v common.Cmd2Value) { |
||||
node := c2v.Value.(common.UpdateMessageNode) |
||||
ctx := c2v.Ctx |
||||
switch node.Action { |
||||
case constant.UpdateMsgFaceUrlAndNickName: |
||||
args := node.Args.(common.UpdateMessageInfo) |
||||
switch args.SessionType { |
||||
case constant.SingleChatType: |
||||
if args.UserID == c.loginUserID { |
||||
conversationIDList, err := c.db.GetAllSingleConversationIDList(ctx) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetAllSingleConversationIDList err", err) |
||||
return |
||||
} else { |
||||
log.ZDebug(ctx, "get single conversationID list", "conversationIDList", conversationIDList) |
||||
for _, conversationID := range conversationIDList { |
||||
err := c.db.UpdateMsgSenderFaceURLAndSenderNickname(ctx, conversationID, args.UserID, args.FaceURL, args.Nickname) |
||||
if err != nil { |
||||
log.ZError(ctx, "UpdateMsgSenderFaceURLAndSenderNickname err", err) |
||||
continue |
||||
} |
||||
} |
||||
|
||||
} |
||||
} else { |
||||
conversationID := c.getConversationIDBySessionType(args.UserID, constant.SingleChatType) |
||||
err := c.db.UpdateMsgSenderFaceURLAndSenderNickname(ctx, conversationID, args.UserID, args.FaceURL, args.Nickname) |
||||
if err != nil { |
||||
log.ZError(ctx, "UpdateMsgSenderFaceURLAndSenderNickname err", err) |
||||
} |
||||
|
||||
} |
||||
case constant.SuperGroupChatType: |
||||
conversationID := c.getConversationIDBySessionType(args.GroupID, constant.SuperGroupChatType) |
||||
err := c.db.UpdateMsgSenderFaceURLAndSenderNickname(ctx, conversationID, args.UserID, args.FaceURL, args.Nickname) |
||||
if err != nil { |
||||
log.ZError(ctx, "UpdateMsgSenderFaceURLAndSenderNickname err", err) |
||||
} |
||||
case constant.NotificationChatType: |
||||
conversationID := c.getConversationIDBySessionType(args.UserID, constant.NotificationChatType) |
||||
err := c.db.UpdateMsgSenderFaceURLAndSenderNickname(ctx, conversationID, args.UserID, args.FaceURL, args.Nickname) |
||||
if err != nil { |
||||
log.ZError(ctx, "UpdateMsgSenderFaceURLAndSenderNickname err", err) |
||||
} |
||||
default: |
||||
log.ZError(ctx, "not support sessionType", nil, "args", args) |
||||
return |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
// funcation (c *Conversation) doSyncReactionExtensions(c2v common.Cmd2Value) {
|
||||
// if c.ConversationListener == nil {
|
||||
// // log.Error("internal", "not set conversationListener")
|
||||
// return
|
||||
// }
|
||||
// node := c2v.Value.(common.SyncReactionExtensionsNode)
|
||||
// ctx := mcontext.NewCtx(node.OperationID)
|
||||
// switch node.Action {
|
||||
// case constant.SyncMessageListReactionExtensions:
|
||||
// args := node.Args.(syncReactionExtensionParams)
|
||||
// // log.Error(node.OperationID, "come SyncMessageListReactionExtensions", args)
|
||||
// var reqList []server_api_params.OperateMessageListReactionExtensionsReq
|
||||
// for _, v := range args.MessageList {
|
||||
// var temp server_api_params.OperateMessageListReactionExtensionsReq
|
||||
// temp.ClientMsgID = v.ClientMsgID
|
||||
// temp.MsgFirstModifyTime = v.MsgFirstModifyTime
|
||||
// reqList = append(reqList, temp)
|
||||
// }
|
||||
// var apiReq server_api_params.GetMessageListReactionExtensionsReq
|
||||
// apiReq.SourceID = args.SourceID
|
||||
// apiReq.TypeKeyList = args.TypeKeyList
|
||||
// apiReq.SessionType = args.SessionType
|
||||
// apiReq.MessageReactionKeyList = reqList
|
||||
// apiReq.IsExternalExtensions = args.IsExternalExtension
|
||||
// apiReq.OperationID = node.OperationID
|
||||
// apiResp, err := util.CallApi[server_api_params.GetMessageListReactionExtensionsResp](ctx, constant.GetMessageListReactionExtensionsRouter, &apiReq)
|
||||
// if err != nil {
|
||||
// // log.NewError(node.OperationID, utils.GetSelfFuncName(), "getMessageListReactionExtensions err:", err.Error())
|
||||
// return
|
||||
// }
|
||||
// // for _, result := range apiResp {
|
||||
// // log.Warn(node.OperationID, "api return reslut is:", result.ClientMsgID, result.ReactionExtensionList)
|
||||
// // }
|
||||
// onLocal := funcation(data []*model_struct.LocalChatLogReactionExtensions) []*server_api_params.SingleMessageExtensionResult {
|
||||
// var result []*server_api_params.SingleMessageExtensionResult
|
||||
// for _, v := range data {
|
||||
// temp := new(server_api_params.SingleMessageExtensionResult)
|
||||
// tempMap := make(map[string]*sdkws.KeyValue)
|
||||
// _ = json.Unmarshal(v.LocalReactionExtensions, &tempMap)
|
||||
// if len(args.TypeKeyList) != 0 {
|
||||
// for s, _ := range tempMap {
|
||||
// if !utils.IsContain(s, args.TypeKeyList) {
|
||||
// delete(tempMap, s)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// temp.ReactionExtensionList = tempMap
|
||||
// temp.ClientMsgID = v.ClientMsgID
|
||||
// result = append(result, temp)
|
||||
// }
|
||||
// return result
|
||||
// }(args.ExtendMessageList)
|
||||
// var onServer []*server_api_params.SingleMessageExtensionResult
|
||||
// for _, v := range *apiResp {
|
||||
// if v.ErrCode == 0 {
|
||||
// onServer = append(onServer, v)
|
||||
// }
|
||||
// }
|
||||
// aInBNot, _, sameA, _ := common.CheckReactionExtensionsDiff(onServer, onLocal)
|
||||
// for _, v := range aInBNot {
|
||||
// // log.Error(node.OperationID, "come InsertMessageReactionExtension", args, v.ClientMsgID)
|
||||
// if len(v.ReactionExtensionList) > 0 {
|
||||
// temp := model_struct.LocalChatLogReactionExtensions{ClientMsgID: v.ClientMsgID, LocalReactionExtensions: []byte(utils.StructToJsonString(v.ReactionExtensionList))}
|
||||
// err := c.db.InsertMessageReactionExtension(ctx, &temp)
|
||||
// if err != nil {
|
||||
// // log.Error(node.OperationID, "InsertMessageReactionExtension err:", err.Error())
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
// var changedKv []*sdkws.KeyValue
|
||||
// for _, value := range v.ReactionExtensionList {
|
||||
// changedKv = append(changedKv, value)
|
||||
// }
|
||||
// if len(changedKv) > 0 {
|
||||
// c.msgListener.OnRecvMessageExtensionsChanged(v.ClientMsgID, utils.StructToJsonString(changedKv))
|
||||
// }
|
||||
// }
|
||||
// // for _, result := range sameA {
|
||||
// // log.ZWarn(ctx, "result", result.ReactionExtensionList, result.ClientMsgID)
|
||||
// // }
|
||||
// for _, v := range sameA {
|
||||
// // log.Error(node.OperationID, "come sameA", v.ClientMsgID, v.ReactionExtensionList)
|
||||
// tempMap := make(map[string]*sdkws.KeyValue)
|
||||
// for _, extensions := range args.ExtendMessageList {
|
||||
// if v.ClientMsgID == extensions.ClientMsgID {
|
||||
// _ = json.Unmarshal(extensions.LocalReactionExtensions, &tempMap)
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// if len(v.ReactionExtensionList) == 0 {
|
||||
// err := c.db.DeleteMessageReactionExtension(ctx, v.ClientMsgID)
|
||||
// if err != nil {
|
||||
// // log.Error(node.OperationID, "DeleteMessageReactionExtension err:", err.Error())
|
||||
// continue
|
||||
// }
|
||||
// var deleteKeyList []string
|
||||
// for key, _ := range tempMap {
|
||||
// deleteKeyList = append(deleteKeyList, key)
|
||||
// }
|
||||
// if len(deleteKeyList) > 0 {
|
||||
// c.msgListener.OnRecvMessageExtensionsDeleted(v.ClientMsgID, utils.StructToJsonString(deleteKeyList))
|
||||
// }
|
||||
// } else {
|
||||
// deleteKeyList, changedKv := funcation(local, server map[string]*sdkws.KeyValue) ([]string, []*sdkws.KeyValue) {
|
||||
// var deleteKeyList []string
|
||||
// var changedKv []*sdkws.KeyValue
|
||||
// for k, v := range local {
|
||||
// ia, ok := server[k]
|
||||
// if ok {
|
||||
// //服务器不同的kv
|
||||
// if ia.Value != v.Value {
|
||||
// changedKv = append(changedKv, ia)
|
||||
// }
|
||||
// } else {
|
||||
// //服务器已经没有kv
|
||||
// deleteKeyList = append(deleteKeyList, k)
|
||||
// }
|
||||
// }
|
||||
// //从服务器新增的kv
|
||||
// for k, v := range server {
|
||||
// _, ok := local[k]
|
||||
// if !ok {
|
||||
// changedKv = append(changedKv, v)
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// return deleteKeyList, changedKv
|
||||
// }(tempMap, v.ReactionExtensionList)
|
||||
// extendMsg := model_struct.LocalChatLogReactionExtensions{ClientMsgID: v.ClientMsgID, LocalReactionExtensions: []byte(utils.StructToJsonString(v.ReactionExtensionList))}
|
||||
// err = c.db.UpdateMessageReactionExtension(ctx, &extendMsg)
|
||||
// if err != nil {
|
||||
// // log.Error(node.OperationID, "UpdateMessageReactionExtension err:", err.Error())
|
||||
// continue
|
||||
// }
|
||||
// if len(deleteKeyList) > 0 {
|
||||
// c.msgListener.OnRecvMessageExtensionsDeleted(v.ClientMsgID, utils.StructToJsonString(deleteKeyList))
|
||||
// }
|
||||
// if len(changedKv) > 0 {
|
||||
// c.msgListener.OnRecvMessageExtensionsChanged(v.ClientMsgID, utils.StructToJsonString(changedKv))
|
||||
// }
|
||||
// }
|
||||
// //err := c.db.GetAndUpdateMessageReactionExtension(v.ClientMsgID, v.ReactionExtensionList)
|
||||
// //if err != nil {
|
||||
// // log.Error(node.OperationID, "GetAndUpdateMessageReactionExtension err:", err.Error())
|
||||
// // continue
|
||||
// //}
|
||||
// //var changedKv []*server_api_params.KeyValue
|
||||
// //for _, value := range v.ReactionExtensionList {
|
||||
// // changedKv = append(changedKv, value)
|
||||
// //}
|
||||
// //if len(changedKv) > 0 {
|
||||
// // c.msgListener.OnRecvMessageExtensionsChanged(v.ClientMsgID, utils.StructToJsonString(changedKv))
|
||||
// //}
|
||||
// }
|
||||
// case constant.SyncMessageListTypeKeyInfo:
|
||||
// messageList := node.Args.([]*sdk_struct.MsgStruct)
|
||||
// var sourceID string
|
||||
// var sessionType int32
|
||||
// var reqList []server_api_params.OperateMessageListReactionExtensionsReq
|
||||
// var temp server_api_params.OperateMessageListReactionExtensionsReq
|
||||
// for _, v := range messageList {
|
||||
// //todo syncMessage must sync
|
||||
// message, err := c.db.GetMessage(ctx, "", v.ClientMsgID)
|
||||
// if err != nil {
|
||||
// // log.Error(node.OperationID, "GetMessageController err:", err.Error(), *v)
|
||||
// continue
|
||||
// }
|
||||
// temp.ClientMsgID = message.ClientMsgID
|
||||
// temp.MsgFirstModifyTime = message.MsgFirstModifyTime
|
||||
// reqList = append(reqList, temp)
|
||||
// switch message.SessionType {
|
||||
// case constant.SingleChatType:
|
||||
// sourceID = message.SendID + message.RecvID
|
||||
// case constant.NotificationChatType:
|
||||
// sourceID = message.RecvID
|
||||
// case constant.GroupChatType, constant.SuperGroupChatType:
|
||||
// sourceID = message.RecvID
|
||||
// }
|
||||
// sessionType = message.SessionType
|
||||
// }
|
||||
// var apiReq server_api_params.GetMessageListReactionExtensionsReq
|
||||
// apiReq.SourceID = sourceID
|
||||
// apiReq.SessionType = sessionType
|
||||
// apiReq.MessageReactionKeyList = reqList
|
||||
// apiReq.OperationID = node.OperationID
|
||||
// //var apiResp server_api_params.GetMessageListReactionExtensionsResp
|
||||
//
|
||||
// apiResp, err := util.CallApi[server_api_params.GetMessageListReactionExtensionsResp](ctx, constant.GetMessageListReactionExtensionsRouter, &apiReq)
|
||||
// if err != nil {
|
||||
// // log.Error(node.OperationID, "GetMessageListReactionExtensions from server err:", err.Error(), apiReq)
|
||||
// return
|
||||
// }
|
||||
// var messageChangedList []*messageKvList
|
||||
// for _, v := range *apiResp {
|
||||
// if v.ErrCode == 0 {
|
||||
// var changedKv []*sdkws.KeyValue
|
||||
// var prefixTypeKey []string
|
||||
// extendMsg, _ := c.db.GetMessageReactionExtension(ctx, v.ClientMsgID)
|
||||
// localKV := make(map[string]*sdkws.KeyValue)
|
||||
// _ = json.Unmarshal(extendMsg.LocalReactionExtensions, &localKV)
|
||||
// for typeKey, value := range v.ReactionExtensionList {
|
||||
// oldValue, ok := localKV[typeKey]
|
||||
// if ok {
|
||||
// if !cmp.Equal(value, oldValue) {
|
||||
// localKV[typeKey] = value
|
||||
// prefixTypeKey = append(prefixTypeKey, getPrefixTypeKey(typeKey))
|
||||
// changedKv = append(changedKv, value)
|
||||
// }
|
||||
// } else {
|
||||
// localKV[typeKey] = value
|
||||
// prefixTypeKey = append(prefixTypeKey, getPrefixTypeKey(typeKey))
|
||||
// changedKv = append(changedKv, value)
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
// extendMsg.LocalReactionExtensions = []byte(utils.StructToJsonString(localKV))
|
||||
// _ = c.db.UpdateMessageReactionExtension(ctx, extendMsg)
|
||||
// if len(changedKv) > 0 {
|
||||
// c.msgListener.OnRecvMessageExtensionsChanged(extendMsg.ClientMsgID, utils.StructToJsonString(changedKv))
|
||||
// }
|
||||
// prefixTypeKey = utils.RemoveRepeatedStringInList(prefixTypeKey)
|
||||
// if len(prefixTypeKey) > 0 && c.msgKvListener != nil {
|
||||
// var result []*sdk.SingleTypeKeyInfoSum
|
||||
// oneMessageChanged := new(messageKvList)
|
||||
// oneMessageChanged.ClientMsgID = extendMsg.ClientMsgID
|
||||
// for _, v := range prefixTypeKey {
|
||||
// singleResult := new(sdk.SingleTypeKeyInfoSum)
|
||||
// singleResult.TypeKey = v
|
||||
// for typeKey, value := range localKV {
|
||||
// if strings.HasPrefix(typeKey, v) {
|
||||
// singleTypeKeyInfo := new(sdk.SingleTypeKeyInfo)
|
||||
// err := json.Unmarshal([]byte(value.Value), singleTypeKeyInfo)
|
||||
// if err != nil {
|
||||
// continue
|
||||
// }
|
||||
// if _, ok := singleTypeKeyInfo.InfoList[c.loginUserID]; ok {
|
||||
// singleResult.IsContainSelf = true
|
||||
// }
|
||||
// for _, info := range singleTypeKeyInfo.InfoList {
|
||||
// v := *info
|
||||
// singleResult.InfoList = append(singleResult.InfoList, &v)
|
||||
// }
|
||||
// singleResult.Counter += singleTypeKeyInfo.Counter
|
||||
// }
|
||||
// }
|
||||
// result = append(result, singleResult)
|
||||
// }
|
||||
// oneMessageChanged.ChangedKvList = result
|
||||
// messageChangedList = append(messageChangedList, oneMessageChanged)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// if len(messageChangedList) > 0 && c.msgKvListener != nil {
|
||||
// c.msgKvListener.OnMessageKvInfoChanged(utils.StructToJsonString(messageChangedList))
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
func (c *Conversation) DoConversationChangedNotification(ctx context.Context, msg *sdkws.MsgData) { |
||||
//var notification sdkws.ConversationChangedNotification
|
||||
tips := &sdkws.ConversationUpdateTips{} |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, tips); err != nil { |
||||
log.ZError(ctx, "UnmarshalNotificationElem err", err, "msg", msg) |
||||
return |
||||
} |
||||
|
||||
c.SyncConversations(ctx, tips.ConversationIDList) |
||||
|
||||
} |
||||
|
||||
func (c *Conversation) DoConversationIsPrivateChangedNotification(ctx context.Context, msg *sdkws.MsgData) { |
||||
tips := &sdkws.ConversationSetPrivateTips{} |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, tips); err != nil { |
||||
log.ZError(ctx, "UnmarshalNotificationElem err", err, "msg", msg) |
||||
return |
||||
} |
||||
|
||||
c.SyncConversations(ctx, []string{tips.ConversationID}) |
||||
|
||||
} |
@ -0,0 +1,57 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import ( |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
|
||||
pbConversation "github.com/openimsdk/protocol/conversation" |
||||
) |
||||
|
||||
func ServerConversationToLocal(conversation *pbConversation.Conversation) *model_struct.LocalConversation { |
||||
return &model_struct.LocalConversation{ |
||||
ConversationID: conversation.ConversationID, |
||||
ConversationType: conversation.ConversationType, |
||||
UserID: conversation.UserID, |
||||
GroupID: conversation.GroupID, |
||||
RecvMsgOpt: conversation.RecvMsgOpt, |
||||
GroupAtType: conversation.GroupAtType, |
||||
IsPinned: conversation.IsPinned, |
||||
BurnDuration: conversation.BurnDuration, |
||||
IsPrivateChat: conversation.IsPrivateChat, |
||||
AttachedInfo: conversation.AttachedInfo, |
||||
Ex: conversation.Ex, |
||||
MsgDestructTime: conversation.MsgDestructTime, |
||||
IsMsgDestruct: conversation.IsMsgDestruct, |
||||
} |
||||
} |
||||
|
||||
func LocalConversationToServer(conversation *model_struct.LocalConversation) *pbConversation.Conversation { |
||||
return &pbConversation.Conversation{ |
||||
ConversationID: conversation.ConversationID, |
||||
ConversationType: conversation.ConversationType, |
||||
UserID: conversation.UserID, |
||||
GroupID: conversation.GroupID, |
||||
RecvMsgOpt: conversation.RecvMsgOpt, |
||||
GroupAtType: conversation.GroupAtType, |
||||
IsPinned: conversation.IsPinned, |
||||
BurnDuration: conversation.BurnDuration, |
||||
IsPrivateChat: conversation.IsPrivateChat, |
||||
AttachedInfo: conversation.AttachedInfo, |
||||
MsgDestructTime: conversation.MsgDestructTime, |
||||
Ex: conversation.Ex, |
||||
IsMsgDestruct: conversation.IsMsgDestruct, |
||||
} |
||||
} |
@ -0,0 +1,492 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/tools/log" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
"os" |
||||
"path/filepath" |
||||
"strings" |
||||
) |
||||
|
||||
func (c *Conversation) CreateTextMessage(ctx context.Context, text string) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Text) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.TextElem = &sdk_struct.TextElem{Content: text} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateAdvancedTextMessage(ctx context.Context, text string, messageEntities []*sdk_struct.MessageEntity) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.AdvancedText) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.AdvancedTextElem = &sdk_struct.AdvancedTextElem{ |
||||
Text: text, |
||||
MessageEntityList: messageEntities, |
||||
} |
||||
return &s, nil |
||||
} |
||||
|
||||
func (c *Conversation) CreateTextAtMessage(ctx context.Context, text string, userIDList []string, usersInfo []*sdk_struct.AtInfo, qs *sdk_struct.MsgStruct) (*sdk_struct.MsgStruct, error) { |
||||
if text == "" { |
||||
return nil, errors.New("text can not be empty") |
||||
} |
||||
if len(userIDList) > 10 { |
||||
return nil, sdkerrs.ErrArgs |
||||
} |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.AtText) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
//Avoid nested references
|
||||
if qs != nil { |
||||
if qs.ContentType == constant.Quote { |
||||
qs.ContentType = constant.Text |
||||
qs.TextElem = &sdk_struct.TextElem{Content: qs.QuoteElem.Text} |
||||
} |
||||
} |
||||
s.AtTextElem = &sdk_struct.AtTextElem{ |
||||
Text: text, |
||||
AtUserList: userIDList, |
||||
AtUsersInfo: usersInfo, |
||||
QuoteMessage: qs, |
||||
} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateLocationMessage(ctx context.Context, description string, longitude, latitude float64) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Location) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.LocationElem = &sdk_struct.LocationElem{ |
||||
Description: description, |
||||
Longitude: longitude, |
||||
Latitude: latitude, |
||||
} |
||||
return &s, nil |
||||
} |
||||
|
||||
func (c *Conversation) CreateCustomMessage(ctx context.Context, data, extension string, description string) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Custom) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.CustomElem = &sdk_struct.CustomElem{ |
||||
Data: data, |
||||
Extension: extension, |
||||
Description: description, |
||||
} |
||||
return &s, nil |
||||
|
||||
} |
||||
func (c *Conversation) CreateQuoteMessage(ctx context.Context, text string, qs *sdk_struct.MsgStruct) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Quote) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
//Avoid nested references
|
||||
if qs.ContentType == constant.Quote { |
||||
qs.ContentType = constant.Text |
||||
qs.TextElem = &sdk_struct.TextElem{Content: qs.QuoteElem.Text} |
||||
} |
||||
s.QuoteElem = &sdk_struct.QuoteElem{ |
||||
Text: text, |
||||
QuoteMessage: qs, |
||||
} |
||||
return &s, nil |
||||
|
||||
} |
||||
func (c *Conversation) CreateAdvancedQuoteMessage(ctx context.Context, text string, qs *sdk_struct.MsgStruct, messageEntities []*sdk_struct.MessageEntity) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Quote) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
//Avoid nested references
|
||||
if qs.ContentType == constant.Quote { |
||||
//qs.Content = qs.QuoteElem.Text
|
||||
qs.ContentType = constant.Text |
||||
qs.TextElem = &sdk_struct.TextElem{Content: qs.QuoteElem.Text} |
||||
} |
||||
s.QuoteElem = &sdk_struct.QuoteElem{ |
||||
Text: text, |
||||
QuoteMessage: qs, |
||||
MessageEntityList: messageEntities, |
||||
} |
||||
return &s, nil |
||||
} |
||||
|
||||
func (c *Conversation) CreateCardMessage(ctx context.Context, card *sdk_struct.CardElem) (*sdk_struct.MsgStruct, |
||||
error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Card) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.CardElem = card |
||||
return &s, nil |
||||
} |
||||
|
||||
func (c *Conversation) CreateVideoMessageFromFullPath(ctx context.Context, videoFullPath string, videoType string, |
||||
duration int64, snapshotFullPath string) (*sdk_struct.MsgStruct, error) { |
||||
dstFile := utils.FileTmpPath(videoFullPath, c.DataDir) //a->b
|
||||
written, err := utils.CopyFile(videoFullPath, dstFile) |
||||
if err != nil { |
||||
//log.Error("internal", "open file failed: ", err, videoFullPath)
|
||||
return nil, err |
||||
} |
||||
log.ZDebug(ctx, "videoFullPath dstFile", "videoFullPath", videoFullPath, |
||||
"dstFile", dstFile, "written", written) |
||||
|
||||
dstFile = utils.FileTmpPath(snapshotFullPath, c.DataDir) //a->b
|
||||
sWritten, err := utils.CopyFile(snapshotFullPath, dstFile) |
||||
if err != nil { |
||||
//log.Error("internal", "open file failed: ", err, snapshotFullPath)
|
||||
return nil, err |
||||
} |
||||
log.ZDebug(ctx, "snapshotFullPath dstFile", "snapshotFullPath", snapshotFullPath, |
||||
"dstFile", dstFile, "sWritten", sWritten) |
||||
|
||||
s := sdk_struct.MsgStruct{} |
||||
err = c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Video) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.VideoElem = &sdk_struct.VideoElem{ |
||||
VideoPath: videoFullPath, |
||||
VideoType: videoType, |
||||
Duration: duration, |
||||
} |
||||
if snapshotFullPath == "" { |
||||
s.VideoElem.SnapshotPath = "" |
||||
} else { |
||||
s.VideoElem.SnapshotPath = snapshotFullPath |
||||
} |
||||
fi, err := os.Stat(s.VideoElem.VideoPath) |
||||
if err != nil { |
||||
//log.Error("internal", "get file Attributes error", err.Error())
|
||||
return nil, err |
||||
} |
||||
s.VideoElem.VideoSize = fi.Size() |
||||
if snapshotFullPath != "" { |
||||
imageInfo, err := getImageInfo(s.VideoElem.SnapshotPath) |
||||
if err != nil { |
||||
log.ZError(ctx, "getImageInfo err:", err, "snapshotFullPath", snapshotFullPath) |
||||
return nil, err |
||||
} |
||||
s.VideoElem.SnapshotHeight = imageInfo.Height |
||||
s.VideoElem.SnapshotWidth = imageInfo.Width |
||||
s.VideoElem.SnapshotSize = imageInfo.Size |
||||
} |
||||
return &s, nil |
||||
|
||||
} |
||||
func (c *Conversation) CreateFileMessageFromFullPath(ctx context.Context, fileFullPath string, fileName string) (*sdk_struct.MsgStruct, error) { |
||||
dstFile := utils.FileTmpPath(fileFullPath, c.DataDir) |
||||
_, err := utils.CopyFile(fileFullPath, dstFile) |
||||
if err != nil { |
||||
//log.Error("internal", "open file failed: ", err.Error(), fileFullPath)
|
||||
return nil, err |
||||
|
||||
} |
||||
s := sdk_struct.MsgStruct{} |
||||
err = c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.File) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
fi, err := os.Stat(fileFullPath) |
||||
if err != nil { |
||||
//log.Error("internal", "get file Attributes error", err.Error())
|
||||
return nil, err |
||||
} |
||||
s.FileElem = &sdk_struct.FileElem{ |
||||
FilePath: fileFullPath, |
||||
FileName: fileName, |
||||
FileSize: fi.Size(), |
||||
} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateImageMessageFromFullPath(ctx context.Context, imageFullPath string) (*sdk_struct.MsgStruct, error) { |
||||
dstFile := utils.FileTmpPath(imageFullPath, c.DataDir) //a->b
|
||||
_, err := utils.CopyFile(imageFullPath, dstFile) |
||||
if err != nil { |
||||
//log.Error(operationID, "open file failed: ", err, imageFullPath)
|
||||
return nil, err |
||||
} |
||||
s := sdk_struct.MsgStruct{} |
||||
err = c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Picture) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
imageInfo, err := getImageInfo(imageFullPath) |
||||
if err != nil { |
||||
//log.Error(operationID, "getImageInfo err:", err.Error())
|
||||
return nil, err |
||||
} |
||||
s.PictureElem = &sdk_struct.PictureElem{ |
||||
SourcePath: imageFullPath, |
||||
SourcePicture: &sdk_struct.PictureBaseInfo{ |
||||
Width: imageInfo.Width, |
||||
Height: imageInfo.Height, |
||||
Type: imageInfo.Type, |
||||
}, |
||||
} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateSoundMessageFromFullPath(ctx context.Context, soundPath string, duration int64) (*sdk_struct.MsgStruct, error) { |
||||
dstFile := utils.FileTmpPath(soundPath, c.DataDir) //a->b
|
||||
_, err := utils.CopyFile(soundPath, dstFile) |
||||
if err != nil { |
||||
//log.Error("internal", "open file failed: ", err, soundPath)
|
||||
return nil, err |
||||
} |
||||
|
||||
s := sdk_struct.MsgStruct{} |
||||
err = c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Sound) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
fi, err := os.Stat(soundPath) |
||||
if err != nil { |
||||
//log.Error("internal", "getSoundInfo err:", err.Error(), s.SoundElem.SoundPath)
|
||||
return nil, err |
||||
} |
||||
s.SoundElem = &sdk_struct.SoundElem{ |
||||
SoundPath: soundPath, |
||||
Duration: duration, |
||||
DataSize: fi.Size(), |
||||
SoundType: strings.Replace(filepath.Ext(fi.Name()), ".", "", 1), |
||||
} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateImageMessage(ctx context.Context, imagePath string) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Picture) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
path := c.DataDir + imagePath |
||||
//path := imagePath
|
||||
imageInfo, err := getImageInfo(path) |
||||
if err != nil { |
||||
//log.Error("internal", "get imageInfo err", err.Error())
|
||||
return nil, err |
||||
} |
||||
s.PictureElem = &sdk_struct.PictureElem{ |
||||
SourcePath: path, |
||||
SourcePicture: &sdk_struct.PictureBaseInfo{ |
||||
Width: imageInfo.Width, |
||||
Height: imageInfo.Height, |
||||
Type: imageInfo.Type, |
||||
}, |
||||
} |
||||
return &s, nil |
||||
|
||||
} |
||||
func (c *Conversation) CreateImageMessageByURL(ctx context.Context, sourcePath string, sourcePicture, bigPicture, snapshotPicture sdk_struct.PictureBaseInfo) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Picture) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.PictureElem = &sdk_struct.PictureElem{ |
||||
SourcePath: sourcePath, |
||||
SourcePicture: &sourcePicture, |
||||
BigPicture: &bigPicture, |
||||
SnapshotPicture: &snapshotPicture, |
||||
} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateSoundMessageByURL(ctx context.Context, soundElem *sdk_struct.SoundBaseInfo) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Sound) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.SoundElem = &sdk_struct.SoundElem{ |
||||
UUID: soundElem.UUID, |
||||
SoundPath: soundElem.SoundPath, |
||||
SourceURL: soundElem.SourceURL, |
||||
DataSize: soundElem.DataSize, |
||||
Duration: soundElem.Duration, |
||||
SoundType: soundElem.SoundType, |
||||
} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateSoundMessage(ctx context.Context, soundPath string, duration int64) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Sound) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
path := c.DataDir + soundPath |
||||
fi, err := os.Stat(path) |
||||
if err != nil { |
||||
//log.Error("internal", "get sound info err", err.Error())
|
||||
return nil, err |
||||
} |
||||
s.SoundElem = &sdk_struct.SoundElem{ |
||||
SoundPath: path, |
||||
Duration: duration, |
||||
DataSize: fi.Size(), |
||||
} |
||||
if typ := strings.Replace(filepath.Ext(fi.Name()), ".", "", 1); typ != "" { |
||||
s.SoundElem.SoundType = "audio/" + strings.ToLower(typ) |
||||
} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateVideoMessageByURL(ctx context.Context, videoElem sdk_struct.VideoBaseInfo) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Video) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.VideoElem = &sdk_struct.VideoElem{ |
||||
VideoPath: videoElem.VideoPath, |
||||
VideoUUID: videoElem.VideoUUID, |
||||
VideoURL: videoElem.VideoURL, |
||||
VideoType: videoElem.VideoType, |
||||
VideoSize: videoElem.VideoSize, |
||||
Duration: videoElem.Duration, |
||||
SnapshotPath: videoElem.SnapshotPath, |
||||
SnapshotUUID: videoElem.SnapshotUUID, |
||||
SnapshotSize: videoElem.SnapshotSize, |
||||
SnapshotURL: videoElem.SnapshotURL, |
||||
SnapshotWidth: videoElem.SnapshotWidth, |
||||
SnapshotHeight: videoElem.SnapshotHeight, |
||||
SnapshotType: videoElem.SnapshotType, |
||||
} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateVideoMessage(ctx context.Context, videoPath string, videoType string, duration int64, snapshotPath string) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Video) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.VideoElem = &sdk_struct.VideoElem{} |
||||
s.VideoElem.VideoPath = c.DataDir + videoPath |
||||
s.VideoElem.VideoType = videoType |
||||
s.VideoElem.Duration = duration |
||||
if snapshotPath == "" { |
||||
s.VideoElem.SnapshotPath = "" |
||||
} else { |
||||
s.VideoElem.SnapshotPath = c.DataDir + snapshotPath |
||||
} |
||||
fi, err := os.Stat(s.VideoElem.VideoPath) |
||||
if err != nil { |
||||
log.ZDebug(ctx, "get video file error", "videoPath", videoPath, "snapshotPath", snapshotPath) |
||||
return nil, err |
||||
} |
||||
s.VideoElem.VideoSize = fi.Size() |
||||
if snapshotPath != "" { |
||||
imageInfo, err := getImageInfo(s.VideoElem.SnapshotPath) |
||||
if err != nil { |
||||
//log.Error("internal", "get snapshot info ", err.Error())
|
||||
return nil, err |
||||
} |
||||
s.VideoElem.SnapshotHeight = imageInfo.Height |
||||
s.VideoElem.SnapshotWidth = imageInfo.Width |
||||
s.VideoElem.SnapshotSize = imageInfo.Size |
||||
} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateFileMessageByURL(ctx context.Context, fileElem sdk_struct.FileBaseInfo) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.File) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.FileElem = &sdk_struct.FileElem{ |
||||
FilePath: fileElem.FilePath, |
||||
UUID: fileElem.UUID, |
||||
SourceURL: fileElem.SourceURL, |
||||
FileName: fileElem.FileName, |
||||
FileSize: fileElem.FileSize, |
||||
FileType: fileElem.FileType, |
||||
} |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateFileMessage(ctx context.Context, filePath string, fileName string) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{FileElem: &sdk_struct.FileElem{}} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.File) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.FileElem.FilePath = c.DataDir + filePath |
||||
s.FileElem.FileName = fileName |
||||
fi, err := os.Stat(s.FileElem.FilePath) |
||||
if err != nil { |
||||
//log.Error("internal", "get file message err", err.Error())
|
||||
return nil, err |
||||
} |
||||
s.FileElem.FileSize = fi.Size() |
||||
s.Content = utils.StructToJsonString(s.FileElem) |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateMergerMessage(ctx context.Context, messages []*sdk_struct.MsgStruct, title string, summaries []string) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{MergeElem: &sdk_struct.MergeElem{}} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Merger) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.MergeElem.AbstractList = summaries |
||||
s.MergeElem.Title = title |
||||
s.MergeElem.MultiMessage = messages |
||||
s.Content = utils.StructToJsonString(s.MergeElem) |
||||
return &s, nil |
||||
} |
||||
func (c *Conversation) CreateFaceMessage(ctx context.Context, index int, data string) (*sdk_struct.MsgStruct, error) { |
||||
s := sdk_struct.MsgStruct{FaceElem: &sdk_struct.FaceElem{}} |
||||
err := c.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Face) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
s.FaceElem.Data = data |
||||
s.FaceElem.Index = index |
||||
s.Content = utils.StructToJsonString(s.FaceElem) |
||||
return &s, nil |
||||
} |
||||
|
||||
func (c *Conversation) CreateForwardMessage(ctx context.Context, s *sdk_struct.MsgStruct) (*sdk_struct.MsgStruct, error) { |
||||
if s.Status != constant.MsgStatusSendSuccess { |
||||
log.ZError(ctx, "only send success message can be Forward", |
||||
errors.New("only send success message can be Forward")) |
||||
return nil, errors.New("only send success message can be Forward") |
||||
} |
||||
err := c.initBasicInfo(ctx, s, constant.UserMsgType, s.ContentType) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
//Forward message seq is set to 0
|
||||
s.Seq = 0 |
||||
s.Status = constant.MsgStatusSendSuccess |
||||
return s, nil |
||||
} |
@ -0,0 +1,245 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
|
||||
"github.com/jinzhu/copier" |
||||
pbMsg "github.com/openimsdk/protocol/msg" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
// Delete the local and server
|
||||
// Delete the local, do not change the server data
|
||||
// To delete the server, you need to change the local message status to delete
|
||||
func (c *Conversation) clearConversationFromLocalAndSvr(ctx context.Context, conversationID string, f func(ctx context.Context, conversationID string) error) error { |
||||
_, err := c.db.GetConversation(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
// Use conversationID to remove conversations and messages from the server first
|
||||
err = c.clearConversationMsgFromSvr(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := c.clearConversationAndDeleteAllMsg(ctx, conversationID, false, f); err != nil { |
||||
return err |
||||
} |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: []string{conversationID}}}) |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}}) |
||||
return nil |
||||
} |
||||
|
||||
func (c *Conversation) clearConversationAndDeleteAllMsg(ctx context.Context, conversationID string, markDelete bool, f func(ctx context.Context, conversationID string) error) error { |
||||
err := c.getConversationMaxSeqAndSetHasRead(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if markDelete { |
||||
err = c.db.MarkDeleteConversationAllMessages(ctx, conversationID) |
||||
} else { |
||||
err = c.db.DeleteConversationAllMessages(ctx, conversationID) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.ZDebug(ctx, "reset conversation", "conversationID", conversationID) |
||||
err = f(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// To delete session information, delete the server first, and then invoke the interface.
|
||||
// The client receives a callback to delete all local information.
|
||||
func (c *Conversation) clearConversationMsgFromSvr(ctx context.Context, conversationID string) error { |
||||
var apiReq pbMsg.ClearConversationsMsgReq |
||||
apiReq.UserID = c.loginUserID |
||||
apiReq.ConversationIDs = []string{conversationID} |
||||
return util.ApiPost(ctx, constant.ClearConversationMsgRouter, &apiReq, nil) |
||||
} |
||||
|
||||
// Delete all messages
|
||||
func (c *Conversation) deleteAllMsgFromLocalAndSvr(ctx context.Context) error { |
||||
// Delete the server first (high error rate), then delete it.
|
||||
err := c.deleteAllMessageFromSvr(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
err = c.deleteAllMsgFromLocal(ctx, false) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}}) |
||||
return nil |
||||
} |
||||
|
||||
// Delete all server messages
|
||||
func (c *Conversation) deleteAllMessageFromSvr(ctx context.Context) error { |
||||
var apiReq pbMsg.UserClearAllMsgReq |
||||
apiReq.UserID = c.loginUserID |
||||
err := util.ApiPost(ctx, constant.ClearAllMsgRouter, &apiReq, nil) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Delete all messages from the local
|
||||
func (c *Conversation) deleteAllMsgFromLocal(ctx context.Context, markDelete bool) error { |
||||
conversations, err := c.db.GetAllConversationListDB(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
var successCids []string |
||||
log.ZDebug(ctx, "deleteAllMsgFromLocal", "conversations", conversations, "markDelete", markDelete) |
||||
for _, v := range conversations { |
||||
if err := c.clearConversationAndDeleteAllMsg(ctx, v.ConversationID, markDelete, c.db.ClearConversation); err != nil { |
||||
log.ZError(ctx, "clearConversation err", err, "conversationID", v.ConversationID) |
||||
continue |
||||
} |
||||
successCids = append(successCids, v.ConversationID) |
||||
} |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: successCids}}) |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}}) |
||||
return nil |
||||
|
||||
} |
||||
|
||||
// Delete a message from the local
|
||||
func (c *Conversation) deleteMessage(ctx context.Context, conversationID string, clientMsgID string) error { |
||||
if err := c.deleteMessageFromSvr(ctx, conversationID, clientMsgID); err != nil { |
||||
return err |
||||
} |
||||
return c.deleteMessageFromLocal(ctx, conversationID, clientMsgID) |
||||
} |
||||
|
||||
// The user deletes part of the message from the server
|
||||
func (c *Conversation) deleteMessageFromSvr(ctx context.Context, conversationID string, clientMsgID string) error { |
||||
_, err := c.db.GetMessage(ctx, conversationID, clientMsgID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
localMessage, err := c.db.GetMessage(ctx, conversationID, clientMsgID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if localMessage.Status == constant.MsgStatusSendFailed { |
||||
log.ZInfo(ctx, "delete msg status is send failed, do not need delete", "msg", localMessage) |
||||
return nil |
||||
} |
||||
if localMessage.Seq == 0 { |
||||
log.ZInfo(ctx, "delete msg seq is 0, try again", "msg", localMessage) |
||||
return sdkerrs.ErrMsgHasNoSeq |
||||
} |
||||
var apiReq pbMsg.DeleteMsgsReq |
||||
apiReq.UserID = c.loginUserID |
||||
apiReq.Seqs = []int64{localMessage.Seq} |
||||
apiReq.ConversationID = conversationID |
||||
return util.ApiPost(ctx, constant.DeleteMsgsRouter, &apiReq, nil) |
||||
} |
||||
|
||||
// Delete messages from local
|
||||
func (c *Conversation) deleteMessageFromLocal(ctx context.Context, conversationID string, clientMsgID string) error { |
||||
s, err := c.db.GetMessage(ctx, conversationID, clientMsgID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := c.db.DeleteConversationMsgs(ctx, conversationID, []string{clientMsgID}); err != nil { |
||||
return err |
||||
} |
||||
if !s.IsRead && s.SendID != c.loginUserID { |
||||
if err := c.db.DecrConversationUnreadCount(ctx, conversationID, 1); err != nil { |
||||
return err |
||||
} |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: conversationID, Action: constant.ConChange, Args: []string{conversationID}}}) |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}}) |
||||
} |
||||
conversation, err := c.db.GetConversation(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
var latestMsg sdk_struct.MsgStruct |
||||
utils.JsonStringToStruct(conversation.LatestMsg, &latestMsg) |
||||
if latestMsg.ClientMsgID == clientMsgID { |
||||
log.ZDebug(ctx, "latesetMsg deleted", "seq", latestMsg.Seq, "clientMsgID", latestMsg.ClientMsgID) |
||||
msgs, err := c.db.GetMessageListNoTime(ctx, conversationID, 1, false) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
latestMsgSendTime := latestMsg.SendTime |
||||
latestMsgStr := "" |
||||
if len(msgs) > 0 { |
||||
copier.Copy(&latestMsg, msgs[0]) |
||||
err := c.msgConvert(&latestMsg) |
||||
if err != nil { |
||||
log.ZError(ctx, "parsing data error", err, latestMsg) |
||||
} |
||||
latestMsgStr = utils.StructToJsonString(latestMsg) |
||||
latestMsgSendTime = latestMsg.SendTime |
||||
} |
||||
if err := c.db.UpdateColumnsConversation(ctx, conversationID, map[string]interface{}{"latest_msg": latestMsgStr, "latest_msg_send_time": latestMsgSendTime}); err != nil { |
||||
return err |
||||
} |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: []string{conversationID}}}) |
||||
} |
||||
c.msgListener().OnMsgDeleted(utils.StructToJsonString(s)) |
||||
return nil |
||||
} |
||||
|
||||
func (c *Conversation) doDeleteMsgs(ctx context.Context, msg *sdkws.MsgData) { |
||||
tips := sdkws.DeleteMsgsTips{} |
||||
utils.UnmarshalNotificationElem(msg.Content, &tips) |
||||
log.ZDebug(ctx, "doDeleteMsgs", "seqs", tips.Seqs) |
||||
for _, v := range tips.Seqs { |
||||
msg, err := c.db.GetMessageBySeq(ctx, tips.ConversationID, v) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetMessageBySeq err", err, "conversationID", tips.ConversationID, "seq", v) |
||||
continue |
||||
} |
||||
var s sdk_struct.MsgStruct |
||||
copier.Copy(&s, msg) |
||||
err = c.msgConvert(&s) |
||||
if err != nil { |
||||
log.ZError(ctx, "parsing data error", err, "msg", msg) |
||||
} |
||||
if err := c.deleteMessageFromLocal(ctx, tips.ConversationID, msg.ClientMsgID); err != nil { |
||||
log.ZError(ctx, "deleteMessageFromLocal err", err, "conversationID", tips.ConversationID, "seq", v) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (c *Conversation) doClearConversations(ctx context.Context, msg *sdkws.MsgData) { |
||||
tips := sdkws.ClearConversationTips{} |
||||
utils.UnmarshalNotificationElem(msg.Content, &tips) |
||||
log.ZDebug(ctx, "doClearConversations", "tips", tips) |
||||
for _, v := range tips.ConversationIDs { |
||||
if err := c.clearConversationAndDeleteAllMsg(ctx, v, false, c.db.ClearConversation); err != nil { |
||||
log.ZError(ctx, "clearConversation err", err, "conversationID", v) |
||||
} |
||||
} |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: tips.ConversationIDs}}) |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}}) |
||||
} |
@ -0,0 +1,217 @@ |
||||
package conversation_msg |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"github.com/jinzhu/copier" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/errs" |
||||
"github.com/openimsdk/tools/log" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
"github.com/patrickmn/go-cache" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
_ int = iota |
||||
stateCodeSuccess |
||||
stateCodeEnd |
||||
) |
||||
|
||||
const ( |
||||
inputStatesSendTime = time.Second * 10 // input status sending interval time
|
||||
inputStatesTimeout = inputStatesSendTime + inputStatesSendTime/2 // input status timeout
|
||||
inputStatesMsgTimeout = inputStatesSendTime / 2 // message sending timeout
|
||||
) |
||||
|
||||
func newTyping(c *Conversation) *typing { |
||||
e := &typing{ |
||||
conv: c, |
||||
send: cache.New(inputStatesSendTime, inputStatesTimeout), |
||||
state: cache.New(inputStatesTimeout, inputStatesTimeout), |
||||
} |
||||
e.platformIDs = make([]int32, 0, len(constant.PlatformID2Name)) |
||||
e.platformIDSet = make(map[int32]struct{}) |
||||
for id := range constant.PlatformID2Name { |
||||
e.platformIDSet[int32(id)] = struct{}{} |
||||
e.platformIDs = append(e.platformIDs, int32(id)) |
||||
} |
||||
datautil.Sort(e.platformIDs, true) |
||||
e.state.OnEvicted(func(key string, val interface{}) { |
||||
var data inputStatesKey |
||||
if err := json.Unmarshal([]byte(key), &data); err != nil { |
||||
return |
||||
} |
||||
e.changes(data.ConversationID, data.UserID) |
||||
}) |
||||
return e |
||||
} |
||||
|
||||
type typing struct { |
||||
send *cache.Cache |
||||
state *cache.Cache |
||||
|
||||
conv *Conversation |
||||
|
||||
platformIDs []int32 |
||||
platformIDSet map[int32]struct{} |
||||
} |
||||
|
||||
func (e *typing) ChangeInputStates(ctx context.Context, conversationID string, focus bool) error { |
||||
if conversationID == "" { |
||||
return errs.ErrArgs.WrapMsg("conversationID can't be empty") |
||||
} |
||||
conversation, err := e.conv.db.GetConversation(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
key := conversation.ConversationID |
||||
if focus { |
||||
if val, ok := e.send.Get(key); ok { |
||||
if val.(int) == stateCodeSuccess { |
||||
log.ZDebug(ctx, "typing stateCodeSuccess", "conversationID", conversationID, "focus", focus) |
||||
return nil |
||||
} |
||||
} |
||||
e.send.SetDefault(key, stateCodeSuccess) |
||||
} else { |
||||
if val, ok := e.send.Get(key); ok { |
||||
if val.(int) == stateCodeEnd { |
||||
log.ZDebug(ctx, "typing stateCodeEnd", "conversationID", conversationID, "focus", focus) |
||||
return nil |
||||
} |
||||
e.send.SetDefault(key, stateCodeEnd) |
||||
} else { |
||||
log.ZDebug(ctx, "typing send not found", "conversationID", conversationID, "focus", focus) |
||||
return nil |
||||
} |
||||
} |
||||
ctx, cancel := context.WithTimeout(ctx, inputStatesMsgTimeout) |
||||
defer cancel() |
||||
if err := e.sendMsg(ctx, conversation, focus); err != nil { |
||||
e.send.Delete(key) |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (e *typing) sendMsg(ctx context.Context, conversation *model_struct.LocalConversation, focus bool) error { |
||||
s := sdk_struct.MsgStruct{} |
||||
err := e.conv.initBasicInfo(ctx, &s, constant.UserMsgType, constant.Typing) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
s.RecvID = conversation.UserID |
||||
s.GroupID = conversation.GroupID |
||||
s.SessionType = conversation.ConversationType |
||||
var typingElem sdk_struct.TypingElem |
||||
if focus { |
||||
typingElem.MsgTips = "yes" |
||||
} else { |
||||
typingElem.MsgTips = "no" |
||||
} |
||||
s.Content = utils.StructToJsonString(typingElem) |
||||
options := make(map[string]bool, 6) |
||||
utils.SetSwitchFromOptions(options, constant.IsHistory, false) |
||||
utils.SetSwitchFromOptions(options, constant.IsPersistent, false) |
||||
utils.SetSwitchFromOptions(options, constant.IsSenderSync, false) |
||||
utils.SetSwitchFromOptions(options, constant.IsConversationUpdate, false) |
||||
utils.SetSwitchFromOptions(options, constant.IsSenderConversationUpdate, false) |
||||
utils.SetSwitchFromOptions(options, constant.IsUnreadCount, false) |
||||
utils.SetSwitchFromOptions(options, constant.IsOfflinePush, false) |
||||
var wsMsgData sdkws.MsgData |
||||
copier.Copy(&wsMsgData, s) |
||||
wsMsgData.Content = []byte(s.Content) |
||||
wsMsgData.CreateTime = s.CreateTime |
||||
wsMsgData.Options = options |
||||
var sendMsgResp sdkws.UserSendMsgResp |
||||
err = e.conv.LongConnMgr.SendReqWaitResp(ctx, &wsMsgData, constant.SendMsg, &sendMsgResp) |
||||
if err != nil { |
||||
log.ZError(ctx, "typing msg to server failed", err, "message", s) |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
type inputStatesKey struct { |
||||
ConversationID string `json:"cid,omitempty"` |
||||
UserID string `json:"uid,omitempty"` |
||||
PlatformID int32 `json:"pid,omitempty"` |
||||
} |
||||
|
||||
func (e *typing) getStateKey(conversationID string, userID string, platformID int32) string { |
||||
data, err := json.Marshal(inputStatesKey{ConversationID: conversationID, UserID: userID, PlatformID: platformID}) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return string(data) |
||||
} |
||||
|
||||
func (e *typing) onNewMsg(ctx context.Context, msg *sdkws.MsgData) { |
||||
var enteringElem sdk_struct.TypingElem |
||||
if err := json.Unmarshal(msg.Content, &enteringElem); err != nil { |
||||
log.ZError(ctx, "typing onNewMsg Unmarshal failed", err, "message", msg) |
||||
return |
||||
} |
||||
if msg.SendID == e.conv.loginUserID { |
||||
return |
||||
} |
||||
if _, ok := e.platformIDSet[msg.SenderPlatformID]; !ok { |
||||
return |
||||
} |
||||
now := time.Now().UnixMilli() |
||||
expirationTimestamp := msg.SendTime + int64(inputStatesSendTime/time.Millisecond) |
||||
if msg.SendTime > now || expirationTimestamp <= now { |
||||
return |
||||
} |
||||
var sourceID string |
||||
if msg.GroupID == "" { |
||||
sourceID = msg.SendID |
||||
} else { |
||||
sourceID = msg.GroupID |
||||
} |
||||
conversationID := e.conv.getConversationIDBySessionType(sourceID, int(msg.SessionType)) |
||||
key := e.getStateKey(conversationID, msg.SendID, msg.SenderPlatformID) |
||||
if enteringElem.MsgTips == "yes" { |
||||
d := time.Duration(expirationTimestamp-now) * time.Millisecond |
||||
if v, t, ok := e.state.GetWithExpiration(key); ok { |
||||
if t.UnixMilli() >= expirationTimestamp { |
||||
return |
||||
} |
||||
e.state.Set(key, v, d) |
||||
} else { |
||||
e.state.Set(key, struct{}{}, d) |
||||
e.changes(conversationID, msg.SendID) |
||||
} |
||||
} else { |
||||
if _, ok := e.state.Get(key); ok { |
||||
e.state.Delete(key) |
||||
} |
||||
} |
||||
} |
||||
|
||||
type InputStatesChangedData struct { |
||||
ConversationID string `json:"conversationID"` |
||||
UserID string `json:"userID"` |
||||
PlatformIDs []int32 `json:"platformIDs"` |
||||
} |
||||
|
||||
func (e *typing) changes(conversationID string, userID string) { |
||||
data := InputStatesChangedData{ConversationID: conversationID, UserID: userID, PlatformIDs: e.GetInputStates(conversationID, userID)} |
||||
e.conv.ConversationListener().OnConversationUserInputStatusChanged(utils.StructToJsonString(data)) |
||||
} |
||||
|
||||
func (e *typing) GetInputStates(conversationID string, userID string) []int32 { |
||||
platformIDs := make([]int32, 0, 1) |
||||
for _, platformID := range e.platformIDs { |
||||
key := e.getStateKey(conversationID, userID, platformID) |
||||
if _, ok := e.state.Get(key); ok { |
||||
platformIDs = append(platformIDs, platformID) |
||||
} |
||||
} |
||||
return platformIDs |
||||
} |
@ -0,0 +1,32 @@ |
||||
package conversation_msg |
||||
|
||||
import ( |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
"github.com/openimsdk/tools/errs" |
||||
_ "golang.org/x/image/bmp" |
||||
_ "golang.org/x/image/tiff" |
||||
_ "golang.org/x/image/webp" |
||||
"image" |
||||
_ "image/gif" |
||||
_ "image/jpeg" |
||||
_ "image/png" |
||||
"os" |
||||
) |
||||
|
||||
func getImageInfo(filePath string) (*sdk_struct.ImageInfo, error) { |
||||
file, err := os.Open(filePath) |
||||
if err != nil { |
||||
return nil, errs.WrapMsg(err, "image file open err") |
||||
} |
||||
defer file.Close() |
||||
info, err := file.Stat() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
img, format, err := image.Decode(file) |
||||
if err != nil { |
||||
return nil, errs.WrapMsg(err, "image file decode err") |
||||
} |
||||
size := img.Bounds().Max |
||||
return &sdk_struct.ImageInfo{Width: int32(size.X), Height: int32(size.Y), Type: "image/" + format, Size: info.Size()}, nil |
||||
} |
@ -0,0 +1,52 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import "sync" |
||||
|
||||
type MaxSeqRecorder struct { |
||||
seqs map[string]int64 |
||||
lock sync.RWMutex |
||||
} |
||||
|
||||
func NewMaxSeqRecorder() MaxSeqRecorder { |
||||
m := make(map[string]int64) |
||||
return MaxSeqRecorder{seqs: m} |
||||
} |
||||
|
||||
func (m *MaxSeqRecorder) Get(conversationID string) int64 { |
||||
m.lock.RLock() |
||||
defer m.lock.RUnlock() |
||||
return m.seqs[conversationID] |
||||
} |
||||
|
||||
func (m *MaxSeqRecorder) Set(conversationID string, seq int64) { |
||||
m.lock.Lock() |
||||
defer m.lock.Unlock() |
||||
m.seqs[conversationID] = seq |
||||
} |
||||
|
||||
func (m *MaxSeqRecorder) Incr(conversationID string, num int64) { |
||||
m.lock.Lock() |
||||
defer m.lock.Unlock() |
||||
m.seqs[conversationID] += num |
||||
} |
||||
|
||||
func (m *MaxSeqRecorder) IsNewMsg(conversationID string, seq int64) bool { |
||||
m.lock.RLock() |
||||
defer m.lock.RUnlock() |
||||
currentSeq := m.seqs[conversationID] |
||||
return seq > currentSeq |
||||
} |
@ -0,0 +1,347 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
sdk "github.com/openimsdk/openim-sdk-core/v3/pkg/sdk_params_callback" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
|
||||
"github.com/openimsdk/tools/log" |
||||
|
||||
"github.com/openimsdk/protocol/sdkws" |
||||
) |
||||
|
||||
// 检测其内部连续性,如果不连续,则向前补齐,获取这一组消息的最大最小seq,以及需要补齐的seq列表长度
|
||||
func (c *Conversation) messageBlocksInternalContinuityCheck(ctx context.Context, conversationID string, notStartTime, isReverse bool, count int, |
||||
startTime int64, list *[]*model_struct.LocalChatLog, messageListCallback *sdk.GetAdvancedHistoryMessageListCallback) (max, min int64, length int) { |
||||
var lostSeqListLength int |
||||
maxSeq, minSeq, haveSeqList := c.getMaxAndMinHaveSeqList(*list) |
||||
log.ZDebug(ctx, "getMaxAndMinHaveSeqList is:", "maxSeq", maxSeq, "minSeq", minSeq, "haveSeqList", haveSeqList) |
||||
if maxSeq != 0 && minSeq != 0 { |
||||
successiveSeqList := func(max, min int64) (seqList []int64) { |
||||
for i := min; i <= max; i++ { |
||||
seqList = append(seqList, i) |
||||
} |
||||
return seqList |
||||
}(maxSeq, minSeq) |
||||
lostSeqList := utils.DifferenceSubset(successiveSeqList, haveSeqList) |
||||
lostSeqListLength = len(lostSeqList) |
||||
log.ZDebug(ctx, "get lost seqList is :", "maxSeq", maxSeq, "minSeq", minSeq, "lostSeqList", lostSeqList, "length:", lostSeqListLength) |
||||
if lostSeqListLength > 0 { |
||||
var pullSeqList []int64 |
||||
if lostSeqListLength <= constant.PullMsgNumForReadDiffusion { |
||||
pullSeqList = lostSeqList |
||||
} else { |
||||
pullSeqList = lostSeqList[lostSeqListLength-constant.PullMsgNumForReadDiffusion : lostSeqListLength] |
||||
} |
||||
c.pullMessageAndReGetHistoryMessages(ctx, conversationID, pullSeqList, notStartTime, isReverse, count, startTime, list, messageListCallback) |
||||
} |
||||
|
||||
} |
||||
return maxSeq, minSeq, lostSeqListLength |
||||
} |
||||
|
||||
// 检测消息块之间的连续性,如果不连续,则向前补齐,返回块之间是否连续,bool
|
||||
func (c *Conversation) messageBlocksBetweenContinuityCheck(ctx context.Context, lastMinSeq, maxSeq int64, conversationID string, |
||||
notStartTime, isReverse bool, count int, startTime int64, list *[]*model_struct.LocalChatLog, messageListCallback *sdk.GetAdvancedHistoryMessageListCallback) bool { |
||||
if lastMinSeq != 0 { |
||||
log.ZDebug(ctx, "get lost LastMinSeq is :", "lastMinSeq", lastMinSeq, "thisMaxSeq", maxSeq) |
||||
if maxSeq != 0 { |
||||
if maxSeq+1 != lastMinSeq { |
||||
startSeq := int64(lastMinSeq) - constant.PullMsgNumForReadDiffusion |
||||
if startSeq <= maxSeq { |
||||
startSeq = int64(maxSeq) + 1 |
||||
} |
||||
successiveSeqList := func(max, min int64) (seqList []int64) { |
||||
for i := min; i <= max; i++ { |
||||
seqList = append(seqList, i) |
||||
} |
||||
return seqList |
||||
}(lastMinSeq-1, startSeq) |
||||
log.ZDebug(ctx, "get lost successiveSeqList is :", "successiveSeqList", successiveSeqList, "length:", len(successiveSeqList)) |
||||
if len(successiveSeqList) > 0 { |
||||
c.pullMessageAndReGetHistoryMessages(ctx, conversationID, successiveSeqList, notStartTime, isReverse, count, startTime, list, messageListCallback) |
||||
} |
||||
} else { |
||||
return true |
||||
} |
||||
|
||||
} else { |
||||
return true |
||||
} |
||||
|
||||
} else { |
||||
return true |
||||
} |
||||
|
||||
return false |
||||
} |
||||
|
||||
// 根据最小seq向前补齐消息,由服务器告诉拉取消息结果是否到底,如果网络,则向前补齐,获取这一组消息的最大最小seq,以及需要补齐的seq列表长度
|
||||
func (c *Conversation) messageBlocksEndContinuityCheck(ctx context.Context, minSeq int64, conversationID string, notStartTime, |
||||
isReverse bool, count int, startTime int64, list *[]*model_struct.LocalChatLog, messageListCallback *sdk.GetAdvancedHistoryMessageListCallback) { |
||||
if minSeq != 0 { |
||||
seqList := func(seq int64) (seqList []int64) { |
||||
startSeq := seq - constant.PullMsgNumForReadDiffusion |
||||
if startSeq <= 0 { |
||||
startSeq = 1 |
||||
} |
||||
log.ZDebug(ctx, "pull start is ", "start seq", startSeq) |
||||
for i := startSeq; i < seq; i++ { |
||||
seqList = append(seqList, i) |
||||
} |
||||
return seqList |
||||
}(minSeq) |
||||
log.ZDebug(ctx, "pull seqList is ", "seqList", seqList, "len", len(seqList)) |
||||
|
||||
if len(seqList) > 0 { |
||||
c.pullMessageAndReGetHistoryMessages(ctx, conversationID, seqList, notStartTime, isReverse, count, startTime, list, messageListCallback) |
||||
} |
||||
|
||||
} else { |
||||
//local don't have messages,本地无消息,但是服务器最大消息不为0
|
||||
seqList := []int64{0, 0} |
||||
c.pullMessageAndReGetHistoryMessages(ctx, conversationID, seqList, notStartTime, isReverse, count, startTime, list, messageListCallback) |
||||
|
||||
} |
||||
|
||||
} |
||||
func (c *Conversation) getMaxAndMinHaveSeqList(messages []*model_struct.LocalChatLog) (max, min int64, seqList []int64) { |
||||
for i := 0; i < len(messages); i++ { |
||||
if messages[i].Seq != 0 { |
||||
seqList = append(seqList, messages[i].Seq) |
||||
} |
||||
if messages[i].Seq != 0 && min == 0 && max == 0 { |
||||
min = messages[i].Seq |
||||
max = messages[i].Seq |
||||
} |
||||
if messages[i].Seq < min && messages[i].Seq != 0 { |
||||
min = messages[i].Seq |
||||
} |
||||
if messages[i].Seq > max { |
||||
max = messages[i].Seq |
||||
|
||||
} |
||||
} |
||||
return max, min, seqList |
||||
} |
||||
|
||||
// 1、保证单次拉取消息量低于sdk单次从服务器拉取量
|
||||
// 2、块中连续性检测
|
||||
// 3、块之间连续性检测
|
||||
func (c *Conversation) pullMessageAndReGetHistoryMessages(ctx context.Context, conversationID string, seqList []int64, |
||||
notStartTime, isReverse bool, count int, startTime int64, list *[]*model_struct.LocalChatLog, |
||||
messageListCallback *sdk.GetAdvancedHistoryMessageListCallback) { |
||||
existedSeqList, err := c.db.GetAlreadyExistSeqList(ctx, conversationID, seqList) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetAlreadyExistSeqList err", err, "conversationID", conversationID, |
||||
"seqList", seqList) |
||||
return |
||||
} |
||||
if len(existedSeqList) == len(seqList) { |
||||
log.ZDebug(ctx, "do not pull message", "seqList", seqList, "existedSeqList", existedSeqList) |
||||
return |
||||
} |
||||
newSeqList := utils.DifferenceSubset(seqList, existedSeqList) |
||||
if len(newSeqList) == 0 { |
||||
log.ZDebug(ctx, "do not pull message", "seqList", seqList, "existedSeqList", existedSeqList, |
||||
"newSeqList", newSeqList) |
||||
return |
||||
} |
||||
var pullMsgResp sdkws.PullMessageBySeqsResp |
||||
var pullMsgReq sdkws.PullMessageBySeqsReq |
||||
pullMsgReq.UserID = c.loginUserID |
||||
var seqRange sdkws.SeqRange |
||||
seqRange.ConversationID = conversationID |
||||
seqRange.Begin = newSeqList[0] |
||||
seqRange.End = newSeqList[len(newSeqList)-1] |
||||
seqRange.Num = int64(len(newSeqList)) |
||||
pullMsgReq.SeqRanges = append(pullMsgReq.SeqRanges, &seqRange) |
||||
log.ZDebug(ctx, "conversation pull message, ", "req", pullMsgReq) |
||||
if notStartTime && !c.LongConnMgr.IsConnected() { |
||||
return |
||||
} |
||||
err = c.SendReqWaitResp(ctx, &pullMsgReq, constant.PullMsgBySeqList, &pullMsgResp) |
||||
if err != nil { |
||||
errHandle(newSeqList, list, err, messageListCallback) |
||||
log.ZDebug(ctx, "pullmsg SendReqWaitResp failed", err, "req") |
||||
} else { |
||||
log.ZDebug(ctx, "syncMsgFromServerSplit pull msg", "resp", pullMsgResp) |
||||
if pullMsgResp.Msgs == nil { |
||||
log.ZWarn(ctx, "syncMsgFromServerSplit pull msg is null", errors.New("pull message is null"), |
||||
"req", pullMsgReq) |
||||
return |
||||
} |
||||
if v, ok := pullMsgResp.Msgs[conversationID]; ok { |
||||
c.pullMessageIntoTable(ctx, pullMsgResp.Msgs, conversationID) |
||||
messageListCallback.IsEnd = v.IsEnd |
||||
|
||||
if notStartTime { |
||||
*list, err = c.db.GetMessageListNoTime(ctx, conversationID, count, isReverse) |
||||
} else { |
||||
*list, err = c.db.GetMessageList(ctx, conversationID, count, startTime, isReverse) |
||||
} |
||||
} |
||||
|
||||
} |
||||
} |
||||
func errHandle(seqList []int64, list *[]*model_struct.LocalChatLog, err error, messageListCallback *sdk.GetAdvancedHistoryMessageListCallback) { |
||||
messageListCallback.ErrCode = 100 |
||||
messageListCallback.ErrMsg = err.Error() |
||||
var result []*model_struct.LocalChatLog |
||||
needPullMaxSeq := seqList[len(seqList)-1] |
||||
for _, chatLog := range *list { |
||||
if chatLog.Seq == 0 || chatLog.Seq > needPullMaxSeq { |
||||
temp := chatLog |
||||
result = append(result, temp) |
||||
} else { |
||||
if chatLog.Seq <= needPullMaxSeq { |
||||
break |
||||
} |
||||
} |
||||
} |
||||
*list = result |
||||
} |
||||
func (c *Conversation) pullMessageIntoTable(ctx context.Context, pullMsgData map[string]*sdkws.PullMsgs, conversationID string) { |
||||
insertMsg := make(map[string][]*model_struct.LocalChatLog, 20) |
||||
updateMsg := make(map[string][]*model_struct.LocalChatLog, 30) |
||||
var insertMessage, selfInsertMessage, othersInsertMessage []*model_struct.LocalChatLog |
||||
var updateMessage []*model_struct.LocalChatLog |
||||
var exceptionMsg []*model_struct.LocalErrChatLog |
||||
|
||||
log.ZDebug(ctx, "do Msg come here, len: ", "msg length", len(pullMsgData)) |
||||
for conversationID, msgs := range pullMsgData { |
||||
for _, v := range msgs.Msgs { |
||||
log.ZDebug(ctx, "msg detail", "msg", v, "conversationID", conversationID) |
||||
msg := c.msgDataToLocalChatLog(v) |
||||
//When the message has been marked and deleted by the cloud, it is directly inserted locally without any conversation and message update.
|
||||
if msg.Status == constant.MsgStatusHasDeleted { |
||||
insertMessage = append(insertMessage, msg) |
||||
continue |
||||
} |
||||
msg.Status = constant.MsgStatusSendSuccess |
||||
// log.Info(operationID, "new msg, seq, ServerMsgID, ClientMsgID", msg.Seq, msg.ServerMsgID, msg.ClientMsgID)
|
||||
//De-analyze data
|
||||
if msg.ClientMsgID == "" { |
||||
exceptionMsg = append(exceptionMsg, c.msgDataToLocalErrChatLog(msg)) |
||||
continue |
||||
} |
||||
if v.SendID == c.loginUserID { //seq
|
||||
// Messages sent by myself //if sent through this terminal
|
||||
m, err := c.db.GetMessage(ctx, conversationID, msg.ClientMsgID) |
||||
if err == nil { |
||||
log.ZInfo(ctx, "have message", "msg", msg) |
||||
if m.Seq == 0 { |
||||
updateMessage = append(updateMessage, msg) |
||||
|
||||
} else { |
||||
exceptionMsg = append(exceptionMsg, c.msgDataToLocalErrChatLog(msg)) |
||||
} |
||||
} else { // send through other terminal
|
||||
log.ZInfo(ctx, "sync message", "msg", msg) |
||||
selfInsertMessage = append(selfInsertMessage, msg) |
||||
} |
||||
} else { //Sent by others
|
||||
if oldMessage, err := c.db.GetMessage(ctx, conversationID, msg.ClientMsgID); err != nil { //Deduplication operation
|
||||
othersInsertMessage = append(othersInsertMessage, msg) |
||||
|
||||
} else { |
||||
if oldMessage.Seq == 0 { |
||||
updateMessage = append(updateMessage, msg) |
||||
} |
||||
} |
||||
} |
||||
|
||||
insertMsg[conversationID] = append(insertMessage, c.faceURLAndNicknameHandle(ctx, selfInsertMessage, othersInsertMessage, conversationID)...) |
||||
updateMsg[conversationID] = updateMessage |
||||
} |
||||
|
||||
//update message
|
||||
if err6 := c.messageController.BatchUpdateMessageList(ctx, updateMsg); err6 != nil { |
||||
log.ZError(ctx, "sync seq normal message err :", err6) |
||||
} |
||||
b3 := utils.GetCurrentTimestampByMill() |
||||
//Normal message storage
|
||||
_ = c.messageController.BatchInsertMessageList(ctx, insertMsg) |
||||
b4 := utils.GetCurrentTimestampByMill() |
||||
log.ZDebug(ctx, "BatchInsertMessageListController, ", "cost time", b4-b3) |
||||
|
||||
//Exception message storage
|
||||
for _, v := range exceptionMsg { |
||||
log.ZWarn(ctx, "exceptionMsg show: ", nil, "msg", *v) |
||||
} |
||||
|
||||
} |
||||
} |
||||
|
||||
// 拉取的消息都需要经过块内部连续性检测以及块和上一块之间的连续性检测不连续则补,补齐的过程中如果出现任何异常只给seq从大到小到断层
|
||||
// 拉取消息不满量,获取服务器中该群最大seq以及用户对于此群最小seq,本地该群的最小seq,如果本地的不为0并且小于等于服务器最小的,说明已经到底部
|
||||
// 如果本地的为0,可以理解为初始化的时候,数据还未同步,或者异常情况,如果服务器最大seq-服务器最小seq>=0说明还未到底部,否则到底部
|
||||
|
||||
func (c *Conversation) faceURLAndNicknameHandle(ctx context.Context, self, others []*model_struct.LocalChatLog, conversationID string) []*model_struct.LocalChatLog { |
||||
lc, err := c.db.GetConversation(ctx, conversationID) |
||||
if err != nil { |
||||
return append(self, others...) |
||||
} |
||||
switch lc.ConversationType { |
||||
case constant.SingleChatType: |
||||
c.singleHandle(ctx, self, others, lc) |
||||
case constant.SuperGroupChatType: |
||||
c.groupHandle(ctx, self, others, lc) |
||||
} |
||||
return append(self, others...) |
||||
} |
||||
func (c *Conversation) singleHandle(ctx context.Context, self, others []*model_struct.LocalChatLog, lc *model_struct.LocalConversation) { |
||||
userInfo, err := c.db.GetLoginUser(ctx, c.loginUserID) |
||||
if err == nil { |
||||
for _, chatLog := range self { |
||||
chatLog.SenderFaceURL = userInfo.FaceURL |
||||
chatLog.SenderNickname = userInfo.Nickname |
||||
} |
||||
} |
||||
if lc.FaceURL != "" && lc.ShowName != "" { |
||||
for _, chatLog := range others { |
||||
chatLog.SenderFaceURL = lc.FaceURL |
||||
chatLog.SenderNickname = lc.ShowName |
||||
} |
||||
} |
||||
|
||||
} |
||||
func (c *Conversation) groupHandle(ctx context.Context, self, others []*model_struct.LocalChatLog, lc *model_struct.LocalConversation) { |
||||
allMessage := append(self, others...) |
||||
localGroupMemberInfo, err := c.group.GetSpecifiedGroupMembersInfo(ctx, lc.GroupID, datautil.Slice(allMessage, func(e *model_struct.LocalChatLog) string { |
||||
return e.SendID |
||||
})) |
||||
if err != nil { |
||||
log.ZError(ctx, "get group member info err", err) |
||||
return |
||||
} |
||||
groupMap := datautil.SliceToMap(localGroupMemberInfo, func(e *model_struct.LocalGroupMember) string { |
||||
return e.UserID |
||||
}) |
||||
for _, chatLog := range allMessage { |
||||
if g, ok := groupMap[chatLog.SendID]; ok { |
||||
if g.FaceURL != "" && g.Nickname != "" { |
||||
chatLog.SenderFaceURL = g.FaceURL |
||||
chatLog.SenderNickname = g.Nickname |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,124 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
|
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
type MessageController struct { |
||||
db db_interface.DataBase |
||||
ch chan common.Cmd2Value |
||||
} |
||||
|
||||
func NewMessageController(db db_interface.DataBase, ch chan common.Cmd2Value) *MessageController { |
||||
return &MessageController{db: db, ch: ch} |
||||
} |
||||
func (m *MessageController) BatchUpdateMessageList(ctx context.Context, updateMsg map[string][]*model_struct.LocalChatLog) error { |
||||
if updateMsg == nil { |
||||
return nil |
||||
} |
||||
for conversationID, messages := range updateMsg { |
||||
conversation, err := m.db.GetConversation(ctx, conversationID) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetConversation err", err, "conversationID", conversationID) |
||||
continue |
||||
} |
||||
latestMsg := &sdk_struct.MsgStruct{} |
||||
if err := json.Unmarshal([]byte(conversation.LatestMsg), latestMsg); err != nil { |
||||
log.ZError(ctx, "Unmarshal err", err, "conversationID", |
||||
conversationID, "latestMsg", conversation.LatestMsg) |
||||
continue |
||||
} |
||||
for _, v := range messages { |
||||
v1 := new(model_struct.LocalChatLog) |
||||
v1.ClientMsgID = v.ClientMsgID |
||||
v1.Seq = v.Seq |
||||
v1.Status = v.Status |
||||
v1.RecvID = v.RecvID |
||||
v1.SessionType = v.SessionType |
||||
v1.ServerMsgID = v.ServerMsgID |
||||
v1.SendTime = v.SendTime |
||||
err := m.db.UpdateMessage(ctx, conversationID, v1) |
||||
if err != nil { |
||||
return utils.Wrap(err, "BatchUpdateMessageList failed") |
||||
} |
||||
if latestMsg.ClientMsgID == v.ClientMsgID { |
||||
latestMsg.ServerMsgID = v.ServerMsgID |
||||
latestMsg.Seq = v.Seq |
||||
latestMsg.SendTime = v.SendTime |
||||
conversation.LatestMsg = utils.StructToJsonString(latestMsg) |
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{ConID: conversation.ConversationID, |
||||
Action: constant.AddConOrUpLatMsg, Args: *conversation}, m.ch) |
||||
|
||||
} |
||||
} |
||||
|
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (m *MessageController) BatchInsertMessageList(ctx context.Context, insertMsg map[string][]*model_struct.LocalChatLog) error { |
||||
if insertMsg == nil { |
||||
return nil |
||||
} |
||||
for conversationID, messages := range insertMsg { |
||||
if len(messages) == 0 { |
||||
continue |
||||
} |
||||
err := m.db.BatchInsertMessageList(ctx, conversationID, messages) |
||||
if err != nil { |
||||
log.ZError(ctx, "insert GetMessage detail err:", err, "conversationID", conversationID, "messages", messages) |
||||
for _, v := range messages { |
||||
e := m.db.InsertMessage(ctx, conversationID, v) |
||||
if e != nil { |
||||
log.ZError(ctx, "InsertMessage err", err, "conversationID", conversationID, "message", v) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (c *Conversation) PullMessageBySeqs(ctx context.Context, seqs []*sdkws.SeqRange) (*sdkws.PullMessageBySeqsResp, error) { |
||||
return util.CallApi[sdkws.PullMessageBySeqsResp](ctx, constant.PullUserMsgBySeqRouter, sdkws.PullMessageBySeqsReq{UserID: c.loginUserID, SeqRanges: seqs}) |
||||
} |
||||
func (m *MessageController) SearchMessageByContentTypeAndKeyword(ctx context.Context, contentType []int, keywordList []string, |
||||
keywordListMatchType int, startTime, endTime int64) (result []*model_struct.LocalChatLog, err error) { |
||||
var list []*model_struct.LocalChatLog |
||||
conversationIDList, err := m.db.GetAllConversationIDList(ctx) |
||||
for _, v := range conversationIDList { |
||||
sList, err := m.db.SearchMessageByContentTypeAndKeyword(ctx, contentType, v, keywordList, keywordListMatchType, startTime, endTime) |
||||
if err != nil { |
||||
// TODO: log.Error(operationID, "search message in group err", err.Error(), v)
|
||||
continue |
||||
} |
||||
list = append(list, sList...) |
||||
} |
||||
|
||||
return list, nil |
||||
} |
@ -0,0 +1,102 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/file" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface" |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
|
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func NewUploadFileCallback(ctx context.Context, progress func(progress int), msg *sdk_struct.MsgStruct, conversationID string, db db_interface.DataBase) file.UploadFileCallback { |
||||
if msg.AttachedInfoElem == nil { |
||||
msg.AttachedInfoElem = &sdk_struct.AttachedInfoElem{} |
||||
} |
||||
if msg.AttachedInfoElem.Progress == nil { |
||||
msg.AttachedInfoElem.Progress = &sdk_struct.UploadProgress{} |
||||
} |
||||
return &msgUploadFileCallback{ctx: ctx, progress: progress, msg: msg, db: db, conversationID: conversationID} |
||||
} |
||||
|
||||
type msgUploadFileCallback struct { |
||||
ctx context.Context |
||||
db db_interface.DataBase |
||||
msg *sdk_struct.MsgStruct |
||||
conversationID string |
||||
value int |
||||
progress func(progress int) |
||||
} |
||||
|
||||
func (c *msgUploadFileCallback) Open(size int64) { |
||||
} |
||||
|
||||
func (c *msgUploadFileCallback) PartSize(partSize int64, num int) { |
||||
} |
||||
|
||||
func (c *msgUploadFileCallback) HashPartProgress(index int, size int64, partHash string) { |
||||
} |
||||
|
||||
func (c *msgUploadFileCallback) HashPartComplete(partsHash string, fileHash string) { |
||||
} |
||||
|
||||
func (c *msgUploadFileCallback) UploadID(uploadID string) { |
||||
c.msg.AttachedInfoElem.Progress.UploadID = uploadID |
||||
data, err := json.Marshal(c.msg.AttachedInfoElem) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
if err := c.db.UpdateColumnsMessage(c.ctx, c.conversationID, c.msg.ClientMsgID, map[string]any{"attached_info": string(data)}); err != nil { |
||||
log.ZError(c.ctx, "update PutProgress message attached info failed", err) |
||||
} |
||||
} |
||||
|
||||
func (c *msgUploadFileCallback) UploadPartComplete(index int, partSize int64, partHash string) { |
||||
} |
||||
|
||||
func (c *msgUploadFileCallback) UploadComplete(fileSize int64, streamSize int64, storageSize int64) { |
||||
c.msg.AttachedInfoElem.Progress.Save = storageSize |
||||
c.msg.AttachedInfoElem.Progress.Current = streamSize |
||||
c.msg.AttachedInfoElem.Progress.Total = fileSize |
||||
data, err := json.Marshal(c.msg.AttachedInfoElem) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
if err := c.db.UpdateColumnsMessage(c.ctx, c.conversationID, c.msg.ClientMsgID, map[string]any{"attached_info": string(data)}); err != nil { |
||||
log.ZError(c.ctx, "update PutProgress message attached info failed", err) |
||||
} |
||||
value := int(float64(streamSize) / float64(fileSize) * 100) |
||||
if c.value < value { |
||||
c.value = value |
||||
c.progress(value) |
||||
} |
||||
} |
||||
|
||||
func (c *msgUploadFileCallback) Complete(size int64, url string, typ int) { |
||||
if c.value != 100 { |
||||
c.progress(100) |
||||
} |
||||
c.msg.AttachedInfoElem.Progress = nil |
||||
data, err := json.Marshal(c.msg.AttachedInfoElem) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
if err := c.db.UpdateColumnsMessage(c.ctx, c.conversationID, c.msg.ClientMsgID, map[string]any{"attached_info": string(data)}); err != nil { |
||||
log.ZError(c.ctx, "update PutComplete message attached info failed", err) |
||||
} |
||||
} |
@ -0,0 +1,281 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"errors" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
|
||||
pbMsg "github.com/openimsdk/protocol/msg" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func (c *Conversation) markMsgAsRead2Svr(ctx context.Context, conversationID string, seqs []int64) error { |
||||
req := &pbMsg.MarkMsgsAsReadReq{UserID: c.loginUserID, ConversationID: conversationID, Seqs: seqs} |
||||
return util.ApiPost(ctx, constant.MarkMsgsAsReadRouter, req, nil) |
||||
} |
||||
|
||||
func (c *Conversation) markConversationAsReadSvr(ctx context.Context, conversationID string, hasReadSeq int64, seqs []int64) error { |
||||
req := &pbMsg.MarkConversationAsReadReq{UserID: c.loginUserID, ConversationID: conversationID, HasReadSeq: hasReadSeq, Seqs: seqs} |
||||
return util.ApiPost(ctx, constant.MarkConversationAsRead, req, nil) |
||||
} |
||||
|
||||
func (c *Conversation) setConversationHasReadSeq(ctx context.Context, conversationID string, hasReadSeq int64) error { |
||||
req := &pbMsg.SetConversationHasReadSeqReq{UserID: c.loginUserID, ConversationID: conversationID, HasReadSeq: hasReadSeq} |
||||
return util.ApiPost(ctx, constant.SetConversationHasReadSeq, req, nil) |
||||
} |
||||
|
||||
func (c *Conversation) getConversationMaxSeqAndSetHasRead(ctx context.Context, conversationID string) error { |
||||
maxSeq, err := c.db.GetConversationNormalMsgSeq(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if maxSeq == 0 { |
||||
return nil |
||||
} |
||||
if err := c.setConversationHasReadSeq(ctx, conversationID, maxSeq); err != nil { |
||||
return err |
||||
} |
||||
if err := c.db.UpdateColumnsConversation(ctx, conversationID, map[string]interface{}{"has_read_seq": maxSeq}); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// mark a conversation's all message as read
|
||||
func (c *Conversation) markConversationMessageAsRead(ctx context.Context, conversationID string) error { |
||||
conversation, err := c.db.GetConversation(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if conversation.UnreadCount == 0 { |
||||
return sdkerrs.ErrUnreadCount |
||||
} |
||||
// get the maximum sequence number of messages in the table that are not sent by oneself
|
||||
peerUserMaxSeq, err := c.db.GetConversationPeerNormalMsgSeq(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
// get the maximum sequence number of messages in the table
|
||||
maxSeq, err := c.db.GetConversationNormalMsgSeq(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
switch conversation.ConversationType { |
||||
case constant.SingleChatType: |
||||
msgs, err := c.db.GetUnreadMessage(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.ZDebug(ctx, "get unread message", "msgs", len(msgs)) |
||||
msgIDs, seqs := c.getAsReadMsgMapAndList(ctx, msgs) |
||||
if len(seqs) == 0 { |
||||
log.ZWarn(ctx, "seqs is empty", nil, "conversationID", conversationID) |
||||
return nil |
||||
} |
||||
log.ZDebug(ctx, "markConversationMessageAsRead", "conversationID", conversationID, "seqs", |
||||
seqs, "peerUserMaxSeq", peerUserMaxSeq, "maxSeq", maxSeq) |
||||
if err := c.markConversationAsReadSvr(ctx, conversationID, maxSeq, seqs); err != nil { |
||||
return err |
||||
} |
||||
_, err = c.db.MarkConversationMessageAsReadDB(ctx, conversationID, msgIDs) |
||||
if err != nil { |
||||
log.ZWarn(ctx, "MarkConversationMessageAsRead err", err, "conversationID", conversationID, "msgIDs", msgIDs) |
||||
} |
||||
case constant.SuperGroupChatType, constant.NotificationChatType: |
||||
log.ZDebug(ctx, "markConversationMessageAsRead", "conversationID", conversationID, "peerUserMaxSeq", peerUserMaxSeq, "maxSeq", maxSeq) |
||||
if err := c.markConversationAsReadSvr(ctx, conversationID, maxSeq, nil); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
|
||||
if err := c.db.UpdateColumnsConversation(ctx, conversationID, map[string]interface{}{"unread_count": 0}); err != nil { |
||||
log.ZError(ctx, "UpdateColumnsConversation err", err, "conversationID", conversationID) |
||||
} |
||||
log.ZDebug(ctx, "update columns sucess") |
||||
c.unreadChangeTrigger(ctx, conversationID, peerUserMaxSeq == maxSeq) |
||||
return nil |
||||
} |
||||
|
||||
// mark a conversation's message as read by seqs
|
||||
func (c *Conversation) markMessagesAsReadByMsgID(ctx context.Context, conversationID string, msgIDs []string) error { |
||||
_, err := c.db.GetConversation(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
msgs, err := c.db.GetMessagesByClientMsgIDs(ctx, conversationID, msgIDs) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(msgs) == 0 { |
||||
return nil |
||||
} |
||||
var hasReadSeq = msgs[0].Seq |
||||
maxSeq, err := c.db.GetConversationNormalMsgSeq(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
markAsReadMsgIDs, seqs := c.getAsReadMsgMapAndList(ctx, msgs) |
||||
log.ZDebug(ctx, "msgs len", "markAsReadMsgIDs", len(markAsReadMsgIDs), "seqs", seqs) |
||||
if len(seqs) == 0 { |
||||
log.ZWarn(ctx, "seqs is empty", nil, "conversationID", conversationID) |
||||
return nil |
||||
} |
||||
if err := c.markMsgAsRead2Svr(ctx, conversationID, seqs); err != nil { |
||||
return err |
||||
} |
||||
decrCount, err := c.db.MarkConversationMessageAsReadDB(ctx, conversationID, markAsReadMsgIDs) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := c.db.DecrConversationUnreadCount(ctx, conversationID, decrCount); err != nil { |
||||
log.ZError(ctx, "decrConversationUnreadCount err", err, "conversationID", conversationID, |
||||
"decrCount", decrCount) |
||||
} |
||||
c.unreadChangeTrigger(ctx, conversationID, hasReadSeq == maxSeq && msgs[0].SendID != c.loginUserID) |
||||
return nil |
||||
} |
||||
|
||||
func (c *Conversation) getAsReadMsgMapAndList(ctx context.Context, |
||||
msgs []*model_struct.LocalChatLog) (asReadMsgIDs []string, seqs []int64) { |
||||
for _, msg := range msgs { |
||||
if !msg.IsRead && msg.SendID != c.loginUserID { |
||||
if msg.Seq == 0 { |
||||
log.ZWarn(ctx, "exception seq", errors.New("exception message "), "msg", msg) |
||||
} else { |
||||
asReadMsgIDs = append(asReadMsgIDs, msg.ClientMsgID) |
||||
seqs = append(seqs, msg.Seq) |
||||
} |
||||
} else { |
||||
log.ZWarn(ctx, "msg can't marked as read", nil, "msg", msg) |
||||
} |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (c *Conversation) unreadChangeTrigger(ctx context.Context, conversationID string, latestMsgIsRead bool) { |
||||
if latestMsgIsRead { |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: conversationID, |
||||
Action: constant.UpdateLatestMessageChange, Args: []string{conversationID}}, Ctx: ctx}) |
||||
} |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: conversationID, |
||||
Action: constant.ConChange, Args: []string{conversationID}}, Ctx: ctx}) |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}, |
||||
Ctx: ctx}) |
||||
} |
||||
|
||||
func (c *Conversation) doUnreadCount(ctx context.Context, conversation *model_struct.LocalConversation, hasReadSeq int64, seqs []int64) { |
||||
if conversation.ConversationType == constant.SingleChatType { |
||||
if len(seqs) != 0 { |
||||
_, err := c.db.MarkConversationMessageAsReadBySeqs(ctx, conversation.ConversationID, seqs) |
||||
if err != nil { |
||||
log.ZWarn(ctx, "MarkConversationMessageAsReadBySeqs err", err, "conversationID", conversation.ConversationID, "seqs", seqs) |
||||
} |
||||
} else { |
||||
log.ZWarn(ctx, "seqs is empty", nil, "conversationID", conversation.ConversationID, "hasReadSeq", hasReadSeq) |
||||
} |
||||
if hasReadSeq > conversation.HasReadSeq { |
||||
decrUnreadCount := hasReadSeq - conversation.HasReadSeq |
||||
if err := c.db.DecrConversationUnreadCount(ctx, conversation.ConversationID, decrUnreadCount); err != nil { |
||||
log.ZError(ctx, "DecrConversationUnreadCount err", err, "conversationID", conversation.ConversationID, "decrUnreadCount", decrUnreadCount) |
||||
} |
||||
if err := c.db.UpdateColumnsConversation(ctx, conversation.ConversationID, map[string]interface{}{"has_read_seq": hasReadSeq}); err != nil { |
||||
log.ZError(ctx, "UpdateColumnsConversation err", err, "conversationID", conversation.ConversationID) |
||||
} |
||||
} |
||||
latestMsg := &sdk_struct.MsgStruct{} |
||||
if err := json.Unmarshal([]byte(conversation.LatestMsg), latestMsg); err != nil { |
||||
log.ZError(ctx, "Unmarshal err", err, "conversationID", conversation.ConversationID, "latestMsg", conversation.LatestMsg) |
||||
} |
||||
if (!latestMsg.IsRead) && datautil.Contain(latestMsg.Seq, seqs...) { |
||||
latestMsg.IsRead = true |
||||
conversation.LatestMsg = utils.StructToJsonString(&latestMsg) |
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{ConID: conversation.ConversationID, Action: constant.AddConOrUpLatMsg, Args: *conversation}, c.GetCh()) |
||||
} |
||||
} else { |
||||
if err := c.db.UpdateColumnsConversation(ctx, conversation.ConversationID, map[string]interface{}{"unread_count": 0}); err != nil { |
||||
log.ZError(ctx, "UpdateColumnsConversation err", err, "conversationID", conversation.ConversationID) |
||||
} |
||||
} |
||||
|
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: conversation.ConversationID, Action: constant.ConChange, Args: []string{conversation.ConversationID}}}) |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}}) |
||||
|
||||
} |
||||
|
||||
func (c *Conversation) doReadDrawing(ctx context.Context, msg *sdkws.MsgData) { |
||||
tips := &sdkws.MarkAsReadTips{} |
||||
err := utils.UnmarshalNotificationElem(msg.Content, tips) |
||||
if err != nil { |
||||
log.ZWarn(ctx, "UnmarshalNotificationElem err", err, "msg", msg) |
||||
return |
||||
} |
||||
log.ZDebug(ctx, "do readDrawing", "tips", tips) |
||||
conversation, err := c.db.GetConversation(ctx, tips.ConversationID) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetConversation err", err, "conversationID", tips.ConversationID) |
||||
return |
||||
} |
||||
if tips.MarkAsReadUserID != c.loginUserID { |
||||
if len(tips.Seqs) == 0 { |
||||
return |
||||
} |
||||
messages, err := c.db.GetMessagesBySeqs(ctx, tips.ConversationID, tips.Seqs) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetMessagesBySeqs err", err, "conversationID", tips.ConversationID, "seqs", tips.Seqs) |
||||
return |
||||
} |
||||
if conversation.ConversationType == constant.SingleChatType { |
||||
latestMsg := &sdk_struct.MsgStruct{} |
||||
if err := json.Unmarshal([]byte(conversation.LatestMsg), latestMsg); err != nil { |
||||
log.ZError(ctx, "Unmarshal err", err, "conversationID", tips.ConversationID, "latestMsg", conversation.LatestMsg) |
||||
} |
||||
var successMsgIDs []string |
||||
for _, message := range messages { |
||||
attachInfo := sdk_struct.AttachedInfoElem{} |
||||
_ = utils.JsonStringToStruct(message.AttachedInfo, &attachInfo) |
||||
attachInfo.HasReadTime = msg.SendTime |
||||
message.AttachedInfo = utils.StructToJsonString(attachInfo) |
||||
message.IsRead = true |
||||
if err = c.db.UpdateMessage(ctx, tips.ConversationID, message); err != nil { |
||||
log.ZError(ctx, "UpdateMessage err", err, "conversationID", tips.ConversationID, "message", message) |
||||
} else { |
||||
if latestMsg.ClientMsgID == message.ClientMsgID { |
||||
latestMsg.IsRead = message.IsRead |
||||
conversation.LatestMsg = utils.StructToJsonString(latestMsg) |
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{ConID: conversation.ConversationID, Action: constant.AddConOrUpLatMsg, Args: *conversation}, c.GetCh()) |
||||
|
||||
} |
||||
successMsgIDs = append(successMsgIDs, message.ClientMsgID) |
||||
} |
||||
} |
||||
var messageReceiptResp = []*sdk_struct.MessageReceipt{{UserID: tips.MarkAsReadUserID, MsgIDList: successMsgIDs, |
||||
SessionType: conversation.ConversationType, ReadTime: msg.SendTime}} |
||||
c.msgListener().OnRecvC2CReadReceipt(utils.StructToJsonString(messageReceiptResp)) |
||||
} |
||||
} else { |
||||
c.doUnreadCount(ctx, conversation, tips.HasReadSeq, tips.Seqs) |
||||
} |
||||
} |
@ -0,0 +1,203 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
"github.com/openimsdk/tools/utils/timeutil" |
||||
|
||||
"github.com/jinzhu/copier" |
||||
pbMsg "github.com/openimsdk/protocol/msg" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func (c *Conversation) doRevokeMsg(ctx context.Context, msg *sdkws.MsgData) { |
||||
var tips sdkws.RevokeMsgTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil { |
||||
log.ZError(ctx, "unmarshal failed", err, "msg", msg) |
||||
return |
||||
} |
||||
log.ZDebug(ctx, "do revokeMessage", "tips", &tips) |
||||
c.revokeMessage(ctx, &tips) |
||||
} |
||||
|
||||
func (c *Conversation) revokeMessage(ctx context.Context, tips *sdkws.RevokeMsgTips) { |
||||
revokedMsg, err := c.db.GetMessageBySeq(ctx, tips.ConversationID, tips.Seq) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetMessageBySeq failed", err, "tips", &tips) |
||||
return |
||||
} |
||||
var revokerRole int32 |
||||
var revokerNickname string |
||||
if tips.IsAdminRevoke || tips.SesstionType == constant.SingleChatType { |
||||
_, userName, err := c.getUserNameAndFaceURL(ctx, tips.RevokerUserID) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetUserNameAndFaceURL failed", err, "tips", &tips) |
||||
} else { |
||||
log.ZDebug(ctx, "revoker user name", "userName", userName) |
||||
} |
||||
revokerNickname = userName |
||||
} else if tips.SesstionType == constant.SuperGroupChatType { |
||||
conversation, err := c.db.GetConversation(ctx, tips.ConversationID) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetConversation failed", err, "conversationID", tips.ConversationID) |
||||
return |
||||
} |
||||
groupMember, err := c.db.GetGroupMemberInfoByGroupIDUserID(ctx, conversation.GroupID, tips.RevokerUserID) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetGroupMemberInfoByGroupIDUserID failed", err, "tips", &tips) |
||||
} else { |
||||
log.ZDebug(ctx, "revoker member name", "groupMember", groupMember) |
||||
revokerRole = groupMember.RoleLevel |
||||
revokerNickname = groupMember.Nickname |
||||
} |
||||
} |
||||
m := sdk_struct.MessageRevoked{ |
||||
RevokerID: tips.RevokerUserID, |
||||
RevokerRole: revokerRole, |
||||
ClientMsgID: revokedMsg.ClientMsgID, |
||||
RevokerNickname: revokerNickname, |
||||
RevokeTime: tips.RevokeTime, |
||||
SourceMessageSendTime: revokedMsg.SendTime, |
||||
SourceMessageSendID: revokedMsg.SendID, |
||||
SourceMessageSenderNickname: revokedMsg.SenderNickname, |
||||
SessionType: tips.SesstionType, |
||||
Seq: tips.Seq, |
||||
Ex: revokedMsg.Ex, |
||||
IsAdminRevoke: tips.IsAdminRevoke, |
||||
} |
||||
// log.ZDebug(ctx, "callback revokeMessage", "m", m)
|
||||
var n sdk_struct.NotificationElem |
||||
n.Detail = utils.StructToJsonString(m) |
||||
if err := c.db.UpdateMessageBySeq(ctx, tips.ConversationID, &model_struct.LocalChatLog{Seq: tips.Seq, |
||||
Content: utils.StructToJsonString(n), ContentType: constant.RevokeNotification}); err != nil { |
||||
log.ZError(ctx, "UpdateMessageBySeq failed", err, "tips", &tips) |
||||
return |
||||
} |
||||
conversation, err := c.db.GetConversation(ctx, tips.ConversationID) |
||||
if err != nil { |
||||
log.ZError(ctx, "GetConversation failed", err, "tips", &tips) |
||||
return |
||||
} |
||||
var latestMsg sdk_struct.MsgStruct |
||||
utils.JsonStringToStruct(conversation.LatestMsg, &latestMsg) |
||||
log.ZDebug(ctx, "latestMsg", "latestMsg", &latestMsg, "seq", tips.Seq) |
||||
if latestMsg.Seq <= tips.Seq { |
||||
var newLatesetMsg sdk_struct.MsgStruct |
||||
msgs, err := c.db.GetMessageListNoTime(ctx, tips.ConversationID, 1, false) |
||||
if err != nil || len(msgs) == 0 { |
||||
log.ZError(ctx, "GetMessageListNoTime failed", err, "tips", &tips) |
||||
return |
||||
} |
||||
log.ZDebug(ctx, "latestMsg is revoked", "seq", tips.Seq, "msg", msgs[0]) |
||||
copier.Copy(&newLatesetMsg, msgs[0]) |
||||
err = c.msgConvert(&newLatesetMsg) |
||||
if err != nil { |
||||
log.ZError(ctx, "parsing data error", err, latestMsg) |
||||
} else { |
||||
log.ZDebug(ctx, "revoke update conversatoin", "msg", utils.StructToJsonString(newLatesetMsg)) |
||||
if err := c.db.UpdateColumnsConversation(ctx, tips.ConversationID, map[string]interface{}{"latest_msg": utils.StructToJsonString(newLatesetMsg), |
||||
"latest_msg_send_time": newLatesetMsg.SendTime}); err != nil { |
||||
log.ZError(ctx, "UpdateColumnsConversation failed", err, "newLatesetMsg", newLatesetMsg) |
||||
} else { |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{Action: constant.ConChange, Args: []string{tips.ConversationID}}}) |
||||
} |
||||
} |
||||
} |
||||
c.msgListener().OnNewRecvMessageRevoked(utils.StructToJsonString(m)) |
||||
msgList, err := c.db.SearchAllMessageByContentType(ctx, conversation.ConversationID, constant.Quote) |
||||
if err != nil { |
||||
log.ZError(ctx, "SearchAllMessageByContentType failed", err, "tips", &tips) |
||||
return |
||||
} |
||||
for _, v := range msgList { |
||||
c.quoteMsgRevokeHandle(ctx, tips.ConversationID, v, m) |
||||
} |
||||
} |
||||
|
||||
func (c *Conversation) quoteMsgRevokeHandle(ctx context.Context, conversationID string, v *model_struct.LocalChatLog, revokedMsg sdk_struct.MessageRevoked) { |
||||
s := sdk_struct.MsgStruct{} |
||||
_ = utils.JsonStringToStruct(v.Content, &s.QuoteElem) |
||||
|
||||
if s.QuoteElem.QuoteMessage == nil { |
||||
return |
||||
} |
||||
if s.QuoteElem.QuoteMessage.ClientMsgID != revokedMsg.ClientMsgID { |
||||
return |
||||
} |
||||
s.QuoteElem.QuoteMessage.Content = utils.StructToJsonString(revokedMsg) |
||||
s.QuoteElem.QuoteMessage.ContentType = constant.RevokeNotification |
||||
v.Content = utils.StructToJsonString(s.QuoteElem) |
||||
if err := c.db.UpdateMessageBySeq(ctx, conversationID, v); err != nil { |
||||
log.ZError(ctx, "UpdateMessage failed", err, "v", v) |
||||
} |
||||
} |
||||
|
||||
func (c *Conversation) revokeOneMessage(ctx context.Context, conversationID, clientMsgID string) error { |
||||
conversation, err := c.db.GetConversation(ctx, conversationID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
message, err := c.db.GetMessage(ctx, conversationID, clientMsgID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if message.Status != constant.MsgStatusSendSuccess { |
||||
return errors.New("only send success message can be revoked") |
||||
} |
||||
switch conversation.ConversationType { |
||||
case constant.SingleChatType: |
||||
if message.SendID != c.loginUserID { |
||||
return errors.New("only send by yourself message can be revoked") |
||||
} |
||||
case constant.SuperGroupChatType: |
||||
if message.SendID != c.loginUserID { |
||||
groupAdmins, err := c.db.GetGroupMemberOwnerAndAdminDB(ctx, conversation.GroupID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
var isAdmin bool |
||||
for _, member := range groupAdmins { |
||||
if member.UserID == c.loginUserID { |
||||
isAdmin = true |
||||
break |
||||
} |
||||
} |
||||
if !isAdmin { |
||||
return errors.New("only group admin can revoke message") |
||||
} |
||||
} |
||||
} |
||||
if err := util.ApiPost(ctx, constant.RevokeMsgRouter, pbMsg.RevokeMsgReq{ConversationID: conversationID, Seq: message.Seq, UserID: c.loginUserID}, nil); err != nil { |
||||
return err |
||||
} |
||||
c.revokeMessage(ctx, &sdkws.RevokeMsgTips{ |
||||
ConversationID: conversationID, |
||||
Seq: message.Seq, |
||||
RevokerUserID: c.loginUserID, |
||||
RevokeTime: timeutil.GetCurrentTimestampBySecond(), |
||||
SesstionType: conversation.ConversationType, |
||||
ClientMsgID: clientMsgID, |
||||
}) |
||||
return nil |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,148 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 conversation_msg |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/syncer" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
"time" |
||||
|
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func (c *Conversation) SyncConversationsAndTriggerCallback(ctx context.Context, conversationsOnServer []*model_struct.LocalConversation) error { |
||||
conversationsOnLocal, err := c.db.GetAllConversations(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := c.batchAddFaceURLAndName(ctx, conversationsOnServer...); err != nil { |
||||
return err |
||||
} |
||||
if err = c.conversationSyncer.Sync(ctx, conversationsOnServer, conversationsOnLocal, func(ctx context.Context, state int, server, local *model_struct.LocalConversation) error { |
||||
if state == syncer.Update || state == syncer.Insert { |
||||
c.doUpdateConversation(common.Cmd2Value{Value: common.UpdateConNode{ConID: server.ConversationID, Action: constant.ConChange, Args: []string{server.ConversationID}}}) |
||||
} |
||||
return nil |
||||
}, true); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (c *Conversation) SyncConversations(ctx context.Context, conversationIDs []string) error { |
||||
conversationsOnServer, err := c.getServerConversationsByIDs(ctx, conversationIDs) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return c.SyncConversationsAndTriggerCallback(ctx, conversationsOnServer) |
||||
} |
||||
|
||||
func (c *Conversation) SyncAllConversations(ctx context.Context) error { |
||||
ccTime := time.Now() |
||||
conversationsOnServer, err := c.getServerConversationList(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.ZDebug(ctx, "get server cost time", "cost time", time.Since(ccTime), "conversation on server", conversationsOnServer) |
||||
return c.SyncConversationsAndTriggerCallback(ctx, conversationsOnServer) |
||||
} |
||||
|
||||
func (c *Conversation) SyncAllConversationHashReadSeqs(ctx context.Context) error { |
||||
log.ZDebug(ctx, "start SyncConversationHashReadSeqs") |
||||
seqs, err := c.getServerHasReadAndMaxSeqs(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(seqs) == 0 { |
||||
return nil |
||||
} |
||||
var conversationChangedIDs []string |
||||
var conversationIDsNeedSync []string |
||||
|
||||
conversationsOnLocal, err := c.db.GetAllConversations(ctx) |
||||
if err != nil { |
||||
log.ZWarn(ctx, "get all conversations err", err) |
||||
return err |
||||
} |
||||
conversationsOnLocalMap := datautil.SliceToMap(conversationsOnLocal, func(e *model_struct.LocalConversation) string { |
||||
return e.ConversationID |
||||
}) |
||||
for conversationID, v := range seqs { |
||||
var unreadCount int32 |
||||
c.maxSeqRecorder.Set(conversationID, v.MaxSeq) |
||||
if v.MaxSeq-v.HasReadSeq < 0 { |
||||
unreadCount = 0 |
||||
log.ZWarn(ctx, "unread count is less than 0", nil, "conversationID", |
||||
conversationID, "maxSeq", v.MaxSeq, "hasReadSeq", v.HasReadSeq) |
||||
} else { |
||||
unreadCount = int32(v.MaxSeq - v.HasReadSeq) |
||||
} |
||||
if conversation, ok := conversationsOnLocalMap[conversationID]; ok { |
||||
if conversation.UnreadCount != unreadCount || conversation.HasReadSeq != v.HasReadSeq { |
||||
if err := c.db.UpdateColumnsConversation(ctx, conversationID, map[string]interface{}{"unread_count": unreadCount, "has_read_seq": v.HasReadSeq}); err != nil { |
||||
log.ZWarn(ctx, "UpdateColumnsConversation err", err, "conversationID", conversationID) |
||||
continue |
||||
} |
||||
conversationChangedIDs = append(conversationChangedIDs, conversationID) |
||||
} |
||||
} else { |
||||
conversationIDsNeedSync = append(conversationIDsNeedSync, conversationID) |
||||
} |
||||
|
||||
} |
||||
if len(conversationIDsNeedSync) > 0 { |
||||
conversationsOnServer, err := c.getServerConversationsByIDs(ctx, conversationIDsNeedSync) |
||||
if err != nil { |
||||
log.ZWarn(ctx, "getServerConversationsByIDs err", err, "conversationIDs", conversationIDsNeedSync) |
||||
return err |
||||
} |
||||
if err := c.batchAddFaceURLAndName(ctx, conversationsOnServer...); err != nil { |
||||
log.ZWarn(ctx, "batchAddFaceURLAndName err", err, "conversationsOnServer", conversationsOnServer) |
||||
return err |
||||
} |
||||
|
||||
for _, conversation := range conversationsOnServer { |
||||
var unreadCount int32 |
||||
v, ok := seqs[conversation.ConversationID] |
||||
if !ok { |
||||
continue |
||||
} |
||||
if v.MaxSeq-v.HasReadSeq < 0 { |
||||
unreadCount = 0 |
||||
log.ZWarn(ctx, "unread count is less than 0", nil, "server seq", v, "conversation", conversation) |
||||
} else { |
||||
unreadCount = int32(v.MaxSeq - v.HasReadSeq) |
||||
} |
||||
conversation.UnreadCount = unreadCount |
||||
conversation.HasReadSeq = v.HasReadSeq |
||||
} |
||||
err = c.db.BatchInsertConversationList(ctx, conversationsOnServer) |
||||
if err != nil { |
||||
log.ZWarn(ctx, "BatchInsertConversationList err", err, "conversationsOnServer", conversationsOnServer) |
||||
} |
||||
|
||||
} |
||||
|
||||
log.ZDebug(ctx, "update conversations", "conversations", conversationChangedIDs) |
||||
if len(conversationChangedIDs) > 0 { |
||||
common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{Action: constant.ConChange, Args: conversationChangedIDs}, c.GetCh()) |
||||
common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{Action: constant.TotalUnreadMessageChanged}, c.GetCh()) |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,89 @@ |
||||
// Copyright © 2023 OpenIM open source community. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package file |
||||
|
||||
func NewBitmap(size int) *Bitmap { |
||||
data := make([]uint64, (size+63)/64) |
||||
return &Bitmap{data: data, size: size} |
||||
} |
||||
|
||||
func ParseBitmap(p []byte, size int) *Bitmap { |
||||
data := make([]uint64, len(p)/8) |
||||
for i := range data { |
||||
data[i] = uint64(p[i*8])<<56 | |
||||
uint64(p[i*8+1])<<48 | |
||||
uint64(p[i*8+2])<<40 | |
||||
uint64(p[i*8+3])<<32 | |
||||
uint64(p[i*8+4])<<24 | |
||||
uint64(p[i*8+5])<<16 | |
||||
uint64(p[i*8+6])<<8 | |
||||
uint64(p[i*8+7]) |
||||
} |
||||
return &Bitmap{ |
||||
data: data, |
||||
size: size, |
||||
} |
||||
} |
||||
|
||||
type Bitmap struct { |
||||
data []uint64 |
||||
size int |
||||
} |
||||
|
||||
func (b *Bitmap) Set(index int) { |
||||
if index < 0 || index >= b.size { |
||||
panic("out of range") |
||||
} |
||||
wordIndex := index / 64 |
||||
bitIndex := uint(index % 64) |
||||
b.data[wordIndex] |= 1 << bitIndex |
||||
} |
||||
|
||||
func (b *Bitmap) Clear(index int) { |
||||
if index < 0 || index >= b.size { |
||||
panic("out of range") |
||||
} |
||||
wordIndex := index / 64 |
||||
bitIndex := uint(index % 64) |
||||
b.data[wordIndex] &= ^(1 << bitIndex) |
||||
} |
||||
|
||||
func (b *Bitmap) Get(index int) bool { |
||||
if index < 0 || index >= b.size { |
||||
panic("out of range") |
||||
} |
||||
wordIndex := index / 64 |
||||
bitIndex := uint(index % 64) |
||||
return (b.data[wordIndex] & (1 << bitIndex)) != 0 |
||||
} |
||||
|
||||
func (b *Bitmap) Size() int { |
||||
return b.size |
||||
} |
||||
|
||||
func (b *Bitmap) Serialize() []byte { |
||||
p := make([]byte, len(b.data)*8) |
||||
for i, word := range b.data { |
||||
p[i*8] = byte(word >> 56) |
||||
p[i*8+1] = byte(word >> 48) |
||||
p[i*8+2] = byte(word >> 40) |
||||
p[i*8+3] = byte(word >> 32) |
||||
p[i*8+4] = byte(word >> 24) |
||||
p[i*8+5] = byte(word >> 16) |
||||
p[i*8+6] = byte(word >> 8) |
||||
p[i*8+7] = byte(word) |
||||
} |
||||
return p |
||||
} |
@ -0,0 +1,62 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 file |
||||
|
||||
import "fmt" |
||||
|
||||
type UploadFileCallback interface { |
||||
Open(size int64) // 文件打开的大小
|
||||
PartSize(partSize int64, num int) // 分片大小,数量
|
||||
HashPartProgress(index int, size int64, partHash string) // 每块分片的hash值
|
||||
HashPartComplete(partsHash string, fileHash string) // 分块完成,服务端标记hash和文件最终hash
|
||||
UploadID(uploadID string) // 上传ID
|
||||
UploadPartComplete(index int, partSize int64, partHash string) // 上传分片进度
|
||||
UploadComplete(fileSize int64, streamSize int64, storageSize int64) // 整体进度
|
||||
Complete(size int64, url string, typ int) // 上传完成
|
||||
} |
||||
|
||||
type emptyUploadCallback struct{} |
||||
|
||||
func (e emptyUploadCallback) Open(size int64) { |
||||
fmt.Println("Callback Open:", size) |
||||
} |
||||
|
||||
func (e emptyUploadCallback) PartSize(partSize int64, num int) { |
||||
fmt.Println("Callback PartSize:", partSize, num) |
||||
} |
||||
|
||||
func (e emptyUploadCallback) HashPartProgress(index int, size int64, partHash string) { |
||||
//fmt.Println("Callback HashPartProgress:", index, size, partHash)
|
||||
} |
||||
|
||||
func (e emptyUploadCallback) HashPartComplete(partsHash string, fileHash string) { |
||||
fmt.Println("Callback HashPartComplete:", partsHash, fileHash) |
||||
} |
||||
|
||||
func (e emptyUploadCallback) UploadID(uploadID string) { |
||||
fmt.Println("Callback UploadID:", uploadID) |
||||
} |
||||
|
||||
func (e emptyUploadCallback) UploadPartComplete(index int, partSize int64, partHash string) { |
||||
fmt.Println("Callback UploadPartComplete:", index, partSize, partHash) |
||||
} |
||||
|
||||
func (e emptyUploadCallback) UploadComplete(fileSize int64, streamSize int64, storageSize int64) { |
||||
fmt.Println("Callback UploadComplete:", fileSize, streamSize, storageSize) |
||||
} |
||||
|
||||
func (e emptyUploadCallback) Complete(size int64, url string, typ int) { |
||||
fmt.Println("Callback Complete:", size, url, typ) |
||||
} |
@ -0,0 +1,24 @@ |
||||
// Copyright © 2023 OpenIM open source community. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package file |
||||
|
||||
import "io" |
||||
|
||||
type ReadFile interface { |
||||
io.Reader |
||||
io.Closer |
||||
Size() int64 |
||||
StartSeek(whence int) error |
||||
} |
@ -0,0 +1,73 @@ |
||||
// Copyright © 2023 OpenIM open source community. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build !js
|
||||
|
||||
package file |
||||
|
||||
import ( |
||||
"bufio" |
||||
"io" |
||||
"os" |
||||
) |
||||
|
||||
const readBufferSize = 1024 * 1024 * 5 // 5mb
|
||||
|
||||
func Open(req *UploadFileReq) (ReadFile, error) { |
||||
file, err := os.Open(req.Filepath) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
info, err := file.Stat() |
||||
if err != nil { |
||||
_ = file.Close() |
||||
return nil, err |
||||
} |
||||
df := &defaultFile{ |
||||
file: file, |
||||
info: info, |
||||
} |
||||
df.resetReaderBuffer() |
||||
return df, nil |
||||
} |
||||
|
||||
type defaultFile struct { |
||||
file *os.File |
||||
info os.FileInfo |
||||
reader io.Reader |
||||
} |
||||
|
||||
func (d *defaultFile) resetReaderBuffer() { |
||||
d.reader = bufio.NewReaderSize(d.file, readBufferSize) |
||||
} |
||||
|
||||
func (d *defaultFile) Read(p []byte) (n int, err error) { |
||||
return d.reader.Read(p) |
||||
} |
||||
|
||||
func (d *defaultFile) Close() error { |
||||
return d.file.Close() |
||||
} |
||||
|
||||
func (d *defaultFile) StartSeek(whence int) error { |
||||
if _, err := d.file.Seek(io.SeekStart, whence); err != nil { |
||||
return err |
||||
} |
||||
d.resetReaderBuffer() |
||||
return nil |
||||
} |
||||
|
||||
func (d *defaultFile) Size() int64 { |
||||
return d.info.Size() |
||||
} |
@ -0,0 +1,156 @@ |
||||
// Copyright © 2023 OpenIM open source community. All rights reserved.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//go:build js && wasm
|
||||
// +build js,wasm
|
||||
|
||||
package file |
||||
|
||||
import ( |
||||
"bufio" |
||||
"errors" |
||||
"github.com/openimsdk/openim-sdk-core/v3/wasm/exec" |
||||
"io" |
||||
"syscall/js" |
||||
) |
||||
|
||||
const readBufferSize = 1024 * 1024 * 5 // 5mb
|
||||
|
||||
func Open(req *UploadFileReq) (ReadFile, error) { |
||||
file := newJsCallFile(req.Uuid) |
||||
size, err := file.Open() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
jf := &jsFile{ |
||||
size: size, |
||||
file: file, |
||||
} |
||||
jf.resetReaderBuffer() |
||||
return jf, nil |
||||
} |
||||
|
||||
type jsFile struct { |
||||
size int64 |
||||
file *jsCallFile |
||||
whence int |
||||
reader io.Reader |
||||
} |
||||
|
||||
func (j *jsFile) resetReaderBuffer() { |
||||
j.reader = bufio.NewReaderSize(&reader{fn: j.read}, readBufferSize) |
||||
} |
||||
|
||||
func (j *jsFile) read(p []byte) (n int, err error) { |
||||
length := len(p) |
||||
if length == 0 { |
||||
return 0, errors.New("read buffer is empty") |
||||
} |
||||
if j.whence >= int(j.size) { |
||||
return 0, io.EOF |
||||
} |
||||
if j.whence+length > int(j.size) { |
||||
length = int(j.size) - j.whence |
||||
} |
||||
data, err := j.file.Read(int64(j.whence), int64(length)) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
if len(data) > len(p) { |
||||
return 0, errors.New("js read data > length") |
||||
} |
||||
j.whence += len(data) |
||||
copy(p, data) |
||||
return len(data), nil |
||||
} |
||||
|
||||
func (j *jsFile) Read(p []byte) (n int, err error) { |
||||
return j.reader.Read(p) |
||||
} |
||||
|
||||
func (j *jsFile) Close() error { |
||||
return j.file.Close() |
||||
} |
||||
|
||||
func (j *jsFile) Size() int64 { |
||||
return j.size |
||||
} |
||||
|
||||
func (j *jsFile) StartSeek(whence int) error { |
||||
if whence < 0 || whence > int(j.size) { |
||||
return errors.New("seek whence is out of range") |
||||
} |
||||
j.whence = whence |
||||
j.resetReaderBuffer() |
||||
return nil |
||||
} |
||||
|
||||
type reader struct { |
||||
fn func(p []byte) (n int, err error) |
||||
} |
||||
|
||||
func (r *reader) Read(p []byte) (n int, err error) { |
||||
return r.fn(p) |
||||
} |
||||
|
||||
type jsCallFile struct { |
||||
uuid string |
||||
} |
||||
|
||||
func newJsCallFile(uuid string) *jsCallFile { |
||||
return &jsCallFile{uuid: uuid} |
||||
} |
||||
|
||||
func (j *jsCallFile) Open() (int64, error) { |
||||
return WasmOpen(j.uuid) |
||||
} |
||||
|
||||
func (j *jsCallFile) Read(offset int64, length int64) ([]byte, error) { |
||||
return WasmRead(j.uuid, offset, length) |
||||
} |
||||
|
||||
func (j *jsCallFile) Close() error { |
||||
return WasmClose(j.uuid) |
||||
} |
||||
|
||||
func WasmOpen(uuid string) (int64, error) { |
||||
result, err := exec.Exec(uuid) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
if v, ok := result.(float64); ok { |
||||
size := int64(v) |
||||
if size < 0 { |
||||
return 0, errors.New("file size < 0") |
||||
} |
||||
return size, nil |
||||
} |
||||
return 0, exec.ErrType |
||||
} |
||||
func WasmRead(uuid string, offset int64, length int64) ([]byte, error) { |
||||
result, err := exec.Exec(uuid, offset, length) |
||||
if err != nil { |
||||
return nil, err |
||||
} else { |
||||
if v, ok := result.(js.Value); ok { |
||||
return exec.ExtractArrayBuffer(v), nil |
||||
} else { |
||||
return nil, exec.ErrType |
||||
} |
||||
} |
||||
} |
||||
func WasmClose(uuid string) error { |
||||
_, err := exec.Exec(uuid) |
||||
return err |
||||
} |
@ -0,0 +1,40 @@ |
||||
package file |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/ccontext" |
||||
"github.com/openimsdk/openim-sdk-core/v3/sdk_struct" |
||||
"path/filepath" |
||||
"testing" |
||||
) |
||||
|
||||
func TestUpload(t *testing.T) { |
||||
conf := &ccontext.GlobalConfig{ |
||||
UserID: `4931176757`, |
||||
Token: `eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJVc2VySUQiOiI0OTMxMTc2NzU3IiwiUGxhdGZvcm1JRCI6MSwiZXhwIjoxNzA3MTE0MjIyLCJuYmYiOjE2OTkzMzc5MjIsImlhdCI6MTY5OTMzODIyMn0.AyNvrMGEdXD5rkvn7ZLHCNs-lNbDCb2otn97yLXia5Y`, |
||||
IMConfig: sdk_struct.IMConfig{ |
||||
ApiAddr: `http://203.56.175.233:10002`, |
||||
}, |
||||
} |
||||
ctx := ccontext.WithInfo(context.WithValue(context.Background(), "operationID", "OP123456"), conf) |
||||
f := NewFile(nil, conf.UserID) |
||||
|
||||
//fp := `C:\Users\openIM\Desktop\微信截图_20231025170714.png`
|
||||
//fp := `C:\Users\openIM\Desktop\my_image (2).tar`
|
||||
//fp := `C:\Users\openIM\Desktop\1234.zip`
|
||||
//fp := `C:\Users\openIM\Desktop\openIM.wasm`
|
||||
//fp := `C:\Users\openIM\Desktop\ubuntu.7z`
|
||||
//fp := `C:\Users\openIM\Desktop\log2023-10-31.log`
|
||||
fp := `C:\Users\openIM\Desktop\protoc.zip` |
||||
|
||||
resp, err := f.UploadFile(ctx, &UploadFileReq{ |
||||
Filepath: fp, |
||||
Name: filepath.Base(fp), |
||||
Cause: "test", |
||||
}, nil) |
||||
if err != nil { |
||||
t.Fatal("failed", err) |
||||
} |
||||
t.Log("success", resp.URL) |
||||
|
||||
} |
@ -0,0 +1,43 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 file |
||||
|
||||
import ( |
||||
"crypto/md5" |
||||
"encoding/hex" |
||||
"hash" |
||||
"io" |
||||
) |
||||
|
||||
func NewMd5Reader(r io.Reader) *Md5Reader { |
||||
return &Md5Reader{h: md5.New(), r: r} |
||||
} |
||||
|
||||
type Md5Reader struct { |
||||
h hash.Hash |
||||
r io.Reader |
||||
} |
||||
|
||||
func (r *Md5Reader) Read(p []byte) (n int, err error) { |
||||
n, err = r.r.Read(p) |
||||
if err == nil && n > 0 { |
||||
r.h.Write(p[:n]) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (r *Md5Reader) Md5() string { |
||||
return hex.EncodeToString(r.h.Sum(nil)) |
||||
} |
@ -0,0 +1,44 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 file |
||||
|
||||
import ( |
||||
"io" |
||||
) |
||||
|
||||
func NewProgressReader(r io.Reader, fn func(current int64)) io.Reader { |
||||
if r == nil || fn == nil { |
||||
return r |
||||
} |
||||
return &Reader{ |
||||
r: r, |
||||
fn: fn, |
||||
} |
||||
} |
||||
|
||||
type Reader struct { |
||||
r io.Reader |
||||
read int64 |
||||
fn func(current int64) |
||||
} |
||||
|
||||
func (r *Reader) Read(p []byte) (n int, err error) { |
||||
n, err = r.r.Read(p) |
||||
if err == nil && n > 0 { |
||||
r.read += int64(n) |
||||
r.fn(r.read) |
||||
} |
||||
return n, err |
||||
} |
@ -0,0 +1,576 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 file |
||||
|
||||
import ( |
||||
"context" |
||||
"crypto/md5" |
||||
"encoding/base64" |
||||
"encoding/hex" |
||||
"errors" |
||||
"fmt" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/tools/errs" |
||||
"io" |
||||
"net/http" |
||||
"net/url" |
||||
"strings" |
||||
"sync" |
||||
"sync/atomic" |
||||
"time" |
||||
|
||||
"github.com/openimsdk/protocol/third" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
type UploadFileReq struct { |
||||
Filepath string `json:"filepath"` |
||||
Name string `json:"name"` |
||||
ContentType string `json:"contentType"` |
||||
Cause string `json:"cause"` |
||||
Uuid string `json:"uuid"` |
||||
} |
||||
|
||||
type UploadFileResp struct { |
||||
URL string `json:"url"` |
||||
} |
||||
|
||||
type partInfo struct { |
||||
ContentType string |
||||
PartSize int64 |
||||
PartNum int |
||||
FileMd5 string |
||||
PartMd5 string |
||||
PartSizes []int64 |
||||
PartMd5s []string |
||||
} |
||||
|
||||
func NewFile(database db_interface.DataBase, loginUserID string) *File { |
||||
return &File{database: database, loginUserID: loginUserID, confLock: &sync.Mutex{}, mapLocker: &sync.Mutex{}, uploading: make(map[string]*lockInfo)} |
||||
} |
||||
|
||||
type File struct { |
||||
database db_interface.DataBase |
||||
loginUserID string |
||||
confLock sync.Locker |
||||
partLimit *third.PartLimitResp |
||||
mapLocker sync.Locker |
||||
uploading map[string]*lockInfo |
||||
} |
||||
|
||||
type lockInfo struct { |
||||
count int32 |
||||
locker sync.Locker |
||||
} |
||||
|
||||
func (f *File) lockHash(hash string) { |
||||
f.mapLocker.Lock() |
||||
locker, ok := f.uploading[hash] |
||||
if !ok { |
||||
locker = &lockInfo{count: 0, locker: &sync.Mutex{}} |
||||
f.uploading[hash] = locker |
||||
} |
||||
atomic.AddInt32(&locker.count, 1) |
||||
f.mapLocker.Unlock() |
||||
locker.locker.Lock() |
||||
} |
||||
|
||||
func (f *File) unlockHash(hash string) { |
||||
f.mapLocker.Lock() |
||||
locker, ok := f.uploading[hash] |
||||
if !ok { |
||||
f.mapLocker.Unlock() |
||||
return |
||||
} |
||||
if atomic.AddInt32(&locker.count, -1) == 0 { |
||||
delete(f.uploading, hash) |
||||
} |
||||
f.mapLocker.Unlock() |
||||
locker.locker.Unlock() |
||||
} |
||||
|
||||
func (f *File) UploadFile(ctx context.Context, req *UploadFileReq, cb UploadFileCallback) (*UploadFileResp, error) { |
||||
if cb == nil { |
||||
cb = emptyUploadCallback{} |
||||
} |
||||
if req.Name == "" { |
||||
return nil, errors.New("name is empty") |
||||
} |
||||
if req.Name[0] == '/' { |
||||
req.Name = req.Name[1:] |
||||
} |
||||
if prefix := f.loginUserID + "/"; !strings.HasPrefix(req.Name, prefix) { |
||||
req.Name = prefix + req.Name |
||||
} |
||||
file, err := Open(req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer file.Close() |
||||
fileSize := file.Size() |
||||
cb.Open(fileSize) |
||||
info, err := f.getPartInfo(ctx, file, fileSize, cb) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if req.ContentType == "" { |
||||
req.ContentType = info.ContentType |
||||
} |
||||
partSize := info.PartSize |
||||
partSizes := info.PartSizes |
||||
partMd5s := info.PartMd5s |
||||
partMd5Val := info.PartMd5 |
||||
if err := file.StartSeek(0); err != nil { |
||||
return nil, err |
||||
} |
||||
f.lockHash(partMd5Val) |
||||
defer f.unlockHash(partMd5Val) |
||||
maxParts := 20 |
||||
if maxParts > len(partSizes) { |
||||
maxParts = len(partSizes) |
||||
} |
||||
uploadInfo, err := f.getUpload(ctx, &third.InitiateMultipartUploadReq{ |
||||
Hash: partMd5Val, |
||||
Size: fileSize, |
||||
PartSize: partSize, |
||||
MaxParts: int32(maxParts), // 一次性获取签名数量
|
||||
Cause: req.Cause, |
||||
Name: req.Name, |
||||
ContentType: req.ContentType, |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if uploadInfo.Resp.Upload == nil { |
||||
cb.Complete(fileSize, uploadInfo.Resp.Url, 0) |
||||
return &UploadFileResp{ |
||||
URL: uploadInfo.Resp.Url, |
||||
}, nil |
||||
} |
||||
if uploadInfo.Resp.Upload.PartSize != partSize { |
||||
f.cleanPartLimit() |
||||
return nil, fmt.Errorf("part fileSize not match, expect %d, got %d", partSize, uploadInfo.Resp.Upload.PartSize) |
||||
} |
||||
cb.UploadID(uploadInfo.Resp.Upload.UploadID) |
||||
uploadedSize := fileSize |
||||
for i := 0; i < len(partSizes); i++ { |
||||
if !uploadInfo.Bitmap.Get(i) { |
||||
uploadedSize -= partSizes[i] |
||||
} |
||||
} |
||||
continueUpload := uploadedSize > 0 |
||||
for i, currentPartSize := range partSizes { |
||||
partNumber := int32(i + 1) |
||||
md5Reader := NewMd5Reader(io.LimitReader(file, currentPartSize)) |
||||
if uploadInfo.Bitmap.Get(i) { |
||||
if _, err := io.Copy(io.Discard, md5Reader); err != nil { |
||||
return nil, err |
||||
} |
||||
} else { |
||||
reader := NewProgressReader(md5Reader, func(current int64) { |
||||
cb.UploadComplete(fileSize, uploadedSize+current, uploadedSize) |
||||
}) |
||||
urlval, header, err := uploadInfo.GetPartSign(ctx, partNumber) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if err := f.doPut(ctx, http.DefaultClient, urlval, header, reader, currentPartSize); err != nil { |
||||
log.ZError(ctx, "doPut", err, "partMd5Val", partMd5Val, "name", req.Name, "partNumber", partNumber) |
||||
return nil, err |
||||
} |
||||
uploadedSize += currentPartSize |
||||
if uploadInfo.DBInfo != nil && uploadInfo.Bitmap != nil { |
||||
uploadInfo.Bitmap.Set(i) |
||||
uploadInfo.DBInfo.UploadInfo = base64.StdEncoding.EncodeToString(uploadInfo.Bitmap.Serialize()) |
||||
if err := f.database.UpdateUpload(ctx, uploadInfo.DBInfo); err != nil { |
||||
log.ZError(ctx, "SetUploadPartPush", err, "partMd5Val", partMd5Val, "name", req.Name, "partNumber", partNumber) |
||||
} |
||||
} |
||||
} |
||||
md5val := md5Reader.Md5() |
||||
if md5val != partMd5s[i] { |
||||
return nil, fmt.Errorf("upload part %d failed, md5 not match, expect %s, got %s", i, partMd5s[i], md5val) |
||||
} |
||||
cb.UploadPartComplete(i, currentPartSize, partMd5s[i]) |
||||
log.ZDebug(ctx, "upload part success", "partMd5Val", md5val, "name", req.Name, "partNumber", partNumber) |
||||
} |
||||
log.ZDebug(ctx, "upload all part success", "partHash", partMd5Val, "name", req.Name) |
||||
resp, err := f.completeMultipartUpload(ctx, &third.CompleteMultipartUploadReq{ |
||||
UploadID: uploadInfo.Resp.Upload.UploadID, |
||||
Parts: partMd5s, |
||||
Name: req.Name, |
||||
ContentType: req.ContentType, |
||||
Cause: req.Cause, |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
typ := 1 |
||||
if continueUpload { |
||||
typ++ |
||||
} |
||||
cb.Complete(fileSize, resp.Url, typ) |
||||
if uploadInfo.DBInfo != nil { |
||||
if err := f.database.DeleteUpload(ctx, info.PartMd5); err != nil { |
||||
log.ZError(ctx, "DeleteUpload", err, "partMd5Val", info.PartMd5, "name", req.Name) |
||||
} |
||||
} |
||||
return &UploadFileResp{ |
||||
URL: resp.Url, |
||||
}, nil |
||||
} |
||||
|
||||
func (f *File) cleanPartLimit() { |
||||
f.confLock.Lock() |
||||
defer f.confLock.Unlock() |
||||
f.partLimit = nil |
||||
} |
||||
|
||||
func (f *File) initiateMultipartUploadResp(ctx context.Context, req *third.InitiateMultipartUploadReq) (*third.InitiateMultipartUploadResp, error) { |
||||
return util.CallApi[third.InitiateMultipartUploadResp](ctx, constant.ObjectInitiateMultipartUpload, req) |
||||
} |
||||
|
||||
func (f *File) authSign(ctx context.Context, req *third.AuthSignReq) (*third.AuthSignResp, error) { |
||||
if len(req.PartNumbers) == 0 { |
||||
return nil, errs.ErrArgs.WrapMsg("partNumbers is empty") |
||||
} |
||||
return util.CallApi[third.AuthSignResp](ctx, constant.ObjectAuthSign, req) |
||||
} |
||||
|
||||
func (f *File) completeMultipartUpload(ctx context.Context, req *third.CompleteMultipartUploadReq) (*third.CompleteMultipartUploadResp, error) { |
||||
return util.CallApi[third.CompleteMultipartUploadResp](ctx, constant.ObjectCompleteMultipartUpload, req) |
||||
} |
||||
|
||||
func (f *File) getPartNum(fileSize int64, partSize int64) int { |
||||
partNum := fileSize / partSize |
||||
if fileSize%partSize != 0 { |
||||
partNum++ |
||||
} |
||||
return int(partNum) |
||||
} |
||||
|
||||
func (f *File) partSize(ctx context.Context, size int64) (int64, error) { |
||||
f.confLock.Lock() |
||||
defer f.confLock.Unlock() |
||||
if f.partLimit == nil { |
||||
resp, err := util.CallApi[third.PartLimitResp](ctx, constant.ObjectPartLimit, &third.PartLimitReq{}) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
f.partLimit = resp |
||||
} |
||||
if size <= 0 { |
||||
return 0, errors.New("size must be greater than 0") |
||||
} |
||||
if size > f.partLimit.MaxPartSize*int64(f.partLimit.MaxNumSize) { |
||||
return 0, fmt.Errorf("size must be less than %db", f.partLimit.MaxPartSize*int64(f.partLimit.MaxNumSize)) |
||||
} |
||||
if size <= f.partLimit.MinPartSize*int64(f.partLimit.MaxNumSize) { |
||||
return f.partLimit.MinPartSize, nil |
||||
} |
||||
partSize := size / int64(f.partLimit.MaxNumSize) |
||||
if size%int64(f.partLimit.MaxNumSize) != 0 { |
||||
partSize++ |
||||
} |
||||
return partSize, nil |
||||
} |
||||
|
||||
func (f *File) accessURL(ctx context.Context, req *third.AccessURLReq) (*third.AccessURLResp, error) { |
||||
return util.CallApi[third.AccessURLResp](ctx, constant.ObjectAccessURL, req) |
||||
} |
||||
|
||||
func (f *File) doHttpReq(req *http.Request) ([]byte, *http.Response, error) { |
||||
resp, err := http.DefaultClient.Do(req) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
defer resp.Body.Close() |
||||
data, err := io.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
return data, resp, nil |
||||
} |
||||
|
||||
func (f *File) partMD5(parts []string) string { |
||||
s := strings.Join(parts, ",") |
||||
md5Sum := md5.Sum([]byte(s)) |
||||
return hex.EncodeToString(md5Sum[:]) |
||||
} |
||||
|
||||
type AuthSignParts struct { |
||||
Sign *third.SignPart |
||||
Times []time.Time |
||||
} |
||||
|
||||
type UploadInfo struct { |
||||
PartNum int |
||||
Bitmap *Bitmap |
||||
DBInfo *model_struct.LocalUpload |
||||
Resp *third.InitiateMultipartUploadResp |
||||
//Signs *AuthSignParts
|
||||
CreateTime time.Time |
||||
BatchSignNum int32 |
||||
f *File |
||||
} |
||||
|
||||
func (u *UploadInfo) getIndex(partNumber int32) int { |
||||
if u.Resp.Upload.Sign == nil { |
||||
return -1 |
||||
} else { |
||||
if u.CreateTime.IsZero() { |
||||
return -1 |
||||
} else { |
||||
if time.Since(u.CreateTime) > time.Minute { |
||||
return -1 |
||||
} |
||||
} |
||||
} |
||||
for i, part := range u.Resp.Upload.Sign.Parts { |
||||
if part.PartNumber == partNumber { |
||||
return i |
||||
} |
||||
} |
||||
return -1 |
||||
} |
||||
|
||||
func (u *UploadInfo) buildRequest(i int) (*url.URL, http.Header, error) { |
||||
sign := u.Resp.Upload.Sign |
||||
part := sign.Parts[i] |
||||
rawURL := sign.Url |
||||
if part.Url != "" { |
||||
rawURL = part.Url |
||||
} |
||||
urlval, err := url.Parse(rawURL) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
if len(sign.Query)+len(part.Query) > 0 { |
||||
query := urlval.Query() |
||||
for i := range sign.Query { |
||||
v := sign.Query[i] |
||||
query[v.Key] = v.Values |
||||
} |
||||
for i := range part.Query { |
||||
v := part.Query[i] |
||||
query[v.Key] = v.Values |
||||
} |
||||
urlval.RawQuery = query.Encode() |
||||
} |
||||
header := make(http.Header) |
||||
for i := range sign.Header { |
||||
v := sign.Header[i] |
||||
header[v.Key] = v.Values |
||||
} |
||||
for i := range part.Header { |
||||
v := part.Header[i] |
||||
header[v.Key] = v.Values |
||||
} |
||||
return urlval, header, nil |
||||
} |
||||
|
||||
func (u *UploadInfo) GetPartSign(ctx context.Context, partNumber int32) (*url.URL, http.Header, error) { |
||||
if partNumber < 1 || int(partNumber) > u.PartNum { |
||||
return nil, nil, errors.New("invalid partNumber") |
||||
} |
||||
if index := u.getIndex(partNumber); index >= 0 { |
||||
return u.buildRequest(index) |
||||
} |
||||
partNumbers := make([]int32, 0, u.BatchSignNum) |
||||
for i := int32(0); i < u.BatchSignNum; i++ { |
||||
if int(partNumber+i) > u.PartNum { |
||||
break |
||||
} |
||||
partNumbers = append(partNumbers, partNumber+i) |
||||
} |
||||
authSignResp, err := u.f.authSign(ctx, &third.AuthSignReq{ |
||||
UploadID: u.Resp.Upload.UploadID, |
||||
PartNumbers: partNumbers, |
||||
}) |
||||
if err != nil { |
||||
return nil, nil, err |
||||
} |
||||
u.Resp.Upload.Sign.Url = authSignResp.Url |
||||
u.Resp.Upload.Sign.Query = authSignResp.Query |
||||
u.Resp.Upload.Sign.Header = authSignResp.Header |
||||
u.Resp.Upload.Sign.Parts = authSignResp.Parts |
||||
u.CreateTime = time.Now() |
||||
index := u.getIndex(partNumber) |
||||
if index < 0 { |
||||
return nil, nil, errs.ErrInternalServer.WrapMsg("server part sign invalid") |
||||
} |
||||
return u.buildRequest(index) |
||||
} |
||||
|
||||
func (f *File) getUpload(ctx context.Context, req *third.InitiateMultipartUploadReq) (*UploadInfo, error) { |
||||
partNum := f.getPartNum(req.Size, req.PartSize) |
||||
var bitmap *Bitmap |
||||
if f.database != nil { |
||||
dbUpload, err := f.database.GetUpload(ctx, req.Hash) |
||||
if err == nil { |
||||
bitmapBytes, err := base64.StdEncoding.DecodeString(dbUpload.UploadInfo) |
||||
if err != nil || len(bitmapBytes) == 0 || partNum <= 1 || dbUpload.ExpireTime-3600*1000 < time.Now().UnixMilli() { |
||||
if err := f.database.DeleteUpload(ctx, req.Hash); err != nil { |
||||
return nil, err |
||||
} |
||||
dbUpload = nil |
||||
} |
||||
if dbUpload == nil { |
||||
bitmap = NewBitmap(partNum) |
||||
} else { |
||||
bitmap = ParseBitmap(bitmapBytes, partNum) |
||||
} |
||||
tUpInfo := &third.UploadInfo{ |
||||
PartSize: req.PartSize, |
||||
Sign: &third.AuthSignParts{}, |
||||
} |
||||
if dbUpload != nil { |
||||
tUpInfo.UploadID = dbUpload.UploadID |
||||
tUpInfo.ExpireTime = dbUpload.ExpireTime |
||||
} |
||||
return &UploadInfo{ |
||||
PartNum: partNum, |
||||
Bitmap: bitmap, |
||||
DBInfo: dbUpload, |
||||
Resp: &third.InitiateMultipartUploadResp{ |
||||
Upload: tUpInfo, |
||||
}, |
||||
BatchSignNum: req.MaxParts, |
||||
f: f, |
||||
}, nil |
||||
} |
||||
log.ZError(ctx, "get upload db", err, "pratsMd5", req.Hash) |
||||
} |
||||
resp, err := f.initiateMultipartUploadResp(ctx, req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if resp.Upload == nil { |
||||
return &UploadInfo{ |
||||
Resp: resp, |
||||
}, nil |
||||
} |
||||
bitmap = NewBitmap(partNum) |
||||
var dbUpload *model_struct.LocalUpload |
||||
if f.database != nil { |
||||
dbUpload = &model_struct.LocalUpload{ |
||||
PartHash: req.Hash, |
||||
UploadID: resp.Upload.UploadID, |
||||
UploadInfo: base64.StdEncoding.EncodeToString(bitmap.Serialize()), |
||||
ExpireTime: resp.Upload.ExpireTime, |
||||
CreateTime: time.Now().UnixMilli(), |
||||
} |
||||
if err := f.database.InsertUpload(ctx, dbUpload); err != nil { |
||||
log.ZError(ctx, "insert upload db", err, "pratsHash", req.Hash, "name", req.Name) |
||||
} |
||||
} |
||||
if req.MaxParts >= 0 && len(resp.Upload.Sign.Parts) != int(req.MaxParts) { |
||||
resp.Upload.Sign.Parts = nil |
||||
} |
||||
return &UploadInfo{ |
||||
PartNum: partNum, |
||||
Bitmap: bitmap, |
||||
DBInfo: dbUpload, |
||||
Resp: resp, |
||||
CreateTime: time.Now(), |
||||
BatchSignNum: req.MaxParts, |
||||
f: f, |
||||
}, nil |
||||
} |
||||
|
||||
func (f *File) doPut(ctx context.Context, client *http.Client, url *url.URL, header http.Header, reader io.Reader, size int64) error { |
||||
rawURL := url.String() |
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, rawURL, reader) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
for key := range header { |
||||
req.Header[key] = header[key] |
||||
} |
||||
req.ContentLength = size |
||||
log.ZDebug(ctx, "do put req", "url", rawURL, "contentLength", size, "header", req.Header) |
||||
resp, err := client.Do(req) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer func() { |
||||
_ = resp.Body.Close() |
||||
}() |
||||
log.ZDebug(ctx, "do put resp status", "url", rawURL, "status", resp.Status, "contentLength", resp.ContentLength, "header", resp.Header) |
||||
body, err := io.ReadAll(resp.Body) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.ZDebug(ctx, "do put resp body", "url", rawURL, "body", string(body)) |
||||
if resp.StatusCode/200 != 1 { |
||||
return fmt.Errorf("PUT %s failed, status code %d, body %s", rawURL, resp.StatusCode, string(body)) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (f *File) getPartInfo(ctx context.Context, r io.Reader, fileSize int64, cb UploadFileCallback) (*partInfo, error) { |
||||
partSize, err := f.partSize(ctx, fileSize) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
partNum := int(fileSize / partSize) |
||||
if fileSize%partSize != 0 { |
||||
partNum++ |
||||
} |
||||
cb.PartSize(partSize, partNum) |
||||
partSizes := make([]int64, partNum) |
||||
for i := 0; i < partNum; i++ { |
||||
partSizes[i] = partSize |
||||
} |
||||
partSizes[partNum-1] = fileSize - partSize*(int64(partNum)-1) |
||||
partMd5s := make([]string, partNum) |
||||
buf := make([]byte, 1024*8) |
||||
fileMd5 := md5.New() |
||||
var contentType string |
||||
for i := 0; i < partNum; i++ { |
||||
h := md5.New() |
||||
r := io.LimitReader(r, partSize) |
||||
for { |
||||
if n, err := r.Read(buf); err == nil { |
||||
if contentType == "" { |
||||
contentType = http.DetectContentType(buf[:n]) |
||||
} |
||||
h.Write(buf[:n]) |
||||
fileMd5.Write(buf[:n]) |
||||
} else if err == io.EOF { |
||||
break |
||||
} else { |
||||
return nil, err |
||||
} |
||||
} |
||||
partMd5s[i] = hex.EncodeToString(h.Sum(nil)) |
||||
cb.HashPartProgress(i, partSizes[i], partMd5s[i]) |
||||
} |
||||
partMd5Val := f.partMD5(partMd5s) |
||||
fileMd5val := hex.EncodeToString(fileMd5.Sum(nil)) |
||||
cb.HashPartComplete(f.partMD5(partMd5s), hex.EncodeToString(fileMd5.Sum(nil))) |
||||
return &partInfo{ |
||||
ContentType: contentType, |
||||
PartSize: partSize, |
||||
PartNum: partNum, |
||||
FileMd5: fileMd5val, |
||||
PartMd5: partMd5Val, |
||||
PartSizes: partSizes, |
||||
PartMd5s: partMd5s, |
||||
}, nil |
||||
} |
@ -0,0 +1,71 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 friend |
||||
|
||||
import ( |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
) |
||||
|
||||
func ServerFriendRequestToLocalFriendRequest(info *sdkws.FriendRequest) *model_struct.LocalFriendRequest { |
||||
return &model_struct.LocalFriendRequest{ |
||||
FromUserID: info.FromUserID, |
||||
FromNickname: info.FromNickname, |
||||
FromFaceURL: info.FromFaceURL, |
||||
//FromGender: info.FromGender,
|
||||
ToUserID: info.ToUserID, |
||||
ToNickname: info.ToNickname, |
||||
ToFaceURL: info.ToFaceURL, |
||||
//ToGender: info.ToGender,
|
||||
HandleResult: info.HandleResult, |
||||
ReqMsg: info.ReqMsg, |
||||
CreateTime: info.CreateTime, |
||||
HandlerUserID: info.HandlerUserID, |
||||
HandleMsg: info.HandleMsg, |
||||
HandleTime: info.HandleTime, |
||||
Ex: info.Ex, |
||||
//AttachedInfo: info.AttachedInfo,
|
||||
} |
||||
} |
||||
|
||||
func ServerFriendToLocalFriend(info *sdkws.FriendInfo) *model_struct.LocalFriend { |
||||
return &model_struct.LocalFriend{ |
||||
OwnerUserID: info.OwnerUserID, |
||||
FriendUserID: info.FriendUser.UserID, |
||||
Remark: info.Remark, |
||||
CreateTime: info.CreateTime, |
||||
AddSource: info.AddSource, |
||||
OperatorUserID: info.OperatorUserID, |
||||
Nickname: info.FriendUser.Nickname, |
||||
FaceURL: info.FriendUser.FaceURL, |
||||
Ex: info.Ex, |
||||
//AttachedInfo: info.FriendUser.AttachedInfo,
|
||||
IsPinned: info.IsPinned, |
||||
} |
||||
} |
||||
|
||||
func ServerBlackToLocalBlack(info *sdkws.BlackInfo) *model_struct.LocalBlack { |
||||
return &model_struct.LocalBlack{ |
||||
OwnerUserID: info.OwnerUserID, |
||||
BlockUserID: info.BlackUserInfo.UserID, |
||||
CreateTime: info.CreateTime, |
||||
AddSource: info.AddSource, |
||||
OperatorUserID: info.OperatorUserID, |
||||
Nickname: info.BlackUserInfo.Nickname, |
||||
FaceURL: info.BlackUserInfo.FaceURL, |
||||
Ex: info.Ex, |
||||
//AttachedInfo: info.FriendUser.AttachedInfo,
|
||||
} |
||||
} |
@ -0,0 +1,199 @@ |
||||
// Copyright 2021 OpenIM Corporation
|
||||
//
|
||||
// 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 friend |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/user" |
||||
"github.com/openimsdk/openim-sdk-core/v3/open_im_sdk_callback" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/page" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/syncer" |
||||
friend "github.com/openimsdk/protocol/relation" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
|
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func NewFriend(loginUserID string, db db_interface.DataBase, user *user.User, conversationCh chan common.Cmd2Value) *Friend { |
||||
f := &Friend{loginUserID: loginUserID, db: db, user: user, conversationCh: conversationCh} |
||||
f.initSyncer() |
||||
return f |
||||
} |
||||
|
||||
type Friend struct { |
||||
friendListener open_im_sdk_callback.OnFriendshipListenerSdk |
||||
loginUserID string |
||||
db db_interface.DataBase |
||||
user *user.User |
||||
friendSyncer *syncer.Syncer[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string] |
||||
blockSyncer *syncer.Syncer[*model_struct.LocalBlack, syncer.NoResp, [2]string] |
||||
requestRecvSyncer *syncer.Syncer[*model_struct.LocalFriendRequest, syncer.NoResp, [2]string] |
||||
requestSendSyncer *syncer.Syncer[*model_struct.LocalFriendRequest, syncer.NoResp, [2]string] |
||||
conversationCh chan common.Cmd2Value |
||||
listenerForService open_im_sdk_callback.OnListenerForService |
||||
} |
||||
|
||||
func (f *Friend) initSyncer() { |
||||
f.friendSyncer = syncer.New2[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string]( |
||||
syncer.WithInsert[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, value *model_struct.LocalFriend) error { |
||||
return f.db.InsertFriend(ctx, value) |
||||
}), |
||||
syncer.WithDelete[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, value *model_struct.LocalFriend) error { |
||||
return f.db.DeleteFriendDB(ctx, value.FriendUserID) |
||||
}), |
||||
syncer.WithUpdate[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, server, local *model_struct.LocalFriend) error { |
||||
return f.db.UpdateFriend(ctx, server) |
||||
}), |
||||
syncer.WithUUID[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(value *model_struct.LocalFriend) [2]string { |
||||
return [...]string{value.OwnerUserID, value.FriendUserID} |
||||
}), |
||||
syncer.WithNotice[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, state int, server, local *model_struct.LocalFriend) error { |
||||
switch state { |
||||
case syncer.Insert: |
||||
f.friendListener.OnFriendAdded(*server) |
||||
case syncer.Delete: |
||||
log.ZDebug(ctx, "syncer OnFriendDeleted", "local", local) |
||||
f.friendListener.OnFriendDeleted(*local) |
||||
case syncer.Update: |
||||
f.friendListener.OnFriendInfoChanged(*server) |
||||
if local.Nickname != server.Nickname || local.FaceURL != server.FaceURL || local.Remark != server.Remark { |
||||
if server.Remark != "" { |
||||
server.Nickname = server.Remark |
||||
} |
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{ |
||||
Action: constant.UpdateConFaceUrlAndNickName, |
||||
Args: common.SourceIDAndSessionType{ |
||||
SourceID: server.FriendUserID, |
||||
SessionType: constant.SingleChatType, |
||||
FaceURL: server.FaceURL, |
||||
Nickname: server.Nickname, |
||||
}, |
||||
}, f.conversationCh) |
||||
_ = common.TriggerCmdUpdateMessage(ctx, common.UpdateMessageNode{ |
||||
Action: constant.UpdateMsgFaceUrlAndNickName, |
||||
Args: common.UpdateMessageInfo{ |
||||
SessionType: constant.SingleChatType, |
||||
UserID: server.FriendUserID, |
||||
FaceURL: server.FaceURL, |
||||
Nickname: server.Nickname, |
||||
}, |
||||
}, f.conversationCh) |
||||
} |
||||
} |
||||
return nil |
||||
}), |
||||
syncer.WithBatchInsert[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, values []*model_struct.LocalFriend) error { |
||||
log.ZDebug(ctx, "BatchInsertFriend", "length", len(values)) |
||||
return f.db.BatchInsertFriend(ctx, values) |
||||
}), |
||||
syncer.WithDeleteAll[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(ctx context.Context, _ string) error { |
||||
return f.db.DeleteAllFriend(ctx) |
||||
}), |
||||
syncer.WithBatchPageReq[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(entityID string) page.PageReq { |
||||
return &friend.GetPaginationFriendsReq{UserID: entityID, |
||||
Pagination: &sdkws.RequestPagination{ShowNumber: 100}} |
||||
}), |
||||
syncer.WithBatchPageRespConvertFunc[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](func(resp *friend.GetPaginationFriendsResp) []*model_struct.LocalFriend { |
||||
return datautil.Batch(ServerFriendToLocalFriend, resp.FriendsInfo) |
||||
}), |
||||
syncer.WithReqApiRouter[*model_struct.LocalFriend, friend.GetPaginationFriendsResp, [2]string](constant.GetFriendListRouter), |
||||
) |
||||
|
||||
f.blockSyncer = syncer.New[*model_struct.LocalBlack, syncer.NoResp, [2]string](func(ctx context.Context, value *model_struct.LocalBlack) error { |
||||
return f.db.InsertBlack(ctx, value) |
||||
}, func(ctx context.Context, value *model_struct.LocalBlack) error { |
||||
return f.db.DeleteBlack(ctx, value.BlockUserID) |
||||
}, func(ctx context.Context, server *model_struct.LocalBlack, local *model_struct.LocalBlack) error { |
||||
return f.db.UpdateBlack(ctx, server) |
||||
}, func(value *model_struct.LocalBlack) [2]string { |
||||
return [...]string{value.OwnerUserID, value.BlockUserID} |
||||
}, nil, func(ctx context.Context, state int, server, local *model_struct.LocalBlack) error { |
||||
switch state { |
||||
case syncer.Insert: |
||||
f.friendListener.OnBlackAdded(*server) |
||||
case syncer.Delete: |
||||
f.friendListener.OnBlackDeleted(*local) |
||||
} |
||||
return nil |
||||
}) |
||||
f.requestRecvSyncer = syncer.New[*model_struct.LocalFriendRequest, syncer.NoResp, [2]string](func(ctx context.Context, value *model_struct.LocalFriendRequest) error { |
||||
return f.db.InsertFriendRequest(ctx, value) |
||||
}, func(ctx context.Context, value *model_struct.LocalFriendRequest) error { |
||||
return f.db.DeleteFriendRequestBothUserID(ctx, value.FromUserID, value.ToUserID) |
||||
}, func(ctx context.Context, server *model_struct.LocalFriendRequest, local *model_struct.LocalFriendRequest) error { |
||||
return f.db.UpdateFriendRequest(ctx, server) |
||||
}, func(value *model_struct.LocalFriendRequest) [2]string { |
||||
return [...]string{value.FromUserID, value.ToUserID} |
||||
}, nil, func(ctx context.Context, state int, server, local *model_struct.LocalFriendRequest) error { |
||||
switch state { |
||||
case syncer.Insert: |
||||
f.friendListener.OnFriendApplicationAdded(*server) |
||||
case syncer.Delete: |
||||
f.friendListener.OnFriendApplicationDeleted(*local) |
||||
case syncer.Update: |
||||
switch server.HandleResult { |
||||
case constant.FriendResponseAgree: |
||||
f.friendListener.OnFriendApplicationAccepted(*server) |
||||
case constant.FriendResponseRefuse: |
||||
f.friendListener.OnFriendApplicationRejected(*server) |
||||
case constant.FriendResponseDefault: |
||||
f.friendListener.OnFriendApplicationAdded(*server) |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
f.requestSendSyncer = syncer.New[*model_struct.LocalFriendRequest, syncer.NoResp, [2]string](func(ctx context.Context, value *model_struct.LocalFriendRequest) error { |
||||
return f.db.InsertFriendRequest(ctx, value) |
||||
}, func(ctx context.Context, value *model_struct.LocalFriendRequest) error { |
||||
return f.db.DeleteFriendRequestBothUserID(ctx, value.FromUserID, value.ToUserID) |
||||
}, func(ctx context.Context, server *model_struct.LocalFriendRequest, local *model_struct.LocalFriendRequest) error { |
||||
return f.db.UpdateFriendRequest(ctx, server) |
||||
}, func(value *model_struct.LocalFriendRequest) [2]string { |
||||
return [...]string{value.FromUserID, value.ToUserID} |
||||
}, nil, func(ctx context.Context, state int, server, local *model_struct.LocalFriendRequest) error { |
||||
switch state { |
||||
case syncer.Insert: |
||||
f.friendListener.OnFriendApplicationAdded(*server) |
||||
case syncer.Delete: |
||||
f.friendListener.OnFriendApplicationDeleted(*local) |
||||
case syncer.Update: |
||||
switch server.HandleResult { |
||||
case constant.FriendResponseAgree: |
||||
f.friendListener.OnFriendApplicationAccepted(*server) |
||||
case constant.FriendResponseRefuse: |
||||
f.friendListener.OnFriendApplicationRejected(*server) |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
} |
||||
|
||||
func (f *Friend) Db() db_interface.DataBase { |
||||
return f.db |
||||
} |
||||
|
||||
func (f *Friend) SetListener(listener func() open_im_sdk_callback.OnFriendshipListener) { |
||||
f.friendListener = open_im_sdk_callback.NewOnFriendshipListenerSdk(listener) |
||||
} |
||||
|
||||
func (f *Friend) SetListenerForService(listener open_im_sdk_callback.OnListenerForService) { |
||||
f.listenerForService = listener |
||||
} |
@ -0,0 +1,33 @@ |
||||
package friend |
||||
|
||||
import ( |
||||
"crypto/md5" |
||||
"encoding/binary" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/protocol/constant" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
func (f *Friend) CalculateHash(friends []*model_struct.LocalFriend) uint64 { |
||||
datautil.SortAny(friends, func(a, b *model_struct.LocalFriend) bool { |
||||
return a.CreateTime > b.CreateTime |
||||
}) |
||||
if len(friends) > constant.MaxSyncPullNumber { |
||||
friends = friends[:constant.MaxSyncPullNumber] |
||||
} |
||||
hashStr := strings.Join(datautil.Slice(friends, func(f *model_struct.LocalFriend) string { |
||||
return strings.Join([]string{ |
||||
f.FriendUserID, |
||||
f.Remark, |
||||
strconv.FormatInt(f.CreateTime, 10), |
||||
strconv.Itoa(int(f.AddSource)), |
||||
f.OperatorUserID, |
||||
f.Ex, |
||||
strconv.FormatBool(f.IsPinned), |
||||
}, ",") |
||||
}), ";") |
||||
sum := md5.Sum([]byte(hashStr)) |
||||
return binary.BigEndian.Uint64(sum[:]) |
||||
} |
@ -0,0 +1,136 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 friend |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func (f *Friend) DoNotification(ctx context.Context, msg *sdkws.MsgData) { |
||||
go func() { |
||||
if err := f.doNotification(ctx, msg); err != nil { |
||||
log.ZError(ctx, "doNotification error", err, "msg", msg) |
||||
} |
||||
}() |
||||
} |
||||
|
||||
func (f *Friend) doNotification(ctx context.Context, msg *sdkws.MsgData) error { |
||||
switch msg.ContentType { |
||||
case constant.FriendApplicationNotification: |
||||
tips := sdkws.FriendApplicationTips{} |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil { |
||||
return err |
||||
} |
||||
return f.SyncBothFriendRequest(ctx, |
||||
tips.FromToUserID.FromUserID, tips.FromToUserID.ToUserID) |
||||
case constant.FriendApplicationApprovedNotification: |
||||
var tips sdkws.FriendApplicationApprovedTips |
||||
err := utils.UnmarshalNotificationElem(msg.Content, &tips) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
|
||||
if tips.FromToUserID.FromUserID == f.loginUserID { |
||||
err = f.SyncFriends(ctx, []string{tips.FromToUserID.ToUserID}) |
||||
} else if tips.FromToUserID.ToUserID == f.loginUserID { |
||||
err = f.SyncFriends(ctx, []string{tips.FromToUserID.FromUserID}) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return f.SyncBothFriendRequest(ctx, tips.FromToUserID.FromUserID, tips.FromToUserID.ToUserID) |
||||
case constant.FriendApplicationRejectedNotification: |
||||
var tips sdkws.FriendApplicationRejectedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil { |
||||
return err |
||||
} |
||||
return f.SyncBothFriendRequest(ctx, tips.FromToUserID.FromUserID, tips.FromToUserID.ToUserID) |
||||
case constant.FriendAddedNotification: |
||||
var tips sdkws.FriendAddedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil { |
||||
return err |
||||
} |
||||
if tips.Friend != nil && tips.Friend.FriendUser != nil { |
||||
if tips.Friend.FriendUser.UserID == f.loginUserID { |
||||
return f.SyncFriends(ctx, []string{tips.Friend.OwnerUserID}) |
||||
} else if tips.Friend.OwnerUserID == f.loginUserID { |
||||
return f.SyncFriends(ctx, []string{tips.Friend.FriendUser.UserID}) |
||||
} |
||||
} |
||||
case constant.FriendDeletedNotification: |
||||
var tips sdkws.FriendDeletedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil { |
||||
return err |
||||
} |
||||
if tips.FromToUserID != nil { |
||||
if tips.FromToUserID.FromUserID == f.loginUserID { |
||||
return f.deleteFriend(ctx, tips.FromToUserID.ToUserID) |
||||
} |
||||
} |
||||
case constant.FriendRemarkSetNotification: |
||||
var tips sdkws.FriendInfoChangedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil { |
||||
return err |
||||
} |
||||
if tips.FromToUserID != nil { |
||||
if tips.FromToUserID.FromUserID == f.loginUserID { |
||||
return f.SyncFriends(ctx, []string{tips.FromToUserID.ToUserID}) |
||||
} |
||||
} |
||||
case constant.FriendInfoUpdatedNotification: |
||||
var tips sdkws.UserInfoUpdatedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil { |
||||
return err |
||||
} |
||||
if tips.UserID != f.loginUserID { |
||||
return f.SyncFriends(ctx, []string{tips.UserID}) |
||||
} |
||||
case constant.BlackAddedNotification: |
||||
var tips sdkws.BlackAddedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil { |
||||
return err |
||||
} |
||||
if tips.FromToUserID.FromUserID == f.loginUserID { |
||||
return f.SyncAllBlackList(ctx) |
||||
} |
||||
case constant.BlackDeletedNotification: |
||||
var tips sdkws.BlackDeletedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil { |
||||
return err |
||||
} |
||||
if tips.FromToUserID.FromUserID == f.loginUserID { |
||||
return f.SyncAllBlackList(ctx) |
||||
} |
||||
case constant.FriendsInfoUpdateNotification: |
||||
|
||||
var tips sdkws.FriendsInfoUpdateTips |
||||
|
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &tips); err != nil { |
||||
return err |
||||
} |
||||
if tips.FromToUserID.ToUserID == f.loginUserID { |
||||
return f.SyncFriends(ctx, tips.FriendIDs) |
||||
} |
||||
default: |
||||
return fmt.Errorf("type failed %d", msg.ContentType) |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,344 @@ |
||||
// Copyright 2021 OpenIM Corporation
|
||||
//
|
||||
// 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 friend |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
friend "github.com/openimsdk/protocol/relation" |
||||
"github.com/openimsdk/protocol/wrapperspb" |
||||
"github.com/openimsdk/tools/errs" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/datafetcher" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
sdk "github.com/openimsdk/openim-sdk-core/v3/pkg/sdk_params_callback" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/server_api_params" |
||||
|
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func (f *Friend) GetSpecifiedFriendsInfo(ctx context.Context, friendUserIDList []string) ([]*server_api_params.FullUserInfo, error) { |
||||
datafetcher := datafetcher.NewDataFetcher( |
||||
f.db, |
||||
f.friendListTableName(), |
||||
f.loginUserID, |
||||
func(localFriend *model_struct.LocalFriend) string { |
||||
return localFriend.FriendUserID |
||||
}, |
||||
func(ctx context.Context, values []*model_struct.LocalFriend) error { |
||||
return f.db.BatchInsertFriend(ctx, values) |
||||
}, |
||||
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalFriend, error) { |
||||
return f.db.GetFriendInfoList(ctx, userIDs) |
||||
}, |
||||
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalFriend, error) { |
||||
serverFriend, err := f.GetDesignatedFriends(ctx, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return datautil.Batch(ServerFriendToLocalFriend, serverFriend), nil |
||||
}, |
||||
) |
||||
localFriendList, err := datafetcher.FetchMissingAndFillLocal(ctx, friendUserIDList) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
log.ZDebug(ctx, "GetDesignatedFriendsInfo", "localFriendList", localFriendList) |
||||
blackList, err := f.db.GetBlackInfoList(ctx, friendUserIDList) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
log.ZDebug(ctx, "GetDesignatedFriendsInfo", "blackList", blackList) |
||||
m := make(map[string]*model_struct.LocalBlack) |
||||
for i, black := range blackList { |
||||
m[black.BlockUserID] = blackList[i] |
||||
} |
||||
res := make([]*server_api_params.FullUserInfo, 0, len(localFriendList)) |
||||
for _, localFriend := range localFriendList { |
||||
res = append(res, &server_api_params.FullUserInfo{ |
||||
PublicInfo: nil, |
||||
FriendInfo: localFriend, |
||||
BlackInfo: m[localFriend.FriendUserID], |
||||
}) |
||||
} |
||||
return res, nil |
||||
} |
||||
|
||||
func (f *Friend) AddFriend(ctx context.Context, userIDReqMsg *friend.ApplyToAddFriendReq) error { |
||||
if userIDReqMsg.FromUserID == "" { |
||||
userIDReqMsg.FromUserID = f.loginUserID |
||||
} |
||||
if err := util.ApiPost(ctx, constant.AddFriendRouter, userIDReqMsg, nil); err != nil { |
||||
return err |
||||
} |
||||
return f.SyncAllFriendApplication(ctx) |
||||
} |
||||
|
||||
func (f *Friend) GetFriendApplicationListAsRecipient(ctx context.Context) ([]*model_struct.LocalFriendRequest, error) { |
||||
return f.db.GetRecvFriendApplication(ctx) |
||||
} |
||||
|
||||
func (f *Friend) GetFriendApplicationListAsApplicant(ctx context.Context) ([]*model_struct.LocalFriendRequest, error) { |
||||
return f.db.GetSendFriendApplication(ctx) |
||||
} |
||||
|
||||
func (f *Friend) AcceptFriendApplication(ctx context.Context, userIDHandleMsg *sdk.ProcessFriendApplicationParams) error { |
||||
return f.RespondFriendApply(ctx, &friend.RespondFriendApplyReq{FromUserID: userIDHandleMsg.ToUserID, ToUserID: f.loginUserID, HandleResult: constant.FriendResponseAgree, HandleMsg: userIDHandleMsg.HandleMsg}) |
||||
} |
||||
|
||||
func (f *Friend) RefuseFriendApplication(ctx context.Context, userIDHandleMsg *sdk.ProcessFriendApplicationParams) error { |
||||
return f.RespondFriendApply(ctx, &friend.RespondFriendApplyReq{FromUserID: userIDHandleMsg.ToUserID, ToUserID: f.loginUserID, HandleResult: constant.FriendResponseRefuse, HandleMsg: userIDHandleMsg.HandleMsg}) |
||||
} |
||||
|
||||
func (f *Friend) RespondFriendApply(ctx context.Context, req *friend.RespondFriendApplyReq) error { |
||||
if req.ToUserID == "" { |
||||
req.ToUserID = f.loginUserID |
||||
} |
||||
if err := util.ApiPost(ctx, constant.AddFriendResponse, req, nil); err != nil { |
||||
return err |
||||
} |
||||
if req.HandleResult == constant.FriendResponseAgree { |
||||
_ = f.SyncFriends(ctx, []string{req.FromUserID}) |
||||
} |
||||
_ = f.SyncAllFriendApplication(ctx) |
||||
return nil |
||||
// return f.SyncFriendApplication(ctx)
|
||||
} |
||||
|
||||
func (f *Friend) CheckFriend(ctx context.Context, friendUserIDList []string) ([]*server_api_params.UserIDResult, error) { |
||||
friendList, err := f.db.GetFriendInfoList(ctx, friendUserIDList) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
blackList, err := f.db.GetBlackInfoList(ctx, friendUserIDList) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
res := make([]*server_api_params.UserIDResult, 0, len(friendUserIDList)) |
||||
for _, v := range friendUserIDList { |
||||
var r server_api_params.UserIDResult |
||||
isBlack := false |
||||
isFriend := false |
||||
for _, b := range blackList { |
||||
if v == b.BlockUserID { |
||||
isBlack = true |
||||
break |
||||
} |
||||
} |
||||
for _, f := range friendList { |
||||
if v == f.FriendUserID { |
||||
isFriend = true |
||||
break |
||||
} |
||||
} |
||||
r.UserID = v |
||||
if isFriend && !isBlack { |
||||
r.Result = 1 |
||||
} else { |
||||
r.Result = 0 |
||||
} |
||||
res = append(res, &r) |
||||
} |
||||
return res, nil |
||||
} |
||||
|
||||
func (f *Friend) DeleteFriend(ctx context.Context, friendUserID string) error { |
||||
if err := util.ApiPost(ctx, constant.DeleteFriendRouter, &friend.DeleteFriendReq{OwnerUserID: f.loginUserID, FriendUserID: friendUserID}, nil); err != nil { |
||||
return err |
||||
} |
||||
return f.deleteFriend(ctx, friendUserID) |
||||
} |
||||
|
||||
func (f *Friend) GetFriendList(ctx context.Context) ([]*server_api_params.FullUserInfo, error) { |
||||
localFriendList, err := f.db.GetAllFriendList(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
localBlackList, err := f.db.GetBlackListDB(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
m := make(map[string]*model_struct.LocalBlack) |
||||
for i, black := range localBlackList { |
||||
m[black.BlockUserID] = localBlackList[i] |
||||
} |
||||
res := make([]*server_api_params.FullUserInfo, 0, len(localFriendList)) |
||||
for _, localFriend := range localFriendList { |
||||
res = append(res, &server_api_params.FullUserInfo{ |
||||
PublicInfo: nil, |
||||
FriendInfo: localFriend, |
||||
BlackInfo: m[localFriend.FriendUserID], |
||||
}) |
||||
} |
||||
return res, nil |
||||
} |
||||
|
||||
func (f *Friend) GetFriendListPage(ctx context.Context, offset, count int32) ([]*server_api_params.FullUserInfo, error) { |
||||
dataFetcher := datafetcher.NewDataFetcher( |
||||
f.db, |
||||
f.friendListTableName(), |
||||
f.loginUserID, |
||||
func(localFriend *model_struct.LocalFriend) string { |
||||
return localFriend.FriendUserID |
||||
}, |
||||
func(ctx context.Context, values []*model_struct.LocalFriend) error { |
||||
return f.db.BatchInsertFriend(ctx, values) |
||||
}, |
||||
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalFriend, error) { |
||||
return f.db.GetFriendInfoList(ctx, userIDs) |
||||
}, |
||||
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalFriend, error) { |
||||
serverFriend, err := f.GetDesignatedFriends(ctx, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return datautil.Batch(ServerFriendToLocalFriend, serverFriend), nil |
||||
}, |
||||
) |
||||
|
||||
localFriendList, err := dataFetcher.FetchWithPagination(ctx, int(offset), int(count)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
// don't need extra handle. only full pull.
|
||||
localBlackList, err := f.db.GetBlackListDB(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
m := make(map[string]*model_struct.LocalBlack) |
||||
for i, black := range localBlackList { |
||||
m[black.BlockUserID] = localBlackList[i] |
||||
} |
||||
res := make([]*server_api_params.FullUserInfo, 0, len(localFriendList)) |
||||
for _, localFriend := range localFriendList { |
||||
res = append(res, &server_api_params.FullUserInfo{ |
||||
PublicInfo: nil, |
||||
FriendInfo: localFriend, |
||||
BlackInfo: m[localFriend.FriendUserID], |
||||
}) |
||||
} |
||||
return res, nil |
||||
} |
||||
|
||||
func (f *Friend) SearchFriends(ctx context.Context, param *sdk.SearchFriendsParam) ([]*sdk.SearchFriendItem, error) { |
||||
if len(param.KeywordList) == 0 || (!param.IsSearchNickname && !param.IsSearchUserID && !param.IsSearchRemark) { |
||||
return nil, sdkerrs.ErrArgs.WrapMsg("keyword is null or search field all false") |
||||
} |
||||
localFriendList, err := f.db.SearchFriendList(ctx, param.KeywordList[0], param.IsSearchUserID, param.IsSearchNickname, param.IsSearchRemark) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
localBlackList, err := f.db.GetBlackListDB(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
m := make(map[string]struct{}) |
||||
for _, black := range localBlackList { |
||||
m[black.BlockUserID] = struct{}{} |
||||
} |
||||
res := make([]*sdk.SearchFriendItem, 0, len(localFriendList)) |
||||
for i, localFriend := range localFriendList { |
||||
var relationship int |
||||
if _, ok := m[localFriend.FriendUserID]; ok { |
||||
relationship = constant.BlackRelationship |
||||
} else { |
||||
relationship = constant.FriendRelationship |
||||
} |
||||
res = append(res, &sdk.SearchFriendItem{ |
||||
LocalFriend: *localFriendList[i], |
||||
Relationship: relationship, |
||||
}) |
||||
} |
||||
return res, nil |
||||
} |
||||
|
||||
func (f *Friend) SetFriendRemark(ctx context.Context, userIDRemark *sdk.SetFriendRemarkParams) error { |
||||
if err := util.ApiPost(ctx, constant.SetFriendRemark, &friend.SetFriendRemarkReq{OwnerUserID: f.loginUserID, FriendUserID: userIDRemark.ToUserID, Remark: userIDRemark.Remark}, nil); err != nil { |
||||
return err |
||||
} |
||||
return f.SyncFriends(ctx, []string{userIDRemark.ToUserID}) |
||||
} |
||||
|
||||
func (f *Friend) PinFriends(ctx context.Context, friends *sdk.SetFriendPinParams) error { |
||||
if err := util.ApiPost(ctx, constant.UpdateFriends, &friend.UpdateFriendsReq{OwnerUserID: f.loginUserID, FriendUserIDs: friends.ToUserIDs, IsPinned: friends.IsPinned}, nil); err != nil { |
||||
return err |
||||
} |
||||
return f.SyncFriends(ctx, friends.ToUserIDs) |
||||
} |
||||
|
||||
func (f *Friend) AddBlack(ctx context.Context, blackUserID string, ex string) error { |
||||
if err := util.ApiPost(ctx, constant.AddBlackRouter, &friend.AddBlackReq{OwnerUserID: f.loginUserID, BlackUserID: blackUserID, Ex: ex}, nil); err != nil { |
||||
return err |
||||
} |
||||
return f.SyncAllBlackList(ctx) |
||||
} |
||||
|
||||
func (f *Friend) RemoveBlack(ctx context.Context, blackUserID string) error { |
||||
if err := util.ApiPost(ctx, constant.RemoveBlackRouter, &friend.RemoveBlackReq{OwnerUserID: f.loginUserID, BlackUserID: blackUserID}, nil); err != nil { |
||||
return err |
||||
} |
||||
return f.SyncAllBlackList(ctx) |
||||
} |
||||
|
||||
func (f *Friend) GetBlackList(ctx context.Context) ([]*model_struct.LocalBlack, error) { |
||||
return f.db.GetBlackListDB(ctx) |
||||
} |
||||
|
||||
func (f *Friend) SetFriendsEx(ctx context.Context, friendIDs []string, ex string) error { |
||||
if err := util.ApiPost(ctx, constant.UpdateFriends, &friend.UpdateFriendsReq{OwnerUserID: f.loginUserID, FriendUserIDs: friendIDs, Ex: &wrapperspb.StringValue{ |
||||
Value: ex, |
||||
}}, nil); err != nil { |
||||
return err |
||||
} |
||||
// Check if the specified ID is a friend
|
||||
friendResults, err := f.CheckFriend(ctx, friendIDs) |
||||
if err != nil { |
||||
return errs.WrapMsg(err, "Error checking friend status") |
||||
} |
||||
|
||||
// Determine if friendID is indeed a friend
|
||||
// Iterate over each friendID
|
||||
for _, friendID := range friendIDs { |
||||
isFriend := false |
||||
|
||||
// Check if this friendID is in the friendResults
|
||||
for _, result := range friendResults { |
||||
if result.UserID == friendID && result.Result == 1 { // Assuming result 1 means they are friends
|
||||
isFriend = true |
||||
break |
||||
} |
||||
} |
||||
|
||||
// If this friendID is not a friend, return an error
|
||||
if !isFriend { |
||||
return errs.ErrRecordNotFound.WrapMsg("Not friend") |
||||
} |
||||
} |
||||
|
||||
// If the code reaches here, all friendIDs are confirmed as friends
|
||||
// Update friend information if they are friends
|
||||
|
||||
updateErr := f.db.UpdateColumnsFriend(ctx, friendIDs, map[string]interface{}{"Ex": ex}) |
||||
if updateErr != nil { |
||||
return errs.WrapMsg(updateErr, "Error updating friend information") |
||||
} |
||||
return nil |
||||
} |
@ -0,0 +1,180 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 friend |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
"time" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
friend "github.com/openimsdk/protocol/relation" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func (f *Friend) SyncBothFriendRequest(ctx context.Context, fromUserID, toUserID string) error { |
||||
var resp friend.GetDesignatedFriendsApplyResp |
||||
if err := util.ApiPost(ctx, constant.GetDesignatedFriendsApplyRouter, &friend.GetDesignatedFriendsApplyReq{FromUserID: fromUserID, ToUserID: toUserID}, &resp); err != nil { |
||||
return nil |
||||
} |
||||
localData, err := f.db.GetBothFriendReq(ctx, fromUserID, toUserID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if toUserID == f.loginUserID { |
||||
return f.requestRecvSyncer.Sync(ctx, datautil.Batch(ServerFriendRequestToLocalFriendRequest, resp.FriendRequests), localData, nil) |
||||
} else if fromUserID == f.loginUserID { |
||||
return f.requestSendSyncer.Sync(ctx, datautil.Batch(ServerFriendRequestToLocalFriendRequest, resp.FriendRequests), localData, nil) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// send
|
||||
func (f *Friend) SyncAllSelfFriendApplication(ctx context.Context) error { |
||||
req := &friend.GetPaginationFriendsApplyFromReq{UserID: f.loginUserID, Pagination: &sdkws.RequestPagination{}} |
||||
fn := func(resp *friend.GetPaginationFriendsApplyFromResp) []*sdkws.FriendRequest { |
||||
return resp.FriendRequests |
||||
} |
||||
requests, err := util.GetPageAll(ctx, constant.GetSelfFriendApplicationListRouter, req, fn) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
localData, err := f.db.GetSendFriendApplication(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return f.requestSendSyncer.Sync(ctx, datautil.Batch(ServerFriendRequestToLocalFriendRequest, requests), localData, nil) |
||||
} |
||||
|
||||
// recv
|
||||
func (f *Friend) SyncAllFriendApplication(ctx context.Context) error { |
||||
req := &friend.GetPaginationFriendsApplyToReq{UserID: f.loginUserID, Pagination: &sdkws.RequestPagination{}} |
||||
fn := func(resp *friend.GetPaginationFriendsApplyToResp) []*sdkws.FriendRequest { return resp.FriendRequests } |
||||
requests, err := util.GetPageAll(ctx, constant.GetFriendApplicationListRouter, req, fn) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
localData, err := f.db.GetRecvFriendApplication(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return f.requestRecvSyncer.Sync(ctx, datautil.Batch(ServerFriendRequestToLocalFriendRequest, requests), localData, nil) |
||||
} |
||||
|
||||
func (f *Friend) SyncAllFriendList(ctx context.Context) error { |
||||
t := time.Now() |
||||
defer func(start time.Time) { |
||||
|
||||
elapsed := time.Since(start).Milliseconds() |
||||
log.ZDebug(ctx, "SyncAllFriendList fn call end", "cost time", fmt.Sprintf("%d ms", elapsed)) |
||||
|
||||
}(t) |
||||
return f.IncrSyncFriends(ctx) |
||||
//req := &friend.GetPaginationFriendsReq{UserID: f.loginUserID, Pagination: &sdkws.RequestPagination{}}
|
||||
//fn := func(resp *friend.GetPaginationFriendsResp) []*sdkws.FriendInfo { return resp.FriendsInfo }
|
||||
//friends, err := util.GetPageAll(ctx, constant.GetFriendListRouter, req, fn)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//localData, err := f.db.GetAllFriendList(ctx)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//log.ZDebug(ctx, "sync friend", "data from server", friends, "data from local", localData)
|
||||
//return f.friendSyncer.Sync(ctx, util.Batch(ServerFriendToLocalFriend, friends), localData, nil)
|
||||
} |
||||
|
||||
func (f *Friend) deleteFriend(ctx context.Context, friendUserID string) error { |
||||
return f.IncrSyncFriends(ctx) |
||||
//friends, err := f.db.GetFriendInfoList(ctx, []string{friendUserID})
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//if len(friends) == 0 {
|
||||
// return sdkerrs.ErrUserIDNotFound.WrapMsg("friendUserID not found")
|
||||
//}
|
||||
//if err := f.db.DeleteFriendDB(ctx, friendUserID); err != nil {
|
||||
// return err
|
||||
//}
|
||||
//f.friendListener.OnFriendDeleted(*friends[0])
|
||||
//return nil
|
||||
} |
||||
|
||||
func (f *Friend) SyncFriends(ctx context.Context, friendIDs []string) error { |
||||
return f.IncrSyncFriends(ctx) |
||||
//var resp friend.GetDesignatedFriendsResp
|
||||
//if err := util.ApiPost(ctx, constant.GetDesignatedFriendsRouter, &friend.GetDesignatedFriendsReq{OwnerUserID: f.loginUserID, FriendUserIDs: friendIDs}, &resp); err != nil {
|
||||
// return err
|
||||
//}
|
||||
//localData, err := f.db.GetFriendInfoList(ctx, friendIDs)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//log.ZDebug(ctx, "sync friend", "data from server", resp.FriendsInfo, "data from local", localData)
|
||||
//return f.friendSyncer.Sync(ctx, util.Batch(ServerFriendToLocalFriend, resp.FriendsInfo), localData, nil)
|
||||
} |
||||
|
||||
//func (f *Friend) SyncFriendPart(ctx context.Context) error {
|
||||
// hashResp, err := util.CallApi[friend.GetFriendHashResp](ctx, constant.GetFriendHash, &friend.GetFriendHashReq{UserID: f.loginUserID})
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// friends, err := f.db.GetAllFriendList(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// hashCode := f.CalculateHash(friends)
|
||||
// log.ZDebug(ctx, "SyncFriendPart", "serverHash", hashResp.Hash, "serverTotal", hashResp.Total, "localHash", hashCode, "localTotal", len(friends))
|
||||
// if hashCode == hashResp.Hash {
|
||||
// return nil
|
||||
// }
|
||||
// req := &friend.GetPaginationFriendsReq{
|
||||
// UserID: f.loginUserID,
|
||||
// Pagination: &sdkws.RequestPagination{PageNumber: pconstant.FirstPageNumber, ShowNumber: pconstant.MaxSyncPullNumber},
|
||||
// }
|
||||
// resp, err := util.CallApi[friend.GetPaginationFriendsResp](ctx, constant.GetFriendListRouter, req)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// serverFriends := util.Batch(ServerFriendToLocalFriend, resp.FriendsInfo)
|
||||
// return f.friendSyncer.Sync(ctx, serverFriends, friends, nil)
|
||||
//}
|
||||
|
||||
func (f *Friend) SyncAllBlackList(ctx context.Context) error { |
||||
req := &friend.GetPaginationBlacksReq{UserID: f.loginUserID, Pagination: &sdkws.RequestPagination{}} |
||||
fn := func(resp *friend.GetPaginationBlacksResp) []*sdkws.BlackInfo { return resp.Blacks } |
||||
serverData, err := util.GetPageAll(ctx, constant.GetBlackListRouter, req, fn) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.ZDebug(ctx, "black from server", "data", serverData) |
||||
localData, err := f.db.GetBlackListDB(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.ZDebug(ctx, "black from local", "data", localData) |
||||
return f.blockSyncer.Sync(ctx, datautil.Batch(ServerBlackToLocalBlack, serverData), localData, nil) |
||||
} |
||||
|
||||
func (f *Friend) GetDesignatedFriends(ctx context.Context, friendIDs []string) ([]*sdkws.FriendInfo, error) { |
||||
resp := &friend.GetDesignatedFriendsResp{} |
||||
if err := util.ApiPost(ctx, constant.GetDesignatedFriendsRouter, &friend.GetDesignatedFriendsReq{OwnerUserID: f.loginUserID, FriendUserIDs: friendIDs}, &resp); err != nil { |
||||
return nil, err |
||||
} |
||||
return resp.FriendsInfo, nil |
||||
} |
@ -0,0 +1,72 @@ |
||||
package friend |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/incrversion" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
friend "github.com/openimsdk/protocol/relation" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
) |
||||
|
||||
const ( |
||||
LocalFriendSyncMaxNum = 1000 |
||||
) |
||||
|
||||
func (f *Friend) IncrSyncFriends(ctx context.Context) error { |
||||
friendSyncer := incrversion.VersionSynchronizer[*model_struct.LocalFriend, *friend.GetIncrementalFriendsResp]{ |
||||
Ctx: ctx, |
||||
DB: f.db, |
||||
TableName: f.friendListTableName(), |
||||
EntityID: f.loginUserID, |
||||
Key: func(localFriend *model_struct.LocalFriend) string { |
||||
return localFriend.FriendUserID |
||||
}, |
||||
Local: func() ([]*model_struct.LocalFriend, error) { |
||||
return f.db.GetAllFriendList(ctx) |
||||
}, |
||||
Server: func(version *model_struct.LocalVersionSync) (*friend.GetIncrementalFriendsResp, error) { |
||||
return util.CallApi[friend.GetIncrementalFriendsResp](ctx, constant.GetIncrementalFriends, &friend.GetIncrementalFriendsReq{ |
||||
UserID: f.loginUserID, |
||||
Version: version.Version, |
||||
VersionID: version.VersionID, |
||||
}) |
||||
}, |
||||
Full: func(resp *friend.GetIncrementalFriendsResp) bool { |
||||
return resp.Full |
||||
}, |
||||
Version: func(resp *friend.GetIncrementalFriendsResp) (string, uint64) { |
||||
return resp.VersionID, resp.Version |
||||
}, |
||||
Delete: func(resp *friend.GetIncrementalFriendsResp) []string { |
||||
return resp.Delete |
||||
}, |
||||
Update: func(resp *friend.GetIncrementalFriendsResp) []*model_struct.LocalFriend { |
||||
return datautil.Batch(ServerFriendToLocalFriend, resp.Update) |
||||
}, |
||||
Insert: func(resp *friend.GetIncrementalFriendsResp) []*model_struct.LocalFriend { |
||||
return datautil.Batch(ServerFriendToLocalFriend, resp.Insert) |
||||
}, |
||||
Syncer: func(server, local []*model_struct.LocalFriend) error { |
||||
return f.friendSyncer.Sync(ctx, server, local, nil) |
||||
}, |
||||
FullSyncer: func(ctx context.Context) error { |
||||
return f.friendSyncer.FullSync(ctx, f.loginUserID) |
||||
}, |
||||
FullID: func(ctx context.Context) ([]string, error) { |
||||
resp, err := util.CallApi[friend.GetFullFriendUserIDsResp](ctx, constant.GetFullFriendUserIDs, &friend.GetFullFriendUserIDsReq{ |
||||
UserID: f.loginUserID, |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return resp.UserIDs, nil |
||||
}, |
||||
} |
||||
return friendSyncer.Sync() |
||||
} |
||||
|
||||
func (f *Friend) friendListTableName() string { |
||||
return model_struct.LocalFriend{}.TableName() |
||||
} |
@ -0,0 +1,13 @@ |
||||
package friend |
||||
|
||||
import ( |
||||
"fmt" |
||||
"testing" |
||||
) |
||||
|
||||
func Test_main(t *testing.T) { |
||||
a := []int{1, 2, 3, 4, 5} |
||||
fmt.Println(a[:3]) |
||||
fmt.Println(a[3:]) |
||||
fmt.Println(a[2:4]) |
||||
} |
@ -0,0 +1,62 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 full |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/friend" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/group" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/user" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
) |
||||
|
||||
type Full struct { |
||||
user *user.User |
||||
friend *friend.Friend |
||||
group *group.Group |
||||
ch chan common.Cmd2Value |
||||
db db_interface.DataBase |
||||
} |
||||
|
||||
func (u *Full) Group() *group.Group { |
||||
return u.group |
||||
} |
||||
|
||||
func NewFull(user *user.User, friend *friend.Friend, group *group.Group, ch chan common.Cmd2Value, |
||||
db db_interface.DataBase) *Full { |
||||
return &Full{user: user, friend: friend, group: group, ch: ch, db: db} |
||||
} |
||||
|
||||
func (u *Full) GetGroupInfoFromLocal2Svr(ctx context.Context, groupID string, sessionType int32) (*model_struct.LocalGroup, error) { |
||||
switch sessionType { |
||||
case constant.GroupChatType: |
||||
return u.group.GetGroupInfoFromLocal2Svr(ctx, groupID) |
||||
case constant.SuperGroupChatType: |
||||
return u.GetGroupInfoByGroupID(ctx, groupID) |
||||
default: |
||||
return nil, fmt.Errorf("sessionType is not support %d", sessionType) |
||||
} |
||||
} |
||||
func (u *Full) GetReadDiffusionGroupIDList(ctx context.Context) ([]string, error) { |
||||
g, err := u.group.GetJoinedDiffusionGroupIDListFromSvr(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return g, err |
||||
} |
@ -0,0 +1,29 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 full |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
) |
||||
|
||||
func (u *Full) GetGroupInfoByGroupID(ctx context.Context, groupID string) (*model_struct.LocalGroup, error) { |
||||
g2, err := u.group.GetGroupInfoFromLocal2Svr(ctx, groupID) |
||||
return g2, err |
||||
} |
||||
|
||||
func (u *Full) GetGroupsInfo(ctx context.Context, groupIDs ...string) (map[string]*model_struct.LocalGroup, error) { |
||||
return u.group.GetGroupsInfoFromLocal2Svr(ctx, groupIDs...) |
||||
} |
@ -0,0 +1,199 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 full |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
api "github.com/openimsdk/openim-sdk-core/v3/pkg/server_api_params" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func (u *Full) GetUsersInfo(ctx context.Context, userIDs []string) ([]*api.FullUserInfo, error) { |
||||
friendList, err := u.db.GetFriendInfoList(ctx, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
blackList, err := u.db.GetBlackInfoList(ctx, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
users, err := u.user.GetServerUserInfo(ctx, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
friendMap := make(map[string]*model_struct.LocalFriend) |
||||
for i, f := range friendList { |
||||
friendMap[f.FriendUserID] = friendList[i] |
||||
} |
||||
blackMap := make(map[string]*model_struct.LocalBlack) |
||||
for i, b := range blackList { |
||||
blackMap[b.BlockUserID] = blackList[i] |
||||
} |
||||
userMap := make(map[string]*api.PublicUser) |
||||
for _, info := range users { |
||||
userMap[info.UserID] = &api.PublicUser{ |
||||
UserID: info.UserID, |
||||
Nickname: info.Nickname, |
||||
FaceURL: info.FaceURL, |
||||
Ex: info.Ex, |
||||
CreateTime: info.CreateTime, |
||||
} |
||||
} |
||||
res := make([]*api.FullUserInfo, 0, len(users)) |
||||
for _, userID := range userIDs { |
||||
info, ok := userMap[userID] |
||||
if !ok { |
||||
continue |
||||
} |
||||
res = append(res, &api.FullUserInfo{ |
||||
PublicInfo: info, |
||||
FriendInfo: friendMap[userID], |
||||
BlackInfo: blackMap[userID], |
||||
}) |
||||
|
||||
// update single conversation
|
||||
|
||||
conversation, err := u.db.GetConversationByUserID(ctx, userID) |
||||
if err != nil { |
||||
log.ZWarn(ctx, "GetConversationByUserID failed", err, "userID", userID) |
||||
} else { |
||||
if _, ok := friendMap[userID]; ok { |
||||
continue |
||||
} |
||||
log.ZDebug(ctx, "GetConversationByUserID", "conversation", conversation) |
||||
if conversation.ShowName != info.Nickname || conversation.FaceURL != info.FaceURL { |
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{Action: constant.UpdateConFaceUrlAndNickName, |
||||
Args: common.SourceIDAndSessionType{SourceID: userID, SessionType: conversation.ConversationType, FaceURL: info.FaceURL, Nickname: info.Nickname}}, u.ch) |
||||
_ = common.TriggerCmdUpdateMessage(ctx, common.UpdateMessageNode{Action: constant.UpdateMsgFaceUrlAndNickName, |
||||
Args: common.UpdateMessageInfo{SessionType: conversation.ConversationType, UserID: userID, FaceURL: info.FaceURL, Nickname: info.Nickname}}, u.ch) |
||||
} |
||||
} |
||||
} |
||||
return res, nil |
||||
} |
||||
|
||||
func (u *Full) GetUsersInfoWithCache(ctx context.Context, userIDs []string, groupID string) ([]*api.FullUserInfoWithCache, error) { |
||||
friendList, err := u.db.GetFriendInfoList(ctx, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
blackList, err := u.db.GetBlackInfoList(ctx, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
users, err := u.user.GetServerUserInfo(ctx, userIDs) |
||||
if err == nil { |
||||
var strangers []*model_struct.LocalStranger |
||||
for _, val := range users { |
||||
strangerTemp := &model_struct.LocalStranger{ |
||||
UserID: val.UserID, |
||||
Nickname: val.Nickname, |
||||
FaceURL: val.FaceURL, |
||||
CreateTime: val.CreateTime, |
||||
AppMangerLevel: val.AppMangerLevel, |
||||
Ex: val.Ex, |
||||
AttachedInfo: val.Ex, |
||||
GlobalRecvMsgOpt: val.GlobalRecvMsgOpt, |
||||
} |
||||
strangers = append(strangers, strangerTemp) |
||||
} |
||||
err := u.db.SetStrangerInfo(ctx, strangers) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} else { |
||||
strangerList, err := u.db.GetStrangerInfo(ctx, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
for _, val := range strangerList { |
||||
userTemp := &sdkws.UserInfo{ |
||||
UserID: val.UserID, |
||||
Nickname: val.Nickname, |
||||
FaceURL: val.FaceURL, |
||||
Ex: val.Ex, |
||||
CreateTime: val.CreateTime, |
||||
AppMangerLevel: val.AppMangerLevel, |
||||
GlobalRecvMsgOpt: val.GlobalRecvMsgOpt, |
||||
} |
||||
users = append(users, userTemp) |
||||
} |
||||
} |
||||
var groupMemberList []*model_struct.LocalGroupMember |
||||
if groupID != "" { |
||||
groupMemberList, err = u.db.GetGroupSomeMemberInfo(ctx, groupID, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
} |
||||
friendMap := make(map[string]*model_struct.LocalFriend) |
||||
for i, f := range friendList { |
||||
friendMap[f.FriendUserID] = friendList[i] |
||||
} |
||||
blackMap := make(map[string]*model_struct.LocalBlack) |
||||
for i, b := range blackList { |
||||
blackMap[b.BlockUserID] = blackList[i] |
||||
} |
||||
groupMemberMap := make(map[string]*model_struct.LocalGroupMember) |
||||
for i, b := range groupMemberList { |
||||
groupMemberMap[b.UserID] = groupMemberList[i] |
||||
} |
||||
userMap := make(map[string]*api.PublicUser) |
||||
for _, info := range users { |
||||
userMap[info.UserID] = &api.PublicUser{ |
||||
UserID: info.UserID, |
||||
Nickname: info.Nickname, |
||||
FaceURL: info.FaceURL, |
||||
Ex: info.Ex, |
||||
CreateTime: info.CreateTime, |
||||
} |
||||
} |
||||
res := make([]*api.FullUserInfoWithCache, 0, len(users)) |
||||
for _, userID := range userIDs { |
||||
info, ok := userMap[userID] |
||||
if !ok { |
||||
continue |
||||
} |
||||
res = append(res, &api.FullUserInfoWithCache{ |
||||
PublicInfo: info, |
||||
FriendInfo: friendMap[userID], |
||||
BlackInfo: blackMap[userID], |
||||
GroupMemberInfo: groupMemberMap[userID], |
||||
}) |
||||
|
||||
// update single conversation
|
||||
|
||||
conversation, err := u.db.GetConversationByUserID(ctx, userID) |
||||
if err != nil { |
||||
log.ZWarn(ctx, "GetConversationByUserID failed", err, "userID", userID) |
||||
} else { |
||||
if _, ok := friendMap[userID]; ok { |
||||
continue |
||||
} |
||||
log.ZDebug(ctx, "GetConversationByUserID", "conversation", conversation) |
||||
if conversation.ShowName != info.Nickname || conversation.FaceURL != info.FaceURL { |
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{Action: constant.UpdateConFaceUrlAndNickName, |
||||
Args: common.SourceIDAndSessionType{SourceID: userID, SessionType: conversation.ConversationType, FaceURL: info.FaceURL, Nickname: info.Nickname}}, u.ch) |
||||
_ = common.TriggerCmdUpdateMessage(ctx, common.UpdateMessageNode{Action: constant.UpdateMsgFaceUrlAndNickName, |
||||
Args: common.UpdateMessageInfo{SessionType: conversation.ConversationType, UserID: userID, FaceURL: info.FaceURL, Nickname: info.Nickname}}, u.ch) |
||||
} |
||||
} |
||||
} |
||||
return res, nil |
||||
} |
@ -0,0 +1,97 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 group |
||||
|
||||
import ( |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
|
||||
"github.com/openimsdk/protocol/sdkws" |
||||
) |
||||
|
||||
func ServerGroupToLocalGroup(info *sdkws.GroupInfo) *model_struct.LocalGroup { |
||||
return &model_struct.LocalGroup{ |
||||
GroupID: info.GroupID, |
||||
GroupName: info.GroupName, |
||||
Notification: info.Notification, |
||||
Introduction: info.Introduction, |
||||
FaceURL: info.FaceURL, |
||||
CreateTime: info.CreateTime, |
||||
Status: info.Status, |
||||
CreatorUserID: info.CreatorUserID, |
||||
GroupType: info.GroupType, |
||||
OwnerUserID: info.OwnerUserID, |
||||
MemberCount: int32(info.MemberCount), |
||||
Ex: info.Ex, |
||||
NeedVerification: info.NeedVerification, |
||||
LookMemberInfo: info.LookMemberInfo, |
||||
ApplyMemberFriend: info.ApplyMemberFriend, |
||||
NotificationUpdateTime: info.NotificationUpdateTime, |
||||
NotificationUserID: info.NotificationUserID, |
||||
//AttachedInfo: info.AttachedInfo, // TODO
|
||||
} |
||||
} |
||||
|
||||
func ServerGroupMemberToLocalGroupMember(info *sdkws.GroupMemberFullInfo) *model_struct.LocalGroupMember { |
||||
return &model_struct.LocalGroupMember{ |
||||
GroupID: info.GroupID, |
||||
UserID: info.UserID, |
||||
Nickname: info.Nickname, |
||||
FaceURL: info.FaceURL, |
||||
RoleLevel: info.RoleLevel, |
||||
JoinTime: info.JoinTime, |
||||
JoinSource: info.JoinSource, |
||||
InviterUserID: info.InviterUserID, |
||||
MuteEndTime: info.MuteEndTime, |
||||
OperatorUserID: info.OperatorUserID, |
||||
Ex: info.Ex, |
||||
//AttachedInfo: info.AttachedInfo, // todo
|
||||
} |
||||
} |
||||
|
||||
func ServerGroupRequestToLocalGroupRequest(info *sdkws.GroupRequest) *model_struct.LocalGroupRequest { |
||||
return &model_struct.LocalGroupRequest{ |
||||
GroupID: info.GroupInfo.GroupID, |
||||
GroupName: info.GroupInfo.GroupName, |
||||
Notification: info.GroupInfo.Notification, |
||||
Introduction: info.GroupInfo.Introduction, |
||||
GroupFaceURL: info.GroupInfo.FaceURL, |
||||
CreateTime: info.GroupInfo.CreateTime, |
||||
Status: info.GroupInfo.Status, |
||||
CreatorUserID: info.GroupInfo.CreatorUserID, |
||||
GroupType: info.GroupInfo.GroupType, |
||||
OwnerUserID: info.GroupInfo.OwnerUserID, |
||||
MemberCount: int32(info.GroupInfo.MemberCount), |
||||
UserID: info.UserInfo.UserID, |
||||
Nickname: info.UserInfo.Nickname, |
||||
UserFaceURL: info.UserInfo.FaceURL, |
||||
//Gender: info.UserInfo.Gender,
|
||||
HandleResult: info.HandleResult, |
||||
ReqMsg: info.ReqMsg, |
||||
HandledMsg: info.HandleMsg, |
||||
ReqTime: info.ReqTime, |
||||
HandleUserID: info.HandleUserID, |
||||
HandledTime: info.HandleTime, |
||||
Ex: info.Ex, |
||||
//AttachedInfo: info.AttachedInfo,
|
||||
JoinSource: info.JoinSource, |
||||
InviterUserID: info.InviterUserID, |
||||
} |
||||
} |
||||
|
||||
func ServerGroupRequestToLocalAdminGroupRequest(info *sdkws.GroupRequest) *model_struct.LocalAdminGroupRequest { |
||||
return &model_struct.LocalAdminGroupRequest{ |
||||
LocalGroupRequest: *ServerGroupRequestToLocalGroupRequest(info), |
||||
} |
||||
} |
@ -0,0 +1,343 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 group |
||||
|
||||
import ( |
||||
"context" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/open_im_sdk_callback" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/common" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/page" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/syncer" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/protocol/group" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
) |
||||
|
||||
func NewGroup(loginUserID string, db db_interface.DataBase, |
||||
conversationCh chan common.Cmd2Value) *Group { |
||||
g := &Group{ |
||||
loginUserID: loginUserID, |
||||
db: db, |
||||
conversationCh: conversationCh, |
||||
} |
||||
g.initSyncer() |
||||
return g |
||||
} |
||||
|
||||
// //utils.GetCurrentTimestampByMill()
|
||||
type Group struct { |
||||
listener func() open_im_sdk_callback.OnGroupListener |
||||
loginUserID string |
||||
db db_interface.DataBase |
||||
groupSyncer *syncer.Syncer[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string] |
||||
groupMemberSyncer *syncer.Syncer[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string] |
||||
groupRequestSyncer *syncer.Syncer[*model_struct.LocalGroupRequest, syncer.NoResp, [2]string] |
||||
groupAdminRequestSyncer *syncer.Syncer[*model_struct.LocalAdminGroupRequest, syncer.NoResp, [2]string] |
||||
joinedSuperGroupCh chan common.Cmd2Value |
||||
heartbeatCmdCh chan common.Cmd2Value |
||||
|
||||
conversationCh chan common.Cmd2Value |
||||
// memberSyncMutex sync.RWMutex
|
||||
|
||||
listenerForService open_im_sdk_callback.OnListenerForService |
||||
} |
||||
|
||||
func (g *Group) initSyncer() { |
||||
g.groupSyncer = syncer.New2[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string]( |
||||
syncer.WithInsert[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, value *model_struct.LocalGroup) error { |
||||
return g.db.InsertGroup(ctx, value) |
||||
}), |
||||
syncer.WithDelete[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, value *model_struct.LocalGroup) error { |
||||
if err := g.db.DeleteGroupAllMembers(ctx, value.GroupID); err != nil { |
||||
return err |
||||
} |
||||
if err := g.db.DeleteVersionSync(ctx, g.groupAndMemberVersionTableName(), value.GroupID); err != nil { |
||||
return err |
||||
} |
||||
return g.db.DeleteGroup(ctx, value.GroupID) |
||||
}), |
||||
syncer.WithUpdate[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, server, local *model_struct.LocalGroup) error { |
||||
log.ZInfo(ctx, "groupSyncer trigger update function", "groupID", server.GroupID, "server", server, "local", local) |
||||
return g.db.UpdateGroup(ctx, server) |
||||
}), |
||||
syncer.WithUUID[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(value *model_struct.LocalGroup) string { |
||||
return value.GroupID |
||||
}), |
||||
syncer.WithNotice[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, state int, server, local *model_struct.LocalGroup) error { |
||||
switch state { |
||||
case syncer.Insert: |
||||
// when a user kicked to the group and invited to the group again, group info maybe updated,
|
||||
// so conversation info need to be updated
|
||||
g.listener().OnJoinedGroupAdded(utils.StructToJsonString(server)) |
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{ |
||||
Action: constant.UpdateConFaceUrlAndNickName, |
||||
Args: common.SourceIDAndSessionType{ |
||||
SourceID: server.GroupID, SessionType: constant.SuperGroupChatType, |
||||
FaceURL: server.FaceURL, Nickname: server.GroupName, |
||||
}, |
||||
}, g.conversationCh) |
||||
case syncer.Delete: |
||||
g.listener().OnJoinedGroupDeleted(utils.StructToJsonString(local)) |
||||
case syncer.Update: |
||||
log.ZInfo(ctx, "groupSyncer trigger update", "groupID", |
||||
server.GroupID, "data", server, "isDismissed", server.Status == constant.GroupStatusDismissed) |
||||
if server.Status == constant.GroupStatusDismissed { |
||||
if err := g.db.DeleteGroupAllMembers(ctx, server.GroupID); err != nil { |
||||
log.ZError(ctx, "delete group all members failed", err) |
||||
} |
||||
g.listener().OnGroupDismissed(utils.StructToJsonString(server)) |
||||
} else { |
||||
g.listener().OnGroupInfoChanged(utils.StructToJsonString(server)) |
||||
if server.GroupName != local.GroupName || local.FaceURL != server.FaceURL { |
||||
_ = common.TriggerCmdUpdateConversation(ctx, common.UpdateConNode{ |
||||
Action: constant.UpdateConFaceUrlAndNickName, |
||||
Args: common.SourceIDAndSessionType{ |
||||
SourceID: server.GroupID, SessionType: constant.SuperGroupChatType, |
||||
FaceURL: server.FaceURL, Nickname: server.GroupName, |
||||
}, |
||||
}, g.conversationCh) |
||||
} |
||||
} |
||||
} |
||||
return nil |
||||
}), |
||||
syncer.WithBatchInsert[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, values []*model_struct.LocalGroup) error { |
||||
return g.db.BatchInsertGroup(ctx, values) |
||||
}), |
||||
syncer.WithDeleteAll[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(ctx context.Context, _ string) error { |
||||
return g.db.DeleteAllGroup(ctx) |
||||
}), |
||||
syncer.WithBatchPageReq[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(entityID string) page.PageReq { |
||||
return &group.GetJoinedGroupListReq{FromUserID: entityID, |
||||
Pagination: &sdkws.RequestPagination{ShowNumber: 100}} |
||||
}), |
||||
syncer.WithBatchPageRespConvertFunc[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](func(resp *group.GetJoinedGroupListResp) []*model_struct.LocalGroup { |
||||
return datautil.Batch(ServerGroupToLocalGroup, resp.Groups) |
||||
}), |
||||
syncer.WithReqApiRouter[*model_struct.LocalGroup, group.GetJoinedGroupListResp, string](constant.GetJoinedGroupListRouter), |
||||
) |
||||
|
||||
g.groupMemberSyncer = syncer.New2[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string]( |
||||
syncer.WithInsert[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, value *model_struct.LocalGroupMember) error { |
||||
return g.db.InsertGroupMember(ctx, value) |
||||
}), |
||||
syncer.WithDelete[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, value *model_struct.LocalGroupMember) error { |
||||
return g.db.DeleteGroupMember(ctx, value.GroupID, value.UserID) |
||||
}), |
||||
syncer.WithUpdate[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, server, local *model_struct.LocalGroupMember) error { |
||||
return g.db.UpdateGroupMember(ctx, server) |
||||
}), |
||||
syncer.WithUUID[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(value *model_struct.LocalGroupMember) [2]string { |
||||
return [...]string{value.GroupID, value.UserID} |
||||
}), |
||||
syncer.WithNotice[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, state int, server, local *model_struct.LocalGroupMember) error { |
||||
switch state { |
||||
case syncer.Insert: |
||||
g.listener().OnGroupMemberAdded(utils.StructToJsonString(server)) |
||||
// When a user is kicked and invited to the group again, group member info will be updated.
|
||||
_ = common.TriggerCmdUpdateMessage(ctx, |
||||
common.UpdateMessageNode{ |
||||
Action: constant.UpdateMsgFaceUrlAndNickName, |
||||
Args: common.UpdateMessageInfo{ |
||||
SessionType: constant.SuperGroupChatType, UserID: server.UserID, FaceURL: server.FaceURL, |
||||
Nickname: server.Nickname, GroupID: server.GroupID, |
||||
}, |
||||
}, g.conversationCh) |
||||
case syncer.Delete: |
||||
g.listener().OnGroupMemberDeleted(utils.StructToJsonString(local)) |
||||
case syncer.Update: |
||||
g.listener().OnGroupMemberInfoChanged(utils.StructToJsonString(server)) |
||||
if server.Nickname != local.Nickname || server.FaceURL != local.FaceURL { |
||||
_ = common.TriggerCmdUpdateMessage(ctx, |
||||
common.UpdateMessageNode{ |
||||
Action: constant.UpdateMsgFaceUrlAndNickName, |
||||
Args: common.UpdateMessageInfo{ |
||||
SessionType: constant.SuperGroupChatType, UserID: server.UserID, FaceURL: server.FaceURL, |
||||
Nickname: server.Nickname, GroupID: server.GroupID, |
||||
}, |
||||
}, g.conversationCh) |
||||
} |
||||
} |
||||
return nil |
||||
}), |
||||
syncer.WithBatchInsert[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, values []*model_struct.LocalGroupMember) error { |
||||
return g.db.BatchInsertGroupMember(ctx, values) |
||||
}), |
||||
syncer.WithDeleteAll[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(ctx context.Context, groupID string) error { |
||||
return g.db.DeleteGroupAllMembers(ctx, groupID) |
||||
}), |
||||
syncer.WithBatchPageReq[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(entityID string) page.PageReq { |
||||
return &group.GetGroupMemberListReq{GroupID: entityID, Pagination: &sdkws.RequestPagination{ShowNumber: 100}} |
||||
}), |
||||
syncer.WithBatchPageRespConvertFunc[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](func(resp *group.GetGroupMemberListResp) []*model_struct.LocalGroupMember { |
||||
return datautil.Batch(ServerGroupMemberToLocalGroupMember, resp.Members) |
||||
}), |
||||
syncer.WithReqApiRouter[*model_struct.LocalGroupMember, group.GetGroupMemberListResp, [2]string](constant.GetGroupMemberListRouter), |
||||
) |
||||
|
||||
g.groupRequestSyncer = syncer.New[*model_struct.LocalGroupRequest, syncer.NoResp, [2]string](func(ctx context.Context, value *model_struct.LocalGroupRequest) error { |
||||
return g.db.InsertGroupRequest(ctx, value) |
||||
}, func(ctx context.Context, value *model_struct.LocalGroupRequest) error { |
||||
return g.db.DeleteGroupRequest(ctx, value.GroupID, value.UserID) |
||||
}, func(ctx context.Context, server, local *model_struct.LocalGroupRequest) error { |
||||
return g.db.UpdateGroupRequest(ctx, server) |
||||
}, func(value *model_struct.LocalGroupRequest) [2]string { |
||||
return [...]string{value.GroupID, value.UserID} |
||||
}, nil, func(ctx context.Context, state int, server, local *model_struct.LocalGroupRequest) error { |
||||
switch state { |
||||
case syncer.Insert: |
||||
g.listener().OnGroupApplicationAdded(utils.StructToJsonString(server)) |
||||
case syncer.Update: |
||||
switch server.HandleResult { |
||||
case constant.FriendResponseAgree: |
||||
g.listener().OnGroupApplicationAccepted(utils.StructToJsonString(server)) |
||||
case constant.FriendResponseRefuse: |
||||
g.listener().OnGroupApplicationRejected(utils.StructToJsonString(server)) |
||||
default: |
||||
g.listener().OnGroupApplicationAdded(utils.StructToJsonString(server)) |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
|
||||
g.groupAdminRequestSyncer = syncer.New[*model_struct.LocalAdminGroupRequest, syncer.NoResp, [2]string](func(ctx context.Context, value *model_struct.LocalAdminGroupRequest) error { |
||||
return g.db.InsertAdminGroupRequest(ctx, value) |
||||
}, func(ctx context.Context, value *model_struct.LocalAdminGroupRequest) error { |
||||
return g.db.DeleteAdminGroupRequest(ctx, value.GroupID, value.UserID) |
||||
}, func(ctx context.Context, server, local *model_struct.LocalAdminGroupRequest) error { |
||||
return g.db.UpdateAdminGroupRequest(ctx, server) |
||||
}, func(value *model_struct.LocalAdminGroupRequest) [2]string { |
||||
return [...]string{value.GroupID, value.UserID} |
||||
}, nil, func(ctx context.Context, state int, server, local *model_struct.LocalAdminGroupRequest) error { |
||||
switch state { |
||||
case syncer.Insert: |
||||
g.listener().OnGroupApplicationAdded(utils.StructToJsonString(server)) |
||||
case syncer.Update: |
||||
switch server.HandleResult { |
||||
case constant.FriendResponseAgree: |
||||
g.listener().OnGroupApplicationAccepted(utils.StructToJsonString(server)) |
||||
case constant.FriendResponseRefuse: |
||||
g.listener().OnGroupApplicationRejected(utils.StructToJsonString(server)) |
||||
default: |
||||
g.listener().OnGroupApplicationAdded(utils.StructToJsonString(server)) |
||||
} |
||||
} |
||||
return nil |
||||
}) |
||||
|
||||
} |
||||
|
||||
func (g *Group) SetGroupListener(listener func() open_im_sdk_callback.OnGroupListener) { |
||||
g.listener = listener |
||||
} |
||||
|
||||
func (g *Group) SetListenerForService(listener open_im_sdk_callback.OnListenerForService) { |
||||
g.listenerForService = listener |
||||
} |
||||
|
||||
func (g *Group) GetGroupOwnerIDAndAdminIDList(ctx context.Context, groupID string) (ownerID string, adminIDList []string, err error) { |
||||
localGroup, err := g.db.GetGroupInfoByGroupID(ctx, groupID) |
||||
if err != nil { |
||||
return "", nil, err |
||||
} |
||||
adminIDList, err = g.db.GetGroupAdminID(ctx, groupID) |
||||
if err != nil { |
||||
return "", nil, err |
||||
} |
||||
return localGroup.OwnerUserID, adminIDList, nil |
||||
} |
||||
|
||||
func (g *Group) GetGroupInfoFromLocal2Svr(ctx context.Context, groupID string) (*model_struct.LocalGroup, error) { |
||||
localGroup, err := g.db.GetGroupInfoByGroupID(ctx, groupID) |
||||
if err == nil { |
||||
return localGroup, nil |
||||
} |
||||
svrGroup, err := g.getGroupsInfoFromSvr(ctx, []string{groupID}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if len(svrGroup) == 0 { |
||||
return nil, sdkerrs.ErrGroupIDNotFound.WrapMsg("server not this group") |
||||
} |
||||
return ServerGroupToLocalGroup(svrGroup[0]), nil |
||||
} |
||||
|
||||
func (g *Group) GetGroupsInfoFromLocal2Svr(ctx context.Context, groupIDs ...string) (map[string]*model_struct.LocalGroup, error) { |
||||
groupMap := make(map[string]*model_struct.LocalGroup) |
||||
if len(groupIDs) == 0 { |
||||
return groupMap, nil |
||||
} |
||||
groups, err := g.db.GetGroups(ctx, groupIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var groupIDsNeedSync []string |
||||
localGroupIDs := datautil.Slice(groups, func(group *model_struct.LocalGroup) string { |
||||
return group.GroupID |
||||
}) |
||||
for _, groupID := range groupIDs { |
||||
if !datautil.Contain(groupID, localGroupIDs...) { |
||||
groupIDsNeedSync = append(groupIDsNeedSync, groupID) |
||||
} |
||||
} |
||||
|
||||
if len(groupIDsNeedSync) > 0 { |
||||
svrGroups, err := g.getGroupsInfoFromSvr(ctx, groupIDsNeedSync) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
for _, svrGroup := range svrGroups { |
||||
groups = append(groups, ServerGroupToLocalGroup(svrGroup)) |
||||
} |
||||
} |
||||
for _, group := range groups { |
||||
groupMap[group.GroupID] = group |
||||
} |
||||
return groupMap, nil |
||||
} |
||||
|
||||
func (g *Group) getGroupsInfoFromSvr(ctx context.Context, groupIDs []string) ([]*sdkws.GroupInfo, error) { |
||||
resp, err := util.CallApi[group.GetGroupsInfoResp](ctx, constant.GetGroupsInfoRouter, &group.GetGroupsInfoReq{GroupIDs: groupIDs}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return resp.GroupInfos, nil |
||||
} |
||||
|
||||
func (g *Group) getGroupAbstractInfoFromSvr(ctx context.Context, groupIDs []string) (*group.GetGroupAbstractInfoResp, error) { |
||||
return util.CallApi[group.GetGroupAbstractInfoResp](ctx, constant.GetGroupAbstractInfoRouter, &group.GetGroupAbstractInfoReq{GroupIDs: groupIDs}) |
||||
} |
||||
|
||||
func (g *Group) GetJoinedDiffusionGroupIDListFromSvr(ctx context.Context) ([]string, error) { |
||||
groups, err := g.GetServerJoinGroup(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
var groupIDs []string |
||||
for _, g := range groups { |
||||
if g.GroupType == constant.WorkingGroup { |
||||
groupIDs = append(groupIDs, g.GroupID) |
||||
} |
||||
} |
||||
return groupIDs, nil |
||||
} |
@ -0,0 +1,245 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 group |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"github.com/openimsdk/tools/errs" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
|
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
) |
||||
|
||||
func (g *Group) DoNotification(ctx context.Context, msg *sdkws.MsgData) { |
||||
go func() { |
||||
if err := g.doNotification(ctx, msg); err != nil { |
||||
log.ZError(ctx, "DoGroupNotification failed", err) |
||||
} |
||||
}() |
||||
} |
||||
|
||||
func (g *Group) doNotification(ctx context.Context, msg *sdkws.MsgData) error { |
||||
switch msg.ContentType { |
||||
case constant.GroupCreatedNotification: // 1501
|
||||
var detail sdkws.GroupCreatedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err := g.IncrSyncJoinGroup(ctx); err != nil { |
||||
return err |
||||
} |
||||
return g.IncrSyncGroupAndMember(ctx, detail.Group.GroupID) |
||||
|
||||
case constant.GroupInfoSetNotification: // 1502
|
||||
var detail sdkws.GroupInfoSetTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, |
||||
nil, nil, detail.Group, detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
case constant.JoinGroupApplicationNotification: // 1503
|
||||
var detail sdkws.JoinGroupApplicationTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
if detail.Applicant.UserID == g.loginUserID { |
||||
return g.SyncSelfGroupApplications(ctx, detail.Group.GroupID) |
||||
} else { |
||||
return g.SyncAdminGroupApplications(ctx, detail.Group.GroupID) |
||||
} |
||||
case constant.MemberQuitNotification: // 1504
|
||||
var detail sdkws.MemberQuitTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
if detail.QuitUser.UserID == g.loginUserID { |
||||
return g.IncrSyncJoinGroup(ctx) |
||||
} else { |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, []*sdkws.GroupMemberFullInfo{detail.QuitUser}, |
||||
nil, nil, detail.Group, detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
} |
||||
case constant.GroupApplicationAcceptedNotification: // 1505
|
||||
var detail sdkws.GroupApplicationAcceptedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
switch detail.ReceiverAs { |
||||
case 0: |
||||
return g.SyncAllSelfGroupApplication(ctx) |
||||
case 1: |
||||
return g.SyncAdminGroupApplications(ctx, detail.Group.GroupID) |
||||
default: |
||||
return errs.New(fmt.Sprintf("GroupApplicationAcceptedNotification ReceiverAs unknown %d", detail.ReceiverAs)).Wrap() |
||||
} |
||||
case constant.GroupApplicationRejectedNotification: // 1506
|
||||
var detail sdkws.GroupApplicationRejectedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
switch detail.ReceiverAs { |
||||
case 0: |
||||
return g.SyncAllSelfGroupApplication(ctx) |
||||
case 1: |
||||
return g.SyncAdminGroupApplications(ctx, detail.Group.GroupID) |
||||
default: |
||||
return errs.New(fmt.Sprintf("GroupApplicationRejectedNotification ReceiverAs unknown %d", detail.ReceiverAs)).Wrap() |
||||
} |
||||
case constant.GroupOwnerTransferredNotification: // 1507
|
||||
var detail sdkws.GroupOwnerTransferredTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
if detail.Group == nil { |
||||
return errs.New(fmt.Sprintf("group is nil, groupID: %s", detail.Group.GroupID)).Wrap() |
||||
} |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, |
||||
[]*sdkws.GroupMemberFullInfo{detail.NewGroupOwner, detail.OldGroupOwnerInfo}, nil, |
||||
detail.Group, detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
case constant.MemberKickedNotification: // 1508
|
||||
var detail sdkws.MemberKickedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
var self bool |
||||
for _, info := range detail.KickedUserList { |
||||
if info.UserID == g.loginUserID { |
||||
self = true |
||||
break |
||||
} |
||||
} |
||||
if self { |
||||
return g.IncrSyncJoinGroup(ctx) |
||||
} else { |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, detail.KickedUserList, nil, |
||||
nil, detail.Group, detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
} |
||||
case constant.MemberInvitedNotification: // 1509
|
||||
var detail sdkws.MemberInvitedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
userIDMap := datautil.SliceSetAny(detail.InvitedUserList, func(e *sdkws.GroupMemberFullInfo) string { |
||||
return e.UserID |
||||
}) |
||||
//自己也是被邀请的一员
|
||||
if _, ok := userIDMap[g.loginUserID]; ok { |
||||
if err := g.IncrSyncJoinGroup(ctx); err != nil { |
||||
return err |
||||
} |
||||
return g.IncrSyncGroupAndMember(ctx, detail.Group.GroupID) |
||||
} else { |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, nil, |
||||
detail.InvitedUserList, detail.Group, detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
} |
||||
case constant.MemberEnterNotification: // 1510
|
||||
var detail sdkws.MemberEnterTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
if detail.EntrantUser.UserID == g.loginUserID { |
||||
if err := g.IncrSyncJoinGroup(ctx); err != nil { |
||||
return err |
||||
} |
||||
return g.IncrSyncGroupAndMember(ctx, detail.Group.GroupID) |
||||
} else { |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, nil, |
||||
[]*sdkws.GroupMemberFullInfo{detail.EntrantUser}, detail.Group, detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
} |
||||
case constant.GroupDismissedNotification: // 1511
|
||||
var detail sdkws.GroupDismissedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
g.listener().OnGroupDismissed(utils.StructToJsonString(detail.Group)) |
||||
|
||||
return g.IncrSyncJoinGroup(ctx) |
||||
case constant.GroupMemberMutedNotification: // 1512
|
||||
var detail sdkws.GroupMemberMutedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, |
||||
[]*sdkws.GroupMemberFullInfo{detail.MutedUser}, nil, nil, |
||||
detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
case constant.GroupMemberCancelMutedNotification: // 1513
|
||||
var detail sdkws.GroupMemberCancelMutedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, |
||||
[]*sdkws.GroupMemberFullInfo{detail.MutedUser}, nil, nil, |
||||
detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
case constant.GroupMutedNotification: // 1514
|
||||
var detail sdkws.GroupMutedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, nil, |
||||
nil, detail.Group, detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
case constant.GroupCancelMutedNotification: // 1515
|
||||
var detail sdkws.GroupCancelMutedTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, nil, |
||||
nil, detail.Group, detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
case constant.GroupMemberInfoSetNotification: // 1516
|
||||
var detail sdkws.GroupMemberInfoSetTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
|
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, |
||||
[]*sdkws.GroupMemberFullInfo{detail.ChangedUser}, nil, nil, |
||||
detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
case constant.GroupMemberSetToAdminNotification: // 1517
|
||||
var detail sdkws.GroupMemberInfoSetTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, |
||||
[]*sdkws.GroupMemberFullInfo{detail.ChangedUser}, nil, nil, |
||||
detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
case constant.GroupMemberSetToOrdinaryUserNotification: // 1518
|
||||
var detail sdkws.GroupMemberInfoSetTips |
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, |
||||
[]*sdkws.GroupMemberFullInfo{detail.ChangedUser}, nil, nil, |
||||
detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
case constant.GroupInfoSetAnnouncementNotification: // 1519
|
||||
var detail sdkws.GroupInfoSetAnnouncementTips //
|
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, nil, |
||||
nil, detail.Group, detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
case constant.GroupInfoSetNameNotification: // 1520
|
||||
var detail sdkws.GroupInfoSetNameTips //
|
||||
if err := utils.UnmarshalNotificationElem(msg.Content, &detail); err != nil { |
||||
return err |
||||
} |
||||
return g.onlineSyncGroupAndMember(ctx, detail.Group.GroupID, nil, |
||||
nil, nil, detail.Group, detail.GroupMemberVersion, detail.GroupMemberVersionID) |
||||
default: |
||||
return errs.New("unknown tips type", "contentType", msg.ContentType).Wrap() |
||||
} |
||||
} |
@ -0,0 +1,383 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 group |
||||
|
||||
import ( |
||||
"context" |
||||
"time" |
||||
|
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/datafetcher" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdk_params_callback" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/sdkerrs" |
||||
|
||||
"github.com/openimsdk/protocol/group" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/protocol/wrapperspb" |
||||
) |
||||
|
||||
func (g *Group) CreateGroup(ctx context.Context, req *group.CreateGroupReq) (*sdkws.GroupInfo, error) { |
||||
if req.OwnerUserID == "" { |
||||
req.OwnerUserID = g.loginUserID |
||||
} |
||||
if req.GroupInfo.GroupType != constant.WorkingGroup { |
||||
return nil, sdkerrs.ErrGroupType |
||||
} |
||||
req.GroupInfo.CreatorUserID = g.loginUserID |
||||
resp, err := util.CallApi[group.CreateGroupResp](ctx, constant.CreateGroupRouter, req) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if err := g.IncrSyncJoinGroup(ctx); err != nil { |
||||
return nil, err |
||||
} |
||||
if err := g.IncrSyncGroupAndMember(ctx, resp.GroupInfo.GroupID); err != nil { |
||||
return nil, err |
||||
} |
||||
return resp.GroupInfo, nil |
||||
} |
||||
|
||||
func (g *Group) JoinGroup(ctx context.Context, groupID, reqMsg string, joinSource int32, ex string) error { |
||||
if err := util.ApiPost(ctx, constant.JoinGroupRouter, &group.JoinGroupReq{GroupID: groupID, ReqMessage: reqMsg, JoinSource: joinSource, InviterUserID: g.loginUserID, Ex: ex}, nil); err != nil { |
||||
return err |
||||
} |
||||
if err := g.SyncSelfGroupApplications(ctx, groupID); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (g *Group) QuitGroup(ctx context.Context, groupID string) error { |
||||
if err := util.ApiPost(ctx, constant.QuitGroupRouter, &group.QuitGroupReq{GroupID: groupID}, nil); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (g *Group) DismissGroup(ctx context.Context, groupID string) error { |
||||
if err := util.ApiPost(ctx, constant.DismissGroupRouter, &group.DismissGroupReq{GroupID: groupID}, nil); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (g *Group) SetGroupApplyMemberFriend(ctx context.Context, groupID string, rule int32) error { |
||||
return g.SetGroupInfo(ctx, &sdkws.GroupInfoForSet{GroupID: groupID, ApplyMemberFriend: wrapperspb.Int32(rule)}) |
||||
} |
||||
|
||||
func (g *Group) SetGroupLookMemberInfo(ctx context.Context, groupID string, rule int32) error { |
||||
return g.SetGroupInfo(ctx, &sdkws.GroupInfoForSet{GroupID: groupID, LookMemberInfo: wrapperspb.Int32(rule)}) |
||||
} |
||||
|
||||
func (g *Group) SetGroupVerification(ctx context.Context, groupID string, verification int32) error { |
||||
return g.SetGroupInfo(ctx, &sdkws.GroupInfoForSet{GroupID: groupID, NeedVerification: wrapperspb.Int32(verification)}) |
||||
} |
||||
|
||||
func (g *Group) ChangeGroupMute(ctx context.Context, groupID string, isMute bool) (err error) { |
||||
if isMute { |
||||
err = util.ApiPost(ctx, constant.MuteGroupRouter, &group.MuteGroupReq{GroupID: groupID}, nil) |
||||
} else { |
||||
err = util.ApiPost(ctx, constant.CancelMuteGroupRouter, &group.CancelMuteGroupReq{GroupID: groupID}, nil) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := g.IncrSyncGroupAndMember(ctx, groupID); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (g *Group) ChangeGroupMemberMute(ctx context.Context, groupID, userID string, mutedSeconds int) (err error) { |
||||
if mutedSeconds == 0 { |
||||
err = util.ApiPost(ctx, constant.CancelMuteGroupMemberRouter, &group.CancelMuteGroupMemberReq{GroupID: groupID, UserID: userID}, nil) |
||||
} else { |
||||
err = util.ApiPost(ctx, constant.MuteGroupMemberRouter, &group.MuteGroupMemberReq{GroupID: groupID, UserID: userID, MutedSeconds: uint32(mutedSeconds)}, nil) |
||||
} |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (g *Group) TransferGroupOwner(ctx context.Context, groupID, newOwnerUserID string) error { |
||||
if err := util.ApiPost(ctx, constant.TransferGroupRouter, &group.TransferGroupOwnerReq{GroupID: groupID, OldOwnerUserID: g.loginUserID, NewOwnerUserID: newOwnerUserID}, nil); err != nil { |
||||
return err |
||||
} |
||||
if err := g.IncrSyncGroupAndMember(ctx, groupID); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (g *Group) KickGroupMember(ctx context.Context, groupID string, reason string, userIDList []string) error { |
||||
if err := util.ApiPost(ctx, constant.KickGroupMemberRouter, &group.KickGroupMemberReq{GroupID: groupID, KickedUserIDs: userIDList, Reason: reason}, nil); err != nil { |
||||
return err |
||||
} |
||||
return g.IncrSyncGroupAndMember(ctx, groupID) |
||||
} |
||||
|
||||
func (g *Group) SetGroupInfo(ctx context.Context, groupInfo *sdkws.GroupInfoForSet) error { |
||||
if err := util.ApiPost(ctx, constant.SetGroupInfoRouter, &group.SetGroupInfoReq{GroupInfoForSet: groupInfo}, nil); err != nil { |
||||
return err |
||||
} |
||||
return g.IncrSyncJoinGroup(ctx) |
||||
} |
||||
|
||||
func (g *Group) SetGroupMemberInfo(ctx context.Context, groupMemberInfo *group.SetGroupMemberInfo) error { |
||||
if err := util.ApiPost(ctx, constant.SetGroupMemberInfoRouter, &group.SetGroupMemberInfoReq{Members: []*group.SetGroupMemberInfo{groupMemberInfo}}, nil); err != nil { |
||||
return err |
||||
} |
||||
return g.IncrSyncGroupAndMember(ctx, groupMemberInfo.GroupID) |
||||
} |
||||
|
||||
func (g *Group) SetGroupMemberRoleLevel(ctx context.Context, groupID, userID string, roleLevel int) error { |
||||
return g.SetGroupMemberInfo(ctx, &group.SetGroupMemberInfo{GroupID: groupID, UserID: userID, RoleLevel: wrapperspb.Int32(int32(roleLevel))}) |
||||
} |
||||
|
||||
func (g *Group) SetGroupMemberNickname(ctx context.Context, groupID, userID string, groupMemberNickname string) error { |
||||
return g.SetGroupMemberInfo(ctx, &group.SetGroupMemberInfo{GroupID: groupID, UserID: userID, Nickname: wrapperspb.String(groupMemberNickname)}) |
||||
} |
||||
|
||||
func (g *Group) GetJoinedGroupList(ctx context.Context) ([]*model_struct.LocalGroup, error) { |
||||
return g.db.GetJoinedGroupListDB(ctx) |
||||
} |
||||
|
||||
func (g *Group) GetJoinedGroupListPage(ctx context.Context, offset, count int32) ([]*model_struct.LocalGroup, error) { |
||||
dataFetcher := datafetcher.NewDataFetcher( |
||||
g.db, |
||||
g.groupTableName(), |
||||
g.loginUserID, |
||||
func(localGroup *model_struct.LocalGroup) string { |
||||
return localGroup.GroupID |
||||
}, |
||||
func(ctx context.Context, values []*model_struct.LocalGroup) error { |
||||
return g.db.BatchInsertGroup(ctx, values) |
||||
}, |
||||
func(ctx context.Context, groupIDs []string) ([]*model_struct.LocalGroup, error) { |
||||
return g.db.GetGroups(ctx, groupIDs) |
||||
}, |
||||
func(ctx context.Context, groupIDs []string) ([]*model_struct.LocalGroup, error) { |
||||
serverGroupInfo, err := g.getGroupsInfoFromSvr(ctx, groupIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return datautil.Batch(ServerGroupToLocalGroup, serverGroupInfo), nil |
||||
}, |
||||
) |
||||
return dataFetcher.FetchWithPagination(ctx, int(offset), int(count)) |
||||
} |
||||
|
||||
func (g *Group) GetSpecifiedGroupsInfo(ctx context.Context, groupIDs []string) ([]*model_struct.LocalGroup, error) { |
||||
dataFetcher := datafetcher.NewDataFetcher( |
||||
g.db, |
||||
g.groupTableName(), |
||||
g.loginUserID, |
||||
func(localGroup *model_struct.LocalGroup) string { |
||||
return localGroup.GroupID |
||||
}, |
||||
func(ctx context.Context, values []*model_struct.LocalGroup) error { |
||||
return g.db.BatchInsertGroup(ctx, values) |
||||
}, |
||||
func(ctx context.Context, groupIDs []string) ([]*model_struct.LocalGroup, error) { |
||||
return g.db.GetGroups(ctx, groupIDs) |
||||
}, |
||||
func(ctx context.Context, groupIDs []string) ([]*model_struct.LocalGroup, error) { |
||||
serverGroupInfo, err := g.getGroupsInfoFromSvr(ctx, groupIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return datautil.Batch(ServerGroupToLocalGroup, serverGroupInfo), nil |
||||
}, |
||||
) |
||||
return dataFetcher.FetchMissingAndFillLocal(ctx, groupIDs) |
||||
} |
||||
|
||||
func (g *Group) SearchGroups(ctx context.Context, param sdk_params_callback.SearchGroupsParam) ([]*model_struct.LocalGroup, error) { |
||||
if len(param.KeywordList) == 0 || (!param.IsSearchGroupName && !param.IsSearchGroupID) { |
||||
return nil, sdkerrs.ErrArgs.WrapMsg("keyword is null or search field all false") |
||||
} |
||||
groups, err := g.db.GetAllGroupInfoByGroupIDOrGroupName(ctx, param.KeywordList[0], param.IsSearchGroupID, param.IsSearchGroupName) // todo param.KeywordList[0]
|
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return groups, nil |
||||
} |
||||
|
||||
// funcation (g *Group) SetGroupInfo(ctx context.Context, groupInfo *sdk_params_callback.SetGroupInfoParam, groupID string) error {
|
||||
// return g.SetGroupInfo(ctx, &sdkws.GroupInfoForSet{
|
||||
// GroupID: groupID,
|
||||
// GroupName: groupInfo.GroupName,
|
||||
// Notification: groupInfo.Notification,
|
||||
// Introduction: groupInfo.Introduction,
|
||||
// FaceURL: groupInfo.FaceURL,
|
||||
// Ex: groupInfo.Ex,
|
||||
// NeedVerification: wrapperspb.Int32Ptr(groupInfo.NeedVerification),
|
||||
// })
|
||||
// }
|
||||
|
||||
func (g *Group) GetGroupMemberOwnerAndAdmin(ctx context.Context, groupID string) ([]*model_struct.LocalGroupMember, error) { |
||||
return g.db.GetGroupMemberOwnerAndAdminDB(ctx, groupID) |
||||
} |
||||
|
||||
func (g *Group) GetGroupMemberListByJoinTimeFilter(ctx context.Context, groupID string, offset, count int32, joinTimeBegin, joinTimeEnd int64, userIDs []string) ([]*model_struct.LocalGroupMember, error) { |
||||
if joinTimeEnd == 0 { |
||||
joinTimeEnd = time.Now().UnixMilli() |
||||
} |
||||
return g.db.GetGroupMemberListSplitByJoinTimeFilter(ctx, groupID, int(offset), int(count), joinTimeBegin, joinTimeEnd, userIDs) |
||||
} |
||||
|
||||
func (g *Group) GetSpecifiedGroupMembersInfo(ctx context.Context, groupID string, userIDList []string) ([]*model_struct.LocalGroupMember, error) { |
||||
dataFetcher := datafetcher.NewDataFetcher( |
||||
g.db, |
||||
g.groupAndMemberVersionTableName(), |
||||
groupID, |
||||
func(localGroupMember *model_struct.LocalGroupMember) string { |
||||
return localGroupMember.UserID |
||||
}, |
||||
func(ctx context.Context, values []*model_struct.LocalGroupMember) error { |
||||
return g.db.BatchInsertGroupMember(ctx, values) |
||||
}, |
||||
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalGroupMember, error) { |
||||
return g.db.GetGroupSomeMemberInfo(ctx, groupID, userIDList) |
||||
}, |
||||
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalGroupMember, error) { |
||||
serverGroupMember, err := g.GetDesignatedGroupMembers(ctx, groupID, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return datautil.Batch(ServerGroupMemberToLocalGroupMember, serverGroupMember), nil |
||||
}, |
||||
) |
||||
return dataFetcher.FetchMissingAndFillLocal(ctx, userIDList) |
||||
// return g.db.GetGroupSomeMemberInfo(ctx, groupID, userIDList)
|
||||
} |
||||
|
||||
func (g *Group) GetGroupMemberList(ctx context.Context, groupID string, filter, offset, count int32) ([]*model_struct.LocalGroupMember, error) { |
||||
dataFetcher := datafetcher.NewDataFetcher( |
||||
g.db, |
||||
g.groupAndMemberVersionTableName(), |
||||
groupID, |
||||
func(localGroupMember *model_struct.LocalGroupMember) string { |
||||
return localGroupMember.UserID |
||||
}, |
||||
func(ctx context.Context, values []*model_struct.LocalGroupMember) error { |
||||
return g.db.BatchInsertGroupMember(ctx, values) |
||||
}, |
||||
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalGroupMember, error) { |
||||
return g.db.GetGroupMemberListByUserIDs(ctx, groupID, filter, userIDs) |
||||
}, |
||||
func(ctx context.Context, userIDs []string) ([]*model_struct.LocalGroupMember, error) { |
||||
serverGroupMember, err := g.GetDesignatedGroupMembers(ctx, groupID, userIDs) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return datautil.Batch(ServerGroupMemberToLocalGroupMember, serverGroupMember), nil |
||||
}, |
||||
) |
||||
return dataFetcher.FetchWithPagination(ctx, int(offset), int(count)) |
||||
} |
||||
|
||||
func (g *Group) GetGroupApplicationListAsRecipient(ctx context.Context) ([]*model_struct.LocalAdminGroupRequest, error) { |
||||
return g.db.GetAdminGroupApplication(ctx) |
||||
} |
||||
|
||||
func (g *Group) GetGroupApplicationListAsApplicant(ctx context.Context) ([]*model_struct.LocalGroupRequest, error) { |
||||
return g.db.GetSendGroupApplication(ctx) |
||||
} |
||||
|
||||
func (g *Group) SearchGroupMembers(ctx context.Context, searchParam *sdk_params_callback.SearchGroupMembersParam) ([]*model_struct.LocalGroupMember, error) { |
||||
return g.db.SearchGroupMembersDB(ctx, searchParam.KeywordList[0], searchParam.GroupID, searchParam.IsSearchMemberNickname, searchParam.IsSearchUserID, searchParam.Offset, searchParam.Count) |
||||
} |
||||
|
||||
func (g *Group) IsJoinGroup(ctx context.Context, groupID string) (bool, error) { |
||||
groupList, err := g.db.GetJoinedGroupListDB(ctx) |
||||
if err != nil { |
||||
return false, err |
||||
} |
||||
for _, localGroup := range groupList { |
||||
if localGroup.GroupID == groupID { |
||||
return true, nil |
||||
} |
||||
} |
||||
return false, nil |
||||
} |
||||
func (g *Group) InviteUserToGroup(ctx context.Context, groupID, reason string, userIDList []string) error { |
||||
if err := util.ApiPost(ctx, constant.InviteUserToGroupRouter, &group.InviteUserToGroupReq{GroupID: groupID, Reason: reason, InvitedUserIDs: userIDList}, nil); err != nil { |
||||
return err |
||||
} |
||||
|
||||
if err := g.IncrSyncGroupAndMember(ctx, groupID); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (g *Group) AcceptGroupApplication(ctx context.Context, groupID, fromUserID, handleMsg string) error { |
||||
return g.HandlerGroupApplication(ctx, &group.GroupApplicationResponseReq{GroupID: groupID, FromUserID: fromUserID, HandledMsg: handleMsg, HandleResult: constant.GroupResponseAgree}) |
||||
} |
||||
|
||||
func (g *Group) RefuseGroupApplication(ctx context.Context, groupID, fromUserID, handleMsg string) error { |
||||
return g.HandlerGroupApplication(ctx, &group.GroupApplicationResponseReq{GroupID: groupID, FromUserID: fromUserID, HandledMsg: handleMsg, HandleResult: constant.GroupResponseRefuse}) |
||||
} |
||||
|
||||
func (g *Group) HandlerGroupApplication(ctx context.Context, req *group.GroupApplicationResponseReq) error { |
||||
if err := util.ApiPost(ctx, constant.AcceptGroupApplicationRouter, req, nil); err != nil { |
||||
return err |
||||
} |
||||
// SyncAdminGroupApplication todo
|
||||
return nil |
||||
} |
||||
|
||||
//func (g *Group) SearchGroupMembersV2(ctx context.Context, req *group.SearchGroupMemberReq) ([]*model_struct.LocalGroupMember, error) {
|
||||
// if err := req.Check(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// info, err := g.db.GetGroupInfoByGroupID(ctx, req.GroupID)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if info.MemberCount <= pconstant.MaxSyncPullNumber {
|
||||
// return g.db.SearchGroupMembersDB(ctx, req.Keyword, req.GroupID, true, false,
|
||||
// int((req.Pagination.PageNumber-1)*req.Pagination.ShowNumber), int(req.Pagination.ShowNumber))
|
||||
// }
|
||||
// resp, err := util.CallApi[group.SearchGroupMemberResp](ctx, constant.SearchGroupMember, req)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return datautil.Slice(resp.Members, g.pbGroupMemberToLocal), nil
|
||||
//}
|
||||
|
||||
func (g *Group) pbGroupMemberToLocal(pb *sdkws.GroupMemberFullInfo) *model_struct.LocalGroupMember { |
||||
return &model_struct.LocalGroupMember{ |
||||
GroupID: pb.GroupID, |
||||
UserID: pb.UserID, |
||||
Nickname: pb.Nickname, |
||||
FaceURL: pb.FaceURL, |
||||
RoleLevel: pb.RoleLevel, |
||||
JoinTime: pb.JoinTime, |
||||
JoinSource: pb.JoinSource, |
||||
InviterUserID: pb.InviterUserID, |
||||
MuteEndTime: pb.MuteEndTime, |
||||
OperatorUserID: pb.OperatorUserID, |
||||
Ex: pb.Ex, |
||||
// AttachedInfo: pb.AttachedInfo,
|
||||
} |
||||
} |
@ -0,0 +1,310 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 group |
||||
|
||||
import ( |
||||
"context" |
||||
"crypto/md5" |
||||
"encoding/binary" |
||||
"encoding/json" |
||||
"errors" |
||||
"fmt" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/protocol/group" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/log" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
"time" |
||||
) |
||||
|
||||
func (g *Group) getGroupHash(members []*model_struct.LocalGroupMember) uint64 { |
||||
userIDs := datautil.Slice(members, func(member *model_struct.LocalGroupMember) string { |
||||
return member.UserID |
||||
}) |
||||
datautil.Sort(userIDs, true) |
||||
memberMap := make(map[string]*sdkws.GroupMemberFullInfo) |
||||
for _, member := range members { |
||||
memberMap[member.UserID] = &sdkws.GroupMemberFullInfo{ |
||||
GroupID: member.GroupID, |
||||
UserID: member.UserID, |
||||
RoleLevel: member.RoleLevel, |
||||
JoinTime: member.JoinTime, |
||||
Nickname: member.Nickname, |
||||
FaceURL: member.FaceURL, |
||||
AppMangerLevel: 0, |
||||
JoinSource: member.JoinSource, |
||||
OperatorUserID: member.OperatorUserID, |
||||
Ex: member.Ex, |
||||
MuteEndTime: member.MuteEndTime, |
||||
InviterUserID: member.InviterUserID, |
||||
} |
||||
} |
||||
res := make([]*sdkws.GroupMemberFullInfo, 0, len(members)) |
||||
for _, userID := range userIDs { |
||||
res = append(res, memberMap[userID]) |
||||
} |
||||
val, _ := json.Marshal(res) |
||||
sum := md5.Sum(val) |
||||
return binary.BigEndian.Uint64(sum[:]) |
||||
} |
||||
|
||||
func (g *Group) SyncAllGroupMember(ctx context.Context, groupID string) error { |
||||
absInfo, err := g.GetGroupAbstractInfo(ctx, groupID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
localData, err := g.db.GetGroupMemberListSplit(ctx, groupID, 0, 0, 9999999) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
hashCode := g.getGroupHash(localData) |
||||
if len(localData) == int(absInfo.GroupMemberNumber) && hashCode == absInfo.GroupMemberListHash { |
||||
log.ZDebug(ctx, "SyncAllGroupMember no change in personnel", "groupID", groupID, "hashCode", hashCode, "absInfo.GroupMemberListHash", absInfo.GroupMemberListHash) |
||||
return nil |
||||
} |
||||
members, err := g.GetServerGroupMembers(ctx, groupID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return g.syncGroupMembers(ctx, groupID, members, localData) |
||||
} |
||||
|
||||
func (g *Group) SyncAllGroupMember2(ctx context.Context, groupID string) error { |
||||
return g.IncrSyncGroupAndMember(ctx, groupID) |
||||
} |
||||
|
||||
func (g *Group) syncGroupMembers(ctx context.Context, groupID string, members []*sdkws.GroupMemberFullInfo, localData []*model_struct.LocalGroupMember) error { |
||||
log.ZInfo(ctx, "SyncGroupMember Info", "groupID", groupID, "members", len(members), "localData", len(localData)) |
||||
err := g.groupMemberSyncer.Sync(ctx, datautil.Batch(ServerGroupMemberToLocalGroupMember, members), localData, nil) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
//if len(members) != len(localData) {
|
||||
log.ZInfo(ctx, "SyncGroupMember Sync Group Member Count", "groupID", groupID, "members", len(members), "localData", len(localData)) |
||||
gs, err := g.GetSpecifiedGroupsInfo(ctx, []string{groupID}) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.ZInfo(ctx, "SyncGroupMember GetGroupsInfo", "groupID", groupID, "len", len(gs), "gs", gs) |
||||
if len(gs) > 0 { |
||||
v := gs[0] |
||||
count, err := g.db.GetGroupMemberCount(ctx, groupID) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if v.MemberCount != count { |
||||
v.MemberCount = count |
||||
if v.GroupType == constant.SuperGroupChatType { |
||||
if err := g.db.UpdateSuperGroup(ctx, v); err != nil { |
||||
//return err
|
||||
log.ZError(ctx, "SyncGroupMember UpdateSuperGroup", err, "groupID", groupID, "info", v) |
||||
} |
||||
} else { |
||||
if err := g.db.UpdateGroup(ctx, v); err != nil { |
||||
log.ZError(ctx, "SyncGroupMember UpdateGroup", err, "groupID", groupID, "info", v) |
||||
} |
||||
} |
||||
data, err := json.Marshal(v) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.ZInfo(ctx, "SyncGroupMember OnGroupInfoChanged", "groupID", groupID, "data", string(data)) |
||||
g.listener().OnGroupInfoChanged(string(data)) |
||||
} |
||||
} |
||||
//}
|
||||
return nil |
||||
} |
||||
|
||||
func (g *Group) SyncGroupMembers(ctx context.Context, groupID string, userIDs ...string) error { |
||||
return g.IncrSyncGroupAndMember(ctx, groupID) |
||||
//members, err := g.GetDesignatedGroupMembers(ctx, groupID, userIDs)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//localData, err := g.db.GetGroupSomeMemberInfo(ctx, groupID, userIDs)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//return g.syncGroupMembers(ctx, groupID, members, localData)
|
||||
} |
||||
|
||||
func (g *Group) SyncGroups(ctx context.Context, groupIDs ...string) error { |
||||
return g.IncrSyncJoinGroup(ctx) |
||||
//groups, err := g.getGroupsInfoFromSvr(ctx, groupIDs)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//localData, err := g.db.GetGroups(ctx, groupIDs)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//if err := g.groupSyncer.Sync(ctx, util.Batch(ServerGroupToLocalGroup, groups), localData, nil); err != nil {
|
||||
// return err
|
||||
//}
|
||||
//return nil
|
||||
} |
||||
|
||||
func (g *Group) deleteGroup(ctx context.Context, groupID string) error { |
||||
return g.IncrSyncJoinGroup(ctx) |
||||
//groupInfo, err := g.db.GetGroupInfoByGroupID(ctx, groupID)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//if err := g.db.DeleteGroup(ctx, groupID); err != nil {
|
||||
// return err
|
||||
//}
|
||||
//g.listener().OnJoinedGroupDeleted(utils.StructToJsonString(groupInfo))
|
||||
//return nil
|
||||
} |
||||
|
||||
// func (g *Group) SyncAllJoinedGroupsAndMembers(ctx context.Context) error {
|
||||
// t := time.Now()
|
||||
// defer func(start time.Time) {
|
||||
//
|
||||
// elapsed := time.Since(start).Milliseconds()
|
||||
// log.ZDebug(ctx, "SyncAllJoinedGroupsAndMembers fn call end", "cost time", fmt.Sprintf("%d ms", elapsed))
|
||||
//
|
||||
// }(t)
|
||||
// _, err := g.syncAllJoinedGroups(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// groups, err := g.db.GetJoinedGroupListDB(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// var wg sync.WaitGroup
|
||||
// for _, group := range groups {
|
||||
// wg.Add(1)
|
||||
// go func(groupID string) {
|
||||
// defer wg.Done()
|
||||
// if err := g.SyncAllGroupMember(ctx, groupID); err != nil {
|
||||
// log.ZError(ctx, "SyncGroupMember failed", err)
|
||||
// }
|
||||
// }(group.GroupID)
|
||||
// }
|
||||
// wg.Wait()
|
||||
// return nil
|
||||
// }
|
||||
func (g *Group) SyncAllJoinedGroupsAndMembers(ctx context.Context) error { |
||||
t := time.Now() |
||||
defer func(start time.Time) { |
||||
|
||||
elapsed := time.Since(start).Milliseconds() |
||||
log.ZDebug(ctx, "SyncAllJoinedGroupsAndMembers fn call end", "cost time", fmt.Sprintf("%d ms", elapsed)) |
||||
|
||||
}(t) |
||||
if err := g.IncrSyncJoinGroup(ctx); err != nil { |
||||
return err |
||||
} |
||||
return g.IncrSyncJoinGroupMember(ctx) |
||||
} |
||||
|
||||
func (g *Group) syncAllJoinedGroups(ctx context.Context) ([]*sdkws.GroupInfo, error) { |
||||
groups, err := g.GetServerJoinGroup(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
localData, err := g.db.GetJoinedGroupListDB(ctx) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if err := g.groupSyncer.Sync(ctx, datautil.Batch(ServerGroupToLocalGroup, groups), localData, nil); err != nil { |
||||
return nil, err |
||||
} |
||||
return groups, nil |
||||
} |
||||
|
||||
func (g *Group) SyncAllSelfGroupApplication(ctx context.Context) error { |
||||
list, err := g.GetServerSelfGroupApplication(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
localData, err := g.db.GetSendGroupApplication(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if err := g.groupRequestSyncer.Sync(ctx, datautil.Batch(ServerGroupRequestToLocalGroupRequest, list), localData, nil); err != nil { |
||||
return err |
||||
} |
||||
// todo
|
||||
return nil |
||||
} |
||||
|
||||
func (g *Group) SyncSelfGroupApplications(ctx context.Context, groupIDs ...string) error { |
||||
return g.SyncAllSelfGroupApplication(ctx) |
||||
} |
||||
|
||||
func (g *Group) SyncAllAdminGroupApplication(ctx context.Context) error { |
||||
requests, err := g.GetServerAdminGroupApplicationList(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
localData, err := g.db.GetAdminGroupApplication(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
return g.groupAdminRequestSyncer.Sync(ctx, datautil.Batch(ServerGroupRequestToLocalAdminGroupRequest, requests), localData, nil) |
||||
} |
||||
|
||||
func (g *Group) SyncAdminGroupApplications(ctx context.Context, groupIDs ...string) error { |
||||
return g.SyncAllAdminGroupApplication(ctx) |
||||
} |
||||
|
||||
func (g *Group) GetServerJoinGroup(ctx context.Context) ([]*sdkws.GroupInfo, error) { |
||||
fn := func(resp *group.GetJoinedGroupListResp) []*sdkws.GroupInfo { return resp.Groups } |
||||
req := &group.GetJoinedGroupListReq{FromUserID: g.loginUserID, Pagination: &sdkws.RequestPagination{}} |
||||
return util.GetPageAll(ctx, constant.GetJoinedGroupListRouter, req, fn) |
||||
} |
||||
|
||||
func (g *Group) GetServerAdminGroupApplicationList(ctx context.Context) ([]*sdkws.GroupRequest, error) { |
||||
fn := func(resp *group.GetGroupApplicationListResp) []*sdkws.GroupRequest { return resp.GroupRequests } |
||||
req := &group.GetGroupApplicationListReq{FromUserID: g.loginUserID, Pagination: &sdkws.RequestPagination{}} |
||||
return util.GetPageAll(ctx, constant.GetRecvGroupApplicationListRouter, req, fn) |
||||
} |
||||
|
||||
func (g *Group) GetServerSelfGroupApplication(ctx context.Context) ([]*sdkws.GroupRequest, error) { |
||||
fn := func(resp *group.GetGroupApplicationListResp) []*sdkws.GroupRequest { return resp.GroupRequests } |
||||
req := &group.GetUserReqApplicationListReq{UserID: g.loginUserID, Pagination: &sdkws.RequestPagination{}} |
||||
return util.GetPageAll(ctx, constant.GetSendGroupApplicationListRouter, req, fn) |
||||
} |
||||
|
||||
func (g *Group) GetServerGroupMembers(ctx context.Context, groupID string) ([]*sdkws.GroupMemberFullInfo, error) { |
||||
req := &group.GetGroupMemberListReq{GroupID: groupID, Pagination: &sdkws.RequestPagination{}} |
||||
fn := func(resp *group.GetGroupMemberListResp) []*sdkws.GroupMemberFullInfo { return resp.Members } |
||||
return util.GetPageAll(ctx, constant.GetGroupMemberListRouter, req, fn) |
||||
} |
||||
|
||||
func (g *Group) GetDesignatedGroupMembers(ctx context.Context, groupID string, userID []string) ([]*sdkws.GroupMemberFullInfo, error) { |
||||
resp := &group.GetGroupMembersInfoResp{} |
||||
if err := util.ApiPost(ctx, constant.GetGroupMembersInfoRouter, &group.GetGroupMembersInfoReq{GroupID: groupID, UserIDs: userID}, resp); err != nil { |
||||
return nil, err |
||||
} |
||||
return resp.Members, nil |
||||
} |
||||
|
||||
func (g *Group) GetGroupAbstractInfo(ctx context.Context, groupID string) (*group.GroupAbstractInfo, error) { |
||||
resp, err := util.CallApi[group.GetGroupAbstractInfoResp](ctx, constant.GetGroupAbstractInfoRouter, &group.GetGroupAbstractInfoReq{GroupIDs: []string{groupID}}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if len(resp.GroupAbstractInfos) == 0 { |
||||
return nil, errors.New("group not found") |
||||
} |
||||
return resp.GroupAbstractInfos[0], nil |
||||
} |
@ -0,0 +1,347 @@ |
||||
package group |
||||
|
||||
import ( |
||||
"context" |
||||
"sync" |
||||
|
||||
"gorm.io/gorm" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/incrversion" |
||||
"github.com/openimsdk/openim-sdk-core/v3/internal/util" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/constant" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
constantpb "github.com/openimsdk/protocol/constant" |
||||
"github.com/openimsdk/protocol/group" |
||||
"github.com/openimsdk/protocol/sdkws" |
||||
"github.com/openimsdk/tools/errs" |
||||
"github.com/openimsdk/tools/log" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
) |
||||
|
||||
type BatchIncrementalReq struct { |
||||
UserID string `json:"user_id"` |
||||
List []*group.GetIncrementalGroupMemberReq `json:"list"` |
||||
} |
||||
type BatchIncrementalResp struct { |
||||
List map[string]*group.GetIncrementalGroupMemberResp `json:"list"` |
||||
} |
||||
|
||||
func (g *Group) getIncrementalGroupMemberBatch(ctx context.Context, groups []*group.GetIncrementalGroupMemberReq) (map[string]*group.GetIncrementalGroupMemberResp, error) { |
||||
resp, err := util.CallApi[BatchIncrementalResp](ctx, constant.GetIncrementalGroupMemberBatch, &BatchIncrementalReq{UserID: g.loginUserID, List: groups}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return resp.List, nil |
||||
} |
||||
|
||||
func (g *Group) groupAndMemberVersionTableName() string { |
||||
return "local_group_entities_version" |
||||
} |
||||
|
||||
func (g *Group) groupTableName() string { |
||||
return model_struct.LocalGroup{}.TableName() |
||||
} |
||||
|
||||
func (g *Group) IncrSyncJoinGroupMember(ctx context.Context) error { |
||||
groups, err := g.db.GetJoinedGroupListDB(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
groupIDs := datautil.Slice(groups, func(e *model_struct.LocalGroup) string { |
||||
return e.GroupID |
||||
}) |
||||
return g.IncrSyncGroupAndMember(ctx, groupIDs...) |
||||
} |
||||
|
||||
func (g *Group) IncrSyncGroupAndMember(ctx context.Context, groupIDs ...string) error { |
||||
var wg sync.WaitGroup |
||||
if len(groupIDs) == 0 { |
||||
return nil |
||||
} |
||||
const maxSyncNum = constantpb.MaxSyncPullNumber |
||||
groupIDSet := datautil.SliceSet(groupIDs) |
||||
var groups []*group.GetIncrementalGroupMemberReq |
||||
if len(groupIDs) > maxSyncNum { |
||||
groups = make([]*group.GetIncrementalGroupMemberReq, 0, maxSyncNum) |
||||
} else { |
||||
groups = make([]*group.GetIncrementalGroupMemberReq, 0, len(groupIDs)) |
||||
} |
||||
for { |
||||
if len(groupIDSet) == 0 { |
||||
return nil |
||||
} |
||||
for groupID := range groupIDSet { |
||||
if len(groups) == cap(groups) { |
||||
break |
||||
} |
||||
req := group.GetIncrementalGroupMemberReq{ |
||||
GroupID: groupID, |
||||
} |
||||
lvs, err := g.db.GetVersionSync(ctx, g.groupAndMemberVersionTableName(), groupID) |
||||
if err == nil { |
||||
req.VersionID = lvs.VersionID |
||||
req.Version = lvs.Version |
||||
} else if errs.Unwrap(err) != gorm.ErrRecordNotFound { |
||||
return err |
||||
} |
||||
groups = append(groups, &req) |
||||
} |
||||
groupVersion, err := g.getIncrementalGroupMemberBatch(ctx, groups) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
groups = groups[:0] |
||||
for groupID, resp := range groupVersion { |
||||
tempResp := resp |
||||
tempGroupID := groupID |
||||
wg.Add(1) |
||||
go func() error { |
||||
if err := g.syncGroupAndMember(ctx, tempGroupID, tempResp); err != nil { |
||||
return err |
||||
} |
||||
wg.Done() |
||||
return nil |
||||
}() |
||||
delete(groupIDSet, tempGroupID) |
||||
} |
||||
wg.Wait() |
||||
num := len(groupIDSet) |
||||
_ = num |
||||
} |
||||
} |
||||
|
||||
func (g *Group) syncGroupAndMember(ctx context.Context, groupID string, resp *group.GetIncrementalGroupMemberResp) error { |
||||
groupMemberSyncer := incrversion.VersionSynchronizer[*model_struct.LocalGroupMember, *group.GetIncrementalGroupMemberResp]{ |
||||
Ctx: ctx, |
||||
DB: g.db, |
||||
TableName: g.groupAndMemberVersionTableName(), |
||||
EntityID: groupID, |
||||
Key: func(localGroupMember *model_struct.LocalGroupMember) string { |
||||
return localGroupMember.UserID |
||||
}, |
||||
Local: func() ([]*model_struct.LocalGroupMember, error) { |
||||
return g.db.GetGroupMemberListByGroupID(ctx, groupID) |
||||
}, |
||||
ServerVersion: func() *group.GetIncrementalGroupMemberResp { |
||||
return resp |
||||
}, |
||||
Full: func(resp *group.GetIncrementalGroupMemberResp) bool { |
||||
return resp.Full |
||||
}, |
||||
Version: func(resp *group.GetIncrementalGroupMemberResp) (string, uint64) { |
||||
return resp.VersionID, resp.Version |
||||
}, |
||||
Delete: func(resp *group.GetIncrementalGroupMemberResp) []string { |
||||
return resp.Delete |
||||
}, |
||||
Update: func(resp *group.GetIncrementalGroupMemberResp) []*model_struct.LocalGroupMember { |
||||
return datautil.Batch(ServerGroupMemberToLocalGroupMember, resp.Update) |
||||
}, |
||||
Insert: func(resp *group.GetIncrementalGroupMemberResp) []*model_struct.LocalGroupMember { |
||||
return datautil.Batch(ServerGroupMemberToLocalGroupMember, resp.Insert) |
||||
}, |
||||
ExtraData: func(resp *group.GetIncrementalGroupMemberResp) any { |
||||
return resp.Group |
||||
}, |
||||
ExtraDataProcessor: func(ctx context.Context, data any) error { |
||||
groupInfo, ok := data.(*sdkws.GroupInfo) |
||||
if !ok { |
||||
return errs.New("group info type error") |
||||
} |
||||
if groupInfo == nil { |
||||
return nil |
||||
} |
||||
local, err := g.db.GetJoinedGroupListDB(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.ZDebug(ctx, "group info", "groupInfo", groupInfo) |
||||
changes := datautil.Batch(ServerGroupToLocalGroup, []*sdkws.GroupInfo{groupInfo}) |
||||
kv := datautil.SliceToMapAny(local, func(e *model_struct.LocalGroup) (string, *model_struct.LocalGroup) { |
||||
return e.GroupID, e |
||||
}) |
||||
for i, change := range changes { |
||||
key := change.GroupID |
||||
kv[key] = changes[i] |
||||
} |
||||
server := datautil.Values(kv) |
||||
return g.groupSyncer.Sync(ctx, server, local, nil) |
||||
}, |
||||
Syncer: func(server, local []*model_struct.LocalGroupMember) error { |
||||
return g.groupMemberSyncer.Sync(ctx, server, local, nil) |
||||
}, |
||||
FullSyncer: func(ctx context.Context) error { |
||||
return g.groupMemberSyncer.FullSync(ctx, groupID) |
||||
}, |
||||
FullID: func(ctx context.Context) ([]string, error) { |
||||
resp, err := util.CallApi[group.GetFullGroupMemberUserIDsResp](ctx, constant.GetFullGroupMemberUserIDs, &group.GetFullGroupMemberUserIDsReq{ |
||||
GroupID: groupID, |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return resp.UserIDs, nil |
||||
}, |
||||
} |
||||
return groupMemberSyncer.Sync() |
||||
} |
||||
|
||||
func (g *Group) onlineSyncGroupAndMember(ctx context.Context, groupID string, deleteGroupMembers, updateGroupMembers, insertGroupMembers []*sdkws.GroupMemberFullInfo, |
||||
updateGroup *sdkws.GroupInfo, version uint64, versionID string) error { |
||||
groupMemberSyncer := incrversion.VersionSynchronizer[*model_struct.LocalGroupMember, *group.GetIncrementalGroupMemberResp]{ |
||||
Ctx: ctx, |
||||
DB: g.db, |
||||
TableName: g.groupAndMemberVersionTableName(), |
||||
EntityID: groupID, |
||||
Key: func(localGroupMember *model_struct.LocalGroupMember) string { |
||||
return localGroupMember.UserID |
||||
}, |
||||
Local: func() ([]*model_struct.LocalGroupMember, error) { |
||||
return g.db.GetGroupMemberListByGroupID(ctx, groupID) |
||||
}, |
||||
ServerVersion: func() *group.GetIncrementalGroupMemberResp { |
||||
return &group.GetIncrementalGroupMemberResp{ |
||||
Version: version, |
||||
VersionID: versionID, |
||||
Full: false, |
||||
Delete: datautil.Slice(deleteGroupMembers, func(e *sdkws.GroupMemberFullInfo) string { |
||||
return e.UserID |
||||
}), |
||||
Insert: insertGroupMembers, |
||||
Update: updateGroupMembers, |
||||
Group: updateGroup, |
||||
} |
||||
}, |
||||
Server: func(version *model_struct.LocalVersionSync) (*group.GetIncrementalGroupMemberResp, error) { |
||||
singleGroupReq := &group.GetIncrementalGroupMemberReq{ |
||||
GroupID: groupID, |
||||
VersionID: version.VersionID, |
||||
Version: version.Version, |
||||
} |
||||
resp, err := util.CallApi[BatchIncrementalResp](ctx, constant.GetIncrementalGroupMemberBatch, |
||||
&BatchIncrementalReq{UserID: g.loginUserID, List: []*group.GetIncrementalGroupMemberReq{singleGroupReq}}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if resp.List != nil { |
||||
if singleGroupResp, ok := resp.List[groupID]; ok { |
||||
return singleGroupResp, nil |
||||
} |
||||
} |
||||
return nil, errs.New("group member version record not found") |
||||
|
||||
}, |
||||
Full: func(resp *group.GetIncrementalGroupMemberResp) bool { |
||||
return resp.Full |
||||
}, |
||||
Version: func(resp *group.GetIncrementalGroupMemberResp) (string, uint64) { |
||||
return resp.VersionID, resp.Version |
||||
}, |
||||
Delete: func(resp *group.GetIncrementalGroupMemberResp) []string { |
||||
return resp.Delete |
||||
}, |
||||
Update: func(resp *group.GetIncrementalGroupMemberResp) []*model_struct.LocalGroupMember { |
||||
return datautil.Batch(ServerGroupMemberToLocalGroupMember, resp.Update) |
||||
}, |
||||
Insert: func(resp *group.GetIncrementalGroupMemberResp) []*model_struct.LocalGroupMember { |
||||
return datautil.Batch(ServerGroupMemberToLocalGroupMember, resp.Insert) |
||||
}, |
||||
ExtraData: func(resp *group.GetIncrementalGroupMemberResp) any { |
||||
return resp.Group |
||||
}, |
||||
ExtraDataProcessor: func(ctx context.Context, data any) error { |
||||
groupInfo, ok := data.(*sdkws.GroupInfo) |
||||
if !ok { |
||||
return errs.New("group info type error") |
||||
} |
||||
if groupInfo == nil { |
||||
return nil |
||||
} |
||||
local, err := g.db.GetJoinedGroupListDB(ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
log.ZDebug(ctx, "group info", "groupInfo", groupInfo) |
||||
changes := datautil.Batch(ServerGroupToLocalGroup, []*sdkws.GroupInfo{groupInfo}) |
||||
kv := datautil.SliceToMapAny(local, func(e *model_struct.LocalGroup) (string, *model_struct.LocalGroup) { |
||||
return e.GroupID, e |
||||
}) |
||||
for i, change := range changes { |
||||
key := change.GroupID |
||||
kv[key] = changes[i] |
||||
} |
||||
server := datautil.Values(kv) |
||||
return g.groupSyncer.Sync(ctx, server, local, nil) |
||||
}, |
||||
Syncer: func(server, local []*model_struct.LocalGroupMember) error { |
||||
return g.groupMemberSyncer.Sync(ctx, server, local, nil) |
||||
}, |
||||
FullSyncer: func(ctx context.Context) error { |
||||
return g.groupMemberSyncer.FullSync(ctx, groupID) |
||||
}, |
||||
FullID: func(ctx context.Context) ([]string, error) { |
||||
resp, err := util.CallApi[group.GetFullGroupMemberUserIDsResp](ctx, constant.GetFullGroupMemberUserIDs, &group.GetFullGroupMemberUserIDsReq{ |
||||
GroupID: groupID, |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return resp.UserIDs, nil |
||||
}, |
||||
} |
||||
return groupMemberSyncer.CheckVersionSync() |
||||
} |
||||
|
||||
func (g *Group) IncrSyncJoinGroup(ctx context.Context) error { |
||||
opt := incrversion.VersionSynchronizer[*model_struct.LocalGroup, *group.GetIncrementalJoinGroupResp]{ |
||||
Ctx: ctx, |
||||
DB: g.db, |
||||
TableName: g.groupTableName(), |
||||
EntityID: g.loginUserID, |
||||
Key: func(LocalGroup *model_struct.LocalGroup) string { |
||||
return LocalGroup.GroupID |
||||
}, |
||||
Local: func() ([]*model_struct.LocalGroup, error) { |
||||
return g.db.GetJoinedGroupListDB(ctx) |
||||
}, |
||||
Server: func(version *model_struct.LocalVersionSync) (*group.GetIncrementalJoinGroupResp, error) { |
||||
return util.CallApi[group.GetIncrementalJoinGroupResp](ctx, constant.GetIncrementalJoinGroup, &group.GetIncrementalJoinGroupReq{ |
||||
UserID: g.loginUserID, |
||||
Version: version.Version, |
||||
VersionID: version.VersionID, |
||||
}) |
||||
}, |
||||
Full: func(resp *group.GetIncrementalJoinGroupResp) bool { |
||||
return resp.Full |
||||
}, |
||||
Version: func(resp *group.GetIncrementalJoinGroupResp) (string, uint64) { |
||||
return resp.VersionID, resp.Version |
||||
}, |
||||
Delete: func(resp *group.GetIncrementalJoinGroupResp) []string { |
||||
return resp.Delete |
||||
}, |
||||
Update: func(resp *group.GetIncrementalJoinGroupResp) []*model_struct.LocalGroup { |
||||
return datautil.Batch(ServerGroupToLocalGroup, resp.Update) |
||||
}, |
||||
Insert: func(resp *group.GetIncrementalJoinGroupResp) []*model_struct.LocalGroup { |
||||
return datautil.Batch(ServerGroupToLocalGroup, resp.Insert) |
||||
}, |
||||
Syncer: func(server, local []*model_struct.LocalGroup) error { |
||||
return g.groupSyncer.Sync(ctx, server, local, nil) |
||||
}, |
||||
FullSyncer: func(ctx context.Context) error { |
||||
return g.groupSyncer.FullSync(ctx, g.loginUserID) |
||||
}, |
||||
FullID: func(ctx context.Context) ([]string, error) { |
||||
resp, err := util.CallApi[group.GetFullJoinGroupIDsResp](ctx, constant.GetFullJoinedGroupIDs, &group.GetFullJoinGroupIDsReq{ |
||||
UserID: g.loginUserID, |
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return resp.GroupIDs, nil |
||||
|
||||
}, |
||||
} |
||||
return opt.Sync() |
||||
} |
@ -0,0 +1 @@ |
||||
package group |
@ -0,0 +1,265 @@ |
||||
package incrversion |
||||
|
||||
import ( |
||||
"context" |
||||
"reflect" |
||||
"sort" |
||||
|
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/db_interface" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/db/model_struct" |
||||
"github.com/openimsdk/tools/errs" |
||||
"github.com/openimsdk/tools/log" |
||||
"github.com/openimsdk/tools/utils/datautil" |
||||
"gorm.io/gorm" |
||||
) |
||||
|
||||
type VersionSynchronizer[V, R any] struct { |
||||
Ctx context.Context |
||||
DB db_interface.VersionSyncModel |
||||
TableName string |
||||
EntityID string |
||||
Key func(V) string |
||||
Local func() ([]V, error) |
||||
ServerVersion func() R |
||||
Server func(version *model_struct.LocalVersionSync) (R, error) |
||||
Full func(resp R) bool |
||||
Version func(resp R) (string, uint64) |
||||
Delete func(resp R) []string |
||||
Update func(resp R) []V |
||||
Insert func(resp R) []V |
||||
ExtraData func(resp R) any |
||||
ExtraDataProcessor func(ctx context.Context, data any) error |
||||
Syncer func(server, local []V) error |
||||
FullSyncer func(ctx context.Context) error |
||||
FullID func(ctx context.Context) ([]string, error) |
||||
} |
||||
|
||||
func (o *VersionSynchronizer[V, R]) getVersionInfo() (*model_struct.LocalVersionSync, error) { |
||||
versionInfo, err := o.DB.GetVersionSync(o.Ctx, o.TableName, o.EntityID) |
||||
if err != nil && errs.Unwrap(err) != gorm.ErrRecordNotFound { |
||||
log.ZWarn(o.Ctx, "get version info", err) |
||||
return nil, err |
||||
|
||||
} |
||||
return versionInfo, nil |
||||
} |
||||
|
||||
func (o *VersionSynchronizer[V, R]) updateVersionInfo(lvs *model_struct.LocalVersionSync, resp R) error { |
||||
lvs.Table = o.TableName |
||||
lvs.EntityID = o.EntityID |
||||
lvs.VersionID, lvs.Version = o.Version(resp) |
||||
return o.DB.SetVersionSync(o.Ctx, lvs) |
||||
} |
||||
func judgeInterfaceIsNil(data any) bool { |
||||
return reflect.ValueOf(data).Kind() == reflect.Ptr && reflect.ValueOf(data).IsNil() |
||||
} |
||||
|
||||
func (o *VersionSynchronizer[V, R]) Sync() error { |
||||
var lvs *model_struct.LocalVersionSync |
||||
var resp R |
||||
var extraData any |
||||
if o.ServerVersion == nil { |
||||
var err error |
||||
lvs, err = o.getVersionInfo() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
resp, err = o.Server(lvs) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
var err error |
||||
lvs, err = o.getVersionInfo() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
resp = o.ServerVersion() |
||||
} |
||||
delIDs := o.Delete(resp) |
||||
changes := o.Update(resp) |
||||
insert := o.Insert(resp) |
||||
if o.ExtraData != nil { |
||||
temp := o.ExtraData(resp) |
||||
if !judgeInterfaceIsNil(temp) { |
||||
extraData = temp |
||||
} |
||||
} |
||||
if len(delIDs) == 0 && len(changes) == 0 && len(insert) == 0 && !o.Full(resp) && extraData == nil { |
||||
log.ZDebug(o.Ctx, "no data to sync", "table", o.TableName, "entityID", o.EntityID) |
||||
return nil |
||||
} |
||||
|
||||
if o.Full(resp) { |
||||
err := o.FullSyncer(o.Ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
lvs.UIDList, err = o.FullID(o.Ctx) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
if len(delIDs) > 0 { |
||||
lvs.UIDList = DeleteElements(lvs.UIDList, delIDs) |
||||
} |
||||
if len(insert) > 0 { |
||||
lvs.UIDList = append(lvs.UIDList, datautil.Slice(insert, o.Key)...) |
||||
|
||||
} |
||||
local, err := o.Local() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
kv := datautil.SliceToMapAny(local, func(v V) (string, V) { |
||||
return o.Key(v), v |
||||
}) |
||||
|
||||
changes = append(changes, insert...) |
||||
|
||||
for i, change := range changes { |
||||
key := o.Key(change) |
||||
kv[key] = changes[i] |
||||
} |
||||
|
||||
for _, id := range delIDs { |
||||
delete(kv, id) |
||||
} |
||||
server := datautil.Values(kv) |
||||
if err := o.Syncer(server, local); err != nil { |
||||
return err |
||||
} |
||||
if extraData != nil && o.ExtraDataProcessor != nil { |
||||
if err := o.ExtraDataProcessor(o.Ctx, extraData); err != nil { |
||||
return err |
||||
} |
||||
|
||||
} |
||||
} |
||||
return o.updateVersionInfo(lvs, resp) |
||||
} |
||||
|
||||
func (o *VersionSynchronizer[V, R]) CheckVersionSync() error { |
||||
lvs, err := o.getVersionInfo() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
var extraData any |
||||
resp := o.ServerVersion() |
||||
delIDs := o.Delete(resp) |
||||
changes := o.Update(resp) |
||||
insert := o.Insert(resp) |
||||
versionID, version := o.Version(resp) |
||||
if o.ExtraData != nil { |
||||
temp := o.ExtraData(resp) |
||||
if !judgeInterfaceIsNil(temp) { |
||||
extraData = temp |
||||
} |
||||
} |
||||
if len(delIDs) == 0 && len(changes) == 0 && len(insert) == 0 && !o.Full(resp) && extraData == nil { |
||||
log.ZWarn(o.Ctx, "exception no data to sync", errs.New("notification no data"), "table", o.TableName, "entityID", o.EntityID) |
||||
return nil |
||||
} |
||||
log.ZDebug(o.Ctx, "check version sync", "table", o.TableName, "entityID", o.EntityID, "versionID", versionID, "localVersionID", lvs.VersionID, "version", version, "localVersion", lvs.Version) |
||||
/// If the version unique ID cannot correspond with the local version,
|
||||
// it indicates that the data might have been tampered with or an exception has occurred.
|
||||
//Trigger the complete client-server incremental synchronization.
|
||||
if versionID != lvs.VersionID { |
||||
log.ZDebug(o.Ctx, "version id not match", errs.New("version id not match"), "versionID", versionID, "localVersionID", lvs.VersionID) |
||||
o.ServerVersion = nil |
||||
return o.Sync() |
||||
} |
||||
if lvs.Version+1 == version { |
||||
if len(delIDs) > 0 { |
||||
lvs.UIDList = DeleteElements(lvs.UIDList, delIDs) |
||||
} |
||||
if len(insert) > 0 { |
||||
lvs.UIDList = append(lvs.UIDList, datautil.Slice(insert, o.Key)...) |
||||
|
||||
} |
||||
local, err := o.Local() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
kv := datautil.SliceToMapAny(local, func(v V) (string, V) { |
||||
return o.Key(v), v |
||||
}) |
||||
changes = append(changes, insert...) |
||||
for i, change := range changes { |
||||
key := o.Key(change) |
||||
kv[key] = changes[i] |
||||
} |
||||
|
||||
for _, id := range delIDs { |
||||
delete(kv, id) |
||||
} |
||||
server := datautil.Values(kv) |
||||
if err := o.Syncer(server, local); err != nil { |
||||
return err |
||||
} |
||||
if extraData != nil && o.ExtraDataProcessor != nil { |
||||
if err := o.ExtraDataProcessor(o.Ctx, extraData); err != nil { |
||||
return err |
||||
} |
||||
|
||||
} |
||||
return o.updateVersionInfo(lvs, resp) |
||||
} else if version <= lvs.Version { |
||||
log.ZWarn(o.Ctx, "version less than local version", errs.New("version less than local version"), |
||||
"table", o.TableName, "entityID", o.EntityID, "version", version, "localVersion", lvs.Version) |
||||
return nil |
||||
} else { |
||||
// If the version number has a gap with the local version number,
|
||||
//it indicates that some pushed data might be missing.
|
||||
//Trigger the complete client-server incremental synchronization.
|
||||
o.ServerVersion = nil |
||||
return o.Sync() |
||||
} |
||||
} |
||||
|
||||
// DeleteElements 删除切片中包含在另一个切片中的元素,并保持切片顺序
|
||||
func DeleteElements[E comparable](es []E, toDelete []E) []E { |
||||
// 将要删除的元素存储在哈希集合中
|
||||
deleteSet := make(map[E]struct{}, len(toDelete)) |
||||
for _, e := range toDelete { |
||||
deleteSet[e] = struct{}{} |
||||
} |
||||
|
||||
// 通过一个索引 j 来跟踪新的切片位置
|
||||
j := 0 |
||||
for _, e := range es { |
||||
if _, found := deleteSet[e]; !found { |
||||
es[j] = e |
||||
j++ |
||||
} |
||||
} |
||||
return es[:j] |
||||
} |
||||
|
||||
// DeleteElement 删除切片中的指定元素,并保持切片顺序
|
||||
func DeleteElement[E comparable](es []E, element E) []E { |
||||
j := 0 |
||||
for _, e := range es { |
||||
if e != element { |
||||
es[j] = e |
||||
j++ |
||||
} |
||||
} |
||||
return es[:j] |
||||
} |
||||
|
||||
// Slice Converts slice types in batches and sorts the resulting slice using a custom comparator
|
||||
func Slice[E any, T any](es []E, fn func(e E) T, less func(a, b T) bool) []T { |
||||
// 转换切片
|
||||
v := make([]T, len(es)) |
||||
for i := 0; i < len(es); i++ { |
||||
v[i] = fn(es[i]) |
||||
} |
||||
|
||||
// 排序切片
|
||||
sort.Slice(v, func(i, j int) bool { |
||||
return less(v[i], v[j]) |
||||
}) |
||||
|
||||
return v |
||||
} |
@ -0,0 +1,61 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 interaction |
||||
|
||||
import ( |
||||
"bytes" |
||||
"compress/gzip" |
||||
"github.com/openimsdk/openim-sdk-core/v3/pkg/utils" |
||||
"io" |
||||
) |
||||
|
||||
type Compressor interface { |
||||
Compress(rawData []byte) ([]byte, error) |
||||
DeCompress(compressedData []byte) ([]byte, error) |
||||
} |
||||
|
||||
type GzipCompressor struct { |
||||
compressProtocol string |
||||
} |
||||
|
||||
func NewGzipCompressor() *GzipCompressor { |
||||
return &GzipCompressor{compressProtocol: "gzip"} |
||||
} |
||||
|
||||
func (g *GzipCompressor) Compress(rawData []byte) ([]byte, error) { |
||||
gzipBuffer := bytes.Buffer{} |
||||
gz := gzip.NewWriter(&gzipBuffer) |
||||
if _, err := gz.Write(rawData); err != nil { |
||||
return nil, utils.Wrap(err, "") |
||||
} |
||||
if err := gz.Close(); err != nil { |
||||
return nil, utils.Wrap(err, "") |
||||
} |
||||
return gzipBuffer.Bytes(), nil |
||||
} |
||||
|
||||
func (g *GzipCompressor) DeCompress(compressedData []byte) ([]byte, error) { |
||||
buff := bytes.NewBuffer(compressedData) |
||||
reader, err := gzip.NewReader(buff) |
||||
if err != nil { |
||||
return nil, utils.Wrap(err, "NewReader failed") |
||||
} |
||||
compressedData, err = io.ReadAll(reader) |
||||
if err != nil { |
||||
return nil, utils.Wrap(err, "ReadAll failed") |
||||
} |
||||
_ = reader.Close() |
||||
return compressedData, nil |
||||
} |
@ -0,0 +1,39 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 interaction |
||||
|
||||
const ( |
||||
WebSocket = iota |
||||
Tcp |
||||
) |
||||
|
||||
const ( |
||||
// MessageText is for UTF-8 encoded text messages like JSON.
|
||||
MessageText = iota + 1 |
||||
// MessageBinary is for binary messages like protobufs.
|
||||
MessageBinary |
||||
// CloseMessage denotes a close control message. The optional message
|
||||
// payload contains a numeric code and text. Use the FormatCloseMessage
|
||||
// function to format a close message payload.
|
||||
CloseMessage = 8 |
||||
|
||||
// PingMessage denotes a ping control message. The optional message payload
|
||||
// is UTF-8 encoded text.
|
||||
PingMessage = 9 |
||||
|
||||
// PongMessage denotes a pong control message. The optional message payload
|
||||
// is UTF-8 encoded text.
|
||||
PongMessage = 10 |
||||
) |
@ -0,0 +1,52 @@ |
||||
// Copyright © 2023 OpenIM SDK. 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 interaction |
||||
|
||||
import ( |
||||
"time" |
||||
|
||||
"github.com/openimsdk/protocol/constant" |
||||
) |
||||
|
||||
type ConnContext struct { |
||||
RemoteAddr string |
||||
} |
||||
|
||||
func (c *ConnContext) Deadline() (deadline time.Time, ok bool) { |
||||
return |
||||
} |
||||
|
||||
func (c *ConnContext) Done() <-chan struct{} { |
||||
return nil |
||||
} |
||||
|
||||
func (c *ConnContext) Err() error { |
||||
return nil |
||||
} |
||||
|
||||
func (c *ConnContext) Value(key any) any { |
||||
switch key { |
||||
case constant.RemoteAddr: |
||||
return c.RemoteAddr |
||||
default: |
||||
return "" |
||||
} |
||||
} |
||||
|
||||
func newContext(remoteAddr string) *ConnContext { |
||||
return &ConnContext{ |
||||
RemoteAddr: remoteAddr, |
||||
} |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue