diff --git a/.env b/.env index 8863f2ec1..0c02bf9f6 100644 --- a/.env +++ b/.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=./ diff --git a/.github/workflows/publish-docker-image.yml b/.github/workflows/publish-docker-image.yml index 7d7d23f17..40b79e61a 100644 --- a/.github/workflows/publish-docker-image.yml +++ b/.github/workflows/publish-docker-image.yml @@ -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-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: Extract metadata for Docker # (tags, labels) - if: success() + # - 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: Load Docker image from file + run: docker load -i image.tar + + - 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 diff --git a/Dockerfile b/Dockerfile index e082dd64c..f8cfbda9e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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 diff --git a/cmd/openim-api/main.go b/cmd/openim-api/main.go index e29ed2a59..3690cfc99 100644 --- a/cmd/openim-api/main.go +++ b/cmd/openim-api/main.go @@ -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() { diff --git a/config/discovery.yml b/config/discovery.yml index 78a36f3d1..6e68cbff4 100644 --- a/config/discovery.yml +++ b/config/discovery.yml @@ -5,9 +5,4 @@ etcd: username: '' password: '' -zookeeper: - schema: openim - address: [ localhost:12181 ] - username: '' - password: '' diff --git a/config/webhooks.yml b/config/webhooks.yml index eee79c650..854d2dc2c 100644 --- a/config/webhooks.yml +++ b/config/webhooks.yml @@ -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 diff --git a/docker-compose.yml b/docker-compose.yml index edac65b13..6d88bac10 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -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 diff --git a/go.mod b/go.mod index 5cc1f9ad3..b6baca2a1 100644 --- a/go.mod +++ b/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 diff --git a/go.sum b/go.sum index 1095134dd..6f5475274 100644 --- a/go.sum +++ b/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= diff --git a/internal/api/auth.go b/internal/api/auth.go index f0790ce98..f41b530bf 100644 --- a/internal/api/auth.go +++ b/internal/api/auth.go @@ -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) { diff --git a/internal/api/jssdk/jssdk.go b/internal/api/jssdk/jssdk.go index 7f136c74c..2fcbf5ec6 100644 --- a/internal/api/jssdk/jssdk.go +++ b/internal/api/jssdk/jssdk.go @@ -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 diff --git a/internal/api/jssdk/stu.go b/internal/api/jssdk/stu.go deleted file mode 100644 index 2f63975b3..000000000 --- a/internal/api/jssdk/stu.go +++ /dev/null @@ -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"` -} diff --git a/internal/api/jssdk/tools.go b/internal/api/jssdk/tools.go index c57457d9f..c19d8970b 100644 --- a/internal/api/jssdk/tools.go +++ b/internal/api/jssdk/tools.go @@ -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 + } + 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, resp) + apiresp.GinSuccess(c, body) } diff --git a/internal/api/jssdk_test.go b/internal/api/jssdk_test.go deleted file mode 100644 index 472ca56b5..000000000 --- a/internal/api/jssdk_test.go +++ /dev/null @@ -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) - -} diff --git a/internal/api/router.go b/internal/api/router.go index 8d2a688f4..560516d30 100644 --- a/internal/api/router.go +++ b/internal/api/router.go @@ -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", } diff --git a/internal/api/user.go b/internal/api/user.go index dba7cd312..b499f71dc 100644 --- a/internal/api/user.go +++ b/internal/api/user.go @@ -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) diff --git a/internal/msggateway/hub_server.go b/internal/msggateway/hub_server.go index 01482aa11..cc2987644 100644 --- a/internal/msggateway/hub_server.go +++ b/internal/msggateway/hub_server.go @@ -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) } } diff --git a/internal/msggateway/online.go b/internal/msggateway/online.go index 27b4544aa..f29869b6e 100644 --- a/internal/msggateway/online.go +++ b/internal/msggateway/online.go @@ -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++ { diff --git a/internal/push/a_test.go b/internal/push/a_test.go deleted file mode 100644 index 8b2d86407..000000000 --- a/internal/push/a_test.go +++ /dev/null @@ -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 {} -} diff --git a/internal/rpc/auth/auth.go b/internal/rpc/auth/auth.go index d870a6c58..06ae89d97 100644 --- a/internal/rpc/auth/auth.go +++ b/internal/rpc/auth/auth.go @@ -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) { diff --git a/internal/rpc/group/callback.go b/internal/rpc/group/callback.go index e748c66dc..559d64ff4 100644 --- a/internal/rpc/group/callback.go +++ b/internal/rpc/group/callback.go @@ -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 diff --git a/internal/rpc/group/db_map.go b/internal/rpc/group/db_map.go index 2f6791d5e..26c9a4625 100644 --- a/internal/rpc/group/db_map.go +++ b/internal/rpc/group/db_map.go @@ -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 diff --git a/internal/rpc/group/group.go b/internal/rpc/group/group.go index 8cb098dde..fce33de6f 100644 --- a/internal/rpc/group/group.go +++ b/internal/rpc/group/group.go @@ -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") diff --git a/internal/rpc/msg/callback.go b/internal/rpc/msg/callback.go index 3b76c2553..c66dd6ca9 100644 --- a/internal/rpc/msg/callback.go +++ b/internal/rpc/msg/callback.go @@ -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), } diff --git a/internal/rpc/msg/filter.go b/internal/rpc/msg/filter.go new file mode 100644 index 000000000..ed1a488f1 --- /dev/null +++ b/internal/rpc/msg/filter.go @@ -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 +} diff --git a/internal/rpc/relation/callback.go b/internal/rpc/relation/callback.go index 69c4c9e0e..09debdea1 100644 --- a/internal/rpc/relation/callback.go +++ b/internal/rpc/relation/callback.go @@ -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{ diff --git a/internal/rpc/relation/friend.go b/internal/rpc/relation/friend.go index f049420d9..2f4843a8e 100644 --- a/internal/rpc/relation/friend.go +++ b/internal/rpc/relation/friend.go @@ -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) { diff --git a/internal/rpc/user/online.go b/internal/rpc/user/online.go index 4e7823306..0e5365ed9 100644 --- a/internal/rpc/user/online.go +++ b/internal/rpc/user/online.go @@ -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 diff --git a/pkg/callbackstruct/constant.go b/pkg/callbackstruct/constant.go index 5d136d3da..73f89a719 100644 --- a/pkg/callbackstruct/constant.go +++ b/pkg/callbackstruct/constant.go @@ -25,6 +25,7 @@ const ( CallbackBeforeAddBlackCommand = "callbackBeforeAddBlackCommand" CallbackAfterAddFriendCommand = "callbackAfterAddFriendCommand" CallbackBeforeAddFriendAgreeCommand = "callbackBeforeAddFriendAgreeCommand" + CallbackAfterAddFriendAgreeCommand = "callbackAfterAddFriendAgreeCommand" CallbackAfterDeleteFriendCommand = "callbackAfterDeleteFriendCommand" CallbackBeforeImportFriendsCommand = "callbackBeforeImportFriendsCommand" CallbackAfterImportFriendsCommand = "callbackAfterImportFriendsCommand" diff --git a/pkg/callbackstruct/friend.go b/pkg/callbackstruct/friend.go index 3674a34da..a81746bfd 100644 --- a/pkg/callbackstruct/friend.go +++ b/pkg/callbackstruct/friend.go @@ -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" ` diff --git a/pkg/common/cmd/msg_gateway_test.go b/pkg/common/cmd/msg_gateway_test.go index 2b68a3e3a..929abcd86 100644 --- a/pkg/common/cmd/msg_gateway_test.go +++ b/pkg/common/cmd/msg_gateway_test.go @@ -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) diff --git a/pkg/common/config/config.go b/pkg/common/config/config.go index 830b1ef9d..77fcbb8aa 100644 --- a/pkg/common/config/config.go +++ b/pkg/common/config/config.go @@ -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"` diff --git a/pkg/common/convert/friend.go b/pkg/common/convert/friend.go index 8d6cfad18..6d346b0f4 100644 --- a/pkg/common/convert/friend.go +++ b/pkg/common/convert/friend.go @@ -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) { diff --git a/pkg/common/storage/controller/auth.go b/pkg/common/storage/controller/auth.go index cb06a197d..94f18b3ae 100644 --- a/pkg/common/storage/controller/auth.go +++ b/pkg/common/storage/controller/auth.go @@ -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) } } diff --git a/pkg/common/storage/controller/msg_transfer.go b/pkg/common/storage/controller/msg_transfer.go index 5e540a2c3..c5dfd011f 100644 --- a/pkg/common/storage/controller/msg_transfer.go +++ b/pkg/common/storage/controller/msg_transfer.go @@ -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 } } diff --git a/pkg/msgprocessor/conversation.go b/pkg/msgprocessor/conversation.go index f8140cc7d..04d772d16 100644 --- a/pkg/msgprocessor/conversation.go +++ b/pkg/msgprocessor/conversation.go @@ -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 { diff --git a/pkg/msgprocessor/conversation_test.go b/pkg/msgprocessor/conversation_test.go deleted file mode 100644 index 32601baec..000000000 --- a/pkg/msgprocessor/conversation_test.go +++ /dev/null @@ -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) - } - }) - } -} diff --git a/tools/url2im/pkg/api.go b/tools/url2im/pkg/api.go index 5bf48c4ea..0ef8c1db7 100644 --- a/tools/url2im/pkg/api.go +++ b/tools/url2im/pkg/api.go @@ -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, - } - var resp auth.UserTokenResp - if err := a.apiPost(ctx, "/auth/user_token", &req, &resp); err != nil { +func (a *Api) GetAdminToken(ctx context.Context) (string, error) { + req := auth.GetAdminTokenReq{ + UserID: a.UserID, + Secret: a.Secret, + } + var resp auth.GetAdminTokenResp + if err := a.apiPost(ctx, "/auth/get_admin_token", &req, &resp); err != nil { return "", err } return resp.Token, nil diff --git a/tools/url2im/pkg/manage.go b/tools/url2im/pkg/manage.go index 3664baa25..9dc1de801 100644 --- a/tools/url2im/pkg/manage.go +++ b/tools/url2im/pkg/manage.go @@ -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 } diff --git a/version/version b/version/version index 0be1fc7d2..aaaff9192 100644 --- a/version/version +++ b/version/version @@ -1 +1 @@ -3.8.0 \ No newline at end of file +3.8.1 \ No newline at end of file