Merge remote-tracking branch 'origin/main' into v3.8-js-sdk-only

# Conflicts:
#	go.mod
#	go.sum
#	pkg/common/storage/controller/auth.go
This commit is contained in:
withchao 2024-10-25 17:31:26 +08:00
commit 5041942563
36 changed files with 967 additions and 373 deletions

View File

@ -1,62 +0,0 @@
# Version logging for OpenIM
<!-- BEGIN MUNGE: GENERATED_TOC -->
<!-- END MUNGE: GENERATED_TOC -->
{{ if .Versions -}}
<a name="unreleased"></a>
## [Unreleased]
{{ if .Unreleased.CommitGroups -}}
{{ range .Unreleased.CommitGroups -}}
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}
{{ range .Versions }}
<a name="{{ .Tag.Name }}"></a>
## {{ if .Tag.Previous }}[{{ .Tag.Name }}]{{ else }}{{ .Tag.Name }}{{ end }} - {{ datetime "2006-01-02" .Tag.Date }}
{{ range .CommitGroups -}}
### {{ .Title }}
{{ range .Commits -}}
- {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
{{ end }}
{{ end -}}
{{- if .RevertCommits -}}
### Reverts
{{ range .RevertCommits -}}
- {{ .Revert.Header }}
{{ end }}
{{ end -}}
{{- if .MergeCommits -}}
### Pull Requests
{{ range .MergeCommits -}}
- {{ .Header }}
{{ end }}
{{ end -}}
{{- if .NoteGroups -}}
{{ range .NoteGroups -}}
### {{ .Title }}
{{ range .Notes }}
{{ .Body }}
{{ end }}
{{ end -}}
{{ end -}}
{{ end -}}
{{- if .Versions }}
[Unreleased]: {{ .Info.RepositoryURL }}/compare/{{ $latest := index .Versions 0 }}{{ $latest.Tag.Name }}...HEAD
{{ range .Versions -}}
{{ if .Tag.Previous -}}
[{{ .Tag.Name }}]: {{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}
{{ end -}}
{{ end -}}
{{ end -}}

View File

@ -1,67 +0,0 @@
bin: git
style: github
template: CHANGELOG.tpl.md
info:
title: CHANGELOG
repository_url: https://github.com/openimsdk/open-im-server
options:
tag_filter_pattern: '^v'
sort: "date"
commits:
filters:
Type:
- feat
- fix
- perf
- refactor
- docs
- test
- chore
- ci
- build
sort_by: Scope
commit_groups:
group_by: Type
sort_by: Title
title_order:
- feat
- fix
- perf
- refactor
- docs
- test
- chore
- ci
- build
title_maps:
feat: Features
header:
pattern: "<regexp>"
pattern_maps:
- PropName
issues:
prefix:
- #
refs:
actions:
- Closes
- Fixes
merges:
pattern: "^Merge branch '(\\w+)'$"
pattern_maps:
- Source
reverts:
pattern: "^Revert \"([\\s\\S]*)\"$"
pattern_maps:
- Header
notes:
keywords:
- BREAKING CHANGE

View File

@ -53,15 +53,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-cmdutils - binary: openim-cmdutils
id: openim-cmdutils id: openim-cmdutils
@ -71,15 +64,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-crontask - binary: openim-crontask
id: openim-crontask id: openim-crontask
@ -89,15 +75,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-msggateway - binary: openim-msggateway
id: openim-msggateway id: openim-msggateway
@ -107,15 +86,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-msgtransfer - binary: openim-msgtransfer
id: openim-msgtransfer id: openim-msgtransfer
@ -125,15 +97,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-push - binary: openim-push
id: openim-push id: openim-push
@ -143,15 +108,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-rpc-auth - binary: openim-rpc-auth
id: openim-rpc-auth id: openim-rpc-auth
@ -161,15 +119,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-rpc-conversation - binary: openim-rpc-conversation
id: openim-rpc-conversation id: openim-rpc-conversation
@ -179,15 +130,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-rpc-friend - binary: openim-rpc-friend
id: openim-rpc-friend id: openim-rpc-friend
@ -197,15 +141,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-rpc-group - binary: openim-rpc-group
id: openim-rpc-group id: openim-rpc-group
@ -215,15 +152,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-rpc-msg - binary: openim-rpc-msg
id: openim-rpc-msg id: openim-rpc-msg
@ -233,15 +163,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-rpc-third - binary: openim-rpc-third
id: openim-rpc-third id: openim-rpc-third
@ -251,15 +174,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
- binary: openim-rpc-user - binary: openim-rpc-user
id: openim-rpc-user id: openim-rpc-user
@ -269,15 +185,8 @@ builds:
- windows - windows
- linux - linux
goarch: goarch:
- s390x
- mips64
- mips64le
- amd64 - amd64
- ppc64le
- arm64 - arm64
goarm:
- "6"
- "7"
# TODONeed a script, such as the init - release to help binary to find the right directory # TODONeed a script, such as the init - release to help binary to find the right directory

View File

@ -13,4 +13,17 @@ rpcRegisterName:
imAdminUserID: [ imAdmin ] imAdminUserID: [ imAdmin ]
# 1: For Android, iOS, Windows, Mac, and web platforms, only one instance can be online at a time # 1: For Android, iOS, Windows, Mac, and web platforms, only one instance can be online at a time
multiLoginPolicy: 1 multiLogin:
policy: 1
maxNumOneEnd: 30
customizeLoginNum:
ios: 1
android: 1
windows: 1
osx: 1
web: 1
miniWeb: 1
linux: 1
aPad: 1
iPad: 1
admin: 1

2
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/gorilla/websocket v1.5.1 github.com/gorilla/websocket v1.5.1
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/openimsdk/protocol v0.0.72-alpha.41 github.com/openimsdk/protocol v0.0.72-alpha.47
github.com/openimsdk/tools v0.0.50-alpha.16 github.com/openimsdk/tools v0.0.50-alpha.16
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_golang v1.18.0

4
go.sum
View File

@ -319,8 +319,8 @@ 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/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 h1:VY9c5x515lTfmdhhPjMvR3BBRrRquAUCFsz7t7vbv7Y=
github.com/openimsdk/gomake v0.0.14-alpha.5/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI= github.com/openimsdk/gomake v0.0.14-alpha.5/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
github.com/openimsdk/protocol v0.0.72-alpha.41 h1:SMMoTc1iu+wtRqUqmIgqPJFejLgPeauOwoJ4VVG4iMQ= github.com/openimsdk/protocol v0.0.72-alpha.47 h1:FGHnEwsA05GxT3vnz7YH3fbVkuoO3P71ZZgkQQ71MjA=
github.com/openimsdk/protocol v0.0.72-alpha.41/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8= github.com/openimsdk/protocol v0.0.72-alpha.47/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 h1:bC1AQvJMuOHtZm8LZRvN8L5mH1Ws2VYdL+TLTs1iGSc=
github.com/openimsdk/tools v0.0.50-alpha.16/go.mod h1:h1cYmfyaVtgFbKmb1Cfsl8XwUOMTt8ubVUQrdGtsUh4= 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 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=

View File

@ -198,6 +198,13 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
objectGroup.POST("/initiate_form_data", t.InitiateFormData) objectGroup.POST("/initiate_form_data", t.InitiateFormData)
objectGroup.POST("/complete_form_data", t.CompleteFormData) objectGroup.POST("/complete_form_data", t.CompleteFormData)
objectGroup.GET("/*name", t.ObjectRedirect) objectGroup.GET("/*name", t.ObjectRedirect)
applicationGroup := r.Group("application")
applicationGroup.POST("/add_version", t.AddApplicationVersion)
applicationGroup.POST("/update_version", t.UpdateApplicationVersion)
applicationGroup.POST("/delete_version", t.DeleteApplicationVersion)
applicationGroup.POST("/latest_version", t.LatestApplicationVersion)
applicationGroup.POST("/page_versions", t.PageApplicationVersion)
} }
// Message // Message
msgGroup := r.Group("/msg") msgGroup := r.Group("/msg")
@ -290,4 +297,6 @@ func GinParseToken(authRPC *rpcclient.Auth) gin.HandlerFunc {
var Whitelist = []string{ var Whitelist = []string{
"/auth/get_admin_token", "/auth/get_admin_token",
"/auth/parse_token", "/auth/parse_token",
"/application/latest_version",
"/application/page_versions",
} }

View File

@ -170,3 +170,23 @@ func (o *ThirdApi) SearchLogs(c *gin.Context) {
func (o *ThirdApi) GetPrometheus(c *gin.Context) { func (o *ThirdApi) GetPrometheus(c *gin.Context) {
c.Redirect(http.StatusFound, o.GrafanaUrl) c.Redirect(http.StatusFound, o.GrafanaUrl)
} }
func (o *ThirdApi) LatestApplicationVersion(c *gin.Context) {
a2r.Call(third.ThirdClient.LatestApplicationVersion, o.Client, c)
}
func (o *ThirdApi) AddApplicationVersion(c *gin.Context) {
a2r.Call(third.ThirdClient.AddApplicationVersion, o.Client, c)
}
func (o *ThirdApi) UpdateApplicationVersion(c *gin.Context) {
a2r.Call(third.ThirdClient.UpdateApplicationVersion, o.Client, c)
}
func (o *ThirdApi) DeleteApplicationVersion(c *gin.Context) {
a2r.Call(third.ThirdClient.DeleteApplicationVersion, o.Client, c)
}
func (o *ThirdApi) PageApplicationVersion(c *gin.Context) {
a2r.Call(third.ThirdClient.PageApplicationVersion, o.Client, c)
}

View File

@ -1,17 +1,3 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msggateway package msggateway
import ( import (
@ -212,7 +198,6 @@ func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *C
if err != nil { if err != nil {
return err return err
} }
wg := errgroup.Group{} wg := errgroup.Group{}
wg.SetLimit(concurrentRequest) wg.SetLimit(concurrentRequest)
@ -321,8 +306,32 @@ func (ws *WsServer) KickUserConn(client *Client) error {
} }
func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Client, newClient *Client) { func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Client, newClient *Client) {
switch ws.msgGatewayConfig.Share.MultiLoginPolicy { kickTokenFunc := func(kickClients []*Client) {
var kickTokens []string
ws.clients.DeleteClients(newClient.UserID, kickClients)
for _, c := range kickClients {
kickTokens = append(kickTokens, c.token)
err := c.KickOnlineMessage()
if err != nil {
log.ZWarn(c.ctx, "KickOnlineMessage", err)
}
}
ctx := mcontext.WithMustInfoCtx(
[]string{newClient.ctx.GetOperationID(), newClient.ctx.GetUserID(),
constant.PlatformIDToName(newClient.PlatformID), newClient.ctx.GetConnID()},
)
if _, err := ws.authClient.KickTokens(ctx, kickTokens); err != nil {
log.ZWarn(newClient.ctx, "kickTokens err", err)
}
}
switch ws.msgGatewayConfig.Share.MultiLogin.Policy {
case constant.DefalutNotKick: case constant.DefalutNotKick:
case constant.WebAndOther:
if constant.PlatformIDToClass(newClient.PlatformID) == constant.WebPlatformStr {
return
}
fallthrough
case constant.PCAndOther: case constant.PCAndOther:
if constant.PlatformIDToClass(newClient.PlatformID) == constant.TerminalPC { if constant.PlatformIDToClass(newClient.PlatformID) == constant.TerminalPC {
return return
@ -347,6 +356,35 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
log.ZWarn(newClient.ctx, "InvalidateToken err", err, "userID", newClient.UserID, log.ZWarn(newClient.ctx, "InvalidateToken err", err, "userID", newClient.UserID,
"platformID", newClient.PlatformID) "platformID", newClient.PlatformID)
} }
case constant.PcMobileAndWeb:
clients, ok := ws.clients.GetAll(newClient.UserID)
if !ok {
return
}
var (
kickClients []*Client
)
for _, client := range clients {
if constant.PlatformIDToClass(client.PlatformID) == constant.PlatformIDToClass(newClient.PlatformID) {
kickClients = append(kickClients, client)
}
}
kickTokenFunc(kickClients)
case constant.SingleTerminalLogin:
clients, ok := ws.clients.GetAll(newClient.UserID)
if !ok {
return
}
var (
kickClients []*Client
)
for _, client := range clients {
kickClients = append(kickClients, client)
}
kickTokenFunc(kickClients)
case constant.Customize:
// todo
} }
} }

View File

@ -23,10 +23,13 @@ import (
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext"
"strings"
) )
const ( const (
geTUI = "geTui" geTUI = "getui"
firebase = "fcm" firebase = "fcm"
jPush = "jpush" jPush = "jpush"
) )
@ -38,6 +41,7 @@ type OfflinePusher interface {
func NewOfflinePusher(pushConf *config.Push, cache cache.ThirdCache, fcmConfigPath string) (OfflinePusher, error) { func NewOfflinePusher(pushConf *config.Push, cache cache.ThirdCache, fcmConfigPath string) (OfflinePusher, error) {
var offlinePusher OfflinePusher var offlinePusher OfflinePusher
pushConf.Enable = strings.ToLower(pushConf.Enable)
switch pushConf.Enable { switch pushConf.Enable {
case geTUI: case geTUI:
offlinePusher = getui.NewClient(pushConf, cache) offlinePusher = getui.NewClient(pushConf, cache)
@ -47,6 +51,7 @@ func NewOfflinePusher(pushConf *config.Push, cache cache.ThirdCache, fcmConfigPa
offlinePusher = jpush.NewClient(pushConf) offlinePusher = jpush.NewClient(pushConf)
default: default:
offlinePusher = dummy.NewClient() offlinePusher = dummy.NewClient()
log.ZWarn(mcontext.WithMustInfoCtx([]string{"push start", "admin", "admin", ""}), "Unknown push config", nil)
} }
return offlinePusher, nil return offlinePusher, nil
} }

View File

@ -55,6 +55,9 @@ func (o *OfflinePushConsumerHandler) handleMsg2OfflinePush(ctx context.Context,
log.ZError(ctx, "offline push msg is empty", errs.New("offlinePushMsg is empty"), "userIDs", offlinePushMsg.UserIDs, "msg", offlinePushMsg.MsgData) log.ZError(ctx, "offline push msg is empty", errs.New("offlinePushMsg is empty"), "userIDs", offlinePushMsg.UserIDs, "msg", offlinePushMsg.MsgData)
return return
} }
if offlinePushMsg.MsgData.Status == constant.MsgStatusSending {
offlinePushMsg.MsgData.Status = constant.MsgStatusSendSuccess
}
log.ZInfo(ctx, "receive to OfflinePush MQ", "userIDs", offlinePushMsg.UserIDs, "msg", offlinePushMsg.MsgData) log.ZInfo(ctx, "receive to OfflinePush MQ", "userIDs", offlinePushMsg.UserIDs, "msg", offlinePushMsg.MsgData)
err := o.offlinePushMsg(ctx, offlinePushMsg.MsgData, offlinePushMsg.UserIDs) err := o.offlinePushMsg(ctx, offlinePushMsg.MsgData, offlinePushMsg.UserIDs)

View File

@ -194,6 +194,9 @@ func (c *ConsumerHandler) shouldPushOffline(_ context.Context, msg *sdkws.MsgDat
} }
func (c *ConsumerHandler) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) ([]*msggateway.SingleMsgToUserResults, error) { func (c *ConsumerHandler) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) ([]*msggateway.SingleMsgToUserResults, error) {
if msg != nil && msg.Status == constant.MsgStatusSending {
msg.Status = constant.MsgStatusSendSuccess
}
onlineUserIDs, offlineUserIDs, err := c.onlineCache.GetUsersOnline(ctx, pushToUserIDs) onlineUserIDs, offlineUserIDs, err := c.onlineCache.GetUsersOnline(ctx, pushToUserIDs)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -65,7 +65,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire), redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire),
config.Share.Secret, config.Share.Secret,
config.RpcConfig.TokenPolicy.Expire, config.RpcConfig.TokenPolicy.Expire,
config.Share.MultiLoginPolicy, config.Share.MultiLogin,
), ),
config: config, config: config,
}) })
@ -230,3 +230,10 @@ func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.Invalidate
} }
return &pbauth.InvalidateTokenResp{}, nil return &pbauth.InvalidateTokenResp{}, nil
} }
func (s *authServer) KickTokens(ctx context.Context, req *pbauth.KickTokensReq) (*pbauth.KickTokensResp, error) {
if err := s.authDatabase.BatchSetTokenMapByUidPid(ctx, req.Tokens); err != nil {
return nil, err
}
return &pbauth.KickTokensResp{}, nil
}

