mirror of
https://github.com/openimsdk/open-im-server.git
synced 2026-07-14 18:11:12 +08:00
Compare commits
6 Commits
256fe9df78
...
cd182ba628
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cd182ba628 | ||
|
|
dd981b976d | ||
|
|
91db18168d | ||
|
|
a0ccbf1883 | ||
|
|
d13d41f99f | ||
|
|
3651714950 |
@ -17,6 +17,8 @@ package msg
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
|
||||
@ -49,38 +51,94 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg.
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get message and add fault tolerance handling
|
||||
_, _, msgs, err := m.MsgDatabase.GetMsgBySeqs(ctx, req.UserID, req.ConversationID, []int64{req.Seq})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(msgs) == 0 || msgs[0] == nil {
|
||||
return nil, errs.ErrRecordNotFound.WrapMsg("msg not found")
|
||||
}
|
||||
if msgs[0].ContentType == constant.MsgRevokeNotification {
|
||||
return nil, servererrs.ErrMsgAlreadyRevoke.WrapMsg("msg already revoke")
|
||||
// Log the error but continue execution
|
||||
log.ZWarn(ctx, "GetMsgBySeqs error when revoking message", err,
|
||||
"userID", req.UserID, "conversationID", req.ConversationID, "seq", req.Seq)
|
||||
} else if len(msgs) == 0 || msgs[0] == nil {
|
||||
// Check if seq is within valid range for the current conversation
|
||||
maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID)
|
||||
if err != nil {
|
||||
log.ZWarn(ctx, "GetMaxSeq error when revoking message", err,
|
||||
"conversationID", req.ConversationID)
|
||||
} else if req.Seq > maxSeq {
|
||||
return nil, errs.ErrArgs.WrapMsg("seq exceeds maxSeq")
|
||||
}
|
||||
|
||||
// Log warning but continue execution
|
||||
log.ZWarn(ctx, "Message not found when revoking, but will proceed", nil,
|
||||
"userID", req.UserID, "conversationID", req.ConversationID, "seq", req.Seq)
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(msgs[0])
|
||||
// If message doesn't exist, create a minimal substitute for revocation notification
|
||||
var msgToRevoke *sdkws.MsgData
|
||||
if len(msgs) == 0 || msgs[0] == nil {
|
||||
// Create a minimal message object with necessary fields
|
||||
msgToRevoke = &sdkws.MsgData{
|
||||
SendID: req.UserID, // Use revoker as sender
|
||||
Seq: req.Seq,
|
||||
SessionType: getSessionTypeFromConversationID(req.ConversationID), // Helper function to get session type
|
||||
ClientMsgID: "missing_" + strconv.FormatInt(req.Seq, 10), // Generate a temporary ID
|
||||
SendTime: time.Now().UnixMilli() - 1000, // Set to a slightly earlier time
|
||||
}
|
||||
|
||||
// Set GroupID or RecvID based on session type
|
||||
if msgToRevoke.SessionType == constant.ReadGroupChatType {
|
||||
// Extract group ID from conversation ID
|
||||
if strings.HasPrefix(req.ConversationID, "sg_") {
|
||||
msgToRevoke.GroupID = req.ConversationID[3:] // Remove "sg_" prefix
|
||||
}
|
||||
} else {
|
||||
// For single chat, parse receiver ID from conversation ID
|
||||
if strings.HasPrefix(req.ConversationID, "si_") || strings.HasPrefix(req.ConversationID, "sp_") {
|
||||
parts := strings.Split(req.ConversationID[3:], "_")
|
||||
if len(parts) == 2 {
|
||||
// Conversation ID format is typically: si_senderID_receiverID
|
||||
// If current user is the sender, then receiver is the other party
|
||||
if parts[0] == req.UserID {
|
||||
msgToRevoke.RecvID = parts[1]
|
||||
} else {
|
||||
msgToRevoke.RecvID = parts[0]
|
||||
}
|
||||
} else {
|
||||
// Set empty if parsing fails
|
||||
msgToRevoke.RecvID = ""
|
||||
}
|
||||
} else {
|
||||
msgToRevoke.RecvID = ""
|
||||
}
|
||||
}
|
||||
} else {
|
||||
msgToRevoke = msgs[0]
|
||||
if msgToRevoke.ContentType == constant.MsgRevokeNotification {
|
||||
return nil, servererrs.ErrMsgAlreadyRevoke.WrapMsg("msg already revoke")
|
||||
}
|
||||
}
|
||||
|
||||
data, _ := json.Marshal(msgToRevoke)
|
||||
log.ZDebug(ctx, "GetMsgBySeqs", "conversationID", req.ConversationID, "seq", req.Seq, "msg", string(data))
|
||||
var role int32
|
||||
if !authverify.IsAppManagerUid(ctx, m.config.Share.IMAdminUserID) {
|
||||
sessionType := msgs[0].SessionType
|
||||
sessionType := msgToRevoke.SessionType
|
||||
switch sessionType {
|
||||
case constant.SingleChatType:
|
||||
if err := authverify.CheckAccessV3(ctx, msgs[0].SendID, m.config.Share.IMAdminUserID); err != nil {
|
||||
if err := authverify.CheckAccessV3(ctx, msgToRevoke.SendID, m.config.Share.IMAdminUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
role = user.AppMangerLevel
|
||||
case constant.ReadGroupChatType:
|
||||
members, err := m.GroupLocalCache.GetGroupMemberInfoMap(ctx, msgs[0].GroupID, datautil.Distinct([]string{req.UserID, msgs[0].SendID}))
|
||||
members, err := m.GroupLocalCache.GetGroupMemberInfoMap(ctx, msgToRevoke.GroupID, datautil.Distinct([]string{req.UserID, msgToRevoke.SendID}))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if req.UserID != msgs[0].SendID {
|
||||
if req.UserID != msgToRevoke.SendID {
|
||||
switch members[req.UserID].RoleLevel {
|
||||
case constant.GroupOwner:
|
||||
case constant.GroupAdmin:
|
||||
if sendMember, ok := members[msgs[0].SendID]; ok {
|
||||
if sendMember, ok := members[msgToRevoke.SendID]; ok {
|
||||
if sendMember.RoleLevel != constant.GroupOrdinaryUsers {
|
||||
return nil, errs.ErrNoPermission.WrapMsg("no permission")
|
||||
}
|
||||
@ -114,20 +172,31 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg.
|
||||
}
|
||||
tips := sdkws.RevokeMsgTips{
|
||||
RevokerUserID: revokerUserID,
|
||||
ClientMsgID: msgs[0].ClientMsgID,
|
||||
ClientMsgID: msgToRevoke.ClientMsgID,
|
||||
RevokeTime: now,
|
||||
Seq: req.Seq,
|
||||
SesstionType: msgs[0].SessionType,
|
||||
SesstionType: msgToRevoke.SessionType,
|
||||
ConversationID: req.ConversationID,
|
||||
IsAdminRevoke: flag,
|
||||
}
|
||||
var recvID string
|
||||
if msgs[0].SessionType == constant.ReadGroupChatType {
|
||||
recvID = msgs[0].GroupID
|
||||
if msgToRevoke.SessionType == constant.ReadGroupChatType {
|
||||
recvID = msgToRevoke.GroupID
|
||||
} else {
|
||||
recvID = msgs[0].RecvID
|
||||
recvID = msgToRevoke.RecvID
|
||||
}
|
||||
m.notificationSender.NotificationWithSessionType(ctx, req.UserID, recvID, constant.MsgRevokeNotification, msgs[0].SessionType, &tips)
|
||||
m.notificationSender.NotificationWithSessionType(ctx, req.UserID, recvID, constant.MsgRevokeNotification, msgToRevoke.SessionType, &tips)
|
||||
m.webhookAfterRevokeMsg(ctx, &m.config.WebhooksConfig.AfterRevokeMsg, req)
|
||||
return &msg.RevokeMsgResp{}, nil
|
||||
}
|
||||
|
||||
func getSessionTypeFromConversationID(conversationID string) int32 {
|
||||
// Conversation ID format is typically: "single chat prefix{userID}" or "group chat prefix{groupID}"
|
||||
if strings.HasPrefix(conversationID, "sp_") || strings.HasPrefix(conversationID, "si_") {
|
||||
return constant.SingleChatType
|
||||
} else if strings.HasPrefix(conversationID, "sg_") {
|
||||
return constant.ReadGroupChatType
|
||||
}
|
||||
// Default to single chat type
|
||||
return constant.SingleChatType
|
||||
}
|
||||
|
||||
19
test/stress-test-v2/README.md
Normal file
19
test/stress-test-v2/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Stress Test V2
|
||||
|
||||
## Usage
|
||||
|
||||
You need set `TestTargetUserList` variables.
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
|
||||
go build -o test/stress-test-v2/stress-test-v2 test/stress-test-v2/main.go
|
||||
```
|
||||
|
||||
### Excute
|
||||
|
||||
```bash
|
||||
|
||||
tools/stress-test-v2/stress-test-v2 -c config/
|
||||
```
|
||||
@ -56,6 +56,7 @@ var (
|
||||
|
||||
const (
|
||||
MaxUser = 100000
|
||||
Max1kUser = 1000
|
||||
Max100KGroup = 100
|
||||
Max999Group = 1000
|
||||
MaxInviteUserLimit = 999
|
||||
@ -161,7 +162,7 @@ func (st *StressTest) PostRequest(ctx context.Context, url string, reqbody any)
|
||||
|
||||
if baseResp.ErrCode != 0 {
|
||||
err = fmt.Errorf(baseResp.ErrMsg)
|
||||
log.ZError(ctx, "Failed to send request", err, "url", url, "reqbody", reqbody, "resp", baseResp)
|
||||
// log.ZError(ctx, "Failed to send request", err, "url", url, "reqbody", reqbody, "resp", baseResp)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -530,9 +531,20 @@ func main() {
|
||||
// Create 100K groups
|
||||
st.Wg.Add(1)
|
||||
go func(idx int) {
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
elapsedTime := time.Since(startTime)
|
||||
log.ZInfo(st.Ctx, "100K group creation completed",
|
||||
"groupID", fmt.Sprintf("v2_StressTest_Group_100K_%d", idx),
|
||||
"index", idx,
|
||||
"duration", elapsedTime.String())
|
||||
}()
|
||||
|
||||
defer st.Wg.Done()
|
||||
defer func() {
|
||||
st.Mutex.Lock()
|
||||
st.Create100kGroupCounter++
|
||||
st.Mutex.Unlock()
|
||||
}()
|
||||
|
||||
groupID := fmt.Sprintf("v2_StressTest_Group_100K_%d", idx)
|
||||
@ -542,7 +554,7 @@ func main() {
|
||||
// continue
|
||||
}
|
||||
|
||||
for i := 0; i < MaxUser/MaxInviteUserLimit; i++ {
|
||||
for i := 0; i <= MaxUser/MaxInviteUserLimit; i++ {
|
||||
InviteUserIDs := make([]string, 0)
|
||||
// ensure TargetUserList is in group
|
||||
InviteUserIDs = append(InviteUserIDs, TestTargetUserList...)
|
||||
@ -556,7 +568,7 @@ func main() {
|
||||
}
|
||||
|
||||
if len(InviteUserIDs) == 0 {
|
||||
log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||
// log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -567,7 +579,7 @@ func main() {
|
||||
}
|
||||
|
||||
if len(InviteUserIDs) == 0 {
|
||||
log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||
// log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -602,9 +614,20 @@ func main() {
|
||||
// Create 999 groups
|
||||
st.Wg.Add(1)
|
||||
go func(idx int) {
|
||||
startTime := time.Now()
|
||||
defer func() {
|
||||
elapsedTime := time.Since(startTime)
|
||||
log.ZInfo(st.Ctx, "999 group creation completed",
|
||||
"groupID", fmt.Sprintf("v2_StressTest_Group_1K_%d", idx),
|
||||
"index", idx,
|
||||
"duration", elapsedTime.String())
|
||||
}()
|
||||
|
||||
defer st.Wg.Done()
|
||||
defer func() {
|
||||
st.Mutex.Lock()
|
||||
st.Create999GroupCounter++
|
||||
st.Mutex.Unlock()
|
||||
}()
|
||||
|
||||
groupID := fmt.Sprintf("v2_StressTest_Group_1K_%d", idx)
|
||||
@ -613,13 +636,13 @@ func main() {
|
||||
log.ZError(st.Ctx, "Create group failed.", err)
|
||||
// continue
|
||||
}
|
||||
for i := 0; i < MaxUser/MaxInviteUserLimit; i++ {
|
||||
for i := 0; i <= Max1kUser/MaxInviteUserLimit; i++ {
|
||||
InviteUserIDs := make([]string, 0)
|
||||
// ensure TargetUserList is in group
|
||||
InviteUserIDs = append(InviteUserIDs, TestTargetUserList...)
|
||||
|
||||
startIdx := max(i*MaxInviteUserLimit, 1)
|
||||
endIdx := min((i+1)*MaxInviteUserLimit, MaxUser)
|
||||
endIdx := min((i+1)*MaxInviteUserLimit, Max1kUser)
|
||||
|
||||
for j := startIdx; j < endIdx; j++ {
|
||||
userCreatedID := fmt.Sprintf("v2_StressTest_User_%d", j)
|
||||
@ -627,7 +650,7 @@ func main() {
|
||||
}
|
||||
|
||||
if len(InviteUserIDs) == 0 {
|
||||
log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||
// log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||
continue
|
||||
}
|
||||
|
||||
@ -638,7 +661,7 @@ func main() {
|
||||
}
|
||||
|
||||
if len(InviteUserIDs) == 0 {
|
||||
log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||
// log.ZWarn(st.Ctx, "InviteUserIDs is empty", nil, "groupID", groupID)
|
||||
continue
|
||||
}
|
||||
|
||||
19
test/stress-test/README.md
Normal file
19
test/stress-test/README.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Stress Test
|
||||
|
||||
## Usage
|
||||
|
||||
You need set `TestTargetUserList` and `DefaultGroupID` variables.
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
|
||||
go build -o test/stress-test/stress-test test/stress-test/main.go
|
||||
```
|
||||
|
||||
### Excute
|
||||
|
||||
```bash
|
||||
|
||||
tools/stress-test/stress-test -c config/
|
||||
```
|
||||
@ -337,7 +337,7 @@ func SetVersion(coll *mongo.Collection, key string, version int) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
||||
defer cancel()
|
||||
option := options.Update().SetUpsert(true)
|
||||
filter := bson.M{"key": key, "value": strconv.Itoa(version)}
|
||||
filter := bson.M{"key": key}
|
||||
update := bson.M{"$set": bson.M{"key": key, "value": strconv.Itoa(version)}}
|
||||
return mongoutil.UpdateOne(ctx, coll, filter, update, false, option)
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
# Stress Test
|
||||
|
||||
## Usage
|
||||
|
||||
You need set `TestTargetUserList` and `DefaultGroupID` variables.
|
||||
|
||||
### Build
|
||||
|
||||
```bash
|
||||
go build -o _output/bin/tools/linux/amd64/stress-test tools/stress-test/main.go
|
||||
|
||||
# or
|
||||
|
||||
go build -o tools/stress-test/stress-test tools/stress-test/main.go
|
||||
```
|
||||
|
||||
### Excute
|
||||
|
||||
```bash
|
||||
_output/bin/tools/linux/amd64/stress-test -c config/
|
||||
|
||||
#or
|
||||
|
||||
tools/stress-test/stress-test -c config/
|
||||
```
|
||||
Loading…
x
Reference in New Issue
Block a user