feat: incr sync version.
This commit is contained in:
parent
e8ccae6349
commit
88b8043224
23
go/chao-sdk-core/.github/.codecov.yml
vendored
Normal file
23
go/chao-sdk-core/.github/.codecov.yml
vendored
Normal file
@ -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
|
||||||
43
go/chao-sdk-core/.github/labels.yml
vendored
Normal file
43
go/chao-sdk-core/.github/labels.yml
vendored
Normal file
@ -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.
|
||||||
53
go/chao-sdk-core/.github/workflows/auto-invite.yml
vendored
Normal file
53
go/chao-sdk-core/.github/workflows/auto-invite.yml
vendored
Normal file
@ -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
|
||||||
60
go/chao-sdk-core/.github/workflows/check-coverage.yml
vendored
Normal file
60
go/chao-sdk-core/.github/workflows/check-coverage.yml
vendored
Normal file
@ -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
|
||||||
76
go/chao-sdk-core/.github/workflows/codeql-analysis.yml
vendored
Normal file
76
go/chao-sdk-core/.github/workflows/codeql-analysis.yml
vendored
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# 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: "Code Scanning - Action"
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
pull_request:
|
||||||
|
branches: [main]
|
||||||
|
schedule:
|
||||||
|
# ┌───────────── minute (0 - 59)
|
||||||
|
# │ ┌───────────── hour (0 - 23)
|
||||||
|
# │ │ ┌───────────── day of the month (1 - 31)
|
||||||
|
# │ │ │ ┌───────────── month (1 - 12 or JAN-DEC)
|
||||||
|
# │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT)
|
||||||
|
# │ │ │ │ │
|
||||||
|
# │ │ │ │ │
|
||||||
|
# │ │ │ │ │
|
||||||
|
# * * * * *
|
||||||
|
- cron: '30 1 * * 0'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
CodeQL-Build:
|
||||||
|
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
# required for all workflows
|
||||||
|
security-events: write
|
||||||
|
|
||||||
|
# only required for workflows in private repositories
|
||||||
|
actions: read
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v2
|
||||||
|
# Override language selection by uncommenting this and choosing your languages
|
||||||
|
# with:
|
||||||
|
# languages: go, javascript, csharp, python, cpp, java, ruby
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below).
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v2
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following
|
||||||
|
# three lines and modify them (or add more) to build your code if your
|
||||||
|
# project uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v2
|
||||||
15
go/chao-sdk-core/.github/workflows/e2e-test.yml
vendored
Normal file
15
go/chao-sdk-core/.github/workflows/e2e-test.yml
vendored
Normal file
@ -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.
|
||||||
|
|
||||||
|
#
|
||||||
44
go/chao-sdk-core/.github/workflows/gosec.yml
vendored
Normal file
44
go/chao-sdk-core/.github/workflows/gosec.yml
vendored
Normal file
@ -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: ./...
|
||||||
31
go/chao-sdk-core/.github/workflows/issue-robot.yml
vendored
Normal file
31
go/chao-sdk-core/.github/workflows/issue-robot.yml
vendored
Normal file
@ -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
|
||||||
61
go/chao-sdk-core/.github/workflows/link-pr.yml
vendored
Normal file
61
go/chao-sdk-core/.github/workflows/link-pr.yml
vendored
Normal file
@ -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 }}
|
||||||
52
go/chao-sdk-core/.github/workflows/opencommit.yml
vendored
Normal file
52
go/chao-sdk-core/.github/workflows/opencommit.yml
vendored
Normal file
@ -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
|
||||||
144
go/chao-sdk-core/.github/workflows/openimci.yml
vendored
Normal file
144
go/chao-sdk-core/.github/workflows/openimci.yml
vendored
Normal file
@ -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
|
||||||
82
go/chao-sdk-core/.github/workflows/release.yml
vendored
Normal file
82
go/chao-sdk-core/.github/workflows/release.yml
vendored
Normal file
@ -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 }}
|
||||||
48
go/chao-sdk-core/.github/workflows/stale.yml
vendored
Normal file
48
go/chao-sdk-core/.github/workflows/stale.yml
vendored
Normal file
@ -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
|
||||||
172
go/chao-sdk-core/.gitignore
vendored
Normal file
172
go/chao-sdk-core/.gitignore
vendored
Normal file
@ -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
|
||||||
934
go/chao-sdk-core/.golangci.yml
Normal file
934
go/chao-sdk-core/.golangci.yml
Normal file
@ -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
|
||||||
223
go/chao-sdk-core/.goreleaser.yaml
Normal file
223
go/chao-sdk-core/.goreleaser.yaml
Normal file
@ -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
|
||||||
62
go/chao-sdk-core/CHANGELOG/.chglog/CHANGELOG.tpl.md
Normal file
62
go/chao-sdk-core/CHANGELOG/.chglog/CHANGELOG.tpl.md
Normal file
@ -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 -}}
|
||||||
81
go/chao-sdk-core/CHANGELOG/.chglog/config.yml
Normal file
81
go/chao-sdk-core/CHANGELOG/.chglog/config.yml
Normal file
@ -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
|
||||||
42
go/chao-sdk-core/CHANGELOG/CHANGELOG-1.0.md
Normal file
42
go/chao-sdk-core/CHANGELOG/CHANGELOG-1.0.md
Normal file
@ -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
go/chao-sdk-core/CHANGELOG/CHANGELOG-1.1.md
Normal file
0
go/chao-sdk-core/CHANGELOG/CHANGELOG-1.1.md
Normal file
92
go/chao-sdk-core/CHANGELOG/CHANGELOG-2.0.md
Normal file
92
go/chao-sdk-core/CHANGELOG/CHANGELOG-2.0.md
Normal file
@ -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
|
||||||
23
go/chao-sdk-core/CHANGELOG/CHANGELOG-2.1.md
Normal file
23
go/chao-sdk-core/CHANGELOG/CHANGELOG-2.1.md
Normal file
@ -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
|
||||||
24
go/chao-sdk-core/CHANGELOG/CHANGELOG-2.2.md
Normal file
24
go/chao-sdk-core/CHANGELOG/CHANGELOG-2.2.md
Normal file
@ -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
|
||||||
32
go/chao-sdk-core/CHANGELOG/CHANGELOG-2.3.md
Normal file
32
go/chao-sdk-core/CHANGELOG/CHANGELOG-2.3.md
Normal file
@ -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
|
||||||
52
go/chao-sdk-core/CHANGELOG/CHANGELOG-2.9.md
Normal file
52
go/chao-sdk-core/CHANGELOG/CHANGELOG-2.9.md
Normal file
@ -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
|
||||||
128
go/chao-sdk-core/CHANGELOG/CHANGELOG-3.0.md
Normal file
128
go/chao-sdk-core/CHANGELOG/CHANGELOG-3.0.md
Normal file
@ -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
|
||||||
104
go/chao-sdk-core/CHANGELOG/CHANGELOG.md
Normal file
104
go/chao-sdk-core/CHANGELOG/CHANGELOG.md
Normal file
@ -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.
|
||||||
435
go/chao-sdk-core/CONTRIBUTING.md
Normal file
435
go/chao-sdk-core/CONTRIBUTING.md
Normal file
@ -0,0 +1,435 @@
|
|||||||
|
# Contributing to OpenIM
|
||||||
|
|
||||||
|
So, you want to hack on OpenIM? Yay!
|
||||||
|
|
||||||
|
First of all, thank you for considering contributing to our project! We appreciate your time and effort, and we value any contribution, whether it's reporting a bug, suggesting a new feature, or submitting a pull request.
|
||||||
|
|
||||||
|
This document provides guidelines and best practices to help you contribute effectively.
|
||||||
|
|
||||||
|
## 📇Topics
|
||||||
|
|
||||||
|
- [What we expect of you](#What-we-expect-of-you)
|
||||||
|
- [Code of Conduct](#Code-of-Conduct)
|
||||||
|
- [Getting Started](#Getting-Started)
|
||||||
|
- [Style and Specification](#Style-and-Specification)
|
||||||
|
- [Engage to help anything](#Engage-to-help-anything)
|
||||||
|
- [Release version](#Release-version)
|
||||||
|
- [Contact Us](#Contact-Us)
|
||||||
|
|
||||||
|
|
||||||
|
## What we expect of you
|
||||||
|
|
||||||
|
We hope that anyone can join OpenIM , even if you are a student, writer, translator
|
||||||
|
|
||||||
|
Please meet the minimum version of the Go language published in [go.mod](./go.mod). If you want to manage the Go language version, we provide tools to install [gvm](https://github.com/moovweb/gvm) in our [Makefile](./Makefile)
|
||||||
|
|
||||||
|
You'd better use Linux as the development environment, Linux with [Makefile](./Makefile) can help you quickly build and test OpenIM project.
|
||||||
|
|
||||||
|
If you are familiar with [Makefile](./Makefile) , you can easily see the clever design of the OpenIM Makefile. Storing the necessary tools such as golangci in the `/tools` directory can avoid some tool version issues.
|
||||||
|
|
||||||
|
The [Makefile](./Makefile) is for every developer, even if you don't know how to use the Makefile tool, don't worry, we provide two great commands to get you up to speed with the Makefile architecture, `make help` and `make help-all`, it can reduce problems of the developing environment.
|
||||||
|
|
||||||
|
|
||||||
|
## Code of Conduct
|
||||||
|
|
||||||
|
#### Code and doc contribution
|
||||||
|
|
||||||
|
Every action to make project OpenIM better is encouraged. On GitHub, every improvement for OpenIM could be via a [PR](https://github.com/openimsdk/openim-sdk-core/pulls) (short for pull request).
|
||||||
|
|
||||||
|
+ If you find a typo, try to fix it!
|
||||||
|
+ If you find a bug, try to fix it!
|
||||||
|
+ If you find some redundant codes, try to remove them!
|
||||||
|
+ If you find some test cases missing, try to add them!
|
||||||
|
+ If you could enhance a feature, please **DO NOT** hesitate!
|
||||||
|
+ If you find code implicit, try to add comments to make it clear!
|
||||||
|
+ If you find code ugly, try to refactor that!
|
||||||
|
+ If you can help to improve documents, it could not be better!
|
||||||
|
+ If you find document incorrect, just do it and fix that!
|
||||||
|
+ ...
|
||||||
|
|
||||||
|
#### Where should I start?
|
||||||
|
|
||||||
|
Getting good at GitHub is the first step, we have a [list of labes](https://github.com/openimsdk/openim-sdk-core/labels) and reading some of the [common tags](https://github.com/openimsdk/openim-sdk-core/labels?sort=count-desc) helps us get involved in the community quickly.GitHub allows you to filter out types of issues and pull requests, which helps you discover items in need of triaging. This table includes some predetermined searches for convenience:
|
||||||
|
|
||||||
|
| Search | What it sorts |
|
||||||
|
| ------------------------------------------------------------ | ------------------------------------------------------- |
|
||||||
|
| [created-asc](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+sort%3Acreated-asc) | Untriaged issues by age |
|
||||||
|
| [needs-triage](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+no%3Alabel) | Issues that need to be assigned to a Labels |
|
||||||
|
| [`is:open is:issue`](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aopen+is%3Aissue+sort%3Aupdated-desc) | Newest incoming issues |
|
||||||
|
| [comments-desc](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+sort%3Acomments-desc) | Busiest untriaged issues, sorted by # of comments |
|
||||||
|
| [comments-asc](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+sort%3Acomments-asc) | Issues that need more attention, based on # of comments |
|
||||||
|
|
||||||
|
We suggest preparing your triage by filtering out the oldest, unlabelled issues and pull requests first.
|
||||||
|
|
||||||
|
1. If you are new to the project, don't know how to contribute OpenIM, please check out the [good first issue](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aopen+label%3A"good+first+issue"+sort%3Aupdated-desc) label and [help wanted](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aopen+is%3Aissue+label%3A"help+wanted"+sort%3Aupdated-desc).
|
||||||
|
2. You should be good at filtering the OpenIM issue tags and finding the ones you like, such as [RFC](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+RFC+label%3ARFC) for big initiatives, features for [feature](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+label%3Akind%2Ffeature+sort%3Aupdated-desc+) proposals, and [bug](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+label%3Akind%2Fbug+sort%3Aupdated-desc+) fixes.
|
||||||
|
3. If you are looking for something to work on, check out our [open issues](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc).
|
||||||
|
4. If you have an idea for a new feature, please [open an issue](https://github.com/openimsdk/openim-sdk-core/issues/new/choose), and we can discuss it.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> Reply to `/assign` or `/assign @yourself` with a question you wish to resolve, and we'll assign the question to you and your name will be listed under `Assignees`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#### Design documents
|
||||||
|
|
||||||
|
For any substantial design, there should be a well-crafted design document. This document is not just a simple record, but also a detailed description and manifestation, which can help team members better understand the design thinking and grasp the design direction. In the process of writing the design document, we can choose to use tools such as `Google Docs` or `Notion`, and even mark **RFC** in [issues](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+RFC+label%3ARFC) or [discussions](https://github.com/openimsdk/openim-sdk-core/discussions) for better collaboration. Of course, after completing the design document, we should also add it to our [Shared Drive](https://drive.google.com/drive/) and notify the appropriate working group to let everyone know of its existence. Only by doing so can we maximize the effectiveness of the design document and provide strong support for the smooth progress of the project.
|
||||||
|
|
||||||
|
Anybody can access the shared Drive for reading. To get access to comment. Once you've done that, head to the [shared Drive](https://drive.google.com/) and behold all the docs.
|
||||||
|
|
||||||
|
In addition to that, we'd love to invite you to [Join Our Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg) where you can play with your imagination, tell us what you're working on, and get a quick response.
|
||||||
|
|
||||||
|
When documenting a new design, we recommend a 2-step approach:
|
||||||
|
|
||||||
|
1. Use the short-form **RFC** template to outline your ideas and get early feedback.
|
||||||
|
2. Once you have received sufficient feedback and consensus, you may use the **longer-form design doc template** to specify and discuss your design in more details.
|
||||||
|
|
||||||
|
**In order to contribute a feature to OpenIM you'll need to go through the following steps:**
|
||||||
|
|
||||||
|
+ Discuss your idea with the appropriate [working groups](https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg) on the working group's Slack channel.
|
||||||
|
+ Once there is general agreement that the feature is useful, create a GitHub issue to track the discussion. The issue should include information about the requirements and use cases that it is trying to address.
|
||||||
|
+ Include a discussion of the proposed design and technical details of the implementation in the issue.
|
||||||
|
|
||||||
|
But keep in mind that there is no guarantee of it being accepted and so it is usually best to get agreement on the **idea/design** before time is spent coding it. However, sometimes seeing the exact code change can help focus discussions, so the choice is up to you.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> A simple example allows you to quickly contribute your first **PR** to OpenIM.
|
||||||
|
To propose PR for the OpenIM item, we assume you have registered a GitHub ID. Then you could finish the preparation in the following steps:
|
||||||
|
|
||||||
|
1. **Fork** the repository(OpenIM)
|
||||||
|
|
||||||
|
2. **CLONE** your own repository to master locally. Use `git clone https://github.com/<your-username>/OpenIM.git` to clone repository to your local machine. Then you can create new branches to finish the change you wish to make.
|
||||||
|
|
||||||
|
3. **Set Remote** upstream to be `https://github.com/openimsdk/openim-sdk-core.git` using the following two commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ git remote add upstream https://github.com/openimsdk/openim-sdk-core.git
|
||||||
|
❯ git remote set-url --push upstream no-pushing
|
||||||
|
```
|
||||||
|
|
||||||
|
With this remote setting, you can check your **git remote configuration** like this:
|
||||||
|
|
||||||
|
```go
|
||||||
|
❯ git remote -v
|
||||||
|
origin https://github.com/<your-username>/OpenIM.git (fetch)
|
||||||
|
origin https://github.com/<your-username>/OpenIM.git (push)
|
||||||
|
upstream https://github.com/openimsdk/openim-sdk-core.git (fetch)
|
||||||
|
upstream no-pushing (push)
|
||||||
|
```
|
||||||
|
|
||||||
|
Adding this, we can easily synchronize local branches with upstream branches.
|
||||||
|
|
||||||
|
4. Create a new branch for your changes (use a descriptive name, such as `fix-bug-123` or `add-new-feature`).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ cd OpenIM
|
||||||
|
❯ git fetch upstream
|
||||||
|
❯ git checkout upstream/main
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> Please don't use `git pull` instead of the above `fetch` and `rebase`. Since `git pull` executes a merge, it creates merge commits. These make the commit history messy and violate the principle that commits ought to be individually understandable and useful.
|
||||||
|
>
|
||||||
|
> You might also consider changing your `.git/config` file via `git config branch.autoSetupRebase always` to change the behavior of `git pull`, or another non-merge option such as `git pull --rebase`.
|
||||||
|
|
||||||
|
Create a new branch:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ git checkout -b <new-branch>
|
||||||
|
```
|
||||||
|
|
||||||
|
Make any change on the `new-branch` then use [Makefile](./Makefile) **build** and **test** your codes.
|
||||||
|
|
||||||
|
5. **Commit your changes** to your local branch, lint before committing and commit with sign-off
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ git rebase upstream/main
|
||||||
|
❯ make lint # golangci-lint run -c .golangci.yml
|
||||||
|
❯ git add -A # add changes to staging
|
||||||
|
❯ git commit -a -s -m "fix: message for your changes" # -s adds a Signed-off-by trailer
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Push your branch** to your forked repository, it is recommended to have only one commit for a **PR**.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# sync up with upstream
|
||||||
|
❯ git fetch upstream
|
||||||
|
❯ git rebase upstream/main
|
||||||
|
❯ git rebase -i <commit-id> # rebase with interactive mode to `squash` your commits into a single one
|
||||||
|
❯ git push # push to the remote repository, if it's a first time push, run git push --set-upstream origin <new-branch>
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use `git commit -s --amend && git push -f` to update modifications on the previous commit.
|
||||||
|
|
||||||
|
If you have developed multiple features in the same branch, you should create PR separately by rebasing to the main branch between each push:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# create new branch, for example git checkout -b feature/infra
|
||||||
|
❯ git checkout -b <new branch>
|
||||||
|
# update some code, feature1
|
||||||
|
❯ git add -A
|
||||||
|
❯ git commit -m -s "feat: feature one"
|
||||||
|
❯ git push # if it's first time push, run git push --set-upstream origin <new-branch>
|
||||||
|
# then create pull request, and merge
|
||||||
|
# update some new feature, feature2, rebase main branch first.
|
||||||
|
❯ git rebase upstream/main # rebase the current branch to upstream/main branch
|
||||||
|
❯ git add -A
|
||||||
|
❯ git commit -m -s "feat: feature two"
|
||||||
|
# then create pull request, and merge
|
||||||
|
```
|
||||||
|
|
||||||
|
7. **Open a pull request** to `OpenIMSDK/openim-sdk-core:main`
|
||||||
|
|
||||||
|
It is recommended to review your changes before filing a pull request. Check if your code doesn't conflict with the main branch and no redundant code is included.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Style and Specification
|
||||||
|
|
||||||
|
We divide the problem into security and general problems:
|
||||||
|
|
||||||
|
#### Reporting security issues
|
||||||
|
|
||||||
|
Security issues are always treated seriously. As our usual principle, we discourage anyone to spread security issues. If you find a security issue of OpenIM, please do not discuss it in public and even do not open a public issue.
|
||||||
|
|
||||||
|
Instead we encourage you to send us a private email to 📮[winxu81@gmail.com](https://mail.google.com/mail/u/0/?fs=1&tf=cm&to=winxu81@gmail.com) to report this.
|
||||||
|
|
||||||
|
#### Reporting general issues
|
||||||
|
|
||||||
|
To be honest, we regard every user of OpenIMas a very kind contributor. After experiencing OpenIM, you may have some feedback for the project. Then feel free to open an issue via [NEW ISSUE](https://github.com/openimsdk/openim-sdk-core/issues/new/choose).
|
||||||
|
|
||||||
|
Since we collaborate project OpenIM in a distributed way, we appreciate **WELL-WRITTEN**, **DETAILED**, **EXPLICIT** issue reports. To make the communication more efficient, we wish everyone could search if your issue is an existing one in the searching list. If you find it existing, please add your details in comments under the existing issue instead of opening a brand new one.
|
||||||
|
|
||||||
|
To make the issue details as standard as possible, we setup an [ISSUE TEMPLATE](https://github.com/openimsdk/.github/tree/main/.github/ISSUE_TEMPLATE) for issue reporters. You can find three kinds of issue templates there: question, bug report and feature request. Please **BE SURE** to follow the instructions to fill fields in template.
|
||||||
|
|
||||||
|
**There are a lot of cases when you could open an issue:**
|
||||||
|
|
||||||
|
+ bug report
|
||||||
|
+ feature request
|
||||||
|
+ OpenIM performance issues
|
||||||
|
+ feature proposal
|
||||||
|
+ feature design
|
||||||
|
+ help wanted
|
||||||
|
+ doc incomplete
|
||||||
|
+ test improvement
|
||||||
|
+ any questions on OpenIM project
|
||||||
|
+ and so on
|
||||||
|
|
||||||
|
Also, we must be reminded when submitting a new question about OpenIM, please remember to remove the sensitive data from your post. Sensitive data could be password, secret key, network locations, private business data and so on.
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
> We have requirements for **Commits**, **PR**, **Docs**, and good standards help us collaborate better and understand what you're doing.
|
||||||
|
|
||||||
|
#### Commit Rules
|
||||||
|
|
||||||
|
Actually in OpenIM, we take two rules serious when committing:
|
||||||
|
|
||||||
|
**🥇 Commit Message:**
|
||||||
|
|
||||||
|
Commit message could help reviewers better understand what the purpose of submitted PR is. It could help accelerate the code review procedure as well. We encourage contributors to use **EXPLICIT** commit message rather than ambiguous message. In general, we advocate the following commit message type:
|
||||||
|
|
||||||
|
We use [Semantic Commits](https://www.conventionalcommits.org/en/v1.0.0/) to make it easier to understand what a commit does and to build pretty changelogs. Please use the following prefixes for your commits:
|
||||||
|
|
||||||
|
+ `docs: xxxx`. For example, "docs: add docs about storage installation".
|
||||||
|
+ `feature: xxxx`.For example, "feature: make result show in sorted order".
|
||||||
|
+ `bugfix: xxxx`. For example, "bugfix: fix panic when input nil parameter".
|
||||||
|
+ `style: xxxx`. For example, "style: format the code style of Constants.java".
|
||||||
|
+ `refactor: xxxx.` For example, "refactor: simplify to make codes more readable".
|
||||||
|
+ `test: xxx`. For example, "test: add unit test case for func InsertIntoArray".
|
||||||
|
+ `chore: xxx.` For example, "chore: integrate travis-ci". It's the type of mantainance change.
|
||||||
|
+ other readable and explicit expression ways.
|
||||||
|
|
||||||
|
On the other side, we discourage contributors from committing message like the following ways:
|
||||||
|
|
||||||
|
+ ~~fix bug~~
|
||||||
|
+ ~~update~~
|
||||||
|
+ ~~add doc~~
|
||||||
|
|
||||||
|
**🥈 Commit Content:**
|
||||||
|
|
||||||
|
Commit content represents all content changes included in one commit. We had better include things in one single commit which could support reviewer's complete review without any other commits' help.
|
||||||
|
|
||||||
|
In another word, contents in one single commit can pass the CI to avoid code mess. In brief, there are two minor rules for us to keep in mind:
|
||||||
|
|
||||||
|
1. avoid very large change in a commit.
|
||||||
|
2. complete and reviewable for each commit.
|
||||||
|
3. words are written in lowercase English, not uppercase English or other languages such as Chinese.
|
||||||
|
|
||||||
|
No matter what the commit message, or commit content is, we do take more emphasis on code review.
|
||||||
|
|
||||||
|
An example for this could be:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ git commit -a -s -m "docs: add a new section to the readme"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### PR Description
|
||||||
|
|
||||||
|
PR is the only way to make change to OpenIM project files. To help reviewers better get your purpose, **PR** description could not be too detailed. We encourage contributors to follow the [PR template](https://github.com/openimsdk/.github/tree/main/.github/PULL_REQUEST_TEMPLATE.md) to finish the pull request.
|
||||||
|
|
||||||
|
You can find some very formal PR in [RFC](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+RFC+label%3ARFC) issues and learn about them.
|
||||||
|
|
||||||
|
**📖 Opening PRs:**
|
||||||
|
|
||||||
|
+ As long as you are working on your **PR**, please mark it as a draft.
|
||||||
|
+ Please make sure that your **PR** is up-to-date with the latest changes in `main`
|
||||||
|
+ Mention the issue that your **PR** is addressing. For example, `Fixes: #{ID_1}, #{ID_2}`
|
||||||
|
+ Make sure that your **PR** passes all checks.
|
||||||
|
|
||||||
|
**🈴 Reviewing PRs:**
|
||||||
|
|
||||||
|
+ Be respectful and constructive.
|
||||||
|
+ Assign yourself to the **PR**.
|
||||||
|
+ Check if all checks are passing.
|
||||||
|
+ Suggest changes instead of simply commenting on found issues.
|
||||||
|
+ If you are unsure about something, ask the author.
|
||||||
|
+ If you are not sure if the changes work, try them out.
|
||||||
|
+ Reach out to other reviewers if you are unsure about something.
|
||||||
|
+ If you are happy with the changes, approve the **PR**.
|
||||||
|
+ Merge the **PR** once it has all approvals and the checks are passing.
|
||||||
|
|
||||||
|
**⚠️ DCO check:**
|
||||||
|
|
||||||
|
We have a [DCO check](https://github.com/apps/dco) which runs on every **PR** to verify that the commit has been signed off.
|
||||||
|
|
||||||
|
Once [installed](https://github.com/apps/dco#usage), this integration will set the [status](https://developer.github.com/v3/repos/statuses/) to `failed` if commits in a **Pull Request** do not contain a valid `Signed-off-by` line.
|
||||||
|
|
||||||
|
To sign off the last commit you made, you can use:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ git commit --amend --signoff
|
||||||
|
```
|
||||||
|
|
||||||
|
Contributors *sign-off* that they adhere to these requirements by adding a `Signed-off-by` line to commit messages.
|
||||||
|
|
||||||
|
Git even has a `-s` command line option to append this automatically to your commit message:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ git commit -s -m "docs: this is my commit message"
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also automate signing off your commits by adding the following to your `.zshrc` or `.bashrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ cat ~/.bashrc || cat ~/.zshrc
|
||||||
|
git() {
|
||||||
|
if [ $# -gt 0 ] && [[ "$1" == "commit" ]] ; then
|
||||||
|
shift
|
||||||
|
command git commit --signoff "$@"
|
||||||
|
else
|
||||||
|
command git "$@"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### CI actions
|
||||||
|
|
||||||
|
We host CI on [GitHub Actions](https://github.com/openimsdk/openim-sdk-core/actions), we will make sure PR pass tests before we can merge it.
|
||||||
|
|
||||||
|
These two kind of tests: `lint` and `unit test`
|
||||||
|
|
||||||
|
`lint` tests if your code matches our code conventions, please consult [golangci-lint](https://golangci-lint.run/) and [lint config](https://github.com/openimsdk/openim-sdk-core/blob/main/.golangci.yml)
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> You can use the [Makefile](./Makefile) to run Lint with the command `make lint`.
|
||||||
|
|
||||||
|
|
||||||
|
`unit test` runs all the test in code, and the code coverage should not less than 60 percent, record us in [codeclimate](https://codeclimate.com/github/OpenIMSDK/openim-sdk-core) OpenIM the unit test coverage data.
|
||||||
|
|
||||||
|
|
||||||
|
> **Note**
|
||||||
|
>
|
||||||
|
> We use the [Makefile](./Makefile) utility, `make tese` to run the unit tests, and the `make cover` utility to check the unit test coverage.
|
||||||
|
|
||||||
|
Try your best to keep every function has been tested, it keeps the function behaves as intended.
|
||||||
|
|
||||||
|
#### Docs Contribution
|
||||||
|
|
||||||
|
**The documentation for OpenIM includes:**
|
||||||
|
|
||||||
|
+ [README.md](https://github.com/openimsdk/openim-sdk-core/blob/main/README.md): This file includes the basic information and instructions for getting started with OpenIM.
|
||||||
|
+ [README_zh-CN.md](https://github.com/openimsdk/openim-sdk-core/blob/main/README_zh-CN.md): This file includes the basic information and instructions for getting started with OpenIM in Chinese.
|
||||||
|
+ [CONTRIBUTING.md](https://github.com/openimsdk/openim-sdk-core/blob/main/CONTRIBUTING.md): This file contains guidelines for contributing to OpenIM's codebase, such as how to submit issues, pull requests, and code reviews.
|
||||||
|
+ [Official Documentation](https://doc.rentsoft.cn): This is the official documentation for OpenIM, which includes comprehensive information on all of its features, configuration options, and troubleshooting tips.
|
||||||
|
|
||||||
|
**Please obey the following rules to better format the docs, which would greatly improve the reading experience.**
|
||||||
|
|
||||||
|
1. Please do not use Chinese punctuations in English docs, and vice versa.
|
||||||
|
2. Please use upper case letters where applicable, like the first letter of sentences / headings, etc.
|
||||||
|
3. Please specify a language for each Markdown code blocks, unless there's no associated languages.
|
||||||
|
4. Please insert a whitespace between Chinese and English words.
|
||||||
|
5. Please use the correct case for technical terms, such as using `HTTP` instead of http, `MySQL` rather than mysql, `Kubernetes` instead of kubernetes, etc.
|
||||||
|
6. Please check if there's any typos in the docs before submitting PRs.
|
||||||
|
|
||||||
|
**Markfile Lint:**
|
||||||
|
We integrated in the CICD actions [markdownlint](https://github.com/markdownlint/markdownlint), it detects Markfile specification.
|
||||||
|
> **Note**
|
||||||
|
> We recommend reading [markdownlint rules](https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md), This document contains a description of all rules, what they are checking for, as well as an examples of documents that break the rule and corrected versions of the examples.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Engage to help anything
|
||||||
|
|
||||||
|
We choose GitHub as the primary place for OpenIM to collaborate. So the latest updates of OpenIM are always here. Although contributions via **PR** is an explicit way to help, we still call for any other ways.
|
||||||
|
|
||||||
|
+ reply to other's [issues](https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) if you could;
|
||||||
|
+ help solve other user's problems;
|
||||||
|
+ help review other's [PR](https://github.com/openimsdk/openim-sdk-core/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc) design;
|
||||||
|
+ discuss about OpenIM to make things clearer;
|
||||||
|
+ advocate [OpenIM](https://google.com/search?q=OpenIM) technology beyond GitHub;
|
||||||
|
+ write blogs on OpenIM and so on.
|
||||||
|
|
||||||
|
In a word, **ANY HELP IS CONTRIBUTION.**
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Release version
|
||||||
|
|
||||||
|
Releases of OpenIM are done using [Release Please](https://github.com/googleapis/release-please) and [GoReleaser](https://goreleaser.com/). The workflow looks like this:
|
||||||
|
|
||||||
|
🎯 **A PR is merged to the `main` branch:**
|
||||||
|
|
||||||
|
+ Release please is triggered, creates or updates a new release PR
|
||||||
|
+ This is done with every merge to main, the current release PR is updated every time
|
||||||
|
|
||||||
|
🎯 **Merging the 'release please' PR to `main`:**
|
||||||
|
|
||||||
|
+ Release please is triggered, creates a new release and updates the changelog based on the commit messages
|
||||||
|
+ GoReleaser is triggered, builds the binaries and attaches them to the release
|
||||||
|
+ Containers are created and pushed to the container registry
|
||||||
|
|
||||||
|
With the next relevant merge, a new release PR will be created and the process starts again
|
||||||
|
|
||||||
|
👀 **Manually setting the version:**
|
||||||
|
|
||||||
|
If you want to manually set the version, you can create a PR with an empty commit message that contains the version number in the commit message. For example:
|
||||||
|
|
||||||
|
Such a commit can get produced as follows:
|
||||||
|
|
||||||
|
````bash
|
||||||
|
❯ git commit --allow-empty -m "chore: release 0.0.3" -m "Release-As: 0.0.3
|
||||||
|
````
|
||||||
|
|
||||||
|
|
||||||
|
## Contact Us
|
||||||
|
|
||||||
|
We value close connections with our users, developers, and contributors here at openim-sdk-core. 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 way 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 openim-sdk-core. You can ask technical questions, seek help, or share your experiences with other users of openim-sdk-core.
|
||||||
|
|
||||||
|
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 [👀 openim-sdk-core 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 openim-sdk-core 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 openim-sdk-core. We will process your request as soon as possible.
|
||||||
|
|
||||||
|
Whether you're looking to join our community or have any questions or suggestions, we welcome you to get in touch with us.
|
||||||
201
go/chao-sdk-core/LICENSE
Normal file
201
go/chao-sdk-core/LICENSE
Normal file
@ -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.
|
||||||
515
go/chao-sdk-core/Makefile
Normal file
515
go/chao-sdk-core/Makefile
Normal file
@ -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
|
||||||
144
go/chao-sdk-core/README.md
Normal file
144
go/chao-sdk-core/README.md
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
<h1 align="center" style="border-bottom: none">
|
||||||
|
<b>
|
||||||
|
<a href="https://doc.rentsoft.cn/">openim-sdk-core</a><br>
|
||||||
|
</b>
|
||||||
|
</h1>
|
||||||
|
<h3 align="center" style="border-bottom: none">
|
||||||
|
⭐️ Used in IOS, Android, PC and other platforms ⭐️ <br>
|
||||||
|
<h3>
|
||||||
|
|
||||||
|
<p align=center>
|
||||||
|
<a href="https://goreportcard.com/report/github.com/openimsdk/openim-sdk-core"><img src="https://goreportcard.com/badge/github.com/openimsdk/openim-sdk-core" alt="A+"></a>
|
||||||
|
<a href="https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22"><img src="https://img.shields.io/github/issues/OpenIMSDK/Open-IM-Server/good%20first%20issue?logo=%22github%22" alt="good first"></a>
|
||||||
|
<a href="https://github.com/openimsdk/openim-sdk-core"><img src="https://img.shields.io/github/stars/OpenIMSDK/openim-sdk-core.svg?style=flat&logo=github&colorB=deeppink&label=stars"></a>
|
||||||
|
<a href="https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg"><img src="https://img.shields.io/badge/Slack-100%2B-blueviolet?logo=slack&logoColor=white"></a>
|
||||||
|
<a href="https://github.com/openimsdk/openim-sdk-core/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green"></a>
|
||||||
|
<a href="https://golang.org/"><img src="https://img.shields.io/badge/Language-Go-blue.svg"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="./README.md"><b>English</b></a> •
|
||||||
|
<a href="./README_zh-CN.md"><b>中文</b></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
|
## 🧩 Awesome features
|
||||||
|
|
||||||
|
OpenIM-SDK-core is a core SDK of OpenIM.
|
||||||
|
|
||||||
|
1. Manage WebSocket long connections, responsible for creating, closing and reconnecting connections.
|
||||||
|
2. Encoding and decoding. Encode and decode messages in binary format to achieve cross-language compatibility.
|
||||||
|
3. Implement basic protocols of OpenIM, such as login, push, etc.
|
||||||
|
4. Provide an event handling mechanism to convert received messages into corresponding events and pass them to upper layer applications for processing.
|
||||||
|
5. Cache management. Manage user, group, blacklists, and other cache information.
|
||||||
|
6. Provide basic IM function APIs such as sending messages, creating groups, etc. Hide the underlying implementation details from the upper layer application.
|
||||||
|
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
> **Note**: You can get started quickly with openim-sdk-core.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Work with Makefile</summary>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ make help # show help
|
||||||
|
❯ make build # build binary
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>Work with actions</summary>
|
||||||
|
|
||||||
|
Actions provide handling of PR and issue.
|
||||||
|
We used the bot @kubbot, It can detect issues in Chinese and translate them to English, and you can interact with it using the command `/comment`.
|
||||||
|
|
||||||
|
Comment in an issue:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ /intive
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>Work with Tools</summary>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ make tools
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
<details>
|
||||||
|
<summary>Work with Docker</summary>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ make deploy
|
||||||
|
```
|
||||||
|
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
## Contributing & Development
|
||||||
|
|
||||||
|
OpenIM Our goal is to build a top-level open source community. We have a set of standards, in the [Community repository](https://github.com/openimsdk/community).
|
||||||
|
|
||||||
|
If you'd like to contribute to this openim-sdk-core repository, please read our [contributor documentation](https://github.com/openimsdk/openim-sdk-core/blob/main/CONTRIBUTING.md).
|
||||||
|
|
||||||
|
## community meeting
|
||||||
|
|
||||||
|
We welcome everyone to join us and contribute to openim-sdk-core, whether you are new to open source or professional. We are committed to promoting an open source culture, so we offer community members neighborhood prizes and reward money in recognition of their contributions. We believe that by working together, we can build a strong community and make valuable open source tools and resources available to more people. So if you are interested in openim-sdk-core, please join our community and start contributing your ideas and skills!
|
||||||
|
|
||||||
|
We take notes of each [biweekly meeting](https://github.com/openimsdk/Open-IM-Server/issues/381) in [GitHub discussions](https://github.com/openimsdk/Open-IM-Server/discussions/categories/meeting), and our minutes are written in [Google Docs](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
|
||||||
|
|
||||||
|
openim-sdk-core maintains a [public roadmap](https://github.com/openimsdk/community/tree/main/roadmaps). It gives a a high-level view of the main priorities for the project, the maturity of different features and projects, and how to influence the project direction.
|
||||||
|
|
||||||
|
## about OpenIM
|
||||||
|
|
||||||
|
### common
|
||||||
|
|
||||||
|
+ https://github.com/openimsdk/automation: OpenIM Automation, cicd, and actions, Robotics.
|
||||||
|
+ https://github.com/openimsdk/community: Community Management for OpenIM
|
||||||
|
|
||||||
|
### OpenIM **Links**
|
||||||
|
|
||||||
|
Contains some common parts of the OpenIM community.
|
||||||
|
|
||||||
|
+ https://github.com/openimsdk/automation: OpenIM Automation, cicd, and actions, Robotics.
|
||||||
|
+ https://github.com/openimsdk/openim-sdk-core: The IMSDK implemented by golang can be used in IOS, Android, PC and other platforms.
|
||||||
|
+ https://github.com/openimsdk/openim-sdk-core: Instant messaging IM server.
|
||||||
|
+ https://github.com/openimsdk/community: Community Management for OpenIM.
|
||||||
|
|
||||||
|
### SDKs
|
||||||
|
|
||||||
|
+ [openim-sdk-core](https://github.com/openimsdk/openim-sdk-core): A cross-platform SDK implemented in golang that can be used in IOS, Android, PC, and other platforms.
|
||||||
|
+ [Open-IM-SDK-iOS](https://github.com/openimsdk/Open-IM-SDK-iOS): An iOS SDK generated based on openim-sdk-core, available for developers to reference.
|
||||||
|
+ [Open-IM-SDK-Android](https://github.com/openimsdk/Open-IM-SDK-Android): An Android SDK generated based on openim-sdk-core, available for developers to reference.
|
||||||
|
+ [Open-IM-SDK-Flutter](https://github.com/openimsdk/Open-IM-SDK-Flutter): A Flutter SDK generated based on Open-IM-SDK-iOS and Open-IM-SDK-Android, available for developers to reference.
|
||||||
|
+ [Open-IM-SDK-Uniapp](https://github.com/openimsdk/Open-IM-SDK-Uniapp): A uni-app SDK generated based on Open-IM-SDK-iOS and Open-IM-SDK-Android, available for developers to reference.
|
||||||
|
|
||||||
|
### Demos
|
||||||
|
|
||||||
|
+ [Open-IM-iOS-Demo](https://github.com/openimsdk/Open-IM-iOS-Demo): An iOS demo based on Open-IM-SDK-iOS, available for developers to reference.
|
||||||
|
+ [Open-IM-Android-Demo](https://github.com/openimsdk/Open-IM-Android-Demo): An Android demo based on Open-IM-SDK-Android, available for developers to reference.
|
||||||
|
+ [Open-IM-Flutter-Demo](https://github.com/openimsdk/Open-IM-Flutter-Demo): A Flutter demo based on Open-IM-SDK-Flutter, available for developers to reference.
|
||||||
|
|
||||||
|
## Used By
|
||||||
|
|
||||||
|
OpenIM is used by the following companies ,let's write it down in [ADOPTER](https://github.com/openimsdk/community/blob/main/ADOPTERS.md).
|
||||||
|
|
||||||
|
Please leave your use cases in the comments [here](https://github.com/openimsdk/Open-IM-Server/issues/379).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[openim-sdk-core](https://github.com/openimsdk/openim-sdk-core) is licensed under the Apache License, Version 2.0. See [LICENSE](https://github.com/openimsdk/openim-sdk-core/tree/main/LICENSE) for the full license text.
|
||||||
|
|
||||||
|
## Thanks to our contributors!
|
||||||
|
|
||||||
|
<a href="https://github.com/openimsdk/openim-sdk-core/graphs/contributors">
|
||||||
|
<img src="https://contrib.rocks/image?repo=OpenIMSDK/openim-sdk-core" />
|
||||||
|
</a>
|
||||||
26
go/chao-sdk-core/README_zh-CN.md
Normal file
26
go/chao-sdk-core/README_zh-CN.md
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<h1 align="center" style="border-bottom: none">
|
||||||
|
<b>
|
||||||
|
<a href="https://doc.rentsoft.cn/">openim-sdk-core</a><br>
|
||||||
|
</b>
|
||||||
|
</h1>
|
||||||
|
<h3 align="center" style="border-bottom: none">
|
||||||
|
⭐️ Used in IOS, Android, PC and other platforms ⭐️ <br>
|
||||||
|
<h3>
|
||||||
|
|
||||||
|
<p align=center>
|
||||||
|
<a href="https://goreportcard.com/report/github.com/openimsdk/openim-sdk-core"><img src="https://goreportcard.com/badge/github.com/openimsdk/openim-sdk-core" alt="A+"></a>
|
||||||
|
<a href="https://github.com/openimsdk/openim-sdk-core/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22"><img src="https://img.shields.io/github/issues/OpenIMSDK/Open-IM-Server/good%20first%20issue?logo=%22github%22" alt="good first"></a>
|
||||||
|
<a href="https://github.com/openimsdk/openim-sdk-core"><img src="https://img.shields.io/github/stars/OpenIMSDK/openim-sdk-core.svg?style=flat&logo=github&colorB=deeppink&label=stars"></a>
|
||||||
|
<a href="https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg"><img src="https://img.shields.io/badge/Slack-100%2B-blueviolet?logo=slack&logoColor=white"></a>
|
||||||
|
<a href="https://github.com/openimsdk/openim-sdk-core/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green"></a>
|
||||||
|
<a href="https://golang.org/"><img src="https://img.shields.io/badge/Language-Go-blue.svg"></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<a href="./README.md"><b>English</b></a> •
|
||||||
|
<a href="./README_zh-CN.md"><b>中文</b></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</p>
|
||||||
234
go/chao-sdk-core/cmd/gordon_main.go
Normal file
234
go/chao-sdk-core/cmd/gordon_main.go
Normal file
@ -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
|
||||||
61
go/chao-sdk-core/cmd/main.go
Normal file
61
go/chao-sdk-core/cmd/main.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
25
go/chao-sdk-core/cmd/online_open_im.go
Normal file
25
go/chao-sdk-core/cmd/online_open_im.go
Normal file
@ -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 {}
|
||||||
|
}
|
||||||
67
go/chao-sdk-core/cmd/parse.go
Normal file
67
go/chao-sdk-core/cmd/parse.go
Normal file
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
38
go/chao-sdk-core/cmd/press_open_im.go
Normal file
38
go/chao-sdk-core/cmd/press_open_im.go
Normal file
@ -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 {}
|
||||||
|
}
|
||||||
38
go/chao-sdk-core/cmd/reliability_open_im.go
Normal file
38
go/chao-sdk-core/cmd/reliability_open_im.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
192
go/chao-sdk-core/cmd/sk_main.go
Normal file
192
go/chao-sdk-core/cmd/sk_main.go
Normal file
@ -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
|
||||||
74
go/chao-sdk-core/docs/.generated_docs
Normal file
74
go/chao-sdk-core/docs/.generated_docs
Normal file
@ -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
|
||||||
1
go/chao-sdk-core/docs/CODEOWNERS
Normal file
1
go/chao-sdk-core/docs/CODEOWNERS
Normal file
@ -0,0 +1 @@
|
|||||||
|
* @openimsdk/go-code-review
|
||||||
129
go/chao-sdk-core/docs/contrib/cicd-actions.md
Normal file
129
go/chao-sdk-core/docs/contrib/cicd-actions.md
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
# Continuous Integration and Automation
|
||||||
|
|
||||||
|
Every change on the OpenIM repository, either made through a pull request or direct push, triggers the continuous integration pipelines defined within the same repository. Needless to say, all the OpenIM contributions can be merged until all the checks pass (AKA having green builds).
|
||||||
|
|
||||||
|
- [Continuous Integration and Automation](#continuous-integration-and-automation)
|
||||||
|
- [CI Platforms](#ci-platforms)
|
||||||
|
- [GitHub Actions](#github-actions)
|
||||||
|
- [Running locally](#running-locally)
|
||||||
|
|
||||||
|
## CI Platforms
|
||||||
|
|
||||||
|
Currently, there are two different platforms involved in running the CI processes:
|
||||||
|
|
||||||
|
- GitHub actions
|
||||||
|
- Drone pipelines on CNCF infrastructure
|
||||||
|
|
||||||
|
### GitHub Actions
|
||||||
|
|
||||||
|
All the existing GitHub Actions are defined as YAML files under the `.github/workflows` directory. These can be grouped into:
|
||||||
|
|
||||||
|
- **PR Checks**. These actions run all the required validations upon PR creation and update. Covering the DCO compliance check, `x86_64` test batteries (unit, integration, smoke), and code coverage.
|
||||||
|
- **Repository automation**. Currently, it only covers issues and epic grooming.
|
||||||
|
|
||||||
|
Everything runs on GitHub's provided runners; thus, the tests are limited to run in `x86_64` architectures.
|
||||||
|
|
||||||
|
|
||||||
|
## Running locally
|
||||||
|
|
||||||
|
A contributor should verify their changes locally to speed up the pull request process. Fortunately, all the CI steps can be on local environments, except for the publishing ones, through either of the following methods:
|
||||||
|
|
||||||
|
**User Makefile:**
|
||||||
|
```bash
|
||||||
|
root@PS2023EVRHNCXG:~/workspaces/openim/openim-sdk-core# make help 😊
|
||||||
|
|
||||||
|
Usage: make <TARGETS> <OPTIONS> ...
|
||||||
|
|
||||||
|
Targets:
|
||||||
|
|
||||||
|
all Run tidy, gen, add-copyright, format, lint, cover, build 🚀
|
||||||
|
build Build binaries by default 🛠️
|
||||||
|
multiarch Build binaries for multiple platforms. See option PLATFORMS. 🌍
|
||||||
|
tidy tidy go.mod ✨
|
||||||
|
vendor vendor go.mod 📦
|
||||||
|
style code style -> fmt,vet,lint 💅
|
||||||
|
fmt Run go fmt against code. ✨
|
||||||
|
vet Run go vet against code. ✅
|
||||||
|
lint Check syntax and styling of go sources. ✔️
|
||||||
|
format Gofmt (reformat) package sources (exclude vendor dir if existed). 🔄
|
||||||
|
test Run unit test. 🧪
|
||||||
|
cover Run unit test and get test coverage. 📊
|
||||||
|
updates Check for updates to go.mod dependencies 🆕
|
||||||
|
imports task to automatically handle import packages in Go files using goimports tool 📥
|
||||||
|
clean Remove all files that are created by building. 🗑️
|
||||||
|
image Build docker images for host arch. 🐳
|
||||||
|
image.multiarch Build docker images for multiple platforms. See option PLATFORMS. 🌍🐳
|
||||||
|
push Build docker images for host arch and push images to registry. 📤🐳
|
||||||
|
push.multiarch Build docker images for multiple platforms and push images to registry. 🌍📤🐳
|
||||||
|
tools Install dependent tools. 🧰
|
||||||
|
gen Generate all necessary files. 🧩
|
||||||
|
swagger Generate swagger document. 📖
|
||||||
|
serve-swagger Serve swagger spec and docs. 🚀📚
|
||||||
|
verify-copyright Verify the license headers for all files. ✅
|
||||||
|
add-copyright Add copyright ensure source code files have license headers. 📄
|
||||||
|
release release the project 🎉
|
||||||
|
help Show this help info. ℹ️
|
||||||
|
help-all Show all help details info. ℹ️📚
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
DEBUG Whether or not to generate debug symbols. Default is 0. ❓
|
||||||
|
|
||||||
|
BINS Binaries to build. Default is all binaries under cmd. 🛠️
|
||||||
|
This option is available when using: make {build}(.multiarch) 🧰
|
||||||
|
Example: make build BINS="openim-api openim_cms_api".
|
||||||
|
|
||||||
|
PLATFORMS Platform to build for. Default is linux_arm64 and linux_amd64. 🌍
|
||||||
|
This option is available when using: make {build}.multiarch 🌍
|
||||||
|
Example: make multiarch PLATFORMS="linux_s390x linux_mips64
|
||||||
|
linux_mips64le darwin_amd64 windows_amd64 linux_amd64 linux_arm64".
|
||||||
|
|
||||||
|
V Set to 1 enable verbose build. Default is 0. 📝
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
How to Use Makefile to Help Contributors Build Projects Quickly 😊
|
||||||
|
|
||||||
|
The `make help` command is a handy tool that provides useful information on how to utilize the Makefile effectively. By running this command, contributors will gain insights into various targets and options available for building projects swiftly.
|
||||||
|
|
||||||
|
Here's a breakdown of the targets and options provided by the Makefile:
|
||||||
|
|
||||||
|
**Targets 😃**
|
||||||
|
|
||||||
|
1. `all`: This target runs multiple tasks like `tidy`, `gen`, `add-copyright`, `format`, `lint`, `cover`, and `build`. It ensures comprehensive project building.
|
||||||
|
2. `build`: The primary target that compiles binaries by default. It is particularly useful for creating the necessary executable files.
|
||||||
|
3. `multiarch`: A target that builds binaries for multiple platforms. Contributors can specify the desired platforms using the `PLATFORMS` option.
|
||||||
|
4. `tidy`: This target cleans up the `go.mod` file, ensuring its consistency.
|
||||||
|
5. `vendor`: A target that updates the project dependencies based on the `go.mod` file.
|
||||||
|
6. `style`: Checks the code style using tools like `fmt`, `vet`, and `lint`. It ensures a consistent coding style throughout the project.
|
||||||
|
7. `fmt`: Formats the code using the `go fmt` command, ensuring proper indentation and formatting.
|
||||||
|
8. `vet`: Runs the `go vet` command to identify common errors in the code.
|
||||||
|
9. `lint`: Validates the syntax and styling of Go source files using a linter.
|
||||||
|
10. `format`: Reformats the package sources using `gofmt`. It excludes the vendor directory if it exists.
|
||||||
|
11. `test`: Executes unit tests to ensure the functionality and stability of the code.
|
||||||
|
12. `cover`: Performs unit tests and calculates the test coverage of the code.
|
||||||
|
13. `updates`: Checks for updates to the project's dependencies specified in the `go.mod` file.
|
||||||
|
14. `imports`: Automatically handles import packages in Go files using the `goimports` tool.
|
||||||
|
15. `clean`: Removes all files generated during the build process, effectively cleaning up the project directory.
|
||||||
|
16. `image`: Builds Docker images for the host architecture.
|
||||||
|
17. `image.multiarch`: Similar to the `image` target, but it builds Docker images for multiple platforms. Contributors can specify the desired platforms using the `PLATFORMS` option.
|
||||||
|
18. `push`: Builds Docker images for the host architecture and pushes them to a registry.
|
||||||
|
19. `push.multiarch`: Builds Docker images for multiple platforms and pushes them to a registry. Contributors can specify the desired platforms using the `PLATFORMS` option.
|
||||||
|
20. `tools`: Installs the necessary tools or dependencies required by the project.
|
||||||
|
21. `gen`: Generates all the required files automatically.
|
||||||
|
22. `swagger`: Generates the swagger document for the project.
|
||||||
|
23. `serve-swagger`: Serves the swagger specification and documentation.
|
||||||
|
24. `verify-copyright`: Verifies the license headers for all project files.
|
||||||
|
25. `add-copyright`: Adds copyright headers to the source code files.
|
||||||
|
26. `release`: Releases the project, presumably for distribution.
|
||||||
|
27. `help`: Displays information about available targets and options.
|
||||||
|
28. `help-all`: Shows detailed information about all available targets and options.
|
||||||
|
|
||||||
|
**Options 😄**
|
||||||
|
|
||||||
|
1. `DEBUG`: A boolean option that determines whether or not to generate debug symbols. The default value is 0 (false).
|
||||||
|
2. `BINS`: Specifies the binaries to build. By default, it builds all binaries under the `cmd` directory. Contributors can provide a list of specific binaries using this option.
|
||||||
|
3. `PLATFORMS`: Specifies the platforms to build for. The default platforms are `linux_arm64` and `linux_amd64`. Contributors can specify multiple platforms by providing a space-separated list of platform names.
|
||||||
|
4. `V`: A boolean option that enables verbose build output when set to 1 (true). The default value is 0 (false).
|
||||||
|
|
||||||
|
With these targets and options in place, contributors can efficiently build projects using the Makefile. Happy coding! 🚀😊
|
||||||
38
go/chao-sdk-core/docs/contrib/code_conventions.md
Normal file
38
go/chao-sdk-core/docs/contrib/code_conventions.md
Normal file
@ -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.
|
||||||
80
go/chao-sdk-core/docs/contrib/development.md
Normal file
80
go/chao-sdk-core/docs/contrib/development.md
Normal file
@ -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.
|
||||||
102
go/chao-sdk-core/docs/contrib/git_workflow.md
Normal file
102
go/chao-sdk-core/docs/contrib/git_workflow.md
Normal file
@ -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.
|
||||||
43
go/chao-sdk-core/go.mod
Normal file
43
go/chao-sdk-core/go.mod
Normal file
@ -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
|
||||||
77
go/chao-sdk-core/go.sum
Normal file
77
go/chao-sdk-core/go.sum
Normal file
@ -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
go/chao-sdk-core/identifier.sqlite
Normal file
0
go/chao-sdk-core/identifier.sqlite
Normal file
49
go/chao-sdk-core/internal/business/business.go
Normal file
49
go/chao-sdk-core/internal/business/business.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
23
go/chao-sdk-core/internal/business/open_im_sdk_business.go
Normal file
23
go/chao-sdk-core/internal/business/open_im_sdk_business.go
Normal file
@ -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
|
||||||
|
}
|
||||||
72
go/chao-sdk-core/internal/cache/cahe.go
vendored
Normal file
72
go/chao-sdk-core/internal/cache/cahe.go
vendored
Normal file
@ -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
|
||||||
|
}
|
||||||
33
go/chao-sdk-core/internal/common/common.go
Normal file
33
go/chao-sdk-core/internal/common/common.go
Normal file
@ -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
|
||||||
|
}
|
||||||
33
go/chao-sdk-core/internal/common/object_storage.go
Normal file
33
go/chao-sdk-core/internal/common/object_storage.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
1178
go/chao-sdk-core/internal/conversation_msg/conversation.go
Normal file
1178
go/chao-sdk-core/internal/conversation_msg/conversation.go
Normal file
File diff suppressed because it is too large
Load Diff
1064
go/chao-sdk-core/internal/conversation_msg/conversation_msg.go
Normal file
1064
go/chao-sdk-core/internal/conversation_msg/conversation_msg.go
Normal file
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})
|
||||||
|
|
||||||
|
}
|
||||||
57
go/chao-sdk-core/internal/conversation_msg/convert.go
Normal file
57
go/chao-sdk-core/internal/conversation_msg/convert.go
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
492
go/chao-sdk-core/internal/conversation_msg/create_message.go
Normal file
492
go/chao-sdk-core/internal/conversation_msg/create_message.go
Normal file
@ -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
|
||||||
|
}
|
||||||
245
go/chao-sdk-core/internal/conversation_msg/delete.go
Normal file
245
go/chao-sdk-core/internal/conversation_msg/delete.go
Normal file
@ -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}})
|
||||||
|
}
|
||||||
217
go/chao-sdk-core/internal/conversation_msg/entering.go
Normal file
217
go/chao-sdk-core/internal/conversation_msg/entering.go
Normal file
@ -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
|
||||||
|
}
|
||||||
32
go/chao-sdk-core/internal/conversation_msg/image.go
Normal file
32
go/chao-sdk-core/internal/conversation_msg/image.go
Normal file
@ -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
|
||||||
|
}
|
||||||
347
go/chao-sdk-core/internal/conversation_msg/message_check.go
Normal file
347
go/chao-sdk-core/internal/conversation_msg/message_check.go
Normal file
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
124
go/chao-sdk-core/internal/conversation_msg/message_controller.go
Normal file
124
go/chao-sdk-core/internal/conversation_msg/message_controller.go
Normal file
@ -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
|
||||||
|
}
|
||||||
102
go/chao-sdk-core/internal/conversation_msg/progress.go
Normal file
102
go/chao-sdk-core/internal/conversation_msg/progress.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
281
go/chao-sdk-core/internal/conversation_msg/read_drawing.go
Normal file
281
go/chao-sdk-core/internal/conversation_msg/read_drawing.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
203
go/chao-sdk-core/internal/conversation_msg/revoke.go
Normal file
203
go/chao-sdk-core/internal/conversation_msg/revoke.go
Normal file
@ -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
|
||||||
|
}
|
||||||
1180
go/chao-sdk-core/internal/conversation_msg/sdk.go
Normal file
1180
go/chao-sdk-core/internal/conversation_msg/sdk.go
Normal file
File diff suppressed because it is too large
Load Diff
148
go/chao-sdk-core/internal/conversation_msg/sync.go
Normal file
148
go/chao-sdk-core/internal/conversation_msg/sync.go
Normal file
@ -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
|
||||||
|
}
|
||||||
89
go/chao-sdk-core/internal/file/bitmap.go
Normal file
89
go/chao-sdk-core/internal/file/bitmap.go
Normal file
@ -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
|
||||||
|
}
|
||||||
62
go/chao-sdk-core/internal/file/cb.go
Normal file
62
go/chao-sdk-core/internal/file/cb.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
24
go/chao-sdk-core/internal/file/file.go
Normal file
24
go/chao-sdk-core/internal/file/file.go
Normal file
@ -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
|
||||||
|
}
|
||||||
73
go/chao-sdk-core/internal/file/file_default.go
Normal file
73
go/chao-sdk-core/internal/file/file_default.go
Normal file
@ -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()
|
||||||
|
}
|
||||||
156
go/chao-sdk-core/internal/file/file_js.go
Normal file
156
go/chao-sdk-core/internal/file/file_js.go
Normal file
@ -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
|
||||||
|
}
|
||||||
40
go/chao-sdk-core/internal/file/file_test.go
Normal file
40
go/chao-sdk-core/internal/file/file_test.go
Normal file
@ -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)
|
||||||
|
|
||||||
|
}
|
||||||
43
go/chao-sdk-core/internal/file/md5.go
Normal file
43
go/chao-sdk-core/internal/file/md5.go
Normal file
@ -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))
|
||||||
|
}
|
||||||
44
go/chao-sdk-core/internal/file/progress.go
Normal file
44
go/chao-sdk-core/internal/file/progress.go
Normal file
@ -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
|
||||||
|
}
|
||||||
576
go/chao-sdk-core/internal/file/upload.go
Normal file
576
go/chao-sdk-core/internal/file/upload.go
Normal file
@ -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
|
||||||
|
}
|
||||||
71
go/chao-sdk-core/internal/friend/conversion.go
Normal file
71
go/chao-sdk-core/internal/friend/conversion.go
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
199
go/chao-sdk-core/internal/friend/friend.go
Normal file
199
go/chao-sdk-core/internal/friend/friend.go
Normal file
@ -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
|
||||||
|
}
|
||||||
33
go/chao-sdk-core/internal/friend/hash.go
Normal file
33
go/chao-sdk-core/internal/friend/hash.go
Normal file
@ -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[:])
|
||||||
|
}
|
||||||
136
go/chao-sdk-core/internal/friend/notification.go
Normal file
136
go/chao-sdk-core/internal/friend/notification.go
Normal file
@ -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
|
||||||
|
}
|
||||||
344
go/chao-sdk-core/internal/friend/sdk.go
Normal file
344
go/chao-sdk-core/internal/friend/sdk.go
Normal file
@ -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
|
||||||
|
}
|
||||||
180
go/chao-sdk-core/internal/friend/sync.go
Normal file
180
go/chao-sdk-core/internal/friend/sync.go
Normal file
@ -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
|
||||||
|
}
|
||||||
72
go/chao-sdk-core/internal/friend/sync2.go
Normal file
72
go/chao-sdk-core/internal/friend/sync2.go
Normal file
@ -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()
|
||||||
|
}
|
||||||
13
go/chao-sdk-core/internal/friend/sync2_test.go
Normal file
13
go/chao-sdk-core/internal/friend/sync2_test.go
Normal file
@ -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])
|
||||||
|
}
|
||||||
62
go/chao-sdk-core/internal/full/full.go
Normal file
62
go/chao-sdk-core/internal/full/full.go
Normal file
@ -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
|
||||||
|
}
|
||||||
29
go/chao-sdk-core/internal/full/full_model.go
Normal file
29
go/chao-sdk-core/internal/full/full_model.go
Normal file
@ -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...)
|
||||||
|
}
|
||||||
199
go/chao-sdk-core/internal/full/open_im_sdk_full.go
Normal file
199
go/chao-sdk-core/internal/full/open_im_sdk_full.go
Normal file
@ -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
|
||||||
|
}
|
||||||
97
go/chao-sdk-core/internal/group/conversion.go
Normal file
97
go/chao-sdk-core/internal/group/conversion.go
Normal file
@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
343
go/chao-sdk-core/internal/group/group.go
Normal file
343
go/chao-sdk-core/internal/group/group.go
Normal file
@ -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
|
||||||
|
}
|
||||||
245
go/chao-sdk-core/internal/group/notification.go
Normal file
245
go/chao-sdk-core/internal/group/notification.go
Normal file
@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
383
go/chao-sdk-core/internal/group/sdk.go
Normal file
383
go/chao-sdk-core/internal/group/sdk.go
Normal file
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
310
go/chao-sdk-core/internal/group/sync.go
Normal file
310
go/chao-sdk-core/internal/group/sync.go
Normal file
@ -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
|
||||||
|
}
|
||||||
347
go/chao-sdk-core/internal/group/sync2.go
Normal file
347
go/chao-sdk-core/internal/group/sync2.go
Normal file
@ -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()
|
||||||
|
}
|
||||||
1
go/chao-sdk-core/internal/group/sync2_test.go
Normal file
1
go/chao-sdk-core/internal/group/sync2_test.go
Normal file
@ -0,0 +1 @@
|
|||||||
|
package group
|
||||||
265
go/chao-sdk-core/internal/incrversion/option.go
Normal file
265
go/chao-sdk-core/internal/incrversion/option.go
Normal file
@ -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
|
||||||
|
}
|
||||||
61
go/chao-sdk-core/internal/interaction/compressor.go
Normal file
61
go/chao-sdk-core/internal/interaction/compressor.go
Normal file
@ -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
|
||||||
|
}
|
||||||
39
go/chao-sdk-core/internal/interaction/constant.go
Normal file
39
go/chao-sdk-core/internal/interaction/constant.go
Normal file
@ -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
|
||||||
|
)
|
||||||
52
go/chao-sdk-core/internal/interaction/context.go
Normal file
52
go/chao-sdk-core/internal/interaction/context.go
Normal file
@ -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…
x
Reference in New Issue
Block a user