View File

@ -465,7 +465,7 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
return nil, err return nil, err
} }
if err = g.notification.MemberEnterNotification(ctx, req.GroupID, req.InvitedUserIDs...); err != nil { if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, opUserID, req.InvitedUserIDs...); err != nil {
return nil, err return nil, err
} }
return &pbgroup.InviteUserToGroupResp{}, nil return &pbgroup.InviteUserToGroupResp{}, nil
@ -1180,36 +1180,53 @@ func (g *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
if err != nil { if err != nil {
return nil, err return nil, err
} }
if group.Status == constant.GroupStatusDismissed { if group.Status == constant.GroupStatusDismissed {
return nil, servererrs.ErrDismissedAlready.Wrap() return nil, servererrs.ErrDismissedAlready.Wrap()
} }
if req.OldOwnerUserID == req.NewOwnerUserID { if req.OldOwnerUserID == req.NewOwnerUserID {
return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID == NewOwnerUserID") return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID == NewOwnerUserID")
} }
members, err := g.db.FindGroupMembers(ctx, req.GroupID, []string{req.OldOwnerUserID, req.NewOwnerUserID}) members, err := g.db.FindGroupMembers(ctx, req.GroupID, []string{req.OldOwnerUserID, req.NewOwnerUserID})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := g.PopulateGroupMember(ctx, members...); err != nil { if err := g.PopulateGroupMember(ctx, members...); err != nil {
return nil, err return nil, err
} }
memberMap := datautil.SliceToMap(members, func(e *model.GroupMember) string { return e.UserID }) memberMap := datautil.SliceToMap(members, func(e *model.GroupMember) string { return e.UserID })
if ids := datautil.Single([]string{req.OldOwnerUserID, req.NewOwnerUserID}, datautil.Keys(memberMap)); len(ids) > 0 { if ids := datautil.Single([]string{req.OldOwnerUserID, req.NewOwnerUserID}, datautil.Keys(memberMap)); len(ids) > 0 {
return nil, errs.ErrArgs.WrapMsg("user not in group " + strings.Join(ids, ",")) return nil, errs.ErrArgs.WrapMsg("user not in group " + strings.Join(ids, ","))
} }
oldOwner := memberMap[req.OldOwnerUserID] oldOwner := memberMap[req.OldOwnerUserID]
if oldOwner == nil { if oldOwner == nil {
return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID not in group " + req.NewOwnerUserID) return nil, errs.ErrArgs.WrapMsg("OldOwnerUserID not in group " + req.NewOwnerUserID)
} }
newOwner := memberMap[req.NewOwnerUserID] newOwner := memberMap[req.NewOwnerUserID]
if newOwner == nil { if newOwner == nil {
return nil, errs.ErrArgs.WrapMsg("NewOwnerUser not in group " + req.NewOwnerUserID) return nil, errs.ErrArgs.WrapMsg("NewOwnerUser not in group " + req.NewOwnerUserID)
} }
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) { if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
if !(mcontext.GetOpUserID(ctx) == oldOwner.UserID && oldOwner.RoleLevel == constant.GroupOwner) { if !(mcontext.GetOpUserID(ctx) == oldOwner.UserID && oldOwner.RoleLevel == constant.GroupOwner) {
return nil, errs.ErrNoPermission.WrapMsg("no permission transfer group owner") return nil, errs.ErrNoPermission.WrapMsg("no permission transfer group owner")
} }
} }
if newOwner.MuteEndTime != time.Unix(0, 0) {
if _, err := g.CancelMuteGroupMember(ctx, &pbgroup.CancelMuteGroupMemberReq{
GroupID: group.GroupID,
UserID: req.NewOwnerUserID}); err != nil {
return nil, err
}
}
if err := g.db.TransferGroupOwner(ctx, req.GroupID, req.OldOwnerUserID, req.NewOwnerUserID, newOwner.RoleLevel); err != nil { if err := g.db.TransferGroupOwner(ctx, req.GroupID, req.OldOwnerUserID, req.NewOwnerUserID, newOwner.RoleLevel); err != nil {
return nil, err return nil, err
} }
@ -1217,6 +1234,7 @@ func (g *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
g.webhookAfterTransferGroupOwner(ctx, &g.config.WebhooksConfig.AfterTransferGroupOwner, req) g.webhookAfterTransferGroupOwner(ctx, &g.config.WebhooksConfig.AfterTransferGroupOwner, req)
g.notification.GroupOwnerTransferredNotification(ctx, req) g.notification.GroupOwnerTransferredNotification(ctx, req)
return &pbgroup.TransferGroupOwnerResp{}, nil return &pbgroup.TransferGroupOwnerResp{}, nil
} }
@ -1425,32 +1443,38 @@ func (g *groupServer) CancelMuteGroupMember(ctx context.Context, req *pbgroup.Ca
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := g.PopulateGroupMember(ctx, member); err != nil { if err := g.PopulateGroupMember(ctx, member); err != nil {
return nil, err return nil, err
} }
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) { if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
opMember, err := g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx)) opMember, err := g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx))
if err != nil { if err != nil {
return nil, err return nil, err
} }
switch member.RoleLevel { switch member.RoleLevel {
case constant.GroupOwner: case constant.GroupOwner:
return nil, errs.ErrNoPermission.WrapMsg("set group owner mute") return nil, errs.ErrNoPermission.WrapMsg("Can not set group owner unmute")
case constant.GroupAdmin: case constant.GroupAdmin:
if opMember.RoleLevel != constant.GroupOwner { if opMember.RoleLevel != constant.GroupOwner {
return nil, errs.ErrNoPermission.WrapMsg("set group admin mute") return nil, errs.ErrNoPermission.WrapMsg("Can not set group admin unmute")
} }
case constant.GroupOrdinaryUsers: case constant.GroupOrdinaryUsers:
if !(opMember.RoleLevel == constant.GroupAdmin || opMember.RoleLevel == constant.GroupOwner) { if !(opMember.RoleLevel == constant.GroupAdmin || opMember.RoleLevel == constant.GroupOwner) {
return nil, errs.ErrNoPermission.WrapMsg("set group ordinary users mute") return nil, errs.ErrNoPermission.WrapMsg("Can not set group ordinary users unmute")
} }
} }
} }
data := UpdateGroupMemberMutedTimeMap(time.Unix(0, 0)) data := UpdateGroupMemberMutedTimeMap(time.Unix(0, 0))
if err := g.db.UpdateGroupMember(ctx, member.GroupID, member.UserID, data); err != nil { if err := g.db.UpdateGroupMember(ctx, member.GroupID, member.UserID, data); err != nil {
return nil, err return nil, err
} }
g.notification.GroupMemberCancelMutedNotification(ctx, req.GroupID, req.UserID) g.notification.GroupMemberCancelMutedNotification(ctx, req.GroupID, req.UserID)
return &pbgroup.CancelMuteGroupMemberResp{}, nil return &pbgroup.CancelMuteGroupMemberResp{}, nil
} }
@ -1526,10 +1550,23 @@ func (g *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
case 0: case 0:
if !isAppManagerUid { if !isAppManagerUid {
roleLevel := dbMembers[opUserIndex].RoleLevel roleLevel := dbMembers[opUserIndex].RoleLevel
if roleLevel != constant.GroupOwner { var (
dbSelf = &model.GroupMember{}
reqSelf *pbgroup.SetGroupMemberInfo
)
switch roleLevel { switch roleLevel {
case constant.GroupOwner:
for _, member := range dbMembers {
if member.UserID == opUserID {
dbSelf = member
break
}
}
case constant.GroupAdmin: case constant.GroupAdmin:
for _, member := range dbMembers { for _, member := range dbMembers {
if member.UserID == opUserID {
dbSelf = member
}
if member.RoleLevel == constant.GroupOwner { if member.RoleLevel == constant.GroupOwner {
return nil, errs.ErrNoPermission.WrapMsg("admin can not change group owner") return nil, errs.ErrNoPermission.WrapMsg("admin can not change group owner")
} }
@ -1539,17 +1576,36 @@ func (g *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
} }
case constant.GroupOrdinaryUsers: case constant.GroupOrdinaryUsers:
for _, member := range dbMembers { for _, member := range dbMembers {
if member.UserID == opUserID {
dbSelf = member
}
if !(member.RoleLevel == constant.GroupOrdinaryUsers && member.UserID == opUserID) { if !(member.RoleLevel == constant.GroupOrdinaryUsers && member.UserID == opUserID) {
return nil, errs.ErrNoPermission.WrapMsg("ordinary users can not change other role level") return nil, errs.ErrNoPermission.WrapMsg("ordinary users can not change other role level")
} }
} }
default: default:
for _, member := range dbMembers { for _, member := range dbMembers {
if member.UserID == opUserID {
dbSelf = member
}
if member.RoleLevel >= roleLevel { if member.RoleLevel >= roleLevel {
return nil, errs.ErrNoPermission.WrapMsg("can not change higher role level") return nil, errs.ErrNoPermission.WrapMsg("can not change higher role level")
} }
} }
} }
for _, member := range req.Members {
if member.UserID == opUserID {
reqSelf = member
break
}
}
if reqSelf != nil && reqSelf.RoleLevel != nil {
if reqSelf.RoleLevel.GetValue() > dbSelf.RoleLevel {
return nil, errs.ErrNoPermission.WrapMsg("can not improve role level by self")
}
if roleLevel == constant.GroupOwner {
return nil, errs.ErrArgs.WrapMsg("group owner can not change own role level") // Prevent the absence of a group owner
}
} }
} }
case 1: case 1:

View File

@ -38,6 +38,7 @@ import (
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/stringutil" "github.com/openimsdk/tools/utils/stringutil"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
"time"
) )
// GroupApplicationReceiver // GroupApplicationReceiver
@ -572,8 +573,51 @@ func (g *GroupNotificationSender) GroupApplicationAgreeMemberEnterNotification(c
return nil return nil
} }
func (g *GroupNotificationSender) MemberEnterNotification(ctx context.Context, groupID string, entrantUserID ...string) error { func (g *GroupNotificationSender) MemberEnterNotification(ctx context.Context, groupID string, entrantUserID string) error {
return g.GroupApplicationAgreeMemberEnterNotification(ctx, groupID, "", entrantUserID...) var err error
defer func() {
if err != nil {
log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err)
}
}()
if !g.config.RpcConfig.EnableHistoryForNewMembers {
conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, groupID)
maxSeq, err := g.msgRpcClient.GetConversationMaxSeq(ctx, conversationID)
if err != nil {
return err
}
if _, err = g.msgRpcClient.SetUserConversationsMinSeq(ctx, &msg.SetUserConversationsMinSeqReq{
UserIDs: []string{entrantUserID},
ConversationID: conversationID,
Seq: maxSeq,
}); err != nil {
return err
}
}
if err := g.conversationRpcClient.GroupChatFirstCreateConversation(ctx, groupID, []string{entrantUserID}); err != nil {
return err
}
var group *sdkws.GroupInfo
group, err = g.getGroupInfo(ctx, groupID)
if err != nil {
return err
}
user, err := g.getGroupMember(ctx, groupID, entrantUserID)
if err != nil {
return err
}
tips := &sdkws.MemberEnterTips{
Group: group,
EntrantUser: user,
OperationTime: time.Now().UnixMilli(),
}
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberEnterNotification, tips)
return nil
} }
func (g *GroupNotificationSender) GroupDismissedNotification(ctx context.Context, tips *sdkws.GroupDismissedTips) { func (g *GroupNotificationSender) GroupDismissedNotification(ctx context.Context, tips *sdkws.GroupDismissedTips) {

View File

@ -29,7 +29,6 @@ import (
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/stringutil"
) )
func (m *msgServer) SendMsg(ctx context.Context, req *pbmsg.SendMsgReq) (*pbmsg.SendMsgResp, error) { func (m *msgServer) SendMsg(ctx context.Context, req *pbmsg.SendMsgReq) (*pbmsg.SendMsgResp, error) {
@ -80,13 +79,17 @@ func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq)
func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgData) { func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgData) {
log.ZDebug(nctx, "setConversationAtInfo", "msg", msg) log.ZDebug(nctx, "setConversationAtInfo", "msg", msg)
ctx := mcontext.NewCtx("@@@" + mcontext.GetOperationID(nctx)) ctx := mcontext.NewCtx("@@@" + mcontext.GetOperationID(nctx))
var atUserID []string var atUserID []string
conversation := &pbconversation.ConversationReq{ conversation := &pbconversation.ConversationReq{
ConversationID: msgprocessor.GetConversationIDByMsg(msg), ConversationID: msgprocessor.GetConversationIDByMsg(msg),
ConversationType: msg.SessionType, ConversationType: msg.SessionType,
GroupID: msg.GroupID, GroupID: msg.GroupID,
} }
tagAll := datautil.Contain(constant.AtAllString, msg.AtUserIDList...) tagAll := datautil.Contain(constant.AtAllString, msg.AtUserIDList...)
if tagAll { if tagAll {
memberUserIDList, err := m.GroupLocalCache.GetGroupMemberIDs(ctx, msg.GroupID) memberUserIDList, err := m.GroupLocalCache.GetGroupMemberIDs(ctx, msg.GroupID)
@ -94,25 +97,35 @@ func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgDa
log.ZWarn(ctx, "GetGroupMemberIDs", err) log.ZWarn(ctx, "GetGroupMemberIDs", err)
return return
} }
atUserID = stringutil.DifferenceString([]string{constant.AtAllString}, msg.AtUserIDList)
memberUserIDList = datautil.DeleteElems(memberUserIDList, msg.SendID)
atUserID = datautil.Single([]string{constant.AtAllString}, msg.AtUserIDList)
if len(atUserID) == 0 { // just @everyone if len(atUserID) == 0 { // just @everyone
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll} conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll}
} else { // @Everyone and @other people } else { // @Everyone and @other people
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAllAtMe} conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAllAtMe}
err = m.Conversation.SetConversations(ctx, atUserID, conversation) err = m.Conversation.SetConversations(ctx, atUserID, conversation)
if err != nil { if err != nil {
log.ZWarn(ctx, "SetConversations", err, "userID", atUserID, "conversation", conversation) log.ZWarn(ctx, "SetConversations", err, "userID", atUserID, "conversation", conversation)
} }
memberUserIDList = stringutil.DifferenceString(atUserID, memberUserIDList)
memberUserIDList = datautil.Single(atUserID, memberUserIDList)
} }
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll} conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtAll}
err = m.Conversation.SetConversations(ctx, memberUserIDList, conversation) err = m.Conversation.SetConversations(ctx, memberUserIDList, conversation)
if err != nil { if err != nil {
log.ZWarn(ctx, "SetConversations", err, "userID", memberUserIDList, "conversation", conversation) log.ZWarn(ctx, "SetConversations", err, "userID", memberUserIDList, "conversation", conversation)
} }
return return
} }
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtMe} conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.AtMe}
err := m.Conversation.SetConversations(ctx, msg.AtUserIDList, conversation) err := m.Conversation.SetConversations(ctx, msg.AtUserIDList, conversation)
if err != nil { if err != nil {
log.ZWarn(ctx, "SetConversations", err, msg.AtUserIDList, conversation) log.ZWarn(ctx, "SetConversations", err, msg.AtUserIDList, conversation)

View File

@ -0,0 +1,117 @@
package third
import (
"context"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/protocol/third"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/datautil"
"github.com/redis/go-redis/v9"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"time"
)
func IsNotFound(err error) bool {
switch errs.Unwrap(err) {
case redis.Nil, mongo.ErrNoDocuments:
return true
default:
return false
}
}
func (t *thirdServer) db2pbApplication(val *model.Application) *third.ApplicationVersion {
return &third.ApplicationVersion{
Id: val.ID.Hex(),
Platform: val.Platform,
Version: val.Version,
Url: val.Url,
Text: val.Text,
Force: val.Force,
Latest: val.Latest,
CreateTime: val.CreateTime.UnixMilli(),
}
}
func (t *thirdServer) LatestApplicationVersion(ctx context.Context, req *third.LatestApplicationVersionReq) (*third.LatestApplicationVersionResp, error) {
res, err := t.applicationDatabase.LatestVersion(ctx, req.Platform)
if err == nil {
return &third.LatestApplicationVersionResp{Version: t.db2pbApplication(res)}, nil
} else if IsNotFound(err) {
return &third.LatestApplicationVersionResp{}, nil
} else {
return nil, err
}
}
func (t *thirdServer) AddApplicationVersion(ctx context.Context, req *third.AddApplicationVersionReq) (*third.AddApplicationVersionResp, error) {
if err := authverify.CheckAdmin(ctx, t.config.Share.IMAdminUserID); err != nil {
return nil, err
}
val := &model.Application{
ID: primitive.NewObjectID(),
Platform: req.Platform,
Version: req.Version,
Url: req.Url,
Text: req.Text,
Force: req.Force,
Latest: req.Latest,
CreateTime: time.Now(),
}
if err := t.applicationDatabase.AddVersion(ctx, val); err != nil {
return nil, err
}
return &third.AddApplicationVersionResp{}, nil
}
func (t *thirdServer) UpdateApplicationVersion(ctx context.Context, req *third.UpdateApplicationVersionReq) (*third.UpdateApplicationVersionResp, error) {
if err := authverify.CheckAdmin(ctx, t.config.Share.IMAdminUserID); err != nil {
return nil, err
}
oid, err := primitive.ObjectIDFromHex(req.Id)
if err != nil {
return nil, errs.ErrArgs.WrapMsg("invalid id " + err.Error())
}
update := make(map[string]any)
putUpdate(update, "platform", req.Platform)
putUpdate(update, "version", req.Version)
putUpdate(update, "url", req.Url)
putUpdate(update, "text", req.Text)
putUpdate(update, "force", req.Force)
putUpdate(update, "latest", req.Latest)
if err := t.applicationDatabase.UpdateVersion(ctx, oid, update); err != nil {
return nil, err
}
return &third.UpdateApplicationVersionResp{}, nil
}
func (t *thirdServer) DeleteApplicationVersion(ctx context.Context, req *third.DeleteApplicationVersionReq) (*third.DeleteApplicationVersionResp, error) {
if err := authverify.CheckAdmin(ctx, t.config.Share.IMAdminUserID); err != nil {
return nil, err
}
ids := make([]primitive.ObjectID, 0, len(req.Id))
for _, id := range req.Id {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, errs.ErrArgs.WrapMsg("invalid id " + err.Error())
}
ids = append(ids, oid)
}
if err := t.applicationDatabase.DeleteVersion(ctx, ids); err != nil {
return nil, err
}
return &third.DeleteApplicationVersionResp{}, nil
}
func (t *thirdServer) PageApplicationVersion(ctx context.Context, req *third.PageApplicationVersionReq) (*third.PageApplicationVersionResp, error) {
total, res, err := t.applicationDatabase.PageVersion(ctx, req.Platform, req.Pagination)
if err != nil {
return nil, err
}
return &third.PageApplicationVersionResp{
Total: total,
Versions: datautil.Slice(res, t.db2pbApplication),
}, nil
}

View File

@ -44,6 +44,7 @@ type thirdServer struct {
defaultExpire time.Duration defaultExpire time.Duration
config *Config config *Config
minio *minio.Minio minio *minio.Minio
applicationDatabase controller.ApplicationDatabase
} }
type Config struct { type Config struct {
@ -74,6 +75,11 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
if err != nil { if err != nil {
return err return err
} }
applicationMgo, err := mgo.NewApplicationMgo(mgocli.GetDB())
if err != nil {
return err
}
// Select the oss method according to the profile policy // Select the oss method according to the profile policy
enable := config.RpcConfig.Object.Enable enable := config.RpcConfig.Object.Enable
var ( var (
@ -104,6 +110,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
defaultExpire: time.Hour * 24 * 7, defaultExpire: time.Hour * 24 * 7,
config: config, config: config,
minio: minioCli, minio: minioCli,
applicationDatabase: controller.NewApplicationDatabase(applicationMgo, redis.NewApplicationRedisCache(applicationMgo, rdb)),
}) })
return nil return nil
} }

