diff --git a/.dockerignore b/.dockerignore index 263798e07..cf6c3cd59 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,9 +5,8 @@ .git # Ignore build artifacts -_output/ logs/ - +_output/ # Ignore non-essential documentation README.md README-zh_CN.md @@ -18,8 +17,6 @@ CHANGELOG/ # Ignore testing and linting configuration .golangci.yml -# Ignore deployment-related files -docker-compose.yaml # Ignore assets assets/ diff --git a/.env b/.env new file mode 100644 index 000000000..92c4d5c41 --- /dev/null +++ b/.env @@ -0,0 +1,13 @@ + +MONGO_IMAGE=mongo:6.0.2 +REDIS_IMAGE=redis:7.0.0 +ZOOKEEPER_IMAGE=bitnami/zookeeper:3.8 +KAFKA_IMAGE=bitnami/kafka:3.5.1 +MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z + + +OPENIM_WEB_FRONT_IMAGE=ghcr.io/openimsdk/openim-web:v3.5.0-docker +OPENIM_ADMIN_FRONT_IMAGE=ghcr.io/openimsdk/openim-admin:toc-base-open-docker.35 + +DATA_DIR=./ + diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..dfdb8b771 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.sh text eol=lf diff --git a/.github/code-language-detector.yml b/.github/code-language-detector.yml index 32a8c1f54..194c2474a 100644 --- a/.github/code-language-detector.yml +++ b/.github/code-language-detector.yml @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +# https://github.com/marketplace/actions/code-language-detector directory: ./ file_types: - .go diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 154386ff5..55ee241d7 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -48,4 +48,4 @@ template: | ## Contributors to this $REPOSITORY release - $CONTRIBUTORS \ No newline at end of file + $CONTRIBUTORS diff --git a/.github/standardizer.yml b/.github/standardizer.yml new file mode 100644 index 000000000..fceb69df1 --- /dev/null +++ b/.github/standardizer.yml @@ -0,0 +1,50 @@ +# https://github.com/marketplace/actions/conformity-checker-for-project +baseConfig: + searchDirectory: "./" + ignoreCase: false + +directoryNaming: + allowHyphens: true + allowUnderscores: false + mustBeLowercase: true + +fileNaming: + allowHyphens: true + allowUnderscores: true + mustBeLowercase: true + +ignoreFormats: + - "\\.log$" + - "\\.env$" + - "README\\.md$" + - "_test\\.go$" + - "\\.md$" + - _test\\.txt$ + - LICENSE + - Dockerfile + - CODEOWNERS + - Makefile + +ignoreDirectories: + - "vendor" + - ".git" + - "deployments" + - "node_modules" + - "logs" + - "CHANGELOG" + - "components" + - "_output" + - "tools/openim-web" + - "CHANGELOG" + - "examples/Test_directory" + - test/testdata + +fileTypeSpecificNaming: + ".yaml": + allowHyphens: true + allowUnderscores: false + mustBeLowercase: true + ".go": + allowHyphens: false + allowUnderscores: true + mustBeLowercase: true \ No newline at end of file diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml index f98221e41..62df69ed5 100644 --- a/.github/workflows/e2e-test.yml +++ b/.github/workflows/e2e-test.yml @@ -73,14 +73,22 @@ jobs: - name: Docker Operations run: | - sudo make init sudo docker compose up -d + sudo bash bootstrap.sh + sudo mage sudo sleep 20 - name: Module Operations run: | - sudo make tidy - sudo make tools.verify.go-gitlint + sudo go mod tidy + echo "===========> Verifying go-gitlint is installed" + if [ ! -f ./_output/tools/go-gitlint ]; then + export GOBIN=$(pwd)/_output/tools + echo "===========> Installing The default installation path is /home/ubuntu/DF/open-im-server/_output/tools/go-gitlint" + sudo go install github.com/marmotedu/go-gitlint/cmd/go-gitlint@latest + echo "===========> go-gitlint is installed in /home/ubuntu/DF/open-im-server/_output/tools/go-gitlint" + fi + - name: Build, Start(make build && make start) run: | @@ -90,7 +98,8 @@ jobs: run: | sudo ./scripts/install/install.sh -s - - name: Exec OpenIM API test (make test-api) +# - name: Exec OpenIM API test (make test-api) + - name: Exec OpenIM test (make test) run: | mkdir -p ./tmp touch ./tmp/test.md @@ -98,23 +107,27 @@ jobs: echo "## OpenIM API Test" >> ./tmp/test.md echo "
Command Output for OpenIM API Test" >> ./tmp/test.md echo "
" >> ./tmp/test.md
-        sudo make test-api | tee -a ./tmp/test.md
+        echo "===========> Run api test"
+        ./scripts/install/test.sh
+        echo "===========> Run api test" >> ./tmp/test.md
+        ./scripts/install/test.sh >> ./tmp/test.md
         echo "
" >> ./tmp/test.md echo "
" >> ./tmp/test.md - sudo make test-api + echo "===========> Run api test" + ./scripts/install/test.sh - - name: Exec OpenIM E2E Test (make test-e2e) - run: | - echo "" >> ./tmp/test.md - echo "## OpenIM E2E Test" >> ./tmp/test.md - echo "
Command Output for OpenIM E2E Test" >> ./tmp/test.md - echo "
" >> ./tmp/test.md
-        sudo make test-e2e | tee -a ./tmp/test.md
-        echo "
" >> ./tmp/test.md - echo "
" >> ./tmp/test.md + # - name: Exec OpenIM E2E Test (make test-e2e) + # run: | + # echo "" >> ./tmp/test.md + # echo "## OpenIM E2E Test" >> ./tmp/test.md + # echo "
Command Output for OpenIM E2E Test" >> ./tmp/test.md + # echo "
" >> ./tmp/test.md
+ #       sudo make test-e2e | tee -a ./tmp/test.md
+ #       echo "
" >> ./tmp/test.md + # echo "
" >> ./tmp/test.md - sudo make test-e2e + # sudo make test-e2e - name: Comment PR with file uses: thollander/actions-comment-pull-request@v2 diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index 31e491d6b..64bd498c5 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -29,7 +29,13 @@ jobs: go-version: '1.21' cache: false - name: OpenIM Scripts Verification(make verify) - run: sudo make verify + run: | + cd scripts + for script in verify-*; do + if [ -x "$script" ]; then + ./"$script" + fi + done - name: golangci-lint uses: golangci/golangci-lint-action@v4.0.0 with: diff --git a/.github/workflows/openimci.yml b/.github/workflows/openimci.yml index d10033a1c..033868a2e 100644 --- a/.github/workflows/openimci.yml +++ b/.github/workflows/openimci.yml @@ -1,3 +1,4 @@ + # Copyright © 2023 OpenIM open source community. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -11,8 +12,7 @@ # 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 +name: OpenIM CI Auto Build on: push: @@ -37,263 +37,154 @@ on: - "**.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: - contents: write - pull-requests: write - environment: - name: openim - strategy: - matrix: - go_version: ["1.19","1.20","1.21"] - os: [ubuntu-latest] - steps: - - name: Setup - uses: actions/checkout@v4 - - - name: Set up Go ${{ matrix.go_version }} - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go_version }} - id: go - - - name: Install Task - uses: arduino/setup-task@v1 - with: - version: '3.x' # If available, use the latest major version that's compatible - repo-token: ${{ secrets.GITHUB_TOKEN }} - - - name: Module Operations - run: | - sudo make tidy - sudo make tools.verify.go-gitlint - - - name: Format Code - run: sudo make format - continue-on-error: true - - - name: Generate Files - run: make gen - continue-on-error: true - - - name: Build Source - run: sudo make build - - - name: Build multiarch PLATFORMS - if: startsWith(github.ref, 'refs/heads/release-') - run: | - sudo make multiarch - - - name: Cleanup Build - run: sudo make clean - - - name: Set Current Directory - id: set_directory - run: echo "::set-output name=directory::$(pwd)" - continue-on-error: true - - - name: Collect and Display Test Coverage - id: collect_coverage - run: | - cd ${{ steps.set_directory.outputs.directory }} - make cover - echo "::set-output name=coverage_file::./_output/tmp/coverage.out" - echo "Test Coverage:" - cat ${{ steps.collect_coverage.outputs.coverage_file }} - continue-on-error: true - - openim-start: - name: Test OpenIM install/start on ${{ matrix.os }}-${{ matrix.arch }} - runs-on: ${{ matrix.os }} - permissions: - contents: write - pull-requests: write - environment: - name: openim - strategy: - matrix: - go_version: ["1.21"] - os: ["ubuntu-latest"] - steps: - - name: Checkout and Install OpenIM - uses: actions/checkout@v4 - - name: Install Task - uses: arduino/setup-task@v1 - with: - version: '3.x' # If available, use the latest major version that's compatible - repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Run OpenIM make install start - run: | - sudo make install - - # - name: Check the OpenIM environment and status - # run: | - # sudo docker images - # sudo docker ps - - name: Check the OpenIM environment and status - if: runner.os == 'Linux' && matrix.arch == 'amd64' - id: docker_info - run: | - sleep 30 - echo "images<> $GITHUB_ENV - sudo docker images >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - echo "containers<> $GITHUB_ENV - sudo docker ps >> $GITHUB_ENV - echo "EOF" >> $GITHUB_ENV - - - name: Comment PR - uses: thollander/actions-comment-pull-request@v2 - if: runner.os == 'Linux' && matrix.arch == 'amd64' - with: - message: | - > [!TIP] - > Run make install to check the status - - ### Docker Images: -
Click to expand docker images - ```bash - ${{ env.images }} - ``` -
- - ### Docker Processes: -
Click to expand docker ps - ```bash - ${{ env.containers }} - ``` -
- GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }} - - execute-scripts: - name: Execute OpenIM Script On ${{ matrix.os }}-${{ matrix.arch }} - runs-on: ${{ matrix.os }} + build-linux: + name: Execute OpenIM Script On Linux + runs-on: ubuntu-latest permissions: - contents: write - pull-requests: write + contents: write + pull-requests: write environment: name: openim strategy: matrix: - go_version: ["1.21"] - os: ["ubuntu-latest", "macos-latest"] arch: [arm64, armv7, amd64] + steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v3 - - name: Set up Go ${{ matrix.go_version }} - uses: actions/setup-go@v5 + - name: Set up Go + uses: actions/setup-go@v4 with: - go-version: ${{ matrix.go_version }} - id: go + go-version: '1.21' - - name: Install Task - uses: arduino/setup-task@v1 - with: - version: '3.x' # If available, use the latest major version that's compatible - repo-token: ${{ secrets.GITHUB_TOKEN }} - - # - name: Install latest Bash (macOS only) - # if: runner.os == 'macOS' && matrix.arch == 'arm64' - # run: | - # /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" - # brew update - - # brew install bash - # brew install gnu-sed - - # echo "/usr/local/bin" >> $GITHUB_PATH - # echo "$(brew --prefix)/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH - # continue-on-error: true - - - name: Set up Docker for Ubuntu - if: runner.os == 'Linux' + - name: Set up Docker for Linux run: | - sudo make init sudo docker compose up -d - sudo sleep 20 - - # - name: Set up Docker for macOS - # if: runner.os == 'macOS' && matrix.arch == 'arm64' - # run: | - # brew install --cask docker - # open /Applications/Docker.app - - # sleep 10 - # docker-compose --version || brew install docker-compose - - # docker-compose up -d - # sleep 20 + sudo sleep 30 # Increased sleep time for better stability + timeout-minutes: 20 # Increased timeout for Docker setup - - name: Module Operations for Ubuntu - if: runner.os == 'Linux' - run: | - sudo make tidy - sudo make tools.verify.go-gitlint - - # - name: Module Operations for macOS - # if: runner.os == 'macOS' - # run: | - # make tidy - # make tools.verify.go-gitlint - - - name: Build, Start, Check Services and Print Logs for Ubuntu - if: runner.os == 'Linux' - run: | - sudo make build - sudo make start - sudo make check - - - name: Restart Services and Print Logs for Ubuntu - if: runner.os == 'Linux' && matrix.arch == 'amd64' - run: | - sudo make restart - sudo make check - - - name: Build, Start, Check Services and Print Logs for macOS - if: runner.os == 'macOS' && matrix.arch == 'arm64' - run: | - make build - openim-test-build-image: - name: Build OpenIM Docker Image - runs-on: ubuntu-latest - permissions: - contents: write - pull-requests: write - environment: - name: openim - steps: - - name: Checkout code - uses: actions/checkout@v4 - - name: Set up Go ${{ matrix.go_version }} - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go_version }} - id: go - - - name: Install Task - uses: arduino/setup-task@v1 - with: - version: '3.x' # If available, use the latest major version that's compatible - repo-token: ${{ secrets.GITHUB_TOKEN }} + - name: init + run: sudo bash bootstrap.sh + timeout-minutes: 20 - - name: Test Docker Build + - name: Build, Start, Check Services and Print Logs for Linux run: | - sudo make init - sudo make image + sudo mage + sudo mage start + sudo mage check + - - name: Get OpenIM Docker Images Status - id: docker_processes + - name: Restart Services and Print Logs run: | - sudo docker images - sudo docker ps + sudo mage stop + sudo mage start + sudo mage check + + +# build-mac: +# name: Execute OpenIM Script On macOS +# runs-on: macos-latest +# permissions: +# contents: write +# pull-requests: write +# environment: +# name: openim +# strategy: +# matrix: +# arch: [arm64, armv7, amd64] +# +# steps: +# - uses: actions/checkout@v3 + +# - name: Set up Go +# uses: actions/setup-go@v4 +# with: +# go-version: '1.21' + + +# while ! docker system info > /dev/null 2>&1; do +# echo "Waiting for Docker to start..." +# sleep 10 # Increased delay to ensure Docker starts properly +# done + +# - name: Install Docker +# run: | +# brew install docker +# brew install docker-compose +# sleep 10 +# docker-compose up -d +# sleep 30 +# timeout-minutes: 20 +# + +# - name: init +# run: sudo bash bootstrap.sh +# timeout-minutes: 20 + +# - name: Build, Start, Check Services and Print Logs for Linux +# run: | +# sudo mage +# sudo mage start +# sudo mage check + +# - name: Restart Services and Print Logs +# run: | +# sudo mage stop +# sudo mage start +# sudo mage check + +# build-windows: +# name: Execute OpenIM Script On Windows +# runs-on: windows-latest +# permissions: +# contents: write +# pull-requests: write +# environment: +# name: openim +# strategy: +# matrix: +# arch: [arm64, armv7, amd64] +# +# steps: +# - uses: actions/checkout@v3 + +# - name: Set up Go +# uses: actions/setup-go@v4 +# with: +# go-version: '1.21' + +# - name: Set up Docker for Windows +# run: | +# $images = @("zookeeper", "redis", "kafka") +# foreach ($image in $images) { +# $tag = "$image:latest" +# docker pull $tag | Out-Null +# if ($LASTEXITCODE -ne 0) { +# Write-Host "Skipping $image as it is not available for Windows" +# } else { +# Write-Host "Successfully pulled $image" +# } +# } +# docker compose up -d +# Start-Sleep -Seconds 30 +# timeout-minutes: 20 +# shell: pwsh + +# - name: init +# run: bootstrap.bat +# timeout-minutes: 20 + +# - name: Build, Start, Check Services and Print Logs for Linux +# run: | +# mage +# mage start +# mage check + +# - name: Restart Services and Print Logs +# run: | +# mage stop +# mage start +# mage check diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index c123566f1..f7c5900ce 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -56,7 +56,8 @@ jobs: - name: Generate all necessary files, such as error code files run: | - make generate + make gen.docgo.doc + make gen echo "Generate all necessary files successfully" continue-on-error: true @@ -68,9 +69,9 @@ jobs: echo "Generate all necessary files successfully" continue-on-error: true - - name: Generate Vertions + - name: Generate Versions Including Pre-release Identifiers run: | - latest_tag=$(git describe --tags `git rev-list --tags --max-count=1` | grep -oE '[0-9]+\.[0-9]+\.[0-9]+') + latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`) echo $latest_tag > pkg/common/config/version continue-on-error: true diff --git a/.gitignore b/.gitignore index 5142fe551..fb8d428d2 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,6 @@ deployments/charts/generated-configs/ ### OpenIM Config ### .env config/config.yaml -config/openim.yaml config/alertmanager.yml config/prometheus.yml config/email.tmpl diff --git a/.golangci.yml b/.golangci.yml index c262cfa2f..ae8cea673 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -39,19 +39,19 @@ run: # 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: - - components - - docs - - util - - .*~ - - api/swagger/docs - - server/docs - - components/mnt/config/certs - - logs + # skip-dirs: + # - components + # - docs + # - util + # - .*~ + # - api/swagger/docs + # - server/docs + # - components/mnt/config/certs + # - logs # default is true. Enables skipping of directories: # vendor$, third_party$, testdata$, examples$, Godeps$, builtin$ - skip-dirs-use-default: true + # 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 @@ -59,15 +59,15 @@ run: # 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 - - ".*_test.go" - - "mocks/" - - ".github/" - - "logs/" - - "_output/" - - "components/" + # skip-files: + # - ".*\\.my\\.go$" + # - _test.go + # - ".*_test.go" + # - "mocks/" + # - ".github/" + # - "logs/" + # - "_output/" + # - "components/" # 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 @@ -87,7 +87,7 @@ run: # output configuration options output: # colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number" - format: colored-line-number + # format: colored-line-number # print lines of code with issue, default is true print-issued-lines: true @@ -118,8 +118,7 @@ linters-settings: right-to-left-isolate: true first-strong-isolate: true pop-directional-isolate: true - # checks assignments with too many blank identifiers; default is 2 - max-blank-identifiers: 2 + dupl: # tokens count to trigger issue, 150 by default threshold: 200 @@ -151,6 +150,11 @@ linters-settings: comparison: true exhaustive: + # Program elements to check for exhaustiveness. + # Default: [ switch ] + check: + - switch + - map # check switch statements in generated files also check-generated: false # indicates that switch statements are to be considered exhaustive if a @@ -162,33 +166,34 @@ linters-settings: 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: + + + forbidigo: # # Forbid the following identifiers (identifiers are written using regexp): - # forbid: - # - ^print.*$ - # - 'fmt\.Print.*' - # - fmt.Println.* # too much log noise + forbid: + # - ^print.*$ + - 'fmt\.Print.*' + - fmt.Println.* # too much log noise + - ^unsafe\..*$ + - ^init$ + - ^os.Exit$ + - ^fmt.Print.*$ + - errors.New.*$ + - ^fmt.Println.*$ + - ^panic$ + - painc # - 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 + lines: 220 statements: 80 - 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/open-im-server + 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 @@ -214,9 +219,6 @@ linters-settings: # 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 @@ -245,9 +247,6 @@ linters-settings: 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 @@ -261,15 +260,10 @@ linters-settings: 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 @@ -286,9 +280,12 @@ linters-settings: # list of regexps for excluding particular comment lines from check exclude: # example: exclude comments which contain numbers - # - '[0-9]+' + - '[0-9]+' + - 'func\s+\w+' + - 'FIXME:' + - '.*func.*' # check that each sentence starts with a capital letter - capital: false + capital: true 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 @@ -311,15 +308,15 @@ linters-settings: # by default extra-rules: false - goheader: - values: - const: + # goheader: + # values: + # const: # define here const type values in format k:v, for example: # COMPANY: MY COMPANY - regexp: + # regexp: # define here regexp type values, for example # AUTHOR: .*@mycompany\.com - template: # |- + # 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. # @@ -337,21 +334,45 @@ linters-settings: # 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: + # 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/open-im-server 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.* + # List of enabled checks, see https://github.com/tommy-muehle/go-mnd/#checks for description. + # Default: ["argument", "case", "condition", "operation", "return", "assign"] + checks: + - argument + - case + - condition + - operation + - return + - assign + # List of numbers to exclude from analysis. + # The numbers should be written as string. + # Values always ignored: "1", "1.0", "0" and "0.0" + # Default: [] + ignored-numbers: + - '0666' + - '0755' + - '42' + # List of file patterns to exclude from analysis. + # Values always ignored: `.+_test.go` + # Default: [] + ignored-files: + - 'magic1_.+\.go$' + # List of function patterns to exclude from analysis. + # Following functions are always ignored: `time.Date`, + # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`, + # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`. + # Default: [] + ignored-functions: + - '^math\.' + - '^webhook\.StatusText$' gomoddirectives: # Allow local `replace` directives. Default is false. replace-local: true @@ -363,6 +384,7 @@ linters-settings: retract-allow-no-explanation: false # Forbid the use of the `exclude` directives. Default is false. exclude-forbidden: false + gomodguard: allowed: modules: @@ -426,9 +448,6 @@ linters-settings: checks: [ "all" ] govet: - # report about shadowed variables - check-shadowing: false - # settings per analyzer settings: printf: # analyzer name, run `go tool vet help` to see all analyzers @@ -445,15 +464,25 @@ linters-settings: 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" + depguard: + rules: + prevent_unmaintained_packages: + list-mode: lax # allow unless explicitely denied + files: + - $all + - "!$test" + allow: + - $gostd + deny: + - pkg: io/ioutil + desc: "replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil" + - pkg: github.com/OpenIMSDK + desc: "The OpenIM organization has been replaced with lowercase, please do not use uppercase organization name, you will use openimsdk" + - pkg: log + desc: "We have a wrapped log package at openim, we recommend you to use our wrapped log package, https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/logging.md" + - pkg: errors + desc: "We have a wrapped errors package at openim, we recommend you to use our wrapped errors package, https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/error-code.md" importas: # if set to `true`, force to use alias. @@ -463,6 +492,8 @@ linters-settings: # using `servingv1` alias for `knative.dev/serving/pkg/apis/serving/v1` package - pkg: knative.dev/serving/pkg/apis/serving/v1 alias: servingv1 + - pkg: gopkg.in/yaml.v2 + alias: yaml # using `autoscalingv1alpha1` alias for `knative.dev/serving/pkg/apis/autoscaling/v1alpha1` package - pkg: knative.dev/serving/pkg/apis/autoscaling/v1alpha1 alias: autoscalingv1alpha1 @@ -471,8 +502,6 @@ linters-settings: # 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. @@ -503,9 +532,6 @@ linters-settings: line-length: 250 # 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. @@ -538,8 +564,6 @@ linters-settings: 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. @@ -563,14 +587,13 @@ linters-settings: strict: false # Please refer to https://github.com/yeya24/promlinter#usage for detailed usage. disabled-linters: - # - "Help" - # - "MetricUnits" - # - "Counter" - # - "HistogramSummaryReserved" - # - "MetricTypeInName" - # - "ReservedChars" - # - "CamelCase" - # - "lintUnitAbbreviations" + - "Help" + - "MetricUnits" + - "Counter" + - "HistogramSummaryReserved" + - "MetricTypeInName" + - "ReservedChars" + - "CamelCase" predeclared: # comma-separated list of predeclared identifiers to not report on @@ -580,6 +603,7 @@ linters-settings: rowserrcheck: packages: - github.com/jmoiron/sqlx + revive: # see https://github.com/mgechev/revive#available-rules for details. ignore-generated-header: true @@ -587,15 +611,27 @@ linters-settings: rules: - name: indent-error-flow severity: warning + - name: exported + severity: warning + - name: var-naming + arguments: [ [ "OpenIM"] ] + # arguments: [ ["ID", "HTTP", "URL", "URI", "API", "APIKey", "Token", "TokenID", "TokenSecret", "TokenKey", "TokenSecret", "JWT", "JWTToken", "JWTTokenID", "JWTTokenSecret", "JWTTokenKey", "JWTTokenSecret", "OAuth", "OAuthToken", "RPC" ] ] + - name: atomic + - name: line-length-limit + severity: error + arguments: [200] + - name: unhandled-error + arguments : ["fmt.Printf", "myFunction"] + staticcheck: # Select the Go version to target. The default is '1.13'. - go: "1.16" + go: "1.20" # https://staticcheck.io/docs/options#checks checks: [ "all" ] stylecheck: # Select the Go version to target. The default is '1.13'. - go: "1.16" + go: "1.20" # https://staticcheck.io/docs/options#checks checks: [ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022" ] @@ -607,7 +643,6 @@ linters-settings: # 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: @@ -653,12 +688,11 @@ linters-settings: # 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: + # 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 @@ -672,6 +706,7 @@ linters-settings: - errors.New( - errors.Unwrap( - .Wrap( + - .WrapMsg( - .Wrapf( - .WithMessage( - .WithMessagef( @@ -679,6 +714,8 @@ linters-settings: ignorePackageGlobs: - encoding/* - github.com/pkg/* + - github.com/openimsdk/* + - github.com/OpenIMSDK/* wsl: # If true append is only allowed to be cuddled if appending value is @@ -705,7 +742,6 @@ linters-settings: # 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: @@ -731,16 +767,21 @@ linters: - errcheck - decorder - ineffassign + - forbidigo - revive - reassign - tparallel - unconvert + - fieldalignment - dupl - dupword - errname - gci - - goheader + - exhaustive + - gocritic - goprintffuncname + - gomnd + - goconst - gosec - misspell # Spelling mistakes - staticcheck # Static analysis @@ -761,6 +802,7 @@ issues: exclude: - tools/.* - test/.* + - components/* - third_party/.* # Excluding configuration per-path, per-linter, per-text and per-source diff --git a/Dockerfile b/Dockerfile index d4c2e2602..746dddf65 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,31 +1,49 @@ -# Build Stage -FROM golang:1.20 AS builder +# Use Go 1.21 Alpine as the base image for building the application +FROM golang:1.21-alpine as builder -# Set go mod installation source and proxy -ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct +# Define the base directory for the application as an environment variable +ENV SERVER_DIR=/openim-server -ENV GO111MODULE=$GO111MODULE -ENV GOPROXY=$GOPROXY +# Set the working directory inside the container based on the environment variable +WORKDIR $SERVER_DIR -# Set up the working directory -WORKDIR /openim/openim-server +# Set the Go proxy to improve dependency resolution speed +ENV GOPROXY=https://goproxy.io,direct +# Copy all files from the current directory into the container +COPY . . -# Copy all files to the container -ADD . . +RUN go mod download -RUN make clean -RUN make build +# Install Mage to use for building the application +RUN go install github.com/magefile/mage@v1.15.0 -FROM ghcr.io/openim-sigs/openim-ubuntu-image:latest +# Optionally build your application if needed +RUN mage build -WORKDIR ${SERVER_WORKDIR} +# Using Alpine Linux with Go environment for the final image +FROM golang:1.21-alpine -# Copy scripts and binary files to the production image -COPY --from=builder ${OPENIM_SERVER_BINDIR} /openim/openim-server/_output/bin -COPY --from=builder ${OPENIM_SERVER_CMDDIR} /openim/openim-server/scripts -COPY --from=builder ${SERVER_WORKDIR}/config /openim/openim-server/config -COPY --from=builder ${SERVER_WORKDIR}/deployments /openim/openim-server/deployments +# Install necessary packages, such as bash +RUN apk add --no-cache bash -CMD ["/openim/openim-server/scripts/docker-start-all.sh"] +# Set the environment and work directory +ENV SERVER_DIR=/openim-server +WORKDIR $SERVER_DIR + + +# Copy the compiled binaries and mage from the builder image to the final image +COPY --from=builder $SERVER_DIR/_output $SERVER_DIR/_output +COPY --from=builder $SERVER_DIR/config $SERVER_DIR/config +COPY --from=builder /go/bin/mage /usr/local/bin/mage +COPY --from=builder $SERVER_DIR/magefile_windows.go $SERVER_DIR/ +COPY --from=builder $SERVER_DIR/magefile_unix.go $SERVER_DIR/ +COPY --from=builder $SERVER_DIR/magefile.go $SERVER_DIR/ +COPY --from=builder $SERVER_DIR/start-config.yml $SERVER_DIR/ +COPY --from=builder $SERVER_DIR/go.mod $SERVER_DIR/ +COPY --from=builder $SERVER_DIR/go.sum $SERVER_DIR/ + +RUN go get github.com/openimsdk/gomake@v0.0.9 + +# Set the command to run when the container starts +ENTRYPOINT ["sh", "-c", "mage start && tail -f /dev/null"] diff --git a/Makefile b/Makefile deleted file mode 100644 index 89b9e4152..000000000 --- a/Makefile +++ /dev/null @@ -1,255 +0,0 @@ -# ============================================================================== -# define the default goal -# - -.DEFAULT_GOAL := help - -## all: Run tidy, gen, add-copyright, format, lint, cover, build ✨ -.PHONY: all -all: tidy gen add-copyright verify test-api lint cover restart - -# ============================================================================== -# Build set - -ROOT_PACKAGE=github.com/openimsdk/open-im-server -# TODO: This is version control for the future https://github.com/openimsdk/open-im-server/issues/574 -VERSION_PACKAGE=github.com/openimsdk/open-im-server/v3/pkg/version - -# ============================================================================== -# Includes - -include scripts/make-rules/common.mk # make sure include common.mk at the first include line -include scripts/make-rules/golang.mk -include scripts/make-rules/image.mk -include scripts/make-rules/copyright.mk -include scripts/make-rules/gen.mk -include scripts/make-rules/dependencies.mk -include scripts/make-rules/tools.mk -include scripts/make-rules/release.mk -include scripts/make-rules/swagger.mk - -# ============================================================================== -# Usage - -define USAGE_OPTIONS - -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-cmdutils". - - 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. -endef -export USAGE_OPTIONS - -# ============================================================================== -# Targets - -## init: Initialize openim server project ✨ -.PHONY: init -init: - @$(MAKE) gen.init - -## init-githooks: Initialize git hooks ✨ -.PHONY: init-githooks -init-githooks: - @$(MAKE) gen.init-githooks - -## gen: Generate all necessary files. ✨ -.PHONY: gen -gen: - @$(MAKE) gen.run - -## demo: Run demo get started with Makefiles quickly ✨ -.PHONY: demo -demo: - @$(MAKE) go.demo - -## version: Check version of openim. ✨ -.PHONY: version -version: - @$(MAKE) go.versionchecker - -## build: Build binaries by default ✨ -.PHONY: build -build: - @$(MAKE) go.build - -## start: Start openim ✨ -.PHONY: start -start: - @$(MAKE) go.start - -## stop: Stop openim ✨ -.PHONY: stop -stop: - @$(MAKE) go.stop - -## restart: Restart openim (make init configuration file is initialized) ✨ -.PHONY: restart -restart: clean stop build start check - -## multiarch: Build binaries for multiple platforms. See option PLATFORMS. ✨ -.PHONY: multiarch -multiarch: - @$(MAKE) go.build.multiarch - -## verify: execute all verity scripts. ✨ -.PHONY: verify -verify: - @$(MAKE) go.verify - -## install: Install deployment openim ✨ -.PHONY: install -install: - @$(MAKE) go.install - -## check: Check OpenIM deployment ✨ -.PHONY: check -check: - @$(MAKE) go.check - -## check-component: Check OpenIM component deployment ✨ -.PHONY: check-component -check-component: - @$(MAKE) go.check-component - -## tidy: tidy go.mod ✨ -.PHONY: tidy -tidy: - @$(GO) mod tidy - -## vendor: vendor go.mod ✨ -.PHONY: vendor -vendor: - @$(GO) mod vendor - -## 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 ./... - -## lint: Check syntax and styling of go sources. ✨ -.PHONY: lint -lint: - @$(MAKE) go.lint - -## format: Gofmt (reformat) package sources (exclude vendor dir if existed). ✨ -.PHONY: format -format: - @$(MAKE) go.format - -## test: Run unit test. ✨ -.PHONY: test -test: - @$(MAKE) go.test - -## cover: Run unit test and get test coverage. ✨ -.PHONY: cover -cover: - @$(MAKE) go.test.cover - -## test-api: Run api test. ✨ -.PHONY: test-api -test-api: - @$(MAKE) go.test.api - -## test-e2e: Run e2e test -test-e2e: - @$(MAKE) go.test.e2e - -## updates: Check for updates to go.mod dependencies. ✨ -.PHONY: updates - @$(MAKE) go.updates - -## imports: task to automatically handle import packages in Go files using goimports tool. ✨ -.PHONY: imports -imports: - @$(MAKE) go.imports - -## clean: Delete all files created by the build, as well as all log files. ✨ -.PHONY: clean -clean: - @$(MAKE) go.clean - -## image: Build docker images for host arch. ✨ -.PHONY: image -image: - @$(MAKE) image.build - -## image.multiarch: Build docker images for multiple platforms. See option PLATFORMS. ✨ -.PHONY: image.multiarch -image.multiarch: - @$(MAKE) image.build.multiarch - -## push: Build docker images for host arch and push images to registry. ✨ -.PHONY: push -push: - @$(MAKE) image.push - -## push.multiarch: Build docker images for multiple platforms and push images to registry. ✨ -.PHONY: push.multiarch -push.multiarch: - @$(MAKE) image.push.multiarch - -## tools: Install dependent tools. ✨ -.PHONY: tools -tools: - @$(MAKE) tools.install - -## swagger: Generate swagger document. ✨ -.PHONY: swagger -swagger: - @$(MAKE) swagger.run - -## serve-swagger: Serve swagger spec and docs. ✨ -.PHONY: swagger.serve -serve-swagger: - @$(MAKE) swagger.serve - -## verify-copyright: Verify the license headers for all files. ✨ -.PHONY: verify-copyright -verify-copyright: - @$(MAKE) copyright.verify - -## add-copyright: Add copyright ensure source code files have license headers. ✨ -.PHONY: add-copyright -add-copyright: - @$(MAKE) copyright.add - -## advertise: Project introduction, become a contributor ✨ -.PHONY: advertise -advertise: - @$(MAKE) copyright.advertise - -## release: release the project ✨ -.PHONY: release -release: release.verify release.ensure-tag - @scripts/release.sh - -## help: Show this help info. ✨ -.PHONY: help -help: Makefile - $(call makehelp) - -## help-all: Show all help details info. ✨ -.PHONY: help-all -help-all: go.help copyright.help tools.help image.help dependencies.help gen.help release.help swagger.help help - $(call makeallhelp) diff --git a/README_zh_CN.md b/README_zh_CN.md index 7eabfa509..f42031165 100644 --- a/README_zh_CN.md +++ b/README_zh_CN.md @@ -52,7 +52,7 @@

## 🟢 扫描微信进群交流 - + ## Ⓜ️ 关于 OpenIM diff --git a/bootstrap.bat b/bootstrap.bat new file mode 100644 index 000000000..819f19c87 --- /dev/null +++ b/bootstrap.bat @@ -0,0 +1,31 @@ +@echo off +SETLOCAL + +mage -version >nul 2>&1 +IF %ERRORLEVEL% EQU 0 ( + echo Mage is already installed. + GOTO DOWNLOAD +) + +go version >nul 2>&1 +IF NOT %ERRORLEVEL% EQU 0 ( + echo Go is not installed. Please install Go and try again. + exit /b 1 +) + +echo Installing Mage... +go install github.com/magefile/mage@latest + +mage -version >nul 2>&1 +IF NOT %ERRORLEVEL% EQU 0 ( + echo Mage installation failed. + echo Please ensure that %GOPATH%/bin is in your PATH. + exit /b 1 +) + +echo Mage installed successfully. + +:DOWNLOAD +go mod download + +ENDLOCAL diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 000000000..f79cd1f11 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +if [[ ":$PATH:" == *":$HOME/.local/bin:"* ]]; then + TARGET_DIR="$HOME/.local/bin" +else + TARGET_DIR="/usr/local/bin" + echo "Using /usr/local/bin as the installation directory. Might require sudo permissions." +fi + +if ! command -v mage &> /dev/null; then + echo "Installing Mage to $TARGET_DIR ..." + GOBIN=$TARGET_DIR go install github.com/magefile/mage@latest +fi + +if ! command -v mage &> /dev/null; then + echo "Mage installation failed." + echo "Please ensure that $TARGET_DIR is in your \$PATH." + exit 1 +fi + +echo "Mage installed successfully." + +go mod download diff --git a/build/images/openim-api/Dockerfile b/build/images/openim-api/Dockerfile index 5832c31a6..662223956 100644 --- a/build/images/openim-api/Dockerfile +++ b/build/images/openim-api/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-cmdutils/Dockerfile b/build/images/openim-cmdutils/Dockerfile index 5afbe6ece..34bcd41f5 100644 --- a/build/images/openim-cmdutils/Dockerfile +++ b/build/images/openim-cmdutils/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-crontask/Dockerfile b/build/images/openim-crontask/Dockerfile index 4019e16c1..90a562926 100644 --- a/build/images/openim-crontask/Dockerfile +++ b/build/images/openim-crontask/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-msggateway/Dockerfile b/build/images/openim-msggateway/Dockerfile index c9d1ce949..d3a8694ed 100644 --- a/build/images/openim-msggateway/Dockerfile +++ b/build/images/openim-msggateway/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-msgtransfer/Dockerfile b/build/images/openim-msgtransfer/Dockerfile index 08a026c35..f94978648 100644 --- a/build/images/openim-msgtransfer/Dockerfile +++ b/build/images/openim-msgtransfer/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-push/Dockerfile b/build/images/openim-push/Dockerfile index 43a3833c2..faebbe9c0 100644 --- a/build/images/openim-push/Dockerfile +++ b/build/images/openim-push/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-rpc-auth/Dockerfile b/build/images/openim-rpc-auth/Dockerfile index 6a64c3e79..1e905d4b2 100644 --- a/build/images/openim-rpc-auth/Dockerfile +++ b/build/images/openim-rpc-auth/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-rpc-conversation/Dockerfile b/build/images/openim-rpc-conversation/Dockerfile index 421755094..5a69aa89f 100644 --- a/build/images/openim-rpc-conversation/Dockerfile +++ b/build/images/openim-rpc-conversation/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-rpc-friend/Dockerfile b/build/images/openim-rpc-friend/Dockerfile index ba3a38116..fad21a880 100644 --- a/build/images/openim-rpc-friend/Dockerfile +++ b/build/images/openim-rpc-friend/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-rpc-group/Dockerfile b/build/images/openim-rpc-group/Dockerfile index 916c8e848..d7dede6b9 100644 --- a/build/images/openim-rpc-group/Dockerfile +++ b/build/images/openim-rpc-group/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-rpc-msg/Dockerfile b/build/images/openim-rpc-msg/Dockerfile index a0ac98c47..71ad85f37 100644 --- a/build/images/openim-rpc-msg/Dockerfile +++ b/build/images/openim-rpc-msg/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-rpc-third/Dockerfile b/build/images/openim-rpc-third/Dockerfile index 9fe17dd40..3560ad0d7 100644 --- a/build/images/openim-rpc-third/Dockerfile +++ b/build/images/openim-rpc-third/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-rpc-user/Dockerfile b/build/images/openim-rpc-user/Dockerfile index bdd0a42ee..a9891ece8 100644 --- a/build/images/openim-rpc-user/Dockerfile +++ b/build/images/openim-rpc-user/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-tools/component/Dockerfile b/build/images/openim-tools/component/Dockerfile index 1f7a1a46a..6bdfa6942 100644 --- a/build/images/openim-tools/component/Dockerfile +++ b/build/images/openim-tools/component/Dockerfile @@ -19,7 +19,6 @@ FROM golang:1.20 AS builder ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct WORKDIR /openim/openim-server diff --git a/build/images/openim-tools/openim-web/Dockerfile b/build/images/openim-tools/openim-web/Dockerfile deleted file mode 100644 index d14501189..000000000 --- a/build/images/openim-tools/openim-web/Dockerfile +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright © 2023 OpenIM. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# OpenIM base image: https://github.com/openim-sigs/openim-base-image - -# Set go mod installation source and proxy - -FROM golang:1.20 AS builder - -ARG GO111MODULE=on -ARG GOPROXY=https://goproxy.io,direct - -WORKDIR /openim/openim-server - -ENV GO111MODULE=$GO111MODULE -ENV GOPROXY=$GOPROXY - -RUN apt-get update && apt-get install -y curl unzip - -RUN curl -LO https://app-1302656840.cos.ap-nanjing.myqcloud.com/dist.zip \ - && unzip dist.zip -d ./ \ - && rm dist.zip - -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -RUN make clean -RUN make build BINS=openim-web - -FROM ghcr.io/openim-sigs/openim-ubuntu-image:latest - -WORKDIR /openim/openim-server - -COPY --from=builder /openim/openim-server/_output/bin/tools /openim/openim-server/_output/bin/tools/ -COPY --from=builder /openim/openim-server/dist /openim/openim-server/dist - -ENV PORT 11001 -ENV DISTPATH /openim/openim-server/dist - -EXPOSE 11001 - -RUN mv ${OPENIM_SERVER_BINDIR}/tools/$(get_os)/$(get_arch)/openim-web /usr/bin/openim-web - -ENTRYPOINT ["bash", "-c", "openim-web -port $PORT -distPath $DISTPATH"] \ No newline at end of file diff --git a/cmd/openim-api/main.go b/cmd/openim-api/main.go index ee19a5c60..58e540c05 100644 --- a/cmd/openim-api/main.go +++ b/cmd/openim-api/main.go @@ -18,14 +18,12 @@ import ( _ "net/http/pprof" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - apiCmd := cmd.NewApiCmd() - apiCmd.AddPortFlag() - apiCmd.AddPrometheusPortFlag() - if err := apiCmd.Execute(); err != nil { - util.ExitWithError(err) + if err := cmd.NewApiCmd().Exec(); err != nil { + program.ExitWithError(err) } + } diff --git a/cmd/openim-cmdutils/main.go b/cmd/openim-cmdutils/main.go index f6b788933..433eefb32 100644 --- a/cmd/openim-cmdutils/main.go +++ b/cmd/openim-cmdutils/main.go @@ -16,7 +16,7 @@ package main import ( "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { @@ -29,6 +29,8 @@ func main() { getCmd.AddCommand(seqCmd.GetSeqCmd(), msgCmd.GetMsgCmd()) getCmd.AddSuperGroupIDFlag() getCmd.AddUserIDFlag() + getCmd.AddConfigDirFlag() + getCmd.AddIndexFlag() getCmd.AddBeginSeqFlag() getCmd.AddLimitFlag() // openIM get seq --userID=xxx @@ -39,6 +41,8 @@ func main() { fixCmd.AddCommand(seqCmd.FixSeqCmd()) fixCmd.AddSuperGroupIDFlag() fixCmd.AddUserIDFlag() + fixCmd.AddConfigDirFlag() + fixCmd.AddIndexFlag() fixCmd.AddFixAllFlag() // openIM fix seq --userID=xxx // openIM fix seq --superGroupID=xxx @@ -47,6 +51,8 @@ func main() { clearCmd.AddCommand(msgCmd.ClearMsgCmd()) clearCmd.AddSuperGroupIDFlag() clearCmd.AddUserIDFlag() + clearCmd.AddConfigDirFlag() + clearCmd.AddIndexFlag() clearCmd.AddClearAllFlag() clearCmd.AddBeginSeqFlag() clearCmd.AddLimitFlag() @@ -55,6 +61,6 @@ func main() { // openIM clear msg --clearAll msgUtilsCmd.AddCommand(&getCmd.Command, &fixCmd.Command, &clearCmd.Command) if err := msgUtilsCmd.Execute(); err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } } diff --git a/cmd/openim-crontask/main.go b/cmd/openim-crontask/main.go index b52029c64..674506518 100644 --- a/cmd/openim-crontask/main.go +++ b/cmd/openim-crontask/main.go @@ -16,12 +16,11 @@ package main import ( "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - cronTaskCmd := cmd.NewCronTaskCmd() - if err := cronTaskCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewCronTaskCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/cmd/openim-msggateway/main.go b/cmd/openim-msggateway/main.go index 01b13560d..6e3eda6bf 100644 --- a/cmd/openim-msggateway/main.go +++ b/cmd/openim-msggateway/main.go @@ -16,15 +16,11 @@ package main import ( "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - msgGatewayCmd := cmd.NewMsgGatewayCmd() - msgGatewayCmd.AddWsPortFlag() - msgGatewayCmd.AddPortFlag() - msgGatewayCmd.AddPrometheusPortFlag() - if err := msgGatewayCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewMsgGatewayCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/cmd/openim-msgtransfer/main.go b/cmd/openim-msgtransfer/main.go index 84fbbd2ea..23b629d69 100644 --- a/cmd/openim-msgtransfer/main.go +++ b/cmd/openim-msgtransfer/main.go @@ -16,14 +16,11 @@ package main import ( "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - msgTransferCmd := cmd.NewMsgTransferCmd() - msgTransferCmd.AddPrometheusPortFlag() - msgTransferCmd.AddTransferProgressFlag() - if err := msgTransferCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewMsgTransferCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/cmd/openim-push/main.go b/cmd/openim-push/main.go index c7d29fc97..cd5106d17 100644 --- a/cmd/openim-push/main.go +++ b/cmd/openim-push/main.go @@ -15,16 +15,12 @@ package main import ( - "github.com/openimsdk/open-im-server/v3/internal/push" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - pushCmd := cmd.NewRpcCmd(cmd.RpcPushServer, push.Start) - pushCmd.AddPortFlag() - pushCmd.AddPrometheusPortFlag() - if err := pushCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewPushRpcCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/cmd/openim-rpc/openim-rpc-auth/main.go b/cmd/openim-rpc/openim-rpc-auth/main.go index da281b70e..158b1f464 100644 --- a/cmd/openim-rpc/openim-rpc-auth/main.go +++ b/cmd/openim-rpc/openim-rpc-auth/main.go @@ -15,16 +15,12 @@ package main import ( - "github.com/openimsdk/open-im-server/v3/internal/rpc/auth" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - authCmd := cmd.NewRpcCmd(cmd.RpcAuthServer, auth.Start) - authCmd.AddPortFlag() - authCmd.AddPrometheusPortFlag() - if err := authCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewAuthRpcCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/cmd/openim-rpc/openim-rpc-conversation/main.go b/cmd/openim-rpc/openim-rpc-conversation/main.go index 6e74b3251..5b2e66c95 100644 --- a/cmd/openim-rpc/openim-rpc-conversation/main.go +++ b/cmd/openim-rpc/openim-rpc-conversation/main.go @@ -15,16 +15,12 @@ package main import ( - "github.com/openimsdk/open-im-server/v3/internal/rpc/conversation" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - rpcCmd := cmd.NewRpcCmd(cmd.RpcConversationServer, conversation.Start) - rpcCmd.AddPortFlag() - rpcCmd.AddPrometheusPortFlag() - if err := rpcCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewConversationRpcCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/cmd/openim-rpc/openim-rpc-friend/main.go b/cmd/openim-rpc/openim-rpc-friend/main.go index a307c01a1..745c40553 100644 --- a/cmd/openim-rpc/openim-rpc-friend/main.go +++ b/cmd/openim-rpc/openim-rpc-friend/main.go @@ -15,16 +15,12 @@ package main import ( - "github.com/openimsdk/open-im-server/v3/internal/rpc/friend" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - rpcCmd := cmd.NewRpcCmd(cmd.RpcFriendServer, friend.Start) - rpcCmd.AddPortFlag() - rpcCmd.AddPrometheusPortFlag() - if err := rpcCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewFriendRpcCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/cmd/openim-rpc/openim-rpc-group/main.go b/cmd/openim-rpc/openim-rpc-group/main.go index 2afb7963c..5badf934e 100644 --- a/cmd/openim-rpc/openim-rpc-group/main.go +++ b/cmd/openim-rpc/openim-rpc-group/main.go @@ -15,16 +15,12 @@ package main import ( - "github.com/openimsdk/open-im-server/v3/internal/rpc/group" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - rpcCmd := cmd.NewRpcCmd(cmd.RpcGroupServer, group.Start) - rpcCmd.AddPortFlag() - rpcCmd.AddPrometheusPortFlag() - if err := rpcCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewGroupRpcCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/cmd/openim-rpc/openim-rpc-msg/main.go b/cmd/openim-rpc/openim-rpc-msg/main.go index bbffbcae7..37f6cf237 100644 --- a/cmd/openim-rpc/openim-rpc-msg/main.go +++ b/cmd/openim-rpc/openim-rpc-msg/main.go @@ -15,16 +15,12 @@ package main import ( - "github.com/openimsdk/open-im-server/v3/internal/rpc/msg" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - rpcCmd := cmd.NewRpcCmd(cmd.RpcMsgServer, msg.Start) - rpcCmd.AddPortFlag() - rpcCmd.AddPrometheusPortFlag() - if err := rpcCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewMsgRpcCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/cmd/openim-rpc/openim-rpc-third/main.go b/cmd/openim-rpc/openim-rpc-third/main.go index 09a8409e6..fcead5f89 100644 --- a/cmd/openim-rpc/openim-rpc-third/main.go +++ b/cmd/openim-rpc/openim-rpc-third/main.go @@ -15,16 +15,12 @@ package main import ( - "github.com/openimsdk/open-im-server/v3/internal/rpc/third" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - rpcCmd := cmd.NewRpcCmd(cmd.RpcThirdServer, third.Start) - rpcCmd.AddPortFlag() - rpcCmd.AddPrometheusPortFlag() - if err := rpcCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewThirdRpcCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/cmd/openim-rpc/openim-rpc-user/main.go b/cmd/openim-rpc/openim-rpc-user/main.go index 18adbfae5..75ab90291 100644 --- a/cmd/openim-rpc/openim-rpc-user/main.go +++ b/cmd/openim-rpc/openim-rpc-user/main.go @@ -15,16 +15,12 @@ package main import ( - "github.com/openimsdk/open-im-server/v3/internal/rpc/user" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" ) func main() { - rpcCmd := cmd.NewRpcCmd(cmd.RpcUserServer, user.Start) - rpcCmd.AddPortFlag() - rpcCmd.AddPrometheusPortFlag() - if err := rpcCmd.Exec(); err != nil { - util.ExitWithError(err) + if err := cmd.NewUserRpcCmd().Exec(); err != nil { + program.ExitWithError(err) } } diff --git a/config/README.md b/config/README.md new file mode 100644 index 000000000..048d7fe36 --- /dev/null +++ b/config/README.md @@ -0,0 +1,63 @@ +--- +title: 'OpenIM Configuration Files and Common Configuration Item Modifications Guide' + +## Configuration Files Explanation + +| Configuration File | Description | +| ------------------------------- | ------------------------------------------------------------ | +| **kafka.yml** | Configurations for Kafka username, password, address, etc. | +| **redis.yml** | Configurations for Redis password, address, etc. | +| **minio.yml** | Configurations for MinIO username, password, address, and external IP/domain; failing to modify external IP or domain may cause image file sending failures | +| **zookeeper.yml** | Configurations for ZooKeeper user, password, address, etc. | +| **mongodb.yml** | Configurations for MongoDB username, password, address, etc. | +| **log.yml** | Configurations for log level and storage directory. | +| **notification.yml** | Configurations for events like adding friends, creating groups, etc. | +| **share.yml** | Common configurations needed by various OpenIM services, such as secret. | +| **webhooks.yml** | Configurations for URLs in Webhook. | +| **local-cache.yml** | Local cache configurations. | +| **openim-rpc-third.yml** | Configurations for listening IP, port, and storage settings for images and videos in openim-rpc-third service. | +| **openim-rpc-user.yml** | Configurations for listening IP and port in openim-rpc-user service. | +| **openim-api.yml** | Configurations for listening IP, port, etc., in openim-api service. | +| **openim-crontask.yml** | Configurations for openim-crontask service. | +| **openim-msggateway.yml** | Configurations for listening IP, port, etc., in openim-msggateway service. | +| **openim-msgtransfer.yml** | Configurations for openim-msgtransfer service. | +| **openim-push.yml** | Configurations for listening IP, port, and offline push settings in openim-push service. | +| **openim-rpc-auth.yml** | Configurations for listening IP, port, and token expiration settings in openim-rpc-auth service. | +| **openim-rpc-conversation.yml** | Configurations for listening IP, port, etc., in openim-rpc-conversation service. | +| **openim-rpc-friend.yml** | Configurations for listening IP, port, etc., in openim-rpc-friend service. | +| **openim-rpc-group.yml** | Configurations for listening IP, port, etc., in openim-rpc-group service. | +| **openim-rpc-msg.yml** | Configurations for listening IP, port, and whether to verify friendship before sending messages in openim-rpc-msg service. | + +## Common Configuration Item Modifications + +| Configuration Item Modification | Configuration File | +| ----------------------------------------------------- | ----------------------- | +| Using MinIO for image and video file object storage | `minio.yml` | +| Adjusting production environment logs | `log.yml` | +| Verifying friendship before sending messages | `openim-rpc-msg.yml` | +| Modifying secret | `share.yml` | +| Using OSS, COS, AWS, Kodo for image and video storage | `openim-rpc-third.yml` | +| Setting multiple login policy | `openim-msggateway.yml` | +| Setting up offline push | `openim-push.yml` | + +## Starting Multiple Instances of an OpenIM Service + +To start multiple instances of an OpenIM service, simply increase the corresponding port numbers and modify the `start-config.yml` file in the project root directory. Restart the service to take effect. For example, the configuration to start 2 instances of `openim-rpc-user` is as follows: + +```yaml +rpc: + registerIP: '' + listenIP: 0.0.0.0 + ports: [ 10110, 10111 ] + +prometheus: + enable: true + ports: [ 20100, 20101 ] +``` + +Modify `start-config.yml`: + +```yaml +serviceBinaries: + openim-rpc-user: 2 +``` diff --git a/config/README_zh_CN.md b/config/README_zh_CN.md new file mode 100644 index 000000000..6ddab0636 --- /dev/null +++ b/config/README_zh_CN.md @@ -0,0 +1,65 @@ +# OpenIM配置文件说明以及常用配置修改说明 + +## 配置文件说明 + +| Configuration File | Description | +| ------------------------------- | ------------------------------------------------------------ | +| **kafka.yml** | Kafka用户名、密码、地址等配置 | +| **redis.yml** | Redis密码、地址等配置 | +| **minio.yml** | MinIO用户名、密码、地址及外网IP域名等配置;未修改外网IP或域名可能导致图片文件发送失败 | +| **zookeeper.yml** | ZooKeeper用户、密码、地址等配置 | +| **mongodb.yml** | MongoDB用户名、密码、地址等配置 | +| **log.yml** | 日志级别及存储目录等配置 | +| **notification.yml** | 添加好友、创建群组等事件通知配置 | +| **share.yml** | OpenIM各服务所需的公共配置,如secret等 | +| **webhooks.yml** | Webhook中URL等配置 | +| **local-cache.yml** | 本地缓存配置 | +| **openim-rpc-third.yml** | openim-rpc-third服务的监听IP、端口及图片视频对象存储配置 | +| **openim-rpc-user.yml** | openim-rpc-user服务的监听IP、端口配置 | +| **openim-api.yml** | openim-api服务的监听IP、端口等配置项 | +| **openim-crontask.yml** | openim-crontask服务配置 | +| **openim-msggateway.yml** | openim-msggateway服务的监听IP、端口等配置 | +| **openim-msgtransfer.yml** | openim-msgtransfer服务配置 | +| **openim-push.yml** | openim-push服务的监听IP、端口及离线推送配置 | +| **openim-rpc-auth.yml** | openim-rpc-auth服务的监听IP、端口及token有效期等配置 | +| **openim-rpc-conversation.yml** | openim-rpc-conversation服务的监听IP、端口等配置 | +| **openim-rpc-friend.yml** | openim-rpc-friend服务的监听IP、端口等配置 | +| **openim-rpc-group.yml** | openim-rpc-group服务的监听IP、端口等配置 | +| **openim-rpc-msg.yml** | openim-rpc-msg服务的监听IP、端口及消息发送是否验证好友关系等配置 | + +## 常用配置修改 + +| 修改配置项 | 配置文件 | +| ----------------------------------------------- | ----------------------- | +| 使用minio作为图片视频文件对象存储 | `minio.yml` | +| 生产环境日志调整 | `log.yml` | +| 发送消息是否验证好友关系 | `openim-rpc-msg.yml` | +| 修改secret | `share.yml` | +| 使用oss, cos, aws, kodo作为图片视频文件对象存储 | `openim-rpc-third.yml` | +| 设置多端互踢策略 | `openim-msggateway.yml` | +| 设置离线推送 | `openim-push.yml` | + +## 启动某个OpenIM服务的多个实例 + +若要启动某个OpenIM的多个实例,只需增加对应的端口数,并修改项目根目录下的`start-config.yml`文件,重启服务即可生效。例如,启动2个`openim-rpc-user`实例的配置如下: + +```yaml +rpc: + registerIP: '' + listenIP: 0.0.0.0 + ports: [ 10110, 10111 ] + +prometheus: + enable: true + ports: [ 20100, 20101 ] +``` + +修改`start-config.yml`: + +```yaml +serviceBinaries: + openim-rpc-user: 2 +``` + + + diff --git a/config/Readme.md b/config/Readme.md deleted file mode 100644 index 72f4577a5..000000000 --- a/config/Readme.md +++ /dev/null @@ -1,243 +0,0 @@ -# OpenIM Configuration Guide - - -* 1. [Directory Structure and File Descriptions](#DirectoryStructureandFileDescriptions) - * 1.1. [Directory Structure](#DirectoryStructure) - * 1.2. [Directory Structure Explanation](#DirectoryStructureExplanation) -* 2. [File Descriptions](#FileDescriptions) - * 2.1. [Files in the Root Directory](#FilesintheRootDirectory) - * 2.2. [Files in the `templates/` Directory](#FilesinthetemplatesDirectory) -* 3. [Configuration File Generation](#ConfigurationFileGeneration) - * 3.1. [How to Use `init-config.sh` Script](#HowtoUseinit-config.shScript) - * 3.2. [Examples of Operations](#ExamplesofOperations) - * 3.3. [Points to Note](#PointstoNote) -* 4. [Example Directory](#ExampleDirectory) - * 4.1. [Overview](#Overview) - * 4.2. [Structure](#Structure) - * 4.3. [How to Use These Examples](#HowtoUseTheseExamples) - * 4.4. [Tips for Using Example Files:](#TipsforUsingExampleFiles:) -* 5. [Configuration Item Descriptions](#ConfigurationItemDescriptions) -* 6. [Version Management and Upgrading](#VersionManagementandUpgrading) - * 6.1. [Pulling the Latest Code](#PullingtheLatestCode) - * 6.2. [Generating the Latest Example Configuration Files](#GeneratingtheLatestExampleConfigurationFiles) - * 6.3. [Comparing Configuration File Differences](#ComparingConfigurationFileDifferences) - * 6.4. [Updating Configuration Files](#UpdatingConfigurationFiles) - * 6.5. [Updating Binary Files and Restarting Services](#UpdatingBinaryFilesandRestartingServices) - * 6.6. [Best Practices for Version Management](#BestPracticesforVersionManagement) -* 7. [How to Contribute](#HowtoContribute) - * 7.1. [OpenIM Configuration Item Descriptions](#OpenIMConfigurationItemDescriptions) - * 7.2. [Modifying Template Files](#ModifyingTemplateFiles) - * 7.3. [Updating Configuration Center Scripts](#UpdatingConfigurationCenterScripts) - * 7.4. [Configuration File Generation Process](#ConfigurationFileGenerationProcess) - * 7.5. [Contribution Guidelines](#ContributionGuidelines) - * 7.6. [Submission and Review](#SubmissionandReview) - - - - - -## 1. Directory Structure and File Descriptions - -This document details the structure of the `config` directory, aiding users in understanding and managing configuration files. - -### 1.1. Directory Structure - -```bash -$ tree config -├── alertmanager.yml -├── config.yaml -├── email.tmpl -├── instance-down-rules.yml -├── notification.yaml -├── prometheus.yml -├── Readme.md -└── templates - ├── alertmanager.yml.template - ├── config.yaml.template - ├── email.tmpl.template - ├── env.template - ├── instance-down-rules.yml.template - ├── notification.yaml.template - ├── open-im-ng-example.conf - ├── prometheus-dashboard.yaml - └── prometheus.yml.template -``` - -### 1.2. Directory Structure Explanation - -- **Root Directory (`config/`)**: Contains actual configuration files and the `templates` subdirectory. -- **`templates/` Subdirectory**: Stores configuration templates for generating or updating configuration files in the root directory. - -## 2. File Descriptions - -### 2.1. Files in the Root Directory - -- **`alertmanager.yml`**: Configuration file for AlertManager, managing and setting up the alert system. -- **`config.yaml`**: The main application configuration file, covering service settings. -- **`email.tmpl`**: Template file for email notifications, defining email format and content. -- **`instance-down-rules.yml`**: Instance downtime rules configuration file for the monitoring system. -- **`notification.yaml`**: Configuration file for notification settings, defining different types of notifications. -- **`prometheus.yml`**: Configuration file for the Prometheus monitoring system, setting monitoring metrics and rules. - -### 2.2. Files in the `templates/` Directory - -- **`alertmanager.yml.template`**: Template for AlertManager configuration. -- **`config.yaml.template`**: Main configuration template for the application. -- **`email.tmpl.template`**: Template for email notifications. -- **`env.template`**: Template for environmental variable configurations, setting environment-related configurations. -- **`instance-down-rules.yml.template`**: Template for instance downtime rules. -- **`notification.yaml.template`**: Template for notification settings. -- **`open-im-ng-example.conf`**: Example configuration file for the application. -- **`prometheus-dashboard.yaml`**: Prometheus dashboard configuration file, specific to the OpenIM application. -- **`prometheus.yml.template`**: Template for Prometheus configuration. - -## 3. Configuration File Generation - -Configuration files can be automatically generated using the `make init` command or the `./scripts/init-config.sh` script. These scripts conveniently extract templates from the `templates` directory and generate or update actual configuration files in the root directory. - -### 3.1. How to Use `init-config.sh` Script - -```bash -$ ./scripts/init-config.sh --help -Usage: init-config.sh [options] -Options: - -h, --help Show this help message - --force Overwrite existing files without prompt - --skip Skip generation if file exists - --examples Generate example files - --clean-config Clean all configuration files - --clean-examples Clean all example files -``` - -### 3.2. Examples of Operations - -- Generate all template configuration files: - - ```bash - $ ./scripts/init-config.sh --examples - ``` - -- Force overwrite existing configuration files: - - ```bash - $ ./scripts/init-config.sh --force - ``` - -### 3.3. Points to Note - -- **Template files should not be directly modified**: Files in the `template` directory are templates included in source code management. Direct modification may lead to version conflicts or management issues. -- **Operations for Windows Users**: Windows users can use the `cp` command to copy files from the `template` directory to the `config/` directory and then modify the configuration items as needed. - -## 4. Example Directory - -Welcome to our project's `examples` directory! This directory contains a range of example files, showcasing various configurations and settings of our software. These examples are intended to provide you with templates that can serve as a starting point for your own configurations. - -### 4.1. Overview - -In this directory, you'll find examples suitable for a variety of use cases. Each file is a template with default values and configurations, demonstrating best practices and typical scenarios. Whether you're just getting started or looking to implement complex settings, these examples should help you get on the right track. - -### 4.2. Structure - -Here's a quick overview of the contents in this directory: - -- `env-example.yaml`: Demonstrates how to set up environmental variables. -- `openim-example.yaml`: Example configuration file for the OpenIM application. -- `prometheus-example.yml`: Example configuration for monitoring with Prometheus. -- `alertmanager-example.yml`: Template for setting up Alertmanager configuration. - -### 4.3. How to Use These Examples - -To use these examples, simply copy the relevant files to your working directory and rename them as needed (for example, removing the `-example` suffix). Then, modify the files according to your needs. - -### 4.4. Tips for Using Example Files: - -1. **Read Comments**: Each file contains comments explaining the various sections and settings. Make sure to read these comments for a better understanding of how to customize the file. -2. **Check Required Changes**: Some examples might require mandatory changes before they can be used effectively (such as setting specific environmental variables). -3. **Version Compatibility**: Ensure that the example files are compatible with the version of the software you are using. - -## 5. Configuration Item Descriptions - -## 6. Version Management and Upgrading - -When managing and upgrading the `config` directory's versions, it is crucial to ensure that the configuration files in both the local `config/` and `config/templates/` directories are kept in sync. This process can ensure that your configuration files are consistent with the latest standard templates, while also maintaining custom settings. - -### 6.1. Pulling the Latest Code - -First, ensure that your local repository is in sync with the remote repository. This can be achieved by pulling the latest code: - -```bash -$ git pull -``` - -### 6.2. Generating the Latest Example Configuration Files - -Next, generate the latest example configuration files. This can be done by running the `init-config.sh` script, using the `--examples` option to generate example files, and the `--skip` option to avoid overwriting existing configuration files: - -```bash -$ ./scripts/init-config.sh --examples --skip -``` - -### 6.3. Comparing Configuration File Differences - -Once the latest example configuration files are generated, you need to compare the configuration files in the `config/` and `config/templates/` directories to find any potential differences. This step ensures that you can identify and integrate any important updates or changes. Tools like `diff` can be helpful in completing this step: - -```bash -$ diff -ur config/ config/templates/ -``` - -### 6.4. Updating Configuration Files - -Based on the comparison results, manually update the configuration files in the `config/` directory to reflect the latest configurations in `config/templates/`. During this process, ensure to retain any custom configuration settings. - -### 6.5. Updating Binary Files and Restarting Services - -After updating the configuration files, the next step is to update any related binary files. This typically involves downloading and installing the latest version of the application or service. Depending on the specific application or service, this might involve running specific update scripts or directly downloading the latest version from official sources. - -Once the binary files are updated, the services need to be restarted to apply the new configurations. Make sure to conduct necessary checks before restarting to ensure the correctness of the configurations. - -### 6.6. Best Practices for Version Management - -- **Record Changes**: When committing changes to a version control system, ensure to log detailed change logs. -- **Stay Synced**: Regularly sync with the remote repository to ensure that your local configurations are in line with the latest developments. -- **Backup**: Backup your current configurations before making any significant changes, so that you can revert to a previous state if necessary. - -By following these steps and best practices, you can ensure effective management and smooth upgrading of your `config` directory. - -## 7. How to Contribute - -If you have an understanding of the logic behind OpenIM's configuration generation, then you will clearly know where to make modifications to contribute code. - -### 7.1. OpenIM Configuration Item Descriptions - -First, it is recommended to read the [OpenIM Configuration Items Document](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/environment.md). This will help you understand the roles of various configuration items and how they affect the operation of OpenIM. - -### 7.2. Modifying Template Files - -To contribute to OpenIM, focus on the `./deployments/templates` directory. This contains various configuration template files, which are the basis for generating the final configuration files. - -When making modifications, ensure that your changes align with OpenIM's configuration requirements and logic. This may involve adding new template files or modifying existing files to reflect new configuration options or structural changes. - -### 7.3. Updating Configuration Center Scripts - -In addition to modifying template files, pay attention to the `./scripts/install/environment.sh` script. In this script, you may need to add or modify environment variables. - -This script is responsible for defining environment variables that influence configuration generation. Therefore, any new configuration items or modifications to existing items need to be reflected here. - -### 7.4. Configuration File Generation Process - -The essence of the `make init` command is to use the environment variables defined in `/scripts/install/environment.sh` to render the template files in the `./deployments/templates` directory, thereby generating the final configuration files. - -When contributing code, ensure that your changes work smoothly in this process and do not cause errors during configuration file generation. - -### 7.5. Contribution Guidelines - -- **Code Review**: Ensure your changes have passed code review. This typically means that the code should be clear, easy to understand, and adhere to the project's coding style and best practices. -- **Testing**: Before submitting changes, conduct thorough tests to ensure new or modified configurations work as expected and do not negatively impact existing functionalities. -- **Documentation**: If you have added a new configuration option or made significant changes to an existing one, update the relevant documentation to assist other users and developers in understanding and utilizing these changes. - -### 7.6. Submission and Review - -After completing your changes, submit your code to the OpenIM repository in the form of a Pull Request (PR). The PR will be reviewed by the project maintainers and you may be asked to make further modifications or provide additional information. \ No newline at end of file diff --git a/config/kafka.yml b/config/kafka.yml new file mode 100644 index 000000000..b1773c80a --- /dev/null +++ b/config/kafka.yml @@ -0,0 +1,18 @@ +username: '' +password: '' +producerAck: "" +compressType: "none" +address: [ localhost:19094 ] +toRedisTopic: "toRedis" +toMongoTopic: "toMongo" +toPushTopic: "toPush" +toRedisGroupID: redis +toMongoGroupID: mongo +toPushGroupID: push +tls: + enableTLS: false + caCrt: "" + clientCrt: "" + clientKey: "" + clientKeyPwd: "" + insecureSkipVerify: false diff --git a/config/local-cache.yml b/config/local-cache.yml new file mode 100644 index 000000000..06e211ebb --- /dev/null +++ b/config/local-cache.yml @@ -0,0 +1,27 @@ +user: + topic: DELETE_CACHE_USER + slotNum: 100 + slotSize: 2000 + successExpire: 300 + failedExpire: 5 + +group: + topic: DELETE_CACHE_GROUP + slotNum: 100 + slotSize: 2000 + successExpire: 300 + failedExpire: 5 + +friend: + topic: DELETE_CACHE_FRIEND + slotNum: 100 + slotSize: 2000 + successExpire: 300 + failedExpire: 5 + +conversation: + topic: DELETE_CACHE_CONVERSATION + slotNum: 100 + slotSize: 2000 + successExpire: 300 + failedExpire: 5 diff --git a/config/log.yml b/config/log.yml new file mode 100644 index 000000000..2194d8917 --- /dev/null +++ b/config/log.yml @@ -0,0 +1,13 @@ +# Log storage path, default is acceptable, change to a full path if modification is needed +storageLocation: ../../../../logs/ +# Log rotation period (in hours), default is acceptable +rotationTime: 24 +# Number of log files to retain, default is acceptable +remainRotationCount: 2 +# Log level settings: 3 for production environment; 6 for more verbose logging in debugging environments +remainLogLevel: 6 +# Whether to output to standard output, default is acceptable +isStdout: false +# Whether to log in JSON format, default is acceptable +isJson: false + diff --git a/config/minio.yml b/config/minio.yml new file mode 100644 index 000000000..0a80bb845 --- /dev/null +++ b/config/minio.yml @@ -0,0 +1,7 @@ +bucket: "openim" +accessKeyID: "root" +secretAccessKey: "openIM123" +sessionToken: '' +internalAddress: "minio:9000" +externalAddress: "http://external_ip:10005" +publicRead: false \ No newline at end of file diff --git a/config/mongodb.yml b/config/mongodb.yml new file mode 100644 index 000000000..12f1f66a5 --- /dev/null +++ b/config/mongodb.yml @@ -0,0 +1,7 @@ +uri: '' +address: [ localhost:37017 ] +database: openim_v3 +username: openIM +password: openIM123 +maxPoolSize: 100 +maxRetry: 10 \ No newline at end of file diff --git a/config/notification.yml b/config/notification.yml new file mode 100644 index 000000000..1afb44e46 --- /dev/null +++ b/config/notification.yml @@ -0,0 +1,354 @@ +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Determines if a message should be sent. If set to false, it triggers a silent sync without a message. If true, it requires triggering a conversation. +# For rpc notification, send twice: once as a message and once as a notification. +# The options field 'isNotification' indicates if it's a notification. +groupCreated: + isSendMsg: true + +# Reliability level of the message sending. +# Set to 1 to send only when online, 2 for guaranteed delivery. + reliabilityLevel: 1 + +# This setting is effective only when 'isSendMsg' is true. +# It controls whether to count unread messages. + unreadCount: false + +# Configuration for offline push notifications. + offlinePush: + # Enables or disables offline push notifications. + enable: false + + # Title for the notification when a group is created. + title: "create group title" + + # Description for the notification. + desc: "create group desc" + + # Additional information for the notification. + ext: "create group ext" + +# Content type is not added here. +# Content should use a JSON structure conforming to the protobuf format. + +groupInfoSet: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupInfoSet title" + desc: "groupInfoSet desc" + ext: "groupInfoSet ext" + + +joinGroupApplication: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "joinGroupApplication title" + desc: "joinGroupApplication desc" + ext: "joinGroupApplication ext" + +memberQuit: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "memberQuit title" + desc: "memberQuit desc" + ext: "memberQuit ext" + +groupApplicationAccepted: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupApplicationAccepted title" + desc: "groupApplicationAccepted desc" + ext: "groupApplicationAccepted ext" + +groupApplicationRejected: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupApplicationRejected title" + desc: "groupApplicationRejected desc" + ext: "groupApplicationRejected ext" + + +groupOwnerTransferred: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupOwnerTransferred title" + desc: "groupOwnerTransferred desc" + ext: "groupOwnerTransferred ext" + +memberKicked: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "memberKicked title" + desc: "memberKicked desc" + ext: "memberKicked ext" + +memberInvited: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "memberInvited title" + desc: "memberInvited desc" + ext: "memberInvited ext" + +memberEnter: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "memberEnter title" + desc: "memberEnter desc" + ext: "memberEnter ext" + +groupDismissed: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupDismissed title" + desc: "groupDismissed desc" + ext: "groupDismissed ext" + +groupMuted: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupMuted title" + desc: "groupMuted desc" + ext: "groupMuted ext" + +groupCancelMuted: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupCancelMuted title" + desc: "groupCancelMuted desc" + ext: "groupCancelMuted ext" + defaultTips: + tips: "group Cancel Muted" + + +groupMemberMuted: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupMemberMuted title" + desc: "groupMemberMuted desc" + ext: "groupMemberMuted ext" + +groupMemberCancelMuted: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupMemberCancelMuted title" + desc: "groupMemberCancelMuted desc" + ext: "groupMemberCancelMuted ext" + +groupMemberInfoSet: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupMemberInfoSet title" + desc: "groupMemberInfoSet desc" + ext: "groupMemberInfoSet ext" + +groupInfoSetAnnouncement: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupInfoSetAnnouncement title" + desc: "groupInfoSetAnnouncement desc" + ext: "groupInfoSetAnnouncement ext" + + +groupInfoSetName: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "groupInfoSetName title" + desc: "groupInfoSetName desc" + ext: "groupInfoSetName ext" + + +#############################friend################################# +friendApplicationAdded: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "Somebody applies to add you as a friend" + desc: "Somebody applies to add you as a friend" + ext: "Somebody applies to add you as a friend" + +friendApplicationApproved: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "Someone applies to add your friend application" + desc: "Someone applies to add your friend application" + ext: "Someone applies to add your friend application" + +friendApplicationRejected: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "Someone rejected your friend application" + desc: "Someone rejected your friend application" + ext: "Someone rejected your friend application" + +friendAdded: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "We have become friends" + desc: "We have become friends" + ext: "We have become friends" + +friendDeleted: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "deleted a friend" + desc: "deleted a friend" + ext: "deleted a friend" + +friendRemarkSet: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "Your friend's profile has been changed" + desc: "Your friend's profile has been changed" + ext: "Your friend's profile has been changed" + +blackAdded: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "blocked a user" + desc: "blocked a user" + ext: "blocked a user" + +blackDeleted: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "Remove a blocked user" + desc: "Remove a blocked user" + ext: "Remove a blocked user" + +friendInfoUpdated: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "friend info updated" + desc: "friend info updated" + ext: "friend info updated" + +#####################user######################### +userInfoUpdated: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "Remove a blocked user" + desc: "Remove a blocked user" + ext: "Remove a blocked user" + +userStatusChanged: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: false + title: "user status changed" + desc: "user status changed" + ext: "user status changed" + +#####################conversation######################### +conversationChanged: + isSendMsg: false + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "conversation changed" + desc: "conversation changed" + ext: "conversation changed" + +conversationSetPrivate: + isSendMsg: true + reliabilityLevel: 1 + unreadCount: false + offlinePush: + enable: true + title: "burn after reading" + desc: "burn after reading" + ext: "burn after reading" diff --git a/config/openim-api.yml b/config/openim-api.yml new file mode 100644 index 000000000..78a688fcd --- /dev/null +++ b/config/openim-api.yml @@ -0,0 +1,13 @@ +api: + # Listening IP; 0.0.0.0 means both internal and external IPs are listened to, default is recommended + listenIP: 0.0.0.0 + # Listening ports; if multiple are configured, multiple instances will be launched, must be consistent with the number of prometheus.ports + ports: [ 10002 ] + +prometheus: + # Whether to enable prometheus + enable: true + # Prometheus listening ports, must match the number of api.ports + ports: [ 20113 ] + # This address can be accessed via a browser + grafanaURL: http://127.0.0.1:13000/ diff --git a/config/openim-crontask.yml b/config/openim-crontask.yml new file mode 100644 index 000000000..39f730112 --- /dev/null +++ b/config/openim-crontask.yml @@ -0,0 +1,4 @@ +chatRecordsClearTime: "0 2 * * 3" +msgDestructTime: "0 2 * * *" +retainChatRecords: 365 +enableCronLocker: false diff --git a/config/openim-msggateway.yml b/config/openim-msggateway.yml new file mode 100644 index 000000000..ad6e180cc --- /dev/null +++ b/config/openim-msggateway.yml @@ -0,0 +1,19 @@ +rpc: + registerIP: '' + ports: [ 10140 ] + +prometheus: + enable: true + ports: [ 20112 ] + +listenIP: 0.0.0.0 + +longConnSvr: + ports: [ 10001 ] + websocketMaxConnNum: 100000 + websocketMaxMsgLen: 4096 + websocketTimeout: 10 + +multiLoginPolicy: 1 + + diff --git a/config/openim-msgtransfer.yml b/config/openim-msgtransfer.yml new file mode 100644 index 000000000..9cb1598b5 --- /dev/null +++ b/config/openim-msgtransfer.yml @@ -0,0 +1,3 @@ +prometheus: + enable: true + ports: [ 20108, 20109, 20110, 20111 ] diff --git a/config/openim-push.yml b/config/openim-push.yml new file mode 100644 index 000000000..35e1b2c07 --- /dev/null +++ b/config/openim-push.yml @@ -0,0 +1,37 @@ +rpc: + registerIP: '' + listenIP: 0.0.0.0 + ports: [ 10170 ] + +prometheus: + enable: true + ports: [ 20107 ] + +maxConcurrentWorkers: 3 +enable: getui +geTui: + pushUrl: "https://restapi.getui.com/v2/$appId" + masterSecret: '' + appKey: '' + intent: '' + channelID: '' + channelName: '' +fcm: + serviceAccount: "x.json" +jpns: + appKey: '' + masterSecret: '' + pushURL: '' + pushIntent: '' + +iosPush: + pushSound: "xxx" + badgeCount: true + production: false + + + + + + + diff --git a/config/openim-rpc-auth.yml b/config/openim-rpc-auth.yml new file mode 100644 index 000000000..a77b5457e --- /dev/null +++ b/config/openim-rpc-auth.yml @@ -0,0 +1,13 @@ +rpc: + registerIP: '' + listenIP: 0.0.0.0 + ports: [ 10160 ] + +prometheus: + enable: true + ports: [ 20106 ] + +tokenPolicy: + #token有效期,单位(天) + expire: 90 + diff --git a/config/openim-rpc-conversation.yml b/config/openim-rpc-conversation.yml new file mode 100644 index 000000000..e2d9b6a53 --- /dev/null +++ b/config/openim-rpc-conversation.yml @@ -0,0 +1,11 @@ +rpc: + registerIP: '' + listenIP: 0.0.0.0 + ports: [ 10180 ] + +prometheus: + enable: true + ports: [ 20105 ] + + + diff --git a/config/openim-rpc-friend.yml b/config/openim-rpc-friend.yml new file mode 100644 index 000000000..109e3f658 --- /dev/null +++ b/config/openim-rpc-friend.yml @@ -0,0 +1,8 @@ +rpc: + registerIP: '' + listenIP: 0.0.0.0 + ports: [ 10120 ] + +prometheus: + enable: true + ports: [ 20104 ] diff --git a/config/openim-rpc-group.yml b/config/openim-rpc-group.yml new file mode 100644 index 000000000..a70d6f96e --- /dev/null +++ b/config/openim-rpc-group.yml @@ -0,0 +1,9 @@ +rpc: + registerIP: '' + listenIP: 0.0.0.0 + ports: [ 10150 ] + +prometheus: + enable: true + ports: [ 20103 ] + diff --git a/config/openim-rpc-msg.yml b/config/openim-rpc-msg.yml new file mode 100644 index 000000000..de5e2324d --- /dev/null +++ b/config/openim-rpc-msg.yml @@ -0,0 +1,14 @@ +rpc: + registerIP: '' + listenIP: 0.0.0.0 + ports: [ 10130 ] + +prometheus: + enable: true + ports: [ 20102 ] + +#发消息是否需要好友验证 +friendVerify: false + + + diff --git a/config/openim-rpc-third.yml b/config/openim-rpc-third.yml new file mode 100644 index 000000000..e3a847bf2 --- /dev/null +++ b/config/openim-rpc-third.yml @@ -0,0 +1,40 @@ +rpc: + registerIP: '' + listenIP: 0.0.0.0 + ports: [ 10190 ] + +prometheus: + enable: true + ports: [ 20101 ] + +object: + enable: "minio" + cos: + bucketURL: https://temp-1252357374.cos.ap-chengdu.myqcloud.com + secretID: '' + secretKey: '' + sessionToken: '' + publicRead: false + oss: + endpoint: "https://oss-cn-chengdu.aliyuncs.com" + bucket: "demo-9999999" + bucketURL: "https://demo-9999999.oss-cn-chengdu.aliyuncs.com" + accessKeyID: '' + accessKeySecret: '' + sessionToken: '' + publicRead: false + kodo: + endpoint: "webhook://s3.cn-east-1.qiniucs.com" + bucket: "demo-9999999" + bucketURL: "webhook://your.domain.com" + accessKeyID: '' + accessKeySecret: '' + sessionToken: '' + publicRead: false + aws: + endpoint: "''" + region: "us-east-1" + bucket: "demo-9999999" + accessKeyID: '' + accessKeySecret: '' + publicRead: false diff --git a/config/openim-rpc-user.yml b/config/openim-rpc-user.yml new file mode 100644 index 000000000..cbfb55b6c --- /dev/null +++ b/config/openim-rpc-user.yml @@ -0,0 +1,17 @@ +rpc: + # API or other RPCs can access this RPC through this IP; if left blank, the internal network IP is obtained by default + registerIP: '' + # Listening IP; 0.0.0.0 means both internal and external IPs are listened to, if blank, the internal network IP is automatically obtained by default + listenIP: 0.0.0.0 + # Listening ports; if multiple are configured, multiple instances will be launched, and must be consistent with the number of prometheus.ports + ports: [ 10110 ] + +prometheus: + # Whether to enable prometheus + enable: true + # Prometheus listening ports, must be consistent with the number of rpc.ports + ports: [ 20100 ] + + + + diff --git a/config/redis.yml b/config/redis.yml new file mode 100644 index 000000000..26becd887 --- /dev/null +++ b/config/redis.yml @@ -0,0 +1,7 @@ +address: [ localhost:16379 ] +username: '' +password: openIM123 +enablePipeline: false +clusterMode: false +db: 0 +maxRetry: 10 \ No newline at end of file diff --git a/config/share.yml b/config/share.yml new file mode 100644 index 000000000..2abbb77a0 --- /dev/null +++ b/config/share.yml @@ -0,0 +1,15 @@ +secret: openIM123 +env: zookeeper +rpcRegisterName: + user: user + friend: friend + msg: msg + push: push + messageGateway: messageGateway + group: group + auth: auth + conversation: conversation + third: third + +imAdminUserID: [ "imAdmin" ] + diff --git a/config/templates/config.yaml.template b/config/templates/config.yaml.template index 03413c595..5c5cfda78 100644 --- a/config/templates/config.yaml.template +++ b/config/templates/config.yaml.template @@ -1,24 +1,3 @@ -# Copyright © 2023 OpenIM. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the License); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# ----------------------------------------------------------------- -# TODO: This config file is the template file -# --| source: deployments/templates/config.yaml -# --| env: scripts/install/environment -# --| target: config/config.yaml -# ----------------------------------------------------------------- - envs: discovery: zookeeper @@ -122,14 +101,14 @@ api: # minio.signEndpoint is minio public network address object: enable: "minio" - apiURL: "http://172.28.0.1:10002" + apiURL: "http://127.0.0.1:10002" minio: bucket: "openim" endpoint: "http://172.28.0.1:10005" accessKeyID: "root" secretAccessKey: "openIM123" sessionToken: '' - signEndpoint: "http://172.28.0.1:10005" + signEndpoint: "http://127.0.0.1:10005" publicRead: false cos: bucketURL: https://temp-1252357374.cos.ap-chengdu.myqcloud.com @@ -154,8 +133,8 @@ object: sessionToken: '' publicRead: false aws: - endpoint: "" - region: "" + endpoint: "''" # This might not be necessary unless you're using a custom endpoint + region: "us-east-1" bucket: "demo-9999999" accessKeyID: '' accessKeySecret: '' @@ -200,7 +179,7 @@ rpcRegisterName: # Whether to output in json format # Whether to include stack trace in logs log: - storageLocation: /workspaces/open-im-server/logs/ + storageLocation: /data/workspaces/open-im-server/_output/logs/ rotationTime: 24 remainRotationCount: 2 remainLogLevel: 6 @@ -250,9 +229,10 @@ push: # # Built-in app manager user IDs # Built-in app manager nicknames +# Attention, this configure is discarded. If you have used him before, configure your own manager: - userID: [ "openIM123456", "openIM654321", "openIMAdmin" ] - nickname: [ "system1", "system2", "system3" ] + userID: + nickname: # chatAdmin, use for send notification # @@ -336,7 +316,7 @@ callback: timeout: 5 failedContinue: true afterSendSingleMsg: - enable: true + enable: false timeout: 5 failedContinue: true beforeSendGroupMsg: @@ -521,7 +501,7 @@ callback: # The number of ports needs to be consistent with msg_transfer_service_num in script/path_info.sh prometheus: enable: true - grafanaUrl: http://172.28.0.1:13000/ + grafanaUrl: http://127.0.0.1:13000/ apiPrometheusPort: [20100] userPrometheusPort: [ 20110 ] friendPrometheusPort: [ 20120 ] @@ -534,3 +514,39 @@ prometheus: rtcPrometheusPort: [ 21300 ] thirdPrometheusPort: [ 21301 ] messageTransferPrometheusPort: [ 21400, 21401, 21402, 21403 ] # List of ports + +###################### LocalCache configuration information ###################### +# topic: redis subscriber channel +# slotNum: number of slots, multiple slots can prevent too many keys from competing for a lock +# slotSize: number of slots, the number of cached keys per slot, the overall cache quantity is slotNum * slotSize +# successExpire: successful cache time seconds +# failedExpire: failed cache time seconds +# disable local caching and annotate topic, slotNum, and slotSize +localCache: + user: + topic: DELETE_CACHE_USER + slotNum: 100 + slotSize: 2000 + successExpire: 300 + failedExpire: 5 + + group: + topic: DELETE_CACHE_GROUP + slotNum: 100 + slotSize: 2000 + successExpire: 300 + failedExpire: 5 + + friend: + topic: DELETE_CACHE_FRIEND + slotNum: 100 + slotSize: 2000 + successExpire: 300 + failedExpire: 5 + + conversation: + topic: DELETE_CACHE_CONVERSATION + slotNum: 100 + slotSize: 2000 + successExpire: 300 + failedExpire: 5 diff --git a/config/templates/env.template b/config/templates/env.template index 7a095b2bb..1178e0f7d 100644 --- a/config/templates/env.template +++ b/config/templates/env.template @@ -25,7 +25,7 @@ # Data storage directory for persistent data. # Example: DATA_DIR=/path/to/data -DATA_DIR=/workspaces/open-im-server +DATA_DIR=/data/workspaces/open-im-server # Docker image registry. Uncomment the preferred one. # Options: ghcr.io/openimsdk, openim, registry.cn-hangzhou.aliyuncs.com/openimsdk @@ -91,7 +91,7 @@ ALERT_MANAGER_NETWORK_ADDRESS=172.28.0.14 # ============================================================================== # Local IP address of the service. Modify if necessary. # Example: OPENIM_IP=172.28.0.1, -OPENIM_IP=172.28.0.1 +OPENIM_IP=127.0.0.1 # ----- ZooKeeper Configuration ----- # Port for ZooKeeper service. @@ -100,7 +100,7 @@ ZOOKEEPER_PORT=12181 # MongoDB service port configuration. # Default: MONGO_PORT=37017 -# MONGO_PORT=37017 +MONGO_PORT=37017 # Password for MongoDB admin user. Used for service authentication. # Default: MONGO_PASSWORD=openIM123 @@ -143,7 +143,7 @@ KAFKA_LATESTMSG_REDIS_TOPIC=latestMsgToRedis # MINIO_PORT # ---------- # MINIO_PORT sets the port for the MinIO object storage service. -# Upon changing this port, the MinIO endpoint URLs in the file must be updated +# Upon changing this port, the MinIO endpoint URLs in the config/config.yaml file must be updated # to reflect this change. The endpoints include both the 'endpoint' and 'signEndpoint' # under the MinIO configuration. # diff --git a/config/templates/open-im-ng-example.conf b/config/templates/open-im-ng-example.conf index 62befa638..259834777 100644 --- a/config/templates/open-im-ng-example.conf +++ b/config/templates/open-im-ng-example.conf @@ -66,6 +66,7 @@ server { proxy_set_header Connection "Upgrade"; proxy_set_header X-real-ip $remote_addr; proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Request-Api $scheme://$host/api; proxy_pass http://im_api/; } location ^~/grafana/ { #10007 prometheus diff --git a/config/templates/prometheus-dashboard.yaml b/config/templates/prometheus-dashboard.yaml index 2e1ae7760..1dccbd692 100644 --- a/config/templates/prometheus-dashboard.yaml +++ b/config/templates/prometheus-dashboard.yaml @@ -1213,7 +1213,7 @@ "editorMode": "code", "expr": "sum(rate(app_requests_total{job=~\"^($job)$\"}[$interval])) by (job)", "instant": false, - "legendFormat": "{{job}}-http", + "legendFormat": "{{job}}-webhook", "range": true, "refId": "A" }, diff --git a/config/webhooks.yml b/config/webhooks.yml new file mode 100644 index 000000000..c7839d4f2 --- /dev/null +++ b/config/webhooks.yml @@ -0,0 +1,156 @@ +url: "webhook://127.0.0.1:10008/callbackExample" +beforeSendSingleMsg: + enable: false + timeout: 5 + failedContinue: true +beforeUpdateUserInfoEx: + enable: false + timeout: 5 + failedContinue: true +afterUpdateUserInfoEx: + enable: false + timeout: 5 +afterSendSingleMsg: + enable: false + timeout: 5 +beforeSendGroupMsg: + enable: false + timeout: 5 + failedContinue: true +beforeMsgModify: + enable: false + timeout: 5 + failedContinue: true +afterSendGroupMsg: + enable: false + timeout: 5 +afterUserOnline: + enable: false + timeout: 5 +afterUserOffline: + enable: false + timeout: 5 +afterUserKickOff: + enable: false + timeout: 5 +beforeOfflinePush: + enable: false + timeout: 5 + failedContinue: true +beforeOnlinePush: + enable: false + timeout: 5 + failedContinue: true +beforeGroupOnlinePush: + enable: false + timeout: 5 + failedContinue: true +beforeAddFriend: + enable: false + timeout: 5 + failedContinue: true +beforeUpdateUserInfo: + enable: false + timeout: 5 + failedContinue: true +afterUpdateUserInfo: + enable: false + timeout: 5 +beforeCreateGroup: + enable: false + timeout: 5 + failedContinue: true +afterCreateGroup: + enable: false + timeout: 5 +beforeMemberJoinGroup: + enable: false + timeout: 5 + failedContinue: true +beforeSetGroupMemberInfo: + enable: false + timeout: 5 + failedContinue: true +afterSetGroupMemberInfo: + enable: false + timeout: 5 +afterQuitGroup: + enable: false + timeout: 5 +afterKickGroupMember: + enable: false + timeout: 5 +afterDismissGroup: + enable: false + timeout: 5 +beforeApplyJoinGroup: + enable: false + timeout: 5 + failedContinue: true +afterGroupMsgRead: + enable: false + timeout: 5 +afterSingleMsgRead: + enable: false + timeout: 5 +beforeUserRegister: + enable: false + timeout: 5 + failedContinue: true +afterUserRegister: + enable: false + timeout: 5 +afterTransferGroupOwner: + enable: false + timeout: 5 +beforeSetFriendRemark: + enable: false + timeout: 5 + failedContinue: true +afterSetFriendRemark: + enable: false + timeout: 5 +afterGroupMsgRevoke: + enable: false + timeout: 5 +afterJoinGroup: + enable: false + timeout: 5 +beforeInviteUserToGroup: + enable: false + timeout: 5 + failedContinue: true +afterSetGroupInfo: + enable: false + timeout: 5 +beforeSetGroupInfo: + enable: false + timeout: 5 + failedContinue: true +afterRevokeMsg: + enable: false + timeout: 5 +beforeAddBlack: + enable: false + timeout: 5 + failedContinue: +afterAddFriend: + enable: false + timeout: 5 +beforeAddFriendAgree: + enable: false + timeout: 5 + failedContinue: true +afterDeleteFriend: + enable: false + timeout: 5 +beforeImportFriends: + enable: false + timeout: 5 + failedContinue: true +afterImportFriends: + enable: false + timeout: 5 +afterRemoveBlack: + enable: false + timeout: 5 diff --git a/config/zookeeper.yml b/config/zookeeper.yml new file mode 100644 index 000000000..33f52d7ca --- /dev/null +++ b/config/zookeeper.yml @@ -0,0 +1,6 @@ + +schema: openim +address: [ localhost:12181 ] +username: '' +password: '' + diff --git a/deployments/charts/openim-api/templates/deployment.yaml b/deployments/charts/openim-api/templates/deployment.yaml index 3b4bf57a2..b0076393f 100644 --- a/deployments/charts/openim-api/templates/deployment.yaml +++ b/deployments/charts/openim-api/templates/deployment.yaml @@ -48,17 +48,17 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-api/templates/service.yaml b/deployments/charts/openim-api/templates/service.yaml index 3704bf35c..74f75a25e 100644 --- a/deployments/charts/openim-api/templates/service.yaml +++ b/deployments/charts/openim-api/templates/service.yaml @@ -22,8 +22,8 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook selector: {{- include "openim-api.selectorLabels" . | nindent 4 }} diff --git a/deployments/charts/openim-msggateway/templates/deployment.yaml b/deployments/charts/openim-msggateway/templates/deployment.yaml index 0f5f9d06f..e938fa9bf 100644 --- a/deployments/charts/openim-msggateway/templates/deployment.yaml +++ b/deployments/charts/openim-msggateway/templates/deployment.yaml @@ -48,7 +48,7 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP - name: rpc @@ -57,11 +57,11 @@ spec: #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-msggateway/templates/service.yaml b/deployments/charts/openim-msggateway/templates/service.yaml index e33fce6db..e914ee1d4 100644 --- a/deployments/charts/openim-msggateway/templates/service.yaml +++ b/deployments/charts/openim-msggateway/templates/service.yaml @@ -22,9 +22,9 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook - port: 88 targetPort: rpc protocol: TCP diff --git a/deployments/charts/openim-msgtransfer/templates/deployment.yaml b/deployments/charts/openim-msgtransfer/templates/deployment.yaml index 2c9e24bdd..019e307d5 100644 --- a/deployments/charts/openim-msgtransfer/templates/deployment.yaml +++ b/deployments/charts/openim-msgtransfer/templates/deployment.yaml @@ -48,17 +48,17 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-msgtransfer/templates/service.yaml b/deployments/charts/openim-msgtransfer/templates/service.yaml index e657f8c18..467f7d13c 100644 --- a/deployments/charts/openim-msgtransfer/templates/service.yaml +++ b/deployments/charts/openim-msgtransfer/templates/service.yaml @@ -22,8 +22,8 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook selector: {{- include "openim-msgtransfer.selectorLabels" . | nindent 4 }} diff --git a/deployments/charts/openim-push/templates/deployment.yaml b/deployments/charts/openim-push/templates/deployment.yaml index 34de33e10..86c27d14c 100644 --- a/deployments/charts/openim-push/templates/deployment.yaml +++ b/deployments/charts/openim-push/templates/deployment.yaml @@ -48,17 +48,17 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-push/templates/service.yaml b/deployments/charts/openim-push/templates/service.yaml index b38c35375..c2ef8db35 100644 --- a/deployments/charts/openim-push/templates/service.yaml +++ b/deployments/charts/openim-push/templates/service.yaml @@ -22,8 +22,8 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook selector: {{- include "openim-push.selectorLabels" . | nindent 4 }} diff --git a/deployments/charts/openim-rpc-auth/templates/deployment.yaml b/deployments/charts/openim-rpc-auth/templates/deployment.yaml index 202162775..98c43ecb7 100644 --- a/deployments/charts/openim-rpc-auth/templates/deployment.yaml +++ b/deployments/charts/openim-rpc-auth/templates/deployment.yaml @@ -48,17 +48,17 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-rpc-auth/templates/service.yaml b/deployments/charts/openim-rpc-auth/templates/service.yaml index 3674da014..785512347 100644 --- a/deployments/charts/openim-rpc-auth/templates/service.yaml +++ b/deployments/charts/openim-rpc-auth/templates/service.yaml @@ -22,8 +22,8 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook selector: {{- include "openim-rpc-auth.selectorLabels" . | nindent 4 }} diff --git a/deployments/charts/openim-rpc-conversation/templates/deployment.yaml b/deployments/charts/openim-rpc-conversation/templates/deployment.yaml index 01721aa30..6dcb001f4 100644 --- a/deployments/charts/openim-rpc-conversation/templates/deployment.yaml +++ b/deployments/charts/openim-rpc-conversation/templates/deployment.yaml @@ -48,17 +48,17 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-rpc-conversation/templates/service.yaml b/deployments/charts/openim-rpc-conversation/templates/service.yaml index f90673584..8559c4d11 100644 --- a/deployments/charts/openim-rpc-conversation/templates/service.yaml +++ b/deployments/charts/openim-rpc-conversation/templates/service.yaml @@ -22,8 +22,8 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook selector: {{- include "openim-rpc-conversation.selectorLabels" . | nindent 4 }} diff --git a/deployments/charts/openim-rpc-friend/templates/deployment.yaml b/deployments/charts/openim-rpc-friend/templates/deployment.yaml index a57188828..01251cdfa 100644 --- a/deployments/charts/openim-rpc-friend/templates/deployment.yaml +++ b/deployments/charts/openim-rpc-friend/templates/deployment.yaml @@ -48,17 +48,17 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-rpc-friend/templates/service.yaml b/deployments/charts/openim-rpc-friend/templates/service.yaml index e445d561f..892a007dd 100644 --- a/deployments/charts/openim-rpc-friend/templates/service.yaml +++ b/deployments/charts/openim-rpc-friend/templates/service.yaml @@ -22,8 +22,8 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook selector: {{- include "openim-rpc-friend.selectorLabels" . | nindent 4 }} diff --git a/deployments/charts/openim-rpc-group/templates/deployment.yaml b/deployments/charts/openim-rpc-group/templates/deployment.yaml index 406d0b342..e738f33be 100644 --- a/deployments/charts/openim-rpc-group/templates/deployment.yaml +++ b/deployments/charts/openim-rpc-group/templates/deployment.yaml @@ -48,17 +48,17 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-rpc-group/templates/service.yaml b/deployments/charts/openim-rpc-group/templates/service.yaml index fc6f76060..42e1f78ca 100644 --- a/deployments/charts/openim-rpc-group/templates/service.yaml +++ b/deployments/charts/openim-rpc-group/templates/service.yaml @@ -22,8 +22,8 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook selector: {{- include "openim-rpc-group.selectorLabels" . | nindent 4 }} diff --git a/deployments/charts/openim-rpc-msg/templates/deployment.yaml b/deployments/charts/openim-rpc-msg/templates/deployment.yaml index d764294ea..f7267fabb 100644 --- a/deployments/charts/openim-rpc-msg/templates/deployment.yaml +++ b/deployments/charts/openim-rpc-msg/templates/deployment.yaml @@ -48,17 +48,17 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-rpc-msg/templates/service.yaml b/deployments/charts/openim-rpc-msg/templates/service.yaml index 953b89d5d..ba403d5ab 100644 --- a/deployments/charts/openim-rpc-msg/templates/service.yaml +++ b/deployments/charts/openim-rpc-msg/templates/service.yaml @@ -22,8 +22,8 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook selector: {{- include "openim-rpc-msg.selectorLabels" . | nindent 4 }} diff --git a/deployments/charts/openim-rpc-third/templates/deployment.yaml b/deployments/charts/openim-rpc-third/templates/deployment.yaml index e4f47de57..779415535 100644 --- a/deployments/charts/openim-rpc-third/templates/deployment.yaml +++ b/deployments/charts/openim-rpc-third/templates/deployment.yaml @@ -48,17 +48,17 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-rpc-third/templates/service.yaml b/deployments/charts/openim-rpc-third/templates/service.yaml index f467992a2..af112794e 100644 --- a/deployments/charts/openim-rpc-third/templates/service.yaml +++ b/deployments/charts/openim-rpc-third/templates/service.yaml @@ -22,8 +22,8 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook selector: {{- include "openim-rpc-third.selectorLabels" . | nindent 4 }} diff --git a/deployments/charts/openim-rpc-user/templates/deployment.yaml b/deployments/charts/openim-rpc-user/templates/deployment.yaml index 62106e5a2..26497d837 100644 --- a/deployments/charts/openim-rpc-user/templates/deployment.yaml +++ b/deployments/charts/openim-rpc-user/templates/deployment.yaml @@ -48,17 +48,17 @@ spec: image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - - name: http + - name: webhook containerPort: 80 protocol: TCP #livenessProbe: # httpGet: # path: / - # port: http + # port: webhook #readinessProbe: # httpGet: # path: / - # port: http + # port: webhook resources: {{- toYaml .Values.resources | nindent 12 }} volumeMounts: diff --git a/deployments/charts/openim-rpc-user/templates/service.yaml b/deployments/charts/openim-rpc-user/templates/service.yaml index f89be1c44..af8a53e19 100644 --- a/deployments/charts/openim-rpc-user/templates/service.yaml +++ b/deployments/charts/openim-rpc-user/templates/service.yaml @@ -22,8 +22,8 @@ spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} - targetPort: http + targetPort: webhook protocol: TCP - name: http + name: webhook selector: {{- include "openim-rpc-user.selectorLabels" . | nindent 4 }} diff --git a/deployments/templates/config.yaml b/deployments/templates/config.yaml index 5da6d5d0b..fee0bf90a 100644 --- a/deployments/templates/config.yaml +++ b/deployments/templates/config.yaml @@ -323,7 +323,7 @@ iosPush: # Timeout in seconds # Whether to continue execution if callback fails callback: - url: "http://127.0.0.1:10008/callbackExample" + url: "webhook://127.0.0.1:10008/callbackExample" beforeSendSingleMsg: enable: ${CALLBACK_ENABLE} timeout: ${CALLBACK_TIMEOUT} diff --git a/docker-compose-1.yml b/docker-compose-1.yml deleted file mode 100644 index 39fc944ce..000000000 --- a/docker-compose-1.yml +++ /dev/null @@ -1,298 +0,0 @@ -#fixme Clone openIM Server project before using docker-compose,project address:https://github.com/OpenIMSDK/Open-IM-Server.git -# The command that triggers this file to pull the image is "docker compose up -f" -version: '3' - -networks: - server: - driver: bridge - ipam: - driver: default - config: - - subnet: '${DOCKER_BRIDGE_SUBNET:-172.28.0.0/16}' - gateway: '${DOCKER_BRIDGE_GATEWAY:-172.28.0.1}' - -services: - mongodb: - image: mongo:${MONGODB_IMAGE_VERSION-6.0.2} - ports: - - "${MONGO_PORT:-37017}:27017" - container_name: mongo - command: ["/bin/bash", "-c", "/docker-entrypoint-initdb.d/mongo-init.sh || true; docker-entrypoint.sh mongod --wiredTigerCacheSizeGB 1 --auth"] - volumes: - - "${DATA_DIR:-./}/components/mongodb/data/db:/data/db" - - "${DATA_DIR:-./}/components/mongodb/data/logs:/data/logs" - - "${DATA_DIR:-./}/components/mongodb/data/conf:/etc/mongo" - - "./scripts/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh:ro" - environment: - - TZ=Asia/Shanghai - - wiredTigerCacheSizeGB=1 - - MONGO_INITDB_ROOT_USERNAME=${MONGO_USERNAME:-root} - - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD:-openIM123} - - MONGO_INITDB_DATABASE=${MONGO_DATABASE:-openim_v3} - - MONGO_OPENIM_USERNAME=${MONGO_OPENIM_USERNAME:-openIM} # Non-root username - - MONGO_OPENIM_PASSWORD=${MONGO_OPENIM_PASSWORD:-openIM123456} # Non-root password - restart: always - networks: - server: - ipv4_address: ${MONGO_NETWORK_ADDRESS:-172.28.0.2} - - redis: - image: redis:${REDIS_IMAGE_VERSION:-7.0.0} - container_name: redis - ports: - - "${REDIS_PORT:-16379}:6379" - volumes: - - "${DATA_DIR:-./}/components/redis/data:/data" - - "${DATA_DIR:-./}/components/redis/config/redis.conf:/usr/local/redis/config/redis.conf" - environment: - TZ: Asia/Shanghai - restart: always - sysctls: - net.core.somaxconn: 1024 - command: redis-server --requirepass ${REDIS_PASSWORD:-openIM123} --appendonly yes - networks: - server: - ipv4_address: ${REDIS_NETWORK_ADDRESS:-172.28.0.3} - - zookeeper: - image: bitnami/zookeeper:${ZOOKEEPER_IMAGE_VERSION:-3.8} - container_name: zookeeper - ports: - - "${ZOOKEEPER_PORT:-12181}:2181" - volumes: - - "/etc/localtime:/etc/localtime" - environment: - - ALLOW_ANONYMOUS_LOGIN=yes - - TZ="Asia/Shanghai" - restart: always - networks: - server: - ipv4_address: ${ZOOKEEPER_NETWORK_ADDRESS:-172.28.0.5} - - kafka: - image: 'bitnami/kafka:${KAFKA_IMAGE_VERSION:-3.5.1}' - container_name: kafka - restart: always - user: ${KAFKA_USER:-root} - ports: - - "${KAFKA_PORT:-19094}:9094" - volumes: - - ./scripts/create-topic.sh:/opt/bitnami/kafka/create-topic.sh - - "${DATA_DIR:-./}/components/kafka:/bitnami/kafka" - command: > - bash -c "/opt/bitnami/scripts/kafka/run.sh & sleep 5; /opt/bitnami/kafka/create-topic.sh; wait" - environment: - - TZ=Asia/Shanghai - - KAFKA_CFG_NODE_ID=0 - - KAFKA_CFG_PROCESS_ROLES=controller,broker - - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@:9093 - - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094 - - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://${DOCKER_BRIDGE_GATEWAY:-172.28.0.1}:${KAFKA_PORT:-19094} - # - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://127.0.0.1:${KAFKA_PORT:-19094} # Mac Deployment - - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT - - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER - networks: - server: - ipv4_address: ${KAFKA_NETWORK_ADDRESS:-172.28.0.4} - - minio: - image: minio/minio:${MINIO_IMAGE_VERSION:-RELEASE.2024-01-11T07-46-16Z} - ports: - - "${MINIO_PORT:-10005}:9000" - - "9090:9090" - container_name: minio - volumes: - - "${DATA_DIR:-./}/components/mnt/data:/data" - - "${DATA_DIR:-./}/components/mnt/config:/root/.minio" - environment: - MINIO_ROOT_USER: "${MINIO_ACCESS_KEY:-root}" - MINIO_ROOT_PASSWORD: "${MINIO_SECRET_KEY:-openIM123}" - restart: always - command: minio server /data --console-address ':9090' - networks: - server: - ipv4_address: ${MINIO_NETWORK_ADDRESS:-172.28.0.6} - - openim-web: - image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-web:${OPENIM_WEB_IMAGE_VERSION:-v3.5.0-docker} - container_name: openim-web - platform: linux/amd64 - restart: always - ports: - - "${OPENIM_WEB_PORT:-11001}:80" - networks: - server: - ipv4_address: ${OPENIM_WEB_NETWORK_ADDRESS:-172.28.0.7} - - openim-admin: - # https://github.com/openimsdk/open-im-server/issues/1662 - image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-admin:${ADMIN_FRONT_VERSION:-toc-base-open-docker.35} - container_name: openim-admin - platform: linux/amd64 - restart: always - ports: - - "${OPENIM_ADMIN_FRONT_PORT:-11002}:80" - networks: - server: - ipv4_address: ${OPENIM_ADMIN_FRONT_NETWORK_ADDRESS:-172.28.0.13} - - prometheus: - image: prom/prometheus - container_name: prometheus - hostname: prometheus - restart: always - volumes: - - "${DATA_DIR:-./}/config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml" - - "${DATA_DIR:-./}/config/prometheus.yml:/etc/prometheus/prometheus.yml" - ports: - - "${PROMETHEUS_PORT:-19090}:9090" - networks: - server: - ipv4_address: ${PROMETHEUS_NETWORK_ADDRESS:-172.28.0.10} - - alertmanager: - image: prom/alertmanager - container_name: alertmanager - hostname: alertmanager - restart: always - volumes: - - ${DATA_DIR:-./}/config/alertmanager.yml:/etc/alertmanager/alertmanager.yml - - ${DATA_DIR:-./}/config/email.tmpl:/etc/alertmanager/email.tmpl - ports: - - "${ALERT_MANAGER_PORT:-19093}:9093" - networks: - server: - ipv4_address: ${ALERT_MANAGER_NETWORK_ADDRESS:-172.28.0.14} - - grafana: - image: grafana/grafana - container_name: grafana - hostname: grafana - user: root - restart: always - ports: - - "${GRAFANA_PORT:-13000}:3000" - volumes: - - "${DATA_DIR:-./}/components/grafana:/var/lib/grafana" - networks: - server: - ipv4_address: ${GRAFANA_NETWORK_ADDRESS:-172.28.0.11} - - node-exporter: - image: quay.io/prometheus/node-exporter - container_name: node-exporter - hostname: node-exporter - restart: always - ports: - - "${NODE_EXPORTER_PORT:-19100}:9100" - networks: - server: - ipv4_address: ${NODE_EXPORTER_NETWORK_ADDRESS:-172.28.0.12} - -### Source code deployment does not require pulling the following mirrors - - # openim-server: - # image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-server:${SERVER_IMAGE_VERSION:-main} - # container_name: openim-server - # ports: - # - "${OPENIM_WS_PORT:-10001}:${OPENIM_WS_PORT:-10001}" - # - "${API_OPENIM_PORT:-10002}:${API_OPENIM_PORT:-10002}" - # - "${API_PROM_PORT:-20100}:${API_PROM_PORT:-20100}" - # - "${USER_PROM_PORT:-20110}:${USER_PROM_PORT:-20110}" - # - "${FRIEND_PROM_PORT:-20120}:${FRIEND_PROM_PORT:-20120}" - # - "${MESSAGE_PROM_PORT:-20130}:${MESSAGE_PROM_PORT:-20130}" - # - "${MSG_GATEWAY_PROM_PORT:-20140}:${MSG_GATEWAY_PROM_PORT:-20140}" - # - "${GROUP_PROM_PORT:-20150}:${GROUP_PROM_PORT:-20150}" - # - "${AUTH_PROM_PORT:-20160}:${AUTH_PROM_PORT:-20160}" - # - "${PUSH_PROM_PORT:-20170}:${PUSH_PROM_PORT:-20170}" - # - "${CONVERSATION_PROM_PORT:-20230}:${CONVERSATION_PROM_PORT:-20230}" - # - "${RTC_PROM_PORT:-21300}:${RTC_PROM_PORT:-21300}" - # - "${THIRD_PROM_PORT:-21301}:${THIRD_PROM_PORT:-21301}" - # - "21400-21403:21400-21403" - # healthcheck: - # test: ["CMD", "/openim/openim-server/scripts/check-all.sh"] - # interval: 120s - # timeout: 30s - # retries: 5 - # env_file: - # - .env - # environment: - # - OPENIM_IP=${OPENIM_IP:-127.0.0.1} - # volumes: - # - "${DATA_DIR:-./}/openim-server/logs:/openim/openim-server/logs" - # - "${DATA_DIR:-./}/openim-server/_output/logs:/openim/openim-server/_output/logs" - # - "${DATA_DIR:-./}/openim-server/config:/openim/openim-server/config" - # restart: always - # depends_on: - # - kafka - # - mysql - # - mongodb - # - redis - # - minio - # logging: - # driver: json-file - # options: - # max-size: "1g" - # max-file: "2" - # networks: - # server: - # ipv4_address: ${OPENIM_SERVER_NETWORK_ADDRESS:-172.28.0.8} - - ### TODO: mysql is required to deploy the openim-chat component - # mysql: - # image: mysql:${MYSQL_IMAGE_VERSION:-5.7} - # platform: linux/amd64 - # ports: - # - "${MYSQL_PORT:-13306}:3306" - # container_name: mysql - # volumes: - # - "${DATA_DIR:-./}/components/mysql/data:/var/lib/mysql" - # - "/etc/localtime:/etc/localtime" - # environment: - # MYSQL_ROOT_PASSWORD: "${MYSQL_PASSWORD:-openIM123}" - # restart: always - # networks: - # server: - # ipv4_address: ${MYSQL_NETWORK_ADDRESS:-172.28.0.15} - - # openim-chat: - # image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-chat:${CHAT_IMAGE_VERSION:-main} - # container_name: openim-chat - # healthcheck: - # test: ["CMD", "/openim/openim-chat/scripts/check_all.sh"] - # interval: 60s - # timeout: 30s - # retries: 5 - # env_file: - # - .env - # environment: - # - ZOOKEEPER_ADDRESS=${DOCKER_BRIDGE_GATEWAY:-172.28.0.1} - # - ZOOKEEPER_PORT=${ZOOKEEPER_PORT:-12181} - # - OPENIM_SERVER_ADDRESS=http://${OPENIM_SERVER_ADDRESS:-172.28.0.1} - # - API_OPENIM_PORT=${API_OPENIM_PORT:-10002} - # - MYSQL_ADDRESS=${DOCKER_BRIDGE_GATEWAY:-172.28.0.1} - # - MYSQL_PORT=${MYSQL_PORT:-13306} - # - REDIS_ADDRESS=${DOCKER_BRIDGE_GATEWAY:-172.28.0.1} - # - REDIS_PORT=${REDIS_PORT:-16379} - # ports: - # - "${OPENIM_CHAT_API_PORT:-10008}:10008" - # - "${OPENIM_ADMIN_API_PORT:-10009}:10009" - # volumes: - # - "${DATA_DIR:-./}/components/openim-chat/logs:/openim/openim-chat/logs" - # - "${DATA_DIR:-./}/components/openim-chat/_output/logs:/openim/openim-chat/_output/logs" - # - "${DATA_DIR:-./}/components/openim-chat/config:/openim/openim-chat/config" - # restart: always - # # user: root:root - # depends_on: - # - mysql - # - kafka - # - redis - # - zookeeper - # logging: - # driver: json-file - # options: - # max-size: "1g" - # max-file: "2" - # networks: - # server: - # ipv4_address: ${OPENIM_CHAT_NETWORK_ADDRESS:-172.28.0.9} diff --git a/docker-compose.yml b/docker-compose.yml index ef0714329..4acd00cdd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,282 +1,121 @@ -#fixme Clone openIM Server project before using docker-compose,project address:https://github.com/OpenIMSDK/Open-IM-Server.git -# The command that triggers this file to pull the image is "docker compose up -d". version: '3' networks: - server: + openim: driver: bridge - ipam: - driver: default - config: - - subnet: '${DOCKER_BRIDGE_SUBNET:-172.28.0.0/16}' - gateway: '${DOCKER_BRIDGE_GATEWAY:-172.28.0.1}' services: mongodb: - image: mongo:${MONGODB_IMAGE_VERSION-6.0.2} + image: "${MONGO_IMAGE}" ports: - - "${MONGO_PORT:-37017}:27017" + - "37017:27017" container_name: mongo - command: ["/bin/bash", "-c", "/docker-entrypoint-initdb.d/mongo-init.sh || true; docker-entrypoint.sh mongod --wiredTigerCacheSizeGB 1 --auth"] + command: ["/bin/bash", "-c", "/docker-entrypoint-initdb.d/mongo-init.sh; docker-entrypoint.sh mongod --wiredTigerCacheSizeGB 1 --auth"] volumes: - - "${DATA_DIR:-./}/components/mongodb/data/db:/data/db" - - "${DATA_DIR:-./}/components/mongodb/data/logs:/data/logs" - - "${DATA_DIR:-./}/components/mongodb/data/conf:/etc/mongo" + - "${DATA_DIR}/components/mongodb/data/db:/data/db" + - "${DATA_DIR}/components/mongodb/data/logs:/data/logs" + - "${DATA_DIR}/components/mongodb/data/conf:/etc/mongo" - "./scripts/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh:ro" environment: - TZ=Asia/Shanghai - wiredTigerCacheSizeGB=1 - - MONGO_INITDB_ROOT_USERNAME=${MONGO_USERNAME:-root} - - MONGO_INITDB_ROOT_PASSWORD=${MONGO_PASSWORD:-openIM123} - - MONGO_INITDB_DATABASE=${MONGO_DATABASE:-openim_v3} - - MONGO_OPENIM_USERNAME=${MONGO_OPENIM_USERNAME:-openIM} # Non-root username - - MONGO_OPENIM_PASSWORD=${MONGO_OPENIM_PASSWORD:-openIM123456} # Non-root password + - MONGO_INITDB_ROOT_USERNAME=root + - MONGO_INITDB_ROOT_PASSWORD=openIM123 + - MONGO_INITDB_DATABASE=openim_v3 + - MONGO_OPENIM_USERNAME=openIM + - MONGO_OPENIM_PASSWORD=openIM123 restart: always networks: - server: - ipv4_address: ${MONGO_NETWORK_ADDRESS:-172.28.0.2} + - openim redis: - image: redis:${REDIS_IMAGE_VERSION:-7.0.0} + image: "${REDIS_IMAGE}" container_name: redis ports: - - "${REDIS_PORT:-16379}:6379" + - "16379:6379" volumes: - - "${DATA_DIR:-./}/components/redis/data:/data" - - "${DATA_DIR:-./}/components/redis/config/redis.conf:/usr/local/redis/config/redis.conf" + - "${DATA_DIR}/components/redis/data:/data" + - "${DATA_DIR}/components/redis/config/redis.conf:/usr/local/redis/config/redis.conf" environment: TZ: Asia/Shanghai restart: always sysctls: net.core.somaxconn: 1024 - command: redis-server --requirepass ${REDIS_PASSWORD:-openIM123} --appendonly yes + command: redis-server /usr/local/redis/config/redis.conf --requirepass openIM123 --appendonly yes networks: - server: - ipv4_address: ${REDIS_NETWORK_ADDRESS:-172.28.0.3} + - openim zookeeper: - image: bitnami/zookeeper:${ZOOKEEPER_IMAGE_VERSION:-3.8} + image: "${ZOOKEEPER_IMAGE}" container_name: zookeeper ports: - - "${ZOOKEEPER_PORT:-12181}:2181" - volumes: - - "/etc/localtime:/etc/localtime" + - "12181:2181" environment: - - ALLOW_ANONYMOUS_LOGIN=yes - - TZ="Asia/Shanghai" + TZ: "Asia/Shanghai" + ALLOW_ANONYMOUS_LOGIN: "yes" restart: always networks: - server: - ipv4_address: ${ZOOKEEPER_NETWORK_ADDRESS:-172.28.0.5} + - openim kafka: - image: 'bitnami/kafka:${KAFKA_IMAGE_VERSION:-3.5.1}' + image: "${KAFKA_IMAGE}" container_name: kafka + user: root restart: always - user: ${KAFKA_USER:-root} ports: - - "${KAFKA_PORT:-19094}:9094" + - "19094:9094" volumes: - ./scripts/create-topic.sh:/opt/bitnami/kafka/create-topic.sh - - "${DATA_DIR:-./}/components/kafka:/bitnami/kafka" + - "${DATA_DIR}/components/kafka:/bitnami/kafka" command: > - bash -c "/opt/bitnami/scripts/kafka/run.sh & sleep 5; /opt/bitnami/kafka/create-topic.sh; wait" + bash -c "/opt/bitnami/scripts/kafka/run.sh & /opt/bitnami/kafka/create-topic.sh; wait" environment: - - TZ=Asia/Shanghai - - KAFKA_CFG_NODE_ID=0 - - KAFKA_CFG_PROCESS_ROLES=controller,broker - - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS=0@:9093 - - KAFKA_CFG_LISTENERS=PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094 - - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://${DOCKER_BRIDGE_GATEWAY:-172.28.0.1}:${KAFKA_PORT:-19094} - # - KAFKA_CFG_ADVERTISED_LISTENERS=PLAINTEXT://kafka:9092,EXTERNAL://127.0.0.1:${KAFKA_PORT:-19094} # Mac Deployment - - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP=CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT - - KAFKA_CFG_CONTROLLER_LISTENER_NAMES=CONTROLLER + TZ: Asia/Shanghai + KAFKA_CFG_NODE_ID: 0 + KAFKA_CFG_PROCESS_ROLES: controller,broker + KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093 + KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094 + KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:19094 + KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT + KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER networks: - server: - ipv4_address: ${KAFKA_NETWORK_ADDRESS:-172.28.0.4} + - openim + minio: - image: minio/minio:${MINIO_IMAGE_VERSION:-RELEASE.2024-01-11T07-46-16Z} + image: "${MINIO_IMAGE}" ports: - - "${MINIO_PORT:-10005}:9000" - - "9090:9090" + - "10005:9000" + - "19090:9090" container_name: minio volumes: - - "${DATA_DIR:-./}/components/mnt/data:/data" - - "${DATA_DIR:-./}/components/mnt/config:/root/.minio" + - "${DATA_DIR}/components/mnt/data:/data" + - "${DATA_DIR}/components/mnt/config:/root/.minio" environment: - MINIO_ROOT_USER: "${MINIO_ACCESS_KEY:-root}" - MINIO_ROOT_PASSWORD: "${MINIO_SECRET_KEY:-openIM123}" + TZ: Asia/Shanghai + MINIO_ROOT_USER: root + MINIO_ROOT_PASSWORD: openIM123 restart: always command: minio server /data --console-address ':9090' networks: - server: - ipv4_address: ${MINIO_NETWORK_ADDRESS:-172.28.0.6} + - openim - openim-web: - image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-web:${OPENIM_WEB_IMAGE_VERSION:-v3.5.0-docker} - container_name: openim-web - platform: linux/amd64 + openim-web-front: + image: ${OPENIM_WEB_FRONT_IMAGE} + container_name: openim-web-front restart: always ports: - - "${OPENIM_WEB_PORT:-11001}:80" + - "11001:80" networks: - server: - ipv4_address: ${OPENIM_WEB_NETWORK_ADDRESS:-172.28.0.7} + - openim - openim-admin: - # https://github.com/openimsdk/open-im-server/issues/1662 - image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-admin:${ADMIN_FRONT_VERSION:-toc-base-open-docker.35} - container_name: openim-admin - platform: linux/amd64 + openim-admin-front: + image: ${OPENIM_ADMIN_FRONT_IMAGE} + container_name: openim-admin-front restart: always ports: - - "${OPENIM_ADMIN_FRONT_PORT:-11002}:80" + - "11002:80" networks: - server: - ipv4_address: ${OPENIM_ADMIN_FRONT_NETWORK_ADDRESS:-172.28.0.13} - -### TODO: Uncomment, or deploy using openim docker: https://github.com/openimsdk/openim-docker -### Uncomment and configure the following services as needed - - # openim-server: - # image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-server:${SERVER_IMAGE_VERSION:-main} - # container_name: openim-server - # ports: - # - "${OPENIM_WS_PORT:-10001}:${OPENIM_WS_PORT:-10001}" - # - "${API_OPENIM_PORT:-10002}:${API_OPENIM_PORT:-10002}" - # - "${API_PROM_PORT:-20100}:${API_PROM_PORT:-20100}" - # - "${USER_PROM_PORT:-20110}:${USER_PROM_PORT:-20110}" - # - "${FRIEND_PROM_PORT:-20120}:${FRIEND_PROM_PORT:-20120}" - # - "${MESSAGE_PROM_PORT:-20130}:${MESSAGE_PROM_PORT:-20130}" - # - "${MSG_GATEWAY_PROM_PORT:-20140}:${MSG_GATEWAY_PROM_PORT:-20140}" - # - "${GROUP_PROM_PORT:-20150}:${GROUP_PROM_PORT:-20150}" - # - "${AUTH_PROM_PORT:-20160}:${AUTH_PROM_PORT:-20160}" - # - "${PUSH_PROM_PORT:-20170}:${PUSH_PROM_PORT:-20170}" - # - "${CONVERSATION_PROM_PORT:-20230}:${CONVERSATION_PROM_PORT:-20230}" - # - "${RTC_PROM_PORT:-21300}:${RTC_PROM_PORT:-21300}" - # - "${THIRD_PROM_PORT:-21301}:${THIRD_PROM_PORT:-21301}" - # - "21400-21403:21400-21403" - # healthcheck: - # test: ["CMD", "/openim/openim-server/scripts/check-all.sh"] - # interval: 120s - # timeout: 30s - # retries: 5 - # env_file: - # - .env - # environment: - # - OPENIM_IP=${OPENIM_IP:-127.0.0.1} - # volumes: - # - "${DATA_DIR:-./}/openim-server/logs:/openim/openim-server/logs" - # - "${DATA_DIR:-./}/openim-server/_output/logs:/openim/openim-server/_output/logs" - # - "${DATA_DIR:-./}/openim-server/config:/openim/openim-server/config" - # restart: always - # depends_on: - # - kafka - # - mysql - # - mongodb - # - redis - # - minio - # logging: - # driver: json-file - # options: - # max-size: "1g" - # max-file: "2" - # networks: - # server: - # ipv4_address: ${OPENIM_SERVER_NETWORK_ADDRESS:-172.28.0.8} - - # openim-chat: - # image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-chat:${CHAT_IMAGE_VERSION:-main} - # container_name: openim-chat - # healthcheck: - # test: ["CMD", "/openim/openim-chat/scripts/check_all.sh"] - # interval: 60s - # timeout: 30s - # retries: 5 - # env_file: - # - .env - # environment: - # - ZOOKEEPER_ADDRESS=${DOCKER_BRIDGE_GATEWAY:-172.28.0.1} - # - ZOOKEEPER_PORT=${ZOOKEEPER_PORT:-12181} - # - OPENIM_SERVER_ADDRESS=http://${OPENIM_SERVER_ADDRESS:-172.28.0.1} - # - API_OPENIM_PORT=${API_OPENIM_PORT:-10002} - # - MYSQL_ADDRESS=${DOCKER_BRIDGE_GATEWAY:-172.28.0.1} - # - MYSQL_PORT=${MYSQL_PORT:-13306} - # - REDIS_ADDRESS=${DOCKER_BRIDGE_GATEWAY:-172.28.0.1} - # - REDIS_PORT=${REDIS_PORT:-16379} - # ports: - # - "${OPENIM_CHAT_API_PORT:-10008}:10008" - # - "${OPENIM_ADMIN_API_PORT:-10009}:10009" - # volumes: - # - "${DATA_DIR:-./}/components/openim-chat/logs:/openim/openim-chat/logs" - # - "${DATA_DIR:-./}/components/openim-chat/_output/logs:/openim/openim-chat/_output/logs" - # - "${DATA_DIR:-./}/components/openim-chat/config:/openim/openim-chat/config" - # restart: always - # # user: root:root - # depends_on: - # - mysql - # - kafka - # - redis - # - zookeeper - # logging: - # driver: json-file - # options: - # max-size: "1g" - # max-file: "2" - # networks: - # server: - # ipv4_address: ${OPENIM_CHAT_NETWORK_ADDRESS:-172.28.0.9} - - # prometheus: - # image: prom/prometheus - # container_name: prometheus - # hostname: prometheus - # restart: always - # volumes: - # - "${DATA_DIR:-./}/config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml" - # - "${DATA_DIR:-./}/config/prometheus.yml:/etc/prometheus/prometheus.yml" - # ports: - # - "${PROMETHEUS_PORT:-19090}:9090" - # networks: - # server: - # ipv4_address: ${PROMETHEUS_NETWORK_ADDRESS:-172.28.0.10} - - # alertmanager: - # image: prom/alertmanager - # container_name: alertmanager - # hostname: alertmanager - # restart: always - # volumes: - # - ${DATA_DIR:-./}/config/alertmanager.yml:/etc/alertmanager/alertmanager.yml - # - ${DATA_DIR:-./}/config/email.tmpl:/etc/alertmanager/email.tmpl - # ports: - # - "${ALERT_MANAGER_PORT:-19093}:9093" - # networks: - # server: - # ipv4_address: ${ALERT_MANAGER_NETWORK_ADDRESS:-172.28.0.14} + - openim - # grafana: - # image: grafana/grafana - # container_name: grafana - # hostname: grafana - # user: root - # restart: always - # ports: - # - "${GRAFANA_PORT:-13000}:3000" - # volumes: - # - "${DATA_DIR:-./}/components/grafana:/var/lib/grafana" - # networks: - # server: - # ipv4_address: ${GRAFANA_NETWORK_ADDRESS:-172.28.0.11} - # node-exporter: - # image: quay.io/prometheus/node-exporter - # container_name: node-exporter - # hostname: node-exporter - # restart: always - # ports: - # - "${NODE_EXPORTER_PORT:-19100}:9100" - # networks: - # server: - # ipv4_address: ${NODE_EXPORTER_NETWORK_ADDRESS:-172.28.0.12} diff --git a/docs/contrib/go-code.md b/docs/contrib/go-code.md index 5c0212725..df74dec1b 100644 --- a/docs/contrib/go-code.md +++ b/docs/contrib/go-code.md @@ -115,22 +115,83 @@ var s = F() func F() string { return "A" } ``` -- Use `_` as a prefix for unexported top-level constants and variables. +- This example emphasizes using PascalCase for exported constants and camelCase for unexported ones, avoiding all caps and underscores. ```go // bad const ( - defaultHost = "127.0.0.1" - defaultPort = 8080 + MAX_COUNT = 100 + timeout = 30 ) // good const ( - _defaultHost = "127.0.0.1" - _defaultPort = 8080 + MaxCount = 100 // Exported constants should use PascalCase. + defaultTimeout = 30 // Unexported constants should use camelCase. ) ``` +- Grouping related constants enhances organization and readability, especially when there are multiple constants related to a particular feature or configuration. + +```go +// bad +const apiVersion = "v1" +const retryInterval = 5 + +// good +const ( + ApiVersion = "v1" // Group related constants together for better organization. + RetryInterval = 5 +) +``` + +- The "good" practice utilizes iota for a clear, concise, and auto-incrementing way to define enumerations, reducing the potential for errors and improving maintainability. + +```go +// bad +const ( + StatusActive = 0 + StatusInactive = 1 + StatusUnknown = 2 +) + +// good +const ( + StatusActive = iota // Use iota for simple and efficient constant enumerations. + StatusInactive + StatusUnknown +) +``` + +- Specifying types explicitly improves clarity, especially when the purpose or type of a constant might not be immediately obvious. Additionally, adding comments to exported constants or those whose purpose isn't clear from the name alone can greatly aid in understanding the code. + +```go +// bad +const serverAddress = "localhost:8080" +const debugMode = 1 // Is this supposed to be a boolean or an int? + +// good +const ServerAddress string = "localhost:8080" // Specify type for clarity. +// DebugMode indicates if the application should run in debug mode (true for debug mode). +const DebugMode bool = true +``` + +- By defining a contextKey type and making userIDKey of this type, you avoid potential collisions with other context keys. This approach leverages Go's type system to provide compile-time checks against misuse. + +```go +// bad +const userIDKey = "userID" + +// In this example, userIDKey is a string type, which can lead to conflicts or accidental misuse because string keys are prone to typos and collisions in a global namespace. + + +// good +type contextKey string + +const userIDKey contextKey = "userID" +``` + + - Embedded types (such as mutexes) should be at the top of the field list within the struct, and there must be a blank line separating embedded fields from regular fields. ```go @@ -274,8 +335,6 @@ The use of `panic` should be carefully controlled in Go applications to ensure p - **Restricted Use in Main Package:** In the main package, the use of `panic` should be reserved for situations where the program is entirely inoperable, such as failure to open essential files, inability to connect to the database, or other critical startup issues. Even in these scenarios, prefer using structured error handling to terminate the program. -- **Use `log.Fatal` for Critical Errors:** Instead of panicking, use `log.Fatal` to log critical errors that prevent the program from operating normally. This approach allows the program to terminate while ensuring the error is properly logged for troubleshooting. - - **Prohibition on Exportable Interfaces:** Exportable interfaces must not invoke `panic`. They should handle errors gracefully and return errors as part of their contract. - **Prefer Errors Over Panic:** It is recommended to use error returns instead of panic to convey errors within a package. This approach promotes error handling that integrates smoothly with Go's error handling idioms. @@ -303,7 +362,7 @@ func SIGTERMExit() { ```go import ( - _ "net/http/pprof" + _ "net/webhook/pprof" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" @@ -357,27 +416,31 @@ The naming convention is a very important part of the code specification. A unif - Don't use broad, meaningless package names like common, util, shared or lib. - The package name should be simple and clear, such as net, time, log. -### 2.2 Function Naming -- The function name is in camel case, and the first letter is uppercase or lowercase according to the access control decision,For example: `MixedCaps` or `mixedCaps`. -- Code automatically generated by code generation tools (such as `xxxx.pb.go`) and underscores used to group related test cases (such as `TestMyFunction_WhatIsBeingTested`) exclude this rule. +### 2.2 Function Naming Conventions + +Function names should adhere to the following guidelines, inspired by OpenIM’s standards and Google’s Go Style Guide: + +- Use camel case for function names. Start with an uppercase letter for public functions (`MixedCaps`) and a lowercase letter for private functions (`mixedCaps`). +- Exceptions to this rule include code automatically generated by tools (e.g., `xxxx.pb.go`) and test functions that use underscores for clarity (e.g., `TestMyFunction_WhatIsBeingTested`). + +### 2.3 File and Directory Naming Practices + +To maintain consistency and readability across the OpenIM project, observe the following naming practices: + +**File Names:** +- Use underscores (`_`) as the default separator in filenames, keeping them short and descriptive. +- Both hyphens (`-`) and underscores (`_`) are allowed, but underscores are preferred for general use. -In accordance with the naming conventions adopted by OpenIM and drawing reference from the Google Naming Conventions as per the guidelines available at https://google.github.io/styleguide/go/, the following expectations for naming practices within the project are set forth: +**Script and Markdown Files:** +- Prefer hyphens (`-`) for shell scripts and Markdown (`.md`) files to enhance searchability and web compatibility. -1. **File Names:** - + Both hyphens (`-`) and underscores (`_`) are permitted when naming files. - + A preference is established for the use of underscores (`_`), suggesting it as the best practice in general scenarios. -2. **Script and Markdown Files:** - + For shell scripts (bash files) and Markdown (`.md`) documents, the use of hyphens (`-`) is recommended. - + This recommendation is based on the improved searchability and compatibility in web browsers when hyphens are used in names. -3. **Directories:** - + A consistent approach is mandated for naming directories, exclusively using hyphens (`-`) to separate words within directory names. +**Directories:** +- Name directories with hyphens (`-`) exclusively to separate words, ensuring consistency and readability. +Remember to keep filenames lowercase and use meaningful, concise identifiers to facilitate better organization and navigation within the project. -### 2.3 File Naming -- Keep the filename short and meaningful. -- Filenames should be lowercase and use underscores to separate words. ### 2.4 Structure Naming @@ -478,9 +541,9 @@ var LintGonicMapper = GonicMapper{ - If the variable type is bool, the name should start with Has, Is, Can or Allow, for example: ```go -var has Conflict bool +var hasConflict bool var isExist bool -var can Manage bool +var canManage bool var allowGitHook bool ``` diff --git a/docs/contrib/go-code1.md b/docs/contrib/go-code1.md new file mode 100644 index 000000000..2206a153e --- /dev/null +++ b/docs/contrib/go-code1.md @@ -0,0 +1,1554 @@ +## OpenIM development specification +We have very high standards for code style and specification, and we want our products to be polished and perfect + +## 1. Code style + +### 1.1 Code format + +- Code must be formatted with `gofmt`. +- Leave spaces between operators and operands. +- It is recommended that a line of code does not exceed 120 characters. If the part exceeds, please use an appropriate line break method. But there are also some exception scenarios, such as import lines, code automatically generated by tools, and struct fields with tags. +- The file length cannot exceed 800 lines. +- Function length cannot exceed 80 lines. +- import specification +- All code must be formatted with `goimports` (it is recommended to set the code Go code editor to: run `goimports` on save). +- Do not use relative paths to import packages, such as `import ../util/net`. +- Import aliases must be used when the package name does not match the last directory name of the import path, or when multiple identical package names conflict. + +```go +// bad +"github.com/dgrijalva/jwt-go/v4" + +//good +jwt "github.com/dgrijalva/jwt-go/v4" +``` +- Imported packages are suggested to be grouped, and anonymous package references use a new group, and anonymous package references are explained. + +```go +import ( + // go standard package + "fmt" + + // third party package + "github.com/jinzhu/gorm" + "github.com/spf13/cobra" + "github.com/spf13/viper" + + // Anonymous packages are grouped separately, and anonymous package references are explained + // import mysql driver + _ "github.com/jinzhu/gorm/dialects/mysql" + + // inner package +) +``` + +### 1.2 Declaration, initialization and definition + +When multiple variables need to be used in a function, the `var` declaration can be used at the beginning of the function. Declaration outside the function must use `var`, do not use `:=`, it is easy to step on the scope of the variable. + +```go +var ( + Width int + Height int +) +``` + +- When initializing a structure reference, please use `&T{}` instead of `new(T)` to make it consistent with structure initialization. + +```go + // bad + sptr := new(T) + sptr.Name = "bar" + + // good + sptr := &T{Name: "bar"} +``` + +- The struct declaration and initialization format takes multiple lines and is defined as follows. + +```go + type User struct{ + Username string + Email string + } + + user := User{ + Username: "belm", + Email: "nosbelm@qq.com", +} +``` + +- Similar declarations are grouped together, and the same applies to constant, variable, and type declarations. + +```go +// bad +import "a" +import "b" + +//good +import ( + "a" + "b" +) +``` + +- Specify container capacity where possible to pre-allocate memory for the container, for example: + +```go +v := make(map[int]string, 4) +v := make([]string, 0, 4) +``` + +- At the top level, use the standard var keyword. Do not specify a type unless it is different from the type of the expression. + +```go +// bad +var s string = F() + +func F() string { return "A" } + +// good +var s = F() +// Since F already explicitly returns a string type, we don't need to explicitly specify the type of _s +// still of that type + +func F() string { return "A" } +``` + +- This example emphasizes using PascalCase for exported constants and camelCase for unexported ones, avoiding all caps and underscores. + +```go +// bad +const ( + MAX_COUNT = 100 + timeout = 30 +) + +// good +const ( + MaxCount = 100 // Exported constants should use PascalCase. + defaultTimeout = 30 // Unexported constants should use camelCase. +) +``` + +- Grouping related constants enhances organization and readability, especially when there are multiple constants related to a particular feature or configuration. + +```go +// bad +const apiVersion = "v1" +const retryInterval = 5 + +// good +const ( + ApiVersion = "v1" // Group related constants together for better organization. + RetryInterval = 5 +) +``` + +- The "good" practice utilizes iota for a clear, concise, and auto-incrementing way to define enumerations, reducing the potential for errors and improving maintainability. + +```go +// bad +const ( + StatusActive = 0 + StatusInactive = 1 + StatusUnknown = 2 +) + +// good +const ( + StatusActive = iota // Use iota for simple and efficient constant enumerations. + StatusInactive + StatusUnknown +) +``` + +- Specifying types explicitly improves clarity, especially when the purpose or type of a constant might not be immediately obvious. Additionally, adding comments to exported constants or those whose purpose isn't clear from the name alone can greatly aid in understanding the code. + +```go +// bad +const serverAddress = "localhost:8080" +const debugMode = 1 // Is this supposed to be a boolean or an int? + +// good +const ServerAddress string = "localhost:8080" // Specify type for clarity. +// DebugMode indicates if the application should run in debug mode (true for debug mode). +const DebugMode bool = true +``` + +- By defining a contextKey type and making userIDKey of this type, you avoid potential collisions with other context keys. This approach leverages Go's type system to provide compile-time checks against misuse. + +```go +// bad +const userIDKey = "userID" + +// In this example, userIDKey is a string type, which can lead to conflicts or accidental misuse because string keys are prone to typos and collisions in a global namespace. + + +// good +type contextKey string + +const userIDKey contextKey = "userID" +``` + + +- Embedded types (such as mutexes) should be at the top of the field list within the struct, and there must be a blank line separating embedded fields from regular fields. + +```go +// bad +type Client struct { + version int + http.Client +} + +//good +type Client struct { + http.Client + + version int +} +``` + + +### 1.5 Unit Tests + +- The unit test filename naming convention is `example_test.go`. +- Write a test case for every important exportable function. +- Because the functions in the unit test file are not external, the exportable structures, functions, etc. can be uncommented. +- If `func (b *Bar) Foo` exists, the single test function can be `func TestBar_Foo`. + +## 2. Naming convention + +The naming convention is a very important part of the code specification. A uniform, short, and precise naming convention can greatly improve the readability of the code and avoid unnecessary bugs. + +### 2.1 Package Naming + +- The package name must be consistent with the directory name, try to use a meaningful and short package name, and do not conflict with the standard library. +- Package names are all lowercase, without uppercase or underscores, and use multi-level directories to divide the hierarchy. +- Item names can connect multiple words with dashes. +- Do not use plurals for the package name and the directory name where the package is located, for example, `net/url` instead of `net/urls`. +- Don't use broad, meaningless package names like common, util, shared or lib. +- The package name should be simple and clear, such as net, time, log. + + +### 2.2 Function Naming Conventions + +Function names should adhere to the following guidelines, inspired by OpenIM’s standards and Google’s Go Style Guide: + +- Use camel case for function names. Start with an uppercase letter for public functions (`MixedCaps`) and a lowercase letter for private functions (`mixedCaps`). +- Exceptions to this rule include code automatically generated by tools (e.g., `xxxx.pb.go`) and test functions that use underscores for clarity (e.g., `TestMyFunction_WhatIsBeingTested`). + +### 2.3 File and Directory Naming Practices + +To maintain consistency and readability across the OpenIM project, observe the following naming practices: + +**File Names:** +- Use underscores (`_`) as the default separator in filenames, keeping them short and descriptive. +- Both hyphens (`-`) and underscores (`_`) are allowed, but underscores are preferred for general use. + +**Script and Markdown Files:** +- Prefer hyphens (`-`) for shell scripts and Markdown (`.md`) files to enhance searchability and web compatibility. + +**Directories:** +- Name directories with hyphens (`-`) exclusively to separate words, ensuring consistency and readability. + +Remember to keep filenames lowercase and use meaningful, concise identifiers to facilitate better organization and navigation within the project. + +### 2.4 Structure Naming + +- The camel case is adopted, and the first letter is uppercase or lowercase according to the access control, such as `MixedCaps` or `mixedCaps`. +- Struct names should not be verbs, but should be nouns, such as `Node`, `NodeSpec`. +- Avoid using meaningless structure names such as Data and Info. +- The declaration and initialization of the structure should take multiple lines, for example: + +```go +// User multi-line declaration +type User struct { + name string + Email string +} + +// multi-line initialization +u := User{ + UserName: "belm", + Email: "nosbelm@qq.com", +} +``` + +### 2.5 Interface Naming + +- The interface naming rules are basically consistent with the structure naming rules: +- Interface names of individual functions suffixed with "er"" (e.g. Reader, Writer) can sometimes lead to broken English, but that's okay. +- The interface name of the two functions is named after the two function names, eg ReadWriter. +- An interface name for more than three functions, similar to a structure name. + +For example: + +```go +// Seeking to an offset before the start of the file is an error. +// Seeking to any positive offset is legal, but the behavior of subsequent +// I/O operations on the underlying object are implementation-dependent. +type Seeker interface { + Seek(offset int64, whence int) (int64, error) +} + +// ReadWriter is the interface that groups the basic Read and Write methods. +type ReadWriter interface { + reader + Writer +} +``` + +### 2.6 Variable Naming + +- Variable names must follow camel case, and the initial letter is uppercase or lowercase according to the access control decision. +- In relatively simple (few objects, highly targeted) environments, some names can be abbreviated from full words to single letters, for example: +- user can be abbreviated as u; +- userID can be abbreviated as uid. +- When using proper nouns, the following rules need to be followed: +- If the variable is private and the proper noun is the first word, use lowercase, such as apiClient. +- In other cases, the original wording of the noun should be used, such as APIClient, repoID, UserID. + +Some common nouns are listed below. + +```go +// A GonicMapper that contains a list of common initialisms taken from golang/lint +var LintGonicMapper = GonicMapper{ + "API": true, + "ASCII": true, + "CPU": true, + "CSS": true, + "DNS": true, + "EOF": true, + "GUID": true, + "HTML": true, + "HTTP": true, + "HTTPS": true, + "ID": true, + "IP": true, + "JSON": true, + "LHS": true, + "QPS": true, + "RAM": true, + "RHS": true, + "RPC": true, + "SLA": true, + "SMTP": true, + "SSH": true, + "TLS": true, + "TTL": true, + "UI": true, + "UID": true, + "UUID": true, + "URI": true, + "URL": true, + "UTF8": true, + "VM": true, + "XML": true, + "XSRF": true, + "XSS": true, +} +``` + +- If the variable type is bool, the name should start with Has, Is, Can or Allow, for example: + +```go +var hasConflict bool +var isExist bool +var canManage bool +var allowGitHook bool +``` + +- Local variables should be as short as possible, for example, use buf to refer to buffer, and use i to refer to index. +- The code automatically generated by the code generation tool can exclude this rule (such as the Id in `xxx.pb.go`) + +### 2.7 Constant Naming + +In Go, constants play a critical role in defining values that do not change throughout the execution of a program. Adhering to best practices in naming constants can significantly improve the readability and maintainability of your code. Here are some guidelines for constant naming: + +- **Camel Case Naming:** The name of a constant must follow the camel case notation. The initial letter should be uppercase or lowercase based on the access control requirements. Uppercase indicates that the constant is exported (visible outside the package), while lowercase indicates package-private visibility (visible only within its own package). + +- **Enumeration Type Constants:** For constants that represent a set of enumerated values, it's recommended to define a corresponding type first. This approach not only enhances type safety but also improves code readability by clearly indicating the purpose of the enumeration. + +**Example:** + +```go +// Code defines an error code type. +type Code int + +// Internal errors. +const ( + // ErrUnknown - 0: An unknown error occurred. + ErrUnknown Code = iota + // ErrFatal - 1: A fatal error occurred. + ErrFatal +) +``` + +In the example above, `Code` is defined as a new type based on `int`. The enumerated constants `ErrUnknown` and `ErrFatal` are then defined with explicit comments to indicate their purpose and values. This pattern is particularly useful for grouping related constants and providing additional context. + +### Global Variables and Constants Across Packages + +- **Use Constants for Global Variables:** When defining variables that are intended to be accessed across packages, prefer using constants to ensure immutability. This practice avoids unintended modifications to the value, which can lead to unpredictable behavior or hard-to-track bugs. + +- **Lowercase for Package-Private Usage:** If a global variable or constant is intended for use only within its own package, it should start with a lowercase letter. This clearly signals its limited scope of visibility, adhering to Go's access control mechanism based on naming conventions. + +**Guideline:** + +- For global constants that need to be accessed across packages, declare them with an uppercase initial letter. This makes them exported, adhering to Go's visibility rules. +- For constants used within the same package, start their names with a lowercase letter to limit their scope to the package. + +**Example:** + +```go +package config + +// MaxConnections - the maximum number of allowed connections. Visible across packages. +const MaxConnections int = 100 + +// minIdleTime - the minimum idle time before a connection is considered stale. Only visible within the config package. +const minIdleTime int = 30 +``` + +In this example, `MaxConnections` is a global constant meant to be accessed across packages, hence it starts with an uppercase letter. On the other hand, `minIdleTime` is intended for use only within the `config` package, so it starts with a lowercase letter. + +Following these guidelines ensures that your Go code is more readable, maintainable, and consistent with Go's design philosophy and access control mechanisms. + + + + +### 2.10 Using Context with IO or Inter-Process Communication (IPC) + +In Go, `context.Context` is a powerful construct for managing deadlines, cancellation signals, and other request-scoped values across API boundaries and between processes. It is particularly important in I/O operations or inter-process communication (IPC), where operations might need to be cancelled or timed out. + +#### Guideline: Use Context for IO and IPC + +- **Mandatory Use of Context:** When performing I/O operations or inter-process communication, it's crucial to use `context.Context` to manage the lifecycle of these operations. This includes setting deadlines, handling cancellation signals, and passing request-scoped values. + +#### Incorrect Example: Ignoring Context in an HTTP Call + +```go +package main + +import ( + "io/ioutil" + "net/http" + "log" +) + +// FetchData makes an HTTP GET request to the specified URL and returns the response body. +// This function does not use context, making it impossible to cancel the request or set a deadline. +func FetchData(url string) (string, error) { + resp, err := http.Get(url) // Incorrect: Ignoring context + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(body), nil +} + +func main() { + data, err := FetchData("http://example.com") + if err != nil { + log.Fatalf("Failed to fetch data: %v", err) + } + log.Println(data) +} +``` + +In this incorrect example, the `FetchData` function makes an HTTP GET request without using a `context`. This approach does not allow the request to be cancelled or a timeout to be set, potentially leading to resources being wasted if the server takes too long to respond or if the operation needs to be aborted for any reason. + +#### Correct Example: Using Context in an HTTP Call + +```go +package main + +import ( + "context" + "io/ioutil" + "net/http" + "log" + "time" +) + +// FetchDataWithContext makes an HTTP GET request to the specified URL using the provided context. +// This allows the request to be cancelled or timed out according to the context's deadline. +func FetchDataWithContext(ctx context.Context, url string) (string, error) { + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return "", err + } + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return "", err + } + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return "", err + } + + return string(body), nil +} + +func main() { + // Create a context with a 5-second timeout + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + data, err := FetchDataWithContext(ctx, "http://example.com") + if err != nil { + log.Fatalf("Failed to fetch data: %v", err) + } + log.Println(data) +} +``` + +In the correct example, `FetchDataWithContext` uses a context to make the HTTP GET request. This allows the operation to be cancelled or subjected to a timeout, as dictated by the context passed to it. The `context.WithTimeout` function is used in `main` to create a context that cancels the request if it takes longer than 5 seconds, demonstrating a practical use of context to manage operation lifecycle. + +### Best Practices for Using Context + +1. **Pass context as the first parameter of a function**, following the convention `func(ctx context.Context, ...)`. +2. **Never ignore the context** provided to you in functions that support it. Always use it in your I/O or IPC operations. +3. **Avoid storing context in a struct**. Contexts are meant to be passed around within the call stack, not stored. +4. **Use context's cancellation and deadline features** to control the lifecycle of blocking operations, especially in network I/O and IPC scenarios. +5. **Propagate context down the call stack** to any function that supports it, ensuring that your application can respond to cancellation signals and deadlines effectively. + +By adhering to these guidelines and examples, you can ensure that your Go applications handle I/O and IPC operations more reliably and efficiently, with proper support for cancellation, timeouts, and request-scoped values. + + + +## 3. 日志规范 + +启动时正常日志,打印流程日志,如链接mongo成功,注意不要打印密码等敏感信息。 + +启动时以及运行中异常终止日志,如果需要终止程序,调用ExitWithError + +运行时日志打印,对于错误日志,使用日志库打印,仅在最上层调用打印;对于debug日志,可以随意打印;对于关键日志打印 info; + +## 5.异常及错误处理 + +任何情况禁止使用panic + +错误需要wrap,并带上message和key value,用户排查问题;错误wrap仅一次,及函数本身出现的错误,或者调用项目之外的函数产生的错误。 + +用errs.New()替代errors.New() + + + + + +### 1.4 Panic Processing + +The use of `panic` should be carefully controlled in Go applications to ensure program stability and predictable error handling. Following are revised guidelines emphasizing the restriction on using `panic` and promoting alternative strategies for error handling and program termination. + +- **Prohibited in Business Logic:** Using `panic` within business logic processing is strictly prohibited. Business logic should handle errors gracefully and use error returns to propagate issues up the call stack. + +- **Restricted Use in Main Package:** In the main package, the use of `panic` should be reserved for situations where the program is entirely inoperable, such as failure to open essential files, inability to connect to the database, or other critical startup issues. Even in these scenarios, prefer using structured error handling to terminate the program. + +- **Prohibition on Exportable Interfaces:** Exportable interfaces must not invoke `panic`. They should handle errors gracefully and return errors as part of their contract. + +- **Prefer Errors Over Panic:** It is recommended to use error returns instead of panic to convey errors within a package. This approach promotes error handling that integrates smoothly with Go's error handling idioms. + +#### Alternative to Panic: Structured Program Termination + +To enforce these guidelines, consider implementing structured functions to terminate the program gracefully in the face of unrecoverable errors, while providing clear error messages. Here are two recommended functions: + +```go +// ExitWithError logs an error message and exits the program with a non-zero status. +func ExitWithError(err error) { + progName := filepath.Base(os.Args[0]) + fmt.Fprintf(os.Stderr, "%s exit -1: %+v\n", progName, err) + os.Exit(-1) +} + +// SIGTERMExit logs a warning message when the program receives a SIGTERM signal and exits with status 0. +func SIGTERMExit() { + progName := filepath.Base(os.Args[0]) + fmt.Fprintf(os.Stderr, "Warning %s receive process terminal SIGTERM exit 0\n", progName) +} +``` + +#### Example Usage: + +```go +import ( + _ "net/webhook/pprof" + + "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" + util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" +) + +func main() { + apiCmd := cmd.NewApiCmd() + apiCmd.AddPortFlag() + apiCmd.AddPrometheusPortFlag() + if err := apiCmd.Execute(); err != nil { + util.ExitWithError(err) + } +} +``` + +In this example, `ExitWithError` is used to terminate the program when an unrecoverable error occurs, providing a clear error message to stderr and exiting with a non-zero status. This approach ensures that critical errors are logged and the program exits in a controlled manner, facilitating troubleshooting and maintaining the stability of the application. + + + +### 1.3 Error Handling + +- `error` is returned as the value of the function, `error` must be handled, or the return value assigned to explicitly ignore. For `defer xx.Close()`, there is no need to explicitly handle it. + +```go +func load() error { +// normal code +} + +// bad +load() + +//good + _ = load() +``` + +- When `error` is returned as the value of a function and there are multiple return values, `error` must be the last parameter. + +```go +// bad +func load() (error, int) { +// normal code +} + +//good +func load() (int, error) { +// normal code +} +``` + +- Perform error handling as early as possible and return as early as possible to reduce nesting. + +```go +// bad +if err != nil { +// error code +} else { +// normal code +} + +//good +if err != nil { +// error handling +return err +} +// normal code +``` + +- If you need to use the result of the function call outside if, you should use the following method. + +```go +// bad +if v, err := foo(); err != nil { +// error handling +} + +// good +v, err := foo() +if err != nil { +// error handling +} +``` + +- Errors should be judged independently, not combined with other logic. + +```go +// bad +v, err := foo() +if err != nil || v == nil { + // error handling + return err +} + +//good +v, err := foo() +if err != nil { + // error handling + return err +} + +if v == nil { + // error handling + return errors. New("invalid value v") +} +``` + +- If the return value needs to be initialized, use the following method. + +```go +v, err := f() +if err != nil { + // error handling + return // or continue. +} +``` + +- Bug description suggestions +- Error descriptions start with a lowercase letter and do not end with punctuation, for example: + +```go +// bad +errors.New("Redis connection failed") +errors.New("redis connection failed.") + +// good +errors.New("redis connection failed") +``` + +- Tell users what they can do, not what they can't. +- When declaring a requirement, use must instead of should. For example, `must be greater than 0, must match regex '[a-z]+'`. +- When declaring that a format is incorrect, use must not. For example, `must not contain`. +- Use may not when declaring an action. For example, `may not be specified when otherField is empty, only name may be specified`. +- When quoting a literal string value, indicate the literal in single quotes. For example, `ust not contain '..'`. +- When referencing another field name, specify that name in backticks. For example, must be greater than `request`. +- When specifying unequal, use words instead of symbols. For example, `must be less than 256, must be greater than or equal to 0 (do not use larger than, bigger than, more than, higher than)`. +- When specifying ranges of numbers, use inclusive ranges whenever possible. +- Go 1.13 or above is recommended, and the error generation method is `fmt.Errorf("module xxx: %w", err)`. + +### 1.6 Type assertion failure handling + +- A single return value from a type assertion will panic for an incorrect type. Always use the "comma ok" idiom. + +```go +// bad +t := n.(int) + +//good +t, ok := n.(int) +if !ok { +// error handling +} +``` + + + +### 2.8 Error naming + +- The Error type should be written in the form of FooError. + +```go +type ExitError struct { +// .... +} +``` + +- The Error variable is written in the form of ErrFoo. + +```go +var ErrFormat = errors. New("unknown format") +``` + +For non-standard Err naming, CICD will report an error + + +### 2.9 Handling Errors Properly + +In Go, proper error handling is crucial for creating reliable and maintainable applications. It's important to ensure that errors are not ignored or discarded, as this can lead to unpredictable behavior and difficult-to-debug issues. Here are the guidelines and examples regarding the proper handling of errors. + +#### Guideline: Do Not Discard Errors + +- **Mandatory Error Propagation:** When calling a function that returns an error, the calling function must handle or propagate the error, instead of ignoring it. This approach ensures that errors are not silently ignored, allowing higher-level logic to make informed decisions about error handling. + +#### Incorrect Example: Discarding an Error + +```go +package main + +import ( + "io/ioutil" + "log" +) + +func ReadFileContent(filename string) string { + content, _ := ioutil.ReadFile(filename) // Incorrect: Error is ignored + return string(content) +} + +func main() { + content := ReadFileContent("example.txt") + log.Println(content) +} +``` + +In this incorrect example, the error returned by `ioutil.ReadFile` is ignored. This can lead to situations where the program continues execution even if the file doesn't exist or cannot be accessed, potentially causing more cryptic errors downstream. + +#### Correct Example: Propagating an Error + +```go +package main + +import ( + "io/ioutil" + "log" +) + +// ReadFileContent attempts to read and return the content of the specified file. +// It returns an error if reading fails. +func ReadFileContent(filename string) (string, error) { + content, err := ioutil.ReadFile(filename) + if err != nil { + // Correct: Propagate the error + return "", err + } + return string(content), nil +} + +func main() { + content, err := ReadFileContent("example.txt") + if err != nil { + log.Fatalf("Failed to read file: %v", err) + } + log.Println(content) +} +``` + +In the correct example, the error returned by `ioutil.ReadFile` is propagated back to the caller. The `main` function then checks the error and terminates the program with an appropriate error message if an error occurred. This approach ensures that errors are handled appropriately, and the program does not proceed with invalid state. + +### Best Practices for Error Handling + +1. **Always check the error returned by a function.** Do not ignore it. +2. **Propagate errors up the call stack unless they can be handled gracefully at the current level.** +3. **Provide context for errors when propagating them, making it easier to trace the source of the error.** This can be achieved using `fmt.Errorf` with the `%w` verb or dedicated wrapping functions provided by some error handling packages. +4. **Log the error at the point where it is handled or makes the program to terminate, to provide insight into the failure.** + +By following these guidelines, you ensure that your Go applications handle errors in a consistent and effective manner, improving their reliability and maintainability. + + + + + + + + + + + + + +## Suggestions + + + +## 3. Comment specification + +- Each exportable name must have a comment, which briefly introduces the exported variables, functions, structures, interfaces, etc. +- All single-line comments are used, and multi-line comments are prohibited. +- Same as the code specification, single-line comments should not be too long, and no more than 120 characters are allowed. If it exceeds, please use a new line to display, and try to keep the format elegant. +- A comment must be a complete sentence, starting with the content to be commented and ending with a period, `the format is // name description.`. For example: + +```go +// bad +// logs the flags in the flagset. +func PrintFlags(flags *pflag. FlagSet) { +// normal code +} + +//good +// PrintFlags logs the flags in the flagset. +func PrintFlags(flags *pflag. FlagSet) { +// normal code +} +``` + +- All commented out code should be deleted before submitting code review, otherwise, it should explain why it is not deleted, and give follow-up processing suggestions. + +- Multiple comments can be separated by blank lines, as follows: + +```go +// Package superman implements methods for saving the world. +// +// Experience has shown that a small number of procedures can prove +// helpful when attempting to save the world. +package superman +``` + +### 3.1 Package Notes + +- Each package has one and only one package-level annotation. +- Package comments are uniformly commented with // in the format of `// Package package description`, for example: + +```go +// Package genericclioptions contains flags which can be added to you command, bound, completed, and produce +// useful helper functions. +package genericclioptions +``` + +### 3.2 Variable/Constant Comments + +- Each variable/constant that can be exported must have a comment description, `the format is // variable name variable description`, for example: + +```go +// ErrSigningMethod defines invalid signing method error. +var ErrSigningMethod = errors. New("Invalid signing method") +``` + +- When there is a large block of constant or variable definition, you can comment a general description in front, and then comment the definition of the constant in detail before or at the end of each line of constant, for example: + +```go +// Code must start with 1xxxxx. +const ( + // ErrSuccess - 200: OK. + ErrSuccess int = iota + 100001 + + // ErrUnknown - 500: Internal server error. + ErrUnknown + + // ErrBind - 400: Error occurred while binding the request body to the struct. + ErrBind + + // ErrValidation - 400: Validation failed. + ErrValidation +) +``` + +### 3.3 Structure Annotation + +- Each structure or interface that needs to be exported must have a comment description, the format is `// structure name structure description.`. +- The name of the exportable member variable in the structure, if the meaning is not clear, a comment must be given and placed before the member variable or at the end of the same line. For example: + +```go +// User represents a user restful resource. It is also used as gorm model. +type User struct { + // Standard object's metadata. + metav1.ObjectMeta `json:"metadata,omitempty"` + + Nickname string `json:"nickname" gorm:"column:nickname"` + Password string `json:"password" gorm:"column:password"` + Email string `json:"email" gorm:"column:email"` + Phone string `json:"phone" gorm:"column:phone"` + IsAdmin int `json:"isAdmin,omitempty" gorm:"column:isAdmin"` +} +``` + +### 3.4 Method Notes + +Each function or method that needs to be exported must have a comment, the format is // function name function description., for examplelike: + +```go +// BeforeUpdate run before update database record. +func (p *Policy) BeforeUpdate() (err error) { +// normal code + return nil +} +``` + +### 3.5 Type annotations + +- Each type definition and type alias that needs to be exported must have a comment description, the format is `// type name type description.`, for example: + +```go +// Code defines an error code type. +type Code int +``` + +## 4. Type + +### 4.1 Strings + +- Empty string judgment. + +```go +// bad +if s == "" { + // normal code +} + +//good +if len(s) == 0 { + // normal code +} +``` + +- `[]byte`/`string` equality comparison. + +```go +// bad +var s1 []byte +var s2 []byte +... +bytes.Equal(s1, s2) == 0 +bytes.Equal(s1, s2) != 0 + +//good +var s1 []byte +var s2 []byte +... +bytes. Compare(s1, s2) == 0 +bytes. Compare(s1, s2) != 0 +``` + +- Complex strings use raw strings to avoid character escaping. + +```go +// bad +regexp.MustCompile("\\.") + +//good +regexp.MustCompile(`\.`) +``` + +### 4.2 Slicing + +- Empty slice judgment. + +```go +// bad +if len(slice) = 0 { + // normal code +} + +//good +if slice != nil && len(slice) == 0 { + // normal code +} +``` + +The above judgment also applies to map and channel. + +- Declare a slice. + +```go +// bad +s := []string{} +s := make([]string, 0) + +//good +var s[]string +``` + +- slice copy. + +```go +// bad +var b1, b2 []byte +for i, v := range b1 { + b2[i] = v +} +for i := range b1 { + b2[i] = b1[i] +} + +//good +copy(b2, b1) +``` + +- slice added. + +```go +// bad +var a, b []int +for _, v := range a { + b = append(b, v) +} + +//good +var a, b []int +b = append(b, a...) +``` + +### 4.3 Structure + +- struct initialization. + +The struct is initialized in multi-line format. + +```go +type user struct { +Id int64 +name string +} + +u1 := user{100, "Colin"} + +u2 := user{ + Id: 200, + Name: "Lex", +} +``` + +- + + + +## 5. Control Structure + +### 5.1 if + +- if accepts the initialization statement, the convention is to create local variables in the following way. + +```go +if err := loadConfig(); err != nil { +// error handling +return err +} +``` + +- if For variables of bool type, true and false judgments should be made directly. + +```go +var isAllow bool +if isAllow { +// normal code +} +``` + +### 5.2 for + +- Create local variables using short declarations. + +```go +sum := 0 +for i := 0; i < 10; i++ { + sum += 1 +} +``` + +- Don't use defer in for loop, defer will only be executed when the function exits. + +```go +// bad +for file := range files { + fd, err := os. Open(file) + if err != nil { + return err +} +defer fd. Close() +// normal code +} + +//good +for file := range files { + func() { + fd, err := os. Open(file) + if err != nil { + return err + } + defer fd. Close() + // normal code + }() +} +``` + +### 5.3 range + +- If only the first item (key) is needed, discard the second. + +```go +for keyIndex := range keys { +// normal code +} +``` + +- If only the second item is required, underline the first item. + +```go +sum := 0 +for _, value := range array { + sum += value +} +``` + +### 5.4 switch + +- must have default. + +```go +switch os := runtime.GOOS; os { + case "linux": + fmt.Println("Linux.") + case "darwin": + fmt.Println("OS X.") + default: + fmt.Printf("%s.\n", os) +} +``` + +### 5.5 goto + +- Business code prohibits the use of goto. +- Try not to use frameworks or other low-level source code. + +## 6. Functions + +- Incoming variables and return variables start with a lowercase letter. +- The number of function parameters cannot exceed 5. +- Function grouping and ordering +- Functions should be sorted in rough calling order. +- Functions in the same file should be grouped by receiver. +- Try to use value transfer instead of pointer transfer. +- The incoming parameters are map, slice, chan, interface, do not pass pointers. + +### 6.1 Function parameters + +- If the function returns two or three arguments of the same type, or if the meaning of the result is not clear from the context, use named returns, otherwise it is not recommended to use named returns, for example: + +```go +func coordinate() (x, y float64, err error) { +// normal code +} +``` + +- Both incoming and returned variables start with a lowercase letter. +- Try to pass by value instead of pointer. +- The number of parameters cannot exceed 5. +- Multiple return values can return up to three, and if there are more than three, please use struct. + +### 6.2 defer + +- When resources are created, resources should be released immediately after defer (defer can be used boldly, the performance of defer is greatly improved in Go1.14 version, and the performance loss of defer can be ignored even in performance-sensitive businesses). +- First judge whether there is an error, and then defer to release resources, for example: + +```go +rep, err := http. Get(url) +if err != nil { + return err +} + +defer resp.Body.Close() +``` + +### 6.3 Method Receiver + +- It is recommended to use the lowercase of the first English letter of the class name as the name of the receiver. +- Don't use a single character in the name of the receiver when the function exceeds 20 lines. +- The name of the receiver cannot use confusing names such as me, this, and self. + +### 6.4 Nesting + +- The nesting depth cannot exceed 4 levels. + +### 6.5 Variable Naming + +- The variable declaration should be placed before the first use of the variable as far as possible, following the principle of proximity. +- If the magic number appears more than twice, it is forbidden to use it and use a constant instead, for example: + +```go +// PI... +const Price = 3.14 + +func getAppleCost(n float64) float64 { +return Price * n +} + +func getOrangeCost(n float64) float64 { +return Price * n +} +``` + +## 7. GOPATH setting specification + +- After Go 1.11, the GOPATH rule has been weakened. Existing code (many libraries must have been created before 1.11) must conform to this rule. It is recommended to keep the GOPATH rule to facilitate code maintenance. +- Only one GOPATH is recommended, multiple GOPATHs are not recommended. If multiple GOPATHs are used, the bin directory where compilation takes effect is under the first GOPATH. + + + +## 8. Dependency Management + +- Go 1.11 and above must use Go Modules. +- When using Go Modules as a dependency management project, it is not recommended to submit the vendor directory. +- When using Go Modules as a dependency management project, the go.sum file must be submitted. + +### 9. Best Practices + +- Minimize the use of global variables, but pass parameters, so that each function is "stateless". This reduces coupling and facilitates division of labor and unit testing. +- Verify interface compliance at compile time, for example: + +```go +type LogHandler struct { + h http.Handler + log *zap. Logger +} +var_http.Handler = LogHandler{} +``` + +- When the server processes a request, it should create a context, save the relevant information of the request (such as requestID), and pass it in the function call chain. + +### 9.1 Performance + +- string represents an immutable string variable, modifying string is a relatively heavy operation, and basically needs to re-apply for memory. Therefore, if there is no special need, use []byte more when you need to modify. +- Prefer strconv over fmt. + +### 9.2 Precautions + +- append Be careful about automatically allocating memory, append may return a newly allocated address. +- If you want to directly modify the value of the map, the value can only be a pointer, otherwise the original value must be overwritten. +- map needs to be locked during concurrency. +- The conversion of interface{} cannot be checked during compilation, it can only be checked at runtime, be careful to cause panic. + + + + + +## 10 Golang CI Lint + +- Golang CI Lint is a fast Go linters runner. It runs linters in parallel, uses caching, and works well with all environments, including CI. + +**In local development, you can use the following command to install Golang CI Lint: ** + +```bash +make lint +``` + +**In CI/CD, Check the Github Actions status code below after you submit the code directly** + +[![OpenIM golangci-lint](https://github.com/openimsdk/open-im-server/actions/workflows/golangci-lint.yml/badge.svg)](https://github.com/openimsdk/open-im-server/actions/workflows/golangci-lint.yml) + +golangci lint can select the types of tools, refer to the official documentation: [https://golangci-lint.run/usage/linters/](https://golangci-lint.run/usage/linters/) + +The types of comments we currently use include: [https://github.com/openimsdk/open-im-server/blob/main/.golangci.yml](https://github.com/openimsdk/open-im-server/blob/main/.golangci.yml) the `linters.enable` field in the file. + +e.g: + +```yaml +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 # Basic type checking + - gofmt # Format check + - govet # Go's standard linting tool + - gosimple # Suggestions for simplifying code + - misspell # Spelling mistakes + - staticcheck # Static analysis + - unused # Checks for unused code + - goimports # Checks if imports are correctly sorted and formatted + - godot # Checks for comment punctuation + - bodyclose # Ensures HTTP response body is closed + - errcheck # Checks for missed error returns + fast: true +``` + +Add that Chinese comments are not allowed in go code, please write a complete golangci lint specification on the basis of the above. + + +### 10.1 Configuration Document + +This configuration document is designed to configure the operational parameters of OpenIM (a hypothetical or specific code analysis tool), customize output formats, and provide detailed settings for specific code checkers (linters). Below is a summary of the document drafted based on the provided configuration information. + +#### 10.1 Runtime Options + +- **Concurrency** (`concurrency`): Default to use the available CPU count, can be manually set to 4 for parallel analysis. +- **Timeout** (`timeout`): Timeout duration for analysis operations, default is 1 minute, set here to 5 minutes. +- **Issue Exit Code** (`issues-exit-code`): Exit code defaults to 1 if at least one issue is found. +- **Test Files** (`tests`): Whether to include test files, defaults to true. +- **Build Tags** (`build-tags`): Specify build tags used by all linters, defaults to an empty list. Example adds `mytag`. +- **Skip Directories** (`skip-dirs`): Configure which directories' issues are not reported, defaults to empty, but some default directories are independently skipped. +- **Skip Files** (`skip-files`): Specify files where issues should not be reported, supports regular expressions. + +#### 10.2 Output Configuration + +- **Format** (`format`): Set output format, default is "colored-line-number". +- **Print Issued Lines** (`print-issued-lines`): Whether to print the lines where issues occur, defaults to true. +- **Print Linter Name** (`print-linter-name`): Whether to print the linter name at the end of issue text, defaults to true. +- **Uniqueness Filter** (`uniq-by-line`): Whether to make issue outputs unique per line, defaults to true. +- **Path Prefix** (`path-prefix`): Prefix to add to output file references, defaults to no prefix. +- **Sort Results** (`sort-results`): Sort results by file path, line number, and column number. + +#### 10.3 Linters Settings + +In the configuration file, the `linters-settings` section allows detailed configuration of individual linters. Below are examples of specific linters settings and their purposes: + +- **bidichk**: Used to check bidirectional text characters, ensuring correct display direction of text, especially when dealing with mixed left-to-right (LTR) and right-to-left (RTL) text. + +- **dogsled**: Monitors excessive use of blank identifiers (`_`) in assignment operations, which may obscure data processing errors or unclear logic. + +- **dupl**: Identifies duplicate code blocks, helping developers avoid code redundancy. The `threshold` parameter in settings allows adjustment of code similarity threshold triggering warnings. + +- **errcheck**: Checks for unhandled errors. In Go, error handling is achieved by checking function return values. This linter helps ensure all errors are properly handled. + +- **exhaustive**: Checks if `switch` statements include all possible values of an enum type, ensuring exhaustiveness of code. This helps avoid forgetting to handle certain cases. + +#### 10.4 Example: `errcheck` + +**Incorrect Code Example**: + +```go +package main + +import ( + "fmt" + "os" +) + +func main() { + f, _ := os.Open("filename.ext") + defer f.Close() +} +``` + +**Issue**: In the above code, the error return value of `os.Open` function is explicitly ignored. This is a common mistake as it may lead to unhandled errors and hard-to-trace bugs. + +**Correct Form**: + +```go +package main + +import ( + "fmt" + "os" +) + +func main() { + f, err := os.Open("filename.ext") + if err != nil { + fmt.Printf("error opening file: %v\n", err) + return + } + defer f.Close() +} +``` + +In the correct form, by checking the error (`err`) returned by `os.Open`, we gracefully handle error cases rather than simply ignoring them. + +#### 10.5 Example: `gofmt` + +**Incorrect Code Example**: + +```go +package main +import "fmt" +func main() { +fmt.Println("Hello, world!") +} +``` + +**Issue**: This code snippet doesn't follow Go's standard formatting rules, for example, incorrect indentation of `fmt.Println`. + +**Correct Form**: + +```go +package main + +import "fmt" + +func main() { + fmt.Println("Hello, world!") +} +``` + +Using `gofmt` tool can automatically fix such formatting issues, ensuring the code adheres to the coding standards of the Go community. + +#### 10.6 Example: `unused` + +**Incorrect Code Example**: + +```go +package main + +func helper() {} + +func main() {} +``` + +**Issue**: The `helper` function is defined but not called anywhere, indicating potential redundant code or missing functionality implementation. + +**Correct Form**: + +```go +package main + +// If the helper function is indeed needed, ensure it's used properly. +func helper() { + // Implement the function's functionality or ensure it's called elsewhere +} + +func main() { + helper() +} +``` + +To improve the section on Linters settings in the document, we'll expand with more detailed explanations and reinforce understanding through examples. + +#### 10.7 Example: `dogsled` + +**Incorrect Code Example**: + +```go +func getValues() (int, int, int) { + return 1, 2, 3 +} + +func main() { + _, _, val := getValues() + fmt.Println(val) // Only interested in the third return value +} +``` + +**Explanation**: In the above code, we use two blank identifiers to ignore the first two return values. Excessive use of blank identifiers can make code reading difficult. + +**Improved Code**: +Consider refactoring the function or the usage of return values to reduce the need for blank identifiers or explicitly comment why ignoring certain values is safe. + +#### 10.8: `exhaustive` + +**Incorrect Code Example**: + +```go +type Fruit int + +const ( + Apple Fruit = iota + Banana + Orange +) + +func getFruitName(f Fruit) string { + switch f { + case Apple: + return "Apple" + case Banana: + return "Banana" + // Missing handling for Orange + } + return "Unknown" +} +``` + +**Explanation**: In this code, the `switch` statement doesn't cover all possible values of the `Fruit` type; the case for `Orange` is missing. + +**Improved Code**: + +```go +func getFruitName(f Fruit) string { + switch f { + case Apple: + return "Apple" + case Banana: + return "Banana" + case Orange: + return "Orange" + } + return "Unknown" +} +``` + +By adding the missing `case`, we ensure the `switch` statement is exhaustive, handling every possible enum value. + +#### 10.9 Optimization of Configuration Files and Application of Code Analysis Tools + +Through these examples, we demonstrate how to improve code quality by identifying and fixing common coding issues. OpenIM's configuration files allow developers to customize linters' behavior according to project requirements, ensuring code compliance with predefined quality standards and style guidelines. + +By employing these tools and configuration strategies, teams can reduce the number of bugs, enhance code maintainability, and facilitate efficient collaboration during code review processes. + + + + + + + diff --git a/docs/contrib/go-doc.md b/docs/contrib/go-doc.md new file mode 100644 index 000000000..65b88514e --- /dev/null +++ b/docs/contrib/go-doc.md @@ -0,0 +1,50 @@ +# Go Language Documentation for OpenIM + +In the realm of software development, especially within Go language projects, documentation plays a crucial role in ensuring code maintainability and ease of use. Properly written and accurate documentation is not only essential for understanding and utilizing software effectively but also needs to be easy to write and maintain. This principle is at the heart of OpenIM's approach to supporting commands and generating documentation. + +## Supported Commands in OpenIM + +OpenIM leverages Go language's documentation standards to facilitate clear and maintainable code documentation. Below are some of the key commands used in OpenIM for documentation purposes: + +### `go doc` Command + +The `go doc` command is used to print documentation for Go language entities such as variables, constants, functions, structures, and interfaces. This command allows specifying the identifier of the program entity to tailor the output. Examples of `go doc` command usage include: + +- `go doc sync.WaitGroup.Add` prints the documentation for a specific method of a type in a package. +- `go doc -u -all sync.WaitGroup` displays all program entities, including unexported ones, for a specified type. +- `go doc -u sync` outputs all program entities for a specified package, focusing on exported ones without detailed comments. + +### `godoc` Command + +For environments lacking internet access, the `godoc` command serves to view the Go language standard library and project dependency library documentation in a web format. Notably, post-Go 1.12 versions, `godoc` is not part of the Go compiler suite. It can be installed using: + +```shell +go get -u -v golang.org/x/tools/cmd/godoc +``` + +The `godoc` command, once running, hosts a local web server (by default on port 6060) to facilitate documentation browsing at http://127.0.0.1:6060. It generates documentation based on the GOROOT and GOPATH directories, showcasing both the project's own documentation and that of third-party packages installed via `go get`. + +### Custom Documentation Generation Commands in OpenIM + +OpenIM includes a suite of commands aimed at initializing, generating, and maintaining project documentation and associated files. Some notable commands are: + +- `gen.init`: Initializes the OpenIM server project. +- `gen.docgo`: Generates missing `doc.go` files for Go packages, crucial for package-level documentation. +- `gen.errcode.doc`: Generates markdown documentation for OpenIM error codes. +- `gen.ca`: Generates CA files for all certificates, enhancing security documentation. + +These commands underscore the project's commitment to thorough and accessible documentation, supporting both developers and users alike. + +## Writing Your Own Documentation + +When creating documentation for Go projects, including OpenIM, it's important to follow certain practices: + +1. **Commenting**: Use single-line (`//`) and block (`/* */`) comments to provide detailed documentation within the code. Block comments are especially useful for package documentation, which should immediately precede the package statement without any intervening blank lines. + +2. **Overview Section**: To create an overview section in the documentation, place a block comment directly before the package statement. This section should succinctly describe the package's purpose and functionality. + +3. **Detailed Descriptions**: Comments placed before functions, structures, or variables will be used to generate detailed descriptions in the documentation. Follow the same commenting rules as for the overview section. + +4. **Examples**: Include example functions prefixed with `Example` to demonstrate usage. Output from these examples can be documented at the end of the function, starting with `// Output:` followed by the expected result. + +Through adherence to these documentation practices, OpenIM ensures that its codebase remains accessible, maintainable, and easy to use for developers and users alike. \ No newline at end of file diff --git a/docs/contrib/images.md b/docs/contrib/images.md index d1a83d639..396ffb2e3 100644 --- a/docs/contrib/images.md +++ b/docs/contrib/images.md @@ -96,4 +96,21 @@ When pulling OpenIM's Docker images, you can choose the most suitable source bas 3. Run the `docker images` command to confirm that the image has been successfully pulled. -This concludes OpenIM's image management strategy and the steps for pulling images. If you have any questions, please feel free to ask. +### Accelerating Deployment for Users in China with Aliyun Mirror or Alternative Image Addresses + +For users in China looking to speed up the deployment process of OpenIM, leveraging a mirror image address is a highly recommended practice. After executing the `make init` command, a `.env` file is generated, which you'll need to edit to configure the image registry source. This configuration is crucial for optimizing download speeds and ensuring a smoother setup process. + +Within the generated `.env` file, you'll find a section dedicated to choosing the image address. It includes options for GitHub (`ghcr.io/openimsdk`), Docker Hub (`openim`), and Ali Cloud (`registry.cn-hangzhou.aliyuncs.com/openimsdk`). To achieve the best performance within China, it is advised to use the Aliyun image address. + +To do this, you need to comment out the current `IMAGE_REGISTRY` setting and uncomment the Aliyun option. Here is how you can adjust it for Aliyun: + +```bash +# Choose the image address: GitHub (ghcr.io/openimsdk), Docker Hub (openim), +# or Ali Cloud (registry.cn-hangzhou.aliyuncs.com/openimsdk). +# Uncomment one of the following three options. Aliyun is recommended for users in China. +# IMAGE_REGISTRY="ghcr.io/openimsdk" +# IMAGE_REGISTRY="openim" +IMAGE_REGISTRY="registry.cn-hangzhou.aliyuncs.com/openimsdk" +``` + +This change directs the deployment process to fetch the required images from the Aliyun registry, significantly improving download and installation speeds due to the geographical and network advantages within China. If, for any reason, you prefer not to use Aliyun or encounter issues, consider switching to another mirror address listed in the `.env` file by following the same uncommenting process. This flexibility ensures that users can select the most suitable image source for their specific situation, leading to a more efficient deployment of OpenIM. diff --git a/docs/contrib/kafka.md b/docs/contrib/kafka.md new file mode 100644 index 000000000..4547c9480 --- /dev/null +++ b/docs/contrib/kafka.md @@ -0,0 +1,162 @@ +# OpenIM Kafka Guide + +This document aims to provide a set of concise guidelines to help you quickly install and use Kafka through Docker Compose. + +## Installing Kafka + +With the Docker Compose script provided by OpenIM, you can easily install Kafka. Use the following command to start Kafka: + +```bash +docker compose up -d +``` + +After executing this command, Kafka will be installed and started. You can confirm the Kafka container is running with the following command: + +```bash +docker ps | grep kafka +``` + +The output of this command, as shown below, displays the status information of the Kafka container: + +``` +be416b5a0851 bitnami/kafka:3.5.1 "/opt/bitnami/script…" 3 days ago Up 2 days 9092/tcp, 0.0.0.0:19094->9094/tcp, :::19094->9094/tcp kafka +``` + +### References + +- Official Docker installation documentation: [Click here](http://events.jianshu.io/p/b60afa35303a) +- Detailed installation guide: [Tutorial on Towards Data Science](https://towardsdatascience.com/how-to-install-apache-kafka-using-docker-the-easy-way-4ceb00817d8b) + +## Using Kafka + +### Entering the Kafka Container + +To execute Kafka commands, you first need to enter the Kafka container. Use the following command: + +```bash +docker exec -it kafka bash +``` + +### Kafka Command Tools + +Inside the Kafka container, you can use various command-line tools to manage Kafka. These tools include but are not limited to: + +- `kafka-topics.sh`: For creating, deleting, listing, or altering topics. +- `kafka-console-producer.sh`: Allows sending messages to a specified topic from the command line. +- `kafka-console-consumer.sh`: Allows reading messages from the command line, with the ability to specify topics. +- `kafka-consumer-groups.sh`: For managing consumer group information. + +### Kafka Client Tool Installation + +For easier Kafka management, you can install Kafka client tools. If you installed Kafka through OpenIM's Docker Compose, you can install the Kafka client tools with the following command: + +```bash +make install.kafkactl +``` + +### Automatic Topic Creation + +When installing Kafka through OpenIM's Docker Compose method, OpenIM automatically creates the following topics: + +- `latestMsgToRedis` +- `msgToPush` +- `offlineMsgToMongoMysql` + +These topics are created using the `scripts/create-topic.sh` script. The script waits for Kafka to be ready before executing the commands to create topics: + +```bash +# Wait for Kafka to be ready +until /opt/bitnami/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092; do + echo "Waiting for Kafka to be ready..." + sleep 2 +done + +# Create topics +/opt/bitnami/kafka/bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 8 --topic latestMsgToRedis +/opt/bitnami/kafka/bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 8 --topic msgToPush +/opt/bitnami/kafka/bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 8 --topic offlineMsgToMongoMysql + +echo "Topics created." +``` + +The optimized and expanded documentation further details some basic commands for operations inside the Kafka container, as well as basic commands for managing Kafka using `kafkactl`. Here is a more detailed guide. + + +## Basic Commands in the Kafka Container + +### Listing Topics + +To list all existing topics, you can use the following command: + +```bash +kafka-topics.sh --list --bootstrap-server localhost:9092 +``` + +### Creating a New Topic + +When creating a new topic, you can specify the number of partitions and the replication factor. Here is the command to create a new topic: + +```bash +kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 1 --topic your_topic_name +``` + +### Producing Messages + +To send messages to a specific topic, you can use the producer command. The following command prompts you to enter messages, which are sent to the specified topic with each press of the Enter key: + +```bash +kafka-console-producer.sh --broker-list localhost:9092 --topic your_topic_name +``` + +### Consuming Messages + +To read messages from a specific topic, you can use the consumer command. The following command reads new messages from the specified topic and outputs them on the console: + +```bash +kafka-console-consumer.sh --bootstrap-server localhost:9092 --topic your_topic_name --from-beginning +``` + +The ` + +--from-beginning` parameter reads messages from the beginning of the topic. If this parameter is omitted, only new messages will be read. + + +## Basic Commands Using `kafkactl` + +`kafkactl` is a command-line tool for managing and operating Kafka clusters. It offers a more modern way to interact with Kafka. + +### Listing Topics + +To list all topics, you can use: + +```bash +kafkactl get topics +``` + +### Creating a New Topic + +To create a new topic with `kafkactl`, use: + +```bash +kafkactl create topic your_topic_name --partitions 1 --replication-factor 1 +``` + +### Producing Messages + +To send messages to a topic, you can use: + +```bash +kafkactl produce your_topic_name --value "your message" +``` + +Here, `"your message"` is the content of the message you want to send. + +### Consuming Messages + +To consume messages from a topic, use: + +```bash +kafkactl consume your_topic_name --from-beginning +``` + +Again, the `--from-beginning` parameter will start consuming messages from the beginning of the topic. If you do not wish to start from the beginning, you can omit this parameter. \ No newline at end of file diff --git a/docs/contrib/logging.md b/docs/contrib/logging.md index e4774929c..03628f427 100644 --- a/docs/contrib/logging.md +++ b/docs/contrib/logging.md @@ -2,7 +2,7 @@ ## Script Logging Documentation Link -If you wish to view the script's logging documentation, you can click on this link: [Logging Documentation](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/docs/contrib/bash-log.md). +If you wish to view the script's logging documentation, you can click on this link: [Logging Documentation](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/bash-log.md). Below is the documentation for logging and error handling in the OpenIM Go project. @@ -66,7 +66,7 @@ The `CodeError` interface is designed to provide a unified mechanism for error h ## Logging Standards and Code Examples -In the OpenIM project, we use the unified logging package `github.com/OpenIMSDK/tools/log` for logging to achieve efficient log management and analysis. This logging package supports structured logging formats, making it easier for developers to handle log information. +In the OpenIM project, we use the unified logging package `github.com/openimsdk/tools/log` for logging to achieve efficient log management and analysis. This logging package supports structured logging formats, making it easier for developers to handle log information. ### Logger Interface and Implementation @@ -96,7 +96,7 @@ func main() { ## Error Handling and Code Examples -We use the `github.com/OpenIMSDK/tools/errs` package for unified error handling and wrapping. +We use the `github.com/openimsdk/tools/errs` package for unified error handling and wrapping. ### CodeError Interface and Implementation @@ -121,7 +121,7 @@ package main import ( "fmt" - "github.com/OpenIMSDK/tools/errs" + "github.com/openimsdk/tools/errs" ) func main() { @@ -164,7 +164,7 @@ More details") import ( "context" - "github.com/OpenIMSDK/tools/log" + "github.com/openimsdk/tools/log" ) func main() { @@ -226,7 +226,7 @@ More details") package main import ( - "github.com/OpenIMSDK/tools/log" + "github.com/openimsdk/tools/log" "context" ) @@ -337,7 +337,19 @@ More details") // Suppose an error occurs here err, _ := someFunc() if err != nil { - return errs.Wrap(err, "doSomething failed") + return errs.WrapMsg(err, "doSomething failed") + } + } + ``` + + It just works if the package is wrong: + + ```go + func doSomething() error { + // Suppose an error occurs here + err, _ := someFunc() + if err != nil { + return errs.Wrap(err) } } ``` @@ -383,4 +395,113 @@ More details") } return nil } - ``` \ No newline at end of file + ``` + + +### About WrapMsg Use + +```go +// "github.com/openimsdk/tools/errs" +func WrapMsg(err error, msg string, kv ...any) error { + if len(kv) == 0 { + if len(msg) == 0 { + return errors.WithStack(err) + } else { + return errors.WithMessage(err, msg) + } + } + var buf bytes.Buffer + if len(msg) > 0 { + buf.WriteString(msg) + buf.WriteString(" ") + } + for i := 0; i < len(kv); i += 2 { + if i > 0 { + buf.WriteString(", ") + } + buf.WriteString(toString(kv[i])) + buf.WriteString("=") + buf.WriteString(toString(kv[i+1])) + } + return errors.WithMessage(err, buf.String()) +} +``` + +1. **Function Signature**: + - `err error`: The original error object. + - `msg string`: The message text to append to the error. + - `kv ...any`: A variable number of parameters used to pass key-value pairs. `any` was introduced in Go 1.18 and is equivalent to `interface{}`, meaning any type. + +2. **Logic**: + - If there are no key-value pairs (`kv` is empty): + - If `msg` is also empty, use `errors.WithStack(err)` to return the original error with the call stack appended. + - If `msg` is not empty, use `errors.WithMessage(err, msg)` to append the message text to the original error. + - If there are key-value pairs, the function constructs a string containing the message text and all key-value pairs. The key-value pairs are added in the format `"key=value"`, separated by commas. If a message text is provided, it is added first, followed by a space. + +3. **Key-Value Pair Formatting**: + - A loop iterates over all the key-value pairs, processing one pair at a time. + - The `toString` function (although not provided in the code, we can assume it converts any type to a string) is used to convert both keys and values to strings, and they are added to a `bytes.Buffer` in the format `"key=value"`. + +4. **Result**: + - Use `errors.WithMessage(err, buf.String())` to append the constructed message text to the original error, and return this new error object. + +Next, let's demonstrate several ways to use the `WrapMsg` function: + +**Example 1: No Additional Information** + +```go +// "github.com/openimsdk/tools/errs" +err := errors.New("original error") +wrappedErr := errs.WrapMsg(err, "") +// wrappedErr will contain the original error and its call stack +``` + +**Example 2: Message Text Only** + +```go +// "github.com/openimsdk/tools/errs" +err := errors.New("original error") +wrappedErr := errs.WrapMsg(err, "additional error information") +// wrappedErr will contain the original error, call stack, and "additional error information" +``` + +**Example 3: Message Text and Key-Value Pairs** + +```go +// "github.com/openimsdk/tools/errs" +err := errors.New("original error") +wrappedErr := errs.WrapMsg(err, "problem occurred", "code", 404, "url", "webhook://example.com") +// wrappedErr will contain the original error, call stack, and "problem occurred code=404, url=http://example.com" +``` + +**Example 4: Key-Value Pairs Only** + +```go +// "github.com/openimsdk/tools/errs" +err := errors.New("original error") +wrappedErr := errs.WrapMsg(err, "", "user", "john_doe", "action", "login") +// wrappedErr will contain the original error, call stack, and "user=john_doe, action=login" +``` + +> [!TIP] WThese examples demonstrate how the `errs.WrapMsg` function can flexibly handle error messages and context data, helping developers to more effectively track and debug their programs. + + +### Example 5: Dynamic Key-Value Pairs from Context +Suppose we have some runtime context variables, such as a user ID and the type of operation being performed, and we want to include these variables in the error message. This can help with later debugging and identifying the specific environment of the issue. + +```go +// Define some context variables +userID := "user123" +operation := "update profile" +errorCode := 500 +requestURL := "webhook://example.com/updateProfile" + +// Create a new error +err := errors.New("original error") + +// Wrap the error, including dynamic key-value pairs from the context +wrappedErr := errs.WrapMsg(err, "operation failed", "user", userID, "action", operation, "code", errorCode, "url", requestURL) +// wrappedErr will contain the original error, call stack, and "operation failed user=user123, action=update profile, code=500, url=http://example.com/updateProfile" +``` + +> [!TIP]In this example, the `WrapMsg` function accepts not just a static error message and additional information, but also dynamic key-value pairs generated from the code's execution context, such as the user ID, operation type, error code, and the URL of the request. Including this contextual information in the error message makes it easier for developers to understand and resolve the issue. \ No newline at end of file diff --git a/docs/contrib/test.md b/docs/contrib/test.md index 37c1792cf..2470a0537 100644 --- a/docs/contrib/test.md +++ b/docs/contrib/test.md @@ -2,14 +2,94 @@ This document serves as a comprehensive guide to understanding and utilizing the `test.sh` script for testing OpenIM RPC services. The `test.sh` script is a collection of bash functions designed to test various aspects of the OpenIM RPC services, ensuring that each part of the API is functioning as expected. -+ Scripts:https://github.com/OpenIMSDK/Open-IM-Server/tree/main/scripts/install/test.sh ++ Scripts:https://github.com/openimsdk/open-im-server/tree/main/scripts/install/test.sh -For some complex, bulky functional tests, performance tests, and various e2e tests, We are all in the current warehouse to https://github.com/OpenIMSDK/Open-IM-Server/tree/main/test or https://github.com/openim-sigs/test-infra directory In the. +For some complex, bulky functional tests, performance tests, and various e2e tests, We are all in the current warehouse to https://github.com/openimsdk/open-im-server/tree/main/test or https://github.com/openim-sigs/test-infra directory In the. + About OpenIM Feature [Test Docs](https://docs.google.com/spreadsheets/d/1zELWkwxgOOZ7u5pmYCqqaFnvZy2SVajv/edit?usp=sharing&ouid=103266350914914783293&rtpof=true&sd=true) +## Util Test -## Usage +Let's restructure and enhance the document under a unified second-level heading, adding clarity and details for better comprehension and visual appeal. + +--- + +## Development Guide + +### Comprehensive Testing Instructions + +#### Running Unit Tests + +- **Command**: To execute unit tests, input the following in your terminal: + ``` + make test + ``` + +#### Evaluating Test Coverage + +- **Overview**: It's crucial to assess how much of your code is covered by tests. +- **Command**: + ```bash + make cover + ``` + This command generates a report detailing the percentage of your code tested, ensuring adherence to quality standards. + +#### Conducting API Tests + +- **Purpose**: API tests validate the interaction and functionality of your application's interfaces. +- **How to Run**: + ``` + make test-api + ``` + Use this to check the integrity and reliability of your API endpoints. + +#### End-to-End (E2E) Testing + +- **Scope**: E2E tests simulate real-user scenarios from start to finish. +- **Execution**: + ``` + make test-e2e + ``` + This comprehensive testing ensures your application performs as expected in real-world situations. + +### Crafting Unit Test Cases + +#### Setup for Test Case Generation + +- **Installation**: Install the `gotests` tool to generate test cases automatically. + ```bash + make install.gotests + ``` + This command installs the `gotests` tool for test case generation. + +- **Environment Preparation**: Define your test template environment variable and generate test cases as shown below: + ```bash + export GOTESTS_TEMPLATE=testify + gotests -i -w -only keyFunc . + ``` + This prepares your environment for test case generation using the `testify` template. + +#### Isolating Function Tests + +- **Single Function Testing**: When you need to focus on testing a single function for detailed examination. +- **Method**: + ```bash + go test -v -run TestKeyFunc + ``` + This command specifically runs tests for `TestKeyFunc`, allowing targeted debugging and validation. + +### Important Note + +- **Quality Assurance**: Throughout your development process, it is imperative to ensure that the unit test coverage meets or surpasses the standards set by OpenIM. +- **Maintaining Standards**: Regularly running your tests with + ```make test``` + supports maintaining high code quality and adherence to OpenIM's rigorous testing benchmarks. + +## E2E Test + +TODO + +## Api Test The `test.sh` script is located within the `./scripts/install/` directory of the OpenIM service's codebase. To use the script, navigate to this directory from your terminal: diff --git a/docs/images/Open-IM.png b/docs/images/Open-IM.png deleted file mode 100644 index b61706550..000000000 Binary files a/docs/images/Open-IM.png and /dev/null differ diff --git a/docs/images/Architecture.jpg b/docs/images/architecture.jpg similarity index 100% rename from docs/images/Architecture.jpg rename to docs/images/architecture.jpg diff --git a/docs/images/Wechat.jpg b/docs/images/wechat.jpg similarity index 100% rename from docs/images/Wechat.jpg rename to docs/images/wechat.jpg diff --git a/go.mod b/go.mod index da2815571..891125a89 100644 --- a/go.mod +++ b/go.mod @@ -1,12 +1,9 @@ module github.com/openimsdk/open-im-server/v3 -go 1.19 +go 1.20 require ( firebase.google.com/go v3.13.0+incompatible - github.com/OpenIMSDK/protocol v0.0.56 - github.com/OpenIMSDK/tools v0.0.37 - github.com/bwmarrin/snowflake v0.3.0 // indirect github.com/dtm-labs/rockscache v0.1.1 github.com/gin-gonic/gin v1.9.1 github.com/go-playground/validator/v10 v10.18.0 @@ -15,36 +12,36 @@ require ( github.com/gorilla/websocket v1.5.1 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect - github.com/minio/minio-go/v7 v7.0.67 github.com/mitchellh/mapstructure v1.5.0 - github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect - github.com/openimsdk/localcache v0.0.1 + github.com/openimsdk/protocol v0.0.64 + github.com/openimsdk/tools v0.0.49-alpha.2 github.com/pkg/errors v0.9.1 // indirect github.com/prometheus/client_golang v1.18.0 - github.com/robfig/cron/v3 v3.0.1 - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/stretchr/testify v1.8.4 + github.com/stretchr/testify v1.9.0 go.mongodb.org/mongo-driver v1.14.0 - golang.org/x/image v0.15.0 google.golang.org/api v0.165.0 - google.golang.org/grpc v1.61.0 - google.golang.org/protobuf v1.32.0 + google.golang.org/grpc v1.62.1 + google.golang.org/protobuf v1.33.0 gopkg.in/yaml.v3 v3.0.1 ) require github.com/google/uuid v1.6.0 require ( - github.com/IBM/sarama v1.42.2 - github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible + github.com/IBM/sarama v1.43.0 + github.com/fatih/color v1.14.1 github.com/go-redis/redis v6.15.9+incompatible + github.com/hashicorp/golang-lru/v2 v2.0.7 + github.com/kelindar/bitmap v1.5.2 + github.com/likexian/gokit v0.25.13 + github.com/openimsdk/gomake v0.0.9 github.com/redis/go-redis/v9 v9.4.0 - github.com/spf13/pflag v1.0.5 + github.com/robfig/cron/v3 v3.0.1 + github.com/shirou/gopsutil v3.21.11+incompatible + github.com/spf13/viper v1.18.2 github.com/stathat/consistent v1.0.0 - github.com/tencentyun/cos-go-sdk-v5 v0.7.46 + go.uber.org/automaxprocs v1.5.3 golang.org/x/sync v0.6.0 - gopkg.in/src-d/go-git.v4 v4.13.1 - gotest.tools v2.2.0+incompatible ) require ( @@ -55,101 +52,112 @@ require ( cloud.google.com/go/iam v1.1.5 // indirect cloud.google.com/go/longrunning v0.5.4 // indirect cloud.google.com/go/storage v1.36.0 // indirect + github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/clbanning/mxj v1.8.4 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dustin/go-humanize v1.0.1 // indirect - github.com/eapache/go-resiliency v1.5.0 // indirect + github.com/eapache/go-resiliency v1.6.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect github.com/eapache/queue v1.1.0 // indirect - github.com/emirpasic/gods v1.12.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-zookeeper/zk v1.0.3 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/golang/snappy v0.0.4 // indirect - github.com/google/go-cmp v0.6.0 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/s2a-go v0.1.7 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/jcmturner/aescts/v2 v2.0.0 // indirect github.com/jcmturner/dnsutils/v2 v2.0.0 // indirect github.com/jcmturner/gofork v1.7.6 // indirect github.com/jcmturner/gokrb5/v8 v8.4.4 // indirect github.com/jcmturner/rpc/v2 v2.0.3 // indirect - github.com/jinzhu/copier v0.3.5 // indirect + github.com/jinzhu/copier v0.4.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/kelindar/simd v1.1.2 // indirect + github.com/klauspost/compress v1.17.7 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/lithammer/shortuuid v3.0.0+incompatible // indirect + github.com/magefile/mage v1.15.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/minio-go/v7 v7.0.69 // indirect github.com/minio/sha256-simd v1.0.1 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect github.com/mozillazg/go-httpheader v0.4.0 // indirect github.com/onsi/ginkgo v1.16.5 // indirect github.com/onsi/gomega v1.18.1 // indirect - github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/pierrec/lz4/v4 v4.1.21 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rs/xid v1.5.0 // indirect - github.com/sergi/go-diff v1.0.0 // indirect - github.com/src-d/gcfg v1.4.0 // indirect - github.com/stretchr/objx v0.5.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/stretchr/objx v0.5.2 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/tencentyun/cos-go-sdk-v5 v0.7.47 // indirect + github.com/tklauser/go-sysconf v0.3.13 // indirect + github.com/tklauser/numcpus v0.7.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/xanzy/ssh-agent v0.2.1 // indirect github.com/xdg-go/pbkdf2 v1.0.0 // indirect github.com/xdg-go/scram v1.1.2 // indirect github.com/xdg-go/stringprep v1.0.4 // indirect github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect go.opentelemetry.io/otel v1.23.0 // indirect go.opentelemetry.io/otel/metric v1.23.0 // indirect go.opentelemetry.io/otel/trace v1.23.0 // indirect - go.uber.org/atomic v1.7.0 // indirect + go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.21.0 // indirect + golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect + golang.org/x/image v0.15.0 // indirect + golang.org/x/net v0.22.0 // indirect golang.org/x/oauth2 v0.17.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.19.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 // indirect - gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect - gopkg.in/warnings.v0 v0.1.2 // indirect - gorm.io/gorm v1.25.4 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect + gorm.io/gorm v1.25.8 // indirect stathat.com/c/consistent v1.0.0 // indirect ) @@ -161,8 +169,6 @@ require ( github.com/spf13/cobra v1.8.0 github.com/ugorji/go/codec v1.2.11 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.21.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect ) - -replace github.com/openimsdk/localcache => ./pkg/localcache diff --git a/go.sum b/go.sum index c3803ef58..5dc0cc524 100644 --- a/go.sum +++ b/go.sum @@ -16,28 +16,16 @@ cloud.google.com/go/storage v1.36.0/go.mod h1:M6M/3V/D3KpzMTJyPOR/HU6n2Si5QdaXYE firebase.google.com/go v3.13.0+incompatible h1:3TdYC3DDi6aHn20qoRkxwGqNgdjtblwVAyRLQwGn/+4= firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIwjt8toICdV5Wh9ptHs= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/IBM/sarama v1.42.2 h1:VoY4hVIZ+WQJ8G9KNY/SQlWguBQXQ9uvFPOnrcu8hEw= -github.com/IBM/sarama v1.42.2/go.mod h1:FLPGUGwYqEs62hq2bVG6Io2+5n+pS6s/WOXVKWSLFtE= -github.com/OpenIMSDK/protocol v0.0.56 h1:mbVFyDBachEsmJLfYW5AU1z2KL8AUEpoHG8RPCIxjgg= -github.com/OpenIMSDK/protocol v0.0.56/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y= -github.com/OpenIMSDK/tools v0.0.37 h1:qvDqmA4RbEJtPjZouWCkVuf/pjm6Y8nUrG5iH2gcnOg= -github.com/OpenIMSDK/tools v0.0.37/go.mod h1:wBfR5CYmEyvxl03QJbTkhz1CluK6J4/lX0lviu8JAjE= +github.com/IBM/sarama v1.43.0 h1:YFFDn8mMI2QL0wOrG0J2sFoVIAFl7hS9JQi2YZsXtJc= +github.com/IBM/sarama v1.43.0/go.mod h1:zlE6HEbC/SMQ9mhEYaF7nNLYOUyrs0obySKCckWP9BM= github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= -github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= -github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= -github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= -github.com/bwmarrin/snowflake v0.3.0 h1:xm67bEhkKh6ij1790JB83OujPR5CzNe8QuQqAgISZN0= -github.com/bwmarrin/snowflake v0.3.0/go.mod h1:NdZxfVWX+oR6y2K0o6qAYv6gIOP9rjG0/E9WsDpxqwE= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= @@ -54,12 +42,12 @@ github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/xds/go v0.0.0-20231109132714-523115ebc101 h1:7To3pQ+pZo0i3dsWEbinPNFs5gPSBOsJtx3wTT94VBY= +github.com/cncf/xds/go v0.0.0-20231128003011-0fa0005c9caa h1:jQCWAUqqlij9Pgj2i/PB79y4KOPYVyFYdROxgaCwdTQ= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= @@ -67,39 +55,40 @@ github.com/dtm-labs/rockscache v0.1.1 h1:6S1vgaHvGqrLd8Ka4hRTKeKPV7v+tT0MSkTIX81 github.com/dtm-labs/rockscache v0.1.1/go.mod h1:c76WX0kyIibmQ2ACxUXvDvaLykoPakivMqIxt+UzE7A= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eapache/go-resiliency v1.5.0 h1:dRsaR00whmQD+SgVKlq/vCRFNgtEb5yppyeVos3Yce0= -github.com/eapache/go-resiliency v1.5.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= +github.com/eapache/go-resiliency v1.6.0 h1:CqGDTLtpwuWKn6Nj3uNUdflaq+/kIPsg0gfNzHton30= +github.com/eapache/go-resiliency v1.6.0/go.mod h1:5yPzW0MIvSe0JDsv0v+DvcjEv2FyD6iZYSs1ZI+iQho= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4ALJ04o5Qqpdz8XLIpNA3WM/iSIXqxtqo7UGVws= github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= -github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/envoyproxy/protoc-gen-validate v1.0.2 h1:QkIBuU5k+x7/QXPvPPnWXWlCdaBFApVqftFV6k087DA= +github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A= +github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= +github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= -github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= -github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= @@ -148,7 +137,6 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= @@ -171,8 +159,9 @@ github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/ github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -180,12 +169,12 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo= @@ -198,9 +187,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= -github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= -github.com/jinzhu/copier v0.3.5 h1:GlvfUwHk62RokgqVNvYsku0TATCF7bAHVwEXoBh3iJg= -github.com/jinzhu/copier v0.3.5/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +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= @@ -208,21 +196,19 @@ github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/ github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd h1:Coekwdh0v2wtGp9Gmz1Ze3eVRAWJMLokvN3QjdzCHLY= -github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kelindar/bitmap v1.5.2 h1:XwX7CTvJtetQZ64zrOkApoZZHBJRkjE23NfqUALA/HE= +github.com/kelindar/bitmap v1.5.2/go.mod h1:j3qZjxH9s4OtvsnFTP2bmPkjqil9Y2xQlxPYHexasEA= +github.com/kelindar/simd v1.1.2 h1:KduKb+M9cMY2HIH8S/cdJyD+5n5EGgq+Aeeleos55To= +github.com/kelindar/simd v1.1.2/go.mod h1:inq4DFudC7W8L5fhxoeZflLRNpWSs0GNx6MlWFvuvr0= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= +github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= @@ -232,20 +218,27 @@ github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible h1:Y6sqxHMyB1D2YSzWkL 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/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM= +github.com/likexian/gokit v0.25.13/go.mod h1:qQhEWFBEfqLCO3/vOEo2EDKd+EycekVtUK4tex+l2H4= github.com/lithammer/shortuuid v3.0.0+incompatible h1:NcD0xWW/MZYXEHa6ITy6kaXN5nwm/V115vj2YXfhS0w= github.com/lithammer/shortuuid v3.0.0+incompatible/go.mod h1:FR74pbAuElzOUuenUHTK2Tciko1/vKuIKS9dSkDrA4w= +github.com/magefile/mage v1.15.0 h1:BvGheCMAsG3bWUDbZ8AyXXpCNwU9u5CB6sM+HNb9HYg= +github.com/magefile/mage v1.15.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= -github.com/minio/minio-go/v7 v7.0.67 h1:BeBvZWAS+kRJm1vGTMJYVjKUNoo0FoEt/wUWdUtfmh8= -github.com/minio/minio-go/v7 v7.0.67/go.mod h1:+UXocnUeZ3wHvVh5s95gcrA4YjMIbccT6ubB+1m054A= +github.com/minio/minio-go/v7 v7.0.69 h1:l8AnsQFyY1xiwa/DaQskY4NXSLA2yrGsW5iD9nRPVS0= +github.com/minio/minio-go/v7 v7.0.69/go.mod h1:XAvOPJQ5Xlzk5o3o/ArO2NMbhSGkimC+bpW/ngRKDmQ= github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM= github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -259,8 +252,6 @@ github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJ github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= github.com/mozillazg/go-httpheader v0.4.0 h1:aBn6aRXtFzyDLZ4VIRLsZbbJloagQfMnCiYgOq6hK4w= github.com/mozillazg/go-httpheader v0.4.0/go.mod h1:PuT8h0pw6efvp8ZeUec1Rs7dwjK08bt6gKSReGMqtdA= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= -github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -275,16 +266,22 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo= -github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= -github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/openimsdk/gomake v0.0.9 h1:ouf25ygN2PMQ68Gfgns/EQRPiLPnp+77SIr68GfE+n4= +github.com/openimsdk/gomake v0.0.9/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI= +github.com/openimsdk/protocol v0.0.64 h1:OrjSs4CgKN9VLvJvrAsc37O7Ru0E0VllXZQSmG/ab7U= +github.com/openimsdk/protocol v0.0.64/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8= +github.com/openimsdk/tools v0.0.49-alpha.2 h1:8IfV6o2ySU7C54sh/MG7ctEp1h3lSNe03OCUDWSk5Ws= +github.com/openimsdk/tools v0.0.49-alpha.2/go.mod h1:P4oGP1Pd+d4ctbLD5U/XQTgl8yu8Hd3skx640Fr69ko= +github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= +github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 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/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk= github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -304,23 +301,31 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4= -github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI= +github.com/spf13/viper v1.18.2 h1:LUXCnvUvSM6FXAsj6nnfc8Q2tp1dIgUfY9Kc8GsSOiQ= +github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMVB+yk= github.com/stathat/consistent v1.0.0 h1:ZFJ1QTRn8npNBKW065raSZ8xfOqhpb8vLOkfp4CcL/U= github.com/stathat/consistent v1.0.0/go.mod h1:uajTPbgSygZBJ+V+0mY7meZ8i0XAcZs7AQ6V121XSxw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= @@ -328,19 +333,23 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0= -github.com/tencentyun/cos-go-sdk-v5 v0.7.46 h1:IeTiMR8qZ7iQWhAGb1niw5vt0T1TfAwPeB8Gn/oTkuk= -github.com/tencentyun/cos-go-sdk-v5 v0.7.46/go.mod h1:DH9US8nB+AJXqwu/AMOrCFN1COv3dpytXuJWHgdg7kE= +github.com/tencentyun/cos-go-sdk-v5 v0.7.47 h1:uoS4Sob16qEYoapkqJq1D1Vnsy9ira9BfNUMtoFYTI4= +github.com/tencentyun/cos-go-sdk-v5 v0.7.47/go.mod h1:DH9US8nB+AJXqwu/AMOrCFN1COv3dpytXuJWHgdg7kE= +github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= +github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= +github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= +github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/xanzy/ssh-agent v0.2.1 h1:TCbipTQL2JiiCprBWx9frJ2eJlCYT00NmctrHxVAr70= -github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= @@ -352,6 +361,8 @@ github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7Jul github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= @@ -367,8 +378,10 @@ go.opentelemetry.io/otel/metric v1.23.0/go.mod h1:MqUW2X2a6Q8RN96E2/nqNoT+z9BSms go.opentelemetry.io/otel/sdk v1.21.0 h1:FTt8qirL1EysG6sTQRZ5TokkU8d0ugCj8htOgThZXQ8= go.opentelemetry.io/otel/trace v1.23.0 h1:37Ik5Ib7xfYVb4V1UtnT97T1jI+AoIYkJyPkuL4iJgI= go.opentelemetry.io/otel/trace v1.23.0/go.mod h1:GSGTbIClEsuZrGIzoEHqsVfxgn5UkggkflQwDScNUsk= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= +go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -377,16 +390,16 @@ go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= +golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= 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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= @@ -402,7 +415,6 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= @@ -413,8 +425,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.17.0 h1:6m3ZPmLEFdVxKKWnKq4VqZ60gutO35zm+zrAHVmHyDQ= golang.org/x/oauth2 v0.17.0/go.mod h1:OzPDGQiuQMguemayvdylqddI7qcD9lnSDb+1FiwQ5HA= @@ -429,10 +441,9 @@ golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -444,18 +455,16 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= +golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= @@ -470,7 +479,6 @@ golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= @@ -492,17 +500,17 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe h1:USL2DhxfgRchafRvt/wYyyQNzwgL7ZiURcozOE/Pkvo= google.golang.org/genproto v0.0.0-20240125205218-1f4bbc51befe/go.mod h1:cc8bqMqtv9gMOr0zHg2Vzff5ULhhL2IXP4sbcn32Dro= -google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac h1:OZkkudMUu9LVQMCoRUbI/1p5VCo9BOrlvkqMvWtqa6s= -google.golang.org/genproto/googleapis/api v0.0.0-20240116215550-a9fa1716bcac/go.mod h1:B5xPO//w8qmBDjGReYLpR6UJPnkldGkCSMoH/2vxJeg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014 h1:FSL3lRCkhaPFxqi0s9o+V4UI2WTzAVOvkgbd4kVV4Wg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240205150955-31a09d347014/go.mod h1:SaPjaZGWb0lPqs6Ittu0spdfrOArqji4ZdeP5IC/9N4= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80 h1:Lj5rbfG876hIAYFjqiJnPHfhXbv+nzTWfm04Fg/XSVU= +google.golang.org/genproto/googleapis/api v0.0.0-20240123012728-ef4313101c80/go.mod h1:4jWUdICTdgc3Ibxmr8nAJiiLHwQBY0UI0XZcEMaFKaA= +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.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.61.0 h1:TOvOcuXn30kRao+gfcvsebNEa5iZIiLkisYEkf7R7o0= -google.golang.org/grpc v1.61.0/go.mod h1:VUbo7IFqmF1QtCAstipjG0GIoq49KvMe9+h1jFLBNJs= +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 v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -514,24 +522,15 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= -google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +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/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg= -gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= -gopkg.in/src-d/go-git-fixtures.v3 v3.5.0 h1:ivZFOIltbce2Mo8IjzUHAFoq/IylO9WHhNOAJK+LsJg= -gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g= -gopkg.in/src-d/go-git.v4 v4.13.1 h1:SRtFyV8Kxc0UP7aCHcijOMQGPxHSmMOPrzulQWolkYE= -gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -540,10 +539,8 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 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/gorm v1.25.4 h1:iyNd8fNAe8W9dvtlgeRI5zSVZPsq3OpcTu37cYcpCmw= -gorm.io/gorm v1.25.4/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gorm.io/gorm v1.25.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo= +gorm.io/gorm v1.25.8/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/go.work b/go.work deleted file mode 100644 index 56eb874d4..000000000 --- a/go.work +++ /dev/null @@ -1,16 +0,0 @@ -go 1.19 - -use ( - . - ./test/typecheck - ./tools/changelog - ./tools/component - ./tools/formitychecker - ./tools/imctl - ./tools/infra - ./tools/ncpu - ./tools/openim-web - ./tools/url2im - ./tools/versionchecker - ./tools/yamlfmt -) diff --git a/internal/api/auth.go b/internal/api/auth.go index 0f7a3933b..f0790ce98 100644 --- a/internal/api/auth.go +++ b/internal/api/auth.go @@ -15,10 +15,10 @@ package api import ( - "github.com/OpenIMSDK/protocol/auth" - "github.com/OpenIMSDK/tools/a2r" "github.com/gin-gonic/gin" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/auth" + "github.com/openimsdk/tools/a2r" ) type AuthApi rpcclient.Auth diff --git a/internal/api/conversation.go b/internal/api/conversation.go index fe6c67001..f273eaa4a 100644 --- a/internal/api/conversation.go +++ b/internal/api/conversation.go @@ -15,10 +15,10 @@ package api import ( - "github.com/OpenIMSDK/protocol/conversation" - "github.com/OpenIMSDK/tools/a2r" "github.com/gin-gonic/gin" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/conversation" + "github.com/openimsdk/tools/a2r" ) type ConversationApi rpcclient.Conversation diff --git a/internal/api/custom_validator.go b/internal/api/custom_validator.go index d135b0dc4..99c84f074 100644 --- a/internal/api/custom_validator.go +++ b/internal/api/custom_validator.go @@ -15,8 +15,8 @@ package api import ( - "github.com/OpenIMSDK/protocol/constant" "github.com/go-playground/validator/v10" + "github.com/openimsdk/protocol/constant" ) // RequiredIf validates if the specified field is required based on the session type. @@ -26,7 +26,7 @@ func RequiredIf(fl validator.FieldLevel) bool { switch sessionType { case constant.SingleChatType, constant.NotificationChatType: return fl.FieldName() != "RecvID" || fl.Field().String() != "" - case constant.GroupChatType, constant.SuperGroupChatType: + case constant.WriteGroupChatType, constant.ReadGroupChatType: return fl.FieldName() != "GroupID" || fl.Field().String() != "" default: return true diff --git a/internal/api/friend.go b/internal/api/friend.go index 24bcbf899..1fea38b31 100644 --- a/internal/api/friend.go +++ b/internal/api/friend.go @@ -15,10 +15,10 @@ package api import ( - "github.com/OpenIMSDK/protocol/friend" - "github.com/OpenIMSDK/tools/a2r" "github.com/gin-gonic/gin" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/friend" + "github.com/openimsdk/tools/a2r" ) type FriendApi rpcclient.Friend diff --git a/internal/api/group.go b/internal/api/group.go index c18ded64b..6079c5343 100644 --- a/internal/api/group.go +++ b/internal/api/group.go @@ -15,10 +15,10 @@ package api import ( - "github.com/OpenIMSDK/protocol/group" - "github.com/OpenIMSDK/tools/a2r" "github.com/gin-gonic/gin" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/group" + "github.com/openimsdk/tools/a2r" ) type GroupApi rpcclient.Group @@ -115,22 +115,14 @@ func (o *GroupApi) GetGroupAbstractInfo(c *gin.Context) { a2r.Call(group.GroupClient.GetGroupAbstractInfo, o.Client, c) } -//func (g *Group) SetGroupMemberNickname(c *gin.Context) { +// func (g *Group) SetGroupMemberNickname(c *gin.Context) { // a2r.Call(group.GroupClient.SetGroupMemberNickname, g.userClient, c) //} // -//func (g *Group) GetGroupAllMemberList(c *gin.Context) { +// func (g *Group) GetGroupAllMemberList(c *gin.Context) { // a2r.Call(group.GroupClient.GetGroupAllMember, g.userClient, c) //} -func (o *GroupApi) GetJoinedSuperGroupList(c *gin.Context) { - a2r.Call(group.GroupClient.GetJoinedSuperGroupList, o.Client, c) -} - -func (o *GroupApi) GetSuperGroupsInfo(c *gin.Context) { - a2r.Call(group.GroupClient.GetSuperGroupsInfo, o.Client, c) -} - func (o *GroupApi) GroupCreateCount(c *gin.Context) { a2r.Call(group.GroupClient.GroupCreateCount, o.Client, c) } diff --git a/internal/api/init.go b/internal/api/init.go new file mode 100644 index 000000000..6e784da9a --- /dev/null +++ b/internal/api/init.go @@ -0,0 +1,114 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package api + +import ( + "context" + "fmt" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/network" + "net" + "net/http" + "os" + "os/signal" + "strconv" + "syscall" + "time" + + kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" + ginprom "github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus" + "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/system/program" +) + +type Config struct { + RpcConfig config.API + MongodbConfig config.Mongo + ZookeeperConfig config.ZooKeeper + NotificationConfig config.Notification + Share config.Share + MinioConfig config.Minio +} + +func Start(ctx context.Context, index int, config *Config) error { + apiPort, err := datautil.GetElemByIndex(config.RpcConfig.Api.Ports, index) + if err != nil { + return err + } + prometheusPort, err := datautil.GetElemByIndex(config.RpcConfig.Prometheus.Ports, index) + if err != nil { + return err + } + + var client discovery.SvcDiscoveryRegistry + + // Determine whether zk is passed according to whether it is a clustered deployment + client, err = kdisc.NewDiscoveryRegister(&config.ZookeeperConfig, &config.Share) + if err != nil { + return errs.WrapMsg(err, "failed to register discovery service") + } + + var ( + netDone = make(chan struct{}, 1) + netErr error + ) + + router := newGinRouter(client, config) + if config.RpcConfig.Prometheus.Enable { + go func() { + p := ginprom.NewPrometheus("app", prommetrics.GetGinCusMetrics("Api")) + p.SetListenAddress(fmt.Sprintf(":%d", prometheusPort)) + if err = p.Use(router); err != nil && err != http.ErrServerClosed { + netErr = errs.WrapMsg(err, fmt.Sprintf("prometheus start err: %d", prometheusPort)) + netDone <- struct{}{} + } + }() + + } + address := net.JoinHostPort(network.GetListenIP(config.RpcConfig.Api.ListenIP), strconv.Itoa(apiPort)) + + server := http.Server{Addr: address, Handler: router} + log.CInfo(ctx, "API server is initializing", "address", address, "apiPort", apiPort, "prometheusPort", prometheusPort) + go func() { + err = server.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + netErr = errs.WrapMsg(err, fmt.Sprintf("api start err: %s", server.Addr)) + netDone <- struct{}{} + + } + }() + + sigs := make(chan os.Signal, 1) + signal.Notify(sigs, syscall.SIGTERM) + + ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) + defer cancel() + select { + case <-sigs: + program.SIGTERMExit() + err := server.Shutdown(ctx) + if err != nil { + return errs.WrapMsg(err, "shutdown err") + } + case <-netDone: + close(netDone) + return netErr + } + return nil +} diff --git a/internal/api/msg.go b/internal/api/msg.go index ad5001459..180342e59 100644 --- a/internal/api/msg.go +++ b/internal/api/msg.go @@ -15,15 +15,6 @@ package api import ( - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/a2r" - "github.com/OpenIMSDK/tools/apiresp" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "github.com/mitchellh/mapstructure" @@ -31,23 +22,38 @@ import ( "github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/a2r" + "github.com/openimsdk/tools/apiresp" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/idutil" + "github.com/openimsdk/tools/utils/jsonutil" + "github.com/openimsdk/tools/utils/timeutil" ) type MessageApi struct { *rpcclient.Message validate *validator.Validate userRpcClient *rpcclient.UserRpcClient + imAdminUserID []string } -func NewMessageApi(msgRpcClient *rpcclient.Message, userRpcClient *rpcclient.User) MessageApi { - return MessageApi{Message: msgRpcClient, validate: validator.New(), userRpcClient: rpcclient.NewUserRpcClientByUser(userRpcClient)} +func NewMessageApi(msgRpcClient *rpcclient.Message, userRpcClient *rpcclient.User, + imAdminUserID []string) MessageApi { + return MessageApi{Message: msgRpcClient, validate: validator.New(), + userRpcClient: rpcclient.NewUserRpcClientByUser(userRpcClient), imAdminUserID: imAdminUserID} } func (MessageApi) SetOptions(options map[string]bool, value bool) { - utils.SetSwitchFromOptions(options, constant.IsHistory, value) - utils.SetSwitchFromOptions(options, constant.IsPersistent, value) - utils.SetSwitchFromOptions(options, constant.IsSenderSync, value) - utils.SetSwitchFromOptions(options, constant.IsConversationUpdate, value) + datautil.SetSwitchFromOptions(options, constant.IsHistory, value) + datautil.SetSwitchFromOptions(options, constant.IsPersistent, value) + datautil.SetSwitchFromOptions(options, constant.IsSenderSync, value) + datautil.SetSwitchFromOptions(options, constant.IsConversationUpdate, value) } func (m MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg) *msg.SendMsgReq { @@ -56,8 +62,8 @@ func (m MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg) switch params.ContentType { case constant.OANotification: notification := sdkws.NotificationElem{} - notification.Detail = utils.StructToJsonString(params.Content) - newContent = utils.StructToJsonString(¬ification) + notification.Detail = jsonutil.StructToJsonString(params.Content) + newContent = jsonutil.StructToJsonString(¬ification) case constant.Text: fallthrough case constant.Picture: @@ -71,19 +77,19 @@ func (m MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg) case constant.File: fallthrough default: - newContent = utils.StructToJsonString(params.Content) + newContent = jsonutil.StructToJsonString(params.Content) } if params.IsOnlineOnly { m.SetOptions(options, false) } if params.NotOfflinePush { - utils.SetSwitchFromOptions(options, constant.IsOfflinePush, false) + datautil.SetSwitchFromOptions(options, constant.IsOfflinePush, false) } pbData := msg.SendMsgReq{ MsgData: &sdkws.MsgData{ SendID: params.SendID, GroupID: params.GroupID, - ClientMsgID: utils.GetMsgID(params.SendID), + ClientMsgID: idutil.GetMsgIDByMD5(params.SendID), SenderPlatformID: params.SenderPlatformID, SenderNickname: params.SenderNickname, SenderFaceURL: params.SenderFaceURL, @@ -91,7 +97,7 @@ func (m MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg) MsgFrom: constant.SysMsgType, ContentType: params.ContentType, Content: []byte(newContent), - CreateTime: utils.GetCurrentTimestampByMill(), + CreateTime: timeutil.GetCurrentTimestampByMill(), SendTime: params.SendTime, Options: options, OfflinePushInfo: params.OfflinePushInfo, @@ -173,14 +179,14 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM return nil, err } default: - return nil, errs.ErrArgs.WithDetail("not support err contentType") + return nil, errs.WrapMsg(errs.ErrArgs, "unsupported content type", "contentType", req.ContentType) } if err := mapstructure.WeakDecode(req.Content, &data); err != nil { - return nil, err + return nil, errs.WrapMsg(err, "failed to decode message content") } - log.ZDebug(c, "getSendMsgReq", "req", req.Content) + log.ZDebug(c, "getSendMsgReq", "decodedContent", data) if err := m.validate.Struct(data); err != nil { - return nil, err + return nil, errs.WrapMsg(err, "validation error") } return m.newUserSendMsgReq(c, &req), nil } @@ -198,9 +204,9 @@ func (m *MessageApi) SendMessage(c *gin.Context) { } // Check if the user has the app manager role. - if !authverify.IsAppManagerUid(c, m.Config) { + if !authverify.IsAppManagerUid(c, m.imAdminUserID) { // Respond with a permission error if the user is not an app manager. - apiresp.GinError(c, errs.ErrNoPermission.Wrap("only app manager can send message")) + apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message")) return } @@ -253,16 +259,16 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) { return } - if !authverify.IsAppManagerUid(c, m.Config) { - apiresp.GinError(c, errs.ErrNoPermission.Wrap("only app manager can send message")) + if !authverify.IsAppManagerUid(c, m.imAdminUserID) { + apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message")) return } sendMsgReq := msg.SendMsgReq{ MsgData: &sdkws.MsgData{ SendID: req.SendUserID, RecvID: req.RecvUserID, - Content: []byte(utils.StructToJsonString(&sdkws.NotificationElem{ - Detail: utils.StructToJsonString(&struct { + Content: []byte(jsonutil.StructToJsonString(&sdkws.NotificationElem{ + Detail: jsonutil.StructToJsonString(&struct { Key string `json:"key"` Data string `json:"data"` }{Key: req.Key, Data: req.Data}), @@ -270,9 +276,9 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) { MsgFrom: constant.SysMsgType, ContentType: constant.BusinessNotification, SessionType: constant.SingleChatType, - CreateTime: utils.GetCurrentTimestampByMill(), - ClientMsgID: utils.GetMsgID(mcontext.GetOpUserID(c)), - Options: config.GetOptionsByNotification(config.NotificationConf{ + CreateTime: timeutil.GetCurrentTimestampByMill(), + ClientMsgID: idutil.GetMsgIDByMD5(mcontext.GetOpUserID(c)), + Options: config.GetOptionsByNotification(config.NotificationConfig{ IsSendMsg: false, ReliabilityLevel: 1, UnreadCount: false, @@ -296,9 +302,8 @@ func (m *MessageApi) BatchSendMsg(c *gin.Context) { apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap()) return } - log.ZInfo(c, "BatchSendMsg", "req", req) - if err := authverify.CheckAdmin(c, m.Config); err != nil { - apiresp.GinError(c, errs.ErrNoPermission.Wrap("only app manager can send message")) + if err := authverify.CheckAdmin(c, m.imAdminUserID); err != nil { + apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message")) return } diff --git a/internal/api/route.go b/internal/api/router.go similarity index 59% rename from internal/api/route.go rename to internal/api/router.go index ca0b6829e..bd2de99db 100644 --- a/internal/api/route.go +++ b/internal/api/router.go @@ -1,135 +1,25 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package api import ( - "context" "fmt" - "net" - "net/http" - "os" - "os/signal" - "strconv" - "syscall" - "time" - - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/apiresp" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mw" - "github.com/OpenIMSDK/tools/tokenverify" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" "github.com/go-playground/validator/v10" - "github.com/openimsdk/open-im-server/v3/pkg/authverify" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" - kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" - ginprom "github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus" - "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" - "github.com/redis/go-redis/v9" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/apiresp" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mw" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "net/http" ) -func Start(config *config.GlobalConfig, port int, proPort int) error { - if port == 0 || proPort == 0 { - err := "port or proPort is empty:" + strconv.Itoa(port) + "," + strconv.Itoa(proPort) - return errs.Wrap(fmt.Errorf(err)) - } - rdb, err := cache.NewRedis(config) - if err != nil { - return err - } - - var client discoveryregistry.SvcDiscoveryRegistry - - // Determine whether zk is passed according to whether it is a clustered deployment - client, err = kdisc.NewDiscoveryRegister(config) - if err != nil { - return errs.Wrap(err, "register discovery err") - } - - if err = client.CreateRpcRootNodes(config.GetServiceNames()); err != nil { - return errs.Wrap(err, "create rpc root nodes error") - } - - if err = client.RegisterConf2Registry(constant.OpenIMCommonConfigKey, config.EncodeConfig()); err != nil { - return errs.Wrap(err) - } - var ( - netDone = make(chan struct{}, 1) - netErr error - ) - router := newGinRouter(client, rdb, config) - if config.Prometheus.Enable { - go func() { - p := ginprom.NewPrometheus("app", prommetrics.GetGinCusMetrics("Api")) - p.SetListenAddress(fmt.Sprintf(":%d", proPort)) - if err = p.Use(router); err != nil && err != http.ErrServerClosed { - netErr = errs.Wrap(err, fmt.Sprintf("prometheus start err: %d", proPort)) - netDone <- struct{}{} - } - }() - - } - - var address string - if config.Api.ListenIP != "" { - address = net.JoinHostPort(config.Api.ListenIP, strconv.Itoa(port)) - } else { - address = net.JoinHostPort("0.0.0.0", strconv.Itoa(port)) - } - - server := http.Server{Addr: address, Handler: router} - - go func() { - err = server.ListenAndServe() - if err != nil && err != http.ErrServerClosed { - netErr = errs.Wrap(err, fmt.Sprintf("api start err: %s", server.Addr)) - netDone <- struct{}{} - - } - }() - - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGTERM) - - ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) - defer cancel() - select { - case <-sigs: - util.SIGTERMExit() - err := server.Shutdown(ctx) - if err != nil { - return errs.Wrap(err, "shutdown err") - } - case <-netDone: - close(netDone) - return netErr - } - return nil -} - -func newGinRouter(disCov discoveryregistry.SvcDiscoveryRegistry, rdb redis.UniversalClient, config *config.GlobalConfig) *gin.Engine { - disCov.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) +func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.Engine { + disCov.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) gin.SetMode(gin.ReleaseMode) r := gin.New() if v, ok := binding.Validator.Engine().(*validator.Validate); ok { @@ -137,17 +27,18 @@ func newGinRouter(disCov discoveryregistry.SvcDiscoveryRegistry, rdb redis.Unive } r.Use(gin.Recovery(), mw.CorsHandler(), mw.GinParseOperationID()) // init rpc client here - userRpc := rpcclient.NewUser(disCov, config) - groupRpc := rpcclient.NewGroup(disCov, config) - friendRpc := rpcclient.NewFriend(disCov, config) - messageRpc := rpcclient.NewMessage(disCov, config) - conversationRpc := rpcclient.NewConversation(disCov, config) - authRpc := rpcclient.NewAuth(disCov, config) - thirdRpc := rpcclient.NewThird(disCov, config) + userRpc := rpcclient.NewUser(disCov, config.Share.RpcRegisterName.User, config.Share.RpcRegisterName.MessageGateway, + config.Share.IMAdminUserID) + groupRpc := rpcclient.NewGroup(disCov, config.Share.RpcRegisterName.Group) + friendRpc := rpcclient.NewFriend(disCov, config.Share.RpcRegisterName.Friend) + messageRpc := rpcclient.NewMessage(disCov, config.Share.RpcRegisterName.Msg) + conversationRpc := rpcclient.NewConversation(disCov, config.Share.RpcRegisterName.Conversation) + authRpc := rpcclient.NewAuth(disCov, config.Share.RpcRegisterName.Auth) + thirdRpc := rpcclient.NewThird(disCov, config.Share.RpcRegisterName.Third, config.RpcConfig.Prometheus.GrafanaURL) u := NewUserApi(*userRpc) - m := NewMessageApi(messageRpc, userRpc) - ParseToken := GinParseToken(rdb, config) + m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID) + ParseToken := GinParseToken(authRpc) userRouterGroup := r.Group("/user") { userRouterGroup.POST("/user_register", u.UserRegister) @@ -224,11 +115,6 @@ func newGinRouter(disCov discoveryregistry.SvcDiscoveryRegistry, rdb redis.Unive groupRouterGroup.POST("/get_groups", g.GetGroups) groupRouterGroup.POST("/get_group_member_user_id", g.GetGroupMemberUserIDs) } - superGroupRouterGroup := r.Group("/super_group", ParseToken) - { - superGroupRouterGroup.POST("/get_joined_group_list", g.GetJoinedSuperGroupList) - superGroupRouterGroup.POST("/get_groups_info", g.GetSuperGroupsInfo) - } // certificate authRouterGroup := r.Group("/auth") { @@ -309,68 +195,26 @@ func newGinRouter(disCov discoveryregistry.SvcDiscoveryRegistry, rdb redis.Unive return r } -func GinParseToken(rdb redis.UniversalClient, config *config.GlobalConfig) gin.HandlerFunc { - dataBase := controller.NewAuthDatabase( - cache.NewMsgCacheModel(rdb, config), - config.Secret, - config.TokenPolicy.Expire, - config, - ) +func GinParseToken(authRPC *rpcclient.Auth) gin.HandlerFunc { return func(c *gin.Context) { switch c.Request.Method { case http.MethodPost: token := c.Request.Header.Get(constant.Token) if token == "" { - log.ZWarn(c, "header get token error", errs.ErrArgs.Wrap("header must have token")) - apiresp.GinError(c, errs.ErrArgs.Wrap("header must have token")) - c.Abort() - return - } - claims, err := tokenverify.GetClaimFromToken(token, authverify.Secret(config.Secret)) - if err != nil { - log.ZWarn(c, "jwt get token error", errs.ErrTokenUnknown.Wrap()) - apiresp.GinError(c, errs.ErrTokenUnknown.Wrap()) + log.ZWarn(c, "header get token error", servererrs.ErrArgs.WrapMsg("header must have token")) + apiresp.GinError(c, servererrs.ErrArgs.WrapMsg("header must have token")) c.Abort() return } - m, err := dataBase.GetTokensWithoutError(c, claims.UserID, claims.PlatformID) + resp, err := authRPC.ParseToken(c, token) if err != nil { - apiresp.GinError(c, errs.ErrTokenNotExist.Wrap()) - c.Abort() - return - } - if len(m) == 0 { - apiresp.GinError(c, errs.ErrTokenNotExist.Wrap()) + apiresp.GinError(c, err) c.Abort() return } - if v, ok := m[token]; ok { - switch v { - case constant.NormalToken: - case constant.KickedToken: - apiresp.GinError(c, errs.ErrTokenKicked.Wrap()) - c.Abort() - return - default: - apiresp.GinError(c, errs.ErrTokenUnknown.Wrap()) - c.Abort() - return - } - } else { - apiresp.GinError(c, errs.ErrTokenNotExist.Wrap()) - c.Abort() - return - } - c.Set(constant.OpUserPlatform, constant.PlatformIDToName(claims.PlatformID)) - c.Set(constant.OpUserID, claims.UserID) + c.Set(constant.OpUserPlatform, constant.PlatformIDToName(int(resp.PlatformID))) + c.Set(constant.OpUserID, resp.UserID) c.Next() } } } - -// // handleGinError logs and returns an error response through Gin context. -// func handleGinError(c *gin.Context, logMessage string, errType errs.CodeError, detail string) { -// wrappedErr := errType.Wrap(detail) -// apiresp.GinError(c, wrappedErr) -// c.Abort() -// } diff --git a/internal/api/statistics.go b/internal/api/statistics.go index 2b80a1585..f5ee99f73 100644 --- a/internal/api/statistics.go +++ b/internal/api/statistics.go @@ -15,10 +15,10 @@ package api import ( - "github.com/OpenIMSDK/protocol/user" - "github.com/OpenIMSDK/tools/a2r" "github.com/gin-gonic/gin" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/user" + "github.com/openimsdk/tools/a2r" ) type StatisticsApi rpcclient.User diff --git a/internal/api/third.go b/internal/api/third.go index 30448ae4d..6baa70ee5 100644 --- a/internal/api/third.go +++ b/internal/api/third.go @@ -15,16 +15,20 @@ package api import ( + "context" + "google.golang.org/grpc" "math/rand" "net/http" + "net/url" "strconv" + "strings" - "github.com/OpenIMSDK/protocol/third" - "github.com/OpenIMSDK/tools/a2r" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mcontext" "github.com/gin-gonic/gin" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/third" + "github.com/openimsdk/tools/a2r" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/mcontext" ) type ThirdApi rpcclient.Third @@ -43,6 +47,35 @@ func (o *ThirdApi) SetAppBadge(c *gin.Context) { // #################### s3 #################### +func setURLPrefixOption[A, B, C any](_ func(client C, ctx context.Context, req *A, options ...grpc.CallOption) (*B, error), fn func(*A) error) *a2r.Option[A, B] { + return &a2r.Option[A, B]{ + BindAfter: fn, + } +} + +func setURLPrefix(c *gin.Context, urlPrefix *string) error { + host := c.GetHeader("X-Request-Api") + if host != "" { + if strings.HasSuffix(host, "/") { + *urlPrefix = host + "object/" + return nil + } else { + *urlPrefix = host + "/object/" + return nil + } + } + u := url.URL{ + Scheme: "http", + Host: c.Request.Host, + Path: "/object/", + } + if c.Request.TLS != nil { + u.Scheme = "https" + } + *urlPrefix = u.String() + return nil +} + func (o *ThirdApi) PartLimit(c *gin.Context) { a2r.Call(third.ThirdClient.PartLimit, o.Client, c) } @@ -52,7 +85,10 @@ func (o *ThirdApi) PartSize(c *gin.Context) { } func (o *ThirdApi) InitiateMultipartUpload(c *gin.Context) { - a2r.Call(third.ThirdClient.InitiateMultipartUpload, o.Client, c) + opt := setURLPrefixOption(third.ThirdClient.InitiateMultipartUpload, func(req *third.InitiateMultipartUploadReq) error { + return setURLPrefix(c, &req.UrlPrefix) + }) + a2r.Call(third.ThirdClient.InitiateMultipartUpload, o.Client, c, opt) } func (o *ThirdApi) AuthSign(c *gin.Context) { @@ -60,7 +96,10 @@ func (o *ThirdApi) AuthSign(c *gin.Context) { } func (o *ThirdApi) CompleteMultipartUpload(c *gin.Context) { - a2r.Call(third.ThirdClient.CompleteMultipartUpload, o.Client, c) + opt := setURLPrefixOption(third.ThirdClient.CompleteMultipartUpload, func(req *third.CompleteMultipartUploadReq) error { + return setURLPrefix(c, &req.UrlPrefix) + }) + a2r.Call(third.ThirdClient.CompleteMultipartUpload, o.Client, c, opt) } func (o *ThirdApi) AccessURL(c *gin.Context) { @@ -72,7 +111,10 @@ func (o *ThirdApi) InitiateFormData(c *gin.Context) { } func (o *ThirdApi) CompleteFormData(c *gin.Context) { - a2r.Call(third.ThirdClient.CompleteFormData, o.Client, c) + opt := setURLPrefixOption(third.ThirdClient.CompleteFormData, func(req *third.CompleteFormDataReq) error { + return setURLPrefix(c, &req.UrlPrefix) + }) + a2r.Call(third.ThirdClient.CompleteFormData, o.Client, c, opt) } func (o *ThirdApi) ObjectRedirect(c *gin.Context) { @@ -126,5 +168,5 @@ func (o *ThirdApi) SearchLogs(c *gin.Context) { } func (o *ThirdApi) GetPrometheus(c *gin.Context) { - c.Redirect(http.StatusFound, o.Config.Prometheus.GrafanaUrl) + c.Redirect(http.StatusFound, o.GrafanaUrl) } diff --git a/internal/api/user.go b/internal/api/user.go index 16b453e46..d48111b9e 100644 --- a/internal/api/user.go +++ b/internal/api/user.go @@ -15,15 +15,15 @@ package api import ( - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msggateway" - "github.com/OpenIMSDK/protocol/user" - "github.com/OpenIMSDK/tools/a2r" - "github.com/OpenIMSDK/tools/apiresp" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" "github.com/gin-gonic/gin" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msggateway" + "github.com/openimsdk/protocol/user" + "github.com/openimsdk/tools/a2r" + "github.com/openimsdk/tools/apiresp" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" ) type UserApi rpcclient.User @@ -69,7 +69,7 @@ func (u *UserApi) GetUsersOnlineStatus(c *gin.Context) { apiresp.GinError(c, err) return } - conns, err := u.Discov.GetConns(c, u.Config.RpcRegisterName.OpenImMessageGatewayName) + conns, err := u.Discov.GetConns(c, u.MessageGateWayRpcName) if err != nil { apiresp.GinError(c, err) return @@ -133,7 +133,7 @@ func (u *UserApi) GetUsersOnlineTokenDetail(c *gin.Context) { apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap()) return } - conns, err := u.Discov.GetConns(c, u.Config.RpcRegisterName.OpenImMessageGatewayName) + conns, err := u.Discov.GetConns(c, u.MessageGateWayRpcName) if err != nil { apiresp.GinError(c, err) return diff --git a/internal/msggateway/callback.go b/internal/msggateway/callback.go index afb83bcc4..1750f779b 100644 --- a/internal/msggateway/callback.go +++ b/internal/msggateway/callback.go @@ -18,21 +18,17 @@ import ( "context" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/mcontext" cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/http" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/mcontext" ) -func CallbackUserOnline(ctx context.Context, globalConfig *config.GlobalConfig, userID string, platformID int, isAppBackground bool, connID string) error { - if !globalConfig.Callback.CallbackUserOnline.Enable { - return nil - } +func (ws *WsServer) webhookAfterUserOnline(ctx context.Context, after *config.AfterConfig, userID string, platformID int, isAppBackground bool, connID string) { req := cbapi.CallbackUserOnlineReq{ UserStatusCallbackReq: cbapi.UserStatusCallbackReq{ UserStatusBaseCallback: cbapi.UserStatusBaseCallback{ - CallbackCommand: cbapi.CallbackUserOnlineCommand, + CallbackCommand: cbapi.CallbackAfterUserOnlineCommand, OperationID: mcontext.GetOperationID(ctx), PlatformID: platformID, Platform: constant.PlatformIDToName(platformID), @@ -43,21 +39,14 @@ func CallbackUserOnline(ctx context.Context, globalConfig *config.GlobalConfig, IsAppBackground: isAppBackground, ConnID: connID, } - resp := cbapi.CommonCallbackResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, &req, &resp, globalConfig.Callback.CallbackUserOnline); err != nil { - return err - } - return nil + ws.webhookClient.AsyncPost(ctx, req.GetCallbackCommand(), req, &cbapi.CommonCallbackResp{}, after) } -func CallbackUserOffline(ctx context.Context, globalConfig *config.GlobalConfig, userID string, platformID int, connID string) error { - if !globalConfig.Callback.CallbackUserOffline.Enable { - return nil - } +func (ws *WsServer) webhookAfterUserOffline(ctx context.Context, after *config.AfterConfig, userID string, platformID int, connID string) { req := &cbapi.CallbackUserOfflineReq{ UserStatusCallbackReq: cbapi.UserStatusCallbackReq{ UserStatusBaseCallback: cbapi.UserStatusBaseCallback{ - CallbackCommand: cbapi.CallbackUserOfflineCommand, + CallbackCommand: cbapi.CallbackAfterUserOfflineCommand, OperationID: mcontext.GetOperationID(ctx), PlatformID: platformID, Platform: constant.PlatformIDToName(platformID), @@ -67,21 +56,14 @@ func CallbackUserOffline(ctx context.Context, globalConfig *config.GlobalConfig, Seq: time.Now().UnixMilli(), ConnID: connID, } - resp := &cbapi.CallbackUserOfflineResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackUserOffline); err != nil { - return err - } - return nil + ws.webhookClient.AsyncPost(ctx, req.GetCallbackCommand(), req, &cbapi.CallbackUserOfflineResp{}, after) } -func CallbackUserKickOff(ctx context.Context, globalConfig *config.GlobalConfig, userID string, platformID int) error { - if !globalConfig.Callback.CallbackUserKickOff.Enable { - return nil - } +func (ws *WsServer) webhookAfterUserKickOff(ctx context.Context, after *config.AfterConfig, userID string, platformID int) { req := &cbapi.CallbackUserKickOffReq{ UserStatusCallbackReq: cbapi.UserStatusCallbackReq{ UserStatusBaseCallback: cbapi.UserStatusBaseCallback{ - CallbackCommand: cbapi.CallbackUserKickOffCommand, + CallbackCommand: cbapi.CallbackAfterUserKickOffCommand, OperationID: mcontext.GetOperationID(ctx), PlatformID: platformID, Platform: constant.PlatformIDToName(platformID), @@ -90,93 +72,5 @@ func CallbackUserKickOff(ctx context.Context, globalConfig *config.GlobalConfig, }, Seq: time.Now().UnixMilli(), } - resp := &cbapi.CommonCallbackResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackUserOffline); err != nil { - return err - } - return nil + ws.webhookClient.AsyncPost(ctx, req.GetCallbackCommand(), req, &cbapi.CommonCallbackResp{}, after) } - -// func callbackUserOnline(operationID, userID string, platformID int, token string, isAppBackground bool, connID -// string) cbApi.CommonCallbackResp { -// callbackResp := cbApi.CommonCallbackResp{OperationID: operationID} -// if !config.Config.Callback.CallbackUserOnline.WithEnable { -// return callbackResp -// } -// callbackUserOnlineReq := cbApi.CallbackUserOnlineReq{ -// Token: token, -// UserStatusCallbackReq: cbApi.UserStatusCallbackReq{ -// UserStatusBaseCallback: cbApi.UserStatusBaseCallback{ -// CallbackCommand: constant.CallbackUserOnlineCommand, -// OperationID: operationID, -// PlatformID: int32(platformID), -// Platform: constant.PlatformIDToName(platformID), -// }, -// UserID: userID, -// }, -// Seq: int(time.Now().UnixNano() / 1e6), -// IsAppBackground: isAppBackground, -// ConnID: connID, -// } -// callbackUserOnlineResp := &cbApi.CallbackUserOnlineResp{CommonCallbackResp: &callbackResp} -// if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, constant.CallbackUserOnlineCommand, -// callbackUserOnlineReq, callbackUserOnlineResp, config.Config.Callback.CallbackUserOnline.CallbackTimeOut); err != nil -// { -// callbackResp.ErrCode = http2.StatusInternalServerError -// callbackResp.ErrMsg = err.Error() -// } -// return callbackResp -//} -//func callbackUserOffline(operationID, userID string, platformID int, connID string) cbApi.CommonCallbackResp { -// callbackResp := cbApi.CommonCallbackResp{OperationID: operationID} -// if !config.Config.Callback.CallbackUserOffline.WithEnable { -// return callbackResp -// } -// callbackOfflineReq := cbApi.CallbackUserOfflineReq{ -// UserStatusCallbackReq: cbApi.UserStatusCallbackReq{ -// UserStatusBaseCallback: cbApi.UserStatusBaseCallback{ -// CallbackCommand: constant.CallbackUserOfflineCommand, -// OperationID: operationID, -// PlatformID: int32(platformID), -// Platform: constant.PlatformIDToName(platformID), -// }, -// UserID: userID, -// }, -// Seq: int(time.Now().UnixNano() / 1e6), -// ConnID: connID, -// } -// callbackUserOfflineResp := &cbApi.CallbackUserOfflineResp{CommonCallbackResp: &callbackResp} -// if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, constant.CallbackUserOfflineCommand, -// callbackOfflineReq, callbackUserOfflineResp, config.Config.Callback.CallbackUserOffline.CallbackTimeOut); err != nil -// { -// callbackResp.ErrCode = http2.StatusInternalServerError -// callbackResp.ErrMsg = err.Error() -// } -// return callbackResp -//} -//func callbackUserKickOff(operationID string, userID string, platformID int) cbApi.CommonCallbackResp { -// callbackResp := cbApi.CommonCallbackResp{OperationID: operationID} -// if !config.Config.Callback.CallbackUserKickOff.WithEnable { -// return callbackResp -// } -// callbackUserKickOffReq := cbApi.CallbackUserKickOffReq{ -// UserStatusCallbackReq: cbApi.UserStatusCallbackReq{ -// UserStatusBaseCallback: cbApi.UserStatusBaseCallback{ -// CallbackCommand: constant.CallbackUserKickOffCommand, -// OperationID: operationID, -// PlatformID: int32(platformID), -// Platform: constant.PlatformIDToName(platformID), -// }, -// UserID: userID, -// }, -// Seq: int(time.Now().UnixNano() / 1e6), -// } -// callbackUserKickOffResp := &cbApi.CallbackUserKickOffResp{CommonCallbackResp: &callbackResp} -// if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, constant.CallbackUserKickOffCommand, -// callbackUserKickOffReq, callbackUserKickOffResp, config.Config.Callback.CallbackUserOffline.CallbackTimeOut); err != -// nil { -// callbackResp.ErrCode = http2.StatusInternalServerError -// callbackResp.ErrMsg = err.Error() -// } -// return callbackResp -//} diff --git a/internal/msggateway/client.go b/internal/msggateway/client.go index 4e843821e..af869dd85 100644 --- a/internal/msggateway/client.go +++ b/internal/msggateway/client.go @@ -16,28 +16,27 @@ package msggateway import ( "context" - "errors" "fmt" "runtime/debug" "sync" "sync/atomic" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/apiresp" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/apiresp" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/stringutil" "google.golang.org/protobuf/proto" ) var ( - ErrConnClosed = errors.New("conn has closed") - ErrNotSupportMessageProtocol = errors.New("not support message protocol") - ErrClientClosed = errors.New("client actively close the connection") - ErrPanic = errors.New("panic error") + ErrConnClosed = errs.New("conn has closed") + ErrNotSupportMessageProtocol = errs.New("not support message protocol") + ErrClientClosed = errs.New("client actively close the connection") + ErrPanic = errs.New("panic error") ) const ( @@ -75,32 +74,20 @@ type Client struct { token string } -// function not used -// func newClient(ctx *UserConnContext, conn LongConn, isCompress bool) *Client { -// return &Client{ -// w: new(sync.Mutex), -// conn: conn, -// PlatformID: utils.StringToInt(ctx.GetPlatformID()), -// IsCompress: isCompress, -// UserID: ctx.GetUserID(), -// ctx: ctx, -// } -// } - // ResetClient updates the client's state with new connection and context information. -func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, isBackground, isCompress bool, longConnServer LongConnServer, token string) { +func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer LongConnServer) { c.w = new(sync.Mutex) c.conn = conn - c.PlatformID = utils.StringToInt(ctx.GetPlatformID()) - c.IsCompress = isCompress - c.IsBackground = isBackground + c.PlatformID = stringutil.StringToInt(ctx.GetPlatformID()) + c.IsCompress = ctx.GetCompression() + c.IsBackground = ctx.GetBackground() c.UserID = ctx.GetUserID() c.ctx = ctx c.longConnServer = longConnServer c.IsBackground = false c.closed.Store(false) c.closedErr = nil - c.token = token + c.token = ctx.GetToken() } func (c *Client) pingHandler(_ string) error { @@ -126,6 +113,7 @@ func (c *Client) readMessage() { c.conn.SetPingHandler(c.pingHandler) for { + log.ZDebug(c.ctx, "readMessage") messageType, message, returnErr := c.conn.ReadMessage() if returnErr != nil { log.ZWarn(c.ctx, "readMessage", returnErr, "messageType", messageType) @@ -187,7 +175,7 @@ func (c *Client) handleMessage(message []byte) error { } if binaryReq.SendID != c.UserID { - return errs.Wrap(errors.New("exception conn userID not same to req userID"), binaryReq.String()) + return errs.New("exception conn userID not same to req userID", "binaryReq", binaryReq.String()) } ctx := mcontext.WithMustInfoCtx( @@ -267,7 +255,7 @@ func (c *Client) replyMessage(ctx context.Context, binaryReq *Req, err error, re } if binaryReq.ReqIdentifier == WsLogoutMsg { - return errs.Wrap(errors.New("user logout")) + return errs.New("user logout", "operationID", binaryReq.OperationID).Wrap() } return nil } diff --git a/internal/msggateway/compressor.go b/internal/msggateway/compressor.go index 140aac4d8..52d315b79 100644 --- a/internal/msggateway/compressor.go +++ b/internal/msggateway/compressor.go @@ -20,7 +20,7 @@ import ( "io" "sync" - "github.com/OpenIMSDK/tools/errs" + "github.com/openimsdk/tools/errs" ) var ( @@ -34,6 +34,7 @@ type Compressor interface { DeCompress(compressedData []byte) ([]byte, error) DecompressWithPool(compressedData []byte) ([]byte, error) } + type GzipCompressor struct { compressProtocol string } @@ -47,11 +48,11 @@ func (g *GzipCompressor) Compress(rawData []byte) ([]byte, error) { gz := gzip.NewWriter(&gzipBuffer) if _, err := gz.Write(rawData); err != nil { - return nil, errs.Wrap(err, "GzipCompressor.Compress: writing to gzip writer failed") + return nil, errs.WrapMsg(err, "GzipCompressor.Compress: writing to gzip writer failed") } if err := gz.Close(); err != nil { - return nil, errs.Wrap(err, "GzipCompressor.Compress: closing gzip writer failed") + return nil, errs.WrapMsg(err, "GzipCompressor.Compress: closing gzip writer failed") } return gzipBuffer.Bytes(), nil @@ -65,10 +66,10 @@ func (g *GzipCompressor) CompressWithPool(rawData []byte) ([]byte, error) { gz.Reset(&gzipBuffer) if _, err := gz.Write(rawData); err != nil { - return nil, errs.Wrap(err, "GzipCompressor.CompressWithPool: error writing data") + return nil, errs.WrapMsg(err, "GzipCompressor.CompressWithPool: error writing data") } if err := gz.Close(); err != nil { - return nil, errs.Wrap(err, "GzipCompressor.CompressWithPool: error closing gzip writer") + return nil, errs.WrapMsg(err, "GzipCompressor.CompressWithPool: error closing gzip writer") } return gzipBuffer.Bytes(), nil } @@ -77,16 +78,16 @@ func (g *GzipCompressor) DeCompress(compressedData []byte) ([]byte, error) { buff := bytes.NewBuffer(compressedData) reader, err := gzip.NewReader(buff) if err != nil { - return nil, errs.Wrap(err, "GzipCompressor.DeCompress: NewReader creation failed") + return nil, errs.WrapMsg(err, "GzipCompressor.DeCompress: NewReader creation failed") } decompressedData, err := io.ReadAll(reader) if err != nil { - return nil, errs.Wrap(err, "GzipCompressor.DeCompress: reading from gzip reader failed") + return nil, errs.WrapMsg(err, "GzipCompressor.DeCompress: reading from gzip reader failed") } if err = reader.Close(); err != nil { // Even if closing the reader fails, we've successfully read the data, // so we return the decompressed data and an error indicating the close failure. - return decompressedData, errs.Wrap(err, "GzipCompressor.DeCompress: closing gzip reader failed") + return decompressedData, errs.WrapMsg(err, "GzipCompressor.DeCompress: closing gzip reader failed") } return decompressedData, nil } @@ -97,16 +98,16 @@ func (g *GzipCompressor) DecompressWithPool(compressedData []byte) ([]byte, erro err := reader.Reset(bytes.NewReader(compressedData)) if err != nil { - return nil, errs.Wrap(err, "GzipCompressor.DecompressWithPool: resetting gzip reader failed") + return nil, errs.WrapMsg(err, "GzipCompressor.DecompressWithPool: resetting gzip reader failed") } decompressedData, err := io.ReadAll(reader) if err != nil { - return nil, errs.Wrap(err, "GzipCompressor.DecompressWithPool: reading from pooled gzip reader failed") + return nil, errs.WrapMsg(err, "GzipCompressor.DecompressWithPool: reading from pooled gzip reader failed") } if err = reader.Close(); err != nil { // Similar to DeCompress, return the data and error for close failure. - return decompressedData, errs.Wrap(err, "GzipCompressor.DecompressWithPool: closing pooled gzip reader failed") + return decompressedData, errs.WrapMsg(err, "GzipCompressor.DecompressWithPool: closing pooled gzip reader failed") } return decompressedData, nil } diff --git a/internal/msggateway/constant.go b/internal/msggateway/constant.go index 045629b4e..64664ac0a 100644 --- a/internal/msggateway/constant.go +++ b/internal/msggateway/constant.go @@ -26,7 +26,7 @@ const ( Compression = "compression" GzipCompressionProtocol = "gzip" BackgroundStatus = "isBackground" - MsgResp = "isMsgResp" + SendResponse = "isMsgResp" ) const ( diff --git a/internal/msggateway/context.go b/internal/msggateway/context.go index ad679c1a1..6c80ece1b 100644 --- a/internal/msggateway/context.go +++ b/internal/msggateway/context.go @@ -15,13 +15,16 @@ package msggateway import ( + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "net/http" "net/url" "strconv" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/utils" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/utils/encrypt" + "github.com/openimsdk/tools/utils/stringutil" + "github.com/openimsdk/tools/utils/timeutil" ) type UserConnContext struct { @@ -54,7 +57,7 @@ func (c *UserConnContext) Value(key any) any { case constant.ConnID: return c.GetConnID() case constant.OpUserPlatform: - return constant.PlatformIDToName(utils.StringToInt(c.GetPlatformID())) + return constant.PlatformIDToName(stringutil.StringToInt(c.GetPlatformID())) case constant.RemoteAddr: return c.RemoteAddr default: @@ -69,7 +72,7 @@ func newContext(respWriter http.ResponseWriter, req *http.Request) *UserConnCont Path: req.URL.Path, Method: req.Method, RemoteAddr: req.RemoteAddr, - ConnID: utils.Md5(req.RemoteAddr + "_" + strconv.Itoa(int(utils.GetCurrentTimestampByMill()))), + ConnID: encrypt.Md5(req.RemoteAddr + "_" + strconv.Itoa(int(timeutil.GetCurrentTimestampByMill()))), } } @@ -133,6 +136,32 @@ func (c *UserConnContext) GetToken() string { return c.Req.URL.Query().Get(Token) } +func (c *UserConnContext) GetCompression() bool { + compression, exists := c.Query(Compression) + if exists && compression == GzipCompressionProtocol { + return true + } else { + compression, exists := c.GetHeader(Compression) + if exists && compression == GzipCompressionProtocol { + return true + } + } + return false +} + +func (c *UserConnContext) ShouldSendResp() bool { + errResp, exists := c.Query(SendResponse) + if exists { + b, err := strconv.ParseBool(errResp) + if err != nil { + return false + } else { + return b + } + } + return false +} + func (c *UserConnContext) SetToken(token string) { c.Req.URL.RawQuery = Token + "=" + token } @@ -144,3 +173,23 @@ func (c *UserConnContext) GetBackground() bool { } return b } +func (c *UserConnContext) ParseEssentialArgs() error { + _, exists := c.Query(Token) + if !exists { + return servererrs.ErrConnArgsErr.WrapMsg("token is empty") + } + _, exists = c.Query(WsUserID) + if !exists { + return servererrs.ErrConnArgsErr.WrapMsg("sendID is empty") + } + platformIDStr, exists := c.Query(PlatformID) + if !exists { + return servererrs.ErrConnArgsErr.WrapMsg("platformID is empty") + } + _, err := strconv.Atoi(platformIDStr) + if err != nil { + return servererrs.ErrConnArgsErr.WrapMsg("platformID is not int") + + } + return nil +} diff --git a/internal/msggateway/encoder.go b/internal/msggateway/encoder.go index cd2c50d96..3af266374 100644 --- a/internal/msggateway/encoder.go +++ b/internal/msggateway/encoder.go @@ -18,7 +18,7 @@ import ( "bytes" "encoding/gob" - "github.com/OpenIMSDK/tools/errs" + "github.com/openimsdk/tools/errs" ) type Encoder interface { @@ -35,9 +35,8 @@ func NewGobEncoder() *GobEncoder { func (g *GobEncoder) Encode(data any) ([]byte, error) { buff := bytes.Buffer{} enc := gob.NewEncoder(&buff) - err := enc.Encode(data) - if err != nil { - return nil, errs.Wrap(err, "GobEncoder.Encode failed") + if err := enc.Encode(data); err != nil { + return nil, errs.WrapMsg(err, "GobEncoder.Encode failed", "action", "encode") } return buff.Bytes(), nil } @@ -45,9 +44,8 @@ func (g *GobEncoder) Encode(data any) ([]byte, error) { func (g *GobEncoder) Decode(encodeData []byte, decodeData any) error { buff := bytes.NewBuffer(encodeData) dec := gob.NewDecoder(buff) - err := dec.Decode(decodeData) - if err != nil { - return errs.Wrap(err, "GobEncoder.Decode failed") + if err := dec.Decode(decodeData); err != nil { + return errs.WrapMsg(err, "GobEncoder.Decode failed", "action", "decode") } return nil } diff --git a/internal/msggateway/http_error.go b/internal/msggateway/http_error.go index 03881cf27..8d9d03522 100644 --- a/internal/msggateway/http_error.go +++ b/internal/msggateway/http_error.go @@ -14,8 +14,12 @@ package msggateway -import "github.com/OpenIMSDK/tools/apiresp" +import ( + "github.com/openimsdk/tools/apiresp" + "github.com/openimsdk/tools/log" +) func httpError(ctx *UserConnContext, err error) { + log.ZWarn(ctx, "ws connection error", err) apiresp.HttpError(ctx.RespWriter, err) } diff --git a/internal/msggateway/hub_server.go b/internal/msggateway/hub_server.go index 146565561..bfe81b602 100644 --- a/internal/msggateway/hub_server.go +++ b/internal/msggateway/hub_server.go @@ -16,38 +16,30 @@ package msggateway import ( "context" - - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msggateway" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" "github.com/openimsdk/open-im-server/v3/pkg/authverify" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msggateway" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" "google.golang.org/grpc" ) -func (s *Server) InitServer(config *config.GlobalConfig, disCov discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error { - rdb, err := cache.NewRedis(config) - if err != nil { - return err - } - - msgModel := cache.NewMsgCacheModel(rdb, config) +func (s *Server) InitServer(ctx context.Context, config *Config, disCov discovery.SvcDiscoveryRegistry, server *grpc.Server) error { s.LongConnServer.SetDiscoveryRegistry(disCov, config) - s.LongConnServer.SetCacheHandler(msgModel) msggateway.RegisterMsgGatewayServer(server, s) return nil } -func (s *Server) Start(conf *config.GlobalConfig) error { - return startrpc.Start( - s.rpcPort, - conf.RpcRegisterName.OpenImMessageGatewayName, - s.prometheusPort, +func (s *Server) Start(ctx context.Context, index int, conf *Config) error { + return startrpc.Start(ctx, &conf.ZookeeperConfig, &conf.MsgGateway.Prometheus, conf.MsgGateway.ListenIP, + conf.MsgGateway.RPC.RegisterIP, + conf.MsgGateway.RPC.Ports, index, + conf.Share.RpcRegisterName.MessageGateway, + &conf.Share, conf, s.InitServer, ) @@ -57,7 +49,7 @@ type Server struct { rpcPort int prometheusPort int LongConnServer LongConnServer - config *config.GlobalConfig + config *Config pushTerminal map[int]struct{} } @@ -65,7 +57,7 @@ func (s *Server) SetLongConnServer(LongConnServer LongConnServer) { s.LongConnServer = LongConnServer } -func NewServer(rpcPort int, proPort int, longConnServer LongConnServer, conf *config.GlobalConfig) *Server { +func NewServer(rpcPort int, proPort int, longConnServer LongConnServer, conf *Config) *Server { s := &Server{ rpcPort: rpcPort, prometheusPort: proPort, @@ -89,8 +81,8 @@ func (s *Server) GetUsersOnlineStatus( ctx context.Context, req *msggateway.GetUsersOnlineStatusReq, ) (*msggateway.GetUsersOnlineStatusResp, error) { - if !authverify.IsAppManagerUid(ctx, s.config) { - return nil, errs.ErrNoPermission.Wrap("only app manager") + if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { + return nil, errs.ErrNoPermission.WrapMsg("only app manager") } var resp msggateway.GetUsersOnlineStatusResp for _, userID := range req.UserIDs { @@ -122,11 +114,9 @@ func (s *Server) GetUsersOnlineStatus( return &resp, nil } -func (s *Server) OnlineBatchPushOneMsg( - ctx context.Context, - req *msggateway.OnlineBatchPushOneMsgReq, -) (*msggateway.OnlineBatchPushOneMsgResp, error) { - panic("implement me") +func (s *Server) OnlineBatchPushOneMsg(ctx context.Context, req *msggateway.OnlineBatchPushOneMsgReq) (*msggateway.OnlineBatchPushOneMsgResp, error) { + // todo implement + return nil, nil } func (s *Server) SuperGroupOnlineBatchPushOneMsg(ctx context.Context, req *msggateway.OnlineBatchPushOneMsgReq, @@ -158,7 +148,7 @@ func (s *Server) SuperGroupOnlineBatchPushOneMsg(ctx context.Context, req *msgga (client.IsBackground && client.PlatformID != constant.IOSPlatformID) { err := client.PushMessage(ctx, req.MsgData) if err != nil { - userPlatform.ResultCode = int64(errs.ErrPushMsgErr.Code()) + userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code()) resp = append(resp, userPlatform) } else { if _, ok := s.pushTerminal[client.PlatformID]; ok { @@ -167,7 +157,7 @@ func (s *Server) SuperGroupOnlineBatchPushOneMsg(ctx context.Context, req *msgga } } } else { - userPlatform.ResultCode = int64(errs.ErrIOSBackgroundPushErr.Code()) + userPlatform.ResultCode = int64(servererrs.ErrIOSBackgroundPushErr.Code()) resp = append(resp, userPlatform) } } @@ -187,7 +177,7 @@ func (s *Server) KickUserOffline( for _, v := range req.KickUserIDList { clients, _, ok := s.LongConnServer.GetUserPlatformCons(v, int(req.PlatformID)) if !ok { - log.ZInfo(ctx, "conn not exist", "userID", v, "platformID", req.PlatformID) + log.ZDebug(ctx, "conn not exist", "userID", v, "platformID", req.PlatformID) continue } @@ -203,10 +193,7 @@ func (s *Server) KickUserOffline( return &msggateway.KickUserOfflineResp{}, nil } -func (s *Server) MultiTerminalLoginCheck( - ctx context.Context, - req *msggateway.MultiTerminalLoginCheckReq, -) (*msggateway.MultiTerminalLoginCheckResp, error) { +func (s *Server) MultiTerminalLoginCheck(ctx context.Context, req *msggateway.MultiTerminalLoginCheckReq) (*msggateway.MultiTerminalLoginCheckResp, error) { if oldClients, userOK, clientOK := s.LongConnServer.GetUserPlatformCons(req.UserID, int(req.PlatformID)); userOK { tempUserCtx := newTempContext() tempUserCtx.SetToken(req.Token) diff --git a/internal/msggateway/init.go b/internal/msggateway/init.go index 4efbb7cdf..727ade0af 100644 --- a/internal/msggateway/init.go +++ b/internal/msggateway/init.go @@ -15,22 +15,43 @@ package msggateway import ( - "fmt" + "context" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/utils/datautil" "time" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/log" ) -// RunWsAndServer run ws server. -func RunWsAndServer(conf *config.GlobalConfig, rpcPort, wsPort, prometheusPort int) error { - fmt.Println("start rpc/msg_gateway server, port: ", rpcPort, wsPort, prometheusPort, ", OpenIM version: ", config.Version) +type Config struct { + MsgGateway config.MsgGateway + ZookeeperConfig config.ZooKeeper + Share config.Share + WebhooksConfig config.Webhooks +} + +// Start run ws server. +func Start(ctx context.Context, index int, conf *Config) error { + log.CInfo(ctx, "MSG-GATEWAY server is initializing", "rpcPorts", conf.MsgGateway.RPC.Ports, + "wsPort", conf.MsgGateway.LongConnSvr.Ports, "prometheusPorts", conf.MsgGateway.Prometheus.Ports) + wsPort, err := datautil.GetElemByIndex(conf.MsgGateway.LongConnSvr.Ports, index) + if err != nil { + return err + } + prometheusPort, err := datautil.GetElemByIndex(conf.MsgGateway.Prometheus.Ports, index) + if err != nil { + return err + } + rpcPort, err := datautil.GetElemByIndex(conf.MsgGateway.RPC.Ports, index) + if err != nil { + return err + } longServer, err := NewWsServer( conf, WithPort(wsPort), - WithMaxConnNum(int64(conf.LongConnSvr.WebsocketMaxConnNum)), - WithHandshakeTimeout(time.Duration(conf.LongConnSvr.WebsocketTimeout)*time.Second), - WithMessageMaxMsgLength(conf.LongConnSvr.WebsocketMaxMsgLen), - WithWriteBufferSize(conf.LongConnSvr.WebsocketWriteBufferSize), + WithMaxConnNum(int64(conf.MsgGateway.LongConnSvr.WebsocketMaxConnNum)), + WithHandshakeTimeout(time.Duration(conf.MsgGateway.LongConnSvr.WebsocketTimeout)*time.Second), + WithMessageMaxMsgLength(conf.MsgGateway.LongConnSvr.WebsocketMaxMsgLen), ) if err != nil { return err @@ -39,7 +60,7 @@ func RunWsAndServer(conf *config.GlobalConfig, rpcPort, wsPort, prometheusPort i hubServer := NewServer(rpcPort, prometheusPort, longServer, conf) netDone := make(chan error) go func() { - err = hubServer.Start(conf) + err = hubServer.Start(ctx, index, conf) netDone <- err }() return hubServer.LongConnServer.Run(netDone) diff --git a/internal/msggateway/long_conn.go b/internal/msggateway/long_conn.go index 7dc79c834..7d5bef4c3 100644 --- a/internal/msggateway/long_conn.go +++ b/internal/msggateway/long_conn.go @@ -15,12 +15,13 @@ package msggateway import ( - "errors" + "encoding/json" + "github.com/openimsdk/tools/apiresp" "net/http" "time" - "github.com/OpenIMSDK/tools/errs" "github.com/gorilla/websocket" + "github.com/openimsdk/tools/errs" ) type LongConn interface { @@ -75,7 +76,7 @@ func (d *GWebSocket) GenerateLongConn(w http.ResponseWriter, r *http.Request) er conn, err := upgrader.Upgrade(w, r, nil) if err != nil { // The upgrader.Upgrade method usually returns enough error messages to diagnose problems that may occur during the upgrade - return errs.Wrap(err, "GenerateLongConn: WebSocket upgrade failed") + return errs.WrapMsg(err, "GenerateLongConn: WebSocket upgrade failed") } d.conn = conn return nil @@ -86,7 +87,7 @@ func (d *GWebSocket) WriteMessage(messageType int, message []byte) error { return d.conn.WriteMessage(messageType, message) } -//func (d *GWebSocket) setSendConn(sendConn *websocket.Conn) { +// func (d *GWebSocket) setSendConn(sendConn *websocket.Conn) { // d.sendConn = sendConn //} @@ -99,24 +100,24 @@ func (d *GWebSocket) SetReadDeadline(timeout time.Duration) error { } func (d *GWebSocket) SetWriteDeadline(timeout time.Duration) error { - // TODO add error if timeout <= 0 { - return errs.Wrap(errors.New("timeout must be greater than 0")) + return errs.New("timeout must be greater than 0") } // TODO SetWriteDeadline Future add error handling if err := d.conn.SetWriteDeadline(time.Now().Add(timeout)); err != nil { - return errs.Wrap(err, "GWebSocket.SetWriteDeadline failed") + return errs.WrapMsg(err, "GWebSocket.SetWriteDeadline failed") } return nil } func (d *GWebSocket) Dial(urlStr string, requestHeader http.Header) (*http.Response, error) { conn, httpResp, err := websocket.DefaultDialer.Dial(urlStr, requestHeader) - if err == nil { - d.conn = conn + if err != nil { + return httpResp, errs.WrapMsg(err, "GWebSocket.Dial failed", "url", urlStr) } - return httpResp, err + d.conn = conn + return httpResp, nil } func (d *GWebSocket) IsNil() bool { @@ -144,6 +145,34 @@ func (d *GWebSocket) SetPingHandler(handler PingPongHandler) { d.conn.SetPingHandler(handler) } -//func (d *GWebSocket) CheckSendConnDiffNow() bool { -// return d.conn == d.sendConn -//} +func (d *GWebSocket) RespondWithError(err error, w http.ResponseWriter, r *http.Request) error { + if err := d.GenerateLongConn(w, r); err != nil { + return err + } + data, err := json.Marshal(apiresp.ParseError(err)) + if err != nil { + _ = d.Close() + return errs.WrapMsg(err, "json marshal failed") + } + + if err := d.WriteMessage(MessageText, data); err != nil { + _ = d.Close() + return errs.WrapMsg(err, "WriteMessage failed") + } + _ = d.Close() + return nil +} + +func (d *GWebSocket) RespondWithSuccess() error { + data, err := json.Marshal(apiresp.ParseError(nil)) + if err != nil { + _ = d.Close() + return errs.WrapMsg(err, "json marshal failed") + } + + if err := d.WriteMessage(MessageText, data); err != nil { + _ = d.Close() + return errs.WrapMsg(err, "WriteMessage failed") + } + return nil +} diff --git a/internal/msggateway/message_handler.go b/internal/msggateway/message_handler.go index 2fbdd5683..8a11e6ab3 100644 --- a/internal/msggateway/message_handler.go +++ b/internal/msggateway/message_handler.go @@ -18,15 +18,15 @@ import ( "context" "sync" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/push" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/utils" "github.com/go-playground/validator/v10" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/push" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/utils/jsonutil" "google.golang.org/protobuf/proto" ) @@ -46,7 +46,7 @@ func (r *Req) String() string { tReq.SendID = r.SendID tReq.OperationID = r.OperationID tReq.MsgIncr = r.MsgIncr - return utils.StructToJsonString(tReq) + return jsonutil.StructToJsonString(tReq) } var reqPool = sync.Pool{ @@ -86,7 +86,7 @@ func (r *Resp) String() string { tResp.OperationID = r.OperationID tResp.ErrCode = r.ErrCode tResp.ErrMsg = r.ErrMsg - return utils.StructToJsonString(tResp) + return jsonutil.StructToJsonString(tResp) } type MessageHandler interface { @@ -106,30 +106,30 @@ type GrpcHandler struct { validate *validator.Validate } -func NewGrpcHandler(validate *validator.Validate, client discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) *GrpcHandler { - msgRpcClient := rpcclient.NewMessageRpcClient(client, config) - pushRpcClient := rpcclient.NewPushRpcClient(client, config) +func NewGrpcHandler(validate *validator.Validate, client discovery.SvcDiscoveryRegistry, rpcRegisterName *config.RpcRegisterName) *GrpcHandler { + msgRpcClient := rpcclient.NewMessageRpcClient(client, rpcRegisterName.Msg) + pushRpcClient := rpcclient.NewPushRpcClient(client, rpcRegisterName.Push) return &GrpcHandler{ msgRpcClient: &msgRpcClient, pushClient: &pushRpcClient, validate: validate, } } -func (g GrpcHandler) GetSeq(context context.Context, data *Req) ([]byte, error) { +func (g GrpcHandler) GetSeq(ctx context.Context, data *Req) ([]byte, error) { req := sdkws.GetMaxSeqReq{} if err := proto.Unmarshal(data.Data, &req); err != nil { - return nil, errs.Wrap(err, "GetSeq: error unmarshaling request") + return nil, errs.WrapMsg(err, "GetSeq: error unmarshaling request", "action", "unmarshal", "dataType", "GetMaxSeqReq") } if err := g.validate.Struct(&req); err != nil { - return nil, errs.Wrap(err, "GetSeq: validation failed") + return nil, errs.WrapMsg(err, "GetSeq: validation failed", "action", "validate", "dataType", "GetMaxSeqReq") } - resp, err := g.msgRpcClient.GetMaxSeq(context, &req) + resp, err := g.msgRpcClient.GetMaxSeq(ctx, &req) if err != nil { return nil, err } c, err := proto.Marshal(resp) if err != nil { - return nil, errs.Wrap(err, "GetSeq: error marshaling response") + return nil, errs.WrapMsg(err, "GetSeq: error marshaling response", "action", "marshal", "dataType", "GetMaxSeqResp") } return c, nil } @@ -137,19 +137,16 @@ func (g GrpcHandler) GetSeq(context context.Context, data *Req) ([]byte, error) // SendMessage handles the sending of messages through gRPC. It unmarshals the request data, // validates the message, and then sends it using the message RPC client. func (g GrpcHandler) SendMessage(ctx context.Context, data *Req) ([]byte, error) { - // Unmarshal the message data from the request. var msgData sdkws.MsgData if err := proto.Unmarshal(data.Data, &msgData); err != nil { - return nil, errs.Wrap(err, "error unmarshalling message data") + return nil, errs.WrapMsg(err, "SendMessage: error unmarshaling message data", "action", "unmarshal", "dataType", "MsgData") } - // Validate the message data structure. if err := g.validate.Struct(&msgData); err != nil { - return nil, errs.Wrap(err, "message data validation failed") + return nil, errs.WrapMsg(err, "SendMessage: message data validation failed", "action", "validate", "dataType", "MsgData") } req := msg.SendMsgReq{MsgData: &msgData} - resp, err := g.msgRpcClient.SendMsg(ctx, &req) if err != nil { return nil, err @@ -157,7 +154,7 @@ func (g GrpcHandler) SendMessage(ctx context.Context, data *Req) ([]byte, error) c, err := proto.Marshal(resp) if err != nil { - return nil, errs.Wrap(err, "error marshaling response") + return nil, errs.WrapMsg(err, "SendMessage: error marshaling response", "action", "marshal", "dataType", "SendMsgResp") } return c, nil @@ -170,7 +167,7 @@ func (g GrpcHandler) SendSignalMessage(context context.Context, data *Req) ([]by } c, err := proto.Marshal(resp) if err != nil { - return nil, errs.Wrap(err, "error marshaling response") + return nil, errs.WrapMsg(err, "error marshaling response", "action", "marshal", "dataType", "SendMsgResp") } return c, nil } @@ -178,10 +175,10 @@ func (g GrpcHandler) SendSignalMessage(context context.Context, data *Req) ([]by func (g GrpcHandler) PullMessageBySeqList(context context.Context, data *Req) ([]byte, error) { req := sdkws.PullMessageBySeqsReq{} if err := proto.Unmarshal(data.Data, &req); err != nil { - return nil, errs.Wrap(err, "error unmarshaling request") + return nil, errs.WrapMsg(err, "error unmarshaling request", "action", "unmarshal", "dataType", "PullMessageBySeqsReq") } if err := g.validate.Struct(data); err != nil { - return nil, errs.Wrap(err, "validation failed") + return nil, errs.WrapMsg(err, "validation failed", "action", "validate", "dataType", "PullMessageBySeqsReq") } resp, err := g.msgRpcClient.PullMessageBySeqList(context, &req) if err != nil { @@ -189,7 +186,7 @@ func (g GrpcHandler) PullMessageBySeqList(context context.Context, data *Req) ([ } c, err := proto.Marshal(resp) if err != nil { - return nil, errs.Wrap(err, "error marshaling response") + return nil, errs.WrapMsg(err, "error marshaling response", "action", "marshal", "dataType", "PullMessageBySeqsResp") } return c, nil } @@ -197,7 +194,7 @@ func (g GrpcHandler) PullMessageBySeqList(context context.Context, data *Req) ([ func (g GrpcHandler) UserLogout(context context.Context, data *Req) ([]byte, error) { req := push.DelUserPushTokenReq{} if err := proto.Unmarshal(data.Data, &req); err != nil { - return nil, errs.Wrap(err, "error unmarshaling request") + return nil, errs.WrapMsg(err, "error unmarshaling request", "action", "unmarshal", "dataType", "DelUserPushTokenReq") } resp, err := g.pushClient.DelUserPushToken(context, &req) if err != nil { @@ -205,7 +202,7 @@ func (g GrpcHandler) UserLogout(context context.Context, data *Req) ([]byte, err } c, err := proto.Marshal(resp) if err != nil { - return nil, errs.Wrap(err, "error marshaling response") + return nil, errs.WrapMsg(err, "error marshaling response", "action", "marshal", "dataType", "DelUserPushTokenResp") } return c, nil } @@ -213,31 +210,10 @@ func (g GrpcHandler) UserLogout(context context.Context, data *Req) ([]byte, err func (g GrpcHandler) SetUserDeviceBackground(_ context.Context, data *Req) ([]byte, bool, error) { req := sdkws.SetAppBackgroundStatusReq{} if err := proto.Unmarshal(data.Data, &req); err != nil { - return nil, false, errs.Wrap(err, "error unmarshaling request") + return nil, false, errs.WrapMsg(err, "error unmarshaling request", "action", "unmarshal", "dataType", "SetAppBackgroundStatusReq") } if err := g.validate.Struct(data); err != nil { - return nil, false, errs.Wrap(err, "validation failed") + return nil, false, errs.WrapMsg(err, "validation failed", "action", "validate", "dataType", "SetAppBackgroundStatusReq") } return nil, req.IsBackground, nil } - -// func (g GrpcHandler) call[T any](ctx context.Context, data Req, m proto.Message, rpc func(ctx context.Context, req -// proto.Message)) ([]byte, error) { -// if err := proto.Unmarshal(data.Data, m); err != nil { -// return nil, err -// } -// if err := g.validate.Struct(m); err != nil { -// return nil, err -// } -// rpc(ctx, m) -// req := msg.SendMsgReq{MsgData: &msgData} -// resp, err := g.notification.Msg.SendMsg(context, &req) -// if err != nil { -// return nil, err -// } -// c, err := proto.Marshal(resp) -// if err != nil { -// return nil, err -// } -// return c, nil -//} diff --git a/internal/msggateway/n_ws_server.go b/internal/msggateway/n_ws_server.go index f5838c703..cf607d470 100644 --- a/internal/msggateway/n_ws_server.go +++ b/internal/msggateway/n_ws_server.go @@ -16,29 +16,25 @@ package msggateway import ( "context" - "encoding/json" - "errors" "fmt" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" + pbAuth "github.com/openimsdk/protocol/auth" + "github.com/openimsdk/tools/mcontext" "net/http" - "strconv" "sync" "sync/atomic" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msggateway" - "github.com/OpenIMSDK/tools/apiresp" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" "github.com/go-playground/validator/v10" - "github.com/openimsdk/open-im-server/v3/pkg/authverify" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" - "github.com/redis/go-redis/v9" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msggateway" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/stringutil" "golang.org/x/sync/errgroup" ) @@ -48,8 +44,7 @@ type LongConnServer interface { GetUserAllCons(userID string) ([]*Client, bool) GetUserPlatformCons(userID string, platform int) ([]*Client, bool, bool) Validate(s any) error - SetCacheHandler(cache cache.MsgModel) - SetDiscoveryRegistry(client discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) + SetDiscoveryRegistry(client discovery.SvcDiscoveryRegistry, config *Config) KickUserConn(client *Client) error UnRegister(c *Client) SetKickHandlerInfo(i *kickHandler) @@ -58,15 +53,8 @@ type LongConnServer interface { MessageHandler } -// bufferPool is unused -// var bufferPool = sync.Pool{ -// New: func() any { -// return make([]byte, 1024) -// }, -// } - type WsServer struct { - globalConfig *config.GlobalConfig + msgGatewayConfig *Config port int wsMaxConnNum int64 registerChan chan *Client @@ -79,12 +67,13 @@ type WsServer struct { handshakeTimeout time.Duration writeBufferSize int validate *validator.Validate - cache cache.MsgModel userClient *rpcclient.UserRpcClient - disCov discoveryregistry.SvcDiscoveryRegistry + authClient *rpcclient.Auth + disCov discovery.SvcDiscoveryRegistry Compressor Encoder MessageHandler + webhookClient *webhook.Client } type kickHandler struct { @@ -93,9 +82,10 @@ type kickHandler struct { newClient *Client } -func (ws *WsServer) SetDiscoveryRegistry(disCov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) { - ws.MessageHandler = NewGrpcHandler(ws.validate, disCov, config) - u := rpcclient.NewUserRpcClient(disCov, config) +func (ws *WsServer) SetDiscoveryRegistry(disCov discovery.SvcDiscoveryRegistry, config *Config) { + ws.MessageHandler = NewGrpcHandler(ws.validate, disCov, &config.Share.RpcRegisterName) + u := rpcclient.NewUserRpcClient(disCov, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID) + ws.authClient = rpcclient.NewAuth(disCov, config.Share.RpcRegisterName.Auth) ws.userClient = &u ws.disCov = disCov } @@ -107,30 +97,17 @@ func (ws *WsServer) SetUserOnlineStatus(ctx context.Context, client *Client, sta } switch status { case constant.Online: - err := CallbackUserOnline(ctx, ws.globalConfig, client.UserID, client.PlatformID, client.IsBackground, client.ctx.GetConnID()) - if err != nil { - log.ZWarn(ctx, "CallbackUserOnline err", err) - } + ws.webhookAfterUserOnline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOnline, client.UserID, client.PlatformID, client.IsBackground, client.ctx.GetConnID()) case constant.Offline: - err := CallbackUserOffline(ctx, ws.globalConfig, client.UserID, client.PlatformID, client.ctx.GetConnID()) - if err != nil { - log.ZWarn(ctx, "CallbackUserOffline err", err) - } + ws.webhookAfterUserOffline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOffline, client.UserID, client.PlatformID, client.ctx.GetConnID()) } } -func (ws *WsServer) SetCacheHandler(cache cache.MsgModel) { - ws.cache = cache -} - func (ws *WsServer) UnRegister(c *Client) { ws.unregisterChan <- c } -func (ws *WsServer) Validate(s any) error { - if s == nil { - return errs.Wrap(errors.New("input cannot be nil")) - } +func (ws *WsServer) Validate(_ any) error { return nil } @@ -142,14 +119,14 @@ func (ws *WsServer) GetUserPlatformCons(userID string, platform int) ([]*Client, return ws.clients.Get(userID, platform) } -func NewWsServer(globalConfig *config.GlobalConfig, opts ...Option) (*WsServer, error) { +func NewWsServer(msgGatewayConfig *Config, opts ...Option) (*WsServer, error) { var config configs for _, o := range opts { o(&config) } v := validator.New() return &WsServer{ - globalConfig: globalConfig, + msgGatewayConfig: msgGatewayConfig, port: config.port, wsMaxConnNum: config.maxConnNum, writeBufferSize: config.writeBufferSize, @@ -166,6 +143,7 @@ func NewWsServer(globalConfig *config.GlobalConfig, opts ...Option) (*WsServer, clients: newUserMap(), Compressor: NewGzipCompressor(), Encoder: NewGobEncoder(), + webhookClient: webhook.NewWebhookClient(msgGatewayConfig.WebhooksConfig.URL), }, nil } @@ -176,7 +154,7 @@ func (ws *WsServer) Run(done chan error) error { shutdownDone = make(chan struct{}, 1) ) - server := http.Server{Addr: ":" + utils.IntToString(ws.port), Handler: nil} + server := http.Server{Addr: ":" + stringutil.IntToString(ws.port), Handler: nil} go func() { for { @@ -196,9 +174,9 @@ func (ws *WsServer) Run(done chan error) error { go func() { http.HandleFunc("/", ws.wsHandler) err := server.ListenAndServe() + defer close(netDone) if err != nil && err != http.ErrServerClosed { - netErr = errs.Wrap(err, "ws start err", server.Addr) - close(netDone) + netErr = errs.WrapMsg(err, "ws start err", server.Addr) } }() ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) @@ -208,7 +186,7 @@ func (ws *WsServer) Run(done chan error) error { case err = <-done: sErr := server.Shutdown(ctx) if sErr != nil { - return errs.Wrap(sErr, "shutdown err") + return errs.WrapMsg(sErr, "shutdown err") } close(shutdownDone) if err != nil { @@ -223,7 +201,7 @@ func (ws *WsServer) Run(done chan error) error { var concurrentRequest = 3 func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *Client) error { - conns, err := ws.disCov.GetConns(ctx, ws.globalConfig.RpcRegisterName.OpenImMessageGatewayName) + conns, err := ws.disCov.GetConns(ctx, ws.msgGatewayConfig.Share.RpcRegisterName.MessageGateway) if err != nil { return err } @@ -279,7 +257,8 @@ func (ws *WsServer) registerClient(client *Client) { if clientOK { ws.clients.Set(client.UserID, client) // There is already a connection to the platform - log.ZInfo(client.ctx, "repeat login", "userID", client.UserID, "platformID", client.PlatformID, "old remote addr", getRemoteAdders(oldClients)) + log.ZInfo(client.ctx, "repeat login", "userID", client.UserID, "platformID", + client.PlatformID, "old remote addr", getRemoteAdders(oldClients)) ws.onlineUserConnNum.Add(1) } else { ws.clients.Set(client.UserID, client) @@ -288,13 +267,14 @@ func (ws *WsServer) registerClient(client *Client) { } wg := sync.WaitGroup{} - if ws.globalConfig.Envs.Discovery == "zookeeper" { + if ws.msgGatewayConfig.Share.Env == "zookeeper" { wg.Add(1) go func() { defer wg.Done() _ = ws.sendUserOnlineInfoToOtherNode(client.ctx, client) }() } + wg.Add(1) go func() { defer wg.Done() @@ -331,7 +311,7 @@ func (ws *WsServer) KickUserConn(client *Client) error { } func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Client, newClient *Client) { - switch ws.globalConfig.MultiLoginPolicy { + switch ws.msgGatewayConfig.MsgGateway.MultiLoginPolicy { case constant.DefalutNotKick: case constant.PCAndOther: if constant.PlatformIDToClass(newClient.PlatformID) == constant.TerminalPC { @@ -349,57 +329,13 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien log.ZWarn(c.ctx, "KickOnlineMessage", err) } } - m, err := ws.cache.GetTokensWithoutError( - newClient.ctx, - newClient.UserID, - newClient.PlatformID, - ) - if err != nil && err != redis.Nil { - log.ZWarn( - newClient.ctx, - "get token from redis err", - err, - "userID", - newClient.UserID, - "platformID", - newClient.PlatformID, - ) - return - } - if m == nil { - log.ZWarn( - newClient.ctx, - "m is nil", - errors.New("m is nil"), - "userID", - newClient.UserID, - "platformID", - newClient.PlatformID, - ) - return - } - log.ZDebug( - newClient.ctx, - "get token from redis", - "userID", - newClient.UserID, - "platformID", - newClient.PlatformID, - "tokenMap", - m, + ctx := mcontext.WithMustInfoCtx( + []string{newClient.ctx.GetOperationID(), newClient.ctx.GetUserID(), + constant.PlatformIDToName(newClient.PlatformID), newClient.ctx.GetConnID()}, ) - - for k := range m { - if k != newClient.ctx.GetToken() { - m[k] = constant.KickedToken - } - } - log.ZDebug(newClient.ctx, "set token map is ", "token map", m, "userID", - newClient.UserID, "token", newClient.ctx.GetToken()) - err = ws.cache.SetTokenMapByUidPid(newClient.ctx, newClient.UserID, newClient.PlatformID, m) - if err != nil { - log.ZWarn(newClient.ctx, "SetTokenMapByUidPid err", err, "userID", newClient.UserID, "platformID", newClient.PlatformID) - return + if _, err := ws.authClient.InvalidateToken(ctx, newClient.token, newClient.UserID, newClient.PlatformID); err != nil { + log.ZWarn(newClient.ctx, "InvalidateToken err", err, "userID", newClient.UserID, + "platformID", newClient.PlatformID) } } } @@ -413,107 +349,94 @@ func (ws *WsServer) unregisterClient(client *Client) { } ws.onlineUserConnNum.Add(-1) ws.SetUserOnlineStatus(client.ctx, client, constant.Offline) - log.ZInfo(client.ctx, "user offline", "close reason", client.closedErr, "online user Num", ws.onlineUserNum.Load(), "online user conn Num", + log.ZInfo(client.ctx, "user offline", "close reason", client.closedErr, "online user Num", + ws.onlineUserNum.Load(), "online user conn Num", ws.onlineUserConnNum.Load(), ) } -func (ws *WsServer) ParseWSArgs(r *http.Request) (args *WSArgs, err error) { - var v WSArgs - defer func() { - args = &v - }() - query := r.URL.Query() - v.MsgResp, _ = strconv.ParseBool(query.Get(MsgResp)) - if ws.onlineUserConnNum.Load() >= ws.wsMaxConnNum { - return nil, errs.ErrConnOverMaxNumLimit.Wrap("over max conn num limit") - } - if v.Token = query.Get(Token); v.Token == "" { - return nil, errs.ErrConnArgsErr.Wrap("token is empty") +// validateRespWithRequest checks if the response matches the expected userID and platformID. +func (ws *WsServer) validateRespWithRequest(ctx *UserConnContext, resp *pbAuth.ParseTokenResp) error { + userID := ctx.GetUserID() + platformID := stringutil.StringToInt32(ctx.GetPlatformID()) + if resp.UserID != userID { + return servererrs.ErrTokenInvalid.WrapMsg(fmt.Sprintf("token uid %s != userID %s", resp.UserID, userID)) } - if v.UserID = query.Get(WsUserID); v.UserID == "" { - return nil, errs.ErrConnArgsErr.Wrap("sendID is empty") + if resp.PlatformID != platformID { + return servererrs.ErrTokenInvalid.WrapMsg(fmt.Sprintf("token platform %d != platformID %d", resp.PlatformID, platformID)) } - platformIDStr := query.Get(PlatformID) - if platformIDStr == "" { - return nil, errs.ErrConnArgsErr.Wrap("platformID is empty") + return nil +} + +func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) { + // Create a new connection context + connContext := newContext(w, r) + + // Check if the current number of online user connections exceeds the maximum limit + if ws.onlineUserConnNum.Load() >= ws.wsMaxConnNum { + // If it exceeds the maximum connection number, return an error via HTTP and stop processing + httpError(connContext, servererrs.ErrConnOverMaxNumLimit.WrapMsg("over max conn num limit")) + return } - platformID, err := strconv.Atoi(platformIDStr) + + // Parse essential arguments (e.g., user ID, Token) + err := connContext.ParseEssentialArgs() if err != nil { - return nil, errs.ErrConnArgsErr.Wrap("platformID is not int") - } - v.PlatformID = platformID - if err = authverify.WsVerifyToken(v.Token, v.UserID, ws.globalConfig.Secret, platformID); err != nil { - return nil, err - } - if query.Get(Compression) == GzipCompressionProtocol { - v.Compression = true - } - if r.Header.Get(Compression) == GzipCompressionProtocol { - v.Compression = true + // If there's an error during parsing, return an error via HTTP and stop processing + + httpError(connContext, err) + return } - m, err := ws.cache.GetTokensWithoutError(context.Background(), v.UserID, platformID) + + // Call the authentication client to parse the Token obtained from the context + resp, err := ws.authClient.ParseToken(connContext, connContext.GetToken()) if err != nil { - return nil, err - } - if v, ok := m[v.Token]; ok { - switch v { - case constant.NormalToken: - case constant.KickedToken: - return nil, errs.ErrTokenKicked.Wrap() - default: - return nil, errs.ErrTokenUnknown.Wrap(fmt.Sprintf("token status is %d", v)) + // If there's an error parsing the Token, decide whether to send the error message via WebSocket based on the context flag + shouldSendError := connContext.ShouldSendResp() + if shouldSendError { + // Create a WebSocket connection object and attempt to send the error message via WebSocket + wsLongConn := newGWebSocket(WebSocket, ws.handshakeTimeout, ws.writeBufferSize) + if err := wsLongConn.RespondWithError(err, w, r); err == nil { + // If the error message is successfully sent via WebSocket, stop processing + return + } } - } else { - return nil, errs.ErrTokenNotExist.Wrap() + // If sending via WebSocket is not required or fails, return the error via HTTP and stop processing + httpError(connContext, err) + return } - return &v, nil -} -type WSArgs struct { - Token string - UserID string - PlatformID int - Compression bool - MsgResp bool -} + // Validate the authentication response matches the request (e.g., user ID and platform ID) + err = ws.validateRespWithRequest(connContext, resp) + if err != nil { + // If validation fails, return an error via HTTP and stop processing + httpError(connContext, err) + return + } -func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) { - connContext := newContext(w, r) - args, pErr := ws.ParseWSArgs(r) - var wsLongConn *GWebSocket - if args.MsgResp { - wsLongConn = newGWebSocket(WebSocket, ws.handshakeTimeout, ws.writeBufferSize) - if err := wsLongConn.GenerateLongConn(w, r); err != nil { - httpError(connContext, err) - return - } - data, err := json.Marshal(apiresp.ParseError(pErr)) - if err != nil { - _ = wsLongConn.Close() - return - } - if err := wsLongConn.WriteMessage(MessageText, data); err != nil { - _ = wsLongConn.Close() - return - } - if pErr != nil { - _ = wsLongConn.Close() - return - } + // Create a WebSocket long connection object + wsLongConn := newGWebSocket(WebSocket, ws.handshakeTimeout, ws.writeBufferSize) + if err := wsLongConn.GenerateLongConn(w, r); err != nil { + //If the creation of the long connection fails, the error is handled internally during the handshake process. + log.ZWarn(connContext, "long connection fails", err) + return } else { - if pErr != nil { - httpError(connContext, pErr) - return - } - wsLongConn = newGWebSocket(WebSocket, ws.handshakeTimeout, ws.writeBufferSize) - if err := wsLongConn.GenerateLongConn(w, r); err != nil { - httpError(connContext, err) - return + // Check if a normal response should be sent via WebSocket + shouldSendSuccessResp := connContext.ShouldSendResp() + if shouldSendSuccessResp { + // Attempt to send a success message through WebSocket + if err := wsLongConn.RespondWithSuccess(); err != nil { + // If the success message is successfully sent, end further processing + return + } } } + + // Retrieve a client object from the client pool, reset its state, and associate it with the current WebSocket long connection client := ws.clientPool.Get().(*Client) - client.ResetClient(connContext, wsLongConn, connContext.GetBackground(), args.Compression, ws, args.Token) + client.ResetClient(connContext, wsLongConn, ws) + + // Register the client with the server and start message processing ws.registerChan <- client go client.readMessage() } diff --git a/internal/msggateway/user_map.go b/internal/msggateway/user_map.go index 89eb20de6..79cc53d1b 100644 --- a/internal/msggateway/user_map.go +++ b/internal/msggateway/user_map.go @@ -18,7 +18,8 @@ import ( "context" "sync" - "github.com/OpenIMSDK/tools/log" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" ) type UserMap struct { @@ -54,6 +55,7 @@ func (u *UserMap) Get(key string, platformID int) ([]*Client, bool, bool) { return nil, userExisted, false } +// Set adds a client to the map. func (u *UserMap) Set(key string, v *Client) { allClients, existed := u.m.Load(key) if existed { @@ -63,6 +65,7 @@ func (u *UserMap) Set(key string, v *Client) { u.m.Store(key, oldClients) } else { log.ZDebug(context.Background(), "Set not existed", "user_id", key, "client_user_id", v.UserID) + var clients []*Client clients = append(clients, v) u.m.Store(key, clients) @@ -98,14 +101,10 @@ func (u *UserMap) delete(key string, connRemoteAddr string) (isDeleteUser bool) return false } -func (u *UserMap) deleteClients(key string, clientsToDelete []*Client) (isDeleteUser bool) { - // Convert the slice of clients to delete into a map for efficient lookup. - deleteMap := make(map[string]struct{}) - for _, client := range clientsToDelete { - deleteMap[client.ctx.GetRemoteAddr()] = struct{}{} - } - - // Load the current clients associated with the key. +func (u *UserMap) deleteClients(key string, clients []*Client) (isDeleteUser bool) { + m := datautil.SliceToMapAny(clients, func(c *Client) (string, struct{}) { + return c.ctx.GetRemoteAddr(), struct{}{} + }) allClients, existed := u.m.Load(key) if !existed { // If the key doesn't exist, return false. @@ -116,7 +115,7 @@ func (u *UserMap) deleteClients(key string, clientsToDelete []*Client) (isDelete oldClients := allClients.([]*Client) var remainingClients []*Client for _, client := range oldClients { - if _, shouldBeDeleted := deleteMap[client.ctx.GetRemoteAddr()]; !shouldBeDeleted { + if _, shouldBeDeleted := m[client.ctx.GetRemoteAddr()]; !shouldBeDeleted { remainingClients = append(remainingClients, client) } } diff --git a/internal/msgtransfer/init.go b/internal/msgtransfer/init.go index ee62db9c1..68d953e90 100644 --- a/internal/msgtransfer/init.go +++ b/internal/msgtransfer/init.go @@ -16,23 +16,26 @@ package msgtransfer import ( "context" - "errors" "fmt" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/redisutil" + "github.com/openimsdk/tools/utils/datautil" "net/http" "os" "os/signal" "syscall" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mw" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/mgo" kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mw" + "github.com/openimsdk/tools/system/program" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/promhttp" @@ -50,59 +53,62 @@ type MsgTransfer struct { // and handle the deletion notification message deleted subscriptions topic: msg_to_mongo ctx context.Context cancel context.CancelFunc - config *config.GlobalConfig } -func StartTransfer(config *config.GlobalConfig, prometheusPort int) error { - rdb, err := cache.NewRedis(config) - if err != nil { - return err - } +type Config struct { + MsgTransfer config.MsgTransfer + RedisConfig config.Redis + MongodbConfig config.Mongo + KafkaConfig config.Kafka + ZookeeperConfig config.ZooKeeper + Share config.Share + WebhooksConfig config.Webhooks +} - mongo, err := unrelation.NewMongo(config) +func Start(ctx context.Context, index int, config *Config) error { + log.CInfo(ctx, "MSG-TRANSFER server is initializing", "prometheusPorts", + config.MsgTransfer.Prometheus.Ports, "index", index) + mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) if err != nil { return err } - - if err = mongo.CreateMsgIndex(); err != nil { + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) + if err != nil { return err } - client, err := kdisc.NewDiscoveryRegister(config) + client, err := kdisc.NewDiscoveryRegister(&config.ZookeeperConfig, &config.Share) if err != nil { return err } - - if err := client.CreateRpcRootNodes(config.GetServiceNames()); err != nil { + client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) + //todo MsgCacheTimeout + msgModel := cache.NewMsgCache(rdb, config.RedisConfig.EnablePipeline) + seqModel := cache.NewSeqCache(rdb) + msgDocModel, err := mgo.NewMsgMongo(mgocli.GetDB()) + if err != nil { return err } - - client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) - msgModel := cache.NewMsgCacheModel(rdb, config) - msgDocModel := unrelation.NewMsgMongoDriver(mongo.GetDatabase(config.Mongo.Database)) - msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, config) + msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqModel, &config.KafkaConfig) if err != nil { return err } - conversationRpcClient := rpcclient.NewConversationRpcClient(client, config) - groupRpcClient := rpcclient.NewGroupRpcClient(client, config) - msgTransfer, err := NewMsgTransfer(config, msgDatabase, &conversationRpcClient, &groupRpcClient) + conversationRpcClient := rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation) + groupRpcClient := rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group) + msgTransfer, err := NewMsgTransfer(&config.KafkaConfig, msgDatabase, &conversationRpcClient, &groupRpcClient) if err != nil { return err } - return msgTransfer.Start(prometheusPort, config) + return msgTransfer.Start(index, config) } -func NewMsgTransfer( - config *config.GlobalConfig, - msgDatabase controller.CommonMsgDatabase, - conversationRpcClient *rpcclient.ConversationRpcClient, - groupRpcClient *rpcclient.GroupRpcClient, -) (*MsgTransfer, error) { - historyCH, err := NewOnlineHistoryRedisConsumerHandler(config, msgDatabase, conversationRpcClient, groupRpcClient) +func NewMsgTransfer(kafkaConf *config.Kafka, msgDatabase controller.CommonMsgDatabase, + conversationRpcClient *rpcclient.ConversationRpcClient, groupRpcClient *rpcclient.GroupRpcClient) (*MsgTransfer, error) { + historyCH, err := NewOnlineHistoryRedisConsumerHandler(kafkaConf, msgDatabase, conversationRpcClient, groupRpcClient) if err != nil { return nil, err } - historyMongoCH, err := NewOnlineHistoryMongoConsumerHandler(config, msgDatabase) + historyMongoCH, err := NewOnlineHistoryMongoConsumerHandler(kafkaConf, msgDatabase) if err != nil { return nil, err } @@ -110,14 +116,13 @@ func NewMsgTransfer( return &MsgTransfer{ historyCH: historyCH, historyMongoCH: historyMongoCH, - config: config, }, nil } -func (m *MsgTransfer) Start(prometheusPort int, config *config.GlobalConfig) error { - fmt.Println("start msg transfer", "prometheusPort:", prometheusPort) - if prometheusPort <= 0 { - return errs.Wrap(errors.New("prometheusPort not correct")) +func (m *MsgTransfer) Start(index int, config *Config) error { + prometheusPort, err := datautil.GetElemByIndex(config.MsgTransfer.Prometheus.Ports, index) + if err != nil { + return err } m.ctx, m.cancel = context.WithCancel(context.Background()) @@ -129,17 +134,17 @@ func (m *MsgTransfer) Start(prometheusPort int, config *config.GlobalConfig) err go m.historyCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyCH) go m.historyMongoCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyMongoCH) - if config.Prometheus.Enable { + if config.MsgTransfer.Prometheus.Enable { go func() { proreg := prometheus.NewRegistry() proreg.MustRegister( collectors.NewGoCollector(), ) - proreg.MustRegister(prommetrics.GetGrpcCusMetrics("Transfer", config)...) + proreg.MustRegister(prommetrics.GetGrpcCusMetrics("Transfer", &config.Share)...) http.Handle("/metrics", promhttp.HandlerFor(proreg, promhttp.HandlerOpts{Registry: proreg})) err := http.ListenAndServe(fmt.Sprintf(":%d", prometheusPort), nil) if err != nil && err != http.ErrServerClosed { - netErr = errs.Wrap(err, fmt.Sprintf("prometheus start err: %d", prometheusPort)) + netErr = errs.WrapMsg(err, "prometheus start error", "prometheusPort", prometheusPort) netDone <- struct{}{} } }() @@ -149,7 +154,7 @@ func (m *MsgTransfer) Start(prometheusPort int, config *config.GlobalConfig) err signal.Notify(sigs, syscall.SIGTERM) select { case <-sigs: - util.SIGTERMExit() + program.SIGTERMExit() // graceful close kafka client. m.cancel() m.historyCH.historyConsumerGroup.Close() diff --git a/internal/msgtransfer/online_history_msg_handler.go b/internal/msgtransfer/online_history_msg_handler.go index 50fc93369..8691e92ab 100644 --- a/internal/msgtransfer/online_history_msg_handler.go +++ b/internal/msgtransfer/online_history_msg_handler.go @@ -23,18 +23,19 @@ import ( "time" "github.com/IBM/sarama" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" "github.com/go-redis/redis" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" - "github.com/openimsdk/open-im-server/v3/pkg/common/kafka" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/mq/kafka" + "github.com/openimsdk/tools/utils/idutil" + "github.com/openimsdk/tools/utils/stringutil" "google.golang.org/protobuf/proto" ) @@ -80,12 +81,12 @@ type OnlineHistoryRedisConsumerHandler struct { groupRpcClient *rpcclient.GroupRpcClient } -func NewOnlineHistoryRedisConsumerHandler( - config *config.GlobalConfig, - database controller.CommonMsgDatabase, - conversationRpcClient *rpcclient.ConversationRpcClient, - groupRpcClient *rpcclient.GroupRpcClient, -) (*OnlineHistoryRedisConsumerHandler, error) { +func NewOnlineHistoryRedisConsumerHandler(kafkaConf *config.Kafka, database controller.CommonMsgDatabase, + conversationRpcClient *rpcclient.ConversationRpcClient, groupRpcClient *rpcclient.GroupRpcClient) (*OnlineHistoryRedisConsumerHandler, error) { + historyConsumerGroup, err := kafka.NewMConsumerGroup(kafkaConf.Build(), kafkaConf.ToRedisGroupID, []string{kafkaConf.ToRedisTopic}) + if err != nil { + return nil, err + } var och OnlineHistoryRedisConsumerHandler och.msgDatabase = database och.msgDistributionCh = make(chan Cmd2Value) // no buffer channel @@ -96,32 +97,7 @@ func NewOnlineHistoryRedisConsumerHandler( } och.conversationRpcClient = conversationRpcClient och.groupRpcClient = groupRpcClient - var err error - - var tlsConfig *kafka.TLSConfig - if config.Kafka.TLS != nil { - tlsConfig = &kafka.TLSConfig{ - CACrt: config.Kafka.TLS.CACrt, - ClientCrt: config.Kafka.TLS.ClientCrt, - ClientKey: config.Kafka.TLS.ClientKey, - ClientKeyPwd: config.Kafka.TLS.ClientKeyPwd, - InsecureSkipVerify: false, - } - } - - och.historyConsumerGroup, err = kafka.NewMConsumerGroup(&kafka.MConsumerGroupConfig{ - KafkaVersion: sarama.V2_0_0_0, - OffsetsInitial: sarama.OffsetNewest, - IsReturnErr: false, - UserName: config.Kafka.Username, - Password: config.Kafka.Password, - }, []string{config.Kafka.LatestMsgToRedis.Topic}, - config.Kafka.Addr, - config.Kafka.ConsumerGroupID.MsgToRedis, - tlsConfig, - ) - // statistics.NewStatistics(&och.singleMsgSuccessCount, config.Config.ModuleName.MsgTransferName, fmt.Sprintf("%d - // second singleMsgCount insert to mongo", constant.StatisticsTimeInterval), constant.StatisticsTimeInterval) + och.historyConsumerGroup = historyConsumerGroup return &och, err } @@ -265,22 +241,13 @@ func (och *OnlineHistoryRedisConsumerHandler) handleNotification( } } -func (och *OnlineHistoryRedisConsumerHandler) toPushTopic( - ctx context.Context, - key, conversationID string, - msgs []*sdkws.MsgData, -) { +func (och *OnlineHistoryRedisConsumerHandler) toPushTopic(ctx context.Context, key, conversationID string, msgs []*sdkws.MsgData) { for _, v := range msgs { och.msgDatabase.MsgToPushMQ(ctx, key, conversationID, v) // nolint: errcheck - } } -func (och *OnlineHistoryRedisConsumerHandler) handleMsg( - ctx context.Context, - key, conversationID string, - storageList, notStorageList []*sdkws.MsgData, -) { +func (och *OnlineHistoryRedisConsumerHandler) handleMsg(ctx context.Context, key, conversationID string, storageList, notStorageList []*sdkws.MsgData) { och.toPushTopic(ctx, key, conversationID, notStorageList) if len(storageList) > 0 { lastSeq, isNewConversation, err := och.msgDatabase.BatchInsertChat2Cache(ctx, conversationID, storageList) @@ -290,7 +257,7 @@ func (och *OnlineHistoryRedisConsumerHandler) handleMsg( } if isNewConversation { switch storageList[0].SessionType { - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: log.ZInfo(ctx, "group chat first create conversation", "conversationID", conversationID) userIDs, err := och.groupRpcClient.GetGroupMemberIDs(ctx, storageList[0].GroupID) @@ -349,14 +316,8 @@ func (och *OnlineHistoryRedisConsumerHandler) MessagesDistributionHandle() { for i, header := range consumerMessages[i].Headers { arr = append(arr, strconv.Itoa(i), string(header.Key), string(header.Value)) } - log.ZInfo( - ctx, - "consumer.kafka.GetContextWithMQHeader", - "len", - len(consumerMessages[i].Headers), - "header", - strings.Join(arr, ", "), - ) + log.ZInfo(ctx, "consumer.kafka.GetContextWithMQHeader", "len", len(consumerMessages[i].Headers), + "header", strings.Join(arr, ", ")) ctxMsg.ctx = kafka.GetContextWithMQHeader(consumerMessages[i].Headers) ctxMsg.message = msgFromMQ log.ZDebug( @@ -381,7 +342,7 @@ func (och *OnlineHistoryRedisConsumerHandler) MessagesDistributionHandle() { log.ZDebug(ctx, "generate map list users len", "length", len(aggregationMsgs)) for uniqueKey, v := range aggregationMsgs { if len(v) >= 0 { - hashCode := utils.GetHashCode(uniqueKey) + hashCode := stringutil.GetHashCode(uniqueKey) channelID := hashCode % ChannelNum newCtx := withAggregationCtx(ctx, v) log.ZDebug( @@ -433,7 +394,7 @@ func (och *OnlineHistoryRedisConsumerHandler) ConsumeClaim( break } } - log.ZDebug(context.Background(), "online new session msg come", "highWaterMarkOffset", + log.ZInfo(context.Background(), "online new session msg come", "highWaterMarkOffset", claim.HighWaterMarkOffset(), "topic", claim.Topic(), "partition", claim.Partition()) var ( @@ -472,7 +433,7 @@ func (och *OnlineHistoryRedisConsumerHandler) ConsumeClaim( rwLock.Unlock() start := time.Now() - ctx := mcontext.WithTriggerIDContext(context.Background(), utils.OperationIDGenerator()) + ctx := mcontext.WithTriggerIDContext(context.Background(), idutil.OperationIDGenerator()) log.ZDebug(ctx, "timer trigger msg consumer start", "length", len(buffer)) for i := 0; i < len(buffer)/split; i++ { och.msgDistributionCh <- Cmd2Value{Cmd: ConsumerMsgs, Value: TriggerChannelValue{ diff --git a/internal/msgtransfer/online_msg_to_mongo_handler.go b/internal/msgtransfer/online_msg_to_mongo_handler.go index 045f82220..978302e76 100644 --- a/internal/msgtransfer/online_msg_to_mongo_handler.go +++ b/internal/msgtransfer/online_msg_to_mongo_handler.go @@ -18,42 +18,22 @@ import ( "context" "github.com/IBM/sarama" - pbmsg "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/tools/log" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" - kfk "github.com/openimsdk/open-im-server/v3/pkg/common/kafka" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" + pbmsg "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mq/kafka" "google.golang.org/protobuf/proto" ) type OnlineHistoryMongoConsumerHandler struct { - historyConsumerGroup *kfk.MConsumerGroup + historyConsumerGroup *kafka.MConsumerGroup msgDatabase controller.CommonMsgDatabase } -func NewOnlineHistoryMongoConsumerHandler(config *config.GlobalConfig, database controller.CommonMsgDatabase) (*OnlineHistoryMongoConsumerHandler, error) { - var tlsConfig *kfk.TLSConfig - if config.Kafka.TLS != nil { - tlsConfig = &kfk.TLSConfig{ - CACrt: config.Kafka.TLS.CACrt, - ClientCrt: config.Kafka.TLS.ClientCrt, - ClientKey: config.Kafka.TLS.ClientKey, - ClientKeyPwd: config.Kafka.TLS.ClientKeyPwd, - InsecureSkipVerify: false, - } - } - historyConsumerGroup, err := kfk.NewMConsumerGroup(&kfk.MConsumerGroupConfig{ - KafkaVersion: sarama.V2_0_0_0, - OffsetsInitial: sarama.OffsetNewest, - IsReturnErr: false, - UserName: config.Kafka.Username, - Password: config.Kafka.Password, - }, []string{config.Kafka.MsgToMongo.Topic}, - config.Kafka.Addr, - config.Kafka.ConsumerGroupID.MsgToMongo, - tlsConfig, - ) +func NewOnlineHistoryMongoConsumerHandler(kafkaConf *config.Kafka, database controller.CommonMsgDatabase) (*OnlineHistoryMongoConsumerHandler, error) { + historyConsumerGroup, err := kafka.NewMConsumerGroup(kafkaConf.Build(), kafkaConf.ToMongoGroupID, []string{kafkaConf.ToMongoTopic}) if err != nil { return nil, err } @@ -65,12 +45,7 @@ func NewOnlineHistoryMongoConsumerHandler(config *config.GlobalConfig, database return mc, nil } -func (mc *OnlineHistoryMongoConsumerHandler) handleChatWs2Mongo( - ctx context.Context, - cMsg *sarama.ConsumerMessage, - key string, - session sarama.ConsumerGroupSession, -) { +func (mc *OnlineHistoryMongoConsumerHandler) handleChatWs2Mongo(ctx context.Context, cMsg *sarama.ConsumerMessage, key string, session sarama.ConsumerGroupSession) { msg := cMsg.Value msgFromMQ := pbmsg.MsgDataToMongoByMQ{} err := proto.Unmarshal(msg, &msgFromMQ) diff --git a/internal/push/callback.go b/internal/push/callback.go index 6415d63d6..889729582 100644 --- a/internal/push/callback.go +++ b/internal/push/callback.go @@ -16,122 +16,135 @@ package push import ( "context" + "encoding/json" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/http" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/datautil" ) -func callbackOfflinePush( - ctx context.Context, - config *config.GlobalConfig, - userIDs []string, - msg *sdkws.MsgData, - offlinePushUserIDs *[]string, -) error { - if !config.Callback.CallbackOfflinePush.Enable || msg.ContentType == constant.Typing { - return nil - } - req := &callbackstruct.CallbackBeforePushReq{ - UserStatusBatchCallbackReq: callbackstruct.UserStatusBatchCallbackReq{ - UserStatusBaseCallback: callbackstruct.UserStatusBaseCallback{ - CallbackCommand: callbackstruct.CallbackOfflinePushCommand, - OperationID: mcontext.GetOperationID(ctx), - PlatformID: int(msg.SenderPlatformID), - Platform: constant.PlatformIDToName(int(msg.SenderPlatformID)), +func (c *ConsumerHandler) webhookBeforeOfflinePush(ctx context.Context, before *config.BeforeConfig, userIDs []string, msg *sdkws.MsgData, offlinePushUserIDs *[]string) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + if msg.ContentType == constant.Typing { + return nil + } + req := &callbackstruct.CallbackBeforePushReq{ + UserStatusBatchCallbackReq: callbackstruct.UserStatusBatchCallbackReq{ + UserStatusBaseCallback: callbackstruct.UserStatusBaseCallback{ + CallbackCommand: callbackstruct.CallbackBeforeOfflinePushCommand, + OperationID: mcontext.GetOperationID(ctx), + PlatformID: int(msg.SenderPlatformID), + Platform: constant.PlatformIDToName(int(msg.SenderPlatformID)), + }, + UserIDList: userIDs, }, - UserIDList: userIDs, - }, - OfflinePushInfo: msg.OfflinePushInfo, - ClientMsgID: msg.ClientMsgID, - SendID: msg.SendID, - GroupID: msg.GroupID, - ContentType: msg.ContentType, - SessionType: msg.SessionType, - AtUserIDs: msg.AtUserIDList, - Content: GetContent(msg), - } + OfflinePushInfo: msg.OfflinePushInfo, + ClientMsgID: msg.ClientMsgID, + SendID: msg.SendID, + GroupID: msg.GroupID, + ContentType: msg.ContentType, + SessionType: msg.SessionType, + AtUserIDs: msg.AtUserIDList, + Content: GetContent(msg), + } - resp := &callbackstruct.CallbackBeforePushResp{} - if err := http.CallBackPostReturn(ctx, config.Callback.CallbackUrl, req, resp, config.Callback.CallbackOfflinePush); err != nil { - return err - } + resp := &callbackstruct.CallbackBeforePushResp{} - if len(resp.UserIDs) != 0 { - *offlinePushUserIDs = resp.UserIDs - } - if resp.OfflinePushInfo != nil { - msg.OfflinePushInfo = resp.OfflinePushInfo - } - return nil -} + if err := c.webhookClient.SyncPost(ctx, req.GetCallbackCommand(), req, resp, before); err != nil { + return err + } -func callbackOnlinePush(ctx context.Context, config *config.GlobalConfig, userIDs []string, msg *sdkws.MsgData) error { - if !config.Callback.CallbackOnlinePush.Enable || utils.Contain(msg.SendID, userIDs...) || msg.ContentType == constant.Typing { + if len(resp.UserIDs) != 0 { + *offlinePushUserIDs = resp.UserIDs + } + if resp.OfflinePushInfo != nil { + msg.OfflinePushInfo = resp.OfflinePushInfo + } return nil - } - req := callbackstruct.CallbackBeforePushReq{ - UserStatusBatchCallbackReq: callbackstruct.UserStatusBatchCallbackReq{ - UserStatusBaseCallback: callbackstruct.UserStatusBaseCallback{ - CallbackCommand: callbackstruct.CallbackOnlinePushCommand, - OperationID: mcontext.GetOperationID(ctx), - PlatformID: int(msg.SenderPlatformID), - Platform: constant.PlatformIDToName(int(msg.SenderPlatformID)), + }) +} + +func (c *ConsumerHandler) webhookBeforeOnlinePush(ctx context.Context, before *config.BeforeConfig, userIDs []string, msg *sdkws.MsgData) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + if datautil.Contain(msg.SendID, userIDs...) || msg.ContentType == constant.Typing { + return nil + } + req := callbackstruct.CallbackBeforePushReq{ + UserStatusBatchCallbackReq: callbackstruct.UserStatusBatchCallbackReq{ + UserStatusBaseCallback: callbackstruct.UserStatusBaseCallback{ + CallbackCommand: callbackstruct.CallbackBeforeOnlinePushCommand, + OperationID: mcontext.GetOperationID(ctx), + PlatformID: int(msg.SenderPlatformID), + Platform: constant.PlatformIDToName(int(msg.SenderPlatformID)), + }, + UserIDList: userIDs, }, - UserIDList: userIDs, - }, - ClientMsgID: msg.ClientMsgID, - SendID: msg.SendID, - GroupID: msg.GroupID, - ContentType: msg.ContentType, - SessionType: msg.SessionType, - AtUserIDs: msg.AtUserIDList, - Content: GetContent(msg), - } - resp := &callbackstruct.CallbackBeforePushResp{} - if err := http.CallBackPostReturn(ctx, config.Callback.CallbackUrl, req, resp, config.Callback.CallbackOnlinePush); err != nil { - return err - } - return nil + ClientMsgID: msg.ClientMsgID, + SendID: msg.SendID, + GroupID: msg.GroupID, + ContentType: msg.ContentType, + SessionType: msg.SessionType, + AtUserIDs: msg.AtUserIDList, + Content: GetContent(msg), + } + resp := &callbackstruct.CallbackBeforePushResp{} + if err := c.webhookClient.SyncPost(ctx, req.GetCallbackCommand(), req, resp, before); err != nil { + return err + } + return nil + }) } -func callbackBeforeSuperGroupOnlinePush( +func (c *ConsumerHandler) webhookBeforeGroupOnlinePush( ctx context.Context, - config *config.GlobalConfig, + before *config.BeforeConfig, groupID string, msg *sdkws.MsgData, pushToUserIDs *[]string, ) error { - if !config.Callback.CallbackBeforeSuperGroupOnlinePush.Enable || msg.ContentType == constant.Typing { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + if msg.ContentType == constant.Typing { + return nil + } + req := callbackstruct.CallbackBeforeSuperGroupOnlinePushReq{ + UserStatusBaseCallback: callbackstruct.UserStatusBaseCallback{ + CallbackCommand: callbackstruct.CallbackBeforeGroupOnlinePushCommand, + OperationID: mcontext.GetOperationID(ctx), + PlatformID: int(msg.SenderPlatformID), + Platform: constant.PlatformIDToName(int(msg.SenderPlatformID)), + }, + ClientMsgID: msg.ClientMsgID, + SendID: msg.SendID, + GroupID: groupID, + ContentType: msg.ContentType, + SessionType: msg.SessionType, + AtUserIDs: msg.AtUserIDList, + Content: GetContent(msg), + Seq: msg.Seq, + } + resp := &callbackstruct.CallbackBeforeSuperGroupOnlinePushResp{} + if err := c.webhookClient.SyncPost(ctx, req.GetCallbackCommand(), req, resp, before); err != nil { + return err + } + if len(resp.UserIDs) != 0 { + *pushToUserIDs = resp.UserIDs + } return nil - } - req := callbackstruct.CallbackBeforeSuperGroupOnlinePushReq{ - UserStatusBaseCallback: callbackstruct.UserStatusBaseCallback{ - CallbackCommand: callbackstruct.CallbackSuperGroupOnlinePushCommand, - OperationID: mcontext.GetOperationID(ctx), - PlatformID: int(msg.SenderPlatformID), - Platform: constant.PlatformIDToName(int(msg.SenderPlatformID)), - }, - ClientMsgID: msg.ClientMsgID, - SendID: msg.SendID, - GroupID: groupID, - ContentType: msg.ContentType, - SessionType: msg.SessionType, - AtUserIDs: msg.AtUserIDList, - Content: GetContent(msg), - Seq: msg.Seq, - } - resp := &callbackstruct.CallbackBeforeSuperGroupOnlinePushResp{} - if err := http.CallBackPostReturn(ctx, config.Callback.CallbackUrl, req, resp, config.Callback.CallbackBeforeSuperGroupOnlinePush); err != nil { - return err - } + }) +} - if len(resp.UserIDs) != 0 { - *pushToUserIDs = resp.UserIDs +func GetContent(msg *sdkws.MsgData) string { + if msg.ContentType >= constant.NotificationBegin && msg.ContentType <= constant.NotificationEnd { + var notification sdkws.NotificationElem + if err := json.Unmarshal(msg.Content, ¬ification); err != nil { + return "" + } + return notification.Detail + } else { + return string(msg.Content) } - return nil } diff --git a/internal/push/consumer_init.go b/internal/push/consumer_init.go deleted file mode 100644 index 351b63f46..000000000 --- a/internal/push/consumer_init.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package push - -import ( - "context" - - "github.com/openimsdk/open-im-server/v3/pkg/common/config" -) - -type Consumer struct { - pushCh ConsumerHandler - // successCount is unused - // successCount uint64 -} - -func NewConsumer(config *config.GlobalConfig, pusher *Pusher) (*Consumer, error) { - c, err := NewConsumerHandler(config, pusher) - if err != nil { - return nil, err - } - return &Consumer{ - pushCh: *c, - }, nil -} - -func (c *Consumer) Start() { - go c.pushCh.pushConsumerGroup.RegisterHandleAndConsumer(context.Background(), &c.pushCh) -} diff --git a/internal/push/offlinepush/dummy/push.go b/internal/push/offlinepush/dummy/push.go index f147886d9..028e7edd3 100644 --- a/internal/push/offlinepush/dummy/push.go +++ b/internal/push/offlinepush/dummy/push.go @@ -16,8 +16,7 @@ package dummy import ( "context" - - "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" ) func NewClient() *Dummy { @@ -27,6 +26,6 @@ func NewClient() *Dummy { type Dummy struct { } -func (d *Dummy) Push(ctx context.Context, userIDs []string, title, content string, opts *offlinepush.Opts) error { +func (d *Dummy) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error { return nil } diff --git a/internal/push/offlinepush/fcm/push.go b/internal/push/offlinepush/fcm/push.go index ed65a5af6..34ad1c0d6 100644 --- a/internal/push/offlinepush/fcm/push.go +++ b/internal/push/offlinepush/fcm/push.go @@ -16,14 +16,15 @@ package fcm import ( "context" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" "path/filepath" firebase "firebase.google.com/go" "firebase.google.com/go/messaging" - "github.com/OpenIMSDK/protocol/constant" - "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/errs" "github.com/redis/go-redis/v9" "google.golang.org/api/option" ) @@ -34,29 +35,32 @@ var Terminal = []int{constant.IOSPlatformID, constant.AndroidPlatformID, constan type Fcm struct { fcmMsgCli *messaging.Client - cache cache.MsgModel + cache cache.ThirdCache } // NewClient initializes a new FCM client using the Firebase Admin SDK. // It requires the FCM service account credentials file located within the project's configuration directory. -func NewClient(globalConfig *config.GlobalConfig, cache cache.MsgModel) *Fcm { - projectRoot := config.GetProjectRoot() - credentialsFilePath := filepath.Join(projectRoot, "config", globalConfig.Push.Fcm.ServiceAccount) +func NewClient(pushConf *config.Push, cache cache.ThirdCache) (*Fcm, error) { + projectRoot, err := config.GetProjectRoot() + if err != nil { + return nil, err + } + credentialsFilePath := filepath.Join(projectRoot, "config", pushConf.FCM.ServiceAccount) opt := option.WithCredentialsFile(credentialsFilePath) fcmApp, err := firebase.NewApp(context.Background(), nil, opt) if err != nil { - return nil + return nil, errs.Wrap(err) } ctx := context.Background() fcmMsgClient, err := fcmApp.Messaging(ctx) if err != nil { - return nil + return nil, errs.Wrap(err) } - return &Fcm{fcmMsgCli: fcmMsgClient, cache: cache} + return &Fcm{fcmMsgCli: fcmMsgClient, cache: cache}, nil } -func (f *Fcm) Push(ctx context.Context, userIDs []string, title, content string, opts *offlinepush.Opts) error { +func (f *Fcm) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error { // accounts->registrationToken allTokens := make(map[string][]string, 0) for _, account := range userIDs { diff --git a/internal/push/offlinepush/getui/body.go b/internal/push/offlinepush/getui/body.go index 46479163f..a96ff4efc 100644 --- a/internal/push/offlinepush/getui/body.go +++ b/internal/push/offlinepush/getui/body.go @@ -133,13 +133,13 @@ type Payload struct { IsSignal bool `json:"isSignal"` } -func newPushReq(config *config.GlobalConfig, title, content string) PushReq { +func newPushReq(pushConf *config.Push, title, content string) PushReq { pushReq := PushReq{PushMessage: &PushMessage{Notification: &Notification{ Title: title, Body: content, ClickType: "startapp", - ChannelID: config.Push.GeTui.ChannelID, - ChannelName: config.Push.GeTui.ChannelName, + ChannelID: pushConf.GeTui.ChannelID, + ChannelName: pushConf.GeTui.ChannelName, }}} return pushReq } diff --git a/internal/push/offlinepush/getui/push.go b/internal/push/offlinepush/getui/push.go index 1a95727e5..8ecea3a62 100644 --- a/internal/push/offlinepush/getui/push.go +++ b/internal/push/offlinepush/getui/push.go @@ -18,25 +18,24 @@ import ( "context" "crypto/sha256" "encoding/hex" - "errors" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" "strconv" "sync" "time" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils/splitter" - "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" - http2 "github.com/openimsdk/open-im-server/v3/pkg/common/http" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/httputil" + "github.com/openimsdk/tools/utils/splitter" "github.com/redis/go-redis/v9" ) var ( - ErrTokenExpire = errors.New("token expire") - ErrUserIDEmpty = errors.New("userIDs is empty") + ErrTokenExpire = errs.New("token expire") + ErrUserIDEmpty = errs.New("userIDs is empty") ) const ( @@ -45,32 +44,34 @@ const ( taskURL = "/push/list/message" batchPushURL = "/push/list/alias" - // codes. + // Codes. tokenExpireCode = 10001 tokenExpireTime = 60 * 60 * 23 taskIDTTL = 1000 * 60 * 60 * 24 ) type Client struct { - cache cache.MsgModel + cache cache.ThirdCache tokenExpireTime int64 taskIDTTL int64 - config *config.GlobalConfig + pushConf *config.Push + httpClient *httputil.HTTPClient } -func NewClient(config *config.GlobalConfig, cache cache.MsgModel) *Client { +func NewClient(pushConf *config.Push, cache cache.ThirdCache) *Client { return &Client{cache: cache, tokenExpireTime: tokenExpireTime, taskIDTTL: taskIDTTL, - config: config, + pushConf: pushConf, + httpClient: httputil.NewHTTPClient(httputil.NewClientConfig()), } } -func (g *Client) Push(ctx context.Context, userIDs []string, title, content string, opts *offlinepush.Opts) error { +func (g *Client) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error { token, err := g.cache.GetGetuiToken(ctx) if err != nil { if errs.Unwrap(err) == redis.Nil { - log.ZInfo(ctx, "getui token not exist in redis") + log.ZDebug(ctx, "getui token not exist in redis") token, err = g.getTokenAndSave2Redis(ctx) if err != nil { return err @@ -79,7 +80,7 @@ func (g *Client) Push(ctx context.Context, userIDs []string, title, content stri return err } } - pushReq := newPushReq(g.config, title, content) + pushReq := newPushReq(g.pushConf, title, content) pushReq.setPushChannel(title, content) if len(userIDs) > 1 { maxNum := 999 @@ -114,13 +115,13 @@ func (g *Client) Push(ctx context.Context, userIDs []string, title, content stri func (g *Client) Auth(ctx context.Context, timeStamp int64) (token string, expireTime int64, err error) { h := sha256.New() h.Write( - []byte(g.config.Push.GeTui.AppKey + strconv.Itoa(int(timeStamp)) + g.config.Push.GeTui.MasterSecret), + []byte(g.pushConf.GeTui.AppKey + strconv.Itoa(int(timeStamp)) + g.pushConf.GeTui.MasterSecret), ) sign := hex.EncodeToString(h.Sum(nil)) reqAuth := AuthReq{ Sign: sign, Timestamp: strconv.Itoa(int(timeStamp)), - AppKey: g.config.Push.GeTui.AppKey, + AppKey: g.pushConf.GeTui.AppKey, } respAuth := AuthResp{} err = g.request(ctx, authURL, reqAuth, "", &respAuth) @@ -163,7 +164,7 @@ func (g *Client) request(ctx context.Context, url string, input any, token strin header := map[string]string{"token": token} resp := &Resp{} resp.Data = output - return g.postReturn(ctx, g.config.Push.GeTui.PushUrl+url, header, input, resp, 3) + return g.postReturn(ctx, g.pushConf.GeTui.PushUrl+url, header, input, resp, 3) } func (g *Client) postReturn( @@ -174,7 +175,7 @@ func (g *Client) postReturn( output RespI, timeout int, ) error { - err := http2.PostReturn(ctx, url, header, input, output, timeout) + err := g.httpClient.PostReturn(ctx, url, header, input, output, timeout) if err != nil { return err } diff --git a/internal/push/offlinepush/jpush/body/audience.go b/internal/push/offlinepush/jpush/body/audience.go index 43a7148b8..9db66ffe6 100644 --- a/internal/push/offlinepush/jpush/body/audience.go +++ b/internal/push/offlinepush/jpush/body/audience.go @@ -32,8 +32,8 @@ func (a *Audience) set(key string, v []string) { a.audience = make(map[string][]string) a.Object = a.audience } - //v, ok = this.audience[key] - //if ok { + // v, ok = this.audience[key] + // if ok { // return //} a.audience[key] = v diff --git a/internal/push/offlinepush/jpush/body/notification.go b/internal/push/offlinepush/jpush/body/notification.go index b25882ea5..42e59c46c 100644 --- a/internal/push/offlinepush/jpush/body/notification.go +++ b/internal/push/offlinepush/jpush/body/notification.go @@ -56,8 +56,8 @@ func (n *Notification) SetExtras(extras Extras) { n.Android.Extras = extras } -func (n *Notification) SetAndroidIntent(config *config.GlobalConfig) { - n.Android.Intent.URL = config.Push.Jpns.PushIntent +func (n *Notification) SetAndroidIntent(pushConf *config.Push) { + n.Android.Intent.URL = pushConf.JPNS.PushIntent } func (n *Notification) IOSEnableMutableContent() { diff --git a/internal/push/offlinepush/jpush/body/platform.go b/internal/push/offlinepush/jpush/body/platform.go index 1ef136f2c..5654165fa 100644 --- a/internal/push/offlinepush/jpush/body/platform.go +++ b/internal/push/offlinepush/jpush/body/platform.go @@ -15,9 +15,9 @@ package body import ( - "errors" + "github.com/openimsdk/tools/errs" - "github.com/OpenIMSDK/protocol/constant" + "github.com/openimsdk/protocol/constant" ) const ( @@ -39,7 +39,7 @@ func (p *Platform) Set(os string) error { } else { switch p.Os.(type) { case string: - return errors.New("platform is all") + return errs.New("platform is all") default: } } @@ -61,7 +61,7 @@ func (p *Platform) Set(os string) error { p.osArry = append(p.osArry, os) p.Os = p.osArry default: - return errors.New("unknow platform") + return errs.New("unknow platform") } return nil @@ -74,7 +74,7 @@ func (p *Platform) SetPlatform(platform string) error { case constant.IOSPlatformStr: return p.SetIOS() default: - return errors.New("platform err") + return errs.New("platform err") } } diff --git a/internal/push/offlinepush/jpush/push.go b/internal/push/offlinepush/jpush/push.go index 2ced4bfd3..dac52597f 100644 --- a/internal/push/offlinepush/jpush/push.go +++ b/internal/push/offlinepush/jpush/push.go @@ -18,19 +18,22 @@ import ( "context" "encoding/base64" "fmt" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" - "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush" "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/jpush/body" "github.com/openimsdk/open-im-server/v3/pkg/common/config" - http2 "github.com/openimsdk/open-im-server/v3/pkg/common/http" + "github.com/openimsdk/tools/utils/httputil" ) type JPush struct { - config *config.GlobalConfig + pushConf *config.Push + httpClient *httputil.HTTPClient } -func NewClient(config *config.GlobalConfig) *JPush { - return &JPush{config: config} +func NewClient(pushConf *config.Push) *JPush { + return &JPush{pushConf: pushConf, + httpClient: httputil.NewHTTPClient(httputil.NewClientConfig()), + } } func (j *JPush) Auth(apiKey, secretKey string, timeStamp int64) (token string, err error) { @@ -48,7 +51,7 @@ func (j *JPush) getAuthorization(appKey string, masterSecret string) string { return Authorization } -func (j *JPush) Push(ctx context.Context, userIDs []string, title, content string, opts *offlinepush.Opts) error { +func (j *JPush) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error { var pf body.Platform pf.SetAll() var au body.Audience @@ -61,12 +64,12 @@ func (j *JPush) Push(ctx context.Context, userIDs []string, title, content strin no.IOSEnableMutableContent() no.SetExtras(extras) no.SetAlert(title) - no.SetAndroidIntent(j.config) + no.SetAndroidIntent(j.pushConf) var msg body.Message msg.SetMsgContent(content) var opt body.Options - opt.SetApnsProduction(j.config.IOSPush.Production) + opt.SetApnsProduction(j.pushConf.IOSPush.Production) var pushObj body.PushObj pushObj.SetPlatform(&pf) pushObj.SetAudience(&au) @@ -78,11 +81,11 @@ func (j *JPush) Push(ctx context.Context, userIDs []string, title, content strin } func (j *JPush) request(ctx context.Context, po body.PushObj, resp any, timeout int) error { - return http2.PostReturn( + return j.httpClient.PostReturn( ctx, - j.config.Push.Jpns.PushUrl, + j.pushConf.JPNS.PushURL, map[string]string{ - "Authorization": j.getAuthorization(j.config.Push.Jpns.AppKey, j.config.Push.Jpns.MasterSecret), + "Authorization": j.getAuthorization(j.pushConf.JPNS.AppKey, j.pushConf.JPNS.MasterSecret), }, po, resp, diff --git a/internal/push/offlinepush/offlinepush_interface.go b/internal/push/offlinepush/offlinepush_interface.go deleted file mode 100644 index a5d4051f9..000000000 --- a/internal/push/offlinepush/offlinepush_interface.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package offlinepush - -import ( - "context" -) - -// OfflinePusher Offline Pusher. -type OfflinePusher interface { - Push(ctx context.Context, userIDs []string, title, content string, opts *Opts) error -} - -// Opts opts. -type Opts struct { - Signal *Signal - IOSPushSound string - IOSBadgeCount bool - Ex string -} - -// Signal message id. -type Signal struct { - ClientMsgID string -} diff --git a/internal/push/offlinepush/offlinepusher.go b/internal/push/offlinepush/offlinepusher.go new file mode 100644 index 000000000..0706be64a --- /dev/null +++ b/internal/push/offlinepush/offlinepusher.go @@ -0,0 +1,52 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package offlinepush + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/dummy" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/fcm" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/getui" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/jpush" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" +) + +const ( + geTUI = "getui" + firebase = "fcm" + jPush = "jpush" +) + +// OfflinePusher Offline Pusher. +type OfflinePusher interface { + Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error +} + +func NewOfflinePusher(pushConf *config.Push, cache cache.ThirdCache) (OfflinePusher, error) { + var offlinePusher OfflinePusher + switch pushConf.Enable { + case geTUI: + offlinePusher = getui.NewClient(pushConf, cache) + case firebase: + return fcm.NewClient(pushConf, cache) + case jPush: + offlinePusher = jpush.NewClient(pushConf) + default: + offlinePusher = dummy.NewClient() + } + return offlinePusher, nil +} diff --git a/internal/push/offlinepush/options/options.go b/internal/push/offlinepush/options/options.go new file mode 100644 index 000000000..056f6b711 --- /dev/null +++ b/internal/push/offlinepush/options/options.go @@ -0,0 +1,14 @@ +package options + +// Opts opts. +type Opts struct { + Signal *Signal + IOSPushSound string + IOSBadgeCount bool + Ex string +} + +// Signal message id. +type Signal struct { + ClientMsgID string +} diff --git a/internal/push/onlinepusher.go b/internal/push/onlinepusher.go new file mode 100644 index 000000000..30bdf3e2e --- /dev/null +++ b/internal/push/onlinepusher.go @@ -0,0 +1,204 @@ +package push + +import ( + "context" + "github.com/openimsdk/protocol/msggateway" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" + "golang.org/x/sync/errgroup" + "google.golang.org/grpc" + "sync" +) + +const ( + KUBERNETES = "k8s" + ZOOKEEPER = "zookeeper" +) + +type OnlinePusher interface { + GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, + pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) + GetOnlinePushFailedUserIDs(ctx context.Context, msg *sdkws.MsgData, wsResults []*msggateway.SingleMsgToUserResults, + pushToUserIDs *[]string) []string +} + +type emptyOnlinePUsher struct{} + +func newEmptyOnlinePUsher() *emptyOnlinePUsher { + return &emptyOnlinePUsher{} +} + +func (emptyOnlinePUsher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, + pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) { + log.ZWarn(ctx, "emptyOnlinePUsher GetConnsAndOnlinePush", nil) + return nil, nil +} +func (u emptyOnlinePUsher) GetOnlinePushFailedUserIDs(ctx context.Context, msg *sdkws.MsgData, + wsResults []*msggateway.SingleMsgToUserResults, pushToUserIDs *[]string) []string { + log.ZWarn(ctx, "emptyOnlinePUsher GetOnlinePushFailedUserIDs", nil) + return nil +} + +func NewOnlinePusher(disCov discovery.SvcDiscoveryRegistry, config *Config) OnlinePusher { + switch config.Share.Env { + case KUBERNETES: + return NewK8sStaticConsistentHash(disCov, config) + case ZOOKEEPER: + return NewDefaultAllNode(disCov, config) + default: + return newEmptyOnlinePUsher() + } +} + +type DefaultAllNode struct { + disCov discovery.SvcDiscoveryRegistry + config *Config +} + +func NewDefaultAllNode(disCov discovery.SvcDiscoveryRegistry, config *Config) *DefaultAllNode { + return &DefaultAllNode{disCov: disCov, config: config} +} + +func (d *DefaultAllNode) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, + pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) { + conns, err := d.disCov.GetConns(ctx, d.config.Share.RpcRegisterName.MessageGateway) + log.ZDebug(ctx, "get gateway conn", "conn length", len(conns)) + if err != nil { + return nil, err + } + + var ( + mu sync.Mutex + wg = errgroup.Group{} + input = &msggateway.OnlineBatchPushOneMsgReq{MsgData: msg, PushToUserIDs: pushToUserIDs} + maxWorkers = d.config.RpcConfig.MaxConcurrentWorkers + ) + + if maxWorkers < 3 { + maxWorkers = 3 + } + + wg.SetLimit(maxWorkers) + + // Online push message + for _, conn := range conns { + conn := conn // loop var safe + wg.Go(func() error { + msgClient := msggateway.NewMsgGatewayClient(conn) + reply, err := msgClient.SuperGroupOnlineBatchPushOneMsg(ctx, input) + if err != nil { + return nil + } + + log.ZDebug(ctx, "push result", "reply", reply) + if reply != nil && reply.SinglePushResult != nil { + mu.Lock() + wsResults = append(wsResults, reply.SinglePushResult...) + mu.Unlock() + } + + return nil + }) + } + + _ = wg.Wait() + + // always return nil + return wsResults, nil +} + +func (d *DefaultAllNode) GetOnlinePushFailedUserIDs(_ context.Context, msg *sdkws.MsgData, + wsResults []*msggateway.SingleMsgToUserResults, pushToUserIDs *[]string) []string { + + onlineSuccessUserIDs := []string{msg.SendID} + for _, v := range wsResults { + //message sender do not need offline push + if msg.SendID == v.UserID { + continue + } + // mobile online push success + if v.OnlinePush { + onlineSuccessUserIDs = append(onlineSuccessUserIDs, v.UserID) + } + + } + + return datautil.SliceSub(*pushToUserIDs, onlineSuccessUserIDs) +} + +type K8sStaticConsistentHash struct { + disCov discovery.SvcDiscoveryRegistry + config *Config +} + +func NewK8sStaticConsistentHash(disCov discovery.SvcDiscoveryRegistry, config *Config) *K8sStaticConsistentHash { + return &K8sStaticConsistentHash{disCov: disCov, config: config} +} + +func (k *K8sStaticConsistentHash) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, + pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) { + + var usersHost = make(map[string][]string) + for _, v := range pushToUserIDs { + tHost, err := k.disCov.GetUserIdHashGatewayHost(ctx, v) + if err != nil { + log.ZError(ctx, "get msg gateway hash error", err) + return nil, err + } + tUsers, tbl := usersHost[tHost] + if tbl { + tUsers = append(tUsers, v) + usersHost[tHost] = tUsers + } else { + usersHost[tHost] = []string{v} + } + } + log.ZDebug(ctx, "genUsers send hosts struct:", "usersHost", usersHost) + var usersConns = make(map[*grpc.ClientConn][]string) + for host, userIds := range usersHost { + tconn, _ := k.disCov.GetConn(ctx, host) + usersConns[tconn] = userIds + } + var ( + mu sync.Mutex + wg = errgroup.Group{} + maxWorkers = k.config.RpcConfig.MaxConcurrentWorkers + ) + if maxWorkers < 3 { + maxWorkers = 3 + } + wg.SetLimit(maxWorkers) + for conn, userIds := range usersConns { + tcon := conn + tuserIds := userIds + wg.Go(func() error { + input := &msggateway.OnlineBatchPushOneMsgReq{MsgData: msg, PushToUserIDs: tuserIds} + msgClient := msggateway.NewMsgGatewayClient(tcon) + reply, err := msgClient.SuperGroupOnlineBatchPushOneMsg(ctx, input) + if err != nil { + return nil + } + log.ZDebug(ctx, "push result", "reply", reply) + if reply != nil && reply.SinglePushResult != nil { + mu.Lock() + wsResults = append(wsResults, reply.SinglePushResult...) + mu.Unlock() + } + return nil + }) + } + _ = wg.Wait() + return wsResults, nil +} +func (k *K8sStaticConsistentHash) GetOnlinePushFailedUserIDs(_ context.Context, _ *sdkws.MsgData, + wsResults []*msggateway.SingleMsgToUserResults, _ *[]string) []string { + var needOfflinePushUserIDs []string + for _, v := range wsResults { + if !v.OnlinePush { + needOfflinePushUserIDs = append(needOfflinePushUserIDs, v.UserID) + } + } + return needOfflinePushUserIDs +} diff --git a/internal/push/push.go b/internal/push/push.go new file mode 100644 index 000000000..18012a864 --- /dev/null +++ b/internal/push/push.go @@ -0,0 +1,71 @@ +package push + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" + pbpush "github.com/openimsdk/protocol/push" + "github.com/openimsdk/tools/db/redisutil" + "github.com/openimsdk/tools/discovery" + "google.golang.org/grpc" +) + +type pushServer struct { + database controller.PushDatabase + disCov discovery.SvcDiscoveryRegistry + offlinePusher offlinepush.OfflinePusher + pushCh *ConsumerHandler +} + +type Config struct { + RpcConfig config.Push + RedisConfig config.Redis + MongodbConfig config.Mongo + KafkaConfig config.Kafka + ZookeeperConfig config.ZooKeeper + NotificationConfig config.Notification + Share config.Share + WebhooksConfig config.Webhooks + LocalCacheConfig config.LocalCache +} + +func (p pushServer) PushMsg(ctx context.Context, req *pbpush.PushMsgReq) (*pbpush.PushMsgResp, error) { + //todo reserved Interface + return nil, nil +} + +func (p pushServer) DelUserPushToken(ctx context.Context, + req *pbpush.DelUserPushTokenReq) (resp *pbpush.DelUserPushTokenResp, err error) { + if err = p.database.DelFcmToken(ctx, req.UserID, int(req.PlatformID)); err != nil { + return nil, err + } + return &pbpush.DelUserPushTokenResp{}, nil +} + +func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) + if err != nil { + return err + } + cacheModel := cache.NewThirdCache(rdb) + offlinePusher, err := offlinepush.NewOfflinePusher(&config.RpcConfig, cacheModel) + if err != nil { + return err + } + database := controller.NewPushDatabase(cacheModel) + + consumer, err := NewConsumerHandler(config, offlinePusher, rdb, client) + if err != nil { + return err + } + pbpush.RegisterPushMsgServiceServer(server, &pushServer{ + database: database, + disCov: client, + offlinePusher: offlinePusher, + pushCh: consumer, + }) + go consumer.pushConsumerGroup.RegisterHandleAndConsumer(ctx, consumer) + return nil +} diff --git a/internal/push/push_handler.go b/internal/push/push_handler.go index 0e68e76b3..e09b0b2ec 100644 --- a/internal/push/push_handler.go +++ b/internal/push/push_handler.go @@ -16,49 +16,64 @@ package push import ( "context" + "encoding/json" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush" + "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" + "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" + "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" + "github.com/openimsdk/open-im-server/v3/pkg/rpccache" + "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/jsonutil" + "github.com/redis/go-redis/v9" "github.com/IBM/sarama" - "github.com/OpenIMSDK/protocol/constant" - pbchat "github.com/OpenIMSDK/protocol/msg" - pbpush "github.com/OpenIMSDK/protocol/push" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - kfk "github.com/openimsdk/open-im-server/v3/pkg/common/kafka" + "github.com/openimsdk/protocol/constant" + pbchat "github.com/openimsdk/protocol/msg" + pbpush "github.com/openimsdk/protocol/push" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mq/kafka" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/timeutil" "google.golang.org/protobuf/proto" ) type ConsumerHandler struct { - pushConsumerGroup *kfk.MConsumerGroup - pusher *Pusher + pushConsumerGroup *kafka.MConsumerGroup + offlinePusher offlinepush.OfflinePusher + onlinePusher OnlinePusher + groupLocalCache *rpccache.GroupLocalCache + conversationLocalCache *rpccache.ConversationLocalCache + msgRpcClient rpcclient.MessageRpcClient + conversationRpcClient rpcclient.ConversationRpcClient + groupRpcClient rpcclient.GroupRpcClient + webhookClient *webhook.Client + config *Config } -func NewConsumerHandler(config *config.GlobalConfig, pusher *Pusher) (*ConsumerHandler, error) { +func NewConsumerHandler(config *Config, offlinePusher offlinepush.OfflinePusher, rdb redis.UniversalClient, + client discovery.SvcDiscoveryRegistry) (*ConsumerHandler, error) { var consumerHandler ConsumerHandler - consumerHandler.pusher = pusher var err error - var tlsConfig *kfk.TLSConfig - if config.Kafka.TLS != nil { - tlsConfig = &kfk.TLSConfig{ - CACrt: config.Kafka.TLS.CACrt, - ClientCrt: config.Kafka.TLS.ClientCrt, - ClientKey: config.Kafka.TLS.ClientKey, - ClientKeyPwd: config.Kafka.TLS.ClientKeyPwd, - InsecureSkipVerify: false, - } - } - consumerHandler.pushConsumerGroup, err = kfk.NewMConsumerGroup(&kfk.MConsumerGroupConfig{ - KafkaVersion: sarama.V2_0_0_0, - OffsetsInitial: sarama.OffsetNewest, - IsReturnErr: false, - UserName: config.Kafka.Username, - Password: config.Kafka.Password, - }, []string{config.Kafka.MsgToPush.Topic}, config.Kafka.Addr, - config.Kafka.ConsumerGroupID.MsgToPush, - tlsConfig) + consumerHandler.pushConsumerGroup, err = kafka.NewMConsumerGroup(config.KafkaConfig.Build(), config.KafkaConfig.ToPushGroupID, + []string{config.KafkaConfig.ToPushTopic}) if err != nil { return nil, err } + consumerHandler.offlinePusher = offlinePusher + consumerHandler.onlinePusher = NewOnlinePusher(client, config) + consumerHandler.groupRpcClient = rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group) + consumerHandler.groupLocalCache = rpccache.NewGroupLocalCache(consumerHandler.groupRpcClient, &config.LocalCacheConfig, rdb) + consumerHandler.msgRpcClient = rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg) + consumerHandler.conversationRpcClient = rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation) + consumerHandler.conversationLocalCache = rpccache.NewConversationLocalCache(consumerHandler.conversationRpcClient, + &config.LocalCacheConfig, rdb) + consumerHandler.webhookClient = webhook.NewWebhookClient(config.WebhooksConfig.URL) + consumerHandler.config = config return &consumerHandler, nil } @@ -73,38 +88,35 @@ func (c *ConsumerHandler) handleMs2PsChat(ctx context.Context, msg []byte) { ConversationID: msgFromMQ.ConversationID, } sec := msgFromMQ.MsgData.SendTime / 1000 - nowSec := utils.GetCurrentTimestampBySecond() + nowSec := timeutil.GetCurrentTimestampBySecond() log.ZDebug(ctx, "push msg", "msg", pbData.String(), "sec", sec, "nowSec", nowSec) if nowSec-sec > 10 { return } var err error switch msgFromMQ.MsgData.SessionType { - case constant.SuperGroupChatType: - err = c.pusher.Push2SuperGroup(ctx, pbData.MsgData.GroupID, pbData.MsgData) + case constant.ReadGroupChatType: + err = c.Push2Group(ctx, pbData.MsgData.GroupID, pbData.MsgData) default: var pushUserIDList []string - isSenderSync := utils.GetSwitchFromOptions(pbData.MsgData.Options, constant.IsSenderSync) + isSenderSync := datautil.GetSwitchFromOptions(pbData.MsgData.Options, constant.IsSenderSync) if !isSenderSync || pbData.MsgData.SendID == pbData.MsgData.RecvID { pushUserIDList = append(pushUserIDList, pbData.MsgData.RecvID) } else { pushUserIDList = append(pushUserIDList, pbData.MsgData.RecvID, pbData.MsgData.SendID) } - err = c.pusher.Push2User(ctx, pushUserIDList, pbData.MsgData) + err = c.Push2User(ctx, pushUserIDList, pbData.MsgData) } if err != nil { - if err == errNoOfflinePusher { - log.ZWarn(ctx, "offline push failed", err, "msg", pbData.String()) - } else { - log.ZError(ctx, "push failed", err, "msg", pbData.String()) - } + log.ZWarn(ctx, "push failed", err, "msg", pbData.String()) } } -func (ConsumerHandler) Setup(_ sarama.ConsumerGroupSession) error { return nil } -func (ConsumerHandler) Cleanup(_ sarama.ConsumerGroupSession) error { return nil } -func (c *ConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, - claim sarama.ConsumerGroupClaim, -) error { + +func (*ConsumerHandler) Setup(sarama.ConsumerGroupSession) error { return nil } + +func (*ConsumerHandler) Cleanup(sarama.ConsumerGroupSession) error { return nil } + +func (c *ConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { for msg := range claim.Messages() { ctx := c.pushConsumerGroup.GetContextFromMsg(msg) c.handleMs2PsChat(ctx, msg.Value) @@ -112,3 +124,243 @@ func (c *ConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, } return nil } + +// Push2User Suitable for two types of conversations, one is SingleChatType and the other is NotificationChatType. +func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *sdkws.MsgData) error { + log.ZDebug(ctx, "Get msg from msg_transfer And push msg", "userIDs", userIDs, "msg", msg.String()) + if err := c.webhookBeforeOnlinePush(ctx, &c.config.WebhooksConfig.BeforeOnlinePush, userIDs, msg); err != nil { + return err + } + wsResults, err := c.onlinePusher.GetConnsAndOnlinePush(ctx, msg, userIDs) + if err != nil { + return err + } + + log.ZDebug(ctx, "single and notification push result", "result", wsResults, "msg", msg, "push_to_userID", userIDs) + + if !c.shouldPushOffline(ctx, msg) { + return nil + } + + for _, v := range wsResults { + //message sender do not need offline push + if msg.SendID == v.UserID { + continue + } + //receiver online push success + if v.OnlinePush { + return nil + } + } + offlinePUshUserID := []string{msg.RecvID} + + //receiver offline push + if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, + offlinePUshUserID, msg, nil); err != nil { + return err + } + + err = c.offlinePushMsg(ctx, msg, offlinePUshUserID) + if err != nil { + return err + } + + return nil +} + +func (c *ConsumerHandler) shouldPushOffline(_ context.Context, msg *sdkws.MsgData) bool { + isOfflinePush := datautil.GetSwitchFromOptions(msg.Options, constant.IsOfflinePush) + if !isOfflinePush { + return false + } + if msg.ContentType == constant.SignalingNotification { + return false + } + return true +} + +func (c *ConsumerHandler) Push2Group(ctx context.Context, groupID string, msg *sdkws.MsgData) (err error) { + log.ZDebug(ctx, "Get super group msg from msg_transfer and push msg", "msg", msg.String(), "groupID", groupID) + var pushToUserIDs []string + if err = c.webhookBeforeGroupOnlinePush(ctx, &c.config.WebhooksConfig.BeforeGroupOnlinePush, groupID, msg, + &pushToUserIDs); err != nil { + return err + } + + err = c.groupMessagesHandler(ctx, groupID, &pushToUserIDs, msg) + if err != nil { + return err + } + + wsResults, err := c.onlinePusher.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs) + if err != nil { + return err + } + + log.ZDebug(ctx, "group push result", "result", wsResults, "msg", msg) + + if !c.shouldPushOffline(ctx, msg) { + return nil + } + needOfflinePushUserIDs := c.onlinePusher.GetOnlinePushFailedUserIDs(ctx, msg, wsResults, &pushToUserIDs) + + //filter some user, like don not disturb or don't need offline push etc. + needOfflinePushUserIDs, err = c.filterGroupMessageOfflinePush(ctx, groupID, msg, needOfflinePushUserIDs) + if err != nil { + return err + } + // Use offline push messaging + if len(needOfflinePushUserIDs) > 0 { + var offlinePushUserIDs []string + err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, needOfflinePushUserIDs, msg, &offlinePushUserIDs) + if err != nil { + return err + } + + if len(offlinePushUserIDs) > 0 { + needOfflinePushUserIDs = offlinePushUserIDs + } + + err = c.offlinePushMsg(ctx, msg, needOfflinePushUserIDs) + if err != nil { + log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg) + return err + } + + } + + return nil +} +func (c *ConsumerHandler) groupMessagesHandler(ctx context.Context, groupID string, pushToUserIDs *[]string, msg *sdkws.MsgData) (err error) { + if len(*pushToUserIDs) == 0 { + *pushToUserIDs, err = c.groupLocalCache.GetGroupMemberIDs(ctx, groupID) + if err != nil { + return err + } + switch msg.ContentType { + case constant.MemberQuitNotification: + var tips sdkws.MemberQuitTips + if unmarshalNotificationElem(msg.Content, &tips) != nil { + return err + } + if err = c.DeleteMemberAndSetConversationSeq(ctx, groupID, []string{tips.QuitUser.UserID}); err != nil { + log.ZError(ctx, "MemberQuitNotification DeleteMemberAndSetConversationSeq", err, "groupID", groupID, "userID", tips.QuitUser.UserID) + } + *pushToUserIDs = append(*pushToUserIDs, tips.QuitUser.UserID) + case constant.MemberKickedNotification: + var tips sdkws.MemberKickedTips + if unmarshalNotificationElem(msg.Content, &tips) != nil { + return err + } + kickedUsers := datautil.Slice(tips.KickedUserList, func(e *sdkws.GroupMemberFullInfo) string { return e.UserID }) + if err = c.DeleteMemberAndSetConversationSeq(ctx, groupID, kickedUsers); err != nil { + log.ZError(ctx, "MemberKickedNotification DeleteMemberAndSetConversationSeq", err, "groupID", groupID, "userIDs", kickedUsers) + } + + *pushToUserIDs = append(*pushToUserIDs, kickedUsers...) + case constant.GroupDismissedNotification: + if msgprocessor.IsNotification(msgprocessor.GetConversationIDByMsg(msg)) { // 消息先到,通知后到 + var tips sdkws.GroupDismissedTips + if unmarshalNotificationElem(msg.Content, &tips) != nil { + return err + } + log.ZInfo(ctx, "GroupDismissedNotificationInfo****", "groupID", groupID, "num", len(*pushToUserIDs), "list", pushToUserIDs) + if len(c.config.Share.IMAdminUserID) > 0 { + ctx = mcontext.WithOpUserIDContext(ctx, c.config.Share.IMAdminUserID[0]) + } + defer func(groupID string) { + if err = c.groupRpcClient.DismissGroup(ctx, groupID); err != nil { + log.ZError(ctx, "DismissGroup Notification clear members", err, "groupID", groupID) + } + }(groupID) + } + } + } + return err +} + +func (c *ConsumerHandler) offlinePushMsg(ctx context.Context, msg *sdkws.MsgData, offlinePushUserIDs []string) error { + title, content, opts, err := c.getOfflinePushInfos(msg) + if err != nil { + return err + } + err = c.offlinePusher.Push(ctx, offlinePushUserIDs, title, content, opts) + if err != nil { + prommetrics.MsgOfflinePushFailedCounter.Inc() + return err + } + return nil +} + +func (c *ConsumerHandler) filterGroupMessageOfflinePush(ctx context.Context, groupID string, msg *sdkws.MsgData, + offlinePushUserIDs []string) (userIDs []string, err error) { + + //todo local cache Obtain the difference set through local comparison. + needOfflinePushUserIDs, err := c.conversationRpcClient.GetConversationOfflinePushUserIDs( + ctx, conversationutil.GenGroupConversationID(groupID), offlinePushUserIDs) + if err != nil { + return nil, err + } + return needOfflinePushUserIDs, nil +} + +func (c *ConsumerHandler) getOfflinePushInfos(msg *sdkws.MsgData) (title, content string, opts *options.Opts, err error) { + type AtTextElem struct { + Text string `json:"text,omitempty"` + AtUserList []string `json:"atUserList,omitempty"` + IsAtSelf bool `json:"isAtSelf"` + } + + opts = &options.Opts{Signal: &options.Signal{}} + if msg.OfflinePushInfo != nil { + opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount + opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound + opts.Ex = msg.OfflinePushInfo.Ex + } + + if msg.OfflinePushInfo != nil { + title = msg.OfflinePushInfo.Title + content = msg.OfflinePushInfo.Desc + } + if title == "" { + switch msg.ContentType { + case constant.Text: + fallthrough + case constant.Picture: + fallthrough + case constant.Voice: + fallthrough + case constant.Video: + fallthrough + case constant.File: + title = constant.ContentType2PushContent[int64(msg.ContentType)] + case constant.AtText: + ac := AtTextElem{} + _ = jsonutil.JsonStringToStruct(string(msg.Content), &ac) + case constant.SignalingNotification: + title = constant.ContentType2PushContent[constant.SignalMsg] + default: + title = constant.ContentType2PushContent[constant.Common] + } + } + if content == "" { + content = title + } + return +} +func (c *ConsumerHandler) DeleteMemberAndSetConversationSeq(ctx context.Context, groupID string, userIDs []string) error { + conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, groupID) + maxSeq, err := c.msgRpcClient.GetConversationMaxSeq(ctx, conversationID) + if err != nil { + return err + } + return c.conversationRpcClient.SetConversationMaxSeq(ctx, userIDs, conversationID, maxSeq) +} +func unmarshalNotificationElem(bytes []byte, t any) error { + var notification sdkws.NotificationElem + if err := json.Unmarshal(bytes, ¬ification); err != nil { + return err + } + + return json.Unmarshal([]byte(notification.Detail), t) +} diff --git a/internal/push/push_rpc_server.go b/internal/push/push_rpc_server.go deleted file mode 100644 index b68b06666..000000000 --- a/internal/push/push_rpc_server.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package push - -import ( - "context" - - "github.com/OpenIMSDK/protocol/constant" - pbpush "github.com/OpenIMSDK/protocol/push" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" - "github.com/openimsdk/open-im-server/v3/pkg/rpccache" - "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" - "google.golang.org/grpc" -) - -type pushServer struct { - pusher *Pusher - config *config.GlobalConfig -} - -func Start(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error { - rdb, err := cache.NewRedis(config) - if err != nil { - return err - } - cacheModel := cache.NewMsgCacheModel(rdb, config) - offlinePusher := NewOfflinePusher(config, cacheModel) - database := controller.NewPushDatabase(cacheModel) - groupRpcClient := rpcclient.NewGroupRpcClient(client, config) - conversationRpcClient := rpcclient.NewConversationRpcClient(client, config) - msgRpcClient := rpcclient.NewMessageRpcClient(client, config) - pusher := NewPusher( - config, - client, - offlinePusher, - database, - rpccache.NewGroupLocalCache(groupRpcClient, rdb), - rpccache.NewConversationLocalCache(conversationRpcClient, rdb), - &conversationRpcClient, - &groupRpcClient, - &msgRpcClient, - ) - - pbpush.RegisterPushMsgServiceServer(server, &pushServer{ - pusher: pusher, - config: config, - }) - - consumer, err := NewConsumer(config, pusher) - if err != nil { - return err - } - - consumer.Start() - - return nil -} - -func (r *pushServer) PushMsg(ctx context.Context, pbData *pbpush.PushMsgReq) (resp *pbpush.PushMsgResp, err error) { - switch pbData.MsgData.SessionType { - case constant.SuperGroupChatType: - err = r.pusher.Push2SuperGroup(ctx, pbData.MsgData.GroupID, pbData.MsgData) - default: - var pushUserIDList []string - isSenderSync := utils.GetSwitchFromOptions(pbData.MsgData.Options, constant.IsSenderSync) - if !isSenderSync { - pushUserIDList = append(pushUserIDList, pbData.MsgData.RecvID) - } else { - pushUserIDList = append(pushUserIDList, pbData.MsgData.RecvID, pbData.MsgData.SendID) - } - err = r.pusher.Push2User(ctx, pushUserIDList, pbData.MsgData) - } - if err != nil { - if err != errNoOfflinePusher { - return nil, err - } - log.ZWarn(ctx, "offline push failed", err, "msg", pbData.String()) - } - return &pbpush.PushMsgResp{}, nil -} - -func (r *pushServer) DelUserPushToken( - ctx context.Context, - req *pbpush.DelUserPushTokenReq, -) (resp *pbpush.DelUserPushTokenResp, err error) { - if err = r.pusher.database.DelFcmToken(ctx, req.UserID, int(req.PlatformID)); err != nil { - return nil, err - } - return &pbpush.DelUserPushTokenResp{}, nil -} diff --git a/internal/push/push_to_client.go b/internal/push/push_to_client.go deleted file mode 100644 index d7f30ebce..000000000 --- a/internal/push/push_to_client.go +++ /dev/null @@ -1,522 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package push - -import ( - "context" - "encoding/json" - "errors" - "sync" - - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/conversation" - "github.com/OpenIMSDK/protocol/msggateway" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" - "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush" - "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/dummy" - "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/fcm" - "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/getui" - "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/jpush" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" - "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" - "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" - "github.com/openimsdk/open-im-server/v3/pkg/rpccache" - "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" - "golang.org/x/sync/errgroup" - "google.golang.org/grpc" -) - -type Pusher struct { - config *config.GlobalConfig - database controller.PushDatabase - discov discoveryregistry.SvcDiscoveryRegistry - offlinePusher offlinepush.OfflinePusher - groupLocalCache *rpccache.GroupLocalCache - conversationLocalCache *rpccache.ConversationLocalCache - msgRpcClient *rpcclient.MessageRpcClient - conversationRpcClient *rpcclient.ConversationRpcClient - groupRpcClient *rpcclient.GroupRpcClient -} - -var errNoOfflinePusher = errors.New("no offlinePusher is configured") - -func NewPusher(config *config.GlobalConfig, discov discoveryregistry.SvcDiscoveryRegistry, offlinePusher offlinepush.OfflinePusher, database controller.PushDatabase, - groupLocalCache *rpccache.GroupLocalCache, conversationLocalCache *rpccache.ConversationLocalCache, - conversationRpcClient *rpcclient.ConversationRpcClient, groupRpcClient *rpcclient.GroupRpcClient, msgRpcClient *rpcclient.MessageRpcClient, -) *Pusher { - return &Pusher{ - config: config, - discov: discov, - database: database, - offlinePusher: offlinePusher, - groupLocalCache: groupLocalCache, - conversationLocalCache: conversationLocalCache, - msgRpcClient: msgRpcClient, - conversationRpcClient: conversationRpcClient, - groupRpcClient: groupRpcClient, - } -} - -func NewOfflinePusher(config *config.GlobalConfig, cache cache.MsgModel) offlinepush.OfflinePusher { - var offlinePusher offlinepush.OfflinePusher - switch config.Push.Enable { - case "getui": - offlinePusher = getui.NewClient(config, cache) - case "fcm": - offlinePusher = fcm.NewClient(config, cache) - case "jpush": - offlinePusher = jpush.NewClient(config) - default: - offlinePusher = dummy.NewClient() - } - return offlinePusher -} - -func (p *Pusher) DeleteMemberAndSetConversationSeq(ctx context.Context, groupID string, userIDs []string) error { - conevrsationID := msgprocessor.GetConversationIDBySessionType(constant.SuperGroupChatType, groupID) - maxSeq, err := p.msgRpcClient.GetConversationMaxSeq(ctx, conevrsationID) - if err != nil { - return err - } - return p.conversationRpcClient.SetConversationMaxSeq(ctx, userIDs, conevrsationID, maxSeq) -} - -func (p *Pusher) Push2User(ctx context.Context, userIDs []string, msg *sdkws.MsgData) error { - log.ZDebug(ctx, "Get msg from msg_transfer And push msg", "userIDs", userIDs, "msg", msg.String()) - if err := callbackOnlinePush(ctx, p.config, userIDs, msg); err != nil { - return err - } - // push - wsResults, err := p.GetConnsAndOnlinePush(ctx, msg, userIDs) - if err != nil { - return err - } - - isOfflinePush := utils.GetSwitchFromOptions(msg.Options, constant.IsOfflinePush) - log.ZDebug(ctx, "push_result", "ws push result", wsResults, "sendData", msg, "isOfflinePush", isOfflinePush, "push_to_userID", userIDs) - - if !isOfflinePush { - return nil - } - - if len(wsResults) == 0 { - return nil - } - onlinePushSuccUserIDSet := utils.SliceSet(utils.Filter(wsResults, func(e *msggateway.SingleMsgToUserResults) (string, bool) { - return e.UserID, e.OnlinePush && e.UserID != "" - })) - offlinePushUserIDList := utils.Filter(wsResults, func(e *msggateway.SingleMsgToUserResults) (string, bool) { - _, exist := onlinePushSuccUserIDSet[e.UserID] - return e.UserID, !exist && e.UserID != "" && e.UserID != msg.SendID - }) - - if len(offlinePushUserIDList) > 0 { - if err = callbackOfflinePush(ctx, p.config, offlinePushUserIDList, msg, &[]string{}); err != nil { - return err - } - err = p.offlinePushMsg(ctx, msg.SendID, msg, offlinePushUserIDList) - if err != nil { - return err - } - } - return nil -} - -func (p *Pusher) UnmarshalNotificationElem(bytes []byte, t any) error { - var notification sdkws.NotificationElem - if err := json.Unmarshal(bytes, ¬ification); err != nil { - return err - } - - return json.Unmarshal([]byte(notification.Detail), t) -} - -/* -k8s deployment,offline push group messages function. -*/ -func (p *Pusher) k8sOfflinePush2SuperGroup(ctx context.Context, groupID string, msg *sdkws.MsgData, wsResults []*msggateway.SingleMsgToUserResults) error { - - var needOfflinePushUserIDs []string - for _, v := range wsResults { - if !v.OnlinePush { - needOfflinePushUserIDs = append(needOfflinePushUserIDs, v.UserID) - } - } - if len(needOfflinePushUserIDs) > 0 { - var offlinePushUserIDs []string - err := callbackOfflinePush(ctx, p.config, needOfflinePushUserIDs, msg, &offlinePushUserIDs) - if err != nil { - return err - } - - if len(offlinePushUserIDs) > 0 { - needOfflinePushUserIDs = offlinePushUserIDs - } - if msg.ContentType != constant.SignalingNotification { - resp, err := p.conversationRpcClient.Client.GetConversationOfflinePushUserIDs( - ctx, - &conversation.GetConversationOfflinePushUserIDsReq{ConversationID: utils.GenGroupConversationID(groupID), UserIDs: needOfflinePushUserIDs}, - ) - if err != nil { - return err - } - if len(resp.UserIDs) > 0 { - err = p.offlinePushMsg(ctx, groupID, msg, resp.UserIDs) - if err != nil { - log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg) - return err - } - } - } - - } - return nil -} -func (p *Pusher) Push2SuperGroup(ctx context.Context, groupID string, msg *sdkws.MsgData) (err error) { - log.ZDebug(ctx, "Get super group msg from msg_transfer and push msg", "msg", msg.String(), "groupID", groupID) - var pushToUserIDs []string - if err = callbackBeforeSuperGroupOnlinePush(ctx, p.config, groupID, msg, &pushToUserIDs); err != nil { - return err - } - - if len(pushToUserIDs) == 0 { - pushToUserIDs, err = p.groupLocalCache.GetGroupMemberIDs(ctx, groupID) - if err != nil { - return err - } - - switch msg.ContentType { - case constant.MemberQuitNotification: - var tips sdkws.MemberQuitTips - if p.UnmarshalNotificationElem(msg.Content, &tips) != nil { - return err - } - defer func(groupID string, userIDs []string) { - if err = p.DeleteMemberAndSetConversationSeq(ctx, groupID, userIDs); err != nil { - log.ZError(ctx, "MemberQuitNotification DeleteMemberAndSetConversationSeq", err, "groupID", groupID, "userIDs", userIDs) - } - }(groupID, []string{tips.QuitUser.UserID}) - pushToUserIDs = append(pushToUserIDs, tips.QuitUser.UserID) - case constant.MemberKickedNotification: - var tips sdkws.MemberKickedTips - if p.UnmarshalNotificationElem(msg.Content, &tips) != nil { - return err - } - kickedUsers := utils.Slice(tips.KickedUserList, func(e *sdkws.GroupMemberFullInfo) string { return e.UserID }) - defer func(groupID string, userIDs []string) { - if err = p.DeleteMemberAndSetConversationSeq(ctx, groupID, userIDs); err != nil { - log.ZError(ctx, "MemberKickedNotification DeleteMemberAndSetConversationSeq", err, "groupID", groupID, "userIDs", userIDs) - } - }(groupID, kickedUsers) - pushToUserIDs = append(pushToUserIDs, kickedUsers...) - case constant.GroupDismissedNotification: - // Messages arrive first, notifications arrive later - if msgprocessor.IsNotification(msgprocessor.GetConversationIDByMsg(msg)) { - var tips sdkws.GroupDismissedTips - if p.UnmarshalNotificationElem(msg.Content, &tips) != nil { - return err - } - log.ZInfo(ctx, "GroupDismissedNotificationInfo****", "groupID", groupID, "num", len(pushToUserIDs), "list", pushToUserIDs) - if len(p.config.Manager.UserID) > 0 { - ctx = mcontext.WithOpUserIDContext(ctx, p.config.Manager.UserID[0]) - } - if len(p.config.Manager.UserID) == 0 && len(p.config.IMAdmin.UserID) > 0 { - ctx = mcontext.WithOpUserIDContext(ctx, p.config.IMAdmin.UserID[0]) - } - defer func(groupID string) { - if err = p.groupRpcClient.DismissGroup(ctx, groupID); err != nil { - log.ZError(ctx, "DismissGroup Notification clear members", err, "groupID", groupID) - } - }(groupID) - } - } - } - - wsResults, err := p.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs) - if err != nil { - return err - } - - log.ZDebug(ctx, "get conn and online push success", "result", wsResults, "msg", msg) - isOfflinePush := utils.GetSwitchFromOptions(msg.Options, constant.IsOfflinePush) - if isOfflinePush && p.config.Envs.Discovery == "k8s" { - return p.k8sOfflinePush2SuperGroup(ctx, groupID, msg, wsResults) - } - if isOfflinePush && p.config.Envs.Discovery == "zookeeper" { - var ( - onlineSuccessUserIDs = []string{msg.SendID} - webAndPcBackgroundUserIDs []string - ) - - for _, v := range wsResults { - if v.OnlinePush && v.UserID != msg.SendID { - onlineSuccessUserIDs = append(onlineSuccessUserIDs, v.UserID) - } - - if v.OnlinePush { - continue - } - - if len(v.Resp) == 0 { - continue - } - - for _, singleResult := range v.Resp { - if singleResult.ResultCode != -2 { - continue - } - - isPC := constant.PlatformIDToName(int(singleResult.RecvPlatFormID)) == constant.TerminalPC - isWebID := singleResult.RecvPlatFormID == constant.WebPlatformID - - if isPC || isWebID { - webAndPcBackgroundUserIDs = append(webAndPcBackgroundUserIDs, v.UserID) - } - } - } - - needOfflinePushUserIDs := utils.DifferenceString(onlineSuccessUserIDs, pushToUserIDs) - - // Use offline push messaging - if len(needOfflinePushUserIDs) > 0 { - var offlinePushUserIDs []string - err = callbackOfflinePush(ctx, p.config, needOfflinePushUserIDs, msg, &offlinePushUserIDs) - if err != nil { - return err - } - - if len(offlinePushUserIDs) > 0 { - needOfflinePushUserIDs = offlinePushUserIDs - } - if msg.ContentType != constant.SignalingNotification { - resp, err := p.conversationRpcClient.Client.GetConversationOfflinePushUserIDs( - ctx, - &conversation.GetConversationOfflinePushUserIDsReq{ConversationID: utils.GenGroupConversationID(groupID), UserIDs: needOfflinePushUserIDs}, - ) - if err != nil { - return err - } - if len(resp.UserIDs) > 0 { - err = p.offlinePushMsg(ctx, groupID, msg, resp.UserIDs) - if err != nil { - log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg) - return err - } - if _, err := p.GetConnsAndOnlinePush(ctx, msg, utils.IntersectString(resp.UserIDs, webAndPcBackgroundUserIDs)); err != nil { - log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg, "userIDs", utils.IntersectString(needOfflinePushUserIDs, webAndPcBackgroundUserIDs)) - return err - } - } - } - - } - } - return nil -} - -func (p *Pusher) k8sOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) { - var usersHost = make(map[string][]string) - for _, v := range pushToUserIDs { - tHost, err := p.discov.GetUserIdHashGatewayHost(ctx, v) - if err != nil { - log.ZError(ctx, "get msggateway hash error", err) - return nil, err - } - tUsers, tbl := usersHost[tHost] - if tbl { - tUsers = append(tUsers, v) - usersHost[tHost] = tUsers - } else { - usersHost[tHost] = []string{v} - } - } - log.ZDebug(ctx, "genUsers send hosts struct:", "usersHost", usersHost) - var usersConns = make(map[*grpc.ClientConn][]string) - for host, userIds := range usersHost { - tconn, _ := p.discov.GetConn(ctx, host) - usersConns[tconn] = userIds - } - var ( - mu sync.Mutex - wg = errgroup.Group{} - maxWorkers = p.config.Push.MaxConcurrentWorkers - ) - if maxWorkers < 3 { - maxWorkers = 3 - } - wg.SetLimit(maxWorkers) - for conn, userIds := range usersConns { - tcon := conn - tuserIds := userIds - wg.Go(func() error { - input := &msggateway.OnlineBatchPushOneMsgReq{MsgData: msg, PushToUserIDs: tuserIds} - msgClient := msggateway.NewMsgGatewayClient(tcon) - reply, err := msgClient.SuperGroupOnlineBatchPushOneMsg(ctx, input) - if err != nil { - return nil - } - log.ZDebug(ctx, "push result", "reply", reply) - if reply != nil && reply.SinglePushResult != nil { - mu.Lock() - wsResults = append(wsResults, reply.SinglePushResult...) - mu.Unlock() - } - return nil - }) - } - _ = wg.Wait() - return wsResults, nil -} -func (p *Pusher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) { - if p.config.Envs.Discovery == "k8s" { - return p.k8sOnlinePush(ctx, msg, pushToUserIDs) - } - conns, err := p.discov.GetConns(ctx, p.config.RpcRegisterName.OpenImMessageGatewayName) - log.ZDebug(ctx, "get gateway conn", "conn length", len(conns)) - if err != nil { - return nil, err - } - - var ( - mu sync.Mutex - wg = errgroup.Group{} - input = &msggateway.OnlineBatchPushOneMsgReq{MsgData: msg, PushToUserIDs: pushToUserIDs} - maxWorkers = p.config.Push.MaxConcurrentWorkers - ) - - if maxWorkers < 3 { - maxWorkers = 3 - } - - wg.SetLimit(maxWorkers) - - // Online push message - for _, conn := range conns { - conn := conn // loop var safe - wg.Go(func() error { - msgClient := msggateway.NewMsgGatewayClient(conn) - reply, err := msgClient.SuperGroupOnlineBatchPushOneMsg(ctx, input) - if err != nil { - return nil - } - - log.ZDebug(ctx, "push result", "reply", reply) - if reply != nil && reply.SinglePushResult != nil { - mu.Lock() - wsResults = append(wsResults, reply.SinglePushResult...) - mu.Unlock() - } - - return nil - }) - } - - _ = wg.Wait() - - // always return nil - return wsResults, nil -} - -func (p *Pusher) offlinePushMsg(ctx context.Context, conversationID string, msg *sdkws.MsgData, offlinePushUserIDs []string) error { - title, content, opts, err := p.getOfflinePushInfos(conversationID, msg) - if err != nil { - return err - } - err = p.offlinePusher.Push(ctx, offlinePushUserIDs, title, content, opts) - if err != nil { - prommetrics.MsgOfflinePushFailedCounter.Inc() - return err - } - return nil -} - -func (p *Pusher) GetOfflinePushOpts(msg *sdkws.MsgData) (opts *offlinepush.Opts, err error) { - opts = &offlinepush.Opts{Signal: &offlinepush.Signal{}} - // if msg.ContentType > constant.SignalingNotificationBegin && msg.ContentType < constant.SignalingNotificationEnd { - // req := &sdkws.SignalReq{} - // if err := proto.Unmarshal(msg.Content, req); err != nil { - // return nil, utils.Wrap(err, "") - // } - // switch req.Payload.(type) { - // case *sdkws.SignalReq_Invite, *sdkws.SignalReq_InviteInGroup: - // opts.Signal = &offlinepush.Signal{ClientMsgID: msg.ClientMsgID} - // } - // } - if msg.OfflinePushInfo != nil { - opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount - opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound - opts.Ex = msg.OfflinePushInfo.Ex - } - return opts, nil -} - -func (p *Pusher) getOfflinePushInfos(conversationID string, msg *sdkws.MsgData) (title, content string, opts *offlinepush.Opts, err error) { - if p.offlinePusher == nil { - err = errNoOfflinePusher - return - } - - type atContent struct { - Text string `json:"text"` - AtUserList []string `json:"atUserList"` - IsAtSelf bool `json:"isAtSelf"` - } - - opts, err = p.GetOfflinePushOpts(msg) - if err != nil { - return - } - - if msg.OfflinePushInfo != nil { - title = msg.OfflinePushInfo.Title - content = msg.OfflinePushInfo.Desc - } - if title == "" { - switch msg.ContentType { - case constant.Text: - fallthrough - case constant.Picture: - fallthrough - case constant.Voice: - fallthrough - case constant.Video: - fallthrough - case constant.File: - title = constant.ContentType2PushContent[int64(msg.ContentType)] - case constant.AtText: - ac := atContent{} - _ = utils.JsonStringToStruct(string(msg.Content), &ac) - if utils.IsContain(conversationID, ac.AtUserList) { - title = constant.ContentType2PushContent[constant.AtText] + constant.ContentType2PushContent[constant.Common] - } else { - title = constant.ContentType2PushContent[constant.GroupMsg] - } - case constant.SignalingNotification: - title = constant.ContentType2PushContent[constant.SignalMsg] - default: - title = constant.ContentType2PushContent[constant.Common] - } - } - if content == "" { - content = title - } - return -} diff --git a/internal/push/tools.go b/internal/push/tools.go deleted file mode 100644 index 760c8c95b..000000000 --- a/internal/push/tools.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package push - -import ( - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - "google.golang.org/protobuf/proto" -) - -func GetContent(msg *sdkws.MsgData) string { - if msg.ContentType >= constant.NotificationBegin && msg.ContentType <= constant.NotificationEnd { - var tips sdkws.TipsComm - _ = proto.Unmarshal(msg.Content, &tips) - content := tips.JsonDetail - return content - } - return string(msg.Content) -} diff --git a/internal/rpc/auth/auth.go b/internal/rpc/auth/auth.go index eb1e2f68a..c6d236b21 100644 --- a/internal/rpc/auth/auth.go +++ b/internal/rpc/auth/auth.go @@ -16,45 +16,54 @@ package auth import ( "context" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/db/redisutil" + "github.com/redis/go-redis/v9" - pbauth "github.com/OpenIMSDK/protocol/auth" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msggateway" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/tokenverify" "github.com/openimsdk/open-im-server/v3/pkg/authverify" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + pbauth "github.com/openimsdk/protocol/auth" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msggateway" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/tokenverify" "google.golang.org/grpc" ) type authServer struct { authDatabase controller.AuthDatabase userRpcClient *rpcclient.UserRpcClient - RegisterCenter discoveryregistry.SvcDiscoveryRegistry - config *config.GlobalConfig + RegisterCenter discovery.SvcDiscoveryRegistry + config *Config +} + +type Config struct { + RpcConfig config.Auth + RedisConfig config.Redis + ZookeeperConfig config.ZooKeeper + Share config.Share } -func Start(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error { - rdb, err := cache.NewRedis(config) +func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) if err != nil { return err } - userRpcClient := rpcclient.NewUserRpcClient(client, config) + userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID) pbauth.RegisterAuthServer(server, &authServer{ userRpcClient: &userRpcClient, RegisterCenter: client, authDatabase: controller.NewAuthDatabase( - cache.NewMsgCacheModel(rdb, config), - config.Secret, - config.TokenPolicy.Expire, - config, + cache.NewTokenCacheModel(rdb), + config.Share.Secret, + config.RpcConfig.TokenPolicy.Expire, ), config: config, }) @@ -63,8 +72,8 @@ func Start(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryReg func (s *authServer) UserToken(ctx context.Context, req *pbauth.UserTokenReq) (*pbauth.UserTokenResp, error) { resp := pbauth.UserTokenResp{} - if req.Secret != s.config.Secret { - return nil, errs.ErrNoPermission.Wrap("secret invalid") + if req.Secret != s.config.Share.Secret { + return nil, errs.ErrNoPermission.WrapMsg("secret invalid") } if _, err := s.userRpcClient.GetUserInfo(ctx, req.UserID); err != nil { return nil, err @@ -75,20 +84,19 @@ func (s *authServer) UserToken(ctx context.Context, req *pbauth.UserTokenReq) (* } prommetrics.UserLoginCounter.Inc() resp.Token = token - resp.ExpireTimeSeconds = s.config.TokenPolicy.Expire * 24 * 60 * 60 + resp.ExpireTimeSeconds = s.config.RpcConfig.TokenPolicy.Expire * 24 * 60 * 60 return &resp, nil } func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenReq) (*pbauth.GetUserTokenResp, error) { - if err := authverify.CheckAdmin(ctx, s.config); err != nil { + if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { return nil, err } resp := pbauth.GetUserTokenResp{} - if authverify.IsManagerUserID(req.UserID, s.config) { - return nil, errs.ErrNoPermission.Wrap("don't get Admin token") + if authverify.IsManagerUserID(req.UserID, s.config.Share.IMAdminUserID) { + return nil, errs.ErrNoPermission.WrapMsg("don't get Admin token") } - if _, err := s.userRpcClient.GetUserInfo(ctx, req.UserID); err != nil { return nil, err } @@ -97,12 +105,12 @@ func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenR return nil, err } resp.Token = token - resp.ExpireTimeSeconds = s.config.TokenPolicy.Expire * 24 * 60 * 60 + resp.ExpireTimeSeconds = s.config.RpcConfig.TokenPolicy.Expire * 24 * 60 * 60 return &resp, nil } func (s *authServer) parseToken(ctx context.Context, tokensString string) (claims *tokenverify.Claims, err error) { - claims, err = tokenverify.GetClaimFromToken(tokensString, authverify.Secret(s.config.Secret)) + claims, err = tokenverify.GetClaimFromToken(tokensString, authverify.Secret(s.config.Share.Secret)) if err != nil { return nil, errs.Wrap(err) } @@ -111,19 +119,19 @@ func (s *authServer) parseToken(ctx context.Context, tokensString string) (claim return nil, err } if len(m) == 0 { - return nil, errs.ErrTokenNotExist.Wrap() + return nil, servererrs.ErrTokenNotExist.Wrap() } if v, ok := m[tokensString]; ok { switch v { case constant.NormalToken: return claims, nil case constant.KickedToken: - return nil, errs.ErrTokenKicked.Wrap() + return nil, servererrs.ErrTokenKicked.Wrap() default: return nil, errs.Wrap(errs.ErrTokenUnknown) } } - return nil, errs.ErrTokenNotExist.Wrap() + return nil, servererrs.ErrTokenNotExist.Wrap() } func (s *authServer) ParseToken( @@ -136,13 +144,13 @@ func (s *authServer) ParseToken( return nil, err } resp.UserID = claims.UserID - resp.Platform = constant.PlatformIDToName(claims.PlatformID) + resp.PlatformID = int32(claims.PlatformID) resp.ExpireTimeSeconds = claims.ExpiresAt.Unix() return resp, nil } func (s *authServer) ForceLogout(ctx context.Context, req *pbauth.ForceLogoutReq) (*pbauth.ForceLogoutResp, error) { - if err := authverify.CheckAdmin(ctx, s.config); err != nil { + if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { return nil, err } if err := s.forceKickOff(ctx, req.UserID, req.PlatformID, mcontext.GetOperationID(ctx)); err != nil { @@ -152,7 +160,7 @@ func (s *authServer) ForceLogout(ctx context.Context, req *pbauth.ForceLogoutReq } func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID int32, operationID string) error { - conns, err := s.RegisterCenter.GetConns(ctx, s.config.RpcRegisterName.OpenImMessageGatewayName) + conns, err := s.RegisterCenter.GetConns(ctx, s.config.Share.RpcRegisterName.MessageGateway) if err != nil { return err } @@ -169,3 +177,27 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID } return nil } +func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.InvalidateTokenReq) (*pbauth.InvalidateTokenResp, error) { + m, err := s.authDatabase.GetTokensWithoutError(ctx, req.UserID, int(req.PlatformID)) + if err != nil && err != redis.Nil { + return nil, err + } + if m == nil { + return nil, errs.New("token map is empty").Wrap() + } + log.ZDebug(ctx, "get token from redis", "userID", req.UserID, "platformID", + req.PlatformID, "tokenMap", m) + + for k := range m { + if k != req.GetPreservedToken() { + m[k] = constant.KickedToken + } + } + log.ZDebug(ctx, "set token map is ", "token map", m, "userID", + req.UserID, "token", req.GetPreservedToken()) + err = s.authDatabase.SetTokenMapByUidPid(ctx, req.UserID, int(req.PlatformID), m) + if err != nil { + return nil, err + } + return &pbauth.InvalidateTokenResp{}, nil +} diff --git a/internal/rpc/conversation/conversaion.go b/internal/rpc/conversation/conversaion.go index e78e4a18f..1bf612923 100644 --- a/internal/rpc/conversation/conversaion.go +++ b/internal/rpc/conversation/conversaion.go @@ -16,26 +16,25 @@ package conversation import ( "context" - "errors" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/db/redisutil" "sort" - "github.com/OpenIMSDK/protocol/constant" - pbconversation "github.com/OpenIMSDK/protocol/conversation" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/tx" - "github.com/OpenIMSDK/tools/utils" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/convert" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/db/mgo" tablerelation "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" - "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" + "github.com/openimsdk/protocol/constant" + pbconversation "github.com/openimsdk/protocol/conversation" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" "google.golang.org/grpc" ) @@ -44,33 +43,43 @@ type conversationServer struct { user *rpcclient.UserRpcClient groupRpcClient *rpcclient.GroupRpcClient conversationDatabase controller.ConversationDatabase - conversationNotificationSender *notification.ConversationNotificationSender - config *config.GlobalConfig + conversationNotificationSender *ConversationNotificationSender + config *Config +} + +type Config struct { + RpcConfig config.Conversation + RedisConfig config.Redis + MongodbConfig config.Mongo + ZookeeperConfig config.ZooKeeper + NotificationConfig config.Notification + Share config.Share + LocalCacheConfig config.LocalCache } -func Start(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error { - rdb, err := cache.NewRedis(config) +func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { + mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) if err != nil { return err } - mongo, err := unrelation.NewMongo(config) + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) if err != nil { return err } - conversationDB, err := mgo.NewConversationMongo(mongo.GetDatabase(config.Mongo.Database)) + conversationDB, err := mgo.NewConversationMongo(mgocli.GetDB()) if err != nil { return err } - groupRpcClient := rpcclient.NewGroupRpcClient(client, config) - msgRpcClient := rpcclient.NewMessageRpcClient(client, config) - userRpcClient := rpcclient.NewUserRpcClient(client, config) + groupRpcClient := rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group) + msgRpcClient := rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg) + userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID) + cache.InitLocalCache(&config.LocalCacheConfig) pbconversation.RegisterConversationServer(server, &conversationServer{ msgRpcClient: &msgRpcClient, user: &userRpcClient, - conversationNotificationSender: notification.NewConversationNotificationSender(config, &msgRpcClient), + conversationNotificationSender: NewConversationNotificationSender(&config.NotificationConfig, &msgRpcClient), groupRpcClient: &groupRpcClient, - conversationDatabase: controller.NewConversationDatabase(conversationDB, cache.NewConversationRedis(rdb, cache.GetDefaultOpt(), conversationDB), tx.NewMongo(mongo.GetClient())), - config: config, + conversationDatabase: controller.NewConversationDatabase(conversationDB, cache.NewConversationRedis(rdb, &config.LocalCacheConfig, cache.GetDefaultOpt(), conversationDB), mgocli.GetTx()), }) return nil } @@ -81,7 +90,7 @@ func (c *conversationServer) GetConversation(ctx context.Context, req *pbconvers return nil, err } if len(conversations) < 1 { - return nil, errs.ErrRecordNotFound.Wrap("conversation not found") + return nil, errs.ErrRecordNotFound.WrapMsg("conversation not found") } resp := &pbconversation.GetConversationResp{Conversation: &pbconversation.Conversation{}} resp.Conversation = convert.ConversationDB2Pb(conversations[0]) @@ -158,7 +167,7 @@ func (c *conversationServer) GetSortedConversationList(ctx context.Context, req c.conversationSort(conversation_isPinTime, resp, conversation_unreadCount, conversationMsg) c.conversationSort(conversation_notPinTime, resp, conversation_unreadCount, conversationMsg) - resp.ConversationElems = utils.Paginate(resp.ConversationElems, int(req.Pagination.GetPageNumber()), int(req.Pagination.GetShowNumber())) + resp.ConversationElems = datautil.Paginate(resp.ConversationElems, int(req.Pagination.GetPageNumber()), int(req.Pagination.GetShowNumber())) return resp, nil } @@ -184,32 +193,30 @@ func (c *conversationServer) GetConversations(ctx context.Context, req *pbconver func (c *conversationServer) SetConversation(ctx context.Context, req *pbconversation.SetConversationReq) (*pbconversation.SetConversationResp, error) { var conversation tablerelation.ConversationModel - if err := utils.CopyStructFields(&conversation, req.Conversation); err != nil { + if err := datautil.CopyStructFields(&conversation, req.Conversation); err != nil { return nil, err } err := c.conversationDatabase.SetUserConversations(ctx, req.Conversation.OwnerUserID, []*tablerelation.ConversationModel{&conversation}) if err != nil { return nil, err } - _ = c.conversationNotificationSender.ConversationChangeNotification(ctx, req.Conversation.OwnerUserID, []string{req.Conversation.ConversationID}) + c.conversationNotificationSender.ConversationChangeNotification(ctx, req.Conversation.OwnerUserID, []string{req.Conversation.ConversationID}) resp := &pbconversation.SetConversationResp{} return resp, nil } // nolint -func (c *conversationServer) SetConversations(ctx context.Context, - req *pbconversation.SetConversationsReq, -) (*pbconversation.SetConversationsResp, error) { +func (c *conversationServer) SetConversations(ctx context.Context, req *pbconversation.SetConversationsReq) (*pbconversation.SetConversationsResp, error) { if req.Conversation == nil { - return nil, errs.ErrArgs.Wrap("conversation must not be nil") + return nil, errs.ErrArgs.WrapMsg("conversation must not be nil") } - if req.Conversation.ConversationType == constant.GroupChatType { + if req.Conversation.ConversationType == constant.WriteGroupChatType { groupInfo, err := c.groupRpcClient.GetGroupInfo(ctx, req.Conversation.GroupID) if err != nil { return nil, err } if groupInfo.Status == constant.GroupStatusDismissed { - return nil, errs.ErrDismissedAlready.Wrap("group dismissed") + return nil, servererrs.ErrDismissedAlready.WrapMsg("group dismissed") } } var unequal int @@ -220,7 +227,7 @@ func (c *conversationServer) SetConversations(ctx context.Context, return nil, err } if len(cs) == 0 { - return nil, errs.ErrRecordNotFound.Wrap("conversation not found") + return nil, errs.ErrRecordNotFound.WrapMsg("conversation not found") } conv = *cs[0] } @@ -272,7 +279,7 @@ func (c *conversationServer) SetConversations(ctx context.Context, unequal++ } } - if req.Conversation.IsPrivateChat != nil && req.Conversation.ConversationType != constant.SuperGroupChatType { + if req.Conversation.IsPrivateChat != nil && req.Conversation.ConversationType != constant.ReadGroupChatType { var conversations []*tablerelation.ConversationModel for _, ownerUserID := range req.UserIDs { conversation2 := conversation @@ -280,45 +287,39 @@ func (c *conversationServer) SetConversations(ctx context.Context, conversation2.IsPrivateChat = req.Conversation.IsPrivateChat.Value conversations = append(conversations, &conversation2) } + if err := c.conversationDatabase.SyncPeerUserPrivateConversationTx(ctx, conversations); err != nil { return nil, err } for _, userID := range req.UserIDs { - err := c.conversationNotificationSender.ConversationSetPrivateNotification(ctx, userID, req.Conversation.UserID, + c.conversationNotificationSender.ConversationSetPrivateNotification(ctx, userID, req.Conversation.UserID, req.Conversation.IsPrivateChat.Value, req.Conversation.ConversationID) - if err != nil { - log.ZWarn(ctx, "send conversation set private notification failed", err, - "userID", userID, "conversationID", req.Conversation.ConversationID) - - continue - } } } + if req.Conversation.BurnDuration != nil { m["burn_duration"] = req.Conversation.BurnDuration.Value if req.Conversation.BurnDuration.Value != conv.BurnDuration { unequal++ } } + if err := c.conversationDatabase.SetUsersConversationFieldTx(ctx, req.UserIDs, &conversation, m); err != nil { return nil, err } + if unequal > 0 { for _, v := range req.UserIDs { c.conversationNotificationSender.ConversationChangeNotification(ctx, v, []string{req.Conversation.ConversationID}) } } + return &pbconversation.SetConversationsResp{}, nil } // Get user IDs with "Do Not Disturb" enabled in super large groups. func (c *conversationServer) GetRecvMsgNotNotifyUserIDs(ctx context.Context, req *pbconversation.GetRecvMsgNotNotifyUserIDsReq) (*pbconversation.GetRecvMsgNotNotifyUserIDsResp, error) { - //userIDs, err := c.conversationDatabase.FindRecvMsgNotNotifyUserIDs(ctx, req.GroupID) - //if err != nil { - // return nil, err - //} - //return &pbconversation.GetRecvMsgNotNotifyUserIDsResp{UserIDs: userIDs}, nil - return nil, errors.New("deprecated") + return nil, errs.New("deprecated") } // create conversation without notification for msg redis transfer. @@ -402,12 +403,9 @@ func (c *conversationServer) GetConversationsByConversationID( return &pbconversation.GetConversationsByConversationIDResp{Conversations: convert.ConversationsDB2Pb(conversations)}, nil } -func (c *conversationServer) GetConversationOfflinePushUserIDs( - ctx context.Context, - req *pbconversation.GetConversationOfflinePushUserIDsReq, -) (*pbconversation.GetConversationOfflinePushUserIDsResp, error) { +func (c *conversationServer) GetConversationOfflinePushUserIDs(ctx context.Context, req *pbconversation.GetConversationOfflinePushUserIDsReq) (*pbconversation.GetConversationOfflinePushUserIDsResp, error) { if req.ConversationID == "" { - return nil, errs.ErrArgs.Wrap("conversationID is empty") + return nil, errs.ErrArgs.WrapMsg("conversationID is empty") } if len(req.UserIDs) == 0 { return &pbconversation.GetConversationOfflinePushUserIDsResp{}, nil @@ -426,21 +424,16 @@ func (c *conversationServer) GetConversationOfflinePushUserIDs( for _, userID := range userIDs { delete(userIDSet, userID) } - return &pbconversation.GetConversationOfflinePushUserIDsResp{UserIDs: utils.Keys(userIDSet)}, nil + return &pbconversation.GetConversationOfflinePushUserIDsResp{UserIDs: datautil.Keys(userIDSet)}, nil } -func (c *conversationServer) conversationSort( - conversations map[int64]string, - resp *pbconversation.GetSortedConversationListResp, - conversation_unreadCount map[string]int64, - conversationMsg map[string]*pbconversation.ConversationElem, -) { +func (c *conversationServer) conversationSort(conversations map[int64]string, resp *pbconversation.GetSortedConversationListResp, conversation_unreadCount map[string]int64, conversationMsg map[string]*pbconversation.ConversationElem) { keys := []int64{} for key := range conversations { keys = append(keys, key) } - sort.Slice(keys[:], func(i, j int) bool { + sort.Slice(keys, func(i, j int) bool { return keys[i] > keys[j] }) index := 0 @@ -474,7 +467,7 @@ func (c *conversationServer) getConversationInfo( sendIDs = append(sendIDs, chatLog.RecvID) } sendIDs = append(sendIDs, chatLog.SendID) - case constant.GroupChatType, constant.SuperGroupChatType: + case constant.WriteGroupChatType, constant.ReadGroupChatType: groupIDs = append(groupIDs, chatLog.GroupID) sendIDs = append(sendIDs, chatLog.SendID) } @@ -500,7 +493,7 @@ func (c *conversationServer) getConversationInfo( for conversationID, chatLog := range chatLogs { pbchatLog := &pbconversation.ConversationElem{} msgInfo := &pbconversation.MsgInfo{} - if err := utils.CopyStructFields(msgInfo, chatLog); err != nil { + if err := datautil.CopyStructFields(msgInfo, chatLog); err != nil { return nil, err } switch chatLog.SessionType { @@ -516,7 +509,7 @@ func (c *conversationServer) getConversationInfo( msgInfo.FaceURL = send.FaceURL msgInfo.SenderName = send.Nickname } - case constant.GroupChatType, constant.SuperGroupChatType: + case constant.WriteGroupChatType, constant.ReadGroupChatType: msgInfo.GroupID = chatLog.GroupID if group, ok := groupMap[chatLog.GroupID]; ok { msgInfo.GroupName = group.GroupName @@ -536,10 +529,7 @@ func (c *conversationServer) getConversationInfo( return conversationMsg, nil } -func (c *conversationServer) GetConversationNotReceiveMessageUserIDs( - ctx context.Context, - req *pbconversation.GetConversationNotReceiveMessageUserIDsReq, -) (*pbconversation.GetConversationNotReceiveMessageUserIDsResp, error) { +func (c *conversationServer) GetConversationNotReceiveMessageUserIDs(ctx context.Context, req *pbconversation.GetConversationNotReceiveMessageUserIDsReq) (*pbconversation.GetConversationNotReceiveMessageUserIDsResp, error) { userIDs, err := c.conversationDatabase.GetConversationNotReceiveMessageUserIDs(ctx, req.ConversationID) if err != nil { return nil, err diff --git a/pkg/rpcclient/notification/conversation.go b/internal/rpc/conversation/notification.go similarity index 74% rename from pkg/rpcclient/notification/conversation.go rename to internal/rpc/conversation/notification.go index 1544d6a1f..994e1d57a 100644 --- a/pkg/rpcclient/notification/conversation.go +++ b/internal/rpc/conversation/notification.go @@ -12,29 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -package notification +package conversation import ( "context" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" ) type ConversationNotificationSender struct { *rpcclient.NotificationSender } -func NewConversationNotificationSender(config *config.GlobalConfig, msgRpcClient *rpcclient.MessageRpcClient) *ConversationNotificationSender { - return &ConversationNotificationSender{rpcclient.NewNotificationSender(config, rpcclient.WithRpcClient(msgRpcClient))} +func NewConversationNotificationSender(conf *config.Notification, msgRpcClient *rpcclient.MessageRpcClient) *ConversationNotificationSender { + return &ConversationNotificationSender{rpcclient.NewNotificationSender(conf, rpcclient.WithRpcClient(msgRpcClient))} } // SetPrivate invote. func (c *ConversationNotificationSender) ConversationSetPrivateNotification(ctx context.Context, sendID, recvID string, isPrivateChat bool, conversationID string, -) error { +) { tips := &sdkws.ConversationSetPrivateTips{ RecvID: recvID, SendID: sendID, @@ -42,23 +42,23 @@ func (c *ConversationNotificationSender) ConversationSetPrivateNotification(ctx ConversationID: conversationID, } - return c.Notification(ctx, sendID, recvID, constant.ConversationPrivateChatNotification, tips) + c.Notification(ctx, sendID, recvID, constant.ConversationPrivateChatNotification, tips) } -func (c *ConversationNotificationSender) ConversationChangeNotification(ctx context.Context, userID string, conversationIDs []string) error { +func (c *ConversationNotificationSender) ConversationChangeNotification(ctx context.Context, userID string, conversationIDs []string) { tips := &sdkws.ConversationUpdateTips{ UserID: userID, ConversationIDList: conversationIDs, } - return c.Notification(ctx, userID, userID, constant.ConversationChangeNotification, tips) + c.Notification(ctx, userID, userID, constant.ConversationChangeNotification, tips) } func (c *ConversationNotificationSender) ConversationUnreadChangeNotification( ctx context.Context, userID, conversationID string, unreadCountTime, hasReadSeq int64, -) error { +) { tips := &sdkws.ConversationHasReadTips{ UserID: userID, ConversationID: conversationID, @@ -66,5 +66,5 @@ func (c *ConversationNotificationSender) ConversationUnreadChangeNotification( UnreadCountTime: unreadCountTime, } - return c.Notification(ctx, userID, userID, constant.ConversationUnreadNotification, tips) + c.Notification(ctx, userID, userID, constant.ConversationUnreadNotification, tips) } diff --git a/internal/rpc/friend/black.go b/internal/rpc/friend/black.go index 4e130360c..1f52286f3 100644 --- a/internal/rpc/friend/black.go +++ b/internal/rpc/friend/black.go @@ -18,11 +18,11 @@ import ( "context" "time" - pbfriend "github.com/OpenIMSDK/protocol/friend" - "github.com/OpenIMSDK/tools/mcontext" "github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/common/convert" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + pbfriend "github.com/openimsdk/protocol/friend" + "github.com/openimsdk/tools/mcontext" ) func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *pbfriend.GetPaginationBlacksReq) (resp *pbfriend.GetPaginationBlacksResp, err error) { @@ -57,15 +57,18 @@ func (s *friendServer) RemoveBlack(ctx context.Context, req *pbfriend.RemoveBlac if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil { return nil, err } + if err := s.blackDatabase.Delete(ctx, []*relation.BlackModel{{OwnerUserID: req.OwnerUserID, BlockUserID: req.BlackUserID}}); err != nil { return nil, err } + s.notificationSender.BlackDeletedNotification(ctx, req) + return &pbfriend.RemoveBlackResp{}, nil } func (s *friendServer) AddBlack(ctx context.Context, req *pbfriend.AddBlackReq) (*pbfriend.AddBlackResp, error) { - if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config); err != nil { + if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil { return nil, err } _, err := s.userRpcClient.GetUsersInfo(ctx, []string{req.OwnerUserID, req.BlackUserID}) @@ -83,10 +86,6 @@ func (s *friendServer) AddBlack(ctx context.Context, req *pbfriend.AddBlackReq) if err := s.blackDatabase.Create(ctx, []*relation.BlackModel{&black}); err != nil { return nil, err } - - if err := s.notificationSender.BlackAddedNotification(ctx, req); err != nil { - return nil, err - } - + s.notificationSender.BlackAddedNotification(ctx, req) return &pbfriend.AddBlackResp{}, nil } diff --git a/internal/rpc/friend/callback.go b/internal/rpc/friend/callback.go index 78d4fc926..0610cdb78 100644 --- a/internal/rpc/friend/callback.go +++ b/internal/rpc/friend/callback.go @@ -16,85 +16,41 @@ package friend import ( "context" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" - pbfriend "github.com/OpenIMSDK/protocol/friend" - "github.com/OpenIMSDK/tools/utils" cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/http" + pbfriend "github.com/openimsdk/protocol/friend" ) -func CallbackBeforeAddFriend(ctx context.Context, globalConfig *config.GlobalConfig, req *pbfriend.ApplyToAddFriendReq) error { - if !globalConfig.Callback.CallbackBeforeAddFriend.Enable { - return nil - } - cbReq := &cbapi.CallbackBeforeAddFriendReq{ - CallbackCommand: cbapi.CallbackBeforeAddFriendCommand, - FromUserID: req.FromUserID, - ToUserID: req.ToUserID, - ReqMsg: req.ReqMsg, - Ex: req.Ex, - } - resp := &cbapi.CallbackBeforeAddFriendResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeAddFriend); err != nil { - return err - } - return nil -} - -func CallbackBeforeSetFriendRemark(ctx context.Context, globalConfig *config.GlobalConfig, req *pbfriend.SetFriendRemarkReq) error { - if !globalConfig.Callback.CallbackBeforeSetFriendRemark.Enable { - return nil - } - cbReq := &cbapi.CallbackBeforeSetFriendRemarkReq{ - CallbackCommand: cbapi.CallbackBeforeSetFriendRemark, +func (s *friendServer) webhookAfterDeleteFriend(ctx context.Context, after *config.AfterConfig, req *pbfriend.DeleteFriendReq) { + cbReq := &cbapi.CallbackAfterDeleteFriendReq{ + CallbackCommand: cbapi.CallbackAfterDeleteFriendCommand, OwnerUserID: req.OwnerUserID, FriendUserID: req.FriendUserID, - Remark: req.Remark, - } - resp := &cbapi.CallbackBeforeSetFriendRemarkResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeAddFriend); err != nil { - return err } - utils.NotNilReplace(&req.Remark, &resp.Remark) - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterDeleteFriendResp{}, after) } -func CallbackAfterSetFriendRemark(ctx context.Context, globalConfig *config.GlobalConfig, req *pbfriend.SetFriendRemarkReq) error { - if !globalConfig.Callback.CallbackAfterSetFriendRemark.Enable { - return nil - } - cbReq := &cbapi.CallbackAfterSetFriendRemarkReq{ - CallbackCommand: cbapi.CallbackAfterSetFriendRemark, - OwnerUserID: req.OwnerUserID, - FriendUserID: req.FriendUserID, - Remark: req.Remark, - } - resp := &cbapi.CallbackAfterSetFriendRemarkResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeAddFriend); err != nil { - return err - } - return nil -} -func CallbackBeforeAddBlack(ctx context.Context, globalConfig *config.GlobalConfig, req *pbfriend.AddBlackReq) error { - if !globalConfig.Callback.CallbackBeforeAddBlack.Enable { +func (s *friendServer) webhookBeforeAddFriend(ctx context.Context, before *config.BeforeConfig, req *pbfriend.ApplyToAddFriendReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &cbapi.CallbackBeforeAddFriendReq{ + CallbackCommand: cbapi.CallbackBeforeAddFriendCommand, + FromUserID: req.FromUserID, + ToUserID: req.ToUserID, + ReqMsg: req.ReqMsg, + Ex: req.Ex, + } + resp := &cbapi.CallbackBeforeAddFriendResp{} + + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } return nil - } - cbReq := &cbapi.CallbackBeforeAddBlackReq{ - CallbackCommand: cbapi.CallbackBeforeAddBlackCommand, - OwnerUserID: req.OwnerUserID, - BlackUserID: req.BlackUserID, - } - resp := &cbapi.CallbackBeforeAddBlackResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeAddBlack); err != nil { - return err - } - return nil + }) } -func CallbackAfterAddFriend(ctx context.Context, globalConfig *config.GlobalConfig, req *pbfriend.ApplyToAddFriendReq) error { - if !globalConfig.Callback.CallbackAfterAddFriend.Enable { - return nil - } + +func (s *friendServer) webhookAfterAddFriend(ctx context.Context, after *config.AfterConfig, req *pbfriend.ApplyToAddFriendReq) { cbReq := &cbapi.CallbackAfterAddFriendReq{ CallbackCommand: cbapi.CallbackAfterAddFriendCommand, FromUserID: req.FromUserID, @@ -102,90 +58,100 @@ func CallbackAfterAddFriend(ctx context.Context, globalConfig *config.GlobalConf ReqMsg: req.ReqMsg, } resp := &cbapi.CallbackAfterAddFriendResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackAfterAddFriend); err != nil { - return err - } - - return nil -} -func CallbackBeforeAddFriendAgree(ctx context.Context, globalConfig *config.GlobalConfig, req *pbfriend.RespondFriendApplyReq) error { - if !globalConfig.Callback.CallbackBeforeAddFriendAgree.Enable { - return nil - } - cbReq := &cbapi.CallbackBeforeAddFriendAgreeReq{ - CallbackCommand: cbapi.CallbackBeforeAddFriendAgreeCommand, - FromUserID: req.FromUserID, - ToUserID: req.ToUserID, - HandleMsg: req.HandleMsg, - HandleResult: req.HandleResult, - } - resp := &cbapi.CallbackBeforeAddFriendAgreeResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeAddFriendAgree); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after) } -func CallbackAfterDeleteFriend(ctx context.Context, globalConfig *config.GlobalConfig, req *pbfriend.DeleteFriendReq) error { - if !globalConfig.Callback.CallbackAfterDeleteFriend.Enable { - return nil - } - cbReq := &cbapi.CallbackAfterDeleteFriendReq{ - CallbackCommand: cbapi.CallbackAfterDeleteFriendCommand, + +func (s *friendServer) webhookAfterSetFriendRemark(ctx context.Context, after *config.AfterConfig, req *pbfriend.SetFriendRemarkReq) { + + cbReq := &cbapi.CallbackAfterSetFriendRemarkReq{ + CallbackCommand: cbapi.CallbackAfterSetFriendRemarkCommand, OwnerUserID: req.OwnerUserID, FriendUserID: req.FriendUserID, + Remark: req.Remark, } - resp := &cbapi.CallbackAfterDeleteFriendResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackAfterDeleteFriend); err != nil { - return err - } - return nil -} -func CallbackBeforeImportFriends(ctx context.Context, globalConfig *config.GlobalConfig, req *pbfriend.ImportFriendReq) error { - if !globalConfig.Callback.CallbackBeforeImportFriends.Enable { - return nil - } - cbReq := &cbapi.CallbackBeforeImportFriendsReq{ - CallbackCommand: cbapi.CallbackBeforeImportFriendsCommand, - OwnerUserID: req.OwnerUserID, - FriendUserIDs: req.FriendUserIDs, - } - resp := &cbapi.CallbackBeforeImportFriendsResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeImportFriends); err != nil { - return err - } - if len(resp.FriendUserIDs) != 0 { - req.FriendUserIDs = resp.FriendUserIDs - } - return nil + resp := &cbapi.CallbackAfterSetFriendRemarkResp{} + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after) } -func CallbackAfterImportFriends(ctx context.Context, globalConfig *config.GlobalConfig, req *pbfriend.ImportFriendReq) error { - if !globalConfig.Callback.CallbackAfterImportFriends.Enable { - return nil - } + +func (s *friendServer) webhookAfterImportFriends(ctx context.Context, after *config.AfterConfig, req *pbfriend.ImportFriendReq) { cbReq := &cbapi.CallbackAfterImportFriendsReq{ CallbackCommand: cbapi.CallbackAfterImportFriendsCommand, OwnerUserID: req.OwnerUserID, FriendUserIDs: req.FriendUserIDs, } resp := &cbapi.CallbackAfterImportFriendsResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackAfterImportFriends); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after) } -func CallbackAfterRemoveBlack(ctx context.Context, globalConfig *config.GlobalConfig, req *pbfriend.RemoveBlackReq) error { - if !globalConfig.Callback.CallbackAfterRemoveBlack.Enable { - return nil - } +func (s *friendServer) webhookAfterRemoveBlack(ctx context.Context, after *config.AfterConfig, req *pbfriend.RemoveBlackReq) { cbReq := &cbapi.CallbackAfterRemoveBlackReq{ CallbackCommand: cbapi.CallbackAfterRemoveBlackCommand, OwnerUserID: req.OwnerUserID, BlackUserID: req.BlackUserID, } resp := &cbapi.CallbackAfterRemoveBlackResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackAfterRemoveBlack); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after) +} + +func (s *friendServer) webhookBeforeSetFriendRemark(ctx context.Context, before *config.BeforeConfig, req *pbfriend.SetFriendRemarkReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &cbapi.CallbackBeforeSetFriendRemarkReq{ + CallbackCommand: cbapi.CallbackBeforeSetFriendRemarkCommand, + OwnerUserID: req.OwnerUserID, + FriendUserID: req.FriendUserID, + Remark: req.Remark, + } + resp := &cbapi.CallbackBeforeSetFriendRemarkResp{} + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } + if resp.Remark != "" { + req.Remark = resp.Remark + } + return nil + }) +} + +func (s *friendServer) webhookBeforeAddBlack(ctx context.Context, before *config.BeforeConfig, req *pbfriend.AddBlackReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &cbapi.CallbackBeforeAddBlackReq{ + CallbackCommand: cbapi.CallbackBeforeAddBlackCommand, + OwnerUserID: req.OwnerUserID, + BlackUserID: req.BlackUserID, + } + resp := &cbapi.CallbackBeforeAddBlackResp{} + return s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before) + }) +} + +func (s *friendServer) webhookBeforeAddFriendAgree(ctx context.Context, before *config.BeforeConfig, req *pbfriend.RespondFriendApplyReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &cbapi.CallbackBeforeAddFriendAgreeReq{ + CallbackCommand: cbapi.CallbackBeforeAddFriendAgreeCommand, + FromUserID: req.FromUserID, + ToUserID: req.ToUserID, + HandleMsg: req.HandleMsg, + HandleResult: req.HandleResult, + } + resp := &cbapi.CallbackBeforeAddFriendAgreeResp{} + return s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before) + }) +} + +func (s *friendServer) webhookBeforeImportFriends(ctx context.Context, before *config.BeforeConfig, req *pbfriend.ImportFriendReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &cbapi.CallbackBeforeImportFriendsReq{ + CallbackCommand: cbapi.CallbackBeforeImportFriendsCommand, + OwnerUserID: req.OwnerUserID, + FriendUserIDs: req.FriendUserIDs, + } + resp := &cbapi.CallbackBeforeImportFriendsResp{} + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } + if len(resp.FriendUserIDs) > 0 { + req.FriendUserIDs = resp.FriendUserIDs + } + return nil + }) } diff --git a/internal/rpc/friend/friend.go b/internal/rpc/friend/friend.go index 4df4085a9..bffda3c04 100644 --- a/internal/rpc/friend/friend.go +++ b/internal/rpc/friend/friend.go @@ -16,25 +16,25 @@ package friend import ( "context" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" + "github.com/openimsdk/tools/db/redisutil" - "github.com/OpenIMSDK/protocol/constant" - pbfriend "github.com/OpenIMSDK/protocol/friend" - "github.com/OpenIMSDK/protocol/sdkws" - registry "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/tx" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/authverify" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/convert" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/db/mgo" tablerelation "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" - "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" + "github.com/openimsdk/protocol/constant" + pbfriend "github.com/openimsdk/protocol/friend" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/utils/datautil" "google.golang.org/grpc" ) @@ -42,67 +42,79 @@ type friendServer struct { friendDatabase controller.FriendDatabase blackDatabase controller.BlackDatabase userRpcClient *rpcclient.UserRpcClient - notificationSender *notification.FriendNotificationSender + notificationSender *FriendNotificationSender conversationRpcClient rpcclient.ConversationRpcClient - RegisterCenter registry.SvcDiscoveryRegistry - config *config.GlobalConfig + RegisterCenter discovery.SvcDiscoveryRegistry + config *Config + webhookClient *webhook.Client +} + +type Config struct { + RpcConfig config.Friend + RedisConfig config.Redis + MongodbConfig config.Mongo + ZookeeperConfig config.ZooKeeper + NotificationConfig config.Notification + Share config.Share + WebhooksConfig config.Webhooks + LocalCacheConfig config.LocalCache } -func Start(config *config.GlobalConfig, client registry.SvcDiscoveryRegistry, server *grpc.Server) error { - // Initialize MongoDB - mongo, err := unrelation.NewMongo(config) +func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { + mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) if err != nil { return err } - - // Initialize Redis - rdb, err := cache.NewRedis(config) + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) if err != nil { return err } - friendMongoDB, err := mgo.NewFriendMongo(mongo.GetDatabase(config.Mongo.Database)) + friendMongoDB, err := mgo.NewFriendMongo(mgocli.GetDB()) if err != nil { return err } - friendRequestMongoDB, err := mgo.NewFriendRequestMongo(mongo.GetDatabase(config.Mongo.Database)) + friendRequestMongoDB, err := mgo.NewFriendRequestMongo(mgocli.GetDB()) if err != nil { return err } - blackMongoDB, err := mgo.NewBlackMongo(mongo.GetDatabase(config.Mongo.Database)) + blackMongoDB, err := mgo.NewBlackMongo(mgocli.GetDB()) if err != nil { return err } // Initialize RPC clients - userRpcClient := rpcclient.NewUserRpcClient(client, config) - msgRpcClient := rpcclient.NewMessageRpcClient(client, config) + userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID) + msgRpcClient := rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg) // Initialize notification sender - notificationSender := notification.NewFriendNotificationSender( - config, + notificationSender := NewFriendNotificationSender( + &config.NotificationConfig, &msgRpcClient, - notification.WithRpcFunc(userRpcClient.GetUsersInfo), + WithRpcFunc(userRpcClient.GetUsersInfo), ) + cache.InitLocalCache(&config.LocalCacheConfig) + // Register Friend server with refactored MongoDB and Redis integrations pbfriend.RegisterFriendServer(server, &friendServer{ friendDatabase: controller.NewFriendDatabase( friendMongoDB, friendRequestMongoDB, - cache.NewFriendCacheRedis(rdb, friendMongoDB, cache.GetDefaultOpt()), - tx.NewMongo(mongo.GetClient()), + cache.NewFriendCacheRedis(rdb, &config.LocalCacheConfig, friendMongoDB, cache.GetDefaultOpt()), + mgocli.GetTx(), ), blackDatabase: controller.NewBlackDatabase( blackMongoDB, - cache.NewBlackCacheRedis(rdb, blackMongoDB, cache.GetDefaultOpt()), + cache.NewBlackCacheRedis(rdb, &config.LocalCacheConfig, blackMongoDB, cache.GetDefaultOpt()), ), userRpcClient: &userRpcClient, notificationSender: notificationSender, RegisterCenter: client, - conversationRpcClient: rpcclient.NewConversationRpcClient(client, config), + conversationRpcClient: rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation), config: config, + webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL), }) return nil @@ -111,18 +123,15 @@ func Start(config *config.GlobalConfig, client registry.SvcDiscoveryRegistry, se // ok. func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *pbfriend.ApplyToAddFriendReq) (resp *pbfriend.ApplyToAddFriendResp, err error) { resp = &pbfriend.ApplyToAddFriendResp{} - if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config); err != nil { + if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config.Share.IMAdminUserID); err != nil { return nil, err } - if req.ToUserID == req.FromUserID { - return nil, errs.ErrCanNotAddYourself.Wrap("req.ToUserID", req.ToUserID) + return nil, servererrs.ErrCanNotAddYourself.WrapMsg("req.ToUserID", req.ToUserID) } - - if err = CallbackBeforeAddFriend(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue { + if err = s.webhookBeforeAddFriend(ctx, &s.config.WebhooksConfig.BeforeAddFriend, req); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } - if _, err := s.userRpcClient.GetUsersInfoMap(ctx, []string{req.ToUserID, req.FromUserID}); err != nil { return nil, err } @@ -131,41 +140,33 @@ func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *pbfriend.Apply if err != nil { return nil, err } - if in1 && in2 { - return nil, errs.ErrRelationshipAlready.Wrap() + return nil, servererrs.ErrRelationshipAlready.WrapMsg("already friends has f") } - if err = s.friendDatabase.AddFriendRequest(ctx, req.FromUserID, req.ToUserID, req.ReqMsg, req.Ex); err != nil { return nil, err } - - if err = s.notificationSender.FriendApplicationAddNotification(ctx, req); err != nil { - return nil, err - } - - if err = CallbackAfterAddFriend(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue { - return nil, err - } + s.notificationSender.FriendApplicationAddNotification(ctx, req) + s.webhookAfterAddFriend(ctx, &s.config.WebhooksConfig.AfterAddFriend, req) return resp, nil } // ok. func (s *friendServer) ImportFriends(ctx context.Context, req *pbfriend.ImportFriendReq) (resp *pbfriend.ImportFriendResp, err error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") - if err := authverify.CheckAdmin(ctx, s.config); err != nil { + if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { return nil, err } if _, err := s.userRpcClient.GetUsersInfo(ctx, append([]string{req.OwnerUserID}, req.FriendUserIDs...)); err != nil { return nil, err } - if utils.Contain(req.OwnerUserID, req.FriendUserIDs...) { - return nil, errs.ErrCanNotAddYourself.Wrap() + if datautil.Contain(req.OwnerUserID, req.FriendUserIDs...) { + return nil, servererrs.ErrCanNotAddYourself.WrapMsg("can not add yourself") } - if utils.Duplicate(req.FriendUserIDs) { - return nil, errs.ErrArgs.Wrap("friend userID repeated") + if datautil.Duplicate(req.FriendUserIDs) { + return nil, errs.ErrArgs.WrapMsg("friend userID repeated") } - if err := CallbackBeforeImportFriends(ctx, s.config, req); err != nil { + + if err := s.webhookBeforeImportFriends(ctx, &s.config.WebhooksConfig.BeforeImportFriends, req); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } @@ -179,17 +180,15 @@ func (s *friendServer) ImportFriends(ctx context.Context, req *pbfriend.ImportFr HandleResult: constant.FriendResponseAgree, }) } - if err := CallbackAfterImportFriends(ctx, s.config, req); err != nil { - return nil, err - } + + s.webhookAfterImportFriends(ctx, &s.config.WebhooksConfig.AfterImportFriends, req) return &pbfriend.ImportFriendResp{}, nil } // ok. func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.RespondFriendApplyReq) (resp *pbfriend.RespondFriendApplyResp, err error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") resp = &pbfriend.RespondFriendApplyResp{} - if err := authverify.CheckAccessV3(ctx, req.ToUserID, s.config); err != nil { + if err := authverify.CheckAccessV3(ctx, req.ToUserID, s.config.Share.IMAdminUserID); err != nil { return nil, err } @@ -200,16 +199,14 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.Res HandleResult: req.HandleResult, } if req.HandleResult == constant.FriendResponseAgree { - if err := CallbackBeforeAddFriendAgree(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue { + if err := s.webhookBeforeAddFriendAgree(ctx, &s.config.WebhooksConfig.BeforeAddFriendAgree, req); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } err := s.friendDatabase.AgreeFriendRequest(ctx, &friendRequest) if err != nil { return nil, err } - if err := s.notificationSender.FriendApplicationAgreedNotification(ctx, req); err != nil { - return nil, err - } + s.notificationSender.FriendApplicationAgreedNotification(ctx, req) return resp, nil } if req.HandleResult == constant.FriendResponseRefuse { @@ -220,12 +217,11 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.Res s.notificationSender.FriendApplicationRefusedNotification(ctx, req) return resp, nil } - return nil, errs.ErrArgs.Wrap("req.HandleResult != -1/1") + return nil, errs.ErrArgs.WrapMsg("req.HandleResult != -1/1") } // ok. func (s *friendServer) DeleteFriend(ctx context.Context, req *pbfriend.DeleteFriendReq) (resp *pbfriend.DeleteFriendResp, err error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") resp = &pbfriend.DeleteFriendResp{} if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil { return nil, err @@ -238,17 +234,13 @@ func (s *friendServer) DeleteFriend(ctx context.Context, req *pbfriend.DeleteFri return nil, err } s.notificationSender.FriendDeletedNotification(ctx, req) - if err := CallbackAfterDeleteFriend(ctx, s.config, req); err != nil { - return nil, err - } + s.webhookAfterDeleteFriend(ctx, &s.config.WebhooksConfig.AfterDeleteFriend, req) return resp, nil } // ok. func (s *friendServer) SetFriendRemark(ctx context.Context, req *pbfriend.SetFriendRemarkReq) (resp *pbfriend.SetFriendRemarkResp, err error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") - - if err = CallbackBeforeSetFriendRemark(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue { + if err = s.webhookBeforeSetFriendRemark(ctx, &s.config.WebhooksConfig.BeforeSetFriendRemark, req); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } resp = &pbfriend.SetFriendRemarkResp{} @@ -262,19 +254,16 @@ func (s *friendServer) SetFriendRemark(ctx context.Context, req *pbfriend.SetFri if err := s.friendDatabase.UpdateRemark(ctx, req.OwnerUserID, req.FriendUserID, req.Remark); err != nil { return nil, err } - if err := CallbackAfterSetFriendRemark(ctx, s.config, req); err != nil && err != errs.ErrCallbackContinue { - return nil, err - } + s.webhookAfterSetFriendRemark(ctx, &s.config.WebhooksConfig.AfterSetFriendRemark, req) s.notificationSender.FriendRemarkSetNotification(ctx, req.OwnerUserID, req.FriendUserID) return resp, nil } // ok. func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *pbfriend.GetDesignatedFriendsReq) (resp *pbfriend.GetDesignatedFriendsResp, err error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") resp = &pbfriend.GetDesignatedFriendsResp{} - if utils.Duplicate(req.FriendUserIDs) { - return nil, errs.ErrArgs.Wrap("friend userID repeated") + if datautil.Duplicate(req.FriendUserIDs) { + return nil, errs.ErrArgs.WrapMsg("friend userID repeated") } friends, err := s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs) if err != nil { @@ -303,7 +292,6 @@ func (s *friendServer) GetDesignatedFriendsApply(ctx context.Context, // Get received friend requests (i.e., those initiated by others). func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *pbfriend.GetPaginationFriendsApplyToReq) (resp *pbfriend.GetPaginationFriendsApplyToResp, err error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") if err := s.userRpcClient.Access(ctx, req.UserID); err != nil { return nil, err } @@ -321,7 +309,6 @@ func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *pbf } func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *pbfriend.GetPaginationFriendsApplyFromReq) (resp *pbfriend.GetPaginationFriendsApplyFromResp, err error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") resp = &pbfriend.GetPaginationFriendsApplyFromResp{} if err := s.userRpcClient.Access(ctx, req.UserID); err != nil { return nil, err @@ -340,7 +327,6 @@ func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *p // ok. func (s *friendServer) IsFriend(ctx context.Context, req *pbfriend.IsFriendReq) (resp *pbfriend.IsFriendResp, err error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") resp = &pbfriend.IsFriendResp{} resp.InUser1Friends, resp.InUser2Friends, err = s.friendDatabase.CheckIn(ctx, req.UserID1, req.UserID2) if err != nil { @@ -350,7 +336,6 @@ func (s *friendServer) IsFriend(ctx context.Context, req *pbfriend.IsFriendReq) } func (s *friendServer) GetPaginationFriends(ctx context.Context, req *pbfriend.GetPaginationFriendsReq) (resp *pbfriend.GetPaginationFriendsResp, err error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") if err := s.userRpcClient.Access(ctx, req.UserID); err != nil { return nil, err } @@ -368,7 +353,6 @@ func (s *friendServer) GetPaginationFriends(ctx context.Context, req *pbfriend.G } func (s *friendServer) GetFriendIDs(ctx context.Context, req *pbfriend.GetFriendIDsReq) (resp *pbfriend.GetFriendIDsResp, err error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") if err := s.userRpcClient.Access(ctx, req.UserID); err != nil { return nil, err } @@ -382,10 +366,10 @@ func (s *friendServer) GetFriendIDs(ctx context.Context, req *pbfriend.GetFriend func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfriend.GetSpecifiedFriendsInfoReq) (*pbfriend.GetSpecifiedFriendsInfoResp, error) { if len(req.UserIDList) == 0 { - return nil, errs.ErrArgs.Wrap("userIDList is empty") + return nil, errs.ErrArgs.WrapMsg("userIDList is empty") } - if utils.Duplicate(req.UserIDList) { - return nil, errs.ErrArgs.Wrap("userIDList repeated") + if datautil.Duplicate(req.UserIDList) { + return nil, errs.ErrArgs.WrapMsg("userIDList repeated") } userMap, err := s.userRpcClient.GetUsersInfoMap(ctx, req.UserIDList) if err != nil { @@ -399,10 +383,10 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien if err != nil { return nil, err } - friendMap := utils.SliceToMap(friends, func(e *tablerelation.FriendModel) string { + friendMap := datautil.SliceToMap(friends, func(e *tablerelation.FriendModel) string { return e.FriendUserID }) - blackMap := utils.SliceToMap(blacks, func(e *tablerelation.BlackModel) string { + blackMap := datautil.SliceToMap(blacks, func(e *tablerelation.BlackModel) string { return e.BlockUserID }) resp := &pbfriend.GetSpecifiedFriendsInfoResp{ @@ -449,10 +433,10 @@ func (s *friendServer) UpdateFriends( req *pbfriend.UpdateFriendsReq, ) (*pbfriend.UpdateFriendsResp, error) { if len(req.FriendUserIDs) == 0 { - return nil, errs.ErrArgs.Wrap("friendIDList is empty") + return nil, errs.ErrArgs.WrapMsg("friendIDList is empty") } - if utils.Duplicate(req.FriendUserIDs) { - return nil, errs.ErrArgs.Wrap("friendIDList repeated") + if datautil.Duplicate(req.FriendUserIDs) { + return nil, errs.ErrArgs.WrapMsg("friendIDList repeated") } _, err := s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs) @@ -477,9 +461,6 @@ func (s *friendServer) UpdateFriends( resp := &pbfriend.UpdateFriendsResp{} - err = s.notificationSender.FriendsInfoUpdateNotification(ctx, req.OwnerUserID, req.FriendUserIDs) - if err != nil { - return nil, errs.Wrap(err, "FriendsInfoUpdateNotification Error") - } + s.notificationSender.FriendsInfoUpdateNotification(ctx, req.OwnerUserID, req.FriendUserIDs) return resp, nil } diff --git a/pkg/rpcclient/notification/friend.go b/internal/rpc/friend/notification.go similarity index 72% rename from pkg/rpcclient/notification/friend.go rename to internal/rpc/friend/notification.go index 751bdf475..f88c9664e 100644 --- a/pkg/rpcclient/notification/friend.go +++ b/internal/rpc/friend/notification.go @@ -12,26 +12,27 @@ // See the License for the specific language governing permissions and // limitations under the License. -package notification +package friend import ( "context" - "github.com/OpenIMSDK/protocol/constant" - pbfriend "github.com/OpenIMSDK/protocol/friend" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/mcontext" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/convert" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" + "github.com/openimsdk/protocol/constant" + pbfriend "github.com/openimsdk/protocol/friend" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/mcontext" ) type FriendNotificationSender struct { *rpcclient.NotificationSender // Target not found err - getUsersInfo func(ctx context.Context, userIDs []string) ([]CommonUser, error) + getUsersInfo func(ctx context.Context, userIDs []string) ([]notification.CommonUser, error) // db controller db controller.FriendDatabase } @@ -48,7 +49,7 @@ func WithDBFunc( fn func(ctx context.Context, userIDs []string) (users []*relationtb.UserModel, err error), ) friendNotificationSenderOptions { return func(s *FriendNotificationSender) { - f := func(ctx context.Context, userIDs []string) (result []CommonUser, err error) { + f := func(ctx context.Context, userIDs []string) (result []notification.CommonUser, err error) { users, err := fn(ctx, userIDs) if err != nil { return nil, err @@ -66,7 +67,7 @@ func WithRpcFunc( fn func(ctx context.Context, userIDs []string) ([]*sdkws.UserInfo, error), ) friendNotificationSenderOptions { return func(s *FriendNotificationSender) { - f := func(ctx context.Context, userIDs []string) (result []CommonUser, err error) { + f := func(ctx context.Context, userIDs []string) (result []notification.CommonUser, err error) { users, err := fn(ctx, userIDs) if err != nil { return nil, err @@ -81,12 +82,12 @@ func WithRpcFunc( } func NewFriendNotificationSender( - config *config.GlobalConfig, + conf *config.Notification, msgRpcClient *rpcclient.MessageRpcClient, opts ...friendNotificationSenderOptions, ) *FriendNotificationSender { f := &FriendNotificationSender{ - NotificationSender: rpcclient.NewNotificationSender(config, rpcclient.WithRpcClient(msgRpcClient)), + NotificationSender: rpcclient.NewNotificationSender(conf, rpcclient.WithRpcClient(msgRpcClient)), } for _, opt := range opts { opt(f) @@ -121,39 +122,39 @@ func (f *FriendNotificationSender) getFromToUserNickname( return users[fromUserID].Nickname, users[toUserID].Nickname, nil } -func (f *FriendNotificationSender) UserInfoUpdatedNotification(ctx context.Context, changedUserID string) error { +func (f *FriendNotificationSender) UserInfoUpdatedNotification(ctx context.Context, changedUserID string) { tips := sdkws.UserInfoUpdatedTips{UserID: changedUserID} - return f.Notification(ctx, mcontext.GetOpUserID(ctx), changedUserID, constant.UserInfoUpdatedNotification, &tips) + f.Notification(ctx, mcontext.GetOpUserID(ctx), changedUserID, constant.UserInfoUpdatedNotification, &tips) } -func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.Context, req *pbfriend.ApplyToAddFriendReq) error { +func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.Context, req *pbfriend.ApplyToAddFriendReq) { tips := sdkws.FriendApplicationTips{FromToUserID: &sdkws.FromToUserID{ FromUserID: req.FromUserID, ToUserID: req.ToUserID, }} - return f.Notification(ctx, req.FromUserID, req.ToUserID, constant.FriendApplicationNotification, &tips) + f.Notification(ctx, req.FromUserID, req.ToUserID, constant.FriendApplicationNotification, &tips) } func (f *FriendNotificationSender) FriendApplicationAgreedNotification( ctx context.Context, req *pbfriend.RespondFriendApplyReq, -) error { +) { tips := sdkws.FriendApplicationApprovedTips{FromToUserID: &sdkws.FromToUserID{ FromUserID: req.FromUserID, ToUserID: req.ToUserID, }, HandleMsg: req.HandleMsg} - return f.Notification(ctx, req.ToUserID, req.FromUserID, constant.FriendApplicationApprovedNotification, &tips) + f.Notification(ctx, req.ToUserID, req.FromUserID, constant.FriendApplicationApprovedNotification, &tips) } func (f *FriendNotificationSender) FriendApplicationRefusedNotification( ctx context.Context, req *pbfriend.RespondFriendApplyReq, -) error { +) { tips := sdkws.FriendApplicationApprovedTips{FromToUserID: &sdkws.FromToUserID{ FromUserID: req.FromUserID, ToUserID: req.ToUserID, }, HandleMsg: req.HandleMsg} - return f.Notification(ctx, req.ToUserID, req.FromUserID, constant.FriendApplicationRejectedNotification, &tips) + f.Notification(ctx, req.ToUserID, req.FromUserID, constant.FriendApplicationRejectedNotification, &tips) } func (f *FriendNotificationSender) FriendAddedNotification( @@ -177,34 +178,37 @@ func (f *FriendNotificationSender) FriendAddedNotification( if err != nil { return err } - return f.Notification(ctx, fromUserID, toUserID, constant.FriendAddedNotification, &tips) + f.Notification(ctx, fromUserID, toUserID, constant.FriendAddedNotification, &tips) + return nil } -func (f *FriendNotificationSender) FriendDeletedNotification(ctx context.Context, req *pbfriend.DeleteFriendReq) error { +func (f *FriendNotificationSender) FriendDeletedNotification(ctx context.Context, req *pbfriend.DeleteFriendReq) { tips := sdkws.FriendDeletedTips{FromToUserID: &sdkws.FromToUserID{ FromUserID: req.OwnerUserID, ToUserID: req.FriendUserID, }} - return f.Notification(ctx, req.OwnerUserID, req.FriendUserID, constant.FriendDeletedNotification, &tips) + f.Notification(ctx, req.OwnerUserID, req.FriendUserID, constant.FriendDeletedNotification, &tips) } -func (f *FriendNotificationSender) FriendRemarkSetNotification(ctx context.Context, fromUserID, toUserID string) error { +func (f *FriendNotificationSender) FriendRemarkSetNotification(ctx context.Context, fromUserID, toUserID string) { tips := sdkws.FriendInfoChangedTips{FromToUserID: &sdkws.FromToUserID{}} tips.FromToUserID.FromUserID = fromUserID tips.FromToUserID.ToUserID = toUserID - return f.Notification(ctx, fromUserID, toUserID, constant.FriendRemarkSetNotification, &tips) + f.Notification(ctx, fromUserID, toUserID, constant.FriendRemarkSetNotification, &tips) } -func (f *FriendNotificationSender) FriendsInfoUpdateNotification(ctx context.Context, toUserID string, friendIDs []string) error { + +func (f *FriendNotificationSender) FriendsInfoUpdateNotification(ctx context.Context, toUserID string, friendIDs []string) { tips := sdkws.FriendsInfoUpdateTips{FromToUserID: &sdkws.FromToUserID{}} tips.FromToUserID.ToUserID = toUserID tips.FriendIDs = friendIDs - return f.Notification(ctx, toUserID, toUserID, constant.FriendsInfoUpdateNotification, &tips) + f.Notification(ctx, toUserID, toUserID, constant.FriendsInfoUpdateNotification, &tips) } -func (f *FriendNotificationSender) BlackAddedNotification(ctx context.Context, req *pbfriend.AddBlackReq) error { + +func (f *FriendNotificationSender) BlackAddedNotification(ctx context.Context, req *pbfriend.AddBlackReq) { tips := sdkws.BlackAddedTips{FromToUserID: &sdkws.FromToUserID{}} tips.FromToUserID.FromUserID = req.OwnerUserID tips.FromToUserID.ToUserID = req.BlackUserID - return f.Notification(ctx, req.OwnerUserID, req.BlackUserID, constant.BlackAddedNotification, &tips) + f.Notification(ctx, req.OwnerUserID, req.BlackUserID, constant.BlackAddedNotification, &tips) } func (f *FriendNotificationSender) BlackDeletedNotification(ctx context.Context, req *pbfriend.RemoveBlackReq) { @@ -212,19 +216,10 @@ func (f *FriendNotificationSender) BlackDeletedNotification(ctx context.Context, FromUserID: req.OwnerUserID, ToUserID: req.BlackUserID, }} - if err := f.Notification(ctx, req.OwnerUserID, req.BlackUserID, constant.BlackDeletedNotification, &blackDeletedTips); err != nil { - //err - } + f.Notification(ctx, req.OwnerUserID, req.BlackUserID, constant.BlackDeletedNotification, &blackDeletedTips) } -func (f *FriendNotificationSender) FriendInfoUpdatedNotification( - ctx context.Context, - changedUserID string, - needNotifiedUserID string, -) { +func (f *FriendNotificationSender) FriendInfoUpdatedNotification(ctx context.Context, changedUserID string, needNotifiedUserID string) { tips := sdkws.UserInfoUpdatedTips{UserID: changedUserID} - if err := f.Notification(ctx, mcontext.GetOpUserID(ctx), needNotifiedUserID, - constant.FriendInfoUpdatedNotification, &tips); err != nil { - // err - } + f.Notification(ctx, mcontext.GetOpUserID(ctx), needNotifiedUserID, constant.FriendInfoUpdatedNotification, &tips) } diff --git a/internal/rpc/group/cache.go b/internal/rpc/group/cache.go index 35c631eff..022a0f4ef 100644 --- a/internal/rpc/group/cache.go +++ b/internal/rpc/group/cache.go @@ -17,27 +17,27 @@ package group import ( "context" - pbgroup "github.com/OpenIMSDK/protocol/group" "github.com/openimsdk/open-im-server/v3/pkg/common/convert" + pbgroup "github.com/openimsdk/protocol/group" ) -func (s *groupServer) GetGroupInfoCache( - ctx context.Context, - req *pbgroup.GetGroupInfoCacheReq, -) (resp *pbgroup.GetGroupInfoCacheResp, err error) { +// GetGroupInfoCache get group info from cache. +func (s *groupServer) GetGroupInfoCache(ctx context.Context, req *pbgroup.GetGroupInfoCacheReq) (*pbgroup.GetGroupInfoCacheResp, error) { group, err := s.db.TakeGroup(ctx, req.GroupID) if err != nil { return nil, err } - resp = &pbgroup.GetGroupInfoCacheResp{GroupInfo: convert.Db2PbGroupInfo(group, "", 0)} - return resp, nil + return &pbgroup.GetGroupInfoCacheResp{ + GroupInfo: convert.Db2PbGroupInfo(group, "", 0), + }, nil } -func (s *groupServer) GetGroupMemberCache(ctx context.Context, req *pbgroup.GetGroupMemberCacheReq) (resp *pbgroup.GetGroupMemberCacheResp, err error) { +func (s *groupServer) GetGroupMemberCache(ctx context.Context, req *pbgroup.GetGroupMemberCacheReq) (*pbgroup.GetGroupMemberCacheResp, error) { members, err := s.db.TakeGroupMember(ctx, req.GroupID, req.GroupMemberID) if err != nil { return nil, err } - resp = &pbgroup.GetGroupMemberCacheResp{Member: convert.Db2PbGroupMember(members)} - return resp, nil + return &pbgroup.GetGroupMemberCacheResp{ + Member: convert.Db2PbGroupMember(members), + }, nil } diff --git a/internal/rpc/group/callback.go b/internal/rpc/group/callback.go index e82177dde..1690e3973 100644 --- a/internal/rpc/group/callback.go +++ b/internal/rpc/group/callback.go @@ -16,70 +16,67 @@ package group import ( "context" - "time" - - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/group" - pbgroup "github.com/OpenIMSDK/protocol/group" - "github.com/OpenIMSDK/protocol/wrapperspb" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/apistruct" "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" - "github.com/openimsdk/open-im-server/v3/pkg/common/http" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/group" + "github.com/openimsdk/protocol/wrapperspb" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/datautil" + "time" ) -func CallbackBeforeCreateGroup(ctx context.Context, globalConfig *config.GlobalConfig, req *group.CreateGroupReq) (err error) { - if !globalConfig.Callback.CallbackBeforeCreateGroup.Enable { - return nil - } - cbReq := &callbackstruct.CallbackBeforeCreateGroupReq{ - CallbackCommand: callbackstruct.CallbackBeforeCreateGroupCommand, - OperationID: mcontext.GetOperationID(ctx), - GroupInfo: req.GroupInfo, - } - cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{ - UserID: req.OwnerUserID, - RoleLevel: constant.GroupOwner, - }) - for _, userID := range req.AdminUserIDs { - cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{ - UserID: userID, - RoleLevel: constant.GroupAdmin, - }) - } - for _, userID := range req.MemberUserIDs { +// CallbackBeforeCreateGroup callback before create group. +func (s *groupServer) webhookBeforeCreateGroup(ctx context.Context, before *config.BeforeConfig, req *group.CreateGroupReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &callbackstruct.CallbackBeforeCreateGroupReq{ + CallbackCommand: callbackstruct.CallbackBeforeCreateGroupCommand, + OperationID: mcontext.GetOperationID(ctx), + GroupInfo: req.GroupInfo, + } cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{ - UserID: userID, - RoleLevel: constant.GroupOrdinaryUsers, + UserID: req.OwnerUserID, + RoleLevel: constant.GroupOwner, }) - } - resp := &callbackstruct.CallbackBeforeCreateGroupResp{} - if err = http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeCreateGroup); err != nil { - return err - } - utils.NotNilReplace(&req.GroupInfo.GroupID, resp.GroupID) - utils.NotNilReplace(&req.GroupInfo.GroupName, resp.GroupName) - utils.NotNilReplace(&req.GroupInfo.Notification, resp.Notification) - utils.NotNilReplace(&req.GroupInfo.Introduction, resp.Introduction) - utils.NotNilReplace(&req.GroupInfo.FaceURL, resp.FaceURL) - utils.NotNilReplace(&req.GroupInfo.OwnerUserID, resp.OwnerUserID) - utils.NotNilReplace(&req.GroupInfo.Ex, resp.Ex) - utils.NotNilReplace(&req.GroupInfo.Status, resp.Status) - utils.NotNilReplace(&req.GroupInfo.CreatorUserID, resp.CreatorUserID) - utils.NotNilReplace(&req.GroupInfo.GroupType, resp.GroupType) - utils.NotNilReplace(&req.GroupInfo.NeedVerification, resp.NeedVerification) - utils.NotNilReplace(&req.GroupInfo.LookMemberInfo, resp.LookMemberInfo) - return nil -} + for _, userID := range req.AdminUserIDs { + cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{ + UserID: userID, + RoleLevel: constant.GroupAdmin, + }) + } + for _, userID := range req.MemberUserIDs { + cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{ + UserID: userID, + RoleLevel: constant.GroupOrdinaryUsers, + }) + } + resp := &callbackstruct.CallbackBeforeCreateGroupResp{} + + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } -func CallbackAfterCreateGroup(ctx context.Context, globalConfig *config.GlobalConfig, req *group.CreateGroupReq) (err error) { - if !globalConfig.Callback.CallbackAfterCreateGroup.Enable { + datautil.NotNilReplace(&req.GroupInfo.GroupID, resp.GroupID) + datautil.NotNilReplace(&req.GroupInfo.GroupName, resp.GroupName) + datautil.NotNilReplace(&req.GroupInfo.Notification, resp.Notification) + datautil.NotNilReplace(&req.GroupInfo.Introduction, resp.Introduction) + datautil.NotNilReplace(&req.GroupInfo.FaceURL, resp.FaceURL) + datautil.NotNilReplace(&req.GroupInfo.OwnerUserID, resp.OwnerUserID) + datautil.NotNilReplace(&req.GroupInfo.Ex, resp.Ex) + datautil.NotNilReplace(&req.GroupInfo.Status, resp.Status) + datautil.NotNilReplace(&req.GroupInfo.CreatorUserID, resp.CreatorUserID) + datautil.NotNilReplace(&req.GroupInfo.GroupType, resp.GroupType) + datautil.NotNilReplace(&req.GroupInfo.NeedVerification, resp.NeedVerification) + datautil.NotNilReplace(&req.GroupInfo.LookMemberInfo, resp.LookMemberInfo) return nil - } + }) +} + +func (s *groupServer) webhookAfterCreateGroup(ctx context.Context, after *config.AfterConfig, req *group.CreateGroupReq) { cbReq := &callbackstruct.CallbackAfterCreateGroupReq{ CallbackCommand: callbackstruct.CallbackAfterCreateGroupCommand, GroupInfo: req.GroupInfo, @@ -100,239 +97,163 @@ func CallbackAfterCreateGroup(ctx context.Context, globalConfig *config.GlobalCo RoleLevel: constant.GroupOrdinaryUsers, }) } - resp := &callbackstruct.CallbackAfterCreateGroupResp{} - if err = http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackAfterCreateGroup); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterCreateGroupResp{}, after) } -func CallbackBeforeMemberJoinGroup( - ctx context.Context, - globalConfig *config.GlobalConfig, - groupMember *relation.GroupMemberModel, - groupEx string, -) (err error) { - if !globalConfig.Callback.CallbackBeforeMemberJoinGroup.Enable { +func (s *groupServer) webhookBeforeMemberJoinGroup(ctx context.Context, before *config.BeforeConfig, groupMember *relation.GroupMemberModel, groupEx string) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &callbackstruct.CallbackBeforeMemberJoinGroupReq{ + CallbackCommand: callbackstruct.CallbackBeforeMemberJoinGroupCommand, + GroupID: groupMember.GroupID, + UserID: groupMember.UserID, + Ex: groupMember.Ex, + GroupEx: groupEx, + } + resp := &callbackstruct.CallbackBeforeMemberJoinGroupResp{} + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } + + if resp.MuteEndTime != nil { + groupMember.MuteEndTime = time.UnixMilli(*resp.MuteEndTime) + } + datautil.NotNilReplace(&groupMember.FaceURL, resp.FaceURL) + datautil.NotNilReplace(&groupMember.Ex, resp.Ex) + datautil.NotNilReplace(&groupMember.Nickname, resp.Nickname) + datautil.NotNilReplace(&groupMember.RoleLevel, resp.RoleLevel) return nil - } - callbackReq := &callbackstruct.CallbackBeforeMemberJoinGroupReq{ - CallbackCommand: callbackstruct.CallbackBeforeMemberJoinGroupCommand, - GroupID: groupMember.GroupID, - UserID: groupMember.UserID, - Ex: groupMember.Ex, - GroupEx: groupEx, - } - resp := &callbackstruct.CallbackBeforeMemberJoinGroupResp{} - err = http.CallBackPostReturn( - ctx, - globalConfig.Callback.CallbackUrl, - callbackReq, - resp, - globalConfig.Callback.CallbackBeforeMemberJoinGroup, - ) - if err != nil { - return err - } - if resp.MuteEndTime != nil { - groupMember.MuteEndTime = time.UnixMilli(*resp.MuteEndTime) - } - utils.NotNilReplace(&groupMember.FaceURL, resp.FaceURL) - utils.NotNilReplace(&groupMember.Ex, resp.Ex) - utils.NotNilReplace(&groupMember.Nickname, resp.Nickname) - utils.NotNilReplace(&groupMember.RoleLevel, resp.RoleLevel) - return nil + }) } -func CallbackBeforeSetGroupMemberInfo(ctx context.Context, globalConfig *config.GlobalConfig, req *group.SetGroupMemberInfo) (err error) { - if !globalConfig.Callback.CallbackBeforeSetGroupMemberInfo.Enable { +func (s *groupServer) webhookBeforeSetGroupMemberInfo(ctx context.Context, before *config.BeforeConfig, req *group.SetGroupMemberInfo) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := callbackstruct.CallbackBeforeSetGroupMemberInfoReq{ + CallbackCommand: callbackstruct.CallbackBeforeSetGroupMemberInfoCommand, + GroupID: req.GroupID, + UserID: req.UserID, + } + if req.Nickname != nil { + cbReq.Nickname = &req.Nickname.Value + } + if req.FaceURL != nil { + cbReq.FaceURL = &req.FaceURL.Value + } + if req.RoleLevel != nil { + cbReq.RoleLevel = &req.RoleLevel.Value + } + if req.Ex != nil { + cbReq.Ex = &req.Ex.Value + } + resp := &callbackstruct.CallbackBeforeSetGroupMemberInfoResp{} + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } + if resp.FaceURL != nil { + req.FaceURL = wrapperspb.String(*resp.FaceURL) + } + if resp.Nickname != nil { + req.Nickname = wrapperspb.String(*resp.Nickname) + } + if resp.RoleLevel != nil { + req.RoleLevel = wrapperspb.Int32(*resp.RoleLevel) + } + if resp.Ex != nil { + req.Ex = wrapperspb.String(*resp.Ex) + } return nil - } - callbackReq := callbackstruct.CallbackBeforeSetGroupMemberInfoReq{ - CallbackCommand: callbackstruct.CallbackBeforeSetGroupMemberInfoCommand, - GroupID: req.GroupID, - UserID: req.UserID, - } - if req.Nickname != nil { - callbackReq.Nickname = &req.Nickname.Value - } - if req.FaceURL != nil { - callbackReq.FaceURL = &req.FaceURL.Value - } - if req.RoleLevel != nil { - callbackReq.RoleLevel = &req.RoleLevel.Value - } - if req.Ex != nil { - callbackReq.Ex = &req.Ex.Value - } - resp := &callbackstruct.CallbackBeforeSetGroupMemberInfoResp{} - err = http.CallBackPostReturn( - ctx, - globalConfig.Callback.CallbackUrl, - callbackReq, - resp, - globalConfig.Callback.CallbackBeforeSetGroupMemberInfo, - ) - if err != nil { - return err - } - if resp.FaceURL != nil { - req.FaceURL = wrapperspb.String(*resp.FaceURL) - } - if resp.Nickname != nil { - req.Nickname = wrapperspb.String(*resp.Nickname) - } - if resp.RoleLevel != nil { - req.RoleLevel = wrapperspb.Int32(*resp.RoleLevel) - } - if resp.Ex != nil { - req.Ex = wrapperspb.String(*resp.Ex) - } - return nil + }) } -func CallbackAfterSetGroupMemberInfo(ctx context.Context, globalConfig *config.GlobalConfig, req *group.SetGroupMemberInfo) (err error) { - if !globalConfig.Callback.CallbackBeforeSetGroupMemberInfo.Enable { - return nil - } - callbackReq := callbackstruct.CallbackAfterSetGroupMemberInfoReq{ + +func (s *groupServer) webhookAfterSetGroupMemberInfo(ctx context.Context, after *config.AfterConfig, req *group.SetGroupMemberInfo) { + cbReq := callbackstruct.CallbackAfterSetGroupMemberInfoReq{ CallbackCommand: callbackstruct.CallbackAfterSetGroupMemberInfoCommand, GroupID: req.GroupID, UserID: req.UserID, } if req.Nickname != nil { - callbackReq.Nickname = &req.Nickname.Value + cbReq.Nickname = &req.Nickname.Value } if req.FaceURL != nil { - callbackReq.FaceURL = &req.FaceURL.Value + cbReq.FaceURL = &req.FaceURL.Value } if req.RoleLevel != nil { - callbackReq.RoleLevel = &req.RoleLevel.Value + cbReq.RoleLevel = &req.RoleLevel.Value } if req.Ex != nil { - callbackReq.Ex = &req.Ex.Value + cbReq.Ex = &req.Ex.Value } - resp := &callbackstruct.CallbackAfterSetGroupMemberInfoResp{} - if err = http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, callbackReq, resp, globalConfig.Callback.CallbackAfterSetGroupMemberInfo); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterSetGroupMemberInfoResp{}, after) } -func CallbackQuitGroup(ctx context.Context, globalConfig *config.GlobalConfig, req *group.QuitGroupReq) (err error) { - if !globalConfig.Callback.CallbackQuitGroup.Enable { - return nil - } +func (s *groupServer) webhookAfterQuitGroup(ctx context.Context, after *config.AfterConfig, req *group.QuitGroupReq) { cbReq := &callbackstruct.CallbackQuitGroupReq{ - CallbackCommand: callbackstruct.CallbackQuitGroupCommand, + CallbackCommand: callbackstruct.CallbackAfterQuitGroupCommand, GroupID: req.GroupID, UserID: req.UserID, } - resp := &callbackstruct.CallbackQuitGroupResp{} - if err = http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackQuitGroup); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackQuitGroupResp{}, after) } -func CallbackKillGroupMember(ctx context.Context, globalConfig *config.GlobalConfig, req *pbgroup.KickGroupMemberReq) (err error) { - if !globalConfig.Callback.CallbackKillGroupMember.Enable { - return nil - } +func (s *groupServer) webhookAfterKickGroupMember(ctx context.Context, after *config.AfterConfig, req *group.KickGroupMemberReq) { cbReq := &callbackstruct.CallbackKillGroupMemberReq{ - CallbackCommand: callbackstruct.CallbackKillGroupCommand, + CallbackCommand: callbackstruct.CallbackAfterKickGroupCommand, GroupID: req.GroupID, KickedUserIDs: req.KickedUserIDs, } - resp := &callbackstruct.CallbackKillGroupMemberResp{} - if err = http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackQuitGroup); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackKillGroupMemberResp{}, after) } -func CallbackDismissGroup(ctx context.Context, globalConfig *config.GlobalConfig, req *callbackstruct.CallbackDisMissGroupReq) (err error) { - if !globalConfig.Callback.CallbackDismissGroup.Enable { - return nil - } - req.CallbackCommand = callbackstruct.CallbackDisMissGroupCommand - resp := &callbackstruct.CallbackDisMissGroupResp{} - if err = http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackQuitGroup); err != nil { - return err - } - return nil +func (s *groupServer) webhookAfterDismissGroup(ctx context.Context, after *config.AfterConfig, req *callbackstruct.CallbackDisMissGroupReq) { + req.CallbackCommand = callbackstruct.CallbackAfterDisMissGroupCommand + s.webhookClient.AsyncPost(ctx, req.GetCallbackCommand(), req, &callbackstruct.CallbackDisMissGroupResp{}, after) } -func CallbackApplyJoinGroupBefore(ctx context.Context, globalConfig *config.GlobalConfig, req *callbackstruct.CallbackJoinGroupReq) (err error) { - if !globalConfig.Callback.CallbackBeforeJoinGroup.Enable { +func (s *groupServer) webhookBeforeApplyJoinGroup(ctx context.Context, before *config.BeforeConfig, req *callbackstruct.CallbackJoinGroupReq) (err error) { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + req.CallbackCommand = callbackstruct.CallbackBeforeJoinGroupCommand + resp := &callbackstruct.CallbackJoinGroupResp{} + if err := s.webhookClient.SyncPost(ctx, req.GetCallbackCommand(), req, resp, before); err != nil { + return err + } return nil - } - - req.CallbackCommand = callbackstruct.CallbackBeforeJoinGroupCommand - - resp := &callbackstruct.CallbackJoinGroupResp{} - if err = http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackBeforeJoinGroup); err != nil { - return err - } - - return nil + }) } -func CallbackAfterTransferGroupOwner(ctx context.Context, globalConfig *config.GlobalConfig, req *pbgroup.TransferGroupOwnerReq) (err error) { - if !globalConfig.Callback.CallbackAfterTransferGroupOwner.Enable { - return nil - } - +func (s *groupServer) webhookAfterTransferGroupOwner(ctx context.Context, after *config.AfterConfig, req *group.TransferGroupOwnerReq) { cbReq := &callbackstruct.CallbackTransferGroupOwnerReq{ - CallbackCommand: callbackstruct.CallbackAfterTransferGroupOwner, + CallbackCommand: callbackstruct.CallbackAfterTransferGroupOwnerCommand, GroupID: req.GroupID, OldOwnerUserID: req.OldOwnerUserID, NewOwnerUserID: req.NewOwnerUserID, } - - resp := &callbackstruct.CallbackTransferGroupOwnerResp{} - if err = http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackAfterTransferGroupOwner); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackTransferGroupOwnerResp{}, after) } -func CallbackBeforeInviteUserToGroup(ctx context.Context, globalConfig *config.GlobalConfig, req *group.InviteUserToGroupReq) (err error) { - if !globalConfig.Callback.CallbackBeforeInviteUserToGroup.Enable { - return nil - } - callbackReq := &callbackstruct.CallbackBeforeInviteUserToGroupReq{ - CallbackCommand: callbackstruct.CallbackBeforeInviteJoinGroupCommand, - OperationID: mcontext.GetOperationID(ctx), - GroupID: req.GroupID, - Reason: req.Reason, - InvitedUserIDs: req.InvitedUserIDs, - } +func (s *groupServer) webhookBeforeInviteUserToGroup(ctx context.Context, before *config.BeforeConfig, req *group.InviteUserToGroupReq) (err error) { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &callbackstruct.CallbackBeforeInviteUserToGroupReq{ + CallbackCommand: callbackstruct.CallbackBeforeInviteJoinGroupCommand, + OperationID: mcontext.GetOperationID(ctx), + GroupID: req.GroupID, + Reason: req.Reason, + InvitedUserIDs: req.InvitedUserIDs, + } - resp := &callbackstruct.CallbackBeforeInviteUserToGroupResp{} - err = http.CallBackPostReturn( - ctx, - globalConfig.Callback.CallbackUrl, - callbackReq, - resp, - globalConfig.Callback.CallbackBeforeInviteUserToGroup, - ) + resp := &callbackstruct.CallbackBeforeInviteUserToGroupResp{} + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } - if err != nil { - return err - } - - if len(resp.RefusedMembersAccount) > 0 { - // Handle the scenario where certain members are refused - // You might want to update the req.Members list or handle it as per your business logic - } - return nil + if len(resp.RefusedMembersAccount) > 0 { + // Handle the scenario where certain members are refused + // You might want to update the req.Members list or handle it as per your business logic + } + return nil + }) } -func CallbackAfterJoinGroup(ctx context.Context, globalConfig *config.GlobalConfig, req *group.JoinGroupReq) error { - if !globalConfig.Callback.CallbackAfterJoinGroup.Enable { - return nil - } - callbackReq := &callbackstruct.CallbackAfterJoinGroupReq{ +func (s *groupServer) webhookAfterJoinGroup(ctx context.Context, after *config.AfterConfig, req *group.JoinGroupReq) { + cbReq := &callbackstruct.CallbackAfterJoinGroupReq{ CallbackCommand: callbackstruct.CallbackAfterJoinGroupCommand, OperationID: mcontext.GetOperationID(ctx), GroupID: req.GroupID, @@ -340,68 +261,60 @@ func CallbackAfterJoinGroup(ctx context.Context, globalConfig *config.GlobalConf JoinSource: req.JoinSource, InviterUserID: req.InviterUserID, } - resp := &callbackstruct.CallbackAfterJoinGroupResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, callbackReq, resp, globalConfig.Callback.CallbackAfterJoinGroup); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterJoinGroupResp{}, after) } -func CallbackBeforeSetGroupInfo(ctx context.Context, globalConfig *config.GlobalConfig, req *group.SetGroupInfoReq) error { - if !globalConfig.Callback.CallbackBeforeSetGroupInfo.Enable { - return nil - } - callbackReq := &callbackstruct.CallbackBeforeSetGroupInfoReq{ - CallbackCommand: callbackstruct.CallbackBeforeSetGroupInfoCommand, - GroupID: req.GroupInfoForSet.GroupID, - Notification: req.GroupInfoForSet.Notification, - Introduction: req.GroupInfoForSet.Introduction, - FaceURL: req.GroupInfoForSet.FaceURL, - GroupName: req.GroupInfoForSet.GroupName, - } +func (s *groupServer) webhookBeforeSetGroupInfo(ctx context.Context, before *config.BeforeConfig, req *group.SetGroupInfoReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &callbackstruct.CallbackBeforeSetGroupInfoReq{ + CallbackCommand: callbackstruct.CallbackBeforeSetGroupInfoCommand, + GroupID: req.GroupInfoForSet.GroupID, + Notification: req.GroupInfoForSet.Notification, + Introduction: req.GroupInfoForSet.Introduction, + FaceURL: req.GroupInfoForSet.FaceURL, + GroupName: req.GroupInfoForSet.GroupName, + } + if req.GroupInfoForSet.Ex != nil { + cbReq.Ex = req.GroupInfoForSet.Ex.Value + } + log.ZDebug(ctx, "debug CallbackBeforeSetGroupInfo", "ex", cbReq.Ex) + if req.GroupInfoForSet.NeedVerification != nil { + cbReq.NeedVerification = req.GroupInfoForSet.NeedVerification.Value + } + if req.GroupInfoForSet.LookMemberInfo != nil { + cbReq.LookMemberInfo = req.GroupInfoForSet.LookMemberInfo.Value + } + if req.GroupInfoForSet.ApplyMemberFriend != nil { + cbReq.ApplyMemberFriend = req.GroupInfoForSet.ApplyMemberFriend.Value + } + resp := &callbackstruct.CallbackBeforeSetGroupInfoResp{} - if req.GroupInfoForSet.Ex != nil { - callbackReq.Ex = req.GroupInfoForSet.Ex.Value - } - log.ZDebug(ctx, "debug CallbackBeforeSetGroupInfo", callbackReq.Ex) - if req.GroupInfoForSet.NeedVerification != nil { - callbackReq.NeedVerification = req.GroupInfoForSet.NeedVerification.Value - } - if req.GroupInfoForSet.LookMemberInfo != nil { - callbackReq.LookMemberInfo = req.GroupInfoForSet.LookMemberInfo.Value - } - if req.GroupInfoForSet.ApplyMemberFriend != nil { - callbackReq.ApplyMemberFriend = req.GroupInfoForSet.ApplyMemberFriend.Value - } - resp := &callbackstruct.CallbackBeforeSetGroupInfoResp{} - - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, callbackReq, resp, globalConfig.Callback.CallbackBeforeSetGroupInfo); err != nil { - return err - } + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } - if resp.Ex != nil { - req.GroupInfoForSet.Ex = wrapperspb.String(*resp.Ex) - } - if resp.NeedVerification != nil { - req.GroupInfoForSet.NeedVerification = wrapperspb.Int32(*resp.NeedVerification) - } - if resp.LookMemberInfo != nil { - req.GroupInfoForSet.LookMemberInfo = wrapperspb.Int32(*resp.LookMemberInfo) - } - if resp.ApplyMemberFriend != nil { - req.GroupInfoForSet.ApplyMemberFriend = wrapperspb.Int32(*resp.ApplyMemberFriend) - } - utils.NotNilReplace(&req.GroupInfoForSet.GroupID, &resp.GroupID) - utils.NotNilReplace(&req.GroupInfoForSet.GroupName, &resp.GroupName) - utils.NotNilReplace(&req.GroupInfoForSet.FaceURL, &resp.FaceURL) - utils.NotNilReplace(&req.GroupInfoForSet.Introduction, &resp.Introduction) - return nil -} -func CallbackAfterSetGroupInfo(ctx context.Context, globalConfig *config.GlobalConfig, req *group.SetGroupInfoReq) error { - if !globalConfig.Callback.CallbackAfterSetGroupInfo.Enable { + if resp.Ex != nil { + req.GroupInfoForSet.Ex = wrapperspb.String(*resp.Ex) + } + if resp.NeedVerification != nil { + req.GroupInfoForSet.NeedVerification = wrapperspb.Int32(*resp.NeedVerification) + } + if resp.LookMemberInfo != nil { + req.GroupInfoForSet.LookMemberInfo = wrapperspb.Int32(*resp.LookMemberInfo) + } + if resp.ApplyMemberFriend != nil { + req.GroupInfoForSet.ApplyMemberFriend = wrapperspb.Int32(*resp.ApplyMemberFriend) + } + datautil.NotNilReplace(&req.GroupInfoForSet.GroupID, &resp.GroupID) + datautil.NotNilReplace(&req.GroupInfoForSet.GroupName, &resp.GroupName) + datautil.NotNilReplace(&req.GroupInfoForSet.FaceURL, &resp.FaceURL) + datautil.NotNilReplace(&req.GroupInfoForSet.Introduction, &resp.Introduction) return nil - } - callbackReq := &callbackstruct.CallbackAfterSetGroupInfoReq{ + }) +} + +func (s *groupServer) webhookAfterSetGroupInfo(ctx context.Context, after *config.AfterConfig, req *group.SetGroupInfoReq) { + cbReq := &callbackstruct.CallbackAfterSetGroupInfoReq{ CallbackCommand: callbackstruct.CallbackAfterSetGroupInfoCommand, GroupID: req.GroupInfoForSet.GroupID, Notification: req.GroupInfoForSet.Notification, @@ -410,20 +323,16 @@ func CallbackAfterSetGroupInfo(ctx context.Context, globalConfig *config.GlobalC GroupName: req.GroupInfoForSet.GroupName, } if req.GroupInfoForSet.Ex != nil { - callbackReq.Ex = &req.GroupInfoForSet.Ex.Value + cbReq.Ex = &req.GroupInfoForSet.Ex.Value } if req.GroupInfoForSet.NeedVerification != nil { - callbackReq.NeedVerification = &req.GroupInfoForSet.NeedVerification.Value + cbReq.NeedVerification = &req.GroupInfoForSet.NeedVerification.Value } if req.GroupInfoForSet.LookMemberInfo != nil { - callbackReq.LookMemberInfo = &req.GroupInfoForSet.LookMemberInfo.Value + cbReq.LookMemberInfo = &req.GroupInfoForSet.LookMemberInfo.Value } if req.GroupInfoForSet.ApplyMemberFriend != nil { - callbackReq.ApplyMemberFriend = &req.GroupInfoForSet.ApplyMemberFriend.Value - } - resp := &callbackstruct.CallbackAfterSetGroupInfoResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, callbackReq, resp, globalConfig.Callback.CallbackAfterSetGroupInfo); err != nil { - return err + cbReq.ApplyMemberFriend = &req.GroupInfoForSet.ApplyMemberFriend.Value } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterSetGroupInfoResp{}, after) } diff --git a/internal/rpc/group/convert.go b/internal/rpc/group/convert.go index ab4d3a2a1..86978ce3a 100644 --- a/internal/rpc/group/convert.go +++ b/internal/rpc/group/convert.go @@ -15,8 +15,8 @@ package group import ( - "github.com/OpenIMSDK/protocol/sdkws" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/sdkws" ) func (s *groupServer) groupDB2PB(group *relation.GroupModel, ownerUserID string, memberCount uint32) *sdkws.GroupInfo { @@ -41,10 +41,7 @@ func (s *groupServer) groupDB2PB(group *relation.GroupModel, ownerUserID string, } } -func (s *groupServer) groupMemberDB2PB( - member *relation.GroupMemberModel, - appMangerLevel int32, -) *sdkws.GroupMemberFullInfo { +func (s *groupServer) groupMemberDB2PB(member *relation.GroupMemberModel, appMangerLevel int32) *sdkws.GroupMemberFullInfo { return &sdkws.GroupMemberFullInfo{ GroupID: member.GroupID, UserID: member.UserID, diff --git a/internal/rpc/group/db_map.go b/internal/rpc/group/db_map.go index e4c18bb17..b4b503b95 100644 --- a/internal/rpc/group/db_map.go +++ b/internal/rpc/group/db_map.go @@ -18,9 +18,9 @@ import ( "context" "time" - pbgroup "github.com/OpenIMSDK/protocol/group" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/mcontext" + pbgroup "github.com/openimsdk/protocol/group" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/mcontext" ) func UpdateGroupInfoMap(ctx context.Context, group *sdkws.GroupInfoForSet) map[string]any { diff --git a/internal/rpc/group/fill.go b/internal/rpc/group/fill.go index ac539de19..c504db8d6 100644 --- a/internal/rpc/group/fill.go +++ b/internal/rpc/group/fill.go @@ -21,5 +21,5 @@ import ( ) func (s *groupServer) PopulateGroupMember(ctx context.Context, members ...*relationtb.GroupMemberModel) error { - return s.Notification.PopulateGroupMember(ctx, members...) + return s.notification.PopulateGroupMember(ctx, members...) } diff --git a/internal/rpc/group/group.go b/internal/rpc/group/group.go index 92d9e27d6..13bd7f9be 100644 --- a/internal/rpc/group/group.go +++ b/internal/rpc/group/group.go @@ -17,96 +17,108 @@ package group import ( "context" "fmt" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "math/big" "math/rand" "strconv" "strings" "time" - "github.com/OpenIMSDK/protocol/constant" - pbconversation "github.com/OpenIMSDK/protocol/conversation" - pbgroup "github.com/OpenIMSDK/protocol/group" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/protocol/wrapperspb" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/mw/specialerror" - "github.com/OpenIMSDK/tools/tx" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/convert" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/db/mgo" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/grouphash" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" + "github.com/openimsdk/protocol/constant" + pbconversation "github.com/openimsdk/protocol/conversation" + pbgroup "github.com/openimsdk/protocol/group" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/protocol/wrapperspb" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/redisutil" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/mw/specialerror" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/encrypt" "google.golang.org/grpc" ) -func Start(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error { - mongo, err := unrelation.NewMongo(config) +type groupServer struct { + db controller.GroupDatabase + user rpcclient.UserRpcClient + notification *GroupNotificationSender + conversationRpcClient rpcclient.ConversationRpcClient + msgRpcClient rpcclient.MessageRpcClient + config *Config + webhookClient *webhook.Client +} + +type Config struct { + RpcConfig config.Group + RedisConfig config.Redis + MongodbConfig config.Mongo + ZookeeperConfig config.ZooKeeper + NotificationConfig config.Notification + Share config.Share + WebhooksConfig config.Webhooks + LocalCacheConfig config.LocalCache +} + +func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { + mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) if err != nil { return err } - rdb, err := cache.NewRedis(config) + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) if err != nil { return err } - groupDB, err := mgo.NewGroupMongo(mongo.GetDatabase(config.Mongo.Database)) + groupDB, err := mgo.NewGroupMongo(mgocli.GetDB()) if err != nil { return err } - groupMemberDB, err := mgo.NewGroupMember(mongo.GetDatabase(config.Mongo.Database)) + groupMemberDB, err := mgo.NewGroupMember(mgocli.GetDB()) if err != nil { return err } - groupRequestDB, err := mgo.NewGroupRequestMgo(mongo.GetDatabase(config.Mongo.Database)) + groupRequestDB, err := mgo.NewGroupRequestMgo(mgocli.GetDB()) if err != nil { return err } - userRpcClient := rpcclient.NewUserRpcClient(client, config) - msgRpcClient := rpcclient.NewMessageRpcClient(client, config) - conversationRpcClient := rpcclient.NewConversationRpcClient(client, config) + userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID) + msgRpcClient := rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg) + conversationRpcClient := rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation) var gs groupServer - database := controller.NewGroupDatabase(rdb, groupDB, groupMemberDB, groupRequestDB, tx.NewMongo(mongo.GetClient()), grouphash.NewGroupHashFromGroupServer(&gs)) + database := controller.NewGroupDatabase(rdb, &config.LocalCacheConfig, groupDB, groupMemberDB, groupRequestDB, mgocli.GetTx(), grouphash.NewGroupHashFromGroupServer(&gs)) gs.db = database - gs.User = userRpcClient - gs.Notification = notification.NewGroupNotificationSender(database, &msgRpcClient, &userRpcClient, config, func(ctx context.Context, userIDs []string) ([]notification.CommonUser, error) { + gs.user = userRpcClient + gs.notification = NewGroupNotificationSender(database, &msgRpcClient, &userRpcClient, config, func(ctx context.Context, userIDs []string) ([]notification.CommonUser, error) { users, err := userRpcClient.GetUsersInfo(ctx, userIDs) if err != nil { return nil, err } - return utils.Slice(users, func(e *sdkws.UserInfo) notification.CommonUser { return e }), nil + return datautil.Slice(users, func(e *sdkws.UserInfo) notification.CommonUser { return e }), nil }) + cache.InitLocalCache(&config.LocalCacheConfig) gs.conversationRpcClient = conversationRpcClient gs.msgRpcClient = msgRpcClient gs.config = config + gs.webhookClient = webhook.NewWebhookClient(config.WebhooksConfig.URL) pbgroup.RegisterGroupServer(server, &gs) return nil } -type groupServer struct { - db controller.GroupDatabase - User rpcclient.UserRpcClient - Notification *notification.GroupNotificationSender - conversationRpcClient rpcclient.ConversationRpcClient - msgRpcClient rpcclient.MessageRpcClient - config *config.GlobalConfig -} - -func (s *groupServer) GetJoinedGroupIDs(ctx context.Context, req *pbgroup.GetJoinedGroupIDsReq) (*pbgroup.GetJoinedGroupIDsResp, error) { - //TODO implement me - panic("implement me") -} - func (s *groupServer) NotificationUserInfoUpdate(ctx context.Context, req *pbgroup.NotificationUserInfoUpdateReq) (*pbgroup.NotificationUserInfoUpdateResp, error) { members, err := s.db.FindGroupMemberUser(ctx, nil, req.UserID) if err != nil { @@ -120,9 +132,7 @@ func (s *groupServer) NotificationUserInfoUpdate(ctx context.Context, req *pbgro groupIDs = append(groupIDs, member.GroupID) } for _, groupID := range groupIDs { - if err = s.Notification.GroupMemberInfoSetNotification(ctx, groupID, req.UserID); err != nil { - return nil, err - } + s.notification.GroupMemberInfoSetNotification(ctx, groupID, req.UserID) } if err = s.db.DeleteGroupMemberHash(ctx, groupIDs); err != nil { return nil, err @@ -132,13 +142,13 @@ func (s *groupServer) NotificationUserInfoUpdate(ctx context.Context, req *pbgro } func (s *groupServer) CheckGroupAdmin(ctx context.Context, groupID string) error { - if !authverify.IsAppManagerUid(ctx, s.config) { + if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { groupMember, err := s.db.TakeGroupMember(ctx, groupID, mcontext.GetOpUserID(ctx)) if err != nil { return err } if !(groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin) { - return errs.ErrNoPermission.Wrap("no group owner or admin") + return errs.ErrNoPermission.WrapMsg("no group owner or admin") } } return nil @@ -148,11 +158,11 @@ func (s *groupServer) GetPublicUserInfoMap(ctx context.Context, userIDs []string if len(userIDs) == 0 { return map[string]*sdkws.PublicUserInfo{}, nil } - users, err := s.User.GetPublicUserInfos(ctx, userIDs, complete) + users, err := s.user.GetPublicUserInfos(ctx, userIDs, complete) if err != nil { return nil, err } - return utils.SliceToMapAny(users, func(e *sdkws.PublicUserInfo) (string, *sdkws.PublicUserInfo) { + return datautil.SliceToMapAny(users, func(e *sdkws.PublicUserInfo) (string, *sdkws.PublicUserInfo) { return e.UserID, e }), nil } @@ -165,7 +175,7 @@ func (s *groupServer) GenGroupID(ctx context.Context, groupID *string) error { if *groupID != "" { _, err := s.db.TakeGroup(ctx, *groupID) if err == nil { - return errs.ErrGroupIDExisted.Wrap("group id existed " + *groupID) + return servererrs.ErrGroupIDExisted.WrapMsg("group id existed " + *groupID) } else if s.IsNotFound(err) { return nil } else { @@ -173,7 +183,7 @@ func (s *groupServer) GenGroupID(ctx context.Context, groupID *string) error { } } for i := 0; i < 10; i++ { - id := utils.Md5(strings.Join([]string{mcontext.GetOperationID(ctx), strconv.FormatInt(time.Now().UnixNano(), 10), strconv.Itoa(rand.Int())}, ",;,")) + id := encrypt.Md5(strings.Join([]string{mcontext.GetOperationID(ctx), strconv.FormatInt(time.Now().UnixNano(), 10), strconv.Itoa(rand.Int())}, ",;,")) bi := big.NewInt(0) bi.SetString(id[0:8], 16) id = bi.String() @@ -187,43 +197,49 @@ func (s *groupServer) GenGroupID(ctx context.Context, groupID *string) error { return err } } - return errs.ErrData.Wrap("group id gen error") + return servererrs.ErrData.WrapMsg("group id gen error") } func (s *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupReq) (*pbgroup.CreateGroupResp, error) { if req.GroupInfo.GroupType != constant.WorkingGroup { - return nil, errs.ErrArgs.Wrap(fmt.Sprintf("group type only supports %d", constant.WorkingGroup)) + return nil, errs.ErrArgs.WrapMsg(fmt.Sprintf("group type only supports %d", constant.WorkingGroup)) } if req.OwnerUserID == "" { - return nil, errs.ErrArgs.Wrap("no group owner") + return nil, errs.ErrArgs.WrapMsg("no group owner") } - if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config); err != nil { + if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil { + return nil, err } userIDs := append(append(req.MemberUserIDs, req.AdminUserIDs...), req.OwnerUserID) opUserID := mcontext.GetOpUserID(ctx) - if !utils.Contain(opUserID, userIDs...) { + if !datautil.Contain(opUserID, userIDs...) { userIDs = append(userIDs, opUserID) } - if utils.Duplicate(userIDs) { - return nil, errs.ErrArgs.Wrap("group member repeated") + + if datautil.Duplicate(userIDs) { + return nil, errs.ErrArgs.WrapMsg("group member repeated") } - userMap, err := s.User.GetUsersInfoMap(ctx, userIDs) + + userMap, err := s.user.GetUsersInfoMap(ctx, userIDs) if err != nil { return nil, err } + if len(userMap) != len(userIDs) { - return nil, errs.ErrUserIDNotFound.Wrap("user not found") + return nil, servererrs.ErrUserIDNotFound.WrapMsg("user not found") } - // Callback Before create Group - if err := CallbackBeforeCreateGroup(ctx, s.config, req); err != nil { + + if err := s.webhookBeforeCreateGroup(ctx, &s.config.WebhooksConfig.BeforeCreateGroup, req); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } + var groupMembers []*relationtb.GroupMemberModel group := convert.Pb2DBGroupInfo(req.GroupInfo) if err := s.GenGroupID(ctx, &group.GroupID); err != nil { return nil, err } + joinGroup := func(userID string, roleLevel int32) error { groupMember := &relationtb.GroupMemberModel{ GroupID: group.GroupID, @@ -235,7 +251,8 @@ func (s *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR JoinTime: time.Now(), MuteEndTime: time.UnixMilli(0), } - if err := CallbackBeforeMemberJoinGroup(ctx, s.config, groupMember, group.Ex); err != nil { + + if err := s.webhookBeforeMemberJoinGroup(ctx, &s.config.WebhooksConfig.BeforeMemberJoinGroup, groupMember, group.Ex); err != nil && err != servererrs.ErrCallbackContinue { return err } groupMembers = append(groupMembers, groupMember) @@ -258,6 +275,7 @@ func (s *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR return nil, err } resp := &pbgroup.CreateGroupResp{GroupInfo: &sdkws.GroupInfo{}} + resp.GroupInfo = convert.Db2PbGroupInfo(group, req.OwnerUserID, uint32(len(userIDs))) resp.GroupInfo.MemberCount = uint32(len(userIDs)) tips := &sdkws.GroupCreatedTips{ @@ -276,11 +294,10 @@ func (s *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR if req.GroupInfo.GroupType == constant.SuperGroup { go func() { for _, userID := range userIDs { - s.Notification.SuperGroupNotification(ctx, userID, userID) + s.notification.SuperGroupNotification(ctx, userID, userID) } }() } else { - // s.Notification.GroupCreatedNotification(ctx, group, groupMembers, userMap) tips := &sdkws.GroupCreatedTips{ Group: resp.GroupInfo, OperationTime: group.CreateTime.UnixMilli(), @@ -294,8 +311,9 @@ func (s *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR break } } - s.Notification.GroupCreatedNotification(ctx, tips) + s.notification.GroupCreatedNotification(ctx, tips) } + reqCallBackAfter := &pbgroup.CreateGroupReq{ MemberUserIDs: userIDs, GroupInfo: resp.GroupInfo, @@ -303,27 +321,25 @@ func (s *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR AdminUserIDs: req.AdminUserIDs, } - if err := CallbackAfterCreateGroup(ctx, s.config, reqCallBackAfter); err != nil { - return nil, err - } + s.webhookAfterCreateGroup(ctx, &s.config.WebhooksConfig.AfterCreateGroup, reqCallBackAfter) return resp, nil } func (s *groupServer) GetJoinedGroupList(ctx context.Context, req *pbgroup.GetJoinedGroupListReq) (*pbgroup.GetJoinedGroupListResp, error) { - resp := &pbgroup.GetJoinedGroupListResp{} - if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config); err != nil { + if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config.Share.IMAdminUserID); err != nil { return nil, err } total, members, err := s.db.PageGetJoinGroup(ctx, req.FromUserID, req.Pagination) if err != nil { return nil, err } + var resp pbgroup.GetJoinedGroupListResp resp.Total = uint32(total) if len(members) == 0 { - return resp, nil + return &resp, nil } - groupIDs := utils.Slice(members, func(e *relationtb.GroupMemberModel) string { + groupIDs := datautil.Slice(members, func(e *relationtb.GroupMemberModel) string { return e.GroupID }) groups, err := s.db.FindGroup(ctx, groupIDs) @@ -341,10 +357,10 @@ func (s *groupServer) GetJoinedGroupList(ctx context.Context, req *pbgroup.GetJo if err := s.PopulateGroupMember(ctx, members...); err != nil { return nil, err } - ownerMap := utils.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string { + ownerMap := datautil.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string { return e.GroupID }) - resp.Groups = utils.Slice(utils.Order(groupIDs, groups, func(group *relationtb.GroupModel) string { + resp.Groups = datautil.Slice(datautil.Order(groupIDs, groups, func(group *relationtb.GroupModel) string { return group.GroupID }), func(group *relationtb.GroupModel) *sdkws.GroupInfo { var userID string @@ -353,17 +369,15 @@ func (s *groupServer) GetJoinedGroupList(ctx context.Context, req *pbgroup.GetJo } return convert.Db2PbGroupInfo(group, userID, groupMemberNum[group.GroupID]) }) - return resp, nil + return &resp, nil } func (s *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.InviteUserToGroupReq) (*pbgroup.InviteUserToGroupResp, error) { - resp := &pbgroup.InviteUserToGroupResp{} - if len(req.InvitedUserIDs) == 0 { - return nil, errs.ErrArgs.Wrap("user empty") + return nil, errs.ErrArgs.WrapMsg("user empty") } - if utils.Duplicate(req.InvitedUserIDs) { - return nil, errs.ErrArgs.Wrap("userID duplicate") + if datautil.Duplicate(req.InvitedUserIDs) { + return nil, errs.ErrArgs.WrapMsg("userID duplicate") } group, err := s.db.TakeGroup(ctx, req.GroupID) if err != nil { @@ -371,18 +385,21 @@ func (s *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite } if group.Status == constant.GroupStatusDismissed { - return nil, errs.ErrDismissedAlready.Wrap() + return nil, servererrs.ErrDismissedAlready.WrapMsg("group dismissed checking group status found it dismissed") } - userMap, err := s.User.GetUsersInfoMap(ctx, req.InvitedUserIDs) + + userMap, err := s.user.GetUsersInfoMap(ctx, req.InvitedUserIDs) if err != nil { return nil, err } + if len(userMap) != len(req.InvitedUserIDs) { - return nil, errs.ErrRecordNotFound.Wrap("user not found") + return nil, errs.ErrRecordNotFound.WrapMsg("user not found") } + var groupMember *relationtb.GroupMemberModel var opUserID string - if !authverify.IsAppManagerUid(ctx, s.config) { + if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { opUserID = mcontext.GetOpUserID(ctx) var err error groupMember, err = s.db.TakeGroupMember(ctx, req.GroupID, opUserID) @@ -394,11 +411,12 @@ func (s *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite } } - if err := CallbackBeforeInviteUserToGroup(ctx, s.config, req); err != nil { + if err := s.webhookBeforeInviteUserToGroup(ctx, &s.config.WebhooksConfig.BeforeInviteUserToGroup, req); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } + if group.NeedVerification == constant.AllNeedVerification { - if !authverify.IsAppManagerUid(ctx, s.config) { + if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { if !(groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin) { var requests []*relationtb.GroupRequestModel for _, userID := range req.InvitedUserIDs { @@ -415,14 +433,14 @@ func (s *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite return nil, err } for _, request := range requests { - s.Notification.JoinGroupApplicationNotification(ctx, &pbgroup.JoinGroupReq{ + s.notification.JoinGroupApplicationNotification(ctx, &pbgroup.JoinGroupReq{ GroupID: request.GroupID, ReqMessage: request.ReqMsg, JoinSource: request.JoinSource, InviterUserID: request.InviterUserID, }) } - return resp, nil + return &pbgroup.InviteUserToGroupResp{}, nil } } } @@ -438,10 +456,12 @@ func (s *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite JoinTime: time.Now(), MuteEndTime: time.UnixMilli(0), } - if err := CallbackBeforeMemberJoinGroup(ctx, s.config, member, group.Ex); err != nil { + + if err := s.webhookBeforeMemberJoinGroup(ctx, &s.config.WebhooksConfig.BeforeMemberJoinGroup, groupMember, group.Ex); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } groupMembers = append(groupMembers, member) + } if err := s.db.CreateGroup(ctx, nil, groupMembers); err != nil { return nil, err @@ -449,8 +469,8 @@ func (s *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite if err := s.conversationRpcClient.GroupChatFirstCreateConversation(ctx, req.GroupID, req.InvitedUserIDs); err != nil { return nil, err } - s.Notification.MemberInvitedNotification(ctx, req.GroupID, req.Reason, req.InvitedUserIDs) - return resp, nil + s.notification.MemberInvitedNotification(ctx, req.GroupID, req.Reason, req.InvitedUserIDs) + return &pbgroup.InviteUserToGroupResp{}, nil } func (s *groupServer) GetGroupAllMember(ctx context.Context, req *pbgroup.GetGroupAllMemberReq) (*pbgroup.GetGroupAllMemberResp, error) { @@ -461,15 +481,14 @@ func (s *groupServer) GetGroupAllMember(ctx context.Context, req *pbgroup.GetGro if err := s.PopulateGroupMember(ctx, members...); err != nil { return nil, err } - resp := &pbgroup.GetGroupAllMemberResp{} - resp.Members = utils.Slice(members, func(e *relationtb.GroupMemberModel) *sdkws.GroupMemberFullInfo { + var resp pbgroup.GetGroupAllMemberResp + resp.Members = datautil.Slice(members, func(e *relationtb.GroupMemberModel) *sdkws.GroupMemberFullInfo { return convert.Db2PbGroupMember(e) }) - return resp, nil + return &resp, nil } func (s *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGroupMemberListReq) (*pbgroup.GetGroupMemberListResp, error) { - resp := &pbgroup.GetGroupMemberListResp{} var ( total int64 members []*relationtb.GroupMemberModel @@ -501,31 +520,32 @@ func (s *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGr } } - GMembers := utils.Paginate(groupMembers, int(req.Pagination.GetPageNumber()), int(req.Pagination.GetShowNumber())) - resp.Members = utils.Batch(convert.Db2PbGroupMember, GMembers) - resp.Total = uint32(total) - return resp, nil - } - resp.Total = uint32(total) - resp.Members = utils.Batch(convert.Db2PbGroupMember, members) - return resp, nil + members := datautil.Paginate(groupMembers, int(req.Pagination.GetPageNumber()), int(req.Pagination.GetShowNumber())) + return &pbgroup.GetGroupMemberListResp{ + Total: uint32(total), + Members: datautil.Batch(convert.Db2PbGroupMember, members), + }, nil + } + return &pbgroup.GetGroupMemberListResp{ + Total: uint32(total), + Members: datautil.Batch(convert.Db2PbGroupMember, members), + }, nil } func (s *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGroupMemberReq) (*pbgroup.KickGroupMemberResp, error) { - resp := &pbgroup.KickGroupMemberResp{} group, err := s.db.TakeGroup(ctx, req.GroupID) if err != nil { return nil, err } if len(req.KickedUserIDs) == 0 { - return nil, errs.ErrArgs.Wrap("KickedUserIDs empty") + return nil, errs.ErrArgs.WrapMsg("KickedUserIDs empty") } - if utils.IsDuplicateStringSlice(req.KickedUserIDs) { - return nil, errs.ErrArgs.Wrap("KickedUserIDs duplicate") + if datautil.Duplicate(req.KickedUserIDs) { + return nil, errs.ErrArgs.WrapMsg("KickedUserIDs duplicate") } opUserID := mcontext.GetOpUserID(ctx) - if utils.IsContain(opUserID, req.KickedUserIDs) { - return nil, errs.ErrArgs.Wrap("opUserID in KickedUserIDs") + if datautil.Contain(opUserID, req.KickedUserIDs...) { + return nil, errs.ErrArgs.WrapMsg("opUserID in KickedUserIDs") } members, err := s.db.FindGroupMembers(ctx, req.GroupID, append(req.KickedUserIDs, opUserID)) if err != nil { @@ -538,27 +558,27 @@ func (s *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou for i, member := range members { memberMap[member.UserID] = members[i] } - isAppManagerUid := authverify.IsAppManagerUid(ctx, s.config) + isAppManagerUid := authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) opMember := memberMap[opUserID] for _, userID := range req.KickedUserIDs { member, ok := memberMap[userID] if !ok { - return nil, errs.ErrUserIDNotFound.Wrap(userID) + return nil, servererrs.ErrUserIDNotFound.WrapMsg(userID) } if !isAppManagerUid { if opMember == nil { - return nil, errs.ErrNoPermission.Wrap("opUserID no in group") + return nil, errs.ErrNoPermission.WrapMsg("opUserID no in group") } switch opMember.RoleLevel { case constant.GroupOwner: case constant.GroupAdmin: if member.RoleLevel == constant.GroupOwner || member.RoleLevel == constant.GroupAdmin { - return nil, errs.ErrNoPermission.Wrap("group admins cannot remove the group owner and other admins") + return nil, errs.ErrNoPermission.WrapMsg("group admins cannot remove the group owner and other admins") } case constant.GroupOrdinaryUsers: - return nil, errs.ErrNoPermission.Wrap("opUserID no permission") + return nil, errs.ErrNoPermission.WrapMsg("opUserID no permission") default: - return nil, errs.ErrNoPermission.Wrap("opUserID roleLevel unknown") + return nil, errs.ErrNoPermission.WrapMsg("opUserID roleLevel unknown") } } } @@ -605,24 +625,21 @@ func (s *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou for _, userID := range req.KickedUserIDs { tips.KickedUserList = append(tips.KickedUserList, convert.Db2PbGroupMember(memberMap[userID])) } - s.Notification.MemberKickedNotification(ctx, tips) + s.notification.MemberKickedNotification(ctx, tips) if err := s.deleteMemberAndSetConversationSeq(ctx, req.GroupID, req.KickedUserIDs); err != nil { return nil, err } + s.webhookAfterKickGroupMember(ctx, &s.config.WebhooksConfig.AfterKickGroupMember, req) - if err := CallbackKillGroupMember(ctx, s.config, req); err != nil { - return nil, err - } - return resp, nil + return &pbgroup.KickGroupMemberResp{}, nil } func (s *groupServer) GetGroupMembersInfo(ctx context.Context, req *pbgroup.GetGroupMembersInfoReq) (*pbgroup.GetGroupMembersInfoResp, error) { - resp := &pbgroup.GetGroupMembersInfoResp{} if len(req.UserIDs) == 0 { - return nil, errs.ErrArgs.Wrap("userIDs empty") + return nil, errs.ErrArgs.WrapMsg("userIDs empty") } if req.GroupID == "" { - return nil, errs.ErrArgs.Wrap("groupID empty") + return nil, errs.ErrArgs.WrapMsg("groupID empty") } members, err := s.db.FindGroupMembers(ctx, req.GroupID, req.UserIDs) if err != nil { @@ -631,10 +648,11 @@ func (s *groupServer) GetGroupMembersInfo(ctx context.Context, req *pbgroup.GetG if err := s.PopulateGroupMember(ctx, members...); err != nil { return nil, err } - resp.Members = utils.Slice(members, func(e *relationtb.GroupMemberModel) *sdkws.GroupMemberFullInfo { - return convert.Db2PbGroupMember(e) - }) - return resp, nil + return &pbgroup.GetGroupMembersInfoResp{ + Members: datautil.Slice(members, func(e *relationtb.GroupMemberModel) *sdkws.GroupMemberFullInfo { + return convert.Db2PbGroupMember(e) + }), + }, nil } // GetGroupApplicationList handles functions that get a list of group requests. @@ -660,20 +678,20 @@ func (s *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup. for _, gr := range groupRequests { userIDs = append(userIDs, gr.UserID) } - userIDs = utils.Distinct(userIDs) - userMap, err := s.User.GetPublicUserInfoMap(ctx, userIDs, true) + userIDs = datautil.Distinct(userIDs) + userMap, err := s.user.GetPublicUserInfoMap(ctx, userIDs, true) if err != nil { return nil, err } - groups, err := s.db.FindGroup(ctx, utils.Distinct(groupIDs)) + groups, err := s.db.FindGroup(ctx, datautil.Distinct(groupIDs)) if err != nil { return nil, err } - groupMap := utils.SliceToMap(groups, func(e *relationtb.GroupModel) string { + groupMap := datautil.SliceToMap(groups, func(e *relationtb.GroupModel) string { return e.GroupID }) - if ids := utils.Single(utils.Keys(groupMap), groupIDs); len(ids) > 0 { - return nil, errs.ErrGroupIDNotFound.Wrap(strings.Join(ids, ",")) + if ids := datautil.Single(datautil.Keys(groupMap), groupIDs); len(ids) > 0 { + return nil, servererrs.ErrGroupIDNotFound.WrapMsg(strings.Join(ids, ",")) } groupMemberNumMap, err := s.db.MapGroupMemberNum(ctx, groupIDs) if err != nil { @@ -686,10 +704,10 @@ func (s *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup. if err := s.PopulateGroupMember(ctx, owners...); err != nil { return nil, err } - ownerMap := utils.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string { + ownerMap := datautil.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string { return e.GroupID }) - resp.GroupRequests = utils.Slice(groupRequests, func(e *relationtb.GroupRequestModel) *sdkws.GroupRequest { + resp.GroupRequests = datautil.Slice(groupRequests, func(e *relationtb.GroupRequestModel) *sdkws.GroupRequest { var ownerUserID string if owner, ok := ownerMap[e.GroupID]; ok { ownerUserID = owner.UserID @@ -700,9 +718,8 @@ func (s *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup. } func (s *groupServer) GetGroupsInfo(ctx context.Context, req *pbgroup.GetGroupsInfoReq) (*pbgroup.GetGroupsInfoResp, error) { - resp := &pbgroup.GetGroupsInfoResp{} if len(req.GroupIDs) == 0 { - return nil, errs.ErrArgs.Wrap("groupID is empty") + return nil, errs.ErrArgs.WrapMsg("groupID is empty") } groups, err := s.db.FindGroup(ctx, req.GroupIDs) if err != nil { @@ -719,31 +736,31 @@ func (s *groupServer) GetGroupsInfo(ctx context.Context, req *pbgroup.GetGroupsI if err := s.PopulateGroupMember(ctx, owners...); err != nil { return nil, err } - ownerMap := utils.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string { + ownerMap := datautil.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string { return e.GroupID }) - resp.GroupInfos = utils.Slice(groups, func(e *relationtb.GroupModel) *sdkws.GroupInfo { - var ownerUserID string - if owner, ok := ownerMap[e.GroupID]; ok { - ownerUserID = owner.UserID - } - return convert.Db2PbGroupInfo(e, ownerUserID, groupMemberNumMap[e.GroupID]) - }) - return resp, nil + return &pbgroup.GetGroupsInfoResp{ + GroupInfos: datautil.Slice(groups, func(e *relationtb.GroupModel) *sdkws.GroupInfo { + var ownerUserID string + if owner, ok := ownerMap[e.GroupID]; ok { + ownerUserID = owner.UserID + } + return convert.Db2PbGroupInfo(e, ownerUserID, groupMemberNumMap[e.GroupID]) + }), + }, nil } func (s *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup.GroupApplicationResponseReq) (*pbgroup.GroupApplicationResponseResp, error) { - defer log.ZInfo(ctx, utils.GetFuncName()+" Return") - if !utils.Contain(req.HandleResult, constant.GroupResponseAgree, constant.GroupResponseRefuse) { - return nil, errs.ErrArgs.Wrap("HandleResult unknown") + if !datautil.Contain(req.HandleResult, constant.GroupResponseAgree, constant.GroupResponseRefuse) { + return nil, errs.ErrArgs.WrapMsg("HandleResult unknown") } - if !authverify.IsAppManagerUid(ctx, s.config) { + if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { groupMember, err := s.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx)) if err != nil { return nil, err } if !(groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin) { - return nil, errs.ErrNoPermission.Wrap("no group owner or admin") + return nil, errs.ErrNoPermission.WrapMsg("no group owner or admin") } } group, err := s.db.TakeGroup(ctx, req.GroupID) @@ -755,7 +772,7 @@ func (s *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup return nil, err } if groupRequest.HandleResult != 0 { - return nil, errs.ErrGroupRequestHandled.Wrap("group request already processed") + return nil, servererrs.ErrGroupRequestHandled.WrapMsg("group request already processed") } var inGroup bool if _, err := s.db.TakeGroupMember(ctx, req.GroupID, req.FromUserID); err == nil { @@ -763,7 +780,7 @@ func (s *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup } else if !s.IsNotFound(err) { return nil, err } - if _, err := s.User.GetPublicUserInfo(ctx, req.FromUserID); err != nil { + if _, err := s.user.GetPublicUserInfo(ctx, req.FromUserID); err != nil { return nil, err } var member *relationtb.GroupMemberModel @@ -781,7 +798,7 @@ func (s *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup OperatorUserID: mcontext.GetOpUserID(ctx), Ex: groupRequest.Ex, } - if err = CallbackBeforeMemberJoinGroup(ctx, s.config, member, group.Ex); err != nil { + if err := s.webhookBeforeMemberJoinGroup(ctx, &s.config.WebhooksConfig.BeforeMemberJoinGroup, member, group.Ex); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } } @@ -794,22 +811,21 @@ func (s *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup if err := s.conversationRpcClient.GroupChatFirstCreateConversation(ctx, req.GroupID, []string{req.FromUserID}); err != nil { return nil, err } - s.Notification.GroupApplicationAcceptedNotification(ctx, req) + s.notification.GroupApplicationAcceptedNotification(ctx, req) if member == nil { log.ZDebug(ctx, "GroupApplicationResponse", "member is nil") } else { - s.Notification.MemberEnterNotification(ctx, req.GroupID, req.FromUserID) + s.notification.MemberEnterNotification(ctx, req.GroupID, req.FromUserID) } case constant.GroupResponseRefuse: - s.Notification.GroupApplicationRejectedNotification(ctx, req) + s.notification.GroupApplicationRejectedNotification(ctx, req) } return &pbgroup.GroupApplicationResponseResp{}, nil } -func (s *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq) (resp *pbgroup.JoinGroupResp, err error) { - defer log.ZInfo(ctx, "JoinGroup.Return") - user, err := s.User.GetUserInfo(ctx, req.InviterUserID) +func (s *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq) (*pbgroup.JoinGroupResp, error) { + user, err := s.user.GetUserInfo(ctx, req.InviterUserID) if err != nil { return nil, err } @@ -818,7 +834,7 @@ func (s *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq) return nil, err } if group.Status == constant.GroupStatusDismissed { - return nil, errs.ErrDismissedAlready.Wrap() + return nil, servererrs.ErrDismissedAlready.Wrap() } reqCall := &callbackstruct.CallbackJoinGroupReq{ @@ -829,17 +845,17 @@ func (s *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq) Ex: req.Ex, } - if err = CallbackApplyJoinGroupBefore(ctx, s.config, reqCall); err != nil { + if err := s.webhookBeforeApplyJoinGroup(ctx, &s.config.WebhooksConfig.BeforeApplyJoinGroup, reqCall); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } + _, err = s.db.TakeGroupMember(ctx, req.GroupID, req.InviterUserID) if err == nil { - return nil, errs.ErrArgs.Wrap("already in group") - } else if !s.IsNotFound(err) && utils.Unwrap(err) != errs.ErrRecordNotFound { + return nil, errs.ErrArgs.Wrap() + } else if !s.IsNotFound(err) && errs.Unwrap(err) != errs.ErrRecordNotFound { return nil, err } - log.ZInfo(ctx, "JoinGroup.groupInfo", "group", group, "eq", group.NeedVerification == constant.Directly) - resp = &pbgroup.JoinGroupResp{} + log.ZDebug(ctx, "JoinGroup.groupInfo", "group", group, "eq", group.NeedVerification == constant.Directly) if group.NeedVerification == constant.Directly { groupMember := &relationtb.GroupMemberModel{ GroupID: group.GroupID, @@ -850,9 +866,11 @@ func (s *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq) JoinTime: time.Now(), MuteEndTime: time.UnixMilli(0), } - if err := CallbackBeforeMemberJoinGroup(ctx, s.config, groupMember, group.Ex); err != nil { + + if err := s.webhookBeforeMemberJoinGroup(ctx, &s.config.WebhooksConfig.BeforeMemberJoinGroup, groupMember, group.Ex); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } + if err := s.db.CreateGroup(ctx, nil, []*relationtb.GroupMemberModel{groupMember}); err != nil { return nil, err } @@ -860,11 +878,10 @@ func (s *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq) if err := s.conversationRpcClient.GroupChatFirstCreateConversation(ctx, req.GroupID, []string{req.InviterUserID}); err != nil { return nil, err } - s.Notification.MemberEnterNotification(ctx, req.GroupID, req.InviterUserID) - if err = CallbackAfterJoinGroup(ctx, s.config, req); err != nil { - return nil, err - } - return resp, nil + s.notification.MemberEnterNotification(ctx, req.GroupID, req.InviterUserID) + s.webhookAfterJoinGroup(ctx, &s.config.WebhooksConfig.AfterJoinGroup, req) + + return &pbgroup.JoinGroupResp{}, nil } groupRequest := relationtb.GroupRequestModel{ UserID: req.InviterUserID, @@ -878,16 +895,15 @@ func (s *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq) if err = s.db.CreateGroupRequest(ctx, []*relationtb.GroupRequestModel{&groupRequest}); err != nil { return nil, err } - s.Notification.JoinGroupApplicationNotification(ctx, req) - return resp, nil + s.notification.JoinGroupApplicationNotification(ctx, req) + return &pbgroup.JoinGroupResp{}, nil } func (s *groupServer) QuitGroup(ctx context.Context, req *pbgroup.QuitGroupReq) (*pbgroup.QuitGroupResp, error) { - resp := &pbgroup.QuitGroupResp{} if req.UserID == "" { req.UserID = mcontext.GetOpUserID(ctx) } else { - if err := authverify.CheckAccessV3(ctx, req.UserID, s.config); err != nil { + if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil { return nil, err } } @@ -896,7 +912,7 @@ func (s *groupServer) QuitGroup(ctx context.Context, req *pbgroup.QuitGroupReq) return nil, err } if member.RoleLevel == constant.GroupOwner { - return nil, errs.ErrNoPermission.Wrap("group owner can't quit") + return nil, errs.ErrNoPermission.WrapMsg("group owner can't quit") } if err := s.PopulateGroupMember(ctx, member); err != nil { return nil, err @@ -905,20 +921,17 @@ func (s *groupServer) QuitGroup(ctx context.Context, req *pbgroup.QuitGroupReq) if err != nil { return nil, err } - _ = s.Notification.MemberQuitNotification(ctx, s.groupMemberDB2PB(member, 0)) + s.notification.MemberQuitNotification(ctx, s.groupMemberDB2PB(member, 0)) if err := s.deleteMemberAndSetConversationSeq(ctx, req.GroupID, []string{req.UserID}); err != nil { return nil, err } + s.webhookAfterQuitGroup(ctx, &s.config.WebhooksConfig.AfterQuitGroup, req) - // callback - if err := CallbackQuitGroup(ctx, s.config, req); err != nil { - return nil, err - } - return resp, nil + return &pbgroup.QuitGroupResp{}, nil } func (s *groupServer) deleteMemberAndSetConversationSeq(ctx context.Context, groupID string, userIDs []string) error { - conevrsationID := msgprocessor.GetConversationIDBySessionType(constant.SuperGroupChatType, groupID) + conevrsationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, groupID) maxSeq, err := s.msgRpcClient.GetConversationMaxSeq(ctx, conevrsationID) if err != nil { return err @@ -928,30 +941,31 @@ func (s *groupServer) deleteMemberAndSetConversationSeq(ctx context.Context, gro func (s *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInfoReq) (*pbgroup.SetGroupInfoResp, error) { var opMember *relationtb.GroupMemberModel - if !authverify.IsAppManagerUid(ctx, s.config) { + if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { var err error opMember, err = s.db.TakeGroupMember(ctx, req.GroupInfoForSet.GroupID, mcontext.GetOpUserID(ctx)) if err != nil { return nil, err } if !(opMember.RoleLevel == constant.GroupOwner || opMember.RoleLevel == constant.GroupAdmin) { - return nil, errs.ErrNoPermission.Wrap("no group owner or admin") + return nil, errs.ErrNoPermission.WrapMsg("no group owner or admin") } if err := s.PopulateGroupMember(ctx, opMember); err != nil { return nil, err } } - if err := CallbackBeforeSetGroupInfo(ctx, s.config, req); err != nil { + + if err := s.webhookBeforeSetGroupInfo(ctx, &s.config.WebhooksConfig.BeforeSetGroupInfo, req); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } + group, err := s.db.TakeGroup(ctx, req.GroupInfoForSet.GroupID) if err != nil { return nil, err } if group.Status == constant.GroupStatusDismissed { - return nil, errs.Wrap(errs.ErrDismissedAlready) + return nil, servererrs.ErrDismissedAlready.Wrap() } - resp := &pbgroup.SetGroupInfoResp{} count, err := s.db.FindGroupMemberNum(ctx, group.GroupID) if err != nil { @@ -966,7 +980,7 @@ func (s *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf } update := UpdateGroupInfoMap(ctx, req.GroupInfoForSet) if len(update) == 0 { - return resp, nil + return &pbgroup.SetGroupInfoResp{}, nil } if err := s.db.UpdateGroup(ctx, group.GroupID, update); err != nil { return nil, err @@ -988,8 +1002,8 @@ func (s *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf num-- func() { conversation := &pbconversation.ConversationReq{ - ConversationID: msgprocessor.GetConversationIDBySessionType(constant.SuperGroupChatType, req.GroupInfoForSet.GroupID), - ConversationType: constant.SuperGroupChatType, + ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupInfoForSet.GroupID), + ConversationType: constant.ReadGroupChatType, GroupID: req.GroupInfoForSet.GroupID, } resp, err := s.GetGroupMemberUserIDs(ctx, &pbgroup.GetGroupMemberUserIDsReq{GroupID: req.GroupInfoForSet.GroupID}) @@ -1002,32 +1016,31 @@ func (s *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf log.ZWarn(ctx, "SetConversations", err, resp.UserIDs, conversation) } }() - _ = s.Notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}) + s.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}) } if req.GroupInfoForSet.GroupName != "" { num-- - _ = s.Notification.GroupInfoSetNameNotification(ctx, &sdkws.GroupInfoSetNameTips{Group: tips.Group, OpUser: tips.OpUser}) + s.notification.GroupInfoSetNameNotification(ctx, &sdkws.GroupInfoSetNameTips{Group: tips.Group, OpUser: tips.OpUser}) } if num > 0 { - _ = s.Notification.GroupInfoSetNotification(ctx, tips) - } - if err := CallbackAfterSetGroupInfo(ctx, s.config, req); err != nil { - return nil, err + s.notification.GroupInfoSetNotification(ctx, tips) } - return resp, nil + + s.webhookAfterSetGroupInfo(ctx, &s.config.WebhooksConfig.AfterSetGroupInfo, req) + + return &pbgroup.SetGroupInfoResp{}, nil } func (s *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.TransferGroupOwnerReq) (*pbgroup.TransferGroupOwnerResp, error) { - resp := &pbgroup.TransferGroupOwnerResp{} group, err := s.db.TakeGroup(ctx, req.GroupID) if err != nil { return nil, err } if group.Status == constant.GroupStatusDismissed { - return nil, errs.ErrDismissedAlready.Wrap("") + return nil, servererrs.ErrDismissedAlready.Wrap() } if req.OldOwnerUserID == req.NewOwnerUserID { - return nil, errs.ErrArgs.Wrap("OldOwnerUserID == NewOwnerUserID") + return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID == NewOwnerUserID") } members, err := s.db.FindGroupMembers(ctx, req.GroupID, []string{req.OldOwnerUserID, req.NewOwnerUserID}) if err != nil { @@ -1036,41 +1049,39 @@ func (s *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans if err := s.PopulateGroupMember(ctx, members...); err != nil { return nil, err } - memberMap := utils.SliceToMap(members, func(e *relationtb.GroupMemberModel) string { return e.UserID }) - if ids := utils.Single([]string{req.OldOwnerUserID, req.NewOwnerUserID}, utils.Keys(memberMap)); len(ids) > 0 { - return nil, errs.ErrArgs.Wrap("user not in group " + strings.Join(ids, ",")) + memberMap := datautil.SliceToMap(members, func(e *relationtb.GroupMemberModel) string { return e.UserID }) + if ids := datautil.Single([]string{req.OldOwnerUserID, req.NewOwnerUserID}, datautil.Keys(memberMap)); len(ids) > 0 { + return nil, errs.ErrArgs.WrapMsg("user not in group " + strings.Join(ids, ",")) } oldOwner := memberMap[req.OldOwnerUserID] if oldOwner == nil { - return nil, errs.ErrArgs.Wrap("OldOwnerUserID not in group " + req.NewOwnerUserID) + return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID not in group " + req.NewOwnerUserID) } newOwner := memberMap[req.NewOwnerUserID] if newOwner == nil { - return nil, errs.ErrArgs.Wrap("NewOwnerUser not in group " + req.NewOwnerUserID) + return nil, errs.ErrArgs.WrapMsg("NewOwnerUser not in group " + req.NewOwnerUserID) } - if !authverify.IsAppManagerUid(ctx, s.config) { + if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { if !(mcontext.GetOpUserID(ctx) == oldOwner.UserID && oldOwner.RoleLevel == constant.GroupOwner) { - return nil, errs.ErrNoPermission.Wrap("no permission transfer group owner") + return nil, errs.ErrNoPermission.WrapMsg("no permission transfer group owner") } } if err := s.db.TransferGroupOwner(ctx, req.GroupID, req.OldOwnerUserID, req.NewOwnerUserID, newOwner.RoleLevel); err != nil { return nil, err } - if err := CallbackAfterTransferGroupOwner(ctx, s.config, req); err != nil { - return nil, err - } - s.Notification.GroupOwnerTransferredNotification(ctx, req) - return resp, nil + s.webhookAfterTransferGroupOwner(ctx, &s.config.WebhooksConfig.AfterTransferGroupOwner, req) + + s.notification.GroupOwnerTransferredNotification(ctx, req) + return &pbgroup.TransferGroupOwnerResp{}, nil } func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq) (*pbgroup.GetGroupsResp, error) { - resp := &pbgroup.GetGroupsResp{} var ( group []*relationtb.GroupModel err error ) - + var resp pbgroup.GetGroupsResp if req.GroupID != "" { group, err = s.db.FindGroup(ctx, []string{req.GroupID}) resp.Total = uint32(len(group)) @@ -1084,7 +1095,7 @@ func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq) return nil, err } - groupIDs := utils.Slice(group, func(e *relationtb.GroupModel) string { + groupIDs := datautil.Slice(group, func(e *relationtb.GroupModel) string { return e.GroupID }) @@ -1093,14 +1104,14 @@ func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq) return nil, err } - ownerMemberMap := utils.SliceToMap(ownerMembers, func(e *relationtb.GroupMemberModel) string { + ownerMemberMap := datautil.SliceToMap(ownerMembers, func(e *relationtb.GroupMemberModel) string { return e.GroupID }) groupMemberNumMap, err := s.db.MapGroupMemberNum(ctx, groupIDs) if err != nil { return nil, err } - resp.Groups = utils.Slice(group, func(group *relationtb.GroupModel) *pbgroup.CMSGroup { + resp.Groups = datautil.Slice(group, func(group *relationtb.GroupModel) *pbgroup.CMSGroup { var ( userID string username string @@ -1111,28 +1122,27 @@ func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq) } return convert.Db2PbCMSGroup(group, userID, username, groupMemberNumMap[group.GroupID]) }) - return resp, nil + return &resp, nil } func (s *groupServer) GetGroupMembersCMS(ctx context.Context, req *pbgroup.GetGroupMembersCMSReq) (*pbgroup.GetGroupMembersCMSResp, error) { - resp := &pbgroup.GetGroupMembersCMSResp{} total, members, err := s.db.SearchGroupMember(ctx, req.UserName, req.GroupID, req.Pagination) if err != nil { return nil, err } + var resp pbgroup.GetGroupMembersCMSResp resp.Total = uint32(total) if err := s.PopulateGroupMember(ctx, members...); err != nil { return nil, err } - resp.Members = utils.Slice(members, func(e *relationtb.GroupMemberModel) *sdkws.GroupMemberFullInfo { + resp.Members = datautil.Slice(members, func(e *relationtb.GroupMemberModel) *sdkws.GroupMemberFullInfo { return convert.Db2PbGroupMember(e) }) - return resp, nil + return &resp, nil } func (s *groupServer) GetUserReqApplicationList(ctx context.Context, req *pbgroup.GetUserReqApplicationListReq) (*pbgroup.GetUserReqApplicationListResp, error) { - resp := &pbgroup.GetUserReqApplicationListResp{} - user, err := s.User.GetPublicUserInfo(ctx, req.UserID) + user, err := s.user.GetPublicUserInfo(ctx, req.UserID) if err != nil { return nil, err } @@ -1140,18 +1150,17 @@ func (s *groupServer) GetUserReqApplicationList(ctx context.Context, req *pbgrou if err != nil { return nil, err } - resp.Total = uint32(total) if len(requests) == 0 { - return resp, nil + return &pbgroup.GetUserReqApplicationListResp{Total: uint32(total)}, nil } - groupIDs := utils.Distinct(utils.Slice(requests, func(e *relationtb.GroupRequestModel) string { + groupIDs := datautil.Distinct(datautil.Slice(requests, func(e *relationtb.GroupRequestModel) string { return e.GroupID })) groups, err := s.db.FindGroup(ctx, groupIDs) if err != nil { return nil, err } - groupMap := utils.SliceToMap(groups, func(e *relationtb.GroupModel) string { + groupMap := datautil.SliceToMap(groups, func(e *relationtb.GroupModel) string { return e.GroupID }) owners, err := s.db.FindGroupsOwner(ctx, groupIDs) @@ -1161,33 +1170,33 @@ func (s *groupServer) GetUserReqApplicationList(ctx context.Context, req *pbgrou if err := s.PopulateGroupMember(ctx, owners...); err != nil { return nil, err } - ownerMap := utils.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string { + ownerMap := datautil.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string { return e.GroupID }) groupMemberNum, err := s.db.MapGroupMemberNum(ctx, groupIDs) if err != nil { return nil, err } - resp.GroupRequests = utils.Slice(requests, func(e *relationtb.GroupRequestModel) *sdkws.GroupRequest { - var ownerUserID string - if owner, ok := ownerMap[e.GroupID]; ok { - ownerUserID = owner.UserID - } - return convert.Db2PbGroupRequest(e, user, convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerUserID, groupMemberNum[e.GroupID])) - }) - return resp, nil + return &pbgroup.GetUserReqApplicationListResp{ + Total: uint32(total), + GroupRequests: datautil.Slice(requests, func(e *relationtb.GroupRequestModel) *sdkws.GroupRequest { + var ownerUserID string + if owner, ok := ownerMap[e.GroupID]; ok { + ownerUserID = owner.UserID + } + return convert.Db2PbGroupRequest(e, user, convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerUserID, groupMemberNum[e.GroupID])) + }), + }, nil } func (s *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGroupReq) (*pbgroup.DismissGroupResp, error) { - defer log.ZInfo(ctx, "DismissGroup.return") - resp := &pbgroup.DismissGroupResp{} owner, err := s.db.TakeGroupOwner(ctx, req.GroupID) if err != nil { return nil, err } - if !authverify.IsAppManagerUid(ctx, s.config) { + if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { if owner.UserID != mcontext.GetOpUserID(ctx) { - return nil, errs.ErrNoPermission.Wrap("not group owner") + return nil, errs.ErrNoPermission.WrapMsg("not group owner") } } if err := s.PopulateGroupMember(ctx, owner); err != nil { @@ -1198,7 +1207,7 @@ func (s *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGrou return nil, err } if !req.DeleteMember && group.Status == constant.GroupStatusDismissed { - return nil, errs.ErrDismissedAlready.Wrap("group status is dismissed") + return nil, servererrs.ErrDismissedAlready.WrapMsg("group status is dismissed") } if err := s.db.DismissGroup(ctx, req.GroupID, req.DeleteMember); err != nil { return nil, err @@ -1215,27 +1224,25 @@ func (s *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGrou if mcontext.GetOpUserID(ctx) == owner.UserID { tips.OpUser = s.groupMemberDB2PB(owner, 0) } - s.Notification.GroupDismissedNotification(ctx, tips) + s.notification.GroupDismissedNotification(ctx, tips) } membersID, err := s.db.FindGroupMemberUserID(ctx, group.GroupID) if err != nil { return nil, err } - reqCall := &callbackstruct.CallbackDisMissGroupReq{ + cbReq := &callbackstruct.CallbackDisMissGroupReq{ GroupID: req.GroupID, OwnerID: owner.UserID, MembersID: membersID, GroupType: string(group.GroupType), } - if err := CallbackDismissGroup(ctx, s.config, reqCall); err != nil { - return nil, err - } - return resp, nil + s.webhookAfterDismissGroup(ctx, &s.config.WebhooksConfig.AfterDismissGroup, cbReq) + + return &pbgroup.DismissGroupResp{}, nil } func (s *groupServer) MuteGroupMember(ctx context.Context, req *pbgroup.MuteGroupMemberReq) (*pbgroup.MuteGroupMemberResp, error) { - resp := &pbgroup.MuteGroupMemberResp{} member, err := s.db.TakeGroupMember(ctx, req.GroupID, req.UserID) if err != nil { return nil, err @@ -1243,21 +1250,21 @@ func (s *groupServer) MuteGroupMember(ctx context.Context, req *pbgroup.MuteGrou if err := s.PopulateGroupMember(ctx, member); err != nil { return nil, err } - if !authverify.IsAppManagerUid(ctx, s.config) { + if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { opMember, err := s.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx)) if err != nil { return nil, err } switch member.RoleLevel { case constant.GroupOwner: - return nil, errs.ErrNoPermission.Wrap("set group owner mute") + return nil, errs.ErrNoPermission.WrapMsg("set group owner mute") case constant.GroupAdmin: if opMember.RoleLevel != constant.GroupOwner { - return nil, errs.ErrNoPermission.Wrap("set group admin mute") + return nil, errs.ErrNoPermission.WrapMsg("set group admin mute") } case constant.GroupOrdinaryUsers: if !(opMember.RoleLevel == constant.GroupAdmin || opMember.RoleLevel == constant.GroupOwner) { - return nil, errs.ErrNoPermission.Wrap("set group ordinary users mute") + return nil, errs.ErrNoPermission.WrapMsg("set group ordinary users mute") } } } @@ -1265,8 +1272,8 @@ func (s *groupServer) MuteGroupMember(ctx context.Context, req *pbgroup.MuteGrou if err := s.db.UpdateGroupMember(ctx, member.GroupID, member.UserID, data); err != nil { return nil, err } - s.Notification.GroupMemberMutedNotification(ctx, req.GroupID, req.UserID, req.MutedSeconds) - return resp, nil + s.notification.GroupMemberMutedNotification(ctx, req.GroupID, req.UserID, req.MutedSeconds) + return &pbgroup.MuteGroupMemberResp{}, nil } func (s *groupServer) CancelMuteGroupMember(ctx context.Context, req *pbgroup.CancelMuteGroupMemberReq) (*pbgroup.CancelMuteGroupMemberResp, error) { @@ -1277,21 +1284,21 @@ func (s *groupServer) CancelMuteGroupMember(ctx context.Context, req *pbgroup.Ca if err := s.PopulateGroupMember(ctx, member); err != nil { return nil, err } - if !authverify.IsAppManagerUid(ctx, s.config) { + if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { opMember, err := s.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx)) if err != nil { return nil, err } switch member.RoleLevel { case constant.GroupOwner: - return nil, errs.ErrNoPermission.Wrap("set group owner mute") + return nil, errs.ErrNoPermission.WrapMsg("set group owner mute") case constant.GroupAdmin: if opMember.RoleLevel != constant.GroupOwner { - return nil, errs.ErrNoPermission.Wrap("set group admin mute") + return nil, errs.ErrNoPermission.WrapMsg("set group admin mute") } case constant.GroupOrdinaryUsers: if !(opMember.RoleLevel == constant.GroupAdmin || opMember.RoleLevel == constant.GroupOwner) { - return nil, errs.ErrNoPermission.Wrap("set group ordinary users mute") + return nil, errs.ErrNoPermission.WrapMsg("set group ordinary users mute") } } } @@ -1299,44 +1306,41 @@ func (s *groupServer) CancelMuteGroupMember(ctx context.Context, req *pbgroup.Ca if err := s.db.UpdateGroupMember(ctx, member.GroupID, member.UserID, data); err != nil { return nil, err } - s.Notification.GroupMemberCancelMutedNotification(ctx, req.GroupID, req.UserID) + s.notification.GroupMemberCancelMutedNotification(ctx, req.GroupID, req.UserID) return &pbgroup.CancelMuteGroupMemberResp{}, nil } func (s *groupServer) MuteGroup(ctx context.Context, req *pbgroup.MuteGroupReq) (*pbgroup.MuteGroupResp, error) { - resp := &pbgroup.MuteGroupResp{} if err := s.CheckGroupAdmin(ctx, req.GroupID); err != nil { return nil, err } if err := s.db.UpdateGroup(ctx, req.GroupID, UpdateGroupStatusMap(constant.GroupStatusMuted)); err != nil { return nil, err } - s.Notification.GroupMutedNotification(ctx, req.GroupID) - return resp, nil + s.notification.GroupMutedNotification(ctx, req.GroupID) + return &pbgroup.MuteGroupResp{}, nil } func (s *groupServer) CancelMuteGroup(ctx context.Context, req *pbgroup.CancelMuteGroupReq) (*pbgroup.CancelMuteGroupResp, error) { - resp := &pbgroup.CancelMuteGroupResp{} if err := s.CheckGroupAdmin(ctx, req.GroupID); err != nil { return nil, err } if err := s.db.UpdateGroup(ctx, req.GroupID, UpdateGroupStatusMap(constant.GroupOk)); err != nil { return nil, err } - s.Notification.GroupCancelMutedNotification(ctx, req.GroupID) - return resp, nil + s.notification.GroupCancelMutedNotification(ctx, req.GroupID) + return &pbgroup.CancelMuteGroupResp{}, nil } func (s *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGroupMemberInfoReq) (*pbgroup.SetGroupMemberInfoResp, error) { - resp := &pbgroup.SetGroupMemberInfoResp{} if len(req.Members) == 0 { - return nil, errs.ErrArgs.Wrap("members empty") + return nil, errs.ErrArgs.WrapMsg("members empty") } opUserID := mcontext.GetOpUserID(ctx) if opUserID == "" { - return nil, errs.ErrNoPermission.Wrap("no op user id") + return nil, errs.ErrNoPermission.WrapMsg("no op user id") } - isAppManagerUid := authverify.IsAppManagerUid(ctx, s.config) + isAppManagerUid := authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) for i := range req.Members { req.Members[i].FaceURL = nil } @@ -1345,10 +1349,10 @@ func (s *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr if member.RoleLevel != nil { switch member.RoleLevel.Value { case constant.GroupOwner: - return nil, errs.ErrNoPermission.Wrap("cannot set ungroup owner") + return nil, errs.ErrNoPermission.WrapMsg("cannot set ungroup owner") case constant.GroupAdmin, constant.GroupOrdinaryUsers: default: - return nil, errs.ErrArgs.Wrap("invalid role level") + return nil, errs.ErrArgs.WrapMsg("invalid role level") } } groupMembers[member.GroupID] = append(groupMembers[member.GroupID], req.Members[i]) @@ -1358,7 +1362,7 @@ func (s *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr userIDs := make([]string, 0, len(members)+1) for _, member := range members { if _, ok := temp[member.UserID]; ok { - return nil, errs.ErrArgs.Wrap(fmt.Sprintf("repeat group %s user %s", member.GroupID, member.UserID)) + return nil, errs.ErrArgs.WrapMsg(fmt.Sprintf("repeat group %s user %s", member.GroupID, member.UserID)) } temp[member.UserID] = struct{}{} userIDs = append(userIDs, member.UserID) @@ -1386,22 +1390,22 @@ func (s *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr case constant.GroupAdmin: for _, member := range dbMembers { if member.RoleLevel == constant.GroupOwner { - return nil, errs.ErrNoPermission.Wrap("admin can not change group owner") + return nil, errs.ErrNoPermission.WrapMsg("admin can not change group owner") } if member.RoleLevel == constant.GroupAdmin && member.UserID != opUserID { - return nil, errs.ErrNoPermission.Wrap("admin can not change other group admin") + return nil, errs.ErrNoPermission.WrapMsg("admin can not change other group admin") } } case constant.GroupOrdinaryUsers: for _, member := range dbMembers { if !(member.RoleLevel == constant.GroupOrdinaryUsers && member.UserID == opUserID) { - return nil, errs.ErrNoPermission.Wrap("ordinary users can not change other role level") + return nil, errs.ErrNoPermission.WrapMsg("ordinary users can not change other role level") } } default: for _, member := range dbMembers { if member.RoleLevel >= roleLevel { - return nil, errs.ErrNoPermission.Wrap("can not change higher role level") + return nil, errs.ErrNoPermission.WrapMsg("can not change higher role level") } } } @@ -1409,21 +1413,24 @@ func (s *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr } case 1: if opUserIndex >= 0 { - return nil, errs.ErrArgs.Wrap("user not in group") + return nil, errs.ErrArgs.WrapMsg("user not in group") } if !isAppManagerUid { - return nil, errs.ErrNoPermission.Wrap("user not in group") + return nil, errs.ErrNoPermission.WrapMsg("user not in group") } default: - return nil, errs.ErrArgs.Wrap("user not in group") + return nil, errs.ErrArgs.WrapMsg("user not in group") } } + for i := 0; i < len(req.Members); i++ { - if err := CallbackBeforeSetGroupMemberInfo(ctx, s.config, req.Members[i]); err != nil { + + if err := s.webhookBeforeSetGroupMemberInfo(ctx, &s.config.WebhooksConfig.BeforeSetGroupMemberInfo, req.Members[i]); err != nil && err != servererrs.ErrCallbackContinue { return nil, err } + } - if err := s.db.UpdateGroupMembers(ctx, utils.Slice(req.Members, func(e *pbgroup.SetGroupMemberInfo) *relationtb.BatchUpdateGroupMember { + if err := s.db.UpdateGroupMembers(ctx, datautil.Slice(req.Members, func(e *pbgroup.SetGroupMemberInfo) *relationtb.BatchUpdateGroupMember { return &relationtb.BatchUpdateGroupMember{ GroupID: e.GroupID, UserID: e.UserID, @@ -1436,59 +1443,56 @@ func (s *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr if member.RoleLevel != nil { switch member.RoleLevel.Value { case constant.GroupAdmin: - s.Notification.GroupMemberSetToAdminNotification(ctx, member.GroupID, member.UserID) + s.notification.GroupMemberSetToAdminNotification(ctx, member.GroupID, member.UserID) case constant.GroupOrdinaryUsers: - s.Notification.GroupMemberSetToOrdinaryUserNotification(ctx, member.GroupID, member.UserID) + s.notification.GroupMemberSetToOrdinaryUserNotification(ctx, member.GroupID, member.UserID) } } if member.Nickname != nil || member.FaceURL != nil || member.Ex != nil { - s.Notification.GroupMemberInfoSetNotification(ctx, member.GroupID, member.UserID) + s.notification.GroupMemberInfoSetNotification(ctx, member.GroupID, member.UserID) } } for i := 0; i < len(req.Members); i++ { - if err := CallbackAfterSetGroupMemberInfo(ctx, s.config, req.Members[i]); err != nil { - return nil, err - } + s.webhookAfterSetGroupMemberInfo(ctx, &s.config.WebhooksConfig.AfterSetGroupMemberInfo, req.Members[i]) } - return resp, nil + return &pbgroup.SetGroupMemberInfoResp{}, nil } func (s *groupServer) GetGroupAbstractInfo(ctx context.Context, req *pbgroup.GetGroupAbstractInfoReq) (*pbgroup.GetGroupAbstractInfoResp, error) { - resp := &pbgroup.GetGroupAbstractInfoResp{} if len(req.GroupIDs) == 0 { - return nil, errs.ErrArgs.Wrap("groupIDs empty") + return nil, errs.ErrArgs.WrapMsg("groupIDs empty") } - if utils.Duplicate(req.GroupIDs) { - return nil, errs.ErrArgs.Wrap("groupIDs duplicate") + if datautil.Duplicate(req.GroupIDs) { + return nil, errs.ErrArgs.WrapMsg("groupIDs duplicate") } groups, err := s.db.FindGroup(ctx, req.GroupIDs) if err != nil { return nil, err } - if ids := utils.Single(req.GroupIDs, utils.Slice(groups, func(group *relationtb.GroupModel) string { + if ids := datautil.Single(req.GroupIDs, datautil.Slice(groups, func(group *relationtb.GroupModel) string { return group.GroupID })); len(ids) > 0 { - return nil, errs.ErrGroupIDNotFound.Wrap("not found group " + strings.Join(ids, ",")) + return nil, servererrs.ErrGroupIDNotFound.WrapMsg("not found group " + strings.Join(ids, ",")) } groupUserMap, err := s.db.MapGroupMemberUserID(ctx, req.GroupIDs) if err != nil { return nil, err } - if ids := utils.Single(req.GroupIDs, utils.Keys(groupUserMap)); len(ids) > 0 { - return nil, errs.ErrGroupIDNotFound.Wrap(fmt.Sprintf("group %s not found member", strings.Join(ids, ","))) + if ids := datautil.Single(req.GroupIDs, datautil.Keys(groupUserMap)); len(ids) > 0 { + return nil, servererrs.ErrGroupIDNotFound.WrapMsg(fmt.Sprintf("group %s not found member", strings.Join(ids, ","))) } - resp.GroupAbstractInfos = utils.Slice(groups, func(group *relationtb.GroupModel) *pbgroup.GroupAbstractInfo { - users := groupUserMap[group.GroupID] - return convert.Db2PbGroupAbstractInfo(group.GroupID, users.MemberNum, users.Hash) - }) - return resp, nil + return &pbgroup.GetGroupAbstractInfoResp{ + GroupAbstractInfos: datautil.Slice(groups, func(group *relationtb.GroupModel) *pbgroup.GroupAbstractInfo { + users := groupUserMap[group.GroupID] + return convert.Db2PbGroupAbstractInfo(group.GroupID, users.MemberNum, users.Hash) + }), + }, nil } func (s *groupServer) GetUserInGroupMembers(ctx context.Context, req *pbgroup.GetUserInGroupMembersReq) (*pbgroup.GetUserInGroupMembersResp, error) { - resp := &pbgroup.GetUserInGroupMembersResp{} if len(req.GroupIDs) == 0 { - return nil, errs.ErrArgs.Wrap("groupIDs empty") + return nil, errs.ErrArgs.WrapMsg("groupIDs empty") } members, err := s.db.FindGroupMemberUser(ctx, req.GroupIDs, req.UserID) if err != nil { @@ -1497,25 +1501,26 @@ func (s *groupServer) GetUserInGroupMembers(ctx context.Context, req *pbgroup.Ge if err := s.PopulateGroupMember(ctx, members...); err != nil { return nil, err } - resp.Members = utils.Slice(members, func(e *relationtb.GroupMemberModel) *sdkws.GroupMemberFullInfo { - return convert.Db2PbGroupMember(e) - }) - return resp, nil + return &pbgroup.GetUserInGroupMembersResp{ + Members: datautil.Slice(members, func(e *relationtb.GroupMemberModel) *sdkws.GroupMemberFullInfo { + return convert.Db2PbGroupMember(e) + }), + }, nil } -func (s *groupServer) GetGroupMemberUserIDs(ctx context.Context, req *pbgroup.GetGroupMemberUserIDsReq) (resp *pbgroup.GetGroupMemberUserIDsResp, err error) { - resp = &pbgroup.GetGroupMemberUserIDsResp{} - resp.UserIDs, err = s.db.FindGroupMemberUserID(ctx, req.GroupID) +func (s *groupServer) GetGroupMemberUserIDs(ctx context.Context, req *pbgroup.GetGroupMemberUserIDsReq) (*pbgroup.GetGroupMemberUserIDsResp, error) { + userIDs, err := s.db.FindGroupMemberUserID(ctx, req.GroupID) if err != nil { return nil, err } - return resp, nil + return &pbgroup.GetGroupMemberUserIDsResp{ + UserIDs: userIDs, + }, nil } func (s *groupServer) GetGroupMemberRoleLevel(ctx context.Context, req *pbgroup.GetGroupMemberRoleLevelReq) (*pbgroup.GetGroupMemberRoleLevelResp, error) { - resp := &pbgroup.GetGroupMemberRoleLevelResp{} if len(req.RoleLevels) == 0 { - return nil, errs.ErrArgs.Wrap("RoleLevels empty") + return nil, errs.ErrArgs.WrapMsg("RoleLevels empty") } members, err := s.db.FindGroupMemberRoleLevels(ctx, req.GroupID, req.RoleLevels) if err != nil { @@ -1524,33 +1529,33 @@ func (s *groupServer) GetGroupMemberRoleLevel(ctx context.Context, req *pbgroup. if err := s.PopulateGroupMember(ctx, members...); err != nil { return nil, err } - resp.Members = utils.Slice(members, func(e *relationtb.GroupMemberModel) *sdkws.GroupMemberFullInfo { - return convert.Db2PbGroupMember(e) - }) - return resp, nil + return &pbgroup.GetGroupMemberRoleLevelResp{ + Members: datautil.Slice(members, func(e *relationtb.GroupMemberModel) *sdkws.GroupMemberFullInfo { + return convert.Db2PbGroupMember(e) + }), + }, nil } func (s *groupServer) GetGroupUsersReqApplicationList(ctx context.Context, req *pbgroup.GetGroupUsersReqApplicationListReq) (*pbgroup.GetGroupUsersReqApplicationListResp, error) { - resp := &pbgroup.GetGroupUsersReqApplicationListResp{} requests, err := s.db.FindGroupRequests(ctx, req.GroupID, req.UserIDs) if err != nil { return nil, err } if len(requests) == 0 { - return resp, nil + return &pbgroup.GetGroupUsersReqApplicationListResp{}, nil } - groupIDs := utils.Distinct(utils.Slice(requests, func(e *relationtb.GroupRequestModel) string { + groupIDs := datautil.Distinct(datautil.Slice(requests, func(e *relationtb.GroupRequestModel) string { return e.GroupID })) groups, err := s.db.FindGroup(ctx, groupIDs) if err != nil { return nil, err } - groupMap := utils.SliceToMap(groups, func(e *relationtb.GroupModel) string { + groupMap := datautil.SliceToMap(groups, func(e *relationtb.GroupModel) string { return e.GroupID }) - if ids := utils.Single(groupIDs, utils.Keys(groupMap)); len(ids) > 0 { - return nil, errs.ErrGroupIDNotFound.Wrap(strings.Join(ids, ",")) + if ids := datautil.Single(groupIDs, datautil.Keys(groupMap)); len(ids) > 0 { + return nil, servererrs.ErrGroupIDNotFound.WrapMsg(strings.Join(ids, ",")) } owners, err := s.db.FindGroupsOwner(ctx, groupIDs) if err != nil { @@ -1559,20 +1564,21 @@ func (s *groupServer) GetGroupUsersReqApplicationList(ctx context.Context, req * if err := s.PopulateGroupMember(ctx, owners...); err != nil { return nil, err } - ownerMap := utils.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string { + ownerMap := datautil.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string { return e.GroupID }) groupMemberNum, err := s.db.MapGroupMemberNum(ctx, groupIDs) if err != nil { return nil, err } - resp.GroupRequests = utils.Slice(requests, func(e *relationtb.GroupRequestModel) *sdkws.GroupRequest { - var ownerUserID string - if owner, ok := ownerMap[e.GroupID]; ok { - ownerUserID = owner.UserID - } - return convert.Db2PbGroupRequest(e, nil, convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerUserID, groupMemberNum[e.GroupID])) - }) - resp.Total = int64(len(resp.GroupRequests)) - return resp, nil + return &pbgroup.GetGroupUsersReqApplicationListResp{ + Total: int64(len(requests)), + GroupRequests: datautil.Slice(requests, func(e *relationtb.GroupRequestModel) *sdkws.GroupRequest { + var ownerUserID string + if owner, ok := ownerMap[e.GroupID]; ok { + ownerUserID = owner.UserID + } + return convert.Db2PbGroupRequest(e, nil, convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerUserID, groupMemberNum[e.GroupID])) + }), + }, nil } diff --git a/pkg/rpcclient/notification/group.go b/internal/rpc/group/notification.go similarity index 57% rename from pkg/rpcclient/notification/group.go rename to internal/rpc/group/notification.go index c72aa839b..6d7cebcbc 100644 --- a/pkg/rpcclient/notification/group.go +++ b/internal/rpc/group/notification.go @@ -12,35 +12,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -package notification +package group import ( "context" "fmt" + "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" - "github.com/OpenIMSDK/protocol/constant" - pbgroup "github.com/OpenIMSDK/protocol/group" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/authverify" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/constant" + pbgroup "github.com/openimsdk/protocol/group" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/stringutil" ) -func NewGroupNotificationSender( - db controller.GroupDatabase, - msgRpcClient *rpcclient.MessageRpcClient, - userRpcClient *rpcclient.UserRpcClient, - config *config.GlobalConfig, - fn func(ctx context.Context, userIDs []string) ([]CommonUser, error), -) *GroupNotificationSender { +func NewGroupNotificationSender(db controller.GroupDatabase, msgRpcClient *rpcclient.MessageRpcClient, userRpcClient *rpcclient.UserRpcClient, config *Config, fn func(ctx context.Context, userIDs []string) ([]notification.CommonUser, error)) *GroupNotificationSender { return &GroupNotificationSender{ - NotificationSender: rpcclient.NewNotificationSender(config, rpcclient.WithRpcClient(msgRpcClient), rpcclient.WithUserRpcClient(userRpcClient)), + NotificationSender: rpcclient.NewNotificationSender(&config.NotificationConfig, rpcclient.WithRpcClient(msgRpcClient), rpcclient.WithUserRpcClient(userRpcClient)), getUsersInfo: fn, db: db, config: config, @@ -49,9 +45,9 @@ func NewGroupNotificationSender( type GroupNotificationSender struct { *rpcclient.NotificationSender - getUsersInfo func(ctx context.Context, userIDs []string) ([]CommonUser, error) + getUsersInfo func(ctx context.Context, userIDs []string) ([]notification.CommonUser, error) db controller.GroupDatabase - config *config.GlobalConfig + config *Config } func (g *GroupNotificationSender) PopulateGroupMember(ctx context.Context, members ...*relation.GroupMemberModel) error { @@ -65,11 +61,11 @@ func (g *GroupNotificationSender) PopulateGroupMember(ctx context.Context, membe } } if len(emptyUserIDs) > 0 { - users, err := g.getUsersInfo(ctx, utils.Keys(emptyUserIDs)) + users, err := g.getUsersInfo(ctx, datautil.Keys(emptyUserIDs)) if err != nil { return err } - userMap := make(map[string]CommonUser) + userMap := make(map[string]notification.CommonUser) for i, user := range users { userMap[user.GetUserID()] = users[i] } @@ -95,7 +91,7 @@ func (g *GroupNotificationSender) getUser(ctx context.Context, userID string) (* return nil, err } if len(users) == 0 { - return nil, errs.ErrUserIDNotFound.Wrap(fmt.Sprintf("user %s not found", userID)) + return nil, servererrs.ErrUserIDNotFound.WrapMsg(fmt.Sprintf("user %s not found", userID)) } return &sdkws.PublicUserInfo{ UserID: users[0].GetUserID(), @@ -177,7 +173,7 @@ func (g *GroupNotificationSender) getGroupMember(ctx context.Context, groupID st return nil, err } if len(members) == 0 { - return nil, errs.ErrInternalServer.Wrap(fmt.Sprintf("group %s member %s not found", groupID, userID)) + return nil, errs.ErrInternalServer.WrapMsg(fmt.Sprintf("group %s member %s not found", groupID, userID)) } return members[0], nil } @@ -191,7 +187,7 @@ func (g *GroupNotificationSender) getGroupOwnerAndAdminUserID(ctx context.Contex return nil, err } fn := func(e *relation.GroupMemberModel) string { return e.UserID } - return utils.Slice(members, fn), nil + return datautil.Slice(members, fn), nil } //nolint:unused @@ -248,11 +244,11 @@ func (g *GroupNotificationSender) groupMemberDB2PB(member *relation.GroupMemberM func (g *GroupNotificationSender) fillOpUser(ctx context.Context, opUser **sdkws.GroupMemberFullInfo, groupID string) (err error) { if opUser == nil { - return errs.ErrInternalServer.Wrap("**sdkws.GroupMemberFullInfo is nil") + return errs.ErrInternalServer.WrapMsg("**sdkws.GroupMemberFullInfo is nil") } userID := mcontext.GetOpUserID(ctx) if groupID != "" { - if authverify.IsManagerUserID(userID, g.config) { + if authverify.IsManagerUserID(userID, g.config.Share.IMAdminUserID) { *opUser = &sdkws.GroupMemberFullInfo{ GroupID: groupID, UserID: userID, @@ -291,121 +287,122 @@ func (g *GroupNotificationSender) fillOpUser(ctx context.Context, opUser **sdkws return nil } -func (g *GroupNotificationSender) GroupCreatedNotification(ctx context.Context, tips *sdkws.GroupCreatedTips) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupCreatedNotification(ctx context.Context, tips *sdkws.GroupCreatedTips) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupCreatedNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupCreatedNotification, tips) } -func (g *GroupNotificationSender) GroupInfoSetNotification(ctx context.Context, tips *sdkws.GroupInfoSetTips) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupInfoSetNotification(ctx context.Context, tips *sdkws.GroupInfoSetTips) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNotification, tips, rpcclient.WithRpcGetUserName()) + g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNotification, tips, rpcclient.WithRpcGetUserName()) } -func (g *GroupNotificationSender) GroupInfoSetNameNotification(ctx context.Context, tips *sdkws.GroupInfoSetNameTips) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupInfoSetNameNotification(ctx context.Context, tips *sdkws.GroupInfoSetNameTips) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNameNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNameNotification, tips) } -func (g *GroupNotificationSender) GroupInfoSetAnnouncementNotification(ctx context.Context, tips *sdkws.GroupInfoSetAnnouncementTips) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupInfoSetAnnouncementNotification(ctx context.Context, tips *sdkws.GroupInfoSetAnnouncementTips) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetAnnouncementNotification, tips, rpcclient.WithRpcGetUserName()) + g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetAnnouncementNotification, tips, rpcclient.WithRpcGetUserName()) } -func (g *GroupNotificationSender) JoinGroupApplicationNotification(ctx context.Context, req *pbgroup.JoinGroupReq) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) JoinGroupApplicationNotification(ctx context.Context, req *pbgroup.JoinGroupReq) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, req.GroupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, req.GroupID) if err != nil { - return err + return } - user, err := g.getUser(ctx, req.InviterUserID) + var user *sdkws.PublicUserInfo + user, err = g.getUser(ctx, req.InviterUserID) if err != nil { - return err + return } userIDs, err := g.getGroupOwnerAndAdminUserID(ctx, req.GroupID) if err != nil { - return err + return } userIDs = append(userIDs, req.InviterUserID, mcontext.GetOpUserID(ctx)) tips := &sdkws.JoinGroupApplicationTips{Group: group, Applicant: user, ReqMsg: req.ReqMessage} - for _, userID := range utils.Distinct(userIDs) { - err = g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.JoinGroupApplicationNotification, tips) - if err != nil { - log.ZError(ctx, "JoinGroupApplicationNotification failed", err, "group", req.GroupID, "userID", userID) - } + for _, userID := range datautil.Distinct(userIDs) { + g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.JoinGroupApplicationNotification, tips) } - return nil } -func (g *GroupNotificationSender) MemberQuitNotification(ctx context.Context, member *sdkws.GroupMemberFullInfo) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) MemberQuitNotification(ctx context.Context, member *sdkws.GroupMemberFullInfo) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, member.GroupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, member.GroupID) if err != nil { - return err + return } tips := &sdkws.MemberQuitTips{Group: group, QuitUser: member} - return g.Notification(ctx, mcontext.GetOpUserID(ctx), member.GroupID, constant.MemberQuitNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), member.GroupID, constant.MemberQuitNotification, tips) } -func (g *GroupNotificationSender) GroupApplicationAcceptedNotification(ctx context.Context, req *pbgroup.GroupApplicationResponseReq) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupApplicationAcceptedNotification(ctx context.Context, req *pbgroup.GroupApplicationResponseReq) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, req.GroupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, req.GroupID) if err != nil { - return err + return } - userIDs, err := g.getGroupOwnerAndAdminUserID(ctx, req.GroupID) + var userIDs []string + userIDs, err = g.getGroupOwnerAndAdminUserID(ctx, req.GroupID) if err != nil { - return err + return } tips := &sdkws.GroupApplicationAcceptedTips{Group: group, HandleMsg: req.HandledMsg} if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + return } for _, userID := range append(userIDs, req.FromUserID) { if userID == req.FromUserID { @@ -413,32 +410,30 @@ func (g *GroupNotificationSender) GroupApplicationAcceptedNotification(ctx conte } else { tips.ReceiverAs = 1 } - err = g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationAcceptedNotification, tips) - if err != nil { - log.ZError(ctx, "failed", err) - } + g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationAcceptedNotification, tips) } - return nil } -func (g *GroupNotificationSender) GroupApplicationRejectedNotification(ctx context.Context, req *pbgroup.GroupApplicationResponseReq) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupApplicationRejectedNotification(ctx context.Context, req *pbgroup.GroupApplicationResponseReq) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, req.GroupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, req.GroupID) if err != nil { - return err + return } - userIDs, err := g.getGroupOwnerAndAdminUserID(ctx, req.GroupID) + var userIDs []string + userIDs, err = g.getGroupOwnerAndAdminUserID(ctx, req.GroupID) if err != nil { - return err + return } tips := &sdkws.GroupApplicationRejectedTips{Group: group, HandleMsg: req.HandledMsg} if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + return } for _, userID := range append(userIDs, req.FromUserID) { if userID == req.FromUserID { @@ -446,265 +441,281 @@ func (g *GroupNotificationSender) GroupApplicationRejectedNotification(ctx conte } else { tips.ReceiverAs = 1 } - err = g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationRejectedNotification, tips) - if err != nil { - log.ZError(ctx, "failed", err) - } + g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationRejectedNotification, tips) } - return nil } -func (g *GroupNotificationSender) GroupOwnerTransferredNotification(ctx context.Context, req *pbgroup.TransferGroupOwnerReq) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupOwnerTransferredNotification(ctx context.Context, req *pbgroup.TransferGroupOwnerReq) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, req.GroupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, req.GroupID) if err != nil { - return err + return } opUserID := mcontext.GetOpUserID(ctx) - member, err := g.getGroupMemberMap(ctx, req.GroupID, []string{opUserID, req.NewOwnerUserID}) + var member map[string]*sdkws.GroupMemberFullInfo + member, err = g.getGroupMemberMap(ctx, req.GroupID, []string{opUserID, req.NewOwnerUserID}) if err != nil { - return err + return } tips := &sdkws.GroupOwnerTransferredTips{Group: group, OpUser: member[opUserID], NewGroupOwner: member[req.NewOwnerUserID]} - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupOwnerTransferredNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupOwnerTransferredNotification, tips) } -func (g *GroupNotificationSender) MemberKickedNotification(ctx context.Context, tips *sdkws.MemberKickedTips) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) MemberKickedNotification(ctx context.Context, tips *sdkws.MemberKickedTips) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.MemberKickedNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.MemberKickedNotification, tips) } -func (g *GroupNotificationSender) MemberInvitedNotification(ctx context.Context, groupID, reason string, invitedUserIDList []string) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) MemberInvitedNotification(ctx context.Context, groupID, reason string, invitedUserIDList []string) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, groupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, groupID) if err != nil { - return err + return } + + var users []*sdkws.GroupMemberFullInfo + users, err = g.getGroupMembers(ctx, groupID, invitedUserIDList) if err != nil { - return err - } - users, err := g.getGroupMembers(ctx, groupID, invitedUserIDList) - if err != nil { - return err + return } tips := &sdkws.MemberInvitedTips{Group: group, InvitedUserList: users} - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err - } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberInvitedNotification, tips) + err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID) + g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberInvitedNotification, tips) } -func (g *GroupNotificationSender) MemberEnterNotification(ctx context.Context, groupID string, entrantUserID string) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) MemberEnterNotification(ctx context.Context, groupID string, entrantUserID string) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, groupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, groupID) if err != nil { - return err + return } - user, err := g.getGroupMember(ctx, groupID, entrantUserID) + var user *sdkws.GroupMemberFullInfo + user, err = g.getGroupMember(ctx, groupID, entrantUserID) if err != nil { - return err + return } tips := &sdkws.MemberEnterTips{Group: group, EntrantUser: user} - return g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberEnterNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberEnterNotification, tips) } -func (g *GroupNotificationSender) GroupDismissedNotification(ctx context.Context, tips *sdkws.GroupDismissedTips) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupDismissedNotification(ctx context.Context, tips *sdkws.GroupDismissedTips) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupDismissedNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupDismissedNotification, tips) } -func (g *GroupNotificationSender) GroupMemberMutedNotification(ctx context.Context, groupID, groupMemberUserID string, mutedSeconds uint32) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupMemberMutedNotification(ctx context.Context, groupID, groupMemberUserID string, mutedSeconds uint32) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, groupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, groupID) if err != nil { - return err + return } - user, err := g.getGroupMemberMap(ctx, groupID, []string{mcontext.GetOpUserID(ctx), groupMemberUserID}) + var user map[string]*sdkws.GroupMemberFullInfo + user, err = g.getGroupMemberMap(ctx, groupID, []string{mcontext.GetOpUserID(ctx), groupMemberUserID}) if err != nil { - return err + return } tips := &sdkws.GroupMemberMutedTips{ Group: group, MutedSeconds: mutedSeconds, OpUser: user[mcontext.GetOpUserID(ctx)], MutedUser: user[groupMemberUserID], } - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberMutedNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberMutedNotification, tips) } -func (g *GroupNotificationSender) GroupMemberCancelMutedNotification(ctx context.Context, groupID, groupMemberUserID string) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupMemberCancelMutedNotification(ctx context.Context, groupID, groupMemberUserID string) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, groupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, groupID) if err != nil { - return err + return } - user, err := g.getGroupMemberMap(ctx, groupID, []string{mcontext.GetOpUserID(ctx), groupMemberUserID}) + var user map[string]*sdkws.GroupMemberFullInfo + user, err = g.getGroupMemberMap(ctx, groupID, []string{mcontext.GetOpUserID(ctx), groupMemberUserID}) if err != nil { - return err + return } tips := &sdkws.GroupMemberCancelMutedTips{Group: group, OpUser: user[mcontext.GetOpUserID(ctx)], MutedUser: user[groupMemberUserID]} - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberCancelMutedNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberCancelMutedNotification, tips) } -func (g *GroupNotificationSender) GroupMutedNotification(ctx context.Context, groupID string) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupMutedNotification(ctx context.Context, groupID string) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, groupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, groupID) if err != nil { - return err + return } - users, err := g.getGroupMembers(ctx, groupID, []string{mcontext.GetOpUserID(ctx)}) + var users []*sdkws.GroupMemberFullInfo + users, err = g.getGroupMembers(ctx, groupID, []string{mcontext.GetOpUserID(ctx)}) if err != nil { - return err + return } tips := &sdkws.GroupMutedTips{Group: group} if len(users) > 0 { tips.OpUser = users[0] } - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMutedNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMutedNotification, tips) } -func (g *GroupNotificationSender) GroupCancelMutedNotification(ctx context.Context, groupID string) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupCancelMutedNotification(ctx context.Context, groupID string) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, groupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, groupID) if err != nil { - return err + return } - users, err := g.getGroupMembers(ctx, groupID, []string{mcontext.GetOpUserID(ctx)}) + var users []*sdkws.GroupMemberFullInfo + users, err = g.getGroupMembers(ctx, groupID, []string{mcontext.GetOpUserID(ctx)}) if err != nil { - return err + return } tips := &sdkws.GroupCancelMutedTips{Group: group} if len(users) > 0 { tips.OpUser = users[0] } - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupCancelMutedNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupCancelMutedNotification, tips) } -func (g *GroupNotificationSender) GroupMemberInfoSetNotification(ctx context.Context, groupID, groupMemberUserID string) (err error) { - group, err := g.getGroupInfo(ctx, groupID) +func (g *GroupNotificationSender) GroupMemberInfoSetNotification(ctx context.Context, groupID, groupMemberUserID string) { + var err error + defer func() { + if err != nil { + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) + } + }() + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, groupID) if err != nil { - return err + return } - user, err := g.getGroupMemberMap(ctx, groupID, []string{groupMemberUserID}) + var user map[string]*sdkws.GroupMemberFullInfo + user, err = g.getGroupMemberMap(ctx, groupID, []string{groupMemberUserID}) if err != nil { - return err + return } tips := &sdkws.GroupMemberInfoSetTips{Group: group, OpUser: user[mcontext.GetOpUserID(ctx)], ChangedUser: user[groupMemberUserID]} - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberInfoSetNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberInfoSetNotification, tips) } -func (g *GroupNotificationSender) GroupMemberSetToAdminNotification(ctx context.Context, groupID, groupMemberUserID string) (err error) { - group, err := g.getGroupInfo(ctx, groupID) +func (g *GroupNotificationSender) GroupMemberSetToAdminNotification(ctx context.Context, groupID, groupMemberUserID string) { + var err error + defer func() { + if err != nil { + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) + } + }() + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, groupID) if err != nil { - return err + return } user, err := g.getGroupMemberMap(ctx, groupID, []string{mcontext.GetOpUserID(ctx), groupMemberUserID}) if err != nil { - return err + return } tips := &sdkws.GroupMemberInfoSetTips{Group: group, OpUser: user[mcontext.GetOpUserID(ctx)], ChangedUser: user[groupMemberUserID]} - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToAdminNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToAdminNotification, tips) } -func (g *GroupNotificationSender) GroupMemberSetToOrdinaryUserNotification(ctx context.Context, groupID, groupMemberUserID string) (err error) { - defer log.ZDebug(ctx, "return") +func (g *GroupNotificationSender) GroupMemberSetToOrdinaryUserNotification(ctx context.Context, groupID, groupMemberUserID string) { + var err error defer func() { if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) + log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) } }() - group, err := g.getGroupInfo(ctx, groupID) + var group *sdkws.GroupInfo + group, err = g.getGroupInfo(ctx, groupID) if err != nil { - return err + return } - user, err := g.getGroupMemberMap(ctx, groupID, []string{mcontext.GetOpUserID(ctx), groupMemberUserID}) + var user map[string]*sdkws.GroupMemberFullInfo + user, err = g.getGroupMemberMap(ctx, groupID, []string{mcontext.GetOpUserID(ctx), groupMemberUserID}) if err != nil { - return err + return } tips := &sdkws.GroupMemberInfoSetTips{Group: group, OpUser: user[mcontext.GetOpUserID(ctx)], ChangedUser: user[groupMemberUserID]} - if err := g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { - return err + if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { + return } - return g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToOrdinaryUserNotification, tips) + g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToOrdinaryUserNotification, tips) } -func (g *GroupNotificationSender) SuperGroupNotification(ctx context.Context, sendID, recvID string) (err error) { - defer log.ZDebug(ctx, "return") - defer func() { - if err != nil { - log.ZError(ctx, utils.GetFuncName(1)+" failed", err) - } - }() - err = g.Notification(ctx, sendID, recvID, constant.SuperGroupUpdateNotification, nil) - return err +func (g *GroupNotificationSender) SuperGroupNotification(ctx context.Context, sendID, recvID string) { + g.Notification(ctx, sendID, recvID, constant.SuperGroupUpdateNotification, nil) } diff --git a/internal/rpc/group/statistics.go b/internal/rpc/group/statistics.go index d909e9503..6adb1261a 100644 --- a/internal/rpc/group/statistics.go +++ b/internal/rpc/group/statistics.go @@ -18,13 +18,13 @@ import ( "context" "time" - "github.com/OpenIMSDK/protocol/group" - "github.com/OpenIMSDK/tools/errs" + "github.com/openimsdk/protocol/group" + "github.com/openimsdk/tools/errs" ) func (s *groupServer) GroupCreateCount(ctx context.Context, req *group.GroupCreateCountReq) (*group.GroupCreateCountResp, error) { if req.Start > req.End { - return nil, errs.ErrArgs.Wrap("start > end") + return nil, errs.ErrArgs.WrapMsg("start > end: %d > %d", req.Start, req.End) } total, err := s.db.CountTotal(ctx, nil) if err != nil { diff --git a/internal/rpc/group/super_group.go b/internal/rpc/group/super_group.go deleted file mode 100644 index f893a79c2..000000000 --- a/internal/rpc/group/super_group.go +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package group - -import ( - "context" - "errors" - - pbgroup "github.com/OpenIMSDK/protocol/group" -) - -func (s *groupServer) GetJoinedSuperGroupList(context.Context, *pbgroup.GetJoinedSuperGroupListReq) (*pbgroup.GetJoinedSuperGroupListResp, error) { - return nil, errors.New("deprecated") -} - -func (s *groupServer) GetSuperGroupsInfo(context.Context, *pbgroup.GetSuperGroupsInfoReq) (resp *pbgroup.GetSuperGroupsInfoResp, err error) { - return nil, errors.New("deprecated") -} diff --git a/internal/rpc/msg/as_read.go b/internal/rpc/msg/as_read.go index ef7c72368..bfba4824f 100644 --- a/internal/rpc/msg/as_read.go +++ b/internal/rpc/msg/as_read.go @@ -17,19 +17,20 @@ package msg import ( "context" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - utils2 "github.com/OpenIMSDK/tools/utils" cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" "github.com/redis/go-redis/v9" ) -func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *msg.GetConversationsHasReadAndMaxSeqReq) (resp *msg.GetConversationsHasReadAndMaxSeqResp, err error) { +func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *msg.GetConversationsHasReadAndMaxSeqReq) (*msg.GetConversationsHasReadAndMaxSeqResp, error) { var conversationIDs []string if len(req.ConversationIDs) == 0 { + var err error conversationIDs, err = m.ConversationLocalCache.GetConversationIDs(ctx, req.UserID) if err != nil { return nil, err @@ -37,14 +38,17 @@ func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *m } else { conversationIDs = req.ConversationIDs } + hasReadSeqs, err := m.MsgDatabase.GetHasReadSeqs(ctx, req.UserID, conversationIDs) if err != nil { return nil, err } + conversations, err := m.ConversationLocalCache.GetConversations(ctx, req.UserID, conversationIDs) if err != nil { return nil, err } + conversationMaxSeqMap := make(map[string]int64) for _, conversation := range conversations { if conversation.MaxSeq != 0 { @@ -55,95 +59,77 @@ func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *m if err != nil { return nil, err } - resp = &msg.GetConversationsHasReadAndMaxSeqResp{Seqs: make(map[string]*msg.Seqs)} - for conversarionID, maxSeq := range maxSeqs { - resp.Seqs[conversarionID] = &msg.Seqs{ - HasReadSeq: hasReadSeqs[conversarionID], + resp := &msg.GetConversationsHasReadAndMaxSeqResp{Seqs: make(map[string]*msg.Seqs)} + for conversationID, maxSeq := range maxSeqs { + resp.Seqs[conversationID] = &msg.Seqs{ + HasReadSeq: hasReadSeqs[conversationID], MaxSeq: maxSeq, } - if v, ok := conversationMaxSeqMap[conversarionID]; ok { - resp.Seqs[conversarionID].MaxSeq = v + if v, ok := conversationMaxSeqMap[conversationID]; ok { + resp.Seqs[conversationID].MaxSeq = v } } return resp, nil } -func (m *msgServer) SetConversationHasReadSeq( - ctx context.Context, - req *msg.SetConversationHasReadSeqReq, -) (resp *msg.SetConversationHasReadSeqResp, err error) { +func (m *msgServer) SetConversationHasReadSeq(ctx context.Context, req *msg.SetConversationHasReadSeqReq) (*msg.SetConversationHasReadSeqResp, error) { maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID) if err != nil { - return + return nil, err } if req.HasReadSeq > maxSeq { - return nil, errs.ErrArgs.Wrap("hasReadSeq must not be bigger than maxSeq") + return nil, errs.ErrArgs.WrapMsg("hasReadSeq must not be bigger than maxSeq") } if err := m.MsgDatabase.SetHasReadSeq(ctx, req.UserID, req.ConversationID, req.HasReadSeq); err != nil { return nil, err } - if err = m.sendMarkAsReadNotification(ctx, req.ConversationID, constant.SingleChatType, req.UserID, - req.UserID, nil, req.HasReadSeq); err != nil { - return - } + m.sendMarkAsReadNotification(ctx, req.ConversationID, constant.SingleChatType, req.UserID, req.UserID, nil, req.HasReadSeq) return &msg.SetConversationHasReadSeqResp{}, nil } -func (m *msgServer) MarkMsgsAsRead( - ctx context.Context, - req *msg.MarkMsgsAsReadReq, -) (resp *msg.MarkMsgsAsReadResp, err error) { +func (m *msgServer) MarkMsgsAsRead(ctx context.Context, req *msg.MarkMsgsAsReadReq) (*msg.MarkMsgsAsReadResp, error) { if len(req.Seqs) < 1 { - return nil, errs.ErrArgs.Wrap("seqs must not be empty") + return nil, errs.ErrArgs.WrapMsg("seqs must not be empty") } maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID) if err != nil { - return + return nil, err } hasReadSeq := req.Seqs[len(req.Seqs)-1] if hasReadSeq > maxSeq { - return nil, errs.ErrArgs.Wrap("hasReadSeq must not be bigger than maxSeq") + return nil, errs.ErrArgs.WrapMsg("hasReadSeq must not be bigger than maxSeq") } conversation, err := m.ConversationLocalCache.GetConversation(ctx, req.UserID, req.ConversationID) if err != nil { - return + return nil, err } - if err = m.MsgDatabase.MarkSingleChatMsgsAsRead(ctx, req.UserID, req.ConversationID, req.Seqs); err != nil { - return + if err := m.MsgDatabase.MarkSingleChatMsgsAsRead(ctx, req.UserID, req.ConversationID, req.Seqs); err != nil { + return nil, err } - currentHasReadSeq, err := m.MsgDatabase.GetHasReadSeq(ctx, req.UserID, req.ConversationID) if err != nil && errs.Unwrap(err) != redis.Nil { - return + return nil, err } if hasReadSeq > currentHasReadSeq { err = m.MsgDatabase.SetHasReadSeq(ctx, req.UserID, req.ConversationID, hasReadSeq) if err != nil { - return + return nil, err } } - req_callback := &cbapi.CallbackSingleMsgReadReq{ + reqCallback := &cbapi.CallbackSingleMsgReadReq{ ConversationID: conversation.ConversationID, UserID: req.UserID, Seqs: req.Seqs, ContentType: conversation.ConversationType, } - if err = CallbackSingleMsgRead(ctx, m.config, req_callback); err != nil { - return nil, err - } - - if err = m.sendMarkAsReadNotification(ctx, req.ConversationID, conversation.ConversationType, req.UserID, - m.conversationAndGetRecvID(conversation, req.UserID), req.Seqs, hasReadSeq); err != nil { - return - } + m.webhookAfterSingleMsgRead(ctx, &m.config.WebhooksConfig.AfterSingleMsgRead, reqCallback) + m.sendMarkAsReadNotification(ctx, req.ConversationID, conversation.ConversationType, req.UserID, + m.conversationAndGetRecvID(conversation, req.UserID), req.Seqs, hasReadSeq) return &msg.MarkMsgsAsReadResp{}, nil } -func (m *msgServer) MarkConversationAsRead( - ctx context.Context, - req *msg.MarkConversationAsReadReq, -) (resp *msg.MarkConversationAsReadResp, err error) { +func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkConversationAsReadReq) (*msg.MarkConversationAsReadResp, error) { conversation, err := m.ConversationLocalCache.GetConversation(ctx, req.UserID, req.ConversationID) if err != nil { return nil, err @@ -154,15 +140,14 @@ func (m *msgServer) MarkConversationAsRead( } var seqs []int64 - log.ZDebug(ctx, "MarkConversationAsRead", "hasReadSeq", hasReadSeq, - "req.HasReadSeq", req.HasReadSeq) + log.ZDebug(ctx, "MarkConversationAsRead", "hasReadSeq", hasReadSeq, "req.HasReadSeq", req.HasReadSeq) if conversation.ConversationType == constant.SingleChatType { for i := hasReadSeq + 1; i <= req.HasReadSeq; i++ { seqs = append(seqs, i) } - //avoid client missed call MarkConversationMessageAsRead by order + // avoid client missed call MarkConversationMessageAsRead by order for _, val := range req.Seqs { - if !utils2.Contain(val, seqs...) { + if !datautil.Contain(val, seqs...) { seqs = append(seqs, val) } } @@ -179,12 +164,9 @@ func (m *msgServer) MarkConversationAsRead( } hasReadSeq = req.HasReadSeq } - if err = m.sendMarkAsReadNotification(ctx, req.ConversationID, conversation.ConversationType, req.UserID, - m.conversationAndGetRecvID(conversation, req.UserID), seqs, hasReadSeq); err != nil { - return nil, err - } - - } else if conversation.ConversationType == constant.SuperGroupChatType || + m.sendMarkAsReadNotification(ctx, req.ConversationID, conversation.ConversationType, req.UserID, + m.conversationAndGetRecvID(conversation, req.UserID), seqs, hasReadSeq) + } else if conversation.ConversationType == constant.ReadGroupChatType || conversation.ConversationType == constant.NotificationChatType { if req.HasReadSeq > hasReadSeq { err = m.MsgDatabase.SetHasReadSeq(ctx, req.UserID, req.ConversationID, req.HasReadSeq) @@ -193,11 +175,8 @@ func (m *msgServer) MarkConversationAsRead( } hasReadSeq = req.HasReadSeq } - if err = m.sendMarkAsReadNotification(ctx, req.ConversationID, constant.SingleChatType, req.UserID, - req.UserID, seqs, hasReadSeq); err != nil { - return nil, err - } - + m.sendMarkAsReadNotification(ctx, req.ConversationID, constant.SingleChatType, req.UserID, + req.UserID, seqs, hasReadSeq) } reqCall := &cbapi.CallbackGroupMsgReadReq{ @@ -206,30 +185,18 @@ func (m *msgServer) MarkConversationAsRead( UnreadMsgNum: req.HasReadSeq, ContentType: int64(conversation.ConversationType), } - if err := CallbackGroupMsgRead(ctx, m.config, reqCall); err != nil { - return nil, err - } + m.webhookAfterGroupMsgRead(ctx, &m.config.WebhooksConfig.AfterGroupMsgRead, reqCall) return &msg.MarkConversationAsReadResp{}, nil } -func (m *msgServer) sendMarkAsReadNotification( - ctx context.Context, - conversationID string, - sessionType int32, - sendID, recvID string, - seqs []int64, - hasReadSeq int64, -) error { +func (m *msgServer) sendMarkAsReadNotification(ctx context.Context, conversationID string, sessionType int32, sendID, recvID string, seqs []int64, hasReadSeq int64) { tips := &sdkws.MarkAsReadTips{ MarkAsReadUserID: sendID, ConversationID: conversationID, Seqs: seqs, HasReadSeq: hasReadSeq, } - err := m.notificationSender.NotificationWithSesstionType(ctx, sendID, recvID, constant.HasReadReceipt, sessionType, tips) - if err != nil { - log.ZWarn(ctx, "send has read Receipt err", err) - } - return nil + m.notificationSender.NotificationWithSessionType(ctx, sendID, recvID, constant.HasReadReceipt, sessionType, tips) + } diff --git a/internal/rpc/msg/callback.go b/internal/rpc/msg/callback.go index 927bbe0c2..10404675e 100644 --- a/internal/rpc/msg/callback.go +++ b/internal/rpc/msg/callback.go @@ -16,16 +16,15 @@ package msg import ( "context" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" - "github.com/OpenIMSDK/protocol/constant" - pbchat "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/http" + "github.com/openimsdk/protocol/constant" + pbchat "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/datautil" "google.golang.org/protobuf/proto" ) @@ -62,138 +61,118 @@ func GetContent(msg *sdkws.MsgData) string { } } -func callbackBeforeSendSingleMsg(ctx context.Context, globalConfig *config.GlobalConfig, msg *pbchat.SendMsgReq) error { - if !globalConfig.Callback.CallbackBeforeSendSingleMsg.Enable || msg.MsgData.ContentType == constant.Typing { +func (m *msgServer) webhookBeforeSendSingleMsg(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + if msg.MsgData.ContentType == constant.Typing { + return nil + } + cbReq := &cbapi.CallbackBeforeSendSingleMsgReq{ + CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeSendSingleMsgCommand), + RecvID: msg.MsgData.RecvID, + } + resp := &cbapi.CallbackBeforeSendSingleMsgResp{} + if err := m.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } + return nil - } - req := &cbapi.CallbackBeforeSendSingleMsgReq{ - CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeSendSingleMsgCommand), - RecvID: msg.MsgData.RecvID, - } - resp := &cbapi.CallbackBeforeSendSingleMsgResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackBeforeSendSingleMsg); err != nil { - return err - } - return nil + }) } -func callbackAfterSendSingleMsg(ctx context.Context, globalConfig *config.GlobalConfig, msg *pbchat.SendMsgReq) error { - if !globalConfig.Callback.CallbackAfterSendSingleMsg.Enable || msg.MsgData.ContentType == constant.Typing { - return nil +func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) { + if msg.MsgData.ContentType == constant.Typing { + return } - req := &cbapi.CallbackAfterSendSingleMsgReq{ + cbReq := &cbapi.CallbackAfterSendSingleMsgReq{ CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand), RecvID: msg.MsgData.RecvID, } - resp := &cbapi.CallbackAfterSendSingleMsgResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackAfterSendSingleMsg); err != nil { - return err - } - return nil + m.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendSingleMsgResp{}, after) } -func callbackBeforeSendGroupMsg(ctx context.Context, globalConfig *config.GlobalConfig, msg *pbchat.SendMsgReq) error { - if !globalConfig.Callback.CallbackBeforeSendGroupMsg.Enable || msg.MsgData.ContentType == constant.Typing { +func (m *msgServer) webhookBeforeSendGroupMsg(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + if msg.MsgData.ContentType == constant.Typing { + return nil + } + cbReq := &cbapi.CallbackBeforeSendGroupMsgReq{ + CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeSendGroupMsgCommand), + GroupID: msg.MsgData.GroupID, + } + resp := &cbapi.CallbackBeforeSendGroupMsgResp{} + if err := m.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } return nil - } - req := &cbapi.CallbackBeforeSendGroupMsgReq{ - CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeSendGroupMsgCommand), - GroupID: msg.MsgData.GroupID, - } - resp := &cbapi.CallbackBeforeSendGroupMsgResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackBeforeSendGroupMsg); err != nil { - return err - } - return nil + }) } -func callbackAfterSendGroupMsg(ctx context.Context, globalConfig *config.GlobalConfig, msg *pbchat.SendMsgReq) error { - if !globalConfig.Callback.CallbackAfterSendGroupMsg.Enable || msg.MsgData.ContentType == constant.Typing { - return nil +func (m *msgServer) webhookAfterSendGroupMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) { + if msg.MsgData.ContentType == constant.Typing { + return } - req := &cbapi.CallbackAfterSendGroupMsgReq{ + cbReq := &cbapi.CallbackAfterSendGroupMsgReq{ CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand), GroupID: msg.MsgData.GroupID, } - resp := &cbapi.CallbackAfterSendGroupMsgResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackAfterSendGroupMsg); err != nil { - return err - } - return nil + m.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendGroupMsgResp{}, after) } -func callbackMsgModify(ctx context.Context, globalConfig *config.GlobalConfig, msg *pbchat.SendMsgReq) error { - if !globalConfig.Callback.CallbackMsgModify.Enable || msg.MsgData.ContentType != constant.Text { +func (m *msgServer) webhookBeforeMsgModify(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + if msg.MsgData.ContentType != constant.Text { + return nil + } + cbReq := &cbapi.CallbackMsgModifyCommandReq{ + CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeMsgModifyCommand), + } + resp := &cbapi.CallbackMsgModifyCommandResp{} + if err := m.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } + + if resp.Content != nil { + msg.MsgData.Content = []byte(*resp.Content) + } + datautil.NotNilReplace(msg.MsgData.OfflinePushInfo, resp.OfflinePushInfo) + datautil.NotNilReplace(&msg.MsgData.RecvID, resp.RecvID) + datautil.NotNilReplace(&msg.MsgData.GroupID, resp.GroupID) + datautil.NotNilReplace(&msg.MsgData.ClientMsgID, resp.ClientMsgID) + datautil.NotNilReplace(&msg.MsgData.ServerMsgID, resp.ServerMsgID) + datautil.NotNilReplace(&msg.MsgData.SenderPlatformID, resp.SenderPlatformID) + datautil.NotNilReplace(&msg.MsgData.SenderNickname, resp.SenderNickname) + datautil.NotNilReplace(&msg.MsgData.SenderFaceURL, resp.SenderFaceURL) + datautil.NotNilReplace(&msg.MsgData.SessionType, resp.SessionType) + datautil.NotNilReplace(&msg.MsgData.MsgFrom, resp.MsgFrom) + datautil.NotNilReplace(&msg.MsgData.ContentType, resp.ContentType) + datautil.NotNilReplace(&msg.MsgData.Status, resp.Status) + datautil.NotNilReplace(&msg.MsgData.Options, resp.Options) + datautil.NotNilReplace(&msg.MsgData.AtUserIDList, resp.AtUserIDList) + datautil.NotNilReplace(&msg.MsgData.AttachedInfo, resp.AttachedInfo) + datautil.NotNilReplace(&msg.MsgData.Ex, resp.Ex) return nil - } - req := &cbapi.CallbackMsgModifyCommandReq{ - CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackMsgModifyCommand), - } - resp := &cbapi.CallbackMsgModifyCommandResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackMsgModify); err != nil { - return err - } - if resp.Content != nil { - msg.MsgData.Content = []byte(*resp.Content) - } - utils.NotNilReplace(msg.MsgData.OfflinePushInfo, resp.OfflinePushInfo) - utils.NotNilReplace(&msg.MsgData.RecvID, resp.RecvID) - utils.NotNilReplace(&msg.MsgData.GroupID, resp.GroupID) - utils.NotNilReplace(&msg.MsgData.ClientMsgID, resp.ClientMsgID) - utils.NotNilReplace(&msg.MsgData.ServerMsgID, resp.ServerMsgID) - utils.NotNilReplace(&msg.MsgData.SenderPlatformID, resp.SenderPlatformID) - utils.NotNilReplace(&msg.MsgData.SenderNickname, resp.SenderNickname) - utils.NotNilReplace(&msg.MsgData.SenderFaceURL, resp.SenderFaceURL) - utils.NotNilReplace(&msg.MsgData.SessionType, resp.SessionType) - utils.NotNilReplace(&msg.MsgData.MsgFrom, resp.MsgFrom) - utils.NotNilReplace(&msg.MsgData.ContentType, resp.ContentType) - utils.NotNilReplace(&msg.MsgData.Status, resp.Status) - utils.NotNilReplace(&msg.MsgData.Options, resp.Options) - utils.NotNilReplace(&msg.MsgData.AtUserIDList, resp.AtUserIDList) - utils.NotNilReplace(&msg.MsgData.AttachedInfo, resp.AttachedInfo) - utils.NotNilReplace(&msg.MsgData.Ex, resp.Ex) - log.ZDebug(ctx, "callbackMsgModify", "msg", msg.MsgData) - return nil + }) } -func CallbackGroupMsgRead(ctx context.Context, globalConfig *config.GlobalConfig, req *cbapi.CallbackGroupMsgReadReq) error { - if !globalConfig.Callback.CallbackGroupMsgRead.Enable { - return nil - } - req.CallbackCommand = cbapi.CallbackGroupMsgReadCommand - resp := &cbapi.CallbackGroupMsgReadResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackGroupMsgRead); err != nil { - return err - } - return nil +func (m *msgServer) webhookAfterGroupMsgRead(ctx context.Context, after *config.AfterConfig, req *cbapi.CallbackGroupMsgReadReq) { + req.CallbackCommand = cbapi.CallbackAfterGroupMsgReadCommand + m.webhookClient.AsyncPost(ctx, req.GetCallbackCommand(), req, &cbapi.CallbackGroupMsgReadResp{}, after) } -func CallbackSingleMsgRead(ctx context.Context, globalConfig *config.GlobalConfig, req *cbapi.CallbackSingleMsgReadReq) error { - if !globalConfig.Callback.CallbackSingleMsgRead.Enable { - return nil - } - req.CallbackCommand = cbapi.CallbackSingleMsgRead +func (m *msgServer) webhookAfterSingleMsgRead(ctx context.Context, after *config.AfterConfig, req *cbapi.CallbackSingleMsgReadReq) { - resp := &cbapi.CallbackSingleMsgReadResp{} + req.CallbackCommand = cbapi.CallbackAfterSingleMsgReadCommand + + m.webhookClient.AsyncPost(ctx, req.GetCallbackCommand(), req, &cbapi.CallbackSingleMsgReadResp{}, after) - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, req, resp, globalConfig.Callback.CallbackSingleMsgRead); err != nil { - return err - } - return nil } -func CallbackAfterRevokeMsg(ctx context.Context, globalConfig *config.GlobalConfig, req *pbchat.RevokeMsgReq) error { - if !globalConfig.Callback.CallbackAfterRevokeMsg.Enable { - return nil - } + +func (m *msgServer) webhookAfterRevokeMsg(ctx context.Context, after *config.AfterConfig, req *pbchat.RevokeMsgReq) { callbackReq := &cbapi.CallbackAfterRevokeMsgReq{ CallbackCommand: cbapi.CallbackAfterRevokeMsgCommand, ConversationID: req.ConversationID, Seq: req.Seq, UserID: req.UserID, } - resp := &cbapi.CallbackAfterRevokeMsgResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, callbackReq, resp, globalConfig.Callback.CallbackAfterRevokeMsg); err != nil { - return err - } - return nil + m.webhookClient.AsyncPost(ctx, callbackReq.GetCallbackCommand(), callbackReq, &cbapi.CallbackAfterRevokeMsgResp{}, after) } diff --git a/internal/rpc/msg/delete.go b/internal/rpc/msg/delete.go index 14e24d23e..e19bba867 100644 --- a/internal/rpc/msg/delete.go +++ b/internal/rpc/msg/delete.go @@ -17,13 +17,13 @@ package msg import ( "context" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/conversation" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/authverify" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/conversation" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/timeutil" ) func (m *msgServer) getMinSeqs(maxSeqs map[string]int64) map[string]int64 { @@ -41,11 +41,8 @@ func (m *msgServer) validateDeleteSyncOpt(opt *msg.DeleteSyncOpt) (isSyncSelf, i return opt.IsSyncSelf, opt.IsSyncOther } -func (m *msgServer) ClearConversationsMsg( - ctx context.Context, - req *msg.ClearConversationsMsgReq, -) (*msg.ClearConversationsMsgResp, error) { - if err := authverify.CheckAccessV3(ctx, req.UserID, m.config); err != nil { +func (m *msgServer) ClearConversationsMsg(ctx context.Context, req *msg.ClearConversationsMsgReq) (*msg.ClearConversationsMsgResp, error) { + if err := authverify.CheckAccessV3(ctx, req.UserID, m.config.Share.IMAdminUserID); err != nil { return nil, err } if err := m.clearConversation(ctx, req.ConversationIDs, req.UserID, req.DeleteSyncOpt); err != nil { @@ -54,18 +51,14 @@ func (m *msgServer) ClearConversationsMsg( return &msg.ClearConversationsMsgResp{}, nil } -func (m *msgServer) UserClearAllMsg( - ctx context.Context, - req *msg.UserClearAllMsgReq, -) (*msg.UserClearAllMsgResp, error) { - if err := authverify.CheckAccessV3(ctx, req.UserID, m.config); err != nil { +func (m *msgServer) UserClearAllMsg(ctx context.Context, req *msg.UserClearAllMsgReq) (*msg.UserClearAllMsgResp, error) { + if err := authverify.CheckAccessV3(ctx, req.UserID, m.config.Share.IMAdminUserID); err != nil { return nil, err } conversationIDs, err := m.ConversationLocalCache.GetConversationIDs(ctx, req.UserID) if err != nil { return nil, err } - log.ZDebug(ctx, "GetMaxSeq", "conversationIDs", conversationIDs) if err := m.clearConversation(ctx, conversationIDs, req.UserID, req.DeleteSyncOpt); err != nil { return nil, err } @@ -73,7 +66,7 @@ func (m *msgServer) UserClearAllMsg( } func (m *msgServer) DeleteMsgs(ctx context.Context, req *msg.DeleteMsgsReq) (*msg.DeleteMsgsResp, error) { - if err := authverify.CheckAccessV3(ctx, req.UserID, m.config); err != nil { + if err := authverify.CheckAccessV3(ctx, req.UserID, m.config.Share.IMAdminUserID); err != nil { return nil, err } isSyncSelf, isSyncOther := m.validateDeleteSyncOpt(req.DeleteSyncOpt) @@ -86,7 +79,7 @@ func (m *msgServer) DeleteMsgs(ctx context.Context, req *msg.DeleteMsgsReq) (*ms return nil, err } tips := &sdkws.DeleteMsgsTips{UserID: req.UserID, ConversationID: req.ConversationID, Seqs: req.Seqs} - m.notificationSender.NotificationWithSesstionType( + m.notificationSender.NotificationWithSessionType( ctx, req.UserID, m.conversationAndGetRecvID(conversations[0], req.UserID), @@ -100,16 +93,13 @@ func (m *msgServer) DeleteMsgs(ctx context.Context, req *msg.DeleteMsgsReq) (*ms } if isSyncSelf { tips := &sdkws.DeleteMsgsTips{UserID: req.UserID, ConversationID: req.ConversationID, Seqs: req.Seqs} - m.notificationSender.NotificationWithSesstionType(ctx, req.UserID, req.UserID, constant.DeleteMsgsNotification, constant.SingleChatType, tips) + m.notificationSender.NotificationWithSessionType(ctx, req.UserID, req.UserID, constant.DeleteMsgsNotification, constant.SingleChatType, tips) } } return &msg.DeleteMsgsResp{}, nil } -func (m *msgServer) DeleteMsgPhysicalBySeq( - ctx context.Context, - req *msg.DeleteMsgPhysicalBySeqReq, -) (*msg.DeleteMsgPhysicalBySeqResp, error) { +func (m *msgServer) DeleteMsgPhysicalBySeq(ctx context.Context, req *msg.DeleteMsgPhysicalBySeqReq) (*msg.DeleteMsgPhysicalBySeqResp, error) { err := m.MsgDatabase.DeleteMsgsPhysicalBySeqs(ctx, req.ConversationID, req.Seqs) if err != nil { return nil, err @@ -117,37 +107,20 @@ func (m *msgServer) DeleteMsgPhysicalBySeq( return &msg.DeleteMsgPhysicalBySeqResp{}, nil } -func (m *msgServer) DeleteMsgPhysical( - ctx context.Context, - req *msg.DeleteMsgPhysicalReq, -) (*msg.DeleteMsgPhysicalResp, error) { - if err := authverify.CheckAdmin(ctx, m.config); err != nil { +func (m *msgServer) DeleteMsgPhysical(ctx context.Context, req *msg.DeleteMsgPhysicalReq) (*msg.DeleteMsgPhysicalResp, error) { + if err := authverify.CheckAdmin(ctx, m.config.Share.IMAdminUserID); err != nil { return nil, err } - remainTime := utils.GetCurrentTimestampBySecond() - req.Timestamp + remainTime := timeutil.GetCurrentTimestampBySecond() - req.Timestamp for _, conversationID := range req.ConversationIDs { if err := m.MsgDatabase.DeleteConversationMsgsAndSetMinSeq(ctx, conversationID, remainTime); err != nil { - log.ZWarn( - ctx, - "DeleteConversationMsgsAndSetMinSeq error", - err, - "conversationID", - conversationID, - "err", - err, - ) + log.ZWarn(ctx, "DeleteConversationMsgsAndSetMinSeq error", err, "conversationID", conversationID, "err", err) } } return &msg.DeleteMsgPhysicalResp{}, nil } -func (m *msgServer) clearConversation( - ctx context.Context, - conversationIDs []string, - userID string, - deleteSyncOpt *msg.DeleteSyncOpt, -) error { - defer log.ZDebug(ctx, "clearConversation return line") +func (m *msgServer) clearConversation(ctx context.Context, conversationIDs []string, userID string, deleteSyncOpt *msg.DeleteSyncOpt) error { conversations, err := m.Conversation.GetConversationsByConversationID(ctx, conversationIDs) if err != nil { return err @@ -171,14 +144,7 @@ func (m *msgServer) clearConversation( // notification 2 self if isSyncSelf { tips := &sdkws.ClearConversationTips{UserID: userID, ConversationIDs: existConversationIDs} - m.notificationSender.NotificationWithSesstionType( - ctx, - userID, - userID, - constant.ClearConversationNotification, - constant.SingleChatType, - tips, - ) + m.notificationSender.NotificationWithSessionType(ctx, userID, userID, constant.ClearConversationNotification, constant.SingleChatType, tips) } } else { if err := m.MsgDatabase.SetMinSeqs(ctx, m.getMinSeqs(maxSeqs)); err != nil { @@ -186,7 +152,7 @@ func (m *msgServer) clearConversation( } for _, conversation := range existConversations { tips := &sdkws.ClearConversationTips{UserID: userID, ConversationIDs: []string{conversation.ConversationID}} - m.notificationSender.NotificationWithSesstionType(ctx, userID, m.conversationAndGetRecvID(conversation, userID), constant.ClearConversationNotification, conversation.ConversationType, tips) + m.notificationSender.NotificationWithSessionType(ctx, userID, m.conversationAndGetRecvID(conversation, userID), constant.ClearConversationNotification, conversation.ConversationType, tips) } } if err := m.MsgDatabase.UserSetHasReadSeqs(ctx, userID, maxSeqs); err != nil { diff --git a/internal/rpc/msg/message_interceptor.go b/internal/rpc/msg/message_interceptor.go deleted file mode 100644 index 97eac613d..000000000 --- a/internal/rpc/msg/message_interceptor.go +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package msg - -import ( - "context" - - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" -) - -type MessageInterceptorFunc func(ctx context.Context, globalConfig *config.GlobalConfig, req *msg.SendMsgReq) (*sdkws.MsgData, error) - -func MessageHasReadEnabled(_ context.Context, globalConfig *config.GlobalConfig, req *msg.SendMsgReq) (*sdkws.MsgData, error) { - switch { - case req.MsgData.ContentType == constant.HasReadReceipt && req.MsgData.SessionType == constant.SingleChatType: - if !globalConfig.SingleMessageHasReadReceiptEnable { - return nil, errs.ErrMessageHasReadDisable.Wrap() - } - return req.MsgData, nil - case req.MsgData.ContentType == constant.HasReadReceipt && req.MsgData.SessionType == constant.SuperGroupChatType: - if !globalConfig.GroupMessageHasReadReceiptEnable { - return nil, errs.ErrMessageHasReadDisable.Wrap() - } - return req.MsgData, nil - } - return req.MsgData, nil -} diff --git a/internal/rpc/msg/msg_status.go b/internal/rpc/msg/msg_status.go index 80c5833a5..b524d6236 100644 --- a/internal/rpc/msg/msg_status.go +++ b/internal/rpc/msg/msg_status.go @@ -17,15 +17,12 @@ package msg import ( "context" - "github.com/OpenIMSDK/protocol/constant" - pbmsg "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/tools/mcontext" + "github.com/openimsdk/protocol/constant" + pbmsg "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/tools/mcontext" ) -func (m *msgServer) SetSendMsgStatus( - ctx context.Context, - req *pbmsg.SetSendMsgStatusReq, -) (*pbmsg.SetSendMsgStatusResp, error) { +func (m *msgServer) SetSendMsgStatus(ctx context.Context, req *pbmsg.SetSendMsgStatusReq) (*pbmsg.SetSendMsgStatusResp, error) { resp := &pbmsg.SetSendMsgStatusResp{} if err := m.MsgDatabase.SetSendMsgStatus(ctx, mcontext.GetOperationID(ctx), req.Status); err != nil { return nil, err @@ -33,10 +30,7 @@ func (m *msgServer) SetSendMsgStatus( return resp, nil } -func (m *msgServer) GetSendMsgStatus( - ctx context.Context, - req *pbmsg.GetSendMsgStatusReq, -) (*pbmsg.GetSendMsgStatusResp, error) { +func (m *msgServer) GetSendMsgStatus(ctx context.Context, req *pbmsg.GetSendMsgStatusReq) (*pbmsg.GetSendMsgStatusResp, error) { resp := &pbmsg.GetSendMsgStatusResp{} status, err := m.MsgDatabase.GetSendMsgStatus(ctx, mcontext.GetOperationID(ctx)) if IsNotFound(err) { diff --git a/pkg/rpcclient/notification/msg.go b/internal/rpc/msg/notification.go similarity index 61% rename from pkg/rpcclient/notification/msg.go rename to internal/rpc/msg/notification.go index beaef1d96..3b13676bf 100644 --- a/pkg/rpcclient/notification/msg.go +++ b/internal/rpc/msg/notification.go @@ -12,40 +12,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -package notification +package msg import ( "context" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" ) type MsgNotificationSender struct { *rpcclient.NotificationSender } -func NewMsgNotificationSender(config *config.GlobalConfig, opts ...rpcclient.NotificationSenderOptions) *MsgNotificationSender { - return &MsgNotificationSender{rpcclient.NewNotificationSender(config, opts...)} +func NewMsgNotificationSender(config *Config, opts ...rpcclient.NotificationSenderOptions) *MsgNotificationSender { + return &MsgNotificationSender{rpcclient.NewNotificationSender(&config.NotificationConfig, opts...)} } -func (m *MsgNotificationSender) UserDeleteMsgsNotification(ctx context.Context, userID, conversationID string, seqs []int64) error { +func (m *MsgNotificationSender) UserDeleteMsgsNotification(ctx context.Context, userID, conversationID string, seqs []int64) { tips := sdkws.DeleteMsgsTips{ UserID: userID, ConversationID: conversationID, Seqs: seqs, } - return m.Notification(ctx, userID, userID, constant.DeleteMsgsNotification, &tips) + m.Notification(ctx, userID, userID, constant.DeleteMsgsNotification, &tips) } -func (m *MsgNotificationSender) MarkAsReadNotification(ctx context.Context, conversationID string, sesstionType int32, sendID, recvID string, seqs []int64, hasReadSeq int64) error { +func (m *MsgNotificationSender) MarkAsReadNotification(ctx context.Context, conversationID string, sessionType int32, sendID, recvID string, seqs []int64, hasReadSeq int64) { tips := &sdkws.MarkAsReadTips{ MarkAsReadUserID: sendID, ConversationID: conversationID, Seqs: seqs, HasReadSeq: hasReadSeq, } - return m.NotificationWithSesstionType(ctx, sendID, recvID, constant.HasReadReceipt, sesstionType, tips) + m.NotificationWithSessionType(ctx, sendID, recvID, constant.HasReadReceipt, sessionType, tips) } diff --git a/internal/rpc/msg/revoke.go b/internal/rpc/msg/revoke.go index 99690b0cc..7dbc307a1 100644 --- a/internal/rpc/msg/revoke.go +++ b/internal/rpc/msg/revoke.go @@ -19,29 +19,29 @@ import ( "encoding/json" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/authverify" - unrelationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/datautil" ) func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg.RevokeMsgResp, error) { - defer log.ZDebug(ctx, "RevokeMsg return line") if req.UserID == "" { - return nil, errs.ErrArgs.Wrap("user_id is empty") + return nil, errs.ErrArgs.WrapMsg("user_id is empty") } if req.ConversationID == "" { - return nil, errs.ErrArgs.Wrap("conversation_id is empty") + return nil, errs.ErrArgs.WrapMsg("conversation_id is empty") } if req.Seq < 0 { - return nil, errs.ErrArgs.Wrap("seq is invalid") + return nil, errs.ErrArgs.WrapMsg("seq is invalid") } - if err := authverify.CheckAccessV3(ctx, req.UserID, m.config); err != nil { + if err := authverify.CheckAccessV3(ctx, req.UserID, m.config.Share.IMAdminUserID); err != nil { return nil, err } user, err := m.UserLocalCache.GetUserInfo(ctx, req.UserID) @@ -53,24 +53,24 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg. return nil, err } if len(msgs) == 0 || msgs[0] == nil { - return nil, errs.ErrRecordNotFound.Wrap("msg not found") + return nil, errs.ErrRecordNotFound.WrapMsg("msg not found") } if msgs[0].ContentType == constant.MsgRevokeNotification { - return nil, errs.ErrMsgAlreadyRevoke.Wrap("msg already revoke") + return nil, servererrs.ErrMsgAlreadyRevoke.WrapMsg("msg already revoke") } data, _ := json.Marshal(msgs[0]) - log.ZInfo(ctx, "GetMsgBySeqs", "conversationID", req.ConversationID, "seq", req.Seq, "msg", string(data)) + log.ZDebug(ctx, "GetMsgBySeqs", "conversationID", req.ConversationID, "seq", req.Seq, "msg", string(data)) var role int32 - if !authverify.IsAppManagerUid(ctx, m.config) { + if !authverify.IsAppManagerUid(ctx, m.config.Share.IMAdminUserID) { switch msgs[0].SessionType { case constant.SingleChatType: - if err := authverify.CheckAccessV3(ctx, msgs[0].SendID, m.config); err != nil { + if err := authverify.CheckAccessV3(ctx, msgs[0].SendID, m.config.Share.IMAdminUserID); err != nil { return nil, err } role = user.AppMangerLevel - case constant.SuperGroupChatType: - members, err := m.GroupLocalCache.GetGroupMemberInfoMap(ctx, msgs[0].GroupID, utils.Distinct([]string{req.UserID, msgs[0].SendID})) + case constant.ReadGroupChatType: + members, err := m.GroupLocalCache.GetGroupMemberInfoMap(ctx, msgs[0].GroupID, datautil.Distinct([]string{req.UserID, msgs[0].SendID})) if err != nil { return nil, err } @@ -79,21 +79,21 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg. case constant.GroupOwner: case constant.GroupAdmin: if members[msgs[0].SendID].RoleLevel != constant.GroupOrdinaryUsers { - return nil, errs.ErrNoPermission.Wrap("no permission") + return nil, errs.ErrNoPermission.WrapMsg("no permission") } default: - return nil, errs.ErrNoPermission.Wrap("no permission") + return nil, errs.ErrNoPermission.WrapMsg("no permission") } } if member := members[req.UserID]; member != nil { role = member.RoleLevel } default: - return nil, errs.ErrInternalServer.Wrap("msg sessionType not supported") + return nil, errs.ErrInternalServer.WrapMsg("msg sessionType not supported") } } now := time.Now().UnixMilli() - err = m.MsgDatabase.RevokeMsg(ctx, req.ConversationID, req.Seq, &unrelationtb.RevokeModel{ + err = m.MsgDatabase.RevokeMsg(ctx, req.ConversationID, req.Seq, &relation.RevokeModel{ Role: role, UserID: req.UserID, Nickname: user.Nickname, @@ -104,11 +104,9 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg. } revokerUserID := mcontext.GetOpUserID(ctx) var flag bool - if len(m.config.Manager.UserID) > 0 { - flag = utils.Contain(revokerUserID, m.config.Manager.UserID...) - } - if len(m.config.Manager.UserID) == 0 && len(m.config.IMAdmin.UserID) > 0 { - flag = utils.Contain(revokerUserID, m.config.IMAdmin.UserID...) + + if len(m.config.Share.IMAdminUserID) > 0 { + flag = datautil.Contain(revokerUserID, m.config.Share.IMAdminUserID...) } tips := sdkws.RevokeMsgTips{ RevokerUserID: revokerUserID, @@ -120,16 +118,12 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg. IsAdminRevoke: flag, } var recvID string - if msgs[0].SessionType == constant.SuperGroupChatType { + if msgs[0].SessionType == constant.ReadGroupChatType { recvID = msgs[0].GroupID } else { recvID = msgs[0].RecvID } - if err := m.notificationSender.NotificationWithSesstionType(ctx, req.UserID, recvID, constant.MsgRevokeNotification, msgs[0].SessionType, &tips); err != nil { - return nil, err - } - if err = CallbackAfterRevokeMsg(ctx, m.config, req); err != nil { - return nil, err - } + m.notificationSender.NotificationWithSessionType(ctx, req.UserID, recvID, constant.MsgRevokeNotification, msgs[0].SessionType, &tips) + m.webhookAfterRevokeMsg(ctx, &m.config.WebhooksConfig.AfterRevokeMsg, req) return &msg.RevokeMsgResp{}, nil } diff --git a/internal/rpc/msg/send.go b/internal/rpc/msg/send.go index ea59c40cf..4a2d21019 100644 --- a/internal/rpc/msg/send.go +++ b/internal/rpc/msg/send.go @@ -17,67 +17,59 @@ package msg import ( "context" - "github.com/OpenIMSDK/protocol/constant" - pbconversation "github.com/OpenIMSDK/protocol/conversation" - pbmsg "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/protocol/wrapperspb" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" + "github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil" + "github.com/openimsdk/protocol/constant" + pbconversation "github.com/openimsdk/protocol/conversation" + pbmsg "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/protocol/wrapperspb" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/stringutil" ) -func (m *msgServer) SendMsg(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, error error) { - resp = &pbmsg.SendMsgResp{} +func (m *msgServer) SendMsg(ctx context.Context, req *pbmsg.SendMsgReq) (*pbmsg.SendMsgResp, error) { if req.MsgData != nil { - flag := isMessageHasReadEnabled(req.MsgData, m.config) - if !flag { - return nil, errs.ErrMessageHasReadDisable.Wrap() - } m.encapsulateMsgData(req.MsgData) switch req.MsgData.SessionType { case constant.SingleChatType: return m.sendMsgSingleChat(ctx, req) case constant.NotificationChatType: return m.sendMsgNotification(ctx, req) - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: return m.sendMsgSuperGroupChat(ctx, req) default: - return nil, errs.ErrArgs.Wrap("unknown sessionType") + return nil, errs.ErrArgs.WrapMsg("unknown sessionType") } - } else { - return nil, errs.ErrArgs.Wrap("msgData is nil") } + return nil, errs.ErrArgs.WrapMsg("msgData is nil") } -func (m *msgServer) sendMsgSuperGroupChat( - ctx context.Context, - req *pbmsg.SendMsgReq, -) (resp *pbmsg.SendMsgResp, err error) { +func (m *msgServer) sendMsgSuperGroupChat(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, err error) { if err = m.messageVerification(ctx, req); err != nil { prommetrics.GroupChatMsgProcessFailedCounter.Inc() return nil, err } - if err = callbackBeforeSendGroupMsg(ctx, m.config, req); err != nil { + + if err = m.webhookBeforeSendGroupMsg(ctx, &m.config.WebhooksConfig.BeforeSendGroupMsg, req); err != nil { return nil, err } - - if err := callbackMsgModify(ctx, m.config, req); err != nil { + if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil { return nil, err } - err = m.MsgDatabase.MsgToMQ(ctx, utils.GenConversationUniqueKeyForGroup(req.MsgData.GroupID), req.MsgData) + err = m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForGroup(req.MsgData.GroupID), req.MsgData) if err != nil { return nil, err } if req.MsgData.ContentType == constant.AtText { go m.setConversationAtInfo(ctx, req.MsgData) } - if err = callbackAfterSendGroupMsg(ctx, m.config, req); err != nil { - log.ZWarn(ctx, "CallbackAfterSendGroupMsg", err) - } + + m.webhookAfterSendGroupMsg(ctx, &m.config.WebhooksConfig.AfterSendGroupMsg, req) prommetrics.GroupChatMsgProcessSuccessCounter.Inc() resp = &pbmsg.SendMsgResp{} resp.SendTime = req.MsgData.SendTime @@ -95,43 +87,39 @@ func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgDa ConversationType: msg.SessionType, GroupID: msg.GroupID, } - tagAll := utils.IsContain(constant.AtAllString, msg.AtUserIDList) + tagAll := datautil.Contain(constant.AtAllString, msg.AtUserIDList...) if tagAll { memberUserIDList, err := m.GroupLocalCache.GetGroupMemberIDs(ctx, msg.GroupID) if err != nil { log.ZWarn(ctx, "GetGroupMemberIDs", err) return } - atUserID = utils.DifferenceString([]string{constant.AtAllString}, msg.AtUserIDList) + atUserID = stringutil.DifferenceString([]string{constant.AtAllString}, msg.AtUserIDList) if len(atUserID) == 0 { // just @everyone conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll} - } else { //@Everyone and @other people + } else { // @Everyone and @other people conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAllAtMe} err = m.Conversation.SetConversations(ctx, atUserID, conversation) if err != nil { log.ZWarn(ctx, "SetConversations", err, "userID", atUserID, "conversation", conversation) } - memberUserIDList = utils.DifferenceString(atUserID, memberUserIDList) + memberUserIDList = stringutil.DifferenceString(atUserID, memberUserIDList) } conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll} err = m.Conversation.SetConversations(ctx, memberUserIDList, conversation) if err != nil { log.ZWarn(ctx, "SetConversations", err, "userID", memberUserIDList, "conversation", conversation) } - } else { - conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtMe} - err := m.Conversation.SetConversations(ctx, msg.AtUserIDList, conversation) - if err != nil { - log.ZWarn(ctx, "SetConversations", err, msg.AtUserIDList, conversation) - } + } + conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtMe} + err := m.Conversation.SetConversations(ctx, msg.AtUserIDList, conversation) + if err != nil { + log.ZWarn(ctx, "SetConversations", err, msg.AtUserIDList, conversation) } } -func (m *msgServer) sendMsgNotification( - ctx context.Context, - req *pbmsg.SendMsgReq, -) (resp *pbmsg.SendMsgResp, err error) { - if err := m.MsgDatabase.MsgToMQ(ctx, utils.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil { +func (m *msgServer) sendMsgNotification(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, err error) { + if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil { return nil, err } resp = &pbmsg.SendMsgResp{ @@ -143,7 +131,6 @@ func (m *msgServer) sendMsgNotification( } func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, err error) { - log.ZDebug(ctx, "sendMsgSingleChat return") if err := m.messageVerification(ctx, req); err != nil { return nil, err } @@ -153,7 +140,7 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq isSend, err = m.modifyMessageByUserMessageReceiveOpt( ctx, req.MsgData.RecvID, - utils.GenConversationIDForSingle(req.MsgData.SendID, req.MsgData.RecvID), + conversationutil.GenConversationIDForSingle(req.MsgData.SendID, req.MsgData.RecvID), constant.SingleChatType, req, ) @@ -165,31 +152,23 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq prommetrics.SingleChatMsgProcessFailedCounter.Inc() return nil, nil } else { - if err = callbackBeforeSendSingleMsg(ctx, m.config, req); err != nil { + if err = m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, req); err != nil { return nil, err } - - if err := callbackMsgModify(ctx, m.config, req); err != nil { + if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil { return nil, err } - if err := m.MsgDatabase.MsgToMQ(ctx, utils.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil { + + if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil { prommetrics.SingleChatMsgProcessFailedCounter.Inc() return nil, err } - err = callbackAfterSendSingleMsg(ctx, m.config, req) - if err != nil { - log.ZWarn(ctx, "CallbackAfterSendSingleMsg", err, "req", req) - } - resp = &pbmsg.SendMsgResp{ + m.webhookAfterSendSingleMsg(ctx, &m.config.WebhooksConfig.AfterSendSingleMsg, req) + prommetrics.SingleChatMsgProcessSuccessCounter.Inc() + return &pbmsg.SendMsgResp{ ServerMsgID: req.MsgData.ServerMsgID, ClientMsgID: req.MsgData.ClientMsgID, SendTime: req.MsgData.SendTime, - } - prommetrics.SingleChatMsgProcessSuccessCounter.Inc() - return resp, nil + }, nil } } - -func (m *msgServer) BatchSendMsg(ctx context.Context, in *pbmsg.BatchSendMessageReq) (*pbmsg.BatchSendMessageResp, error) { - return nil, nil -} diff --git a/internal/rpc/msg/seq.go b/internal/rpc/msg/seq.go index dfc2ad0b1..27465c210 100644 --- a/internal/rpc/msg/seq.go +++ b/internal/rpc/msg/seq.go @@ -17,13 +17,10 @@ package msg import ( "context" - pbmsg "github.com/OpenIMSDK/protocol/msg" + pbmsg "github.com/openimsdk/protocol/msg" ) -func (m *msgServer) GetConversationMaxSeq( - ctx context.Context, - req *pbmsg.GetConversationMaxSeqReq, -) (resp *pbmsg.GetConversationMaxSeqResp, err error) { +func (m *msgServer) GetConversationMaxSeq(ctx context.Context, req *pbmsg.GetConversationMaxSeqReq) (*pbmsg.GetConversationMaxSeqResp, error) { maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID) if err != nil { return nil, err diff --git a/internal/rpc/msg/server.go b/internal/rpc/msg/server.go index f2d735a70..3f4df8d4b 100644 --- a/internal/rpc/msg/server.go +++ b/internal/rpc/msg/server.go @@ -15,69 +15,84 @@ package msg import ( - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/conversation" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/tools/discoveryregistry" + "context" "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/redisutil" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/mgo" "github.com/openimsdk/open-im-server/v3/pkg/rpccache" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/conversation" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/tools/discovery" "google.golang.org/grpc" ) +type MessageInterceptorFunc func(ctx context.Context, globalConfig *Config, req *msg.SendMsgReq) (*sdkws.MsgData, error) type ( + // MessageInterceptorChain defines a chain of message interceptor functions. MessageInterceptorChain []MessageInterceptorFunc - msgServer struct { - RegisterCenter discoveryregistry.SvcDiscoveryRegistry - MsgDatabase controller.CommonMsgDatabase - Conversation *rpcclient.ConversationRpcClient - UserLocalCache *rpccache.UserLocalCache - FriendLocalCache *rpccache.FriendLocalCache - GroupLocalCache *rpccache.GroupLocalCache - ConversationLocalCache *rpccache.ConversationLocalCache - Handlers MessageInterceptorChain - notificationSender *rpcclient.NotificationSender - config *config.GlobalConfig + + // MsgServer encapsulates dependencies required for message handling. + msgServer struct { + RegisterCenter discovery.SvcDiscoveryRegistry // Service discovery registry for service registration. + MsgDatabase controller.CommonMsgDatabase // Interface for message database operations. + Conversation *rpcclient.ConversationRpcClient // RPC client for conversation service. + UserLocalCache *rpccache.UserLocalCache // Local cache for user data. + FriendLocalCache *rpccache.FriendLocalCache // Local cache for friend data. + GroupLocalCache *rpccache.GroupLocalCache // Local cache for group data. + ConversationLocalCache *rpccache.ConversationLocalCache // Local cache for conversation data. + Handlers MessageInterceptorChain // Chain of handlers for processing messages. + notificationSender *rpcclient.NotificationSender // RPC client for sending notifications. + config *Config // Global configuration settings. + webhookClient *webhook.Client + } + + Config struct { + RpcConfig config.Msg + RedisConfig config.Redis + MongodbConfig config.Mongo + KafkaConfig config.Kafka + ZookeeperConfig config.ZooKeeper + NotificationConfig config.Notification + Share config.Share + WebhooksConfig config.Webhooks + LocalCacheConfig config.LocalCache } ) func (m *msgServer) addInterceptorHandler(interceptorFunc ...MessageInterceptorFunc) { m.Handlers = append(m.Handlers, interceptorFunc...) -} -//func (m *msgServer) execInterceptorHandler(ctx context.Context, config *config.GlobalConfig, req *msg.SendMsgReq) error { -// for _, handler := range m.Handlers { -// msgData, err := handler(ctx, config, req) -// if err != nil { -// return err -// } -// req.MsgData = msgData -// } -// return nil -//} +} -func Start(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error { - rdb, err := cache.NewRedis(config) +func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { + mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) if err != nil { return err } - mongo, err := unrelation.NewMongo(config) + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) if err != nil { return err } - if err := mongo.CreateMsgIndex(); err != nil { + msgDocModel, err := mgo.NewMsgMongo(mgocli.GetDB()) + if err != nil { return err } - cacheModel := cache.NewMsgCacheModel(rdb, config) - msgDocModel := unrelation.NewMsgMongoDriver(mongo.GetDatabase(config.Mongo.Database)) - conversationClient := rpcclient.NewConversationRpcClient(client, config) - userRpcClient := rpcclient.NewUserRpcClient(client, config) - groupRpcClient := rpcclient.NewGroupRpcClient(client, config) - friendRpcClient := rpcclient.NewFriendRpcClient(client, config) - msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, cacheModel, config) + //todo MsgCacheTimeout + msgModel := cache.NewMsgCache(rdb, config.RedisConfig.EnablePipeline) + seqModel := cache.NewSeqCache(rdb) + conversationClient := rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation) + userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID) + groupRpcClient := rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group) + friendRpcClient := rpcclient.NewFriendRpcClient(client, config.Share.RpcRegisterName.Friend) + msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqModel, &config.KafkaConfig) if err != nil { return err } @@ -85,28 +100,29 @@ func Start(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryReg Conversation: &conversationClient, MsgDatabase: msgDatabase, RegisterCenter: client, - UserLocalCache: rpccache.NewUserLocalCache(userRpcClient, rdb), - GroupLocalCache: rpccache.NewGroupLocalCache(groupRpcClient, rdb), - ConversationLocalCache: rpccache.NewConversationLocalCache(conversationClient, rdb), - FriendLocalCache: rpccache.NewFriendLocalCache(friendRpcClient, rdb), + UserLocalCache: rpccache.NewUserLocalCache(userRpcClient, &config.LocalCacheConfig, rdb), + GroupLocalCache: rpccache.NewGroupLocalCache(groupRpcClient, &config.LocalCacheConfig, rdb), + ConversationLocalCache: rpccache.NewConversationLocalCache(conversationClient, &config.LocalCacheConfig, rdb), + FriendLocalCache: rpccache.NewFriendLocalCache(friendRpcClient, &config.LocalCacheConfig, rdb), config: config, + webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL), } - s.notificationSender = rpcclient.NewNotificationSender(config, rpcclient.WithLocalSendMsg(s.SendMsg)) - s.addInterceptorHandler(MessageHasReadEnabled) + + s.notificationSender = rpcclient.NewNotificationSender(&config.NotificationConfig, rpcclient.WithLocalSendMsg(s.SendMsg)) msg.RegisterMsgServer(server, s) return nil } -func (m *msgServer) conversationAndGetRecvID(conversation *conversation.Conversation, userID string) (recvID string) { +func (m *msgServer) conversationAndGetRecvID(conversation *conversation.Conversation, userID string) string { if conversation.ConversationType == constant.SingleChatType || conversation.ConversationType == constant.NotificationChatType { if userID == conversation.OwnerUserID { - recvID = conversation.UserID + return conversation.UserID } else { - recvID = conversation.OwnerUserID + return conversation.OwnerUserID } - } else if conversation.ConversationType == constant.SuperGroupChatType { - recvID = conversation.GroupID + } else if conversation.ConversationType == constant.ReadGroupChatType { + return conversation.GroupID } - return + return "" } diff --git a/internal/rpc/msg/statistics.go b/internal/rpc/msg/statistics.go index e62954dea..15a0aaa57 100644 --- a/internal/rpc/msg/statistics.go +++ b/internal/rpc/msg/statistics.go @@ -18,28 +18,20 @@ import ( "context" "time" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/utils" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/utils/datautil" ) func (m *msgServer) GetActiveUser(ctx context.Context, req *msg.GetActiveUserReq) (*msg.GetActiveUserResp, error) { - msgCount, userCount, users, dateCount, err := m.MsgDatabase.RangeUserSendCount( - ctx, - time.UnixMilli(req.Start), - time.UnixMilli(req.End), - req.Group, - req.Ase, - req.Pagination.PageNumber, - req.Pagination.ShowNumber, - ) + msgCount, userCount, users, dateCount, err := m.MsgDatabase.RangeUserSendCount(ctx, time.UnixMilli(req.Start), time.UnixMilli(req.End), req.Group, req.Ase, req.Pagination.PageNumber, req.Pagination.ShowNumber) if err != nil { return nil, err } var pbUsers []*msg.ActiveUser if len(users) > 0 { - userIDs := utils.Slice(users, func(e *unrelation.UserCount) string { return e.UserID }) + userIDs := datautil.Slice(users, func(e *relation.UserCount) string { return e.UserID }) userMap, err := m.UserLocalCache.GetUsersInfoMap(ctx, userIDs) if err != nil { return nil, err @@ -68,20 +60,13 @@ func (m *msgServer) GetActiveUser(ctx context.Context, req *msg.GetActiveUserReq } func (m *msgServer) GetActiveGroup(ctx context.Context, req *msg.GetActiveGroupReq) (*msg.GetActiveGroupResp, error) { - msgCount, groupCount, groups, dateCount, err := m.MsgDatabase.RangeGroupSendCount( - ctx, - time.UnixMilli(req.Start), - time.UnixMilli(req.End), - req.Ase, - req.Pagination.PageNumber, - req.Pagination.ShowNumber, - ) + msgCount, groupCount, groups, dateCount, err := m.MsgDatabase.RangeGroupSendCount(ctx, time.UnixMilli(req.Start), time.UnixMilli(req.End), req.Ase, req.Pagination.PageNumber, req.Pagination.ShowNumber) if err != nil { return nil, err } var pbgroups []*msg.ActiveGroup if len(groups) > 0 { - groupIDs := utils.Slice(groups, func(e *unrelation.GroupCount) string { return e.GroupID }) + groupIDs := datautil.Slice(groups, func(e *relation.GroupCount) string { return e.GroupID }) resp, err := m.GroupLocalCache.GetGroupInfos(ctx, groupIDs) if err != nil { return nil, err diff --git a/internal/rpc/msg/sync_msg.go b/internal/rpc/msg/sync_msg.go index 0a8b3f89e..afb79506e 100644 --- a/internal/rpc/msg/sync_msg.go +++ b/internal/rpc/msg/sync_msg.go @@ -16,20 +16,19 @@ package msg import ( "context" + "github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/timeutil" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/log" ) -func (m *msgServer) PullMessageBySeqs( - ctx context.Context, - req *sdkws.PullMessageBySeqsReq, -) (*sdkws.PullMessageBySeqsResp, error) { +func (m *msgServer) PullMessageBySeqs(ctx context.Context, req *sdkws.PullMessageBySeqsReq) (*sdkws.PullMessageBySeqsResp, error) { resp := &sdkws.PullMessageBySeqsResp{} resp.Msgs = make(map[string]*sdkws.PullMsgs) resp.NotificationMsgs = make(map[string]*sdkws.PullMsgs) @@ -88,7 +87,7 @@ func (m *msgServer) PullMessageBySeqs( } func (m *msgServer) GetMaxSeq(ctx context.Context, req *sdkws.GetMaxSeqReq) (*sdkws.GetMaxSeqResp, error) { - if err := authverify.CheckAccessV3(ctx, req.UserID, m.config); err != nil { + if err := authverify.CheckAccessV3(ctx, req.UserID, m.config.Share.IMAdminUserID); err != nil { return nil, err } conversationIDs, err := m.ConversationLocalCache.GetConversationIDs(ctx, req.UserID) @@ -96,9 +95,9 @@ func (m *msgServer) GetMaxSeq(ctx context.Context, req *sdkws.GetMaxSeqReq) (*sd return nil, err } for _, conversationID := range conversationIDs { - conversationIDs = append(conversationIDs, utils.GetNotificationConversationIDByConversationID(conversationID)) + conversationIDs = append(conversationIDs, conversationutil.GetNotificationConversationIDByConversationID(conversationID)) } - conversationIDs = append(conversationIDs, utils.GetSelfNotificationConversationID(req.UserID)) + conversationIDs = append(conversationIDs, conversationutil.GetSelfNotificationConversationID(req.UserID)) log.ZDebug(ctx, "GetMaxSeq", "conversationIDs", conversationIDs) maxSeqs, err := m.MsgDatabase.GetMaxSeqs(ctx, conversationIDs) if err != nil { @@ -133,7 +132,7 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq switch chatLog.SessionType { case constant.SingleChatType, constant.NotificationChatType: recvIDs = append(recvIDs, chatLog.RecvID) - case constant.GroupChatType, constant.SuperGroupChatType: + case constant.WriteGroupChatType, constant.ReadGroupChatType: groupIDs = append(groupIDs, chatLog.GroupID) } } @@ -175,7 +174,7 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq // Construct response with updated information for _, chatLog := range chatLogs { pbchatLog := &msg.ChatLog{} - utils.CopyStructFields(pbchatLog, chatLog) + datautil.CopyStructFields(pbchatLog, chatLog) pbchatLog.SendTime = chatLog.SendTime pbchatLog.CreateTime = chatLog.CreateTime if chatLog.SenderNickname == "" { @@ -184,7 +183,7 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq switch chatLog.SessionType { case constant.SingleChatType, constant.NotificationChatType: pbchatLog.RecvNickname = recvMap[chatLog.RecvID] - case constant.GroupChatType, constant.SuperGroupChatType: + case constant.WriteGroupChatType, constant.ReadGroupChatType: groupInfo := groupMap[chatLog.GroupID] pbchatLog.SenderFaceURL = groupInfo.FaceURL pbchatLog.GroupMemberCount = groupInfo.MemberCount // Reflects actual member count @@ -200,5 +199,5 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq } func (m *msgServer) GetServerTime(ctx context.Context, _ *msg.GetServerTimeReq) (*msg.GetServerTimeResp, error) { - return &msg.GetServerTimeResp{ServerTime: utils.GetCurrentTimestampByMill()}, nil + return &msg.GetServerTimeResp{ServerTime: timeutil.GetCurrentTimestampByMill()}, nil } diff --git a/internal/rpc/msg/utils.go b/internal/rpc/msg/utils.go index 48665562a..69b4d0bf6 100644 --- a/internal/rpc/msg/utils.go +++ b/internal/rpc/msg/utils.go @@ -15,34 +15,13 @@ package msg import ( - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/utils" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/errs" "github.com/redis/go-redis/v9" "go.mongodb.org/mongo-driver/mongo" ) -func isMessageHasReadEnabled(msgData *sdkws.MsgData, config *config.GlobalConfig) bool { - switch { - case msgData.ContentType == constant.HasReadReceipt && msgData.SessionType == constant.SingleChatType: - if config.SingleMessageHasReadReceiptEnable { - return true - } else { - return false - } - case msgData.ContentType == constant.HasReadReceipt && msgData.SessionType == constant.SuperGroupChatType: - if config.GroupMessageHasReadReceiptEnable { - return true - } else { - return false - } - } - return true -} - func IsNotFound(err error) bool { - switch utils.Unwrap(err) { + switch errs.Unwrap(err) { case redis.Nil, mongo.ErrNoDocuments: return true default: diff --git a/internal/rpc/msg/verify.go b/internal/rpc/msg/verify.go index 5bfd7013e..33879bfe7 100644 --- a/internal/rpc/msg/verify.go +++ b/internal/rpc/msg/verify.go @@ -16,16 +16,18 @@ package msg import ( "context" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/encrypt" + "github.com/openimsdk/tools/utils/timeutil" "math/rand" "strconv" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" ) var ExcludeContentType = []int{constant.HasReadReceipt} @@ -50,10 +52,7 @@ type MessageRevoked struct { func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgReq) error { switch data.MsgData.SessionType { case constant.SingleChatType: - if len(m.config.Manager.UserID) > 0 && utils.IsContain(data.MsgData.SendID, m.config.Manager.UserID) { - return nil - } - if utils.IsContain(data.MsgData.SendID, m.config.IMAdmin.UserID) { + if datautil.Contain(data.MsgData.SendID, m.config.Share.IMAdminUserID...) { return nil } if data.MsgData.ContentType <= constant.NotificationEnd && @@ -65,35 +64,33 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe return err } if black { - return errs.ErrBlockedByPeer.Wrap() + return servererrs.ErrBlockedByPeer.Wrap() } - if m.config.MessageVerify.FriendVerify != nil && *m.config.MessageVerify.FriendVerify { + if m.config.RpcConfig.FriendVerify { friend, err := m.FriendLocalCache.IsFriend(ctx, data.MsgData.SendID, data.MsgData.RecvID) if err != nil { return err } if !friend { - return errs.ErrNotPeersFriend.Wrap() + return servererrs.ErrNotPeersFriend.Wrap() } return nil } return nil - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: groupInfo, err := m.GroupLocalCache.GetGroupInfo(ctx, data.MsgData.GroupID) if err != nil { return err } if groupInfo.Status == constant.GroupStatusDismissed && data.MsgData.ContentType != constant.GroupDismissedNotification { - return errs.ErrDismissedAlready.Wrap() + return servererrs.ErrDismissedAlready.Wrap() } if groupInfo.GroupType == constant.SuperGroup { return nil } - if len(m.config.Manager.UserID) > 0 && utils.IsContain(data.MsgData.SendID, m.config.Manager.UserID) { - return nil - } - if utils.IsContain(data.MsgData.SendID, m.config.IMAdmin.UserID) { + + if datautil.Contain(data.MsgData.SendID, m.config.Share.IMAdminUserID...) { return nil } if data.MsgData.ContentType <= constant.NotificationEnd && @@ -105,13 +102,13 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe return err } if _, ok := memberIDs[data.MsgData.SendID]; !ok { - return errs.ErrNotInGroupYet.Wrap() + return servererrs.ErrNotInGroupYet.Wrap() } groupMemberInfo, err := m.GroupLocalCache.GetGroupMember(ctx, data.MsgData.GroupID, data.MsgData.SendID) if err != nil { if errs.ErrRecordNotFound.Is(err) { - return errs.ErrNotInGroupYet.Wrap(err.Error()) + return servererrs.ErrNotInGroupYet.WrapMsg(err.Error()) } return err } @@ -119,10 +116,10 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe return nil } else { if groupMemberInfo.MuteEndTime >= time.Now().UnixMilli() { - return errs.ErrMutedInGroup.Wrap() + return servererrs.ErrMutedInGroup.Wrap() } if groupInfo.Status == constant.GroupStatusMuted && groupMemberInfo.RoleLevel != constant.GroupAdmin { - return errs.ErrMutedGroup.Wrap() + return servererrs.ErrMutedGroup.Wrap() } } return nil @@ -134,7 +131,7 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe func (m *msgServer) encapsulateMsgData(msg *sdkws.MsgData) { msg.ServerMsgID = GetMsgID(msg.SendID) if msg.SendTime == 0 { - msg.SendTime = utils.GetCurrentTimestampByMill() + msg.SendTime = timeutil.GetCurrentTimestampByMill() } switch msg.ContentType { case constant.Text: @@ -159,36 +156,30 @@ func (m *msgServer) encapsulateMsgData(msg *sdkws.MsgData) { fallthrough case constant.Quote: case constant.Revoke: - utils.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false) - utils.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false) case constant.HasReadReceipt: - utils.SetSwitchFromOptions(msg.Options, constant.IsConversationUpdate, false) - utils.SetSwitchFromOptions(msg.Options, constant.IsSenderConversationUpdate, false) - utils.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false) - utils.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsConversationUpdate, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsSenderConversationUpdate, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false) case constant.Typing: - utils.SetSwitchFromOptions(msg.Options, constant.IsHistory, false) - utils.SetSwitchFromOptions(msg.Options, constant.IsPersistent, false) - utils.SetSwitchFromOptions(msg.Options, constant.IsSenderSync, false) - utils.SetSwitchFromOptions(msg.Options, constant.IsConversationUpdate, false) - utils.SetSwitchFromOptions(msg.Options, constant.IsSenderConversationUpdate, false) - utils.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false) - utils.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsHistory, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsPersistent, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsSenderSync, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsConversationUpdate, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsSenderConversationUpdate, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false) + datautil.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false) } } func GetMsgID(sendID string) string { - t := time.Now().Format("2006-01-02 15:04:05") - return utils.Md5(t + "-" + sendID + "-" + strconv.Itoa(rand.Int())) + t := timeutil.GetCurrentTimeFormatted() + return encrypt.Md5(t + "-" + sendID + "-" + strconv.Itoa(rand.Int())) } -func (m *msgServer) modifyMessageByUserMessageReceiveOpt( - ctx context.Context, - userID, conversationID string, - sessionType int, - pb *msg.SendMsgReq, -) (bool, error) { - defer log.ZDebug(ctx, "modifyMessageByUserMessageReceiveOpt return") +func (m *msgServer) modifyMessageByUserMessageReceiveOpt(ctx context.Context, userID, conversationID string, sessionType int, pb *msg.SendMsgReq) (bool, error) { opt, err := m.UserLocalCache.GetUserGlobalMsgRecvOpt(ctx, userID) if err != nil { return false, err @@ -201,10 +192,9 @@ func (m *msgServer) modifyMessageByUserMessageReceiveOpt( if pb.MsgData.Options == nil { pb.MsgData.Options = make(map[string]bool, 10) } - utils.SetSwitchFromOptions(pb.MsgData.Options, constant.IsOfflinePush, false) + datautil.SetSwitchFromOptions(pb.MsgData.Options, constant.IsOfflinePush, false) return true, nil } - // conversationID := utils.GetConversationIDBySessionType(conversationID, sessionType) singleOpt, err := m.ConversationLocalCache.GetSingleConversationRecvMsgOpt(ctx, userID, conversationID) if errs.ErrRecordNotFound.Is(err) { return true, nil @@ -215,7 +205,7 @@ func (m *msgServer) modifyMessageByUserMessageReceiveOpt( case constant.ReceiveMessage: return true, nil case constant.NotReceiveMessage: - if utils.IsContainInt(int(pb.MsgData.ContentType), ExcludeContentType) { + if datautil.Contain(int(pb.MsgData.ContentType), ExcludeContentType...) { return true, nil } return false, nil @@ -223,7 +213,7 @@ func (m *msgServer) modifyMessageByUserMessageReceiveOpt( if pb.MsgData.Options == nil { pb.MsgData.Options = make(map[string]bool, 10) } - utils.SetSwitchFromOptions(pb.MsgData.Options, constant.IsOfflinePush, false) + datautil.SetSwitchFromOptions(pb.MsgData.Options, constant.IsOfflinePush, false) return true, nil } return true, nil diff --git a/internal/rpc/third/log.go b/internal/rpc/third/log.go index 97428254c..7712851ed 100644 --- a/internal/rpc/third/log.go +++ b/internal/rpc/third/log.go @@ -17,16 +17,16 @@ package third import ( "context" "crypto/rand" - "fmt" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/third" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/utils" - utils2 "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/authverify" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/third" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/stringutil" ) func genLogID() string { @@ -45,7 +45,7 @@ func genLogID() string { } func (t *thirdServer) UploadLogs(ctx context.Context, req *third.UploadLogsReq) (*third.UploadLogsResp, error) { - var DBlogs []*relationtb.LogModel + var dbLogs []*relationtb.LogModel userID := ctx.Value(constant.OpUserID).(string) platform := constant.PlatformID2Name[int(req.Platform)] for _, fileURL := range req.FileURLs { @@ -70,11 +70,11 @@ func (t *thirdServer) UploadLogs(ctx context.Context, req *third.UploadLogsReq) } } if log.LogID == "" { - return nil, errs.ErrData.Wrap("LogModel id gen error") + return nil, servererrs.ErrData.WrapMsg("LogModel id gen error") } - DBlogs = append(DBlogs, &log) + dbLogs = append(dbLogs, &log) } - err := t.thirdDatabase.UploadLogs(ctx, DBlogs) + err := t.thirdDatabase.UploadLogs(ctx, dbLogs) if err != nil { return nil, err } @@ -82,7 +82,7 @@ func (t *thirdServer) UploadLogs(ctx context.Context, req *third.UploadLogsReq) } func (t *thirdServer) DeleteLogs(ctx context.Context, req *third.DeleteLogsReq) (*third.DeleteLogsResp, error) { - if err := authverify.CheckAdmin(ctx, t.config); err != nil { + if err := authverify.CheckAdmin(ctx, t.config.Share.IMAdminUserID); err != nil { return nil, err } userID := "" @@ -94,8 +94,8 @@ func (t *thirdServer) DeleteLogs(ctx context.Context, req *third.DeleteLogsReq) for _, log := range logs { logIDs = append(logIDs, log.LogID) } - if ids := utils2.Single(req.LogIDs, logIDs); len(ids) > 0 { - return nil, errs.ErrRecordNotFound.Wrap(fmt.Sprintf("logIDs not found%#v", ids)) + if ids := datautil.Single(req.LogIDs, logIDs); len(ids) > 0 { + return nil, errs.ErrRecordNotFound.WrapMsg("logIDs not found", "logIDs", ids) } err = t.thirdDatabase.DeleteLogs(ctx, req.LogIDs, userID) if err != nil { @@ -110,7 +110,7 @@ func dbToPbLogInfos(logs []*relationtb.LogModel) []*third.LogInfo { return &third.LogInfo{ Filename: log.FileName, UserID: log.UserID, - Platform: utils.StringToInt32(log.Platform), + Platform: stringutil.StringToInt32(log.Platform), Url: log.Url, CreateTime: log.CreateTime.UnixMilli(), LogID: log.LogID, @@ -119,11 +119,11 @@ func dbToPbLogInfos(logs []*relationtb.LogModel) []*third.LogInfo { Ex: log.Ex, } } - return utils.Slice(logs, db2pbForLogInfo) + return datautil.Slice(logs, db2pbForLogInfo) } func (t *thirdServer) SearchLogs(ctx context.Context, req *third.SearchLogsReq) (*third.SearchLogsResp, error) { - if err := authverify.CheckAdmin(ctx, t.config); err != nil { + if err := authverify.CheckAdmin(ctx, t.config.Share.IMAdminUserID); err != nil { return nil, err } var ( @@ -131,7 +131,7 @@ func (t *thirdServer) SearchLogs(ctx context.Context, req *third.SearchLogsReq) userIDs []string ) if req.StartTime > req.EndTime { - return nil, errs.ErrArgs.Wrap("startTime>endTime") + return nil, errs.ErrArgs.WrapMsg("startTime>endTime") } if req.StartTime == 0 && req.EndTime == 0 { t := time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC) diff --git a/internal/rpc/third/s3.go b/internal/rpc/third/s3.go index f79b73a99..21d982268 100644 --- a/internal/rpc/third/s3.go +++ b/internal/rpc/third/s3.go @@ -23,15 +23,16 @@ import ( "strconv" "time" - "github.com/OpenIMSDK/protocol/third" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" "github.com/google/uuid" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3/cont" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" + "github.com/openimsdk/protocol/third" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/s3" + "github.com/openimsdk/tools/s3/cont" + "github.com/openimsdk/tools/utils/datautil" ) func (t *thirdServer) PartLimit(ctx context.Context, req *third.PartLimitReq) (*third.PartLimitResp, error) { @@ -52,7 +53,6 @@ func (t *thirdServer) PartSize(ctx context.Context, req *third.PartSizeReq) (*th } func (t *thirdServer) InitiateMultipartUpload(ctx context.Context, req *third.InitiateMultipartUploadReq) (*third.InitiateMultipartUploadResp, error) { - defer log.ZDebug(ctx, "return") if err := t.checkUploadName(ctx, req.Name); err != nil { return nil, err } @@ -74,7 +74,7 @@ func (t *thirdServer) InitiateMultipartUpload(ctx context.Context, req *third.In return nil, err } return &third.InitiateMultipartUploadResp{ - Url: t.apiAddress(obj.Name), + Url: t.apiAddress(req.UrlPrefix, obj.Name), }, nil } return nil, err @@ -107,8 +107,7 @@ func (t *thirdServer) InitiateMultipartUpload(ctx context.Context, req *third.In } func (t *thirdServer) AuthSign(ctx context.Context, req *third.AuthSignReq) (*third.AuthSignResp, error) { - defer log.ZDebug(ctx, "return") - partNumbers := utils.Slice(req.PartNumbers, func(partNumber int32) int { return int(partNumber) }) + partNumbers := datautil.Slice(req.PartNumbers, func(partNumber int32) int { return int(partNumber) }) result, err := t.s3dataBase.AuthSign(ctx, req.UploadID, partNumbers) if err != nil { return nil, err @@ -131,7 +130,6 @@ func (t *thirdServer) AuthSign(ctx context.Context, req *third.AuthSignReq) (*th } func (t *thirdServer) CompleteMultipartUpload(ctx context.Context, req *third.CompleteMultipartUploadReq) (*third.CompleteMultipartUploadResp, error) { - defer log.ZDebug(ctx, "return") if err := t.checkUploadName(ctx, req.Name); err != nil { return nil, err } @@ -153,7 +151,7 @@ func (t *thirdServer) CompleteMultipartUpload(ctx context.Context, req *third.Co return nil, err } return &third.CompleteMultipartUploadResp{ - Url: t.apiAddress(obj.Name), + Url: t.apiAddress(req.UrlPrefix, obj.Name), }, nil } @@ -169,7 +167,7 @@ func (t *thirdServer) AccessURL(ctx context.Context, req *third.AccessURLReq) (* opt.Image.Height, _ = strconv.Atoi(req.Query["height"]) log.ZDebug(ctx, "AccessURL image", "name", req.Name, "option", opt.Image) default: - return nil, errs.ErrArgs.Wrap("invalid query type") + return nil, errs.ErrArgs.WrapMsg("invalid query type") } } expireTime, rawURL, err := t.s3dataBase.AccessURL(ctx, req.Name, t.defaultExpire, opt) @@ -184,10 +182,10 @@ func (t *thirdServer) AccessURL(ctx context.Context, req *third.AccessURLReq) (* func (t *thirdServer) InitiateFormData(ctx context.Context, req *third.InitiateFormDataReq) (*third.InitiateFormDataResp, error) { if req.Name == "" { - return nil, errs.ErrArgs.Wrap("name is empty") + return nil, errs.ErrArgs.WrapMsg("name is empty") } if req.Size <= 0 { - return nil, errs.ErrArgs.Wrap("size must be greater than 0") + return nil, errs.ErrArgs.WrapMsg("size must be greater than 0") } if err := t.checkUploadName(ctx, req.Name); err != nil { return nil, err @@ -209,7 +207,7 @@ func (t *thirdServer) InitiateFormData(ctx context.Context, req *third.InitiateF } uid, err := uuid.NewRandom() if err != nil { - return nil, errs.Wrap(err, "uuid NewRandom failed") + return nil, errs.WrapMsg(err, "uuid NewRandom failed") } if key == "" { date := time.Now().Format("20060102") @@ -224,7 +222,7 @@ func (t *thirdServer) InitiateFormData(ctx context.Context, req *third.InitiateF } mateData, err := json.Marshal(&mate) if err != nil { - return nil, errs.Wrap(err, "marshal failed") + return nil, errs.WrapMsg(err, "marshal failed") } resp, err := t.s3dataBase.FormData(ctx, key, req.Size, req.ContentType, duration) if err != nil { @@ -237,7 +235,7 @@ func (t *thirdServer) InitiateFormData(ctx context.Context, req *third.InitiateF Header: toPbMapArray(resp.Header), FormData: resp.FormData, Expires: resp.Expires.UnixMilli(), - SuccessCodes: utils.Slice(resp.SuccessCodes, func(code int) int32 { + SuccessCodes: datautil.Slice(resp.SuccessCodes, func(code int) int32 { return int32(code) }), }, nil @@ -245,15 +243,15 @@ func (t *thirdServer) InitiateFormData(ctx context.Context, req *third.InitiateF func (t *thirdServer) CompleteFormData(ctx context.Context, req *third.CompleteFormDataReq) (*third.CompleteFormDataResp, error) { if req.Id == "" { - return nil, errs.ErrArgs.Wrap("id is empty") + return nil, errs.ErrArgs.WrapMsg("id is empty") } data, err := base64.RawStdEncoding.DecodeString(req.Id) if err != nil { - return nil, errs.ErrArgs.Wrap("invalid id " + err.Error()) + return nil, errs.ErrArgs.WrapMsg("invalid id " + err.Error()) } var mate FormDataMate if err := json.Unmarshal(data, &mate); err != nil { - return nil, errs.ErrArgs.Wrap("invalid id " + err.Error()) + return nil, errs.ErrArgs.WrapMsg("invalid id " + err.Error()) } if err := t.checkUploadName(ctx, mate.Name); err != nil { return nil, err @@ -263,7 +261,7 @@ func (t *thirdServer) CompleteFormData(ctx context.Context, req *third.CompleteF return nil, err } if info.Size > 0 && info.Size != mate.Size { - return nil, errs.ErrData.Wrap("file size mismatch") + return nil, servererrs.ErrData.WrapMsg("file size mismatch") } obj := &relation.ObjectModel{ Name: mate.Name, @@ -278,11 +276,11 @@ func (t *thirdServer) CompleteFormData(ctx context.Context, req *third.CompleteF if err := t.s3dataBase.SetObject(ctx, obj); err != nil { return nil, err } - return &third.CompleteFormDataResp{Url: t.apiAddress(mate.Name)}, nil + return &third.CompleteFormDataResp{Url: t.apiAddress(req.UrlPrefix, mate.Name)}, nil } -func (t *thirdServer) apiAddress(name string) string { - return t.apiURL + name +func (t *thirdServer) apiAddress(prefix, name string) string { + return prefix + name } type FormDataMate struct { diff --git a/internal/rpc/third/third.go b/internal/rpc/third/third.go index 928fb0f42..9bf8cafa9 100644 --- a/internal/rpc/third/third.go +++ b/internal/rpc/third/third.go @@ -17,73 +17,79 @@ package third import ( "context" "fmt" - "net/url" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" "time" - "github.com/OpenIMSDK/protocol/third" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/db/mgo" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3/cos" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3/minio" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3/oss" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/third" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/redisutil" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/s3" + "github.com/openimsdk/tools/s3/cos" + "github.com/openimsdk/tools/s3/minio" + "github.com/openimsdk/tools/s3/oss" "google.golang.org/grpc" ) -func Start(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error { - mongo, err := unrelation.NewMongo(config) +type thirdServer struct { + thirdDatabase controller.ThirdDatabase + s3dataBase controller.S3Database + userRpcClient rpcclient.UserRpcClient + defaultExpire time.Duration + config *Config +} +type Config struct { + RpcConfig config.Third + RedisConfig config.Redis + MongodbConfig config.Mongo + ZookeeperConfig config.ZooKeeper + NotificationConfig config.Notification + Share config.Share + MinioConfig config.Minio + LocalCacheConfig config.LocalCache +} + +func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { + mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) if err != nil { return err } - logdb, err := mgo.NewLogMongo(mongo.GetDatabase(config.Mongo.Database)) + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) if err != nil { return err } - s3db, err := mgo.NewS3Mongo(mongo.GetDatabase(config.Mongo.Database)) + logdb, err := mgo.NewLogMongo(mgocli.GetDB()) if err != nil { return err } - apiURL := config.Object.ApiURL - if apiURL == "" { - return errs.Wrap(fmt.Errorf("api is empty")) - } - if _, err := url.Parse(config.Object.ApiURL); err != nil { - return err - } - if apiURL[len(apiURL)-1] != '/' { - apiURL += "/" - } - apiURL += "object/" - rdb, err := cache.NewRedis(config) + s3db, err := mgo.NewS3Mongo(mgocli.GetDB()) if err != nil { return err } // Select the oss method according to the profile policy - enable := config.Object.Enable + enable := config.RpcConfig.Object.Enable var o s3.Interface switch enable { case "minio": - o, err = minio.NewMinio(cache.NewMinioCache(rdb), minio.Config(config.Object.Minio)) + o, err = minio.NewMinio(ctx, cache.NewMinioCache(rdb), *config.MinioConfig.Build()) case "cos": - o, err = cos.NewCos(cos.Config(config.Object.Cos)) + o, err = cos.NewCos(*config.RpcConfig.Object.Cos.Build()) case "oss": - o, err = oss.NewOSS(oss.Config(config.Object.Oss)) + o, err = oss.NewOSS(*config.RpcConfig.Object.Oss.Build()) default: err = fmt.Errorf("invalid object enable: %s", enable) } if err != nil { return err } + cache.InitLocalCache(&config.LocalCacheConfig) third.RegisterThirdServer(server, &thirdServer{ - apiURL: apiURL, - thirdDatabase: controller.NewThirdDatabase(cache.NewMsgCacheModel(rdb, config), logdb), - userRpcClient: rpcclient.NewUserRpcClient(client, config), + thirdDatabase: controller.NewThirdDatabase(cache.NewThirdCache(rdb), logdb), + userRpcClient: rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID), s3dataBase: controller.NewS3Database(rdb, o, s3db), defaultExpire: time.Hour * 24 * 7, config: config, @@ -91,15 +97,6 @@ func Start(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryReg return nil } -type thirdServer struct { - apiURL string - thirdDatabase controller.ThirdDatabase - s3dataBase controller.S3Database - userRpcClient rpcclient.UserRpcClient - defaultExpire time.Duration - config *config.GlobalConfig -} - func (t *thirdServer) FcmUpdateToken(ctx context.Context, req *third.FcmUpdateTokenReq) (resp *third.FcmUpdateTokenResp, err error) { err = t.thirdDatabase.FcmUpdateToken(ctx, req.Account, int(req.PlatformID), req.FcmToken, req.ExpireTime) if err != nil { diff --git a/internal/rpc/third/tool.go b/internal/rpc/third/tool.go index d8491d354..ac4be3968 100644 --- a/internal/rpc/third/tool.go +++ b/internal/rpc/third/tool.go @@ -16,15 +16,14 @@ package third import ( "context" - "errors" "fmt" "strings" "unicode/utf8" - "github.com/OpenIMSDK/protocol/third" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mcontext" "github.com/openimsdk/open-im-server/v3/pkg/authverify" + "github.com/openimsdk/protocol/third" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/mcontext" ) func toPbMapArray(m map[string][]string) []*third.KeyValues { @@ -43,21 +42,21 @@ func toPbMapArray(m map[string][]string) []*third.KeyValues { func (t *thirdServer) checkUploadName(ctx context.Context, name string) error { if name == "" { - return errs.ErrArgs.Wrap("name is empty") + return errs.ErrArgs.WrapMsg("name is empty") } if name[0] == '/' { - return errs.ErrArgs.Wrap("name cannot start with `/`") + return errs.ErrArgs.WrapMsg("name cannot start with `/`") } if err := checkValidObjectName(name); err != nil { - return errs.ErrArgs.Wrap(err.Error()) + return errs.ErrArgs.WrapMsg(err.Error()) } opUserID := mcontext.GetOpUserID(ctx) if opUserID == "" { - return errs.ErrNoPermission.Wrap("opUserID is empty") + return errs.ErrNoPermission.WrapMsg("opUserID is empty") } - if !authverify.IsManagerUserID(opUserID, t.config) { + if !authverify.IsManagerUserID(opUserID, t.config.Share.IMAdminUserID) { if !strings.HasPrefix(name, opUserID+"/") { - return errs.ErrNoPermission.Wrap(fmt.Sprintf("name must start with `%s/`", opUserID)) + return errs.ErrNoPermission.WrapMsg(fmt.Sprintf("name must start with `%s/`", opUserID)) } } return nil @@ -65,21 +64,21 @@ func (t *thirdServer) checkUploadName(ctx context.Context, name string) error { func checkValidObjectNamePrefix(objectName string) error { if len(objectName) > 1024 { - return errors.New("object name cannot be longer than 1024 characters") + return errs.New("object name cannot be longer than 1024 characters") } if !utf8.ValidString(objectName) { - return errors.New("object name with non UTF-8 strings are not supported") + return errs.New("object name with non UTF-8 strings are not supported") } return nil } func checkValidObjectName(objectName string) error { if strings.TrimSpace(objectName) == "" { - return errors.New("object name cannot be empty") + return errs.New("object name cannot be empty") } return checkValidObjectNamePrefix(objectName) } func (t *thirdServer) IsManagerUserID(opUserID string) bool { - return authverify.IsManagerUserID(opUserID, t.config) + return authverify.IsManagerUserID(opUserID, t.config.Share.IMAdminUserID) } diff --git a/internal/rpc/user/callback.go b/internal/rpc/user/callback.go index 34f211973..1bdf399d2 100644 --- a/internal/rpc/user/callback.go +++ b/internal/rpc/user/callback.go @@ -16,118 +16,101 @@ package user import ( "context" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" + "github.com/openimsdk/tools/utils/datautil" - pbuser "github.com/OpenIMSDK/protocol/user" - "github.com/OpenIMSDK/tools/utils" cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/http" + pbuser "github.com/openimsdk/protocol/user" ) -func CallbackBeforeUpdateUserInfo(ctx context.Context, globalConfig *config.GlobalConfig, req *pbuser.UpdateUserInfoReq) error { - if !globalConfig.Callback.CallbackBeforeUpdateUserInfo.Enable { +func (s *userServer) webhookBeforeUpdateUserInfo(ctx context.Context, before *config.BeforeConfig, req *pbuser.UpdateUserInfoReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &cbapi.CallbackBeforeUpdateUserInfoReq{ + CallbackCommand: cbapi.CallbackBeforeUpdateUserInfoCommand, + UserID: req.UserInfo.UserID, + FaceURL: &req.UserInfo.FaceURL, + Nickname: &req.UserInfo.Nickname, + } + resp := &cbapi.CallbackBeforeUpdateUserInfoResp{} + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } + + datautil.NotNilReplace(&req.UserInfo.FaceURL, resp.FaceURL) + datautil.NotNilReplace(&req.UserInfo.Ex, resp.Ex) + datautil.NotNilReplace(&req.UserInfo.Nickname, resp.Nickname) return nil - } - cbReq := &cbapi.CallbackBeforeUpdateUserInfoReq{ - CallbackCommand: cbapi.CallbackBeforeUpdateUserInfoCommand, - UserID: req.UserInfo.UserID, - FaceURL: &req.UserInfo.FaceURL, - Nickname: &req.UserInfo.Nickname, - } - resp := &cbapi.CallbackBeforeUpdateUserInfoResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeUpdateUserInfo); err != nil { - return err - } - utils.NotNilReplace(&req.UserInfo.FaceURL, resp.FaceURL) - utils.NotNilReplace(&req.UserInfo.Ex, resp.Ex) - utils.NotNilReplace(&req.UserInfo.Nickname, resp.Nickname) - return nil + }) } -func CallbackAfterUpdateUserInfo(ctx context.Context, globalConfig *config.GlobalConfig, req *pbuser.UpdateUserInfoReq) error { - if !globalConfig.Callback.CallbackAfterUpdateUserInfo.Enable { - return nil - } + +func (s *userServer) webhookAfterUpdateUserInfo(ctx context.Context, after *config.AfterConfig, req *pbuser.UpdateUserInfoReq) { cbReq := &cbapi.CallbackAfterUpdateUserInfoReq{ CallbackCommand: cbapi.CallbackAfterUpdateUserInfoCommand, UserID: req.UserInfo.UserID, FaceURL: req.UserInfo.FaceURL, Nickname: req.UserInfo.Nickname, } - resp := &cbapi.CallbackAfterUpdateUserInfoResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeUpdateUserInfo); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterUpdateUserInfoResp{}, after) } -func CallbackBeforeUpdateUserInfoEx(ctx context.Context, globalConfig *config.GlobalConfig, req *pbuser.UpdateUserInfoExReq) error { - if !globalConfig.Callback.CallbackBeforeUpdateUserInfoEx.Enable { + +func (s *userServer) webhookBeforeUpdateUserInfoEx(ctx context.Context, before *config.BeforeConfig, req *pbuser.UpdateUserInfoExReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &cbapi.CallbackBeforeUpdateUserInfoExReq{ + CallbackCommand: cbapi.CallbackBeforeUpdateUserInfoExCommand, + UserID: req.UserInfo.UserID, + FaceURL: req.UserInfo.FaceURL, + Nickname: req.UserInfo.Nickname, + } + resp := &cbapi.CallbackBeforeUpdateUserInfoExResp{} + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } + + datautil.NotNilReplace(req.UserInfo.FaceURL, resp.FaceURL) + datautil.NotNilReplace(req.UserInfo.Ex, resp.Ex) + datautil.NotNilReplace(req.UserInfo.Nickname, resp.Nickname) return nil - } - cbReq := &cbapi.CallbackBeforeUpdateUserInfoExReq{ - CallbackCommand: cbapi.CallbackBeforeUpdateUserInfoExCommand, - UserID: req.UserInfo.UserID, - FaceURL: req.UserInfo.FaceURL, - Nickname: req.UserInfo.Nickname, - } - resp := &cbapi.CallbackBeforeUpdateUserInfoExResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeUpdateUserInfoEx); err != nil { - return err - } - utils.NotNilReplace(req.UserInfo.FaceURL, resp.FaceURL) - utils.NotNilReplace(req.UserInfo.Ex, resp.Ex) - utils.NotNilReplace(req.UserInfo.Nickname, resp.Nickname) - return nil + }) } -func CallbackAfterUpdateUserInfoEx(ctx context.Context, globalConfig *config.GlobalConfig, req *pbuser.UpdateUserInfoExReq) error { - if !globalConfig.Callback.CallbackAfterUpdateUserInfoEx.Enable { - return nil - } + +func (s *userServer) webhookAfterUpdateUserInfoEx(ctx context.Context, after *config.AfterConfig, req *pbuser.UpdateUserInfoExReq) { cbReq := &cbapi.CallbackAfterUpdateUserInfoExReq{ CallbackCommand: cbapi.CallbackAfterUpdateUserInfoExCommand, UserID: req.UserInfo.UserID, FaceURL: req.UserInfo.FaceURL, Nickname: req.UserInfo.Nickname, } - resp := &cbapi.CallbackAfterUpdateUserInfoExResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeUpdateUserInfoEx); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterUpdateUserInfoExResp{}, after) } -func CallbackBeforeUserRegister(ctx context.Context, globalConfig *config.GlobalConfig, req *pbuser.UserRegisterReq) error { - if !globalConfig.Callback.CallbackBeforeUserRegister.Enable { - return nil - } - cbReq := &cbapi.CallbackBeforeUserRegisterReq{ - CallbackCommand: cbapi.CallbackBeforeUserRegisterCommand, - Secret: req.Secret, - Users: req.Users, - } +func (s *userServer) webhookBeforeUserRegister(ctx context.Context, before *config.BeforeConfig, req *pbuser.UserRegisterReq) error { + return webhook.WithCondition(ctx, before, func(ctx context.Context) error { + cbReq := &cbapi.CallbackBeforeUserRegisterReq{ + CallbackCommand: cbapi.CallbackBeforeUserRegisterCommand, + Secret: req.Secret, + Users: req.Users, + } - resp := &cbapi.CallbackBeforeUserRegisterResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackBeforeUpdateUserInfo); err != nil { - return err - } - if len(resp.Users) != 0 { - req.Users = resp.Users - } - return nil -} + resp := &cbapi.CallbackBeforeUserRegisterResp{} + + if err := s.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { + return err + } -func CallbackAfterUserRegister(ctx context.Context, globalConfig *config.GlobalConfig, req *pbuser.UserRegisterReq) error { - if !globalConfig.Callback.CallbackAfterUserRegister.Enable { + if len(resp.Users) != 0 { + req.Users = resp.Users + } return nil - } + }) +} + +func (s *userServer) webhookAfterUserRegister(ctx context.Context, after *config.AfterConfig, req *pbuser.UserRegisterReq) { cbReq := &cbapi.CallbackAfterUserRegisterReq{ CallbackCommand: cbapi.CallbackAfterUserRegisterCommand, Secret: req.Secret, Users: req.Users, } - resp := &cbapi.CallbackAfterUserRegisterResp{} - if err := http.CallBackPostReturn(ctx, globalConfig.Callback.CallbackUrl, cbReq, resp, globalConfig.Callback.CallbackAfterUpdateUserInfo); err != nil { - return err - } - return nil + s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterUserRegisterResp{}, after) } diff --git a/pkg/rpcclient/notification/user.go b/internal/rpc/user/notification.go similarity index 72% rename from pkg/rpcclient/notification/user.go rename to internal/rpc/user/notification.go index 610967fd5..348cd5628 100644 --- a/pkg/rpcclient/notification/user.go +++ b/internal/rpc/user/notification.go @@ -12,22 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package notification +package user import ( "context" + "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" ) type UserNotificationSender struct { *rpcclient.NotificationSender - getUsersInfo func(ctx context.Context, userIDs []string) ([]CommonUser, error) + getUsersInfo func(ctx context.Context, userIDs []string) ([]notification.CommonUser, error) // db controller db controller.UserDatabase } @@ -44,7 +44,7 @@ func WithUserFunc( fn func(ctx context.Context, userIDs []string) (users []*relationtb.UserModel, err error), ) userNotificationSenderOptions { return func(u *UserNotificationSender) { - f := func(ctx context.Context, userIDs []string) (result []CommonUser, err error) { + f := func(ctx context.Context, userIDs []string) (result []notification.CommonUser, err error) { users, err := fn(ctx, userIDs) if err != nil { return nil, err @@ -58,13 +58,9 @@ func WithUserFunc( } } -func NewUserNotificationSender( - config *config.GlobalConfig, - msgRpcClient *rpcclient.MessageRpcClient, - opts ...userNotificationSenderOptions, -) *UserNotificationSender { +func NewUserNotificationSender(config *Config, msgRpcClient *rpcclient.MessageRpcClient, opts ...userNotificationSenderOptions) *UserNotificationSender { f := &UserNotificationSender{ - NotificationSender: rpcclient.NewNotificationSender(config, rpcclient.WithRpcClient(msgRpcClient)), + NotificationSender: rpcclient.NewNotificationSender(&config.NotificationConfig, rpcclient.WithRpcClient(msgRpcClient)), } for _, opt := range opts { opt(f) @@ -101,24 +97,24 @@ func NewUserNotificationSender( func (u *UserNotificationSender) UserStatusChangeNotification( ctx context.Context, tips *sdkws.UserStatusChangeTips, -) error { - return u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserStatusChangeNotification, tips) +) { + u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserStatusChangeNotification, tips) } func (u *UserNotificationSender) UserCommandUpdateNotification( ctx context.Context, tips *sdkws.UserCommandUpdateTips, -) error { - return u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserCommandUpdateNotification, tips) +) { + u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserCommandUpdateNotification, tips) } func (u *UserNotificationSender) UserCommandAddNotification( ctx context.Context, tips *sdkws.UserCommandAddTips, -) error { - return u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserCommandAddNotification, tips) +) { + u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserCommandAddNotification, tips) } func (u *UserNotificationSender) UserCommandDeleteNotification( ctx context.Context, tips *sdkws.UserCommandDeleteTips, -) error { - return u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserCommandDeleteNotification, tips) +) { + u.Notification(ctx, tips.FromUserID, tips.ToUserID, constant.UserCommandDeleteNotification, tips) } diff --git a/internal/rpc/user/statistics.go b/internal/rpc/user/statistics.go index 42068f2ce..709c0146b 100644 --- a/internal/rpc/user/statistics.go +++ b/internal/rpc/user/statistics.go @@ -18,27 +18,24 @@ import ( "context" "time" - pbuser "github.com/OpenIMSDK/protocol/user" - "github.com/OpenIMSDK/tools/errs" + pbuser "github.com/openimsdk/protocol/user" + "github.com/openimsdk/tools/errs" ) -func (s *userServer) UserRegisterCount( - ctx context.Context, - req *pbuser.UserRegisterCountReq, -) (*pbuser.UserRegisterCountResp, error) { +func (s *userServer) UserRegisterCount(ctx context.Context, req *pbuser.UserRegisterCountReq) (*pbuser.UserRegisterCountResp, error) { if req.Start > req.End { - return nil, errs.ErrArgs.Wrap("start > end") + return nil, errs.ErrArgs.WrapMsg("start > end") } - total, err := s.CountTotal(ctx, nil) + total, err := s.db.CountTotal(ctx, nil) if err != nil { return nil, err } start := time.UnixMilli(req.Start) - before, err := s.CountTotal(ctx, &start) + before, err := s.db.CountTotal(ctx, &start) if err != nil { return nil, err } - count, err := s.CountRangeEverydayTotal(ctx, start, time.UnixMilli(req.End)) + count, err := s.db.CountRangeEverydayTotal(ctx, start, time.UnixMilli(req.End)) if err != nil { return nil, err } diff --git a/internal/rpc/user/user.go b/internal/rpc/user/user.go index 3d13cd7b6..c453ac9f8 100644 --- a/internal/rpc/user/user.go +++ b/internal/rpc/user/user.go @@ -16,91 +16,100 @@ package user import ( "context" - "fmt" + "github.com/openimsdk/open-im-server/v3/internal/rpc/friend" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" + "github.com/openimsdk/tools/db/redisutil" "math/rand" "strings" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - pbuser "github.com/OpenIMSDK/protocol/user" - registry "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/pagination" - "github.com/OpenIMSDK/tools/tx" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/authverify" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/convert" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/db/mgo" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" tablerelation "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" - "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" + pbuser "github.com/openimsdk/protocol/user" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" + registry "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" "google.golang.org/grpc" ) type userServer struct { - controller.UserDatabase - friendNotificationSender *notification.FriendNotificationSender - userNotificationSender *notification.UserNotificationSender + db controller.UserDatabase + friendNotificationSender *friend.FriendNotificationSender + userNotificationSender *UserNotificationSender friendRpcClient *rpcclient.FriendRpcClient groupRpcClient *rpcclient.GroupRpcClient RegisterCenter registry.SvcDiscoveryRegistry - config *config.GlobalConfig + config *Config + webhookClient *webhook.Client } -func (s *userServer) GetGroupOnlineUser(ctx context.Context, req *pbuser.GetGroupOnlineUserReq) (*pbuser.GetGroupOnlineUserResp, error) { - //TODO implement me - panic("implement me") +type Config struct { + RpcConfig config.User + RedisConfig config.Redis + MongodbConfig config.Mongo + KafkaConfig config.Kafka + ZookeeperConfig config.ZooKeeper + NotificationConfig config.Notification + Share config.Share + WebhooksConfig config.Webhooks + LocalCacheConfig config.LocalCache } -func Start(config *config.GlobalConfig, client registry.SvcDiscoveryRegistry, server *grpc.Server) error { - rdb, err := cache.NewRedis(config) +func Start(ctx context.Context, config *Config, client registry.SvcDiscoveryRegistry, server *grpc.Server) error { + mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) if err != nil { return err } - mongo, err := unrelation.NewMongo(config) + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) if err != nil { return err } users := make([]*tablerelation.UserModel, 0) - if len(config.IMAdmin.UserID) != len(config.IMAdmin.Nickname) { - return errs.Wrap(fmt.Errorf("the count of ImAdmin.UserID is not equal to the count of ImAdmin.Nickname")) - } - for k, v := range config.IMAdmin.UserID { - users = append(users, &tablerelation.UserModel{UserID: v, Nickname: config.IMAdmin.Nickname[k], AppMangerLevel: constant.AppNotificationAdmin}) + + for _, v := range config.Share.IMAdminUserID { + users = append(users, &tablerelation.UserModel{UserID: v, Nickname: v, AppMangerLevel: constant.AppNotificationAdmin}) } - userDB, err := mgo.NewUserMongo(mongo.GetDatabase(config.Mongo.Database)) + userDB, err := mgo.NewUserMongo(mgocli.GetDB()) if err != nil { return err } - cache := cache.NewUserCacheRedis(rdb, userDB, cache.GetDefaultOpt()) - userMongoDB := unrelation.NewUserMongoDriver(mongo.GetDatabase(config.Mongo.Database)) - database := controller.NewUserDatabase(userDB, cache, tx.NewMongo(mongo.GetClient()), userMongoDB) - friendRpcClient := rpcclient.NewFriendRpcClient(client, config) - groupRpcClient := rpcclient.NewGroupRpcClient(client, config) - msgRpcClient := rpcclient.NewMessageRpcClient(client, config) + userCache := cache.NewUserCacheRedis(rdb, &config.LocalCacheConfig, userDB, cache.GetDefaultOpt()) + userMongoDB := mgo.NewUserMongoDriver(mgocli.GetDB()) + database := controller.NewUserDatabase(userDB, userCache, mgocli.GetTx(), userMongoDB) + friendRpcClient := rpcclient.NewFriendRpcClient(client, config.Share.RpcRegisterName.Friend) + groupRpcClient := rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group) + msgRpcClient := rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg) + cache.InitLocalCache(&config.LocalCacheConfig) u := &userServer{ - UserDatabase: database, + db: database, RegisterCenter: client, friendRpcClient: &friendRpcClient, groupRpcClient: &groupRpcClient, - friendNotificationSender: notification.NewFriendNotificationSender(config, &msgRpcClient, notification.WithDBFunc(database.FindWithError)), - userNotificationSender: notification.NewUserNotificationSender(config, &msgRpcClient, notification.WithUserFunc(database.FindWithError)), + friendNotificationSender: friend.NewFriendNotificationSender(&config.NotificationConfig, &msgRpcClient, friend.WithDBFunc(database.FindWithError)), + userNotificationSender: NewUserNotificationSender(config, &msgRpcClient, WithUserFunc(database.FindWithError)), config: config, + webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL), } pbuser.RegisterUserServer(server, u) - return u.UserDatabase.InitOnce(context.Background(), users) + return u.db.InitOnce(context.Background(), users) } func (s *userServer) GetDesignateUsers(ctx context.Context, req *pbuser.GetDesignateUsersReq) (resp *pbuser.GetDesignateUsersResp, err error) { resp = &pbuser.GetDesignateUsersResp{} - users, err := s.FindWithError(ctx, req.UserIDs) + users, err := s.db.FindWithError(ctx, req.UserIDs) if err != nil { return nil, err } @@ -108,20 +117,26 @@ func (s *userServer) GetDesignateUsers(ctx context.Context, req *pbuser.GetDesig return resp, nil } +// deprecated: + +//UpdateUserInfo + func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInfoReq) (resp *pbuser.UpdateUserInfoResp, err error) { resp = &pbuser.UpdateUserInfoResp{} - err = authverify.CheckAccessV3(ctx, req.UserInfo.UserID, s.config) + err = authverify.CheckAccessV3(ctx, req.UserInfo.UserID, s.config.Share.IMAdminUserID) if err != nil { return nil, err } - if err := CallbackBeforeUpdateUserInfo(ctx, s.config, req); err != nil { + + if err := s.webhookBeforeUpdateUserInfo(ctx, &s.config.WebhooksConfig.BeforeUpdateUserInfo, req); err != nil { return nil, err } + data := convert.UserPb2DBMap(req.UserInfo) - if err := s.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil { + if err := s.db.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil { return nil, err } - _ = s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID) + s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID) friends, err := s.friendRpcClient.GetFriendIDs(ctx, req.UserInfo.UserID) if err != nil { return nil, err @@ -134,9 +149,7 @@ func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserI for _, friendID := range friends { s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID) } - if err = CallbackAfterUpdateUserInfo(ctx, s.config, req); err != nil { - return nil, err - } + s.webhookAfterUpdateUserInfo(ctx, &s.config.WebhooksConfig.AfterUpdateUserInfo, req) if err = s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil { return nil, err } @@ -144,19 +157,18 @@ func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserI } func (s *userServer) UpdateUserInfoEx(ctx context.Context, req *pbuser.UpdateUserInfoExReq) (resp *pbuser.UpdateUserInfoExResp, err error) { resp = &pbuser.UpdateUserInfoExResp{} - err = authverify.CheckAccessV3(ctx, req.UserInfo.UserID, s.config) + err = authverify.CheckAccessV3(ctx, req.UserInfo.UserID, s.config.Share.IMAdminUserID) if err != nil { return nil, err } - - if err = CallbackBeforeUpdateUserInfoEx(ctx, s.config, req); err != nil { + if err = s.webhookBeforeUpdateUserInfoEx(ctx, &s.config.WebhooksConfig.BeforeUpdateUserInfoEx, req); err != nil { return nil, err } data := convert.UserPb2DBMapEx(req.UserInfo) - if err = s.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil { + if err = s.db.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil { return nil, err } - _ = s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID) + s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID) friends, err := s.friendRpcClient.GetFriendIDs(ctx, req.UserInfo.UserID) if err != nil { return nil, err @@ -169,9 +181,7 @@ func (s *userServer) UpdateUserInfoEx(ctx context.Context, req *pbuser.UpdateUse for _, friendID := range friends { s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID) } - if err := CallbackAfterUpdateUserInfoEx(ctx, s.config, req); err != nil { - return nil, err - } + s.webhookAfterUpdateUserInfoEx(ctx, &s.config.WebhooksConfig.AfterUpdateUserInfoEx, req) if err := s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil { return nil, err } @@ -179,12 +189,12 @@ func (s *userServer) UpdateUserInfoEx(ctx context.Context, req *pbuser.UpdateUse } func (s *userServer) SetGlobalRecvMessageOpt(ctx context.Context, req *pbuser.SetGlobalRecvMessageOptReq) (resp *pbuser.SetGlobalRecvMessageOptResp, err error) { resp = &pbuser.SetGlobalRecvMessageOptResp{} - if _, err := s.FindWithError(ctx, []string{req.UserID}); err != nil { + if _, err := s.db.FindWithError(ctx, []string{req.UserID}); err != nil { return nil, err } m := make(map[string]any, 1) m["global_recv_msg_opt"] = req.GlobalRecvMsgOpt - if err := s.UpdateByMap(ctx, req.UserID, m); err != nil { + if err := s.db.UpdateByMap(ctx, req.UserID, m); err != nil { return nil, err } s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserID) @@ -193,14 +203,14 @@ func (s *userServer) SetGlobalRecvMessageOpt(ctx context.Context, req *pbuser.Se func (s *userServer) AccountCheck(ctx context.Context, req *pbuser.AccountCheckReq) (resp *pbuser.AccountCheckResp, err error) { resp = &pbuser.AccountCheckResp{} - if utils.Duplicate(req.CheckUserIDs) { - return nil, errs.ErrArgs.Wrap("userID repeated") + if datautil.Duplicate(req.CheckUserIDs) { + return nil, errs.ErrArgs.WrapMsg("userID repeated") } - err = authverify.CheckAdmin(ctx, s.config) + err = authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID) if err != nil { return nil, err } - users, err := s.Find(ctx, req.CheckUserIDs) + users, err := s.db.Find(ctx, req.CheckUserIDs) if err != nil { return nil, err } @@ -222,13 +232,13 @@ func (s *userServer) AccountCheck(ctx context.Context, req *pbuser.AccountCheckR func (s *userServer) GetPaginationUsers(ctx context.Context, req *pbuser.GetPaginationUsersReq) (resp *pbuser.GetPaginationUsersResp, err error) { if req.UserID == "" && req.NickName == "" { - total, users, err := s.PageFindUser(ctx, constant.IMOrdinaryUser, constant.AppOrdinaryUsers, req.Pagination) + total, users, err := s.db.PageFindUser(ctx, constant.IMOrdinaryUser, constant.AppOrdinaryUsers, req.Pagination) if err != nil { return nil, err } return &pbuser.GetPaginationUsersResp{Total: int32(total), Users: convert.UsersDB2Pb(users)}, err } else { - total, users, err := s.PageFindUserWithKeyword(ctx, constant.IMOrdinaryUser, constant.AppOrdinaryUsers, req.UserID, req.NickName, req.Pagination) + total, users, err := s.db.PageFindUserWithKeyword(ctx, constant.IMOrdinaryUser, constant.AppOrdinaryUsers, req.UserID, req.NickName, req.Pagination) if err != nil { return nil, err } @@ -241,33 +251,33 @@ func (s *userServer) GetPaginationUsers(ctx context.Context, req *pbuser.GetPagi func (s *userServer) UserRegister(ctx context.Context, req *pbuser.UserRegisterReq) (resp *pbuser.UserRegisterResp, err error) { resp = &pbuser.UserRegisterResp{} if len(req.Users) == 0 { - return nil, errs.ErrArgs.Wrap("users is empty") + return nil, errs.ErrArgs.WrapMsg("users is empty") } - if req.Secret != s.config.Secret { - log.ZDebug(ctx, "UserRegister", s.config.Secret, req.Secret) - return nil, errs.ErrNoPermission.Wrap("secret invalid") + if req.Secret != s.config.Share.Secret { + log.ZDebug(ctx, "UserRegister", s.config.Share.Secret, req.Secret) + return nil, errs.ErrNoPermission.WrapMsg("secret invalid") } - if utils.DuplicateAny(req.Users, func(e *sdkws.UserInfo) string { return e.UserID }) { - return nil, errs.ErrArgs.Wrap("userID repeated") + if datautil.DuplicateAny(req.Users, func(e *sdkws.UserInfo) string { return e.UserID }) { + return nil, errs.ErrArgs.WrapMsg("userID repeated") } userIDs := make([]string, 0) for _, user := range req.Users { if user.UserID == "" { - return nil, errs.ErrArgs.Wrap("userID is empty") + return nil, errs.ErrArgs.WrapMsg("userID is empty") } if strings.Contains(user.UserID, ":") { - return nil, errs.ErrArgs.Wrap("userID contains ':' is invalid userID") + return nil, errs.ErrArgs.WrapMsg("userID contains ':' is invalid userID") } userIDs = append(userIDs, user.UserID) } - exist, err := s.IsExist(ctx, userIDs) + exist, err := s.db.IsExist(ctx, userIDs) if err != nil { return nil, err } if exist { - return nil, errs.ErrRegisteredAlready.Wrap("userID registered already") + return nil, servererrs.ErrRegisteredAlready.WrapMsg("userID registered already") } - if err := CallbackBeforeUserRegister(ctx, s.config, req); err != nil { + if err := s.webhookBeforeUserRegister(ctx, &s.config.WebhooksConfig.BeforeUserRegister, req); err != nil { return nil, err } now := time.Now() @@ -283,18 +293,16 @@ func (s *userServer) UserRegister(ctx context.Context, req *pbuser.UserRegisterR GlobalRecvMsgOpt: user.GlobalRecvMsgOpt, }) } - if err := s.Create(ctx, users); err != nil { + if err := s.db.Create(ctx, users); err != nil { return nil, err } - if err := CallbackAfterUserRegister(ctx, s.config, req); err != nil { - return nil, err - } + s.webhookAfterUserRegister(ctx, &s.config.WebhooksConfig.AfterUserRegister, req) return resp, nil } func (s *userServer) GetGlobalRecvMessageOpt(ctx context.Context, req *pbuser.GetGlobalRecvMessageOptReq) (resp *pbuser.GetGlobalRecvMessageOptResp, err error) { - user, err := s.FindWithError(ctx, []string{req.UserID}) + user, err := s.db.FindWithError(ctx, []string{req.UserID}) if err != nil { return nil, err } @@ -303,7 +311,7 @@ func (s *userServer) GetGlobalRecvMessageOpt(ctx context.Context, req *pbuser.Ge // GetAllUserID Get user account by page. func (s *userServer) GetAllUserID(ctx context.Context, req *pbuser.GetAllUserIDReq) (resp *pbuser.GetAllUserIDResp, err error) { - total, userIDs, err := s.UserDatabase.GetAllUserID(ctx, req.Pagination) + total, userIDs, err := s.db.GetAllUserID(ctx, req.Pagination) if err != nil { return nil, err } @@ -313,18 +321,18 @@ func (s *userServer) GetAllUserID(ctx context.Context, req *pbuser.GetAllUserIDR // SubscribeOrCancelUsersStatus Subscribe online or cancel online users. func (s *userServer) SubscribeOrCancelUsersStatus(ctx context.Context, req *pbuser.SubscribeOrCancelUsersStatusReq) (resp *pbuser.SubscribeOrCancelUsersStatusResp, err error) { if req.Genre == constant.SubscriberUser { - err = s.UserDatabase.SubscribeUsersStatus(ctx, req.UserID, req.UserIDs) + err = s.db.SubscribeUsersStatus(ctx, req.UserID, req.UserIDs) if err != nil { return nil, err } var status []*pbuser.OnlineStatus - status, err = s.UserDatabase.GetUserStatus(ctx, req.UserIDs) + status, err = s.db.GetUserStatus(ctx, req.UserIDs) if err != nil { return nil, err } return &pbuser.SubscribeOrCancelUsersStatusResp{StatusList: status}, nil } else if req.Genre == constant.Unsubscribe { - err = s.UserDatabase.UnsubscribeUsersStatus(ctx, req.UserID, req.UserIDs) + err = s.db.UnsubscribeUsersStatus(ctx, req.UserID, req.UserIDs) if err != nil { return nil, err } @@ -335,7 +343,7 @@ func (s *userServer) SubscribeOrCancelUsersStatus(ctx context.Context, req *pbus // GetUserStatus Get the online status of the user. func (s *userServer) GetUserStatus(ctx context.Context, req *pbuser.GetUserStatusReq) (resp *pbuser.GetUserStatusResp, err error) { - onlineStatusList, err := s.UserDatabase.GetUserStatus(ctx, req.UserIDs) + onlineStatusList, err := s.db.GetUserStatus(ctx, req.UserIDs) if err != nil { return nil, err } @@ -345,11 +353,11 @@ func (s *userServer) GetUserStatus(ctx context.Context, req *pbuser.GetUserStatu // SetUserStatus Synchronize user's online status. func (s *userServer) SetUserStatus(ctx context.Context, req *pbuser.SetUserStatusReq) (resp *pbuser.SetUserStatusResp, err error) { - err = s.UserDatabase.SetUserStatus(ctx, req.UserID, req.Status, req.PlatformID) + err = s.db.SetUserStatus(ctx, req.UserID, req.Status, req.PlatformID) if err != nil { return nil, err } - list, err := s.UserDatabase.GetSubscribedList(ctx, req.UserID) + list, err := s.db.GetSubscribedList(ctx, req.UserID) if err != nil { return nil, err } @@ -369,11 +377,11 @@ func (s *userServer) SetUserStatus(ctx context.Context, req *pbuser.SetUserStatu // GetSubscribeUsersStatus Get the online status of subscribers. func (s *userServer) GetSubscribeUsersStatus(ctx context.Context, req *pbuser.GetSubscribeUsersStatusReq) (*pbuser.GetSubscribeUsersStatusResp, error) { - userList, err := s.UserDatabase.GetAllSubscribeList(ctx, req.UserID) + userList, err := s.db.GetAllSubscribeList(ctx, req.UserID) if err != nil { return nil, err } - onlineStatusList, err := s.UserDatabase.GetUserStatus(ctx, userList) + onlineStatusList, err := s.db.GetUserStatus(ctx, userList) if err != nil { return nil, err } @@ -382,7 +390,7 @@ func (s *userServer) GetSubscribeUsersStatus(ctx context.Context, // ProcessUserCommandAdd user general function add. func (s *userServer) ProcessUserCommandAdd(ctx context.Context, req *pbuser.ProcessUserCommandAddReq) (*pbuser.ProcessUserCommandAddResp, error) { - err := authverify.CheckAccessV3(ctx, req.UserID, s.config) + err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID) if err != nil { return nil, err } @@ -395,8 +403,8 @@ func (s *userServer) ProcessUserCommandAdd(ctx context.Context, req *pbuser.Proc if req.Ex != nil { value = req.Ex.Value } - // Assuming you have a method in s.UserDatabase to add a user command - err = s.UserDatabase.AddUserCommand(ctx, req.UserID, req.Type, req.Uuid, value, ex) + // Assuming you have a method in s.db to add a user command + err = s.db.AddUserCommand(ctx, req.UserID, req.Type, req.Uuid, value, ex) if err != nil { return nil, err } @@ -404,21 +412,18 @@ func (s *userServer) ProcessUserCommandAdd(ctx context.Context, req *pbuser.Proc FromUserID: req.UserID, ToUserID: req.UserID, } - err = s.userNotificationSender.UserCommandAddNotification(ctx, tips) - if err != nil { - return nil, err - } + s.userNotificationSender.UserCommandAddNotification(ctx, tips) return &pbuser.ProcessUserCommandAddResp{}, nil } // ProcessUserCommandDelete user general function delete. func (s *userServer) ProcessUserCommandDelete(ctx context.Context, req *pbuser.ProcessUserCommandDeleteReq) (*pbuser.ProcessUserCommandDeleteResp, error) { - err := authverify.CheckAccessV3(ctx, req.UserID, s.config) + err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID) if err != nil { return nil, err } - err = s.UserDatabase.DeleteUserCommand(ctx, req.UserID, req.Type, req.Uuid) + err = s.db.DeleteUserCommand(ctx, req.UserID, req.Type, req.Uuid) if err != nil { return nil, err } @@ -426,17 +431,13 @@ func (s *userServer) ProcessUserCommandDelete(ctx context.Context, req *pbuser.P FromUserID: req.UserID, ToUserID: req.UserID, } - err = s.userNotificationSender.UserCommandDeleteNotification(ctx, tips) - if err != nil { - return nil, err - } - + s.userNotificationSender.UserCommandDeleteNotification(ctx, tips) return &pbuser.ProcessUserCommandDeleteResp{}, nil } // ProcessUserCommandUpdate user general function update. func (s *userServer) ProcessUserCommandUpdate(ctx context.Context, req *pbuser.ProcessUserCommandUpdateReq) (*pbuser.ProcessUserCommandUpdateResp, error) { - err := authverify.CheckAccessV3(ctx, req.UserID, s.config) + err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID) if err != nil { return nil, err } @@ -450,8 +451,8 @@ func (s *userServer) ProcessUserCommandUpdate(ctx context.Context, req *pbuser.P val["ex"] = req.Ex.Value } - // Assuming you have a method in s.UserDatabase to update a user command - err = s.UserDatabase.UpdateUserCommand(ctx, req.UserID, req.Type, req.Uuid, val) + // Assuming you have a method in s.db to update a user command + err = s.db.UpdateUserCommand(ctx, req.UserID, req.Type, req.Uuid, val) if err != nil { return nil, err } @@ -459,21 +460,18 @@ func (s *userServer) ProcessUserCommandUpdate(ctx context.Context, req *pbuser.P FromUserID: req.UserID, ToUserID: req.UserID, } - err = s.userNotificationSender.UserCommandUpdateNotification(ctx, tips) - if err != nil { - return nil, err - } + s.userNotificationSender.UserCommandUpdateNotification(ctx, tips) return &pbuser.ProcessUserCommandUpdateResp{}, nil } func (s *userServer) ProcessUserCommandGet(ctx context.Context, req *pbuser.ProcessUserCommandGetReq) (*pbuser.ProcessUserCommandGetResp, error) { - err := authverify.CheckAccessV3(ctx, req.UserID, s.config) + err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID) if err != nil { return nil, err } // Fetch user commands from the database - commands, err := s.UserDatabase.GetUserCommands(ctx, req.UserID, req.Type) + commands, err := s.db.GetUserCommands(ctx, req.UserID, req.Type) if err != nil { return nil, err } @@ -497,12 +495,12 @@ func (s *userServer) ProcessUserCommandGet(ctx context.Context, req *pbuser.Proc } func (s *userServer) ProcessUserCommandGetAll(ctx context.Context, req *pbuser.ProcessUserCommandGetAllReq) (*pbuser.ProcessUserCommandGetAllResp, error) { - err := authverify.CheckAccessV3(ctx, req.UserID, s.config) + err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID) if err != nil { return nil, err } // Fetch user commands from the database - commands, err := s.UserDatabase.GetAllUserCommands(ctx, req.UserID) + commands, err := s.db.GetAllUserCommands(ctx, req.UserID) if err != nil { return nil, err } @@ -526,14 +524,14 @@ func (s *userServer) ProcessUserCommandGetAll(ctx context.Context, req *pbuser.P } func (s *userServer) AddNotificationAccount(ctx context.Context, req *pbuser.AddNotificationAccountReq) (*pbuser.AddNotificationAccountResp, error) { - if err := authverify.CheckIMAdmin(ctx, s.config); err != nil { + if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { return nil, err } if req.UserID == "" { for i := 0; i < 20; i++ { userId := s.genUserID() - _, err := s.UserDatabase.FindWithError(ctx, []string{userId}) + _, err := s.db.FindWithError(ctx, []string{userId}) if err == nil { continue } @@ -541,12 +539,12 @@ func (s *userServer) AddNotificationAccount(ctx context.Context, req *pbuser.Add break } if req.UserID == "" { - return nil, errs.ErrInternalServer.Wrap("gen user id failed") + return nil, errs.ErrInternalServer.WrapMsg("gen user id failed") } } else { - _, err := s.UserDatabase.FindWithError(ctx, []string{req.UserID}) + _, err := s.db.FindWithError(ctx, []string{req.UserID}) if err == nil { - return nil, errs.ErrArgs.Wrap("userID is used") + return nil, errs.ErrArgs.WrapMsg("userID is used") } } @@ -557,7 +555,7 @@ func (s *userServer) AddNotificationAccount(ctx context.Context, req *pbuser.Add CreateTime: time.Now(), AppMangerLevel: constant.AppNotificationAdmin, } - if err := s.UserDatabase.Create(ctx, []*tablerelation.UserModel{user}); err != nil { + if err := s.db.Create(ctx, []*tablerelation.UserModel{user}); err != nil { return nil, err } @@ -569,11 +567,11 @@ func (s *userServer) AddNotificationAccount(ctx context.Context, req *pbuser.Add } func (s *userServer) UpdateNotificationAccountInfo(ctx context.Context, req *pbuser.UpdateNotificationAccountInfoReq) (*pbuser.UpdateNotificationAccountInfoResp, error) { - if err := authverify.CheckIMAdmin(ctx, s.config); err != nil { + if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { return nil, err } - if _, err := s.UserDatabase.FindWithError(ctx, []string{req.UserID}); err != nil { + if _, err := s.db.FindWithError(ctx, []string{req.UserID}); err != nil { return nil, errs.ErrArgs.Wrap() } @@ -587,7 +585,7 @@ func (s *userServer) UpdateNotificationAccountInfo(ctx context.Context, req *pbu user["face_url"] = req.FaceURL } - if err := s.UserDatabase.UpdateByMap(ctx, req.UserID, user); err != nil { + if err := s.db.UpdateByMap(ctx, req.UserID, user); err != nil { return nil, err } @@ -596,7 +594,7 @@ func (s *userServer) UpdateNotificationAccountInfo(ctx context.Context, req *pbu func (s *userServer) SearchNotificationAccount(ctx context.Context, req *pbuser.SearchNotificationAccountReq) (*pbuser.SearchNotificationAccountResp, error) { // Check if user is an admin - if err := authverify.CheckIMAdmin(ctx, s.config); err != nil { + if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { return nil, err } @@ -606,7 +604,7 @@ func (s *userServer) SearchNotificationAccount(ctx context.Context, req *pbuser. // If a keyword is provided in the request if req.Keyword != "" { // Find users by keyword - users, err = s.UserDatabase.Find(ctx, []string{req.Keyword}) + users, err = s.db.Find(ctx, []string{req.Keyword}) if err != nil { return nil, err } @@ -618,7 +616,7 @@ func (s *userServer) SearchNotificationAccount(ctx context.Context, req *pbuser. } // Find users by nickname if no users found by keyword - users, err = s.UserDatabase.FindByNickname(ctx, req.Keyword) + users, err = s.db.FindByNickname(ctx, req.Keyword) if err != nil { return nil, err } @@ -627,7 +625,7 @@ func (s *userServer) SearchNotificationAccount(ctx context.Context, req *pbuser. } // If no keyword, find users with notification settings - users, err = s.UserDatabase.FindNotification(ctx, constant.AppNotificationAdmin) + users, err = s.db.FindNotification(ctx, constant.AppNotificationAdmin) if err != nil { return nil, err } @@ -638,17 +636,17 @@ func (s *userServer) SearchNotificationAccount(ctx context.Context, req *pbuser. func (s *userServer) GetNotificationAccount(ctx context.Context, req *pbuser.GetNotificationAccountReq) (*pbuser.GetNotificationAccountResp, error) { if req.UserID == "" { - return nil, errs.ErrArgs.Wrap("userID is empty") + return nil, errs.ErrArgs.WrapMsg("userID is empty") } - user, err := s.UserDatabase.GetUserByID(ctx, req.UserID) + user, err := s.db.GetUserByID(ctx, req.UserID) if err != nil { - return nil, errs.ErrUserIDNotFound.Wrap() + return nil, servererrs.ErrUserIDNotFound.Wrap() } if user.AppMangerLevel == constant.AppAdmin || user.AppMangerLevel == constant.AppNotificationAdmin { return &pbuser.GetNotificationAccountResp{}, nil } - return nil, errs.ErrNoPermission.Wrap("notification messages cannot be sent for this ID") + return nil, errs.ErrNoPermission.WrapMsg("notification messages cannot be sent for this ID") } func (s *userServer) genUserID() string { @@ -670,7 +668,7 @@ func (s *userServer) userModelToResp(users []*relation.UserModel, pagination pag accounts := make([]*pbuser.NotificationAccountInfo, 0) var total int64 for _, v := range users { - if v.AppMangerLevel == constant.AppNotificationAdmin && !utils.IsContain(v.UserID, s.config.IMAdmin.UserID) { + if v.AppMangerLevel == constant.AppNotificationAdmin && !datautil.Contain(v.UserID, s.config.Share.IMAdminUserID...) { temp := &pbuser.NotificationAccountInfo{ UserID: v.UserID, FaceURL: v.FaceURL, @@ -681,7 +679,7 @@ func (s *userServer) userModelToResp(users []*relation.UserModel, pagination pag } } - notificationAccounts := utils.Paginate(accounts, int(pagination.GetPageNumber()), int(pagination.GetShowNumber())) + notificationAccounts := datautil.Paginate(accounts, int(pagination.GetPageNumber()), int(pagination.GetShowNumber())) return &pbuser.SearchNotificationAccountResp{Total: total, NotificationAccounts: notificationAccounts} } diff --git a/internal/tools/conversation.go b/internal/tools/conversation.go index b555a3361..3e2a88ffd 100644 --- a/internal/tools/conversation.go +++ b/internal/tools/conversation.go @@ -19,14 +19,15 @@ import ( "math/rand" "time" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/idutil" + "github.com/openimsdk/tools/utils/stringutil" ) -//func (c *MsgTool) ConversationsDestructMsgs() { +// func (c *MsgTool) ConversationsDestructMsgs() { // log.ZInfo(context.Background(), "start msg destruct cron task") // ctx := mcontext.NewCtx(utils.GetSelfFuncName()) // conversations, err := c.conversationDatabase.GetConversationIDsNeedDestruct(ctx) @@ -70,7 +71,7 @@ import ( func (c *MsgTool) ConversationsDestructMsgs() { log.ZInfo(context.Background(), "start msg destruct cron task") - ctx := mcontext.NewCtx(utils.GetSelfFuncName()) + ctx := mcontext.NewCtx(stringutil.GetSelfFuncName()) num, err := c.conversationDatabase.GetAllConversationIDsNumber(ctx) if err != nil { log.ZError(ctx, "GetAllConversationIDsNumber failed", err) @@ -117,7 +118,7 @@ func (c *MsgTool) ConversationsDestructMsgs() { } } for _, conversation := range temp { - ctx = mcontext.NewCtx(utils.GetSelfFuncName() + "-" + utils.OperationIDGenerator() + "-" + conversation.ConversationID + "-" + conversation.OwnerUserID) + ctx = mcontext.NewCtx(stringutil.GetSelfFuncName() + "-" + idutil.OperationIDGenerator() + "-" + conversation.ConversationID + "-" + conversation.OwnerUserID) log.ZDebug( ctx, "UserMsgsDestruct", @@ -141,9 +142,7 @@ func (c *MsgTool) ConversationsDestructMsgs() { log.ZError(ctx, "updateUsersConversationField failed", err, "conversationID", conversation.ConversationID, "ownerUserID", conversation.OwnerUserID) continue } - if err := c.msgNotificationSender.UserDeleteMsgsNotification(ctx, conversation.OwnerUserID, conversation.ConversationID, seqs); err != nil { - log.ZError(ctx, "userDeleteMsgsNotification failed", err, "conversationID", conversation.ConversationID, "ownerUserID", conversation.OwnerUserID) - } + c.msgNotificationSender.UserDeleteMsgsNotification(ctx, conversation.OwnerUserID, conversation.ConversationID, seqs) } } } diff --git a/internal/tools/cron_task.go b/internal/tools/cron_task.go index 7535e9b96..82ce95eda 100644 --- a/internal/tools/cron_task.go +++ b/internal/tools/cron_task.go @@ -16,46 +16,58 @@ package tools import ( "context" - "fmt" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/db/redisutil" "os" "os/signal" "syscall" "time" - "github.com/OpenIMSDK/tools/errs" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" "github.com/redis/go-redis/v9" "github.com/robfig/cron/v3" ) -func StartTask(config *config.GlobalConfig) error { - fmt.Println("cron task start, config", config.ChatRecordsClearTime) +type CronTaskConfig struct { + CronTask config.CronTask + RedisConfig config.Redis + MongodbConfig config.Mongo + ZookeeperConfig config.ZooKeeper + Share config.Share + KafkaConfig config.Kafka +} + +func Start(ctx context.Context, config *CronTaskConfig) error { + + log.CInfo(ctx, "CRON-TASK server is initializing", "chatRecordsClearTime", + config.CronTask.ChatRecordsClearTime, "msgDestructTime", config.CronTask.MsgDestructTime) - msgTool, err := InitMsgTool(config) + msgTool, err := InitMsgTool(ctx, config) if err != nil { return err } msgTool.convertTools() - rdb, err := cache.NewRedis(config) + rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) if err != nil { return err } // register cron tasks var crontab = cron.New() - fmt.Printf("Start chatRecordsClearTime cron task, cron config: %s\n", config.ChatRecordsClearTime) - _, err = crontab.AddFunc(config.ChatRecordsClearTime, cronWrapFunc(config, rdb, "cron_clear_msg_and_fix_seq", msgTool.AllConversationClearMsgAndFixSeq)) + + _, err = crontab.AddFunc(config.CronTask.ChatRecordsClearTime, + cronWrapFunc(config, rdb, "cron_clear_msg_and_fix_seq", msgTool.AllConversationClearMsgAndFixSeq)) if err != nil { return errs.Wrap(err) } - fmt.Printf("Start msgDestruct cron task, cron config: %s\n", config.MsgDestructTime) - _, err = crontab.AddFunc(config.MsgDestructTime, cronWrapFunc(config, rdb, "cron_conversations_destruct_msgs", msgTool.ConversationsDestructMsgs)) + _, err = crontab.AddFunc(config.CronTask.MsgDestructTime, + cronWrapFunc(config, rdb, "cron_conversations_destruct_msgs", msgTool.ConversationsDestructMsgs)) if err != nil { - return errs.Wrap(err, "cron_conversations_destruct_msgs") + return errs.WrapMsg(err, "cron_conversations_destruct_msgs") } // start crontab @@ -66,10 +78,10 @@ func StartTask(config *config.GlobalConfig) error { <-sigs // stop crontab, Wait for the running task to exit. - ctx := crontab.Stop() + cronCtx := crontab.Stop() select { - case <-ctx.Done(): + case <-cronCtx.Done(): // graceful exit case <-time.After(15 * time.Second): @@ -91,8 +103,8 @@ func netlock(rdb redis.UniversalClient, key string, ttl time.Duration) bool { return ok } -func cronWrapFunc(config *config.GlobalConfig, rdb redis.UniversalClient, key string, fn func()) func() { - enableCronLocker := config.EnableCronLocker +func cronWrapFunc(config *CronTaskConfig, rdb redis.UniversalClient, key string, fn func()) func() { + enableCronLocker := config.CronTask.EnableCronLocker return func() { // if don't enable cron-locker, call fn directly. if !enableCronLocker { diff --git a/internal/tools/cron_task_test.go b/internal/tools/cron_task_test.go index 17346b1c5..0bea8a436 100644 --- a/internal/tools/cron_task_test.go +++ b/internal/tools/cron_task_test.go @@ -15,22 +15,11 @@ package tools import ( - "flag" - "fmt" - "math/rand" - "os" - "sync" "testing" "time" - "github.com/OpenIMSDK/tools/errs" - "gopkg.in/yaml.v3" - "github.com/redis/go-redis/v9" - "github.com/robfig/cron/v3" "github.com/stretchr/testify/assert" - - "github.com/openimsdk/open-im-server/v3/pkg/common/config" ) func TestDisLock(t *testing.T) { @@ -51,74 +40,74 @@ func TestDisLock(t *testing.T) { assert.Equal(t, true, netlock(rdb, "cron-2", 2*time.Second)) } -func TestCronWrapFunc(t *testing.T) { - rdb := redis.NewClient(&redis.Options{}) - defer rdb.Close() - - once := sync.Once{} - done := make(chan struct{}, 1) - cb := func() { - once.Do(func() { - close(done) - }) - } - - start := time.Now() - key := fmt.Sprintf("cron-%v", rand.Int31()) - crontab := cron.New(cron.WithSeconds()) - crontab.AddFunc("*/1 * * * * *", cronWrapFunc(config.NewGlobalConfig(), rdb, key, cb)) - crontab.Start() - <-done - - dur := time.Since(start) - assert.LessOrEqual(t, dur.Seconds(), float64(2*time.Second)) - crontab.Stop() -} - -func TestCronWrapFuncWithNetlock(t *testing.T) { - conf, err := initCfg() - if err != nil { - panic(err) - } - conf.EnableCronLocker = true - rdb := redis.NewClient(&redis.Options{}) - defer rdb.Close() - - done := make(chan string, 10) - - crontab := cron.New(cron.WithSeconds()) - - key := fmt.Sprintf("cron-%v", rand.Int31()) - crontab.AddFunc("*/1 * * * * *", cronWrapFunc(conf, rdb, key, func() { - done <- "host1" - })) - crontab.AddFunc("*/1 * * * * *", cronWrapFunc(conf, rdb, key, func() { - done <- "host2" - })) - crontab.Start() - - time.Sleep(12 * time.Second) - // the ttl of netlock is 5s, so expected value is 2. - assert.Equal(t, len(done), 2) - - crontab.Stop() -} - -func initCfg() (*config.GlobalConfig, error) { - const ( - defaultCfgPath = "../../../../../config/config.yaml" - ) - - cfgPath := flag.String("c", defaultCfgPath, "Path to the configuration file") - data, err := os.ReadFile(*cfgPath) - if err != nil { - return nil, errs.Wrap(err, "ReadFile unmarshal failed") - } - - conf := config.NewGlobalConfig() - err = yaml.Unmarshal(data, &conf) - if err != nil { - return nil, errs.Wrap(err, "InitConfig unmarshal failed") - } - return conf, nil -} +//func TestCronWrapFunc(t *testing.T) { +// rdb := redis.NewClient(&redis.Options{}) +// defer rdb.Close() +// +// once := sync.Once{} +// done := make(chan struct{}, 1) +// cb := func() { +// once.Do(func() { +// close(done) +// }) +// } +// +// start := time.Now() +// key := fmt.Sprintf("cron-%v", rand.Int31()) +// crontab := cron.New(cron.WithSeconds()) +// crontab.AddFunc("*/1 * * * * *", cronWrapFunc(config.NewGlobalConfig(), rdb, key, cb)) +// crontab.Start() +// <-done +// +// dur := time.Since(start) +// assert.LessOrEqual(t, dur.Seconds(), float64(2*time.Second)) +// crontab.Stop() +//} +// +//func TestCronWrapFuncWithNetlock(t *testing.T) { +// conf, err := initCfg() +// if err != nil { +// panic(err) +// } +// conf.EnableCronLocker = true +// rdb := redis.NewClient(&redis.Options{}) +// defer rdb.Close() +// +// done := make(chan string, 10) +// +// crontab := cron.New(cron.WithSeconds()) +// +// key := fmt.Sprintf("cron-%v", rand.Int31()) +// crontab.AddFunc("*/1 * * * * *", cronWrapFunc(conf, rdb, key, func() { +// done <- "host1" +// })) +// crontab.AddFunc("*/1 * * * * *", cronWrapFunc(conf, rdb, key, func() { +// done <- "host2" +// })) +// crontab.Start() +// +// time.Sleep(12 * time.Second) +// // the ttl of netlock is 5s, so expected value is 2. +// assert.Equal(t, len(done), 2) +// +// crontab.Stop() +//} +// +//func initCfg() (*config.GlobalConfig, error) { +// const ( +// defaultCfgPath = "../../../../../config/config.yaml" +// ) +// +// cfgPath := flag.String("c", defaultCfgPath, "Path to the configuration file") +// data, err := os.ReadFile(*cfgPath) +// if err != nil { +// return nil, errs.WrapMsg(err, "ReadFile unmarshal failed") +// } +// +// conf := config.NewGlobalConfig() +// err = yaml.Unmarshal(data, &conf) +// if err != nil { +// return nil, errs.WrapMsg(err, "InitConfig unmarshal failed") +// } +// return conf, nil +//} diff --git a/internal/tools/msg.go b/internal/tools/msg.go index 67c3895cb..42af1efdc 100644 --- a/internal/tools/msg.go +++ b/internal/tools/msg.go @@ -17,27 +17,18 @@ package tools import ( "context" "fmt" + "github.com/openimsdk/open-im-server/v3/internal/rpc/msg" "math" "math/rand" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/mw" - "github.com/OpenIMSDK/tools/tx" - "github.com/OpenIMSDK/tools/utils" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/controller" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/mgo" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" - kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" - "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" - "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" + "github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/stringutil" "github.com/redis/go-redis/v9" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" ) type MsgTool struct { @@ -45,13 +36,13 @@ type MsgTool struct { conversationDatabase controller.ConversationDatabase userDatabase controller.UserDatabase groupDatabase controller.GroupDatabase - msgNotificationSender *notification.MsgNotificationSender - Config *config.GlobalConfig + msgNotificationSender *msg.MsgNotificationSender + config *CronTaskConfig } func NewMsgTool(msgDatabase controller.CommonMsgDatabase, userDatabase controller.UserDatabase, groupDatabase controller.GroupDatabase, conversationDatabase controller.ConversationDatabase, - msgNotificationSender *notification.MsgNotificationSender, config *config.GlobalConfig, + msgNotificationSender *msg.MsgNotificationSender, config *CronTaskConfig, ) *MsgTool { return &MsgTool{ msgDatabase: msgDatabase, @@ -59,69 +50,71 @@ func NewMsgTool(msgDatabase controller.CommonMsgDatabase, userDatabase controlle groupDatabase: groupDatabase, conversationDatabase: conversationDatabase, msgNotificationSender: msgNotificationSender, - Config: config, + config: config, } } -func InitMsgTool(config *config.GlobalConfig) (*MsgTool, error) { - rdb, err := cache.NewRedis(config) - if err != nil { - return nil, err - } - mongo, err := unrelation.NewMongo(config) - if err != nil { - return nil, err - } - discov, err := kdisc.NewDiscoveryRegister(config) - if err != nil { - return nil, err - } - discov.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) - userDB, err := mgo.NewUserMongo(mongo.GetDatabase(config.Mongo.Database)) - if err != nil { - return nil, err - } - msgDatabase, err := controller.InitCommonMsgDatabase(rdb, mongo.GetDatabase(config.Mongo.Database), config) - if err != nil { - return nil, err - } - userMongoDB := unrelation.NewUserMongoDriver(mongo.GetDatabase(config.Mongo.Database)) - ctxTx := tx.NewMongo(mongo.GetClient()) - userDatabase := controller.NewUserDatabase( - userDB, - cache.NewUserCacheRedis(rdb, userDB, cache.GetDefaultOpt()), - ctxTx, - userMongoDB, - ) - groupDB, err := mgo.NewGroupMongo(mongo.GetDatabase(config.Mongo.Database)) - if err != nil { - return nil, err - } - groupMemberDB, err := mgo.NewGroupMember(mongo.GetDatabase(config.Mongo.Database)) - if err != nil { - return nil, err - } - groupRequestDB, err := mgo.NewGroupRequestMgo(mongo.GetDatabase(config.Mongo.Database)) - if err != nil { - return nil, err - } - conversationDB, err := mgo.NewConversationMongo(mongo.GetDatabase(config.Mongo.Database)) - if err != nil { - return nil, err - } - groupDatabase := controller.NewGroupDatabase(rdb, groupDB, groupMemberDB, groupRequestDB, ctxTx, nil) - conversationDatabase := controller.NewConversationDatabase( - conversationDB, - cache.NewConversationRedis(rdb, cache.GetDefaultOpt(), conversationDB), - ctxTx, - ) - msgRpcClient := rpcclient.NewMessageRpcClient(discov, config) - msgNotificationSender := notification.NewMsgNotificationSender(config, rpcclient.WithRpcClient(&msgRpcClient)) - msgTool := NewMsgTool(msgDatabase, userDatabase, groupDatabase, conversationDatabase, msgNotificationSender, config) - return msgTool, nil +func InitMsgTool(ctx context.Context, config *CronTaskConfig) (*MsgTool, error) { + ch := make(chan int) + <-ch + //mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) + //if err != nil { + // return nil, err + //} + //rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) + //if err != nil { + // return nil, err + //} + //discov, err := kdisc.NewDiscoveryRegister(&config.ZookeeperConfig, &config.Share) + //if err != nil { + // return nil, err + //} + //discov.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) + //userDB, err := mgo.NewUserMongo(mgocli.GetDB()) + //if err != nil { + // return nil, err + //} + ////msgDatabase, err := controller.InitCommonMsgDatabase(rdb, mgocli.GetDB(), config) + //if err != nil { + // return nil, err + //} + //userMongoDB := mgo.NewUserMongoDriver(mgocli.GetDB()) + //userDatabase := controller.NewUserDatabase( + // userDB, + // cache.NewUserCacheRedis(rdb, userDB, cache.GetDefaultOpt()), + // mgocli.GetTx(), + // userMongoDB, + //) + //groupDB, err := mgo.NewGroupMongo(mgocli.GetDB()) + //if err != nil { + // return nil, err + //} + //groupMemberDB, err := mgo.NewGroupMember(mgocli.GetDB()) + //if err != nil { + // return nil, err + //} + //groupRequestDB, err := mgo.NewGroupRequestMgo(mgocli.GetDB()) + //if err != nil { + // return nil, err + //} + //conversationDB, err := mgo.NewConversationMongo(mgocli.GetDB()) + //if err != nil { + // return nil, err + //} + //groupDatabase := controller.NewGroupDatabase(rdb, groupDB, groupMemberDB, groupRequestDB, mgocli.GetTx(), nil) + //conversationDatabase := controller.NewConversationDatabase( + // conversationDB, + // cache.NewConversationRedis(rdb, cache.GetDefaultOpt(), conversationDB), + // mgocli.GetTx(), + //) + //msgRpcClient := rpcclient.NewMessageRpcClient(discov, config.Share.RpcRegisterName.Msg) + //msgNotificationSender := notification.NewMsgNotificationSender(config, rpcclient.WithRpcClient(&msgRpcClient)) + //msgTool := NewMsgTool(msgDatabase, userDatabase, groupDatabase, conversationDatabase, msgNotificationSender, config) + //return msgTool, nil + return nil, nil } -//func (c *MsgTool) AllConversationClearMsgAndFixSeq() { +// func (c *MsgTool) AllConversationClearMsgAndFixSeq() { // ctx := mcontext.NewCtx(utils.GetSelfFuncName()) // log.ZInfo(ctx, "============================ start del cron task ============================") // conversationIDs, err := c.conversationDatabase.GetAllConversationIDs(ctx) @@ -137,7 +130,7 @@ func InitMsgTool(config *config.GlobalConfig) (*MsgTool, error) { //} func (c *MsgTool) AllConversationClearMsgAndFixSeq() { - ctx := mcontext.NewCtx(utils.GetSelfFuncName()) + ctx := mcontext.NewCtx(stringutil.GetSelfFuncName()) log.ZInfo(ctx, "============================ start del cron task ============================") num, err := c.conversationDatabase.GetAllConversationIDsNumber(ctx) if err != nil { @@ -179,8 +172,9 @@ func (c *MsgTool) AllConversationClearMsgAndFixSeq() { func (c *MsgTool) ClearConversationsMsg(ctx context.Context, conversationIDs []string) { for _, conversationID := range conversationIDs { - if err := c.msgDatabase.DeleteConversationMsgsAndSetMinSeq(ctx, conversationID, int64(c.Config.RetainChatRecords*24*60*60)); err != nil { - log.ZError(ctx, "DeleteUserSuperGroupMsgsAndSetMinSeq failed", err, "conversationID", conversationID, "DBRetainChatRecords", c.Config.RetainChatRecords) + if err := c.msgDatabase.DeleteConversationMsgsAndSetMinSeq(ctx, conversationID, int64(c.config.CronTask.RetainChatRecords*24*60*60)); err != nil { + log.ZError(ctx, "DeleteUserSuperGroupMsgsAndSetMinSeq failed", err, "conversationID", + conversationID, "DBRetainChatRecords", c.config.CronTask.RetainChatRecords) } if err := c.checkMaxSeq(ctx, conversationID); err != nil { log.ZError(ctx, "fixSeq failed", err, "conversationID", conversationID) @@ -220,7 +214,7 @@ func (c *MsgTool) FixAllSeq(ctx context.Context) error { return err } for _, conversationID := range conversationIDs { - conversationIDs = append(conversationIDs, utils.GetNotificationConversationIDByConversationID(conversationID)) + conversationIDs = append(conversationIDs, conversationutil.GetNotificationConversationIDByConversationID(conversationID)) } for _, conversationID := range conversationIDs { if err := c.checkMaxSeq(ctx, conversationID); err != nil { diff --git a/internal/tools/msg_doc_convert.go b/internal/tools/msg_doc_convert.go index eea1b69e8..e7625ce21 100644 --- a/internal/tools/msg_doc_convert.go +++ b/internal/tools/msg_doc_convert.go @@ -15,10 +15,10 @@ package tools import ( - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" ) func (c *MsgTool) convertTools() { diff --git a/magefile.go b/magefile.go new file mode 100644 index 000000000..98ffa48f3 --- /dev/null +++ b/magefile.go @@ -0,0 +1,43 @@ +//go:build mage +// +build mage + +package main + +import ( + "github.com/openimsdk/gomake/mageutil" + "os" + "strings" +) + +var Default = Build + +func Build() { + platforms := os.Getenv("PLATFORMS") + if platforms == "" { + platforms = mageutil.DetectPlatform() + } + + for _, platform := range strings.Split(platforms, " ") { + mageutil.CompileForPlatform(platform) + } + + mageutil.PrintGreen("All binaries under cmd and tools were successfully compiled.") +} + +func Start() { + mageutil.InitForSSC() + err := setMaxOpenFiles() + if err != nil { + mageutil.PrintRed("setMaxOpenFiles failed " + err.Error()) + os.Exit(1) + } + mageutil.StartToolsAndServices() +} + +func Stop() { + mageutil.StopAndCheckBinaries() +} + +func Check() { + mageutil.CheckAndReportBinariesStatus() +} diff --git a/magefile_unix.go b/magefile_unix.go new file mode 100644 index 000000000..ff6b6de4e --- /dev/null +++ b/magefile_unix.go @@ -0,0 +1,20 @@ +//go:build mage && !windows +// +build mage,!windows + +package main + +import ( + "github.com/openimsdk/gomake/mageutil" + "syscall" +) + +func setMaxOpenFiles() error { + var rLimit syscall.Rlimit + err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &rLimit) + if err != nil { + return err + } + rLimit.Max = uint64(mageutil.MaxFileDescriptors) + rLimit.Cur = uint64(mageutil.MaxFileDescriptors) + return syscall.Setrlimit(syscall.RLIMIT_NOFILE, &rLimit) +} diff --git a/magefile_windows.go b/magefile_windows.go new file mode 100644 index 000000000..7441bfd9c --- /dev/null +++ b/magefile_windows.go @@ -0,0 +1,8 @@ +//go:build mage +// +build mage + +package main + +func setMaxOpenFiles() error { + return nil +} diff --git a/pkg/apistruct/doc.go b/pkg/apistruct/doc.go index 2f1404584..6be41c9e5 100644 --- a/pkg/apistruct/doc.go +++ b/pkg/apistruct/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/apistruct/manage.go b/pkg/apistruct/manage.go index f9f542835..e79b47722 100644 --- a/pkg/apistruct/manage.go +++ b/pkg/apistruct/manage.go @@ -15,7 +15,7 @@ package apistruct import ( - sdkws "github.com/OpenIMSDK/protocol/sdkws" + sdkws "github.com/openimsdk/protocol/sdkws" ) // SendMsg defines the structure for sending messages with various metadata. diff --git a/pkg/apistruct/msg.go b/pkg/apistruct/msg.go index d1ce427fc..f4a9f884c 100644 --- a/pkg/apistruct/msg.go +++ b/pkg/apistruct/msg.go @@ -29,6 +29,7 @@ type PictureElem struct { BigPicture PictureBaseInfo `mapstructure:"bigPicture" validate:"required"` SnapshotPicture PictureBaseInfo `mapstructure:"snapshotPicture" validate:"required"` } + type SoundElem struct { UUID string `mapstructure:"uuid"` SoundPath string `mapstructure:"soundPath"` @@ -36,6 +37,7 @@ type SoundElem struct { DataSize int64 `mapstructure:"dataSize"` Duration int64 `mapstructure:"duration" validate:"required,min=1"` } + type VideoElem struct { VideoPath string `mapstructure:"videoPath"` VideoUUID string `mapstructure:"videoUUID"` @@ -50,6 +52,7 @@ type VideoElem struct { SnapshotWidth int32 `mapstructure:"snapshotWidth" validate:"required"` SnapshotHeight int32 `mapstructure:"snapshotHeight" validate:"required"` } + type FileElem struct { FilePath string `mapstructure:"filePath"` UUID string `mapstructure:"uuid"` diff --git a/pkg/authverify/doc.go b/pkg/authverify/doc.go index f9173a708..14d63a410 100644 --- a/pkg/authverify/doc.go +++ b/pkg/authverify/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/authverify/token.go b/pkg/authverify/token.go index 8127e08df..a96d6de20 100644 --- a/pkg/authverify/token.go +++ b/pkg/authverify/token.go @@ -18,12 +18,10 @@ import ( "context" "fmt" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/tokenverify" - "github.com/OpenIMSDK/tools/utils" "github.com/golang-jwt/jwt/v4" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/datautil" ) func Secret(secret string) jwt.Keyfunc { @@ -32,63 +30,29 @@ func Secret(secret string) jwt.Keyfunc { } } -func CheckAccessV3(ctx context.Context, ownerUserID string, config *config.GlobalConfig) (err error) { +func CheckAccessV3(ctx context.Context, ownerUserID string, imAdminUserID []string) (err error) { opUserID := mcontext.GetOpUserID(ctx) - if len(config.Manager.UserID) > 0 && utils.IsContain(opUserID, config.Manager.UserID) { - return nil - } - if utils.IsContain(opUserID, config.IMAdmin.UserID) { + if datautil.Contain(opUserID, imAdminUserID...) { return nil } if opUserID == ownerUserID { return nil } - return errs.ErrNoPermission.Wrap("ownerUserID", ownerUserID) + return servererrs.ErrNoPermission.WrapMsg("ownerUserID", ownerUserID) } -func IsAppManagerUid(ctx context.Context, config *config.GlobalConfig) bool { - return (len(config.Manager.UserID) > 0 && utils.IsContain(mcontext.GetOpUserID(ctx), config.Manager.UserID)) || - utils.IsContain(mcontext.GetOpUserID(ctx), config.IMAdmin.UserID) +func IsAppManagerUid(ctx context.Context, imAdminUserID []string) bool { + return datautil.Contain(mcontext.GetOpUserID(ctx), imAdminUserID...) } -func CheckAdmin(ctx context.Context, config *config.GlobalConfig) error { - if len(config.Manager.UserID) > 0 && utils.IsContain(mcontext.GetOpUserID(ctx), config.Manager.UserID) { - return nil - } - if utils.IsContain(mcontext.GetOpUserID(ctx), config.IMAdmin.UserID) { - return nil - } - return errs.ErrNoPermission.Wrap(fmt.Sprintf("user %s is not admin userID", mcontext.GetOpUserID(ctx))) -} +func CheckAdmin(ctx context.Context, imAdminUserID []string) error { -func CheckIMAdmin(ctx context.Context, config *config.GlobalConfig) error { - if utils.IsContain(mcontext.GetOpUserID(ctx), config.IMAdmin.UserID) { - return nil - } - if len(config.Manager.UserID) > 0 && utils.IsContain(mcontext.GetOpUserID(ctx), config.Manager.UserID) { + if datautil.Contain(mcontext.GetOpUserID(ctx), imAdminUserID...) { return nil } - return errs.ErrNoPermission.Wrap(fmt.Sprintf("user %s is not CheckIMAdmin userID", mcontext.GetOpUserID(ctx))) -} - -func ParseRedisInterfaceToken(redisToken any, secret string) (*tokenverify.Claims, error) { - return tokenverify.GetClaimFromToken(string(redisToken.([]uint8)), Secret(secret)) -} - -func IsManagerUserID(opUserID string, config *config.GlobalConfig) bool { - return (len(config.Manager.UserID) > 0 && utils.IsContain(opUserID, config.Manager.UserID)) || utils.IsContain(opUserID, config.IMAdmin.UserID) + return servererrs.ErrNoPermission.WrapMsg(fmt.Sprintf("user %s is not admin userID", mcontext.GetOpUserID(ctx))) } -func WsVerifyToken(token, userID, secret string, platformID int) error { - claim, err := tokenverify.GetClaimFromToken(token, Secret(secret)) - if err != nil { - return err - } - if claim.UserID != userID { - return errs.ErrTokenInvalid.Wrap(fmt.Sprintf("token uid %s != userID %s", claim.UserID, userID)) - } - if claim.PlatformID != platformID { - return errs.ErrTokenInvalid.Wrap(fmt.Sprintf("token platform %d != %d", claim.PlatformID, platformID)) - } - return nil +func IsManagerUserID(opUserID string, imAdminUserID []string) bool { + return datautil.Contain(opUserID, imAdminUserID...) } diff --git a/pkg/callbackstruct/common.go b/pkg/callbackstruct/common.go index c58b9e415..d6714f5f2 100644 --- a/pkg/callbackstruct/common.go +++ b/pkg/callbackstruct/common.go @@ -14,7 +14,10 @@ package callbackstruct -import "github.com/OpenIMSDK/tools/errs" +import ( + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" + "github.com/openimsdk/tools/errs" +) const ( Next = 1 @@ -61,7 +64,7 @@ type CommonCallbackResp struct { } func (c CommonCallbackResp) Parse() error { - if c.ActionCode != errs.NoError || c.NextCode == Next { + if c.ActionCode == servererrs.NoError && c.NextCode == Next { return errs.NewCodeError(int(c.ErrCode), c.ErrMsg).WithDetail(c.ErrDlt) } return nil diff --git a/pkg/callbackstruct/constant.go b/pkg/callbackstruct/constant.go index f3bcf1383..66e1598cd 100644 --- a/pkg/callbackstruct/constant.go +++ b/pkg/callbackstruct/constant.go @@ -14,47 +14,44 @@ package callbackstruct -const CallbackBeforeInviteJoinGroupCommand = "callbackBeforeInviteJoinGroupCommand" -const CallbackAfterJoinGroupCommand = "callbackAfterJoinGroupCommand" -const CallbackAfterSetGroupInfoCommand = "callbackAfterSetGroupInfoCommand" -const CallbackBeforeSetGroupInfoCommand = "callbackBeforeSetGroupInfoCommand" - -const CallbackAfterRevokeMsgCommand = "callbackBeforeAfterMsgCommand" -const CallbackBeforeAddBlackCommand = "callbackBeforeAddBlackCommand" -const CallbackAfterAddFriendCommand = "callbackAfterAddFriendCommand" -const CallbackBeforeAddFriendAgreeCommand = "callbackBeforeAddFriendAgreeCommand" - -const CallbackAfterDeleteFriendCommand = "callbackAfterDeleteFriendCommand" -const CallbackBeforeImportFriendsCommand = "callbackBeforeImportFriendsCommand" -const CallbackAfterImportFriendsCommand = "callbackAfterImportFriendsCommand" -const CallbackAfterRemoveBlackCommand = "callbackAfterRemoveBlackCommand" - const ( - CallbackQuitGroupCommand = "callbackQuitGroupCommand" - CallbackKillGroupCommand = "callbackKillGroupCommand" - CallbackDisMissGroupCommand = "callbackDisMissGroupCommand" + CallbackBeforeInviteJoinGroupCommand = "callbackBeforeInviteJoinGroupCommand" + CallbackAfterJoinGroupCommand = "callbackAfterJoinGroupCommand" + CallbackAfterSetGroupInfoCommand = "callbackAfterSetGroupInfoCommand" + CallbackBeforeSetGroupInfoCommand = "callbackBeforeSetGroupInfoCommand" + CallbackAfterRevokeMsgCommand = "callbackBeforeAfterMsgCommand" + CallbackBeforeAddBlackCommand = "callbackBeforeAddBlackCommand" + CallbackAfterAddFriendCommand = "callbackAfterAddFriendCommand" + CallbackBeforeAddFriendAgreeCommand = "callbackBeforeAddFriendAgreeCommand" + CallbackAfterDeleteFriendCommand = "callbackAfterDeleteFriendCommand" + CallbackBeforeImportFriendsCommand = "callbackBeforeImportFriendsCommand" + CallbackAfterImportFriendsCommand = "callbackAfterImportFriendsCommand" + CallbackAfterRemoveBlackCommand = "callbackAfterRemoveBlackCommand" + CallbackAfterQuitGroupCommand = "callbackAfterQuitGroupCommand" + CallbackAfterKickGroupCommand = "callbackAfterKickGroupCommand" + CallbackAfterDisMissGroupCommand = "callbackAfterDisMissGroupCommand" CallbackBeforeJoinGroupCommand = "callbackBeforeJoinGroupCommand" - CallbackGroupMsgReadCommand = "callbackGroupMsgReadCommand" - CallbackMsgModifyCommand = "callbackMsgModifyCommand" + CallbackAfterGroupMsgReadCommand = "callbackAfterGroupMsgReadCommand" + CallbackBeforeMsgModifyCommand = "callbackBeforeMsgModifyCommand" CallbackAfterUpdateUserInfoCommand = "callbackAfterUpdateUserInfoCommand" CallbackAfterUpdateUserInfoExCommand = "callbackAfterUpdateUserInfoExCommand" CallbackBeforeUpdateUserInfoExCommand = "callbackBeforeUpdateUserInfoExCommand" CallbackBeforeUserRegisterCommand = "callbackBeforeUserRegisterCommand" CallbackAfterUserRegisterCommand = "callbackAfterUserRegisterCommand" - CallbackAfterTransferGroupOwner = "callbackAfterTransferGroupOwner" - CallbackBeforeSetFriendRemark = "callbackBeforeSetFriendRemark" - CallbackAfterSetFriendRemark = "callbackAfterSetFriendRemark" - CallbackSingleMsgRead = "callbackSingleMsgRead" + CallbackAfterTransferGroupOwnerCommand = "callbackAfterTransferGroupOwnerCommand" + CallbackBeforeSetFriendRemarkCommand = "callbackBeforeSetFriendRemarkCommand" + CallbackAfterSetFriendRemarkCommand = "callbackAfterSetFriendRemarkCommand" + CallbackAfterSingleMsgReadCommand = "callbackAfterSingleMsgReadCommand" CallbackBeforeSendSingleMsgCommand = "callbackBeforeSendSingleMsgCommand" CallbackAfterSendSingleMsgCommand = "callbackAfterSendSingleMsgCommand" CallbackBeforeSendGroupMsgCommand = "callbackBeforeSendGroupMsgCommand" CallbackAfterSendGroupMsgCommand = "callbackAfterSendGroupMsgCommand" - CallbackUserOnlineCommand = "callbackUserOnlineCommand" - CallbackUserOfflineCommand = "callbackUserOfflineCommand" - CallbackUserKickOffCommand = "callbackUserKickOffCommand" - CallbackOfflinePushCommand = "callbackOfflinePushCommand" - CallbackOnlinePushCommand = "callbackOnlinePushCommand" - CallbackSuperGroupOnlinePushCommand = "callbackSuperGroupOnlinePushCommand" + CallbackAfterUserOnlineCommand = "callbackAfterUserOnlineCommand" + CallbackAfterUserOfflineCommand = "callbackAfterUserOfflineCommand" + CallbackAfterUserKickOffCommand = "callbackAfterUserKickOffCommand" + CallbackBeforeOfflinePushCommand = "callbackBeforeOfflinePushCommand" + CallbackBeforeOnlinePushCommand = "callbackBeforeOnlinePushCommand" + CallbackBeforeGroupOnlinePushCommand = "callbackBeforeGroupOnlinePushCommand" CallbackBeforeAddFriendCommand = "callbackBeforeAddFriendCommand" CallbackBeforeUpdateUserInfoCommand = "callbackBeforeUpdateUserInfoCommand" CallbackBeforeCreateGroupCommand = "callbackBeforeCreateGroupCommand" diff --git a/pkg/callbackstruct/doc.go b/pkg/callbackstruct/doc.go index c3445c60e..965186c6c 100644 --- a/pkg/callbackstruct/doc.go +++ b/pkg/callbackstruct/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/callbackstruct/group.go b/pkg/callbackstruct/group.go index 467061a4a..e78d45ab4 100644 --- a/pkg/callbackstruct/group.go +++ b/pkg/callbackstruct/group.go @@ -15,8 +15,8 @@ package callbackstruct import ( - common "github.com/OpenIMSDK/protocol/sdkws" "github.com/openimsdk/open-im-server/v3/pkg/apistruct" + common "github.com/openimsdk/protocol/sdkws" ) type CallbackCommand string diff --git a/pkg/callbackstruct/message.go b/pkg/callbackstruct/message.go index 2864e28b1..902fa6110 100644 --- a/pkg/callbackstruct/message.go +++ b/pkg/callbackstruct/message.go @@ -15,7 +15,7 @@ package callbackstruct import ( - sdkws "github.com/OpenIMSDK/protocol/sdkws" + sdkws "github.com/openimsdk/protocol/sdkws" ) type CallbackBeforeSendSingleMsgReq struct { diff --git a/pkg/callbackstruct/push.go b/pkg/callbackstruct/push.go index 0566c35e2..0b0f29b51 100644 --- a/pkg/callbackstruct/push.go +++ b/pkg/callbackstruct/push.go @@ -14,7 +14,7 @@ package callbackstruct -import common "github.com/OpenIMSDK/protocol/sdkws" +import common "github.com/openimsdk/protocol/sdkws" type CallbackBeforePushReq struct { UserStatusBatchCallbackReq diff --git a/pkg/callbackstruct/revoke.go b/pkg/callbackstruct/revoke.go index 1f5e0b0c1..b36985ed3 100644 --- a/pkg/callbackstruct/revoke.go +++ b/pkg/callbackstruct/revoke.go @@ -20,6 +20,7 @@ type CallbackAfterRevokeMsgReq struct { Seq int64 `json:"seq"` UserID string `json:"userID"` } + type CallbackAfterRevokeMsgResp struct { CommonCallbackResp } diff --git a/pkg/callbackstruct/user.go b/pkg/callbackstruct/user.go index 98536882d..504c7ffb7 100644 --- a/pkg/callbackstruct/user.go +++ b/pkg/callbackstruct/user.go @@ -15,8 +15,8 @@ package callbackstruct import ( - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/protocol/wrapperspb" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/protocol/wrapperspb" ) type CallbackBeforeUpdateUserInfoReq struct { @@ -26,6 +26,7 @@ type CallbackBeforeUpdateUserInfoReq struct { FaceURL *string `json:"faceURL"` Ex *string `json:"ex"` } + type CallbackBeforeUpdateUserInfoResp struct { CommonCallbackResp Nickname *string `json:"nickName"` diff --git a/pkg/common/cachekey/doc.go b/pkg/common/cachekey/doc.go new file mode 100644 index 000000000..4975537ec --- /dev/null +++ b/pkg/common/cachekey/doc.go @@ -0,0 +1,15 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cachekey // import "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" diff --git a/pkg/common/cachekey/token.go b/pkg/common/cachekey/token.go new file mode 100644 index 000000000..94468dc31 --- /dev/null +++ b/pkg/common/cachekey/token.go @@ -0,0 +1,11 @@ +package cachekey + +import "github.com/openimsdk/protocol/constant" + +const ( + UidPidToken = "UID_PID_TOKEN_STATUS:" +) + +func GetTokenKey(userID string, platformID int) string { + return UidPidToken + userID + ":" + constant.PlatformIDToName(platformID) +} diff --git a/pkg/common/cmd/api.go b/pkg/common/cmd/api.go index bee16fdad..022fb1097 100644 --- a/pkg/common/cmd/api.go +++ b/pkg/common/cmd/api.go @@ -15,43 +15,40 @@ package cmd import ( - "github.com/OpenIMSDK/protocol/constant" + "context" "github.com/openimsdk/open-im-server/v3/internal/api" "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/system/program" "github.com/spf13/cobra" ) type ApiCmd struct { *RootCmd - initFunc func(config *config.GlobalConfig, port int, promPort int) error + ctx context.Context + configMap map[string]any + apiConfig *api.Config } func NewApiCmd() *ApiCmd { - ret := &ApiCmd{RootCmd: NewRootCmd("api"), initFunc: api.Start} - ret.SetRootCmdPt(ret) - ret.addPreRun() - ret.addRunE() - return ret -} - -func (a *ApiCmd) addPreRun() { - a.Command.PreRun = func(cmd *cobra.Command, args []string) { - a.port = a.getPortFlag(cmd) - a.prometheusPort = a.getPrometheusPortFlag(cmd) + var apiConfig api.Config + ret := &ApiCmd{apiConfig: &apiConfig} + ret.configMap = map[string]any{ + OpenIMAPICfgFileName: &apiConfig.RpcConfig, + ZookeeperConfigFileName: &apiConfig.ZookeeperConfig, + ShareFileName: &apiConfig.Share, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() } + return ret } -func (a *ApiCmd) addRunE() { - a.Command.RunE = func(cmd *cobra.Command, args []string) error { - return a.initFunc(a.config, a.port, a.prometheusPort) - } +func (a *ApiCmd) Exec() error { + return a.Execute() } -func (a *ApiCmd) GetPortFromConfig(portType string) int { - if portType == constant.FlagPort { - return a.config.Api.OpenImApiPort[0] - } else if portType == constant.FlagPrometheusPort { - return a.config.Prometheus.ApiPrometheusPort[0] - } - return 0 +func (a *ApiCmd) runE() error { + return api.Start(a.ctx, a.Index(), a.apiConfig) } diff --git a/pkg/common/cmd/auth.go b/pkg/common/cmd/auth.go new file mode 100644 index 000000000..5ed02ffd0 --- /dev/null +++ b/pkg/common/cmd/auth.go @@ -0,0 +1,59 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/rpc/auth" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" + "github.com/openimsdk/tools/system/program" + "github.com/spf13/cobra" +) + +type AuthRpcCmd struct { + *RootCmd + ctx context.Context + configMap map[string]any + authConfig *auth.Config +} + +func NewAuthRpcCmd() *AuthRpcCmd { + var authConfig auth.Config + ret := &AuthRpcCmd{authConfig: &authConfig} + ret.configMap = map[string]any{ + OpenIMRPCAuthCfgFileName: &authConfig.RpcConfig, + RedisConfigFileName: &authConfig.RedisConfig, + ZookeeperConfigFileName: &authConfig.ZookeeperConfig, + ShareFileName: &authConfig.Share, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + + return ret +} + +func (a *AuthRpcCmd) Exec() error { + return a.Execute() +} + +func (a *AuthRpcCmd) runE() error { + return startrpc.Start(a.ctx, &a.authConfig.ZookeeperConfig, &a.authConfig.RpcConfig.Prometheus, a.authConfig.RpcConfig.RPC.ListenIP, + a.authConfig.RpcConfig.RPC.RegisterIP, a.authConfig.RpcConfig.RPC.Ports, + a.Index(), a.authConfig.Share.RpcRegisterName.Auth, &a.authConfig.Share, a.authConfig, auth.Start) +} diff --git a/pkg/common/cmd/constant.go b/pkg/common/cmd/constant.go index c332ce3a6..55eb4a069 100644 --- a/pkg/common/cmd/constant.go +++ b/pkg/common/cmd/constant.go @@ -14,13 +14,83 @@ package cmd +import ( + "strings" +) + +var ( + FileName string + NotificationFileName string + ShareFileName string + WebhooksConfigFileName string + LocalCacheConfigFileName string + KafkaConfigFileName string + RedisConfigFileName string + ZookeeperConfigFileName string + MongodbConfigFileName string + MinioConfigFileName string + LogConfigFileName string + OpenIMAPICfgFileName string + OpenIMCronTaskCfgFileName string + OpenIMMsgGatewayCfgFileName string + OpenIMMsgTransferCfgFileName string + OpenIMPushCfgFileName string + OpenIMRPCAuthCfgFileName string + OpenIMRPCConversationCfgFileName string + OpenIMRPCFriendCfgFileName string + OpenIMRPCGroupCfgFileName string + OpenIMRPCMsgCfgFileName string + OpenIMRPCThirdCfgFileName string + OpenIMRPCUserCfgFileName string +) + +var ConfigEnvPrefixMap map[string]string + +func init() { + FileName = "config.yaml" + NotificationFileName = "notification.yml" + ShareFileName = "share.yml" + WebhooksConfigFileName = "webhooks.yml" + LocalCacheConfigFileName = "local-cache.yml" + KafkaConfigFileName = "kafka.yml" + RedisConfigFileName = "redis.yml" + ZookeeperConfigFileName = "zookeeper.yml" + MongodbConfigFileName = "mongodb.yml" + MinioConfigFileName = "minio.yml" + LogConfigFileName = "log.yml" + OpenIMAPICfgFileName = "openim-api.yml" + OpenIMCronTaskCfgFileName = "openim-crontask.yml" + OpenIMMsgGatewayCfgFileName = "openim-msggateway.yml" + OpenIMMsgTransferCfgFileName = "openim-msgtransfer.yml" + OpenIMPushCfgFileName = "openim-push.yml" + OpenIMRPCAuthCfgFileName = "openim-rpc-auth.yml" + OpenIMRPCConversationCfgFileName = "openim-rpc-conversation.yml" + OpenIMRPCFriendCfgFileName = "openim-rpc-friend.yml" + OpenIMRPCGroupCfgFileName = "openim-rpc-group.yml" + OpenIMRPCMsgCfgFileName = "openim-rpc-msg.yml" + OpenIMRPCThirdCfgFileName = "openim-rpc-third.yml" + OpenIMRPCUserCfgFileName = "openim-rpc-user.yml" + + ConfigEnvPrefixMap = make(map[string]string) + fileNames := []string{ + FileName, NotificationFileName, ShareFileName, WebhooksConfigFileName, + KafkaConfigFileName, RedisConfigFileName, ZookeeperConfigFileName, + MongodbConfigFileName, MinioConfigFileName, LogConfigFileName, + OpenIMAPICfgFileName, OpenIMCronTaskCfgFileName, OpenIMMsgGatewayCfgFileName, + OpenIMMsgTransferCfgFileName, OpenIMPushCfgFileName, OpenIMRPCAuthCfgFileName, + OpenIMRPCConversationCfgFileName, OpenIMRPCFriendCfgFileName, OpenIMRPCGroupCfgFileName, + OpenIMRPCMsgCfgFileName, OpenIMRPCThirdCfgFileName, OpenIMRPCUserCfgFileName, + } + + for _, fileName := range fileNames { + envKey := strings.TrimSuffix(strings.TrimSuffix(fileName, ".yml"), ".yaml") + envKey = "IMENV_" + envKey + envKey = strings.ToUpper(strings.ReplaceAll(envKey, "-", "_")) + ConfigEnvPrefixMap[fileName] = envKey + } +} + const ( - RpcPushServer = "push" - RpcAuthServer = "auth" - RpcConversationServer = "conversation" - RpcFriendServer = "friend" - RpcGroupServer = "group" - RpcMsgServer = "msg" - RpcThirdServer = "third" - RpcUserServer = "user" + FlagConf = "config_folder_path" + FlagTransferIndex = "index" ) diff --git a/pkg/common/cmd/conversation.go b/pkg/common/cmd/conversation.go new file mode 100644 index 000000000..0a617c729 --- /dev/null +++ b/pkg/common/cmd/conversation.go @@ -0,0 +1,61 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/rpc/conversation" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" + "github.com/openimsdk/tools/system/program" + "github.com/spf13/cobra" +) + +type ConversationRpcCmd struct { + *RootCmd + ctx context.Context + configMap map[string]any + conversationConfig *conversation.Config +} + +func NewConversationRpcCmd() *ConversationRpcCmd { + var conversationConfig conversation.Config + ret := &ConversationRpcCmd{conversationConfig: &conversationConfig} + ret.configMap = map[string]any{ + OpenIMRPCConversationCfgFileName: &conversationConfig.RpcConfig, + RedisConfigFileName: &conversationConfig.RedisConfig, + ZookeeperConfigFileName: &conversationConfig.ZookeeperConfig, + MongodbConfigFileName: &conversationConfig.MongodbConfig, + ShareFileName: &conversationConfig.Share, + NotificationFileName: &conversationConfig.NotificationConfig, + LocalCacheConfigFileName: &conversationConfig.LocalCacheConfig, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + return ret +} + +func (a *ConversationRpcCmd) Exec() error { + return a.Execute() +} + +func (a *ConversationRpcCmd) runE() error { + return startrpc.Start(a.ctx, &a.conversationConfig.ZookeeperConfig, &a.conversationConfig.RpcConfig.Prometheus, a.conversationConfig.RpcConfig.RPC.ListenIP, + a.conversationConfig.RpcConfig.RPC.RegisterIP, a.conversationConfig.RpcConfig.RPC.Ports, + a.Index(), a.conversationConfig.Share.RpcRegisterName.Conversation, &a.conversationConfig.Share, a.conversationConfig, conversation.Start) +} diff --git a/pkg/common/cmd/cron_task.go b/pkg/common/cmd/cron_task.go index d8c9dd2a8..0e94cf52c 100644 --- a/pkg/common/cmd/cron_task.go +++ b/pkg/common/cmd/cron_task.go @@ -15,34 +15,43 @@ package cmd import ( + "context" "github.com/openimsdk/open-im-server/v3/internal/tools" "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/system/program" "github.com/spf13/cobra" ) type CronTaskCmd struct { *RootCmd - initFunc func(config *config.GlobalConfig) error + ctx context.Context + configMap map[string]any + cronTaskConfig *tools.CronTaskConfig } func NewCronTaskCmd() *CronTaskCmd { - ret := &CronTaskCmd{RootCmd: NewRootCmd("cronTask", WithCronTaskLogName()), - initFunc: tools.StartTask} - ret.addRunE() - ret.SetRootCmdPt(ret) - return ret -} - -func (c *CronTaskCmd) addRunE() { - c.Command.RunE = func(cmd *cobra.Command, args []string) error { - return c.initFunc(c.config) + var cronTaskConfig tools.CronTaskConfig + ret := &CronTaskCmd{cronTaskConfig: &cronTaskConfig} + ret.configMap = map[string]any{ + OpenIMCronTaskCfgFileName: &cronTaskConfig.CronTask, + RedisConfigFileName: &cronTaskConfig.RedisConfig, + MongodbConfigFileName: &cronTaskConfig.MongodbConfig, + ZookeeperConfigFileName: &cronTaskConfig.ZookeeperConfig, + ShareFileName: &cronTaskConfig.Share, + KafkaConfigFileName: &cronTaskConfig.KafkaConfig, } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + return ret } -func (c *CronTaskCmd) Exec() error { - return c.Execute() +func (a *CronTaskCmd) Exec() error { + return a.Execute() } -func (c *CronTaskCmd) GetPortFromConfig(portType string) int { - return 0 +func (a *CronTaskCmd) runE() error { + return tools.Start(a.ctx, a.cronTaskConfig) } diff --git a/pkg/common/cmd/doc.go b/pkg/common/cmd/doc.go index 991a85eb9..96d80b64a 100644 --- a/pkg/common/cmd/doc.go +++ b/pkg/common/cmd/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/common/cmd/friend.go b/pkg/common/cmd/friend.go new file mode 100644 index 000000000..b8d46f77e --- /dev/null +++ b/pkg/common/cmd/friend.go @@ -0,0 +1,62 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/rpc/friend" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" + "github.com/openimsdk/tools/system/program" + "github.com/spf13/cobra" +) + +type FriendRpcCmd struct { + *RootCmd + ctx context.Context + configMap map[string]any + friendConfig *friend.Config +} + +func NewFriendRpcCmd() *FriendRpcCmd { + var friendConfig friend.Config + ret := &FriendRpcCmd{friendConfig: &friendConfig} + ret.configMap = map[string]any{ + OpenIMRPCFriendCfgFileName: &friendConfig.RpcConfig, + RedisConfigFileName: &friendConfig.RedisConfig, + ZookeeperConfigFileName: &friendConfig.ZookeeperConfig, + MongodbConfigFileName: &friendConfig.MongodbConfig, + ShareFileName: &friendConfig.Share, + NotificationFileName: &friendConfig.NotificationConfig, + WebhooksConfigFileName: &friendConfig.WebhooksConfig, + LocalCacheConfigFileName: &friendConfig.LocalCacheConfig, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + return ret +} + +func (a *FriendRpcCmd) Exec() error { + return a.Execute() +} + +func (a *FriendRpcCmd) runE() error { + return startrpc.Start(a.ctx, &a.friendConfig.ZookeeperConfig, &a.friendConfig.RpcConfig.Prometheus, a.friendConfig.RpcConfig.RPC.ListenIP, + a.friendConfig.RpcConfig.RPC.RegisterIP, a.friendConfig.RpcConfig.RPC.Ports, + a.Index(), a.friendConfig.Share.RpcRegisterName.Friend, &a.friendConfig.Share, a.friendConfig, friend.Start) +} diff --git a/pkg/common/cmd/group.go b/pkg/common/cmd/group.go new file mode 100644 index 000000000..8bf977824 --- /dev/null +++ b/pkg/common/cmd/group.go @@ -0,0 +1,62 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/rpc/group" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" + "github.com/openimsdk/tools/system/program" + "github.com/spf13/cobra" +) + +type GroupRpcCmd struct { + *RootCmd + ctx context.Context + configMap map[string]any + groupConfig *group.Config +} + +func NewGroupRpcCmd() *GroupRpcCmd { + var groupConfig group.Config + ret := &GroupRpcCmd{groupConfig: &groupConfig} + ret.configMap = map[string]any{ + OpenIMRPCGroupCfgFileName: &groupConfig.RpcConfig, + RedisConfigFileName: &groupConfig.RedisConfig, + ZookeeperConfigFileName: &groupConfig.ZookeeperConfig, + MongodbConfigFileName: &groupConfig.MongodbConfig, + ShareFileName: &groupConfig.Share, + NotificationFileName: &groupConfig.NotificationConfig, + WebhooksConfigFileName: &groupConfig.WebhooksConfig, + LocalCacheConfigFileName: &groupConfig.LocalCacheConfig, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + return ret +} + +func (a *GroupRpcCmd) Exec() error { + return a.Execute() +} + +func (a *GroupRpcCmd) runE() error { + return startrpc.Start(a.ctx, &a.groupConfig.ZookeeperConfig, &a.groupConfig.RpcConfig.Prometheus, a.groupConfig.RpcConfig.RPC.ListenIP, + a.groupConfig.RpcConfig.RPC.RegisterIP, a.groupConfig.RpcConfig.RPC.Ports, + a.Index(), a.groupConfig.Share.RpcRegisterName.Group, &a.groupConfig.Share, a.groupConfig, group.Start) +} diff --git a/pkg/common/cmd/msg.go b/pkg/common/cmd/msg.go new file mode 100644 index 000000000..a3b521b4b --- /dev/null +++ b/pkg/common/cmd/msg.go @@ -0,0 +1,63 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/rpc/msg" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" + "github.com/openimsdk/tools/system/program" + "github.com/spf13/cobra" +) + +type MsgRpcCmd struct { + *RootCmd + ctx context.Context + configMap map[string]any + msgConfig *msg.Config +} + +func NewMsgRpcCmd() *MsgRpcCmd { + var msgConfig msg.Config + ret := &MsgRpcCmd{msgConfig: &msgConfig} + ret.configMap = map[string]any{ + OpenIMRPCMsgCfgFileName: &msgConfig.RpcConfig, + RedisConfigFileName: &msgConfig.RedisConfig, + ZookeeperConfigFileName: &msgConfig.ZookeeperConfig, + MongodbConfigFileName: &msgConfig.MongodbConfig, + KafkaConfigFileName: &msgConfig.KafkaConfig, + ShareFileName: &msgConfig.Share, + NotificationFileName: &msgConfig.NotificationConfig, + WebhooksConfigFileName: &msgConfig.WebhooksConfig, + LocalCacheConfigFileName: &msgConfig.LocalCacheConfig, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + return ret +} + +func (a *MsgRpcCmd) Exec() error { + return a.Execute() +} + +func (a *MsgRpcCmd) runE() error { + return startrpc.Start(a.ctx, &a.msgConfig.ZookeeperConfig, &a.msgConfig.RpcConfig.Prometheus, a.msgConfig.RpcConfig.RPC.ListenIP, + a.msgConfig.RpcConfig.RPC.RegisterIP, a.msgConfig.RpcConfig.RPC.Ports, + a.Index(), a.msgConfig.Share.RpcRegisterName.Msg, &a.msgConfig.Share, a.msgConfig, msg.Start) +} diff --git a/pkg/common/cmd/msg_gateway.go b/pkg/common/cmd/msg_gateway.go index 37aedd933..897fd7008 100644 --- a/pkg/common/cmd/msg_gateway.go +++ b/pkg/common/cmd/msg_gateway.go @@ -15,61 +15,43 @@ package cmd import ( - "log" + "context" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/OpenIMSDK/protocol/constant" "github.com/openimsdk/open-im-server/v3/internal/msggateway" + + "github.com/openimsdk/tools/system/program" "github.com/spf13/cobra" ) type MsgGatewayCmd struct { *RootCmd + ctx context.Context + configMap map[string]any + msgGatewayConfig *msggateway.Config } func NewMsgGatewayCmd() *MsgGatewayCmd { - ret := &MsgGatewayCmd{NewRootCmd("msgGateway")} - ret.addRunE() - ret.SetRootCmdPt(ret) - return ret -} - -func (m *MsgGatewayCmd) AddWsPortFlag() { - m.Command.Flags().IntP(constant.FlagWsPort, "w", 0, "ws server listen port") -} - -func (m *MsgGatewayCmd) getWsPortFlag(cmd *cobra.Command) int { - port, err := cmd.Flags().GetInt(constant.FlagWsPort) - if err != nil { - log.Println("Error getting ws port flag:", err) - } - if port == 0 { - port = m.PortFromConfig(constant.FlagWsPort) + var msgGatewayConfig msggateway.Config + ret := &MsgGatewayCmd{msgGatewayConfig: &msgGatewayConfig} + ret.configMap = map[string]any{ + OpenIMMsgGatewayCfgFileName: &msgGatewayConfig.MsgGateway, + ZookeeperConfigFileName: &msgGatewayConfig.ZookeeperConfig, + ShareFileName: &msgGatewayConfig.Share, + WebhooksConfigFileName: &msgGatewayConfig.WebhooksConfig, } - return port -} - -func (m *MsgGatewayCmd) addRunE() { - m.Command.RunE = func(cmd *cobra.Command, args []string) error { - return msggateway.RunWsAndServer(m.config, m.getPortFlag(cmd), m.getWsPortFlag(cmd), m.getPrometheusPortFlag(cmd)) + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() } + return ret } func (m *MsgGatewayCmd) Exec() error { return m.Execute() } -func (m *MsgGatewayCmd) GetPortFromConfig(portType string) int { - switch portType { - case constant.FlagWsPort: - return m.config.LongConnSvr.OpenImWsPort[0] - - case constant.FlagPort: - return m.config.LongConnSvr.OpenImMessageGatewayPort[0] - - case constant.FlagPrometheusPort: - return m.config.Prometheus.MessageGatewayPrometheusPort[0] - - default: - return 0 - } +func (m *MsgGatewayCmd) runE() error { + return msggateway.Start(m.ctx, m.Index(), m.msgGatewayConfig) } diff --git a/pkg/common/cmd/msg_gateway_test.go b/pkg/common/cmd/msg_gateway_test.go index c0ea2b057..d820627b5 100644 --- a/pkg/common/cmd/msg_gateway_test.go +++ b/pkg/common/cmd/msg_gateway_test.go @@ -15,11 +15,12 @@ package cmd import ( - "testing" - - "github.com/OpenIMSDK/protocol/constant" + "github.com/openimsdk/protocol/auth" + "github.com/openimsdk/tools/apiresp" + "github.com/openimsdk/tools/utils/jsonutil" "github.com/stretchr/testify/mock" - "gotest.tools/assert" + "math" + "testing" ) // MockRootCmd is a mock type for the RootCmd type @@ -32,20 +33,29 @@ func (m *MockRootCmd) Execute() error { return args.Error(0) } -func TestMsgGatewayCmd_GetPortFromConfig(t *testing.T) { - msgGatewayCmd := &MsgGatewayCmd{RootCmd: &RootCmd{}} - tests := []struct { - portType string - want int - }{ - {constant.FlagWsPort, 8080}, // Replace 8080 with the expected port from the config - {constant.FlagPort, 8081}, // Replace 8081 with the expected port from the config - {"invalid", 0}, +func TestName(t *testing.T) { + resp := &apiresp.ApiResponse{ + ErrCode: 1234, + ErrMsg: "test", + ErrDlt: "4567", + Data: &auth.UserTokenResp{ + Token: "1234567", + ExpireTimeSeconds: math.MaxInt64, + }, + } + data, err := resp.MarshalJSON() + if err != nil { + panic(err) } - for _, tt := range tests { - t.Run(tt.portType, func(t *testing.T) { - got := msgGatewayCmd.GetPortFromConfig(tt.portType) - assert.Equal(t, tt.want, got) - }) + t.Log(string(data)) + + var rReso apiresp.ApiResponse + rReso.Data = &auth.UserTokenResp{} + + if err := jsonutil.JsonUnmarshal(data, &rReso); err != nil { + panic(err) } + + t.Logf("%+v\n", rReso) + } diff --git a/pkg/common/cmd/msg_transfer.go b/pkg/common/cmd/msg_transfer.go index d98154f3a..86f42dc56 100644 --- a/pkg/common/cmd/msg_transfer.go +++ b/pkg/common/cmd/msg_transfer.go @@ -15,53 +15,44 @@ package cmd import ( - "fmt" - - "github.com/OpenIMSDK/protocol/constant" + "context" "github.com/openimsdk/open-im-server/v3/internal/msgtransfer" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/system/program" "github.com/spf13/cobra" ) type MsgTransferCmd struct { *RootCmd + ctx context.Context + configMap map[string]any + msgTransferConfig *msgtransfer.Config } func NewMsgTransferCmd() *MsgTransferCmd { - ret := &MsgTransferCmd{NewRootCmd("msgTransfer")} - ret.addRunE() - ret.SetRootCmdPt(ret) - return ret -} - -func (m *MsgTransferCmd) addRunE() { - m.Command.RunE = func(cmd *cobra.Command, args []string) error { - return msgtransfer.StartTransfer(m.config, m.getPrometheusPortFlag(cmd)) + var msgTransferConfig msgtransfer.Config + ret := &MsgTransferCmd{msgTransferConfig: &msgTransferConfig} + ret.configMap = map[string]any{ + OpenIMMsgTransferCfgFileName: &msgTransferConfig.MsgTransfer, + RedisConfigFileName: &msgTransferConfig.RedisConfig, + MongodbConfigFileName: &msgTransferConfig.MongodbConfig, + KafkaConfigFileName: &msgTransferConfig.KafkaConfig, + ZookeeperConfigFileName: &msgTransferConfig.ZookeeperConfig, + ShareFileName: &msgTransferConfig.Share, + WebhooksConfigFileName: &msgTransferConfig.WebhooksConfig, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() } + return ret } func (m *MsgTransferCmd) Exec() error { return m.Execute() } -func (m *MsgTransferCmd) GetPortFromConfig(portType string) int { - if portType == constant.FlagPort { - return 0 - } else if portType == constant.FlagPrometheusPort { - n := m.getTransferProgressFlagValue() - return m.config.Prometheus.MessageTransferPrometheusPort[n] - } - return 0 -} - -func (m *MsgTransferCmd) AddTransferProgressFlag() { - m.Command.Flags().IntP(constant.FlagTransferProgressIndex, "n", 0, "transfer progress index") -} - -func (m *MsgTransferCmd) getTransferProgressFlagValue() int { - nIndex, err := m.Command.Flags().GetInt(constant.FlagTransferProgressIndex) - if err != nil { - fmt.Println("get transfer cmd error,make sure it is k8s env or not") - return 0 - } - return nIndex +func (m *MsgTransferCmd) runE() error { + return msgtransfer.Start(m.ctx, m.Index(), m.msgTransferConfig) } diff --git a/pkg/common/cmd/msg_utils.go b/pkg/common/cmd/msg_utils.go index df15acd87..fc5eaceae 100644 --- a/pkg/common/cmd/msg_utils.go +++ b/pkg/common/cmd/msg_utils.go @@ -15,8 +15,10 @@ package cmd import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/tools" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/tools/system/program" "github.com/spf13/cobra" ) @@ -28,6 +30,14 @@ type MsgUtilsCmd struct { func (m *MsgUtilsCmd) AddUserIDFlag() { m.Command.PersistentFlags().StringP("userID", "u", "", "openIM userID") } +func (m *MsgUtilsCmd) AddIndexFlag() { + m.Command.PersistentFlags().IntP(FlagTransferIndex, "i", 0, "process startup sequence number") +} + +func (m *MsgUtilsCmd) AddConfigDirFlag() { + m.Command.PersistentFlags().StringP(FlagConf, "c", "", "path of config directory") + +} func (m *MsgUtilsCmd) getUserIDFlag(cmdLines *cobra.Command) string { userID, _ := cmdLines.Flags().GetString("userID") @@ -44,7 +54,7 @@ func (m *MsgUtilsCmd) AddFixAllFlag() { } */ func (m *MsgUtilsCmd) AddClearAllFlag() { - m.Command.PersistentFlags().BoolP("clearAll", "c", false, "openIM clear all seqs") + m.Command.PersistentFlags().BoolP("clearAll", "", false, "openIM clear all seqs") } /* func (m *MsgUtilsCmd) getClearAllFlag(cmdLines *cobra.Command) bool { @@ -136,9 +146,9 @@ func NewSeqCmd() *SeqCmd { func (s *SeqCmd) GetSeqCmd() *cobra.Command { s.Command.Run = func(cmdLines *cobra.Command, args []string) { - _, err := tools.InitMsgTool(s.MsgTool.Config) + _, err := tools.InitMsgTool(context.Background(), nil) if err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } userID := s.getUserIDFlag(cmdLines) superGroupID := s.getSuperGroupIDFlag(cmdLines) diff --git a/pkg/common/cmd/push.go b/pkg/common/cmd/push.go new file mode 100644 index 000000000..0140ced23 --- /dev/null +++ b/pkg/common/cmd/push.go @@ -0,0 +1,63 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/push" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" + "github.com/openimsdk/tools/system/program" + "github.com/spf13/cobra" +) + +type PushRpcCmd struct { + *RootCmd + ctx context.Context + configMap map[string]any + pushConfig *push.Config +} + +func NewPushRpcCmd() *PushRpcCmd { + var pushConfig push.Config + ret := &PushRpcCmd{pushConfig: &pushConfig} + ret.configMap = map[string]any{ + OpenIMPushCfgFileName: &pushConfig.RpcConfig, + RedisConfigFileName: &pushConfig.RedisConfig, + ZookeeperConfigFileName: &pushConfig.ZookeeperConfig, + MongodbConfigFileName: &pushConfig.MongodbConfig, + KafkaConfigFileName: &pushConfig.KafkaConfig, + ShareFileName: &pushConfig.Share, + NotificationFileName: &pushConfig.NotificationConfig, + WebhooksConfigFileName: &pushConfig.WebhooksConfig, + LocalCacheConfigFileName: &pushConfig.LocalCacheConfig, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + return ret +} + +func (a *PushRpcCmd) Exec() error { + return a.Execute() +} + +func (a *PushRpcCmd) runE() error { + return startrpc.Start(a.ctx, &a.pushConfig.ZookeeperConfig, &a.pushConfig.RpcConfig.Prometheus, a.pushConfig.RpcConfig.RPC.ListenIP, + a.pushConfig.RpcConfig.RPC.RegisterIP, a.pushConfig.RpcConfig.RPC.Ports, + a.Index(), a.pushConfig.Share.RpcRegisterName.Push, &a.pushConfig.Share, a.pushConfig, push.Start) +} diff --git a/pkg/common/cmd/root.go b/pkg/common/cmd/root.go index 478942a5b..900281367 100644 --- a/pkg/common/cmd/root.go +++ b/pkg/common/cmd/root.go @@ -16,34 +16,34 @@ package cmd import ( "fmt" + "path/filepath" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" "github.com/openimsdk/open-im-server/v3/pkg/common/config" - config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" "github.com/spf13/cobra" ) -type RootCmdPt interface { - GetPortFromConfig(portType string) int -} - type RootCmd struct { Command cobra.Command - Name string + processName string port int prometheusPort int - cmdItf RootCmdPt - config *config.GlobalConfig + log config.Log + index int +} + +func (r *RootCmd) Index() int { + return r.index } -func (rc *RootCmd) Port() int { - return rc.port +func (r *RootCmd) Port() int { + return r.port } type CmdOpts struct { loggerPrefixName string + configMap map[string]any } func WithCronTaskLogName() func(*CmdOpts) { @@ -57,41 +57,63 @@ func WithLogName(logName string) func(*CmdOpts) { opts.loggerPrefixName = logName } } +func WithConfigMap(configMap map[string]any) func(*CmdOpts) { + return func(opts *CmdOpts) { + opts.configMap = configMap + } +} -func NewRootCmd(name string, opts ...func(*CmdOpts)) *RootCmd { - rootCmd := &RootCmd{Name: name, config: config.NewGlobalConfig()} +func NewRootCmd(processName string, opts ...func(*CmdOpts)) *RootCmd { + rootCmd := &RootCmd{processName: processName} cmd := cobra.Command{ - Use: "Start openIM application", - Short: fmt.Sprintf(`Start %s `, name), - Long: fmt.Sprintf(`Start %s `, name), + Use: "Start openIM application", + Long: fmt.Sprintf(`Start %s `, processName), PersistentPreRunE: func(cmd *cobra.Command, args []string) error { return rootCmd.persistentPreRun(cmd, opts...) }, + SilenceUsage: true, + SilenceErrors: false, } + cmd.Flags().StringP(FlagConf, "c", "", "path of config directory") + cmd.Flags().IntP(FlagTransferIndex, "i", 0, "process startup sequence number") + rootCmd.Command = cmd - rootCmd.addConfFlag() return rootCmd } -func (rc *RootCmd) persistentPreRun(cmd *cobra.Command, opts ...func(*CmdOpts)) error { - if err := rc.initializeConfiguration(cmd); err != nil { - return fmt.Errorf("failed to get configuration from command: %w", err) +func (r *RootCmd) persistentPreRun(cmd *cobra.Command, opts ...func(*CmdOpts)) error { + cmdOpts := r.applyOptions(opts...) + if err := r.initializeConfiguration(cmd, cmdOpts); err != nil { + return err } - cmdOpts := rc.applyOptions(opts...) - - if err := rc.initializeLogger(cmdOpts); err != nil { - return errs.Wrap(err, "failed to initialize logger") + if err := r.initializeLogger(cmdOpts); err != nil { + return errs.WrapMsg(err, "failed to initialize logger") } return nil } -func (rc *RootCmd) initializeConfiguration(cmd *cobra.Command) error { - return rc.getConfFromCmdAndInit(cmd) +func (r *RootCmd) initializeConfiguration(cmd *cobra.Command, opts *CmdOpts) error { + configDirectory, _, err := r.getFlag(cmd) + if err != nil { + return err + } + // Load common configuration file + //opts.configMap[ShareFileName] = StructEnvPrefix{EnvPrefix: shareEnvPrefix, ConfigStruct: &r.share} + for configFileName, configStruct := range opts.configMap { + err := config.LoadConfig(filepath.Join(configDirectory, configFileName), + ConfigEnvPrefixMap[configFileName], configStruct) + if err != nil { + return err + } + } + // Load common log configuration file + return config.LoadConfig(filepath.Join(configDirectory, LogConfigFileName), + ConfigEnvPrefixMap[LogConfigFileName], &r.log) } -func (rc *RootCmd) applyOptions(opts ...func(*CmdOpts)) *CmdOpts { +func (r *RootCmd) applyOptions(opts ...func(*CmdOpts)) *CmdOpts { cmdOpts := defaultCmdOpts() for _, opt := range opts { opt(cmdOpts) @@ -100,92 +122,45 @@ func (rc *RootCmd) applyOptions(opts ...func(*CmdOpts)) *CmdOpts { return cmdOpts } -func (rc *RootCmd) initializeLogger(cmdOpts *CmdOpts) error { - logConfig := rc.config.Log - - return log.InitFromConfig( +func (r *RootCmd) initializeLogger(cmdOpts *CmdOpts) error { + err := log.InitFromConfig( cmdOpts.loggerPrefixName, - rc.Name, - logConfig.RemainLogLevel, - logConfig.IsStdout, - logConfig.IsJson, - logConfig.StorageLocation, - logConfig.RemainRotationCount, - logConfig.RotationTime, + r.processName, + r.log.RemainLogLevel, + r.log.IsStdout, + r.log.IsJson, + r.log.StorageLocation, + r.log.RemainRotationCount, + r.log.RotationTime, + config.Version, ) + if err != nil { + return errs.Wrap(err) + } + return errs.Wrap(log.InitConsoleLogger(r.processName, r.log.RemainLogLevel, r.log.IsJson, config.Version)) + } func defaultCmdOpts() *CmdOpts { return &CmdOpts{ - loggerPrefixName: "openim-all", + loggerPrefixName: "openim-service-log", } } -func (r *RootCmd) SetRootCmdPt(cmdItf RootCmdPt) { - r.cmdItf = cmdItf -} - -func (r *RootCmd) addConfFlag() { - r.Command.Flags().StringP(constant.FlagConf, "c", "", "path to config file folder") -} - -func (r *RootCmd) AddPortFlag() { - r.Command.Flags().IntP(constant.FlagPort, "p", 0, "server listen port") -} - -func (r *RootCmd) getPortFlag(cmd *cobra.Command) int { - port, err := cmd.Flags().GetInt(constant.FlagPort) +func (r *RootCmd) getFlag(cmd *cobra.Command) (string, int, error) { + configDirectory, err := cmd.Flags().GetString(FlagConf) if err != nil { - // Wrapping the error with additional context - return 0 + return "", 0, errs.Wrap(err) } - if port == 0 { - port = r.PortFromConfig(constant.FlagPort) - } - return port -} - -// // GetPortFlag returns the port flag. -func (r *RootCmd) GetPortFlag() int { - return r.port -} - -func (r *RootCmd) AddPrometheusPortFlag() { - r.Command.Flags().IntP(constant.FlagPrometheusPort, "", 0, "server prometheus listen port") -} - -func (r *RootCmd) getPrometheusPortFlag(cmd *cobra.Command) int { - port, err := cmd.Flags().GetInt(constant.FlagPrometheusPort) - if err != nil || port == 0 { - port = r.PortFromConfig(constant.FlagPrometheusPort) - if err != nil { - return 0 - } + index, err := cmd.Flags().GetInt(FlagTransferIndex) + if err != nil { + return "", 0, errs.Wrap(err) } - return port -} - -func (r *RootCmd) GetPrometheusPortFlag() int { - return r.prometheusPort -} - -func (r *RootCmd) getConfFromCmdAndInit(cmdLines *cobra.Command) error { - configFolderPath, _ := cmdLines.Flags().GetString(constant.FlagConf) - fmt.Println("The directory of the configuration file to start the process:", configFolderPath) - return config2.InitConfig(r.config, configFolderPath) + r.index = index + return configDirectory, index, nil } func (r *RootCmd) Execute() error { return r.Command.Execute() } - -func (r *RootCmd) AddCommand(cmds ...*cobra.Command) { - r.Command.AddCommand(cmds...) -} - -func (r *RootCmd) PortFromConfig(portType string) int { - // Retrieve the port and cache it - port := r.cmdItf.GetPortFromConfig(portType) - return port -} diff --git a/pkg/common/cmd/rpc.go b/pkg/common/cmd/rpc.go deleted file mode 100644 index 9c6dbddd6..000000000 --- a/pkg/common/cmd/rpc.go +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cmd - -import ( - "errors" - - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" - "github.com/spf13/cobra" - "google.golang.org/grpc" -) - -type rpcInitFuc func(config *config2.GlobalConfig, disCov discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error - -type RpcCmd struct { - *RootCmd - RpcRegisterName string - initFunc rpcInitFuc -} - -func NewRpcCmd(name string, initFunc rpcInitFuc) *RpcCmd { - ret := &RpcCmd{RootCmd: NewRootCmd(name), initFunc: initFunc} - ret.addPreRun() - ret.addRunE() - ret.SetRootCmdPt(ret) - return ret -} - -func (a *RpcCmd) addPreRun() { - a.Command.PreRun = func(cmd *cobra.Command, args []string) { - a.port = a.getPortFlag(cmd) - a.prometheusPort = a.getPrometheusPortFlag(cmd) - } -} - -func (a *RpcCmd) addRunE() { - a.Command.RunE = func(cmd *cobra.Command, args []string) error { - rpcRegisterName, err := a.GetRpcRegisterNameFromConfig() - if err != nil { - return err - } else { - return a.StartSvr(rpcRegisterName, a.initFunc) - } - } -} - -func (a *RpcCmd) Exec() error { - return a.Execute() -} - -func (a *RpcCmd) StartSvr(name string, rpcFn func(config *config2.GlobalConfig, disCov discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error) error { - if a.GetPortFlag() == 0 { - return errs.Wrap(errors.New("port is required")) - } - return startrpc.Start(a.GetPortFlag(), name, a.GetPrometheusPortFlag(), a.config, rpcFn) -} - -func (a *RpcCmd) GetPortFromConfig(portType string) int { - switch a.Name { - case RpcPushServer: - if portType == constant.FlagPort { - return a.config.RpcPort.OpenImPushPort[0] - } - if portType == constant.FlagPrometheusPort { - return a.config.Prometheus.PushPrometheusPort[0] - } - case RpcAuthServer: - if portType == constant.FlagPort { - return a.config.RpcPort.OpenImAuthPort[0] - } - if portType == constant.FlagPrometheusPort { - return a.config.Prometheus.AuthPrometheusPort[0] - } - case RpcConversationServer: - if portType == constant.FlagPort { - return a.config.RpcPort.OpenImConversationPort[0] - } - if portType == constant.FlagPrometheusPort { - return a.config.Prometheus.ConversationPrometheusPort[0] - } - case RpcFriendServer: - if portType == constant.FlagPort { - return a.config.RpcPort.OpenImFriendPort[0] - } - if portType == constant.FlagPrometheusPort { - return a.config.Prometheus.FriendPrometheusPort[0] - } - case RpcGroupServer: - if portType == constant.FlagPort { - return a.config.RpcPort.OpenImGroupPort[0] - } - if portType == constant.FlagPrometheusPort { - return a.config.Prometheus.GroupPrometheusPort[0] - } - case RpcMsgServer: - if portType == constant.FlagPort { - return a.config.RpcPort.OpenImMessagePort[0] - } - if portType == constant.FlagPrometheusPort { - return a.config.Prometheus.MessagePrometheusPort[0] - } - case RpcThirdServer: - if portType == constant.FlagPort { - return a.config.RpcPort.OpenImThirdPort[0] - } - if portType == constant.FlagPrometheusPort { - return a.config.Prometheus.ThirdPrometheusPort[0] - } - case RpcUserServer: - if portType == constant.FlagPort { - return a.config.RpcPort.OpenImUserPort[0] - } - if portType == constant.FlagPrometheusPort { - return a.config.Prometheus.UserPrometheusPort[0] - } - } - return 0 -} - -func (a *RpcCmd) GetRpcRegisterNameFromConfig() (string, error) { - switch a.Name { - case RpcPushServer: - return a.config.RpcRegisterName.OpenImPushName, nil - case RpcAuthServer: - return a.config.RpcRegisterName.OpenImAuthName, nil - case RpcConversationServer: - return a.config.RpcRegisterName.OpenImConversationName, nil - case RpcFriendServer: - return a.config.RpcRegisterName.OpenImFriendName, nil - case RpcGroupServer: - return a.config.RpcRegisterName.OpenImGroupName, nil - case RpcMsgServer: - return a.config.RpcRegisterName.OpenImMsgName, nil - case RpcThirdServer: - return a.config.RpcRegisterName.OpenImThirdName, nil - case RpcUserServer: - return a.config.RpcRegisterName.OpenImUserName, nil - } - return "", errs.Wrap(errors.New("can not get rpc register name"), a.Name) -} diff --git a/pkg/common/cmd/third.go b/pkg/common/cmd/third.go new file mode 100644 index 000000000..0dfa7d5be --- /dev/null +++ b/pkg/common/cmd/third.go @@ -0,0 +1,62 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/rpc/third" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" + "github.com/openimsdk/tools/system/program" + "github.com/spf13/cobra" +) + +type ThirdRpcCmd struct { + *RootCmd + ctx context.Context + configMap map[string]any + thirdConfig *third.Config +} + +func NewThirdRpcCmd() *ThirdRpcCmd { + var thirdConfig third.Config + ret := &ThirdRpcCmd{thirdConfig: &thirdConfig} + ret.configMap = map[string]any{ + OpenIMRPCThirdCfgFileName: &thirdConfig.RpcConfig, + RedisConfigFileName: &thirdConfig.RedisConfig, + ZookeeperConfigFileName: &thirdConfig.ZookeeperConfig, + MongodbConfigFileName: &thirdConfig.MongodbConfig, + ShareFileName: &thirdConfig.Share, + NotificationFileName: &thirdConfig.NotificationConfig, + MinioConfigFileName: &thirdConfig.MinioConfig, + LocalCacheConfigFileName: &thirdConfig.LocalCacheConfig, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + return ret +} + +func (a *ThirdRpcCmd) Exec() error { + return a.Execute() +} + +func (a *ThirdRpcCmd) runE() error { + return startrpc.Start(a.ctx, &a.thirdConfig.ZookeeperConfig, &a.thirdConfig.RpcConfig.Prometheus, a.thirdConfig.RpcConfig.RPC.ListenIP, + a.thirdConfig.RpcConfig.RPC.RegisterIP, a.thirdConfig.RpcConfig.RPC.Ports, + a.Index(), a.thirdConfig.Share.RpcRegisterName.Third, &a.thirdConfig.Share, a.thirdConfig, third.Start) +} diff --git a/pkg/common/cmd/user.go b/pkg/common/cmd/user.go new file mode 100644 index 000000000..315b93256 --- /dev/null +++ b/pkg/common/cmd/user.go @@ -0,0 +1,63 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/internal/rpc/user" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" + "github.com/openimsdk/tools/system/program" + "github.com/spf13/cobra" +) + +type UserRpcCmd struct { + *RootCmd + ctx context.Context + configMap map[string]any + userConfig *user.Config +} + +func NewUserRpcCmd() *UserRpcCmd { + var userConfig user.Config + ret := &UserRpcCmd{userConfig: &userConfig} + ret.configMap = map[string]any{ + OpenIMRPCUserCfgFileName: &userConfig.RpcConfig, + RedisConfigFileName: &userConfig.RedisConfig, + ZookeeperConfigFileName: &userConfig.ZookeeperConfig, + MongodbConfigFileName: &userConfig.MongodbConfig, + KafkaConfigFileName: &userConfig.KafkaConfig, + ShareFileName: &userConfig.Share, + NotificationFileName: &userConfig.NotificationConfig, + WebhooksConfigFileName: &userConfig.WebhooksConfig, + LocalCacheConfigFileName: &userConfig.LocalCacheConfig, + } + ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) + ret.ctx = context.WithValue(context.Background(), "version", config.Version) + ret.Command.RunE = func(cmd *cobra.Command, args []string) error { + return ret.runE() + } + return ret +} + +func (a *UserRpcCmd) Exec() error { + return a.Execute() +} + +func (a *UserRpcCmd) runE() error { + return startrpc.Start(a.ctx, &a.userConfig.ZookeeperConfig, &a.userConfig.RpcConfig.Prometheus, a.userConfig.RpcConfig.RPC.ListenIP, + a.userConfig.RpcConfig.RPC.RegisterIP, a.userConfig.RpcConfig.RPC.Ports, + a.Index(), a.userConfig.Share.RpcRegisterName.User, &a.userConfig.Share, a.userConfig, user.Start) +} diff --git a/pkg/common/config/config.go b/pkg/common/config/config.go index 8bc871355..df2639cab 100644 --- a/pkg/common/config/config.go +++ b/pkg/common/config/config.go @@ -15,433 +15,516 @@ package config import ( - "bytes" + "fmt" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/redisutil" + "github.com/openimsdk/tools/mq/kafka" + "github.com/openimsdk/tools/s3/cos" + "github.com/openimsdk/tools/s3/minio" + "github.com/openimsdk/tools/s3/oss" + "net" "time" - - "github.com/OpenIMSDK/tools/discoveryregistry" - "gopkg.in/yaml.v3" ) -var Config GlobalConfig +type CacheConfig struct { + Topic string `mapstructure:"topic"` + SlotNum int `mapstructure:"slotNum"` + SlotSize int `mapstructure:"slotSize"` + SuccessExpire int `mapstructure:"successExpire"` + FailedExpire int `mapstructure:"failedExpire"` +} -const ConfKey = "conf" +type LocalCache struct { + User CacheConfig `mapstructure:"user"` + Group CacheConfig `mapstructure:"group"` + Friend CacheConfig `mapstructure:"friend"` + Conversation CacheConfig `mapstructure:"conversation"` +} -type CallBackConfig struct { - Enable bool `yaml:"enable"` - CallbackTimeOut int `yaml:"timeout"` - CallbackFailedContinue *bool `yaml:"failedContinue"` +type Log struct { + StorageLocation string `mapstructure:"storageLocation"` + RotationTime uint `mapstructure:"rotationTime"` + RemainRotationCount uint `mapstructure:"remainRotationCount"` + RemainLogLevel int `mapstructure:"remainLogLevel"` + IsStdout bool `mapstructure:"isStdout"` + IsJson bool `mapstructure:"isJson"` + WithStack bool `mapstructure:"withStack"` } -type NotificationConf struct { - IsSendMsg bool `yaml:"isSendMsg"` - ReliabilityLevel int `yaml:"reliabilityLevel"` // 1 online 2 persistent - UnreadCount bool `yaml:"unreadCount"` - OfflinePush POfflinePush `yaml:"offlinePush"` +type Minio struct { + Bucket string `mapstructure:"bucket"` + AccessKeyID string `mapstructure:"accessKeyID"` + SecretAccessKey string `mapstructure:"secretAccessKey"` + SessionToken string `mapstructure:"sessionToken"` + InternalAddress string `mapstructure:"internalAddress"` + ExternalAddress string `mapstructure:"externalAddress"` + PublicRead bool `mapstructure:"publicRead"` } -type POfflinePush struct { - Enable bool `yaml:"enable"` - Title string `yaml:"title"` - Desc string `yaml:"desc"` - Ext string `yaml:"ext"` +type Mongo struct { + URI string `mapstructure:"uri"` + Address []string `mapstructure:"address"` + Database string `mapstructure:"database"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + MaxPoolSize int `mapstructure:"maxPoolSize"` + MaxRetry int `mapstructure:"maxRetry"` +} +type Kafka struct { + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + ProducerAck string `mapstructure:"producerAck"` + CompressType string `mapstructure:"compressType"` + Address []string `mapstructure:"address"` + ToRedisTopic string `mapstructure:"toRedisTopic"` + ToMongoTopic string `mapstructure:"toMongoTopic"` + ToPushTopic string `mapstructure:"toPushTopic"` + ToRedisGroupID string `mapstructure:"toRedisGroupID"` + ToMongoGroupID string `mapstructure:"toMongoGroupID"` + ToPushGroupID string `mapstructure:"toPushGroupID"` + Tls TLSConfig `mapstructure:"tls"` +} +type TLSConfig struct { + EnableTLS bool `mapstructure:"enableTLS"` + CACrt string `mapstructure:"caCrt"` + ClientCrt string `mapstructure:"clientCrt"` + ClientKey string `mapstructure:"clientKey"` + ClientKeyPwd string `mapstructure:"clientKeyPwd"` + InsecureSkipVerify bool `mapstructure:"insecureSkipVerify"` } -type MYSQL struct { - Address []string `yaml:"address"` - Username string `yaml:"username"` - Password string `yaml:"password"` - Database string `yaml:"database"` - MaxOpenConn int `yaml:"maxOpenConn"` - MaxIdleConn int `yaml:"maxIdleConn"` - MaxLifeTime int `yaml:"maxLifeTime"` - LogLevel int `yaml:"logLevel"` - SlowThreshold int `yaml:"slowThreshold"` +type API struct { + Api struct { + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"api"` + Prometheus struct { + Enable bool `mapstructure:"enable"` + Ports []int `mapstructure:"ports"` + GrafanaURL string `mapstructure:"grafanaURL"` + } `mapstructure:"prometheus"` } -type GlobalConfig struct { - Envs struct { - Discovery string `yaml:"discovery"` - } - Zookeeper struct { - Schema string `yaml:"schema"` - ZkAddr []string `yaml:"address"` - Username string `yaml:"username"` - Password string `yaml:"password"` - } `yaml:"zookeeper"` - - Mysql *MYSQL `yaml:"mysql"` - - Mongo struct { - Uri string `yaml:"uri"` - Address []string `yaml:"address"` - Database string `yaml:"database"` - Username string `yaml:"username"` - Password string `yaml:"password"` - MaxPoolSize int `yaml:"maxPoolSize"` - } `yaml:"mongo"` - - Redis struct { - ClusterMode bool `yaml:"clusterMode"` - Address []string `yaml:"address"` - Username string `yaml:"username"` - Password string `yaml:"password"` - EnablePipeline bool `yaml:"enablePipeline"` - } `yaml:"redis"` - - Kafka struct { - Username string `yaml:"username"` - Password string `yaml:"password"` - ProducerAck string `yaml:"producerAck"` - CompressType string `yaml:"compressType"` - Addr []string `yaml:"addr"` - TLS *struct { - CACrt string `yaml:"caCrt"` - ClientCrt string `yaml:"clientCrt"` - ClientKey string `yaml:"clientKey"` - ClientKeyPwd string `yaml:"clientKeyPwd"` - InsecureSkipVerify bool `yaml:"insecureSkipVerify"` - } `yaml:"tls"` - LatestMsgToRedis struct { - Topic string `yaml:"topic"` - } `yaml:"latestMsgToRedis"` - MsgToMongo struct { - Topic string `yaml:"topic"` - } `yaml:"offlineMsgToMongo"` - MsgToPush struct { - Topic string `yaml:"topic"` - } `yaml:"msgToPush"` - ConsumerGroupID struct { - MsgToRedis string `yaml:"msgToRedis"` - MsgToMongo string `yaml:"msgToMongo"` - MsgToMySql string `yaml:"msgToMySql"` - MsgToPush string `yaml:"msgToPush"` - } `yaml:"consumerGroupID"` - } `yaml:"kafka"` - - Rpc struct { - RegisterIP string `yaml:"registerIP"` - ListenIP string `yaml:"listenIP"` - } `yaml:"rpc"` +type CronTask struct { + ChatRecordsClearTime string `mapstructure:"chatRecordsClearTime"` + MsgDestructTime string `mapstructure:"msgDestructTime"` + RetainChatRecords int `mapstructure:"retainChatRecords"` + EnableCronLocker bool `yaml:"enableCronLocker"` +} - Api struct { - OpenImApiPort []int `yaml:"openImApiPort"` - ListenIP string `yaml:"listenIP"` - } `yaml:"api"` - - Object struct { - Enable string `yaml:"enable"` - ApiURL string `yaml:"apiURL"` - Minio struct { - Bucket string `yaml:"bucket"` - Endpoint string `yaml:"endpoint"` - AccessKeyID string `yaml:"accessKeyID"` - SecretAccessKey string `yaml:"secretAccessKey"` - SessionToken string `yaml:"sessionToken"` - SignEndpoint string `yaml:"signEndpoint"` - PublicRead bool `yaml:"publicRead"` - } `yaml:"minio"` - Cos struct { - BucketURL string `yaml:"bucketURL"` - SecretID string `yaml:"secretID"` - SecretKey string `yaml:"secretKey"` - SessionToken string `yaml:"sessionToken"` - PublicRead bool `yaml:"publicRead"` - } `yaml:"cos"` - Oss struct { - Endpoint string `yaml:"endpoint"` - Bucket string `yaml:"bucket"` - BucketURL string `yaml:"bucketURL"` - AccessKeyID string `yaml:"accessKeyID"` - AccessKeySecret string `yaml:"accessKeySecret"` - SessionToken string `yaml:"sessionToken"` - PublicRead bool `yaml:"publicRead"` - } `yaml:"oss"` - Kodo struct { - Endpoint string `yaml:"endpoint"` - Bucket string `yaml:"bucket"` - BucketURL string `yaml:"bucketURL"` - AccessKeyID string `yaml:"accessKeyID"` - AccessKeySecret string `yaml:"accessKeySecret"` - SessionToken string `yaml:"sessionToken"` - PublicRead bool `yaml:"publicRead"` - } `yaml:"kodo"` - Aws struct { - Endpoint string `yaml:"endpoint"` - Region string `yaml:"region"` - Bucket string `yaml:"bucket"` - AccessKeyID string `yaml:"accessKeyID"` - AccessKeySecret string `yaml:"accessKeySecret"` - PublicRead bool `yaml:"publicRead"` - } `yaml:"aws"` - } `yaml:"object"` - - RpcPort struct { - OpenImUserPort []int `yaml:"openImUserPort"` - OpenImFriendPort []int `yaml:"openImFriendPort"` - OpenImMessagePort []int `yaml:"openImMessagePort"` - OpenImMessageGatewayPort []int `yaml:"openImMessageGatewayPort"` - OpenImGroupPort []int `yaml:"openImGroupPort"` - OpenImAuthPort []int `yaml:"openImAuthPort"` - OpenImPushPort []int `yaml:"openImPushPort"` - OpenImConversationPort []int `yaml:"openImConversationPort"` - OpenImRtcPort []int `yaml:"openImRtcPort"` - OpenImThirdPort []int `yaml:"openImThirdPort"` - } `yaml:"rpcPort"` - - RpcRegisterName struct { - OpenImUserName string `yaml:"openImUserName"` - OpenImFriendName string `yaml:"openImFriendName"` - OpenImMsgName string `yaml:"openImMsgName"` - OpenImPushName string `yaml:"openImPushName"` - OpenImMessageGatewayName string `yaml:"openImMessageGatewayName"` - OpenImGroupName string `yaml:"openImGroupName"` - OpenImAuthName string `yaml:"openImAuthName"` - OpenImConversationName string `yaml:"openImConversationName"` - OpenImThirdName string `yaml:"openImThirdName"` - } `yaml:"rpcRegisterName"` - - Log struct { - StorageLocation string `yaml:"storageLocation"` - RotationTime uint `yaml:"rotationTime"` - RemainRotationCount uint `yaml:"remainRotationCount"` - RemainLogLevel int `yaml:"remainLogLevel"` - IsStdout bool `yaml:"isStdout"` - IsJson bool `yaml:"isJson"` - WithStack bool `yaml:"withStack"` - } `yaml:"log"` +type OfflinePushConfig struct { + Enable bool `mapstructure:"enable"` + Title string `mapstructure:"title"` + Desc string `mapstructure:"desc"` + Ext string `mapstructure:"ext"` +} + +type NotificationConfig struct { + IsSendMsg bool `mapstructure:"isSendMsg"` + ReliabilityLevel int `mapstructure:"reliabilityLevel"` + UnreadCount bool `mapstructure:"unreadCount"` + OfflinePush OfflinePushConfig `mapstructure:"offlinePush"` +} + +type Notification struct { + GroupCreated NotificationConfig `mapstructure:"groupCreated"` + GroupInfoSet NotificationConfig `mapstructure:"groupInfoSet"` + JoinGroupApplication NotificationConfig `mapstructure:"joinGroupApplication"` + MemberQuit NotificationConfig `mapstructure:"memberQuit"` + GroupApplicationAccepted NotificationConfig `mapstructure:"groupApplicationAccepted"` + GroupApplicationRejected NotificationConfig `mapstructure:"groupApplicationRejected"` + GroupOwnerTransferred NotificationConfig `mapstructure:"groupOwnerTransferred"` + MemberKicked NotificationConfig `mapstructure:"memberKicked"` + MemberInvited NotificationConfig `mapstructure:"memberInvited"` + MemberEnter NotificationConfig `mapstructure:"memberEnter"` + GroupDismissed NotificationConfig `mapstructure:"groupDismissed"` + GroupMuted NotificationConfig `mapstructure:"groupMuted"` + GroupCancelMuted NotificationConfig `mapstructure:"groupCancelMuted"` + GroupMemberMuted NotificationConfig `mapstructure:"groupMemberMuted"` + GroupMemberCancelMuted NotificationConfig `mapstructure:"groupMemberCancelMuted"` + GroupMemberInfoSet NotificationConfig `mapstructure:"groupMemberInfoSet"` + GroupMemberSetToAdmin NotificationConfig `yaml:"groupMemberSetToAdmin"` + GroupMemberSetToOrdinary NotificationConfig `yaml:"groupMemberSetToOrdinaryUser"` + GroupInfoSetAnnouncement NotificationConfig `mapstructure:"groupInfoSetAnnouncement"` + GroupInfoSetName NotificationConfig `mapstructure:"groupInfoSetName"` + FriendApplicationAdded NotificationConfig `mapstructure:"friendApplicationAdded"` + FriendApplicationApproved NotificationConfig `mapstructure:"friendApplicationApproved"` + FriendApplicationRejected NotificationConfig `mapstructure:"friendApplicationRejected"` + FriendAdded NotificationConfig `mapstructure:"friendAdded"` + FriendDeleted NotificationConfig `mapstructure:"friendDeleted"` + FriendRemarkSet NotificationConfig `mapstructure:"friendRemarkSet"` + BlackAdded NotificationConfig `mapstructure:"blackAdded"` + BlackDeleted NotificationConfig `mapstructure:"blackDeleted"` + FriendInfoUpdated NotificationConfig `mapstructure:"friendInfoUpdated"` + UserInfoUpdated NotificationConfig `mapstructure:"userInfoUpdated"` + UserStatusChanged NotificationConfig `mapstructure:"userStatusChanged"` + ConversationChanged NotificationConfig `mapstructure:"conversationChanged"` + ConversationSetPrivate NotificationConfig `mapstructure:"conversationSetPrivate"` +} +type Prometheus struct { + Enable bool `mapstructure:"enable"` + Ports []int `mapstructure:"ports"` +} + +type MsgGateway struct { + RPC struct { + RegisterIP string `mapstructure:"registerIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"rpc"` + Prometheus Prometheus `mapstructure:"prometheus"` + ListenIP string `mapstructure:"listenIP"` LongConnSvr struct { - OpenImMessageGatewayPort []int `yaml:"openImMessageGatewayPort"` - OpenImWsPort []int `yaml:"openImWsPort"` - WebsocketMaxConnNum int `yaml:"websocketMaxConnNum"` - WebsocketMaxMsgLen int `yaml:"websocketMaxMsgLen"` - WebsocketTimeout int `yaml:"websocketTimeout"` - WebsocketWriteBufferSize int `yaml:"websocketWriteBufferSize"` - } `yaml:"longConnSvr"` - - Push struct { - MaxConcurrentWorkers int `yaml:"maxConcurrentWorkers"` - Enable string `yaml:"enable"` - GeTui struct { - PushUrl string `yaml:"pushUrl"` - AppKey string `yaml:"appKey"` - Intent string `yaml:"intent"` - MasterSecret string `yaml:"masterSecret"` - ChannelID string `yaml:"channelID"` - ChannelName string `yaml:"channelName"` - } `yaml:"geTui"` - Fcm struct { - ServiceAccount string `yaml:"serviceAccount"` - } `yaml:"fcm"` - Jpns struct { - AppKey string `yaml:"appKey"` - MasterSecret string `yaml:"masterSecret"` - PushUrl string `yaml:"pushUrl"` - PushIntent string `yaml:"pushIntent"` - } `yaml:"jpns"` - } - Manager struct { - UserID []string `yaml:"userID"` - Nickname []string `yaml:"nickname"` - } `yaml:"manager"` - - IMAdmin struct { - UserID []string `yaml:"userID"` - Nickname []string `yaml:"nickname"` - } `yaml:"im-admin"` - - MultiLoginPolicy int `yaml:"multiLoginPolicy"` - ChatPersistenceMysql bool `yaml:"chatPersistenceMysql"` - MsgCacheTimeout int `yaml:"msgCacheTimeout"` - GroupMessageHasReadReceiptEnable bool `yaml:"groupMessageHasReadReceiptEnable"` - SingleMessageHasReadReceiptEnable bool `yaml:"singleMessageHasReadReceiptEnable"` - RetainChatRecords int `yaml:"retainChatRecords"` - ChatRecordsClearTime string `yaml:"chatRecordsClearTime"` - MsgDestructTime string `yaml:"msgDestructTime"` - Secret string `yaml:"secret"` - EnableCronLocker bool `yaml:"enableCronLocker"` - TokenPolicy struct { - Expire int64 `yaml:"expire"` - } `yaml:"tokenPolicy"` - MessageVerify struct { - FriendVerify *bool `yaml:"friendVerify"` - } `yaml:"messageVerify"` - - LocalCache localCache `yaml:"localCache"` + Ports []int `mapstructure:"ports"` + WebsocketMaxConnNum int `mapstructure:"websocketMaxConnNum"` + WebsocketMaxMsgLen int `mapstructure:"websocketMaxMsgLen"` + WebsocketTimeout int `mapstructure:"websocketTimeout"` + } `mapstructure:"longConnSvr"` + MultiLoginPolicy int `mapstructure:"multiLoginPolicy"` +} +type MsgTransfer struct { + Prometheus Prometheus `mapstructure:"prometheus"` +} + +type Push struct { + RPC struct { + RegisterIP string `mapstructure:"registerIP"` + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"rpc"` + Prometheus Prometheus `mapstructure:"prometheus"` + MaxConcurrentWorkers int `mapstructure:"maxConcurrentWorkers"` + Enable string `mapstructure:"enable"` + GeTui struct { + PushUrl string `mapstructure:"pushUrl"` + MasterSecret string `mapstructure:"masterSecret"` + AppKey string `mapstructure:"appKey"` + Intent string `mapstructure:"intent"` + ChannelID string `mapstructure:"channelID"` + ChannelName string `mapstructure:"channelName"` + } `mapstructure:"geTui"` + FCM struct { + ServiceAccount string `mapstructure:"serviceAccount"` + } `mapstructure:"fcm"` + JPNS struct { + AppKey string `mapstructure:"appKey"` + MasterSecret string `mapstructure:"masterSecret"` + PushURL string `mapstructure:"pushURL"` + PushIntent string `mapstructure:"pushIntent"` + } `mapstructure:"jpns"` IOSPush struct { - PushSound string `yaml:"pushSound"` - BadgeCount bool `yaml:"badgeCount"` - Production bool `yaml:"production"` - } `yaml:"iosPush"` - Callback struct { - CallbackUrl string `yaml:"url"` - CallbackBeforeSendSingleMsg CallBackConfig `yaml:"beforeSendSingleMsg"` - CallbackAfterSendSingleMsg CallBackConfig `yaml:"afterSendSingleMsg"` - CallbackBeforeSendGroupMsg CallBackConfig `yaml:"beforeSendGroupMsg"` - CallbackAfterSendGroupMsg CallBackConfig `yaml:"afterSendGroupMsg"` - CallbackMsgModify CallBackConfig `yaml:"msgModify"` - CallbackSingleMsgRead CallBackConfig `yaml:"singleMsgRead"` - CallbackGroupMsgRead CallBackConfig `yaml:"groupMsgRead"` - CallbackUserOnline CallBackConfig `yaml:"userOnline"` - CallbackUserOffline CallBackConfig `yaml:"userOffline"` - CallbackUserKickOff CallBackConfig `yaml:"userKickOff"` - CallbackOfflinePush CallBackConfig `yaml:"offlinePush"` - CallbackOnlinePush CallBackConfig `yaml:"onlinePush"` - CallbackBeforeSuperGroupOnlinePush CallBackConfig `yaml:"superGroupOnlinePush"` - CallbackBeforeAddFriend CallBackConfig `yaml:"beforeAddFriend"` - CallbackBeforeSetFriendRemark CallBackConfig `yaml:"callbackBeforeSetFriendRemark"` - CallbackAfterSetFriendRemark CallBackConfig `yaml:"callbackAfterSetFriendRemark"` - CallbackBeforeUpdateUserInfo CallBackConfig `yaml:"beforeUpdateUserInfo"` - CallbackBeforeUpdateUserInfoEx CallBackConfig `yaml:"beforeUpdateUserInfoEx"` - CallbackAfterUpdateUserInfoEx CallBackConfig `yaml:"afterUpdateUserInfoEx"` - CallbackBeforeUserRegister CallBackConfig `yaml:"beforeUserRegister"` - CallbackAfterUpdateUserInfo CallBackConfig `yaml:"updateUserInfo"` - CallbackAfterUserRegister CallBackConfig `yaml:"afterUserRegister"` - CallbackBeforeCreateGroup CallBackConfig `yaml:"beforeCreateGroup"` - CallbackAfterCreateGroup CallBackConfig `yaml:"afterCreateGroup"` - CallbackBeforeMemberJoinGroup CallBackConfig `yaml:"beforeMemberJoinGroup"` - CallbackBeforeSetGroupMemberInfo CallBackConfig `yaml:"beforeSetGroupMemberInfo"` - CallbackAfterSetGroupMemberInfo CallBackConfig `yaml:"afterSetGroupMemberInfo"` - CallbackQuitGroup CallBackConfig `yaml:"quitGroup"` - CallbackKillGroupMember CallBackConfig `yaml:"killGroupMember"` - CallbackDismissGroup CallBackConfig `yaml:"dismissGroup"` - CallbackBeforeJoinGroup CallBackConfig `yaml:"joinGroup"` - CallbackAfterTransferGroupOwner CallBackConfig `yaml:"transferGroupOwner"` - CallbackBeforeInviteUserToGroup CallBackConfig `yaml:"beforeInviteUserToGroup"` - CallbackAfterJoinGroup CallBackConfig `yaml:"joinGroupAfter"` - CallbackAfterSetGroupInfo CallBackConfig `yaml:"setGroupInfoAfter"` - CallbackBeforeSetGroupInfo CallBackConfig `yaml:"setGroupInfoBefore"` - CallbackAfterRevokeMsg CallBackConfig `yaml:"revokeMsgAfter"` - CallbackBeforeAddBlack CallBackConfig `yaml:"addBlackBefore"` - CallbackAfterAddFriend CallBackConfig `yaml:"addFriendAfter"` - CallbackBeforeAddFriendAgree CallBackConfig `yaml:"addFriendAgreeBefore"` - - CallbackAfterDeleteFriend CallBackConfig `yaml:"deleteFriendAfter"` - CallbackBeforeImportFriends CallBackConfig `yaml:"importFriendsBefore"` - CallbackAfterImportFriends CallBackConfig `yaml:"importFriendsAfter"` - CallbackAfterRemoveBlack CallBackConfig `yaml:"removeBlackAfter"` - } `yaml:"callback"` + PushSound string `mapstructure:"pushSound"` + BadgeCount bool `mapstructure:"badgeCount"` + Production bool `mapstructure:"production"` + } `mapstructure:"iosPush"` +} - Prometheus struct { - Enable bool `yaml:"enable"` - GrafanaUrl string `yaml:"grafanaUrl"` - ApiPrometheusPort []int `yaml:"apiPrometheusPort"` - UserPrometheusPort []int `yaml:"userPrometheusPort"` - FriendPrometheusPort []int `yaml:"friendPrometheusPort"` - MessagePrometheusPort []int `yaml:"messagePrometheusPort"` - MessageGatewayPrometheusPort []int `yaml:"messageGatewayPrometheusPort"` - GroupPrometheusPort []int `yaml:"groupPrometheusPort"` - AuthPrometheusPort []int `yaml:"authPrometheusPort"` - PushPrometheusPort []int `yaml:"pushPrometheusPort"` - ConversationPrometheusPort []int `yaml:"conversationPrometheusPort"` - RtcPrometheusPort []int `yaml:"rtcPrometheusPort"` - MessageTransferPrometheusPort []int `yaml:"messageTransferPrometheusPort"` - ThirdPrometheusPort []int `yaml:"thirdPrometheusPort"` - } `yaml:"prometheus"` - Notification notification `yaml:"notification"` -} - -func NewGlobalConfig() *GlobalConfig { - return &GlobalConfig{} -} - -type notification struct { - GroupCreated NotificationConf `yaml:"groupCreated"` - GroupInfoSet NotificationConf `yaml:"groupInfoSet"` - JoinGroupApplication NotificationConf `yaml:"joinGroupApplication"` - MemberQuit NotificationConf `yaml:"memberQuit"` - GroupApplicationAccepted NotificationConf `yaml:"groupApplicationAccepted"` - GroupApplicationRejected NotificationConf `yaml:"groupApplicationRejected"` - GroupOwnerTransferred NotificationConf `yaml:"groupOwnerTransferred"` - MemberKicked NotificationConf `yaml:"memberKicked"` - MemberInvited NotificationConf `yaml:"memberInvited"` - MemberEnter NotificationConf `yaml:"memberEnter"` - GroupDismissed NotificationConf `yaml:"groupDismissed"` - GroupMuted NotificationConf `yaml:"groupMuted"` - GroupCancelMuted NotificationConf `yaml:"groupCancelMuted"` - GroupMemberMuted NotificationConf `yaml:"groupMemberMuted"` - GroupMemberCancelMuted NotificationConf `yaml:"groupMemberCancelMuted"` - GroupMemberInfoSet NotificationConf `yaml:"groupMemberInfoSet"` - GroupMemberSetToAdmin NotificationConf `yaml:"groupMemberSetToAdmin"` - GroupMemberSetToOrdinary NotificationConf `yaml:"groupMemberSetToOrdinaryUser"` - GroupInfoSetAnnouncement NotificationConf `yaml:"groupInfoSetAnnouncement"` - GroupInfoSetName NotificationConf `yaml:"groupInfoSetName"` - ////////////////////////user/////////////////////// - UserInfoUpdated NotificationConf `yaml:"userInfoUpdated"` - UserStatusChanged NotificationConf `yaml:"userStatusChanged"` - //////////////////////friend/////////////////////// - FriendApplicationAdded NotificationConf `yaml:"friendApplicationAdded"` - FriendApplicationApproved NotificationConf `yaml:"friendApplicationApproved"` - FriendApplicationRejected NotificationConf `yaml:"friendApplicationRejected"` - FriendAdded NotificationConf `yaml:"friendAdded"` - FriendDeleted NotificationConf `yaml:"friendDeleted"` - FriendRemarkSet NotificationConf `yaml:"friendRemarkSet"` - BlackAdded NotificationConf `yaml:"blackAdded"` - BlackDeleted NotificationConf `yaml:"blackDeleted"` - FriendInfoUpdated NotificationConf `yaml:"friendInfoUpdated"` - //////////////////////conversation/////////////////////// - ConversationChanged NotificationConf `yaml:"conversationChanged"` - ConversationSetPrivate NotificationConf `yaml:"conversationSetPrivate"` +type Auth struct { + RPC struct { + RegisterIP string `mapstructure:"registerIP"` + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"rpc"` + Prometheus Prometheus `mapstructure:"prometheus"` + TokenPolicy struct { + Expire int64 `mapstructure:"expire"` + } `mapstructure:"tokenPolicy"` } -type LocalCache struct { - Topic string `yaml:"topic"` - SlotNum int `yaml:"slotNum"` - SlotSize int `yaml:"slotSize"` - SuccessExpire int `yaml:"successExpire"` // second - FailedExpire int `yaml:"failedExpire"` // second +type Conversation struct { + RPC struct { + RegisterIP string `mapstructure:"registerIP"` + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"rpc"` + Prometheus Prometheus `mapstructure:"prometheus"` } -func (l LocalCache) Failed() time.Duration { - return time.Second * time.Duration(l.FailedExpire) +type Friend struct { + RPC struct { + RegisterIP string `mapstructure:"registerIP"` + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"rpc"` + Prometheus Prometheus `mapstructure:"prometheus"` } -func (l LocalCache) Success() time.Duration { - return time.Second * time.Duration(l.SuccessExpire) +type Group struct { + RPC struct { + RegisterIP string `mapstructure:"registerIP"` + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"rpc"` + Prometheus Prometheus `mapstructure:"prometheus"` } -func (l LocalCache) Enable() bool { - return l.Topic != "" && l.SlotNum > 0 && l.SlotSize > 0 +type Msg struct { + RPC struct { + RegisterIP string `mapstructure:"registerIP"` + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"rpc"` + Prometheus Prometheus `mapstructure:"prometheus"` + FriendVerify bool `mapstructure:"friendVerify"` +} + +type Third struct { + RPC struct { + RegisterIP string `mapstructure:"registerIP"` + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"rpc"` + Prometheus Prometheus `mapstructure:"prometheus"` + Object struct { + Enable string `mapstructure:"enable"` + Cos Cos `mapstructure:"cos"` + Oss Oss `mapstructure:"oss"` + Kodo struct { + Endpoint string `mapstructure:"endpoint"` + Bucket string `mapstructure:"bucket"` + BucketURL string `mapstructure:"bucketURL"` + AccessKeyID string `mapstructure:"accessKeyID"` + AccessKeySecret string `mapstructure:"accessKeySecret"` + SessionToken string `mapstructure:"sessionToken"` + PublicRead bool `mapstructure:"publicRead"` + } `mapstructure:"kodo"` + Aws struct { + Endpoint string `mapstructure:"endpoint"` + Region string `mapstructure:"region"` + Bucket string `mapstructure:"bucket"` + AccessKeyID string `mapstructure:"accessKeyID"` + AccessKeySecret string `mapstructure:"accessKeySecret"` + PublicRead bool `mapstructure:"publicRead"` + } `mapstructure:"aws"` + } `mapstructure:"object"` +} +type Cos struct { + BucketURL string `mapstructure:"bucketURL"` + SecretID string `mapstructure:"secretID"` + SecretKey string `mapstructure:"secretKey"` + SessionToken string `mapstructure:"sessionToken"` + PublicRead bool `mapstructure:"publicRead"` +} +type Oss struct { + Endpoint string `mapstructure:"endpoint"` + Bucket string `mapstructure:"bucket"` + BucketURL string `mapstructure:"bucketURL"` + AccessKeyID string `mapstructure:"accessKeyID"` + AccessKeySecret string `mapstructure:"accessKeySecret"` + SessionToken string `mapstructure:"sessionToken"` + PublicRead bool `mapstructure:"publicRead"` +} + +type User struct { + RPC struct { + RegisterIP string `mapstructure:"registerIP"` + ListenIP string `mapstructure:"listenIP"` + Ports []int `mapstructure:"ports"` + } `mapstructure:"rpc"` + Prometheus Prometheus `mapstructure:"prometheus"` +} + +type Redis struct { + Address []string `mapstructure:"address"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + EnablePipeline bool `mapstructure:"enablePipeline"` + ClusterMode bool `mapstructure:"clusterMode"` + DB int `mapstructure:"db"` + MaxRetry int `mapstructure:"MaxRetry"` } -type localCache struct { - User LocalCache `yaml:"user"` - Group LocalCache `yaml:"group"` - Friend LocalCache `yaml:"friend"` - Conversation LocalCache `yaml:"conversation"` +type BeforeConfig struct { + Enable bool `mapstructure:"enable"` + Timeout int `mapstructure:"timeout"` + FailedContinue bool `mapstructure:"failedContinue"` } -func (c *GlobalConfig) GetServiceNames() []string { +type AfterConfig struct { + Enable bool `mapstructure:"enable"` + Timeout int `mapstructure:"timeout"` +} + +type Share struct { + Secret string `mapstructure:"secret"` + Env string `mapstructure:"env"` + RpcRegisterName RpcRegisterName `mapstructure:"rpcRegisterName"` + IMAdminUserID []string `mapstructure:"imAdminUserID"` +} +type RpcRegisterName struct { + User string `mapstructure:"user"` + Friend string `mapstructure:"friend"` + Msg string `mapstructure:"msg"` + Push string `mapstructure:"push"` + MessageGateway string `mapstructure:"messageGateway"` + Group string `mapstructure:"group"` + Auth string `mapstructure:"auth"` + Conversation string `mapstructure:"conversation"` + Third string `mapstructure:"third"` +} + +func (r *RpcRegisterName) GetServiceNames() []string { return []string{ - c.RpcRegisterName.OpenImUserName, - c.RpcRegisterName.OpenImFriendName, - c.RpcRegisterName.OpenImMsgName, - c.RpcRegisterName.OpenImPushName, - c.RpcRegisterName.OpenImMessageGatewayName, - c.RpcRegisterName.OpenImGroupName, - c.RpcRegisterName.OpenImAuthName, - c.RpcRegisterName.OpenImConversationName, - c.RpcRegisterName.OpenImThirdName, + r.User, + r.Friend, + r.Msg, + r.Push, + r.MessageGateway, + r.Group, + r.Auth, + r.Conversation, + r.Third, } } -func (c *GlobalConfig) RegisterConf2Registry(registry discoveryregistry.SvcDiscoveryRegistry) error { - data, err := yaml.Marshal(c) - if err != nil { - return err +// FullConfig stores all configurations for before and after events +type Webhooks struct { + URL string `mapstructure:"url"` + BeforeSendSingleMsg BeforeConfig `mapstructure:"beforeSendSingleMsg"` + BeforeUpdateUserInfoEx BeforeConfig `mapstructure:"beforeUpdateUserInfoEx"` + AfterUpdateUserInfoEx AfterConfig `mapstructure:"afterUpdateUserInfoEx"` + AfterSendSingleMsg AfterConfig `mapstructure:"afterSendSingleMsg"` + BeforeSendGroupMsg BeforeConfig `mapstructure:"beforeSendGroupMsg"` + BeforeMsgModify BeforeConfig `mapstructure:"beforeMsgModify"` + AfterSendGroupMsg AfterConfig `mapstructure:"afterSendGroupMsg"` + AfterUserOnline AfterConfig `mapstructure:"afterUserOnline"` + AfterUserOffline AfterConfig `mapstructure:"afterUserOffline"` + AfterUserKickOff AfterConfig `mapstructure:"afterUserKickOff"` + BeforeOfflinePush BeforeConfig `mapstructure:"beforeOfflinePush"` + BeforeOnlinePush BeforeConfig `mapstructure:"beforeOnlinePush"` + BeforeGroupOnlinePush BeforeConfig `mapstructure:"beforeGroupOnlinePush"` + BeforeAddFriend BeforeConfig `mapstructure:"beforeAddFriend"` + BeforeUpdateUserInfo BeforeConfig `mapstructure:"beforeUpdateUserInfo"` + AfterUpdateUserInfo AfterConfig `mapstructure:"afterUpdateUserInfo"` + BeforeCreateGroup BeforeConfig `mapstructure:"beforeCreateGroup"` + AfterCreateGroup AfterConfig `mapstructure:"afterCreateGroup"` + BeforeMemberJoinGroup BeforeConfig `mapstructure:"beforeMemberJoinGroup"` + BeforeSetGroupMemberInfo BeforeConfig `mapstructure:"beforeSetGroupMemberInfo"` + AfterSetGroupMemberInfo AfterConfig `mapstructure:"afterSetGroupMemberInfo"` + AfterQuitGroup AfterConfig `mapstructure:"afterQuitGroup"` + AfterKickGroupMember AfterConfig `mapstructure:"afterKickGroupMember"` + AfterDismissGroup AfterConfig `mapstructure:"afterDismissGroup"` + BeforeApplyJoinGroup BeforeConfig `mapstructure:"beforeApplyJoinGroup"` + AfterGroupMsgRead AfterConfig `mapstructure:"afterGroupMsgRead"` + AfterSingleMsgRead AfterConfig `mapstructure:"afterSingleMsgRead"` + BeforeUserRegister BeforeConfig `mapstructure:"beforeUserRegister"` + AfterUserRegister AfterConfig `mapstructure:"afterUserRegister"` + AfterTransferGroupOwner AfterConfig `mapstructure:"afterTransferGroupOwner"` + BeforeSetFriendRemark BeforeConfig `mapstructure:"beforeSetFriendRemark"` + AfterSetFriendRemark AfterConfig `mapstructure:"afterSetFriendRemark"` + AfterGroupMsgRevoke AfterConfig `mapstructure:"afterGroupMsgRevoke"` + AfterJoinGroup AfterConfig `mapstructure:"afterJoinGroup"` + BeforeInviteUserToGroup BeforeConfig `mapstructure:"beforeInviteUserToGroup"` + AfterSetGroupInfo AfterConfig `mapstructure:"afterSetGroupInfo"` + BeforeSetGroupInfo BeforeConfig `mapstructure:"beforeSetGroupInfo"` + AfterRevokeMsg AfterConfig `mapstructure:"afterRevokeMsg"` + BeforeAddBlack BeforeConfig `mapstructure:"beforeAddBlack"` + AfterAddFriend AfterConfig `mapstructure:"afterAddFriend"` + BeforeAddFriendAgree BeforeConfig `mapstructure:"beforeAddFriendAgree"` + AfterDeleteFriend AfterConfig `mapstructure:"afterDeleteFriend"` + BeforeImportFriends BeforeConfig `mapstructure:"beforeImportFriends"` + AfterImportFriends AfterConfig `mapstructure:"afterImportFriends"` + AfterRemoveBlack AfterConfig `mapstructure:"afterRemoveBlack"` +} + +type ZooKeeper struct { + Schema string `mapstructure:"schema"` + Address []string `mapstructure:"address"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` +} + +func (m *Mongo) Build() *mongoutil.Config { + return &mongoutil.Config{ + Uri: m.URI, + Address: m.Address, + Database: m.Database, + Username: m.Username, + Password: m.Password, + MaxPoolSize: m.MaxPoolSize, + MaxRetry: m.MaxRetry, } - return registry.RegisterConf2Registry(ConfKey, data) } -func (c *GlobalConfig) GetConfFromRegistry(registry discoveryregistry.SvcDiscoveryRegistry) ([]byte, error) { - return registry.GetConfFromRegistry(ConfKey) +func (r *Redis) Build() *redisutil.Config { + return &redisutil.Config{ + ClusterMode: r.ClusterMode, + Address: r.Address, + Username: r.Username, + Password: r.Password, + DB: r.DB, + MaxRetry: r.MaxRetry, + } } -func (c *GlobalConfig) EncodeConfig() []byte { - buf := bytes.NewBuffer(nil) - if err := yaml.NewEncoder(buf).Encode(c); err != nil { - panic(err) +func (k *Kafka) Build() *kafka.Config { + return &kafka.Config{ + Username: k.Username, + Password: k.Password, + ProducerAck: k.ProducerAck, + CompressType: k.CompressType, + Addr: k.Address, + TLS: kafka.TLSConfig{ + EnableTLS: k.Tls.EnableTLS, + CACrt: k.Tls.CACrt, + ClientCrt: k.Tls.ClientCrt, + ClientKey: k.Tls.ClientKey, + ClientKeyPwd: k.Tls.ClientKeyPwd, + InsecureSkipVerify: k.Tls.InsecureSkipVerify, + }, + } +} +func (m *Minio) Build() *minio.Config { + conf := minio.Config{ + Bucket: m.Bucket, + AccessKeyID: m.AccessKeyID, + SecretAccessKey: m.SecretAccessKey, + SessionToken: m.SessionToken, + PublicRead: m.PublicRead, } - return buf.Bytes() + if _, _, err := net.SplitHostPort(m.InternalAddress); err == nil { + conf.Endpoint = fmt.Sprintf("http://%s", m.InternalAddress) + } else { + conf.Endpoint = m.InternalAddress + } + if _, _, err := net.SplitHostPort(m.ExternalAddress); err == nil { + conf.SignEndpoint = fmt.Sprintf("http://%s", m.ExternalAddress) + } else { + conf.SignEndpoint = m.ExternalAddress + } + return &conf +} +func (c *Cos) Build() *cos.Config { + return &cos.Config{ + BucketURL: c.BucketURL, + SecretID: c.SecretID, + SecretKey: c.SecretKey, + SessionToken: c.SessionToken, + PublicRead: c.PublicRead, + } +} + +func (o *Oss) Build() *oss.Config { + return &oss.Config{ + Endpoint: o.Endpoint, + Bucket: o.Bucket, + BucketURL: o.BucketURL, + AccessKeyID: o.AccessKeyID, + AccessKeySecret: o.AccessKeySecret, + SessionToken: o.SessionToken, + PublicRead: o.PublicRead, + } +} + +func (l *CacheConfig) Failed() time.Duration { + return time.Second * time.Duration(l.FailedExpire) +} + +func (l *CacheConfig) Success() time.Duration { + return time.Second * time.Duration(l.SuccessExpire) +} + +func (l *CacheConfig) Enable() bool { + return l.Topic != "" && l.SlotNum > 0 && l.SlotSize > 0 } diff --git a/pkg/common/config/constant.go b/pkg/common/config/constant.go new file mode 100644 index 000000000..f425a624c --- /dev/null +++ b/pkg/common/config/constant.go @@ -0,0 +1,37 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +const ConfKey = "conf" + +const ( + // DefaultDirPerm is used for creating general directories, allowing the owner to read, write, and execute, + // while the group and others can only read and execute. + DefaultDirPerm = 0755 + + // PrivateFilePerm is used for sensitive files, allowing only the owner to read and write. + PrivateFilePerm = 0600 + + // ExecFilePerm is used for executable files, allowing the owner to read, write, and execute, + // while the group and others can only read. + ExecFilePerm = 0754 + + // SharedDirPerm is used for shared directories, allowing the owner and group to read, write, and execute, + // with no permissions for others. + SharedDirPerm = 0770 + + // ReadOnlyDirPerm is used for read-only directories, allowing the owner, group, and others to only read. + ReadOnlyDirPerm = 0555 +) diff --git a/pkg/common/config/doc.go b/pkg/common/config/doc.go index 4b76cb9fa..189a5b583 100644 --- a/pkg/common/config/doc.go +++ b/pkg/common/config/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/common/config/load_config.go b/pkg/common/config/load_config.go new file mode 100644 index 000000000..9272896b4 --- /dev/null +++ b/pkg/common/config/load_config.go @@ -0,0 +1,27 @@ +package config + +import ( + "github.com/mitchellh/mapstructure" + "github.com/openimsdk/tools/errs" + "github.com/spf13/viper" + "strings" +) + +func LoadConfig(path string, envPrefix string, config any) error { + v := viper.New() + v.SetConfigFile(path) + v.SetEnvPrefix(envPrefix) + v.AutomaticEnv() + v.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) + + if err := v.ReadInConfig(); err != nil { + return errs.WrapMsg(err, "failed to read config file", "path", path, "envPrefix", envPrefix) + } + + if err := v.Unmarshal(config, func(config *mapstructure.DecoderConfig) { + config.TagName = "mapstructure" + }); err != nil { + return errs.WrapMsg(err, "failed to unmarshal config", "path", path, "envPrefix", envPrefix) + } + return nil +} diff --git a/pkg/common/config/load_config_test.go b/pkg/common/config/load_config_test.go new file mode 100644 index 000000000..256214565 --- /dev/null +++ b/pkg/common/config/load_config_test.go @@ -0,0 +1,38 @@ +package config + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestLoadLogConfig(t *testing.T) { + var log Log + err := LoadConfig("../../../config/log.yml", "IMENV_LOG", &log) + assert.Nil(t, err) + assert.Equal(t, "../../../../logs/", log.StorageLocation) +} + +func TestLoadMinioConfig(t *testing.T) { + var storageConfig Minio + err := LoadConfig("../../../config/minio.yml", "IMENV_MINIO", &storageConfig) + assert.Nil(t, err) + assert.Equal(t, "openim", storageConfig.Bucket) +} + +func TestLoadWebhooksConfig(t *testing.T) { + var webhooks Webhooks + err := LoadConfig("../../../config/webhooks.yml", "IMENV_WEBHOOKS", &webhooks) + assert.Nil(t, err) + assert.Equal(t, 5, webhooks.BeforeAddBlack.Timeout) + +} + +func TestLoadOpenIMRpcUserConfig(t *testing.T) { + var user User + err := LoadConfig("../../../config/openim-rpc-user.yml", "IMENV_OPENIM_RPC_USER", &user) + assert.Nil(t, err) + //export IMENV_OPENIM_RPC_USER_RPC_LISTENIP="0.0.0.0" + assert.Equal(t, "0.0.0.0", user.RPC.ListenIP) + //export IMENV_OPENIM_RPC_USER_RPC_PORTS="10110,10111,10112" + assert.Equal(t, []int{10110, 10111, 10112}, user.RPC.Ports) +} diff --git a/pkg/common/config/parse.go b/pkg/common/config/parse.go index bfbf6daf7..28e9f5db6 100644 --- a/pkg/common/config/parse.go +++ b/pkg/common/config/parse.go @@ -16,15 +16,14 @@ package config import ( _ "embed" - "fmt" "os" "path/filepath" - "github.com/OpenIMSDK/protocol/constant" - "gopkg.in/yaml.v3" - "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" - "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/field" + "gopkg.in/yaml.v3" ) //go:embed version @@ -37,34 +36,33 @@ const ( ) // return absolude path join ../config/, this is k8s container config path. -func GetDefaultConfigPath() string { +func GetDefaultConfigPath() (string, error) { executablePath, err := os.Executable() if err != nil { - fmt.Println("GetDefaultConfigPath error:", err.Error()) - return "" + return "", errs.WrapMsg(err, "failed to get executable path") } - configPath, err := genutil.OutDir(filepath.Join(filepath.Dir(executablePath), "../config/")) + configPath, err := field.OutDir(filepath.Join(filepath.Dir(executablePath), "../config/")) if err != nil { - fmt.Fprintf(os.Stderr, "failed to get output directory: %v\n", err) - os.Exit(1) + return "", errs.WrapMsg(err, "failed to get output directory", "outDir", filepath.Join(filepath.Dir(executablePath), "../config/")) } - return configPath + return configPath, nil } // getProjectRoot returns the absolute path of the project root directory. -func GetProjectRoot() string { - executablePath, _ := os.Executable() - - projectRoot, err := genutil.OutDir(filepath.Join(filepath.Dir(executablePath), "../../../../..")) +func GetProjectRoot() (string, error) { + executablePath, err := os.Executable() + if err != nil { + return "", errs.Wrap(err) + } + projectRoot, err := field.OutDir(filepath.Join(filepath.Dir(executablePath), "../../../../..")) if err != nil { - fmt.Fprintf(os.Stderr, "failed to get output directory: %v\n", err) - os.Exit(1) + return "", errs.Wrap(err) } - return projectRoot + return projectRoot, nil } -func GetOptionsByNotification(cfg NotificationConf) msgprocessor.Options { +func GetOptionsByNotification(cfg NotificationConfig) msgprocessor.Options { opts := msgprocessor.NewOptions() if cfg.UnreadCount { @@ -91,37 +89,21 @@ func initConfig(config any, configName, configFolderPath string) error { _, err := os.Stat(configFolderPath) if err != nil { if !os.IsNotExist(err) { - fmt.Println("stat config path error:", err.Error()) - return fmt.Errorf("stat config path error: %w", err) + return errs.WrapMsg(err, "stat config path error", "config Folder Path", configFolderPath) + } + path, err := GetProjectRoot() + if err != nil { + return err } - configFolderPath = filepath.Join(GetProjectRoot(), "config", configName) - fmt.Println("flag's path,enviment's path,default path all is not exist,using project path:", configFolderPath) + configFolderPath = filepath.Join(path, "config", configName) } data, err := os.ReadFile(configFolderPath) if err != nil { - return fmt.Errorf("read file error: %w", err) + return errs.WrapMsg(err, "read file error", "config Folder Path", configFolderPath) } if err = yaml.Unmarshal(data, config); err != nil { - return fmt.Errorf("unmarshal yaml error: %w", err) + return errs.WrapMsg(err, "unmarshal yaml error", "config Folder Path", configFolderPath) } - fmt.Println("The path of the configuration file to start the process:", configFolderPath) return nil } - -func InitConfig(config *GlobalConfig, configFolderPath string) error { - if configFolderPath == "" { - envConfigPath := os.Getenv("OPENIMCONFIG") - if envConfigPath != "" { - configFolderPath = envConfigPath - } else { - configFolderPath = GetDefaultConfigPath() - } - } - - if err := initConfig(config, FileName, configFolderPath); err != nil { - return err - } - - return initConfig(&config.Notification, NotificationFileName, configFolderPath) -} diff --git a/pkg/common/config/parse_test.go b/pkg/common/config/parse_test.go deleted file mode 100644 index 35b019b83..000000000 --- a/pkg/common/config/parse_test.go +++ /dev/null @@ -1,131 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package config - -import ( - _ "embed" - "fmt" - "reflect" - "testing" - - "gopkg.in/yaml.v3" - - "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" -) - -func TestGetDefaultConfigPath(t *testing.T) { - tests := []struct { - name string - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got, _ := GetDefaultConfigPath(); got != tt.want { - t.Errorf("GetDefaultConfigPath() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGetProjectRoot(t *testing.T) { - tests := []struct { - name string - want string - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got, _ := GetProjectRoot(); got != tt.want { - t.Errorf("GetProjectRoot() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestGetOptionsByNotification(t *testing.T) { - type args struct { - cfg NotificationConf - } - tests := []struct { - name string - args args - want msgprocessor.Options - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := GetOptionsByNotification(tt.args.cfg); !reflect.DeepEqual(got, tt.want) { - t.Errorf("GetOptionsByNotification() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_initConfig(t *testing.T) { - type args struct { - config any - configName string - configFolderPath string - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := initConfig(tt.args.config, tt.args.configName, tt.args.configFolderPath); (err != nil) != tt.wantErr { - t.Errorf("initConfig() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestInitConfig(t *testing.T) { - type args struct { - configFolderPath string - } - tests := []struct { - name string - args args - config *GlobalConfig - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := InitConfig(tt.config, tt.args.configFolderPath); (err != nil) != tt.wantErr { - t.Errorf("InitConfig() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestName(t *testing.T) { - Config.LocalCache.Friend.Topic = "friend" - Config.LocalCache.Friend.SlotNum = 500 - Config.LocalCache.Friend.SlotSize = 20000 - - data, _ := yaml.Marshal(&Config) - - fmt.Println(string(data)) -} diff --git a/pkg/common/config/version b/pkg/common/config/version index 40c341bdc..240bba906 100644 --- a/pkg/common/config/version +++ b/pkg/common/config/version @@ -1 +1 @@ -3.6.0 +3.7.0 \ No newline at end of file diff --git a/pkg/common/convert/black.go b/pkg/common/convert/black.go index 683517fbc..635b1a586 100644 --- a/pkg/common/convert/black.go +++ b/pkg/common/convert/black.go @@ -17,9 +17,9 @@ package convert import ( "context" - "github.com/OpenIMSDK/protocol/sdkws" - sdk "github.com/OpenIMSDK/protocol/sdkws" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/sdkws" + sdk "github.com/openimsdk/protocol/sdkws" ) func BlackDB2Pb(ctx context.Context, blackDBs []*relation.BlackModel, f func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (blackPbs []*sdk.BlackInfo, err error) { diff --git a/pkg/common/convert/conversation.go b/pkg/common/convert/conversation.go index b3f4913eb..510f40d70 100644 --- a/pkg/common/convert/conversation.go +++ b/pkg/common/convert/conversation.go @@ -15,15 +15,15 @@ package convert import ( - "github.com/OpenIMSDK/protocol/conversation" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/conversation" + "github.com/openimsdk/tools/utils/datautil" ) func ConversationDB2Pb(conversationDB *relation.ConversationModel) *conversation.Conversation { conversationPB := &conversation.Conversation{} conversationPB.LatestMsgDestructTime = conversationDB.LatestMsgDestructTime.Unix() - if err := utils.CopyStructFields(conversationPB, conversationDB); err != nil { + if err := datautil.CopyStructFields(conversationPB, conversationDB); err != nil { return nil } return conversationPB @@ -32,7 +32,7 @@ func ConversationDB2Pb(conversationDB *relation.ConversationModel) *conversation func ConversationsDB2Pb(conversationsDB []*relation.ConversationModel) (conversationsPB []*conversation.Conversation) { for _, conversationDB := range conversationsDB { conversationPB := &conversation.Conversation{} - if err := utils.CopyStructFields(conversationPB, conversationDB); err != nil { + if err := datautil.CopyStructFields(conversationPB, conversationDB); err != nil { continue } conversationPB.LatestMsgDestructTime = conversationDB.LatestMsgDestructTime.Unix() @@ -43,7 +43,7 @@ func ConversationsDB2Pb(conversationsDB []*relation.ConversationModel) (conversa func ConversationPb2DB(conversationPB *conversation.Conversation) *relation.ConversationModel { conversationDB := &relation.ConversationModel{} - if err := utils.CopyStructFields(conversationDB, conversationPB); err != nil { + if err := datautil.CopyStructFields(conversationDB, conversationPB); err != nil { return nil } return conversationDB @@ -52,7 +52,7 @@ func ConversationPb2DB(conversationPB *conversation.Conversation) *relation.Conv func ConversationsPb2DB(conversationsPB []*conversation.Conversation) (conversationsDB []*relation.ConversationModel) { for _, conversationPB := range conversationsPB { conversationDB := &relation.ConversationModel{} - if err := utils.CopyStructFields(conversationDB, conversationPB); err != nil { + if err := datautil.CopyStructFields(conversationDB, conversationPB); err != nil { continue } conversationsDB = append(conversationsDB, conversationDB) diff --git a/pkg/common/convert/doc.go b/pkg/common/convert/doc.go index 29c1144c1..623b4a5c0 100644 --- a/pkg/common/convert/doc.go +++ b/pkg/common/convert/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/common/convert/friend.go b/pkg/common/convert/friend.go index 4231eb49d..ad8f9071e 100644 --- a/pkg/common/convert/friend.go +++ b/pkg/common/convert/friend.go @@ -18,19 +18,20 @@ import ( "context" "fmt" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/timeutil" ) func FriendPb2DB(friend *sdkws.FriendInfo) *relation.FriendModel { dbFriend := &relation.FriendModel{} - err := utils.CopyStructFields(dbFriend, friend) + err := datautil.CopyStructFields(dbFriend, friend) if err != nil { return nil } dbFriend.FriendUserID = friend.FriendUser.UserID - dbFriend.CreateTime = utils.UnixSecondToTime(friend.CreateTime) + dbFriend.CreateTime = timeutil.UnixSecondToTime(friend.CreateTime) return dbFriend } @@ -71,7 +72,7 @@ func FriendsDB2Pb( } for _, friend := range friendsDB { friendPb := &sdkws.FriendInfo{FriendUser: &sdkws.UserInfo{}} - err := utils.CopyStructFields(friendPb, friend) + err := datautil.CopyStructFields(friendPb, friend) if err != nil { return nil, err } @@ -88,10 +89,7 @@ func FriendsDB2Pb( } -func FriendRequestDB2Pb(ctx context.Context, - friendRequests []*relation.FriendRequestModel, - getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error), -) ([]*sdkws.FriendRequest, error) { +func FriendRequestDB2Pb(ctx context.Context, friendRequests []*relation.FriendRequestModel, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) ([]*sdkws.FriendRequest, error) { if len(friendRequests) == 0 { return nil, nil } @@ -100,7 +98,7 @@ func FriendRequestDB2Pb(ctx context.Context, userIDMap[friendRequest.ToUserID] = struct{}{} userIDMap[friendRequest.FromUserID] = struct{}{} } - users, err := getUsers(ctx, utils.Keys(userIDMap)) + users, err := getUsers(ctx, datautil.Keys(userIDMap)) if err != nil { return nil, err } diff --git a/pkg/common/convert/group.go b/pkg/common/convert/group.go index 63372f21d..9b7353cfd 100644 --- a/pkg/common/convert/group.go +++ b/pkg/common/convert/group.go @@ -17,9 +17,9 @@ package convert import ( "time" - pbgroup "github.com/OpenIMSDK/protocol/group" - sdkws "github.com/OpenIMSDK/protocol/sdkws" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + pbgroup "github.com/openimsdk/protocol/group" + sdkws "github.com/openimsdk/protocol/sdkws" ) func Db2PbGroupInfo(m *relation.GroupModel, ownerUserID string, memberCount uint32) *sdkws.GroupInfo { @@ -55,12 +55,7 @@ func Pb2DbGroupRequest(req *pbgroup.GroupApplicationResponseReq, handleUserID st } } -func Db2PbCMSGroup( - m *relation.GroupModel, - ownerUserID string, - ownerUserName string, - memberCount uint32, -) *pbgroup.CMSGroup { +func Db2PbCMSGroup(m *relation.GroupModel, ownerUserID string, ownerUserName string, memberCount uint32) *pbgroup.CMSGroup { return &pbgroup.CMSGroup{ GroupInfo: Db2PbGroupInfo(m, ownerUserID, memberCount), GroupOwnerUserID: ownerUserID, @@ -85,11 +80,7 @@ func Db2PbGroupMember(m *relation.GroupMemberModel) *sdkws.GroupMemberFullInfo { } } -func Db2PbGroupRequest( - m *relation.GroupRequestModel, - user *sdkws.PublicUserInfo, - group *sdkws.GroupInfo, -) *sdkws.GroupRequest { +func Db2PbGroupRequest(m *relation.GroupRequestModel, user *sdkws.PublicUserInfo, group *sdkws.GroupInfo) *sdkws.GroupRequest { return &sdkws.GroupRequest{ UserInfo: user, GroupInfo: group, @@ -137,7 +128,7 @@ func Pb2DBGroupInfo(m *sdkws.GroupInfo) *relation.GroupModel { } } -//func Pb2DbGroupMember(m *sdkws.UserInfo) *relation.GroupMemberModel { +// func Pb2DbGroupMember(m *sdkws.UserInfo) *relation.GroupMemberModel { // return &relation.GroupMemberModel{ // UserID: m.UserID, // Nickname: m.Nickname, diff --git a/pkg/common/convert/msg.go b/pkg/common/convert/msg.go index 34638049b..594a0ffc2 100644 --- a/pkg/common/convert/msg.go +++ b/pkg/common/convert/msg.go @@ -15,16 +15,16 @@ package convert import ( - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" ) -func MsgPb2DB(msg *sdkws.MsgData) *unrelation.MsgDataModel { +func MsgPb2DB(msg *sdkws.MsgData) *relation.MsgDataModel { if msg == nil { return nil } - var msgDataModel unrelation.MsgDataModel + var msgDataModel relation.MsgDataModel msgDataModel.SendID = msg.SendID msgDataModel.RecvID = msg.RecvID msgDataModel.GroupID = msg.GroupID @@ -43,7 +43,7 @@ func MsgPb2DB(msg *sdkws.MsgData) *unrelation.MsgDataModel { msgDataModel.Status = msg.Status msgDataModel.Options = msg.Options if msg.OfflinePushInfo != nil { - msgDataModel.OfflinePush = &unrelation.OfflinePushModel{ + msgDataModel.OfflinePush = &relation.OfflinePushModel{ Title: msg.OfflinePushInfo.Title, Desc: msg.OfflinePushInfo.Desc, Ex: msg.OfflinePushInfo.Ex, @@ -57,7 +57,7 @@ func MsgPb2DB(msg *sdkws.MsgData) *unrelation.MsgDataModel { return &msgDataModel } -func MsgDB2Pb(msgModel *unrelation.MsgDataModel) *sdkws.MsgData { +func MsgDB2Pb(msgModel *relation.MsgDataModel) *sdkws.MsgData { if msgModel == nil { return nil } diff --git a/pkg/common/convert/user.go b/pkg/common/convert/user.go index 38afd8c19..a9378e1a0 100644 --- a/pkg/common/convert/user.go +++ b/pkg/common/convert/user.go @@ -17,8 +17,8 @@ package convert import ( "time" - "github.com/OpenIMSDK/protocol/sdkws" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/sdkws" ) func UsersDB2Pb(users []*relationtb.UserModel) []*sdkws.UserInfo { diff --git a/pkg/common/convert/user_test.go b/pkg/common/convert/user_test.go index a24efb53c..88eb812d2 100644 --- a/pkg/common/convert/user_test.go +++ b/pkg/common/convert/user_test.go @@ -18,7 +18,7 @@ import ( "reflect" "testing" - "github.com/OpenIMSDK/protocol/sdkws" + "github.com/openimsdk/protocol/sdkws" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" ) diff --git a/pkg/common/db/cache/black.go b/pkg/common/db/cache/black.go index a5304b4b0..615f2cbf1 100644 --- a/pkg/common/db/cache/black.go +++ b/pkg/common/db/cache/black.go @@ -18,11 +18,11 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/log" "github.com/dtm-labs/rockscache" "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" "github.com/openimsdk/open-im-server/v3/pkg/common/config" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/log" "github.com/redis/go-redis/v9" ) @@ -48,10 +48,10 @@ type BlackCacheRedis struct { blackDB relationtb.BlackModelInterface } -func NewBlackCacheRedis(rdb redis.UniversalClient, blackDB relationtb.BlackModelInterface, options rockscache.Options) BlackCache { +func NewBlackCacheRedis(rdb redis.UniversalClient, localCache *config.LocalCache, blackDB relationtb.BlackModelInterface, options rockscache.Options) BlackCache { rcClient := rockscache.NewClient(rdb, options) mc := NewMetaCacheRedis(rcClient) - b := config.Config.LocalCache.Friend + b := localCache.Friend log.ZDebug(context.Background(), "black local cache init", "Topic", b.Topic, "SlotNum", b.SlotNum, "SlotSize", b.SlotSize, "enable", b.Enable()) mc.SetTopic(b.Topic) mc.SetRawRedisClient(rdb) diff --git a/pkg/common/db/cache/config.go b/pkg/common/db/cache/config.go index c760e0c03..bb5bd449b 100644 --- a/pkg/common/db/cache/config.go +++ b/pkg/common/db/cache/config.go @@ -27,29 +27,26 @@ var ( subscribe map[string][]string ) -func getPublishKey(topic string, key []string) []string { - if topic == "" || len(key) == 0 { - return nil - } +func InitLocalCache(localCache *config.LocalCache) { once.Do(func() { list := []struct { - Local config.LocalCache + Local config.CacheConfig Keys []string }{ { - Local: config.Config.LocalCache.User, + Local: localCache.User, Keys: []string{cachekey.UserInfoKey, cachekey.UserGlobalRecvMsgOptKey}, }, { - Local: config.Config.LocalCache.Group, + Local: localCache.Group, Keys: []string{cachekey.GroupMemberIDsKey, cachekey.GroupInfoKey, cachekey.GroupMemberInfoKey}, }, { - Local: config.Config.LocalCache.Friend, + Local: localCache.Friend, Keys: []string{cachekey.FriendIDsKey, cachekey.BlackIDsKey}, }, { - Local: config.Config.LocalCache.Conversation, + Local: localCache.Conversation, Keys: []string{cachekey.ConversationKey, cachekey.ConversationIDsKey, cachekey.ConversationNotReceiveMessageUserIDsKey}, }, } @@ -60,6 +57,12 @@ func getPublishKey(topic string, key []string) []string { } } }) +} + +func getPublishKey(topic string, key []string) []string { + if topic == "" || len(key) == 0 { + return nil + } prefix, ok := subscribe[topic] if !ok { return nil diff --git a/pkg/common/db/cache/conversation.go b/pkg/common/db/cache/conversation.go index c8f752cd3..bd189f2a9 100644 --- a/pkg/common/db/cache/conversation.go +++ b/pkg/common/db/cache/conversation.go @@ -20,24 +20,25 @@ import ( "strings" "time" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" "github.com/dtm-labs/rockscache" "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" "github.com/openimsdk/open-im-server/v3/pkg/common/config" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/encrypt" "github.com/redis/go-redis/v9" ) const ( - //conversationKey = "CONVERSATION:" - //conversationIDsKey = "CONVERSATION_IDS:" - //conversationIDsHashKey = "CONVERSATION_IDS_HASH:" - //conversationHasReadSeqKey = "CONVERSATION_HAS_READ_SEQ:" - //recvMsgOptKey = "RECV_MSG_OPT:" - //superGroupRecvMsgNotNotifyUserIDsKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS:" - //superGroupRecvMsgNotNotifyUserIDsHashKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS_HASH:" - //conversationNotReceiveMessageUserIDsKey = "CONVERSATION_NOT_RECEIVE_MESSAGE_USER_IDS:". + // ConversationKey = "CONVERSATION:" + // conversationIDsKey = "CONVERSATION_IDS:" + // conversationIDsHashKey = "CONVERSATION_IDS_HASH:" + // conversationHasReadSeqKey = "CONVERSATION_HAS_READ_SEQ:" + // recvMsgOptKey = "RECV_MSG_OPT:" + // superGroupRecvMsgNotNotifyUserIDsKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS:" + // superGroupRecvMsgNotNotifyUserIDsHashKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS_HASH:" + // conversationNotReceiveMessageUserIDsKey = "CONVERSATION_NOT_RECEIVE_MESSAGE_USER_IDS:". conversationExpireTime = time.Second * 60 * 60 * 12 ) @@ -66,13 +67,13 @@ type ConversationCache interface { GetUserRecvMsgOpt(ctx context.Context, ownerUserID, conversationID string) (opt int, err error) DelUserRecvMsgOpt(ownerUserID, conversationID string) ConversationCache // get one super group recv msg but do not notification userID list - //GetSuperGroupRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) (userIDs []string, err error) + // GetSuperGroupRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) (userIDs []string, err error) DelSuperGroupRecvMsgNotNotifyUserIDs(groupID string) ConversationCache // get one super group recv msg but do not notification userID list hash - //GetSuperGroupRecvMsgNotNotifyUserIDsHash(ctx context.Context, groupID string) (hash uint64, err error) + // GetSuperGroupRecvMsgNotNotifyUserIDsHash(ctx context.Context, groupID string) (hash uint64, err error) DelSuperGroupRecvMsgNotNotifyUserIDsHash(groupID string) ConversationCache - //GetUserAllHasReadSeqs(ctx context.Context, ownerUserID string) (map[string]int64, error) + // GetUserAllHasReadSeqs(ctx context.Context, ownerUserID string) (map[string]int64, error) DelUserAllHasReadSeqs(ownerUserID string, conversationIDs ...string) ConversationCache GetConversationsByConversationID(ctx context.Context, @@ -82,10 +83,10 @@ type ConversationCache interface { DelConversationNotReceiveMessageUserIDs(conversationIDs ...string) ConversationCache } -func NewConversationRedis(rdb redis.UniversalClient, opts rockscache.Options, db relationtb.ConversationModelInterface) ConversationCache { +func NewConversationRedis(rdb redis.UniversalClient, localCache *config.LocalCache, opts rockscache.Options, db relationtb.ConversationModelInterface) ConversationCache { rcClient := rockscache.NewClient(rdb, opts) mc := NewMetaCacheRedis(rcClient) - c := config.Config.LocalCache.Conversation + c := localCache.Conversation log.ZDebug(context.Background(), "black local cache init", "Topic", c.Topic, "SlotNum", c.SlotNum, "SlotSize", c.SlotSize, "enable", c.Enable()) mc.SetTopic(c.Topic) mc.SetRawRedisClient(rdb) @@ -104,11 +105,11 @@ type ConversationRedisCache struct { expireTime time.Duration } -//func NewNewConversationRedis( +// func NewNewConversationRedis( // rdb redis.UniversalClient, // conversationDB *relation.ConversationGorm, // options rockscache.Options, -//) ConversationCache { +// ) ConversationCache { // rcClient := rockscache.NewClient(rdb, options) // // return &ConversationRedisCache{ @@ -188,9 +189,9 @@ func (c *ConversationRedisCache) GetUserConversationIDsHash(ctx context.Context, if err != nil { return 0, err } - utils.Sort(conversationIDs, true) + datautil.Sort(conversationIDs, true) bi := big.NewInt(0) - bi.SetString(utils.Md5(strings.Join(conversationIDs, ";"))[0:8], 16) + bi.SetString(encrypt.Md5(strings.Join(conversationIDs, ";"))[0:8], 16) return bi.Uint64(), nil }, ) @@ -232,15 +233,15 @@ func (c *ConversationRedisCache) DelConversations(ownerUserID string, conversati // } // } -// return 0, errors.New("not found key:" + key + " in keys") +// return 0, errs.New("not found key:" + key + " in keys") // } func (c *ConversationRedisCache) GetConversations(ctx context.Context, ownerUserID string, conversationIDs []string) ([]*relationtb.ConversationModel, error) { - //var keys []string - //for _, conversarionID := range conversationIDs { + // var keys []string + // for _, conversarionID := range conversationIDs { // keys = append(keys, c.getConversationKey(ownerUserID, conversarionID)) //} - //return batchGetCache( + // return batchGetCache( // ctx, // c.rcClient, // keys, @@ -262,11 +263,11 @@ func (c *ConversationRedisCache) GetUserAllConversations(ctx context.Context, ow if err != nil { return nil, err } - //var keys []string - //for _, conversarionID := range conversationIDs { + // var keys []string + // for _, conversarionID := range conversationIDs { // keys = append(keys, c.getConversationKey(ownerUserID, conversarionID)) //} - //return batchGetCache( + // return batchGetCache( // ctx, // c.rcClient, // keys, @@ -285,7 +286,7 @@ func (c *ConversationRedisCache) GetUserRecvMsgOpt(ctx context.Context, ownerUse }) } -//func (c *ConversationRedisCache) GetSuperGroupRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) (userIDs []string, err error) { +// func (c *ConversationRedisCache) GetSuperGroupRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) (userIDs []string, err error) { // return getCache(ctx, c.rcClient, c.getSuperGroupRecvNotNotifyUserIDsKey(groupID), c.expireTime, func(ctx context.Context) (userIDs []string, err error) { // return c.conversationDB.FindSuperGroupRecvMsgNotNotifyUserIDs(ctx, groupID) // }) @@ -316,7 +317,7 @@ func (c *ConversationRedisCache) DelSuperGroupRecvMsgNotNotifyUserIDs(groupID st return cache } -//func (c *ConversationRedisCache) GetSuperGroupRecvMsgNotNotifyUserIDsHash(ctx context.Context, groupID string) (hash uint64, err error) { +// func (c *ConversationRedisCache) GetSuperGroupRecvMsgNotNotifyUserIDsHash(ctx context.Context, groupID string) (hash uint64, err error) { // return getCache(ctx, c.rcClient, c.getSuperGroupRecvNotNotifyUserIDsHashKey(groupID), c.expireTime, func(ctx context.Context) (hash uint64, err error) { // userIDs, err := c.GetSuperGroupRecvMsgNotNotifyUserIDs(ctx, groupID) // if err != nil { @@ -337,30 +338,6 @@ func (c *ConversationRedisCache) DelSuperGroupRecvMsgNotNotifyUserIDsHash(groupI return cache } -/* func (c *ConversationRedisCache) getUserAllHasReadSeqsIndex(conversationID string, conversationIDs []string) (int, error) { - for _i, _conversationID := range conversationIDs { - if _conversationID == conversationID { - return _i, nil - } - } - - return 0, errors.New("not found key:" + conversationID + " in keys") -} */ - -/* func (c *ConversationRedisCache) GetUserAllHasReadSeqs(ctx context.Context, ownerUserID string) (map[string]int64, error) { - conversationIDs, err := c.GetUserConversationIDs(ctx, ownerUserID) - if err != nil { - return nil, err - } - var keys []string - for _, conversarionID := range conversationIDs { - keys = append(keys, c.getConversationHasReadSeqKey(ownerUserID, conversarionID)) - } - return batchGetCacheMap(ctx, c.rcClient, keys, conversationIDs, c.expireTime, c.getUserAllHasReadSeqsIndex, func(ctx context.Context) (map[string]int64, error) { - return c.conversationDB.GetUserAllHasReadSeqs(ctx, ownerUserID) - }) -} */ - func (c *ConversationRedisCache) DelUserAllHasReadSeqs(ownerUserID string, conversationIDs ...string) ConversationCache { cache := c.NewCache() for _, conversationID := range conversationIDs { diff --git a/pkg/common/db/cache/doc.go b/pkg/common/db/cache/doc.go index 77651e3de..a5c237249 100644 --- a/pkg/common/db/cache/doc.go +++ b/pkg/common/db/cache/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/common/db/cache/friend.go b/pkg/common/db/cache/friend.go index 2d20705ef..73fe5ea69 100644 --- a/pkg/common/db/cache/friend.go +++ b/pkg/common/db/cache/friend.go @@ -18,20 +18,20 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" "github.com/dtm-labs/rockscache" "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" "github.com/openimsdk/open-im-server/v3/pkg/common/config" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" "github.com/redis/go-redis/v9" ) const ( friendExpireTime = time.Second * 60 * 60 * 12 - //friendIDsKey = "FRIEND_IDS:" - //TwoWayFriendsIDsKey = "COMMON_FRIENDS_IDS:" - //friendKey = "FRIEND_INFO:". + // FriendIDsKey = "FRIEND_IDS:" + // TwoWayFriendsIDsKey = "COMMON_FRIENDS_IDS:" + // friendKey = "FRIEND_INFO:". ) // FriendCache is an interface for caching friend-related data. @@ -58,11 +58,11 @@ type FriendCacheRedis struct { } // NewFriendCacheRedis creates a new instance of FriendCacheRedis. -func NewFriendCacheRedis(rdb redis.UniversalClient, friendDB relationtb.FriendModelInterface, +func NewFriendCacheRedis(rdb redis.UniversalClient, localCache *config.LocalCache, friendDB relationtb.FriendModelInterface, options rockscache.Options) FriendCache { rcClient := rockscache.NewClient(rdb, options) mc := NewMetaCacheRedis(rcClient) - f := config.Config.LocalCache.Friend + f := localCache.Friend log.ZDebug(context.Background(), "friend local cache init", "Topic", f.Topic, "SlotNum", f.SlotNum, "SlotSize", f.SlotSize, "enable", f.Enable()) mc.SetTopic(f.Topic) mc.SetRawRedisClient(rdb) @@ -129,7 +129,7 @@ func (f *FriendCacheRedis) GetTwoWayFriendIDs(ctx context.Context, ownerUserID s if err != nil { return nil, err } - if utils.IsContain(ownerUserID, friendFriendID) { + if datautil.Contain(ownerUserID, friendFriendID...) { twoWayFriendIDs = append(twoWayFriendIDs, ownerUserID) } } diff --git a/pkg/common/db/cache/group.go b/pkg/common/db/cache/group.go index 7022461cb..66c2d65c4 100644 --- a/pkg/common/db/cache/group.go +++ b/pkg/common/db/cache/group.go @@ -19,26 +19,19 @@ import ( "fmt" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" "github.com/dtm-labs/rockscache" "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" "github.com/openimsdk/open-im-server/v3/pkg/common/config" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" "github.com/redis/go-redis/v9" ) const ( groupExpireTime = time.Second * 60 * 60 * 12 - //groupInfoKey = "GROUP_INFO:" - //groupMemberIDsKey = "GROUP_MEMBER_IDS:" - //groupMembersHashKey = "GROUP_MEMBERS_HASH2:" - //groupMemberInfoKey = "GROUP_MEMBER_INFO:" - //joinedGroupsKey = "JOIN_GROUPS_KEY:" - //groupMemberNumKey = "GROUP_MEMBER_NUM_CACHE:" - //groupRoleLevelMemberIDsKey = "GROUP_ROLE_LEVEL_MEMBER_IDS:". ) type GroupHash interface { @@ -94,6 +87,7 @@ type GroupCacheRedis struct { func NewGroupCacheRedis( rdb redis.UniversalClient, + localCache *config.LocalCache, groupDB relationtb.GroupModelInterface, groupMemberDB relationtb.GroupMemberModelInterface, groupRequestDB relationtb.GroupRequestModelInterface, @@ -102,7 +96,7 @@ func NewGroupCacheRedis( ) GroupCache { rcClient := rockscache.NewClient(rdb, opts) mc := NewMetaCacheRedis(rcClient) - g := config.Config.LocalCache.Group + g := localCache.Group mc.SetTopic(g.Topic) log.ZDebug(context.Background(), "group local cache init", "Topic", g.Topic, "SlotNum", g.SlotNum, "SlotSize", g.SlotSize, "enable", g.Enable()) mc.SetRawRedisClient(rdb) @@ -227,7 +221,7 @@ func (g *GroupCacheRedis) DelGroupAllRoleLevel(groupID string) GroupCache { func (g *GroupCacheRedis) GetGroupMembersHash(ctx context.Context, groupID string) (hashCode uint64, err error) { if g.groupHash == nil { - return 0, errs.ErrInternalServer.Wrap("group hash is nil") + return 0, errs.ErrInternalServer.WrapMsg("group hash is nil") } return getCache(ctx, g.rcClient, g.getGroupMembersHashKey(groupID), g.expireTime, func(ctx context.Context) (uint64, error) { return g.groupHash.GetGroupHash(ctx, groupID) @@ -236,7 +230,7 @@ func (g *GroupCacheRedis) GetGroupMembersHash(ctx context.Context, groupID strin func (g *GroupCacheRedis) GetGroupMemberHashMap(ctx context.Context, groupIDs []string) (map[string]*relationtb.GroupSimpleUserID, error) { if g.groupHash == nil { - return nil, errs.ErrInternalServer.Wrap("group hash is nil") + return nil, errs.ErrInternalServer.WrapMsg("group hash is nil") } res := make(map[string]*relationtb.GroupSimpleUserID) for _, groupID := range groupIDs { @@ -244,7 +238,7 @@ func (g *GroupCacheRedis) GetGroupMemberHashMap(ctx context.Context, groupIDs [] if err != nil { return nil, err } - log.ZInfo(ctx, "GetGroupMemberHashMap", "groupID", groupID, "hash", hash) + log.ZDebug(ctx, "GetGroupMemberHashMap", "groupID", groupID, "hash", hash) num, err := g.GetGroupMemberNum(ctx, groupID) if err != nil { return nil, err @@ -330,11 +324,11 @@ func (g *GroupCacheRedis) GetGroupMembersPage( return 0, nil, err } if userIDs != nil { - userIDs = utils.BothExist(userIDs, groupMemberIDs) + userIDs = datautil.BothExist(userIDs, groupMemberIDs) } else { userIDs = groupMemberIDs } - groupMembers, err = g.GetGroupMembersInfo(ctx, groupID, utils.Paginate(userIDs, int(showNumber), int(showNumber))) + groupMembers, err = g.GetGroupMembersInfo(ctx, groupID, datautil.Paginate(userIDs, int(showNumber), int(showNumber))) return uint32(len(userIDs)), groupMembers, err } @@ -390,7 +384,7 @@ func (g *GroupCacheRedis) GetGroupOwner(ctx context.Context, groupID string) (*r return nil, err } if len(members) == 0 { - return nil, errs.ErrRecordNotFound.Wrap(fmt.Sprintf("group %s owner not found", groupID)) + return nil, errs.ErrRecordNotFound.WrapMsg(fmt.Sprintf("group %s owner not found", groupID)) } return members[0], nil } diff --git a/pkg/common/db/cache/init_redis.go b/pkg/common/db/cache/init_redis.go deleted file mode 100644 index 8f4e2c592..000000000 --- a/pkg/common/db/cache/init_redis.go +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cache - -import ( - "context" - "errors" - "fmt" - "os" - "strings" - "time" - - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mw/specialerror" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/redis/go-redis/v9" -) - -var ( - // singleton pattern. - redisClient redis.UniversalClient -) - -const ( - maxRetry = 10 // number of retries -) - -// NewRedis Initialize redis connection. -func NewRedis(config *config.GlobalConfig) (redis.UniversalClient, error) { - if redisClient != nil { - return redisClient, nil - } - - // Read configuration from environment variables - overrideConfigFromEnv(config) - - if len(config.Redis.Address) == 0 { - return nil, errs.Wrap(errors.New("redis address is empty")) - } - specialerror.AddReplace(redis.Nil, errs.ErrRecordNotFound) - var rdb redis.UniversalClient - if len(config.Redis.Address) > 1 || config.Redis.ClusterMode { - rdb = redis.NewClusterClient(&redis.ClusterOptions{ - Addrs: config.Redis.Address, - Username: config.Redis.Username, - Password: config.Redis.Password, // no password set - PoolSize: 50, - MaxRetries: maxRetry, - }) - } else { - rdb = redis.NewClient(&redis.Options{ - Addr: config.Redis.Address[0], - Username: config.Redis.Username, - Password: config.Redis.Password, - DB: 0, // use default DB - PoolSize: 100, // connection pool size - MaxRetries: maxRetry, - }) - } - - var err error - ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) - defer cancel() - err = rdb.Ping(ctx).Err() - if err != nil { - errMsg := fmt.Sprintf("address:%s, username:%s, password:%s, clusterMode:%t, enablePipeline:%t", config.Redis.Address, config.Redis.Username, - config.Redis.Password, config.Redis.ClusterMode, config.Redis.EnablePipeline) - return nil, errs.Wrap(err, errMsg) - } - redisClient = rdb - return rdb, err -} - -// overrideConfigFromEnv overrides configuration fields with environment variables if present. -func overrideConfigFromEnv(config *config.GlobalConfig) { - if envAddr := os.Getenv("REDIS_ADDRESS"); envAddr != "" { - if envPort := os.Getenv("REDIS_PORT"); envPort != "" { - addresses := strings.Split(envAddr, ",") - for i, addr := range addresses { - addresses[i] = addr + ":" + envPort - } - config.Redis.Address = addresses - } else { - config.Redis.Address = strings.Split(envAddr, ",") - } - } - - if envUser := os.Getenv("REDIS_USERNAME"); envUser != "" { - config.Redis.Username = envUser - } - - if envPass := os.Getenv("REDIS_PASSWORD"); envPass != "" { - config.Redis.Password = envPass - } -} diff --git a/pkg/common/db/cache/meta_cache.go b/pkg/common/db/cache/meta_cache.go index 431b7c867..e633e4fbd 100644 --- a/pkg/common/db/cache/meta_cache.go +++ b/pkg/common/db/cache/meta_cache.go @@ -17,15 +17,14 @@ package cache import ( "context" "encoding/json" - "errors" "fmt" "time" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mw/specialerror" - "github.com/OpenIMSDK/tools/utils" "github.com/dtm-labs/rockscache" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mw/specialerror" + "github.com/openimsdk/tools/utils/datautil" "github.com/redis/go-redis/v9" ) @@ -35,7 +34,7 @@ const ( retryInterval = time.Millisecond * 100 ) -var errIndex = errors.New("err index") +var errIndex = errs.New("err index") type metaCache interface { ExecDel(ctx context.Context, distinct ...bool) error @@ -74,7 +73,7 @@ func (m *metaCacheRedis) Copy() metaCache { keys: keys, maxRetryTimes: m.maxRetryTimes, retryInterval: m.retryInterval, - redisClient: redisClient, + redisClient: m.redisClient, } } @@ -88,7 +87,7 @@ func (m *metaCacheRedis) SetRawRedisClient(cli redis.UniversalClient) { func (m *metaCacheRedis) ExecDel(ctx context.Context, distinct ...bool) error { if len(distinct) > 0 && distinct[0] { - m.keys = utils.Distinct(m.keys) + m.keys = datautil.Distinct(m.keys) } if len(m.keys) > 0 { log.ZDebug(ctx, "delete cache", "topic", m.topic, "keys", m.keys) @@ -150,7 +149,7 @@ func getCache[T any](ctx context.Context, rcClient *rockscache.Client, key strin } bs, err := json.Marshal(t) if err != nil { - return "", errs.Wrap(err, "marshal failed") + return "", errs.WrapMsg(err, "marshal failed") } write = true @@ -163,12 +162,12 @@ func getCache[T any](ctx context.Context, rcClient *rockscache.Client, key strin return t, nil } if v == "" { - return t, errs.ErrRecordNotFound.Wrap("cache is not found") + return t, errs.ErrRecordNotFound.WrapMsg("cache is not found") } err = json.Unmarshal([]byte(v), &t) if err != nil { errInfo := fmt.Sprintf("cache json.Unmarshal failed, key:%s, value:%s, expire:%s", key, v, expire) - return t, errs.Wrap(err, errInfo) + return t, errs.WrapMsg(err, errInfo) } return t, nil @@ -240,14 +239,14 @@ func batchGetCache2[T any, K comparable]( return res, nil } -//func batchGetCacheMap[T any]( +// func batchGetCacheMap[T any]( // ctx context.Context, // rcClient *rockscache.Client, // keys, originKeys []string, // expire time.Duration, // keyIndexFn func(s string, keys []string) (int, error), // fn func(ctx context.Context) (map[string]T, error), -//) (map[string]T, error) { +// ) (map[string]T, error) { // batchMap, err := rcClient.FetchBatch2(ctx, keys, expire, func(idxs []int) (m map[int]string, err error) { // tArrays, err := fn(ctx) // if err != nil { diff --git a/pkg/common/db/cache/msg.go b/pkg/common/db/cache/msg.go index b8e9dbc77..e8a86b71b 100644 --- a/pkg/common/db/cache/msg.go +++ b/pkg/common/db/cache/msg.go @@ -20,30 +20,28 @@ import ( "strconv" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" "github.com/gogo/protobuf/jsonpb" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/stringutil" "github.com/redis/go-redis/v9" "golang.org/x/sync/errgroup" ) +const msgCacheTimeout = 86400 * time.Second + const ( maxSeq = "MAX_SEQ:" minSeq = "MIN_SEQ:" conversationUserMinSeq = "CON_USER_MIN_SEQ:" hasReadSeq = "HAS_READ_SEQ:" - //appleDeviceToken = "DEVICE_TOKEN". getuiToken = "GETUI_TOKEN" getuiTaskID = "GETUI_TASK_ID" - //signalCache = "SIGNAL_CACHE:" - //signalListCache = "SIGNAL_LIST_CACHE:". - FCM_TOKEN = "FCM_TOKEN:" + FCM_TOKEN = "FCM_TOKEN:" messageCache = "MESSAGE_CACHE:" messageDelUserList = "MESSAGE_DEL_USER_LIST:" @@ -51,56 +49,17 @@ const ( sendMsgFailedFlag = "SEND_MSG_FAILED_FLAG:" userBadgeUnreadCountSum = "USER_BADGE_UNREAD_COUNT_SUM:" exTypeKeyLocker = "EX_LOCK:" - uidPidToken = "UID_PID_TOKEN_STATUS:" ) var concurrentLimit = 3 -type SeqCache interface { - SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error - GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) - GetMaxSeq(ctx context.Context, conversationID string) (int64, error) - SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error - SetMinSeqs(ctx context.Context, seqs map[string]int64) error - GetMinSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) - GetMinSeq(ctx context.Context, conversationID string) (int64, error) - GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error) - GetConversationUserMinSeqs(ctx context.Context, conversationID string, userIDs []string) (map[string]int64, error) - SetConversationUserMinSeq(ctx context.Context, conversationID string, userID string, minSeq int64) error - // seqs map: key userID value minSeq - SetConversationUserMinSeqs(ctx context.Context, conversationID string, seqs map[string]int64) (err error) - // seqs map: key conversationID value minSeq - SetUserConversationsMinSeqs(ctx context.Context, userID string, seqs map[string]int64) error - // has read seq - SetHasReadSeq(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error - // k: user, v: seq - SetHasReadSeqs(ctx context.Context, conversationID string, hasReadSeqs map[string]int64) error - // k: conversation, v :seq - UserSetHasReadSeqs(ctx context.Context, userID string, hasReadSeqs map[string]int64) error - GetHasReadSeqs(ctx context.Context, userID string, conversationIDs []string) (map[string]int64, error) - GetHasReadSeq(ctx context.Context, userID string, conversationID string) (int64, error) -} - -type thirdCache interface { - SetFcmToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) (err error) - GetFcmToken(ctx context.Context, account string, platformID int) (string, error) - DelFcmToken(ctx context.Context, account string, platformID int) error - IncrUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) - SetUserBadgeUnreadCountSum(ctx context.Context, userID string, value int) error - GetUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) - SetGetuiToken(ctx context.Context, token string, expireTime int64) error - GetGetuiToken(ctx context.Context) (string, error) - SetGetuiTaskID(ctx context.Context, taskID string, expireTime int64) error - GetGetuiTaskID(ctx context.Context) (string, error) -} - -type MsgModel interface { - SeqCache - thirdCache - AddTokenFlag(ctx context.Context, userID string, platformID int, token string, flag int) error - GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error) - SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error - DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error +//type MsgModel interface { +// SeqCache +// ThirdCache +// MsgCache +//} + +type MsgCache interface { GetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsg []*sdkws.MsgData, failedSeqList []int64, err error) SetMessageToCache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (int, error) UserDeleteMsgs(ctx context.Context, conversationID string, seqs []int64, userID string) error @@ -121,303 +80,31 @@ type MsgModel interface { UnLockMessageTypeKey(ctx context.Context, clientMsgID string, TypeKey string) error } -func NewMsgCacheModel(client redis.UniversalClient, config *config.GlobalConfig) MsgModel { - return &msgCache{rdb: client, config: config} +//func NewMsgCacheModel(client redis.UniversalClient, msgCacheTimeout int, redisConf *config.Redis) MsgModel { +// return &msgCache{rdb: client, msgCacheTimeout: msgCacheTimeout, redisConf: redisConf} +//} + +func NewMsgCache(client redis.UniversalClient, redisEnablePipeline bool) MsgCache { + return &msgCache{rdb: client, msgCacheTimeout: msgCacheTimeout, redisEnablePipeline: redisEnablePipeline} } type msgCache struct { metaCache - rdb redis.UniversalClient - config *config.GlobalConfig -} - -func (c *msgCache) getMaxSeqKey(conversationID string) string { - return maxSeq + conversationID -} - -func (c *msgCache) getMinSeqKey(conversationID string) string { - return minSeq + conversationID -} - -func (c *msgCache) getHasReadSeqKey(conversationID string, userID string) string { - return hasReadSeq + userID + ":" + conversationID -} - -func (c *msgCache) getConversationUserMinSeqKey(conversationID, userID string) string { - return conversationUserMinSeq + conversationID + "u:" + userID -} - -func (c *msgCache) setSeq(ctx context.Context, conversationID string, seq int64, getkey func(conversationID string) string) error { - return errs.Wrap(c.rdb.Set(ctx, getkey(conversationID), seq, 0).Err()) -} - -func (c *msgCache) getSeq(ctx context.Context, conversationID string, getkey func(conversationID string) string) (int64, error) { - val, err := c.rdb.Get(ctx, getkey(conversationID)).Int64() - if err != nil { - return 0, errs.Wrap(err) - } - return val, nil -} - -func (c *msgCache) getSeqs(ctx context.Context, items []string, getkey func(s string) string) (m map[string]int64, err error) { - m = make(map[string]int64, len(items)) - for i, v := range items { - res, err := c.rdb.Get(ctx, getkey(v)).Result() - if err != nil && err != redis.Nil { - return nil, errs.Wrap(err) - } - val := utils.StringToInt64(res) - if val != 0 { - m[items[i]] = val - } - } - - return m, nil -} - -func (c *msgCache) SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error { - return c.setSeq(ctx, conversationID, maxSeq, c.getMaxSeqKey) -} - -func (c *msgCache) GetMaxSeqs(ctx context.Context, conversationIDs []string) (m map[string]int64, err error) { - return c.getSeqs(ctx, conversationIDs, c.getMaxSeqKey) -} - -func (c *msgCache) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) { - return c.getSeq(ctx, conversationID, c.getMaxSeqKey) -} - -func (c *msgCache) SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error { - return c.setSeq(ctx, conversationID, minSeq, c.getMinSeqKey) -} - -func (c *msgCache) setSeqs(ctx context.Context, seqs map[string]int64, getkey func(key string) string) error { - for conversationID, seq := range seqs { - if err := c.rdb.Set(ctx, getkey(conversationID), seq, 0).Err(); err != nil { - return errs.Wrap(err) - } - } - return nil -} - -func (c *msgCache) SetMinSeqs(ctx context.Context, seqs map[string]int64) error { - return c.setSeqs(ctx, seqs, c.getMinSeqKey) -} - -func (c *msgCache) GetMinSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) { - return c.getSeqs(ctx, conversationIDs, c.getMinSeqKey) -} - -func (c *msgCache) GetMinSeq(ctx context.Context, conversationID string) (int64, error) { - return c.getSeq(ctx, conversationID, c.getMinSeqKey) -} - -func (c *msgCache) GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error) { - val, err := c.rdb.Get(ctx, c.getConversationUserMinSeqKey(conversationID, userID)).Int64() - if err != nil { - return 0, errs.Wrap(err) - } - return val, nil -} - -func (c *msgCache) GetConversationUserMinSeqs(ctx context.Context, conversationID string, userIDs []string) (m map[string]int64, err error) { - return c.getSeqs(ctx, userIDs, func(userID string) string { - return c.getConversationUserMinSeqKey(conversationID, userID) - }) -} - -func (c *msgCache) SetConversationUserMinSeq(ctx context.Context, conversationID string, userID string, minSeq int64) error { - return errs.Wrap(c.rdb.Set(ctx, c.getConversationUserMinSeqKey(conversationID, userID), minSeq, 0).Err()) -} - -func (c *msgCache) SetConversationUserMinSeqs(ctx context.Context, conversationID string, seqs map[string]int64) (err error) { - return c.setSeqs(ctx, seqs, func(userID string) string { - return c.getConversationUserMinSeqKey(conversationID, userID) - }) -} - -func (c *msgCache) SetUserConversationsMinSeqs(ctx context.Context, userID string, seqs map[string]int64) (err error) { - return c.setSeqs(ctx, seqs, func(conversationID string) string { - return c.getConversationUserMinSeqKey(conversationID, userID) - }) -} - -func (c *msgCache) SetHasReadSeq(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error { - return errs.Wrap(c.rdb.Set(ctx, c.getHasReadSeqKey(conversationID, userID), hasReadSeq, 0).Err()) -} - -func (c *msgCache) SetHasReadSeqs(ctx context.Context, conversationID string, hasReadSeqs map[string]int64) error { - return c.setSeqs(ctx, hasReadSeqs, func(userID string) string { - return c.getHasReadSeqKey(conversationID, userID) - }) -} - -func (c *msgCache) UserSetHasReadSeqs(ctx context.Context, userID string, hasReadSeqs map[string]int64) error { - return c.setSeqs(ctx, hasReadSeqs, func(conversationID string) string { - return c.getHasReadSeqKey(conversationID, userID) - }) -} - -func (c *msgCache) GetHasReadSeqs(ctx context.Context, userID string, conversationIDs []string) (map[string]int64, error) { - return c.getSeqs(ctx, conversationIDs, func(conversationID string) string { - return c.getHasReadSeqKey(conversationID, userID) - }) -} - -func (c *msgCache) GetHasReadSeq(ctx context.Context, userID string, conversationID string) (int64, error) { - val, err := c.rdb.Get(ctx, c.getHasReadSeqKey(conversationID, userID)).Int64() - if err != nil { - return 0, err - } - return val, nil -} - -func (c *msgCache) AddTokenFlag(ctx context.Context, userID string, platformID int, token string, flag int) error { - key := uidPidToken + userID + ":" + constant.PlatformIDToName(platformID) - return errs.Wrap(c.rdb.HSet(ctx, key, token, flag).Err()) -} - -func (c *msgCache) GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error) { - key := uidPidToken + userID + ":" + constant.PlatformIDToName(platformID) - m, err := c.rdb.HGetAll(ctx, key).Result() - if err != nil { - return nil, errs.Wrap(err) - } - mm := make(map[string]int) - for k, v := range m { - mm[k] = utils.StringToInt(v) - } - - return mm, nil -} - -func (c *msgCache) SetTokenMapByUidPid(ctx context.Context, userID string, platform int, m map[string]int) error { - key := uidPidToken + userID + ":" + constant.PlatformIDToName(platform) - mm := make(map[string]any) - for k, v := range m { - mm[k] = v - } - - return errs.Wrap(c.rdb.HSet(ctx, key, mm).Err()) -} - -func (c *msgCache) DeleteTokenByUidPid(ctx context.Context, userID string, platform int, fields []string) error { - key := uidPidToken + userID + ":" + constant.PlatformIDToName(platform) - - return errs.Wrap(c.rdb.HDel(ctx, key, fields...).Err()) -} - -func (c *msgCache) getMessageCacheKey(conversationID string, seq int64) string { - return messageCache + conversationID + "_" + strconv.Itoa(int(seq)) + rdb redis.UniversalClient + msgCacheTimeout time.Duration + redisEnablePipeline bool } func (c *msgCache) allMessageCacheKey(conversationID string) string { return messageCache + conversationID + "_*" } -func (c *msgCache) GetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) { - if c.config.Redis.EnablePipeline { - return c.PipeGetMessagesBySeq(ctx, conversationID, seqs) - } - - return c.ParallelGetMessagesBySeq(ctx, conversationID, seqs) -} - -func (c *msgCache) PipeGetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) { - pipe := c.rdb.Pipeline() - - results := []*redis.StringCmd{} - for _, seq := range seqs { - results = append(results, pipe.Get(ctx, c.getMessageCacheKey(conversationID, seq))) - } - - _, err = pipe.Exec(ctx) - if err != nil && err != redis.Nil { - return seqMsgs, failedSeqs, errs.Wrap(err, "pipe.get") - } - - for idx, res := range results { - seq := seqs[idx] - if res.Err() != nil { - log.ZError(ctx, "GetMessagesBySeq failed", err, "conversationID", conversationID, "seq", seq, "err", res.Err()) - failedSeqs = append(failedSeqs, seq) - continue - } - - msg := sdkws.MsgData{} - if err = msgprocessor.String2Pb(res.Val(), &msg); err != nil { - log.ZError(ctx, "GetMessagesBySeq Unmarshal failed", err, "res", res, "conversationID", conversationID, "seq", seq) - failedSeqs = append(failedSeqs, seq) - continue - } - - if msg.Status == constant.MsgDeleted { - failedSeqs = append(failedSeqs, seq) - continue - } - - seqMsgs = append(seqMsgs, &msg) - } - - return -} - -func (c *msgCache) ParallelGetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) { - type entry struct { - err error - msg *sdkws.MsgData - } - - wg := errgroup.Group{} - wg.SetLimit(concurrentLimit) - - results := make([]entry, len(seqs)) // set slice len/cap to length of seqs. - for idx, seq := range seqs { - // closure safe var - idx := idx - seq := seq - - wg.Go(func() error { - res, err := c.rdb.Get(ctx, c.getMessageCacheKey(conversationID, seq)).Result() - if err != nil { - log.ZError(ctx, "GetMessagesBySeq failed", err, "conversationID", conversationID, "seq", seq) - results[idx] = entry{err: err} - return nil - } - - msg := sdkws.MsgData{} - if err = msgprocessor.String2Pb(res, &msg); err != nil { - log.ZError(ctx, "GetMessagesBySeq Unmarshal failed", err, "res", res, "conversationID", conversationID, "seq", seq) - results[idx] = entry{err: err} - return nil - } - - if msg.Status == constant.MsgDeleted { - results[idx] = entry{err: err} - return nil - } - - results[idx] = entry{msg: &msg} - return nil - }) - } - - _ = wg.Wait() - - for idx, res := range results { - if res.err != nil { - failedSeqs = append(failedSeqs, seqs[idx]) - continue - } - - seqMsgs = append(seqMsgs, res.msg) - } - - return +func (c *msgCache) getMessageCacheKey(conversationID string, seq int64) string { + return messageCache + conversationID + "_" + strconv.Itoa(int(seq)) } func (c *msgCache) SetMessageToCache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (int, error) { - if c.config.Redis.EnablePipeline { + if c.redisEnablePipeline { return c.PipeSetMessageToCache(ctx, conversationID, msgs) } return c.ParallelSetMessageToCache(ctx, conversationID, msgs) @@ -432,7 +119,7 @@ func (c *msgCache) PipeSetMessageToCache(ctx context.Context, conversationID str } key := c.getMessageCacheKey(conversationID, msg.Seq) - _ = pipe.Set(ctx, key, s, time.Duration(c.config.MsgCacheTimeout)*time.Second) + _ = pipe.Set(ctx, key, s, c.msgCacheTimeout) } results, err := pipe.Exec(ctx) @@ -462,7 +149,7 @@ func (c *msgCache) ParallelSetMessageToCache(ctx context.Context, conversationID } key := c.getMessageCacheKey(conversationID, msg.Seq) - if err := c.rdb.Set(ctx, key, s, time.Duration(c.config.MsgCacheTimeout)*time.Second).Err(); err != nil { + if err := c.rdb.Set(ctx, key, s, c.msgCacheTimeout).Err(); err != nil { return errs.Wrap(err) } return nil @@ -471,7 +158,7 @@ func (c *msgCache) ParallelSetMessageToCache(ctx context.Context, conversationID err := wg.Wait() if err != nil { - return 0, errs.Wrap(err, "wg.Wait failed") + return 0, errs.WrapMsg(err, "wg.Wait failed") } return len(msgs), nil @@ -497,17 +184,17 @@ func (c *msgCache) UserDeleteMsgs(ctx context.Context, conversationID string, se if err != nil { return errs.Wrap(err) } - if err := c.rdb.Expire(ctx, delUserListKey, time.Duration(c.config.MsgCacheTimeout)*time.Second).Err(); err != nil { + if err := c.rdb.Expire(ctx, delUserListKey, c.msgCacheTimeout).Err(); err != nil { return errs.Wrap(err) } - if err := c.rdb.Expire(ctx, userDelListKey, time.Duration(c.config.MsgCacheTimeout)*time.Second).Err(); err != nil { + if err := c.rdb.Expire(ctx, userDelListKey, c.msgCacheTimeout).Err(); err != nil { return errs.Wrap(err) } } return nil - //pipe := c.rdb.Pipeline() - //for _, seq := range seqs { + // pipe := c.rdb.Pipeline() + // for _, seq := range seqs { // delUserListKey := c.getMessageDelUserListKey(conversationID, seq) // userDelListKey := c.getUserDelList(conversationID, userID) // err := pipe.SAdd(ctx, delUserListKey, userID).Err() @@ -525,8 +212,8 @@ func (c *msgCache) UserDeleteMsgs(ctx context.Context, conversationID string, se // return errs.Wrap(err) // } //} - //_, err := pipe.Exec(ctx) - //return errs.Wrap(err) + // _, err := pipe.Exec(ctx) + // return errs.Wrap(err) } func (c *msgCache) GetUserDelList(ctx context.Context, userID, conversationID string) (seqs []int64, err error) { @@ -536,7 +223,7 @@ func (c *msgCache) GetUserDelList(ctx context.Context, userID, conversationID st } seqs = make([]int64, len(result)) for i, v := range result { - seqs[i] = utils.StringToInt64(v) + seqs[i] = stringutil.StringToInt64(v) } return seqs, nil @@ -566,7 +253,7 @@ func (c *msgCache) DelUserDeleteMsgsList(ctx context.Context, conversationID str } } } - //for _, seq := range seqs { + // for _, seq := range seqs { // delUsers, err := c.rdb.SMembers(ctx, c.getMessageDelUserListKey(conversationID, seq)).Result() // if err != nil { // log.ZWarn(ctx, "DelUserDeleteMsgsList failed", err, "conversationID", conversationID, "seq", seq) @@ -605,7 +292,7 @@ func (c *msgCache) DelUserDeleteMsgsList(ctx context.Context, conversationID str } func (c *msgCache) DeleteMessages(ctx context.Context, conversationID string, seqs []int64) error { - if c.config.Redis.EnablePipeline { + if c.redisEnablePipeline { return c.PipeDeleteMessages(ctx, conversationID, seqs) } @@ -638,7 +325,7 @@ func (c *msgCache) PipeDeleteMessages(ctx context.Context, conversationID string results, err := pipe.Exec(ctx) if err != nil { - return errs.Wrap(err, "pipe.del") + return errs.WrapMsg(err, "pipe.del") } for _, res := range results { @@ -687,7 +374,7 @@ func (c *msgCache) DelMsgFromCache(ctx context.Context, userID string, seqs []in if err != nil { return errs.Wrap(err) } - if err := c.rdb.Set(ctx, key, s, time.Duration(c.config.MsgCacheTimeout)*time.Second).Err(); err != nil { + if err := c.rdb.Set(ctx, key, s, c.msgCacheTimeout).Err(); err != nil { return errs.Wrap(err) } } @@ -695,30 +382,6 @@ func (c *msgCache) DelMsgFromCache(ctx context.Context, userID string, seqs []in return nil } -func (c *msgCache) SetGetuiToken(ctx context.Context, token string, expireTime int64) error { - return errs.Wrap(c.rdb.Set(ctx, getuiToken, token, time.Duration(expireTime)*time.Second).Err()) -} - -func (c *msgCache) GetGetuiToken(ctx context.Context) (string, error) { - val, err := c.rdb.Get(ctx, getuiToken).Result() - if err != nil { - return "", errs.Wrap(err) - } - return val, nil -} - -func (c *msgCache) SetGetuiTaskID(ctx context.Context, taskID string, expireTime int64) error { - return errs.Wrap(c.rdb.Set(ctx, getuiTaskID, taskID, time.Duration(expireTime)*time.Second).Err()) -} - -func (c *msgCache) GetGetuiTaskID(ctx context.Context) (string, error) { - val, err := c.rdb.Get(ctx, getuiTaskID).Result() - if err != nil { - return "", errs.Wrap(err) - } - return val, nil -} - func (c *msgCache) SetSendMsgStatus(ctx context.Context, id string, status int32) error { return errs.Wrap(c.rdb.Set(ctx, sendMsgFailedFlag+id, status, time.Hour*24).Err()) } @@ -729,37 +392,6 @@ func (c *msgCache) GetSendMsgStatus(ctx context.Context, id string) (int32, erro return int32(result), errs.Wrap(err) } -func (c *msgCache) SetFcmToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) (err error) { - return errs.Wrap(c.rdb.Set(ctx, FCM_TOKEN+account+":"+strconv.Itoa(platformID), fcmToken, time.Duration(expireTime)*time.Second).Err()) -} - -func (c *msgCache) GetFcmToken(ctx context.Context, account string, platformID int) (string, error) { - val, err := c.rdb.Get(ctx, FCM_TOKEN+account+":"+strconv.Itoa(platformID)).Result() - if err != nil { - return "", errs.Wrap(err) - } - return val, nil -} - -func (c *msgCache) DelFcmToken(ctx context.Context, account string, platformID int) error { - return errs.Wrap(c.rdb.Del(ctx, FCM_TOKEN+account+":"+strconv.Itoa(platformID)).Err()) -} - -func (c *msgCache) IncrUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) { - seq, err := c.rdb.Incr(ctx, userBadgeUnreadCountSum+userID).Result() - - return int(seq), errs.Wrap(err) -} - -func (c *msgCache) SetUserBadgeUnreadCountSum(ctx context.Context, userID string, value int) error { - return errs.Wrap(c.rdb.Set(ctx, userBadgeUnreadCountSum+userID, value, 0).Err()) -} - -func (c *msgCache) GetUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) { - val, err := c.rdb.Get(ctx, userBadgeUnreadCountSum+userID).Int() - return val, errs.Wrap(err) -} - func (c *msgCache) LockMessageTypeKey(ctx context.Context, clientMsgID string, TypeKey string) error { key := exTypeKeyLocker + clientMsgID + "_" + TypeKey @@ -776,9 +408,9 @@ func (c *msgCache) getMessageReactionExPrefix(clientMsgID string, sessionType in switch sessionType { case constant.SingleChatType: return "EX_SINGLE_" + clientMsgID - case constant.GroupChatType: + case constant.WriteGroupChatType: return "EX_GROUP_" + clientMsgID - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: return "EX_SUPER_GROUP_" + clientMsgID case constant.NotificationChatType: return "EX_NOTIFICATION" + clientMsgID @@ -818,3 +450,104 @@ func (c *msgCache) GetOneMessageAllReactionList(ctx context.Context, clientMsgID func (c *msgCache) DeleteOneMessageKey(ctx context.Context, clientMsgID string, sessionType int32, subKey string) error { return errs.Wrap(c.rdb.HDel(ctx, c.getMessageReactionExPrefix(clientMsgID, sessionType), subKey).Err()) } + +func (c *msgCache) GetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) { + if c.redisEnablePipeline { + return c.PipeGetMessagesBySeq(ctx, conversationID, seqs) + } + + return c.ParallelGetMessagesBySeq(ctx, conversationID, seqs) +} + +func (c *msgCache) PipeGetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) { + pipe := c.rdb.Pipeline() + + results := []*redis.StringCmd{} + for _, seq := range seqs { + results = append(results, pipe.Get(ctx, c.getMessageCacheKey(conversationID, seq))) + } + + _, err = pipe.Exec(ctx) + if err != nil && err != redis.Nil { + return seqMsgs, failedSeqs, errs.WrapMsg(err, "pipe.get") + } + + for idx, res := range results { + seq := seqs[idx] + if res.Err() != nil { + log.ZError(ctx, "GetMessagesBySeq failed", err, "conversationID", conversationID, "seq", seq, "err", res.Err()) + failedSeqs = append(failedSeqs, seq) + continue + } + + msg := sdkws.MsgData{} + if err = msgprocessor.String2Pb(res.Val(), &msg); err != nil { + log.ZError(ctx, "GetMessagesBySeq Unmarshal failed", err, "res", res, "conversationID", conversationID, "seq", seq) + failedSeqs = append(failedSeqs, seq) + continue + } + + if msg.Status == constant.MsgDeleted { + failedSeqs = append(failedSeqs, seq) + continue + } + + seqMsgs = append(seqMsgs, &msg) + } + + return +} + +func (c *msgCache) ParallelGetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) { + type entry struct { + err error + msg *sdkws.MsgData + } + + wg := errgroup.Group{} + wg.SetLimit(concurrentLimit) + + results := make([]entry, len(seqs)) // set slice len/cap to length of seqs. + for idx, seq := range seqs { + // closure safe var + idx := idx + seq := seq + + wg.Go(func() error { + res, err := c.rdb.Get(ctx, c.getMessageCacheKey(conversationID, seq)).Result() + if err != nil { + log.ZError(ctx, "GetMessagesBySeq failed", err, "conversationID", conversationID, "seq", seq) + results[idx] = entry{err: err} + return nil + } + + msg := sdkws.MsgData{} + if err = msgprocessor.String2Pb(res, &msg); err != nil { + log.ZError(ctx, "GetMessagesBySeq Unmarshal failed", err, "res", res, "conversationID", conversationID, "seq", seq) + results[idx] = entry{err: err} + return nil + } + + if msg.Status == constant.MsgDeleted { + results[idx] = entry{err: err} + return nil + } + + results[idx] = entry{msg: &msg} + return nil + }) + } + + _ = wg.Wait() + + for idx, res := range results { + if res.err != nil { + failedSeqs = append(failedSeqs, seqs[idx]) + continue + } + + seqMsgs = append(seqMsgs, res.msg) + } + + return +} diff --git a/pkg/common/db/cache/msg_test.go b/pkg/common/db/cache/msg_test.go index 65413199a..481b4012c 100644 --- a/pkg/common/db/cache/msg_test.go +++ b/pkg/common/db/cache/msg_test.go @@ -20,7 +20,7 @@ import ( "math/rand" "testing" - "github.com/OpenIMSDK/protocol/sdkws" + "github.com/openimsdk/protocol/sdkws" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/assert" ) diff --git a/pkg/common/db/cache/s3.go b/pkg/common/db/cache/s3.go index 28e993be0..1610283ca 100644 --- a/pkg/common/db/cache/s3.go +++ b/pkg/common/db/cache/s3.go @@ -16,12 +16,14 @@ package cache import ( "context" + "github.com/openimsdk/tools/s3/cont" + "github.com/openimsdk/tools/s3/minio" "strconv" "time" "github.com/dtm-labs/rockscache" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/s3" "github.com/redis/go-redis/v9" ) @@ -83,7 +85,7 @@ type S3Cache interface { DelS3Key(engine string, keys ...string) S3Cache } -func NewS3Cache(rdb redis.UniversalClient, s3 s3.Interface) S3Cache { +func NewS3Cache(rdb redis.UniversalClient, s3 s3.Interface) cont.S3Cache { rcClient := rockscache.NewClient(rdb, rockscache.NewDefaultOptions()) return &s3CacheRedis{ rcClient: rcClient, @@ -100,7 +102,7 @@ type s3CacheRedis struct { expireTime time.Duration } -func (g *s3CacheRedis) NewCache() S3Cache { +func (g *s3CacheRedis) newCache() *s3CacheRedis { return &s3CacheRedis{ rcClient: g.rcClient, expireTime: g.expireTime, @@ -109,14 +111,14 @@ func (g *s3CacheRedis) NewCache() S3Cache { } } -func (g *s3CacheRedis) DelS3Key(engine string, keys ...string) S3Cache { - s3cache := g.NewCache() +func (g *s3CacheRedis) DelS3Key(ctx context.Context, engine string, keys ...string) error { + s3cache := g.newCache() ks := make([]string, 0, len(keys)) for _, key := range keys { ks = append(ks, g.getS3Key(engine, key)) } s3cache.AddKeys(ks...) - return s3cache + return s3cache.ExecDel(ctx) } func (g *s3CacheRedis) getS3Key(engine string, name string) string { @@ -137,7 +139,7 @@ type MinioCache interface { DelImageThumbnailKey(key string, format string, width int, height int) MinioCache } -func NewMinioCache(rdb redis.UniversalClient) MinioCache { +func NewMinioCache(rdb redis.UniversalClient) minio.Cache { rcClient := rockscache.NewClient(rdb, rockscache.NewDefaultOptions()) return &minioCacheRedis{ rcClient: rcClient, @@ -152,7 +154,7 @@ type minioCacheRedis struct { expireTime time.Duration } -func (g *minioCacheRedis) NewCache() MinioCache { +func (g *minioCacheRedis) newCache() *minioCacheRedis { return &minioCacheRedis{ rcClient: g.rcClient, expireTime: g.expireTime, @@ -160,20 +162,20 @@ func (g *minioCacheRedis) NewCache() MinioCache { } } -func (g *minioCacheRedis) DelObjectImageInfoKey(keys ...string) MinioCache { - s3cache := g.NewCache() +func (g *minioCacheRedis) DelObjectImageInfoKey(ctx context.Context, keys ...string) error { + s3cache := g.newCache() ks := make([]string, 0, len(keys)) for _, key := range keys { ks = append(ks, g.getObjectImageInfoKey(key)) } s3cache.AddKeys(ks...) - return s3cache + return s3cache.ExecDel(ctx) } -func (g *minioCacheRedis) DelImageThumbnailKey(key string, format string, width int, height int) MinioCache { - s3cache := g.NewCache() +func (g *minioCacheRedis) DelImageThumbnailKey(ctx context.Context, key string, format string, width int, height int) error { + s3cache := g.newCache() s3cache.AddKeys(g.getMinioImageThumbnailKey(key, format, width, height)) - return s3cache + return s3cache.ExecDel(ctx) } func (g *minioCacheRedis) getObjectImageInfoKey(key string) string { @@ -184,7 +186,7 @@ func (g *minioCacheRedis) getMinioImageThumbnailKey(key string, format string, w return "MINIO:THUMBNAIL:" + format + ":w" + strconv.Itoa(width) + ":h" + strconv.Itoa(height) + ":" + key } -func (g *minioCacheRedis) GetImageObjectKeyInfo(ctx context.Context, key string, fn func(ctx context.Context) (*MinioImageInfo, error)) (*MinioImageInfo, error) { +func (g *minioCacheRedis) GetImageObjectKeyInfo(ctx context.Context, key string, fn func(ctx context.Context) (*minio.ImageInfo, error)) (*minio.ImageInfo, error) { info, err := getCache(ctx, g.rcClient, g.getObjectImageInfoKey(key), g.expireTime, fn) if err != nil { return nil, err diff --git a/pkg/common/db/cache/seq.go b/pkg/common/db/cache/seq.go new file mode 100644 index 000000000..6fbb09183 --- /dev/null +++ b/pkg/common/db/cache/seq.go @@ -0,0 +1,182 @@ +package cache + +import ( + "context" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/utils/stringutil" + "github.com/redis/go-redis/v9" +) + +type SeqCache interface { + SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error + GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) + GetMaxSeq(ctx context.Context, conversationID string) (int64, error) + SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error + SetMinSeqs(ctx context.Context, seqs map[string]int64) error + GetMinSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) + GetMinSeq(ctx context.Context, conversationID string) (int64, error) + GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error) + GetConversationUserMinSeqs(ctx context.Context, conversationID string, userIDs []string) (map[string]int64, error) + SetConversationUserMinSeq(ctx context.Context, conversationID string, userID string, minSeq int64) error + // seqs map: key userID value minSeq + SetConversationUserMinSeqs(ctx context.Context, conversationID string, seqs map[string]int64) (err error) + // seqs map: key conversationID value minSeq + SetUserConversationsMinSeqs(ctx context.Context, userID string, seqs map[string]int64) error + // has read seq + SetHasReadSeq(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error + // k: user, v: seq + SetHasReadSeqs(ctx context.Context, conversationID string, hasReadSeqs map[string]int64) error + // k: conversation, v :seq + UserSetHasReadSeqs(ctx context.Context, userID string, hasReadSeqs map[string]int64) error + GetHasReadSeqs(ctx context.Context, userID string, conversationIDs []string) (map[string]int64, error) + GetHasReadSeq(ctx context.Context, userID string, conversationID string) (int64, error) +} + +func NewSeqCache(rdb redis.UniversalClient) SeqCache { + return &seqCache{rdb: rdb} +} + +type seqCache struct { + rdb redis.UniversalClient +} + +func (c *seqCache) getMaxSeqKey(conversationID string) string { + return maxSeq + conversationID +} + +func (c *seqCache) getMinSeqKey(conversationID string) string { + return minSeq + conversationID +} + +func (c *seqCache) getHasReadSeqKey(conversationID string, userID string) string { + return hasReadSeq + userID + ":" + conversationID +} + +func (c *seqCache) getConversationUserMinSeqKey(conversationID, userID string) string { + return conversationUserMinSeq + conversationID + "u:" + userID +} + +func (c *seqCache) setSeq(ctx context.Context, conversationID string, seq int64, getkey func(conversationID string) string) error { + return errs.Wrap(c.rdb.Set(ctx, getkey(conversationID), seq, 0).Err()) +} + +func (c *seqCache) getSeq(ctx context.Context, conversationID string, getkey func(conversationID string) string) (int64, error) { + val, err := c.rdb.Get(ctx, getkey(conversationID)).Int64() + if err != nil { + return 0, errs.Wrap(err) + } + return val, nil +} + +func (c *seqCache) getSeqs(ctx context.Context, items []string, getkey func(s string) string) (m map[string]int64, err error) { + m = make(map[string]int64, len(items)) + for i, v := range items { + res, err := c.rdb.Get(ctx, getkey(v)).Result() + if err != nil && err != redis.Nil { + return nil, errs.Wrap(err) + } + val := stringutil.StringToInt64(res) + if val != 0 { + m[items[i]] = val + } + } + + return m, nil +} + +func (c *seqCache) SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error { + return c.setSeq(ctx, conversationID, maxSeq, c.getMaxSeqKey) +} + +func (c *seqCache) GetMaxSeqs(ctx context.Context, conversationIDs []string) (m map[string]int64, err error) { + return c.getSeqs(ctx, conversationIDs, c.getMaxSeqKey) +} + +func (c *seqCache) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) { + return c.getSeq(ctx, conversationID, c.getMaxSeqKey) +} + +func (c *seqCache) SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error { + return c.setSeq(ctx, conversationID, minSeq, c.getMinSeqKey) +} + +func (c *seqCache) setSeqs(ctx context.Context, seqs map[string]int64, getkey func(key string) string) error { + for conversationID, seq := range seqs { + if err := c.rdb.Set(ctx, getkey(conversationID), seq, 0).Err(); err != nil { + return errs.Wrap(err) + } + } + return nil +} + +func (c *seqCache) SetMinSeqs(ctx context.Context, seqs map[string]int64) error { + return c.setSeqs(ctx, seqs, c.getMinSeqKey) +} + +func (c *seqCache) GetMinSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) { + return c.getSeqs(ctx, conversationIDs, c.getMinSeqKey) +} + +func (c *seqCache) GetMinSeq(ctx context.Context, conversationID string) (int64, error) { + return c.getSeq(ctx, conversationID, c.getMinSeqKey) +} + +func (c *seqCache) GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error) { + val, err := c.rdb.Get(ctx, c.getConversationUserMinSeqKey(conversationID, userID)).Int64() + if err != nil { + return 0, errs.Wrap(err) + } + return val, nil +} + +func (c *seqCache) GetConversationUserMinSeqs(ctx context.Context, conversationID string, userIDs []string) (m map[string]int64, err error) { + return c.getSeqs(ctx, userIDs, func(userID string) string { + return c.getConversationUserMinSeqKey(conversationID, userID) + }) +} + +func (c *seqCache) SetConversationUserMinSeq(ctx context.Context, conversationID string, userID string, minSeq int64) error { + return errs.Wrap(c.rdb.Set(ctx, c.getConversationUserMinSeqKey(conversationID, userID), minSeq, 0).Err()) +} + +func (c *seqCache) SetConversationUserMinSeqs(ctx context.Context, conversationID string, seqs map[string]int64) (err error) { + return c.setSeqs(ctx, seqs, func(userID string) string { + return c.getConversationUserMinSeqKey(conversationID, userID) + }) +} + +func (c *seqCache) SetUserConversationsMinSeqs(ctx context.Context, userID string, seqs map[string]int64) (err error) { + return c.setSeqs(ctx, seqs, func(conversationID string) string { + return c.getConversationUserMinSeqKey(conversationID, userID) + }) +} + +func (c *seqCache) SetHasReadSeq(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error { + return errs.Wrap(c.rdb.Set(ctx, c.getHasReadSeqKey(conversationID, userID), hasReadSeq, 0).Err()) +} + +func (c *seqCache) SetHasReadSeqs(ctx context.Context, conversationID string, hasReadSeqs map[string]int64) error { + return c.setSeqs(ctx, hasReadSeqs, func(userID string) string { + return c.getHasReadSeqKey(conversationID, userID) + }) +} + +func (c *seqCache) UserSetHasReadSeqs(ctx context.Context, userID string, hasReadSeqs map[string]int64) error { + return c.setSeqs(ctx, hasReadSeqs, func(conversationID string) string { + return c.getHasReadSeqKey(conversationID, userID) + }) +} + +func (c *seqCache) GetHasReadSeqs(ctx context.Context, userID string, conversationIDs []string) (map[string]int64, error) { + return c.getSeqs(ctx, conversationIDs, func(conversationID string) string { + return c.getHasReadSeqKey(conversationID, userID) + }) +} + +func (c *seqCache) GetHasReadSeq(ctx context.Context, userID string, conversationID string) (int64, error) { + val, err := c.rdb.Get(ctx, c.getHasReadSeqKey(conversationID, userID)).Int64() + if err != nil { + return 0, err + } + return val, nil +} diff --git a/pkg/common/db/cache/third.go b/pkg/common/db/cache/third.go new file mode 100644 index 000000000..d2900a32d --- /dev/null +++ b/pkg/common/db/cache/third.go @@ -0,0 +1,85 @@ +package cache + +import ( + "context" + "github.com/openimsdk/tools/errs" + "github.com/redis/go-redis/v9" + "strconv" + "time" +) + +type ThirdCache interface { + SetFcmToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) (err error) + GetFcmToken(ctx context.Context, account string, platformID int) (string, error) + DelFcmToken(ctx context.Context, account string, platformID int) error + IncrUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) + SetUserBadgeUnreadCountSum(ctx context.Context, userID string, value int) error + GetUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) + SetGetuiToken(ctx context.Context, token string, expireTime int64) error + GetGetuiToken(ctx context.Context) (string, error) + SetGetuiTaskID(ctx context.Context, taskID string, expireTime int64) error + GetGetuiTaskID(ctx context.Context) (string, error) +} + +func NewThirdCache(rdb redis.UniversalClient) ThirdCache { + return &thirdCache{rdb: rdb} +} + +type thirdCache struct { + rdb redis.UniversalClient +} + +func (c *thirdCache) SetFcmToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) (err error) { + return errs.Wrap(c.rdb.Set(ctx, FCM_TOKEN+account+":"+strconv.Itoa(platformID), fcmToken, time.Duration(expireTime)*time.Second).Err()) +} + +func (c *thirdCache) GetFcmToken(ctx context.Context, account string, platformID int) (string, error) { + val, err := c.rdb.Get(ctx, FCM_TOKEN+account+":"+strconv.Itoa(platformID)).Result() + if err != nil { + return "", errs.Wrap(err) + } + return val, nil +} + +func (c *thirdCache) DelFcmToken(ctx context.Context, account string, platformID int) error { + return errs.Wrap(c.rdb.Del(ctx, FCM_TOKEN+account+":"+strconv.Itoa(platformID)).Err()) +} + +func (c *thirdCache) IncrUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) { + seq, err := c.rdb.Incr(ctx, userBadgeUnreadCountSum+userID).Result() + + return int(seq), errs.Wrap(err) +} + +func (c *thirdCache) SetUserBadgeUnreadCountSum(ctx context.Context, userID string, value int) error { + return errs.Wrap(c.rdb.Set(ctx, userBadgeUnreadCountSum+userID, value, 0).Err()) +} + +func (c *thirdCache) GetUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) { + val, err := c.rdb.Get(ctx, userBadgeUnreadCountSum+userID).Int() + return val, errs.Wrap(err) +} + +func (c *thirdCache) SetGetuiToken(ctx context.Context, token string, expireTime int64) error { + return errs.Wrap(c.rdb.Set(ctx, getuiToken, token, time.Duration(expireTime)*time.Second).Err()) +} + +func (c *thirdCache) GetGetuiToken(ctx context.Context) (string, error) { + val, err := c.rdb.Get(ctx, getuiToken).Result() + if err != nil { + return "", errs.Wrap(err) + } + return val, nil +} + +func (c *thirdCache) SetGetuiTaskID(ctx context.Context, taskID string, expireTime int64) error { + return errs.Wrap(c.rdb.Set(ctx, getuiTaskID, taskID, time.Duration(expireTime)*time.Second).Err()) +} + +func (c *thirdCache) GetGetuiTaskID(ctx context.Context) (string, error) { + val, err := c.rdb.Get(ctx, getuiTaskID).Result() + if err != nil { + return "", errs.Wrap(err) + } + return val, nil +} diff --git a/pkg/common/db/cache/token.go b/pkg/common/db/cache/token.go new file mode 100644 index 000000000..88580e932 --- /dev/null +++ b/pkg/common/db/cache/token.go @@ -0,0 +1,56 @@ +package cache + +import ( + "context" + + "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/utils/stringutil" + "github.com/redis/go-redis/v9" +) + +func NewTokenCacheModel(rdb redis.UniversalClient) TokenModel { + return &tokenCache{ + rdb: rdb, + } +} + +type TokenModel interface { + AddTokenFlag(ctx context.Context, userID string, platformID int, token string, flag int) error + GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error) + SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error + DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error +} + +type tokenCache struct { + rdb redis.UniversalClient +} + +func (c *tokenCache) AddTokenFlag(ctx context.Context, userID string, platformID int, token string, flag int) error { + return errs.Wrap(c.rdb.HSet(ctx, cachekey.GetTokenKey(userID, platformID), token, flag).Err()) +} + +func (c *tokenCache) GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error) { + m, err := c.rdb.HGetAll(ctx, cachekey.GetTokenKey(userID, platformID)).Result() + if err != nil { + return nil, errs.Wrap(err) + } + mm := make(map[string]int) + for k, v := range m { + mm[k] = stringutil.StringToInt(v) + } + + return mm, nil +} + +func (c *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error { + mm := make(map[string]any) + for k, v := range m { + mm[k] = v + } + return errs.Wrap(c.rdb.HSet(ctx, cachekey.GetTokenKey(userID, platformID), mm).Err()) +} + +func (c *tokenCache) DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error { + return errs.Wrap(c.rdb.HDel(ctx, cachekey.GetTokenKey(userID, platformID), fields...).Err()) +} diff --git a/pkg/common/db/cache/user.go b/pkg/common/db/cache/user.go index a60f55836..c10e9611a 100644 --- a/pkg/common/db/cache/user.go +++ b/pkg/common/db/cache/user.go @@ -22,25 +22,22 @@ import ( "strconv" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/user" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" "github.com/dtm-labs/rockscache" "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" "github.com/openimsdk/open-im-server/v3/pkg/common/config" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/user" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" "github.com/redis/go-redis/v9" ) const ( - userExpireTime = time.Second * 60 * 60 * 12 - //userInfoKey = "USER_INFO:". - userGlobalRecvMsgOptKey = "USER_GLOBAL_RECV_MSG_OPT_KEY:" + userExpireTime = time.Second * 60 * 60 * 12 olineStatusKey = "ONLINE_STATUS:" userOlineStatusExpireTime = time.Second * 60 * 60 * 24 statusMod = 501 - platformID = "_PlatformIDSuffix" ) type UserCache interface { @@ -58,20 +55,16 @@ type UserCache interface { type UserCacheRedis struct { metaCache rdb redis.UniversalClient - //userDB relationtb.UserModelInterface + // userDB relationtb.UserModelInterface userDB relationtb.UserModelInterface expireTime time.Duration rcClient *rockscache.Client } -func NewUserCacheRedis( - rdb redis.UniversalClient, - userDB relationtb.UserModelInterface, - options rockscache.Options, -) UserCache { +func NewUserCacheRedis(rdb redis.UniversalClient, localCache *config.LocalCache, userDB relationtb.UserModelInterface, options rockscache.Options) UserCache { rcClient := rockscache.NewClient(rdb, options) mc := NewMetaCacheRedis(rcClient) - u := config.Config.LocalCache.User + u := localCache.User log.ZDebug(context.Background(), "user local cache init", "Topic", u.Topic, "SlotNum", u.SlotNum, "SlotSize", u.SlotSize, "enable", u.Enable()) mc.SetTopic(u.Topic) mc.SetRawRedisClient(rdb) @@ -294,7 +287,7 @@ func (u *UserCacheRedis) refreshStatusOnline(ctx context.Context, userID string, onlineStatus.UserID = userID newjsonData, err := json.Marshal(&onlineStatus) if err != nil { - return errs.Wrap(err, "json.Marshal failed") + return errs.WrapMsg(err, "json.Marshal failed") } _, err = u.rdb.HSet(ctx, key, userID, string(newjsonData)).Result() if err != nil { diff --git a/pkg/common/db/controller/auth.go b/pkg/common/db/controller/auth.go index 7cafa1c48..8190e5017 100644 --- a/pkg/common/db/controller/auth.go +++ b/pkg/common/db/controller/auth.go @@ -17,13 +17,12 @@ package controller import ( "context" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/tokenverify" "github.com/golang-jwt/jwt/v4" "github.com/openimsdk/open-im-server/v3/pkg/authverify" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/tokenverify" ) type AuthDatabase interface { @@ -31,17 +30,18 @@ type AuthDatabase interface { GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error) // Create token CreateToken(ctx context.Context, userID string, platformID int) (string, error) + + SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error } type authDatabase struct { - cache cache.MsgModel + cache cache.TokenModel accessSecret string accessExpire int64 - config *config.GlobalConfig } -func NewAuthDatabase(cache cache.MsgModel, accessSecret string, accessExpire int64, config *config.GlobalConfig) AuthDatabase { - return &authDatabase{cache: cache, accessSecret: accessSecret, accessExpire: accessExpire, config: config} +func NewAuthDatabase(cache cache.TokenModel, accessSecret string, accessExpire int64) AuthDatabase { + return &authDatabase{cache: cache, accessSecret: accessSecret, accessExpire: accessExpire} } // If the result is empty. @@ -49,6 +49,10 @@ func (a *authDatabase) GetTokensWithoutError(ctx context.Context, userID string, return a.cache.GetTokensWithoutError(ctx, userID, platformID) } +func (a *authDatabase) SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error { + return a.cache.SetTokenMapByUidPid(ctx, userID, platformID, m) +} + // Create Token. func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformID int) (string, error) { tokens, err := a.cache.GetTokensWithoutError(ctx, userID, platformID) @@ -57,7 +61,7 @@ func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformI } var deleteTokenKey []string for k, v := range tokens { - _, err = tokenverify.GetClaimFromToken(k, authverify.Secret(a.config.Secret)) + _, err = tokenverify.GetClaimFromToken(k, authverify.Secret(a.accessSecret)) if err != nil || v != constant.NormalToken { deleteTokenKey = append(deleteTokenKey, k) } @@ -73,7 +77,7 @@ func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformI token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString([]byte(a.accessSecret)) if err != nil { - return "", errs.Wrap(err, "token.SignedString") + return "", errs.WrapMsg(err, "token.SignedString") } return tokenString, a.cache.AddTokenFlag(ctx, userID, platformID, tokenString, constant.NormalToken) } diff --git a/pkg/common/db/controller/black.go b/pkg/common/db/controller/black.go index c4b253c2f..5991a9dfe 100644 --- a/pkg/common/db/controller/black.go +++ b/pkg/common/db/controller/black.go @@ -17,11 +17,11 @@ package controller import ( "context" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/pagination" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" ) type BlackDatabase interface { @@ -86,7 +86,7 @@ func (b *blackDatabase) CheckIn(ctx context.Context, userID1, userID2 string) (i return } log.ZDebug(ctx, "blackIDs", "user1BlackIDs", userID1BlackIDs, "user2BlackIDs", userID2BlackIDs) - return utils.IsContain(userID2, userID1BlackIDs), utils.IsContain(userID1, userID2BlackIDs), nil + return datautil.Contain(userID2, userID1BlackIDs...), datautil.Contain(userID1, userID2BlackIDs...), nil } // FindBlackIDs Get Blacklist List. diff --git a/pkg/common/db/controller/conversation.go b/pkg/common/db/controller/conversation.go index 3d46e4fbc..567bcb270 100644 --- a/pkg/common/db/controller/conversation.go +++ b/pkg/common/db/controller/conversation.go @@ -18,14 +18,15 @@ import ( "context" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/pagination" - "github.com/OpenIMSDK/tools/tx" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/db/tx" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/stringutil" ) type ConversationDatabase interface { @@ -62,11 +63,11 @@ type ConversationDatabase interface { GetConversationIDsNeedDestruct(ctx context.Context) ([]*relationtb.ConversationModel, error) // GetConversationNotReceiveMessageUserIDs gets user IDs for users in a conversation who have not received messages. GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error) - //GetUserAllHasReadSeqs(ctx context.Context, ownerUserID string) (map[string]int64, error) - //FindRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) ([]string, error) + // GetUserAllHasReadSeqs(ctx context.Context, ownerUserID string) (map[string]int64, error) + // FindRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) ([]string, error) } -func NewConversationDatabase(conversation relationtb.ConversationModelInterface, cache cache.ConversationCache, tx tx.CtxTx) ConversationDatabase { +func NewConversationDatabase(conversation relationtb.ConversationModelInterface, cache cache.ConversationCache, tx tx.Tx) ConversationDatabase { return &conversationDatabase{ conversationDB: conversation, cache: cache, @@ -77,7 +78,7 @@ func NewConversationDatabase(conversation relationtb.ConversationModelInterface, type conversationDatabase struct { conversationDB relationtb.ConversationModelInterface cache cache.ConversationCache - tx tx.CtxTx + tx tx.Tx } func (c *conversationDatabase) SetUsersConversationFieldTx(ctx context.Context, userIDs []string, conversation *relationtb.ConversationModel, fieldMap map[string]any) (err error) { @@ -105,13 +106,13 @@ func (c *conversationDatabase) SetUsersConversationFieldTx(ctx context.Context, cache = cache.DelConversationNotReceiveMessageUserIDs(conversation.ConversationID) } } - NotUserIDs := utils.DifferenceString(haveUserIDs, userIDs) + NotUserIDs := stringutil.DifferenceString(haveUserIDs, userIDs) log.ZDebug(ctx, "SetUsersConversationFieldTx", "NotUserIDs", NotUserIDs, "haveUserIDs", haveUserIDs, "userIDs", userIDs) var conversations []*relationtb.ConversationModel now := time.Now() for _, v := range NotUserIDs { temp := new(relationtb.ConversationModel) - if err = utils.CopyStructFields(temp, conversation); err != nil { + if err = datautil.CopyStructFields(temp, conversation); err != nil { return err } temp.OwnerUserID = v @@ -205,7 +206,7 @@ func (c *conversationDatabase) GetUserAllConversation(ctx context.Context, owner func (c *conversationDatabase) SetUserConversations(ctx context.Context, ownerUserID string, conversations []*relationtb.ConversationModel) error { return c.tx.Transaction(ctx, func(ctx context.Context) error { cache := c.cache.NewCache() - groupIDs := utils.Distinct(utils.Filter(conversations, func(e *relationtb.ConversationModel) (string, bool) { + groupIDs := datautil.Distinct(datautil.Filter(conversations, func(e *relationtb.ConversationModel) (string, bool) { return e.GroupID, e.GroupID != "" })) for _, groupID := range groupIDs { @@ -235,7 +236,7 @@ func (c *conversationDatabase) SetUserConversations(ctx context.Context, ownerUs var notExistConversations []*relationtb.ConversationModel for _, conversation := range conversations { - if !utils.IsContain(conversation.ConversationID, existConversationIDs) { + if !datautil.Contain(conversation.ConversationID, existConversationIDs...) { notExistConversations = append(notExistConversations, conversation) } } @@ -246,28 +247,28 @@ func (c *conversationDatabase) SetUserConversations(ctx context.Context, ownerUs } cache = cache.DelConversationIDs(ownerUserID). DelUserConversationIDsHash(ownerUserID). - DelConversationNotReceiveMessageUserIDs(utils.Slice(notExistConversations, func(e *relationtb.ConversationModel) string { return e.ConversationID })...) + DelConversationNotReceiveMessageUserIDs(datautil.Slice(notExistConversations, func(e *relationtb.ConversationModel) string { return e.ConversationID })...) } return cache.ExecDel(ctx) }) } -//func (c *conversationDatabase) FindRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) ([]string, error) { +// func (c *conversationDatabase) FindRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) ([]string, error) { // return c.cache.GetSuperGroupRecvMsgNotNotifyUserIDs(ctx, groupID) //} func (c *conversationDatabase) CreateGroupChatConversation(ctx context.Context, groupID string, userIDs []string) error { return c.tx.Transaction(ctx, func(ctx context.Context) error { cache := c.cache.NewCache() - conversationID := msgprocessor.GetConversationIDBySessionType(constant.SuperGroupChatType, groupID) + conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, groupID) existConversationUserIDs, err := c.conversationDB.FindUserID(ctx, userIDs, []string{conversationID}) if err != nil { return err } - notExistUserIDs := utils.DifferenceString(userIDs, existConversationUserIDs) + notExistUserIDs := stringutil.DifferenceString(userIDs, existConversationUserIDs) var conversations []*relationtb.ConversationModel for _, v := range notExistUserIDs { - conversation := relationtb.ConversationModel{ConversationType: constant.SuperGroupChatType, GroupID: groupID, OwnerUserID: v, ConversationID: conversationID} + conversation := relationtb.ConversationModel{ConversationType: constant.ReadGroupChatType, GroupID: groupID, OwnerUserID: v, ConversationID: conversationID} conversations = append(conversations, &conversation) cache = cache.DelConversations(v, conversationID).DelConversationNotReceiveMessageUserIDs(conversationID) } diff --git a/pkg/common/db/controller/doc.go b/pkg/common/db/controller/doc.go index 72316f128..97ec08799 100644 --- a/pkg/common/db/controller/doc.go +++ b/pkg/common/db/controller/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/common/db/controller/friend.go b/pkg/common/db/controller/friend.go index 3c81d922c..49136f228 100644 --- a/pkg/common/db/controller/friend.go +++ b/pkg/common/db/controller/friend.go @@ -19,15 +19,15 @@ import ( "fmt" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "github.com/OpenIMSDK/tools/pagination" - "github.com/OpenIMSDK/tools/tx" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/db/tx" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/utils/datautil" ) type FriendDatabase interface { @@ -80,11 +80,11 @@ type FriendDatabase interface { type friendDatabase struct { friend relation.FriendModelInterface friendRequest relation.FriendRequestModelInterface - tx tx.CtxTx + tx tx.Tx cache cache.FriendCache } -func NewFriendDatabase(friend relation.FriendModelInterface, friendRequest relation.FriendRequestModelInterface, cache cache.FriendCache, tx tx.CtxTx) FriendDatabase { +func NewFriendDatabase(friend relation.FriendModelInterface, friendRequest relation.FriendRequestModelInterface, cache cache.FriendCache, tx tx.Tx) FriendDatabase { return &friendDatabase{friend: friend, friendRequest: friendRequest, cache: cache, tx: tx} } @@ -106,8 +106,8 @@ func (f *friendDatabase) CheckIn(ctx context.Context, userID1, userID2 string) ( } // Check if userID2 is in userID1's friend list and vice versa - inUser1Friends = utils.IsContain(userID2, userID1FriendIDs) - inUser2Friends = utils.IsContain(userID1, userID2FriendIDs) + inUser1Friends = datautil.Contain(userID2, userID1FriendIDs...) + inUser2Friends = datautil.Contain(userID1, userID2FriendIDs...) return inUser1Friends, inUser2Friends, nil } @@ -139,7 +139,7 @@ func (f *friendDatabase) AddFriendRequest(ctx context.Context, fromUserID, toUse func (f *friendDatabase) BecomeFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, addSource int32) (err error) { return f.tx.Transaction(ctx, func(ctx context.Context) error { cache := f.cache.NewCache() - // User find friends + // user find friends fs1, err := f.friend.FindFriends(ctx, ownerUserID, friendUserIDs) if err != nil { return err @@ -148,7 +148,7 @@ func (f *friendDatabase) BecomeFriends(ctx context.Context, ownerUserID string, for _, v := range friendUserIDs { fs1 = append(fs1, &relation.FriendModel{OwnerUserID: ownerUserID, FriendUserID: v, AddSource: addSource, OperatorUserID: opUserID}) } - fs11 := utils.DistinctAny(fs1, func(e *relation.FriendModel) string { + fs11 := datautil.DistinctAny(fs1, func(e *relation.FriendModel) string { return e.FriendUserID }) @@ -165,7 +165,7 @@ func (f *friendDatabase) BecomeFriends(ctx context.Context, ownerUserID string, fs2 = append(fs2, &relation.FriendModel{OwnerUserID: v, FriendUserID: ownerUserID, AddSource: addSource, OperatorUserID: opUserID}) newFriendIDs = append(newFriendIDs, v) } - fs22 := utils.DistinctAny(fs2, func(e *relation.FriendModel) string { + fs22 := datautil.DistinctAny(fs2, func(e *relation.FriendModel) string { return e.OwnerUserID }) err = f.friend.Create(ctx, fs22) @@ -212,14 +212,13 @@ func (f *friendDatabase) RefuseFriendRequest(ctx context.Context, friendRequest // AgreeFriendRequest accepts a friend request. It first checks for an existing, unprocessed request. func (f *friendDatabase) AgreeFriendRequest(ctx context.Context, friendRequest *relation.FriendRequestModel) (err error) { return f.tx.Transaction(ctx, func(ctx context.Context) error { - defer log.ZDebug(ctx, "return line") now := time.Now() fr, err := f.friendRequest.Take(ctx, friendRequest.FromUserID, friendRequest.ToUserID) if err != nil { return err } if fr.HandleResult != 0 { - return errs.ErrArgs.Wrap("the friend request has been processed") + return errs.ErrArgs.WrapMsg("the friend request has been processed") } friendRequest.HandlerUserID = mcontext.GetOpUserID(ctx) friendRequest.HandleResult = constant.FriendResponseAgree @@ -246,7 +245,7 @@ func (f *friendDatabase) AgreeFriendRequest(ctx context.Context, friendRequest * if err != nil { return err } - existsMap := utils.SliceSet(utils.Slice(exists, func(friend *relation.FriendModel) [2]string { + existsMap := datautil.SliceSet(datautil.Slice(exists, func(friend *relation.FriendModel) [2]string { return [...]string{friend.OwnerUserID, friend.FriendUserID} // My - Friend })) var adds []*relation.FriendModel diff --git a/pkg/common/db/controller/group.go b/pkg/common/db/controller/group.go index 45bf87b6f..ddf72b7bf 100644 --- a/pkg/common/db/controller/group.go +++ b/pkg/common/db/controller/group.go @@ -16,15 +16,16 @@ package controller import ( "context" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/pagination" - "github.com/OpenIMSDK/tools/tx" - "github.com/OpenIMSDK/tools/utils" "github.com/dtm-labs/rockscache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/db/tx" + "github.com/openimsdk/tools/utils/datautil" "github.com/redis/go-redis/v9" ) @@ -107,10 +108,11 @@ type GroupDatabase interface { func NewGroupDatabase( rdb redis.UniversalClient, + localCache *config.LocalCache, groupDB relationtb.GroupModelInterface, groupMemberDB relationtb.GroupMemberModelInterface, groupRequestDB relationtb.GroupRequestModelInterface, - ctxTx tx.CtxTx, + ctxTx tx.Tx, groupHash cache.GroupHash, ) GroupDatabase { rcOptions := rockscache.NewDefaultOptions() @@ -121,7 +123,7 @@ func NewGroupDatabase( groupMemberDB: groupMemberDB, groupRequestDB: groupRequestDB, ctxTx: ctxTx, - cache: cache.NewGroupCacheRedis(rdb, groupDB, groupMemberDB, groupRequestDB, groupHash, rcOptions), + cache: cache.NewGroupCacheRedis(rdb, localCache, groupDB, groupMemberDB, groupRequestDB, groupHash, rcOptions), } } @@ -129,7 +131,7 @@ type groupDatabase struct { groupDB relationtb.GroupModelInterface groupMemberDB relationtb.GroupMemberModelInterface groupRequestDB relationtb.GroupRequestModelInterface - ctxTx tx.CtxTx + ctxTx tx.Tx cache cache.GroupCache } @@ -270,7 +272,7 @@ func (g *groupDatabase) PageGetJoinGroup(ctx context.Context, userID string, pag if err != nil { return 0, nil, err } - for _, groupID := range utils.Paginate(groupIDs, int(pagination.GetPageNumber()), int(pagination.GetShowNumber())) { + for _, groupID := range datautil.Paginate(groupIDs, int(pagination.GetPageNumber()), int(pagination.GetShowNumber())) { groupMembers, err := g.cache.GetGroupMembersInfo(ctx, groupID, []string{userID}) if err != nil { return 0, nil, err @@ -285,7 +287,7 @@ func (g *groupDatabase) PageGetGroupMember(ctx context.Context, groupID string, if err != nil { return 0, nil, err } - pageIDs := utils.Paginate(groupMemberIDs, int(pagination.GetPageNumber()), int(pagination.GetShowNumber())) + pageIDs := datautil.Paginate(groupMemberIDs, int(pagination.GetPageNumber()), int(pagination.GetShowNumber())) if len(pageIDs) == 0 { return int64(len(groupMemberIDs)), nil, nil } diff --git a/pkg/common/db/controller/msg.go b/pkg/common/db/controller/msg.go index ccf209b7a..130e35d20 100644 --- a/pkg/common/db/controller/msg.go +++ b/pkg/common/db/controller/msg.go @@ -17,22 +17,21 @@ package controller import ( "context" "encoding/json" - "errors" "time" - "github.com/OpenIMSDK/protocol/constant" - pbmsg "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/convert" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" - unrelationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" - "github.com/openimsdk/open-im-server/v3/pkg/common/kafka" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" + "github.com/openimsdk/protocol/constant" + pbmsg "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mq/kafka" + "github.com/openimsdk/tools/utils/datautil" + "github.com/openimsdk/tools/utils/timeutil" "github.com/redis/go-redis/v9" "go.mongodb.org/mongo-driver/mongo" ) @@ -47,7 +46,7 @@ type CommonMsgDatabase interface { // BatchInsertChat2DB inserts a batch of messages into the database for a specific conversation. BatchInsertChat2DB(ctx context.Context, conversationID string, msgs []*sdkws.MsgData, currentMaxSeq int64) error // RevokeMsg revokes a message in a conversation. - RevokeMsg(ctx context.Context, conversationID string, seq int64, revoke *unrelationtb.RevokeModel) error + RevokeMsg(ctx context.Context, conversationID string, seq int64, revoke *relation.RevokeModel) error // MarkSingleChatMsgsAsRead marks messages as read for a single chat by sequence numbers. MarkSingleChatMsgsAsRead(ctx context.Context, userID string, conversationID string, seqs []int64) error // DeleteMessagesFromCache deletes message caches from Redis by sequence numbers. @@ -100,75 +99,54 @@ type CommonMsgDatabase interface { MsgToPushMQ(ctx context.Context, key, conversarionID string, msg2mq *sdkws.MsgData) (int32, int64, error) MsgToMongoMQ(ctx context.Context, key, conversarionID string, msgs []*sdkws.MsgData, lastSeq int64) error - RangeUserSendCount( - ctx context.Context, - start time.Time, - end time.Time, - group bool, - ase bool, - pageNumber int32, - showNumber int32, - ) (msgCount int64, userCount int64, users []*unrelationtb.UserCount, dateCount map[string]int64, err error) - RangeGroupSendCount( - ctx context.Context, - start time.Time, - end time.Time, - ase bool, - pageNumber int32, - showNumber int32, - ) (msgCount int64, userCount int64, groups []*unrelationtb.GroupCount, dateCount map[string]int64, err error) + RangeUserSendCount(ctx context.Context, start time.Time, end time.Time, group bool, ase bool, pageNumber int32, showNumber int32) (msgCount int64, userCount int64, users []*relation.UserCount, dateCount map[string]int64, err error) + RangeGroupSendCount(ctx context.Context, start time.Time, end time.Time, ase bool, pageNumber int32, showNumber int32) (msgCount int64, userCount int64, groups []*relation.GroupCount, dateCount map[string]int64, err error) ConvertMsgsDocLen(ctx context.Context, conversationIDs []string) } -func NewCommonMsgDatabase(msgDocModel unrelationtb.MsgDocModelInterface, cacheModel cache.MsgModel, config *config.GlobalConfig) (CommonMsgDatabase, error) { - producerConfig := &kafka.ProducerConfig{ - ProducerAck: config.Kafka.ProducerAck, - CompressType: config.Kafka.CompressType, - Username: config.Kafka.Username, - Password: config.Kafka.Password, - } - - var tlsConfig *kafka.TLSConfig - if config.Kafka.TLS != nil { - tlsConfig = &kafka.TLSConfig{ - CACrt: config.Kafka.TLS.CACrt, - ClientCrt: config.Kafka.TLS.ClientCrt, - ClientKey: config.Kafka.TLS.ClientKey, - ClientKeyPwd: config.Kafka.TLS.ClientKeyPwd, - InsecureSkipVerify: false, - } +func NewCommonMsgDatabase(msgDocModel relation.MsgDocModelInterface, msg cache.MsgCache, seq cache.SeqCache, kafkaConf *config.Kafka) (CommonMsgDatabase, error) { + conf, err := kafka.BuildProducerConfig(*kafkaConf.Build()) + if err != nil { + return nil, err } - producerToRedis, err := kafka.NewKafkaProducer(config.Kafka.Addr, config.Kafka.LatestMsgToRedis.Topic, producerConfig, tlsConfig) + producerToRedis, err := kafka.NewKafkaProducer(conf, kafkaConf.Address, kafkaConf.ToRedisTopic) if err != nil { return nil, err } - producerToMongo, err := kafka.NewKafkaProducer(config.Kafka.Addr, config.Kafka.MsgToMongo.Topic, producerConfig, tlsConfig) + producerToMongo, err := kafka.NewKafkaProducer(conf, kafkaConf.Address, kafkaConf.ToMongoTopic) if err != nil { return nil, err } - producerToPush, err := kafka.NewKafkaProducer(config.Kafka.Addr, config.Kafka.MsgToPush.Topic, producerConfig, tlsConfig) + producerToPush, err := kafka.NewKafkaProducer(conf, kafkaConf.Address, kafkaConf.ToPushTopic) if err != nil { return nil, err } return &commonMsgDatabase{ msgDocDatabase: msgDocModel, - cache: cacheModel, + msg: msg, + seq: seq, producer: producerToRedis, producerToMongo: producerToMongo, producerToPush: producerToPush, }, nil } -func InitCommonMsgDatabase(rdb redis.UniversalClient, database *mongo.Database, config *config.GlobalConfig) (CommonMsgDatabase, error) { - cacheModel := cache.NewMsgCacheModel(rdb, config) - msgDocModel := unrelation.NewMsgMongoDriver(database) - return NewCommonMsgDatabase(msgDocModel, cacheModel, config) -} +//func InitCommonMsgDatabase(rdb redis.UniversalClient, database *mongo.Database, config *tools.CronTaskConfig) (CommonMsgDatabase, error) { +// msgDocModel, err := mgo.NewMsgMongo(database) +// if err != nil { +// return nil, err +// } +// //todo MsgCacheTimeout +// msg := cache.NewMsgCache(rdb, 86400, config.RedisConfig.EnablePipeline) +// seq := cache.NewSeqCache(rdb) +// return NewCommonMsgDatabase(msgDocModel, msg, seq, &config.KafkaConfig) +//} type commonMsgDatabase struct { - msgDocDatabase unrelationtb.MsgDocModelInterface - msg unrelationtb.MsgDocModel - cache cache.MsgModel + msgDocDatabase relation.MsgDocModelInterface + msgTable relation.MsgDocModel + msg cache.MsgCache + seq cache.SeqCache producer *kafka.Producer producerToMongo *kafka.Producer producerToModify *kafka.Producer @@ -209,24 +187,24 @@ func (db *commonMsgDatabase) BatchInsertBlock(ctx context.Context, conversationI if len(fields) == 0 { return nil } - num := db.msg.GetSingleGocMsgNum() + num := db.msgTable.GetSingleGocMsgNum() // num = 100 for i, field := range fields { // Check the type of the field var ok bool switch key { case updateKeyMsg: - var msg *unrelationtb.MsgDataModel - msg, ok = field.(*unrelationtb.MsgDataModel) + var msg *relation.MsgDataModel + msg, ok = field.(*relation.MsgDataModel) if msg != nil && msg.Seq != firstSeq+int64(i) { - return errs.ErrInternalServer.Wrap("seq is invalid") + return errs.ErrInternalServer.WrapMsg("seq is invalid") } case updateKeyRevoke: - _, ok = field.(*unrelationtb.RevokeModel) + _, ok = field.(*relation.RevokeModel) default: - return errs.ErrInternalServer.Wrap("key is invalid") + return errs.ErrInternalServer.WrapMsg("key is invalid") } if !ok { - return errs.ErrInternalServer.Wrap("field type is invalid") + return errs.ErrInternalServer.WrapMsg("field type is invalid") } } // Returns true if the document exists in the database, false if the document does not exist in the database @@ -235,8 +213,8 @@ func (db *commonMsgDatabase) BatchInsertBlock(ctx context.Context, conversationI res *mongo.UpdateResult err error ) - docID := db.msg.GetDocID(conversationID, seq) - index := db.msg.GetMsgIndex(seq) + docID := db.msgTable.GetDocID(conversationID, seq) + index := db.msgTable.GetMsgIndex(seq) field := fields[i] switch key { case updateKeyMsg: @@ -261,31 +239,31 @@ func (db *commonMsgDatabase) BatchInsertBlock(ctx context.Context, conversationI continue // The current data has been updated, skip the current data } } - doc := unrelationtb.MsgDocModel{ - DocID: db.msg.GetDocID(conversationID, seq), - Msg: make([]*unrelationtb.MsgInfoModel, num), + doc := relation.MsgDocModel{ + DocID: db.msgTable.GetDocID(conversationID, seq), + Msg: make([]*relation.MsgInfoModel, num), } var insert int // Inserted data number for j := i; j < len(fields); j++ { seq = firstSeq + int64(j) - if db.msg.GetDocID(conversationID, seq) != doc.DocID { + if db.msgTable.GetDocID(conversationID, seq) != doc.DocID { break } insert++ switch key { case updateKeyMsg: - doc.Msg[db.msg.GetMsgIndex(seq)] = &unrelationtb.MsgInfoModel{ - Msg: fields[j].(*unrelationtb.MsgDataModel), + doc.Msg[db.msgTable.GetMsgIndex(seq)] = &relation.MsgInfoModel{ + Msg: fields[j].(*relation.MsgDataModel), } case updateKeyRevoke: - doc.Msg[db.msg.GetMsgIndex(seq)] = &unrelationtb.MsgInfoModel{ - Revoke: fields[j].(*unrelationtb.RevokeModel), + doc.Msg[db.msgTable.GetMsgIndex(seq)] = &relation.MsgInfoModel{ + Revoke: fields[j].(*relation.RevokeModel), } } } for i, model := range doc.Msg { if model == nil { - model = &unrelationtb.MsgInfoModel{} + model = &relation.MsgInfoModel{} doc.Msg[i] = model } if model.DelList == nil { @@ -308,16 +286,16 @@ func (db *commonMsgDatabase) BatchInsertBlock(ctx context.Context, conversationI func (db *commonMsgDatabase) BatchInsertChat2DB(ctx context.Context, conversationID string, msgList []*sdkws.MsgData, currentMaxSeq int64) error { if len(msgList) == 0 { - return errs.ErrArgs.Wrap("msgList is empty") + return errs.ErrArgs.WrapMsg("msgList is empty") } msgs := make([]any, len(msgList)) for i, msg := range msgList { if msg == nil { continue } - var offlinePushModel *unrelationtb.OfflinePushModel + var offlinePushModel *relation.OfflinePushModel if msg.OfflinePushInfo != nil { - offlinePushModel = &unrelationtb.OfflinePushModel{ + offlinePushModel = &relation.OfflinePushModel{ Title: msg.OfflinePushInfo.Title, Desc: msg.OfflinePushInfo.Desc, Ex: msg.OfflinePushInfo.Ex, @@ -325,7 +303,7 @@ func (db *commonMsgDatabase) BatchInsertChat2DB(ctx context.Context, conversatio IOSBadgeCount: msg.OfflinePushInfo.IOSBadgeCount, } } - msgs[i] = &unrelationtb.MsgDataModel{ + msgs[i] = &relation.MsgDataModel{ SendID: msg.SendID, RecvID: msg.RecvID, GroupID: msg.GroupID, @@ -352,15 +330,15 @@ func (db *commonMsgDatabase) BatchInsertChat2DB(ctx context.Context, conversatio return db.BatchInsertBlock(ctx, conversationID, msgs, updateKeyMsg, msgList[0].Seq) } -func (db *commonMsgDatabase) RevokeMsg(ctx context.Context, conversationID string, seq int64, revoke *unrelationtb.RevokeModel) error { +func (db *commonMsgDatabase) RevokeMsg(ctx context.Context, conversationID string, seq int64, revoke *relation.RevokeModel) error { return db.BatchInsertBlock(ctx, conversationID, []any{revoke}, updateKeyRevoke, seq) } func (db *commonMsgDatabase) MarkSingleChatMsgsAsRead(ctx context.Context, userID string, conversationID string, totalSeqs []int64) error { - for docID, seqs := range db.msg.GetDocIDSeqsMap(conversationID, totalSeqs) { + for docID, seqs := range db.msgTable.GetDocIDSeqsMap(conversationID, totalSeqs) { var indexes []int64 for _, seq := range seqs { - indexes = append(indexes, db.msg.GetMsgIndex(seq)) + indexes = append(indexes, db.msgTable.GetMsgIndex(seq)) } log.ZDebug(ctx, "MarkSingleChatMsgsAsRead", "userID", userID, "docID", docID, "indexes", indexes) if err := db.msgDocDatabase.MarkSingleChatMsgsAsRead(ctx, userID, docID, indexes); err != nil { @@ -372,25 +350,25 @@ func (db *commonMsgDatabase) MarkSingleChatMsgsAsRead(ctx context.Context, userI } func (db *commonMsgDatabase) DeleteMessagesFromCache(ctx context.Context, conversationID string, seqs []int64) error { - return db.cache.DeleteMessages(ctx, conversationID, seqs) + return db.msg.DeleteMessages(ctx, conversationID, seqs) } func (db *commonMsgDatabase) DelUserDeleteMsgsList(ctx context.Context, conversationID string, seqs []int64) { - db.cache.DelUserDeleteMsgsList(ctx, conversationID, seqs) + db.msg.DelUserDeleteMsgsList(ctx, conversationID, seqs) } func (db *commonMsgDatabase) BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNew bool, err error) { - currentMaxSeq, err := db.cache.GetMaxSeq(ctx, conversationID) + currentMaxSeq, err := db.seq.GetMaxSeq(ctx, conversationID) if err != nil && errs.Unwrap(err) != redis.Nil { - log.ZError(ctx, "db.cache.GetMaxSeq", err) + log.ZError(ctx, "db.seq.GetMaxSeq", err) return 0, false, err } lenList := len(msgs) - if int64(lenList) > db.msg.GetSingleGocMsgNum() { - return 0, false, errors.New("too large") + if int64(lenList) > db.msgTable.GetSingleGocMsgNum() { + return 0, false, errs.New("message count exceeds limit", "limit", db.msgTable.GetSingleGocMsgNum()).Wrap() } if lenList < 1 { - return 0, false, errors.New("too short as 0") + return 0, false, errs.New("no messages to insert", "minCount", 1).Wrap() } if errs.Unwrap(err) == redis.Nil { isNew = true @@ -402,19 +380,22 @@ func (db *commonMsgDatabase) BatchInsertChat2Cache(ctx context.Context, conversa m.Seq = currentMaxSeq userSeqMap[m.SendID] = m.Seq } - failedNum, err := db.cache.SetMessageToCache(ctx, conversationID, msgs) + + failedNum, err := db.msg.SetMessageToCache(ctx, conversationID, msgs) if err != nil { prommetrics.MsgInsertRedisFailedCounter.Add(float64(failedNum)) log.ZError(ctx, "setMessageToCache error", err, "len", len(msgs), "conversationID", conversationID) } else { prommetrics.MsgInsertRedisSuccessCounter.Inc() } - err = db.cache.SetMaxSeq(ctx, conversationID, currentMaxSeq) + + err = db.seq.SetMaxSeq(ctx, conversationID, currentMaxSeq) if err != nil { - log.ZError(ctx, "db.cache.SetMaxSeq error", err, "conversationID", conversationID) + log.ZError(ctx, "db.seq.SetMaxSeq error", err, "conversationID", conversationID) prommetrics.SeqSetFailedCounter.Inc() } - err = db.cache.SetHasReadSeqs(ctx, conversationID, userSeqMap) + + err = db.seq.SetHasReadSeqs(ctx, conversationID, userSeqMap) if err != nil { log.ZError(ctx, "SetHasReadSeqs error", err, "userSeqMap", userSeqMap, "conversationID", conversationID) prommetrics.SeqSetFailedCounter.Inc() @@ -423,7 +404,7 @@ func (db *commonMsgDatabase) BatchInsertChat2Cache(ctx context.Context, conversa } func (db *commonMsgDatabase) getMsgBySeqs(ctx context.Context, userID, conversationID string, seqs []int64) (totalMsgs []*sdkws.MsgData, err error) { - for docID, seqs := range db.msg.GetDocIDSeqsMap(conversationID, seqs) { + for docID, seqs := range db.msgTable.GetDocIDSeqsMap(conversationID, seqs) { // log.ZDebug(ctx, "getMsgBySeqs", "docID", docID, "seqs", seqs) msgs, err := db.findMsgInfoBySeq(ctx, userID, docID, conversationID, seqs) if err != nil { @@ -436,7 +417,7 @@ func (db *commonMsgDatabase) getMsgBySeqs(ctx context.Context, userID, conversat return totalMsgs, nil } -func (db *commonMsgDatabase) handlerDBMsg(ctx context.Context, cache map[int64][]*unrelationtb.MsgInfoModel, userID, conversationID string, msg *unrelationtb.MsgInfoModel) { +func (db *commonMsgDatabase) handlerDBMsg(ctx context.Context, cache map[int64][]*relation.MsgInfoModel, userID, conversationID string, msg *relation.MsgInfoModel) { if msg.IsRead { msg.Msg.IsRead = true } @@ -458,12 +439,12 @@ func (db *commonMsgDatabase) handlerDBMsg(ctx context.Context, cache map[int64][ if quoteMsg.QuoteMessage == nil || quoteMsg.QuoteMessage.ContentType == constant.MsgRevokeNotification { return } - var msgs []*unrelationtb.MsgInfoModel + var msgs []*relation.MsgInfoModel if v, ok := cache[quoteMsg.QuoteMessage.Seq]; ok { msgs = v } else { if quoteMsg.QuoteMessage.Seq > 0 { - ms, err := db.msgDocDatabase.GetMsgBySeqIndexIn1Doc(ctx, userID, db.msg.GetDocID(conversationID, quoteMsg.QuoteMessage.Seq), []int64{quoteMsg.QuoteMessage.Seq}) + ms, err := db.msgDocDatabase.GetMsgBySeqIndexIn1Doc(ctx, userID, db.msgTable.GetDocID(conversationID, quoteMsg.QuoteMessage.Seq), []int64{quoteMsg.QuoteMessage.Seq}) if err != nil { log.ZError(ctx, "GetMsgBySeqIndexIn1Doc", err, "conversationID", conversationID, "seq", quoteMsg.QuoteMessage.Seq) return @@ -487,17 +468,17 @@ func (db *commonMsgDatabase) handlerDBMsg(ctx context.Context, cache map[int64][ return } msg.Msg.Content = string(data) - if _, err := db.msgDocDatabase.UpdateMsg(ctx, db.msg.GetDocID(conversationID, msg.Msg.Seq), db.msg.GetMsgIndex(msg.Msg.Seq), "msg", msg.Msg); err != nil { + if _, err := db.msgDocDatabase.UpdateMsg(ctx, db.msgTable.GetDocID(conversationID, msg.Msg.Seq), db.msgTable.GetMsgIndex(msg.Msg.Seq), "msg", msg.Msg); err != nil { log.ZError(ctx, "UpdateMsgContent", err) } } -func (db *commonMsgDatabase) findMsgInfoBySeq(ctx context.Context, userID, docID string, conversationID string, seqs []int64) (totalMsgs []*unrelationtb.MsgInfoModel, err error) { +func (db *commonMsgDatabase) findMsgInfoBySeq(ctx context.Context, userID, docID string, conversationID string, seqs []int64) (totalMsgs []*relation.MsgInfoModel, err error) { msgs, err := db.msgDocDatabase.GetMsgBySeqIndexIn1Doc(ctx, userID, docID, seqs) if err != nil { return nil, err } - tempCache := make(map[int64][]*unrelationtb.MsgInfoModel) + tempCache := make(map[int64][]*relation.MsgInfoModel) for _, msg := range msgs { db.handlerDBMsg(ctx, tempCache, userID, conversationID, msg) } @@ -506,7 +487,7 @@ func (db *commonMsgDatabase) findMsgInfoBySeq(ctx context.Context, userID, docID func (db *commonMsgDatabase) getMsgBySeqsRange(ctx context.Context, userID string, conversationID string, allSeqs []int64, begin, end int64) (seqMsgs []*sdkws.MsgData, err error) { log.ZDebug(ctx, "getMsgBySeqsRange", "conversationID", conversationID, "allSeqs", allSeqs, "begin", begin, "end", end) - for docID, seqs := range db.msg.GetDocIDSeqsMap(conversationID, allSeqs) { + for docID, seqs := range db.msgTable.GetDocIDSeqsMap(conversationID, allSeqs) { log.ZDebug(ctx, "getMsgBySeqsRange", "docID", docID, "seqs", seqs) msgs, err := db.findMsgInfoBySeq(ctx, userID, docID, conversationID, seqs) if err != nil { @@ -542,23 +523,23 @@ func (db *commonMsgDatabase) getMsgBySeqsRange(ctx context.Context, userID strin // "userMinSeq" can be set as the same value as the conversation's "maxSeq" at the moment they join the group. // This ensures that their message retrieval starts from the point they joined. func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID string, conversationID string, begin, end, num, userMaxSeq int64) (int64, int64, []*sdkws.MsgData, error) { - userMinSeq, err := db.cache.GetConversationUserMinSeq(ctx, conversationID, userID) + userMinSeq, err := db.seq.GetConversationUserMinSeq(ctx, conversationID, userID) if err != nil && errs.Unwrap(err) != redis.Nil { return 0, 0, nil, err } - minSeq, err := db.cache.GetMinSeq(ctx, conversationID) + minSeq, err := db.seq.GetMinSeq(ctx, conversationID) if err != nil && errs.Unwrap(err) != redis.Nil { return 0, 0, nil, err } if userMinSeq > minSeq { minSeq = userMinSeq } - //"minSeq" represents the startSeq value that the user can retrieve. + // "minSeq" represents the startSeq value that the user can retrieve. if minSeq > end { - log.ZInfo(ctx, "minSeq > end", "minSeq", minSeq, "end", end) + log.ZWarn(ctx, "minSeq > end", errs.New("minSeq>end"), "minSeq", minSeq, "end", end) return 0, 0, nil, nil } - maxSeq, err := db.cache.GetMaxSeq(ctx, conversationID) + maxSeq, err := db.seq.GetMaxSeq(ctx, conversationID) if err != nil && errs.Unwrap(err) != redis.Nil { return 0, 0, nil, err } @@ -568,7 +549,7 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin maxSeq = userMaxSeq } } - //"maxSeq" represents the endSeq value that the user can retrieve. + // "maxSeq" represents the endSeq value that the user can retrieve. if begin < minSeq { begin = minSeq @@ -576,9 +557,9 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin if end > maxSeq { end = maxSeq } - //"begin" and "end" represent the actual startSeq and endSeq values that the user can retrieve. + // "begin" and "end" represent the actual startSeq and endSeq values that the user can retrieve. if end < begin { - return 0, 0, nil, errs.ErrArgs.Wrap("seq end < begin") + return 0, 0, nil, errs.ErrArgs.WrapMsg("seq end < begin") } var seqs []int64 if end-begin+1 <= num { @@ -591,25 +572,13 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin } } - //167 178 10 - //if end-num < { - // - //} - //var seqs []int64 - //for i := end; i > end-num; i-- { - // if i >= begin { - // seqs = append([]int64{i}, seqs...) - // } else { - // break - // } - //} if len(seqs) == 0 { return 0, 0, nil, nil } newBegin := seqs[0] newEnd := seqs[len(seqs)-1] log.ZDebug(ctx, "GetMsgBySeqsRange", "first seqs", seqs, "newBegin", newBegin, "newEnd", newEnd) - cachedMsgs, failedSeqs, err := db.cache.GetMessagesBySeq(ctx, conversationID, seqs) + cachedMsgs, failedSeqs, err := db.msg.GetMessagesBySeq(ctx, conversationID, seqs) if err != nil { if err != redis.Nil { @@ -618,13 +587,13 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin } var successMsgs []*sdkws.MsgData if len(cachedMsgs) > 0 { - delSeqs, err := db.cache.GetUserDelList(ctx, userID, conversationID) + delSeqs, err := db.msg.GetUserDelList(ctx, userID, conversationID) if err != nil && errs.Unwrap(err) != redis.Nil { return 0, 0, nil, err } var cacheDelNum int for _, msg := range cachedMsgs { - if !utils.Contain(msg.Seq, delSeqs...) { + if !datautil.Contain(msg.Seq, delSeqs...) { successMsgs = append(successMsgs, msg) } else { cacheDelNum += 1 @@ -635,7 +604,7 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin for i := 1; i <= cacheDelNum; { newSeq := newBegin - int64(i) if newSeq >= begin { - if !utils.Contain(newSeq, delSeqs...) { + if !datautil.Contain(newSeq, delSeqs...) { log.ZDebug(ctx, "seq del in cache, a new seq in range append", "new seq", newSeq) reGetSeqsCache = append(reGetSeqsCache, newSeq) i++ @@ -646,7 +615,7 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin } if len(reGetSeqsCache) > 0 { log.ZDebug(ctx, "reGetSeqsCache", "reGetSeqsCache", reGetSeqsCache) - cachedMsgs, failedSeqs2, err := db.cache.GetMessagesBySeq(ctx, conversationID, reGetSeqsCache) + cachedMsgs, failedSeqs2, err := db.msg.GetMessagesBySeq(ctx, conversationID, reGetSeqsCache) if err != nil { if err != redis.Nil { @@ -676,15 +645,15 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin } func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, conversationID string, seqs []int64) (int64, int64, []*sdkws.MsgData, error) { - userMinSeq, err := db.cache.GetConversationUserMinSeq(ctx, conversationID, userID) + userMinSeq, err := db.seq.GetConversationUserMinSeq(ctx, conversationID, userID) if err != nil && errs.Unwrap(err) != redis.Nil { return 0, 0, nil, err } - minSeq, err := db.cache.GetMinSeq(ctx, conversationID) + minSeq, err := db.seq.GetMinSeq(ctx, conversationID) if err != nil && errs.Unwrap(err) != redis.Nil { return 0, 0, nil, err } - maxSeq, err := db.cache.GetMaxSeq(ctx, conversationID) + maxSeq, err := db.seq.GetMaxSeq(ctx, conversationID) if err != nil && errs.Unwrap(err) != redis.Nil { return 0, 0, nil, err } @@ -697,28 +666,14 @@ func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, co newSeqs = append(newSeqs, seq) } } - successMsgs, failedSeqs, err := db.cache.GetMessagesBySeq(ctx, conversationID, newSeqs) + successMsgs, failedSeqs, err := db.msg.GetMessagesBySeq(ctx, conversationID, newSeqs) if err != nil { if err != redis.Nil { log.ZError(ctx, "get message from redis exception", err, "failedSeqs", failedSeqs, "conversationID", conversationID) } } - log.ZInfo( - ctx, - "db.cache.GetMessagesBySeq", - "userID", - userID, - "conversationID", - conversationID, - "seqs", - seqs, - "successMsgs", - len(successMsgs), - "failedSeqs", - failedSeqs, - "conversationID", - conversationID, - ) + log.ZDebug(ctx, "db.seq.GetMessagesBySeq", "userID", userID, "conversationID", conversationID, "seqs", + seqs, "len(successMsgs)", len(successMsgs), "failedSeqs", failedSeqs) if len(failedSeqs) > 0 { mongoMsgs, err := db.getMsgBySeqs(ctx, userID, conversationID, failedSeqs) @@ -739,17 +694,17 @@ func (db *commonMsgDatabase) DeleteConversationMsgsAndSetMinSeq(ctx context.Cont if err != nil { return err } - log.ZInfo(ctx, "DeleteConversationMsgsAndSetMinSeq", "conversationID", conversationID, "minSeq", minSeq) + log.ZDebug(ctx, "DeleteConversationMsgsAndSetMinSeq", "conversationID", conversationID, "minSeq", minSeq) if minSeq == 0 { return nil } if remainTime == 0 { - err = db.cache.CleanUpOneConversationAllMsg(ctx, conversationID) + err = db.msg.CleanUpOneConversationAllMsg(ctx, conversationID) if err != nil { log.ZWarn(ctx, "CleanUpOneUserAllMsg", err, "conversationID", conversationID) } } - return db.cache.SetMinSeq(ctx, conversationID, minSeq) + return db.seq.SetMinSeq(ctx, conversationID, minSeq) } func (db *commonMsgDatabase) UserMsgsDestruct(ctx context.Context, userID string, conversationID string, destructTime int64, lastMsgDestructTime time.Time) (seqs []int64, err error) { @@ -759,7 +714,7 @@ func (db *commonMsgDatabase) UserMsgsDestruct(ctx context.Context, userID string msgDocModel, err := db.msgDocDatabase.GetMsgDocModelByIndex(ctx, conversationID, index, 1) if err != nil || msgDocModel.DocID == "" { if err != nil { - if err == unrelation.ErrMsgListNotExist { + if err == relation.ErrMsgListNotExist { log.ZDebug(ctx, "not doc find", "conversationID", conversationID, "userID", userID, "index", index) } else { log.ZError(ctx, "deleteMsgRecursion GetUserMsgListByIndex failed", err, "conversationID", conversationID, "index", index) @@ -769,14 +724,14 @@ func (db *commonMsgDatabase) UserMsgsDestruct(ctx context.Context, userID string break } index++ - //&& msgDocModel.Msg[0].Msg.SendTime > lastMsgDestructTime.UnixMilli() + // && msgDocModel.Msg[0].Msg.SendTime > lastMsgDestructTime.UnixMilli() if len(msgDocModel.Msg) > 0 { i := 0 var over bool for _, msg := range msgDocModel.Msg { i++ if msg != nil && msg.Msg != nil && msg.Msg.SendTime+destructTime*1000 <= time.Now().UnixMilli() { - if msg.Msg.SendTime+destructTime*1000 > lastMsgDestructTime.UnixMilli() && !utils.Contain(userID, msg.DelList...) { + if msg.Msg.SendTime+destructTime*1000 > lastMsgDestructTime.UnixMilli() && !datautil.Contain(userID, msg.DelList...) { seqs = append(seqs, msg.Msg.Seq) } } else { @@ -794,12 +749,12 @@ func (db *commonMsgDatabase) UserMsgsDestruct(ctx context.Context, userID string log.ZDebug(ctx, "UserMsgsDestruct", "conversationID", conversationID, "userID", userID, "seqs", seqs) if len(seqs) > 0 { userMinSeq := seqs[len(seqs)-1] + 1 - currentUserMinSeq, err := db.cache.GetConversationUserMinSeq(ctx, conversationID, userID) + currentUserMinSeq, err := db.seq.GetConversationUserMinSeq(ctx, conversationID, userID) if err != nil && errs.Unwrap(err) != redis.Nil { return nil, err } if currentUserMinSeq < userMinSeq { - if err := db.cache.SetConversationUserMinSeq(ctx, conversationID, userID, userMinSeq); err != nil { + if err := db.seq.SetConversationUserMinSeq(ctx, conversationID, userID, userMinSeq); err != nil { return nil, err } } @@ -826,7 +781,7 @@ func (db *commonMsgDatabase) deleteMsgRecursion(ctx context.Context, conversatio msgDocModel, err := db.msgDocDatabase.GetMsgDocModelByIndex(ctx, conversationID, index, 1) if err != nil || msgDocModel.DocID == "" { if err != nil { - if err == unrelation.ErrMsgListNotExist { + if err == relation.ErrMsgListNotExist { log.ZDebug(ctx, "deleteMsgRecursion ErrMsgListNotExist", "conversationID", conversationID, "index:", index) } else { log.ZError(ctx, "deleteMsgRecursion GetUserMsgListByIndex failed", err, "conversationID", conversationID, "index", index) @@ -840,10 +795,10 @@ func (db *commonMsgDatabase) deleteMsgRecursion(ctx context.Context, conversatio return delStruct.getSetMinSeq() + 1, nil } log.ZDebug(ctx, "doc info", "conversationID", conversationID, "index", index, "docID", msgDocModel.DocID, "len", len(msgDocModel.Msg)) - if int64(len(msgDocModel.Msg)) > db.msg.GetSingleGocMsgNum() { + if int64(len(msgDocModel.Msg)) > db.msgTable.GetSingleGocMsgNum() { log.ZWarn(ctx, "msgs too large", nil, "lenth", len(msgDocModel.Msg), "docID:", msgDocModel.DocID) } - if msgDocModel.IsFull() && msgDocModel.Msg[len(msgDocModel.Msg)-1].Msg.SendTime+(remainTime*1000) < utils.GetCurrentTimestampByMill() { + if msgDocModel.IsFull() && msgDocModel.Msg[len(msgDocModel.Msg)-1].Msg.SendTime+(remainTime*1000) < timeutil.GetCurrentTimestampByMill() { log.ZDebug(ctx, "doc is full and all msg is expired", "docID", msgDocModel.DocID) delStruct.delDocIDs = append(delStruct.delDocIDs, msgDocModel.DocID) delStruct.minSeq = msgDocModel.Msg[len(msgDocModel.Msg)-1].Msg.Seq @@ -851,7 +806,7 @@ func (db *commonMsgDatabase) deleteMsgRecursion(ctx context.Context, conversatio var delMsgIndexs []int for i, MsgInfoModel := range msgDocModel.Msg { if MsgInfoModel != nil && MsgInfoModel.Msg != nil { - if utils.GetCurrentTimestampByMill() > MsgInfoModel.Msg.SendTime+(remainTime*1000) { + if timeutil.GetCurrentTimestampByMill() > MsgInfoModel.Msg.SendTime+(remainTime*1000) { delMsgIndexs = append(delMsgIndexs, i) } } @@ -868,13 +823,13 @@ func (db *commonMsgDatabase) deleteMsgRecursion(ctx context.Context, conversatio } func (db *commonMsgDatabase) DeleteMsgsPhysicalBySeqs(ctx context.Context, conversationID string, allSeqs []int64) error { - if err := db.cache.DeleteMessages(ctx, conversationID, allSeqs); err != nil { + if err := db.msg.DeleteMessages(ctx, conversationID, allSeqs); err != nil { return err } - for docID, seqs := range db.msg.GetDocIDSeqsMap(conversationID, allSeqs) { + for docID, seqs := range db.msgTable.GetDocIDSeqsMap(conversationID, allSeqs) { var indexes []int for _, seq := range seqs { - indexes = append(indexes, int(db.msg.GetMsgIndex(seq))) + indexes = append(indexes, int(db.msgTable.GetMsgIndex(seq))) } if err := db.msgDocDatabase.DeleteMsgsInOneDocByIndex(ctx, docID, indexes); err != nil { return err @@ -884,7 +839,7 @@ func (db *commonMsgDatabase) DeleteMsgsPhysicalBySeqs(ctx context.Context, conve } func (db *commonMsgDatabase) DeleteUserMsgsBySeqs(ctx context.Context, userID string, conversationID string, seqs []int64) error { - cachedMsgs, _, err := db.cache.GetMessagesBySeq(ctx, conversationID, seqs) + cachedMsgs, _, err := db.msg.GetMessagesBySeq(ctx, conversationID, seqs) if err != nil && errs.Unwrap(err) != redis.Nil { log.ZWarn(ctx, "DeleteUserMsgsBySeqs", err, "conversationID", conversationID, "seqs", seqs) return err @@ -894,14 +849,14 @@ func (db *commonMsgDatabase) DeleteUserMsgsBySeqs(ctx context.Context, userID st for _, msg := range cachedMsgs { cacheSeqs = append(cacheSeqs, msg.Seq) } - if err := db.cache.UserDeleteMsgs(ctx, conversationID, cacheSeqs, userID); err != nil { + if err := db.msg.UserDeleteMsgs(ctx, conversationID, cacheSeqs, userID); err != nil { return err } } - for docID, seqs := range db.msg.GetDocIDSeqsMap(conversationID, seqs) { + for docID, seqs := range db.msgTable.GetDocIDSeqsMap(conversationID, seqs) { for _, seq := range seqs { - if _, err := db.msgDocDatabase.PushUnique(ctx, docID, db.msg.GetMsgIndex(seq), "del_list", []string{userID}); err != nil { + if _, err := db.msgDocDatabase.PushUnique(ctx, docID, db.msgTable.GetMsgIndex(seq), "del_list", []string{userID}); err != nil { return err } } @@ -915,91 +870,91 @@ func (db *commonMsgDatabase) DeleteMsgsBySeqs(ctx context.Context, conversationI func (db *commonMsgDatabase) CleanUpUserConversationsMsgs(ctx context.Context, user string, conversationIDs []string) { for _, conversationID := range conversationIDs { - maxSeq, err := db.cache.GetMaxSeq(ctx, conversationID) + maxSeq, err := db.seq.GetMaxSeq(ctx, conversationID) if err != nil { if err == redis.Nil { - log.ZInfo(ctx, "max seq is nil", "conversationID", conversationID) + log.ZDebug(ctx, "max seq is nil", "conversationID", conversationID) } else { log.ZError(ctx, "get max seq failed", err, "conversationID", conversationID) } continue } - if err := db.cache.SetMinSeq(ctx, conversationID, maxSeq+1); err != nil { + if err := db.seq.SetMinSeq(ctx, conversationID, maxSeq+1); err != nil { log.ZError(ctx, "set min seq failed", err, "conversationID", conversationID, "minSeq", maxSeq+1) } } } func (db *commonMsgDatabase) SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error { - return db.cache.SetMaxSeq(ctx, conversationID, maxSeq) + return db.seq.SetMaxSeq(ctx, conversationID, maxSeq) } func (db *commonMsgDatabase) GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) { - return db.cache.GetMaxSeqs(ctx, conversationIDs) + return db.seq.GetMaxSeqs(ctx, conversationIDs) } func (db *commonMsgDatabase) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) { - return db.cache.GetMaxSeq(ctx, conversationID) + return db.seq.GetMaxSeq(ctx, conversationID) } func (db *commonMsgDatabase) SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error { - return db.cache.SetMinSeq(ctx, conversationID, minSeq) + return db.seq.SetMinSeq(ctx, conversationID, minSeq) } func (db *commonMsgDatabase) SetMinSeqs(ctx context.Context, seqs map[string]int64) error { - return db.cache.SetMinSeqs(ctx, seqs) + return db.seq.SetMinSeqs(ctx, seqs) } func (db *commonMsgDatabase) GetMinSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) { - return db.cache.GetMinSeqs(ctx, conversationIDs) + return db.seq.GetMinSeqs(ctx, conversationIDs) } func (db *commonMsgDatabase) GetMinSeq(ctx context.Context, conversationID string) (int64, error) { - return db.cache.GetMinSeq(ctx, conversationID) + return db.seq.GetMinSeq(ctx, conversationID) } func (db *commonMsgDatabase) GetConversationUserMinSeq(ctx context.Context, conversationID string, userID string) (int64, error) { - return db.cache.GetConversationUserMinSeq(ctx, conversationID, userID) + return db.seq.GetConversationUserMinSeq(ctx, conversationID, userID) } func (db *commonMsgDatabase) GetConversationUserMinSeqs(ctx context.Context, conversationID string, userIDs []string) (map[string]int64, error) { - return db.cache.GetConversationUserMinSeqs(ctx, conversationID, userIDs) + return db.seq.GetConversationUserMinSeqs(ctx, conversationID, userIDs) } func (db *commonMsgDatabase) SetConversationUserMinSeq(ctx context.Context, conversationID string, userID string, minSeq int64) error { - return db.cache.SetConversationUserMinSeq(ctx, conversationID, userID, minSeq) + return db.seq.SetConversationUserMinSeq(ctx, conversationID, userID, minSeq) } func (db *commonMsgDatabase) SetConversationUserMinSeqs(ctx context.Context, conversationID string, seqs map[string]int64) (err error) { - return db.cache.SetConversationUserMinSeqs(ctx, conversationID, seqs) + return db.seq.SetConversationUserMinSeqs(ctx, conversationID, seqs) } func (db *commonMsgDatabase) SetUserConversationsMinSeqs(ctx context.Context, userID string, seqs map[string]int64) error { - return db.cache.SetUserConversationsMinSeqs(ctx, userID, seqs) + return db.seq.SetUserConversationsMinSeqs(ctx, userID, seqs) } func (db *commonMsgDatabase) UserSetHasReadSeqs(ctx context.Context, userID string, hasReadSeqs map[string]int64) error { - return db.cache.UserSetHasReadSeqs(ctx, userID, hasReadSeqs) + return db.seq.UserSetHasReadSeqs(ctx, userID, hasReadSeqs) } func (db *commonMsgDatabase) SetHasReadSeq(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error { - return db.cache.SetHasReadSeq(ctx, userID, conversationID, hasReadSeq) + return db.seq.SetHasReadSeq(ctx, userID, conversationID, hasReadSeq) } func (db *commonMsgDatabase) GetHasReadSeqs(ctx context.Context, userID string, conversationIDs []string) (map[string]int64, error) { - return db.cache.GetHasReadSeqs(ctx, userID, conversationIDs) + return db.seq.GetHasReadSeqs(ctx, userID, conversationIDs) } func (db *commonMsgDatabase) GetHasReadSeq(ctx context.Context, userID string, conversationID string) (int64, error) { - return db.cache.GetHasReadSeq(ctx, userID, conversationID) + return db.seq.GetHasReadSeq(ctx, userID, conversationID) } func (db *commonMsgDatabase) SetSendMsgStatus(ctx context.Context, id string, status int32) error { - return db.cache.SetSendMsgStatus(ctx, id, status) + return db.msg.SetSendMsgStatus(ctx, id, status) } func (db *commonMsgDatabase) GetSendMsgStatus(ctx context.Context, id string) (int32, error) { - return db.cache.GetSendMsgStatus(ctx, id) + return db.msg.GetSendMsgStatus(ctx, id) } func (db *commonMsgDatabase) GetConversationMinMaxSeqInMongoAndCache(ctx context.Context, conversationID string) (minSeqMongo, maxSeqMongo, minSeqCache, maxSeqCache int64, err error) { @@ -1007,11 +962,11 @@ func (db *commonMsgDatabase) GetConversationMinMaxSeqInMongoAndCache(ctx context if err != nil { return } - minSeqCache, err = db.cache.GetMinSeq(ctx, conversationID) + minSeqCache, err = db.seq.GetMinSeq(ctx, conversationID) if err != nil { return } - maxSeqCache, err = db.cache.GetMaxSeq(ctx, conversationID) + maxSeqCache, err = db.seq.GetMaxSeq(ctx, conversationID) if err != nil { return } @@ -1044,7 +999,7 @@ func (db *commonMsgDatabase) RangeUserSendCount( ase bool, pageNumber int32, showNumber int32, -) (msgCount int64, userCount int64, users []*unrelationtb.UserCount, dateCount map[string]int64, err error) { +) (msgCount int64, userCount int64, users []*relation.UserCount, dateCount map[string]int64, err error) { return db.msgDocDatabase.RangeUserSendCount(ctx, start, end, group, ase, pageNumber, showNumber) } @@ -1055,7 +1010,7 @@ func (db *commonMsgDatabase) RangeGroupSendCount( ase bool, pageNumber int32, showNumber int32, -) (msgCount int64, userCount int64, groups []*unrelationtb.GroupCount, dateCount map[string]int64, err error) { +) (msgCount int64, userCount int64, groups []*relation.GroupCount, dateCount map[string]int64, err error) { return db.msgDocDatabase.RangeGroupSendCount(ctx, start, end, ase, pageNumber, showNumber) } @@ -1078,12 +1033,12 @@ func (db *commonMsgDatabase) FindOneByDocIDs(ctx context.Context, conversationID totalMsgs := make(map[string]*sdkws.MsgData) for _, conversationID := range conversationIDs { seq := seqs[conversationID] - docID := db.msg.GetDocID(conversationID, seq) + docID := db.msgTable.GetDocID(conversationID, seq) msgs, err := db.msgDocDatabase.FindOneByDocID(ctx, docID) if err != nil { return nil, err } - index := db.msg.GetMsgIndex(seq) + index := db.msgTable.GetMsgIndex(seq) totalMsgs[conversationID] = convert.MsgDB2Pb(msgs.Msg[index].Msg) } return totalMsgs, nil diff --git a/pkg/common/db/controller/msg_test.go b/pkg/common/db/controller/msg_test.go deleted file mode 100644 index 4c2ab20da..000000000 --- a/pkg/common/db/controller/msg_test.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package controller - -import ( - "context" - "fmt" - "math/rand" - "strconv" - "sync" - "testing" - "time" - - "github.com/OpenIMSDK/tools/log" - - "go.mongodb.org/mongo-driver/bson" - - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - unrelationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" -) - -func Test_BatchInsertChat2DB(t *testing.T) { - conf := config.NewGlobalConfig() - conf.Mongo.Address = []string{"192.168.44.128:37017"} - // conf.Mongo.Timeout = 60 - conf.Mongo.Database = "openIM" - // conf.Mongo.Source = "admin" - conf.Mongo.Username = "root" - conf.Mongo.Password = "openIM123" - conf.Mongo.MaxPoolSize = 100 - conf.RetainChatRecords = 3650 - conf.ChatRecordsClearTime = "0 2 * * 3" - - mongo, err := unrelation.NewMongo(conf) - if err != nil { - t.Fatal(err) - } - err = mongo.GetDatabase(conf.Mongo.Database).Client().Ping(context.Background(), nil) - if err != nil { - panic(err) - } - - db := &commonMsgDatabase{ - msgDocDatabase: unrelation.NewMsgMongoDriver(mongo.GetDatabase(conf.Mongo.Database)), - } - - //ctx := context.Background() - //msgs := make([]*sdkws.MsgData, 0, 1) - //for i := 0; i < cap(msgs); i++ { - // msgs = append(msgs, &sdkws.MsgData{ - // Content: []byte(fmt.Sprintf("test-%d", i)), - // SendTime: time.Now().UnixMilli(), - // }) - //} - //err = db.BatchInsertChat2DB(ctx, "test", msgs, 0) - //if err != nil { - // panic(err) - //} - - _ = db.BatchInsertChat2DB - c := mongo.GetDatabase(conf.Mongo.Database).Collection("msg") - - ch := make(chan int) - rand.Seed(time.Now().UnixNano()) - - index := 10 - - var wg sync.WaitGroup - for i := 0; i < 1000; i++ { - wg.Add(1) - go func(channelID int) { - defer wg.Done() - <-ch - var arr []string - for i := 0; i < 500; i++ { - arr = append(arr, strconv.Itoa(i+1)) - } - rand.Shuffle(len(arr), func(i, j int) { - arr[i], arr[j] = arr[j], arr[i] - }) - for j, s := range arr { - if j == 0 { - fmt.Printf("channnelID: %d, arr[0]: %s\n", channelID, arr[j]) - } - filter := bson.M{"doc_id": "test:0"} - update := bson.M{ - "$addToSet": bson.M{ - fmt.Sprintf("msgs.%d.del_list", index): bson.M{"$each": []string{s}}, - }, - } - _, err := c.UpdateOne(context.Background(), filter, update) - if err != nil { - t.Fatal(err) - } - } - }(i) - } - - for i := 0; i < 1000; i++ { - wg.Add(1) - go func() { - defer wg.Done() - <-ch - var arr []string - for i := 0; i < 500; i++ { - arr = append(arr, strconv.Itoa(1001+i)) - } - rand.Shuffle(len(arr), func(i, j int) { - arr[i], arr[j] = arr[j], arr[i] - }) - for _, s := range arr { - filter := bson.M{"doc_id": "test:0"} - update := bson.M{ - "$addToSet": bson.M{ - fmt.Sprintf("msgs.%d.read_list", index): bson.M{"$each": []string{s}}, - }, - } - _, err := c.UpdateOne(context.Background(), filter, update) - if err != nil { - t.Fatal(err) - } - } - }() - } - - time.Sleep(time.Second * 2) - - close(ch) - - wg.Wait() - -} - -func GetDB() *commonMsgDatabase { - conf := config.NewGlobalConfig() - conf.Mongo.Address = []string{"203.56.175.233:37017"} - // conf.Mongo.Timeout = 60 - conf.Mongo.Database = "openim_v3" - // conf.Mongo.Source = "admin" - conf.Mongo.Username = "root" - conf.Mongo.Password = "openIM123" - conf.Mongo.MaxPoolSize = 100 - conf.RetainChatRecords = 3650 - conf.ChatRecordsClearTime = "0 2 * * 3" - - mongo, err := unrelation.NewMongo(conf) - if err != nil { - panic(err) - } - err = mongo.GetDatabase(conf.Mongo.Database).Client().Ping(context.Background(), nil) - if err != nil { - panic(err) - } - return &commonMsgDatabase{ - msgDocDatabase: unrelation.NewMsgMongoDriver(mongo.GetDatabase(conf.Mongo.Database)), - } -} - -func Test_Insert(t *testing.T) { - db := GetDB() - ctx := context.Background() - var arr []any - for i := 0; i < 345; i++ { - if i%2 == 0 { - arr = append(arr, (*unrelationtb.MsgDataModel)(nil)) - continue - } - arr = append(arr, &unrelationtb.MsgDataModel{ - Seq: int64(i), - Content: fmt.Sprintf("test-%d", i), - }) - } - if err := db.BatchInsertBlock(ctx, "test", arr, updateKeyMsg, 1); err != nil { - t.Fatal(err) - } -} - -func Test_Revoke(t *testing.T) { - db := GetDB() - ctx := context.Background() - var arr []any - for i := 0; i < 456; i++ { - arr = append(arr, &unrelationtb.RevokeModel{ - UserID: "uid_" + strconv.Itoa(i), - Nickname: "uname_" + strconv.Itoa(i), - Time: time.Now().UnixMilli(), - }) - } - if err := db.BatchInsertBlock(ctx, "test", arr, updateKeyRevoke, 123); err != nil { - t.Fatal(err) - } -} - -func Test_FindBySeq(t *testing.T) { - if err := log.InitFromConfig("", "", 6, true, false, "", 2, 1); err != nil { - t.Fatal(err) - } - db := GetDB() - ctx := context.Background() - fmt.Println( - db.msgDocDatabase.(*unrelation.MsgMongoDriver).GetMsgBySeqIndexIn1Doc(ctx, "100", "si_100_101:0", []int64{1}), - ) - //res, err := db.msgDocDatabase.GetMsgBySeqIndexIn1Doc(ctx, "123456", "test:0", []int64{1, 2, 3}) - //if err != nil { - // t.Fatal(err) - //} - //db.GetMsgBySeqs(ctx, "100", "si_100_101:0", []int64{6}) - //data, _ := json.Marshal(res) - //fmt.Println(string(data)) -} - -//func Test_Delete(t *testing.T) { -// db := GetDB() -// ctx := context.Background() -// var arr []any -// for i := 0; i < 123; i++ { -// arr = append(arr, []string{"uid_1", "uid_2"}) -// } -// if err := db.BatchInsertBlock(ctx, "test", arr, updateKeyDel, 210); err != nil { -// t.Fatal(err) -// } -//} - -func TestName(t *testing.T) { - db := GetDB() - var seqs []int64 - for i := int64(1); i <= 50; i++ { - seqs = append(seqs, i) - } - msgs, err := db.getMsgBySeqsRange(context.Background(), "4931176757", "si_3866692501_4931176757", seqs, seqs[0], seqs[len(seqs)-1]) - if err != nil { - t.Fatal(err) - } - - t.Log(msgs) - -} diff --git a/pkg/common/db/controller/push.go b/pkg/common/db/controller/push.go index 76d8c3efb..390d70b7b 100644 --- a/pkg/common/db/controller/push.go +++ b/pkg/common/db/controller/push.go @@ -25,10 +25,10 @@ type PushDatabase interface { } type pushDataBase struct { - cache cache.MsgModel + cache cache.ThirdCache } -func NewPushDatabase(cache cache.MsgModel) PushDatabase { +func NewPushDatabase(cache cache.ThirdCache) PushDatabase { return &pushDataBase{cache: cache} } diff --git a/pkg/common/db/controller/s3.go b/pkg/common/db/controller/s3.go index e847c9c8f..eae47c421 100644 --- a/pkg/common/db/controller/s3.go +++ b/pkg/common/db/controller/s3.go @@ -20,9 +20,9 @@ import ( "time" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3/cont" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/s3" + "github.com/openimsdk/tools/s3/cont" "github.com/redis/go-redis/v9" ) diff --git a/pkg/common/db/controller/third.go b/pkg/common/db/controller/third.go index 996d82c45..be618843f 100644 --- a/pkg/common/db/controller/third.go +++ b/pkg/common/db/controller/third.go @@ -18,9 +18,9 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/db/pagination" ) type ThirdDatabase interface { @@ -34,7 +34,7 @@ type ThirdDatabase interface { } type thirdDatabase struct { - cache cache.MsgModel + cache cache.ThirdCache logdb relation.LogInterface } @@ -58,7 +58,7 @@ func (t *thirdDatabase) UploadLogs(ctx context.Context, logs []*relation.LogMode return t.logdb.Create(ctx, logs) } -func NewThirdDatabase(cache cache.MsgModel, logdb relation.LogInterface) ThirdDatabase { +func NewThirdDatabase(cache cache.ThirdCache, logdb relation.LogInterface) ThirdDatabase { return &thirdDatabase{cache: cache, logdb: logdb} } diff --git a/pkg/common/db/controller/user.go b/pkg/common/db/controller/user.go index 0e1bdd314..b2aba41fa 100644 --- a/pkg/common/db/controller/user.go +++ b/pkg/common/db/controller/user.go @@ -16,16 +16,16 @@ package controller import ( "context" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/db/tx" + "github.com/openimsdk/tools/utils/datautil" "time" - "github.com/OpenIMSDK/protocol/user" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/pagination" - "github.com/OpenIMSDK/tools/tx" - "github.com/OpenIMSDK/tools/utils" + "github.com/openimsdk/protocol/user" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" - unrelationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" ) type UserDatabase interface { @@ -39,13 +39,11 @@ type UserDatabase interface { FindNotification(ctx context.Context, level int64) (users []*relation.UserModel, err error) // Create Insert multiple external guarantees that the userID is not repeated and does not exist in the db Create(ctx context.Context, users []*relation.UserModel) (err error) - // Update update (non-zero value) external guarantee userID exists - //Update(ctx context.Context, user *relation.UserModel) (err error) // UpdateByMap update (zero value) external guarantee userID exists UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error) // FindUser PageFindUser(ctx context.Context, level1 int64, level2 int64, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) - //FindUser with keyword + // FindUser with keyword PageFindUserWithKeyword(ctx context.Context, level1 int64, level2 int64, userID string, nickName string, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) // Page If not found, no error is returned Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) @@ -74,7 +72,7 @@ type UserDatabase interface { // SetUserStatus Set the user status and store the user status in redis SetUserStatus(ctx context.Context, userID string, status, platformID int32) error - //CRUD user command + // CRUD user command AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error @@ -83,19 +81,19 @@ type UserDatabase interface { } type userDatabase struct { - tx tx.CtxTx + tx tx.Tx userDB relation.UserModelInterface cache cache.UserCache - mongoDB unrelationtb.UserModelInterface + mongoDB relation.SubscribeUserModelInterface } -func NewUserDatabase(userDB relation.UserModelInterface, cache cache.UserCache, tx tx.CtxTx, mongoDB unrelationtb.UserModelInterface) UserDatabase { +func NewUserDatabase(userDB relation.UserModelInterface, cache cache.UserCache, tx tx.Tx, mongoDB relation.SubscribeUserModelInterface) UserDatabase { return &userDatabase{userDB: userDB, cache: cache, tx: tx, mongoDB: mongoDB} } func (u *userDatabase) InitOnce(ctx context.Context, users []*relation.UserModel) error { // Extract user IDs from the given user models. - userIDs := utils.Slice(users, func(e *relation.UserModel) string { + userIDs := datautil.Slice(users, func(e *relation.UserModel) string { return e.UserID }) @@ -106,7 +104,7 @@ func (u *userDatabase) InitOnce(ctx context.Context, users []*relation.UserModel } // Determine which users are missing from the database. - missingUsers := utils.SliceAnySub(users, existingUsers, func(e *relation.UserModel) string { + missingUsers := datautil.SliceAnySub(users, existingUsers, func(e *relation.UserModel) string { return e.UserID }) @@ -127,7 +125,7 @@ func (u *userDatabase) FindWithError(ctx context.Context, userIDs []string) (use return } if len(users) != len(userIDs) { - err = errs.ErrRecordNotFound.Wrap("userID not found") + err = errs.ErrRecordNotFound.WrapMsg("userID not found") } return } @@ -137,12 +135,10 @@ func (u *userDatabase) Find(ctx context.Context, userIDs []string) (users []*rel return u.cache.GetUsersInfo(ctx, userIDs) } -// Find userInfo By Nickname. func (u *userDatabase) FindByNickname(ctx context.Context, nickname string) (users []*relation.UserModel, err error) { return u.userDB.TakeByNickname(ctx, nickname) } -// Find notificationAccouts. func (u *userDatabase) FindNotification(ctx context.Context, level int64) (users []*relation.UserModel, err error) { return u.userDB.TakeNotification(ctx, level) } @@ -153,20 +149,12 @@ func (u *userDatabase) Create(ctx context.Context, users []*relation.UserModel) if err = u.userDB.Create(ctx, users); err != nil { return err } - return u.cache.DelUsersInfo(utils.Slice(users, func(e *relation.UserModel) string { + return u.cache.DelUsersInfo(datautil.Slice(users, func(e *relation.UserModel) string { return e.UserID })...).ExecDel(ctx) }) } -//// Update (non-zero value) externally guarantees that userID exists. -//func (u *userDatabase) Update(ctx context.Context, user *relation.UserModel) (err error) { -// if err := u.userDB.Update(ctx, user); err != nil { -// return err -// } -// return u.cache.DelUsersInfo(user.UserID).ExecDel(ctx) -//} - // UpdateByMap update (zero value) externally guarantees that userID exists. func (u *userDatabase) UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error) { return u.tx.Transaction(ctx, func(ctx context.Context) error { @@ -186,13 +174,7 @@ func (u *userDatabase) PageFindUser(ctx context.Context, level1 int64, level2 in return u.userDB.PageFindUser(ctx, level1, level2, pagination) } -func (u *userDatabase) PageFindUserWithKeyword( - ctx context.Context, - level1 int64, - level2 int64, - userID, nickName string, - pagination pagination.Pagination, -) (count int64, users []*relation.UserModel, err error) { +func (u *userDatabase) PageFindUserWithKeyword(ctx context.Context, level1 int64, level2 int64, userID, nickName string, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) { return u.userDB.PageFindUserWithKeyword(ctx, level1, level2, userID, nickName, pagination) } @@ -267,19 +249,24 @@ func (u *userDatabase) GetUserStatus(ctx context.Context, userIDs []string) ([]* func (u *userDatabase) SetUserStatus(ctx context.Context, userID string, status, platformID int32) error { return u.cache.SetUserStatus(ctx, userID, status, platformID) } + func (u *userDatabase) AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error { return u.userDB.AddUserCommand(ctx, userID, Type, UUID, value, ex) } + func (u *userDatabase) DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error { return u.userDB.DeleteUserCommand(ctx, userID, Type, UUID) } + func (u *userDatabase) UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error { return u.userDB.UpdateUserCommand(ctx, userID, Type, UUID, val) } + func (u *userDatabase) GetUserCommands(ctx context.Context, userID string, Type int32) ([]*user.CommandInfoResp, error) { commands, err := u.userDB.GetUserCommand(ctx, userID, Type) return commands, err } + func (u *userDatabase) GetAllUserCommands(ctx context.Context, userID string) ([]*user.AllCommandInfoResp, error) { commands, err := u.userDB.GetAllUserCommand(ctx, userID) return commands, err diff --git a/pkg/common/db/mgo/black.go b/pkg/common/db/mgo/black.go index c555e0b77..d588aece6 100644 --- a/pkg/common/db/mgo/black.go +++ b/pkg/common/db/mgo/black.go @@ -17,9 +17,9 @@ package mgo import ( "context" - "github.com/OpenIMSDK/tools/mgoutil" - "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -63,42 +63,42 @@ func (b *BlackMgo) blacksFilter(blacks []*relation.BlackModel) bson.M { } func (b *BlackMgo) Create(ctx context.Context, blacks []*relation.BlackModel) (err error) { - return mgoutil.InsertMany(ctx, b.coll, blacks) + return mongoutil.InsertMany(ctx, b.coll, blacks) } func (b *BlackMgo) Delete(ctx context.Context, blacks []*relation.BlackModel) (err error) { if len(blacks) == 0 { return nil } - return mgoutil.DeleteMany(ctx, b.coll, b.blacksFilter(blacks)) + return mongoutil.DeleteMany(ctx, b.coll, b.blacksFilter(blacks)) } func (b *BlackMgo) UpdateByMap(ctx context.Context, ownerUserID, blockUserID string, args map[string]any) (err error) { if len(args) == 0 { return nil } - return mgoutil.UpdateOne(ctx, b.coll, b.blackFilter(ownerUserID, blockUserID), bson.M{"$set": args}, false) + return mongoutil.UpdateOne(ctx, b.coll, b.blackFilter(ownerUserID, blockUserID), bson.M{"$set": args}, false) } func (b *BlackMgo) Find(ctx context.Context, blacks []*relation.BlackModel) (blackList []*relation.BlackModel, err error) { - return mgoutil.Find[*relation.BlackModel](ctx, b.coll, b.blacksFilter(blacks)) + return mongoutil.Find[*relation.BlackModel](ctx, b.coll, b.blacksFilter(blacks)) } func (b *BlackMgo) Take(ctx context.Context, ownerUserID, blockUserID string) (black *relation.BlackModel, err error) { - return mgoutil.FindOne[*relation.BlackModel](ctx, b.coll, b.blackFilter(ownerUserID, blockUserID)) + return mongoutil.FindOne[*relation.BlackModel](ctx, b.coll, b.blackFilter(ownerUserID, blockUserID)) } func (b *BlackMgo) FindOwnerBlacks(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, blacks []*relation.BlackModel, err error) { - return mgoutil.FindPage[*relation.BlackModel](ctx, b.coll, bson.M{"owner_user_id": ownerUserID}, pagination) + return mongoutil.FindPage[*relation.BlackModel](ctx, b.coll, bson.M{"owner_user_id": ownerUserID}, pagination) } func (b *BlackMgo) FindOwnerBlackInfos(ctx context.Context, ownerUserID string, userIDs []string) (blacks []*relation.BlackModel, err error) { if len(userIDs) == 0 { - return mgoutil.Find[*relation.BlackModel](ctx, b.coll, bson.M{"owner_user_id": ownerUserID}) + return mongoutil.Find[*relation.BlackModel](ctx, b.coll, bson.M{"owner_user_id": ownerUserID}) } - return mgoutil.Find[*relation.BlackModel](ctx, b.coll, bson.M{"owner_user_id": ownerUserID, "block_user_id": bson.M{"$in": userIDs}}) + return mongoutil.Find[*relation.BlackModel](ctx, b.coll, bson.M{"owner_user_id": ownerUserID, "block_user_id": bson.M{"$in": userIDs}}) } func (b *BlackMgo) FindBlackUserIDs(ctx context.Context, ownerUserID string) (blackUserIDs []string, err error) { - return mgoutil.Find[string](ctx, b.coll, bson.M{"owner_user_id": ownerUserID}, options.Find().SetProjection(bson.M{"_id": 0, "block_user_id": 1})) + return mongoutil.Find[string](ctx, b.coll, bson.M{"owner_user_id": ownerUserID}, options.Find().SetProjection(bson.M{"_id": 0, "block_user_id": 1})) } diff --git a/pkg/common/db/mgo/conversation.go b/pkg/common/db/mgo/conversation.go index bc37ed759..d10bda39b 100644 --- a/pkg/common/db/mgo/conversation.go +++ b/pkg/common/db/mgo/conversation.go @@ -18,11 +18,11 @@ import ( "context" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mgoutil" - "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/errs" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -48,15 +48,15 @@ type ConversationMgo struct { } func (c *ConversationMgo) Create(ctx context.Context, conversations []*relation.ConversationModel) (err error) { - return mgoutil.InsertMany(ctx, c.coll, conversations) + return mongoutil.InsertMany(ctx, c.coll, conversations) } func (c *ConversationMgo) Delete(ctx context.Context, groupIDs []string) (err error) { - return mgoutil.DeleteMany(ctx, c.coll, bson.M{"group_id": bson.M{"$in": groupIDs}}) + return mongoutil.DeleteMany(ctx, c.coll, bson.M{"group_id": bson.M{"$in": groupIDs}}) } func (c *ConversationMgo) UpdateByMap(ctx context.Context, userIDs []string, conversationID string, args map[string]any) (rows int64, err error) { - res, err := mgoutil.UpdateMany(ctx, c.coll, bson.M{"owner_user_id": bson.M{"$in": userIDs}, "conversation_id": conversationID}, bson.M{"$set": args}) + res, err := mongoutil.UpdateMany(ctx, c.coll, bson.M{"owner_user_id": bson.M{"$in": userIDs}, "conversation_id": conversationID}, bson.M{"$set": args}) if err != nil { return 0, err } @@ -64,36 +64,35 @@ func (c *ConversationMgo) UpdateByMap(ctx context.Context, userIDs []string, con } func (c *ConversationMgo) Update(ctx context.Context, conversation *relation.ConversationModel) (err error) { - return mgoutil.UpdateOne(ctx, c.coll, bson.M{"owner_user_id": conversation.OwnerUserID, "conversation_id": conversation.ConversationID}, bson.M{"$set": conversation}, true) + return mongoutil.UpdateOne(ctx, c.coll, bson.M{"owner_user_id": conversation.OwnerUserID, "conversation_id": conversation.ConversationID}, bson.M{"$set": conversation}, true) } func (c *ConversationMgo) Find(ctx context.Context, ownerUserID string, conversationIDs []string) (conversations []*relation.ConversationModel, err error) { - return mgoutil.Find[*relation.ConversationModel](ctx, c.coll, bson.M{"owner_user_id": ownerUserID, "conversation_id": bson.M{"$in": conversationIDs}}) + return mongoutil.Find[*relation.ConversationModel](ctx, c.coll, bson.M{"owner_user_id": ownerUserID, "conversation_id": bson.M{"$in": conversationIDs}}) } func (c *ConversationMgo) FindUserID(ctx context.Context, userIDs []string, conversationIDs []string) ([]string, error) { - return mgoutil.Find[string]( + return mongoutil.Find[string]( ctx, c.coll, bson.M{"owner_user_id": bson.M{"$in": userIDs}, "conversation_id": bson.M{"$in": conversationIDs}}, options.Find().SetProjection(bson.M{"_id": 0, "owner_user_id": 1}), ) } - func (c *ConversationMgo) FindUserIDAllConversationID(ctx context.Context, userID string) ([]string, error) { - return mgoutil.Find[string](ctx, c.coll, bson.M{"owner_user_id": userID}, options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1})) + return mongoutil.Find[string](ctx, c.coll, bson.M{"owner_user_id": userID}, options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1})) } func (c *ConversationMgo) Take(ctx context.Context, userID, conversationID string) (conversation *relation.ConversationModel, err error) { - return mgoutil.FindOne[*relation.ConversationModel](ctx, c.coll, bson.M{"owner_user_id": userID, "conversation_id": conversationID}) + return mongoutil.FindOne[*relation.ConversationModel](ctx, c.coll, bson.M{"owner_user_id": userID, "conversation_id": conversationID}) } func (c *ConversationMgo) FindConversationID(ctx context.Context, userID string, conversationIDs []string) (existConversationID []string, err error) { - return mgoutil.Find[string](ctx, c.coll, bson.M{"owner_user_id": userID, "conversation_id": bson.M{"$in": conversationIDs}}, options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1})) + return mongoutil.Find[string](ctx, c.coll, bson.M{"owner_user_id": userID, "conversation_id": bson.M{"$in": conversationIDs}}, options.Find().SetProjection(bson.M{"_id": 0, "conversation_id": 1})) } func (c *ConversationMgo) FindUserIDAllConversations(ctx context.Context, userID string) (conversations []*relation.ConversationModel, err error) { - return mgoutil.Find[*relation.ConversationModel](ctx, c.coll, bson.M{"owner_user_id": userID}) + return mongoutil.Find[*relation.ConversationModel](ctx, c.coll, bson.M{"owner_user_id": userID}) } func (c *ConversationMgo) FindRecvMsgUserIDs(ctx context.Context, conversationID string, recvOpts []int) ([]string, error) { @@ -103,22 +102,22 @@ func (c *ConversationMgo) FindRecvMsgUserIDs(ctx context.Context, conversationID } else { filter = bson.M{"conversation_id": conversationID, "recv_msg_opt": bson.M{"$in": recvOpts}} } - return mgoutil.Find[string](ctx, c.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "owner_user_id": 1})) + return mongoutil.Find[string](ctx, c.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "owner_user_id": 1})) } func (c *ConversationMgo) GetUserRecvMsgOpt(ctx context.Context, ownerUserID, conversationID string) (opt int, err error) { - return mgoutil.FindOne[int](ctx, c.coll, bson.M{"owner_user_id": ownerUserID, "conversation_id": conversationID}, options.FindOne().SetProjection(bson.M{"recv_msg_opt": 1})) + return mongoutil.FindOne[int](ctx, c.coll, bson.M{"owner_user_id": ownerUserID, "conversation_id": conversationID}, options.FindOne().SetProjection(bson.M{"recv_msg_opt": 1})) } func (c *ConversationMgo) GetAllConversationIDs(ctx context.Context) ([]string, error) { - return mgoutil.Aggregate[string](ctx, c.coll, []bson.M{ + return mongoutil.Aggregate[string](ctx, c.coll, []bson.M{ {"$group": bson.M{"_id": "$conversation_id"}}, {"$project": bson.M{"_id": 0, "conversation_id": "$_id"}}, }) } func (c *ConversationMgo) GetAllConversationIDsNumber(ctx context.Context) (int64, error) { - counts, err := mgoutil.Aggregate[int64](ctx, c.coll, []bson.M{ + counts, err := mongoutil.Aggregate[int64](ctx, c.coll, []bson.M{ {"$group": bson.M{"_id": "$conversation_id"}}, {"$group": bson.M{"_id": nil, "count": bson.M{"$sum": 1}}}, {"$project": bson.M{"_id": 0}}, @@ -133,16 +132,16 @@ func (c *ConversationMgo) GetAllConversationIDsNumber(ctx context.Context) (int6 } func (c *ConversationMgo) PageConversationIDs(ctx context.Context, pagination pagination.Pagination) (conversationIDs []string, err error) { - return mgoutil.FindPageOnly[string](ctx, c.coll, bson.M{}, pagination, options.Find().SetProjection(bson.M{"conversation_id": 1})) + return mongoutil.FindPageOnly[string](ctx, c.coll, bson.M{}, pagination, options.Find().SetProjection(bson.M{"conversation_id": 1})) } func (c *ConversationMgo) GetConversationsByConversationID(ctx context.Context, conversationIDs []string) ([]*relation.ConversationModel, error) { - return mgoutil.Find[*relation.ConversationModel](ctx, c.coll, bson.M{"conversation_id": bson.M{"$in": conversationIDs}}) + return mongoutil.Find[*relation.ConversationModel](ctx, c.coll, bson.M{"conversation_id": bson.M{"$in": conversationIDs}}) } func (c *ConversationMgo) GetConversationIDsNeedDestruct(ctx context.Context) ([]*relation.ConversationModel, error) { - //"is_msg_destruct = 1 && msg_destruct_time != 0 && (UNIX_TIMESTAMP(NOW()) > (msg_destruct_time + UNIX_TIMESTAMP(latest_msg_destruct_time)) || latest_msg_destruct_time is NULL)" - return mgoutil.Find[*relation.ConversationModel](ctx, c.coll, bson.M{ + // "is_msg_destruct = 1 && msg_destruct_time != 0 && (UNIX_TIMESTAMP(NOW()) > (msg_destruct_time + UNIX_TIMESTAMP(latest_msg_destruct_time)) || latest_msg_destruct_time is NULL)" + return mongoutil.Find[*relation.ConversationModel](ctx, c.coll, bson.M{ "is_msg_destruct": 1, "msg_destruct_time": bson.M{"$ne": 0}, "$or": []bson.M{ @@ -162,7 +161,7 @@ func (c *ConversationMgo) GetConversationIDsNeedDestruct(ctx context.Context) ([ } func (c *ConversationMgo) GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error) { - return mgoutil.Find[string]( + return mongoutil.Find[string]( ctx, c.coll, bson.M{"conversation_id": conversationID, "recv_msg_opt": bson.M{"$ne": constant.ReceiveMessage}}, diff --git a/pkg/common/db/s3/doc.go b/pkg/common/db/mgo/doc.go similarity index 81% rename from pkg/common/db/s3/doc.go rename to pkg/common/db/mgo/doc.go index a43710676..f9d43e885 100644 --- a/pkg/common/db/s3/doc.go +++ b/pkg/common/db/mgo/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package s3 // import "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" +package mgo // import "github.com/openimsdk/open-im-server/v3/pkg/common/db/mgo" diff --git a/pkg/common/db/mgo/friend.go b/pkg/common/db/mgo/friend.go index aa7775ce0..269bb594a 100644 --- a/pkg/common/db/mgo/friend.go +++ b/pkg/common/db/mgo/friend.go @@ -17,9 +17,9 @@ package mgo import ( "context" - "github.com/OpenIMSDK/tools/mgoutil" - "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -48,7 +48,7 @@ func NewFriendMongo(db *mongo.Database) (relation.FriendModelInterface, error) { // Create inserts multiple friend records. func (f *FriendMgo) Create(ctx context.Context, friends []*relation.FriendModel) error { - return mgoutil.InsertMany(ctx, f.coll, friends) + return mongoutil.InsertMany(ctx, f.coll, friends) } // Delete removes specified friends of the owner user. @@ -57,7 +57,7 @@ func (f *FriendMgo) Delete(ctx context.Context, ownerUserID string, friendUserID "owner_user_id": ownerUserID, "friend_user_id": bson.M{"$in": friendUserIDs}, } - return mgoutil.DeleteOne(ctx, f.coll, filter) + return mongoutil.DeleteOne(ctx, f.coll, filter) } // UpdateByMap updates specific fields of a friend document using a map. @@ -69,7 +69,7 @@ func (f *FriendMgo) UpdateByMap(ctx context.Context, ownerUserID string, friendU "owner_user_id": ownerUserID, "friend_user_id": friendUserID, } - return mgoutil.UpdateOne(ctx, f.coll, filter, bson.M{"$set": args}, true) + return mongoutil.UpdateOne(ctx, f.coll, filter, bson.M{"$set": args}, true) } // Update modifies multiple friend documents. @@ -92,7 +92,7 @@ func (f *FriendMgo) Take(ctx context.Context, ownerUserID, friendUserID string) "owner_user_id": ownerUserID, "friend_user_id": friendUserID, } - return mgoutil.FindOne[*relation.FriendModel](ctx, f.coll, filter) + return mongoutil.FindOne[*relation.FriendModel](ctx, f.coll, filter) } // FindUserState finds the friendship status between two users. @@ -103,7 +103,7 @@ func (f *FriendMgo) FindUserState(ctx context.Context, userID1, userID2 string) {"owner_user_id": userID2, "friend_user_id": userID1}, }, } - return mgoutil.Find[*relation.FriendModel](ctx, f.coll, filter) + return mongoutil.Find[*relation.FriendModel](ctx, f.coll, filter) } // FindFriends retrieves a list of friends for a given owner. Missing friends do not cause an error. @@ -112,7 +112,7 @@ func (f *FriendMgo) FindFriends(ctx context.Context, ownerUserID string, friendU "owner_user_id": ownerUserID, "friend_user_id": bson.M{"$in": friendUserIDs}, } - return mgoutil.Find[*relation.FriendModel](ctx, f.coll, filter) + return mongoutil.Find[*relation.FriendModel](ctx, f.coll, filter) } // FindReversalFriends finds users who have added the specified user as a friend. @@ -121,25 +121,25 @@ func (f *FriendMgo) FindReversalFriends(ctx context.Context, friendUserID string "owner_user_id": bson.M{"$in": ownerUserIDs}, "friend_user_id": friendUserID, } - return mgoutil.Find[*relation.FriendModel](ctx, f.coll, filter) + return mongoutil.Find[*relation.FriendModel](ctx, f.coll, filter) } // FindOwnerFriends retrieves a paginated list of friends for a given owner. func (f *FriendMgo) FindOwnerFriends(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (int64, []*relation.FriendModel, error) { filter := bson.M{"owner_user_id": ownerUserID} - return mgoutil.FindPage[*relation.FriendModel](ctx, f.coll, filter, pagination) + return mongoutil.FindPage[*relation.FriendModel](ctx, f.coll, filter, pagination) } // FindInWhoseFriends finds users who have added the specified user as a friend, with pagination. func (f *FriendMgo) FindInWhoseFriends(ctx context.Context, friendUserID string, pagination pagination.Pagination) (int64, []*relation.FriendModel, error) { filter := bson.M{"friend_user_id": friendUserID} - return mgoutil.FindPage[*relation.FriendModel](ctx, f.coll, filter, pagination) + return mongoutil.FindPage[*relation.FriendModel](ctx, f.coll, filter, pagination) } // FindFriendUserIDs retrieves a list of friend user IDs for a given owner. func (f *FriendMgo) FindFriendUserIDs(ctx context.Context, ownerUserID string) ([]string, error) { filter := bson.M{"owner_user_id": ownerUserID} - return mgoutil.Find[string](ctx, f.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "friend_user_id": 1})) + return mongoutil.Find[string](ctx, f.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "friend_user_id": 1})) } func (f *FriendMgo) UpdateFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, val map[string]any) error { @@ -158,6 +158,6 @@ func (f *FriendMgo) UpdateFriends(ctx context.Context, ownerUserID string, frien update := bson.M{"$set": val} // Perform the update operation for all matching documents - _, err := mgoutil.UpdateMany(ctx, f.coll, filter, update) + _, err := mongoutil.UpdateMany(ctx, f.coll, filter, update) return err } diff --git a/pkg/common/db/mgo/friend_request.go b/pkg/common/db/mgo/friend_request.go index 3e0588a0b..704b68126 100644 --- a/pkg/common/db/mgo/friend_request.go +++ b/pkg/common/db/mgo/friend_request.go @@ -17,9 +17,9 @@ package mgo import ( "context" - "github.com/OpenIMSDK/tools/mgoutil" - "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -45,11 +45,11 @@ type FriendRequestMgo struct { } func (f *FriendRequestMgo) FindToUserID(ctx context.Context, toUserID string, pagination pagination.Pagination) (total int64, friendRequests []*relation.FriendRequestModel, err error) { - return mgoutil.FindPage[*relation.FriendRequestModel](ctx, f.coll, bson.M{"to_user_id": toUserID}, pagination) + return mongoutil.FindPage[*relation.FriendRequestModel](ctx, f.coll, bson.M{"to_user_id": toUserID}, pagination) } func (f *FriendRequestMgo) FindFromUserID(ctx context.Context, fromUserID string, pagination pagination.Pagination) (total int64, friendRequests []*relation.FriendRequestModel, err error) { - return mgoutil.FindPage[*relation.FriendRequestModel](ctx, f.coll, bson.M{"from_user_id": fromUserID}, pagination) + return mongoutil.FindPage[*relation.FriendRequestModel](ctx, f.coll, bson.M{"from_user_id": fromUserID}, pagination) } func (f *FriendRequestMgo) FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*relation.FriendRequestModel, err error) { @@ -57,22 +57,22 @@ func (f *FriendRequestMgo) FindBothFriendRequests(ctx context.Context, fromUserI {"from_user_id": fromUserID, "to_user_id": toUserID}, {"from_user_id": toUserID, "to_user_id": fromUserID}, }} - return mgoutil.Find[*relation.FriendRequestModel](ctx, f.coll, filter) + return mongoutil.Find[*relation.FriendRequestModel](ctx, f.coll, filter) } func (f *FriendRequestMgo) Create(ctx context.Context, friendRequests []*relation.FriendRequestModel) error { - return mgoutil.InsertMany(ctx, f.coll, friendRequests) + return mongoutil.InsertMany(ctx, f.coll, friendRequests) } func (f *FriendRequestMgo) Delete(ctx context.Context, fromUserID, toUserID string) (err error) { - return mgoutil.DeleteOne(ctx, f.coll, bson.M{"from_user_id": fromUserID, "to_user_id": toUserID}) + return mongoutil.DeleteOne(ctx, f.coll, bson.M{"from_user_id": fromUserID, "to_user_id": toUserID}) } func (f *FriendRequestMgo) UpdateByMap(ctx context.Context, formUserID, toUserID string, args map[string]any) (err error) { if len(args) == 0 { return nil } - return mgoutil.UpdateOne(ctx, f.coll, bson.M{"from_user_id": formUserID, "to_user_id": toUserID}, bson.M{"$set": args}, true) + return mongoutil.UpdateOne(ctx, f.coll, bson.M{"from_user_id": formUserID, "to_user_id": toUserID}, bson.M{"$set": args}, true) } func (f *FriendRequestMgo) Update(ctx context.Context, friendRequest *relation.FriendRequestModel) (err error) { @@ -99,11 +99,11 @@ func (f *FriendRequestMgo) Update(ctx context.Context, friendRequest *relation.F return nil } filter := bson.M{"from_user_id": friendRequest.FromUserID, "to_user_id": friendRequest.ToUserID} - return mgoutil.UpdateOne(ctx, f.coll, filter, bson.M{"$set": updater}, true) + return mongoutil.UpdateOne(ctx, f.coll, filter, bson.M{"$set": updater}, true) } func (f *FriendRequestMgo) Find(ctx context.Context, fromUserID, toUserID string) (friendRequest *relation.FriendRequestModel, err error) { - return mgoutil.FindOne[*relation.FriendRequestModel](ctx, f.coll, bson.M{"from_user_id": fromUserID, "to_user_id": toUserID}) + return mongoutil.FindOne[*relation.FriendRequestModel](ctx, f.coll, bson.M{"from_user_id": fromUserID, "to_user_id": toUserID}) } func (f *FriendRequestMgo) Take(ctx context.Context, fromUserID, toUserID string) (friendRequest *relation.FriendRequestModel, err error) { diff --git a/pkg/common/db/mgo/group.go b/pkg/common/db/mgo/group.go index c63e3c376..0169c2339 100644 --- a/pkg/common/db/mgo/group.go +++ b/pkg/common/db/mgo/group.go @@ -18,11 +18,11 @@ import ( "context" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mgoutil" - "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/errs" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -47,7 +47,7 @@ type GroupMgo struct { } func (g *GroupMgo) Create(ctx context.Context, groups []*relation.GroupModel) (err error) { - return mgoutil.InsertMany(ctx, g.coll, groups) + return mongoutil.InsertMany(ctx, g.coll, groups) } func (g *GroupMgo) UpdateStatus(ctx context.Context, groupID string, status int32) (err error) { @@ -58,21 +58,23 @@ func (g *GroupMgo) UpdateMap(ctx context.Context, groupID string, args map[strin if len(args) == 0 { return nil } - return mgoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID}, bson.M{"$set": args}, true) + return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID}, bson.M{"$set": args}, true) } func (g *GroupMgo) Find(ctx context.Context, groupIDs []string) (groups []*relation.GroupModel, err error) { - return mgoutil.Find[*relation.GroupModel](ctx, g.coll, bson.M{"group_id": bson.M{"$in": groupIDs}}) + return mongoutil.Find[*relation.GroupModel](ctx, g.coll, bson.M{"group_id": bson.M{"$in": groupIDs}}) } func (g *GroupMgo) Take(ctx context.Context, groupID string) (group *relation.GroupModel, err error) { - return mgoutil.FindOne[*relation.GroupModel](ctx, g.coll, bson.M{"group_id": groupID}) + return mongoutil.FindOne[*relation.GroupModel](ctx, g.coll, bson.M{"group_id": groupID}) } func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*relation.GroupModel, err error) { + // Define the sorting options opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}}) - return mgoutil.FindPage[*relation.GroupModel](ctx, g.coll, bson.M{ + // Perform the search with pagination and sorting + return mongoutil.FindPage[*relation.GroupModel](ctx, g.coll, bson.M{ "group_name": bson.M{"$regex": keyword}, "status": bson.M{"$ne": constant.GroupStatusDismissed}, }, pagination, opts) @@ -80,9 +82,9 @@ func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagina func (g *GroupMgo) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) { if before == nil { - return mgoutil.Count(ctx, g.coll, bson.M{}) + return mongoutil.Count(ctx, g.coll, bson.M{}) } - return mgoutil.Count(ctx, g.coll, bson.M{"create_time": bson.M{"$lt": before}}) + return mongoutil.Count(ctx, g.coll, bson.M{"create_time": bson.M{"$lt": before}}) } func (g *GroupMgo) CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) { @@ -113,7 +115,7 @@ func (g *GroupMgo) CountRangeEverydayTotal(ctx context.Context, start time.Time, Date string `bson:"_id"` Count int64 `bson:"count"` } - items, err := mgoutil.Aggregate[Item](ctx, g.coll, pipeline) + items, err := mongoutil.Aggregate[Item](ctx, g.coll, pipeline) if err != nil { return nil, err } diff --git a/pkg/common/db/mgo/group_member.go b/pkg/common/db/mgo/group_member.go index e1af34f7c..29d69d0f0 100644 --- a/pkg/common/db/mgo/group_member.go +++ b/pkg/common/db/mgo/group_member.go @@ -17,11 +17,11 @@ package mgo import ( "context" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mgoutil" - "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/errs" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -47,7 +47,7 @@ type GroupMemberMgo struct { } func (g *GroupMemberMgo) Create(ctx context.Context, groupMembers []*relation.GroupMemberModel) (err error) { - return mgoutil.InsertMany(ctx, g.coll, groupMembers) + return mongoutil.InsertMany(ctx, g.coll, groupMembers) } func (g *GroupMemberMgo) Delete(ctx context.Context, groupID string, userIDs []string) (err error) { @@ -55,7 +55,7 @@ func (g *GroupMemberMgo) Delete(ctx context.Context, groupID string, userIDs []s if len(userIDs) > 0 { filter["user_id"] = bson.M{"$in": userIDs} } - return mgoutil.DeleteMany(ctx, g.coll, filter) + return mongoutil.DeleteMany(ctx, g.coll, filter) } func (g *GroupMemberMgo) UpdateRoleLevel(ctx context.Context, groupID string, userID string, roleLevel int32) error { @@ -63,41 +63,41 @@ func (g *GroupMemberMgo) UpdateRoleLevel(ctx context.Context, groupID string, us } func (g *GroupMemberMgo) Update(ctx context.Context, groupID string, userID string, data map[string]any) (err error) { - return mgoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}, bson.M{"$set": data}, true) + return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}, bson.M{"$set": data}, true) } func (g *GroupMemberMgo) Find(ctx context.Context, groupIDs []string, userIDs []string, roleLevels []int32) (groupMembers []*relation.GroupMemberModel, err error) { - //TODO implement me + // TODO implement me panic("implement me") } func (g *GroupMemberMgo) FindMemberUserID(ctx context.Context, groupID string) (userIDs []string, err error) { - return mgoutil.Find[string](ctx, g.coll, bson.M{"group_id": groupID}, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1})) + return mongoutil.Find[string](ctx, g.coll, bson.M{"group_id": groupID}, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1})) } func (g *GroupMemberMgo) Take(ctx context.Context, groupID string, userID string) (groupMember *relation.GroupMemberModel, err error) { - return mgoutil.FindOne[*relation.GroupMemberModel](ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}) + return mongoutil.FindOne[*relation.GroupMemberModel](ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}) } func (g *GroupMemberMgo) TakeOwner(ctx context.Context, groupID string) (groupMember *relation.GroupMemberModel, err error) { - return mgoutil.FindOne[*relation.GroupMemberModel](ctx, g.coll, bson.M{"group_id": groupID, "role_level": constant.GroupOwner}) + return mongoutil.FindOne[*relation.GroupMemberModel](ctx, g.coll, bson.M{"group_id": groupID, "role_level": constant.GroupOwner}) } func (g *GroupMemberMgo) FindRoleLevelUserIDs(ctx context.Context, groupID string, roleLevel int32) ([]string, error) { - return mgoutil.Find[string](ctx, g.coll, bson.M{"group_id": groupID, "role_level": roleLevel}, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1})) + return mongoutil.Find[string](ctx, g.coll, bson.M{"group_id": groupID, "role_level": roleLevel}, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1})) } func (g *GroupMemberMgo) SearchMember(ctx context.Context, keyword string, groupID string, pagination pagination.Pagination) (total int64, groupList []*relation.GroupMemberModel, err error) { filter := bson.M{"group_id": groupID, "nickname": bson.M{"$regex": keyword}} - return mgoutil.FindPage[*relation.GroupMemberModel](ctx, g.coll, filter, pagination) + return mongoutil.FindPage[*relation.GroupMemberModel](ctx, g.coll, filter, pagination) } func (g *GroupMemberMgo) FindUserJoinedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) { - return mgoutil.Find[string](ctx, g.coll, bson.M{"user_id": userID}, options.Find().SetProjection(bson.M{"_id": 0, "group_id": 1})) + return mongoutil.Find[string](ctx, g.coll, bson.M{"user_id": userID}, options.Find().SetProjection(bson.M{"_id": 0, "group_id": 1})) } func (g *GroupMemberMgo) TakeGroupMemberNum(ctx context.Context, groupID string) (count int64, err error) { - return mgoutil.Count(ctx, g.coll, bson.M{"group_id": groupID}) + return mongoutil.Count(ctx, g.coll, bson.M{"group_id": groupID}) } func (g *GroupMemberMgo) FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) { @@ -107,7 +107,7 @@ func (g *GroupMemberMgo) FindUserManagedGroupID(ctx context.Context, userID stri "$in": []int{constant.GroupOwner, constant.GroupAdmin}, }, } - return mgoutil.Find[string](ctx, g.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "group_id": 1})) + return mongoutil.Find[string](ctx, g.coll, filter, options.Find().SetProjection(bson.M{"_id": 0, "group_id": 1})) } func (g *GroupMemberMgo) IsUpdateRoleLevel(data map[string]any) bool { diff --git a/pkg/common/db/mgo/group_request.go b/pkg/common/db/mgo/group_request.go index 9aee0e960..17cbeab17 100644 --- a/pkg/common/db/mgo/group_request.go +++ b/pkg/common/db/mgo/group_request.go @@ -17,10 +17,10 @@ package mgo import ( "context" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mgoutil" - "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/errs" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -46,29 +46,29 @@ type GroupRequestMgo struct { } func (g *GroupRequestMgo) Create(ctx context.Context, groupRequests []*relation.GroupRequestModel) (err error) { - return mgoutil.InsertMany(ctx, g.coll, groupRequests) + return mongoutil.InsertMany(ctx, g.coll, groupRequests) } func (g *GroupRequestMgo) Delete(ctx context.Context, groupID string, userID string) (err error) { - return mgoutil.DeleteOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}) + return mongoutil.DeleteOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}) } func (g *GroupRequestMgo) UpdateHandler(ctx context.Context, groupID string, userID string, handledMsg string, handleResult int32) (err error) { - return mgoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}, bson.M{"$set": bson.M{"handle_msg": handledMsg, "handle_result": handleResult}}, true) + return mongoutil.UpdateOne(ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}, bson.M{"$set": bson.M{"handle_msg": handledMsg, "handle_result": handleResult}}, true) } func (g *GroupRequestMgo) Take(ctx context.Context, groupID string, userID string) (groupRequest *relation.GroupRequestModel, err error) { - return mgoutil.FindOne[*relation.GroupRequestModel](ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}) + return mongoutil.FindOne[*relation.GroupRequestModel](ctx, g.coll, bson.M{"group_id": groupID, "user_id": userID}) } func (g *GroupRequestMgo) FindGroupRequests(ctx context.Context, groupID string, userIDs []string) ([]*relation.GroupRequestModel, error) { - return mgoutil.Find[*relation.GroupRequestModel](ctx, g.coll, bson.M{"group_id": groupID, "user_id": bson.M{"$in": userIDs}}) + return mongoutil.Find[*relation.GroupRequestModel](ctx, g.coll, bson.M{"group_id": groupID, "user_id": bson.M{"$in": userIDs}}) } func (g *GroupRequestMgo) Page(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, groups []*relation.GroupRequestModel, err error) { - return mgoutil.FindPage[*relation.GroupRequestModel](ctx, g.coll, bson.M{"user_id": userID}, pagination) + return mongoutil.FindPage[*relation.GroupRequestModel](ctx, g.coll, bson.M{"user_id": userID}, pagination) } func (g *GroupRequestMgo) PageGroup(ctx context.Context, groupIDs []string, pagination pagination.Pagination) (total int64, groups []*relation.GroupRequestModel, err error) { - return mgoutil.FindPage[*relation.GroupRequestModel](ctx, g.coll, bson.M{"group_id": bson.M{"$in": groupIDs}}, pagination) + return mongoutil.FindPage[*relation.GroupRequestModel](ctx, g.coll, bson.M{"group_id": bson.M{"$in": groupIDs}}, pagination) } diff --git a/pkg/common/db/mgo/log.go b/pkg/common/db/mgo/log.go index ca28d5964..36a0bbbc5 100644 --- a/pkg/common/db/mgo/log.go +++ b/pkg/common/db/mgo/log.go @@ -18,9 +18,9 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/mgoutil" - "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -57,7 +57,7 @@ type LogMgo struct { } func (l *LogMgo) Create(ctx context.Context, log []*relation.LogModel) error { - return mgoutil.InsertMany(ctx, l.coll, log) + return mongoutil.InsertMany(ctx, l.coll, log) } func (l *LogMgo) Search(ctx context.Context, keyword string, start time.Time, end time.Time, pagination pagination.Pagination) (int64, []*relation.LogModel, error) { @@ -65,19 +65,19 @@ func (l *LogMgo) Search(ctx context.Context, keyword string, start time.Time, en if keyword != "" { filter["user_id"] = bson.M{"$regex": keyword} } - return mgoutil.FindPage[*relation.LogModel](ctx, l.coll, filter, pagination, options.Find().SetSort(bson.M{"create_time": -1})) + return mongoutil.FindPage[*relation.LogModel](ctx, l.coll, filter, pagination, options.Find().SetSort(bson.M{"create_time": -1})) } func (l *LogMgo) Delete(ctx context.Context, logID []string, userID string) error { if userID == "" { - return mgoutil.DeleteMany(ctx, l.coll, bson.M{"log_id": bson.M{"$in": logID}}) + return mongoutil.DeleteMany(ctx, l.coll, bson.M{"log_id": bson.M{"$in": logID}}) } - return mgoutil.DeleteMany(ctx, l.coll, bson.M{"log_id": bson.M{"$in": logID}, "user_id": userID}) + return mongoutil.DeleteMany(ctx, l.coll, bson.M{"log_id": bson.M{"$in": logID}, "user_id": userID}) } func (l *LogMgo) Get(ctx context.Context, logIDs []string, userID string) ([]*relation.LogModel, error) { if userID == "" { - return mgoutil.Find[*relation.LogModel](ctx, l.coll, bson.M{"log_id": bson.M{"$in": logIDs}}) + return mongoutil.Find[*relation.LogModel](ctx, l.coll, bson.M{"log_id": bson.M{"$in": logIDs}}) } - return mgoutil.Find[*relation.LogModel](ctx, l.coll, bson.M{"log_id": bson.M{"$in": logIDs}, "user_id": userID}) + return mongoutil.Find[*relation.LogModel](ctx, l.coll, bson.M{"log_id": bson.M{"$in": logIDs}, "user_id": userID}) } diff --git a/pkg/common/db/unrelation/msg.go b/pkg/common/db/mgo/msg.go similarity index 56% rename from pkg/common/db/unrelation/msg.go rename to pkg/common/db/mgo/msg.go index a129f3e99..40ee5e423 100644 --- a/pkg/common/db/unrelation/msg.go +++ b/pkg/common/db/mgo/msg.go @@ -1,68 +1,54 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package unrelation +package mgo import ( "context" - "encoding/json" - "errors" "fmt" "time" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - table "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/utils/jsonutil" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" - "google.golang.org/protobuf/proto" ) -var ErrMsgListNotExist = errors.New("user not have msg in mongoDB") - -type MsgMongoDriver struct { - MsgCollection *mongo.Collection - model table.MsgDocModel +func NewMsgMongo(db *mongo.Database) (relation.MsgDocModelInterface, error) { + coll := db.Collection(new(relation.MsgDocModel).TableName()) + _, err := coll.Indexes().CreateOne(context.Background(), mongo.IndexModel{ + Keys: bson.D{ + {Key: "doc_id", Value: 1}, + }, + Options: options.Index().SetUnique(true), + }) + if err != nil { + return nil, errs.Wrap(err) + } + return &MsgMgo{coll: coll}, nil } -func NewMsgMongoDriver(database *mongo.Database) table.MsgDocModelInterface { - collection := database.Collection(table.MsgDocModel{}.TableName()) - return &MsgMongoDriver{MsgCollection: collection} +type MsgMgo struct { + coll *mongo.Collection + model relation.MsgDocModel } -func (m *MsgMongoDriver) PushMsgsToDoc(ctx context.Context, docID string, msgsToMongo []table.MsgInfoModel) error { - return m.MsgCollection.FindOneAndUpdate(ctx, bson.M{"doc_id": docID}, bson.M{"$push": bson.M{"msgs": bson.M{"$each": msgsToMongo}}}). - Err() +func (m *MsgMgo) PushMsgsToDoc(ctx context.Context, docID string, msgsToMongo []relation.MsgInfoModel) error { + filter := bson.M{"doc_id": docID} + update := bson.M{"$push": bson.M{"msgs": bson.M{"$each": msgsToMongo}}} + return mongoutil.UpdateOne(ctx, m.coll, filter, update, false) } -func (m *MsgMongoDriver) Create(ctx context.Context, model *table.MsgDocModel) error { - _, err := m.MsgCollection.InsertOne(ctx, model) - return err +func (m *MsgMgo) Create(ctx context.Context, model *relation.MsgDocModel) error { + return mongoutil.InsertMany(ctx, m.coll, []*relation.MsgDocModel{model}) } -func (m *MsgMongoDriver) UpdateMsg( - ctx context.Context, - docID string, - index int64, - key string, - value any, -) (*mongo.UpdateResult, error) { +func (m *MsgMgo) UpdateMsg(ctx context.Context, docID string, index int64, key string, value any) (*mongo.UpdateResult, error) { var field string if key == "" { field = fmt.Sprintf("msgs.%d", index) @@ -71,21 +57,10 @@ func (m *MsgMongoDriver) UpdateMsg( } filter := bson.M{"doc_id": docID} update := bson.M{"$set": bson.M{field: value}} - res, err := m.MsgCollection.UpdateOne(ctx, filter, update) - if err != nil { - return nil, errs.Wrap(err) - } - return res, nil + return mongoutil.UpdateOneResult(ctx, m.coll, filter, update) } -// PushUnique value must slice. -func (m *MsgMongoDriver) PushUnique( - ctx context.Context, - docID string, - index int64, - key string, - value any, -) (*mongo.UpdateResult, error) { +func (m *MsgMgo) PushUnique(ctx context.Context, docID string, index int64, key string, value any) (*mongo.UpdateResult, error) { var field string if key == "" { field = fmt.Sprintf("msgs.%d", index) @@ -98,138 +73,24 @@ func (m *MsgMongoDriver) PushUnique( field: bson.M{"$each": value}, }, } - res, err := m.MsgCollection.UpdateOne(ctx, filter, update) - if err != nil { - return nil, errs.Wrap(err) - } - return res, nil -} - -func (m *MsgMongoDriver) UpdateMsgContent(ctx context.Context, docID string, index int64, msg []byte) error { - _, err := m.MsgCollection.UpdateOne( - ctx, - bson.M{"doc_id": docID}, - bson.M{"$set": bson.M{fmt.Sprintf("msgs.%d.msg", index): msg}}, - ) - if err != nil { - return errs.Wrap(err) - } - return nil -} - -func (m *MsgMongoDriver) UpdateMsgStatusByIndexInOneDoc(ctx context.Context, docID string, msg *sdkws.MsgData, seqIndex int, status int32) error { - msg.Status = status - bytes, err := proto.Marshal(msg) - if err != nil { - return errs.Wrap(err) - } - _, err = m.MsgCollection.UpdateOne( - ctx, - bson.M{"doc_id": docID}, - bson.M{"$set": bson.M{fmt.Sprintf("msgs.%d.msg", seqIndex): bytes}}, - ) - if err != nil { - return errs.Wrap(err, fmt.Sprintf("docID is %s, seqIndex is %d", docID, seqIndex)) - } - return nil -} - -func (m *MsgMongoDriver) FindOneByDocID(ctx context.Context, docID string) (*table.MsgDocModel, error) { - doc := &table.MsgDocModel{} - err := m.MsgCollection.FindOne(ctx, bson.M{"doc_id": docID}).Decode(doc) - return doc, err -} - -func (m *MsgMongoDriver) GetMsgDocModelByIndex( - ctx context.Context, - conversationID string, - index, sort int64, -) (*table.MsgDocModel, error) { - if sort != 1 && sort != -1 { - return nil, errs.ErrArgs.Wrap("mongo sort must be 1 or -1") - } - findOpts := options.Find().SetLimit(1).SetSkip(index).SetSort(bson.M{"doc_id": sort}) - cursor, err := m.MsgCollection.Find( - ctx, - bson.M{"doc_id": primitive.Regex{Pattern: fmt.Sprintf("^%s:", conversationID)}}, - findOpts, - ) - if err != nil { - return nil, errs.Wrap(err, fmt.Sprintf("conversationID is %s", conversationID)) - } - var msgs []table.MsgDocModel - err = cursor.All(ctx, &msgs) - if err != nil { - return nil, errs.Wrap(err, fmt.Sprintf("cursor is %s", cursor.Current.String())) - } - if len(msgs) > 0 { - return &msgs[0], nil - } - return nil, ErrMsgListNotExist + return mongoutil.UpdateOneResult(ctx, m.coll, filter, update) } -func (m *MsgMongoDriver) GetNewestMsg(ctx context.Context, conversationID string) (*table.MsgInfoModel, error) { - var skip int64 = 0 - for { - msgDocModel, err := m.GetMsgDocModelByIndex(ctx, conversationID, skip, -1) - if err != nil { - return nil, err - } - for i := len(msgDocModel.Msg) - 1; i >= 0; i-- { - if msgDocModel.Msg[i].Msg != nil { - return msgDocModel.Msg[i], nil - } - } - skip++ - } -} - -func (m *MsgMongoDriver) GetOldestMsg(ctx context.Context, conversationID string) (*table.MsgInfoModel, error) { - var skip int64 = 0 - for { - msgDocModel, err := m.GetMsgDocModelByIndex(ctx, conversationID, skip, 1) - if err != nil { - return nil, err - } - for i, v := range msgDocModel.Msg { - if v.Msg != nil { - return msgDocModel.Msg[i], nil - } - } - skip++ - } +func (m *MsgMgo) UpdateMsgContent(ctx context.Context, docID string, index int64, msg []byte) error { + filter := bson.M{"doc_id": docID} + update := bson.M{"$set": bson.M{fmt.Sprintf("msgs.%d.msg", index): msg}} + return mongoutil.UpdateOne(ctx, m.coll, filter, update, false) } -func (m *MsgMongoDriver) DeleteMsgsInOneDocByIndex(ctx context.Context, docID string, indexes []int) error { - updates := bson.M{ - "$set": bson.M{}, - } - for _, index := range indexes { - updates["$set"].(bson.M)[fmt.Sprintf("msgs.%d", index)] = bson.M{ - "msg": nil, - } - } - _, err := m.MsgCollection.UpdateMany(ctx, bson.M{"doc_id": docID}, updates) - if err != nil { - return errs.Wrap(err, fmt.Sprintf("docID is %s, indexes is %v", docID, indexes)) - } - return nil +func (m *MsgMgo) IsExistDocID(ctx context.Context, docID string) (bool, error) { + return mongoutil.Exist(ctx, m.coll, bson.M{"doc_id": docID}) } -func (m *MsgMongoDriver) DeleteDocs(ctx context.Context, docIDs []string) error { - if docIDs == nil { - return nil - } - _, err := m.MsgCollection.DeleteMany(ctx, bson.M{"doc_id": bson.M{"$in": docIDs}}) - return err +func (m *MsgMgo) FindOneByDocID(ctx context.Context, docID string) (*relation.MsgDocModel, error) { + return mongoutil.FindOne[*relation.MsgDocModel](ctx, m.coll, bson.M{"doc_id": docID}) } -func (m *MsgMongoDriver) GetMsgBySeqIndexIn1Doc( - ctx context.Context, - userID string, - docID string, - seqs []int64, -) (msgs []*table.MsgInfoModel, err error) { +func (m *MsgMgo) GetMsgBySeqIndexIn1Doc(ctx context.Context, userID, docID string, seqs []int64) ([]*relation.MsgInfoModel, error) { indexs := make([]int64, 0, len(seqs)) for _, seq := range seqs { indexs = append(indexs, m.model.GetMsgIndex(seq)) @@ -270,20 +131,14 @@ func (m *MsgMongoDriver) GetMsgBySeqIndexIn1Doc( {Key: "msgs.del_list", Value: 0}, }}}, } - - cur, err := m.MsgCollection.Aggregate(ctx, pipeline) + msgDocModel, err := mongoutil.Aggregate[*relation.MsgDocModel](ctx, m.coll, pipeline) if err != nil { - return nil, errs.Wrap(err) - } - defer cur.Close(ctx) - var msgDocModel []table.MsgDocModel - if err := cur.All(ctx, &msgDocModel); err != nil { - return nil, errs.Wrap(err, fmt.Sprintf("docID is %s, seqs is %v", docID, seqs)) + return nil, err } if len(msgDocModel) == 0 { return nil, errs.Wrap(mongo.ErrNoDocuments) } - msgs = make([]*table.MsgInfoModel, 0, len(msgDocModel[0].Msg)) + msgs := make([]*relation.MsgInfoModel, 0, len(msgDocModel[0].Msg)) for i := range msgDocModel[0].Msg { msg := msgDocModel[0].Msg[i] if msg == nil || msg.Msg == nil { @@ -303,16 +158,16 @@ func (m *MsgMongoDriver) GetMsgBySeqIndexIn1Doc( Seq: msg.Msg.Seq, Ex: msg.Msg.Ex, } - data, err := json.Marshal(&revokeContent) + data, err := jsonutil.JsonMarshal(&revokeContent) if err != nil { - return nil, errs.Wrap(err, fmt.Sprintf("docID is %s, seqs is %v", docID, seqs)) + return nil, errs.WrapMsg(err, fmt.Sprintf("docID is %s, seqs is %v", docID, seqs)) } elem := sdkws.NotificationElem{ Detail: string(data), } - content, err := json.Marshal(&elem) + content, err := jsonutil.JsonMarshal(&elem) if err != nil { - return nil, errs.Wrap(err, fmt.Sprintf("docID is %s, seqs is %v", docID, seqs)) + return nil, errs.WrapMsg(err, fmt.Sprintf("docID is %s, seqs is %v", docID, seqs)) } msg.Msg.ContentType = constant.MsgRevokeNotification msg.Msg.Content = string(content) @@ -322,16 +177,72 @@ func (m *MsgMongoDriver) GetMsgBySeqIndexIn1Doc( return msgs, nil } -func (m *MsgMongoDriver) IsExistDocID(ctx context.Context, docID string) (bool, error) { - count, err := m.MsgCollection.CountDocuments(ctx, bson.M{"doc_id": docID}) +func (m *MsgMgo) GetNewestMsg(ctx context.Context, conversationID string) (*relation.MsgInfoModel, error) { + for skip := int64(0); ; skip++ { + msgDocModel, err := m.GetMsgDocModelByIndex(ctx, conversationID, skip, -1) + if err != nil { + return nil, err + } + for i := len(msgDocModel.Msg) - 1; i >= 0; i-- { + if msgDocModel.Msg[i].Msg != nil { + return msgDocModel.Msg[i], nil + } + } + } +} + +func (m *MsgMgo) GetOldestMsg(ctx context.Context, conversationID string) (*relation.MsgInfoModel, error) { + for skip := int64(0); ; skip++ { + msgDocModel, err := m.GetMsgDocModelByIndex(ctx, conversationID, skip, 1) + if err != nil { + return nil, err + } + for i, v := range msgDocModel.Msg { + if v.Msg != nil { + return msgDocModel.Msg[i], nil + } + } + } +} + +func (m *MsgMgo) DeleteDocs(ctx context.Context, docIDs []string) error { + if len(docIDs) == 0 { + return nil + } + return mongoutil.DeleteMany(ctx, m.coll, bson.M{"doc_id": bson.M{"$in": docIDs}}) +} + +func (m *MsgMgo) GetMsgDocModelByIndex(ctx context.Context, conversationID string, index, sort int64) (*relation.MsgDocModel, error) { + if sort != 1 && sort != -1 { + return nil, errs.ErrArgs.WrapMsg("mongo sort must be 1 or -1") + } + opt := options.Find().SetLimit(1).SetSkip(index).SetSort(bson.M{"doc_id": sort}).SetLimit(1) + filter := bson.M{"doc_id": primitive.Regex{Pattern: fmt.Sprintf("^%s:", conversationID)}} + msgs, err := mongoutil.Find[*relation.MsgDocModel](ctx, m.coll, filter, opt) if err != nil { - return false, errs.Wrap(err, fmt.Sprintf("docID is %s", docID)) + return nil, err } - return count > 0, nil + if len(msgs) > 0 { + return msgs[0], nil + } + return nil, errs.Wrap(relation.ErrMsgListNotExist) } -func (m *MsgMongoDriver) MarkSingleChatMsgsAsRead(ctx context.Context, userID string, docID string, indexes []int64) error { - updates := []mongo.WriteModel{} +func (m *MsgMgo) DeleteMsgsInOneDocByIndex(ctx context.Context, docID string, indexes []int) error { + update := bson.M{ + "$set": bson.M{}, + } + for _, index := range indexes { + update["$set"].(bson.M)[fmt.Sprintf("msgs.%d", index)] = bson.M{ + "msg": nil, + } + } + _, err := mongoutil.UpdateMany(ctx, m.coll, bson.M{"doc_id": docID}, update) + return err +} + +func (m *MsgMgo) MarkSingleChatMsgsAsRead(ctx context.Context, userID string, docID string, indexes []int64) error { + var updates []mongo.WriteModel for _, index := range indexes { filter := bson.M{ "doc_id": docID, @@ -349,204 +260,110 @@ func (m *MsgMongoDriver) MarkSingleChatMsgsAsRead(ctx context.Context, userID st SetUpdate(update) updates = append(updates, updateModel) } - _, err := m.MsgCollection.BulkWrite(ctx, updates) - return errs.Wrap(err, fmt.Sprintf("docID is %s, indexes is %v", docID, indexes)) + if _, err := m.coll.BulkWrite(ctx, updates); err != nil { + return errs.WrapMsg(err, fmt.Sprintf("docID is %s, indexes is %v", docID, indexes)) + } + return nil +} + +func (m *MsgMgo) SearchMessage(ctx context.Context, req *msg.SearchMessageReq) (int32, []*relation.MsgInfoModel, error) { + var pipe mongo.Pipeline + condition := bson.A{} + if req.SendTime != "" { + // Changed to keyed fields for bson.M to avoid govet errors + condition = append(condition, bson.M{"$eq": bson.A{bson.M{"$dateToString": bson.M{"format": "%Y-%m-%d", "date": bson.M{"$toDate": "$$item.msg.send_time"}}}, req.SendTime}}) + } + if req.ContentType != 0 { + condition = append(condition, bson.M{"$eq": bson.A{"$$item.msg.content_type", req.ContentType}}) + } + if req.SessionType != 0 { + condition = append(condition, bson.M{"$eq": bson.A{"$$item.msg.session_type", req.SessionType}}) + } + if req.RecvID != "" { + condition = append(condition, bson.M{"$regexFind": bson.M{"input": "$$item.msg.recv_id", "regex": req.RecvID}}) + } + if req.SendID != "" { + condition = append(condition, bson.M{"$regexFind": bson.M{"input": "$$item.msg.send_id", "regex": req.SendID}}) + } + + or := bson.A{ + bson.M{"doc_id": bson.M{"$regex": "^si_", "$options": "i"}}, + bson.M{"doc_id": bson.M{"$regex": "^g_", "$options": "i"}}, + bson.M{"doc_id": bson.M{"$regex": "^sg_", "$options": "i"}}, + } + + // Use bson.D with keyed fields to specify the order explicitly + pipe = mongo.Pipeline{ + {{"$match", bson.D{{Key: "$or", Value: or}}}}, + {{"$project", bson.D{ + {Key: "msgs", Value: bson.D{ + {Key: "$filter", Value: bson.D{ + {Key: "input", Value: "$msgs"}, + {Key: "as", Value: "item"}, + {Key: "cond", Value: bson.D{{Key: "$and", Value: condition}}}, + }}, + }}, + {Key: "doc_id", Value: 1}, + }}}, + {{"$unwind", bson.M{"path": "$msgs"}}}, + {{"$sort", bson.M{"msgs.msg.send_time": -1}}}, + } + type docModel struct { + DocID string `bson:"doc_id"` + Msg *relation.MsgInfoModel `bson:"msgs"` + } + msgsDocs, err := mongoutil.Aggregate[*docModel](ctx, m.coll, pipe) + if err != nil { + return 0, nil, err + } + msgs := make([]*relation.MsgInfoModel, 0) + for _, doc := range msgsDocs { + msgInfo := doc.Msg + if msgInfo == nil || msgInfo.Msg == nil { + continue + } + if msgInfo.Revoke != nil { + revokeContent := sdkws.MessageRevokedContent{ + RevokerID: msgInfo.Revoke.UserID, + RevokerRole: msgInfo.Revoke.Role, + ClientMsgID: msgInfo.Msg.ClientMsgID, + RevokerNickname: msgInfo.Revoke.Nickname, + RevokeTime: msgInfo.Revoke.Time, + SourceMessageSendTime: msgInfo.Msg.SendTime, + SourceMessageSendID: msgInfo.Msg.SendID, + SourceMessageSenderNickname: msgInfo.Msg.SenderNickname, + SessionType: msgInfo.Msg.SessionType, + Seq: msgInfo.Msg.Seq, + Ex: msgInfo.Msg.Ex, + } + data, err := jsonutil.JsonMarshal(&revokeContent) + if err != nil { + return 0, nil, errs.WrapMsg(err, "json.Marshal revokeContent") + } + elem := sdkws.NotificationElem{Detail: string(data)} + content, err := jsonutil.JsonMarshal(&elem) + if err != nil { + return 0, nil, errs.WrapMsg(err, "json.Marshal elem") + } + msgInfo.Msg.ContentType = constant.MsgRevokeNotification + msgInfo.Msg.Content = string(content) + } + msgs = append(msgs, msgInfo) + } + start := (req.Pagination.PageNumber - 1) * req.Pagination.ShowNumber + n := int32(len(msgs)) + if start >= n { + return n, []*relation.MsgInfoModel{}, nil + } + if start+req.Pagination.ShowNumber < n { + msgs = msgs[start : start+req.Pagination.ShowNumber] + } else { + msgs = msgs[start:] + } + return n, msgs, nil } -// RangeUserSendCount -// db.msg.aggregate([ -// -// { -// $match: { -// "msgs.msg.send_time": { -// "$gte": 0, -// "$lt": 1788122092317 -// } -// } -// }, -// { -// "$addFields": { -// "msgs": { -// "$filter": { -// "input": "$msgs", -// "as": "item", -// "cond": { -// "$and": [ -// { -// $gte: ["$$item.msg.send_time", 0] -// }, -// { -// $lt: ["$$item.msg.send_time", 1788122092317] -// } -// ] -// } -// } -// } -// } -// }, -// { -// "$project": { -// "_id": 0, -// -// }, -// -// }, -// { -// "$project": { -// "result": { -// "$map": { -// "input": "$msgs", -// "as": "item", -// "in": { -// user_id: "$$item.msg.send_id", -// send_date: { -// $dateToString: { -// format: "%Y-%m-%d", -// date: { -// $toDate: "$$item.msg.send_time" -// } -// } -// } -// } -// } -// } -// }, -// -// }, -// { -// "$unwind": "$result" -// }, -// { -// "$group": { -// _id: "$result.send_date", -// count: { -// $sum: 1 -// }, -// original: { -// $push: "$$ROOT" -// } -// } -// }, -// { -// "$addFields": { -// "dates": "$$ROOT" -// } -// }, -// { -// "$project": { -// "_id": 0, -// "count": 0, -// "dates.original": 0, -// -// }, -// -// }, -// { -// "$group": { -// _id: null, -// count: { -// $sum: 1 -// }, -// dates: { -// $push: "$dates" -// }, -// original: { -// $push: "$original" -// }, -// -// } -// }, -// { -// "$unwind": "$original" -// }, -// { -// "$unwind": "$original" -// }, -// { -// "$group": { -// _id: "$original.result.user_id", -// count: { -// $sum: 1 -// }, -// original: { -// $push: "$dates" -// }, -// -// } -// }, -// { -// "$addFields": { -// "dates": { -// $arrayElemAt: ["$original", 0] -// } -// } -// }, -// { -// "$project": { -// original: 0 -// } -// }, -// { -// $sort: { -// count: - 1 -// } -// }, -// { -// "$group": { -// _id: null, -// user_count: { -// $sum: 1 -// }, -// users: { -// $push: "$$ROOT" -// }, -// -// } -// }, -// { -// "$addFields": { -// "dates": { -// $arrayElemAt: ["$users", 0] -// } -// } -// }, -// { -// "$addFields": { -// "dates": "$dates.dates" -// } -// }, -// { -// "$project": { -// _id: 0, -// "users.dates": 0, -// -// } -// }, -// { -// "$addFields": { -// "msg_count": { -// $sum: "$users.count" -// } -// } -// }, -// { -// "$addFields": { -// users: { -// $slice: ["$users", 0, 10] -// } -// } -// } -// -// ]). -func (m *MsgMongoDriver) RangeUserSendCount( - ctx context.Context, - start time.Time, - end time.Time, - group bool, - ase bool, - pageNumber int32, - showNumber int32, -) (msgCount int64, userCount int64, users []*table.UserCount, dateCount map[string]int64, err error) { +func (m *MsgMgo) RangeUserSendCount(ctx context.Context, start time.Time, end time.Time, group bool, ase bool, pageNumber int32, showNumber int32) (msgCount int64, userCount int64, users []*relation.UserCount, dateCount map[string]int64, err error) { var sort int if ase { sort = 1 @@ -773,21 +590,16 @@ func (m *MsgMongoDriver) RangeUserSendCount( }, }, } - cur, err := m.MsgCollection.Aggregate(ctx, pipeline, options.Aggregate().SetAllowDiskUse(true)) + result, err := mongoutil.Aggregate[*Result](ctx, m.coll, pipeline, options.Aggregate().SetAllowDiskUse(true)) if err != nil { - return 0, 0, nil, nil, errs.Wrap(err) - } - defer cur.Close(ctx) - var result []Result - if err = cur.All(ctx, &result); err != nil { - return 0, 0, nil, nil, errs.Wrap(err) + return 0, 0, nil, nil, err } if len(result) == 0 { return 0, 0, nil, nil, errs.Wrap(err) } - users = make([]*table.UserCount, len(result[0].Users)) + users = make([]*relation.UserCount, len(result[0].Users)) for i, r := range result[0].Users { - users[i] = &table.UserCount{ + users[i] = &relation.UserCount{ UserID: r.UserID, Count: r.Count, } @@ -799,14 +611,7 @@ func (m *MsgMongoDriver) RangeUserSendCount( return result[0].MsgCount, result[0].UserCount, users, dateCount, nil } -func (m *MsgMongoDriver) RangeGroupSendCount( - ctx context.Context, - start time.Time, - end time.Time, - ase bool, - pageNumber int32, - showNumber int32, -) (msgCount int64, userCount int64, groups []*table.GroupCount, dateCount map[string]int64, err error) { +func (m *MsgMgo) RangeGroupSendCount(ctx context.Context, start time.Time, end time.Time, ase bool, pageNumber int32, showNumber int32) (msgCount int64, userCount int64, groups []*relation.GroupCount, dateCount map[string]int64, err error) { var sort int if ase { sort = 1 @@ -1022,21 +827,16 @@ func (m *MsgMongoDriver) RangeGroupSendCount( }, }, } - cur, err := m.MsgCollection.Aggregate(ctx, pipeline, options.Aggregate().SetAllowDiskUse(true)) + result, err := mongoutil.Aggregate[*Result](ctx, m.coll, pipeline, options.Aggregate().SetAllowDiskUse(true)) if err != nil { - return 0, 0, nil, nil, errs.Wrap(err) - } - defer cur.Close(ctx) - var result []Result - if err = cur.All(ctx, &result); err != nil { - return 0, 0, nil, nil, errs.Wrap(err) + return 0, 0, nil, nil, err } if len(result) == 0 { return 0, 0, nil, nil, errs.Wrap(err) } - groups = make([]*table.GroupCount, len(result[0].Groups)) + groups = make([]*relation.GroupCount, len(result[0].Groups)) for i, r := range result[0].Groups { - groups[i] = &table.GroupCount{ + groups[i] = &relation.GroupCount{ GroupID: r.GroupID, Count: r.Count, } @@ -1048,113 +848,51 @@ func (m *MsgMongoDriver) RangeGroupSendCount( return result[0].MsgCount, result[0].UserCount, groups, dateCount, nil } -func (m *MsgMongoDriver) SearchMessage(ctx context.Context, req *msg.SearchMessageReq) (int32, []*table.MsgInfoModel, error) { - total, msgs, err := m.searchMessage(ctx, req) - if err != nil { - return 0, nil, err - } - return total, msgs, nil -} - -func (m *MsgMongoDriver) searchMessage(ctx context.Context, req *msg.SearchMessageReq) (int32, []*table.MsgInfoModel, error) { - var pipe mongo.Pipeline - condition := bson.A{} - if req.SendTime != "" { - // Changed to keyed fields for bson.M to avoid govet errors - condition = append(condition, bson.M{"$eq": bson.A{bson.M{"$dateToString": bson.M{"format": "%Y-%m-%d", "date": bson.M{"$toDate": "$$item.msg.send_time"}}}, req.SendTime}}) - } - if req.ContentType != 0 { - condition = append(condition, bson.M{"$eq": bson.A{"$$item.msg.content_type", req.ContentType}}) - } - if req.SessionType != 0 { - condition = append(condition, bson.M{"$eq": bson.A{"$$item.msg.session_type", req.SessionType}}) - } - if req.RecvID != "" { - condition = append(condition, bson.M{"$regexFind": bson.M{"input": "$$item.msg.recv_id", "regex": req.RecvID}}) - } - if req.SendID != "" { - condition = append(condition, bson.M{"$regexFind": bson.M{"input": "$$item.msg.send_id", "regex": req.SendID}}) - } - - or := bson.A{ - bson.M{"doc_id": bson.M{"$regex": "^si_", "$options": "i"}}, - bson.M{"doc_id": bson.M{"$regex": "^g_", "$options": "i"}}, - bson.M{"doc_id": bson.M{"$regex": "^sg_", "$options": "i"}}, - } - - // Use bson.D with keyed fields to specify the order explicitly - pipe = mongo.Pipeline{ - {{"$match", bson.D{{Key: "$or", Value: or}}}}, - {{"$project", bson.D{ - {Key: "msgs", Value: bson.D{ - {Key: "$filter", Value: bson.D{ - {Key: "input", Value: "$msgs"}, - {Key: "as", Value: "item"}, - {Key: "cond", Value: bson.D{{Key: "$and", Value: condition}}}, - }}, - }}, - {Key: "doc_id", Value: 1}, - }}}, - {{"$unwind", bson.M{"path": "$msgs"}}}, - {{"$sort", bson.M{"msgs.msg.send_time": -1}}}, - } - cursor, err := m.MsgCollection.Aggregate(ctx, pipe) - if err != nil { - return 0, nil, err - } - type docModel struct { - DocID string `bson:"doc_id"` - Msg *table.MsgInfoModel `bson:"msgs"` - } - var msgsDocs []docModel - err = cursor.All(ctx, &msgsDocs) - if err != nil { - return 0, nil, errs.Wrap(err, "cursor.All msgsDocs") - } - log.ZDebug(ctx, "query mongoDB", "result", msgsDocs) - msgs := make([]*table.MsgInfoModel, 0) - for _, doc := range msgsDocs { - msgInfo := doc.Msg - if msgInfo == nil || msgInfo.Msg == nil { +func (m *MsgMgo) ConvertMsgsDocLen(ctx context.Context, conversationIDs []string) { + for _, conversationID := range conversationIDs { + regex := primitive.Regex{Pattern: fmt.Sprintf("^%s:", conversationID)} + msgDocs, err := mongoutil.Find[*relation.MsgDocModel](ctx, m.coll, bson.M{"doc_id": regex}) + if err != nil { + log.ZError(ctx, "convertAll find msg doc failed", err, "conversationID", conversationID) continue } - if msgInfo.Revoke != nil { - revokeContent := sdkws.MessageRevokedContent{ - RevokerID: msgInfo.Revoke.UserID, - RevokerRole: msgInfo.Revoke.Role, - ClientMsgID: msgInfo.Msg.ClientMsgID, - RevokerNickname: msgInfo.Revoke.Nickname, - RevokeTime: msgInfo.Revoke.Time, - SourceMessageSendTime: msgInfo.Msg.SendTime, - SourceMessageSendID: msgInfo.Msg.SendID, - SourceMessageSenderNickname: msgInfo.Msg.SenderNickname, - SessionType: msgInfo.Msg.SessionType, - Seq: msgInfo.Msg.Seq, - Ex: msgInfo.Msg.Ex, + if len(msgDocs) < 1 { + continue + } + log.ZDebug(ctx, "msg doc convert", "conversationID", conversationID, "len(msgDocs)", len(msgDocs)) + if len(msgDocs[0].Msg) == int(m.model.GetSingleGocMsgNum5000()) { + if err := mongoutil.DeleteMany(ctx, m.coll, bson.M{"doc_id": regex}); err != nil { + log.ZError(ctx, "convertAll delete many failed", err, "conversationID", conversationID) + continue } - data, err := json.Marshal(&revokeContent) - if err != nil { - return 0, nil, errs.Wrap(err, "json.Marshal revokeContent") + var newMsgDocs []any + for _, msgDoc := range msgDocs { + if int64(len(msgDoc.Msg)) == m.model.GetSingleGocMsgNum() { + continue + } + var index int64 + for index < int64(len(msgDoc.Msg)) { + msg := msgDoc.Msg[index] + if msg != nil && msg.Msg != nil { + msgDocModel := relation.MsgDocModel{DocID: m.model.GetDocID(conversationID, msg.Msg.Seq)} + end := index + m.model.GetSingleGocMsgNum() + if int(end) >= len(msgDoc.Msg) { + msgDocModel.Msg = msgDoc.Msg[index:] + } else { + msgDocModel.Msg = msgDoc.Msg[index:end] + } + newMsgDocs = append(newMsgDocs, msgDocModel) + index = end + } else { + break + } + } } - elem := sdkws.NotificationElem{Detail: string(data)} - content, err := json.Marshal(&elem) - if err != nil { - return 0, nil, errs.Wrap(err, "json.Marshal elem") + if err = mongoutil.InsertMany(ctx, m.coll, newMsgDocs); err != nil { + log.ZError(ctx, "convertAll insert many failed", err, "conversationID", conversationID, "len(newMsgDocs)", len(newMsgDocs)) + } else { + log.ZDebug(ctx, "msg doc convert", "conversationID", conversationID, "len(newMsgDocs)", len(newMsgDocs)) } - msgInfo.Msg.ContentType = constant.MsgRevokeNotification - msgInfo.Msg.Content = string(content) } - msgs = append(msgs, msgInfo) - } - start := (req.Pagination.PageNumber - 1) * req.Pagination.ShowNumber - n := int32(len(msgs)) - if start >= n { - return n, []*table.MsgInfoModel{}, nil } - if start+req.Pagination.ShowNumber < n { - msgs = msgs[start : start+req.Pagination.ShowNumber] - } else { - msgs = msgs[start:] - } - return n, msgs, nil } diff --git a/pkg/common/db/mgo/object.go b/pkg/common/db/mgo/object.go index e9d639f19..1c628bb51 100644 --- a/pkg/common/db/mgo/object.go +++ b/pkg/common/db/mgo/object.go @@ -17,9 +17,9 @@ package mgo import ( "context" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mgoutil" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/errs" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -54,16 +54,16 @@ func (o *S3Mongo) SetObject(ctx context.Context, obj *relation.ObjectModel) erro "group": obj.Group, "create_time": obj.CreateTime, } - return mgoutil.UpdateOne(ctx, o.coll, filter, bson.M{"$set": update}, false, options.Update().SetUpsert(true)) + return mongoutil.UpdateOne(ctx, o.coll, filter, bson.M{"$set": update}, false, options.Update().SetUpsert(true)) } func (o *S3Mongo) Take(ctx context.Context, engine string, name string) (*relation.ObjectModel, error) { if engine == "" { - return mgoutil.FindOne[*relation.ObjectModel](ctx, o.coll, bson.M{"name": name}) + return mongoutil.FindOne[*relation.ObjectModel](ctx, o.coll, bson.M{"name": name}) } - return mgoutil.FindOne[*relation.ObjectModel](ctx, o.coll, bson.M{"name": name, "engine": engine}) + return mongoutil.FindOne[*relation.ObjectModel](ctx, o.coll, bson.M{"name": name, "engine": engine}) } func (o *S3Mongo) Delete(ctx context.Context, engine string, name string) error { - return mgoutil.DeleteOne(ctx, o.coll, bson.M{"name": name, "engine": engine}) + return mongoutil.DeleteOne(ctx, o.coll, bson.M{"name": name, "engine": engine}) } diff --git a/pkg/common/db/unrelation/user.go b/pkg/common/db/mgo/subscribe.go similarity index 92% rename from pkg/common/db/unrelation/user.go rename to pkg/common/db/mgo/subscribe.go index cbf395bf8..f2057dc45 100644 --- a/pkg/common/db/unrelation/user.go +++ b/pkg/common/db/mgo/subscribe.go @@ -12,13 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package unrelation +package mgo import ( "context" - "github.com/OpenIMSDK/tools/errs" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" + "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/tools/errs" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" @@ -35,9 +35,9 @@ const ( MaximumSubscription = 3000 ) -func NewUserMongoDriver(database *mongo.Database) unrelation.UserModelInterface { +func NewUserMongoDriver(database *mongo.Database) relation.SubscribeUserModelInterface { return &UserMongoDriver{ - userCollection: database.Collection(unrelation.SubscribeUser), + userCollection: database.Collection(relation.SubscribeUser), } } @@ -117,7 +117,7 @@ func (u *UserMongoDriver) AddSubscriptionList(ctx context.Context, userID string opts, ) if err != nil { - return errs.Wrap(err, "transaction failed") + return errs.WrapMsg(err, "transaction failed") } } return nil @@ -155,7 +155,7 @@ func (u *UserMongoDriver) RemoveSubscribedListFromUser(ctx context.Context, user // GetAllSubscribeList Get all users subscribed by this user. func (u *UserMongoDriver) GetAllSubscribeList(ctx context.Context, userID string) (userIDList []string, err error) { - var user unrelation.UserModel + var user relation.SubscribeUserModel cursor := u.userCollection.FindOne( ctx, bson.M{"user_id": SubscriptionPrefix + userID}) @@ -172,7 +172,7 @@ func (u *UserMongoDriver) GetAllSubscribeList(ctx context.Context, userID string // GetSubscribedList Get the user subscribed by those users. func (u *UserMongoDriver) GetSubscribedList(ctx context.Context, userID string) (userIDList []string, err error) { - var user unrelation.UserModel + var user relation.SubscribeUserModel cursor := u.userCollection.FindOne( ctx, bson.M{"user_id": SubscribedPrefix + userID}) diff --git a/pkg/common/db/mgo/user.go b/pkg/common/db/mgo/user.go index 9ca2eb178..696479871 100644 --- a/pkg/common/db/mgo/user.go +++ b/pkg/common/db/mgo/user.go @@ -18,11 +18,11 @@ import ( "context" "time" - "github.com/OpenIMSDK/protocol/user" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mgoutil" - "github.com/OpenIMSDK/tools/pagination" "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" + "github.com/openimsdk/protocol/user" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/pagination" + "github.com/openimsdk/tools/errs" "go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" @@ -48,34 +48,34 @@ type UserMgo struct { } func (u *UserMgo) Create(ctx context.Context, users []*relation.UserModel) error { - return mgoutil.InsertMany(ctx, u.coll, users) + return mongoutil.InsertMany(ctx, u.coll, users) } func (u *UserMgo) UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error) { if len(args) == 0 { return nil } - return mgoutil.UpdateOne(ctx, u.coll, bson.M{"user_id": userID}, bson.M{"$set": args}, true) + return mongoutil.UpdateOne(ctx, u.coll, bson.M{"user_id": userID}, bson.M{"$set": args}, true) } func (u *UserMgo) Find(ctx context.Context, userIDs []string) (users []*relation.UserModel, err error) { - return mgoutil.Find[*relation.UserModel](ctx, u.coll, bson.M{"user_id": bson.M{"$in": userIDs}}) + return mongoutil.Find[*relation.UserModel](ctx, u.coll, bson.M{"user_id": bson.M{"$in": userIDs}}) } func (u *UserMgo) Take(ctx context.Context, userID string) (user *relation.UserModel, err error) { - return mgoutil.FindOne[*relation.UserModel](ctx, u.coll, bson.M{"user_id": userID}) + return mongoutil.FindOne[*relation.UserModel](ctx, u.coll, bson.M{"user_id": userID}) } func (u *UserMgo) TakeNotification(ctx context.Context, level int64) (user []*relation.UserModel, err error) { - return mgoutil.Find[*relation.UserModel](ctx, u.coll, bson.M{"app_manger_level": level}) + return mongoutil.Find[*relation.UserModel](ctx, u.coll, bson.M{"app_manger_level": level}) } func (u *UserMgo) TakeByNickname(ctx context.Context, nickname string) (user []*relation.UserModel, err error) { - return mgoutil.Find[*relation.UserModel](ctx, u.coll, bson.M{"nickname": nickname}) + return mongoutil.Find[*relation.UserModel](ctx, u.coll, bson.M{"nickname": nickname}) } func (u *UserMgo) Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) { - return mgoutil.FindPage[*relation.UserModel](ctx, u.coll, bson.M{}, pagination) + return mongoutil.FindPage[*relation.UserModel](ctx, u.coll, bson.M{}, pagination) } func (u *UserMgo) PageFindUser(ctx context.Context, level1 int64, level2 int64, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) { @@ -86,7 +86,7 @@ func (u *UserMgo) PageFindUser(ctx context.Context, level1 int64, level2 int64, }, } - return mgoutil.FindPage[*relation.UserModel](ctx, u.coll, query, pagination) + return mongoutil.FindPage[*relation.UserModel](ctx, u.coll, query, pagination) } func (u *UserMgo) PageFindUserWithKeyword( @@ -121,26 +121,26 @@ func (u *UserMgo) PageFindUserWithKeyword( } // Perform the paginated search - return mgoutil.FindPage[*relation.UserModel](ctx, u.coll, query, pagination) + return mongoutil.FindPage[*relation.UserModel](ctx, u.coll, query, pagination) } func (u *UserMgo) GetAllUserID(ctx context.Context, pagination pagination.Pagination) (int64, []string, error) { - return mgoutil.FindPage[string](ctx, u.coll, bson.M{}, pagination, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1})) + return mongoutil.FindPage[string](ctx, u.coll, bson.M{}, pagination, options.Find().SetProjection(bson.M{"_id": 0, "user_id": 1})) } func (u *UserMgo) Exist(ctx context.Context, userID string) (exist bool, err error) { - return mgoutil.Exist(ctx, u.coll, bson.M{"user_id": userID}) + return mongoutil.Exist(ctx, u.coll, bson.M{"user_id": userID}) } func (u *UserMgo) GetUserGlobalRecvMsgOpt(ctx context.Context, userID string) (opt int, err error) { - return mgoutil.FindOne[int](ctx, u.coll, bson.M{"user_id": userID}, options.FindOne().SetProjection(bson.M{"_id": 0, "global_recv_msg_opt": 1})) + return mongoutil.FindOne[int](ctx, u.coll, bson.M{"user_id": userID}, options.FindOne().SetProjection(bson.M{"_id": 0, "global_recv_msg_opt": 1})) } func (u *UserMgo) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) { if before == nil { - return mgoutil.Count(ctx, u.coll, bson.M{}) + return mongoutil.Count(ctx, u.coll, bson.M{}) } - return mgoutil.Count(ctx, u.coll, bson.M{"create_time": bson.M{"$lt": before}}) + return mongoutil.Count(ctx, u.coll, bson.M{"create_time": bson.M{"$lt": before}}) } func (u *UserMgo) AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error { @@ -308,7 +308,7 @@ func (u *UserMgo) CountRangeEverydayTotal(ctx context.Context, start time.Time, Date string `bson:"_id"` Count int64 `bson:"count"` } - items, err := mgoutil.Aggregate[Item](ctx, u.coll, pipeline) + items, err := mongoutil.Aggregate[Item](ctx, u.coll, pipeline) if err != nil { return nil, err } diff --git a/pkg/common/db/s3/cont/consts.go b/pkg/common/db/s3/cont/consts.go deleted file mode 100644 index b51878e49..000000000 --- a/pkg/common/db/s3/cont/consts.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cont - -const ( - // hashPath defines the storage path for hash data within the 'openim' directory. - hashPath = "openim/data/hash/" - - // tempPath specifies the directory for temporary files in the 'openim' structure. - tempPath = "openim/temp/" - - // DirectPath indicates the directory for direct uploads or access within the 'openim' structure. - DirectPath = "openim/direct" - - // UploadTypeMultipart represents the identifier for multipart uploads, - // allowing large files to be uploaded in chunks. - UploadTypeMultipart = 1 - - // UploadTypePresigned signifies the use of presigned URLs for uploads, - // facilitating secure, authorized file transfers without requiring direct access to the storage credentials. - UploadTypePresigned = 2 - - // partSeparator is used as a delimiter in multipart upload processes, - // separating individual file parts. - partSeparator = "," -) diff --git a/pkg/common/db/s3/cont/controller.go b/pkg/common/db/s3/cont/controller.go deleted file mode 100644 index 915109aa5..000000000 --- a/pkg/common/db/s3/cont/controller.go +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cont - -import ( - "context" - "crypto/md5" - "encoding/hex" - "errors" - "fmt" - "path" - "strings" - "time" - - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/google/uuid" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" -) - -func New(cache cache.S3Cache, impl s3.Interface) *Controller { - return &Controller{ - cache: cache, - impl: impl, - } -} - -type Controller struct { - cache cache.S3Cache - impl s3.Interface -} - -func (c *Controller) Engine() string { - return c.impl.Engine() -} - -func (c *Controller) HashPath(md5 string) string { - return path.Join(hashPath, md5) -} - -func (c *Controller) NowPath() string { - now := time.Now() - return path.Join( - fmt.Sprintf("%04d", now.Year()), - fmt.Sprintf("%02d", now.Month()), - fmt.Sprintf("%02d", now.Day()), - fmt.Sprintf("%02d", now.Hour()), - fmt.Sprintf("%02d", now.Minute()), - fmt.Sprintf("%02d", now.Second()), - ) -} - -func (c *Controller) UUID() string { - id := uuid.New() - return hex.EncodeToString(id[:]) -} - -func (c *Controller) PartSize(ctx context.Context, size int64) (int64, error) { - return c.impl.PartSize(ctx, size) -} - -func (c *Controller) PartLimit() *s3.PartLimit { - return c.impl.PartLimit() -} - -func (c *Controller) StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error) { - return c.cache.GetKey(ctx, c.impl.Engine(), name) -} - -func (c *Controller) GetHashObject(ctx context.Context, hash string) (*s3.ObjectInfo, error) { - return c.StatObject(ctx, c.HashPath(hash)) -} - -func (c *Controller) InitiateUpload(ctx context.Context, hash string, size int64, expire time.Duration, maxParts int) (*InitiateUploadResult, error) { - defer log.ZDebug(ctx, "return") - if size < 0 { - return nil, errors.New("invalid size") - } - if hashBytes, err := hex.DecodeString(hash); err != nil { - return nil, err - } else if len(hashBytes) != md5.Size { - return nil, errors.New("invalid md5") - } - partSize, err := c.impl.PartSize(ctx, size) - if err != nil { - return nil, err - } - partNumber := int(size / partSize) - if size%partSize > 0 { - partNumber++ - } - if maxParts > 0 && partNumber > 0 && partNumber < maxParts { - return nil, fmt.Errorf("too many parts: %d", partNumber) - } - if info, err := c.StatObject(ctx, c.HashPath(hash)); err == nil { - return nil, &HashAlreadyExistsError{Object: info} - } else if !c.impl.IsNotFound(err) { - return nil, err - } - if size <= partSize { - // Pre-signed upload - key := path.Join(tempPath, c.NowPath(), fmt.Sprintf("%s_%d_%s.presigned", hash, size, c.UUID())) - rawURL, err := c.impl.PresignedPutObject(ctx, key, expire) - if err != nil { - return nil, err - } - return &InitiateUploadResult{ - UploadID: newMultipartUploadID(multipartUploadID{ - Type: UploadTypePresigned, - ID: "", - Key: key, - Size: size, - Hash: hash, - }), - PartSize: partSize, - Sign: &s3.AuthSignResult{ - Parts: []s3.SignPart{ - { - PartNumber: 1, - URL: rawURL, - }, - }, - }, - }, nil - } else { - // Fragment upload - upload, err := c.impl.InitiateMultipartUpload(ctx, c.HashPath(hash)) - if err != nil { - return nil, err - } - if maxParts < 0 { - maxParts = partNumber - } - var authSign *s3.AuthSignResult - if maxParts > 0 { - partNumbers := make([]int, maxParts) - for i := 0; i < maxParts; i++ { - partNumbers[i] = i + 1 - } - authSign, err = c.impl.AuthSign(ctx, upload.UploadID, upload.Key, time.Hour*24, partNumbers) - if err != nil { - return nil, err - } - } - return &InitiateUploadResult{ - UploadID: newMultipartUploadID(multipartUploadID{ - Type: UploadTypeMultipart, - ID: upload.UploadID, - Key: upload.Key, - Size: size, - Hash: hash, - }), - PartSize: partSize, - Sign: authSign, - }, nil - } -} - -func (c *Controller) CompleteUpload(ctx context.Context, uploadID string, partHashs []string) (*UploadResult, error) { - defer log.ZDebug(ctx, "return") - upload, err := parseMultipartUploadID(uploadID) - if err != nil { - return nil, err - } - if md5Sum := md5.Sum([]byte(strings.Join(partHashs, partSeparator))); hex.EncodeToString(md5Sum[:]) != upload.Hash { - return nil, errors.New("md5 mismatching") - } - if info, err := c.StatObject(ctx, c.HashPath(upload.Hash)); err == nil { - return &UploadResult{ - Key: info.Key, - Size: info.Size, - Hash: info.ETag, - }, nil - } else if !c.IsNotFound(err) { - return nil, err - } - cleanObject := make(map[string]struct{}) - defer func() { - for key := range cleanObject { - _ = c.impl.DeleteObject(ctx, key) - } - }() - var targetKey string - switch upload.Type { - case UploadTypeMultipart: - parts := make([]s3.Part, len(partHashs)) - for i, part := range partHashs { - parts[i] = s3.Part{ - PartNumber: i + 1, - ETag: part, - } - } - // todo: Validation size - result, err := c.impl.CompleteMultipartUpload(ctx, upload.ID, upload.Key, parts) - if err != nil { - return nil, err - } - targetKey = result.Key - case UploadTypePresigned: - uploadInfo, err := c.StatObject(ctx, upload.Key) - if err != nil { - return nil, err - } - cleanObject[uploadInfo.Key] = struct{}{} - if uploadInfo.Size != upload.Size { - return nil, errors.New("upload size mismatching") - } - md5Sum := md5.Sum([]byte(strings.Join([]string{uploadInfo.ETag}, partSeparator))) - if md5val := hex.EncodeToString(md5Sum[:]); md5val != upload.Hash { - return nil, errs.ErrArgs.Wrap(fmt.Sprintf("md5 mismatching %s != %s", md5val, upload.Hash)) - } - // Prevents concurrent operations at this time that cause files to be overwritten - copyInfo, err := c.impl.CopyObject(ctx, uploadInfo.Key, upload.Key+"."+c.UUID()) - if err != nil { - return nil, err - } - cleanObject[copyInfo.Key] = struct{}{} - if copyInfo.ETag != uploadInfo.ETag { - return nil, errors.New("[concurrency]copy md5 mismatching") - } - hashCopyInfo, err := c.impl.CopyObject(ctx, copyInfo.Key, c.HashPath(upload.Hash)) - if err != nil { - return nil, err - } - log.ZInfo(ctx, "hashCopyInfo", "value", fmt.Sprintf("%+v", hashCopyInfo)) - targetKey = hashCopyInfo.Key - default: - return nil, errors.New("invalid upload id type") - } - if err := c.cache.DelS3Key(c.impl.Engine(), targetKey).ExecDel(ctx); err != nil { - return nil, err - } - return &UploadResult{ - Key: targetKey, - Size: upload.Size, - Hash: upload.Hash, - }, nil -} - -func (c *Controller) AuthSign(ctx context.Context, uploadID string, partNumbers []int) (*s3.AuthSignResult, error) { - upload, err := parseMultipartUploadID(uploadID) - if err != nil { - return nil, err - } - switch upload.Type { - case UploadTypeMultipart: - return c.impl.AuthSign(ctx, upload.ID, upload.Key, time.Hour*24, partNumbers) - case UploadTypePresigned: - return nil, errors.New("presigned id not support auth sign") - default: - return nil, errors.New("invalid upload id type") - } -} - -func (c *Controller) IsNotFound(err error) bool { - return c.impl.IsNotFound(err) || errs.ErrRecordNotFound.Is(err) -} - -func (c *Controller) AccessURL(ctx context.Context, name string, expire time.Duration, opt *s3.AccessURLOption) (string, error) { - if opt.Image != nil { - opt.Filename = "" - opt.ContentType = "" - } - return c.impl.AccessURL(ctx, name, expire, opt) -} - -func (c *Controller) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) { - return c.impl.FormData(ctx, name, size, contentType, duration) -} diff --git a/pkg/common/db/s3/cont/error.go b/pkg/common/db/s3/cont/error.go deleted file mode 100644 index 8225274d3..000000000 --- a/pkg/common/db/s3/cont/error.go +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cont - -import ( - "fmt" - - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" -) - -type HashAlreadyExistsError struct { - Object *s3.ObjectInfo -} - -func (e *HashAlreadyExistsError) Error() string { - return fmt.Sprintf("hash already exists: %s", e.Object.Key) -} diff --git a/pkg/common/db/s3/cont/id.go b/pkg/common/db/s3/cont/id.go deleted file mode 100644 index 47f37d4aa..000000000 --- a/pkg/common/db/s3/cont/id.go +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cont - -import ( - "encoding/base64" - "encoding/json" - "fmt" -) - -type multipartUploadID struct { - Type int `json:"a,omitempty"` - ID string `json:"b,omitempty"` - Key string `json:"c,omitempty"` - Size int64 `json:"d,omitempty"` - Hash string `json:"e,omitempty"` -} - -func newMultipartUploadID(id multipartUploadID) string { - data, err := json.Marshal(id) - if err != nil { - panic(err) - } - return base64.StdEncoding.EncodeToString(data) -} - -func parseMultipartUploadID(id string) (*multipartUploadID, error) { - data, err := base64.StdEncoding.DecodeString(id) - if err != nil { - return nil, fmt.Errorf("invalid multipart upload id: %w", err) - } - var upload multipartUploadID - if err := json.Unmarshal(data, &upload); err != nil { - return nil, fmt.Errorf("invalid multipart upload id: %w", err) - } - return &upload, nil -} diff --git a/pkg/common/db/s3/cont/structs.go b/pkg/common/db/s3/cont/structs.go deleted file mode 100644 index de484cc5f..000000000 --- a/pkg/common/db/s3/cont/structs.go +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cont - -import "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" - -type InitiateUploadResult struct { - // UploadID uniquely identifies the upload session for tracking and management purposes. - UploadID string `json:"uploadID"` - - // PartSize specifies the size of each part in a multipart upload. This is relevant for breaking down large uploads into manageable pieces. - PartSize int64 `json:"partSize"` - - // Sign contains the authentication and signature information necessary for securely uploading each part. This could include signed URLs or tokens. - Sign *s3.AuthSignResult `json:"sign"` -} - -type UploadResult struct { - Hash string `json:"hash"` - Size int64 `json:"size"` - Key string `json:"key"` -} diff --git a/pkg/common/db/s3/cos/cos.go b/pkg/common/db/s3/cos/cos.go deleted file mode 100644 index 9852d2a98..000000000 --- a/pkg/common/db/s3/cos/cos.go +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cos - -import ( - "context" - "crypto/hmac" - "crypto/sha1" - "encoding/base64" - "encoding/hex" - "encoding/json" - "errors" - "fmt" - "net/http" - "net/url" - "strconv" - "strings" - "time" - - "github.com/OpenIMSDK/tools/errs" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" - "github.com/tencentyun/cos-go-sdk-v5" -) - -const ( - minPartSize int64 = 1024 * 1024 * 1 // 1MB - maxPartSize int64 = 1024 * 1024 * 1024 * 5 // 5GB - maxNumSize int64 = 1000 -) - -const ( - imagePng = "png" - imageJpg = "jpg" - imageJpeg = "jpeg" - imageGif = "gif" - imageWebp = "webp" -) - -const successCode = http.StatusOK - -type Config struct { - BucketURL string - SecretID string - SecretKey string - SessionToken string - PublicRead bool -} - -func NewCos(conf Config) (s3.Interface, error) { - u, err := url.Parse(conf.BucketURL) - if err != nil { - panic(err) - } - client := cos.NewClient(&cos.BaseURL{BucketURL: u}, &http.Client{ - Transport: &cos.AuthorizationTransport{ - SecretID: conf.SecretID, - SecretKey: conf.SecretKey, - SessionToken: conf.SessionToken, - }, - }) - return &Cos{ - publicRead: conf.PublicRead, - copyURL: u.Host + "/", - client: client, - credential: client.GetCredential(), - }, nil -} - -type Cos struct { - publicRead bool - copyURL string - client *cos.Client - credential *cos.Credential -} - -func (c *Cos) Engine() string { - return "tencent-cos" -} - -func (c *Cos) PartLimit() *s3.PartLimit { - return &s3.PartLimit{ - MinPartSize: minPartSize, - MaxPartSize: maxPartSize, - MaxNumSize: maxNumSize, - } -} - -func (c *Cos) InitiateMultipartUpload(ctx context.Context, name string) (*s3.InitiateMultipartUploadResult, error) { - result, _, err := c.client.Object.InitiateMultipartUpload(ctx, name, nil) - if err != nil { - return nil, err - } - return &s3.InitiateMultipartUploadResult{ - UploadID: result.UploadID, - Bucket: result.Bucket, - Key: result.Key, - }, nil -} - -func (c *Cos) CompleteMultipartUpload(ctx context.Context, uploadID string, name string, parts []s3.Part) (*s3.CompleteMultipartUploadResult, error) { - opts := &cos.CompleteMultipartUploadOptions{ - Parts: make([]cos.Object, len(parts)), - } - for i, part := range parts { - opts.Parts[i] = cos.Object{ - PartNumber: part.PartNumber, - ETag: strings.ReplaceAll(part.ETag, `"`, ``), - } - } - result, _, err := c.client.Object.CompleteMultipartUpload(ctx, name, uploadID, opts) - if err != nil { - return nil, err - } - return &s3.CompleteMultipartUploadResult{ - Location: result.Location, - Bucket: result.Bucket, - Key: result.Key, - ETag: result.ETag, - }, nil -} - -func (c *Cos) PartSize(ctx context.Context, size int64) (int64, error) { - if size <= 0 { - return 0, errors.New("size must be greater than 0") - } - if size > maxPartSize*maxNumSize { - return 0, fmt.Errorf("COS size must be less than the maximum allowed limit") - } - if size <= minPartSize*maxNumSize { - return minPartSize, nil - } - partSize := size / maxNumSize - if size%maxNumSize != 0 { - partSize++ - } - return partSize, nil -} - -func (c *Cos) AuthSign(ctx context.Context, uploadID string, name string, expire time.Duration, partNumbers []int) (*s3.AuthSignResult, error) { - result := s3.AuthSignResult{ - URL: c.client.BaseURL.BucketURL.String() + "/" + cos.EncodeURIComponent(name), - Query: url.Values{"uploadId": {uploadID}}, - Header: make(http.Header), - Parts: make([]s3.SignPart, len(partNumbers)), - } - req, err := http.NewRequestWithContext(ctx, http.MethodPut, result.URL, nil) - if err != nil { - return nil, err - } - cos.AddAuthorizationHeader(c.credential.SecretID, c.credential.SecretKey, c.credential.SessionToken, req, cos.NewAuthTime(expire)) - result.Header = req.Header - for i, partNumber := range partNumbers { - result.Parts[i] = s3.SignPart{ - PartNumber: partNumber, - Query: url.Values{"partNumber": {strconv.Itoa(partNumber)}}, - } - } - return &result, nil -} - -func (c *Cos) PresignedPutObject(ctx context.Context, name string, expire time.Duration) (string, error) { - rawURL, err := c.client.Object.GetPresignedURL(ctx, http.MethodPut, name, c.credential.SecretID, c.credential.SecretKey, expire, nil) - if err != nil { - return "", err - } - return rawURL.String(), nil -} - -func (c *Cos) DeleteObject(ctx context.Context, name string) error { - _, err := c.client.Object.Delete(ctx, name) - return err -} - -func (c *Cos) StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error) { - if name != "" && name[0] == '/' { - name = name[1:] - } - info, err := c.client.Object.Head(ctx, name, nil) - if err != nil { - return nil, err - } - res := &s3.ObjectInfo{Key: name} - if res.ETag = strings.ToLower(strings.ReplaceAll(info.Header.Get("ETag"), `"`, "")); res.ETag == "" { - return nil, errors.New("StatObject etag not found") - } - if contentLengthStr := info.Header.Get("Content-Length"); contentLengthStr == "" { - return nil, errors.New("StatObject content-length not found") - } else { - res.Size, err = strconv.ParseInt(contentLengthStr, 10, 64) - if err != nil { - return nil, fmt.Errorf("StatObject content-length parse error: %w", err) - } - if res.Size < 0 { - return nil, errors.New("StatObject content-length must be greater than 0") - } - } - if lastModified := info.Header.Get("Last-Modified"); lastModified == "" { - return nil, errors.New("StatObject last-modified not found") - } else { - res.LastModified, err = time.Parse(http.TimeFormat, lastModified) - if err != nil { - return nil, fmt.Errorf("StatObject last-modified parse error: %w", err) - } - } - return res, nil -} - -func (c *Cos) CopyObject(ctx context.Context, src string, dst string) (*s3.CopyObjectInfo, error) { - sourceURL := c.copyURL + src - result, _, err := c.client.Object.Copy(ctx, dst, sourceURL, nil) - if err != nil { - return nil, err - } - return &s3.CopyObjectInfo{ - Key: dst, - ETag: strings.ReplaceAll(result.ETag, `"`, ``), - }, nil -} - -func (c *Cos) IsNotFound(err error) bool { - switch e := errs.Unwrap(err).(type) { - case *cos.ErrorResponse: - return e.Response.StatusCode == http.StatusNotFound || e.Code == "NoSuchKey" - default: - return false - } -} - -func (c *Cos) AbortMultipartUpload(ctx context.Context, uploadID string, name string) error { - _, err := c.client.Object.AbortMultipartUpload(ctx, name, uploadID) - return err -} - -func (c *Cos) ListUploadedParts(ctx context.Context, uploadID string, name string, partNumberMarker int, maxParts int) (*s3.ListUploadedPartsResult, error) { - result, _, err := c.client.Object.ListParts(ctx, name, uploadID, &cos.ObjectListPartsOptions{ - MaxParts: strconv.Itoa(maxParts), - PartNumberMarker: strconv.Itoa(partNumberMarker), - }) - if err != nil { - return nil, err - } - res := &s3.ListUploadedPartsResult{ - Key: result.Key, - UploadID: result.UploadID, - UploadedParts: make([]s3.UploadedPart, len(result.Parts)), - } - res.MaxParts, _ = strconv.Atoi(result.MaxParts) - res.NextPartNumberMarker, _ = strconv.Atoi(result.NextPartNumberMarker) - for i, part := range result.Parts { - lastModified, _ := time.Parse(http.TimeFormat, part.LastModified) - res.UploadedParts[i] = s3.UploadedPart{ - PartNumber: part.PartNumber, - LastModified: lastModified, - ETag: part.ETag, - Size: part.Size, - } - } - return res, nil -} - -func (c *Cos) AccessURL(ctx context.Context, name string, expire time.Duration, opt *s3.AccessURLOption) (string, error) { - var imageMogr string - var option cos.PresignedURLOptions - if opt != nil { - query := make(url.Values) - if opt.Image != nil { - // https://cloud.tencent.com/document/product/436/44880 - style := make([]string, 0, 2) - wh := make([]string, 2) - if opt.Image.Width > 0 { - wh[0] = strconv.Itoa(opt.Image.Width) - } - if opt.Image.Height > 0 { - wh[1] = strconv.Itoa(opt.Image.Height) - } - if opt.Image.Width > 0 || opt.Image.Height > 0 { - style = append(style, strings.Join(wh, "x")) - } - switch opt.Image.Format { - case - imagePng, - imageJpg, - imageJpeg, - imageGif, - imageWebp: - style = append(style, "format/"+opt.Image.Format) - } - if len(style) > 0 { - imageMogr = "imageMogr2/thumbnail/" + strings.Join(style, "/") + "/ignore-error/1" - } - } - if opt.ContentType != "" { - query.Set("response-content-type", opt.ContentType) - } - if opt.Filename != "" { - query.Set("response-content-disposition", `attachment; filename=`+strconv.Quote(opt.Filename)) - } - if len(query) > 0 { - option.Query = &query - } - } - if expire <= 0 { - expire = time.Hour * 24 * 365 * 99 // 99 years - } else if expire < time.Second { - expire = time.Second - } - rawURL, err := c.getPresignedURL(ctx, name, expire, &option) - if err != nil { - return "", err - } - if imageMogr != "" { - if rawURL.RawQuery == "" { - rawURL.RawQuery = imageMogr - } else { - rawURL.RawQuery = rawURL.RawQuery + "&" + imageMogr - } - } - return rawURL.String(), nil -} - -func (c *Cos) getPresignedURL(ctx context.Context, name string, expire time.Duration, opt *cos.PresignedURLOptions) (*url.URL, error) { - if !c.publicRead { - return c.client.Object.GetPresignedURL(ctx, http.MethodGet, name, c.credential.SecretID, c.credential.SecretKey, expire, opt) - } - return c.client.Object.GetObjectURL(name), nil -} - -func (c *Cos) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) { - // https://cloud.tencent.com/document/product/436/14690 - now := time.Now() - expiration := now.Add(duration) - keyTime := fmt.Sprintf("%d;%d", now.Unix(), expiration.Unix()) - conditions := []any{ - map[string]string{"q-sign-algorithm": "sha1"}, - map[string]string{"q-ak": c.credential.SecretID}, - map[string]string{"q-sign-time": keyTime}, - map[string]string{"key": name}, - } - if contentType != "" { - conditions = append(conditions, map[string]string{"Content-Type": contentType}) - } - policy := map[string]any{ - "expiration": expiration.Format("2006-01-02T15:04:05.000Z"), - "conditions": conditions, - } - policyJson, err := json.Marshal(policy) - if err != nil { - return nil, err - } - signKey := hmacSha1val(c.credential.SecretKey, keyTime) - strToSign := sha1val(string(policyJson)) - signature := hmacSha1val(signKey, strToSign) - - fd := &s3.FormData{ - URL: c.client.BaseURL.BucketURL.String(), - File: "file", - Expires: expiration, - FormData: map[string]string{ - "policy": base64.StdEncoding.EncodeToString(policyJson), - "q-sign-algorithm": "sha1", - "q-ak": c.credential.SecretID, - "q-key-time": keyTime, - "q-signature": signature, - "key": name, - "success_action_status": strconv.Itoa(successCode), - }, - SuccessCodes: []int{successCode}, - } - if contentType != "" { - fd.FormData["Content-Type"] = contentType - } - if c.credential.SessionToken != "" { - fd.FormData["x-cos-security-token"] = c.credential.SessionToken - } - return fd, nil -} - -func hmacSha1val(key, msg string) string { - v := hmac.New(sha1.New, []byte(key)) - v.Write([]byte(msg)) - return hex.EncodeToString(v.Sum(nil)) -} - -func sha1val(msg string) string { - sha1Hash := sha1.New() - sha1Hash.Write([]byte(msg)) - return hex.EncodeToString(sha1Hash.Sum(nil)) -} diff --git a/pkg/common/db/s3/cos/internal.go b/pkg/common/db/s3/cos/internal.go deleted file mode 100644 index 064546953..000000000 --- a/pkg/common/db/s3/cos/internal.go +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package cos - -import ( - "context" - "net/http" - "net/url" - _ "unsafe" - - "github.com/tencentyun/cos-go-sdk-v5" -) - -//go:linkname newRequest github.com/tencentyun/cos-go-sdk-v5.(*Client).newRequest -func newRequest(c *cos.Client, ctx context.Context, baseURL *url.URL, uri, method string, body any, optQuery any, optHeader any) (req *http.Request, err error) diff --git a/pkg/common/db/s3/minio/image.go b/pkg/common/db/s3/minio/image.go deleted file mode 100644 index 3223993f4..000000000 --- a/pkg/common/db/s3/minio/image.go +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package minio - -import ( - "image" - _ "image/gif" - _ "image/jpeg" - _ "image/png" - "io" - - _ "golang.org/x/image/bmp" - _ "golang.org/x/image/tiff" - _ "golang.org/x/image/webp" -) - -const ( - formatPng = "png" - formatJpeg = "jpeg" - formatJpg = "jpg" - formatGif = "gif" -) - -func ImageStat(reader io.Reader) (image.Image, string, error) { - return image.Decode(reader) -} - -func ImageWidthHeight(img image.Image) (int, int) { - bounds := img.Bounds().Max - return bounds.X, bounds.Y -} - -func resizeImage(img image.Image, maxWidth, maxHeight int) image.Image { - bounds := img.Bounds() - imgWidth := bounds.Max.X - imgHeight := bounds.Max.Y - - // Calculating scaling - scaleWidth := float64(maxWidth) / float64(imgWidth) - scaleHeight := float64(maxHeight) / float64(imgHeight) - - // If both are 0, then no scaling is done and the original image is returned - if maxWidth == 0 && maxHeight == 0 { - return img - } - - // If both width and height are greater than 0, select a smaller zoom ratio to maintain the aspect ratio - if maxWidth > 0 && maxHeight > 0 { - scale := scaleWidth - if scaleHeight < scaleWidth { - scale = scaleHeight - } - - // Calculate Thumbnail Size - thumbnailWidth := int(float64(imgWidth) * scale) - thumbnailHeight := int(float64(imgHeight) * scale) - - // Thumbnails are generated using the Resample method of the "image" library. - thumbnail := image.NewRGBA(image.Rect(0, 0, thumbnailWidth, thumbnailHeight)) - for y := 0; y < thumbnailHeight; y++ { - for x := 0; x < thumbnailWidth; x++ { - srcX := int(float64(x) / scale) - srcY := int(float64(y) / scale) - thumbnail.Set(x, y, img.At(srcX, srcY)) - } - } - - return thumbnail - } - - // If only width or height is specified, thumbnails are generated based on the maximum not to exceed rule - if maxWidth > 0 { - thumbnailWidth := maxWidth - thumbnailHeight := int(float64(imgHeight) * scaleWidth) - - // Thumbnails are generated using the Resample method of the "image" library. - thumbnail := image.NewRGBA(image.Rect(0, 0, thumbnailWidth, thumbnailHeight)) - for y := 0; y < thumbnailHeight; y++ { - for x := 0; x < thumbnailWidth; x++ { - srcX := int(float64(x) / scaleWidth) - srcY := int(float64(y) / scaleWidth) - thumbnail.Set(x, y, img.At(srcX, srcY)) - } - } - - return thumbnail - } - - if maxHeight > 0 { - thumbnailWidth := int(float64(imgWidth) * scaleHeight) - thumbnailHeight := maxHeight - - // Thumbnails are generated using the Resample method of the "image" library. - thumbnail := image.NewRGBA(image.Rect(0, 0, thumbnailWidth, thumbnailHeight)) - for y := 0; y < thumbnailHeight; y++ { - for x := 0; x < thumbnailWidth; x++ { - srcX := int(float64(x) / scaleHeight) - srcY := int(float64(y) / scaleHeight) - thumbnail.Set(x, y, img.At(srcX, srcY)) - } - } - - return thumbnail - } - - // By default, the original image is returned - return img -} diff --git a/pkg/common/db/s3/minio/internal.go b/pkg/common/db/s3/minio/internal.go deleted file mode 100644 index 7e9dcd9e4..000000000 --- a/pkg/common/db/s3/minio/internal.go +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package minio - -import ( - "net/url" - _ "unsafe" - - "github.com/minio/minio-go/v7" -) - -//go:linkname makeTargetURL github.com/minio/minio-go/v7.(*Client).makeTargetURL -func makeTargetURL(client *minio.Client, bucketName, objectName, bucketLocation string, isVirtualHostStyle bool, queryValues url.Values) (*url.URL, error) diff --git a/pkg/common/db/s3/minio/minio.go b/pkg/common/db/s3/minio/minio.go deleted file mode 100644 index 10526998f..000000000 --- a/pkg/common/db/s3/minio/minio.go +++ /dev/null @@ -1,499 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package minio - -import ( - "context" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "path" - "reflect" - "strconv" - "strings" - "sync" - "time" - "unsafe" - - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/minio/minio-go/v7" - "github.com/minio/minio-go/v7/pkg/credentials" - "github.com/minio/minio-go/v7/pkg/signer" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" -) - -const ( - unsignedPayload = "UNSIGNED-PAYLOAD" -) - -const ( - minPartSize int64 = 1024 * 1024 * 5 // 5MB - maxPartSize int64 = 1024 * 1024 * 1024 * 5 // 5GB - maxNumSize int64 = 10000 -) - -const ( - maxImageWidth = 1024 - maxImageHeight = 1024 - maxImageSize = 1024 * 1024 * 50 - imageThumbnailPath = "openim/thumbnail" -) - -const successCode = http.StatusOK - -type Config struct { - Bucket string - Endpoint string - AccessKeyID string - SecretAccessKey string - SessionToken string - SignEndpoint string - PublicRead bool -} - -func NewMinio(cache cache.MinioCache, conf Config) (s3.Interface, error) { - u, err := url.Parse(conf.Endpoint) - if err != nil { - return nil, err - } - opts := &minio.Options{ - Creds: credentials.NewStaticV4(conf.AccessKeyID, conf.SecretAccessKey, conf.SessionToken), - Secure: u.Scheme == "https", - } - client, err := minio.New(u.Host, opts) - if err != nil { - return nil, err - } - m := &Minio{ - conf: conf, - bucket: conf.Bucket, - core: &minio.Core{Client: client}, - lock: &sync.Mutex{}, - init: false, - cache: cache, - } - if conf.SignEndpoint == "" || conf.SignEndpoint == conf.Endpoint { - m.opts = opts - m.sign = m.core.Client - m.prefix = u.Path - u.Path = "" - conf.Endpoint = u.String() - m.signEndpoint = conf.Endpoint - } else { - su, err := url.Parse(conf.SignEndpoint) - if err != nil { - return nil, err - } - m.opts = &minio.Options{ - Creds: credentials.NewStaticV4(conf.AccessKeyID, conf.SecretAccessKey, conf.SessionToken), - Secure: su.Scheme == "https", - } - m.sign, err = minio.New(su.Host, m.opts) - if err != nil { - return nil, err - } - m.prefix = su.Path - su.Path = "" - conf.SignEndpoint = su.String() - m.signEndpoint = conf.SignEndpoint - } - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - if err := m.initMinio(ctx); err != nil { - fmt.Println("init minio error:", err) - } - return m, nil -} - -type Minio struct { - conf Config - bucket string - signEndpoint string - location string - opts *minio.Options - core *minio.Core - sign *minio.Client - lock sync.Locker - init bool - prefix string - cache cache.MinioCache -} - -func (m *Minio) initMinio(ctx context.Context) error { - if m.init { - return nil - } - m.lock.Lock() - defer m.lock.Unlock() - if m.init { - return nil - } - exists, err := m.core.Client.BucketExists(ctx, m.conf.Bucket) - if err != nil { - return fmt.Errorf("check bucket exists error: %w", err) - } - if !exists { - if err = m.core.Client.MakeBucket(ctx, m.conf.Bucket, minio.MakeBucketOptions{}); err != nil { - return fmt.Errorf("make bucket error: %w", err) - } - } - if m.conf.PublicRead { - policy := fmt.Sprintf( - `{"Version": "2012-10-17","Statement": [{"Action": ["s3:GetObject","s3:PutObject"],"Effect": "Allow","Principal": {"AWS": ["*"]},"Resource": ["arn:aws:s3:::%s/*"],"Sid": ""}]}`, - m.conf.Bucket, - ) - if err = m.core.Client.SetBucketPolicy(ctx, m.conf.Bucket, policy); err != nil { - return err - } - } - m.location, err = m.core.Client.GetBucketLocation(ctx, m.conf.Bucket) - if err != nil { - return err - } - func() { - if m.conf.SignEndpoint == "" || m.conf.SignEndpoint == m.conf.Endpoint { - return - } - defer func() { - if r := recover(); r != nil { - m.sign = m.core.Client - log.ZWarn( - context.Background(), - "set sign bucket location cache panic", - errors.New("failed to get private field value"), - "recover", - fmt.Sprintf("%+v", r), - "development version", - "github.com/minio/minio-go/v7 v7.0.61", - ) - } - }() - blc := reflect.ValueOf(m.sign).Elem().FieldByName("bucketLocCache") - vblc := reflect.New(reflect.PtrTo(blc.Type())) - *(*unsafe.Pointer)(vblc.UnsafePointer()) = unsafe.Pointer(blc.UnsafeAddr()) - vblc.Elem().Elem().Interface().(interface{ Set(string, string) }).Set(m.conf.Bucket, m.location) - }() - m.init = true - return nil -} - -func (m *Minio) Engine() string { - return "minio" -} - -func (m *Minio) PartLimit() *s3.PartLimit { - return &s3.PartLimit{ - MinPartSize: minPartSize, - MaxPartSize: maxPartSize, - MaxNumSize: maxNumSize, - } -} - -func (m *Minio) InitiateMultipartUpload(ctx context.Context, name string) (*s3.InitiateMultipartUploadResult, error) { - if err := m.initMinio(ctx); err != nil { - return nil, err - } - uploadID, err := m.core.NewMultipartUpload(ctx, m.bucket, name, minio.PutObjectOptions{}) - if err != nil { - return nil, err - } - return &s3.InitiateMultipartUploadResult{ - Bucket: m.bucket, - Key: name, - UploadID: uploadID, - }, nil -} - -func (m *Minio) CompleteMultipartUpload(ctx context.Context, uploadID string, name string, parts []s3.Part) (*s3.CompleteMultipartUploadResult, error) { - if err := m.initMinio(ctx); err != nil { - return nil, err - } - minioParts := make([]minio.CompletePart, len(parts)) - for i, part := range parts { - minioParts[i] = minio.CompletePart{ - PartNumber: part.PartNumber, - ETag: strings.ToLower(part.ETag), - } - } - upload, err := m.core.CompleteMultipartUpload(ctx, m.bucket, name, uploadID, minioParts, minio.PutObjectOptions{}) - if err != nil { - return nil, err - } - m.delObjectImageInfoKey(ctx, name, upload.Size) - return &s3.CompleteMultipartUploadResult{ - Location: upload.Location, - Bucket: upload.Bucket, - Key: upload.Key, - ETag: strings.ToLower(upload.ETag), - }, nil -} - -func (m *Minio) PartSize(ctx context.Context, size int64) (int64, error) { - if size <= 0 { - return 0, errors.New("size must be greater than 0") - } - if size > maxPartSize*maxNumSize { - return 0, fmt.Errorf("MINIO size must be less than the maximum allowed limit") - } - if size <= minPartSize*maxNumSize { - return minPartSize, nil - } - partSize := size / maxNumSize - if size%maxNumSize != 0 { - partSize++ - } - return partSize, nil -} - -func (m *Minio) AuthSign(ctx context.Context, uploadID string, name string, expire time.Duration, partNumbers []int) (*s3.AuthSignResult, error) { - if err := m.initMinio(ctx); err != nil { - return nil, err - } - creds, err := m.opts.Creds.Get() - if err != nil { - return nil, err - } - result := s3.AuthSignResult{ - URL: m.signEndpoint + "/" + m.bucket + "/" + name, - Query: url.Values{"uploadId": {uploadID}}, - Parts: make([]s3.SignPart, len(partNumbers)), - } - for i, partNumber := range partNumbers { - rawURL := result.URL + "?partNumber=" + strconv.Itoa(partNumber) + "&uploadId=" + uploadID - request, err := http.NewRequestWithContext(ctx, http.MethodPut, rawURL, nil) - if err != nil { - return nil, err - } - request.Header.Set("X-Amz-Content-Sha256", unsignedPayload) - request = signer.SignV4Trailer(*request, creds.AccessKeyID, creds.SecretAccessKey, creds.SessionToken, m.location, nil) - result.Parts[i] = s3.SignPart{ - PartNumber: partNumber, - Query: url.Values{"partNumber": {strconv.Itoa(partNumber)}}, - Header: request.Header, - } - } - if m.prefix != "" { - result.URL = m.signEndpoint + m.prefix + "/" + m.bucket + "/" + name - } - return &result, nil -} - -func (m *Minio) PresignedPutObject(ctx context.Context, name string, expire time.Duration) (string, error) { - if err := m.initMinio(ctx); err != nil { - return "", err - } - rawURL, err := m.sign.PresignedPutObject(ctx, m.bucket, name, expire) - if err != nil { - return "", err - } - if m.prefix != "" { - rawURL.Path = path.Join(m.prefix, rawURL.Path) - } - return rawURL.String(), nil -} - -func (m *Minio) DeleteObject(ctx context.Context, name string) error { - if err := m.initMinio(ctx); err != nil { - return err - } - return m.core.Client.RemoveObject(ctx, m.bucket, name, minio.RemoveObjectOptions{}) -} - -func (m *Minio) StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error) { - if err := m.initMinio(ctx); err != nil { - return nil, err - } - info, err := m.core.Client.StatObject(ctx, m.bucket, name, minio.StatObjectOptions{}) - if err != nil { - return nil, err - } - return &s3.ObjectInfo{ - ETag: strings.ToLower(info.ETag), - Key: info.Key, - Size: info.Size, - LastModified: info.LastModified, - }, nil -} - -func (m *Minio) CopyObject(ctx context.Context, src string, dst string) (*s3.CopyObjectInfo, error) { - if err := m.initMinio(ctx); err != nil { - return nil, err - } - result, err := m.core.Client.CopyObject(ctx, minio.CopyDestOptions{ - Bucket: m.bucket, - Object: dst, - }, minio.CopySrcOptions{ - Bucket: m.bucket, - Object: src, - }) - if err != nil { - return nil, err - } - return &s3.CopyObjectInfo{ - Key: dst, - ETag: strings.ToLower(result.ETag), - }, nil -} - -func (m *Minio) IsNotFound(err error) bool { - switch e := errs.Unwrap(err).(type) { - case minio.ErrorResponse: - return e.StatusCode == http.StatusNotFound || e.Code == "NoSuchKey" - case *minio.ErrorResponse: - return e.StatusCode == http.StatusNotFound || e.Code == "NoSuchKey" - default: - return false - } -} - -func (m *Minio) AbortMultipartUpload(ctx context.Context, uploadID string, name string) error { - if err := m.initMinio(ctx); err != nil { - return err - } - return m.core.AbortMultipartUpload(ctx, m.bucket, name, uploadID) -} - -func (m *Minio) ListUploadedParts(ctx context.Context, uploadID string, name string, partNumberMarker int, maxParts int) (*s3.ListUploadedPartsResult, error) { - if err := m.initMinio(ctx); err != nil { - return nil, err - } - result, err := m.core.ListObjectParts(ctx, m.bucket, name, uploadID, partNumberMarker, maxParts) - if err != nil { - return nil, err - } - res := &s3.ListUploadedPartsResult{ - Key: result.Key, - UploadID: result.UploadID, - MaxParts: result.MaxParts, - NextPartNumberMarker: result.NextPartNumberMarker, - UploadedParts: make([]s3.UploadedPart, len(result.ObjectParts)), - } - for i, part := range result.ObjectParts { - res.UploadedParts[i] = s3.UploadedPart{ - PartNumber: part.PartNumber, - LastModified: part.LastModified, - ETag: part.ETag, - Size: part.Size, - } - } - return res, nil -} - -func (m *Minio) PresignedGetObject(ctx context.Context, name string, expire time.Duration, query url.Values) (string, error) { - if expire <= 0 { - expire = time.Hour * 24 * 365 * 99 // 99 years - } else if expire < time.Second { - expire = time.Second - } - var ( - rawURL *url.URL - err error - ) - if m.conf.PublicRead { - rawURL, err = makeTargetURL(m.sign, m.bucket, name, m.location, false, query) - } else { - rawURL, err = m.sign.PresignedGetObject(ctx, m.bucket, name, expire, query) - } - if err != nil { - return "", err - } - if m.prefix != "" { - rawURL.Path = path.Join(m.prefix, rawURL.Path) - } - return rawURL.String(), nil -} - -func (m *Minio) AccessURL(ctx context.Context, name string, expire time.Duration, opt *s3.AccessURLOption) (string, error) { - if err := m.initMinio(ctx); err != nil { - return "", err - } - reqParams := make(url.Values) - if opt != nil { - if opt.ContentType != "" { - reqParams.Set("response-content-type", opt.ContentType) - } - if opt.Filename != "" { - reqParams.Set("response-content-disposition", `attachment; filename=`+strconv.Quote(opt.Filename)) - } - } - if opt.Image == nil || (opt.Image.Width < 0 && opt.Image.Height < 0 && opt.Image.Format == "") || (opt.Image.Width > maxImageWidth || opt.Image.Height > maxImageHeight) { - return m.PresignedGetObject(ctx, name, expire, reqParams) - } - return m.getImageThumbnailURL(ctx, name, expire, opt.Image) -} - -func (m *Minio) getObjectData(ctx context.Context, name string, limit int64) ([]byte, error) { - object, err := m.core.Client.GetObject(ctx, m.bucket, name, minio.GetObjectOptions{}) - if err != nil { - return nil, err - } - defer object.Close() - if limit < 0 { - return io.ReadAll(object) - } - return io.ReadAll(io.LimitReader(object, limit)) -} - -func (m *Minio) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) { - if err := m.initMinio(ctx); err != nil { - return nil, err - } - policy := minio.NewPostPolicy() - if err := policy.SetKey(name); err != nil { - return nil, err - } - expires := time.Now().Add(duration) - if err := policy.SetExpires(expires); err != nil { - return nil, err - } - if size > 0 { - if err := policy.SetContentLengthRange(0, size); err != nil { - return nil, err - } - } - if err := policy.SetSuccessStatusAction(strconv.Itoa(successCode)); err != nil { - return nil, err - } - if contentType != "" { - if err := policy.SetContentType(contentType); err != nil { - return nil, err - } - } - if err := policy.SetBucket(m.bucket); err != nil { - return nil, err - } - u, fd, err := m.core.PresignedPostPolicy(ctx, policy) - if err != nil { - return nil, err - } - sign, err := url.Parse(m.signEndpoint) - if err != nil { - return nil, err - } - u.Scheme = sign.Scheme - u.Host = sign.Host - return &s3.FormData{ - URL: u.String(), - File: "file", - Header: nil, - FormData: fd, - Expires: expires, - SuccessCodes: []int{successCode}, - }, nil -} diff --git a/pkg/common/db/s3/minio/thumbnail.go b/pkg/common/db/s3/minio/thumbnail.go deleted file mode 100644 index 1bf96c27c..000000000 --- a/pkg/common/db/s3/minio/thumbnail.go +++ /dev/null @@ -1,150 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package minio - -import ( - "bytes" - "context" - "errors" - "fmt" - "image" - "image/gif" - "image/jpeg" - "image/png" - "net/url" - "path/filepath" - "strings" - "time" - - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/minio/minio-go/v7" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/cache" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" -) - -func (m *Minio) getImageThumbnailURL(ctx context.Context, name string, expire time.Duration, opt *s3.Image) (string, error) { - var img image.Image - info, err := m.cache.GetImageObjectKeyInfo(ctx, name, func(ctx context.Context) (info *cache.MinioImageInfo, err error) { - info, img, err = m.getObjectImageInfo(ctx, name) - return - }) - if err != nil { - return "", err - } - if !info.IsImg { - return "", errs.ErrData.Wrap("object not image") - } - if opt.Width > info.Width || opt.Width <= 0 { - opt.Width = info.Width - } - if opt.Height > info.Height || opt.Height <= 0 { - opt.Height = info.Height - } - opt.Format = strings.ToLower(opt.Format) - if opt.Format == formatJpg { - opt.Format = formatJpeg - } - switch opt.Format { - case formatPng, formatJpeg, formatGif: - default: - opt.Format = "" - } - reqParams := make(url.Values) - if opt.Width == info.Width && opt.Height == info.Height && (opt.Format == info.Format || opt.Format == "") { - reqParams.Set("response-content-type", "image/"+info.Format) - return m.PresignedGetObject(ctx, name, expire, reqParams) - } - if opt.Format == "" { - switch opt.Format { - case formatGif: - opt.Format = formatGif - case formatJpeg: - opt.Format = formatJpeg - case formatPng: - opt.Format = formatPng - default: - opt.Format = formatPng - } - } - key, err := m.cache.GetThumbnailKey(ctx, name, opt.Format, opt.Width, opt.Height, func(ctx context.Context) (string, error) { - if img == nil { - var reader *minio.Object - reader, err = m.core.Client.GetObject(ctx, m.bucket, name, minio.GetObjectOptions{}) - if err != nil { - return "", err - } - defer reader.Close() - img, _, err = ImageStat(reader) - if err != nil { - return "", err - } - } - thumbnail := resizeImage(img, opt.Width, opt.Height) - buf := bytes.NewBuffer(nil) - switch opt.Format { - case formatPng: - err = png.Encode(buf, thumbnail) - case formatJpeg: - err = jpeg.Encode(buf, thumbnail, nil) - case formatGif: - err = gif.Encode(buf, thumbnail, nil) - } - cacheKey := filepath.Join(imageThumbnailPath, info.Etag, fmt.Sprintf("image_w%d_h%d.%s", opt.Width, opt.Height, opt.Format)) - if _, err = m.core.Client.PutObject(ctx, m.bucket, cacheKey, buf, int64(buf.Len()), minio.PutObjectOptions{}); err != nil { - return "", err - } - return cacheKey, nil - }) - if err != nil { - return "", err - } - reqParams.Set("response-content-type", "image/"+opt.Format) - return m.PresignedGetObject(ctx, key, expire, reqParams) -} - -func (m *Minio) getObjectImageInfo(ctx context.Context, name string) (*cache.MinioImageInfo, image.Image, error) { - fileInfo, err := m.StatObject(ctx, name) - if err != nil { - return nil, nil, err - } - if fileInfo.Size > maxImageSize { - return nil, nil, errors.New("file size too large") - } - imageData, err := m.getObjectData(ctx, name, fileInfo.Size) - if err != nil { - return nil, nil, err - } - var info cache.MinioImageInfo - imageInfo, format, err := ImageStat(bytes.NewReader(imageData)) - if err == nil { - info.IsImg = true - info.Format = format - info.Width, info.Height = ImageWidthHeight(imageInfo) - } else { - info.IsImg = false - } - info.Etag = fileInfo.ETag - return &info, imageInfo, nil -} - -func (m *Minio) delObjectImageInfoKey(ctx context.Context, key string, size int64) { - if size > 0 && size > maxImageSize { - return - } - if err := m.cache.DelObjectImageInfoKey(key).ExecDel(ctx); err != nil { - log.ZError(ctx, "DelObjectImageInfoKey failed", err, "key", key) - } -} diff --git a/pkg/common/db/s3/oss/internal.go b/pkg/common/db/s3/oss/internal.go deleted file mode 100644 index 155708ffd..000000000 --- a/pkg/common/db/s3/oss/internal.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package oss - -import ( - "net/http" - "net/url" - _ "unsafe" - - "github.com/aliyun/aliyun-oss-go-sdk/oss" -) - -//go:linkname signHeader github.com/aliyun/aliyun-oss-go-sdk/oss.Conn.signHeader -func signHeader(c oss.Conn, req *http.Request, canonicalizedResource string) - -//go:linkname getURLParams github.com/aliyun/aliyun-oss-go-sdk/oss.Conn.getURLParams -func getURLParams(c oss.Conn, params map[string]any) string - -//go:linkname getURL github.com/aliyun/aliyun-oss-go-sdk/oss.urlMaker.getURL -func getURL(um urlMaker, bucket, object, params string) *url.URL - -type urlMaker struct { - Scheme string - NetLoc string - Type int - IsProxy bool -} diff --git a/pkg/common/db/s3/oss/oss.go b/pkg/common/db/s3/oss/oss.go deleted file mode 100644 index e485db277..000000000 --- a/pkg/common/db/s3/oss/oss.go +++ /dev/null @@ -1,382 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package oss - -import ( - "context" - "crypto/hmac" - "crypto/sha1" - "encoding/base64" - "encoding/json" - "errors" - "fmt" - "io" - "net/http" - "net/url" - "reflect" - "strconv" - "strings" - "time" - - "github.com/OpenIMSDK/tools/errs" - "github.com/aliyun/aliyun-oss-go-sdk/oss" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3" -) - -const ( - minPartSize int64 = 1024 * 1024 * 1 // 1MB - maxPartSize int64 = 1024 * 1024 * 1024 * 5 // 5GB - maxNumSize int64 = 10000 -) - -const ( - imagePng = "png" - imageJpg = "jpg" - imageJpeg = "jpeg" - imageGif = "gif" - imageWebp = "webp" -) - -const successCode = http.StatusOK - -type Config struct { - Endpoint string - Bucket string - BucketURL string - AccessKeyID string - AccessKeySecret string - SessionToken string - PublicRead bool -} - -func NewOSS(conf Config) (s3.Interface, error) { - if conf.BucketURL == "" { - return nil, errs.Wrap(errors.New("bucket url is empty")) - } - client, err := oss.New(conf.Endpoint, conf.AccessKeyID, conf.AccessKeySecret) - if err != nil { - return nil, err - } - bucket, err := client.Bucket(conf.Bucket) - if err != nil { - return nil, errs.Wrap(err, "ali-oss bucket error") - } - if conf.BucketURL[len(conf.BucketURL)-1] != '/' { - conf.BucketURL += "/" - } - return &OSS{ - bucketURL: conf.BucketURL, - bucket: bucket, - credentials: client.Config.GetCredentials(), - um: *(*urlMaker)(reflect.ValueOf(bucket.Client.Conn).Elem().FieldByName("url").UnsafePointer()), - publicRead: conf.PublicRead, - }, nil -} - -type OSS struct { - bucketURL string - bucket *oss.Bucket - credentials oss.Credentials - um urlMaker - publicRead bool -} - -func (o *OSS) Engine() string { - return "ali-oss" -} - -func (o *OSS) PartLimit() *s3.PartLimit { - return &s3.PartLimit{ - MinPartSize: minPartSize, - MaxPartSize: maxPartSize, - MaxNumSize: maxNumSize, - } -} - -func (o *OSS) InitiateMultipartUpload(ctx context.Context, name string) (*s3.InitiateMultipartUploadResult, error) { - result, err := o.bucket.InitiateMultipartUpload(name) - if err != nil { - return nil, err - } - return &s3.InitiateMultipartUploadResult{ - UploadID: result.UploadID, - Bucket: result.Bucket, - Key: result.Key, - }, nil -} - -func (o *OSS) CompleteMultipartUpload(ctx context.Context, uploadID string, name string, parts []s3.Part) (*s3.CompleteMultipartUploadResult, error) { - ossParts := make([]oss.UploadPart, len(parts)) - for i, part := range parts { - ossParts[i] = oss.UploadPart{ - PartNumber: part.PartNumber, - ETag: strings.ToUpper(part.ETag), - } - } - result, err := o.bucket.CompleteMultipartUpload(oss.InitiateMultipartUploadResult{ - UploadID: uploadID, - Bucket: o.bucket.BucketName, - Key: name, - }, ossParts) - if err != nil { - return nil, err - } - return &s3.CompleteMultipartUploadResult{ - Location: result.Location, - Bucket: result.Bucket, - Key: result.Key, - ETag: strings.ToLower(strings.ReplaceAll(result.ETag, `"`, ``)), - }, nil -} - -func (o *OSS) PartSize(ctx context.Context, size int64) (int64, error) { - if size <= 0 { - return 0, errs.Wrap(errors.New("size must be greater than 0")) - } - if size > maxPartSize*maxNumSize { - return 0, errs.Wrap(errors.New("size must be less than the maximum allowed limit")) - } - if size <= minPartSize*maxNumSize { - return minPartSize, nil - } - partSize := size / maxNumSize - if size%maxNumSize != 0 { - partSize++ - } - return partSize, nil -} - -func (o *OSS) AuthSign(ctx context.Context, uploadID string, name string, expire time.Duration, partNumbers []int) (*s3.AuthSignResult, error) { - result := s3.AuthSignResult{ - URL: o.bucketURL + name, - Query: url.Values{"uploadId": {uploadID}}, - Header: make(http.Header), - Parts: make([]s3.SignPart, len(partNumbers)), - } - for i, partNumber := range partNumbers { - rawURL := fmt.Sprintf(`%s%s?partNumber=%d&uploadId=%s`, o.bucketURL, name, partNumber, uploadID) - request, err := http.NewRequest(http.MethodPut, rawURL, nil) - if err != nil { - return nil, err - } - if o.credentials.GetSecurityToken() != "" { - request.Header.Set(oss.HTTPHeaderOssSecurityToken, o.credentials.GetSecurityToken()) - } - now := time.Now().UTC().Format(http.TimeFormat) - request.Header.Set(oss.HTTPHeaderHost, request.Host) - request.Header.Set(oss.HTTPHeaderDate, now) - request.Header.Set(oss.HttpHeaderOssDate, now) - signHeader(*o.bucket.Client.Conn, request, fmt.Sprintf(`/%s/%s?partNumber=%d&uploadId=%s`, o.bucket.BucketName, name, partNumber, uploadID)) - delete(request.Header, oss.HTTPHeaderDate) - result.Parts[i] = s3.SignPart{ - PartNumber: partNumber, - Query: url.Values{"partNumber": {strconv.Itoa(partNumber)}}, - URL: request.URL.String(), - Header: request.Header, - } - } - return &result, nil -} - -func (o *OSS) PresignedPutObject(ctx context.Context, name string, expire time.Duration) (string, error) { - return o.bucket.SignURL(name, http.MethodPut, int64(expire/time.Second)) -} - -func (o *OSS) StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error) { - header, err := o.bucket.GetObjectMeta(name) - if err != nil { - return nil, err - } - res := &s3.ObjectInfo{Key: name} - if res.ETag = strings.ToLower(strings.ReplaceAll(header.Get("ETag"), `"`, ``)); res.ETag == "" { - return nil, errs.Wrap(errors.New("StatObject etag not found")) - } - if contentLengthStr := header.Get("Content-Length"); contentLengthStr == "" { - return nil, errors.New("StatObject content-length not found") - } else { - res.Size, err = strconv.ParseInt(contentLengthStr, 10, 64) - if err != nil { - return nil, errs.Wrap(err, "StatObject content-length parse error") - } - if res.Size < 0 { - return nil, errs.Wrap(errors.New("StatObject content-length must be greater than 0")) - } - } - if lastModified := header.Get("Last-Modified"); lastModified == "" { - return nil, errs.Wrap(errors.New("StatObject last-modified not found")) - } else { - res.LastModified, err = time.Parse(http.TimeFormat, lastModified) - if err != nil { - return nil, errs.Wrap(err, "StatObject last-modified parse error") - } - } - return res, nil -} - -func (o *OSS) DeleteObject(ctx context.Context, name string) error { - return o.bucket.DeleteObject(name) -} - -func (o *OSS) CopyObject(ctx context.Context, src string, dst string) (*s3.CopyObjectInfo, error) { - result, err := o.bucket.CopyObject(src, dst) - if err != nil { - return nil, errs.Wrap(err, "CopyObject error") - } - return &s3.CopyObjectInfo{ - Key: dst, - ETag: strings.ToLower(strings.ReplaceAll(result.ETag, `"`, ``)), - }, nil -} - -func (o *OSS) IsNotFound(err error) bool { - switch e := errs.Unwrap(err).(type) { - case oss.ServiceError: - return e.StatusCode == http.StatusNotFound || e.Code == "NoSuchKey" - case *oss.ServiceError: - return e.StatusCode == http.StatusNotFound || e.Code == "NoSuchKey" - default: - return false - } -} - -func (o *OSS) AbortMultipartUpload(ctx context.Context, uploadID string, name string) error { - return o.bucket.AbortMultipartUpload(oss.InitiateMultipartUploadResult{ - UploadID: uploadID, - Key: name, - Bucket: o.bucket.BucketName, - }) -} - -func (o *OSS) ListUploadedParts(ctx context.Context, uploadID string, name string, partNumberMarker int, maxParts int) (*s3.ListUploadedPartsResult, error) { - result, err := o.bucket.ListUploadedParts(oss.InitiateMultipartUploadResult{ - UploadID: uploadID, - Key: name, - Bucket: o.bucket.BucketName, - }, oss.MaxUploads(100), oss.MaxParts(maxParts), oss.PartNumberMarker(partNumberMarker)) - if err != nil { - return nil, errs.Wrap(err, "ListUploadedParts error") - } - res := &s3.ListUploadedPartsResult{ - Key: result.Key, - UploadID: result.UploadID, - MaxParts: result.MaxParts, - UploadedParts: make([]s3.UploadedPart, len(result.UploadedParts)), - } - res.NextPartNumberMarker, _ = strconv.Atoi(result.NextPartNumberMarker) - for i, part := range result.UploadedParts { - res.UploadedParts[i] = s3.UploadedPart{ - PartNumber: part.PartNumber, - LastModified: part.LastModified, - ETag: part.ETag, - Size: int64(part.Size), - } - } - return res, nil -} - -func (o *OSS) AccessURL(ctx context.Context, name string, expire time.Duration, opt *s3.AccessURLOption) (string, error) { - var opts []oss.Option - if opt != nil { - if opt.Image != nil { - // Docs Address: https://help.aliyun.com/zh/oss/user-guide/resize-images-4?spm=a2c4g.11186623.0.0.4b3b1e4fWW6yji - var format string - switch opt.Image.Format { - case - imagePng, - imageJpg, - imageJpeg, - imageGif, - imageWebp: - format = opt.Image.Format - default: - opt.Image.Format = imageJpg - } - // https://oss-console-img-demo-cn-hangzhou.oss-cn-hangzhou.aliyuncs.com/example.jpg?x-oss-process=image/resize,h_100,m_lfit - process := "image/resize,m_lfit" - if opt.Image.Width > 0 { - process += ",w_" + strconv.Itoa(opt.Image.Width) - } - if opt.Image.Height > 0 { - process += ",h_" + strconv.Itoa(opt.Image.Height) - } - process += ",format," + format - opts = append(opts, oss.Process(process)) - } - if !o.publicRead { - if opt.ContentType != "" { - opts = append(opts, oss.ResponseContentType(opt.ContentType)) - } - if opt.Filename != "" { - opts = append(opts, oss.ResponseContentDisposition(`attachment; filename=`+strconv.Quote(opt.Filename))) - } - } - } - if expire <= 0 { - expire = time.Hour * 24 * 365 * 99 // 99 years - } else if expire < time.Second { - expire = time.Second - } - if !o.publicRead { - return o.bucket.SignURL(name, http.MethodGet, int64(expire/time.Second), opts...) - } - rawParams, err := oss.GetRawParams(opts) - if err != nil { - return "", errs.Wrap(err, "AccessURL error") - } - params := getURLParams(*o.bucket.Client.Conn, rawParams) - return getURL(o.um, o.bucket.BucketName, name, params).String(), nil -} - -func (o *OSS) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) { - // https://help.aliyun.com/zh/oss/developer-reference/postobject?spm=a2c4g.11186623.0.0.1cb83cebkP55nn - expires := time.Now().Add(duration) - conditions := []any{ - map[string]string{"bucket": o.bucket.BucketName}, - map[string]string{"key": name}, - } - if size > 0 { - conditions = append(conditions, []any{"content-length-range", 0, size}) - } - policy := map[string]any{ - "expiration": expires.Format("2006-01-02T15:04:05.000Z"), - "conditions": conditions, - } - policyJson, err := json.Marshal(policy) - if err != nil { - return nil, errs.Wrap(err, "Marshal json error") - } - policyStr := base64.StdEncoding.EncodeToString(policyJson) - h := hmac.New(sha1.New, []byte(o.credentials.GetAccessKeySecret())) - if _, err := io.WriteString(h, policyStr); err != nil { - return nil, errs.Wrap(err, "WriteString error") - } - fd := &s3.FormData{ - URL: o.bucketURL, - File: "file", - Expires: expires, - FormData: map[string]string{ - "key": name, - "policy": policyStr, - "OSSAccessKeyId": o.credentials.GetAccessKeyID(), - "success_action_status": strconv.Itoa(successCode), - "signature": base64.StdEncoding.EncodeToString(h.Sum(nil)), - }, - SuccessCodes: []int{successCode}, - } - if contentType != "" { - fd.FormData["x-oss-content-type"] = contentType - } - return fd, nil -} diff --git a/pkg/common/db/s3/s3.go b/pkg/common/db/s3/s3.go deleted file mode 100644 index d3dd90ae9..000000000 --- a/pkg/common/db/s3/s3.go +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package s3 - -import ( - "context" - "net/http" - "net/url" - "time" -) - -type PartLimit struct { - MinPartSize int64 `json:"minPartSize"` - MaxPartSize int64 `json:"maxPartSize"` - MaxNumSize int64 `json:"maxNumSize"` -} - -type InitiateMultipartUploadResult struct { - Bucket string `json:"bucket"` - Key string `json:"key"` - UploadID string `json:"uploadID"` -} - -type MultipartUploadRequest struct { - UploadID string `json:"uploadId"` - Bucket string `json:"bucket"` - Key string `json:"key"` - Method string `json:"method"` - URL string `json:"url"` - Query url.Values `json:"query"` - Header http.Header `json:"header"` - PartKey string `json:"partKey"` - PartSize int64 `json:"partSize"` - FirstPart int `json:"firstPart"` -} - -type Part struct { - PartNumber int `json:"partNumber"` - ETag string `json:"etag"` -} - -type CompleteMultipartUploadResult struct { - Location string `json:"location"` - Bucket string `json:"bucket"` - Key string `json:"key"` - ETag string `json:"etag"` -} - -type SignResult struct { - Parts []SignPart `json:"parts"` -} - -type ObjectInfo struct { - ETag string `json:"etag"` - Key string `json:"name"` - Size int64 `json:"size"` - LastModified time.Time `json:"lastModified"` -} - -type CopyObjectInfo struct { - Key string `json:"name"` - ETag string `json:"etag"` -} - -type FormData struct { - URL string `json:"url"` - File string `json:"file"` - Header http.Header `json:"header"` - FormData map[string]string `json:"form"` - Expires time.Time `json:"expires"` - SuccessCodes []int `json:"successActionStatus"` -} - -type SignPart struct { - PartNumber int `json:"partNumber"` - URL string `json:"url"` - Query url.Values `json:"query"` - Header http.Header `json:"header"` -} - -type AuthSignResult struct { - URL string `json:"url"` - Query url.Values `json:"query"` - Header http.Header `json:"header"` - Parts []SignPart `json:"parts"` -} - -type InitiateUpload struct { - UploadID string `json:"uploadId"` - Bucket string `json:"bucket"` - Key string `json:"key"` - Method string `json:"method"` - URL string `json:"url"` - Query url.Values `json:"query"` - Header http.Header `json:"header"` - PartKey string `json:"partKey"` - PartSize int64 `json:"partSize"` - FirstPart int `json:"firstPart"` -} - -type UploadedPart struct { - PartNumber int `json:"partNumber"` - LastModified time.Time `json:"lastModified"` - ETag string `json:"etag"` - Size int64 `json:"size"` -} - -type ListUploadedPartsResult struct { - Key string `xml:"Key"` - UploadID string `xml:"UploadId"` - NextPartNumberMarker int `xml:"NextPartNumberMarker"` - MaxParts int `xml:"MaxParts"` - UploadedParts []UploadedPart `xml:"Part"` -} - -type Image struct { - Format string `json:"format"` - Width int `json:"width"` - Height int `json:"height"` -} - -type AccessURLOption struct { - ContentType string `json:"contentType"` - Filename string `json:"filename"` - Image *Image `json:"image"` -} - -type Interface interface { - Engine() string - PartLimit() *PartLimit - - InitiateMultipartUpload(ctx context.Context, name string) (*InitiateMultipartUploadResult, error) - CompleteMultipartUpload(ctx context.Context, uploadID string, name string, parts []Part) (*CompleteMultipartUploadResult, error) - - PartSize(ctx context.Context, size int64) (int64, error) - AuthSign(ctx context.Context, uploadID string, name string, expire time.Duration, partNumbers []int) (*AuthSignResult, error) - - PresignedPutObject(ctx context.Context, name string, expire time.Duration) (string, error) - - DeleteObject(ctx context.Context, name string) error - - CopyObject(ctx context.Context, src string, dst string) (*CopyObjectInfo, error) - - StatObject(ctx context.Context, name string) (*ObjectInfo, error) - - IsNotFound(err error) bool - - AbortMultipartUpload(ctx context.Context, uploadID string, name string) error - ListUploadedParts(ctx context.Context, uploadID string, name string, partNumberMarker int, maxParts int) (*ListUploadedPartsResult, error) - - AccessURL(ctx context.Context, name string, expire time.Duration, opt *AccessURLOption) (string, error) - - FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*FormData, error) -} diff --git a/pkg/common/db/table/relation/black.go b/pkg/common/db/table/relation/black.go index 50499054c..f5d1cb236 100644 --- a/pkg/common/db/table/relation/black.go +++ b/pkg/common/db/table/relation/black.go @@ -18,7 +18,7 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/pagination" + "github.com/openimsdk/tools/db/pagination" ) type BlackModel struct { @@ -33,8 +33,8 @@ type BlackModel struct { type BlackModelInterface interface { Create(ctx context.Context, blacks []*BlackModel) (err error) Delete(ctx context.Context, blacks []*BlackModel) (err error) - //UpdateByMap(ctx context.Context, ownerUserID, blockUserID string, args map[string]any) (err error) - //Update(ctx context.Context, blacks []*BlackModel) (err error) + // UpdateByMap(ctx context.Context, ownerUserID, blockUserID string, args map[string]any) (err error) + // Update(ctx context.Context, blacks []*BlackModel) (err error) Find(ctx context.Context, blacks []*BlackModel) (blackList []*BlackModel, err error) Take(ctx context.Context, ownerUserID, blockUserID string) (black *BlackModel, err error) FindOwnerBlacks(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, blacks []*BlackModel, err error) diff --git a/pkg/common/db/table/relation/conversation.go b/pkg/common/db/table/relation/conversation.go index 583e41c0f..4990c96c6 100644 --- a/pkg/common/db/table/relation/conversation.go +++ b/pkg/common/db/table/relation/conversation.go @@ -18,7 +18,7 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/pagination" + "github.com/openimsdk/tools/db/pagination" ) type ConversationModel struct { diff --git a/pkg/common/db/unrelation/doc.go b/pkg/common/db/table/relation/doc.go similarity index 79% rename from pkg/common/db/unrelation/doc.go rename to pkg/common/db/table/relation/doc.go index 1a15e42c4..c711dec70 100644 --- a/pkg/common/db/unrelation/doc.go +++ b/pkg/common/db/table/relation/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package unrelation // import "github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation" +package relation // import "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation" diff --git a/pkg/common/db/table/relation/friend.go b/pkg/common/db/table/relation/friend.go index 73f7454df..4c84e773d 100644 --- a/pkg/common/db/table/relation/friend.go +++ b/pkg/common/db/table/relation/friend.go @@ -18,7 +18,7 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/pagination" + "github.com/openimsdk/tools/db/pagination" ) // FriendModel represents the data structure for a friend relationship in MongoDB. diff --git a/pkg/common/db/table/relation/friend_request.go b/pkg/common/db/table/relation/friend_request.go index 8dceb0778..d59e3bb0b 100644 --- a/pkg/common/db/table/relation/friend_request.go +++ b/pkg/common/db/table/relation/friend_request.go @@ -18,7 +18,7 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/pagination" + "github.com/openimsdk/tools/db/pagination" ) type FriendRequestModel struct { diff --git a/pkg/common/db/table/relation/group.go b/pkg/common/db/table/relation/group.go index 1f969cd4f..f479a4745 100644 --- a/pkg/common/db/table/relation/group.go +++ b/pkg/common/db/table/relation/group.go @@ -18,7 +18,7 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/pagination" + "github.com/openimsdk/tools/db/pagination" ) type GroupModel struct { diff --git a/pkg/common/db/table/relation/group_member.go b/pkg/common/db/table/relation/group_member.go index 88ab87739..37f1cfc03 100644 --- a/pkg/common/db/table/relation/group_member.go +++ b/pkg/common/db/table/relation/group_member.go @@ -18,7 +18,7 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/pagination" + "github.com/openimsdk/tools/db/pagination" ) type GroupMemberModel struct { @@ -36,10 +36,10 @@ type GroupMemberModel struct { } type GroupMemberModelInterface interface { - //NewTx(tx any) GroupMemberModelInterface + // NewTx(tx any) GroupMemberModelInterface Create(ctx context.Context, groupMembers []*GroupMemberModel) (err error) Delete(ctx context.Context, groupID string, userIDs []string) (err error) - //DeleteGroup(ctx context.Context, groupIDs []string) (err error) + // DeleteGroup(ctx context.Context, groupIDs []string) (err error) Update(ctx context.Context, groupID string, userID string, data map[string]any) (err error) UpdateRoleLevel(ctx context.Context, groupID string, userID string, roleLevel int32) error FindMemberUserID(ctx context.Context, groupID string) (userIDs []string, err error) @@ -47,11 +47,11 @@ type GroupMemberModelInterface interface { TakeOwner(ctx context.Context, groupID string) (groupMember *GroupMemberModel, err error) SearchMember(ctx context.Context, keyword string, groupID string, pagination pagination.Pagination) (total int64, groupList []*GroupMemberModel, err error) FindRoleLevelUserIDs(ctx context.Context, groupID string, roleLevel int32) ([]string, error) - //MapGroupMemberNum(ctx context.Context, groupIDs []string) (count map[string]uint32, err error) - //FindJoinUserID(ctx context.Context, groupIDs []string) (groupUsers map[string][]string, err error) + // MapGroupMemberNum(ctx context.Context, groupIDs []string) (count map[string]uint32, err error) + // FindJoinUserID(ctx context.Context, groupIDs []string) (groupUsers map[string][]string, err error) FindUserJoinedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) TakeGroupMemberNum(ctx context.Context, groupID string) (count int64, err error) - //FindUsersJoinedGroupID(ctx context.Context, userIDs []string) (map[string][]string, error) + // FindUsersJoinedGroupID(ctx context.Context, userIDs []string) (map[string][]string, error) FindUserManagedGroupID(ctx context.Context, userID string) (groupIDs []string, err error) IsUpdateRoleLevel(data map[string]any) bool } diff --git a/pkg/common/db/table/relation/group_request.go b/pkg/common/db/table/relation/group_request.go index 39999d799..7e9b258de 100644 --- a/pkg/common/db/table/relation/group_request.go +++ b/pkg/common/db/table/relation/group_request.go @@ -18,7 +18,7 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/pagination" + "github.com/openimsdk/tools/db/pagination" ) type GroupRequestModel struct { diff --git a/pkg/common/db/table/relation/log.go b/pkg/common/db/table/relation/log.go index ba63c0c2b..afc32c68e 100644 --- a/pkg/common/db/table/relation/log.go +++ b/pkg/common/db/table/relation/log.go @@ -18,7 +18,7 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/pagination" + "github.com/openimsdk/tools/db/pagination" ) type LogModel struct { diff --git a/pkg/common/db/table/unrelation/msg.go b/pkg/common/db/table/relation/msg.go similarity index 88% rename from pkg/common/db/table/unrelation/msg.go rename to pkg/common/db/table/relation/msg.go index 60dd7c260..14b8dbb30 100644 --- a/pkg/common/db/table/unrelation/msg.go +++ b/pkg/common/db/table/relation/msg.go @@ -12,15 +12,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -package unrelation +package relation import ( "context" "strconv" "time" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" "go.mongodb.org/mongo-driver/mongo" ) @@ -32,6 +33,8 @@ const ( NewestList = -1 ) +var ErrMsgListNotExist = errs.New("user not have msg in mongoDB") + type MsgDocModel struct { DocID string `bson:"doc_id"` Msg []*MsgInfoModel `bson:"msgs"` @@ -110,23 +113,8 @@ type MsgDocModelInterface interface { DeleteMsgsInOneDocByIndex(ctx context.Context, docID string, indexes []int) error MarkSingleChatMsgsAsRead(ctx context.Context, userID string, docID string, indexes []int64) error SearchMessage(ctx context.Context, req *msg.SearchMessageReq) (int32, []*MsgInfoModel, error) - RangeUserSendCount( - ctx context.Context, - start time.Time, - end time.Time, - group bool, - ase bool, - pageNumber int32, - showNumber int32, - ) (msgCount int64, userCount int64, users []*UserCount, dateCount map[string]int64, err error) - RangeGroupSendCount( - ctx context.Context, - start time.Time, - end time.Time, - ase bool, - pageNumber int32, - showNumber int32, - ) (msgCount int64, userCount int64, groups []*GroupCount, dateCount map[string]int64, err error) + RangeUserSendCount(ctx context.Context, start time.Time, end time.Time, group bool, ase bool, pageNumber int32, showNumber int32) (msgCount int64, userCount int64, users []*UserCount, dateCount map[string]int64, err error) + RangeGroupSendCount(ctx context.Context, start time.Time, end time.Time, ase bool, pageNumber int32, showNumber int32) (msgCount int64, userCount int64, groups []*GroupCount, dateCount map[string]int64, err error) ConvertMsgsDocLen(ctx context.Context, conversationIDs []string) } @@ -165,11 +153,11 @@ func (m MsgDocModel) GetDocIDSeqsMap(conversationID string, seqs []int64) map[st return t } -func (m MsgDocModel) GetMsgIndex(seq int64) int64 { +func (MsgDocModel) GetMsgIndex(seq int64) int64 { return (seq - 1) % singleGocMsgNum } -func (m MsgDocModel) indexGen(conversationID string, seqSuffix int64) string { +func (MsgDocModel) indexGen(conversationID string, seqSuffix int64) string { return conversationID + ":" + strconv.FormatInt(seqSuffix, 10) } diff --git a/pkg/common/db/table/unrelation/user.go b/pkg/common/db/table/relation/subscribe.go similarity index 86% rename from pkg/common/db/table/unrelation/user.go rename to pkg/common/db/table/relation/subscribe.go index 1505829e5..4e184cf38 100644 --- a/pkg/common/db/table/unrelation/user.go +++ b/pkg/common/db/table/relation/subscribe.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package unrelation +package relation import "context" @@ -21,18 +21,18 @@ const ( SubscribeUser = "subscribe_user" ) -// UserModel collection structure. -type UserModel struct { +// SubscribeUserModel collection structure. +type SubscribeUserModel struct { UserID string `bson:"user_id" json:"userID"` UserIDList []string `bson:"user_id_list" json:"userIDList"` } -func (UserModel) TableName() string { +func (SubscribeUserModel) TableName() string { return SubscribeUser } -// UserModelInterface Operation interface of user mongodb. -type UserModelInterface interface { +// SubscribeUserModelInterface Operation interface of user mongodb. +type SubscribeUserModelInterface interface { // AddSubscriptionList Subscriber's handling of thresholds. AddSubscriptionList(ctx context.Context, userID string, userIDList []string) error // UnsubscriptionList Handling of unsubscribe. diff --git a/pkg/common/db/table/relation/user.go b/pkg/common/db/table/relation/user.go index 375930ddf..938a8a77d 100644 --- a/pkg/common/db/table/relation/user.go +++ b/pkg/common/db/table/relation/user.go @@ -18,8 +18,8 @@ import ( "context" "time" - "github.com/OpenIMSDK/protocol/user" - "github.com/OpenIMSDK/tools/pagination" + "github.com/openimsdk/protocol/user" + "github.com/openimsdk/tools/db/pagination" ) type UserModel struct { @@ -65,7 +65,7 @@ type UserModelInterface interface { CountTotal(ctx context.Context, before *time.Time) (count int64, err error) // Get user total quantity every day CountRangeEverydayTotal(ctx context.Context, start time.Time, end time.Time) (map[string]int64, error) - //CRUD user command + // CRUD user command AddUserCommand(ctx context.Context, userID string, Type int32, UUID string, value string, ex string) error DeleteUserCommand(ctx context.Context, userID string, Type int32, UUID string) error UpdateUserCommand(ctx context.Context, userID string, Type int32, UUID string, val map[string]any) error diff --git a/pkg/common/db/table/relation/utils.go b/pkg/common/db/table/relation/utils.go index 380f2410e..006da4808 100644 --- a/pkg/common/db/table/relation/utils.go +++ b/pkg/common/db/table/relation/utils.go @@ -15,7 +15,7 @@ package relation import ( - "github.com/OpenIMSDK/tools/utils" + "github.com/openimsdk/tools/errs" "go.mongodb.org/mongo-driver/mongo" ) @@ -31,5 +31,5 @@ type GroupSimpleUserID struct { } func IsNotFound(err error) bool { - return utils.Unwrap(err) == mongo.ErrNoDocuments + return errs.Unwrap(err) == mongo.ErrNoDocuments } diff --git a/pkg/common/db/table/unrelation/common.go b/pkg/common/db/table/unrelation/common.go deleted file mode 100644 index bd46ccc2a..000000000 --- a/pkg/common/db/table/unrelation/common.go +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package unrelation - -type CommonUserModel struct { - UserID string `bson:"user_id"` - UserName string `bson:"user_name"` -} diff --git a/pkg/common/db/table/unrelation/super_group.go b/pkg/common/db/table/unrelation/super_group.go deleted file mode 100644 index 1fd80c67a..000000000 --- a/pkg/common/db/table/unrelation/super_group.go +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package unrelation - -//import ( -// "context" -//) -// -//const ( -// CSuperGroup = "super_group" -// CUserToSuperGroup = "user_to_super_group" -//) -// -//type SuperGroupModel struct { -// GroupID string `bson:"group_id" json:"groupID"` -// MemberIDs []string `bson:"member_id_list" json:"memberIDList"` -//} -// -//func (SuperGroupModel) TableName() string { -// return CSuperGroup -//} -// -//type UserToSuperGroupModel struct { -// UserID string `bson:"user_id" json:"userID"` -// GroupIDs []string `bson:"group_id_list" json:"groupIDList"` -//} -// -//func (UserToSuperGroupModel) TableName() string { -// return CUserToSuperGroup -//} -// -//type SuperGroupModelInterface interface { -// CreateSuperGroup(ctx context.Context, groupID string, initMemberIDs []string) error -// TakeSuperGroup(ctx context.Context, groupID string) (group *SuperGroupModel, err error) -// FindSuperGroup(ctx context.Context, groupIDs []string) (groups []*SuperGroupModel, err error) -// AddUserToSuperGroup(ctx context.Context, groupID string, userIDs []string) error -// RemoverUserFromSuperGroup(ctx context.Context, groupID string, userIDs []string) error -// GetSuperGroupByUserID(ctx context.Context, userID string) (*UserToSuperGroupModel, error) -// DeleteSuperGroup(ctx context.Context, groupID string) error -// RemoveGroupFromUser(ctx context.Context, groupID string, userIDs []string) error -//} diff --git a/pkg/common/db/unrelation/mongo.go b/pkg/common/db/unrelation/mongo.go deleted file mode 100644 index 834e81237..000000000 --- a/pkg/common/db/unrelation/mongo.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package unrelation - -import ( - "context" - "fmt" - "os" - "strings" - "time" - - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mw/specialerror" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/mongo" - "go.mongodb.org/mongo-driver/mongo/options" -) - -const ( - maxRetry = 10 // number of retries - mongoConnTimeout = 10 * time.Second -) - -type Mongo struct { - db *mongo.Client - config *config.GlobalConfig -} - -// NewMongo Initialize MongoDB connection. -func NewMongo(config *config.GlobalConfig) (*Mongo, error) { - specialerror.AddReplace(mongo.ErrNoDocuments, errs.ErrRecordNotFound) - uri := buildMongoURI(config) - - var mongoClient *mongo.Client - var err error - - // Retry connecting to MongoDB - for i := 0; i <= maxRetry; i++ { - ctx, cancel := context.WithTimeout(context.Background(), mongoConnTimeout) - defer cancel() - mongoClient, err = mongo.Connect(ctx, options.Client().ApplyURI(uri)) - if err == nil { - if err = mongoClient.Ping(ctx, nil); err != nil { - return nil, errs.Wrap(err, uri) - } - return &Mongo{db: mongoClient, config: config}, nil - } - if shouldRetry(err) { - time.Sleep(time.Second) // exponential backoff could be implemented here - continue - } - } - return nil, errs.Wrap(err, uri) -} - -func buildMongoURI(config *config.GlobalConfig) string { - uri := os.Getenv("MONGO_URI") - if uri != "" { - return uri - } - - if config.Mongo.Uri != "" { - return config.Mongo.Uri - } - - username := os.Getenv("MONGO_OPENIM_USERNAME") - password := os.Getenv("MONGO_OPENIM_PASSWORD") - address := os.Getenv("MONGO_ADDRESS") - port := os.Getenv("MONGO_PORT") - database := os.Getenv("MONGO_DATABASE") - maxPoolSize := os.Getenv("MONGO_MAX_POOL_SIZE") - - if username == "" { - username = config.Mongo.Username - } - if password == "" { - password = config.Mongo.Password - } - if address == "" { - address = strings.Join(config.Mongo.Address, ",") - } else if port != "" { - address = fmt.Sprintf("%s:%s", address, port) - } - if database == "" { - database = config.Mongo.Database - } - if maxPoolSize == "" { - maxPoolSize = fmt.Sprint(config.Mongo.MaxPoolSize) - } - - if username != "" && password != "" { - - return fmt.Sprintf("mongodb://%s:%s@%s/%s?maxPoolSize=%s", username, password, address, database, maxPoolSize) - } - return fmt.Sprintf("mongodb://%s/%s?maxPoolSize=%s", address, database, maxPoolSize) -} - -func shouldRetry(err error) bool { - if cmdErr, ok := err.(mongo.CommandError); ok { - return cmdErr.Code != 13 && cmdErr.Code != 18 - } - return true -} - -// GetClient returns the MongoDB client. -func (m *Mongo) GetClient() *mongo.Client { - return m.db -} - -// GetDatabase returns the specific database from MongoDB. -func (m *Mongo) GetDatabase(database string) *mongo.Database { - return m.db.Database(database) -} - -// CreateMsgIndex creates an index for messages in MongoDB. -func (m *Mongo) CreateMsgIndex() error { - return m.createMongoIndex(unrelation.Msg, true, "doc_id") -} - -// createMongoIndex creates an index in a MongoDB collection. -func (m *Mongo) createMongoIndex(collection string, isUnique bool, keys ...string) error { - db := m.GetDatabase(m.config.Mongo.Database).Collection(collection) - opts := options.CreateIndexes().SetMaxTime(10 * time.Second) - indexView := db.Indexes() - - keysDoc := buildIndexKeys(keys) - - index := mongo.IndexModel{ - Keys: keysDoc, - } - if isUnique { - index.Options = options.Index().SetUnique(true) - } - - _, err := indexView.CreateOne(context.Background(), index, opts) - if err != nil { - return errs.Wrap(err, "CreateIndex") - } - return nil -} - -// buildIndexKeys builds the BSON document for index keys. -func buildIndexKeys(keys []string) bson.D { - keysDoc := bson.D{} - for _, key := range keys { - direction := 1 // default direction is ascending - if strings.HasPrefix(key, "-") { - direction = -1 // descending order for prefixed with "-" - key = strings.TrimLeft(key, "-") - } - keysDoc = append(keysDoc, bson.E{Key: key, Value: direction}) - } - return keysDoc -} diff --git a/pkg/common/db/unrelation/msg_convert.go b/pkg/common/db/unrelation/msg_convert.go deleted file mode 100644 index 30c74e927..000000000 --- a/pkg/common/db/unrelation/msg_convert.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package unrelation - -import ( - "context" - "fmt" - - "github.com/OpenIMSDK/tools/log" - table "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" - "go.mongodb.org/mongo-driver/bson" - "go.mongodb.org/mongo-driver/bson/primitive" -) - -func (m *MsgMongoDriver) ConvertMsgsDocLen(ctx context.Context, conversationIDs []string) { - for _, conversationID := range conversationIDs { - regex := primitive.Regex{Pattern: fmt.Sprintf("^%s:", conversationID)} - cursor, err := m.MsgCollection.Find(ctx, bson.M{"doc_id": regex}) - if err != nil { - log.ZError(ctx, "convertAll find msg doc failed", err, "conversationID", conversationID) - continue - } - var msgDocs []table.MsgDocModel - err = cursor.All(ctx, &msgDocs) - if err != nil { - log.ZError(ctx, "convertAll cursor all failed", err, "conversationID", conversationID) - continue - } - if len(msgDocs) < 1 { - continue - } - log.ZInfo(ctx, "msg doc convert", "conversationID", conversationID, "len(msgDocs)", len(msgDocs)) - if len(msgDocs[0].Msg) == int(m.model.GetSingleGocMsgNum5000()) { - if _, err := m.MsgCollection.DeleteMany(ctx, bson.M{"doc_id": regex}); err != nil { - log.ZError(ctx, "convertAll delete many failed", err, "conversationID", conversationID) - continue - } - var newMsgDocs []any - for _, msgDoc := range msgDocs { - if int64(len(msgDoc.Msg)) == m.model.GetSingleGocMsgNum() { - continue - } - var index int64 - for index < int64(len(msgDoc.Msg)) { - msg := msgDoc.Msg[index] - if msg != nil && msg.Msg != nil { - msgDocModel := table.MsgDocModel{DocID: m.model.GetDocID(conversationID, msg.Msg.Seq)} - end := index + m.model.GetSingleGocMsgNum() - if int(end) >= len(msgDoc.Msg) { - msgDocModel.Msg = msgDoc.Msg[index:] - } else { - msgDocModel.Msg = msgDoc.Msg[index:end] - } - newMsgDocs = append(newMsgDocs, msgDocModel) - index = end - } else { - break - } - } - } - _, err = m.MsgCollection.InsertMany(ctx, newMsgDocs) - if err != nil { - log.ZError(ctx, "convertAll insert many failed", err, "conversationID", conversationID, "len(newMsgDocs)", len(newMsgDocs)) - } else { - log.ZInfo(ctx, "msg doc convert", "conversationID", conversationID, "len(newMsgDocs)", len(newMsgDocs)) - } - } - } -} diff --git a/pkg/common/db/unrelation/super_group.go b/pkg/common/db/unrelation/super_group.go deleted file mode 100644 index 6c2bb6aaf..000000000 --- a/pkg/common/db/unrelation/super_group.go +++ /dev/null @@ -1,163 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package unrelation - -// -//import ( -// "context" -// -// "go.mongodb.org/mongo-driver/bson" -// "go.mongodb.org/mongo-driver/mongo" -// "go.mongodb.org/mongo-driver/mongo/options" -// -// "github.com/OpenIMSDK/tools/utils" -// -// "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" -//) -// -//func NewSuperGroupMongoDriver(database *mongo.Database) unrelation.SuperGroupModelInterface { -// return &SuperGroupMongoDriver{ -// superGroupCollection: database.Collection(unrelation.CSuperGroup), -// userToSuperGroupCollection: database.Collection(unrelation.CUserToSuperGroup), -// } -//} -// -//type SuperGroupMongoDriver struct { -// superGroupCollection *mongo.Collection -// userToSuperGroupCollection *mongo.Collection -//} -// -//func (s *SuperGroupMongoDriver) CreateSuperGroup(ctx context.Context, groupID string, initMemberIDs []string) error { -// _, err := s.superGroupCollection.InsertOne(ctx, &unrelation.SuperGroupModel{ -// GroupID: groupID, -// MemberIDs: initMemberIDs, -// }) -// if err != nil { -// return err -// } -// for _, userID := range initMemberIDs { -// _, err = s.userToSuperGroupCollection.UpdateOne( -// ctx, -// bson.M{"user_id": userID}, -// bson.M{"$addToSet": bson.M{"group_id_list": groupID}}, -// &options.UpdateOptions{ -// Upsert: utils.ToPtr(true), -// }, -// ) -// if err != nil { -// return err -// } -// } -// return nil -//} -// -//func (s *SuperGroupMongoDriver) TakeSuperGroup( -// ctx context.Context, -// groupID string, -//) (group *unrelation.SuperGroupModel, err error) { -// if err := s.superGroupCollection.FindOne(ctx, bson.M{"group_id": groupID}).Decode(&group); err != nil { -// return nil, utils.Wrap(err, "") -// } -// return group, nil -//} -// -//func (s *SuperGroupMongoDriver) FindSuperGroup( -// ctx context.Context, -// groupIDs []string, -//) (groups []*unrelation.SuperGroupModel, err error) { -// cursor, err := s.superGroupCollection.Find(ctx, bson.M{"group_id": bson.M{ -// "$in": groupIDs, -// }}) -// if err != nil { -// return nil, err -// } -// defer cursor.Close(ctx) -// if err := cursor.All(ctx, &groups); err != nil { -// return nil, utils.Wrap(err, "") -// } -// return groups, nil -//} -// -//func (s *SuperGroupMongoDriver) AddUserToSuperGroup(ctx context.Context, groupID string, userIDs []string) error { -// _, err := s.superGroupCollection.UpdateOne( -// ctx, -// bson.M{"group_id": groupID}, -// bson.M{"$addToSet": bson.M{"member_id_list": bson.M{"$each": userIDs}}}, -// ) -// if err != nil { -// return err -// } -// upsert := true -// opts := &options.UpdateOptions{ -// Upsert: &upsert, -// } -// for _, userID := range userIDs { -// _, err = s.userToSuperGroupCollection.UpdateOne( -// ctx, -// bson.M{"user_id": userID}, -// bson.M{"$addToSet": bson.M{"group_id_list": groupID}}, -// opts, -// ) -// if err != nil { -// return utils.Wrap(err, "transaction failed") -// } -// } -// return nil -//} -// -//func (s *SuperGroupMongoDriver) RemoverUserFromSuperGroup(ctx context.Context, groupID string, userIDs []string) error { -// _, err := s.superGroupCollection.UpdateOne( -// ctx, -// bson.M{"group_id": groupID}, -// bson.M{"$pull": bson.M{"member_id_list": bson.M{"$in": userIDs}}}, -// ) -// if err != nil { -// return err -// } -// err = s.RemoveGroupFromUser(ctx, groupID, userIDs) -// if err != nil { -// return err -// } -// return nil -//} -// -//func (s *SuperGroupMongoDriver) GetSuperGroupByUserID( -// ctx context.Context, -// userID string, -//) (*unrelation.UserToSuperGroupModel, error) { -// var user unrelation.UserToSuperGroupModel -// err := s.userToSuperGroupCollection.FindOne(ctx, bson.M{"user_id": userID}).Decode(&user) -// return &user, utils.Wrap(err, "") -//} -// -//func (s *SuperGroupMongoDriver) DeleteSuperGroup(ctx context.Context, groupID string) error { -// group, err := s.TakeSuperGroup(ctx, groupID) -// if err != nil { -// return err -// } -// if _, err := s.superGroupCollection.DeleteOne(ctx, bson.M{"group_id": groupID}); err != nil { -// return utils.Wrap(err, "") -// } -// return s.RemoveGroupFromUser(ctx, groupID, group.MemberIDs) -//} -// -//func (s *SuperGroupMongoDriver) RemoveGroupFromUser(ctx context.Context, groupID string, userIDs []string) error { -// _, err := s.userToSuperGroupCollection.UpdateOne( -// ctx, -// bson.M{"user_id": bson.M{"$in": userIDs}}, -// bson.M{"$pull": bson.M{"group_id_list": groupID}}, -// ) -// return utils.Wrap(err, "") -//} diff --git a/pkg/common/discoveryregister/direct/directResolver.go b/pkg/common/discoveryregister/direct/direct_resolver.go similarity index 98% rename from pkg/common/discoveryregister/direct/directResolver.go rename to pkg/common/discoveryregister/direct/direct_resolver.go index a706ce5e4..821378277 100644 --- a/pkg/common/discoveryregister/direct/directResolver.go +++ b/pkg/common/discoveryregister/direct/direct_resolver.go @@ -19,7 +19,7 @@ import ( "math/rand" "strings" - "github.com/OpenIMSDK/tools/log" + "github.com/openimsdk/tools/log" "google.golang.org/grpc/resolver" ) diff --git a/pkg/common/discoveryregister/direct/directconn.go b/pkg/common/discoveryregister/direct/directconn.go index df03825e5..1cbe56dd5 100644 --- a/pkg/common/discoveryregister/direct/directconn.go +++ b/pkg/common/discoveryregister/direct/directconn.go @@ -14,159 +14,161 @@ package direct -import ( - "context" - "errors" - "fmt" - - "github.com/OpenIMSDK/tools/errs" - config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials/insecure" -) - -type ServiceAddresses map[string][]int - -func getServiceAddresses(config *config2.GlobalConfig) ServiceAddresses { - return ServiceAddresses{ - config.RpcRegisterName.OpenImUserName: config.RpcPort.OpenImUserPort, - config.RpcRegisterName.OpenImFriendName: config.RpcPort.OpenImFriendPort, - config.RpcRegisterName.OpenImMsgName: config.RpcPort.OpenImMessagePort, - config.RpcRegisterName.OpenImMessageGatewayName: config.LongConnSvr.OpenImMessageGatewayPort, - config.RpcRegisterName.OpenImGroupName: config.RpcPort.OpenImGroupPort, - config.RpcRegisterName.OpenImAuthName: config.RpcPort.OpenImAuthPort, - config.RpcRegisterName.OpenImPushName: config.RpcPort.OpenImPushPort, - config.RpcRegisterName.OpenImConversationName: config.RpcPort.OpenImConversationPort, - config.RpcRegisterName.OpenImThirdName: config.RpcPort.OpenImThirdPort, - } -} - -type ConnDirect struct { - additionalOpts []grpc.DialOption - currentServiceAddress string - conns map[string][]*grpc.ClientConn - resolverDirect *ResolverDirect - config *config2.GlobalConfig -} - -func (cd *ConnDirect) GetClientLocalConns() map[string][]*grpc.ClientConn { - return nil -} - -func (cd *ConnDirect) GetUserIdHashGatewayHost(ctx context.Context, userId string) (string, error) { - return "", nil -} - -func (cd *ConnDirect) Register(serviceName, host string, port int, opts ...grpc.DialOption) error { - return nil -} - -func (cd *ConnDirect) UnRegister() error { - return nil -} - -func (cd *ConnDirect) CreateRpcRootNodes(serviceNames []string) error { - return nil -} - -func (cd *ConnDirect) RegisterConf2Registry(key string, conf []byte) error { - return nil -} - -func (cd *ConnDirect) GetConfFromRegistry(key string) ([]byte, error) { - return nil, nil -} - -func (cd *ConnDirect) Close() { - -} - -func NewConnDirect(config *config2.GlobalConfig) (*ConnDirect, error) { - return &ConnDirect{ - conns: make(map[string][]*grpc.ClientConn), - resolverDirect: NewResolverDirect(), - config: config, - }, nil -} - -func (cd *ConnDirect) GetConns(ctx context.Context, - serviceName string, opts ...grpc.DialOption) ([]*grpc.ClientConn, error) { - - if conns, exists := cd.conns[serviceName]; exists { - return conns, nil - } - ports := getServiceAddresses(cd.config)[serviceName] - var connections []*grpc.ClientConn - for _, port := range ports { - conn, err := cd.dialServiceWithoutResolver(ctx, fmt.Sprintf(cd.config.Rpc.ListenIP+":%d", port), append(cd.additionalOpts, opts...)...) - if err != nil { - return nil, errs.Wrap(fmt.Errorf("connect to port %d failed,serviceName %s, IP %s", port, serviceName, cd.config.Rpc.ListenIP)) - } - connections = append(connections, conn) - } - - if len(connections) == 0 { - return nil, errs.Wrap(errors.New("no connections found for service"), "serviceName", serviceName) - } - return connections, nil -} - -func (cd *ConnDirect) GetConn(ctx context.Context, serviceName string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { - // Get service addresses - addresses := getServiceAddresses(cd.config) - address, ok := addresses[serviceName] - if !ok { - return nil, errs.Wrap(errors.New("unknown service name"), "serviceName", serviceName) - } - var result string - for _, addr := range address { - if result != "" { - result = result + "," + fmt.Sprintf(cd.config.Rpc.ListenIP+":%d", addr) - } else { - result = fmt.Sprintf(cd.config.Rpc.ListenIP+":%d", addr) - } - } - // Try to dial a new connection - conn, err := cd.dialService(ctx, result, append(cd.additionalOpts, opts...)...) - if err != nil { - return nil, errs.Wrap(err, "address", result) - } - - // Store the new connection - cd.conns[serviceName] = append(cd.conns[serviceName], conn) - return conn, nil -} - -func (cd *ConnDirect) GetSelfConnTarget() string { - return cd.currentServiceAddress -} - -func (cd *ConnDirect) AddOption(opts ...grpc.DialOption) { - cd.additionalOpts = append(cd.additionalOpts, opts...) -} - -func (cd *ConnDirect) CloseConn(conn *grpc.ClientConn) { - if conn != nil { - conn.Close() - } -} - -func (cd *ConnDirect) dialService(ctx context.Context, address string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { - options := append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) - conn, err := grpc.DialContext(ctx, cd.resolverDirect.Scheme()+":///"+address, options...) - - if err != nil { - return nil, errs.Wrap(err, "address", address) - } - return conn, nil -} - -func (cd *ConnDirect) dialServiceWithoutResolver(ctx context.Context, address string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { - options := append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) - conn, err := grpc.DialContext(ctx, address, options...) - - if err != nil { - return nil, errs.Wrap(err) - } - return conn, nil -} +//import ( +// "context" +// "fmt" +// +// config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config" +// "github.com/openimsdk/tools/errs" +// "google.golang.org/grpc" +// "google.golang.org/grpc/credentials/insecure" +//) +// +//type ServiceAddresses map[string][]int +// +//func getServiceAddresses(rpcRegisterName *config2.RpcRegisterName, +// rpcPort *config2.RpcPort, longConnSvrPort []int) ServiceAddresses { +// return ServiceAddresses{ +// rpcRegisterName.OpenImUserName: rpcPort.OpenImUserPort, +// rpcRegisterName.OpenImFriendName: rpcPort.OpenImFriendPort, +// rpcRegisterName.OpenImMsgName: rpcPort.OpenImMessagePort, +// rpcRegisterName.OpenImMessageGatewayName: longConnSvrPort, +// rpcRegisterName.OpenImGroupName: rpcPort.OpenImGroupPort, +// rpcRegisterName.OpenImAuthName: rpcPort.OpenImAuthPort, +// rpcRegisterName.OpenImPushName: rpcPort.OpenImPushPort, +// rpcRegisterName.OpenImConversationName: rpcPort.OpenImConversationPort, +// rpcRegisterName.OpenImThirdName: rpcPort.OpenImThirdPort, +// } +//} +// +//type ConnDirect struct { +// additionalOpts []grpc.DialOption +// currentServiceAddress string +// conns map[string][]*grpc.ClientConn +// resolverDirect *ResolverDirect +// config *config2.GlobalConfig +//} +// +//func (cd *ConnDirect) GetClientLocalConns() map[string][]*grpc.ClientConn { +// return nil +//} +// +//func (cd *ConnDirect) GetUserIdHashGatewayHost(ctx context.Context, userId string) (string, error) { +// return "", nil +//} +// +//func (cd *ConnDirect) Register(serviceName, host string, port int, opts ...grpc.DialOption) error { +// return nil +//} +// +//func (cd *ConnDirect) UnRegister() error { +// return nil +//} +// +//func (cd *ConnDirect) CreateRpcRootNodes(serviceNames []string) error { +// return nil +//} +// +//func (cd *ConnDirect) RegisterConf2Registry(key string, conf []byte) error { +// return nil +//} +// +//func (cd *ConnDirect) GetConfFromRegistry(key string) ([]byte, error) { +// return nil, nil +//} +// +//func (cd *ConnDirect) Close() { +// +//} +// +//func NewConnDirect(config *config2.GlobalConfig) (*ConnDirect, error) { +// return &ConnDirect{ +// conns: make(map[string][]*grpc.ClientConn), +// resolverDirect: NewResolverDirect(), +// config: config, +// }, nil +//} +// +//func (cd *ConnDirect) GetConns(ctx context.Context, +// serviceName string, opts ...grpc.DialOption) ([]*grpc.ClientConn, error) { +// +// if conns, exists := cd.conns[serviceName]; exists { +// return conns, nil +// } +// ports := getServiceAddresses(&cd.config.RpcRegisterName, +// &cd.config.RpcPort, cd.config.LongConnSvr.OpenImMessageGatewayPort)[serviceName] +// var connections []*grpc.ClientConn +// for _, port := range ports { +// conn, err := cd.dialServiceWithoutResolver(ctx, fmt.Sprintf(cd.config.Rpc.ListenIP+":%d", port), append(cd.additionalOpts, opts...)...) +// if err != nil { +// return nil, errs.Wrap(fmt.Errorf("connect to port %d failed,serviceName %s, IP %s", port, serviceName, cd.config.Rpc.ListenIP)) +// } +// connections = append(connections, conn) +// } +// +// if len(connections) == 0 { +// return nil, errs.New("no connections found for service", "serviceName", serviceName).Wrap() +// } +// return connections, nil +//} +// +//func (cd *ConnDirect) GetConn(ctx context.Context, serviceName string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { +// // Get service addresses +// addresses := getServiceAddresses(&cd.config.RpcRegisterName, +// &cd.config.RpcPort, cd.config.LongConnSvr.OpenImMessageGatewayPort) +// address, ok := addresses[serviceName] +// if !ok { +// return nil, errs.New("unknown service name", "serviceName", serviceName).Wrap() +// } +// var result string +// for _, addr := range address { +// if result != "" { +// result = result + "," + fmt.Sprintf(cd.config.Rpc.ListenIP+":%d", addr) +// } else { +// result = fmt.Sprintf(cd.config.Rpc.ListenIP+":%d", addr) +// } +// } +// // Try to dial a new connection +// conn, err := cd.dialService(ctx, result, append(cd.additionalOpts, opts...)...) +// if err != nil { +// return nil, errs.WrapMsg(err, "address", result) +// } +// +// // Store the new connection +// cd.conns[serviceName] = append(cd.conns[serviceName], conn) +// return conn, nil +//} +// +//func (cd *ConnDirect) GetSelfConnTarget() string { +// return cd.currentServiceAddress +//} +// +//func (cd *ConnDirect) AddOption(opts ...grpc.DialOption) { +// cd.additionalOpts = append(cd.additionalOpts, opts...) +//} +// +//func (cd *ConnDirect) CloseConn(conn *grpc.ClientConn) { +// if conn != nil { +// conn.Close() +// } +//} +// +//func (cd *ConnDirect) dialService(ctx context.Context, address string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { +// options := append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) +// conn, err := grpc.DialContext(ctx, cd.resolverDirect.Scheme()+":///"+address, options...) +// +// if err != nil { +// return nil, errs.WrapMsg(err, "address", address) +// } +// return conn, nil +//} +// +//func (cd *ConnDirect) dialServiceWithoutResolver(ctx context.Context, address string, opts ...grpc.DialOption) (*grpc.ClientConn, error) { +// options := append(opts, grpc.WithTransportCredentials(insecure.NewCredentials())) +// conn, err := grpc.DialContext(ctx, address, options...) +// +// if err != nil { +// return nil, errs.Wrap(err) +// } +// return conn, nil +//} diff --git a/pkg/common/db/table/unrelation/doc.go b/pkg/common/discoveryregister/direct/doc.go similarity index 78% rename from pkg/common/db/table/unrelation/doc.go rename to pkg/common/discoveryregister/direct/doc.go index 7596206a0..b3cd0f804 100644 --- a/pkg/common/db/table/unrelation/doc.go +++ b/pkg/common/discoveryregister/direct/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package unrelation // import "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation" +package direct // import "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister/direct" diff --git a/pkg/common/discoveryregister/discoveryregister.go b/pkg/common/discoveryregister/discoveryregister.go index d3acf85f6..38d7382fa 100644 --- a/pkg/common/discoveryregister/discoveryregister.go +++ b/pkg/common/discoveryregister/discoveryregister.go @@ -15,32 +15,39 @@ package discoveryregister import ( - "errors" - "os" - - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" "github.com/openimsdk/open-im-server/v3/pkg/common/config" - "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister/direct" "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister/kubernetes" - "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister/zookeeper" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/discovery/zookeeper" + "github.com/openimsdk/tools/errs" + "time" ) -// NewDiscoveryRegister creates a new service discovery and registry client based on the provided environment type. -func NewDiscoveryRegister(config *config.GlobalConfig) (discoveryregistry.SvcDiscoveryRegistry, error) { +const ( + zookeeperConst = "zookeeper" + kubenetesConst = "k8s" + directConst = "direct" +) - if os.Getenv("ENVS_DISCOVERY") != "" { - config.Envs.Discovery = os.Getenv("ENVS_DISCOVERY") - } +// NewDiscoveryRegister creates a new service discovery and registry client based on the provided environment type. +func NewDiscoveryRegister(zookeeperConfig *config.ZooKeeper, share *config.Share) (discovery.SvcDiscoveryRegistry, error) { + switch share.Env { + case zookeeperConst: - switch config.Envs.Discovery { - case "zookeeper": - return zookeeper.NewZookeeperDiscoveryRegister(config) - case "k8s": - return kubernetes.NewK8sDiscoveryRegister(config.RpcRegisterName.OpenImMessageGatewayName) - case "direct": - return direct.NewConnDirect(config) + return zookeeper.NewZkClient( + zookeeperConfig.Address, + zookeeperConfig.Schema, + zookeeper.WithFreq(time.Hour), + zookeeper.WithUserNameAndPassword(zookeeperConfig.Username, zookeeperConfig.Password), + zookeeper.WithRoundRobin(), + zookeeper.WithTimeout(10), + ) + case kubenetesConst: + return kubernetes.NewK8sDiscoveryRegister(share.RpcRegisterName.MessageGateway) + case directConst: + //return direct.NewConnDirect(config) default: - return nil, errs.Wrap(errors.New("envType not correct")) + return nil, errs.New("unsupported discovery type", "type", share.Env).Wrap() } + return nil, nil } diff --git a/pkg/common/discoveryregister/discoveryregister_test.go b/pkg/common/discoveryregister/discoveryregister_test.go index 08aa5d5d5..417226645 100644 --- a/pkg/common/discoveryregister/discoveryregister_test.go +++ b/pkg/common/discoveryregister/discoveryregister_test.go @@ -16,12 +16,6 @@ package discoveryregister import ( "os" - "testing" - - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/stretchr/testify/assert" ) func setupTestEnvironment() { @@ -32,35 +26,35 @@ func setupTestEnvironment() { os.Setenv("ZOOKEEPER_PASSWORD", "") } -func TestNewDiscoveryRegister(t *testing.T) { - setupTestEnvironment() - conf := config.NewGlobalConfig() - tests := []struct { - envType string - gatewayName string - expectedError bool - expectedResult bool - }{ - {"zookeeper", "MessageGateway", false, true}, - {"k8s", "MessageGateway", false, true}, - {"direct", "MessageGateway", false, true}, - {"invalid", "MessageGateway", true, false}, - } - - for _, test := range tests { - conf.Envs.Discovery = test.envType - conf.RpcRegisterName.OpenImMessageGatewayName = test.gatewayName - client, err := NewDiscoveryRegister(conf) - - if test.expectedError { - assert.Error(t, err) - } else { - assert.NoError(t, err) - if test.expectedResult { - assert.Implements(t, (*discoveryregistry.SvcDiscoveryRegistry)(nil), client) - } else { - assert.Nil(t, client) - } - } - } -} +//func TestNewDiscoveryRegister(t *testing.T) { +// setupTestEnvironment() +// conf := config.NewGlobalConfig() +// tests := []struct { +// envType string +// gatewayName string +// expectedError bool +// expectedResult bool +// }{ +// {"zookeeper", "MessageGateway", false, true}, +// {"k8s", "MessageGateway", false, true}, +// {"direct", "MessageGateway", false, true}, +// {"invalid", "MessageGateway", true, false}, +// } +// +// for _, test := range tests { +// conf.Envs.Discovery = test.envType +// conf.RpcRegisterName.OpenImMessageGatewayName = test.gatewayName +// client, err := NewDiscoveryRegister(conf) +// +// if test.expectedError { +// assert.Error(t, err) +// } else { +// assert.NoError(t, err) +// if test.expectedResult { +// assert.Implements(t, (*discovery.SvcDiscoveryRegistry)(nil), client) +// } else { +// assert.Nil(t, client) +// } +// } +// } +//} diff --git a/pkg/common/discoveryregister/doc.go b/pkg/common/discoveryregister/doc.go new file mode 100644 index 000000000..46bbe7001 --- /dev/null +++ b/pkg/common/discoveryregister/doc.go @@ -0,0 +1,15 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package discoveryregister // import "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" diff --git a/pkg/common/discoveryregister/kubernetes/doc.go b/pkg/common/discoveryregister/kubernetes/doc.go new file mode 100644 index 000000000..8615caa6b --- /dev/null +++ b/pkg/common/discoveryregister/kubernetes/doc.go @@ -0,0 +1,15 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package kubernetes // import "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister/kubernetes" diff --git a/pkg/common/discoveryregister/kubernetes/kubernetes.go b/pkg/common/discoveryregister/kubernetes/kubernetes.go index 83af53db0..f1ce0bbdc 100644 --- a/pkg/common/discoveryregister/kubernetes/kubernetes.go +++ b/pkg/common/discoveryregister/kubernetes/kubernetes.go @@ -22,8 +22,8 @@ import ( "strconv" "strings" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/log" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/log" "github.com/stathat/consistent" "google.golang.org/grpc" ) @@ -36,7 +36,7 @@ type K8sDR struct { gatewayName string } -func NewK8sDiscoveryRegister(gatewayName string) (discoveryregistry.SvcDiscoveryRegistry, error) { +func NewK8sDiscoveryRegister(gatewayName string) (discovery.SvcDiscoveryRegistry, error) { gatewayConsistent := consistent.New() gatewayHosts := getMsgGatewayHost(context.Background(), gatewayName) for _, v := range gatewayHosts { @@ -74,6 +74,7 @@ func (cli *K8sDR) GetConfFromRegistry(key string) ([]byte, error) { return nil, nil } + func (cli *K8sDR) GetUserIdHashGatewayHost(ctx context.Context, userId string) (string, error) { host, err := cli.gatewayHostConsistent.Get(userId) if err != nil { @@ -81,6 +82,7 @@ func (cli *K8sDR) GetUserIdHashGatewayHost(ctx context.Context, userId string) ( } return host, err } + func getSelfHost(ctx context.Context, gatewayName string) string { port := 88 instance := "openimserver" @@ -102,6 +104,7 @@ func getSelfHost(ctx context.Context, gatewayName string) string { } // like openimserver-openim-msggateway-0.openimserver-openim-msggateway-headless.openim-lin.svc.cluster.local:88. +// Replica set in kubernetes environment func getMsgGatewayHost(ctx context.Context, gatewayName string) []string { port := 88 instance := "openimserver" @@ -122,7 +125,7 @@ func getMsgGatewayHost(ctx context.Context, gatewayName string) []string { host := fmt.Sprintf("%s-openim-msggateway-%d.%s-openim-msggateway-headless.%s.svc.cluster.local:%d", instance, i, instance, ns, port) ret = append(ret, host) } - log.ZInfo(ctx, "getMsgGatewayHost", "instance", instance, "selfPodName", selfPodName, "replicas", replicas, "ns", ns, "ret", ret) + log.ZDebug(ctx, "getMsgGatewayHost", "instance", instance, "selfPodName", selfPodName, "replicas", replicas, "ns", ns, "ret", ret) return ret } @@ -187,9 +190,10 @@ func (cli *K8sDR) CloseConn(conn *grpc.ClientConn) { // do not use this method for call rpc. func (cli *K8sDR) GetClientLocalConns() map[string][]*grpc.ClientConn { - fmt.Println("should not call this function!!!!!!!!!!!!!!!!!!!!!!!!!") + log.ZError(context.Background(), "should not call this function!", nil) return nil } + func (cli *K8sDR) Close() { } diff --git a/pkg/common/discoveryregister/zookeeper/doc.go b/pkg/common/discoveryregister/zookeeper/doc.go new file mode 100644 index 000000000..1c24d77ac --- /dev/null +++ b/pkg/common/discoveryregister/zookeeper/doc.go @@ -0,0 +1,15 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package zookeeper // import "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister/zookeeper" diff --git a/pkg/common/discoveryregister/zookeeper/zookeeper.go b/pkg/common/discoveryregister/zookeeper/zookeeper.go index 0aa40a907..1d11414b6 100644 --- a/pkg/common/discoveryregister/zookeeper/zookeeper.go +++ b/pkg/common/discoveryregister/zookeeper/zookeeper.go @@ -15,46 +15,10 @@ package zookeeper import ( - "fmt" "os" "strings" - "time" - - "github.com/OpenIMSDK/tools/discoveryregistry" - openkeeper "github.com/OpenIMSDK/tools/discoveryregistry/zookeeper" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" ) -// NewZookeeperDiscoveryRegister creates a new instance of ZookeeperDR for Zookeeper service discovery and registration. -func NewZookeeperDiscoveryRegister(config *config.GlobalConfig) (discoveryregistry.SvcDiscoveryRegistry, error) { - schema := getEnv("ZOOKEEPER_SCHEMA", config.Zookeeper.Schema) - zkAddr := getZkAddrFromEnv(config.Zookeeper.ZkAddr) - username := getEnv("ZOOKEEPER_USERNAME", config.Zookeeper.Username) - password := getEnv("ZOOKEEPER_PASSWORD", config.Zookeeper.Password) - - zk, err := openkeeper.NewClient( - zkAddr, - schema, - openkeeper.WithFreq(time.Hour), - openkeeper.WithUserNameAndPassword(username, password), - openkeeper.WithRoundRobin(), - openkeeper.WithTimeout(10), - openkeeper.WithLogger(log.NewZkLogger()), - ) - if err != nil { - uriFormat := "address:%s, username:%s, password:%s, schema:%s." - errInfo := fmt.Sprintf(uriFormat, - config.Zookeeper.ZkAddr, - config.Zookeeper.Username, - config.Zookeeper.Password, - config.Zookeeper.Schema) - return nil, errs.Wrap(err, errInfo) - } - return zk, nil -} - // getEnv returns the value of an environment variable if it exists, otherwise it returns the fallback value. func getEnv(key, fallback string) string { if value, exists := os.LookupEnv(key); exists { diff --git a/pkg/common/ginprometheus/doc.go b/pkg/common/ginprometheus/doc.go new file mode 100644 index 000000000..7d81b7a8a --- /dev/null +++ b/pkg/common/ginprometheus/doc.go @@ -0,0 +1,15 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package ginprometheus // import "github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus" diff --git a/pkg/common/http/http_client.go b/pkg/common/http/http_client.go deleted file mode 100644 index 83908b8d3..000000000 --- a/pkg/common/http/http_client.go +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package http - -import ( - "bytes" - "context" - "encoding/json" - "io" - "net/http" - "time" - - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" -) - -var ( - // define http client. - client = &http.Client{ - Timeout: 15 * time.Second, // max timeout is 15s - } -) - -func init() { - // reset http default transport - http.DefaultTransport.(*http.Transport).MaxConnsPerHost = 100 // default: 2 -} - -func Get(url string) (response []byte, err error) { - hclient := http.Client{Timeout: 5 * time.Second} - resp, err := hclient.Get(url) - if err != nil { - return nil, err - } - - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err - } - return body, nil -} - -func Post(ctx context.Context, url string, header map[string]string, data any, timeout int) (content []byte, err error) { - if timeout > 0 { - var cancel func() - ctx, cancel = context.WithTimeout(ctx, time.Second*time.Duration(timeout)) - defer cancel() - } - - jsonStr, err := json.Marshal(data) - if err != nil { - return nil, errs.Wrap(err, "Post: JSON marshal failed") - } - - req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBuffer(jsonStr)) - if err != nil { - return nil, errs.Wrap(err, "Post: NewRequestWithContext failed") - } - - if operationID, _ := ctx.Value(constant.OperationID).(string); operationID != "" { - req.Header.Set(constant.OperationID, operationID) - } - for k, v := range header { - req.Header.Set(k, v) - } - req.Header.Add("content-type", "application/json; charset=utf-8") - - resp, err := client.Do(req) - if err != nil { - return nil, errs.Wrap(err, "Post: client.Do failed") - } - defer resp.Body.Close() - - result, err := io.ReadAll(resp.Body) - if err != nil { - return nil, errs.Wrap(err, "Post: ReadAll failed") - } - - return result, nil -} - -func PostReturn(ctx context.Context, url string, header map[string]string, input, output any, timeOutSecond int) error { - b, err := Post(ctx, url, header, input, timeOutSecond) - if err != nil { - return err - } - err = json.Unmarshal(b, output) - if err != nil { - return errs.Wrap(err, "PostReturn: JSON unmarshal failed") - } - return nil -} - -func callBackPostReturn(ctx context.Context, url, command string, input interface{}, output callbackstruct.CallbackResp, callbackConfig config.CallBackConfig) error { - url = url + "/" + command - log.ZInfo(ctx, "callback", "url", url, "input", input, "config", callbackConfig) - b, err := Post(ctx, url, nil, input, callbackConfig.CallbackTimeOut) - if err != nil { - if callbackConfig.CallbackFailedContinue != nil && *callbackConfig.CallbackFailedContinue { - log.ZInfo(ctx, "callback failed but continue", err, "url", url) - return nil - } - log.ZWarn(ctx, "callback network failed", err, "url", url, "input", input) - return errs.ErrNetwork.Wrap(err.Error()) - } - if err = json.Unmarshal(b, output); err != nil { - if callbackConfig.CallbackFailedContinue != nil && *callbackConfig.CallbackFailedContinue { - log.ZWarn(ctx, "callback failed but continue", err, "url", url) - return nil - } - log.ZWarn(ctx, "callback json unmarshal failed", err, "url", url, "input", input, "response", string(b)) - return errs.ErrData.WithDetail(err.Error() + "response format error") - } - if err := output.Parse(); err != nil { - log.ZWarn(ctx, "callback parse failed", err, "url", url, "input", input, "response", string(b)) - } - log.ZInfo(ctx, "callback success", "url", url, "input", input, "response", string(b)) - return nil -} - -func CallBackPostReturn(ctx context.Context, url string, req callbackstruct.CallbackReq, resp callbackstruct.CallbackResp, callbackConfig config.CallBackConfig) error { - return callBackPostReturn(ctx, url, req.GetCallbackCommand(), req, resp, callbackConfig) -} diff --git a/pkg/common/http/http_client_test.go b/pkg/common/http/http_client_test.go deleted file mode 100644 index 5d2588673..000000000 --- a/pkg/common/http/http_client_test.go +++ /dev/null @@ -1,154 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package http - -import ( - "context" - "reflect" - "testing" - - "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" -) - -func TestGet(t *testing.T) { - type args struct { - url string - } - tests := []struct { - name string - args args - wantResponse []byte - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotResponse, err := Get(tt.args.url) - if (err != nil) != tt.wantErr { - t.Errorf("Get() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotResponse, tt.wantResponse) { - t.Errorf("Get() = %v, want %v", gotResponse, tt.wantResponse) - } - }) - } -} - -func TestPost(t *testing.T) { - type args struct { - ctx context.Context - url string - header map[string]string - data any - timeout int - } - tests := []struct { - name string - args args - wantContent []byte - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - gotContent, err := Post(tt.args.ctx, tt.args.url, tt.args.header, tt.args.data, tt.args.timeout) - if (err != nil) != tt.wantErr { - t.Errorf("Post() error = %v, wantErr %v", err, tt.wantErr) - return - } - if !reflect.DeepEqual(gotContent, tt.wantContent) { - t.Errorf("Post() = %v, want %v", gotContent, tt.wantContent) - } - }) - } -} - -func TestPostReturn(t *testing.T) { - type args struct { - ctx context.Context - url string - header map[string]string - input any - output any - timeOutSecond int - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := PostReturn(tt.args.ctx, tt.args.url, tt.args.header, tt.args.input, tt.args.output, tt.args.timeOutSecond); (err != nil) != tt.wantErr { - t.Errorf("PostReturn() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func Test_callBackPostReturn(t *testing.T) { - type args struct { - ctx context.Context - url string - command string - input any - output callbackstruct.CallbackResp - callbackConfig config.CallBackConfig - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := callBackPostReturn(tt.args.ctx, tt.args.url, tt.args.command, tt.args.input, tt.args.output, tt.args.callbackConfig); (err != nil) != tt.wantErr { - t.Errorf("callBackPostReturn() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} - -func TestCallBackPostReturn(t *testing.T) { - type args struct { - ctx context.Context - url string - req callbackstruct.CallbackReq - resp callbackstruct.CallbackResp - callbackConfig config.CallBackConfig - } - tests := []struct { - name string - args args - wantErr bool - }{ - // TODO: Add test cases. - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := CallBackPostReturn(tt.args.ctx, tt.args.url, tt.args.req, tt.args.resp, tt.args.callbackConfig); (err != nil) != tt.wantErr { - t.Errorf("CallBackPostReturn() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/pkg/common/kafka/consumer.go b/pkg/common/kafka/consumer.go deleted file mode 100644 index 6627c21ae..000000000 --- a/pkg/common/kafka/consumer.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package kafka - -import ( - "sync" - - "github.com/IBM/sarama" - "github.com/OpenIMSDK/tools/errs" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" -) - -type Consumer struct { - addr []string - WG sync.WaitGroup - Topic string - PartitionList []int32 - Consumer sarama.Consumer -} - -func NewKafkaConsumer(addr []string, topic string, config *config.GlobalConfig) (*Consumer, error) { - p := Consumer{} - p.Topic = topic - p.addr = addr - consumerConfig := sarama.NewConfig() - if config.Kafka.Username != "" && config.Kafka.Password != "" { - consumerConfig.Net.SASL.Enable = true - consumerConfig.Net.SASL.User = config.Kafka.Username - consumerConfig.Net.SASL.Password = config.Kafka.Password - } - var tlsConfig *TLSConfig - if config.Kafka.TLS != nil { - tlsConfig = &TLSConfig{ - CACrt: config.Kafka.TLS.CACrt, - ClientCrt: config.Kafka.TLS.ClientCrt, - ClientKey: config.Kafka.TLS.ClientKey, - ClientKeyPwd: config.Kafka.TLS.ClientKeyPwd, - InsecureSkipVerify: false, - } - } - err := SetupTLSConfig(consumerConfig, tlsConfig) - if err != nil { - return nil, err - } - consumer, err := sarama.NewConsumer(p.addr, consumerConfig) - if err != nil { - return nil, errs.Wrap(err, "NewKafkaConsumer: creating consumer failed") - } - p.Consumer = consumer - - partitionList, err := consumer.Partitions(p.Topic) - if err != nil { - return nil, errs.Wrap(err, "NewKafkaConsumer: getting partitions failed") - } - p.PartitionList = partitionList - - return &p, nil - -} diff --git a/pkg/common/kafka/consumer_group.go b/pkg/common/kafka/consumer_group.go deleted file mode 100644 index 95794aacb..000000000 --- a/pkg/common/kafka/consumer_group.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package kafka - -import ( - "context" - "errors" - "strings" - - "github.com/IBM/sarama" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" -) - -type MConsumerGroup struct { - sarama.ConsumerGroup - groupID string - topics []string -} - -type MConsumerGroupConfig struct { - KafkaVersion sarama.KafkaVersion - OffsetsInitial int64 - IsReturnErr bool - UserName string - Password string -} - -func NewMConsumerGroup(consumerConfig *MConsumerGroupConfig, topics, addrs []string, groupID string, tlsConfig *TLSConfig) (*MConsumerGroup, error) { - consumerGroupConfig := sarama.NewConfig() - consumerGroupConfig.Version = consumerConfig.KafkaVersion - consumerGroupConfig.Consumer.Offsets.Initial = consumerConfig.OffsetsInitial - consumerGroupConfig.Consumer.Return.Errors = consumerConfig.IsReturnErr - if consumerConfig.UserName != "" && consumerConfig.Password != "" { - consumerGroupConfig.Net.SASL.Enable = true - consumerGroupConfig.Net.SASL.User = consumerConfig.UserName - consumerGroupConfig.Net.SASL.Password = consumerConfig.Password - } - - SetupTLSConfig(consumerGroupConfig, tlsConfig) - consumerGroup, err := sarama.NewConsumerGroup(addrs, groupID, consumerGroupConfig) - if err != nil { - return nil, errs.Wrap(err, strings.Join(topics, ","), strings.Join(addrs, ","), groupID, consumerConfig.UserName, consumerConfig.Password) - } - - return &MConsumerGroup{ - consumerGroup, - groupID, - topics, - }, nil -} - -func (mc *MConsumerGroup) GetContextFromMsg(cMsg *sarama.ConsumerMessage) context.Context { - return GetContextWithMQHeader(cMsg.Headers) -} - -func (mc *MConsumerGroup) RegisterHandleAndConsumer(ctx context.Context, handler sarama.ConsumerGroupHandler) { - log.ZDebug(ctx, "register consumer group", "groupID", mc.groupID) - for { - err := mc.ConsumerGroup.Consume(ctx, mc.topics, handler) - if errors.Is(err, sarama.ErrClosedConsumerGroup) { - return - } - if errors.Is(err, context.Canceled) { - return - } - if err != nil { - log.ZWarn(ctx, "consume err", err, "topic", mc.topics, "groupID", mc.groupID) - } - } -} - -func (mc *MConsumerGroup) Close() error { - return mc.ConsumerGroup.Close() -} diff --git a/pkg/common/kafka/producer.go b/pkg/common/kafka/producer.go deleted file mode 100644 index 8ee938b51..000000000 --- a/pkg/common/kafka/producer.go +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package kafka - -import ( - "bytes" - "context" - "errors" - "fmt" - "strings" - "time" - - "github.com/IBM/sarama" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/mcontext" - "google.golang.org/protobuf/proto" -) - -const maxRetry = 10 // number of retries - -var errEmptyMsg = errors.New("kafka binary msg is empty") - -// Producer represents a Kafka producer. -type Producer struct { - addr []string - topic string - config *sarama.Config - producer sarama.SyncProducer -} - -type ProducerConfig struct { - ProducerAck string - CompressType string - Username string - Password string -} - -// NewKafkaProducer initializes a new Kafka producer. -func NewKafkaProducer(addr []string, topic string, producerConfig *ProducerConfig, tlsConfig *TLSConfig) (*Producer, error) { - p := Producer{ - addr: addr, - topic: topic, - config: sarama.NewConfig(), - } - - // Set producer return flags - p.config.Producer.Return.Successes = true - p.config.Producer.Return.Errors = true - - // Set partitioner strategy - p.config.Producer.Partitioner = sarama.NewHashPartitioner - - // Configure producer acknowledgement level - configureProducerAck(&p, producerConfig.ProducerAck) - - // Configure message compression - configureCompression(&p, producerConfig.CompressType) - - // Get Kafka configuration from environment variables or fallback to config file - kafkaUsername := getEnvOrConfig("KAFKA_USERNAME", producerConfig.Username) - kafkaPassword := getEnvOrConfig("KAFKA_PASSWORD", producerConfig.Password) - kafkaAddr := getKafkaAddrFromEnv(addr) // Updated to use the new function - - // Configure SASL authentication if credentials are provided - if kafkaUsername != "" && kafkaPassword != "" { - p.config.Net.SASL.Enable = true - p.config.Net.SASL.User = kafkaUsername - p.config.Net.SASL.Password = kafkaPassword - } - - // Set the Kafka address - p.addr = kafkaAddr - - // Set up TLS configuration (if required) - SetupTLSConfig(p.config, tlsConfig) - - // Create the producer with retries - var err error - for i := 0; i <= maxRetry; i++ { - p.producer, err = sarama.NewSyncProducer(p.addr, p.config) - if err == nil { - return &p, errs.Wrap(err) - } - time.Sleep(1 * time.Second) // Wait before retrying - } - // Panic if unable to create producer after retries - if err != nil { - return nil, errs.Wrap(errors.New("failed to create Kafka producer: " + err.Error())) - } - - return &p, nil -} - -// configureProducerAck configures the producer's acknowledgement level. -func configureProducerAck(p *Producer, ackConfig string) { - switch strings.ToLower(ackConfig) { - case "no_response": - p.config.Producer.RequiredAcks = sarama.NoResponse - case "wait_for_local": - p.config.Producer.RequiredAcks = sarama.WaitForLocal - case "wait_for_all": - p.config.Producer.RequiredAcks = sarama.WaitForAll - default: - p.config.Producer.RequiredAcks = sarama.WaitForAll - } -} - -// configureCompression configures the message compression type for the producer. -func configureCompression(p *Producer, compressType string) { - var compress = sarama.CompressionNone - err := compress.UnmarshalText(bytes.ToLower([]byte(compressType))) - if err != nil { - fmt.Printf("Failed to configure compression: %v\n", err) - return - } - p.config.Producer.Compression = compress -} - -// GetMQHeaderWithContext extracts message queue headers from the context. -func GetMQHeaderWithContext(ctx context.Context) ([]sarama.RecordHeader, error) { - operationID, opUserID, platform, connID, err := mcontext.GetCtxInfos(ctx) - if err != nil { - return nil, err - } - return []sarama.RecordHeader{ - {Key: []byte(constant.OperationID), Value: []byte(operationID)}, - {Key: []byte(constant.OpUserID), Value: []byte(opUserID)}, - {Key: []byte(constant.OpUserPlatform), Value: []byte(platform)}, - {Key: []byte(constant.ConnID), Value: []byte(connID)}, - }, nil -} - -// GetContextWithMQHeader creates a context from message queue headers. -func GetContextWithMQHeader(header []*sarama.RecordHeader) context.Context { - var values []string - for _, recordHeader := range header { - values = append(values, string(recordHeader.Value)) - } - return mcontext.WithMustInfoCtx(values) // Attach extracted values to context -} - -// SendMessage sends a message to the Kafka topic configured in the Producer. -func (p *Producer) SendMessage(ctx context.Context, key string, msg proto.Message) (int32, int64, error) { - log.ZDebug(ctx, "SendMessage", "msg", msg, "topic", p.topic, "key", key) - - // Marshal the protobuf message - bMsg, err := proto.Marshal(msg) - if err != nil { - return 0, 0, errs.Wrap(err, "kafka proto Marshal err") - } - if len(bMsg) == 0 { - return 0, 0, errs.Wrap(errEmptyMsg, "") - } - - // Prepare Kafka message - kMsg := &sarama.ProducerMessage{ - Topic: p.topic, - Key: sarama.StringEncoder(key), - Value: sarama.ByteEncoder(bMsg), - } - - // Validate message key and value - if kMsg.Key.Length() == 0 || kMsg.Value.Length() == 0 { - return 0, 0, errs.Wrap(errEmptyMsg) - } - - // Attach context metadata as headers - header, err := GetMQHeaderWithContext(ctx) - if err != nil { - return 0, 0, err - } - kMsg.Headers = header - - // Send the message - partition, offset, err := p.producer.SendMessage(kMsg) - if err != nil { - log.ZWarn(ctx, "p.producer.SendMessage error", err) - return 0, 0, errs.Wrap(err) - } - - log.ZDebug(ctx, "ByteEncoder SendMessage end", "key", kMsg.Key, "key length", kMsg.Value.Length()) - return partition, offset, nil -} diff --git a/pkg/common/kafka/util.go b/pkg/common/kafka/util.go deleted file mode 100644 index 4e2a02714..000000000 --- a/pkg/common/kafka/util.go +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package kafka - -import ( - "fmt" - "os" - "strings" - - "github.com/IBM/sarama" - "github.com/openimsdk/open-im-server/v3/pkg/common/tls" -) - -type TLSConfig struct { - CACrt string - ClientCrt string - ClientKey string - ClientKeyPwd string - InsecureSkipVerify bool -} - -// SetupTLSConfig set up the TLS config from config file. -func SetupTLSConfig(cfg *sarama.Config, tlsConfig *TLSConfig) error { - if tlsConfig != nil { - cfg.Net.TLS.Enable = true - tlsConfig, err := tls.NewTLSConfig( - tlsConfig.ClientCrt, - tlsConfig.ClientKey, - tlsConfig.CACrt, - []byte(tlsConfig.ClientKeyPwd), - tlsConfig.InsecureSkipVerify, - ) - if err != nil { - return err - } - cfg.Net.TLS.Config = tlsConfig - } - return nil -} - -// getEnvOrConfig returns the value of the environment variable if it exists, -// otherwise, it returns the value from the configuration file. -func getEnvOrConfig(envName string, configValue string) string { - if value, exists := os.LookupEnv(envName); exists { - return value - } - return configValue -} - -// getKafkaAddrFromEnv returns the Kafka addresses combined from the KAFKA_ADDRESS and KAFKA_PORT environment variables. -// If the environment variables are not set, it returns the fallback value. -func getKafkaAddrFromEnv(fallback []string) []string { - envAddr := os.Getenv("KAFKA_ADDRESS") - envPort := os.Getenv("KAFKA_PORT") - - if envAddr != "" && envPort != "" { - addresses := strings.Split(envAddr, ",") - for i, addr := range addresses { - addresses[i] = fmt.Sprintf("%s:%s", addr, envPort) - } - return addresses - } - - return fallback -} diff --git a/pkg/common/prommetrics/doc.go b/pkg/common/prommetrics/doc.go new file mode 100644 index 000000000..c5108b4cb --- /dev/null +++ b/pkg/common/prommetrics/doc.go @@ -0,0 +1,15 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package prommetrics // import "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" diff --git a/pkg/common/prommetrics/prommetrics.go b/pkg/common/prommetrics/prommetrics.go index 9089e7b5f..47e5d02b8 100644 --- a/pkg/common/prommetrics/prommetrics.go +++ b/pkg/common/prommetrics/prommetrics.go @@ -31,17 +31,17 @@ func NewGrpcPromObj(cusMetrics []prometheus.Collector) (*prometheus.Registry, *g return reg, grpcMetrics, nil } -func GetGrpcCusMetrics(registerName string, config *config2.GlobalConfig) []prometheus.Collector { +func GetGrpcCusMetrics(registerName string, share *config2.Share) []prometheus.Collector { switch registerName { - case config.RpcRegisterName.OpenImMessageGatewayName: + case share.RpcRegisterName.MessageGateway: return []prometheus.Collector{OnlineUserGauge} - case config.RpcRegisterName.OpenImMsgName: + case share.RpcRegisterName.Msg: return []prometheus.Collector{SingleChatMsgProcessSuccessCounter, SingleChatMsgProcessFailedCounter, GroupChatMsgProcessSuccessCounter, GroupChatMsgProcessFailedCounter} case "Transfer": return []prometheus.Collector{MsgInsertRedisSuccessCounter, MsgInsertRedisFailedCounter, MsgInsertMongoSuccessCounter, MsgInsertMongoFailedCounter, SeqSetFailedCounter} - case config.RpcRegisterName.OpenImPushName: + case share.RpcRegisterName.Push: return []prometheus.Collector{MsgOfflinePushFailedCounter} - case config.RpcRegisterName.OpenImAuthName: + case share.RpcRegisterName.Auth: return []prometheus.Collector{UserLoginCounter} default: return nil diff --git a/pkg/common/prommetrics/prommetrics_test.go b/pkg/common/prommetrics/prommetrics_test.go index eb6f3c771..65b05652f 100644 --- a/pkg/common/prommetrics/prommetrics_test.go +++ b/pkg/common/prommetrics/prommetrics_test.go @@ -19,8 +19,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/stretchr/testify/assert" - - config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config" ) func TestNewGrpcPromObj(t *testing.T) { @@ -57,22 +55,22 @@ func TestNewGrpcPromObj(t *testing.T) { assert.True(t, found, "Custom metric not found in registry") } -func TestGetGrpcCusMetrics(t *testing.T) { - conf := config2.NewGlobalConfig() - - config2.InitConfig(conf, "../../config") - // Test various cases based on the switch statement in the GetGrpcCusMetrics function. - testCases := []struct { - name string - expected int // The expected number of metrics for each case. - }{ - {conf.RpcRegisterName.OpenImMessageGatewayName, 1}, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - metrics := GetGrpcCusMetrics(tc.name, conf) - assert.Len(t, metrics, tc.expected) - }) - } -} +//func TestGetGrpcCusMetrics(t *testing.T) { +// conf := config2.NewGlobalConfig() +// +// config2.InitConfig(conf, "../../config") +// // Test various cases based on the switch statement in the GetGrpcCusMetrics function. +// testCases := []struct { +// name string +// expected int // The expected number of metrics for each case. +// }{ +// {conf.RpcRegisterName.OpenImMessageGatewayName, 1}, +// } +// +// for _, tc := range testCases { +// t.Run(tc.name, func(t *testing.T) { +// metrics := GetGrpcCusMetrics(tc.name, &conf.RpcRegisterName) +// assert.Len(t, metrics, tc.expected) +// }) +// } +//} diff --git a/pkg/common/redispubsub/doc.go b/pkg/common/redispubsub/doc.go new file mode 100644 index 000000000..19b2e38f2 --- /dev/null +++ b/pkg/common/redispubsub/doc.go @@ -0,0 +1,15 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package redispubsub // import "github.com/openimsdk/open-im-server/v3/pkg/common/redispubsub" diff --git a/pkg/common/servererrs/code.go b/pkg/common/servererrs/code.go new file mode 100644 index 000000000..3d0aa4a71 --- /dev/null +++ b/pkg/common/servererrs/code.go @@ -0,0 +1,95 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package servererrs + +// UnknownCode represents the error code when code is not parsed or parsed code equals 0. +const UnknownCode = 1000 + +// Error codes for various error scenarios. +const ( + FormattingError = 10001 // Error in formatting + HasRegistered = 10002 // user has already registered + NotRegistered = 10003 // user is not registered + PasswordErr = 10004 // Password error + GetIMTokenErr = 10005 // Error in getting IM token + RepeatSendCode = 10006 // Repeat sending code + MailSendCodeErr = 10007 // Error in sending code via email + SmsSendCodeErr = 10008 // Error in sending code via SMS + CodeInvalidOrExpired = 10009 // Code is invalid or expired + RegisterFailed = 10010 // Registration failed + ResetPasswordFailed = 10011 // Resetting password failed + RegisterLimit = 10012 // Registration limit exceeded + LoginLimit = 10013 // Login limit exceeded + InvitationError = 10014 // Error in invitation +) + +// General error codes. +const ( + NoError = 0 // No error + DatabaseError = 90002 // Database error (redis/mysql, etc.) + NetworkError = 90004 // Network error + DataError = 90007 // Data error + + CallbackError = 80000 + + // General error codes. + ServerInternalError = 500 // Server internal error + ArgsError = 1001 // Input parameter error + NoPermissionError = 1002 // Insufficient permission + DuplicateKeyError = 1003 + RecordNotFoundError = 1004 // Record does not exist + + // Account error codes. + UserIDNotFoundError = 1101 // UserID does not exist or is not registered + RegisteredAlreadyError = 1102 // user is already registered + + // Group error codes. + GroupIDNotFoundError = 1201 // GroupID does not exist + GroupIDExisted = 1202 // GroupID already exists + NotInGroupYetError = 1203 // Not in the group yet + DismissedAlreadyError = 1204 // Group has already been dismissed + GroupTypeNotSupport = 1205 + GroupRequestHandled = 1206 + + // Relationship error codes. + CanNotAddYourselfError = 1301 // Cannot add yourself as a friend + BlockedByPeer = 1302 // Blocked by the peer + NotPeersFriend = 1303 // Not the peer's friend + RelationshipAlreadyError = 1304 // Already in a friend relationship + + // Message error codes. + MessageHasReadDisable = 1401 + MutedInGroup = 1402 // Member muted in the group + MutedGroup = 1403 // Group is muted + MsgAlreadyRevoke = 1404 // Message already revoked + + // Token error codes. + TokenExpiredError = 1501 + TokenInvalidError = 1502 + TokenMalformedError = 1503 + TokenNotValidYetError = 1504 + TokenUnknownError = 1505 + TokenKickedError = 1506 + TokenNotExistError = 1507 + + // Long connection gateway error codes. + ConnOverMaxNumLimit = 1601 + ConnArgsErr = 1602 + PushMsgErr = 1603 + IOSBackgroundPushErr = 1604 + + // S3 error codes. + FileUploadedExpiredError = 1701 // Upload expired +) diff --git a/pkg/common/servererrs/doc.go b/pkg/common/servererrs/doc.go new file mode 100644 index 000000000..d408a7514 --- /dev/null +++ b/pkg/common/servererrs/doc.go @@ -0,0 +1 @@ +package servererrs // import "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" diff --git a/pkg/common/servererrs/predefine.go b/pkg/common/servererrs/predefine.go new file mode 100644 index 000000000..ab09aa512 --- /dev/null +++ b/pkg/common/servererrs/predefine.go @@ -0,0 +1,68 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package servererrs + +import "github.com/openimsdk/tools/errs" + +var ( + ErrDatabase = errs.NewCodeError(DatabaseError, "DatabaseError") + ErrNetwork = errs.NewCodeError(NetworkError, "NetworkError") + ErrCallback = errs.NewCodeError(CallbackError, "CallbackError") + ErrCallbackContinue = errs.NewCodeError(CallbackError, "ErrCallbackContinue") + + ErrInternalServer = errs.NewCodeError(ServerInternalError, "ServerInternalError") + ErrArgs = errs.NewCodeError(ArgsError, "ArgsError") + ErrNoPermission = errs.NewCodeError(NoPermissionError, "NoPermissionError") + ErrDuplicateKey = errs.NewCodeError(DuplicateKeyError, "DuplicateKeyError") + ErrRecordNotFound = errs.NewCodeError(RecordNotFoundError, "RecordNotFoundError") + + ErrUserIDNotFound = errs.NewCodeError(UserIDNotFoundError, "UserIDNotFoundError") + ErrGroupIDNotFound = errs.NewCodeError(GroupIDNotFoundError, "GroupIDNotFoundError") + ErrGroupIDExisted = errs.NewCodeError(GroupIDExisted, "GroupIDExisted") + + ErrNotInGroupYet = errs.NewCodeError(NotInGroupYetError, "NotInGroupYetError") + ErrDismissedAlready = errs.NewCodeError(DismissedAlreadyError, "DismissedAlreadyError") + ErrRegisteredAlready = errs.NewCodeError(RegisteredAlreadyError, "RegisteredAlreadyError") + ErrGroupTypeNotSupport = errs.NewCodeError(GroupTypeNotSupport, "") + ErrGroupRequestHandled = errs.NewCodeError(GroupRequestHandled, "GroupRequestHandled") + + ErrData = errs.NewCodeError(DataError, "DataError") + ErrTokenExpired = errs.NewCodeError(TokenExpiredError, "TokenExpiredError") + ErrTokenInvalid = errs.NewCodeError(TokenInvalidError, "TokenInvalidError") // + ErrTokenMalformed = errs.NewCodeError(TokenMalformedError, "TokenMalformedError") // + ErrTokenNotValidYet = errs.NewCodeError(TokenNotValidYetError, "TokenNotValidYetError") // + ErrTokenUnknown = errs.NewCodeError(TokenUnknownError, "TokenUnknownError") // + ErrTokenKicked = errs.NewCodeError(TokenKickedError, "TokenKickedError") + ErrTokenNotExist = errs.NewCodeError(TokenNotExistError, "TokenNotExistError") // + + ErrMessageHasReadDisable = errs.NewCodeError(MessageHasReadDisable, "MessageHasReadDisable") + + ErrCanNotAddYourself = errs.NewCodeError(CanNotAddYourselfError, "CanNotAddYourselfError") + ErrBlockedByPeer = errs.NewCodeError(BlockedByPeer, "BlockedByPeer") + ErrNotPeersFriend = errs.NewCodeError(NotPeersFriend, "NotPeersFriend") + ErrRelationshipAlready = errs.NewCodeError(RelationshipAlreadyError, "RelationshipAlreadyError") + + ErrMutedInGroup = errs.NewCodeError(MutedInGroup, "MutedInGroup") + ErrMutedGroup = errs.NewCodeError(MutedGroup, "MutedGroup") + ErrMsgAlreadyRevoke = errs.NewCodeError(MsgAlreadyRevoke, "MsgAlreadyRevoke") + + ErrConnOverMaxNumLimit = errs.NewCodeError(ConnOverMaxNumLimit, "ConnOverMaxNumLimit") + + ErrConnArgsErr = errs.NewCodeError(ConnArgsErr, "args err, need token, sendID, platformID") + ErrPushMsgErr = errs.NewCodeError(PushMsgErr, "push msg err") + ErrIOSBackgroundPushErr = errs.NewCodeError(IOSBackgroundPushErr, "ios background push err") + + ErrFileUploadedExpired = errs.NewCodeError(FileUploadedExpiredError, "FileUploadedExpiredError") +) diff --git a/pkg/common/servererrs/relation.go b/pkg/common/servererrs/relation.go new file mode 100644 index 000000000..62b056147 --- /dev/null +++ b/pkg/common/servererrs/relation.go @@ -0,0 +1,58 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package servererrs + +import "github.com/openimsdk/tools/errs" + +var Relation = &relation{m: make(map[int]map[int]struct{})} + +func init() { + Relation.Add(errs.RecordNotFoundError, UserIDNotFoundError) + Relation.Add(errs.RecordNotFoundError, GroupIDNotFoundError) + Relation.Add(errs.DuplicateKeyError, GroupIDExisted) +} + +type relation struct { + m map[int]map[int]struct{} +} + +func (r *relation) Add(codes ...int) { + if len(codes) < 2 { + panic("codes length must be greater than 2") + } + for i := 1; i < len(codes); i++ { + parent := codes[i-1] + s, ok := r.m[parent] + if !ok { + s = make(map[int]struct{}) + r.m[parent] = s + } + for _, code := range codes[i:] { + s[code] = struct{}{} + } + } +} + +func (r *relation) Is(parent, child int) bool { + if parent == child { + return true + } + s, ok := r.m[parent] + if !ok { + return false + } + _, ok = s[child] + return ok +} diff --git a/pkg/common/startrpc/doc.go b/pkg/common/startrpc/doc.go index 206e7900f..fce7309f4 100644 --- a/pkg/common/startrpc/doc.go +++ b/pkg/common/startrpc/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,5 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Package startrpc start rpc server. -package startrpc +package startrpc // import "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" diff --git a/pkg/common/startrpc/start.go b/pkg/common/startrpc/start.go index bf95509d1..ebcd5aa7c 100644 --- a/pkg/common/startrpc/start.go +++ b/pkg/common/startrpc/start.go @@ -16,8 +16,10 @@ package startrpc import ( "context" - "errors" "fmt" + config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/utils/datautil" + "github.com/prometheus/client_golang/prometheus" "net" "net/http" "os" @@ -27,59 +29,61 @@ import ( "syscall" "time" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/mw" - "github.com/OpenIMSDK/tools/network" grpcprometheus "github.com/grpc-ecosystem/go-grpc-prometheus" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config" kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" - "github.com/prometheus/client_golang/prometheus" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mw" + "github.com/openimsdk/tools/system/program" + "github.com/openimsdk/tools/utils/network" "github.com/prometheus/client_golang/prometheus/promhttp" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) // Start rpc server. -func Start( - rpcPort int, - rpcRegisterName string, - prometheusPort int, - config *config2.GlobalConfig, - rpcFn func(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error, - options ...grpc.ServerOption, -) error { - fmt.Printf("start %s server, port: %d, prometheusPort: %d, OpenIM version: %s\n", - rpcRegisterName, rpcPort, prometheusPort, config2.Version) - rpcTcpAddr := net.JoinHostPort(network.GetListenIP(config.Rpc.ListenIP), strconv.Itoa(rpcPort)) +func Start[T any](ctx context.Context, zookeeperConfig *config2.ZooKeeper, prometheusConfig *config2.Prometheus, listenIP, + registerIP string, rpcPorts []int, index int, rpcRegisterName string, share *config2.Share, config T, rpcFn func(ctx context.Context, + config T, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error, options ...grpc.ServerOption) error { + + rpcPort, err := datautil.GetElemByIndex(rpcPorts, index) + if err != nil { + return err + } + prometheusPort, err := datautil.GetElemByIndex(prometheusConfig.Ports, index) + if err != nil { + return err + } + log.CInfo(ctx, "RPC server is initializing", "rpcRegisterName", rpcRegisterName, "rpcPort", rpcPort, + "prometheusPort", prometheusPort) + rpcTcpAddr := net.JoinHostPort(network.GetListenIP(listenIP), strconv.Itoa(rpcPort)) listener, err := net.Listen( "tcp", rpcTcpAddr, ) if err != nil { - return errs.Wrap(err, "listen err", rpcTcpAddr) + return errs.WrapMsg(err, "listen err", "rpcTcpAddr", rpcTcpAddr) } defer listener.Close() - client, err := kdisc.NewDiscoveryRegister(config) + client, err := kdisc.NewDiscoveryRegister(zookeeperConfig, share) if err != nil { return err } defer client.Close() client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) - registerIP, err := network.GetRpcRegisterIP(config.Rpc.RegisterIP) + registerIP, err = network.GetRpcRegisterIP(registerIP) if err != nil { - return errs.Wrap(err) + return err } var reg *prometheus.Registry var metric *grpcprometheus.ServerMetrics - if config.Prometheus.Enable { - cusMetrics := prommetrics.GetGrpcCusMetrics(rpcRegisterName, config) + if prometheusConfig.Enable { + cusMetrics := prommetrics.GetGrpcCusMetrics(rpcRegisterName, share) reg, metric, _ = prommetrics.NewGrpcPromObj(cusMetrics) options = append(options, mw.GrpcServer(), grpc.StreamInterceptor(metric.StreamServerInterceptor()), grpc.UnaryInterceptor(metric.UnaryServerInterceptor())) @@ -93,10 +97,11 @@ func Start( once.Do(srv.GracefulStop) }() - err = rpcFn(config, client, srv) + err = rpcFn(ctx, config, client, srv) if err != nil { return err } + err = client.Register( rpcRegisterName, registerIP, @@ -104,7 +109,7 @@ func Start( grpc.WithTransportCredentials(insecure.NewCredentials()), ) if err != nil { - return errs.Wrap(err) + return err } var ( @@ -112,13 +117,14 @@ func Start( netErr error httpServer *http.Server ) + go func() { - if config.Prometheus.Enable && prometheusPort != 0 { + if prometheusConfig.Enable && prometheusPort != 0 { metric.InitializeMetrics(srv) // Create a HTTP server for prometheus. httpServer = &http.Server{Handler: promhttp.HandlerFor(reg, promhttp.HandlerOpts{}), Addr: fmt.Sprintf("0.0.0.0:%d", prometheusPort)} if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - netErr = errs.Wrap(err, "prometheus start err", httpServer.Addr) + netErr = errs.WrapMsg(err, "prometheus start err", httpServer.Addr) netDone <- struct{}{} } } @@ -127,7 +133,7 @@ func Start( go func() { err := srv.Serve(listener) if err != nil { - netErr = errs.Wrap(err, "rpc start err: ", rpcTcpAddr) + netErr = errs.WrapMsg(err, "rpc start err: ", rpcTcpAddr) netDone <- struct{}{} } }() @@ -136,7 +142,7 @@ func Start( signal.Notify(sigs, syscall.SIGTERM) select { case <-sigs: - util.SIGTERMExit() + program.SIGTERMExit() ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) defer cancel() if err := gracefulStopWithCtx(ctx, srv.GracefulStop); err != nil { @@ -146,7 +152,7 @@ func Start( defer cancel() err := httpServer.Shutdown(ctx) if err != nil { - return errs.Wrap(err, "shutdown err") + return errs.WrapMsg(err, "shutdown err") } return nil case <-netDone: @@ -163,7 +169,7 @@ func gracefulStopWithCtx(ctx context.Context, f func()) error { }() select { case <-ctx.Done(): - return errs.Wrap(errors.New("timeout, ctx graceful stop")) + return errs.New("timeout, ctx graceful stop") case <-done: return nil } diff --git a/pkg/common/startrpc/start_test.go b/pkg/common/startrpc/start_test.go deleted file mode 100644 index 754fc9c50..000000000 --- a/pkg/common/startrpc/start_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package startrpc - -import ( - "fmt" - "net" - "testing" - "time" - - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - - "github.com/OpenIMSDK/tools/discoveryregistry" - "google.golang.org/grpc" -) - -// mockRpcFn is a mock gRPC function for testing. -func mockRpcFn(config *config.GlobalConfig, client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) error { - // Implement a mock gRPC service registration logic if needed - return nil -} - -// TestStart tests the Start function for starting the RPC server. -func TestStart(t *testing.T) { - // Use an available port for testing purposes. - testRpcPort := 12345 - testPrometheusPort := 12346 - testRpcRegisterName := "testService" - - doneChan := make(chan error, 1) - - go func() { - err := Start(testRpcPort, testRpcRegisterName, testPrometheusPort, - config.NewGlobalConfig(), mockRpcFn) - doneChan <- err - }() - - // Give some time for the server to start. - time.Sleep(2 * time.Second) - - // Test if the server is listening on the RPC port. - conn, err := net.Dial("tcp", fmt.Sprintf(":%d", testRpcPort)) - if err != nil { - // t.Fatalf("Failed to dial the RPC server: %v", err) - // TODO: Fix this test - t.Skip("Failed to dial the RPC server") - } - conn.Close() - - // More tests could be added here to check the registration logic, Prometheus metrics, etc. - - // Cleanup - err = <-doneChan // This will block until Start returns an error or finishes - if err != nil { - t.Fatalf("Start returned an error: %v", err) - } -} diff --git a/pkg/common/tls/tls.go b/pkg/common/tls/tls.go deleted file mode 100755 index 9666ed9c8..000000000 --- a/pkg/common/tls/tls.go +++ /dev/null @@ -1,88 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package tls - -import ( - "crypto/tls" - "crypto/x509" - "encoding/pem" - "errors" - "os" - - "github.com/OpenIMSDK/tools/errs" -) - -// decryptPEM decrypts a PEM block using a password. -func decryptPEM(data []byte, passphrase []byte) ([]byte, error) { - if len(passphrase) == 0 { - return data, nil - } - b, _ := pem.Decode(data) - d, err := x509.DecryptPEMBlock(b, passphrase) - if err != nil { - return nil, err - } - return pem.EncodeToMemory(&pem.Block{ - Type: b.Type, - Bytes: d, - }), nil -} - -func readEncryptablePEMBlock(path string, pwd []byte) ([]byte, error) { - data, err := os.ReadFile(path) - if err != nil { - return nil, err - } - return decryptPEM(data, pwd) -} - -// NewTLSConfig setup the TLS config from general config file. -func NewTLSConfig(clientCertFile, clientKeyFile, caCertFile string, keyPwd []byte, insecureSkipVerify bool) (*tls.Config, error) { - tlsConfig := tls.Config{} - - if clientCertFile != "" && clientKeyFile != "" { - certPEMBlock, err := os.ReadFile(clientCertFile) - if err != nil { - return nil, errs.Wrap(err, "NewTLSConfig: failed to read client cert file") - } - keyPEMBlock, err := readEncryptablePEMBlock(clientKeyFile, keyPwd) - if err != nil { - return nil, err - } - - cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) - if err != nil { - return nil, errs.Wrap(err, "NewTLSConfig: failed to create X509 key pair") - } - tlsConfig.Certificates = []tls.Certificate{cert} - } - - if caCertFile != "" { - caCert, err := os.ReadFile(caCertFile) - if err != nil { - return nil, errs.Wrap(err, "NewTLSConfig: failed to read CA cert file") - } - - caCertPool := x509.NewCertPool() - if ok := caCertPool.AppendCertsFromPEM(caCert); !ok { - return nil, errors.New("NewTLSConfig: not a valid CA cert") - } - tlsConfig.RootCAs = caCertPool - } - - tlsConfig.InsecureSkipVerify = insecureSkipVerify - - return &tlsConfig, nil -} diff --git a/pkg/common/version/base.go b/pkg/common/version/base.go deleted file mode 100644 index 9a656e03a..000000000 --- a/pkg/common/version/base.go +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package version - -// Base version information. -// -// This is the fallback data used when version information from git is not -// provided via go ldflags. It provides an approximation of the Kubernetes -// version for ad-hoc builds (e.g. `go build`) that cannot get the version -// information from git. -// -// If you are looking at these fields in the git tree, they look -// strange. They are modified on the fly by the build process. The -// in-tree values are dummy values used for "git archive", which also -// works for GitHub tar downloads. -// -// When releasing a new Kubernetes version, this file is updated by -// build/mark_new_version.sh to reflect the new version, and then a -// git annotated tag (using format vX.Y where X == Major version and Y -// == Minor version) is created to point to the commit that updates. -var ( - // TODO: Deprecate gitMajor and gitMinor, use only gitVersion - // instead. First step in deprecation, keep the fields but make - // them irrelevant. (Next we'll take it out, which may muck with - // scripts consuming the kubectl version output - but most of - // these should be looking at gitVersion already anyways.) - gitMajor string = "" // major version, always numeric - gitMinor string = "" // minor version, numeric possibly followed by "+" - - // semantic version, derived by build scripts (see - // https://github.com/kubernetes/sig-release/blob/master/release-engineering/versioning.md#kubernetes-release-versioning - // https://kubernetes.io/releases/version-skew-policy/ - // for a detailed discussion of this field) - // - // TODO: This field is still called "gitVersion" for legacy - // reasons. For prerelease versions, the build metadata on the - // semantic version is a git hash, but the version itself is no - // longer the direct output of "git describe", but a slight - // translation to be semver compliant. - - // NOTE: The $Format strings are replaced during 'git archive' thanks to the - // companion .gitattributes file containing 'export-subst' in this same - // directory. See also https://git-scm.com/docs/gitattributes - gitVersion string = "latest" - gitCommit string = "" // sha1 from git, output of $(git rev-parse HEAD) - gitTreeState string = "" // state of git tree, either "clean" or "dirty" - - buildDate string = "1970-01-01T00:00:00Z" // build date in ISO8601 format, output of $(date -u +'%Y-%m-%dT%H:%M:%SZ') -) diff --git a/pkg/common/version/types.go b/pkg/common/version/types.go deleted file mode 100644 index da9c1ed90..000000000 --- a/pkg/common/version/types.go +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package version - -// Info contains versioning information. -// TODO: Add []string of api versions supported? It's still unclear -// how we'll want to distribute that information. -type Info struct { - Major string `json:"major,omitempty"` - Minor string `json:"minor,omitempty"` - GitVersion string `json:"gitVersion"` - GitTreeState string `json:"gitTreeState,omitempty"` - GitCommit string `json:"gitCommit,omitempty"` - BuildDate string `json:"buildDate"` - GoVersion string `json:"goVersion"` - Compiler string `json:"compiler"` - Platform string `json:"platform"` -} - -type Output struct { - OpenIMServerVersion Info `json:"OpenIMServerVersion,omitempty" yaml:"OpenIMServerVersion,omitempty"` - OpenIMClientVersion *OpenIMClientVersion `json:"OpenIMClientVersion,omitempty" yaml:"OpenIMClientVersion,omitempty"` -} - -type OpenIMClientVersion struct { - ClientVersion string `json:"clientVersion,omitempty" yaml:"clientVersion,omitempty"` //sdk core version -} - -// String returns info as a human-friendly version string. -func (info Info) String() string { - return info.GitVersion -} diff --git a/pkg/common/version/version.go b/pkg/common/version/version.go deleted file mode 100644 index 3b271b3f6..000000000 --- a/pkg/common/version/version.go +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package version - -import ( - "fmt" - "runtime" - - "gopkg.in/src-d/go-git.v4" -) - -// Get returns the overall codebase version. It's for detecting -// what code a binary was built from. -func Get() Info { - // These variables typically come from -ldflags settings and in - // their absence fallback to the settings in ./base.go - return Info{ - Major: gitMajor, - Minor: gitMinor, - GitVersion: gitVersion, - GitTreeState: gitTreeState, - GitCommit: gitCommit, - BuildDate: buildDate, - GoVersion: runtime.Version(), - Compiler: runtime.Compiler, - Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH), - } -} - -// GetClientVersion returns the git version of the OpenIM client repository. -func GetClientVersion() (*OpenIMClientVersion, error) { - clientVersion, err := getClientVersion() - if err != nil { - return nil, err - } - return &OpenIMClientVersion{ - ClientVersion: clientVersion, - }, nil -} - -func getClientVersion() (string, error) { - repo, err := git.PlainClone("/tmp/openim-sdk-core", false, &git.CloneOptions{ - URL: "https://github.com/OpenIMSDK/openim-sdk-core", - }) - if err != nil { - return "", fmt.Errorf("error cloning repository: %w", err) - } - - ref, err := repo.Head() - if err != nil { - return "", fmt.Errorf("error getting head reference: %w", err) - } - - return ref.Hash().String(), nil -} - -// GetSingleVersion returns single version of sealer. -func GetSingleVersion() string { - return gitVersion -} diff --git a/pkg/common/webhook/condition.go b/pkg/common/webhook/condition.go new file mode 100644 index 000000000..2c038f567 --- /dev/null +++ b/pkg/common/webhook/condition.go @@ -0,0 +1,13 @@ +package webhook + +import ( + "context" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" +) + +func WithCondition(ctx context.Context, before *config.BeforeConfig, callback func(context.Context) error) error { + if !before.Enable { + return nil + } + return callback(ctx) +} diff --git a/pkg/common/db/s3/cont/doc.go b/pkg/common/webhook/doc.go similarity index 80% rename from pkg/common/db/s3/cont/doc.go rename to pkg/common/webhook/doc.go index 45737bc7c..3a8e3e622 100644 --- a/pkg/common/db/s3/cont/doc.go +++ b/pkg/common/webhook/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cont // import "github.com/openimsdk/open-im-server/v3/pkg/common/db/s3/cont" +package webhook // import "github.com/openimsdk/open-im-server/v3/pkg/common/webhook" diff --git a/pkg/common/webhook/http_client.go b/pkg/common/webhook/http_client.go new file mode 100644 index 000000000..e46f08806 --- /dev/null +++ b/pkg/common/webhook/http_client.go @@ -0,0 +1,86 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package webhook + +import ( + "context" + "encoding/json" + "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/mq/memamq" + "github.com/openimsdk/tools/utils/httputil" + "net/http" +) + +type Client struct { + client *httputil.HTTPClient + url string + queue *memamq.MemoryQueue +} + +const ( + webhookWorkerCount = 2 + webhookBufferSize = 100 +) + +func NewWebhookClient(url string, options ...*memamq.MemoryQueue) *Client { + var queue *memamq.MemoryQueue + if len(options) > 0 && options[0] != nil { + queue = options[0] + } else { + queue = memamq.NewMemoryQueue(webhookWorkerCount, webhookBufferSize) + } + + http.DefaultTransport.(*http.Transport).MaxConnsPerHost = 100 // Enhance the default number of max connections per host + + return &Client{ + client: httputil.NewHTTPClient(httputil.NewClientConfig()), + url: url, + queue: queue, + } +} + +func (c *Client) SyncPost(ctx context.Context, command string, req callbackstruct.CallbackReq, resp callbackstruct.CallbackResp, before *config.BeforeConfig) error { + return c.post(ctx, command, req, resp, before.Timeout) +} + +func (c *Client) AsyncPost(ctx context.Context, command string, req callbackstruct.CallbackReq, resp callbackstruct.CallbackResp, after *config.AfterConfig) { + if after.Enable { + c.queue.Push(func() { c.post(ctx, command, req, resp, after.Timeout) }) + } +} + +func (c *Client) post(ctx context.Context, command string, input interface{}, output callbackstruct.CallbackResp, timeout int) error { + ctx = mcontext.WithMustInfoCtx([]string{mcontext.GetOperationID(ctx), mcontext.GetOpUserID(ctx), mcontext.GetOpUserPlatform(ctx), mcontext.GetConnID(ctx)}) + fullURL := c.url + "/" + command + log.ZInfo(ctx, "webhook", "url", fullURL, "input", input, "config", timeout) + operationID, _ := ctx.Value(constant.OperationID).(string) + b, err := c.client.Post(ctx, fullURL, map[string]string{constant.OperationID: operationID}, input, timeout) + if err != nil { + return servererrs.ErrNetwork.WrapMsg(err.Error(), "post url", fullURL) + } + if err = json.Unmarshal(b, output); err != nil { + return servererrs.ErrData.WithDetail(err.Error() + " response format error") + } + if err := output.Parse(); err != nil { + return err + } + log.ZInfo(ctx, "webhook success", "url", fullURL, "input", input, "response", string(b)) + return nil +} diff --git a/internal/rpc/statistics/statistics.go b/pkg/common/webhook/http_client_test.go similarity index 96% rename from internal/rpc/statistics/statistics.go rename to pkg/common/webhook/http_client_test.go index 2f81301a1..3c3aeb809 100644 --- a/internal/rpc/statistics/statistics.go +++ b/pkg/common/webhook/http_client_test.go @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package statistics +package webhook diff --git a/pkg/localcache/cache.go b/pkg/localcache/cache.go index 91c490922..0e040ad38 100644 --- a/pkg/localcache/cache.go +++ b/pkg/localcache/cache.go @@ -19,8 +19,8 @@ import ( "hash/fnv" "unsafe" - "github.com/openimsdk/localcache/link" - "github.com/openimsdk/localcache/lru" + "github.com/openimsdk/open-im-server/v3/pkg/localcache/link" + "github.com/openimsdk/open-im-server/v3/pkg/localcache/lru" ) type Cache[V any] interface { diff --git a/pkg/localcache/doc.go b/pkg/localcache/doc.go new file mode 100644 index 000000000..24d44b090 --- /dev/null +++ b/pkg/localcache/doc.go @@ -0,0 +1,15 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package localcache // import "github.com/openimsdk/open-im-server/v3/pkg/localcache" diff --git a/pkg/localcache/go.mod b/pkg/localcache/go.mod deleted file mode 100644 index 5f0793042..000000000 --- a/pkg/localcache/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/openimsdk/localcache - -go 1.19 - -require github.com/hashicorp/golang-lru/v2 v2.0.7 diff --git a/pkg/localcache/link/doc.go b/pkg/localcache/link/doc.go new file mode 100644 index 000000000..e01e7d23d --- /dev/null +++ b/pkg/localcache/link/doc.go @@ -0,0 +1,15 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package link // import "github.com/openimsdk/open-im-server/v3/pkg/localcache/link" diff --git a/pkg/common/kafka/doc.go b/pkg/localcache/lru/doc.go similarity index 80% rename from pkg/common/kafka/doc.go rename to pkg/localcache/lru/doc.go index 0f0c69a76..50edcc11b 100644 --- a/pkg/common/kafka/doc.go +++ b/pkg/localcache/lru/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package kafka // import "github.com/openimsdk/open-im-server/v3/pkg/common/kafka" +package lru // import "github.com/openimsdk/open-im-server/v3/pkg/localcache/lru" diff --git a/pkg/localcache/lru/lru_lazy_test.go b/pkg/localcache/lru/lru_lazy_test.go index 167dd2135..ab0fa50a0 100644 --- a/pkg/localcache/lru/lru_lazy_test.go +++ b/pkg/localcache/lru/lru_lazy_test.go @@ -92,12 +92,7 @@ func TestName(t *testing.T) { defer wg.Done() //t.Log(key) fn(key, 10000, func() (string, error) { - //time.Sleep(time.Second * 3) - //t.Log(time.Now(), "key", key, "fetch") - //if rand.Uint32()%5 == 0 { - // return "value_" + key, nil - //} - //return "", errors.New("rand error") + return "value_" + key, nil }) }() diff --git a/pkg/localcache/option.go b/pkg/localcache/option.go index e60d5aa07..00bb9d044 100644 --- a/pkg/localcache/option.go +++ b/pkg/localcache/option.go @@ -18,7 +18,7 @@ import ( "context" "time" - "github.com/openimsdk/localcache/lru" + "github.com/openimsdk/open-im-server/v3/pkg/localcache/lru" ) func defaultOption() *option { diff --git a/pkg/msgprocessor/conversation.go b/pkg/msgprocessor/conversation.go index e3ea89fad..b369269cc 100644 --- a/pkg/msgprocessor/conversation.go +++ b/pkg/msgprocessor/conversation.go @@ -18,9 +18,9 @@ import ( "sort" "strings" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" "google.golang.org/protobuf/proto" ) @@ -30,9 +30,9 @@ func GetNotificationConversationIDByMsg(msg *sdkws.MsgData) string { l := []string{msg.SendID, msg.RecvID} sort.Strings(l) return "n_" + strings.Join(l, "_") - case constant.GroupChatType: + case constant.WriteGroupChatType: return "n_" + msg.GroupID - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: return "n_" + msg.GroupID case constant.NotificationChatType: return "n_" + msg.SendID + "_" + msg.RecvID @@ -46,9 +46,9 @@ func GetChatConversationIDByMsg(msg *sdkws.MsgData) string { l := []string{msg.SendID, msg.RecvID} sort.Strings(l) return "si_" + strings.Join(l, "_") - case constant.GroupChatType: + case constant.WriteGroupChatType: return "g_" + msg.GroupID - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: return "sg_" + msg.GroupID case constant.NotificationChatType: return "sn_" + msg.SendID + "_" + msg.RecvID @@ -63,7 +63,7 @@ func GenConversationUniqueKey(msg *sdkws.MsgData) string { l := []string{msg.SendID, msg.RecvID} sort.Strings(l) return strings.Join(l, "_") - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: return msg.GroupID } return "" @@ -79,12 +79,12 @@ func GetConversationIDByMsg(msg *sdkws.MsgData) string { return "n_" + strings.Join(l, "_") } return "si_" + strings.Join(l, "_") // single chat - case constant.GroupChatType: + case constant.WriteGroupChatType: if !options.IsNotNotification() { return "n_" + msg.GroupID // group chat } return "g_" + msg.GroupID // group chat - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: if !options.IsNotNotification() { return "n_" + msg.GroupID // super group chat } @@ -106,9 +106,9 @@ func GetConversationIDBySessionType(sessionType int, ids ...string) string { switch sessionType { case constant.SingleChatType: return "si_" + strings.Join(ids, "_") // single chat - case constant.GroupChatType: + case constant.WriteGroupChatType: return "g_" + ids[0] // group chat - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: return "sg_" + ids[0] // super group chat case constant.NotificationChatType: return "sn_" + ids[0] // server notification chat @@ -134,7 +134,7 @@ func GetNotificationConversationID(sessionType int, ids ...string) string { switch sessionType { case constant.SingleChatType: return "n_" + strings.Join(ids, "_") // single chat - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: return "n_" + ids[0] // super group chat } return "" @@ -158,7 +158,7 @@ func ParseConversationID(msg *sdkws.MsgData) (isNotification bool, conversationI return true, "n_" + strings.Join(l, "_") } return false, "si_" + strings.Join(l, "_") // single chat - case constant.SuperGroupChatType: + case constant.ReadGroupChatType: if !options.IsNotNotification() { return true, "n_" + msg.GroupID // super group chat } diff --git a/pkg/msgprocessor/conversation_test.go b/pkg/msgprocessor/conversation_test.go index c3fdb459d..32601baec 100644 --- a/pkg/msgprocessor/conversation_test.go +++ b/pkg/msgprocessor/conversation_test.go @@ -17,7 +17,7 @@ package msgprocessor import ( "testing" - "github.com/OpenIMSDK/protocol/sdkws" + "github.com/openimsdk/protocol/sdkws" "google.golang.org/protobuf/proto" ) diff --git a/pkg/msgprocessor/doc.go b/pkg/msgprocessor/doc.go index d1e24ce30..e8c96befb 100644 --- a/pkg/msgprocessor/doc.go +++ b/pkg/msgprocessor/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/msgprocessor/options.go b/pkg/msgprocessor/options.go index c6e209b98..406145ef4 100644 --- a/pkg/msgprocessor/options.go +++ b/pkg/msgprocessor/options.go @@ -14,7 +14,7 @@ package msgprocessor -import "github.com/OpenIMSDK/protocol/constant" +import "github.com/openimsdk/protocol/constant" type ( Options map[string]bool diff --git a/pkg/rpccache/conversation.go b/pkg/rpccache/conversation.go index 027ef42fa..55897a8da 100644 --- a/pkg/rpccache/conversation.go +++ b/pkg/rpccache/conversation.go @@ -17,18 +17,18 @@ package rpccache import ( "context" - pbconversation "github.com/OpenIMSDK/protocol/conversation" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/openimsdk/localcache" "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/localcache" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + pbconversation "github.com/openimsdk/protocol/conversation" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" "github.com/redis/go-redis/v9" ) -func NewConversationLocalCache(client rpcclient.ConversationRpcClient, cli redis.UniversalClient) *ConversationLocalCache { - lc := config.Config.LocalCache.Conversation +func NewConversationLocalCache(client rpcclient.ConversationRpcClient, localCache *config.LocalCache, cli redis.UniversalClient) *ConversationLocalCache { + lc := localCache.Conversation log.ZDebug(context.Background(), "ConversationLocalCache", "topic", lc.Topic, "slotNum", lc.SlotNum, "slotSize", lc.SlotSize, "enable", lc.Enable()) x := &ConversationLocalCache{ client: client, diff --git a/pkg/common/http/doc.go b/pkg/rpccache/doc.go similarity index 81% rename from pkg/common/http/doc.go rename to pkg/rpccache/doc.go index e6fd3ec1e..4244bb239 100644 --- a/pkg/common/http/doc.go +++ b/pkg/rpccache/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,4 +12,4 @@ // See the License for the specific language governing permissions and // limitations under the License. -package http // import "github.com/openimsdk/open-im-server/v3/pkg/common/http" +package rpccache // import "github.com/openimsdk/open-im-server/v3/pkg/rpccache" diff --git a/pkg/rpccache/friend.go b/pkg/rpccache/friend.go index 35122fff5..3e9e7863a 100644 --- a/pkg/rpccache/friend.go +++ b/pkg/rpccache/friend.go @@ -17,16 +17,16 @@ package rpccache import ( "context" - "github.com/OpenIMSDK/tools/log" - "github.com/openimsdk/localcache" "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/localcache" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/tools/log" "github.com/redis/go-redis/v9" ) -func NewFriendLocalCache(client rpcclient.FriendRpcClient, cli redis.UniversalClient) *FriendLocalCache { - lc := config.Config.LocalCache.Friend +func NewFriendLocalCache(client rpcclient.FriendRpcClient, localCache *config.LocalCache, cli redis.UniversalClient) *FriendLocalCache { + lc := localCache.Friend log.ZDebug(context.Background(), "FriendLocalCache", "topic", lc.Topic, "slotNum", lc.SlotNum, "slotSize", lc.SlotSize, "enable", lc.Enable()) x := &FriendLocalCache{ client: client, diff --git a/pkg/rpccache/group.go b/pkg/rpccache/group.go index 402f63185..7ba22beb8 100644 --- a/pkg/rpccache/group.go +++ b/pkg/rpccache/group.go @@ -17,18 +17,18 @@ package rpccache import ( "context" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/openimsdk/localcache" "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/localcache" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" "github.com/redis/go-redis/v9" ) -func NewGroupLocalCache(client rpcclient.GroupRpcClient, cli redis.UniversalClient) *GroupLocalCache { - lc := config.Config.LocalCache.Group +func NewGroupLocalCache(client rpcclient.GroupRpcClient, localCache *config.LocalCache, cli redis.UniversalClient) *GroupLocalCache { + lc := localCache.Group log.ZDebug(context.Background(), "GroupLocalCache", "topic", lc.Topic, "slotNum", lc.SlotNum, "slotSize", lc.SlotSize, "enable", lc.Enable()) x := &GroupLocalCache{ client: client, diff --git a/pkg/rpccache/subscriber.go b/pkg/rpccache/subscriber.go index cd65094e2..3046f84b1 100644 --- a/pkg/rpccache/subscriber.go +++ b/pkg/rpccache/subscriber.go @@ -18,7 +18,7 @@ import ( "context" "encoding/json" - "github.com/OpenIMSDK/tools/log" + "github.com/openimsdk/tools/log" "github.com/redis/go-redis/v9" ) diff --git a/pkg/rpccache/user.go b/pkg/rpccache/user.go index c48b05821..0a7a4e4b8 100644 --- a/pkg/rpccache/user.go +++ b/pkg/rpccache/user.go @@ -17,18 +17,18 @@ package rpccache import ( "context" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/openimsdk/localcache" "github.com/openimsdk/open-im-server/v3/pkg/common/cachekey" "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/open-im-server/v3/pkg/localcache" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/log" "github.com/redis/go-redis/v9" ) -func NewUserLocalCache(client rpcclient.UserRpcClient, cli redis.UniversalClient) *UserLocalCache { - lc := config.Config.LocalCache.User +func NewUserLocalCache(client rpcclient.UserRpcClient, localCache *config.LocalCache, cli redis.UniversalClient) *UserLocalCache { + lc := localCache.User log.ZDebug(context.Background(), "UserLocalCache", "topic", lc.Topic, "slotNum", lc.SlotNum, "slotSize", lc.SlotSize, "enable", lc.Enable()) x := &UserLocalCache{ client: client, diff --git a/pkg/rpcclient/auth.go b/pkg/rpcclient/auth.go index 24597120f..6665936bd 100644 --- a/pkg/rpcclient/auth.go +++ b/pkg/rpcclient/auth.go @@ -16,26 +16,48 @@ package rpcclient import ( "context" - - "github.com/OpenIMSDK/protocol/auth" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/protocol/auth" + pbAuth "github.com/openimsdk/protocol/auth" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/system/program" "google.golang.org/grpc" ) -func NewAuth(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) *Auth { - conn, err := discov.GetConn(context.Background(), config.RpcRegisterName.OpenImAuthName) +func NewAuth(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) *Auth { + conn, err := discov.GetConn(context.Background(), rpcRegisterName) if err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } client := auth.NewAuthClient(conn) - return &Auth{discov: discov, conn: conn, Client: client, Config: config} + return &Auth{discov: discov, conn: conn, Client: client} } type Auth struct { conn grpc.ClientConnInterface Client auth.AuthClient - discov discoveryregistry.SvcDiscoveryRegistry - Config *config.GlobalConfig + discov discovery.SvcDiscoveryRegistry +} + +func (a *Auth) ParseToken(ctx context.Context, token string) (*pbAuth.ParseTokenResp, error) { + req := pbAuth.ParseTokenReq{ + Token: token, + } + resp, err := a.Client.ParseToken(ctx, &req) + if err != nil { + return nil, err + } + return resp, err +} + +func (a *Auth) InvalidateToken(ctx context.Context, preservedToken, userID string, platformID int) (*pbAuth.InvalidateTokenResp, error) { + req := pbAuth.InvalidateTokenReq{ + PreservedToken: preservedToken, + UserID: userID, + PlatformID: int32(platformID), + } + resp, err := a.Client.InvalidateToken(ctx, &req) + if err != nil { + return nil, err + } + return resp, err } diff --git a/pkg/rpcclient/conversation.go b/pkg/rpcclient/conversation.go index 127e029e1..6eb6c9461 100644 --- a/pkg/rpcclient/conversation.go +++ b/pkg/rpcclient/conversation.go @@ -18,34 +18,32 @@ import ( "context" "fmt" - pbconversation "github.com/OpenIMSDK/protocol/conversation" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + pbconversation "github.com/openimsdk/protocol/conversation" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/errs" + "github.com/openimsdk/tools/system/program" "google.golang.org/grpc" ) type Conversation struct { Client pbconversation.ConversationClient conn grpc.ClientConnInterface - discov discoveryregistry.SvcDiscoveryRegistry - Config *config.GlobalConfig + discov discovery.SvcDiscoveryRegistry } -func NewConversation(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) *Conversation { - conn, err := discov.GetConn(context.Background(), config.RpcRegisterName.OpenImConversationName) +func NewConversation(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) *Conversation { + conn, err := discov.GetConn(context.Background(), rpcRegisterName) if err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } client := pbconversation.NewConversationClient(conn) - return &Conversation{discov: discov, conn: conn, Client: client, Config: config} + return &Conversation{discov: discov, conn: conn, Client: client} } type ConversationRpcClient Conversation -func NewConversationRpcClient(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) ConversationRpcClient { - return ConversationRpcClient(*NewConversation(discov, config)) +func NewConversationRpcClient(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) ConversationRpcClient { + return ConversationRpcClient(*NewConversation(discov, rpcRegisterName)) } func (c *ConversationRpcClient) GetSingleConversationRecvMsgOpt(ctx context.Context, userID, conversationID string) (int32, error) { @@ -109,7 +107,7 @@ func (c *ConversationRpcClient) GetConversationsByConversationID(ctx context.Con return nil, err } if len(resp.Conversations) == 0 { - return nil, errs.ErrRecordNotFound.Wrap(fmt.Sprintf("conversationIDs: %v not found", conversationIDs)) + return nil, errs.ErrRecordNotFound.WrapMsg(fmt.Sprintf("conversationIDs: %v not found", conversationIDs)) } return resp.Conversations, nil } diff --git a/pkg/rpcclient/doc.go b/pkg/rpcclient/doc.go index 0fdfacc60..66b0aee91 100644 --- a/pkg/rpcclient/doc.go +++ b/pkg/rpcclient/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/rpcclient/friend.go b/pkg/rpcclient/friend.go index 5a5f38698..5543afe4f 100644 --- a/pkg/rpcclient/friend.go +++ b/pkg/rpcclient/friend.go @@ -17,34 +17,32 @@ package rpcclient import ( "context" - "github.com/OpenIMSDK/protocol/friend" - sdkws "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/protocol/friend" + sdkws "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/system/program" "google.golang.org/grpc" ) type Friend struct { conn grpc.ClientConnInterface Client friend.FriendClient - discov discoveryregistry.SvcDiscoveryRegistry - Config *config.GlobalConfig + discov discovery.SvcDiscoveryRegistry } -func NewFriend(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) *Friend { - conn, err := discov.GetConn(context.Background(), config.RpcRegisterName.OpenImFriendName) +func NewFriend(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) *Friend { + conn, err := discov.GetConn(context.Background(), rpcRegisterName) if err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } client := friend.NewFriendClient(conn) - return &Friend{discov: discov, conn: conn, Client: client, Config: config} + return &Friend{discov: discov, conn: conn, Client: client} } type FriendRpcClient Friend -func NewFriendRpcClient(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) FriendRpcClient { - return FriendRpcClient(*NewFriend(discov, config)) +func NewFriendRpcClient(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) FriendRpcClient { + return FriendRpcClient(*NewFriend(discov, rpcRegisterName)) } func (f *FriendRpcClient) GetFriendsInfo( diff --git a/pkg/rpcclient/group.go b/pkg/rpcclient/group.go index 3e9c93e7a..30d0b3288 100644 --- a/pkg/rpcclient/group.go +++ b/pkg/rpcclient/group.go @@ -18,35 +18,33 @@ import ( "context" "strings" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/group" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/utils" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/group" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/system/program" + "github.com/openimsdk/tools/utils/datautil" ) type Group struct { Client group.GroupClient - discov discoveryregistry.SvcDiscoveryRegistry - Config *config.GlobalConfig + discov discovery.SvcDiscoveryRegistry } -func NewGroup(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) *Group { - conn, err := discov.GetConn(context.Background(), config.RpcRegisterName.OpenImGroupName) +func NewGroup(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) *Group { + conn, err := discov.GetConn(context.Background(), rpcRegisterName) if err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } client := group.NewGroupClient(conn) - return &Group{discov: discov, Client: client, Config: config} + return &Group{discov: discov, Client: client} } type GroupRpcClient Group -func NewGroupRpcClient(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) GroupRpcClient { - return GroupRpcClient(*NewGroup(discov, config)) +func NewGroupRpcClient(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) GroupRpcClient { + return GroupRpcClient(*NewGroup(discov, rpcRegisterName)) } func (g *GroupRpcClient) GetGroupInfos(ctx context.Context, groupIDs []string, complete bool) ([]*sdkws.GroupInfo, error) { @@ -57,10 +55,10 @@ func (g *GroupRpcClient) GetGroupInfos(ctx context.Context, groupIDs []string, c return nil, err } if complete { - if ids := utils.Single(groupIDs, utils.Slice(resp.GroupInfos, func(e *sdkws.GroupInfo) string { + if ids := datautil.Single(groupIDs, datautil.Slice(resp.GroupInfos, func(e *sdkws.GroupInfo) string { return e.GroupID })); len(ids) > 0 { - return nil, errs.ErrGroupIDNotFound.Wrap(strings.Join(ids, ",")) + return nil, servererrs.ErrGroupIDNotFound.WrapMsg(strings.Join(ids, ",")) } } return resp.GroupInfos, nil @@ -83,7 +81,7 @@ func (g *GroupRpcClient) GetGroupInfoMap( if err != nil { return nil, err } - return utils.SliceToMap(groups, func(e *sdkws.GroupInfo) string { + return datautil.SliceToMap(groups, func(e *sdkws.GroupInfo) string { return e.GroupID }), nil } @@ -102,10 +100,10 @@ func (g *GroupRpcClient) GetGroupMemberInfos( return nil, err } if complete { - if ids := utils.Single(userIDs, utils.Slice(resp.Members, func(e *sdkws.GroupMemberFullInfo) string { + if ids := datautil.Single(userIDs, datautil.Slice(resp.Members, func(e *sdkws.GroupMemberFullInfo) string { return e.UserID })); len(ids) > 0 { - return nil, errs.ErrNotInGroupYet.Wrap(strings.Join(ids, ",")) + return nil, servererrs.ErrNotInGroupYet.WrapMsg(strings.Join(ids, ",")) } } return resp.Members, nil @@ -133,7 +131,7 @@ func (g *GroupRpcClient) GetGroupMemberInfoMap( if err != nil { return nil, err } - return utils.SliceToMap(members, func(e *sdkws.GroupMemberFullInfo) string { + return datautil.SliceToMap(members, func(e *sdkws.GroupMemberFullInfo) string { return e.UserID }), nil } diff --git a/pkg/rpcclient/grouphash/doc.go b/pkg/rpcclient/grouphash/doc.go new file mode 100644 index 000000000..c780701d9 --- /dev/null +++ b/pkg/rpcclient/grouphash/doc.go @@ -0,0 +1,15 @@ +// Copyright © 2024 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package grouphash // import "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/grouphash" diff --git a/pkg/rpcclient/grouphash/grouphash.go b/pkg/rpcclient/grouphash/grouphash.go index dee47ad44..5015b88c8 100644 --- a/pkg/rpcclient/grouphash/grouphash.go +++ b/pkg/rpcclient/grouphash/grouphash.go @@ -20,9 +20,9 @@ import ( "encoding/binary" "encoding/json" - "github.com/OpenIMSDK/protocol/group" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/utils" + "github.com/openimsdk/protocol/group" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/utils/datautil" ) func NewGroupHashFromGroupClient(x group.GroupClient) *GroupHash { @@ -79,9 +79,9 @@ func (gh *GroupHash) GetGroupHash(ctx context.Context, groupID string) (uint64, if err != nil { return 0, err } - utils.Sort(userIDs, true) + datautil.Sort(userIDs, true) } - memberMap := utils.SliceToMap(members, func(e *sdkws.GroupMemberFullInfo) string { + memberMap := datautil.SliceToMap(members, func(e *sdkws.GroupMemberFullInfo) string { return e.UserID }) res := make([]*sdkws.GroupMemberFullInfo, 0, len(members)) diff --git a/pkg/rpcclient/msg.go b/pkg/rpcclient/msg.go index 54a4671a3..36daf9f66 100644 --- a/pkg/rpcclient/msg.go +++ b/pkg/rpcclient/msg.go @@ -17,62 +17,64 @@ package rpcclient import ( "context" "encoding/json" - "fmt" - - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/msg" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/log" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/common/config" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/msg" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/log" + "github.com/openimsdk/tools/mcontext" + "github.com/openimsdk/tools/mq/memamq" + "github.com/openimsdk/tools/system/program" + "github.com/openimsdk/tools/utils/idutil" + "github.com/openimsdk/tools/utils/jsonutil" + "github.com/openimsdk/tools/utils/timeutil" "google.golang.org/grpc" "google.golang.org/protobuf/proto" + "time" ) -func newContentTypeConf(conf *config.GlobalConfig) map[int32]config.NotificationConf { - return map[int32]config.NotificationConf{ +func newContentTypeConf(conf *config.Notification) map[int32]config.NotificationConfig { + return map[int32]config.NotificationConfig{ // group - constant.GroupCreatedNotification: conf.Notification.GroupCreated, - constant.GroupInfoSetNotification: conf.Notification.GroupInfoSet, - constant.JoinGroupApplicationNotification: conf.Notification.JoinGroupApplication, - constant.MemberQuitNotification: conf.Notification.MemberQuit, - constant.GroupApplicationAcceptedNotification: conf.Notification.GroupApplicationAccepted, - constant.GroupApplicationRejectedNotification: conf.Notification.GroupApplicationRejected, - constant.GroupOwnerTransferredNotification: conf.Notification.GroupOwnerTransferred, - constant.MemberKickedNotification: conf.Notification.MemberKicked, - constant.MemberInvitedNotification: conf.Notification.MemberInvited, - constant.MemberEnterNotification: conf.Notification.MemberEnter, - constant.GroupDismissedNotification: conf.Notification.GroupDismissed, - constant.GroupMutedNotification: conf.Notification.GroupMuted, - constant.GroupCancelMutedNotification: conf.Notification.GroupCancelMuted, - constant.GroupMemberMutedNotification: conf.Notification.GroupMemberMuted, - constant.GroupMemberCancelMutedNotification: conf.Notification.GroupMemberCancelMuted, - constant.GroupMemberInfoSetNotification: conf.Notification.GroupMemberInfoSet, - constant.GroupMemberSetToAdminNotification: conf.Notification.GroupMemberSetToAdmin, - constant.GroupMemberSetToOrdinaryUserNotification: conf.Notification.GroupMemberSetToOrdinary, - constant.GroupInfoSetAnnouncementNotification: conf.Notification.GroupInfoSetAnnouncement, - constant.GroupInfoSetNameNotification: conf.Notification.GroupInfoSetName, + constant.GroupCreatedNotification: conf.GroupCreated, + constant.GroupInfoSetNotification: conf.GroupInfoSet, + constant.JoinGroupApplicationNotification: conf.JoinGroupApplication, + constant.MemberQuitNotification: conf.MemberQuit, + constant.GroupApplicationAcceptedNotification: conf.GroupApplicationAccepted, + constant.GroupApplicationRejectedNotification: conf.GroupApplicationRejected, + constant.GroupOwnerTransferredNotification: conf.GroupOwnerTransferred, + constant.MemberKickedNotification: conf.MemberKicked, + constant.MemberInvitedNotification: conf.MemberInvited, + constant.MemberEnterNotification: conf.MemberEnter, + constant.GroupDismissedNotification: conf.GroupDismissed, + constant.GroupMutedNotification: conf.GroupMuted, + constant.GroupCancelMutedNotification: conf.GroupCancelMuted, + constant.GroupMemberMutedNotification: conf.GroupMemberMuted, + constant.GroupMemberCancelMutedNotification: conf.GroupMemberCancelMuted, + constant.GroupMemberInfoSetNotification: conf.GroupMemberInfoSet, + constant.GroupMemberSetToAdminNotification: conf.GroupMemberSetToAdmin, + constant.GroupMemberSetToOrdinaryUserNotification: conf.GroupMemberSetToOrdinary, + constant.GroupInfoSetAnnouncementNotification: conf.GroupInfoSetAnnouncement, + constant.GroupInfoSetNameNotification: conf.GroupInfoSetName, // user - constant.UserInfoUpdatedNotification: conf.Notification.UserInfoUpdated, - constant.UserStatusChangeNotification: conf.Notification.UserStatusChanged, + constant.UserInfoUpdatedNotification: conf.UserInfoUpdated, + constant.UserStatusChangeNotification: conf.UserStatusChanged, // friend - constant.FriendApplicationNotification: conf.Notification.FriendApplicationAdded, - constant.FriendApplicationApprovedNotification: conf.Notification.FriendApplicationApproved, - constant.FriendApplicationRejectedNotification: conf.Notification.FriendApplicationRejected, - constant.FriendAddedNotification: conf.Notification.FriendAdded, - constant.FriendDeletedNotification: conf.Notification.FriendDeleted, - constant.FriendRemarkSetNotification: conf.Notification.FriendRemarkSet, - constant.BlackAddedNotification: conf.Notification.BlackAdded, - constant.BlackDeletedNotification: conf.Notification.BlackDeleted, - constant.FriendInfoUpdatedNotification: conf.Notification.FriendInfoUpdated, - constant.FriendsInfoUpdateNotification: conf.Notification.FriendInfoUpdated, //use the same FriendInfoUpdated + constant.FriendApplicationNotification: conf.FriendApplicationAdded, + constant.FriendApplicationApprovedNotification: conf.FriendApplicationApproved, + constant.FriendApplicationRejectedNotification: conf.FriendApplicationRejected, + constant.FriendAddedNotification: conf.FriendAdded, + constant.FriendDeletedNotification: conf.FriendDeleted, + constant.FriendRemarkSetNotification: conf.FriendRemarkSet, + constant.BlackAddedNotification: conf.BlackAdded, + constant.BlackDeletedNotification: conf.BlackDeleted, + constant.FriendInfoUpdatedNotification: conf.FriendInfoUpdated, + constant.FriendsInfoUpdateNotification: conf.FriendInfoUpdated, // use the same FriendInfoUpdated // conversation - constant.ConversationChangeNotification: conf.Notification.ConversationChanged, - constant.ConversationUnreadNotification: conf.Notification.ConversationChanged, - constant.ConversationPrivateChatNotification: conf.Notification.ConversationSetPrivate, + constant.ConversationChangeNotification: conf.ConversationChanged, + constant.ConversationUnreadNotification: conf.ConversationChanged, + constant.ConversationPrivateChatNotification: conf.ConversationSetPrivate, // msg constant.MsgRevokeNotification: {IsSendMsg: false, ReliabilityLevel: constant.ReliableNotificationNoMsg}, constant.HasReadReceipt: {IsSendMsg: false, ReliabilityLevel: constant.ReliableNotificationNoMsg}, @@ -83,26 +85,26 @@ func newContentTypeConf(conf *config.GlobalConfig) map[int32]config.Notification func newSessionTypeConf() map[int32]int32 { return map[int32]int32{ // group - constant.GroupCreatedNotification: constant.SuperGroupChatType, - constant.GroupInfoSetNotification: constant.SuperGroupChatType, + constant.GroupCreatedNotification: constant.ReadGroupChatType, + constant.GroupInfoSetNotification: constant.ReadGroupChatType, constant.JoinGroupApplicationNotification: constant.SingleChatType, - constant.MemberQuitNotification: constant.SuperGroupChatType, + constant.MemberQuitNotification: constant.ReadGroupChatType, constant.GroupApplicationAcceptedNotification: constant.SingleChatType, constant.GroupApplicationRejectedNotification: constant.SingleChatType, - constant.GroupOwnerTransferredNotification: constant.SuperGroupChatType, - constant.MemberKickedNotification: constant.SuperGroupChatType, - constant.MemberInvitedNotification: constant.SuperGroupChatType, - constant.MemberEnterNotification: constant.SuperGroupChatType, - constant.GroupDismissedNotification: constant.SuperGroupChatType, - constant.GroupMutedNotification: constant.SuperGroupChatType, - constant.GroupCancelMutedNotification: constant.SuperGroupChatType, - constant.GroupMemberMutedNotification: constant.SuperGroupChatType, - constant.GroupMemberCancelMutedNotification: constant.SuperGroupChatType, - constant.GroupMemberInfoSetNotification: constant.SuperGroupChatType, - constant.GroupMemberSetToAdminNotification: constant.SuperGroupChatType, - constant.GroupMemberSetToOrdinaryUserNotification: constant.SuperGroupChatType, - constant.GroupInfoSetAnnouncementNotification: constant.SuperGroupChatType, - constant.GroupInfoSetNameNotification: constant.SuperGroupChatType, + constant.GroupOwnerTransferredNotification: constant.ReadGroupChatType, + constant.MemberKickedNotification: constant.ReadGroupChatType, + constant.MemberInvitedNotification: constant.ReadGroupChatType, + constant.MemberEnterNotification: constant.ReadGroupChatType, + constant.GroupDismissedNotification: constant.ReadGroupChatType, + constant.GroupMutedNotification: constant.ReadGroupChatType, + constant.GroupCancelMutedNotification: constant.ReadGroupChatType, + constant.GroupMemberMutedNotification: constant.ReadGroupChatType, + constant.GroupMemberCancelMutedNotification: constant.ReadGroupChatType, + constant.GroupMemberInfoSetNotification: constant.ReadGroupChatType, + constant.GroupMemberSetToAdminNotification: constant.ReadGroupChatType, + constant.GroupMemberSetToOrdinaryUserNotification: constant.ReadGroupChatType, + constant.GroupInfoSetAnnouncementNotification: constant.ReadGroupChatType, + constant.GroupInfoSetNameNotification: constant.ReadGroupChatType, // user constant.UserInfoUpdatedNotification: constant.SingleChatType, constant.UserStatusChangeNotification: constant.SingleChatType, @@ -129,23 +131,22 @@ func newSessionTypeConf() map[int32]int32 { type Message struct { conn grpc.ClientConnInterface Client msg.MsgClient - discov discoveryregistry.SvcDiscoveryRegistry - Config *config.GlobalConfig + discov discovery.SvcDiscoveryRegistry } -func NewMessage(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) *Message { - conn, err := discov.GetConn(context.Background(), config.RpcRegisterName.OpenImMsgName) +func NewMessage(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) *Message { + conn, err := discov.GetConn(context.Background(), rpcRegisterName) if err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } client := msg.NewMsgClient(conn) - return &Message{discov: discov, conn: conn, Client: client, Config: config} + return &Message{discov: discov, conn: conn, Client: client} } type MessageRpcClient Message -func NewMessageRpcClient(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) MessageRpcClient { - return MessageRpcClient(*NewMessage(discov, config)) +func NewMessageRpcClient(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) MessageRpcClient { + return MessageRpcClient(*NewMessage(discov, rpcRegisterName)) } // SendMsg sends a message through the gRPC client and returns the response. @@ -212,10 +213,17 @@ func (m *MessageRpcClient) GetConversationMaxSeq(ctx context.Context, conversati } type NotificationSender struct { - contentTypeConf map[int32]config.NotificationConf + contentTypeConf map[int32]config.NotificationConfig sessionTypeConf map[int32]int32 sendMsg func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) getUserInfo func(ctx context.Context, userID string) (*sdkws.UserInfo, error) + queue *memamq.MemoryQueue +} + +func WithQueue(queue *memamq.MemoryQueue) NotificationSenderOptions { + return func(s *NotificationSender) { + s.queue = queue + } } type NotificationSenderOptions func(*NotificationSender) @@ -238,11 +246,19 @@ func WithUserRpcClient(userRpcClient *UserRpcClient) NotificationSenderOptions { } } -func NewNotificationSender(config *config.GlobalConfig, opts ...NotificationSenderOptions) *NotificationSender { - notificationSender := &NotificationSender{contentTypeConf: newContentTypeConf(config), sessionTypeConf: newSessionTypeConf()} +const ( + notificationWorkerCount = 2 + notificationBufferSize = 200 +) + +func NewNotificationSender(conf *config.Notification, opts ...NotificationSenderOptions) *NotificationSender { + notificationSender := &NotificationSender{contentTypeConf: newContentTypeConf(conf), sessionTypeConf: newSessionTypeConf()} for _, opt := range opts { opt(notificationSender) } + if notificationSender.queue == nil { + notificationSender.queue = memamq.NewMemoryQueue(notificationWorkerCount, notificationBufferSize) + } return notificationSender } @@ -258,12 +274,15 @@ func WithRpcGetUserName() NotificationOptions { } } -func (s *NotificationSender) NotificationWithSesstionType(ctx context.Context, sendID, recvID string, contentType, sesstionType int32, m proto.Message, opts ...NotificationOptions) (err error) { - n := sdkws.NotificationElem{Detail: utils.StructToJsonString(m)} +func (s *NotificationSender) send(ctx context.Context, sendID, recvID string, contentType, sessionType int32, m proto.Message, opts ...NotificationOptions) { + ctx = mcontext.WithMustInfoCtx([]string{mcontext.GetOperationID(ctx), mcontext.GetOpUserID(ctx), mcontext.GetOpUserPlatform(ctx), mcontext.GetConnID(ctx)}) + ctx, cancel := context.WithTimeout(ctx, time.Second*time.Duration(5)) + defer cancel() + n := sdkws.NotificationElem{Detail: jsonutil.StructToJsonString(m)} content, err := json.Marshal(&n) if err != nil { - errInfo := fmt.Sprintf("MsgClient Notification json.Marshal failed, sendID:%s, recvID:%s, contentType:%d, msg:%s", sendID, recvID, contentType, m) - return errs.Wrap(err, errInfo) + log.ZWarn(ctx, "json.Marshal failed", err, "sendID", sendID, "recvID", recvID, "contentType", contentType, "msg", jsonutil.StructToJsonString(m)) + return } notificationOpt := ¬ificationOpt{} for _, opt := range opts { @@ -275,26 +294,24 @@ func (s *NotificationSender) NotificationWithSesstionType(ctx context.Context, s if notificationOpt.WithRpcGetUsername && s.getUserInfo != nil { userInfo, err = s.getUserInfo(ctx, sendID) if err != nil { - errInfo := fmt.Sprintf("getUserInfo failed, sendID:%s", sendID) - return errs.Wrap(err, errInfo) - } else { - msg.SenderNickname = userInfo.Nickname - msg.SenderFaceURL = userInfo.FaceURL + log.ZWarn(ctx, "getUserInfo failed", err, "sendID", sendID) + return } + msg.SenderNickname = userInfo.Nickname + msg.SenderFaceURL = userInfo.FaceURL } var offlineInfo sdkws.OfflinePushInfo - var title, desc, ex string msg.SendID = sendID msg.RecvID = recvID msg.Content = content msg.MsgFrom = constant.SysMsgType msg.ContentType = contentType - msg.SessionType = sesstionType - if msg.SessionType == constant.SuperGroupChatType { + msg.SessionType = sessionType + if msg.SessionType == constant.ReadGroupChatType { msg.GroupID = recvID } - msg.CreateTime = utils.GetCurrentTimestampByMill() - msg.ClientMsgID = utils.GetMsgID(sendID) + msg.CreateTime = timeutil.GetCurrentTimestampByMill() + msg.ClientMsgID = idutil.GetMsgIDByMD5(sendID) optionsConfig := s.contentTypeConf[contentType] if sendID == recvID && contentType == constant.HasReadReceipt { optionsConfig.ReliabilityLevel = constant.UnreliableNotification @@ -302,21 +319,20 @@ func (s *NotificationSender) NotificationWithSesstionType(ctx context.Context, s options := config.GetOptionsByNotification(optionsConfig) s.SetOptionsByContentType(ctx, options, contentType) msg.Options = options - offlineInfo.Title = title - offlineInfo.Desc = desc - offlineInfo.Ex = ex msg.OfflinePushInfo = &offlineInfo req.MsgData = &msg _, err = s.sendMsg(ctx, &req) if err != nil { - errInfo := fmt.Sprintf("MsgClient Notification SendMsg failed, req:%s", &req) - return errs.Wrap(err, errInfo) + log.ZWarn(ctx, "SendMsg failed", err, "req", req.String()) } - return err } -func (s *NotificationSender) Notification(ctx context.Context, sendID, recvID string, contentType int32, m proto.Message, opts ...NotificationOptions) error { - return s.NotificationWithSesstionType(ctx, sendID, recvID, contentType, s.sessionTypeConf[contentType], m, opts...) +func (s *NotificationSender) NotificationWithSessionType(ctx context.Context, sendID, recvID string, contentType, sessionType int32, m proto.Message, opts ...NotificationOptions) { + s.queue.Push(func() { s.send(ctx, sendID, recvID, contentType, sessionType, m, opts...) }) +} + +func (s *NotificationSender) Notification(ctx context.Context, sendID, recvID string, contentType int32, m proto.Message, opts ...NotificationOptions) { + s.NotificationWithSessionType(ctx, sendID, recvID, contentType, s.sessionTypeConf[contentType], m, opts...) } func (s *NotificationSender) SetOptionsByContentType(_ context.Context, options map[string]bool, contentType int32) { diff --git a/pkg/rpcclient/notification/doc.go b/pkg/rpcclient/notification/doc.go index 2d409bdc9..8ce57ca4e 100644 --- a/pkg/rpcclient/notification/doc.go +++ b/pkg/rpcclient/notification/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/rpcclient/push.go b/pkg/rpcclient/push.go index c0aa9efa4..c549e454a 100644 --- a/pkg/rpcclient/push.go +++ b/pkg/rpcclient/push.go @@ -17,23 +17,22 @@ package rpcclient import ( "context" - "github.com/OpenIMSDK/protocol/push" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/protocol/push" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/system/program" "google.golang.org/grpc" ) type Push struct { conn grpc.ClientConnInterface Client push.PushMsgServiceClient - discov discoveryregistry.SvcDiscoveryRegistry + discov discovery.SvcDiscoveryRegistry } -func NewPush(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) *Push { - conn, err := discov.GetConn(context.Background(), config.RpcRegisterName.OpenImPushName) +func NewPush(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) *Push { + conn, err := discov.GetConn(context.Background(), rpcRegisterName) if err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } return &Push{ discov: discov, @@ -44,8 +43,8 @@ func NewPush(discov discoveryregistry.SvcDiscoveryRegistry, config *config.Globa type PushRpcClient Push -func NewPushRpcClient(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) PushRpcClient { - return PushRpcClient(*NewPush(discov, config)) +func NewPushRpcClient(discov discovery.SvcDiscoveryRegistry, rpcRegisterName string) PushRpcClient { + return PushRpcClient(*NewPush(discov, rpcRegisterName)) } func (p *PushRpcClient) DelUserPushToken(ctx context.Context, req *push.DelUserPushTokenReq) (*push.DelUserPushTokenResp, error) { diff --git a/pkg/rpcclient/third.go b/pkg/rpcclient/third.go index a1f8ae180..4c71dff6a 100644 --- a/pkg/rpcclient/third.go +++ b/pkg/rpcclient/third.go @@ -16,58 +16,28 @@ package rpcclient import ( "context" - "net/url" - "github.com/OpenIMSDK/protocol/third" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/minio/minio-go/v7" - "github.com/minio/minio-go/v7/pkg/credentials" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/protocol/third" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/system/program" "google.golang.org/grpc" ) type Third struct { - conn grpc.ClientConnInterface - Client third.ThirdClient - discov discoveryregistry.SvcDiscoveryRegistry - MinioClient *minio.Client - Config *config.GlobalConfig + conn grpc.ClientConnInterface + Client third.ThirdClient + discov discovery.SvcDiscoveryRegistry + GrafanaUrl string } -func NewThird(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) *Third { - conn, err := discov.GetConn(context.Background(), config.RpcRegisterName.OpenImThirdName) +func NewThird(discov discovery.SvcDiscoveryRegistry, rpcRegisterName, grafanaUrl string) *Third { + conn, err := discov.GetConn(context.Background(), rpcRegisterName) if err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } client := third.NewThirdClient(conn) - minioClient, err := minioInit(config) if err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } - return &Third{discov: discov, Client: client, conn: conn, MinioClient: minioClient, Config: config} -} - -func minioInit(config *config.GlobalConfig) (*minio.Client, error) { - minioClient := &minio.Client{} - initUrl := config.Object.Minio.Endpoint - minioUrl, err := url.Parse(initUrl) - if err != nil { - return nil, errs.Wrap(err, "minioInit: failed to parse MinIO endpoint URL") - } - opts := &minio.Options{ - Creds: credentials.NewStaticV4(config.Object.Minio.AccessKeyID, config.Object.Minio.SecretAccessKey, ""), - // Region: config.Credential.Minio.Location, - } - if minioUrl.Scheme == "http" { - opts.Secure = false - } else if minioUrl.Scheme == "https" { - opts.Secure = true - } - minioClient, err = minio.New(minioUrl.Host, opts) - if err != nil { - return nil, errs.Wrap(err, "minioInit: failed to create MinIO client") - } - return minioClient, nil + return &Third{discov: discov, Client: client, conn: conn, GrafanaUrl: grafanaUrl} } diff --git a/pkg/rpcclient/user.go b/pkg/rpcclient/user.go index 08ad41dc1..aab96603e 100644 --- a/pkg/rpcclient/user.go +++ b/pkg/rpcclient/user.go @@ -18,33 +18,37 @@ import ( "context" "strings" - "github.com/OpenIMSDK/protocol/sdkws" - "github.com/OpenIMSDK/protocol/user" - "github.com/OpenIMSDK/tools/discoveryregistry" - "github.com/OpenIMSDK/tools/errs" - "github.com/OpenIMSDK/tools/utils" "github.com/openimsdk/open-im-server/v3/pkg/authverify" - "github.com/openimsdk/open-im-server/v3/pkg/common/config" - util "github.com/openimsdk/open-im-server/v3/pkg/util/genutil" + "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" + "github.com/openimsdk/protocol/sdkws" + "github.com/openimsdk/protocol/user" + "github.com/openimsdk/tools/discovery" + "github.com/openimsdk/tools/system/program" + "github.com/openimsdk/tools/utils/datautil" "google.golang.org/grpc" ) // User represents a structure holding connection details for the User RPC client. type User struct { - conn grpc.ClientConnInterface - Client user.UserClient - Discov discoveryregistry.SvcDiscoveryRegistry - Config *config.GlobalConfig + conn grpc.ClientConnInterface + Client user.UserClient + Discov discovery.SvcDiscoveryRegistry + MessageGateWayRpcName string + imAdminUserID []string } // NewUser initializes and returns a User instance based on the provided service discovery registry. -func NewUser(discov discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) *User { - conn, err := discov.GetConn(context.Background(), config.RpcRegisterName.OpenImUserName) +func NewUser(discov discovery.SvcDiscoveryRegistry, rpcRegisterName, messageGateWayRpcName string, + imAdminUserID []string) *User { + conn, err := discov.GetConn(context.Background(), rpcRegisterName) if err != nil { - util.ExitWithError(err) + program.ExitWithError(err) } client := user.NewUserClient(conn) - return &User{Discov: discov, Client: client, conn: conn, Config: config} + return &User{Discov: discov, Client: client, + conn: conn, + MessageGateWayRpcName: messageGateWayRpcName, + imAdminUserID: imAdminUserID} } // UserRpcClient represents the structure for a User RPC client. @@ -57,8 +61,9 @@ func NewUserRpcClientByUser(user *User) *UserRpcClient { } // NewUserRpcClient initializes a UserRpcClient based on the provided service discovery registry. -func NewUserRpcClient(client discoveryregistry.SvcDiscoveryRegistry, config *config.GlobalConfig) UserRpcClient { - return UserRpcClient(*NewUser(client, config)) +func NewUserRpcClient(client discovery.SvcDiscoveryRegistry, rpcRegisterName string, + imAdminUserID []string) UserRpcClient { + return UserRpcClient(*NewUser(client, rpcRegisterName, "", imAdminUserID)) } // GetUsersInfo retrieves information for multiple users based on their user IDs. @@ -72,10 +77,10 @@ func (u *UserRpcClient) GetUsersInfo(ctx context.Context, userIDs []string) ([]* if err != nil { return nil, err } - if ids := utils.Single(userIDs, utils.Slice(resp.UsersInfo, func(e *sdkws.UserInfo) string { + if ids := datautil.Single(userIDs, datautil.Slice(resp.UsersInfo, func(e *sdkws.UserInfo) string { return e.UserID })); len(ids) > 0 { - return nil, errs.ErrUserIDNotFound.Wrap(strings.Join(ids, ",")) + return nil, servererrs.ErrUserIDNotFound.WrapMsg(strings.Join(ids, ",")) } return resp.UsersInfo, nil } @@ -95,7 +100,7 @@ func (u *UserRpcClient) GetUsersInfoMap(ctx context.Context, userIDs []string) ( if err != nil { return nil, err } - return utils.SliceToMap(users, func(e *sdkws.UserInfo) string { + return datautil.SliceToMap(users, func(e *sdkws.UserInfo) string { return e.UserID }), nil } @@ -110,7 +115,7 @@ func (u *UserRpcClient) GetPublicUserInfos( if err != nil { return nil, err } - return utils.Slice(users, func(e *sdkws.UserInfo) *sdkws.PublicUserInfo { + return datautil.Slice(users, func(e *sdkws.UserInfo) *sdkws.PublicUserInfo { return &sdkws.PublicUserInfo{ UserID: e.UserID, Nickname: e.Nickname, @@ -139,7 +144,7 @@ func (u *UserRpcClient) GetPublicUserInfoMap( if err != nil { return nil, err } - return utils.SliceToMap(users, func(e *sdkws.PublicUserInfo) string { + return datautil.SliceToMap(users, func(e *sdkws.PublicUserInfo) string { return e.UserID }), nil } @@ -161,7 +166,7 @@ func (u *UserRpcClient) Access(ctx context.Context, ownerUserID string) error { if err != nil { return err } - return authverify.CheckAccessV3(ctx, ownerUserID, u.Config) + return authverify.CheckAccessV3(ctx, ownerUserID, u.imAdminUserID) } // GetAllUserIDs retrieves all user IDs with pagination options. diff --git a/pkg/statistics/doc.go b/pkg/statistics/doc.go index 8051c4c71..7b02631dc 100644 --- a/pkg/statistics/doc.go +++ b/pkg/statistics/doc.go @@ -1,4 +1,4 @@ -// Copyright © 2023 OpenIM. All rights reserved. +// Copyright © 2024 OpenIM. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/pkg/statistics/statistics.go b/pkg/statistics/statistics.go index 6dfc8155c..222fd5cbd 100644 --- a/pkg/statistics/statistics.go +++ b/pkg/statistics/statistics.go @@ -18,7 +18,7 @@ import ( "context" "time" - "github.com/OpenIMSDK/tools/log" + "github.com/openimsdk/tools/log" ) type Statistics struct { diff --git a/pkg/util/conversationutil/conversationutil.go b/pkg/util/conversationutil/conversationutil.go new file mode 100644 index 000000000..5683d8df8 --- /dev/null +++ b/pkg/util/conversationutil/conversationutil.go @@ -0,0 +1,46 @@ +package conversationutil + +import ( + "sort" + "strings" +) + +func GenConversationIDForSingle(sendID, recvID string) string { + l := []string{sendID, recvID} + sort.Strings(l) + return "si_" + strings.Join(l, "_") +} + +func GenConversationUniqueKeyForGroup(groupID string) string { + return groupID +} + +func GenGroupConversationID(groupID string) string { + return "sg_" + groupID +} + +func GenConversationUniqueKeyForSingle(sendID, recvID string) string { + l := []string{sendID, recvID} + sort.Strings(l) + return strings.Join(l, "_") +} + +func GetNotificationConversationIDByConversationID(conversationID string) string { + l := strings.Split(conversationID, "_") + if len(l) > 1 { + l[0] = "n" + return strings.Join(l, "_") + } + return "" +} + +func GetSelfNotificationConversationID(userID string) string { + return "n_" + userID + "_" + userID +} + +func GetSeqsBeginEnd(seqs []int64) (int64, int64) { + if len(seqs) == 0 { + return 0, 0 + } + return seqs[0], seqs[len(seqs)-1] +} diff --git a/pkg/util/conversationutil/doc.go b/pkg/util/conversationutil/doc.go new file mode 100644 index 000000000..90325ccb0 --- /dev/null +++ b/pkg/util/conversationutil/doc.go @@ -0,0 +1 @@ +package conversationutil // import "github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil" diff --git a/pkg/util/flag/flag.go b/pkg/util/flag/flag.go deleted file mode 100644 index 0a8e527ab..000000000 --- a/pkg/util/flag/flag.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2024 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package flag - -import ( - "flag" - "log" - "strings" - - "github.com/spf13/pflag" -) - -// WordSepNormalizeFunc changes all flags that contain "_" separators. -func WordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName { - if strings.Contains(name, "_") { - return pflag.NormalizedName(strings.ReplaceAll(name, "_", "-")) - } - return pflag.NormalizedName(name) -} - -// WarnWordSepNormalizeFunc changes and warns for flags that contain "_" separators. -func WarnWordSepNormalizeFunc(f *pflag.FlagSet, name string) pflag.NormalizedName { - if strings.Contains(name, "_") { - normalizedName := strings.ReplaceAll(name, "_", "-") - log.Printf("WARNING: flag %s has been deprecated and will be removed in a future version. Use %s instead.", name, normalizedName) - return pflag.NormalizedName(normalizedName) - } - return pflag.NormalizedName(name) -} - -// InitFlags normalizes, parses, then logs the command line flags. -func InitFlags() { - pflag.CommandLine.SetNormalizeFunc(WordSepNormalizeFunc) - pflag.CommandLine.AddGoFlagSet(flag.CommandLine) -} - -// PrintFlags logs the flags in the flagset. -func PrintFlags(flags *pflag.FlagSet) { - flags.VisitAll(func(flag *pflag.Flag) { - log.Printf("FLAG: --%s=%q", flag.Name, flag.Value) - }) -} diff --git a/pkg/util/genutil/genutil.go b/pkg/util/genutil/genutil.go deleted file mode 100644 index 95735485d..000000000 --- a/pkg/util/genutil/genutil.go +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright © 2024 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package genutil - -import ( - "fmt" - "os" - "path/filepath" - - "github.com/OpenIMSDK/tools/errs" -) - -// OutDir creates the absolute path name from path and checks path exists. -// Returns absolute path including trailing '/' or error if path does not exist. -func OutDir(path string) (string, error) { - outDir, err := filepath.Abs(path) - if err != nil { - return "", errs.Wrap(err, "output directory %s does not exist", path) - } - - stat, err := os.Stat(outDir) - if err != nil { - return "", errs.Wrap(err, "output directory %s does not exist", outDir) - } - - if !stat.IsDir() { - return "", errs.Wrap(err, "output directory %s is not a directory", outDir) - } - outDir += "/" - return outDir, nil -} - -func ExitWithError(err error) { - progName := filepath.Base(os.Args[0]) - fmt.Fprintf(os.Stderr, "%s exit -1: %+v\n", progName, err) - os.Exit(-1) -} - -func SIGTERMExit() { - progName := filepath.Base(os.Args[0]) - fmt.Fprintf(os.Stderr, "Warning %s receive process terminal SIGTERM exit 0\n", progName) -} diff --git a/pkg/util/genutil/genutil_test.go b/pkg/util/genutil/genutil_test.go deleted file mode 100644 index 050d14040..000000000 --- a/pkg/util/genutil/genutil_test.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright © 2024 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package genutil - -import ( - "testing" -) - -func TestValidDir(t *testing.T) { - _, err := OutDir("./") - if err != nil { - t.Fatal(err) - } -} - -func TestInvalidDir(t *testing.T) { - _, err := OutDir("./nondir") - if err == nil { - t.Fatal("expected an error") - } -} - -func TestNotDir(t *testing.T) { - _, err := OutDir("./genutils_test.go") - if err == nil { - t.Fatal("expected an error") - } -} diff --git a/scripts/check-all.sh b/scripts/check-all.sh index d41023384..54296b22d 100755 --- a/scripts/check-all.sh +++ b/scripts/check-all.sh @@ -20,9 +20,6 @@ # READ: https://github.com/openimsdk/open-im-server/tree/main/scripts/install/environment.sh - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. source "${OPENIM_ROOT}/scripts/install/common.sh" @@ -33,7 +30,7 @@ fi OPENIM_VERBOSE=4 -openim::log::info "\n# Begin to check all openim service" +openim::log::info "\n# Begin to check all OpenIM service" openim::log::status "Check all dependent service ports" # Elegant printing function @@ -65,16 +62,16 @@ print_services_and_ports "${OPENIM_SERVER_NAME_TARGETS[@]}" "${OPENIM_SERVER_POR print_services_and_ports "${OPENIM_DEPENDENCY_TARGETS[@]}" "${OPENIM_DEPENDENCY_PORT_TARGETS[@]}" # OpenIM check -echo "++ The port being checked: ${OPENIM_SERVER_PORT_LISTARIES[@]}" -openim::log::info "\n## Check all dependent service ports" -echo "++ The port being checked: ${OPENIM_DEPENDENCY_PORT_LISTARIES[@]}" +#echo "++ The port being checked: ${OPENIM_SERVER_PORT_LISTARIES[@]}" +openim::log::info "\n## Check all dependent components service ports" +#echo "++ The port being checked: ${OPENIM_DEPENDENCY_PORT_LISTARIES[@]}" # Later, after discarding Docker, the Docker keyword is unreliable, and Kubepods is used if grep -qE 'docker|kubepods' /proc/1/cgroup || [ -f /.dockerenv ]; then openim::color::echo ${COLOR_CYAN} "Environment in the interior of the container" else - openim::color::echo ${COLOR_CYAN} "The environment is outside the container" + openim::color::echo ${COLOR_CYAN}"The environment is outside the container" openim::util::check_ports ${OPENIM_DEPENDENCY_PORT_LISTARIES[@]} fi @@ -82,30 +79,35 @@ if [[ $? -ne 0 ]]; then openim::log::error_exit "The service does not start properly, please check the port, query variable definition!" echo "+++ https://github.com/openimsdk/open-im-server/tree/main/scripts/install/environment.sh +++" else - openim::log::success "All components depended on by openim are running normally! " + openim::log::success "All components depended on by OpenIM are running normally! " fi -openim::log::info "\n## Check openim service name:\n${OPENIM_OUTPUT_HOSTBIN}/openim-msgtransfer" +openim::log::status "Check OpenIM service:" +openim::log::colorless "${OPENIM_OUTPUT_HOSTBIN}/openim-msgtransfer" result=$(. $(dirname ${BASH_SOURCE})/install/openim-msgtransfer.sh openim::msgtransfer::check) if [[ $? -ne 0 ]]; then - echo "+++ cat openim log file >>> ${LOG_FILE}" - openim::log::error "check process failed.\n $result" + #echo "+++ cat openim log file >>> ${LOG_FILE}" + + openim::log::error "The service is not running properly, please check the logs $result" fi -echo "Check openim service name:" +openim::log::status "Check OpenIM service:" for item in "${OPENIM_ALL_SERVICE_LIBRARIES_NO_TRANSFER[@]}"; do - echo "$item" + openim::log::colorless "$item" done + result=$(openim::util::check_process_names ${OPENIM_ALL_SERVICE_LIBRARIES_NO_TRANSFER[@]}) if [[ $? -ne 0 ]]; then - echo "+++ cat openim log file >>> ${LOG_FILE}" - openim::log::error "check process failed.\n " + #echo "+++ cat OpenIM log file >>> ${LOG_FILE}" + openim::log::error "The service is not running properly, please check the logs " echo "$result" exit 1 else - openim::log::success "All openim services are running normally! " + openim::log::status "List the ports listened to by the OpenIM service:" + openim::util::find_ports_for_all_services ${OPENIM_ALL_SERVICE_LIBRARIES_NO_TRANSFER[@]} + openim::util::find_ports_for_all_services ${OPENIM_MSGTRANSFER_BINARY[@]} + openim::log::success "All OpenIM services are running normally! " fi - diff --git a/scripts/cherry-pick.sh b/scripts/cherry-pick.sh index ff303269d..b95289cd4 100755 --- a/scripts/cherry-pick.sh +++ b/scripts/cherry-pick.sh @@ -35,8 +35,8 @@ DRY_RUN=${DRY_RUN:-""} REGENERATE_DOCS=${REGENERATE_DOCS:-""} UPSTREAM_REMOTE=${UPSTREAM_REMOTE:-upstream} FORK_REMOTE=${FORK_REMOTE:-origin} -MAIN_REPO_ORG=${MAIN_REPO_ORG:-$(git remote get-url "$UPSTREAM_REMOTE" | awk '{gsub(/http[s]:\/\/|git@/,"")}1' | awk -F'[@:./]' 'NR==1{print $3}')} -MAIN_REPO_NAME=${MAIN_REPO_NAME:-$(git remote get-url "$UPSTREAM_REMOTE" | awk '{gsub(/http[s]:\/\/|git@/,"")}1' | awk -F'[@:./]' 'NR==1{print $4}')} +MAIN_REPO_ORG=${MAIN_REPO_ORG:-$(git remote get-url "$UPSTREAM_REMOTE" | awk '{gsub(/webhook[s]:\/\/|git@/,"")}1' | awk -F'[@:./]' 'NR==1{print $3}')} +MAIN_REPO_NAME=${MAIN_REPO_NAME:-$(git remote get-url "$UPSTREAM_REMOTE" | awk '{gsub(/webhook[s]:\/\/|git@/,"")}1' | awk -F'[@:./]' 'NR==1{print $4}')} if [[ -z ${GITHUB_USER:-} ]]; then openim::log::error_exit "Please export GITHUB_USER= (or GH organization, if that's where your fork lives)" diff --git a/scripts/create-topic.sh b/scripts/create-topic.sh index 5c291ff08..206075fb8 100755 --- a/scripts/create-topic.sh +++ b/scripts/create-topic.sh @@ -14,14 +14,42 @@ # limitations under the License. # Wait for Kafka to be ready -until /opt/bitnami/kafka/bin/kafka-topics.sh --list --bootstrap-server localhost:9092; do - echo "Waiting for Kafka to be ready..." - sleep 2 + +KAFKA_SERVER=localhost:9092 + +MAX_ATTEMPTS=300 +attempt_num=1 + +echo "Waiting for Kafka to be ready..." + +until /opt/bitnami/kafka/bin/kafka-topics.sh --list --bootstrap-server $KAFKA_SERVER; do + echo "Attempt $attempt_num of $MAX_ATTEMPTS: Kafka not ready yet..." + if [ $attempt_num -eq $MAX_ATTEMPTS ]; then + echo "Kafka not ready after $MAX_ATTEMPTS attempts, exiting" + exit 1 + fi + attempt_num=$((attempt_num+1)) + sleep 1 done -# Create topics -/opt/bitnami/kafka/bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 8 --topic latestMsgToRedis -/opt/bitnami/kafka/bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 8 --topic msgToPush -/opt/bitnami/kafka/bin/kafka-topics.sh --create --bootstrap-server localhost:9092 --replication-factor 1 --partitions 8 --topic offlineMsgToMongoMysql +echo "Kafka is ready. Creating topics..." + + +topics=("toRedis" "toMongo" "toPush") +partitions=8 +replicationFactor=1 + +for topic in "${topics[@]}"; do + if /opt/bitnami/kafka/bin/kafka-topics.sh --create \ + --bootstrap-server $KAFKA_SERVER \ + --replication-factor $replicationFactor \ + --partitions $partitions \ + --topic $topic + then + echo "Topic $topic created." + else + echo "Failed to create topic $topic." + fi +done -echo "Topics created." \ No newline at end of file +echo "All topics created." diff --git a/scripts/demo.sh b/scripts/demo.sh deleted file mode 100755 index 4b877b9ed..000000000 --- a/scripts/demo.sh +++ /dev/null @@ -1,156 +0,0 @@ -#!/usr/bin/env bash -# Copyright © 2023 OpenIM. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -if ! command -v pv &> /dev/null -then - echo "pv not found, installing..." - if [ -e /etc/debian_version ]; then - sudo apt-get update - sudo apt-get install -y pv - elif [ -e /etc/redhat-release ]; then - sudo yum install -y pv - else - echo "Unsupported OS, please install pv manually." - exit 1 - fi -fi - -readonly t_reset=$(tput sgr0) -readonly green=$(tput bold; tput setaf 2) -readonly yellow=$(tput bold; tput setaf 3) -readonly blue=$(tput bold; tput setaf 6) -readonly timeout=$(if [ "$(uname)" == "Darwin" ]; then echo "1"; else echo "0.1"; fi) -readonly ipv6regex='(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))' - -clear -. $(dirname ${BASH_SOURCE})/lib/util.sh - -openim::util::ensure-bash-version - -trap 'openim::util::onCtrlC' INT - -function openim::util::onCtrlC() { - echo -e "\n${t_reset}Ctrl+C Press it. It's exiting openim make init..." - exit 0 -} - -openim::util::desc "========> Welcome to the OpenIM Demo" -openim::util::desc "========> We'll help you get started with OpenIM quickly" -openim::util::desc "========> Press Enter to continue...." -openim::util::run "make advertise" -clear - -openim::util::desc "========> Initialize the project and generate configuration files" -openim::util::run "make init" -clear - -# openim::util::desc "========> You can look git diff" -# openim::util::run "git diff" -# clear - -openim::util::desc "You can learn a lot about automation using make help" -openim::util::run "make help" -clear - -openim::util::desc "You can learn a lot about automation using make help-all" -openim::util::run "make help-all" -clear - -openim::util::desc "First, let's verify and install some necessary tools" -openim::util::run "make tools" -clear - -openim::util::desc "========> Start the basic openim docker components" -openim::util::desc "========> You can use docker-compose ps to check the status of the container" -openim::util::run "docker compose up -d" -clear - -openim::util::desc "========> Use make init-githooks Initialize git hooks " -openim::util::run "make init-githooks" -clear - -openim::util::desc "The specification is pretty high, you need to be bound on your branch name, as well as commit messages" -openim::util::run "git commit -a -s -m 'feta: commit demo against specification'" -openim::util::run "# git commit -a -s -m 'feat: commit demo against specification' --amend" -clear - -openim::util::desc "How did we teach you how to build OpenIM" -openim::util::desc "A full build startup check" -openim::util::run "# make all" -openim::util::desc "Build one OpenIM binary" -openim::util::desc "BINS: openim-api openim-cmdutils openim-crontask openim-msggateway openim-msgtransfer openim-push openim-rpc changelog infra ncpu yamlfmt" -openim::util::run "make build BINS=openim-api" -openim::util::run "make build" - -openim::util::desc "Build binaries for all platforms" -openim::util::run "make multiarch -j BINS=openim-crontask PLATFORMS='linux_arm64 linux_amd64' " - -openim::util::desc "If you wish to use dlv for debugging, either binary or process" -openim::util::desc "You need to enable debug mode" -openim::util::run "make build BINS=openim-cmdutils DEBUG=1" -clear - -openim::util::desc "Next, let's learn how to start the OpenIM service. For starting, we have two ways" -openim::util::desc "The first is Background startup" -openim::util::run "make start" -openim::util::desc "The second way is through the Linux system way" -openim::util::run "./scripts/install/install.sh --help" -clear - -openim::util::desc "Next, let's learn how to check the OpenIM service. For checking, we have two ways" -openim::util::run "make check" -clear - -openim::util::desc "Next, let's learn how to stop the OpenIM service. For stopping, we have two ways" -openim::util::run "make stop" -clear - -openim::util::desc "Run tidy to format and fix imports" -openim::util::run "make tidy" -clear - -openim::util::desc "Vendor go.mod dependencies" -openim::util::run "# make vendor" -clear - -openim::util::desc "Run unit tests" -openim::util::run "# make test" -clear - -openim::util::desc "Run unit tests and get test coverage" -openim::util::run "# make cover" -clear - -openim::util::desc "Check for updates to go.mod dependencies" -openim::util::run "# make updates" -clear - -openim::util::desc "You can learn a lot about automation using make clean, remove all files that are created by building" -openim::util::run "make clean" -clear - -openim::util::desc "Generate all necessary files" -openim::util::run "make gen" -clear - -openim::util::desc "Verify the license headers for all files" -openim::util::run "make verify-copyright" -clear - -openim::util::desc "Add copyright" -openim::util::run "make add-copyright" -clear - -exit 0 diff --git a/scripts/docker-check-service.sh b/scripts/docker-check-service.sh deleted file mode 100755 index 30ca89b5a..000000000 --- a/scripts/docker-check-service.sh +++ /dev/null @@ -1,91 +0,0 @@ -#!/usr/bin/env bash - -# Copyright © 2023 OpenIM. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. -source "${OPENIM_ROOT}/scripts/install/common.sh" - -cd "$OPENIM_ROOT" - -openim::util::check_docker_and_compose_versions - -progress() { - local _main_pid="$1" - local _length=20 - local _ratio=1 - local _colors=("31" "32" "33" "34" "35" "36" "37") - local _wave=("▁" "▂" "▃" "▄" "▅" "▆" "▇" "█" "▇" "▆" "▅" "▄" "▃" "▂") - - while pgrep -P "$_main_pid" &> /dev/null; do - local _mark='>' - local _progress_bar= - for ((i = 1; i <= _length; i++)); do - if ((i > _ratio)); then - _mark='-' - fi - _progress_bar="${_progress_bar}${_mark}" - done - - local _color_idx=$((_ratio % ${#_colors[@]})) - local _color_prefix="\033[${_colors[_color_idx]}m" - local _reset_suffix="\033[0m" - - local _wave_idx=$((_ratio % ${#_wave[@]})) - local _wave_progress=${_wave[_wave_idx]} - - printf "Progress: ${_color_prefix}${_progress_bar}${_reset_suffix} ${_wave_progress} Countdown: %2ds \r" "$_countdown" - ((_ratio++)) - ((_ratio > _length)) && _ratio=1 - sleep 0.1 - done -} - -countdown() { - local _duration="$1" - - for ((i = _duration; i >= 1; i--)); do - printf "\rCountdown: %2ds \r" "$i" - sleep 1 - done - printf "\rCountdown: %2ds \r" "$_duration" -} - -do_sth() { - echo "++++++++++++++++++++++++" - progress $$ & - local _progress_pid=$! - local _countdown=30 - - countdown "$_countdown" & - local _countdown_pid=$! - - sleep 30 - - kill "$_progress_pid" "$_countdown_pid" - - "${SCRIPTS_ROOT}/check-all.sh" - echo -e "${PURPLE_PREFIX}=========> Check docker-compose status ${COLOR_SUFFIX} \n" -} - -set -e - -do_sth & -do_sth_pid=$(jobs -p | tail -1) - -progress "${do_sth_pid}" & -progress_pid=$(jobs -p | tail -1) - -wait "${do_sth_pid}" -printf "Progress: done \n" diff --git a/scripts/docker-start-all.sh b/scripts/docker-start-all.sh index 4f8747a2a..116038b53 100755 --- a/scripts/docker-start-all.sh +++ b/scripts/docker-start-all.sh @@ -21,9 +21,6 @@ #fixme This scripts is the total startup scripts #fixme The full name of the shell scripts that needs to be started is placed in the need_to_start_server_shell array -OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. -source "${OPENIM_ROOT}/scripts/install/common.sh" -openim::log::info "\n# Use Docker to start all openim service" -trap 'openim::util::onCtrlC' INT -"${OPENIM_ROOT}"/scripts/start-all.sh 2>&1 & -tail -f ${DOCKER_LOG_FILE} +cd /openim/openim-server +mage start +tail -f /dev/null diff --git a/scripts/gendoc.sh b/scripts/gendoc.sh index ece090190..43b69c3ed 100755 --- a/scripts/gendoc.sh +++ b/scripts/gendoc.sh @@ -13,66 +13,72 @@ # See the License for the specific language governing permissions and # limitations under the License. -DEFAULT_DIRS=( - "pkg" - "internal/pkg" -) -BASE_URL="github.com/openimsdk/open-im-server" +#!/bin/bash + +DEFAULT_DIRS=("pkg") +BASE_URL="github.com/openimsdk/open-im-server/v3" +REMOVE_DOC=false usage() { echo "Usage: $0 [OPTIONS]" echo - echo "This script iterates over directories and generates doc.go if necessary." - echo "By default, it processes 'pkg' and 'internal/pkg' directories." + echo "This script iterates over directories. By default, it generates doc.go files for 'pkg' and 'internal/pkg'." echo echo "Options:" - echo " -d DIRS, --dirs DIRS Specify the directories to be processed, separated by commas. E.g., 'pkg,internal/pkg'." - echo " -u URL, --url URL Set the base URL for the import path. Default is '$BASE_URL'." - echo " -h, --help Show this help message." + echo " -d DIRS, --dirs DIRS Specify directories to process, separated by commas (e.g., 'pkg,internal/pkg')." + echo " -u URL, --url URL Set the base URL for the import path. Default is '$BASE_URL'." + echo " -r, --remove Remove all doc.go files in the specified directories." + echo " -h, --help Show this help message." echo } process_dir() { - local dir=$1 - local base_url=$2 - - for d in $(find $dir -type d); do - if [ ! -f $d/doc.go ]; then - if ls $d/*.go > /dev/null 2>&1; then - echo $d/doc.go - echo "package $(basename $d) // import \"$base_url/$d\"" > $d/doc.go + local dir="$1" + local base_url="$2" + local remove_doc="$3" + + find "$dir" -type d | while read -r d; do + if [ "$remove_doc" = true ]; then + if [ -f "$d/doc.go" ]; then + echo "Removing $d/doc.go" + rm -f "$d/doc.go" + fi + else + if [ ! -f "$d/doc.go" ] && ls "$d/"*.go &>/dev/null; then + echo "Creating $d/doc.go" + echo "package $(basename "$d") // import \"$base_url/$(echo "$d" | sed "s|^\./||")\"" >"$d/doc.go" fi fi done } while [[ $# -gt 0 ]]; do - key="$1" - - case $key in + case "$1" in -d|--dirs) - IFS=',' read -ra DIRS <<< "$2" - shift # shift past argument - shift # shift past value - ;; - -u|--url) - BASE_URL="$2" - shift # shift past argument - shift # shift past value - ;; - -h|--help) - usage - exit 0 - ;; - *) - usage - exit 1 - ;; - esac + IFS=',' read -ra DIRS <<< "$2" + shift 2 + ;; + -u|--url) + BASE_URL="$2" + shift 2 + ;; + -r|--remove) + REMOVE_DOC=true + shift + ;; + -h|--help) + usage + exit 0 + ;; + *) + usage + exit 1 + ;; + esac done -DIRS=${DIRS:-${DEFAULT_DIRS[@]}} +DIRS=(${DIRS:-"${DEFAULT_DIRS[@]}"}) for dir in "${DIRS[@]}"; do - process_dir $dir $BASE_URL + process_dir "$dir" "$BASE_URL" "$REMOVE_DOC" done diff --git a/scripts/init-config.sh b/scripts/init-config.sh index c5b21d061..ef2cd0a40 100755 --- a/scripts/init-config.sh +++ b/scripts/init-config.sh @@ -65,6 +65,8 @@ GENERATE_EXAMPLES=false CLEAN_CONFIG=false CLEAN_EXAMPLES=false +FILES_PROCESSED=false + # Function to display help information show_help() { echo "Usage: $(basename "$0") [options]" @@ -126,7 +128,7 @@ process_file() { if [[ "${FORCE_OVERWRITE}" == true ]]; then openim::log::info "Force overwriting ${output_file}." elif [[ "${SKIP_EXISTING}" == true ]]; then - openim::log::info "Skipping generation of ${output_file} as it already exists." + #openim::log::info "Skipping generation of ${output_file} as it already exists." return else echo -n "File ${output_file} already exists. Overwrite? (Y/N): " @@ -173,8 +175,7 @@ process_file() { exit 1 } fi - - sleep 0.5 + FILES_PROCESSED=true } clean_config_files() { @@ -259,4 +260,6 @@ if [[ "${GENERATE_EXAMPLES}" == true ]] && [[ "${CLEAN_EXAMPLES}" == false ]]; t generate_example_files fi -openim::log::success "Configuration and example files operation complete!" +if [[ "${FILES_PROCESSED}" == true ]]; then + openim::log::success "Configuration and example files operation complete!" +fi diff --git a/scripts/install/openim-api.sh b/scripts/install/openim-api.sh index cd3d5eb08..8403382e6 100755 --- a/scripts/install/openim-api.sh +++ b/scripts/install/openim-api.sh @@ -89,7 +89,7 @@ function openim::api::start_service() { echo "Starting service with command: $cmd" #nohup $cmd >> "${LOG_FILE}" 2> >(tee -a "${STDERR_LOG_FILE}" "$TMP_LOG_FILE" >&2) & - nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >/dev/null & + nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >> "${LOG_FILE}" 2>&1 & if [ $? -ne 0 ]; then openim::log::error_exit "Failed to start ${binary_name} on port ${service_port}." return 1 diff --git a/scripts/install/openim-crontask.sh b/scripts/install/openim-crontask.sh index 191701082..d785b37a4 100755 --- a/scripts/install/openim-crontask.sh +++ b/scripts/install/openim-crontask.sh @@ -53,7 +53,7 @@ function openim::crontask::start() { openim::log::status "start cron_task process, path: ${OPENIM_CRONTASK_BINARY}" #nohup ${OPENIM_CRONTASK_BINARY} -c ${OPENIM_PUSH_CONFIG} >> ${LOG_FILE} 2> >(tee -a "${STDERR_LOG_FILE}" "$TMP_LOG_FILE" >&2) & cmd="${OPENIM_CRONTASK_BINARY} -c ${OPENIM_PUSH_CONFIG}" - nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >/dev/null & + nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >> "${LOG_FILE}" 2>&1 & return 0 } diff --git a/scripts/install/openim-msggateway.sh b/scripts/install/openim-msggateway.sh index 25051aa6e..ed7910805 100755 --- a/scripts/install/openim-msggateway.sh +++ b/scripts/install/openim-msggateway.sh @@ -62,7 +62,7 @@ function openim::msggateway::start() { PROMETHEUS_PORT_OPTION="--prometheus_port ${MSG_GATEWAY_PROM_PORTS_ARRAY[$i]}" fi cmd="${OPENIM_MSGGATEWAY_BINARY} --port ${OPENIM_MSGGATEWAY_PORTS_ARRAY[$i]} --ws_port ${OPENIM_WS_PORTS_ARRAY[$i]} $PROMETHEUS_PORT_OPTION -c ${OPENIM_MSGGATEWAY_CONFIG}" - nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >/dev/null & + nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >> "${LOG_FILE}" 2>&1 & # nohup ${OPENIM_MSGGATEWAY_BINARY} --port ${OPENIM_MSGGATEWAY_PORTS_ARRAY[$i]} --ws_port ${OPENIM_WS_PORTS_ARRAY[$i]} $PROMETHEUS_PORT_OPTION -c ${OPENIM_MSGGATEWAY_CONFIG} >> ${LOG_FILE} 2> >(tee -a "${STDERR_LOG_FILE}" "$TMP_LOG_FILE" >&2) & done diff --git a/scripts/install/openim-msgtransfer.sh b/scripts/install/openim-msgtransfer.sh index 23ce79c36..8403d1cfc 100755 --- a/scripts/install/openim-msgtransfer.sh +++ b/scripts/install/openim-msgtransfer.sh @@ -58,7 +58,8 @@ function openim::msgtransfer::start() { PROMETHEUS_PORT_OPTION="--prometheus_port ${PROMETHEUS_MSG_TRANSFER_PORT}" fi cmd="${OPENIM_MSGTRANSFER_BINARY} ${PROMETHEUS_PORT_OPTION} -c ${OPENIM_MSGTRANSFER_CONFIG} -n ${i}" - nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >/dev/null & + #nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >/dev/null & + nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >> "${LOG_FILE}" 2>&1 & #nohup ${OPENIM_MSGTRANSFER_BINARY} ${PROMETHEUS_PORT_OPTION} -c ${OPENIM_MSGTRANSFER_CONFIG} -n ${i} >> ${LOG_FILE} 2> >(tee -a "${STDERR_LOG_FILE}" "$TMP_LOG_FILE" >&2) & done return 0 @@ -82,7 +83,7 @@ function openim::msgtransfer::check() { fi done else - openim::log::error "Expected $OPENIM_MSGGATEWAY_NUM openim msgtransfer processes, but found $NUM_PROCESSES msgtransfer processes." + openim::log::error "Expected $OPENIM_MSGGATEWAY_NUM OpenIM msgtransfer processes, but found $NUM_PROCESSES msgtransfer processes running" return 1 fi return 0 diff --git a/scripts/install/openim-push.sh b/scripts/install/openim-push.sh index 8dea4b1f1..aafb24c7b 100755 --- a/scripts/install/openim-push.sh +++ b/scripts/install/openim-push.sh @@ -72,7 +72,8 @@ function openim::push::start() { for (( i=0; i<${#OPENIM_PUSH_PORTS_ARRAY[@]}; i++ )); do openim::log::info "start push process, port: ${OPENIM_PUSH_PORTS_ARRAY[$i]}, prometheus port: ${PUSH_PROM_PORTS_ARRAY[$i]}" cmd="${OPENIM_PUSH_BINARY} --port ${OPENIM_PUSH_PORTS_ARRAY[$i]} -c ${OPENIM_PUSH_CONFIG} --prometheus_port ${PUSH_PROM_PORTS_ARRAY[$i]}" - nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >/dev/null & + #nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >/dev/null & + nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >> "${LOG_FILE}" 2>&1 & #nohup ${OPENIM_PUSH_BINARY} --port ${OPENIM_PUSH_PORTS_ARRAY[$i]} -c ${OPENIM_PUSH_CONFIG} --prometheus_port ${PUSH_PROM_PORTS_ARRAY[$i]} >> ${LOG_FILE} 2> >(tee -a "${STDERR_LOG_FILE}" "$TMP_LOG_FILE" >&2) & done return 0 diff --git a/scripts/install/openim-rpc.sh b/scripts/install/openim-rpc.sh index c3ac12449..1265a11c6 100755 --- a/scripts/install/openim-rpc.sh +++ b/scripts/install/openim-rpc.sh @@ -166,7 +166,8 @@ function openim::rpc::start_service() { cmd="${cmd} --prometheus_port ${prometheus_port}" fi #nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "${STDERR_LOG_FILE}" "$TMP_LOG_FILE" >&2) & - nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >/dev/null & + #nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >/dev/null & + nohup ${cmd} >> "${LOG_FILE}" 2> >(tee -a "$TMP_LOG_FILE" | while read line; do echo -e "\e[31m${line}\e[0m"; done >&2) >> "${LOG_FILE}" 2>&1 & return 0 } diff --git a/scripts/install/openim-tools.sh b/scripts/install/openim-tools.sh index 7f6e1f52b..57de9772d 100755 --- a/scripts/install/openim-tools.sh +++ b/scripts/install/openim-tools.sh @@ -83,67 +83,65 @@ function openim::tools::start_service() { local prometheus_port="$4" local cmd="${OPENIM_OUTPUT_HOSTBIN_TOOLS}/${binary_name}" - openim::log::info "Starting PATH: ${OPENIM_OUTPUT_HOSTBIN_TOOLS}/${binary_name}..." + #openim::log::info "Starting PATH: ${OPENIM_OUTPUT_HOSTBIN_TOOLS}/${binary_name}..." if [ -n "${config}" ]; then - printf "Specifying config: %s\n" "${config}" + # printf "Specifying config: %s\n" "${config}" cmd="${cmd} -c ${config}/config.yaml" fi if [ -n "${service_port}" ]; then - printf "Specifying service port: %s\n" "${service_port}" + #printf "Specifying service port: %s\n" "${service_port}" cmd="${cmd} --port ${service_port}" fi if [ -n "${prometheus_port}" ]; then - printf "Specifying prometheus port: %s\n" "${prometheus_port}" + #printf "Specifying prometheus port: %s\n" "${prometheus_port}" cmd="${cmd} --prometheus_port ${prometheus_port}" fi - openim::log::status "Starting binary ${binary_name}..." + #openim::log::status "Starting binary ${binary_name}..." - - ${cmd} - + # openim::log::info "cmd: $cmd" + ${cmd} local status=$? - - if [ $status -eq 0 ]; then - openim::log::info "Service ${binary_name} started successfully." - return 0 - else - openim::log::error "Failed to start service ${binary_name}." - return 1 - fi + if [ $status -eq 0 ]; then + openim::log::colorless "Service ${binary_name} started successfully." + return 0 + else + openim::log::error "Failed to start service ${binary_name}." + return 1 + fi } function openim::tools::start() { openim::log::info "Starting OpenIM Tools..." for tool in "${OPENIM_TOOLS_NAME_LISTARIES[@]}"; do - openim::log::info "Starting tool ${tool}..." + openim::log::colorless "Starting tool ${tool}..." # openim::tools::start_service ${tool} - sleep 0.2 + #sleep 0.2 done } function openim::tools::pre-start() { - openim::log::info "Preparing to start OpenIM Tools..." + #openim::log::info "Preparing to start OpenIM Tools..." for tool in "${OPENIM_TOOLS_PRE_START_NAME_LISTARIES[@]}"; do - openim::log::info "Starting tool ${tool}..." + openim::log::colorless "Starting tool: ${tool}" if ! openim::tools::start_service ${tool} ${OPNEIM_CONFIG}; then openim::log::error "Failed to start ${tool}, aborting..." return 1 fi done - openim::log::info "All tools started successfully." + #openim::log::info "All tools started successfully." } function openim::tools::post-start() { - openim::log::info "Post-start actions for OpenIM Tools..." + #openim::log::info "Post-start actions for OpenIM Tools..." for tool in "${OPENIM_TOOLS_POST_START_NAME_LISTARIES[@]}"; do - openim::log::info "Starting tool ${tool}..." + openim::log::colorless "Starting tool: ${tool}" openim::tools::start_service ${tool} done } diff --git a/scripts/lib/golang.sh b/scripts/lib/golang.sh index 7b9d7e60c..51db58d89 100755 --- a/scripts/lib/golang.sh +++ b/scripts/lib/golang.sh @@ -149,7 +149,7 @@ openim::golang::start_script_list() { openim-msgtransfer.sh openim-msggateway.sh openim-crontask.sh - openim-tools.sh + #openim-tools.sh ) local result=() for target in "${targets[@]}"; do @@ -181,7 +181,7 @@ openim::golang::check_openim_binaries() { done return 1 else - echo "All binaries have been installed in ${OPENIM_OUTPUT_HOSTBIN}。" + echo -e "All binaries have been installed in: \n${OPENIM_OUTPUT_HOSTBIN}\n${OPENIM_OUTPUT_HOSTBIN_TOOLS}" return 0 fi } @@ -314,14 +314,9 @@ openim::golang::setup_platforms readonly OPENIM_CLIENT_TARGETS=( changelog component - conversion-msg - conversion-mysql - formitychecker imctl infra ncpu - openim-web - up35 versionchecker yamlfmt ) diff --git a/scripts/lib/logging.sh b/scripts/lib/logging.sh index fec71b19d..0d15463eb 100755 --- a/scripts/lib/logging.sh +++ b/scripts/lib/logging.sh @@ -210,16 +210,26 @@ openim::log::status() { if [[ ${OPENIM_VERBOSE} < ${V} ]]; then return fi - - timestamp=$(date +"[%Y-%m-%d %H:%M:%S %Z]") - echo_log "${timestamp} ${1}" + + local COLOR_BLUE="\033[0;34m" + local COLOR_RESET="\033[0m" + + local timestamp=$(date +"[%Y-%m-%d %H:%M:%S %Z]") + + echo_log() { + echo -e "$@" + } + + + echo_log "${COLOR_BLUE}${timestamp} ${1}${COLOR_RESET}" shift for message; do - echo_log " ${message}" + echo_log "${COLOR_BLUE}${message}${COLOR_RESET}" done } + openim::log::success() { local V="${V:-0}" if [[ ${OPENIM_VERBOSE} < ${V} ]]; then @@ -232,7 +242,6 @@ openim::log::success() { - function openim::log::test_log() { echo_log "test log" openim::log::info "openim::log::info" @@ -249,3 +258,14 @@ function openim::log::print_blue() { echo -e "\033[0;36m$1\033[0m" } + +openim::log::colorless() { + local V="${V:-0}" + if [[ ${OPENIM_VERBOSE} < ${V} ]]; then + return + fi + timestamp=$(date +"[%Y-%m-%d %H:%M:%S %Z]") + echo_log -e "${timestamp} ${1} " +} + + diff --git a/scripts/lib/release.sh b/scripts/lib/release.sh index c1fbd00a1..3e6cde103 100755 --- a/scripts/lib/release.sh +++ b/scripts/lib/release.sh @@ -243,7 +243,7 @@ function openim::release::package_client_tarballs() { local client_bins=("${OPENIM_CLIENT_BINARIES[@]}") - # client_bins: changelog component conversion-msg conversion-mysql formitychecker imctl infra ncpu openim-web up35 versionchecker yamlfmt + # client_bins: changelog component imctl infra ncpu versionchecker yamlfmt # Copy client binclient_bins:aries openim::log::info " Copy client binaries: ${client_bins[@]/#/${LOCAL_OUTPUT_BINTOOLSPATH}/${platform}/}" openim::log::info " Copy client binaries to: ${release_stage}/client/bin" diff --git a/scripts/lib/util.sh b/scripts/lib/util.sh index dcce56c12..13bd263ca 100755 --- a/scripts/lib/util.sh +++ b/scripts/lib/util.sh @@ -360,8 +360,8 @@ openim::util::check_ports() { # If any of the processes is not running, return a status of 1. if [[ ${#not_started[@]} -ne 0 ]]; then - openim::color::echo $COLOR_RED "OpenIM Stdout Log >> cat ${LOG_FILE}" - openim::color::echo $COLOR_RED "OpenIM Stderr Log >> cat ${STDERR_LOG_FILE}" + #openim::color::echo $COLOR_RED "OpenIM Stdout Log >> cat ${LOG_FILE}" + #openim::color::echo $COLOR_RED "OpenIM Stderr Log >> cat ${STDERR_LOG_FILE}" cat "$TMP_LOG_FILE" | awk '{print "\033[31m" $0 "\033[0m"}' return 1 else @@ -446,8 +446,8 @@ openim::util::check_process_names() { # Return status if [[ ${#not_started[@]} -ne 0 ]]; then - openim::color::echo $COLOR_RED "OpenIM Stdout Log >> cat ${LOG_FILE}" - openim::color::echo $COLOR_RED "OpenIM Stderr Log >> cat ${STDERR_LOG_FILE}" + #openim::color::echo $COLOR_RED "OpenIM Stdout Log >> cat ${LOG_FILE}" + #openim::color::echo $COLOR_RED "OpenIM Stderr Log >> cat ${STDERR_LOG_FILE}" cat "$TMP_LOG_FILE" | awk '{print "\033[31m" $0 "\033[0m"}' return 1 else @@ -599,33 +599,36 @@ openim::util::stop_services_with_name() { # An array to collect information about processes that were stopped. local stopped=() - echo "Stopping services with names: $*" # Iterate over each given service name. for server_name in "$@"; do # Use the `pgrep` command to find process IDs related to the given service name. local pids=$(pgrep -f "$server_name") - # If no process was found with the name, add it to the not_stopped list if [[ -z $pids ]]; then not_stopped+=("$server_name") continue fi local stopped_this_time=false - for pid in $pids; do + for pid in $pids; do - # Exclude the PID of the current script - if [[ "$pid" == "$$" ]]; then - continue - fi + # Exclude the PID of the current script + if [[ "$pid" == "$$" ]]; then + continue + fi + + # If there's a Process ID, it means the service with the name is running. + if [[ -n $pid ]]; then + # Print the binary path for the PID + binary_path=$(readlink -f /proc/$pid/exe) + openim::log::colorless "stop PID $pid full path: $binary_path" + + # Try to stop the service by killing its process. + if kill -15 $pid 2>/dev/null; then + stopped_this_time=true + fi + fi + done - # If there's a Process ID, it means the service with the name is running. - if [[ -n $pid ]]; then - # Try to stop the service by killing its process. - if kill -15 $pid 2>/dev/null; then - stopped_this_time=true - fi - fi - done if $stopped_this_time; then stopped+=("$server_name") @@ -2848,6 +2851,46 @@ function openim::util::check_process_names_for_stop() { +function openim::util::find_process_ports() { + local process_path="$1" + if [[ -z "$process_path" ]]; then + echo "Usage: find_process_ports /path/to/process" + return 1 + fi + + local protocol_ports="" + while read -r line; do + local port_protocol=($line) + local port=${port_protocol[0]##*:} + local protocol=${port_protocol[1]} + protocol_ports="${protocol_ports}${protocol} ${port}, " + + done < <(lsof -nP -iTCP -iUDP | grep LISTEN | grep "$(pgrep -f "$process_path")" | awk '{print $9, $8}') + + protocol_ports=${protocol_ports%, } + + if [[ -z "$protocol_ports" ]]; then + openim::log::colorless "$process_path is not listening on any ports" + else + openim::log::colorless "$process_path is listening on protocol & port: $protocol_ports" + fi +} + + + + + +function openim::util::find_ports_for_all_services() { + local services=("$@") + for service in "${services[@]}"; do + openim::util::find_process_ports "$service" + done +} + + + + + if [[ "$*" =~ openim::util:: ]];then eval $* fi diff --git a/scripts/list-feature-tests.sh b/scripts/list-feature-tests.sh index d6eaa4873..f48a7a7e3 100755 --- a/scripts/list-feature-tests.sh +++ b/scripts/list-feature-tests.sh @@ -19,8 +19,5 @@ # Usage: `scripts/list-feature-tests.sh`. - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. grep "\[Feature:\w+\]" "${OPENIM_ROOT}"/test/e2e/**/*.go -Eoh | LC_ALL=C sort -u \ No newline at end of file diff --git a/scripts/make-rules/common-versions.mk b/scripts/make-rules/common-versions.mk new file mode 100644 index 000000000..572585fce --- /dev/null +++ b/scripts/make-rules/common-versions.mk @@ -0,0 +1,58 @@ +# Copyright © 2023 OpenIMSDK. +# +# 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. +# ============================================================================== +# OpenIM Makefile Versions used +# +# Define the latest version for each tool to ensure consistent versioning across installations +GOLANGCI_LINT_VERSION ?= latest +GOIMPORTS_VERSION ?= latest +ADDLICENSE_VERSION ?= latest +DEEPCOPY_GEN_VERSION ?= latest +CONVERSION_GEN_VERSION ?= latest +GINKGO_VERSION ?= v1.16.2 +GO_GITLINT_VERSION ?= latest +GO_JUNIT_REPORT_VERSION ?= latest +GOTESTS_VERSION ?= latest +SWAGGER_VERSION ?= latest +KUBE_SCORE_VERSION ?= latest +KUBECONFORM_VERSION ?= latest +GSEMVER_VERSION ?= latest +GIT_CHGLOG_VERSION ?= latest +KO_VERSION ?= latest +GITHUB_RELEASE_VERSION ?= latest +COSCLI_VERSION ?= v0.19.0-beta +MINIO_VERSION ?= latest +DELVE_VERSION ?= latest +AIR_VERSION ?= latest +GOLINES_VERSION ?= latest +GO_MOD_OUTDATED_VERSION ?= latest +CFSSL_VERSION ?= latest +DEPTH_VERSION ?= latest +GO_CALLVIS_VERSION ?= latest +MISSPELL_VERSION ?= latest +GOTHANKS_VERSION ?= latest +RICHGO_VERSION ?= latest +RTS_VERSION ?= latest +TYPECHECK_VERSION ?= latest +COMMENT_LANG_DETECTOR_VERSION ?= latest +STANDARDIZER_VERSION ?= latest +GO_TESTS_VERSION ?= v1.6.0 +GO_APIDIFF_VERSION ?= v0.8.2 +KAFKACTL_VERSION ?= latest +GOTESTSUM_VERSION ?= latest + +WIRE_VERSION ?= latest +# WIRE_VERSION ?= $(call get_go_version,github.com/google/wire) +MOCKGEN_VERSION ?= $(call get_go_version,github.com/golang/mock) +PROTOC_GEN_GO_VERSION ?= $(call get_go_version,github.com/golang/protobuf/protoc-gen-go) \ No newline at end of file diff --git a/scripts/make-rules/common.mk b/scripts/make-rules/common.mk index f8537b6ca..ffbb69a55 100644 --- a/scripts/make-rules/common.mk +++ b/scripts/make-rules/common.mk @@ -78,6 +78,13 @@ VERSION := $(shell git describe --tags --always --match='v*') # v2.3.3: git tag endif +# Helper function to get dependency version from go.mod +get_gomod_version = $(shell go list -m $1 | awk '{print $$2}') +define go_install +$(info ===========> Installing $(1)@$(2)) +$(GO) install $(1)@$(2) +endef + # 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)) diff --git a/scripts/make-rules/gen.mk b/scripts/make-rules/gen.mk index 1671fafff..fba0132ff 100644 --- a/scripts/make-rules/gen.mk +++ b/scripts/make-rules/gen.mk @@ -88,11 +88,6 @@ gen.docgo.check: gen.docgo.doc gen.docgo.add: @git ls-files --others '*/doc.go' | $(XARGS) -- git add -## gen.docgo: Generate missing doc.go for go packages ✨ -.PHONY: gen.defaultconfigs -gen.defaultconfigs: - @${ROOT_DIR}/scripts/gen_default_config.sh - ## gen.docgo: Generate missing doc.go for go packages ✨ .PHONY: gen.clean gen.clean: @@ -101,5 +96,5 @@ gen.clean: ## gen.help: show help for gen .PHONY: gen.help -gen.help: scripts/make-rules/gen.mk +gen.help: scripts/make-rules/gen.m $(call smallhelp) \ No newline at end of file diff --git a/scripts/make-rules/golang.mk b/scripts/make-rules/golang.mk index 5a5d1a788..cdc5a5fc9 100644 --- a/scripts/make-rules/golang.mk +++ b/scripts/make-rules/golang.mk @@ -17,7 +17,7 @@ # GO := go -GO_SUPPORTED_VERSIONS ?= 1.19|1.20|1.21|1.22|1.23 +GO_MINIMUM_VERSION ?= 1.19 GO_LDFLAGS += -X $(VERSION_PACKAGE).gitVersion=$(GIT_TAG) \ -X $(VERSION_PACKAGE).gitCommit=$(GIT_COMMIT) \ @@ -104,25 +104,25 @@ go.build: go.build.verify $(addprefix go.build., $(addprefix $(PLATFORM)., $(BIN ## go.start: Start openim .PHONY: go.start go.start: - @echo "===========> Starting openim" + @echo "=========================> Starting OpenIM <=========================" @$(ROOT_DIR)/scripts/start-all.sh ## go.stop: Stop openim .PHONY: go.stop go.stop: - @echo "===========> Stopping openim" + @echo "=========================> Stopping OpenIM <=========================" @$(ROOT_DIR)/scripts/stop-all.sh ## go.check: Check openim .PHONY: go.check go.check: - @echo "===========> Checking openim" + @echo "=========================> Checking OpenIM <=========================" @$(ROOT_DIR)/scripts/check-all.sh ## go.check-component: Check openim component .PHONY: go.check-component go.check-component: - @echo "===========> Checking openim component" + @echo "=========================> Checking OpenIM component <=========================" @$(ROOT_DIR)/scripts/install/openim-tools.sh openim::tools::pre-start ## go.versionchecker: Design, detect some environment variables and versions @@ -132,8 +132,8 @@ go.versionchecker: ## go.build.verify: Verify that a suitable version of Go exists .PHONY: go.build.verify go.build.verify: -ifneq ($(shell $(GO) version | grep -q -E '\bgo($(GO_SUPPORTED_VERSIONS))\b' && echo 0 || echo 1), 0) - $(error unsupported go version. Please make install one of the following supported version: '$(GO_SUPPORTED_VERSIONS)') +ifneq ($(shell $(GO) version|awk -v min=$(GO_MINIMUM_VERSION) '{gsub(/go/,"",$$3);if($$3 >= min){print 0}else{print 1}}'), 0) + $(error unsupported go version. Please install a go version which is greater than or equal to '$(GO_MINIMUM_VERSION)') endif ## go.build.%: Build binaries for a specific platform diff --git a/scripts/make-rules/tools.mk b/scripts/make-rules/tools.mk index 5d39258ea..5335d094d 100644 --- a/scripts/make-rules/tools.mk +++ b/scripts/make-rules/tools.mk @@ -64,81 +64,101 @@ tools.verify.%: ## install.golangci-lint: Install golangci-lint .PHONY: install.golangci-lint install.golangci-lint: - @$(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@latest + @$(GO) install github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_VERSION) ## install.goimports: Install goimports, used to format go source files .PHONY: install.goimports install.goimports: - @$(GO) install golang.org/x/tools/cmd/goimports@latest + @$(GO) install golang.org/x/tools/cmd/goimports@$(GOIMPORTS_VERSION) ## install.addlicense: Install addlicense, used to add license header to source files .PHONY: install.addlicense install.addlicense: - @$(GO) install github.com/google/addlicense@latest + @$(GO) install github.com/google/addlicense@$(ADDLICENSE_VERSION) ## install.deepcopy-gen: Install deepcopy-gen, used to generate deep copy functions .PHONY: install.deepcopy-gen install.deepcopy-gen: - @$(GO) install k8s.io/code-generator/cmd/deepcopy-gen@latest + @$(GO) install k8s.io/code-generator/cmd/deepcopy-gen@$(DEEPCOPY_GEN_VERSION) ## install.conversion-gen: Install conversion-gen, used to generate conversion functions .PHONY: install.conversion-gen install.conversion-gen: - @$(GO) install k8s.io/code-generator/cmd/conversion-gen@latest + @$(GO) install k8s.io/code-generator/cmd/conversion-gen@$(CONVERSION_GEN_VERSION) ## install.ginkgo: Install ginkgo to run a single test or set of tests .PHONY: install.ginkgo install.ginkgo: - @$(GO) install github.com/onsi/ginkgo/ginkgo@v1.16.2 + @$(GO) install github.com/onsi/ginkgo/ginkgo@$(GINKGO_VERSION) -## Install go-gitlint: Install go-gitlint, used to check git commit message +## install.go-gitlint: Install go-gitlint, used to check git commit message .PHONY: install.go-gitlint install.go-gitlint: - @$(GO) install github.com/marmotedu/go-gitlint/cmd/go-gitlint@latest + @$(GO) install github.com/marmotedu/go-gitlint/cmd/go-gitlint@$(GO_GITLINT_VERSION) ## install.go-junit-report: Install go-junit-report, used to convert go test output to junit xml .PHONY: install.go-junit-report install.go-junit-report: - @$(GO) install github.com/jstemmer/go-junit-report@latest + @$(GO) install github.com/jstemmer/go-junit-report@$(GO_JUNIT_REPORT_VERSION) ## install.gotests: Install gotests, used to generate go tests +.PHONY: install.gotests +install.gotests: + @$(GO) install github.com/cweill/gotests/gotests@$(GO_TESTS_VERSION) + +## install.kafkactl: Install kafkactl command line tool. +.PHONY: install.kafkactl +install.kafkactl: + @$(GO) install github.com/deviceinsight/kafkactl@$(KAFKACTL_VERSION) + +## install.go-apidiff: Install go-apidiff, used to check api changes +.PHONY: install.go-apidiff +install.go-apidiff: + @$(GO) install github.com/joelanford/go-apidiff@$(GO_APIDIFF_VERSION) + +## install.swagger: Install swagger, used to generate swagger documentation .PHONY: install.swagger install.swagger: - @$(GO) install github.com/go-swagger/go-swagger/cmd/swagger@latest + @$(GO) install github.com/go-swagger/go-swagger/cmd/swagger@$(SWAGGER_VERSION) # ============================================================================== # Tools that might be used include go gvm # +## install.gotestsum: Install gotestsum, used to run go tests +.PHONY: install.gotestsum +install.gotestsum: + @$(GO) install gotest.tools/gotestsum@$(GOTESTSUM_VERSION) + ## 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 + @$(GO) install github.com/zegl/kube-score/cmd/kube-score@$(KUBE_SCORE_VERSION) ## install.kubeconform: Install kubeconform, used to check kubernetes yaml files .PHONY: install.kubeconform install.kubeconform: - @$(GO) install github.com/yannh/kubeconform/cmd/kubeconform@latest + @$(GO) install github.com/yannh/kubeconform/cmd/kubeconform@$(KUBECONFORM_VERSION) ## install.gsemver: Install gsemver, used to generate semver .PHONY: install.gsemver install.gsemver: - @$(GO) install github.com/arnaud-deprez/gsemver@latest + @$(GO) install github.com/arnaud-deprez/gsemver@$(GSEMVER_VERSION) ## 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 + @$(GO) install github.com/git-chglog/git-chglog/cmd/git-chglog@$(GIT_CHGLOG_VERSION) ## install.ko: Install ko, used to build go program into container images .PHONY: install.ko install.ko: - @$(GO) install github.com/google/ko@latest + @$(GO) install github.com/google/ko@$(KO_VERSION) ## 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 + @$(GO) install github.com/github-release/github-release@$(GITHUB_RELEASE_VERSION) ## install.coscli: Install coscli, used to upload files to cos # example: ./coscli cp/sync -r /home/off-line/docker-off-line/ cos://openim-1306374445/openim/image/amd/off-line/off-line/ -e cos.ap-guangzhou.myqcloud.com @@ -146,7 +166,7 @@ install.github-release: # amd64 .PHONY: install.coscli install.coscli: - @wget -q https://github.com/tencentyun/coscli/releases/download/v0.19.0-beta/coscli-linux -O ${TOOLS_DIR}/coscli + @wget -q https://github.com/tencentyun/coscli/releases/download/$(COSCLI_VERSION)/coscli-linux -O ${TOOLS_DIR}/coscli @chmod +x ${TOOLS_DIR}/coscli ## install.coscmd: Install coscmd, used to upload files to cos @@ -157,50 +177,50 @@ install.coscmd: ## install.minio: Install minio, used to upload files to minio .PHONY: install.minio install.minio: - @$(GO) install github.com/minio/minio@latest + @$(GO) install github.com/minio/minio@$(MINIO_VERSION) ## install.delve: Install delve, used to debug go program .PHONY: install.delve install.delve: - @$(GO) install github.com/go-delve/delve/cmd/dlv@latest + @$(GO) install github.com/go-delve/delve/cmd/dlv@$(DELVE_VERSION) ## install.air: Install air, used to hot reload go program .PHONY: install.air install.air: - @$(GO) install github.com/cosmtrek/air@latest + @$(GO) install github.com/cosmtrek/air@$(AIR_VERSION) ## install.gvm: Install gvm, gvm is a Go version manager, built on top of the official go tool. -# github: https://github.com/moovweb/gvm .PHONY: install.gvm install.gvm: - @echo "===========> Installing gvm,The default installation path is ~/.gvm/scripts/gvm" + @echo "===========> Installing gvm, The default installation path is ~/.gvm/scripts/gvm" @bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) - @$(shell source /root/.gvm/scripts/gvm) + @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 + @$(GO) install github.com/segmentio/golines@$(GOLINES_VERSION) ## 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 + @$(GO) install github.com/psampaz/go-mod-outdated@$(GO_MOD_OUTDATED_VERSION) ## install.mockgen: Install mockgen, used to generate mock functions .PHONY: install.mockgen install.mockgen: - @$(GO) install github.com/golang/mock/mockgen@latest + @$(GO) install github.com/golang/mock/mockgen@$(MOCKGEN_VERSION) + +## install.wire: Install wire, used to generate wire files +.PHONY: install.wire +install.wire: + @$(GO) install github.com/google/wire/cmd/wire@$(WIRE_VERSION) -## 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 + @$(GO) install github.com/golang/protobuf/protoc-gen-go@$(PROTOC_GEN_GO_VERSION) ## install.cfssl: Install cfssl, used to generate certificates .PHONY: install.cfssl @@ -210,32 +230,49 @@ 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 + @$(GO) install github.com/KyleBanks/depth/cmd/depth@$(DEPTH_VERSION) ## 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 + @$(GO) install github.com/ofabry/go-callvis@$(GO_CALLVIS_VERSION) -## install.misspell +## install.misspell: Install misspell .PHONY: install.misspell install.misspell: - @$(GO) install github.com/client9/misspell/cmd/misspell@latest + @$(GO) install github.com/client9/misspell/cmd/misspell@$(MISSPELL_VERSION) ## install.gothanks: Install gothanks, used to thank go dependencies .PHONY: install.gothanks install.gothanks: - @$(GO) install github.com/psampaz/gothanks@latest + @$(GO) install github.com/psampaz/gothanks@$(GOTHANKS_VERSION) ## install.richgo: Install richgo .PHONY: install.richgo install.richgo: - @$(GO) install github.com/kyoh86/richgo@latest + @$(GO) install github.com/kyoh86/richgo@$(RICHGO_VERSION) ## install.rts: Install rts .PHONY: install.rts install.rts: - @$(GO) install github.com/galeone/rts/cmd/rts@latest + @$(GO) install github.com/galeone/rts/cmd/rts@$(RTS_VERSION) + +# ================= kubecub openim tools ========================================= +# https://github.com/kubecub +## install.typecheck: Install kubecub typecheck, checks for go code +.PHONY: install.typecheck +install.typecheck: + @$(GO) install github.com/kubecub/typecheck@$(TYPECHECK_VERSION) + +## install.comment-lang-detector: Install kubecub comment-lang-detector, checks for go code comment language +.PHONY: install.comment-lang-detector +install.comment-lang-detector: + @$(GO) install github.com/kubecub/comment-lang-detector/cmd/cld@$(COMMENT_LANG_DETECTOR_VERSION) + +## install.standardizer: Install kubecub standardizer, checks for go code standardization +.PHONY: install.standardizer +install.standardizer: + @$(GO) install github.com/kubecub/standardizer@$(STANDARDIZER_VERSION) ## tools.help: Display help information about the tools package .PHONY: tools.help diff --git a/scripts/mongo-init.sh b/scripts/mongo-init.sh index 41d9ca0aa..01199c480 100755 --- a/scripts/mongo-init.sh +++ b/scripts/mongo-init.sh @@ -12,19 +12,37 @@ # See the License for the specific language governing permissions and # limitations under the License. -set -e - mongosh <`. - - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. source "${OPENIM_ROOT}/scripts/lib/init.sh" diff --git a/scripts/start-all.sh b/scripts/start-all.sh index 952995032..6f4a6c574 100755 --- a/scripts/start-all.sh +++ b/scripts/start-all.sh @@ -16,18 +16,9 @@ #FIXME This script is the startup script for multiple servers. #FIXME The full names of the shell scripts that need to be started are placed in the `need_to_start_server_shell` array. - -#!/bin/bash - - - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. source "${OPENIM_ROOT}/scripts/install/common.sh" - - # Function to execute the scripts. function execute_start_scripts() { for script_path in "${OPENIM_SERVER_SCRIPT_START_LIST[@]}"; do @@ -42,8 +33,7 @@ function execute_start_scripts() { # Check if the script file exists and is executable. if [[ -x "$script_path" ]]; then - openim::log::print_blue "Starting script: ${script_path##*/}" # Log the script name. - + openim::log::colorless "Starting script: ${script_path##*/}" # Log the script name. # Execute the script with the constructed argument. result=$("$script_path" "$arg") if [[ $? -ne 0 ]]; then @@ -59,41 +49,31 @@ function execute_start_scripts() { done } - - - if openim::util::is_running_in_container; then exec >> ${DOCKER_LOG_FILE} 2>&1 fi - - openim::golang::check_openim_binaries if [[ $? -ne 0 ]]; then openim::log::error "OpenIM binaries are not found. Please run 'make build' to build binaries." "${OPENIM_ROOT}"/scripts/build-all-service.sh fi - "${OPENIM_ROOT}"/scripts/init-config.sh --skip #openim::log::print_blue "Execute the following script in sequence: ${OPENIM_SERVER_SCRIPTARIES[@]}" - # TODO Prelaunch tools, simple for now, can abstract functions later TOOLS_START_SCRIPTS_PATH=${START_SCRIPTS_PATH}/openim-tools.sh -openim::log::print_blue "\n## Pre Starting OpenIM services" - - - -if ! ${TOOLS_START_SCRIPTS_PATH} openim::tools::pre-start; then - openim::log::error "Pre Starting OpenIM services failed, aborting..." - exit 1 -fi +openim::log::status "Start the pre-start tools:" +# if ! ${TOOLS_START_SCRIPTS_PATH} openim::tools::pre-start; then +# openim::log::error "Start the pre-start tools, aborting!" +# exit 1 +# fi -openim::log::print_blue "Pre Starting OpenIM services processed successfully" +openim::log::colorless "pre-start has been successfully completed!" result=$("${OPENIM_ROOT}"/scripts/stop-all.sh) if [[ $? -ne 0 ]]; then @@ -102,28 +82,28 @@ if [[ $? -ne 0 ]]; then exit 1 fi - - -openim::log::status "\n## Starting openim scripts: " +openim::log::status "Start the OpenIM startup scripts: " execute_start_scripts +openim::log::status "OpenIM startup scripts have been successfully completed!" sleep 2 result=$(. $(dirname ${BASH_SOURCE})/install/openim-msgtransfer.sh openim::msgtransfer::check) if [[ $? -ne 0 ]]; then - openim::log::error "The program may fail to start.\n $result" + openim::log::error "The OpenIM services may fail to start.\n $result" exit 1 fi - result=$(openim::util::check_process_names ${OPENIM_ALL_SERVICE_LIBRARIES_NO_TRANSFER[@]}) if [[ $? -ne 0 ]]; then - openim::log::error "The program may fail to start.\n $result" + openim::log::error "The OpenIM services may fail to start.\n $result" exit 1 fi +openim::log::status "Start the post-start tools:" +# ${TOOLS_START_SCRIPTS_PATH} openim::tools::post-start +openim::log::status "post-start has been successfully completed!" +openim::util::find_ports_for_all_services ${OPENIM_ALL_SERVICE_LIBRARIES_NO_TRANSFER[@]} +openim::util::find_ports_for_all_services ${OPENIM_MSGTRANSFER_BINARY[@]} -openim::log::info "\n## Post Starting openim services" -${TOOLS_START_SCRIPTS_PATH} openim::tools::post-start - -openim::log::success "All openim services have been successfully started!" \ No newline at end of file +openim::log::success "All OpenIM services have been successfully started!" \ No newline at end of file diff --git a/scripts/update-generated-docs.sh b/scripts/update-generated-docs.sh index d48a4067b..4c1fbfccc 100755 --- a/scripts/update-generated-docs.sh +++ b/scripts/update-generated-docs.sh @@ -18,10 +18,6 @@ # immediately before exporting docs. We do not want to check these documents in # by default. - - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. source "${OPENIM_ROOT}/scripts/lib/init.sh" @@ -33,7 +29,7 @@ BINS=( genman genyaml ) -make -C "${OPENIM_ROOT}" WHAT="${BINS[*]}" +make -C "${OPENIM_ROOT}" BINS="${BINS[*]}" openim::util::ensure-temp-dir diff --git a/scripts/update-yamlfmt.sh b/scripts/update-yamlfmt.sh index 24ec60de9..8de0cc84c 100755 --- a/scripts/update-yamlfmt.sh +++ b/scripts/update-yamlfmt.sh @@ -13,11 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. - - - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. source "${OPENIM_ROOT}/scripts/lib/init.sh" diff --git a/scripts/verify-shellcheck.sh b/scripts/verify-shellcheck.sh index 0c4f165bf..3e56038dd 100755 --- a/scripts/verify-shellcheck.sh +++ b/scripts/verify-shellcheck.sh @@ -17,10 +17,6 @@ # This script lints each shell script by `shellcheck`. # Usage: `scripts/verify-shellcheck.sh`. - - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. source "${OPENIM_ROOT}/scripts/lib/init.sh" diff --git a/scripts/verify-spelling.sh b/scripts/verify-spelling.sh index fa0852866..c718c1ad1 100755 --- a/scripts/verify-spelling.sh +++ b/scripts/verify-spelling.sh @@ -17,10 +17,6 @@ # working directory by client9/misspell package. # Usage: `scripts/verify-spelling.sh`. - - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. export OPENIM_ROOT source "${OPENIM_ROOT}/scripts/lib/init.sh" diff --git a/scripts/verify-standardizer.sh b/scripts/verify-standardizer.sh new file mode 100755 index 000000000..08a13b9a2 --- /dev/null +++ b/scripts/verify-standardizer.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# Copyright © 2023 OpenIM. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# This script does a fast type check of script srnetes code for all platforms. +# Usage: `scripts/verify-standardizer.sh`. + +OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +source "${OPENIM_ROOT}/scripts/lib/init.sh" + +openim::golang::verify_go_version + +cd "${OPENIM_ROOT}" +ret=0 +scripts/run-in-gopath.sh \ +make tools.verify.standardizer +${OPENIM_ROOT}/_output/tools/standardizer || ret=$? +if [[ $ret -ne 0 ]]; then + openim::log::error "Failed to check the directory name or file name. Your name may not meet the specification. Please check the configuration file and the directory or file name." >&2 + openim::log::error "Please see https://github.com/kubecub/standardizer for more information." >&2 + exit 1 +fi diff --git a/scripts/verify-typecheck.sh b/scripts/verify-typecheck.sh index c9b2aaf30..f6c14844f 100755 --- a/scripts/verify-typecheck.sh +++ b/scripts/verify-typecheck.sh @@ -16,26 +16,19 @@ # This script does a fast type check of script srnetes code for all platforms. # Usage: `scripts/verify-typecheck.sh`. - - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. source "${OPENIM_ROOT}/scripts/lib/init.sh" openim::golang::verify_go_version cd "${OPENIM_ROOT}" - -# As of June, 2020 the typecheck tool is written in terms of go/packages, but -# that library doesn't work well with multiple modules. Until that is done, -# force this tooling to run in a fake GOPATH. ret=0 TYPECHECK_SERIAL="${TYPECHECK_SERIAL:-false}" scripts/run-in-gopath.sh \ -go run test/typecheck/typecheck.go "$@" "--serial=$TYPECHECK_SERIAL" || ret=$? +make tools.verify.typecheck +${OPENIM_ROOT}/_output/tools/typecheck "$@" "--serial=$TYPECHECK_SERIAL" || ret=$? if [[ $ret -ne 0 ]]; then openim::log::error "Type Check has failed. This may cause cross platform build failures." >&2 - openim::log::error "Please see https://github.com/openimsdk/open-im-server/tree/main/test/typecheck for more information." >&2 + openim::log::error "Please see https://github.com/kubecub/typecheck for more information." >&2 exit 1 fi diff --git a/scripts/verify-yamlfmt.sh b/scripts/verify-yamlfmt.sh index 3acbf457c..a0aa583a8 100755 --- a/scripts/verify-yamlfmt.sh +++ b/scripts/verify-yamlfmt.sh @@ -19,10 +19,6 @@ # # Usage: `scripts/verify-yamlfmt.sh`. - - - - OPENIM_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. source "${OPENIM_ROOT}/scripts/lib/init.sh" diff --git a/start-config.yml b/start-config.yml new file mode 100644 index 000000000..cd9663c98 --- /dev/null +++ b/start-config.yml @@ -0,0 +1,19 @@ +serviceBinaries: + openim-api: 1 + openim-crontask: 1 + openim-rpc-user: 1 + openim-msggateway: 1 + openim-push: 1 + openim-msgtransfer: 1 + openim-rpc-conversation: 1 + openim-rpc-auth: 1 + openim-rpc-group: 1 + openim-rpc-friend: 1 + openim-rpc-msg: 1 + openim-rpc-third: 1 +toolBinaries: + - ncpu + - check-free-memory + - versionchecker + - check-component +maxFileDescriptors: 10000 diff --git a/test/e2e/framework/helpers/chat/chat.go b/test/e2e/framework/helpers/chat/chat.go index aa37c34b5..0613ff569 100644 --- a/test/e2e/framework/helpers/chat/chat.go +++ b/test/e2e/framework/helpers/chat/chat.go @@ -38,7 +38,6 @@ func main() { // } latestVersion := defaultTemplateVersion - // getLatestVersion // getLatestVersion // Construct the download URL @@ -101,9 +100,8 @@ func main() { select {} } -// getLatestVersion fetches the latest version number from a given URL. /* func getLatestVersion(url string) (string, error) { - resp, err := http.Get(url) + resp, err := webhook.Get(url) if err != nil { return "", err } diff --git a/test/testdata/requests/sendMessage.json b/test/testdata/requests/send-message.json similarity index 100% rename from test/testdata/requests/sendMessage.json rename to test/testdata/requests/send-message.json diff --git a/test/typecheck/README.md b/test/typecheck/README.md deleted file mode 100644 index e5b76d4c6..000000000 --- a/test/typecheck/README.md +++ /dev/null @@ -1,52 +0,0 @@ -# OpenIM Typecheck: Cross-Platform Source Code Type Checking for Go - -## Introduction - -OpenIM Typecheck is a robust tool designed for cross-platform source code type checking across all Go build platforms. This utility leverages Go’s built-in parsing and type-check libraries (`go/parser` and `go/types`) to deliver efficient and reliable code analysis. - -## Advantages - -- **Speed**: A complete compilation with OpenIM can take approximately 3 minutes. In contrast, OpenIM Typecheck achieves this in mere seconds, significantly enhancing productivity. -- **Resource Efficiency**: Unlike the typical requirement of over 40GB of RAM for standard processes, Typecheck operates effectively with less than 8GB of RAM. This reduction in resource consumption makes it highly suitable for a variety of systems, reducing overheads and facilitating smoother operations. - -## Implementation - -OpenIM Typecheck employs Go's native parsing and type-checking libraries (`go/parser` and `go/types`). However, it's important to note that these libraries aren't identical to those used by the Go compiler. While occasional mismatches may occur, these libraries generally provide close approximations to the compiler's functionality, offering a reliable basis for type checking. - -## Error Handling - -Typecheck's approach to error handling is pragmatic, focusing on practicality and build continuity. - -**Errors reported by `go/types` but not by `go build`**: -- **Actual Errors** (as per the specification): - - These should ideally be rectified. If rectification is not feasible, such as in cases of ongoing work or external dependencies in the code, these errors can be overlooked. - - Example: Unused variables within a closure. -- **False Positives**: - - These errors should be ignored and, where appropriate, reported upstream for resolution. - - Example: Type mismatches between staging and generated types. - -**Errors reported by `go build` but not by us**: -- CGo-related errors, including both syntax and linker issues, are outside our scope. - -## Usage - -### Locally - -To run Typecheck locally, simply use the following command: - -```bash -make verify -``` - -### Continuous Integration (CI) - -In CI environments, Typecheck can be integrated into the workflow as follows: - -```yaml -- name: Typecheck - run: make verify -``` - -This streamlined process facilitates efficient error detection and resolution, ensuring a robust and reliable build pipeline. - -More to learn about typecheck [share blog](https://nsddd.top/posts/concurrent-type-checking-and-cross-platform-development-in-go/) \ No newline at end of file diff --git a/test/typecheck/go.mod b/test/typecheck/go.mod deleted file mode 100644 index 9ef1b1da7..000000000 --- a/test/typecheck/go.mod +++ /dev/null @@ -1,10 +0,0 @@ -module github.com/openimsdk/open-im-server/test/typecheck - -go 1.19 - -require golang.org/x/tools v0.12.0 - -require ( - golang.org/x/mod v0.12.0 // indirect - golang.org/x/sys v0.11.0 // indirect -) diff --git a/test/typecheck/go.sum b/test/typecheck/go.sum deleted file mode 100644 index 14a66101b..000000000 --- a/test/typecheck/go.sum +++ /dev/null @@ -1,7 +0,0 @@ -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= diff --git a/test/typecheck/typecheck.go b/test/typecheck/typecheck.go deleted file mode 100644 index 975ce988d..000000000 --- a/test/typecheck/typecheck.go +++ /dev/null @@ -1,319 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -// do a fast type check of openim code, for all platforms. -package main - -import ( - "flag" - "fmt" - "io" - "log" - "os" - "path/filepath" - "sort" - "strings" - "sync" - "time" - - "golang.org/x/tools/go/packages" -) - -var ( - verbose = flag.Bool("verbose", false, "print more information") - cross = flag.Bool("cross", true, "build for all platforms") - platforms = flag.String("platform", "", "comma-separated list of platforms to typecheck") - timings = flag.Bool("time", false, "output times taken for each phase") - defuses = flag.Bool("defuse", false, "output defs/uses") - serial = flag.Bool("serial", false, "don't type check platforms in parallel (equivalent to --parallel=1)") - parallel = flag.Int("parallel", 2, "limits how many platforms can be checked in parallel. 0 means no limit.") - skipTest = flag.Bool("skip-test", false, "don't type check test code") - tags = flag.String("tags", "", "comma-separated list of build tags to apply in addition to go's defaults") - ignoreDirs = flag.String("ignore-dirs", "", "comma-separated list of directories to ignore in addition to the default hardcoded list including staging, vendor, and hidden dirs") - - // When processed in order, windows and darwin are early to make - // interesting OS-based errors happen earlier. - crossPlatforms = []string{ - "linux/amd64", "windows/386", - "darwin/amd64", "darwin/arm64", - "linux/386", "linux/arm", - "windows/amd64", "linux/arm64", - "linux/ppc64le", "linux/s390x", - "windows/arm64", - } - - // directories we always ignore - standardIgnoreDirs = []string{ - // Staging code is symlinked from vendor/k8s.io, and uses import - // paths as if it were inside of vendor/. It fails typechecking - // inside of staging/, but works when typechecked as part of vendor/. - "staging", - "components", - "logs", - // OS-specific vendor code tends to be imported by OS-specific - // packages. We recursively typecheck imported vendored packages for - // each OS, but don't typecheck everything for every OS. - "vendor", - "test", - "_output", - "*/mw/rpc_server_interceptor.go", - // Tools we use for maintaining the code base but not necessarily - // ship as part of the release - "sopenim::golang::setup_env:tools/yamlfmt/yamlfmt.go:tools", - } -) - -func newConfig(platform string) *packages.Config { - platSplit := strings.Split(platform, "/") - goos, goarch := platSplit[0], platSplit[1] - mode := packages.NeedName | packages.NeedFiles | packages.NeedTypes | packages.NeedSyntax | packages.NeedDeps | packages.NeedImports | packages.NeedModule - if *defuses { - mode = mode | packages.NeedTypesInfo - } - env := append(os.Environ(), - "CGO_ENABLED=1", - fmt.Sprintf("GOOS=%s", goos), - fmt.Sprintf("GOARCH=%s", goarch)) - tagstr := "selinux" - if *tags != "" { - tagstr = tagstr + "," + *tags - } - flags := []string{"-tags", tagstr} - - return &packages.Config{ - Mode: mode, - Env: env, - BuildFlags: flags, - Tests: !(*skipTest), - } -} - -type collector struct { - dirs []string - ignoreDirs []string -} - -func newCollector(ignoreDirs string) collector { - c := collector{ - ignoreDirs: append([]string(nil), standardIgnoreDirs...), - } - if ignoreDirs != "" { - c.ignoreDirs = append(c.ignoreDirs, strings.Split(ignoreDirs, ",")...) - } - return c -} - -func (c *collector) walk(roots []string) error { - for _, root := range roots { - err := filepath.Walk(root, c.handlePath) - if err != nil { - return err - } - } - sort.Strings(c.dirs) - return nil -} - -// handlePath walks the filesystem recursively, collecting directories, -// ignoring some unneeded directories (hidden/vendored) that are handled -// specially later. -func (c *collector) handlePath(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - if info.IsDir() { - name := info.Name() - // Ignore hidden directories (.git, .cache, etc) - if (len(name) > 1 && (name[0] == '.' || name[0] == '_')) || name == "testdata" { - if *verbose { - fmt.Printf("DBG: skipping dir %s\n", path) - } - return filepath.SkipDir - } - for _, dir := range c.ignoreDirs { - if path == dir { - if *verbose { - fmt.Printf("DBG: ignoring dir %s\n", path) - } - return filepath.SkipDir - } - } - // Make dirs into relative pkg names. - // NOTE: can't use filepath.Join because it elides the leading "./" - pkg := path - if !strings.HasPrefix(pkg, "./") { - pkg = "./" + pkg - } - c.dirs = append(c.dirs, pkg) - if *verbose { - fmt.Printf("DBG: added dir %s\n", path) - } - } - return nil -} - -func (c *collector) verify(plat string) ([]string, error) { - errors := []packages.Error{} - start := time.Now() - config := newConfig(plat) - - rootPkgs, err := packages.Load(config, c.dirs...) - if err != nil { - return nil, err - } - - // Recursively import all deps and flatten to one list. - allMap := map[string]*packages.Package{} - for _, pkg := range rootPkgs { - if *verbose { - serialFprintf(os.Stdout, "pkg %q has %d GoFiles\n", pkg.PkgPath, len(pkg.GoFiles)) - } - allMap[pkg.PkgPath] = pkg - if len(pkg.Imports) > 0 { - for _, imp := range pkg.Imports { - if *verbose { - serialFprintf(os.Stdout, "pkg %q imports %q\n", pkg.PkgPath, imp.PkgPath) - } - allMap[imp.PkgPath] = imp - } - } - } - keys := make([]string, 0, len(allMap)) - for k := range allMap { - keys = append(keys, k) - } - sort.Strings(keys) - allList := make([]*packages.Package, 0, len(keys)) - for _, k := range keys { - allList = append(allList, allMap[k]) - } - - for _, pkg := range allList { - if len(pkg.GoFiles) > 0 { - if len(pkg.Errors) > 0 && (pkg.PkgPath == "main" || strings.Contains(pkg.PkgPath, ".")) { - errors = append(errors, pkg.Errors...) - } - } - if *defuses { - for id, obj := range pkg.TypesInfo.Defs { - serialFprintf(os.Stdout, "%s: %q defines %v\n", - pkg.Fset.Position(id.Pos()), id.Name, obj) - } - for id, obj := range pkg.TypesInfo.Uses { - serialFprintf(os.Stdout, "%s: %q uses %v\n", - pkg.Fset.Position(id.Pos()), id.Name, obj) - } - } - } - if *timings { - serialFprintf(os.Stdout, "%s took %.1fs\n", plat, time.Since(start).Seconds()) - } - return dedup(errors), nil -} - -func dedup(errors []packages.Error) []string { - ret := []string{} - - m := map[string]bool{} - for _, e := range errors { - es := e.Error() - if !m[es] { - ret = append(ret, es) - m[es] = true - } - } - return ret -} - -var outMu sync.Mutex - -func serialFprintf(w io.Writer, format string, a ...any) (n int, err error) { - outMu.Lock() - defer outMu.Unlock() - return fmt.Fprintf(w, format, a...) -} - -func main() { - flag.Parse() - args := flag.Args() - - if *verbose { - *serial = true // to avoid confusing interleaved logs - } - - if len(args) == 0 { - args = append(args, ".") - } - - c := newCollector(*ignoreDirs) - - if err := c.walk(args); err != nil { - log.Fatalf("Error walking: %v", err) - } - - plats := crossPlatforms[:] - if *platforms != "" { - plats = strings.Split(*platforms, ",") - } else if !*cross { - plats = plats[:1] - } - - var wg sync.WaitGroup - var failMu sync.Mutex - failed := false - - if *serial { - *parallel = 1 - } else if *parallel == 0 { - *parallel = len(plats) - } - throttle := make(chan int, *parallel) - - for _, plat := range plats { - wg.Add(1) - go func(plat string) { - // block until there's room for this task - throttle <- 1 - defer func() { - // indicate this task is done - <-throttle - }() - - f := false - serialFprintf(os.Stdout, "type-checking %s\n", plat) - errors, err := c.verify(plat) - if err != nil { - serialFprintf(os.Stderr, "ERROR(%s): failed to verify: %v\n", plat, err) - f = true - } else if len(errors) > 0 { - for _, e := range errors { - // Special case CGo errors which may depend on headers we - // don't have. - if !strings.HasSuffix(e, "could not import C (no metadata for C)") { - f = true - serialFprintf(os.Stderr, "ERROR(%s): %s\n", plat, e) - } - } - } - failMu.Lock() - failed = failed || f - failMu.Unlock() - wg.Done() - }(plat) - } - wg.Wait() - if failed { - os.Exit(1) - } -} diff --git a/test/typecheck/typecheck_test.go b/test/typecheck/typecheck_test.go deleted file mode 100644 index 3f6924cbd..000000000 --- a/test/typecheck/typecheck_test.go +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "errors" - "flag" - "os" - "path/filepath" - "testing" - - "golang.org/x/tools/go/packages" -) - -// This exists because `go` is not always in the PATH when running CI. -var goBinary = flag.String("go", "", "path to a `go` binary") - -func TestVerify(t *testing.T) { - // x/tools/packages is going to literally exec `go`, so it needs some - // setup. - setEnvVars(t) - - tcs := []struct { - path string - expect int - }{ - // {"./testdata/good", 0}, - // {"./testdata/bad", 18}, - } - - for _, tc := range tcs { - c := newCollector("") - if err := c.walk([]string{tc.path}); err != nil { - t.Fatalf("error walking %s: %v", tc.path, err) - } - - errs, err := c.verify("linux/amd64") - if err != nil { - t.Errorf("unexpected error: %v", err) - } else if len(errs) != tc.expect { - t.Errorf("Expected %d errors, got %d: %v", tc.expect, len(errs), errs) - } - } -} - -func setEnvVars(t testing.TB) { - t.Helper() - if *goBinary != "" { - newPath := filepath.Dir(*goBinary) - curPath := os.Getenv("PATH") - if curPath != "" { - newPath = newPath + ":" + curPath - } - t.Setenv("PATH", newPath) - } - if os.Getenv("HOME") == "" { - t.Setenv("HOME", "/tmp") - } -} - -func TestHandlePath(t *testing.T) { - c := collector{ - ignoreDirs: standardIgnoreDirs, - } - e := errors.New("ex") - i, _ := os.Stat(".") // i.IsDir() == true - if c.handlePath("foo", nil, e) != e { - t.Error("handlePath not returning errors") - } - if c.handlePath("vendor", i, nil) != filepath.SkipDir { - t.Error("should skip vendor") - } -} - -func TestDedup(t *testing.T) { - testcases := []struct { - input []packages.Error - expected int - }{{ - input: nil, - expected: 0, - }, { - input: []packages.Error{ - {Pos: "file:7", Msg: "message", Kind: packages.ParseError}, - }, - expected: 1, - }, { - input: []packages.Error{ - {Pos: "file:7", Msg: "message1", Kind: packages.ParseError}, - {Pos: "file:8", Msg: "message2", Kind: packages.ParseError}, - }, - expected: 2, - }, { - input: []packages.Error{ - {Pos: "file:7", Msg: "message1", Kind: packages.ParseError}, - {Pos: "file:8", Msg: "message2", Kind: packages.ParseError}, - {Pos: "file:7", Msg: "message1", Kind: packages.ParseError}, - }, - expected: 2, - }} - - for i, tc := range testcases { - out := dedup(tc.input) - if len(out) != tc.expected { - t.Errorf("[%d] dedup(%v) = '%v', expected %d", - i, tc.input, out, tc.expected) - } - } -} diff --git a/tools/changelog/go.mod b/tools/changelog/go.mod deleted file mode 100644 index b5a2272a4..000000000 --- a/tools/changelog/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/openimsdk/open-im-server/v3/tools/changelog - -go 1.19 diff --git a/tools/changelog/changelog.go b/tools/changelog/main.go similarity index 100% rename from tools/changelog/changelog.go rename to tools/changelog/main.go diff --git a/tools/check-component/main.go b/tools/check-component/main.go new file mode 100644 index 000000000..d3225abb8 --- /dev/null +++ b/tools/check-component/main.go @@ -0,0 +1,165 @@ +// Copyright © 2023 OpenIM. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "context" + "flag" + "fmt" + "github.com/openimsdk/open-im-server/v3/pkg/common/cmd" + "github.com/openimsdk/open-im-server/v3/pkg/common/config" + "github.com/openimsdk/tools/db/mongoutil" + "github.com/openimsdk/tools/db/redisutil" + "github.com/openimsdk/tools/discovery/zookeeper" + "github.com/openimsdk/tools/mq/kafka" + "github.com/openimsdk/tools/s3/minio" + "github.com/openimsdk/tools/system/program" + "io/ioutil" + "log" + "os" + "path/filepath" + "time" +) + +const maxRetry = 180 + +func CheckZookeeper(ctx context.Context, config *config.ZooKeeper) error { + // Temporary disable logging + originalLogger := log.Default().Writer() + log.SetOutput(ioutil.Discard) + defer log.SetOutput(originalLogger) // Ensure logging is restored + return zookeeper.Check(ctx, config.Address, config.Schema, zookeeper.WithUserNameAndPassword(config.Username, config.Password)) +} + +func CheckMongo(ctx context.Context, config *config.Mongo) error { + return mongoutil.Check(ctx, config.Build()) +} + +func CheckRedis(ctx context.Context, config *config.Redis) error { + return redisutil.Check(ctx, config.Build()) +} + +func CheckMinIO(ctx context.Context, config *config.Minio) error { + return minio.Check(ctx, config.Build()) +} + +func CheckKafka(ctx context.Context, conf *config.Kafka) error { + return kafka.Check(ctx, conf.Build(), []string{conf.ToMongoTopic, conf.ToRedisTopic, conf.ToPushTopic}) +} + +func initConfig(configDir string) (*config.Mongo, *config.Redis, *config.Kafka, *config.Minio, *config.ZooKeeper, error) { + var ( + mongoConfig = &config.Mongo{} + redisConfig = &config.Redis{} + kafkaConfig = &config.Kafka{} + minioConfig = &config.Minio{} + zookeeperConfig = &config.ZooKeeper{} + ) + err := config.LoadConfig(filepath.Join(configDir, cmd.MongodbConfigFileName), cmd.ConfigEnvPrefixMap[cmd.MongodbConfigFileName], mongoConfig) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + err = config.LoadConfig(filepath.Join(configDir, cmd.RedisConfigFileName), cmd.ConfigEnvPrefixMap[cmd.RedisConfigFileName], redisConfig) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + err = config.LoadConfig(filepath.Join(configDir, cmd.KafkaConfigFileName), cmd.ConfigEnvPrefixMap[cmd.KafkaConfigFileName], kafkaConfig) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + err = config.LoadConfig(filepath.Join(configDir, cmd.MinioConfigFileName), cmd.ConfigEnvPrefixMap[cmd.MinioConfigFileName], minioConfig) + if err != nil { + return nil, nil, nil, nil, nil, err + } + + err = config.LoadConfig(filepath.Join(configDir, cmd.ZookeeperConfigFileName), cmd.ConfigEnvPrefixMap[cmd.ZookeeperConfigFileName], zookeeperConfig) + if err != nil { + return nil, nil, nil, nil, nil, err + } + return mongoConfig, redisConfig, kafkaConfig, minioConfig, zookeeperConfig, nil +} + +func main() { + var index int + var configDir string + flag.IntVar(&index, "i", 0, "Index number") + defaultConfigDir := filepath.Join("..", "..", "..", "..", "..", "config") + flag.StringVar(&configDir, "c", defaultConfigDir, "Configuration dir") + flag.Parse() + + fmt.Printf("%s Index: %d, Config Path: %s\n", filepath.Base(os.Args[0]), index, configDir) + + mongoConfig, redisConfig, kafkaConfig, minioConfig, zookeeperConfig, err := initConfig(configDir) + if err != nil { + program.ExitWithError(err) + } + + ctx := context.Background() + err = performChecks(ctx, mongoConfig, redisConfig, kafkaConfig, minioConfig, zookeeperConfig, maxRetry) + if err != nil { + // Assume program.ExitWithError logs the error and exits. + // Replace with your error handling logic as necessary. + program.ExitWithError(err) + } +} + +func performChecks(ctx context.Context, mongoConfig *config.Mongo, redisConfig *config.Redis, kafkaConfig *config.Kafka, minioConfig *config.Minio, zookeeperConfig *config.ZooKeeper, maxRetry int) error { + checksDone := make(map[string]bool) + + checks := map[string]func() error{ + "Zookeeper": func() error { + return CheckZookeeper(ctx, zookeeperConfig) + }, + "Mongo": func() error { + return CheckMongo(ctx, mongoConfig) + }, + "Redis": func() error { + return CheckRedis(ctx, redisConfig) + }, + "MinIO": func() error { + return CheckMinIO(ctx, minioConfig) + }, + "Kafka": func() error { + return CheckKafka(ctx, kafkaConfig) + }, + } + + for i := 0; i < maxRetry; i++ { + allSuccess := true + for name, check := range checks { + if !checksDone[name] { + if err := check(); err != nil { + fmt.Printf("%s check failed: %v\n", name, err) + allSuccess = false + } else { + fmt.Printf("%s check succeeded.\n", name) + checksDone[name] = true + } + } + } + + if allSuccess { + fmt.Println("All components checks passed successfully.") + return nil + } + + time.Sleep(1 * time.Second) + } + + return fmt.Errorf("not all components checks passed successfully after %d attempts", maxRetry) +} diff --git a/tools/check-free-memory/main.go b/tools/check-free-memory/main.go new file mode 100644 index 000000000..e182a1544 --- /dev/null +++ b/tools/check-free-memory/main.go @@ -0,0 +1,26 @@ +package main + +import ( + "fmt" + "os" + + "github.com/shirou/gopsutil/mem" +) + +func main() { + vMem, err := mem.VirtualMemory() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get virtual memory info: %v\n", err) + os.Exit(1) + } + + // Use the Available field to get the available memory + availableMemoryGB := float64(vMem.Available) / float64(1024*1024*1024) + + if availableMemoryGB < 1.0 { + fmt.Fprintf(os.Stderr, "System available memory is less than 1GB: %.2fGB\n", availableMemoryGB) + os.Exit(1) + } else { + fmt.Printf("System available memory is sufficient: %.2fGB\n", availableMemoryGB) + } +} diff --git a/tools/component/component.go b/tools/component/component.go deleted file mode 100644 index e1f86e120..000000000 --- a/tools/component/component.go +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "errors" - "flag" - "fmt" - "os" - "strconv" - "strings" - "time" - - "github.com/IBM/sarama" - - "github.com/openimsdk/open-im-server/v3/pkg/common/kafka" - - "github.com/OpenIMSDK/tools/component" - "github.com/OpenIMSDK/tools/errs" - - "gopkg.in/yaml.v3" - - "github.com/openimsdk/open-im-server/v3/pkg/common/config" -) - -const ( - // defaultCfgPath is the default path of the configuration file. - defaultCfgPath = "../../../../../config/config.yaml" - maxRetry = 100 -) - -var ( - cfgPath = flag.String("c", defaultCfgPath, "Path to the configuration file") -) - -func initCfg() (*config.GlobalConfig, error) { - data, err := os.ReadFile(*cfgPath) - if err != nil { - return nil, errs.Wrap(err, "ReadFile unmarshal failed") - } - - conf := config.NewGlobalConfig() - err = yaml.Unmarshal(data, &conf) - if err != nil { - return nil, errs.Wrap(err, "InitConfig unmarshal failed") - } - return conf, nil -} - -type checkFunc struct { - name string - function func(*config.GlobalConfig) error - flag bool - config *config.GlobalConfig -} - -// colorErrPrint prints formatted string in red to stderr -func colorErrPrint(msg string) { - // ANSI escape code for red text - const redColor = "\033[31m" - // ANSI escape code to reset color - const resetColor = "\033[0m" - msg = redColor + msg + resetColor - // Print to stderr in red - fmt.Fprintf(os.Stderr, "%s\n", msg) -} - -func colorSuccessPrint(format string, a ...interface{}) { - // ANSI escape code for green text is \033[32m - // \033[0m resets the color - fmt.Printf("\033[32m"+format+"\033[0m", a...) -} - -func main() { - flag.Parse() - - conf, err := initCfg() - if err != nil { - fmt.Printf("Read config failed: %v\n", err) - return - } - - err = configGetEnv(conf) - if err != nil { - fmt.Printf("configGetEnv failed, err:%v", err) - return - } - - checks := []checkFunc{ - {name: "Mongo", function: checkMongo, config: conf}, - {name: "Redis", function: checkRedis, config: conf}, - {name: "Zookeeper", function: checkZookeeper, config: conf}, - {name: "Kafka", function: checkKafka, config: conf}, - } - if conf.Object.Enable == "minio" { - checks = append(checks, checkFunc{name: "Minio", function: checkMinio, config: conf}) - } - - for i := 0; i < maxRetry; i++ { - if i != 0 { - time.Sleep(1 * time.Second) - } - fmt.Printf("Checking components round %v...\n", i+1) - - var err error - allSuccess := true - for index, check := range checks { - if !check.flag { - err = check.function(check.config) - if err != nil { - allSuccess = false - colorErrPrint(fmt.Sprintf("Check component: %s, failed: %v", check.name, err.Error())) - - if check.name == "Minio" { - if errors.Is(err, errMinioNotEnabled) || - errors.Is(err, errSignEndPoint) || - errors.Is(err, errApiURL) { - checks[index].flag = true - continue - } - break - } - } else { - checks[index].flag = true - component.SuccessPrint(fmt.Sprintf("%s connected successfully", check.name)) - } - } - - } - if allSuccess { - component.SuccessPrint("All components started successfully!") - return - } - } - component.ErrorPrint("Some components checked failed!") - os.Exit(-1) -} - -var errMinioNotEnabled = errors.New("minio.Enable is not configured to use MinIO") - -var errSignEndPoint = errors.New("minio.signEndPoint contains 127.0.0.1, causing issues with image sending") -var errApiURL = errors.New("object.apiURL contains 127.0.0.1, causing issues with image sending") - -// checkMongo checks the MongoDB connection without retries -func checkMongo(config *config.GlobalConfig) error { - mongoStu := &component.Mongo{ - URL: config.Mongo.Uri, - Address: config.Mongo.Address, - Database: config.Mongo.Database, - Username: config.Mongo.Username, - Password: config.Mongo.Password, - MaxPoolSize: config.Mongo.MaxPoolSize, - } - err := component.CheckMongo(mongoStu) - - return err -} - -// checkRedis checks the Redis connection -func checkRedis(config *config.GlobalConfig) error { - redisStu := &component.Redis{ - Address: config.Redis.Address, - Username: config.Redis.Username, - Password: config.Redis.Password, - } - err := component.CheckRedis(redisStu) - return err -} - -// checkMinio checks the MinIO connection -func checkMinio(config *config.GlobalConfig) error { - if strings.Contains(config.Object.ApiURL, "127.0.0.1") { - return errs.Wrap(errApiURL) - } - if config.Object.Enable != "minio" { - return errs.Wrap(errMinioNotEnabled) - } - if strings.Contains(config.Object.Minio.Endpoint, "127.0.0.1") { - return errs.Wrap(errSignEndPoint) - } - - minio := &component.Minio{ - ApiURL: config.Object.ApiURL, - Endpoint: config.Object.Minio.Endpoint, - AccessKeyID: config.Object.Minio.AccessKeyID, - SecretAccessKey: config.Object.Minio.SecretAccessKey, - SignEndpoint: config.Object.Minio.SignEndpoint, - UseSSL: getEnv("MINIO_USE_SSL", "false"), - } - err := component.CheckMinio(minio) - return err -} - -// checkZookeeper checks the Zookeeper connection -func checkZookeeper(config *config.GlobalConfig) error { - zkStu := &component.Zookeeper{ - Schema: config.Zookeeper.Schema, - ZkAddr: config.Zookeeper.ZkAddr, - Username: config.Zookeeper.Username, - Password: config.Zookeeper.Password, - } - err := component.CheckZookeeper(zkStu) - return err -} - -// checkKafka checks the Kafka connection -func checkKafka(config *config.GlobalConfig) error { - // Prioritize environment variables - kafkaStu := &component.Kafka{ - Username: config.Kafka.Username, - Password: config.Kafka.Password, - Addr: config.Kafka.Addr, - } - - kafkaClient, err := component.CheckKafka(kafkaStu) - if err != nil { - return err - } - defer kafkaClient.Close() - - // Verify if necessary topics exist - topics, err := kafkaClient.Topics() - if err != nil { - return errs.Wrap(err) - } - - requiredTopics := []string{ - config.Kafka.MsgToMongo.Topic, - config.Kafka.MsgToPush.Topic, - config.Kafka.LatestMsgToRedis.Topic, - } - - for _, requiredTopic := range requiredTopics { - if !isTopicPresent(requiredTopic, topics) { - return errs.Wrap(err, fmt.Sprintf("Kafka doesn't contain topic: %v", requiredTopic)) - } - } - - var tlsConfig *kafka.TLSConfig - if config.Kafka.TLS != nil { - tlsConfig = &kafka.TLSConfig{ - CACrt: config.Kafka.TLS.CACrt, - ClientCrt: config.Kafka.TLS.ClientCrt, - ClientKey: config.Kafka.TLS.ClientKey, - ClientKeyPwd: config.Kafka.TLS.ClientKeyPwd, - InsecureSkipVerify: config.Kafka.TLS.InsecureSkipVerify, - } - } - - _, err = kafka.NewMConsumerGroup(&kafka.MConsumerGroupConfig{ - KafkaVersion: sarama.V2_0_0_0, - OffsetsInitial: sarama.OffsetNewest, - IsReturnErr: false, - UserName: config.Kafka.Username, - Password: config.Kafka.Password, - }, []string{config.Kafka.LatestMsgToRedis.Topic}, - config.Kafka.Addr, config.Kafka.ConsumerGroupID.MsgToRedis, tlsConfig) - if err != nil { - return err - } - - _, err = kafka.NewMConsumerGroup(&kafka.MConsumerGroupConfig{ - KafkaVersion: sarama.V2_0_0_0, - OffsetsInitial: sarama.OffsetNewest, IsReturnErr: false, - }, []string{config.Kafka.MsgToMongo.Topic}, - config.Kafka.Addr, config.Kafka.ConsumerGroupID.MsgToMongo, tlsConfig) - if err != nil { - return err - } - - _, err = kafka.NewMConsumerGroup(&kafka.MConsumerGroupConfig{ - KafkaVersion: sarama.V2_0_0_0, - OffsetsInitial: sarama.OffsetNewest, IsReturnErr: false, - }, []string{config.Kafka.MsgToPush.Topic}, config.Kafka.Addr, - config.Kafka.ConsumerGroupID.MsgToPush, tlsConfig) - if err != nil { - return err - } - - return nil -} - -// isTopicPresent checks if a topic is present in the list of topics -func isTopicPresent(topic string, topics []string) bool { - for _, t := range topics { - if t == topic { - return true - } - } - return false -} - -func configGetEnv(config *config.GlobalConfig) error { - config.Mongo.Uri = getEnv("MONGO_URI", config.Mongo.Uri) - config.Mongo.Username = getEnv("MONGO_OPENIM_USERNAME", config.Mongo.Username) - config.Mongo.Password = getEnv("MONGO_OPENIM_PASSWORD", config.Mongo.Password) - config.Mongo.Address = getArrEnv("MONGO_ADDRESS", "MONGO_PORT", config.Mongo.Address) - config.Mongo.Database = getEnv("MONGO_DATABASE", config.Mongo.Database) - maxPoolSize, err := getEnvInt("MONGO_MAX_POOL_SIZE", config.Mongo.MaxPoolSize) - if err != nil { - return errs.Wrap(err, "MONGO_MAX_POOL_SIZE") - } - config.Mongo.MaxPoolSize = maxPoolSize - - config.Redis.Username = getEnv("REDIS_USERNAME", config.Redis.Username) - config.Redis.Password = getEnv("REDIS_PASSWORD", config.Redis.Password) - config.Redis.Address = getArrEnv("REDIS_ADDRESS", "REDIS_PORT", config.Redis.Address) - - config.Object.ApiURL = getEnv("OBJECT_APIURL", config.Object.ApiURL) - config.Object.Minio.Endpoint = getEnv("MINIO_ENDPOINT", config.Object.Minio.Endpoint) - config.Object.Minio.AccessKeyID = getEnv("MINIO_ACCESS_KEY_ID", config.Object.Minio.AccessKeyID) - config.Object.Minio.SecretAccessKey = getEnv("MINIO_SECRET_ACCESS_KEY", config.Object.Minio.SecretAccessKey) - config.Object.Minio.SignEndpoint = getEnv("MINIO_SIGN_ENDPOINT", config.Object.Minio.SignEndpoint) - - config.Zookeeper.Schema = getEnv("ZOOKEEPER_SCHEMA", config.Zookeeper.Schema) - config.Zookeeper.ZkAddr = getArrEnv("ZOOKEEPER_ADDRESS", "ZOOKEEPER_PORT", config.Zookeeper.ZkAddr) - config.Zookeeper.Username = getEnv("ZOOKEEPER_USERNAME", config.Zookeeper.Username) - config.Zookeeper.Password = getEnv("ZOOKEEPER_PASSWORD", config.Zookeeper.Password) - - config.Kafka.Username = getEnv("KAFKA_USERNAME", config.Kafka.Username) - config.Kafka.Password = getEnv("KAFKA_PASSWORD", config.Kafka.Password) - config.Kafka.Addr = getArrEnv("KAFKA_ADDRESS", "KAFKA_PORT", config.Kafka.Addr) - config.Object.Minio.Endpoint = getMinioAddr("MINIO_ENDPOINT", "MINIO_ADDRESS", "MINIO_PORT", config.Object.Minio.Endpoint) - return nil -} - -func getMinioAddr(key1, key2, key3, fallback string) string { - // Prioritize environment variables - endpoint := getEnv(key1, fallback) - address, addressExist := os.LookupEnv(key2) - port, portExist := os.LookupEnv(key3) - if portExist && addressExist { - endpoint = "http://" + address + ":" + port - return endpoint - } - return endpoint -} - -// Helper function to get environment variable or default value -func getEnv(key, fallback string) string { - if value, exists := os.LookupEnv(key); exists { - return value - } - return fallback -} - -// Helper function to get environment variable or default value -func getEnvInt(key string, fallback int) (int, error) { - if value, exists := os.LookupEnv(key); exists { - val, err := strconv.Atoi(value) - if err != nil { - return 0, errs.Wrap(err, "string to int failed") - } - return val, nil - } - return fallback, nil -} - -func getArrEnv(key1, key2 string, fallback []string) []string { - address, addrExists := os.LookupEnv(key1) - port, portExists := os.LookupEnv(key2) - - if addrExists && portExists { - addresses := strings.Split(address, ",") - for i, addr := range addresses { - addresses[i] = addr + ":" + port - } - return addresses - } - - if addrExists && !portExists { - addresses := strings.Split(address, ",") - for i, addr := range addresses { - addresses[i] = addr + ":" + "0" - } - return addresses - } - - if !addrExists && portExists { - result := make([]string, len(fallback)) - for i, addr := range fallback { - add := strings.Split(addr, ":") - result[i] = add[0] + ":" + port - } - return result - } - return fallback -} diff --git a/tools/component/component_test.go b/tools/component/component_test.go deleted file mode 100644 index c56361b2c..000000000 --- a/tools/component/component_test.go +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "context" - "strconv" - "testing" - "time" - - "github.com/redis/go-redis/v9" -) - -func TestRedis(t *testing.T) { - conf, err := initCfg() - conf.Redis.Address = []string{ - "172.16.8.142:7000", - //"172.16.8.142:7000", "172.16.8.142:7001", "172.16.8.142:7002", "172.16.8.142:7003", "172.16.8.142:7004", "172.16.8.142:7005", - } - - var redisClient redis.UniversalClient - defer func() { - if redisClient != nil { - redisClient.Close() - } - }() - if len(conf.Redis.Address) > 1 { - redisClient = redis.NewClusterClient(&redis.ClusterOptions{ - Addrs: conf.Redis.Address, - Username: conf.Redis.Username, - Password: conf.Redis.Password, - }) - } else { - redisClient = redis.NewClient(&redis.Options{ - Addr: conf.Redis.Address[0], - Username: conf.Redis.Username, - Password: conf.Redis.Password, - }) - } - _, err = redisClient.Ping(context.Background()).Result() - if err != nil { - t.Fatal(err) - } - - for i := 0; i < 1000000; i++ { - val, err := redisClient.Set(context.Background(), "b_"+strconv.Itoa(i), "test", time.Second*10).Result() - t.Log("index", i, "resp", val, "err", err) - if err != nil { - return - } - } - -} diff --git a/tools/component/go.mod b/tools/component/go.mod deleted file mode 100644 index 05a27bcad..000000000 --- a/tools/component/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/openimsdk/open-im-server/v3/tools/component - -go 1.19 diff --git a/tools/formitychecker/README.md b/tools/formitychecker/README.md deleted file mode 100644 index 7cabf8a66..000000000 --- a/tools/formitychecker/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# Development of a Go-Based Conformity Checker for Project File and Directory Naming Standards - -### 1. Project Overview - -#### Project Name - -- `GoConformityChecker` - -#### Functionality Description - -- Checks if the file and subdirectory names in a specified directory adhere to specific naming conventions. -- Supports specific file types (e.g., `.go`, `.yml`, `.yaml`, `.md`, `.sh`, etc.). -- Allows users to specify directories to be checked and directories to be ignored. -- More read https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/code-conventions.md - -#### Naming Conventions - -- Go files: Only underscores are allowed. -- YAML, YML, and Markdown files: Only hyphens are allowed. -- Directories: Only underscores are allowed. - -### 2. File Structure - -- `main.go`: Entry point of the program, handles command-line arguments. -- `checker/checker.go`: Contains the core logic. -- `config/config.go`: Parses and stores configuration information. - -### 3. Core Code Design - -#### main.go - -- Parses command-line arguments, including the directory to be checked and directories to be ignored. -- Calls the `checker` module for checking. - -#### config.go - -- Defines a configuration structure, such as directories to check and ignore. - -#### checker.go - -- Iterates through the specified directory. -- Applies different naming rules based on file types and directory names. -- Records files or directories that do not conform to the standards. - -### 4. Pseudocode Example - -#### main.go - -```go -package main - -import ( - "flag" - "fmt" - "GoConformityChecker/checker" -) - -func main() { - // Parse command-line arguments - var targetDir string - var ignoreDirs string - flag.StringVar(&targetDir, "target", ".", "Directory to check") - flag.StringVar(&ignoreDirs, "ignore", "", "Directories to ignore") - flag.Parse() - - // Call the checker - err := checker.CheckDirectory(targetDir, ignoreDirs) - if err != nil { - fmt.Println("Error:", err) - } -} -``` - -#### checker.go - -```go -package checker - -import ( - // Import necessary packages -) - -func CheckDirectory(targetDir, ignoreDirs string) error { - // Iterate through the directory, applying rules to check file and directory names - // Return any found errors or non-conformities - return nil -} -``` - -### 5. Implementation Details - -- **File and Directory Traversal**: Use Go's `path/filepath` package to traverse directories and subdirectories. -- **Naming Rules Checking**: Apply different regex expressions for naming checks based on file extensions. -- **Error Handling and Reporting**: Record files or directories that do not conform and report to the user. - -### 6. Future Development and Extensions - -- Support more file types and naming rules. -- Provide more detailed error reports, such as showing line numbers and specific naming mistakes. -- Add a graphical or web interface for non-command-line users. - -The above is an overview of the entire project's design. Following this design, specific coding implementation can begin. Note that the actual implementation may need adjustments based on real-world conditions. \ No newline at end of file diff --git a/tools/formitychecker/checker/checker.go b/tools/formitychecker/checker/checker.go deleted file mode 100644 index b17cc5427..000000000 --- a/tools/formitychecker/checker/checker.go +++ /dev/null @@ -1,113 +0,0 @@ -// Copyright © 2024 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package checker - -import ( - "fmt" - "os" - "path/filepath" - "regexp" - "strings" - - "github.com/OpenIMSDK/tools/errs" - - "github.com/openimsdk/open-im-server/tools/formitychecker/config" -) - -var ( - underscoreRegex = regexp.MustCompile(`^[a-zA-Z0-9_]+\.[a-zA-Z0-9]+$`) - hyphenRegex = regexp.MustCompile(`^[a-zA-Z0-9\-]+\.[a-zA-Z0-9]+$`) -) - -// CheckDirectory initiates the checking process for the specified directories using configuration from config.Config. -func CheckDirectory(cfg *config.Config) error { - ignoreMap := make(map[string]struct{}) - for _, dir := range cfg.IgnoreDirs { - ignoreMap[dir] = struct{}{} - } - - for _, targetDir := range cfg.TargetDirs { - err := filepath.Walk(targetDir, func(path string, info os.FileInfo, err error) error { - if err != nil { - return errs.Wrap(err, fmt.Sprintf("error walking directory '%s'", targetDir)) - } - - // Skip if the directory is in the ignore list - dirName := filepath.Base(filepath.Dir(path)) - if _, ok := ignoreMap[dirName]; ok && info.IsDir() { - return filepath.SkipDir - } - - // Check the naming convention - if err := checkNamingConvention(path, info); err != nil { - fmt.Println(err) - } - - return nil - }) - - if err != nil { - return fmt.Errorf("error checking directory '%s': %w", targetDir, err) - } - } - - return nil -} - -// checkNamingConvention checks if the file or directory name conforms to the standard naming conventions. -func checkNamingConvention(path string, info os.FileInfo) error { - fileName := info.Name() - - // Handle special cases for directories like .git - if info.IsDir() && strings.HasPrefix(fileName, ".") { - return nil // Skip special directories - } - - // Extract the main part of the name (without extension for files) - mainName := fileName - if !info.IsDir() { - mainName = strings.TrimSuffix(fileName, filepath.Ext(fileName)) - } - - // Determine the type of file and apply corresponding naming rule - switch { - case info.IsDir(): - if !isValidName(mainName, "_") { // Directory names must only contain underscores - return fmt.Errorf("!!! invalid directory name: %s", path) - } - case strings.HasSuffix(fileName, ".go"): - if !isValidName(mainName, "_") { // Go files must only contain underscores - return fmt.Errorf("!!! invalid Go file name: %s", path) - } - case strings.HasSuffix(fileName, ".yml"), strings.HasSuffix(fileName, ".yaml"), strings.HasSuffix(fileName, ".md"): - if !isValidName(mainName, "-") { // YML, YAML, and Markdown files must only contain hyphens - return fmt.Errorf("!!! invalid file name: %s", path) - } - } - - return nil -} - -// isValidName checks if the file name conforms to the specified rule (underscore or hyphen). -func isValidName(name, charType string) bool { - switch charType { - case "_": - return underscoreRegex.MatchString(name) - case "-": - return hyphenRegex.MatchString(name) - default: - return false - } -} diff --git a/tools/formitychecker/config/config.go b/tools/formitychecker/config/config.go deleted file mode 100644 index 0c4f6a16b..000000000 --- a/tools/formitychecker/config/config.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2024 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package config - -import ( - "strings" -) - -// Config holds all the configuration parameters for the checker. -type Config struct { - TargetDirs []string // Directories to check - IgnoreDirs []string // Directories to ignore -} - -// NewConfig creates and returns a new Config instance. -func NewConfig(targetDirs, ignoreDirs string) *Config { - return &Config{ - TargetDirs: parseDirs(targetDirs), - IgnoreDirs: parseDirs(ignoreDirs), - } -} - -// parseDirs splits a comma-separated string into a slice of directory names. -func parseDirs(dirs string) []string { - if dirs == "" { - return nil - } - return strings.Split(dirs, ",") -} diff --git a/tools/formitychecker/formitychecker.go b/tools/formitychecker/formitychecker.go deleted file mode 100644 index 2bedbfb32..000000000 --- a/tools/formitychecker/formitychecker.go +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright © 2024 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "flag" - "fmt" - - "github.com/openimsdk/open-im-server/tools/formitychecker/checker" - "github.com/openimsdk/open-im-server/tools/formitychecker/config" -) - -func main() { - defaultTargetDirs := "." - defaultIgnoreDirs := "components,.git" - - var targetDirs string - var ignoreDirs string - flag.StringVar(&targetDirs, "target", defaultTargetDirs, "Directories to check (default: current directory)") - flag.StringVar(&ignoreDirs, "ignore", defaultIgnoreDirs, "Directories to ignore (default: A/, B/)") - flag.Parse() - - conf := config.NewConfig(targetDirs, ignoreDirs) - - err := checker.CheckDirectory(conf) - if err != nil { - fmt.Println("Error:", err) - } -} diff --git a/tools/formitychecker/go.mod b/tools/formitychecker/go.mod deleted file mode 100644 index 698b77647..000000000 --- a/tools/formitychecker/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/openimsdk/open-im-server/tools/formitychecker - -go 1.19 diff --git a/tools/imctl/go.mod b/tools/imctl/go.mod deleted file mode 100644 index e3a720b27..000000000 --- a/tools/imctl/go.mod +++ /dev/null @@ -1,18 +0,0 @@ -module github.com/openimsdk/open-im-server/v3/tools/imctl - -go 1.19 - -require ( - github.com/MakeNowJust/heredoc/v2 v2.0.1 - github.com/mitchellh/go-wordwrap v1.0.1 - github.com/moby/term v0.5.0 - github.com/russross/blackfriday v1.6.0 - github.com/spf13/cobra v1.7.0 - github.com/spf13/pflag v1.0.5 -) - -require ( - github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - golang.org/x/sys v0.1.0 // indirect -) diff --git a/tools/imctl/go.sum b/tools/imctl/go.sum deleted file mode 100644 index 3d4c61343..000000000 --- a/tools/imctl/go.sum +++ /dev/null @@ -1,24 +0,0 @@ -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= -github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/MakeNowJust/heredoc/v2 v2.0.1 h1:rlCHh70XXXv7toz95ajQWOWQnN4WNLt0TdpZYIR/J6A= -github.com/MakeNowJust/heredoc/v2 v2.0.1/go.mod h1:6/2Abh5s+hc3g9nbWLe9ObDIOhaRrqsyY9MWy+4JdRM= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= -github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/imctl/imctl.go b/tools/imctl/main.go similarity index 100% rename from tools/imctl/imctl.go rename to tools/imctl/main.go diff --git a/tools/infra/go.mod b/tools/infra/go.mod deleted file mode 100644 index 8c66e2654..000000000 --- a/tools/infra/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/openimsdk/open-im-server/v3/tools/infra - -go 1.19 - -require github.com/fatih/color v1.15.0 - -require ( - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - golang.org/x/sys v0.6.0 // indirect -) diff --git a/tools/infra/go.sum b/tools/infra/go.sum deleted file mode 100644 index 2624c9db0..000000000 --- a/tools/infra/go.sum +++ /dev/null @@ -1,10 +0,0 @@ -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/tools/infra/infra.go b/tools/infra/main.go similarity index 90% rename from tools/infra/infra.go rename to tools/infra/main.go index bc01a00eb..f6225a352 100644 --- a/tools/infra/infra.go +++ b/tools/infra/main.go @@ -25,13 +25,14 @@ func printLinks() { blue := color.New(color.FgBlue).SprintFunc() fmt.Printf("OpenIM Github: %s\n", blue("https://github.com/OpenIMSDK/Open-IM-Server")) fmt.Printf("Slack Invitation: %s\n", blue("https://openimsdk.slack.com")) + fmt.Printf("Follow Twitter: %s\n", blue("https://twitter.com/founder_im63606")) } func main() { yellow := color.New(color.FgYellow) blue := color.New(color.FgBlue, color.Bold) - yellow.Println("Current module is still under development.") + yellow.Println("Please use the release branch or tag for production environments!") message := ` ____ _____ __ __ diff --git a/tools/ncpu/go.mod b/tools/ncpu/go.mod deleted file mode 100644 index 66697e350..000000000 --- a/tools/ncpu/go.mod +++ /dev/null @@ -1,5 +0,0 @@ -module github.com/openimsdk/open-im-server/v3/tools/ncpu - -go 1.19 - -require go.uber.org/automaxprocs v1.5.3 diff --git a/tools/ncpu/go.sum b/tools/ncpu/go.sum deleted file mode 100644 index 804f593b5..000000000 --- a/tools/ncpu/go.sum +++ /dev/null @@ -1,7 +0,0 @@ -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g= -github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY= -go.uber.org/automaxprocs v1.5.3 h1:kWazyxZUrS3Gs4qUpbwo5kEIMGe/DAvi5Z4tl2NW4j8= -go.uber.org/automaxprocs v1.5.3/go.mod h1:eRbA25aqJrxAbsLO0xy5jVwPt7FQnRgjW+efnwa1WM0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/tools/ncpu/ncpu.go b/tools/ncpu/main.go similarity index 100% rename from tools/ncpu/ncpu.go rename to tools/ncpu/main.go diff --git a/tools/ncpu/ncpu_test.go b/tools/ncpu/main_test.go similarity index 100% rename from tools/ncpu/ncpu_test.go rename to tools/ncpu/main_test.go diff --git a/tools/openim-web/Dockerfile b/tools/openim-web/Dockerfile deleted file mode 100644 index 2e0cd9e8e..000000000 --- a/tools/openim-web/Dockerfile +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright © 2023 OpenIM. All rights reserved. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# 使用官方Go镜像作为基础镜像 -FROM golang:1.21 AS build-env -ENV CGO_ENABLED=0 -# 设置工作目录 -WORKDIR /app - -# 安装curl和unzip工具 -#RUN apt-get update && apt-get install -y curl unzip - -# 从GitHub下载并解压dist.zip -#RUN curl -LO https://github.com/OpenIMSDK/dist.zip \ - # && unzip dist.zip -d ./ \ - # && rm dist.zip - -# 复制Go代码到容器 -COPY . . - -# 编译Go代码 -RUN go build -o openim-web - -# 使用轻量级的基础镜像 -FROM debian:buster-slim - -# 将编译好的二进制文件和dist资源复制到新的容器 -WORKDIR /app -COPY --from=build-env /app/openim-web /app/openim-web -COPY --from=build-env /app/dist /app/dist - -# 开放容器的20001端口 -EXPOSE 20001 - -# 指定容器启动命令 -ENTRYPOINT ["/app/openim-web"] \ No newline at end of file diff --git a/tools/openim-web/README.md b/tools/openim-web/README.md deleted file mode 100644 index 5794a946d..000000000 --- a/tools/openim-web/README.md +++ /dev/null @@ -1,78 +0,0 @@ -# OpenIM Web Service - -- [OpenIM Web Service](#openim-web-service) - - [Overview](#overview) - - [User](#user) - - [Docker Deployment](#docker-deployment) - - [Build the Docker Image](#build-the-docker-image) - - [Run the Docker Container](#run-the-docker-container) - - [Configuration](#configuration) - - [Contributions](#contributions) - - -OpenIM Web Service is a lightweight containerized service built with Go. The service serves static files and allows customization via environment variables. - -## Overview - -- Built using Go. -- Deployed as a Docker container. -- Serves static files from a directory which can be set via an environment variable. -- The default port for the service is `20001`, but it can be customized using an environment variable. - -## User - -example: - -```bash -$ ./openim-web -h -Usage of ./openim-web: - -distPath string - Path to the distribution (default "/app/dist") - -port string - Port to run the server on (default "20001") -``` - -Variables can be set as above, Environment variables can also be set - -example: - -```bash -$ export OPENIM_WEB_PPRT="11001" -``` - -Initialize the env configuration file: - -```bash -$ make init -``` - -## Docker Deployment - -### Build the Docker Image - -Even though we've implemented automation, it's to make the developer experience easier: - -To build the Docker image for OpenIM Web Service: - -```bash -$ docker build -t openim-web . -``` - -### Run the Docker Container - -To run the service: - -```bash -$ docker run -e DIST_PATH=/app/dist -e PORT=20001 -p 20001:20001 openim-web -``` - -## Configuration - -You can configure the OpenIM Web Service using the following environment variables: - -- **DIST_PATH**: The path to the directory containing the static files. Default: `/app/dist`. -- **PORT**: The port on which the service listens. Default: `11001`. - -## Contributions - -We welcome contributions from the community. If you find any bugs or have feature suggestions, please create an issue or send a pull request. \ No newline at end of file diff --git a/tools/openim-web/go.mod b/tools/openim-web/go.mod deleted file mode 100644 index deb24f4e5..000000000 --- a/tools/openim-web/go.mod +++ /dev/null @@ -1,7 +0,0 @@ -module github.com/openimsdk/open-im-server/v3/tools/openim-web - -go 1.19 - -require gopkg.in/yaml.v2 v2.4.0 - -require github.com/NYTimes/gziphandler v1.1.1 // indirect diff --git a/tools/openim-web/go.sum b/tools/openim-web/go.sum deleted file mode 100644 index 54ca3deb1..000000000 --- a/tools/openim-web/go.sum +++ /dev/null @@ -1,10 +0,0 @@ -github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -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= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/tools/openim-web/openim-web.go b/tools/openim-web/openim-web.go deleted file mode 100644 index c913e35e7..000000000 --- a/tools/openim-web/openim-web.go +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "flag" - "log" - "net/http" - "os" - - "github.com/NYTimes/gziphandler" -) - -var ( - distPathFlag string - portFlag string -) - -func init() { - flag.StringVar(&distPathFlag, "distPath", "/app/dist", "Path to the distribution") - flag.StringVar(&portFlag, "port", "11001", "Port to run the server on") -} - -func main() { - flag.Parse() - - distPath := getConfigValue("DIST_PATH", distPathFlag, "/app/dist") - fs := http.FileServer(http.Dir(distPath)) - - withGzip := gziphandler.GzipHandler(fs) - - http.Handle("/", withGzip) - - port := getConfigValue("PORT", portFlag, "11001") - log.Printf("Server listening on port %s in %s...", port, distPath) - err := http.ListenAndServe(":"+port, nil) - if err != nil { - log.Fatal(err) - } -} - -func getConfigValue(envKey, flagValue, fallback string) string { - envVal := os.Getenv(envKey) - if envVal != "" { - return envVal - } - if flagValue != "" { - return flagValue - } - return fallback -} diff --git a/tools/openim-web/openim-web_test.go b/tools/openim-web/openim-web_test.go deleted file mode 100644 index dd1c93316..000000000 --- a/tools/openim-web/openim-web_test.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright © 2023 OpenIM. All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package main - -import ( - "os" - "testing" -) - -func TestGetConfigValue(t *testing.T) { - tests := []struct { - name string - envKey string - envValue string - flagValue string - fallback string - wantResult string - }{ - { - name: "environment variable set", - envKey: "TEST_KEY", - envValue: "envValue", - flagValue: "", - fallback: "default", - wantResult: "envValue", - }, - { - name: "flag set and environment variable not set", - envKey: "TEST_KEY", - envValue: "", - flagValue: "flagValue", - fallback: "default", - wantResult: "flagValue", - }, - { - name: "nothing set, use fallback", - envKey: "TEST_KEY", - envValue: "", - flagValue: "", - fallback: "default", - wantResult: "default", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if tt.envValue != "" { - os.Setenv(tt.envKey, tt.envValue) - defer os.Unsetenv(tt.envKey) - } - - got := getConfigValue(tt.envKey, tt.flagValue, tt.fallback) - - if got != tt.wantResult { - t.Errorf("getConfigValue(%s, %s, %s) = %s; want %s", tt.envKey, tt.flagValue, tt.fallback, got, tt.wantResult) - } - }) - } -} diff --git a/tools/url2im/go.mod b/tools/url2im/go.mod deleted file mode 100644 index b6011909d..000000000 --- a/tools/url2im/go.mod +++ /dev/null @@ -1,20 +0,0 @@ -module github.com/openimsdk/open-im-server/v3/tools/url2im - -go 1.20 - -require ( - github.com/OpenIMSDK/protocol v0.0.21 - github.com/kelindar/bitmap v1.5.1 -) - -require ( - github.com/golang/protobuf v1.5.3 // indirect - github.com/kelindar/simd v1.1.2 // indirect - github.com/klauspost/cpuid/v2 v2.2.4 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.31.0 // indirect -) diff --git a/tools/url2im/go.sum b/tools/url2im/go.sum deleted file mode 100644 index 1970dce2c..000000000 --- a/tools/url2im/go.sum +++ /dev/null @@ -1,33 +0,0 @@ -github.com/OpenIMSDK/protocol v0.0.21 h1:5H6H+hJ9d/VgRqttvxD/zfK9Asd+4M8Eknk5swSbUVY= -github.com/OpenIMSDK/protocol v0.0.21/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/kelindar/bitmap v1.5.1 h1:+ZmZdwHbJ+CGE+q/aAJ74KJSnp0vOlGD7KY5x51mVzk= -github.com/kelindar/bitmap v1.5.1/go.mod h1:j3qZjxH9s4OtvsnFTP2bmPkjqil9Y2xQlxPYHexasEA= -github.com/kelindar/simd v1.1.2 h1:KduKb+M9cMY2HIH8S/cdJyD+5n5EGgq+Aeeleos55To= -github.com/kelindar/simd v1.1.2/go.mod h1:inq4DFudC7W8L5fhxoeZflLRNpWSs0GNx6MlWFvuvr0= -github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= -github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/tools/url2im/pkg/api.go b/tools/url2im/pkg/api.go index 7575b078a..5bf48c4ea 100644 --- a/tools/url2im/pkg/api.go +++ b/tools/url2im/pkg/api.go @@ -18,14 +18,14 @@ import ( "bytes" "context" "encoding/json" - "errors" "fmt" "io" "net/http" - "github.com/OpenIMSDK/protocol/auth" - "github.com/OpenIMSDK/protocol/constant" - "github.com/OpenIMSDK/protocol/third" + "github.com/openimsdk/protocol/auth" + "github.com/openimsdk/protocol/constant" + "github.com/openimsdk/protocol/third" + "github.com/openimsdk/tools/errs" ) type Api struct { @@ -39,7 +39,7 @@ type Api struct { func (a *Api) apiPost(ctx context.Context, path string, req any, resp any) error { operationID, _ := ctx.Value("operationID").(string) if operationID == "" { - return errors.New("call api operationID is empty") + return errs.New("call api operationID is empty") } reqBody, err := json.Marshal(req) if err != nil { diff --git a/tools/url2im/pkg/manage.go b/tools/url2im/pkg/manage.go index 70c6713fc..5e1626da9 100644 --- a/tools/url2im/pkg/manage.go +++ b/tools/url2im/pkg/manage.go @@ -20,8 +20,8 @@ import ( "crypto/md5" "encoding/hex" "encoding/json" - "errors" "fmt" + "github.com/openimsdk/tools/errs" "io" "log" "net/http" @@ -34,7 +34,7 @@ import ( "sync/atomic" "time" - "github.com/OpenIMSDK/protocol/third" + "github.com/openimsdk/protocol/third" ) type Upload struct { @@ -256,10 +256,10 @@ func (m *Manage) RunTask(ctx context.Context, task Task) (string, error) { func (m *Manage) partSize(size int64) (int64, error) { if size <= 0 { - return 0, errors.New("size must be greater than 0") + return 0, errs.New("size must be greater than 0") } if size > m.partLimit.MaxPartSize*int64(m.partLimit.MaxNumSize) { - return 0, fmt.Errorf("size must be less than %db", m.partLimit.MaxPartSize*int64(m.partLimit.MaxNumSize)) + return 0, errs.New("size must be less than", "size", m.partLimit.MaxPartSize*int64(m.partLimit.MaxNumSize)) } if size <= m.partLimit.MinPartSize*int64(m.partLimit.MaxNumSize) { return m.partLimit.MinPartSize, nil @@ -392,7 +392,7 @@ func (m *Manage) HttpGet(ctx context.Context, url string) (*http.Response, error } if response.StatusCode != http.StatusOK { _ = response.Body.Close() - return nil, fmt.Errorf("http get %s status %s", url, response.Status) + return nil, fmt.Errorf("webhook get %s status %s", url, response.Status) } return response, nil } diff --git a/tools/versionchecker/go.mod b/tools/versionchecker/go.mod deleted file mode 100644 index 7c274fcf9..000000000 --- a/tools/versionchecker/go.mod +++ /dev/null @@ -1,11 +0,0 @@ -module github.com/openimsdk/open-im-server/v3/tools/versionchecker - -go 1.19 - -require github.com/fatih/color v1.15.0 - -require ( - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - golang.org/x/sys v0.6.0 // indirect -) diff --git a/tools/versionchecker/go.sum b/tools/versionchecker/go.sum deleted file mode 100644 index 2624c9db0..000000000 --- a/tools/versionchecker/go.sum +++ /dev/null @@ -1,10 +0,0 @@ -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/tools/versionchecker/versionchecker.go b/tools/versionchecker/main.go similarity index 82% rename from tools/versionchecker/versionchecker.go rename to tools/versionchecker/main.go index 12254b58e..bec7daa79 100644 --- a/tools/versionchecker/versionchecker.go +++ b/tools/versionchecker/main.go @@ -19,10 +19,9 @@ import ( "fmt" "os/exec" "runtime" - "time" "github.com/fatih/color" - //"github.com/openimsdk/open-im-server/v3/pkg/common/version" + "github.com/openimsdk/tools/utils/timeutil" ) func ExecuteCommand(cmdName string, args ...string) (string, error) { @@ -40,8 +39,7 @@ func ExecuteCommand(cmdName string, args ...string) (string, error) { } func printTime() string { - currentTime := time.Now() - formattedTime := currentTime.Format("2006-01-02 15:04:05") + formattedTime := timeutil.GetCurrentTimeFormatted() return fmt.Sprintf("Current Date & Time: %s", formattedTime) } @@ -60,14 +58,6 @@ func getDockerVersion() string { return version } -func getDockerComposeVersion() string { - version, err := ExecuteCommand("docker-compose", "--version") - if err != nil { - return "Docker Compose is not installed. Please install it to get the version." - } - return version -} - func getKubernetesVersion() string { version, err := ExecuteCommand("kubectl", "version", "--client", "--short") if err != nil { @@ -101,20 +91,15 @@ func getGitVersion() string { func main() { // red := color.New(color.FgRed).SprintFunc() - green := color.New(color.FgGreen).SprintFunc() + // green := color.New(color.FgGreen).SprintFunc() blue := color.New(color.FgBlue).SprintFunc() - yellow := color.New(color.FgYellow).SprintFunc() - - fmt.Println(green(printTime())) - fmt.Println(yellow("# Diagnostic Tool Result\n")) + // yellow := color.New(color.FgYellow).SprintFunc() fmt.Println(blue("## Go Version")) fmt.Println(getGoVersion()) fmt.Println(blue("## Branch Type")) fmt.Println(getGitVersion()) fmt.Println(blue("## Docker Version")) fmt.Println(getDockerVersion()) - fmt.Println(blue("## Docker Compose Version")) - fmt.Println(getDockerComposeVersion()) fmt.Println(blue("## Kubernetes Version")) fmt.Println(getKubernetesVersion()) // fmt.Println(blue("## OpenIM Versions")) diff --git a/tools/yamlfmt/go.mod b/tools/yamlfmt/go.mod deleted file mode 100644 index 7c496a530..000000000 --- a/tools/yamlfmt/go.mod +++ /dev/null @@ -1,8 +0,0 @@ -module github.com/openimsdk/open-im-server/v3/tools/yamlfmt - -go 1.19 - -require ( - github.com/likexian/gokit v0.25.13 - gopkg.in/yaml.v3 v3.0.1 -) diff --git a/tools/yamlfmt/go.sum b/tools/yamlfmt/go.sum deleted file mode 100644 index 0cf090cc8..000000000 --- a/tools/yamlfmt/go.sum +++ /dev/null @@ -1,6 +0,0 @@ -github.com/likexian/gokit v0.25.13 h1:p2Uw3+6fGG53CwdU2Dz0T6bOycdb2+bAFAa3ymwWVkM= -github.com/likexian/gokit v0.25.13/go.mod h1:qQhEWFBEfqLCO3/vOEo2EDKd+EycekVtUK4tex+l2H4= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tools/yamlfmt/yamlfmt.go b/tools/yamlfmt/main.go similarity index 100% rename from tools/yamlfmt/yamlfmt.go rename to tools/yamlfmt/main.go diff --git a/tools/yamlfmt/yamlfmt_test.go b/tools/yamlfmt/main_test.go similarity index 100% rename from tools/yamlfmt/yamlfmt_test.go rename to tools/yamlfmt/main_test.go