mirror of
				https://github.com/openimsdk/open-im-server.git
				synced 2025-11-04 19:32:17 +08:00 
			
		
		
		
	merge: update code from main to v3.8-js-sdk-only. (#2720)
* fix: fix update groupName invalid. (#2673) * refactor: change platform to platformID (#2670) * feat: don`t return nil data (#2675) Co-authored-by: Monet Lee <monet_lee@163.com> * refactor: update fields type in userStatus and check registered. (#2676) * fix: usertoken auth. (#2677) * refactor: update fields type in userStatus and check registered. * fix: usertoken auth. * update contents. * update content. * update * fix * update pb file. * feat: add friend agree after callback (#2680) * fix: sn not sort (#2682) * fix: GroupApplicationAcceptedNotification * fix: GroupApplicationAcceptedNotification * fix: NotificationUserInfoUpdate * cicd: robot automated Change * fix: component * fix: getConversationInfo * feat: cron task * feat: cron task * feat: cron task * feat: cron task * feat: cron task * fix: minio config url recognition error * update gomake version * update gomake version * fix: seq conversion bug * fix: redis pipe exec * fix: ImportFriends * fix: A large number of logs keysAndValues length is not even * feat: mark read aggregate write * feat: online status supports redis cluster * feat: online status supports redis cluster * feat: online status supports redis cluster * merge * merge * read seq is written to mongo * read seq is written to mongo * fix: invitation to join group notification * fix: friend op_user_id * feat: optimizing asynchronous context * feat: optimizing memamq size * feat: add GetSeqMessage * feat: GroupApplicationAgreeMemberEnterNotification * feat: GroupApplicationAgreeMemberEnterNotification * feat: go.mod * feat: go.mod * feat: join group notification and get seq * feat: join group notification and get seq * feat: avoid pulling messages from sessions with a large number of max seq values of 0 * feat: API supports gzip * go.mod * fix: nil pointer error on close * fix: listen error * fix: listen error * update go.mod * feat: add log * fix: token parse token value * fix: GetMsgBySeqs boundary issues * fix: sn_ not sort * fix: sn_ not sort * fix: sn_ not sort --------- Co-authored-by: withchao <withchao@users.noreply.github.com> * refactor: add GetAdminToken interface. (#2684) * refactor: add GetAdminToken interface. * update config. * fix: admin token (#2686) * fix: update workflows logic. (#2688) * refactor: add GetAdminToken interface. * update config. * update workflows logic. * fix: admin token (#2687) * update the front image (#2692) * update the front image * update version * feat: improve publish docker image workflows (#2697) * refactor: add GetAdminToken interface. * update config. * update workflows logic. * feat: improve publish docker image workflows * update condition logic. * fix: update load file logic. (#2700) * refactor: add GetAdminToken interface. * update config. * update workflows logic. * feat: improve publish docker image workflows * update condition logic. * fix: update load file logic. * feat: Msg filter (#2703) * feat: msg filter * feat: msg filter * feat: msg filter * feat: provide the interface required by js sdk (#2712) * fix: GroupApplicationAcceptedNotification * fix: GroupApplicationAcceptedNotification * fix: NotificationUserInfoUpdate * cicd: robot automated Change * fix: component * fix: getConversationInfo * feat: cron task * feat: cron task * feat: cron task * feat: cron task * feat: cron task * fix: minio config url recognition error * update gomake version * update gomake version * fix: seq conversion bug * fix: redis pipe exec * fix: ImportFriends * fix: A large number of logs keysAndValues length is not even * feat: mark read aggregate write * feat: online status supports redis cluster * feat: online status supports redis cluster * feat: online status supports redis cluster * merge * merge * read seq is written to mongo * read seq is written to mongo * fix: invitation to join group notification * fix: friend op_user_id * feat: optimizing asynchronous context * feat: optimizing memamq size * feat: add GetSeqMessage * feat: GroupApplicationAgreeMemberEnterNotification * feat: GroupApplicationAgreeMemberEnterNotification * feat: go.mod * feat: go.mod * feat: join group notification and get seq * feat: join group notification and get seq * feat: avoid pulling messages from sessions with a large number of max seq values of 0 * feat: API supports gzip * go.mod * fix: nil pointer error on close * fix: listen error * fix: listen error * update go.mod * feat: add log * fix: token parse token value * fix: GetMsgBySeqs boundary issues * fix: sn_ not sort * fix: sn_ not sort * fix: sn_ not sort * fix: jssdk add * fix: jssdk support * fix: jssdk support * fix: jssdk support --------- Co-authored-by: withchao <withchao@users.noreply.github.com> * Line webhook (#2716) * feat: online and offline webhook * feat: online and offline webhook * feat: remove zk * fix: the message I sent is not set to read seq in mongodb (#2718) * fix: GroupApplicationAcceptedNotification * fix: GroupApplicationAcceptedNotification * fix: NotificationUserInfoUpdate * cicd: robot automated Change * fix: component * fix: getConversationInfo * feat: cron task * feat: cron task * feat: cron task * feat: cron task * feat: cron task * fix: minio config url recognition error * update gomake version * update gomake version * fix: seq conversion bug * fix: redis pipe exec * fix: ImportFriends * fix: A large number of logs keysAndValues length is not even * feat: mark read aggregate write * feat: online status supports redis cluster * feat: online status supports redis cluster * feat: online status supports redis cluster * merge * merge * read seq is written to mongo * read seq is written to mongo * fix: invitation to join group notification * fix: friend op_user_id * feat: optimizing asynchronous context * feat: optimizing memamq size * feat: add GetSeqMessage * feat: GroupApplicationAgreeMemberEnterNotification * feat: GroupApplicationAgreeMemberEnterNotification * feat: go.mod * feat: go.mod * feat: join group notification and get seq * feat: join group notification and get seq * feat: avoid pulling messages from sessions with a large number of max seq values of 0 * feat: API supports gzip * go.mod * fix: nil pointer error on close * fix: listen error * fix: listen error * update go.mod * feat: add log * fix: token parse token value * fix: GetMsgBySeqs boundary issues * fix: sn_ not sort * fix: sn_ not sort * fix: sn_ not sort * fix: jssdk add * fix: jssdk support * fix: jssdk support * fix: jssdk support * fix: the message I sent is not set to read seq in mongodb --------- Co-authored-by: withchao <withchao@users.noreply.github.com> * fix: cannot modify group member avatars (#2719) * fix: GroupApplicationAcceptedNotification * fix: GroupApplicationAcceptedNotification * fix: NotificationUserInfoUpdate * cicd: robot automated Change * fix: component * fix: getConversationInfo * feat: cron task * feat: cron task * feat: cron task * feat: cron task * feat: cron task * fix: minio config url recognition error * update gomake version * update gomake version * fix: seq conversion bug * fix: redis pipe exec * fix: ImportFriends * fix: A large number of logs keysAndValues length is not even * feat: mark read aggregate write * feat: online status supports redis cluster * feat: online status supports redis cluster * feat: online status supports redis cluster * merge * merge * read seq is written to mongo * read seq is written to mongo * fix: invitation to join group notification * fix: friend op_user_id * feat: optimizing asynchronous context * feat: optimizing memamq size * feat: add GetSeqMessage * feat: GroupApplicationAgreeMemberEnterNotification * feat: GroupApplicationAgreeMemberEnterNotification * feat: go.mod * feat: go.mod * feat: join group notification and get seq * feat: join group notification and get seq * feat: avoid pulling messages from sessions with a large number of max seq values of 0 * feat: API supports gzip * go.mod * fix: nil pointer error on close * fix: listen error * fix: listen error * update go.mod * feat: add log * fix: token parse token value * fix: GetMsgBySeqs boundary issues * fix: sn_ not sort * fix: sn_ not sort * fix: sn_ not sort * fix: jssdk add * fix: jssdk support * fix: jssdk support * fix: jssdk support * fix: the message I sent is not set to read seq in mongodb * fix: cannot modify group member avatars --------- Co-authored-by: withchao <withchao@users.noreply.github.com> --------- Co-authored-by: Monet Lee <monet_lee@163.com> Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com> Co-authored-by: chao <48119764+withchao@users.noreply.github.com> Co-authored-by: withchao <withchao@users.noreply.github.com> Co-authored-by: skiffer-git <72860476+skiffer-git@users.noreply.github.com>
This commit is contained in:
		
							parent
							
								
									c8dd598fbc
								
							
						
					
					
						commit
						9fb5ee5b55
					
				
							
								
								
									
										8
									
								
								.env
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								.env
									
									
									
									
									
								
							@ -8,12 +8,12 @@ PROMETHEUS_IMAGE=prom/prometheus:v2.45.6
 | 
			
		||||
ALERTMANAGER_IMAGE=prom/alertmanager:v0.27.0
 | 
			
		||||
GRAFANA_IMAGE=grafana/grafana:11.0.1
 | 
			
		||||
 | 
			
		||||
OPENIM_WEB_FRONT_IMAGE=openim/openim-web-front:release-v3.8.0
 | 
			
		||||
OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.8.0
 | 
			
		||||
OPENIM_WEB_FRONT_IMAGE=openim/openim-web-front:release-v3.8.1
 | 
			
		||||
OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.8.2
 | 
			
		||||
 | 
			
		||||
#FRONT_IMAGE: use aliyun images
 | 
			
		||||
#OPENIM_WEB_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-web-front:release-v3.5.1
 | 
			
		||||
#OPENIM_ADMIN_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-admin-front:release-v1.7
 | 
			
		||||
#OPENIM_WEB_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-web-front:release-v3.8.1
 | 
			
		||||
#OPENIM_ADMIN_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-admin-front:release-v1.8.2
 | 
			
		||||
 | 
			
		||||
DATA_DIR=./
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										83
									
								
								.github/workflows/publish-docker-image.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										83
									
								
								.github/workflows/publish-docker-image.yml
									
									
									
									
										vendored
									
									
								
							@ -4,6 +4,8 @@ on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - release-*
 | 
			
		||||
    # tags:
 | 
			
		||||
    #   - 'v*'
 | 
			
		||||
 | 
			
		||||
  release:
 | 
			
		||||
    types: [published]
 | 
			
		||||
@ -15,11 +17,8 @@ on:
 | 
			
		||||
        required: true
 | 
			
		||||
        default: "v3.8.0"
 | 
			
		||||
 | 
			
		||||
# env:
 | 
			
		||||
#   GO_VERSION: "1.21"
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  publish-docker-images:
 | 
			
		||||
  build-and-test:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
@ -28,16 +27,22 @@ jobs:
 | 
			
		||||
 | 
			
		||||
      - name: Set up QEMU
 | 
			
		||||
        uses: docker/setup-qemu-action@v3
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
      - name: Set up Docker Buildx
 | 
			
		||||
        uses: docker/setup-buildx-action@v3
 | 
			
		||||
 | 
			
		||||
      - name: Build and push Docker image
 | 
			
		||||
      - name: Build Docker image
 | 
			
		||||
        id: build
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          context: ./main-repo
 | 
			
		||||
          load: true
 | 
			
		||||
          tags: "openim/openim-server:local"
 | 
			
		||||
          cache-from: type=gha
 | 
			
		||||
          cache-to: type=gha,mode=max
 | 
			
		||||
 | 
			
		||||
      - name: Save Docker image to file
 | 
			
		||||
        run: docker save -o image.tar openim/openim-server:local
 | 
			
		||||
 | 
			
		||||
      - name: Checkout compose repository
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
@ -63,38 +68,39 @@ jobs:
 | 
			
		||||
          docker compose up -d
 | 
			
		||||
          sleep 60
 | 
			
		||||
 | 
			
		||||
      - name: Check openim-server health
 | 
			
		||||
        run: |
 | 
			
		||||
          timeout=300
 | 
			
		||||
          interval=30
 | 
			
		||||
          elapsed=0
 | 
			
		||||
          while [[ $elapsed -le $timeout ]]; do
 | 
			
		||||
            if ! docker exec openim-server mage check; then
 | 
			
		||||
              echo "openim-server is not ready, waiting..."
 | 
			
		||||
              sleep $interval
 | 
			
		||||
              elapsed=$(($elapsed + $interval))
 | 
			
		||||
            else
 | 
			
		||||
              echo "Health check successful"
 | 
			
		||||
              exit 0
 | 
			
		||||
            fi
 | 
			
		||||
          done
 | 
			
		||||
          echo "Health check failed after 5 minutes"
 | 
			
		||||
          exit 1
 | 
			
		||||
      # - name: Check openim-server health
 | 
			
		||||
      #   run: |
 | 
			
		||||
      #     timeout=300
 | 
			
		||||
      #     interval=30
 | 
			
		||||
      #     elapsed=0
 | 
			
		||||
      #     while [[ $elapsed -le $timeout ]]; do
 | 
			
		||||
      #       if ! docker exec openim-server mage check; then
 | 
			
		||||
      #         echo "openim-server is not ready, waiting..."
 | 
			
		||||
      #         sleep $interval
 | 
			
		||||
      #         elapsed=$(($elapsed + $interval))
 | 
			
		||||
      #       else
 | 
			
		||||
      #         echo "Health check successful"
 | 
			
		||||
      #         exit 0
 | 
			
		||||
      #       fi
 | 
			
		||||
      #     done
 | 
			
		||||
      #     echo "Health check failed after 5 minutes"
 | 
			
		||||
      #     exit 1
 | 
			
		||||
 | 
			
		||||
      - name: Check openim-chat health
 | 
			
		||||
        if: success()
 | 
			
		||||
        run: |
 | 
			
		||||
          if ! docker exec openim-chat mage check; then
 | 
			
		||||
              echo "openim-chat check failed"
 | 
			
		||||
              exit 1
 | 
			
		||||
            else
 | 
			
		||||
              echo "Health check successful"
 | 
			
		||||
              exit 0
 | 
			
		||||
            fi
 | 
			
		||||
      # - name: Check openim-chat health
 | 
			
		||||
      #   if: success()
 | 
			
		||||
      #   run: |
 | 
			
		||||
      #     if ! docker exec openim-chat mage check; then
 | 
			
		||||
      #         echo "openim-chat check failed"
 | 
			
		||||
      #         exit 1
 | 
			
		||||
      #       else
 | 
			
		||||
      #         echo "Health check successful"
 | 
			
		||||
      #         exit 0
 | 
			
		||||
      #       fi
 | 
			
		||||
 | 
			
		||||
      - name: Load Docker image from file
 | 
			
		||||
        run: docker load -i image.tar
 | 
			
		||||
 | 
			
		||||
      - name: Extract metadata for Docker #  (tags, labels) 
 | 
			
		||||
        if: success()
 | 
			
		||||
      - name: Extract metadata for Docker (tags, labels)
 | 
			
		||||
        id: meta
 | 
			
		||||
        uses: docker/metadata-action@v5.5.1
 | 
			
		||||
        with:
 | 
			
		||||
@ -102,18 +108,17 @@ jobs:
 | 
			
		||||
            openim/openim-server
 | 
			
		||||
            ghcr.io/openimsdk/openim-server
 | 
			
		||||
            registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server
 | 
			
		||||
 | 
			
		||||
          # generate Docker tags based on the following events/attributes
 | 
			
		||||
          tags: |
 | 
			
		||||
            type=ref,event=tag
 | 
			
		||||
            type=schedule
 | 
			
		||||
            type=ref,event=branch
 | 
			
		||||
            type=ref,event=pr
 | 
			
		||||
            type=semver,pattern={{version}}
 | 
			
		||||
            type=semver,pattern=v{{version}}
 | 
			
		||||
            type=semver,pattern={{major}}.{{minor}}
 | 
			
		||||
            type=semver,pattern={{major}}
 | 
			
		||||
            type=semver,pattern=release-{{raw}}
 | 
			
		||||
            type=sha
 | 
			
		||||
            type=raw,value=${{ github.event.inputs.tag }}
 | 
			
		||||
 | 
			
		||||
      - name: Log in to Docker Hub
 | 
			
		||||
        uses: docker/login-action@v2
 | 
			
		||||
@ -135,7 +140,7 @@ jobs:
 | 
			
		||||
          username: ${{ secrets.ALIREGISTRY_USERNAME }}
 | 
			
		||||
          password: ${{ secrets.ALIREGISTRY_TOKEN }}
 | 
			
		||||
 | 
			
		||||
      - name: Build and push Docker images
 | 
			
		||||
      - name: Push Docker images
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
        with:
 | 
			
		||||
          context: ./main-repo
 | 
			
		||||
 | 
			
		||||
@ -1,5 +1,5 @@
 | 
			
		||||
# Use Go 1.21 Alpine as the base image for building the application
 | 
			
		||||
FROM golang:1.21-alpine as builder
 | 
			
		||||
FROM golang:1.21-alpine AS builder
 | 
			
		||||
 | 
			
		||||
# Define the base directory for the application as an environment variable
 | 
			
		||||
ENV SERVER_DIR=/openim-server
 | 
			
		||||
 | 
			
		||||
@ -15,10 +15,9 @@
 | 
			
		||||
package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	_ "net/http/pprof"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/open-im-server/v3/pkg/common/cmd"
 | 
			
		||||
	"github.com/openimsdk/tools/system/program"
 | 
			
		||||
	_ "net/http/pprof"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
 | 
			
		||||
@ -5,9 +5,4 @@ etcd:
 | 
			
		||||
  username: ''
 | 
			
		||||
  password: ''
 | 
			
		||||
 | 
			
		||||
zookeeper:
 | 
			
		||||
  schema: openim
 | 
			
		||||
  address: [ localhost:12181 ]
 | 
			
		||||
  username: ''
 | 
			
		||||
  password: ''
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,8 +1,18 @@
 | 
			
		||||
url: webhook://127.0.0.1:10008/callbackExample
 | 
			
		||||
url: http://127.0.0.1:10006/callbackExample
 | 
			
		||||
beforeSendSingleMsg:
 | 
			
		||||
  enable: false
 | 
			
		||||
  timeout: 5
 | 
			
		||||
  failedContinue: true
 | 
			
		||||
  # Only the contentType in allowedTypes will send the callback.
 | 
			
		||||
  # Supports two formats: a single type or a range. The range is defined by the lower and upper bounds connected with a hyphen ("-").
 | 
			
		||||
  # e.g. allowedTypes: [1, 100, 200-500, 600-700] means that only contentType within the range
 | 
			
		||||
  # {1, 100} ∪ [200, 500] ∪ [600, 700] will be allowed through the filter.
 | 
			
		||||
  # If not set, all contentType messages will through this filter.
 | 
			
		||||
  allowedTypes: []
 | 
			
		||||
  # Only the contentType not in deniedTypes will send the callback.
 | 
			
		||||
  # Supports two formats, same as allowedTypes.
 | 
			
		||||
  # If not set, all contentType messages will through this filter.
 | 
			
		||||
  deniedTypes: []
 | 
			
		||||
beforeUpdateUserInfoEx:
 | 
			
		||||
  enable:  false
 | 
			
		||||
  timeout: 5
 | 
			
		||||
@ -16,17 +26,29 @@ afterSendSingleMsg:
 | 
			
		||||
  # Only the senID/recvID specified in attentionIds will send the callback
 | 
			
		||||
  # if not set, all user messages will be callback
 | 
			
		||||
  attentionIds: []
 | 
			
		||||
  # See beforeSendSingleMsg comment.
 | 
			
		||||
  allowedTypes: []
 | 
			
		||||
  deniedTypes: []
 | 
			
		||||
beforeSendGroupMsg:
 | 
			
		||||
  enable: false
 | 
			
		||||
  timeout: 5
 | 
			
		||||
  failedContinue: true
 | 
			
		||||
  # See beforeSendSingleMsg comment.
 | 
			
		||||
  allowedTypes: []
 | 
			
		||||
  deniedTypes: []
 | 
			
		||||
beforeMsgModify:
 | 
			
		||||
  enable: false
 | 
			
		||||
  timeout: 5
 | 
			
		||||
  failedContinue: true
 | 
			
		||||
  # See beforeSendSingleMsg comment.
 | 
			
		||||
  allowedTypes: []
 | 
			
		||||
  deniedTypes: []
 | 
			
		||||
afterSendGroupMsg:
 | 
			
		||||
  enable: false
 | 
			
		||||
  timeout: 5
 | 
			
		||||
  # See beforeSendSingleMsg comment.
 | 
			
		||||
  allowedTypes: []
 | 
			
		||||
  deniedTypes: []
 | 
			
		||||
afterUserOnline:
 | 
			
		||||
  enable: false
 | 
			
		||||
  timeout: 5
 | 
			
		||||
@ -151,6 +173,9 @@ beforeAddFriendAgree:
 | 
			
		||||
  enable: false
 | 
			
		||||
  timeout: 5
 | 
			
		||||
  failedContinue: true
 | 
			
		||||
afterAddFriendAgree:
 | 
			
		||||
  enable: false
 | 
			
		||||
  timeout: 5
 | 
			
		||||
afterDeleteFriend:
 | 
			
		||||
  enable: false
 | 
			
		||||
  timeout: 5
 | 
			
		||||
 | 
			
		||||
@ -43,19 +43,6 @@ services:
 | 
			
		||||
    networks:
 | 
			
		||||
      - openim
 | 
			
		||||
 | 
			
		||||
  zookeeper:
 | 
			
		||||
    image: "${ZOOKEEPER_IMAGE}"
 | 
			
		||||
    container_name: zookeeper
 | 
			
		||||
    ports:
 | 
			
		||||
      - "12181:2181"
 | 
			
		||||
    environment:
 | 
			
		||||
      #JVMFLAGS: "-Xms32m -Xmx128m"
 | 
			
		||||
      TZ: "Asia/Shanghai"
 | 
			
		||||
      ALLOW_ANONYMOUS_LOGIN: "yes"
 | 
			
		||||
    restart: always
 | 
			
		||||
    networks:
 | 
			
		||||
      - openim
 | 
			
		||||
 | 
			
		||||
  etcd:
 | 
			
		||||
    image: "${ETCD_IMAGE}"
 | 
			
		||||
    container_name: etcd
 | 
			
		||||
@ -142,6 +129,7 @@ services:
 | 
			
		||||
#    image: ${PROMETHEUS_IMAGE}
 | 
			
		||||
#    container_name: prometheus
 | 
			
		||||
#    restart: always
 | 
			
		||||
#    user: root
 | 
			
		||||
#    volumes:
 | 
			
		||||
#      - ./config/prometheus.yml:/etc/prometheus/prometheus.yml
 | 
			
		||||
#      - ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										4
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								go.mod
									
									
									
									
									
								
							@ -12,8 +12,8 @@ require (
 | 
			
		||||
	github.com/gorilla/websocket v1.5.1
 | 
			
		||||
	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
 | 
			
		||||
	github.com/mitchellh/mapstructure v1.5.0
 | 
			
		||||
	github.com/openimsdk/protocol v0.0.72-alpha.30
 | 
			
		||||
	github.com/openimsdk/tools v0.0.50-alpha.12
 | 
			
		||||
	github.com/openimsdk/protocol v0.0.72-alpha.41
 | 
			
		||||
	github.com/openimsdk/tools v0.0.50-alpha.16
 | 
			
		||||
	github.com/pkg/errors v0.9.1 // indirect
 | 
			
		||||
	github.com/prometheus/client_golang v1.18.0
 | 
			
		||||
	github.com/stretchr/testify v1.9.0
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										8
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										8
									
								
								go.sum
									
									
									
									
									
								
							@ -319,10 +319,10 @@ github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
 | 
			
		||||
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
 | 
			
		||||
github.com/openimsdk/gomake v0.0.14-alpha.5 h1:VY9c5x515lTfmdhhPjMvR3BBRrRquAUCFsz7t7vbv7Y=
 | 
			
		||||
github.com/openimsdk/gomake v0.0.14-alpha.5/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
 | 
			
		||||
github.com/openimsdk/protocol v0.0.72-alpha.30 h1:LBIqDzD55cSQy3wX8fgSa3blz8+Cv54ae96/qUMINwM=
 | 
			
		||||
github.com/openimsdk/protocol v0.0.72-alpha.30/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8=
 | 
			
		||||
github.com/openimsdk/tools v0.0.50-alpha.12 h1:rV3BxgqN+F79vZvdoQ+97Eob8ScsRVEM8D+Wrcl23uo=
 | 
			
		||||
github.com/openimsdk/tools v0.0.50-alpha.12/go.mod h1:h1cYmfyaVtgFbKmb1Cfsl8XwUOMTt8ubVUQrdGtsUh4=
 | 
			
		||||
github.com/openimsdk/protocol v0.0.72-alpha.41 h1:SMMoTc1iu+wtRqUqmIgqPJFejLgPeauOwoJ4VVG4iMQ=
 | 
			
		||||
github.com/openimsdk/protocol v0.0.72-alpha.41/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8=
 | 
			
		||||
github.com/openimsdk/tools v0.0.50-alpha.16 h1:bC1AQvJMuOHtZm8LZRvN8L5mH1Ws2VYdL+TLTs1iGSc=
 | 
			
		||||
github.com/openimsdk/tools v0.0.50-alpha.16/go.mod h1:h1cYmfyaVtgFbKmb1Cfsl8XwUOMTt8ubVUQrdGtsUh4=
 | 
			
		||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
 | 
			
		||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
 | 
			
		||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
 | 
			
		||||
 | 
			
		||||
@ -27,8 +27,8 @@ func NewAuthApi(client rpcclient.Auth) AuthApi {
 | 
			
		||||
	return AuthApi(client)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *AuthApi) UserToken(c *gin.Context) {
 | 
			
		||||
	a2r.Call(auth.AuthClient.UserToken, o.Client, c)
 | 
			
		||||
func (o *AuthApi) GetAdminToken(c *gin.Context) {
 | 
			
		||||
	a2r.Call(auth.AuthClient.GetAdminToken, o.Client, c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (o *AuthApi) GetUserToken(c *gin.Context) {
 | 
			
		||||
 | 
			
		||||
@ -1,11 +1,15 @@
 | 
			
		||||
package jssdk
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/openimsdk/protocol/conversation"
 | 
			
		||||
	"github.com/openimsdk/protocol/group"
 | 
			
		||||
	"github.com/openimsdk/protocol/jssdk"
 | 
			
		||||
	"github.com/openimsdk/protocol/msg"
 | 
			
		||||
	"github.com/openimsdk/protocol/relation"
 | 
			
		||||
	"github.com/openimsdk/protocol/sdkws"
 | 
			
		||||
	"github.com/openimsdk/tools/a2r"
 | 
			
		||||
	"github.com/openimsdk/protocol/user"
 | 
			
		||||
	"github.com/openimsdk/tools/mcontext"
 | 
			
		||||
	"github.com/openimsdk/tools/utils/datautil"
 | 
			
		||||
	"sort"
 | 
			
		||||
@ -16,16 +20,22 @@ const (
 | 
			
		||||
	defaultGetActiveConversation = 100
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func NewJSSdkApi(msg msg.MsgClient, conv conversation.ConversationClient) *JSSdk {
 | 
			
		||||
func NewJSSdkApi(user user.UserClient, friend relation.FriendClient, group group.GroupClient, msg msg.MsgClient, conv conversation.ConversationClient) *JSSdk {
 | 
			
		||||
	return &JSSdk{
 | 
			
		||||
		msg:  msg,
 | 
			
		||||
		conv: conv,
 | 
			
		||||
		user:   user,
 | 
			
		||||
		friend: friend,
 | 
			
		||||
		group:  group,
 | 
			
		||||
		msg:    msg,
 | 
			
		||||
		conv:   conv,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type JSSdk struct {
 | 
			
		||||
	msg  msg.MsgClient
 | 
			
		||||
	conv conversation.ConversationClient
 | 
			
		||||
	user   user.UserClient
 | 
			
		||||
	friend relation.FriendClient
 | 
			
		||||
	group  group.GroupClient
 | 
			
		||||
	msg    msg.MsgClient
 | 
			
		||||
	conv   conversation.ConversationClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (x *JSSdk) GetActiveConversations(c *gin.Context) {
 | 
			
		||||
@ -36,25 +46,71 @@ func (x *JSSdk) GetConversations(c *gin.Context) {
 | 
			
		||||
	call(c, x.getConversations)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, error) {
 | 
			
		||||
	req, err := a2r.ParseRequest[ActiveConversationsReq](ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
func (x *JSSdk) fillConversations(ctx context.Context, conversations []*jssdk.ConversationMsg) error {
 | 
			
		||||
	if len(conversations) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	var (
 | 
			
		||||
		userIDs  []string
 | 
			
		||||
		groupIDs []string
 | 
			
		||||
	)
 | 
			
		||||
	for _, c := range conversations {
 | 
			
		||||
		if c.Conversation.GroupID == "" {
 | 
			
		||||
			userIDs = append(userIDs, c.Conversation.UserID)
 | 
			
		||||
		} else {
 | 
			
		||||
			groupIDs = append(groupIDs, c.Conversation.GroupID)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	var (
 | 
			
		||||
		userMap   map[string]*sdkws.UserInfo
 | 
			
		||||
		friendMap map[string]*relation.FriendInfoOnly
 | 
			
		||||
		groupMap  map[string]*sdkws.GroupInfo
 | 
			
		||||
	)
 | 
			
		||||
	if len(userIDs) > 0 {
 | 
			
		||||
		users, err := field(ctx, x.user.GetDesignateUsers, &user.GetDesignateUsersReq{UserIDs: userIDs}, (*user.GetDesignateUsersResp).GetUsersInfo)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		friends, err := field(ctx, x.friend.GetFriendInfo, &relation.GetFriendInfoReq{OwnerUserID: conversations[0].Conversation.OwnerUserID, FriendUserIDs: userIDs}, (*relation.GetFriendInfoResp).GetFriendInfos)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		userMap = datautil.SliceToMap(users, (*sdkws.UserInfo).GetUserID)
 | 
			
		||||
		friendMap = datautil.SliceToMap(friends, (*relation.FriendInfoOnly).GetFriendUserID)
 | 
			
		||||
	}
 | 
			
		||||
	if len(groupIDs) > 0 {
 | 
			
		||||
		resp, err := x.group.GetGroupsInfo(ctx, &group.GetGroupsInfoReq{GroupIDs: groupIDs})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		groupMap = datautil.SliceToMap(resp.GroupInfos, (*sdkws.GroupInfo).GetGroupID)
 | 
			
		||||
	}
 | 
			
		||||
	for _, c := range conversations {
 | 
			
		||||
		if c.Conversation.GroupID == "" {
 | 
			
		||||
			c.User = userMap[c.Conversation.UserID]
 | 
			
		||||
			c.Friend = friendMap[c.Conversation.UserID]
 | 
			
		||||
		} else {
 | 
			
		||||
			c.Group = groupMap[c.Conversation.GroupID]
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActiveConversationsReq) (*jssdk.GetActiveConversationsResp, error) {
 | 
			
		||||
	if req.Count <= 0 || req.Count > maxGetActiveConversation {
 | 
			
		||||
		req.Count = defaultGetActiveConversation
 | 
			
		||||
	}
 | 
			
		||||
	opUserID := mcontext.GetOpUserID(ctx)
 | 
			
		||||
	req.OwnerUserID = mcontext.GetOpUserID(ctx)
 | 
			
		||||
	conversationIDs, err := field(ctx, x.conv.GetConversationIDs,
 | 
			
		||||
		&conversation.GetConversationIDsReq{UserID: opUserID}, (*conversation.GetConversationIDsResp).GetConversationIDs)
 | 
			
		||||
		&conversation.GetConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetConversationIDsResp).GetConversationIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if len(conversationIDs) == 0 {
 | 
			
		||||
		return &ConversationsResp{}, nil
 | 
			
		||||
		return &jssdk.GetActiveConversationsResp{}, nil
 | 
			
		||||
	}
 | 
			
		||||
	readSeq, err := field(ctx, x.msg.GetHasReadSeqs,
 | 
			
		||||
		&msg.GetHasReadSeqsReq{UserID: opUserID, ConversationIDs: conversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
 | 
			
		||||
		&msg.GetHasReadSeqsReq{UserID: req.OwnerUserID, ConversationIDs: conversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
@ -64,24 +120,24 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if len(activeConversation) == 0 {
 | 
			
		||||
		return &ConversationsResp{}, nil
 | 
			
		||||
		return &jssdk.GetActiveConversationsResp{}, nil
 | 
			
		||||
	}
 | 
			
		||||
	sortConversations := sortActiveConversations{
 | 
			
		||||
		Conversation: activeConversation,
 | 
			
		||||
	}
 | 
			
		||||
	if len(activeConversation) > 1 {
 | 
			
		||||
		pinnedConversationIDs, err := field(ctx, x.conv.GetPinnedConversationIDs,
 | 
			
		||||
			&conversation.GetPinnedConversationIDsReq{UserID: opUserID}, (*conversation.GetPinnedConversationIDsResp).GetConversationIDs)
 | 
			
		||||
			&conversation.GetPinnedConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetPinnedConversationIDsResp).GetConversationIDs)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		sortConversations.PinnedConversationIDs = datautil.SliceSet(pinnedConversationIDs)
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(&sortConversations)
 | 
			
		||||
	sortList := sortConversations.Top(req.Count)
 | 
			
		||||
	sortList := sortConversations.Top(int(req.Count))
 | 
			
		||||
	conversations, err := field(ctx, x.conv.GetConversations,
 | 
			
		||||
		&conversation.GetConversationsReq{
 | 
			
		||||
			OwnerUserID: opUserID,
 | 
			
		||||
			OwnerUserID: req.OwnerUserID,
 | 
			
		||||
			ConversationIDs: datautil.Slice(sortList, func(c *msg.ActiveConversation) string {
 | 
			
		||||
				return c.ConversationID
 | 
			
		||||
			})}, (*conversation.GetConversationsResp).GetConversations)
 | 
			
		||||
@ -90,7 +146,7 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
 | 
			
		||||
	}
 | 
			
		||||
	msgs, err := field(ctx, x.msg.GetSeqMessage,
 | 
			
		||||
		&msg.GetSeqMessageReq{
 | 
			
		||||
			UserID: opUserID,
 | 
			
		||||
			UserID: req.OwnerUserID,
 | 
			
		||||
			Conversations: datautil.Slice(sortList, func(c *msg.ActiveConversation) *msg.ConversationSeqs {
 | 
			
		||||
				return &msg.ConversationSeqs{
 | 
			
		||||
					ConversationID: c.ConversationID,
 | 
			
		||||
@ -104,7 +160,7 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
 | 
			
		||||
	conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string {
 | 
			
		||||
		return c.ConversationID
 | 
			
		||||
	})
 | 
			
		||||
	resp := make([]ConversationMsg, 0, len(sortList))
 | 
			
		||||
	resp := make([]*jssdk.ConversationMsg, 0, len(sortList))
 | 
			
		||||
	for _, c := range sortList {
 | 
			
		||||
		conv, ok := conversationMap[c.ConversationID]
 | 
			
		||||
		if !ok {
 | 
			
		||||
@ -114,13 +170,16 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
 | 
			
		||||
		if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
 | 
			
		||||
			lastMsg = msgList.Msgs[0]
 | 
			
		||||
		}
 | 
			
		||||
		resp = append(resp, ConversationMsg{
 | 
			
		||||
		resp = append(resp, &jssdk.ConversationMsg{
 | 
			
		||||
			Conversation: conv,
 | 
			
		||||
			LastMsg:      lastMsg,
 | 
			
		||||
			MaxSeq:       c.MaxSeq,
 | 
			
		||||
			ReadSeq:      readSeq[c.ConversationID],
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	if err := x.fillConversations(ctx, resp); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var unreadCount int64
 | 
			
		||||
	for _, c := range activeConversation {
 | 
			
		||||
		count := c.MaxSeq - readSeq[c.ConversationID]
 | 
			
		||||
@ -128,24 +187,20 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
 | 
			
		||||
			unreadCount += count
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &ConversationsResp{
 | 
			
		||||
	return &jssdk.GetActiveConversationsResp{
 | 
			
		||||
		Conversations: resp,
 | 
			
		||||
		UnreadCount:   unreadCount,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) {
 | 
			
		||||
	req, err := a2r.ParseRequest[conversation.GetConversationsReq](ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
func (x *JSSdk) getConversations(ctx context.Context, req *jssdk.GetConversationsReq) (*jssdk.GetConversationsResp, error) {
 | 
			
		||||
	req.OwnerUserID = mcontext.GetOpUserID(ctx)
 | 
			
		||||
	conversations, err := field(ctx, x.conv.GetConversations, req, (*conversation.GetConversationsResp).GetConversations)
 | 
			
		||||
	conversations, err := field(ctx, x.conv.GetConversations, &conversation.GetConversationsReq{OwnerUserID: req.OwnerUserID, ConversationIDs: req.ConversationIDs}, (*conversation.GetConversationsResp).GetConversations)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if len(conversations) == 0 {
 | 
			
		||||
		return &ConversationsResp{}, nil
 | 
			
		||||
		return &jssdk.GetConversationsResp{}, nil
 | 
			
		||||
	}
 | 
			
		||||
	req.ConversationIDs = datautil.Slice(conversations, func(c *conversation.Conversation) string {
 | 
			
		||||
		return c.ConversationID
 | 
			
		||||
@ -177,19 +232,22 @@ func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	resp := make([]ConversationMsg, 0, len(conversations))
 | 
			
		||||
	resp := make([]*jssdk.ConversationMsg, 0, len(conversations))
 | 
			
		||||
	for _, c := range conversations {
 | 
			
		||||
		var lastMsg *sdkws.MsgData
 | 
			
		||||
		if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
 | 
			
		||||
			lastMsg = msgList.Msgs[0]
 | 
			
		||||
		}
 | 
			
		||||
		resp = append(resp, ConversationMsg{
 | 
			
		||||
		resp = append(resp, &jssdk.ConversationMsg{
 | 
			
		||||
			Conversation: c,
 | 
			
		||||
			LastMsg:      lastMsg,
 | 
			
		||||
			MaxSeq:       maxSeqs[c.ConversationID],
 | 
			
		||||
			ReadSeq:      readSeqs[c.ConversationID],
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	if err := x.fillConversations(ctx, resp); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	var unreadCount int64
 | 
			
		||||
	for conversationID, maxSeq := range maxSeqs {
 | 
			
		||||
		count := maxSeq - readSeqs[conversationID]
 | 
			
		||||
@ -197,7 +255,7 @@ func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) {
 | 
			
		||||
			unreadCount += count
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &ConversationsResp{
 | 
			
		||||
	return &jssdk.GetConversationsResp{
 | 
			
		||||
		Conversations: resp,
 | 
			
		||||
		UnreadCount:   unreadCount,
 | 
			
		||||
	}, nil
 | 
			
		||||
 | 
			
		||||
@ -1,22 +0,0 @@
 | 
			
		||||
package jssdk
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/openimsdk/protocol/conversation"
 | 
			
		||||
	"github.com/openimsdk/protocol/sdkws"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type ActiveConversationsReq struct {
 | 
			
		||||
	Count int `json:"count"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ConversationMsg struct {
 | 
			
		||||
	Conversation *conversation.Conversation `json:"conversation"`
 | 
			
		||||
	LastMsg      *sdkws.MsgData             `json:"lastMsg"`
 | 
			
		||||
	MaxSeq       int64                      `json:"maxSeq"`
 | 
			
		||||
	ReadSeq      int64                      `json:"readSeq"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ConversationsResp struct {
 | 
			
		||||
	UnreadCount   int64             `json:"unreadCount"`
 | 
			
		||||
	Conversations []ConversationMsg `json:"conversations"`
 | 
			
		||||
}
 | 
			
		||||
@ -3,8 +3,14 @@ package jssdk
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"github.com/gin-gonic/gin"
 | 
			
		||||
	"github.com/openimsdk/tools/a2r"
 | 
			
		||||
	"github.com/openimsdk/tools/apiresp"
 | 
			
		||||
	"github.com/openimsdk/tools/checker"
 | 
			
		||||
	"github.com/openimsdk/tools/errs"
 | 
			
		||||
	"google.golang.org/grpc"
 | 
			
		||||
	"google.golang.org/protobuf/proto"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func field[A, B, C any](ctx context.Context, fn func(ctx context.Context, req *A, opts ...grpc.CallOption) (*B, error), req *A, get func(*B) C) (C, error) {
 | 
			
		||||
@ -16,11 +22,56 @@ func field[A, B, C any](ctx context.Context, fn func(ctx context.Context, req *A
 | 
			
		||||
	return get(resp), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func call[R any](c *gin.Context, fn func(ctx *gin.Context) (R, error)) {
 | 
			
		||||
	resp, err := fn(c)
 | 
			
		||||
func call[A, B any](c *gin.Context, fn func(ctx context.Context, req *A) (*B, error)) {
 | 
			
		||||
	var isJSON bool
 | 
			
		||||
	switch contentType := c.GetHeader("Content-Type"); {
 | 
			
		||||
	case contentType == "":
 | 
			
		||||
		isJSON = true
 | 
			
		||||
	case strings.Contains(contentType, "application/json"):
 | 
			
		||||
		isJSON = true
 | 
			
		||||
	case strings.Contains(contentType, "application/protobuf"):
 | 
			
		||||
	case strings.Contains(contentType, "application/x-protobuf"):
 | 
			
		||||
	default:
 | 
			
		||||
		apiresp.GinError(c, errs.ErrArgs.WrapMsg("unsupported content type"))
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	var req *A
 | 
			
		||||
	if isJSON {
 | 
			
		||||
		var err error
 | 
			
		||||
		req, err = a2r.ParseRequest[A](c)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			apiresp.GinError(c, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	} else {
 | 
			
		||||
		body, err := io.ReadAll(c.Request.Body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			apiresp.GinError(c, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		req = new(A)
 | 
			
		||||
		if err := proto.Unmarshal(body, any(req).(proto.Message)); err != nil {
 | 
			
		||||
			apiresp.GinError(c, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		if err := checker.Validate(&req); err != nil {
 | 
			
		||||
			apiresp.GinError(c, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	resp, err := fn(c, req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		apiresp.GinError(c, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	apiresp.GinSuccess(c, resp)
 | 
			
		||||
	if isJSON {
 | 
			
		||||
		apiresp.GinSuccess(c, resp)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	body, err := proto.Marshal(any(resp).(proto.Message))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		apiresp.GinError(c, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	apiresp.GinSuccess(c, body)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -1,37 +0,0 @@
 | 
			
		||||
package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/openimsdk/protocol/msg"
 | 
			
		||||
	"sort"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestName(t *testing.T) {
 | 
			
		||||
	val := sortActiveConversations{
 | 
			
		||||
		Conversation: []*msg.ActiveConversation{
 | 
			
		||||
			{
 | 
			
		||||
				ConversationID: "100",
 | 
			
		||||
				LastTime:       100,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				ConversationID: "200",
 | 
			
		||||
				LastTime:       200,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				ConversationID: "300",
 | 
			
		||||
				LastTime:       300,
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				ConversationID: "400",
 | 
			
		||||
				LastTime:       400,
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		//PinnedConversationIDs: map[string]struct{}{
 | 
			
		||||
		//	"100": {},
 | 
			
		||||
		//	"300": {},
 | 
			
		||||
		//},
 | 
			
		||||
	}
 | 
			
		||||
	sort.Sort(&val)
 | 
			
		||||
	t.Log(val)
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -2,6 +2,7 @@ package api
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/open-im-server/v3/internal/api/jssdk"
 | 
			
		||||
 | 
			
		||||
	"github.com/gin-contrib/gzip"
 | 
			
		||||
@ -76,7 +77,7 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
 | 
			
		||||
	r.Use(prommetricsGin(), gin.Recovery(), mw.CorsHandler(), mw.GinParseOperationID(), GinParseToken(authRpc))
 | 
			
		||||
	u := NewUserApi(*userRpc)
 | 
			
		||||
	m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID)
 | 
			
		||||
	j := jssdk.NewJSSdkApi(messageRpc.Client, conversationRpc.Client)
 | 
			
		||||
	j := jssdk.NewJSSdkApi(userRpc.Client, friendRpc.Client, groupRpc.Client, messageRpc.Client, conversationRpc.Client)
 | 
			
		||||
	userRouterGroup := r.Group("/user")
 | 
			
		||||
	{
 | 
			
		||||
		userRouterGroup.POST("/user_register", u.UserRegister)
 | 
			
		||||
@ -168,7 +169,7 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
 | 
			
		||||
	authRouterGroup := r.Group("/auth")
 | 
			
		||||
	{
 | 
			
		||||
		a := NewAuthApi(*authRpc)
 | 
			
		||||
		authRouterGroup.POST("/user_token", a.UserToken)
 | 
			
		||||
		authRouterGroup.POST("/get_admin_token", a.GetAdminToken)
 | 
			
		||||
		authRouterGroup.POST("/get_user_token", a.GetUserToken)
 | 
			
		||||
		authRouterGroup.POST("/parse_token", a.ParseToken)
 | 
			
		||||
		authRouterGroup.POST("/force_logout", a.ForceLogout)
 | 
			
		||||
@ -287,6 +288,6 @@ func GinParseToken(authRPC *rpcclient.Auth) gin.HandlerFunc {
 | 
			
		||||
 | 
			
		||||
// Whitelist api not parse token
 | 
			
		||||
var Whitelist = []string{
 | 
			
		||||
	"/auth/user_token",
 | 
			
		||||
	"/auth/get_admin_token",
 | 
			
		||||
	"/auth/parse_token",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -107,14 +107,14 @@ func (u *UserApi) GetUsersOnlineStatus(c *gin.Context) {
 | 
			
		||||
			if v2.UserID == v1 {
 | 
			
		||||
				flag = true
 | 
			
		||||
				res.UserID = v1
 | 
			
		||||
				res.Status = constant.OnlineStatus
 | 
			
		||||
				res.Status = constant.Online
 | 
			
		||||
				res.DetailPlatformStatus = append(res.DetailPlatformStatus, v2.DetailPlatformStatus...)
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if !flag {
 | 
			
		||||
			res.UserID = v1
 | 
			
		||||
			res.Status = constant.OfflineStatus
 | 
			
		||||
			res.Status = constant.Offline
 | 
			
		||||
		}
 | 
			
		||||
		respResult = append(respResult, res)
 | 
			
		||||
	}
 | 
			
		||||
@ -153,26 +153,26 @@ func (u *UserApi) GetUsersOnlineTokenDetail(c *gin.Context) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, v1 := range req.UserIDs {
 | 
			
		||||
		m := make(map[string][]string, 10)
 | 
			
		||||
		m := make(map[int32][]string, 10)
 | 
			
		||||
		flag = false
 | 
			
		||||
		temp := new(msggateway.SingleDetail)
 | 
			
		||||
		for _, v2 := range wsResult {
 | 
			
		||||
			if v2.UserID == v1 {
 | 
			
		||||
				flag = true
 | 
			
		||||
				temp.UserID = v1
 | 
			
		||||
				temp.Status = constant.OnlineStatus
 | 
			
		||||
				temp.Status = constant.Online
 | 
			
		||||
				for _, status := range v2.DetailPlatformStatus {
 | 
			
		||||
					if v, ok := m[status.Platform]; ok {
 | 
			
		||||
						m[status.Platform] = append(v, status.Token)
 | 
			
		||||
					if v, ok := m[status.PlatformID]; ok {
 | 
			
		||||
						m[status.PlatformID] = append(v, status.Token)
 | 
			
		||||
					} else {
 | 
			
		||||
						m[status.Platform] = []string{status.Token}
 | 
			
		||||
						m[status.PlatformID] = []string{status.Token}
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		for p, tokens := range m {
 | 
			
		||||
			t := new(msggateway.SinglePlatformToken)
 | 
			
		||||
			t.Platform = p
 | 
			
		||||
			t.PlatformID = p
 | 
			
		||||
			t.Token = tokens
 | 
			
		||||
			t.Total = int32(len(tokens))
 | 
			
		||||
			temp.SinglePlatformToken = append(temp.SinglePlatformToken, t)
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,8 @@ package msggateway
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/open-im-server/v3/pkg/authverify"
 | 
			
		||||
	"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
 | 
			
		||||
	"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
 | 
			
		||||
@ -30,7 +32,6 @@ import (
 | 
			
		||||
	"github.com/openimsdk/tools/mq/memamq"
 | 
			
		||||
	"github.com/openimsdk/tools/utils/datautil"
 | 
			
		||||
	"google.golang.org/grpc"
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func (s *Server) InitServer(ctx context.Context, config *Config, disCov discovery.SvcDiscoveryRegistry, server *grpc.Server) error {
 | 
			
		||||
@ -111,15 +112,14 @@ func (s *Server) GetUsersOnlineStatus(
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ps := new(msggateway.GetUsersOnlineStatusResp_SuccessDetail)
 | 
			
		||||
			ps.Platform = constant.PlatformIDToName(client.PlatformID)
 | 
			
		||||
			ps.Status = constant.OnlineStatus
 | 
			
		||||
			ps.PlatformID = int32(client.PlatformID)
 | 
			
		||||
			ps.ConnID = client.ctx.GetConnID()
 | 
			
		||||
			ps.Token = client.token
 | 
			
		||||
			ps.IsBackground = client.IsBackground
 | 
			
		||||
			uresp.Status = constant.OnlineStatus
 | 
			
		||||
			uresp.Status = constant.Online
 | 
			
		||||
			uresp.DetailPlatformStatus = append(uresp.DetailPlatformStatus, ps)
 | 
			
		||||
		}
 | 
			
		||||
		if uresp.Status == constant.OnlineStatus {
 | 
			
		||||
		if uresp.Status == constant.Online {
 | 
			
		||||
			resp.SuccessResult = append(resp.SuccessResult, uresp)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -90,6 +90,19 @@ func (ws *WsServer) ChangeOnlineStatus(concurrent int) {
 | 
			
		||||
		if _, err := ws.userClient.Client.SetUserOnlineStatus(ctx, req); err != nil {
 | 
			
		||||
			log.ZError(ctx, "update user online status", err)
 | 
			
		||||
		}
 | 
			
		||||
		for _, ss := range req.Status {
 | 
			
		||||
			for _, online := range ss.Online {
 | 
			
		||||
				client, _, _ := ws.clients.Get(ss.UserID, int(online))
 | 
			
		||||
				back := false
 | 
			
		||||
				if len(client) > 0 {
 | 
			
		||||
					back = client[0].IsBackground
 | 
			
		||||
				}
 | 
			
		||||
				ws.webhookAfterUserOnline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOnline, ss.UserID, int(online), back, ss.ConnID)
 | 
			
		||||
			}
 | 
			
		||||
			for _, offline := range ss.Offline {
 | 
			
		||||
				ws.webhookAfterUserOffline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOffline, ss.UserID, int(offline), ss.ConnID)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for i := 0; i < concurrent; i++ {
 | 
			
		||||
 | 
			
		||||
@ -1,29 +0,0 @@
 | 
			
		||||
package push
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/openimsdk/protocol/sdkws"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestName(t *testing.T) {
 | 
			
		||||
	var c ConsumerHandler
 | 
			
		||||
	c.readCh = make(chan *sdkws.MarkAsReadTips)
 | 
			
		||||
 | 
			
		||||
	go c.loopRead()
 | 
			
		||||
 | 
			
		||||
	go func() {
 | 
			
		||||
		for i := 0; ; i++ {
 | 
			
		||||
			seq := int64(i + 1)
 | 
			
		||||
			if seq%3 == 0 {
 | 
			
		||||
				seq = 1
 | 
			
		||||
			}
 | 
			
		||||
			c.readCh <- &sdkws.MarkAsReadTips{
 | 
			
		||||
				ConversationID:   "c100",
 | 
			
		||||
				MarkAsReadUserID: "u100",
 | 
			
		||||
				HasReadSeq:       seq,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	select {}
 | 
			
		||||
}
 | 
			
		||||
@ -20,6 +20,7 @@ import (
 | 
			
		||||
	"github.com/openimsdk/open-im-server/v3/pkg/common/config"
 | 
			
		||||
	redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
 | 
			
		||||
	"github.com/openimsdk/tools/db/redisutil"
 | 
			
		||||
	"github.com/openimsdk/tools/utils/datautil"
 | 
			
		||||
	"github.com/redis/go-redis/v9"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/open-im-server/v3/pkg/authverify"
 | 
			
		||||
@ -71,18 +72,26 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *authServer) UserToken(ctx context.Context, req *pbauth.UserTokenReq) (*pbauth.UserTokenResp, error) {
 | 
			
		||||
	resp := pbauth.UserTokenResp{}
 | 
			
		||||
func (s *authServer) GetAdminToken(ctx context.Context, req *pbauth.GetAdminTokenReq) (*pbauth.GetAdminTokenResp, error) {
 | 
			
		||||
	resp := pbauth.GetAdminTokenResp{}
 | 
			
		||||
	if req.Secret != s.config.Share.Secret {
 | 
			
		||||
		return nil, errs.ErrNoPermission.WrapMsg("secret invalid")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !datautil.Contain(req.UserID, s.config.Share.IMAdminUserID...) {
 | 
			
		||||
		return nil, errs.ErrArgs.WrapMsg("userID is error.", "userID", req.UserID, "adminUserID", s.config.Share.IMAdminUserID)
 | 
			
		||||
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := s.userRpcClient.GetUserInfo(ctx, req.UserID); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	token, err := s.authDatabase.CreateToken(ctx, req.UserID, int(req.PlatformID))
 | 
			
		||||
 | 
			
		||||
	token, err := s.authDatabase.CreateToken(ctx, req.UserID, int(constant.AdminPlatformID))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	prommetrics.UserLoginCounter.Inc()
 | 
			
		||||
	resp.Token = token
 | 
			
		||||
	resp.ExpireTimeSeconds = s.config.RpcConfig.TokenPolicy.Expire * 24 * 60 * 60
 | 
			
		||||
@ -93,6 +102,11 @@ func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenR
 | 
			
		||||
	if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if req.PlatformID == constant.AdminPlatformID {
 | 
			
		||||
		return nil, errs.ErrNoPermission.WrapMsg("platformID invalid. platformID must not be adminPlatformID")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp := pbauth.GetUserTokenResp{}
 | 
			
		||||
 | 
			
		||||
	if authverify.IsManagerUserID(req.UserID, s.config.Share.IMAdminUserID) {
 | 
			
		||||
 | 
			
		||||
@ -374,7 +374,7 @@ func (s *groupServer) webhookBeforeSetGroupInfoEx(ctx context.Context, before *c
 | 
			
		||||
		if req.Ex != nil {
 | 
			
		||||
			cbReq.Ex = req.Ex
 | 
			
		||||
		}
 | 
			
		||||
		log.ZDebug(ctx, "debug CallbackBeforeSetGroupInfoEX", "ex", cbReq.Ex)
 | 
			
		||||
		log.ZDebug(ctx, "debug CallbackBeforeSetGroupInfoEx", "ex", cbReq.Ex)
 | 
			
		||||
 | 
			
		||||
		if req.NeedVerification != nil {
 | 
			
		||||
			cbReq.NeedVerification = req.NeedVerification
 | 
			
		||||
 | 
			
		||||
@ -58,8 +58,12 @@ func UpdateGroupInfoMap(ctx context.Context, group *sdkws.GroupInfoForSet) map[s
 | 
			
		||||
func UpdateGroupInfoExMap(ctx context.Context, group *pbgroup.SetGroupInfoExReq) (map[string]any, error) {
 | 
			
		||||
	m := make(map[string]any)
 | 
			
		||||
 | 
			
		||||
	if group.GroupName != nil && group.GroupName.Value != "" {
 | 
			
		||||
		return nil, errs.ErrArgs.WrapMsg("group name is empty")
 | 
			
		||||
	if group.GroupName != nil {
 | 
			
		||||
		if group.GroupName.Value != "" {
 | 
			
		||||
			m["group_name"] = group.GroupName.Value
 | 
			
		||||
		} else {
 | 
			
		||||
			return nil, errs.ErrArgs.WrapMsg("group name is empty")
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if group.Notification != nil {
 | 
			
		||||
		m["notification"] = group.Notification.Value
 | 
			
		||||
 | 
			
		||||
@ -1485,9 +1485,6 @@ func (g *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
 | 
			
		||||
		return nil, errs.ErrNoPermission.WrapMsg("no op user id")
 | 
			
		||||
	}
 | 
			
		||||
	isAppManagerUid := authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID)
 | 
			
		||||
	for i := range req.Members {
 | 
			
		||||
		req.Members[i].FaceURL = nil
 | 
			
		||||
	}
 | 
			
		||||
	groupMembers := make(map[string][]*pbgroup.SetGroupMemberInfo)
 | 
			
		||||
	for i, member := range req.Members {
 | 
			
		||||
		if member.RoleLevel != nil {
 | 
			
		||||
@ -1764,6 +1761,7 @@ func (g *groupServer) GetSpecifiedUserGroupRequestInfo(ctx context.Context, req
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		adminIDs = append(adminIDs, owners[0].UserID)
 | 
			
		||||
		adminIDs = append(adminIDs, g.config.Share.IMAdminUserID...)
 | 
			
		||||
 | 
			
		||||
		if !datautil.Contain(req.UserID, adminIDs...) {
 | 
			
		||||
			return nil, errs.ErrNoPermission.WrapMsg("opUser no permission")
 | 
			
		||||
 | 
			
		||||
@ -67,6 +67,9 @@ func (m *msgServer) webhookBeforeSendSingleMsg(ctx context.Context, before *conf
 | 
			
		||||
		if msg.MsgData.ContentType == constant.Typing {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if !filterBeforeMsg(msg, before) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		cbReq := &cbapi.CallbackBeforeSendSingleMsgReq{
 | 
			
		||||
			CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeSendSingleMsgCommand),
 | 
			
		||||
			RecvID:            msg.MsgData.RecvID,
 | 
			
		||||
@ -84,9 +87,7 @@ func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config
 | 
			
		||||
	if msg.MsgData.ContentType == constant.Typing {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	// According to the attentionIds configuration, only some users are sent
 | 
			
		||||
	attentionIds := after.AttentionIds
 | 
			
		||||
	if attentionIds != nil && !datautil.Contain(msg.MsgData.RecvID, attentionIds...) && !datautil.Contain(msg.MsgData.SendID, attentionIds...) {
 | 
			
		||||
	if !filterAfterMsg(msg, after) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	cbReq := &cbapi.CallbackAfterSendSingleMsgReq{
 | 
			
		||||
@ -98,6 +99,9 @@ func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config
 | 
			
		||||
 | 
			
		||||
func (m *msgServer) webhookBeforeSendGroupMsg(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error {
 | 
			
		||||
	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
 | 
			
		||||
		if !filterBeforeMsg(msg, before) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if msg.MsgData.ContentType == constant.Typing {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
@ -117,6 +121,9 @@ func (m *msgServer) webhookAfterSendGroupMsg(ctx context.Context, after *config.
 | 
			
		||||
	if msg.MsgData.ContentType == constant.Typing {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	if !filterAfterMsg(msg, after) {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	cbReq := &cbapi.CallbackAfterSendGroupMsgReq{
 | 
			
		||||
		CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand),
 | 
			
		||||
		GroupID:           msg.MsgData.GroupID,
 | 
			
		||||
@ -129,6 +136,9 @@ func (m *msgServer) webhookBeforeMsgModify(ctx context.Context, before *config.B
 | 
			
		||||
		if msg.MsgData.ContentType != constant.Text {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		if !filterBeforeMsg(msg, before) {
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		cbReq := &cbapi.CallbackMsgModifyCommandReq{
 | 
			
		||||
			CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeMsgModifyCommand),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										67
									
								
								internal/rpc/msg/filter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								internal/rpc/msg/filter.go
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,67 @@
 | 
			
		||||
package msg
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/openimsdk/open-im-server/v3/pkg/common/config"
 | 
			
		||||
	pbchat "github.com/openimsdk/protocol/msg"
 | 
			
		||||
	"github.com/openimsdk/tools/utils/datautil"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	separator = "-"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func filterAfterMsg(msg *pbchat.SendMsgReq, after *config.AfterConfig) bool {
 | 
			
		||||
	return filterMsg(msg, after.AttentionIds, after.AllowedTypes, after.DeniedTypes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func filterBeforeMsg(msg *pbchat.SendMsgReq, before *config.BeforeConfig) bool {
 | 
			
		||||
	return filterMsg(msg, nil, before.AllowedTypes, before.DeniedTypes)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func filterMsg(msg *pbchat.SendMsgReq, attentionIds, allowedTypes, deniedTypes []string) bool {
 | 
			
		||||
	// According to the attentionIds configuration, only some users are sent
 | 
			
		||||
	if len(attentionIds) != 0 && !datautil.Contains([]string{msg.MsgData.SendID, msg.MsgData.RecvID}, attentionIds...) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if len(allowedTypes) != 0 && !isInInterval(msg.MsgData.ContentType, allowedTypes) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	if len(deniedTypes) != 0 && isInInterval(msg.MsgData.ContentType, deniedTypes) {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
	return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func isInInterval(contentType int32, interval []string) bool {
 | 
			
		||||
	for _, v := range interval {
 | 
			
		||||
		if strings.Contains(v, separator) {
 | 
			
		||||
			// is interval
 | 
			
		||||
			bounds := strings.Split(v, separator)
 | 
			
		||||
			if len(bounds) != 2 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			bottom, err := strconv.Atoi(bounds[0])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			top, err := strconv.Atoi(bounds[1])
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if datautil.BetweenEq(int(contentType), bottom, top) {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		} else {
 | 
			
		||||
			iv, err := strconv.Atoi(v)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			if int(contentType) == iv {
 | 
			
		||||
				return true
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
@ -138,6 +138,18 @@ func (s *friendServer) webhookBeforeAddFriendAgree(ctx context.Context, before *
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *friendServer) webhookAfterAddFriendAgree(ctx context.Context, after *config.AfterConfig, req *relation.RespondFriendApplyReq) {
 | 
			
		||||
	cbReq := &cbapi.CallbackAfterAddFriendAgreeReq{
 | 
			
		||||
		CallbackCommand: cbapi.CallbackAfterAddFriendAgreeCommand,
 | 
			
		||||
		FromUserID:      req.FromUserID,
 | 
			
		||||
		ToUserID:        req.ToUserID,
 | 
			
		||||
		HandleMsg:       req.HandleMsg,
 | 
			
		||||
		HandleResult:    req.HandleResult,
 | 
			
		||||
	}
 | 
			
		||||
	resp := &cbapi.CallbackAfterAddFriendAgreeResp{}
 | 
			
		||||
	s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *friendServer) webhookBeforeImportFriends(ctx context.Context, before *config.BeforeConfig, req *relation.ImportFriendReq) error {
 | 
			
		||||
	return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
 | 
			
		||||
		cbReq := &cbapi.CallbackBeforeImportFriendsReq{
 | 
			
		||||
 | 
			
		||||
@ -212,6 +212,7 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *relation.Res
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		s.webhookAfterAddFriendAgree(ctx, &s.config.WebhooksConfig.AfterAddFriendAgree, req)
 | 
			
		||||
		s.notificationSender.FriendApplicationAgreedNotification(ctx, req)
 | 
			
		||||
		return resp, nil
 | 
			
		||||
	}
 | 
			
		||||
@ -272,7 +273,14 @@ func (s *friendServer) SetFriendRemark(ctx context.Context, req *relation.SetFri
 | 
			
		||||
	return &relation.SetFriendRemarkResp{}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ok.
 | 
			
		||||
func (s *friendServer) GetFriendInfo(ctx context.Context, req *relation.GetFriendInfoReq) (*relation.GetFriendInfoResp, error) {
 | 
			
		||||
	friends, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	return &relation.GetFriendInfoResp{FriendInfos: convert.FriendOnlyDB2PbOnly(friends)}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *relation.GetDesignatedFriendsReq) (resp *relation.GetDesignatedFriendsResp, err error) {
 | 
			
		||||
	resp = &relation.GetDesignatedFriendsResp{}
 | 
			
		||||
	if datautil.Duplicate(req.FriendUserIDs) {
 | 
			
		||||
 | 
			
		||||
@ -2,6 +2,7 @@ package user
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/tools/utils/datautil"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/protocol/constant"
 | 
			
		||||
@ -61,7 +62,7 @@ func (s *userServer) SetUserStatus(ctx context.Context, req *pbuser.SetUserStatu
 | 
			
		||||
	case constant.Online:
 | 
			
		||||
		online = []int32{req.PlatformID}
 | 
			
		||||
	case constant.Offline:
 | 
			
		||||
		online = []int32{req.PlatformID}
 | 
			
		||||
		offline = []int32{req.PlatformID}
 | 
			
		||||
	}
 | 
			
		||||
	if err := s.online.SetUserOnline(ctx, req.UserID, online, offline); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,7 @@ const (
 | 
			
		||||
	CallbackBeforeAddBlackCommand           = "callbackBeforeAddBlackCommand"
 | 
			
		||||
	CallbackAfterAddFriendCommand           = "callbackAfterAddFriendCommand"
 | 
			
		||||
	CallbackBeforeAddFriendAgreeCommand     = "callbackBeforeAddFriendAgreeCommand"
 | 
			
		||||
	CallbackAfterAddFriendAgreeCommand      = "callbackAfterAddFriendAgreeCommand"
 | 
			
		||||
	CallbackAfterDeleteFriendCommand        = "callbackAfterDeleteFriendCommand"
 | 
			
		||||
	CallbackBeforeImportFriendsCommand      = "callbackBeforeImportFriendsCommand"
 | 
			
		||||
	CallbackAfterImportFriendsCommand       = "callbackAfterImportFriendsCommand"
 | 
			
		||||
 | 
			
		||||
@ -90,6 +90,18 @@ type CallbackBeforeAddFriendAgreeResp struct {
 | 
			
		||||
	CommonCallbackResp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CallbackAfterAddFriendAgreeReq struct {
 | 
			
		||||
	CallbackCommand `json:"callbackCommand"`
 | 
			
		||||
	FromUserID      string `json:"fromUserID" `
 | 
			
		||||
	ToUserID        string `json:"blackUserID"`
 | 
			
		||||
	HandleResult    int32  `json:"HandleResult"`
 | 
			
		||||
	HandleMsg       string `json:"HandleMsg"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CallbackAfterAddFriendAgreeResp struct {
 | 
			
		||||
	CommonCallbackResp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type CallbackAfterDeleteFriendReq struct {
 | 
			
		||||
	CallbackCommand `json:"callbackCommand"`
 | 
			
		||||
	OwnerUserID     string `json:"ownerUserID" `
 | 
			
		||||
 | 
			
		||||
@ -15,13 +15,14 @@
 | 
			
		||||
package cmd
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/protocol/auth"
 | 
			
		||||
	"github.com/openimsdk/tools/apiresp"
 | 
			
		||||
	"github.com/openimsdk/tools/utils/jsonutil"
 | 
			
		||||
	"github.com/stretchr/testify/mock"
 | 
			
		||||
	"go.mongodb.org/mongo-driver/bson/primitive"
 | 
			
		||||
	"math"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// MockRootCmd is a mock type for the RootCmd type
 | 
			
		||||
@ -39,7 +40,7 @@ func TestName(t *testing.T) {
 | 
			
		||||
		ErrCode: 1234,
 | 
			
		||||
		ErrMsg:  "test",
 | 
			
		||||
		ErrDlt:  "4567",
 | 
			
		||||
		Data: &auth.UserTokenResp{
 | 
			
		||||
		Data: &auth.GetUserTokenResp{
 | 
			
		||||
			Token:             "1234567",
 | 
			
		||||
			ExpireTimeSeconds: math.MaxInt64,
 | 
			
		||||
		},
 | 
			
		||||
@ -51,7 +52,7 @@ func TestName(t *testing.T) {
 | 
			
		||||
	t.Log(string(data))
 | 
			
		||||
 | 
			
		||||
	var rReso apiresp.ApiResponse
 | 
			
		||||
	rReso.Data = &auth.UserTokenResp{}
 | 
			
		||||
	rReso.Data = &auth.GetUserTokenResp{}
 | 
			
		||||
 | 
			
		||||
	if err := jsonutil.JsonUnmarshal(data, &rReso); err != nil {
 | 
			
		||||
		panic(err)
 | 
			
		||||
 | 
			
		||||
@ -345,15 +345,19 @@ type Redis struct {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type BeforeConfig struct {
 | 
			
		||||
	Enable         bool `mapstructure:"enable"`
 | 
			
		||||
	Timeout        int  `mapstructure:"timeout"`
 | 
			
		||||
	FailedContinue bool `mapstructure:"failedContinue"`
 | 
			
		||||
	Enable         bool     `mapstructure:"enable"`
 | 
			
		||||
	Timeout        int      `mapstructure:"timeout"`
 | 
			
		||||
	FailedContinue bool     `mapstructure:"failedContinue"`
 | 
			
		||||
	AllowedTypes   []string `mapstructure:"allowedTypes"`
 | 
			
		||||
	DeniedTypes    []string `mapstructure:"deniedTypes"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type AfterConfig struct {
 | 
			
		||||
	Enable       bool     `mapstructure:"enable"`
 | 
			
		||||
	Timeout      int      `mapstructure:"timeout"`
 | 
			
		||||
	AttentionIds []string `mapstructure:"attentionIds"`
 | 
			
		||||
	AllowedTypes []string `mapstructure:"allowedTypes"`
 | 
			
		||||
	DeniedTypes  []string `mapstructure:"deniedTypes"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Share struct {
 | 
			
		||||
@ -434,6 +438,7 @@ type Webhooks struct {
 | 
			
		||||
	BeforeAddBlack           BeforeConfig `mapstructure:"beforeAddBlack"`
 | 
			
		||||
	AfterAddFriend           AfterConfig  `mapstructure:"afterAddFriend"`
 | 
			
		||||
	BeforeAddFriendAgree     BeforeConfig `mapstructure:"beforeAddFriendAgree"`
 | 
			
		||||
	AfterAddFriendAgree      AfterConfig  `mapstructure:"afterAddFriendAgree"`
 | 
			
		||||
	AfterDeleteFriend        AfterConfig  `mapstructure:"afterDeleteFriend"`
 | 
			
		||||
	BeforeImportFriends      BeforeConfig `mapstructure:"beforeImportFriends"`
 | 
			
		||||
	AfterImportFriends       AfterConfig  `mapstructure:"afterImportFriends"`
 | 
			
		||||
 | 
			
		||||
@ -18,6 +18,7 @@ import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
 | 
			
		||||
	"github.com/openimsdk/protocol/relation"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/protocol/sdkws"
 | 
			
		||||
	"github.com/openimsdk/tools/utils/datautil"
 | 
			
		||||
@ -35,9 +36,7 @@ func FriendPb2DB(friend *sdkws.FriendInfo) *model.Friend {
 | 
			
		||||
	return dbFriend
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FriendDB2Pb(ctx context.Context, friendDB *model.Friend,
 | 
			
		||||
	getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error),
 | 
			
		||||
) (*sdkws.FriendInfo, error) {
 | 
			
		||||
func FriendDB2Pb(ctx context.Context, friendDB *model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (*sdkws.FriendInfo, error) {
 | 
			
		||||
	users, err := getUsers(ctx, []string{friendDB.FriendUserID})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
@ -53,11 +52,7 @@ func FriendDB2Pb(ctx context.Context, friendDB *model.Friend,
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FriendsDB2Pb(
 | 
			
		||||
	ctx context.Context,
 | 
			
		||||
	friendsDB []*model.Friend,
 | 
			
		||||
	getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error),
 | 
			
		||||
) (friendsPb []*sdkws.FriendInfo, err error) {
 | 
			
		||||
func FriendsDB2Pb(ctx context.Context, friendsDB []*model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (friendsPb []*sdkws.FriendInfo, err error) {
 | 
			
		||||
	if len(friendsDB) == 0 {
 | 
			
		||||
		return nil, nil
 | 
			
		||||
	}
 | 
			
		||||
@ -86,7 +81,21 @@ func FriendsDB2Pb(
 | 
			
		||||
		friendsPb = append(friendsPb, friendPb)
 | 
			
		||||
	}
 | 
			
		||||
	return friendsPb, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FriendOnlyDB2PbOnly(friendsDB []*model.Friend) []*relation.FriendInfoOnly {
 | 
			
		||||
	return datautil.Slice(friendsDB, func(f *model.Friend) *relation.FriendInfoOnly {
 | 
			
		||||
		return &relation.FriendInfoOnly{
 | 
			
		||||
			OwnerUserID:    f.OwnerUserID,
 | 
			
		||||
			FriendUserID:   f.FriendUserID,
 | 
			
		||||
			Remark:         f.Remark,
 | 
			
		||||
			CreateTime:     f.CreateTime.UnixMilli(),
 | 
			
		||||
			AddSource:      f.AddSource,
 | 
			
		||||
			OperatorUserID: f.OperatorUserID,
 | 
			
		||||
			Ex:             f.Ex,
 | 
			
		||||
			IsPinned:       f.IsPinned,
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func FriendRequestDB2Pb(ctx context.Context, friendRequests []*model.FriendRequest, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) ([]*sdkws.FriendRequest, error) {
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@ package controller
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"github.com/openimsdk/tools/log"
 | 
			
		||||
 | 
			
		||||
	"github.com/golang-jwt/jwt/v4"
 | 
			
		||||
	"github.com/openimsdk/open-im-server/v3/pkg/authverify"
 | 
			
		||||
@ -77,12 +78,23 @@ func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformI
 | 
			
		||||
			return "", err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	const adminTokenMaxNum = 30
 | 
			
		||||
	if platformID == constant.AdminPlatformID {
 | 
			
		||||
		if len(kickedTokenKey) > adminTokenMaxNum {
 | 
			
		||||
			kickedTokenKey = kickedTokenKey[:len(kickedTokenKey)-adminTokenMaxNum]
 | 
			
		||||
		} else {
 | 
			
		||||
			kickedTokenKey = nil
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if len(kickedTokenKey) != 0 {
 | 
			
		||||
		for _, k := range kickedTokenKey {
 | 
			
		||||
			err := a.cache.SetTokenFlagEx(ctx, userID, platformID, k, constant.KickedToken)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return "", err
 | 
			
		||||
			}
 | 
			
		||||
			log.ZDebug(ctx, "kicked token in create token", "token", k)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -254,7 +254,7 @@ func (db *msgTransferDatabase) BatchInsertChat2Cache(ctx context.Context, conver
 | 
			
		||||
 | 
			
		||||
func (db *msgTransferDatabase) setHasReadSeqs(ctx context.Context, conversationID string, userSeqMap map[string]int64) error {
 | 
			
		||||
	for userID, seq := range userSeqMap {
 | 
			
		||||
		if err := db.seqUser.SetUserReadSeq(ctx, conversationID, userID, seq); err != nil {
 | 
			
		||||
		if err := db.seqUser.SetUserReadSeqToDB(ctx, conversationID, userID, seq); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -39,7 +39,9 @@ func GetNotificationConversationIDByMsg(msg *sdkws.MsgData) string {
 | 
			
		||||
	case constant.ReadGroupChatType:
 | 
			
		||||
		return "n_" + msg.GroupID
 | 
			
		||||
	case constant.NotificationChatType:
 | 
			
		||||
		return "n_" + msg.SendID + "_" + msg.RecvID
 | 
			
		||||
		l := []string{msg.SendID, msg.RecvID}
 | 
			
		||||
		sort.Strings(l)
 | 
			
		||||
		return "n_" + strings.Join(l, "_")
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
@ -55,21 +57,11 @@ func GetChatConversationIDByMsg(msg *sdkws.MsgData) string {
 | 
			
		||||
	case constant.ReadGroupChatType:
 | 
			
		||||
		return "sg_" + msg.GroupID
 | 
			
		||||
	case constant.NotificationChatType:
 | 
			
		||||
		return "sn_" + msg.SendID + "_" + msg.RecvID
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GenConversationUniqueKey(msg *sdkws.MsgData) string {
 | 
			
		||||
	switch msg.SessionType {
 | 
			
		||||
	case constant.SingleChatType, constant.NotificationChatType:
 | 
			
		||||
		l := []string{msg.SendID, msg.RecvID}
 | 
			
		||||
		sort.Strings(l)
 | 
			
		||||
		return strings.Join(l, "_")
 | 
			
		||||
	case constant.ReadGroupChatType:
 | 
			
		||||
		return msg.GroupID
 | 
			
		||||
		return "sn_" + strings.Join(l, "_")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -94,10 +86,12 @@ func GetConversationIDByMsg(msg *sdkws.MsgData) string {
 | 
			
		||||
		}
 | 
			
		||||
		return "sg_" + msg.GroupID // super group chat
 | 
			
		||||
	case constant.NotificationChatType:
 | 
			
		||||
		l := []string{msg.SendID, msg.RecvID}
 | 
			
		||||
		sort.Strings(l)
 | 
			
		||||
		if !options.IsNotNotification() {
 | 
			
		||||
			return "n_" + msg.SendID + "_" + msg.RecvID // super group chat
 | 
			
		||||
			return "n_" + strings.Join(l, "_")
 | 
			
		||||
		}
 | 
			
		||||
		return "sn_" + msg.SendID + "_" + msg.RecvID // server notification chat
 | 
			
		||||
		return "sn_" + strings.Join(l, "_")
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
@ -120,30 +114,6 @@ func GetConversationIDBySessionType(sessionType int, ids ...string) string {
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetNotificationConversationIDByConversationID(conversationID string) string {
 | 
			
		||||
	l := strings.Split(conversationID, "_")
 | 
			
		||||
	if len(l) > 1 {
 | 
			
		||||
		l[0] = "n"
 | 
			
		||||
		return strings.Join(l, "_")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetNotificationConversationID(sessionType int, ids ...string) string {
 | 
			
		||||
	sort.Strings(ids)
 | 
			
		||||
	if len(ids) > 2 || len(ids) < 1 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	switch sessionType {
 | 
			
		||||
	case constant.SingleChatType:
 | 
			
		||||
		return "n_" + strings.Join(ids, "_") // single chat
 | 
			
		||||
	case constant.ReadGroupChatType:
 | 
			
		||||
		return "n_" + ids[0] // super group chat
 | 
			
		||||
	}
 | 
			
		||||
	return ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func IsNotification(conversationID string) bool {
 | 
			
		||||
	return strings.HasPrefix(conversationID, "n_")
 | 
			
		||||
}
 | 
			
		||||
@ -152,30 +122,6 @@ func IsNotificationByMsg(msg *sdkws.MsgData) bool {
 | 
			
		||||
	return !Options(msg.Options).IsNotNotification()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ParseConversationID(msg *sdkws.MsgData) (isNotification bool, conversationID string) {
 | 
			
		||||
	options := Options(msg.Options)
 | 
			
		||||
	switch msg.SessionType {
 | 
			
		||||
	case constant.SingleChatType:
 | 
			
		||||
		l := []string{msg.SendID, msg.RecvID}
 | 
			
		||||
		sort.Strings(l)
 | 
			
		||||
		if !options.IsNotNotification() {
 | 
			
		||||
			return true, "n_" + strings.Join(l, "_")
 | 
			
		||||
		}
 | 
			
		||||
		return false, "si_" + strings.Join(l, "_") // single chat
 | 
			
		||||
	case constant.ReadGroupChatType:
 | 
			
		||||
		if !options.IsNotNotification() {
 | 
			
		||||
			return true, "n_" + msg.GroupID // super group chat
 | 
			
		||||
		}
 | 
			
		||||
		return false, "sg_" + msg.GroupID // super group chat
 | 
			
		||||
	case constant.NotificationChatType:
 | 
			
		||||
		if !options.IsNotNotification() {
 | 
			
		||||
			return true, "n_" + msg.SendID + "_" + msg.RecvID // super group chat
 | 
			
		||||
		}
 | 
			
		||||
		return false, "sn_" + msg.SendID + "_" + msg.RecvID // server notification chat
 | 
			
		||||
	}
 | 
			
		||||
	return false, ""
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type MsgBySeq []*sdkws.MsgData
 | 
			
		||||
 | 
			
		||||
func (s MsgBySeq) Len() int {
 | 
			
		||||
 | 
			
		||||
@ -1,334 +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 msgprocessor
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/protocol/sdkws"
 | 
			
		||||
	"google.golang.org/protobuf/proto"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestGetNotificationConversationIDByMsg(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		msg *sdkws.MsgData
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want string
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := GetNotificationConversationIDByMsg(tt.args.msg); got != tt.want {
 | 
			
		||||
				t.Errorf("GetNotificationConversationIDByMsg() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetChatConversationIDByMsg(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		msg *sdkws.MsgData
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want string
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := GetChatConversationIDByMsg(tt.args.msg); got != tt.want {
 | 
			
		||||
				t.Errorf("GetChatConversationIDByMsg() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGenConversationUniqueKey(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		msg *sdkws.MsgData
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want string
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := GenConversationUniqueKey(tt.args.msg); got != tt.want {
 | 
			
		||||
				t.Errorf("GenConversationUniqueKey() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetConversationIDByMsg(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		msg *sdkws.MsgData
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want string
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := GetConversationIDByMsg(tt.args.msg); got != tt.want {
 | 
			
		||||
				t.Errorf("GetConversationIDByMsg() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetConversationIDBySessionType(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		sessionType int
 | 
			
		||||
		ids         []string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want string
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := GetConversationIDBySessionType(tt.args.sessionType, tt.args.ids...); got != tt.want {
 | 
			
		||||
				t.Errorf("GetConversationIDBySessionType() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetNotificationConversationIDByConversationID(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		conversationID string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want string
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := GetNotificationConversationIDByConversationID(tt.args.conversationID); got != tt.want {
 | 
			
		||||
				t.Errorf("GetNotificationConversationIDByConversationID() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestGetNotificationConversationID(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		sessionType int
 | 
			
		||||
		ids         []string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want string
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := GetNotificationConversationID(tt.args.sessionType, tt.args.ids...); got != tt.want {
 | 
			
		||||
				t.Errorf("GetNotificationConversationID() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsNotification(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		conversationID string
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := IsNotification(tt.args.conversationID); got != tt.want {
 | 
			
		||||
				t.Errorf("IsNotification() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestIsNotificationByMsg(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		msg *sdkws.MsgData
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		args args
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := IsNotificationByMsg(tt.args.msg); got != tt.want {
 | 
			
		||||
				t.Errorf("IsNotificationByMsg() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestParseConversationID(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		msg *sdkws.MsgData
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name               string
 | 
			
		||||
		args               args
 | 
			
		||||
		wantIsNotification bool
 | 
			
		||||
		wantConversationID string
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			gotIsNotification, gotConversationID := ParseConversationID(tt.args.msg)
 | 
			
		||||
			if gotIsNotification != tt.wantIsNotification {
 | 
			
		||||
				t.Errorf("ParseConversationID() gotIsNotification = %v, want %v", gotIsNotification, tt.wantIsNotification)
 | 
			
		||||
			}
 | 
			
		||||
			if gotConversationID != tt.wantConversationID {
 | 
			
		||||
				t.Errorf("ParseConversationID() gotConversationID = %v, want %v", gotConversationID, tt.wantConversationID)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMsgBySeq_Len(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		s    MsgBySeq
 | 
			
		||||
		want int
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := tt.s.Len(); got != tt.want {
 | 
			
		||||
				t.Errorf("MsgBySeq.Len() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMsgBySeq_Less(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		i int
 | 
			
		||||
		j int
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		s    MsgBySeq
 | 
			
		||||
		args args
 | 
			
		||||
		want bool
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			if got := tt.s.Less(tt.args.i, tt.args.j); got != tt.want {
 | 
			
		||||
				t.Errorf("MsgBySeq.Less() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestMsgBySeq_Swap(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		i int
 | 
			
		||||
		j int
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name string
 | 
			
		||||
		s    MsgBySeq
 | 
			
		||||
		args args
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			tt.s.Swap(tt.args.i, tt.args.j)
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPb2String(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		pb proto.Message
 | 
			
		||||
	}
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name    string
 | 
			
		||||
		args    args
 | 
			
		||||
		want    string
 | 
			
		||||
		wantErr bool
 | 
			
		||||
	}{
 | 
			
		||||
		// TODO: Add test cases.
 | 
			
		||||
	}
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			got, err := Pb2String(tt.args.pb)
 | 
			
		||||
			if (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("Pb2String() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			if got != tt.want {
 | 
			
		||||
				t.Errorf("Pb2String() = %v, want %v", got, tt.want)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestString2Pb(t *testing.T) {
 | 
			
		||||
	type args struct {
 | 
			
		||||
		s  string
 | 
			
		||||
		pb proto.Message
 | 
			
		||||
	}
 | 
			
		||||
	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 := String2Pb(tt.args.s, tt.args.pb); (err != nil) != tt.wantErr {
 | 
			
		||||
				t.Errorf("String2Pb() error = %v, wantErr %v", err, tt.wantErr)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@ -23,7 +23,6 @@ import (
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/protocol/auth"
 | 
			
		||||
	"github.com/openimsdk/protocol/constant"
 | 
			
		||||
	"github.com/openimsdk/protocol/third"
 | 
			
		||||
	"github.com/openimsdk/tools/errs"
 | 
			
		||||
)
 | 
			
		||||
@ -88,14 +87,13 @@ func (a *Api) apiPost(ctx context.Context, path string, req any, resp any) error
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *Api) GetToken(ctx context.Context) (string, error) {
 | 
			
		||||
	req := auth.UserTokenReq{
 | 
			
		||||
		UserID:     a.UserID,
 | 
			
		||||
		Secret:     a.Secret,
 | 
			
		||||
		PlatformID: constant.AdminPlatformID,
 | 
			
		||||
func (a *Api) GetAdminToken(ctx context.Context) (string, error) {
 | 
			
		||||
	req := auth.GetAdminTokenReq{
 | 
			
		||||
		UserID: a.UserID,
 | 
			
		||||
		Secret: a.Secret,
 | 
			
		||||
	}
 | 
			
		||||
	var resp auth.UserTokenResp
 | 
			
		||||
	if err := a.apiPost(ctx, "/auth/user_token", &req, &resp); err != nil {
 | 
			
		||||
	var resp auth.GetAdminTokenResp
 | 
			
		||||
	if err := a.apiPost(ctx, "/auth/get_admin_token", &req, &resp); err != nil {
 | 
			
		||||
		return "", err
 | 
			
		||||
	}
 | 
			
		||||
	return resp.Token, nil
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,6 @@ import (
 | 
			
		||||
	"encoding/hex"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/openimsdk/tools/errs"
 | 
			
		||||
	"io"
 | 
			
		||||
	"log"
 | 
			
		||||
	"net/http"
 | 
			
		||||
@ -34,6 +33,8 @@ import (
 | 
			
		||||
	"sync/atomic"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/tools/errs"
 | 
			
		||||
 | 
			
		||||
	"github.com/openimsdk/protocol/third"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@ -95,7 +96,7 @@ func (m *Manage) Run() error {
 | 
			
		||||
	}
 | 
			
		||||
	var err error
 | 
			
		||||
	ctx := context.WithValue(m.ctx, "operationID", fmt.Sprintf("%s_init", m.prefix))
 | 
			
		||||
	m.api.Token, err = m.api.GetToken(ctx)
 | 
			
		||||
	m.api.Token, err = m.api.GetAdminToken(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
3.8.0
 | 
			
		||||
3.8.1
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user