View File

@ -82,3 +82,11 @@ func checkValidObjectName(objectName string) error {
func (t *thirdServer) IsManagerUserID(opUserID string) bool { func (t *thirdServer) IsManagerUserID(opUserID string) bool {
return authverify.IsManagerUserID(opUserID, t.config.Share.IMAdminUserID) return authverify.IsManagerUserID(opUserID, t.config.Share.IMAdminUserID)
} }
func putUpdate[T any](update map[string]any, name string, val interface{ GetValuePtr() *T }) {
ptrVal := val.GetValuePtr()
if ptrVal == nil {
return
}
update[name] = *ptrVal
}

View File

@ -116,18 +116,17 @@ func Start(ctx context.Context, config *Config, client registry.SvcDiscoveryRegi
func (s *userServer) GetDesignateUsers(ctx context.Context, req *pbuser.GetDesignateUsersReq) (resp *pbuser.GetDesignateUsersResp, err error) { func (s *userServer) GetDesignateUsers(ctx context.Context, req *pbuser.GetDesignateUsersReq) (resp *pbuser.GetDesignateUsersResp, err error) {
resp = &pbuser.GetDesignateUsersResp{} resp = &pbuser.GetDesignateUsersResp{}
users, err := s.db.FindWithError(ctx, req.UserIDs) users, err := s.db.Find(ctx, req.UserIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp.UsersInfo = convert.UsersDB2Pb(users) resp.UsersInfo = convert.UsersDB2Pb(users)
return resp, nil return resp, nil
} }
// deprecated: // deprecated:
// UpdateUserInfo
//UpdateUserInfo
func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInfoReq) (resp *pbuser.UpdateUserInfoResp, err error) { func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInfoReq) (resp *pbuser.UpdateUserInfoResp, err error) {
resp = &pbuser.UpdateUserInfoResp{} resp = &pbuser.UpdateUserInfoResp{}
err = authverify.CheckAccessV3(ctx, req.UserInfo.UserID, s.config.Share.IMAdminUserID) err = authverify.CheckAccessV3(ctx, req.UserInfo.UserID, s.config.Share.IMAdminUserID)

View File

@ -364,8 +364,26 @@ type Share struct {
Secret string `mapstructure:"secret"` Secret string `mapstructure:"secret"`
RpcRegisterName RpcRegisterName `mapstructure:"rpcRegisterName"` RpcRegisterName RpcRegisterName `mapstructure:"rpcRegisterName"`
IMAdminUserID []string `mapstructure:"imAdminUserID"` IMAdminUserID []string `mapstructure:"imAdminUserID"`
MultiLoginPolicy int `mapstructure:"multiLoginPolicy"` MultiLogin MultiLogin `mapstructure:"multiLogin"`
} }
type MultiLogin struct {
Policy int `mapstructure:"policy"`
MaxNumOneEnd int `mapstructure:"maxNumOneEnd"`
CustomizeLoginNum struct {
IOS int `mapstructure:"ios"`
Android int `mapstructure:"android"`
Windows int `mapstructure:"windows"`
OSX int `mapstructure:"osx"`
Web int `mapstructure:"web"`
MiniWeb int `mapstructure:"miniWeb"`
Linux int `mapstructure:"linux"`
APad int `mapstructure:"aPad"`
IPad int `mapstructure:"iPad"`
Admin int `mapstructure:"admin"`
} `mapstructure:"customizeLoginNum"`
}
type RpcRegisterName struct { type RpcRegisterName struct {
User string `mapstructure:"user"` User string `mapstructure:"user"`
Friend string `mapstructure:"friend"` Friend string `mapstructure:"friend"`

11
pkg/common/storage/cache/application.go vendored Normal file
View File

@ -0,0 +1,11 @@
package cache
import (
"context"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
)
type ApplicationCache interface {
LatestVersion(ctx context.Context, platform string) (*model.Application, error)
DeleteCache(ctx context.Context, platforms []string) error
}

View File

@ -0,0 +1,9 @@
package cachekey
const (
ApplicationLatestVersion = "APPLICATION_LATEST_VERSION:"
)
func GetApplicationLatestVersionKey(platform string) string {
return ApplicationLatestVersion + platform
}

View File

@ -1,6 +1,9 @@
package cachekey package cachekey
import "github.com/openimsdk/protocol/constant" import (
"github.com/openimsdk/protocol/constant"
"strings"
)
const ( const (
UidPidToken = "UID_PID_TOKEN_STATUS:" UidPidToken = "UID_PID_TOKEN_STATUS:"
@ -9,3 +12,17 @@ const (
func GetTokenKey(userID string, platformID int) string { func GetTokenKey(userID string, platformID int) string {
return UidPidToken + userID + ":" + constant.PlatformIDToName(platformID) return UidPidToken + userID + ":" + constant.PlatformIDToName(platformID)
} }
func GetAllPlatformTokenKey(userID string) []string {
res := make([]string, len(constant.PlatformID2Name))
for k := range constant.PlatformID2Name {
res[k-1] = GetTokenKey(userID, k)
}
return res
}
func GetPlatformIDByTokenKey(key string) int {
splitKey := strings.Split(key, ":")
platform := splitKey[len(splitKey)-1]
return constant.PlatformNameToID(platform)
}

View File

@ -0,0 +1,43 @@
package redis
import (
"context"
"github.com/dtm-labs/rockscache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/tools/utils/datautil"
"github.com/redis/go-redis/v9"
"time"
)
func NewApplicationRedisCache(db database.Application, rdb redis.UniversalClient) *ApplicationRedisCache {
return &ApplicationRedisCache{
db: db,
rcClient: rockscache.NewClient(rdb, *GetRocksCacheOptions()),
deleter: NewBatchDeleterRedis(rdb, GetRocksCacheOptions(), nil),
expireTime: time.Hour * 24 * 7,
}
}
type ApplicationRedisCache struct {
db database.Application
rcClient *rockscache.Client
deleter *BatchDeleterRedis
expireTime time.Duration
}
func (a *ApplicationRedisCache) LatestVersion(ctx context.Context, platform string) (*model.Application, error) {
return getCache(ctx, a.rcClient, cachekey.GetApplicationLatestVersionKey(platform), a.expireTime, func(ctx context.Context) (*model.Application, error) {
return a.db.LatestVersion(ctx, platform)
})
}
func (a *ApplicationRedisCache) DeleteCache(ctx context.Context, platforms []string) error {
if len(platforms) == 0 {
return nil
}
return a.deleter.ExecDelWithKeys(ctx, datautil.Slice(platforms, func(platform string) string {
return cachekey.GetApplicationLatestVersionKey(platform)
}))
}

View File

@ -1,17 +1,3 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package redis package redis
import ( import (
@ -21,6 +7,7 @@ import (
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"strconv" "strconv"
"sync"
"time" "time"
) )
@ -67,6 +54,43 @@ func (c *tokenCache) GetTokensWithoutError(ctx context.Context, userID string, p
return mm, nil return mm, nil
} }
func (c *tokenCache) GetAllTokensWithoutError(ctx context.Context, userID string) (map[int]map[string]int, error) {
var (
res = make(map[int]map[string]int)
resLock = sync.Mutex{}
)
keys := cachekey.GetAllPlatformTokenKey(userID)
if err := ProcessKeysBySlot(ctx, c.rdb, keys, func(ctx context.Context, slot int64, keys []string) error {
pipe := c.rdb.Pipeline()
mapRes := make([]*redis.MapStringStringCmd, len(keys))
for i, key := range keys {
mapRes[i] = pipe.HGetAll(ctx, key)
}
_, err := pipe.Exec(ctx)
if err != nil {
return err
}
for i, m := range mapRes {
mm := make(map[string]int)
for k, v := range m.Val() {
state, err := strconv.Atoi(v)
if err != nil {
return errs.WrapMsg(err, "redis token value is not int", "value", v, "userID", userID)
}
mm[k] = state
}
resLock.Lock()
res[cachekey.GetPlatformIDByTokenKey(keys[i])] = mm
resLock.Unlock()
}
return nil
}); err != nil {
return nil, err
}
return res, nil
}
func (c *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error { func (c *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error {
mm := make(map[string]any) mm := make(map[string]any)
for k, v := range m { for k, v := range m {
@ -75,6 +99,18 @@ func (c *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, pla
return errs.Wrap(c.rdb.HSet(ctx, cachekey.GetTokenKey(userID, platformID), mm).Err()) return errs.Wrap(c.rdb.HSet(ctx, cachekey.GetTokenKey(userID, platformID), mm).Err())
} }
func (c *tokenCache) BatchSetTokenMapByUidPid(ctx context.Context, tokens map[string]map[string]int) error {
pipe := c.rdb.Pipeline()
for k, v := range tokens {
pipe.HSet(ctx, k, v)
}
_, err := pipe.Exec(ctx)
if err != nil {
return errs.Wrap(err)
}
return nil
}
func (c *tokenCache) DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error { func (c *tokenCache) DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error {
return errs.Wrap(c.rdb.HDel(ctx, cachekey.GetTokenKey(userID, platformID), fields...).Err()) return errs.Wrap(c.rdb.HDel(ctx, cachekey.GetTokenKey(userID, platformID), fields...).Err())
} }

View File

@ -9,6 +9,8 @@ type TokenModel interface {
// SetTokenFlagEx set token and flag with expire time // SetTokenFlagEx set token and flag with expire time
SetTokenFlagEx(ctx context.Context, userID string, platformID int, token string, flag int) error SetTokenFlagEx(ctx context.Context, userID string, platformID int, token string, flag int) error
GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error) GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error)
GetAllTokensWithoutError(ctx context.Context, userID string) (map[int]map[string]int, error)
SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error
BatchSetTokenMapByUidPid(ctx context.Context, tokens map[string]map[string]int) error
DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error
} }

View File

@ -0,0 +1,69 @@
package controller
import (
"context"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type ApplicationDatabase interface {
LatestVersion(ctx context.Context, platform string) (*model.Application, error)
AddVersion(ctx context.Context, val *model.Application) error
UpdateVersion(ctx context.Context, id primitive.ObjectID, update map[string]any) error
DeleteVersion(ctx context.Context, id []primitive.ObjectID) error
PageVersion(ctx context.Context, platforms []string, page pagination.Pagination) (int64, []*model.Application, error)
}
func NewApplicationDatabase(db database.Application, cache cache.ApplicationCache) ApplicationDatabase {
return &applicationDatabase{db: db, cache: cache}
}
type applicationDatabase struct {
db database.Application
cache cache.ApplicationCache
}
func (a *applicationDatabase) LatestVersion(ctx context.Context, platform string) (*model.Application, error) {
return a.cache.LatestVersion(ctx, platform)
}
func (a *applicationDatabase) AddVersion(ctx context.Context, val *model.Application) error {
if err := a.db.AddVersion(ctx, val); err != nil {
return err
}
return a.cache.DeleteCache(ctx, []string{val.Platform})
}
func (a *applicationDatabase) UpdateVersion(ctx context.Context, id primitive.ObjectID, update map[string]any) error {
platforms, err := a.db.FindPlatform(ctx, []primitive.ObjectID{id})
if err != nil {
return err
}
if err := a.db.UpdateVersion(ctx, id, update); err != nil {
return err
}
if p, ok := update["platform"]; ok {
if val, ok := p.(string); ok {
platforms = append(platforms, val)
}
}
return a.cache.DeleteCache(ctx, platforms)
}
func (a *applicationDatabase) DeleteVersion(ctx context.Context, id []primitive.ObjectID) error {
platforms, err := a.db.FindPlatform(ctx, id)
if err != nil {
return err
}
if err := a.db.DeleteVersion(ctx, id); err != nil {
return err
}
return a.cache.DeleteCache(ctx, platforms)
}
func (a *applicationDatabase) PageVersion(ctx context.Context, platforms []string, page pagination.Pagination) (int64, []*model.Application, error) {
return a.db.PageVersion(ctx, platforms, page)
}

View File

@ -1,26 +1,12 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package controller package controller
import ( import (
"context" "context"
"github.com/openimsdk/tools/log"
"github.com/golang-jwt/jwt/v4" "github.com/golang-jwt/jwt/v4"
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/tokenverify" "github.com/openimsdk/tools/tokenverify"
@ -32,18 +18,41 @@ type AuthDatabase interface {
// Create token // Create token
CreateToken(ctx context.Context, userID string, platformID int) (string, error) CreateToken(ctx context.Context, userID string, platformID int) (string, error)
BatchSetTokenMapByUidPid(ctx context.Context, tokens []string) error
SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error
} }
type multiLoginConfig struct {
Policy int
MaxNumOneEnd int
CustomizeLoginNum map[int]int
}
type authDatabase struct { type authDatabase struct {
cache cache.TokenModel cache cache.TokenModel
accessSecret string accessSecret string
accessExpire int64 accessExpire int64
multiLoginPolicy int multiLogin multiLoginConfig
} }
func NewAuthDatabase(cache cache.TokenModel, accessSecret string, accessExpire int64, policy int) AuthDatabase { func NewAuthDatabase(cache cache.TokenModel, accessSecret string, accessExpire int64, multiLogin config.MultiLogin) AuthDatabase {
return &authDatabase{cache: cache, accessSecret: accessSecret, accessExpire: accessExpire, multiLoginPolicy: policy} return &authDatabase{cache: cache, accessSecret: accessSecret, accessExpire: accessExpire, multiLogin: multiLoginConfig{
Policy: multiLogin.Policy,
MaxNumOneEnd: multiLogin.MaxNumOneEnd,
CustomizeLoginNum: map[int]int{
constant.IOSPlatformID: multiLogin.CustomizeLoginNum.IOS,
constant.AndroidPlatformID: multiLogin.CustomizeLoginNum.Android,
constant.WindowsPlatformID: multiLogin.CustomizeLoginNum.Windows,
constant.OSXPlatformID: multiLogin.CustomizeLoginNum.OSX,
constant.WebPlatformID: multiLogin.CustomizeLoginNum.Web,
constant.MiniWebPlatformID: multiLogin.CustomizeLoginNum.MiniWeb,
constant.LinuxPlatformID: multiLogin.CustomizeLoginNum.Linux,
constant.AndroidPadPlatformID: multiLogin.CustomizeLoginNum.APad,
constant.IPadPlatformID: multiLogin.CustomizeLoginNum.IPad,
constant.AdminPlatformID: multiLogin.CustomizeLoginNum.Admin,
},
}}
} }
// If the result is empty. // If the result is empty.
@ -55,22 +64,38 @@ func (a *authDatabase) SetTokenMapByUidPid(ctx context.Context, userID string, p
return a.cache.SetTokenMapByUidPid(ctx, userID, platformID, m) return a.cache.SetTokenMapByUidPid(ctx, userID, platformID, m)
} }
func (a *authDatabase) BatchSetTokenMapByUidPid(ctx context.Context, tokens []string) error {
setMap := make(map[string]map[string]int)
for _, token := range tokens {
claims, err := tokenverify.GetClaimFromToken(token, authverify.Secret(a.accessSecret))
key := cachekey.GetTokenKey(claims.UserID, claims.PlatformID)
if err != nil {
continue
} else {
if v, ok := setMap[key]; ok {
v[token] = constant.KickedToken
} else {
setMap[key] = map[string]int{
token: constant.KickedToken,
}
}
}
}
if err := a.cache.BatchSetTokenMapByUidPid(ctx, setMap); err != nil {
return err
}
return nil
}
// Create Token. // Create Token.
func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformID int) (string, error) { func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformID int) (string, error) {
// todo: get all platform token tokens, err := a.cache.GetAllTokensWithoutError(ctx, userID)
tokens, err := a.cache.GetTokensWithoutError(ctx, userID, platformID)
if err != nil { if err != nil {
return "", err return "", err
} }
var deleteTokenKey []string deleteTokenKey, kickedTokenKey, err := a.checkToken(ctx, tokens, platformID)
var kickedTokenKey []string if err != nil {
for k, v := range tokens { return "", err
t, err := tokenverify.GetClaimFromToken(k, authverify.Secret(a.accessSecret))
if err != nil || v != constant.NormalToken {
deleteTokenKey = append(deleteTokenKey, k)
} else if a.checkKickToken(ctx, platformID, t) {
kickedTokenKey = append(kickedTokenKey, k)
}
} }
if len(deleteTokenKey) != 0 { if len(deleteTokenKey) != 0 {
err = a.cache.DeleteTokenByUidPid(ctx, userID, platformID, deleteTokenKey) err = a.cache.DeleteTokenByUidPid(ctx, userID, platformID, deleteTokenKey)
@ -78,16 +103,6 @@ func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformI
return "", err 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 { if len(kickedTokenKey) != 0 {
for _, k := range kickedTokenKey { for _, k := range kickedTokenKey {
err := a.cache.SetTokenFlagEx(ctx, userID, platformID, k, constant.KickedToken) err := a.cache.SetTokenFlagEx(ctx, userID, platformID, k, constant.KickedToken)
@ -111,22 +126,140 @@ func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformI
return tokenString, nil return tokenString, nil
} }
func (a *authDatabase) checkKickToken(ctx context.Context, platformID int, token *tokenverify.Claims) bool { func (a *authDatabase) checkToken(ctx context.Context, tokens map[int]map[string]int, platformID int) ([]string, []string, error) {
switch a.multiLoginPolicy { // todo: Move the logic for handling old data to another location.
var (
loginTokenMap = make(map[int][]string) // The length of the value of the map must be greater than 0
deleteToken = make([]string, 0)
kickToken = make([]string, 0)
adminToken = make([]string, 0)
unkickTerminal = ""
)
for plfID, tks := range tokens {
for k, v := range tks {
_, err := tokenverify.GetClaimFromToken(k, authverify.Secret(a.accessSecret))
if err != nil || v != constant.NormalToken {
deleteToken = append(deleteToken, k)
} else {
if plfID != constant.AdminPlatformID {
loginTokenMap[plfID] = append(loginTokenMap[plfID], k)
} else {
adminToken = append(adminToken, k)
}
}
}
}
switch a.multiLogin.Policy {
case constant.DefalutNotKick: case constant.DefalutNotKick:
return false for plt, ts := range loginTokenMap {
case constant.PCAndOther: l := len(ts)
if constant.PlatformIDToClass(platformID) == constant.TerminalPC || if platformID == plt {
constant.PlatformIDToClass(token.PlatformID) == constant.TerminalPC { l++
return false }
limit := a.multiLogin.MaxNumOneEnd
if l > limit {
kickToken = append(kickToken, ts[:l-limit]...)
}
} }
return true
case constant.AllLoginButSameTermKick: case constant.AllLoginButSameTermKick:
if platformID == token.PlatformID { for plt, ts := range loginTokenMap {
return true kickToken = append(kickToken, ts[:len(ts)-1]...)
if plt == platformID {
kickToken = append(kickToken, ts[len(ts)-1])
}
}
case constant.SingleTerminalLogin:
for _, ts := range loginTokenMap {
kickToken = append(kickToken, ts...)
}
case constant.WebAndOther:
unkickTerminal = constant.WebPlatformStr
fallthrough
case constant.PCAndOther:
if unkickTerminal == "" {
unkickTerminal = constant.TerminalPC
}
if constant.PlatformIDToClass(platformID) != unkickTerminal {
for plt, ts := range loginTokenMap {
if constant.PlatformIDToClass(plt) != unkickTerminal {
kickToken = append(kickToken, ts...)
}
}
} else {
var (
preKick []string
isReserve = true
)
for plt, ts := range loginTokenMap {
if constant.PlatformIDToClass(plt) != unkickTerminal {
// Keep a token from another end
if isReserve {
isReserve = false
kickToken = append(kickToken, ts[:len(ts)-1]...)
preKick = append(preKick, ts[len(ts)-1])
continue
} else {
// Prioritize keeping Android
if plt == constant.AndroidPlatformID {
kickToken = append(kickToken, preKick...)
kickToken = append(kickToken, ts[:len(ts)-1]...)
} else {
kickToken = append(kickToken, ts...)
}
}
}
}
}
case constant.PcMobileAndWeb:
var (
reserved = make(map[string]bool)
)
for plt, ts := range loginTokenMap {
if constant.PlatformIDToClass(plt) == constant.PlatformIDToClass(platformID) {
kickToken = append(kickToken, ts...)
} else {
if !reserved[constant.PlatformIDToClass(plt)] {
reserved[constant.PlatformIDToClass(plt)] = true
kickToken = append(kickToken, ts[:len(ts)-1]...)
continue
} else {
kickToken = append(kickToken, ts...)
}
}
}
case constant.Customize:
if a.multiLogin.CustomizeLoginNum[platformID] <= 0 {
return nil, nil, errs.New("Do not allow login on this end").Wrap()
}
for plt, ts := range loginTokenMap {
l := len(ts)
if platformID == plt {
l++
}
// a.multiLogin.CustomizeLoginNum[platformID] must > 0
limit := min(a.multiLogin.CustomizeLoginNum[plt], a.multiLogin.MaxNumOneEnd)
if l > limit {
kickToken = append(kickToken, ts[:l-limit]...)
}
} }
return false
default: default:
return false return nil, nil, errs.New("unknown multiLogin policy").Wrap()
} }
var adminTokenMaxNum = a.multiLogin.MaxNumOneEnd
if a.multiLogin.Policy == constant.Customize {
adminTokenMaxNum = a.multiLogin.CustomizeLoginNum[constant.AdminPlatformID]
}
l := len(adminToken)
if platformID == constant.AdminPlatformID {
l++
}
if l > adminTokenMaxNum {
kickToken = append(kickToken, adminToken[:l-adminTokenMaxNum]...)
}
return deleteToken, kickToken, nil
} }

View File

@ -16,9 +16,10 @@ package controller
import ( import (
"context" "context"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
@ -194,7 +195,7 @@ func (c *conversationDatabase) SyncPeerUserPrivateConversationTx(ctx context.Con
return c.tx.Transaction(ctx, func(ctx context.Context) error { return c.tx.Transaction(ctx, func(ctx context.Context) error {
cache := c.cache.CloneConversationCache() cache := c.cache.CloneConversationCache()
for _, conversation := range conversations { for _, conversation := range conversations {
cache = cache.DelConversationVersionUserIDs(conversation.OwnerUserID) cache = cache.DelConversationVersionUserIDs(conversation.OwnerUserID, conversation.UserID)
for _, v := range [][2]string{{conversation.OwnerUserID, conversation.UserID}, {conversation.UserID, conversation.OwnerUserID}} { for _, v := range [][2]string{{conversation.OwnerUserID, conversation.UserID}, {conversation.UserID, conversation.OwnerUserID}} {
ownerUserID := v[0] ownerUserID := v[0]
userID := v[1] userID := v[1]

View File

@ -2,6 +2,7 @@ package controller
import ( import (
"context" "context"
"github.com/openimsdk/protocol/constant"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
@ -83,6 +84,9 @@ func (db *msgTransferDatabase) BatchInsertChat2DB(ctx context.Context, conversat
IOSBadgeCount: msg.OfflinePushInfo.IOSBadgeCount, IOSBadgeCount: msg.OfflinePushInfo.IOSBadgeCount,
} }
} }
if msg.Status == constant.MsgStatusSending {
msg.Status = constant.MsgStatusSendSuccess
}
msgs[i] = &model.MsgDataModel{ msgs[i] = &model.MsgDataModel{
SendID: msg.SendID, SendID: msg.SendID,
RecvID: msg.RecvID, RecvID: msg.RecvID,

View File

@ -0,0 +1,17 @@
package database
import (
"context"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type Application interface {
LatestVersion(ctx context.Context, platform string) (*model.Application, error)
AddVersion(ctx context.Context, val *model.Application) error
UpdateVersion(ctx context.Context, id primitive.ObjectID, update map[string]any) error
DeleteVersion(ctx context.Context, id []primitive.ObjectID) error
PageVersion(ctx context.Context, platforms []string, page pagination.Pagination) (int64, []*model.Application, error)
FindPlatform(ctx context.Context, id []primitive.ObjectID) ([]string, error)
}

View File

@ -0,0 +1,82 @@
package mgo
import (
"context"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/pagination"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func NewApplicationMgo(db *mongo.Database) (*ApplicationMgo, error) {
coll := db.Collection("application")
_, err := coll.Indexes().CreateMany(context.Background(), []mongo.IndexModel{
{
Keys: bson.D{
{Key: "platform", Value: 1},
{Key: "version", Value: 1},
},
Options: options.Index().SetUnique(true),
},
{
Keys: bson.D{
{Key: "latest", Value: -1},
},
},
})
if err != nil {
return nil, err
}
return &ApplicationMgo{coll: coll}, nil
}
type ApplicationMgo struct {
coll *mongo.Collection
}
func (a *ApplicationMgo) sort() any {
return bson.D{{"latest", -1}, {"_id", -1}}
}
func (a *ApplicationMgo) LatestVersion(ctx context.Context, platform string) (*model.Application, error) {
return mongoutil.FindOne[*model.Application](ctx, a.coll, bson.M{"platform": platform}, options.FindOne().SetSort(a.sort()))
}
func (a *ApplicationMgo) AddVersion(ctx context.Context, val *model.Application) error {
if val.ID.IsZero() {
val.ID = primitive.NewObjectID()
}
return mongoutil.InsertMany(ctx, a.coll, []*model.Application{val})
}
func (a *ApplicationMgo) UpdateVersion(ctx context.Context, id primitive.ObjectID, update map[string]any) error {
if len(update) == 0 {
return nil
}
return mongoutil.UpdateOne(ctx, a.coll, bson.M{"_id": id}, bson.M{"$set": update}, true)
}
func (a *ApplicationMgo) DeleteVersion(ctx context.Context, id []primitive.ObjectID) error {
if len(id) == 0 {
return nil
}
return mongoutil.DeleteMany(ctx, a.coll, bson.M{"_id": bson.M{"$in": id}})
}
func (a *ApplicationMgo) PageVersion(ctx context.Context, platforms []string, page pagination.Pagination) (int64, []*model.Application, error) {
filter := bson.M{}
if len(platforms) > 0 {
filter["platform"] = bson.M{"$in": platforms}
}
return mongoutil.FindPage[*model.Application](ctx, a.coll, filter, page, options.Find().SetSort(a.sort()))
}
func (a *ApplicationMgo) FindPlatform(ctx context.Context, id []primitive.ObjectID) ([]string, error) {
if len(id) == 0 {
return nil, nil
}
return mongoutil.Find[string](ctx, a.coll, bson.M{"_id": bson.M{"$in": id}}, options.Find().SetProjection(bson.M{"_id": 0, "platform": 1}))
}

View File

@ -0,0 +1,17 @@
package model
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"time"
)
type Application struct {
ID primitive.ObjectID `bson:"_id"`
Platform string `bson:"platform"`
Version string `bson:"version"`
Url string `bson:"url"`
Text string `bson:"text"`
Force bool `bson:"force"`
Latest bool `bson:"latest"`
CreateTime time.Time `bson:"create_time"`
}

View File

@ -16,6 +16,7 @@ package rpccache
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
"github.com/openimsdk/open-im-server/v3/pkg/localcache" "github.com/openimsdk/open-im-server/v3/pkg/localcache"
@ -97,6 +98,7 @@ func (u *UserLocalCache) GetUsersInfo(ctx context.Context, userIDs []string) ([]
user, err := u.GetUserInfo(ctx, userID) user, err := u.GetUserInfo(ctx, userID)
if err != nil { if err != nil {
if errs.ErrRecordNotFound.Is(err) { if errs.ErrRecordNotFound.Is(err) {
log.ZWarn(ctx, "User info notFound", err, "userID", userID)
continue continue
} }
return nil, err return nil, err

View File

@ -16,8 +16,8 @@ package rpcclient
import ( import (
"context" "context"
"github.com/openimsdk/protocol/auth" "github.com/openimsdk/protocol/auth"
pbAuth "github.com/openimsdk/protocol/auth"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/system/program" "github.com/openimsdk/tools/system/program"
"google.golang.org/grpc" "google.golang.org/grpc"
@ -38,8 +38,8 @@ type Auth struct {
discov discovery.SvcDiscoveryRegistry discov discovery.SvcDiscoveryRegistry
} }
func (a *Auth) ParseToken(ctx context.Context, token string) (*pbAuth.ParseTokenResp, error) { func (a *Auth) ParseToken(ctx context.Context, token string) (*auth.ParseTokenResp, error) {
req := pbAuth.ParseTokenReq{ req := auth.ParseTokenReq{
Token: token, Token: token,
} }
resp, err := a.Client.ParseToken(ctx, &req) resp, err := a.Client.ParseToken(ctx, &req)
@ -49,8 +49,8 @@ func (a *Auth) ParseToken(ctx context.Context, token string) (*pbAuth.ParseToken
return resp, err return resp, err
} }
func (a *Auth) InvalidateToken(ctx context.Context, preservedToken, userID string, platformID int) (*pbAuth.InvalidateTokenResp, error) { func (a *Auth) InvalidateToken(ctx context.Context, preservedToken, userID string, platformID int) (*auth.InvalidateTokenResp, error) {
req := pbAuth.InvalidateTokenReq{ req := auth.InvalidateTokenReq{
PreservedToken: preservedToken, PreservedToken: preservedToken,
UserID: userID, UserID: userID,
PlatformID: int32(platformID), PlatformID: int32(platformID),
@ -61,3 +61,14 @@ func (a *Auth) InvalidateToken(ctx context.Context, preservedToken, userID strin
} }
return resp, err return resp, err
} }
func (a *Auth) KickTokens(ctx context.Context, tokens []string) (*auth.KickTokensResp, error) {
req := auth.KickTokensReq{
Tokens: tokens,
}
resp, err := a.Client.KickTokens(ctx, &req)
if err != nil {
return nil, err
}
return resp, err
}