From 194a94cb8ef918a6e42e538e91e189e548f146af Mon Sep 17 00:00:00 2001
From: chao <48119764+withchao@users.noreply.github.com>
Date: Wed, 8 Nov 2023 03:29:28 -0600
Subject: [PATCH 01/20] fix: initiateUpload sign list number (#1358)
* optimize scheduled deletion
* optimize scheduled deletion
* optimize scheduled deletion
* optimize scheduled deletion
* minio cache
* fix: conflicts
* feat: minio cache
* feat: cache optimize
* feat: cache optimize
* feat: cache optimize
* feat: cache optimize
* feat: cache optimize
* fix: initiateUpload sign list number
---
pkg/common/db/s3/cont/controller.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pkg/common/db/s3/cont/controller.go b/pkg/common/db/s3/cont/controller.go
index 2d9d7d32d..2db1b800f 100644
--- a/pkg/common/db/s3/cont/controller.go
+++ b/pkg/common/db/s3/cont/controller.go
@@ -144,7 +144,7 @@ func (c *Controller) InitiateUpload(ctx context.Context, hash string, size int64
}
var authSign *s3.AuthSignResult
if maxParts > 0 {
- partNumbers := make([]int, partNumber)
+ partNumbers := make([]int, maxParts)
for i := 0; i < maxParts; i++ {
partNumbers[i] = i + 1
}
From 3ba861fd90dafb1fb7a32a045d8ae8cef8d85b5f Mon Sep 17 00:00:00 2001
From: Gordon <46924906+FGadvancer@users.noreply.github.com>
Date: Wed, 8 Nov 2023 18:01:28 +0800
Subject: [PATCH 02/20] fix: msg pull change and fcm redis flag fix. (#1367)
* fix: to start im or chat, ZooKeeper must be started first.
* fix: msg gateway start output err info
Signed-off-by: Gordon <1432970085@qq.com>
* fix: msg gateway start output err info
Signed-off-by: Gordon <1432970085@qq.com>
* chore: package path changes
Signed-off-by: withchao <993506633@qq.com>
* fix: go mod update
Signed-off-by: Gordon <1432970085@qq.com>
* fix: token update
Signed-off-by: Gordon <1432970085@qq.com>
* chore: package path changes
Signed-off-by: withchao <993506633@qq.com>
* chore: package path changes
Signed-off-by: withchao <993506633@qq.com>
* fix: token update
Signed-off-by: Gordon <1432970085@qq.com>
* fix: token update
Signed-off-by: Gordon <1432970085@qq.com>
* fix: token update
Signed-off-by: Gordon <1432970085@qq.com>
* fix: token update
Signed-off-by: Gordon <1432970085@qq.com>
* fix: token update
Signed-off-by: Gordon <1432970085@qq.com>
* fix: token update
Signed-off-by: Gordon <1432970085@qq.com>
* fix: get all userID
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: msggateway add online status call
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* refactor: log change
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* refactor: log change
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* chore: network mode change
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* cicd: robot automated Change
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* cicd: robot automated Change
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* cicd: robot automated Change
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* feat: add api of get server time
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* feat: remove go work sum
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* cicd: robot automated Change
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: pull message add isRead field
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: check msg-transfer script
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: script update
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: start don't kill old process
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* cicd: robot automated Change
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: check component
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: pull message set isRead only message come from single.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* cicd: robot automated Change
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: multiple gateway kick user each other.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: multiple gateway kick user each other.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: multiple gateway kick user each other.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: multiple gateway kick user each other.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: multiple gateway kick user each other.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: multiple gateway kick user each other.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* cicd: robot automated Change
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
* fix: multiple gateway kick user each other.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: add ex field to update group info.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* cicd: robot automated Change
* cicd: robot automated Change
* refactor: change project module name.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* refactor: change project module name.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* refactor: change project module name.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* cicd: robot automated Change
* test: for pressure test.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* test: for pressure test.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* test: for pressure test.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* test: message log.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* cicd: robot automated Change
* fxi: component check output valid info.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fxi: component check output valid info.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* test: send message test log.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* cicd: robot automated Change
* cicd: robot automated Change
* test: remove info log.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* feat: api of send message add sendTime field.
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
* fix: add callback for update user's info.
* cicd: robot automated Change
* fix: change callback command name.
* cicd: robot automated Change
* fix: single chat unread status change.
* fix: single chat unread status change.
* fix: single chat unread status change.
* fix: user status change.
* cicd: robot automated Change
* fix: user status change.
* fix: user status change.
* fix: user status change.
* cicd: robot automated Change
* fix: ws close when user logout.
* fix: remove repeat platform on online status.
* cicd: robot automated Change
* fix: api send messages for notification conversation .
* fix: api send messages for notification conversation .
* fix: api send messages for notification conversation .
* fix: api send messages for notification conversation .
* fix: api send messages for notification conversation .
* fix: api send messages for notification conversation.
* fix: api send messages for notification conversation.
* fix: api send messages for notification conversation.
* fix: api send messages for notification conversation.
* fix: api send messages for notification conversation.
* fix: api send messages for notification conversation.
* re: remove router of unsubscribeStatus.
* re: remove router of unsubscribeStatus.
* re: remove router of unsubscribeStatus.
* re: remove router of unsubscribeStatus.
* fix: reset branch
* fix: not support redis cluster. CROSSSLOT Keys in request don't hash to the same slot
* fix: update user.FaceURL do not trigger GroupMemberInfoSetNotification
* cicd: robot automated Change
* fix: api send messages for notification conversation.
* fix: api send messages for notification conversation.
* fix: zk add close to avoid zk block.
* fix: go mod update.
* fix: msg pull change and fcm redis flag fix.
---------
Signed-off-by: Gordon <1432970085@qq.com>
Signed-off-by: withchao <993506633@qq.com>
Signed-off-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
Signed-off-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: withchao <993506633@qq.com>
Co-authored-by: Xinwei Xiong <3293172751NSS@gmail.com>
Co-authored-by: FGadvancer
Co-authored-by: withchao
---
internal/api/msg.go | 2 +-
pkg/common/db/cache/msg.go | 8 ++---
pkg/common/db/controller/msg.go | 53 ++++++++++++++++++++++++++++-----
3 files changed, 50 insertions(+), 13 deletions(-)
diff --git a/internal/api/msg.go b/internal/api/msg.go
index 028d77ca4..38e207cfb 100644
--- a/internal/api/msg.go
+++ b/internal/api/msg.go
@@ -53,7 +53,7 @@ func (MessageApi) SetOptions(options map[string]bool, value bool) {
utils.SetSwitchFromOptions(options, constant.IsConversationUpdate, value)
}
-func (m MessageApi) newUserSendMsgReq(c *gin.Context, params *apistruct.SendMsg) *msg.SendMsgReq {
+func (m MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg) *msg.SendMsgReq {
var newContent string
options := make(map[string]bool, 5)
switch params.ContentType {
diff --git a/pkg/common/db/cache/msg.go b/pkg/common/db/cache/msg.go
index 2c869befb..50fb617aa 100644
--- a/pkg/common/db/cache/msg.go
+++ b/pkg/common/db/cache/msg.go
@@ -51,7 +51,7 @@ const (
getuiTaskID = "GETUI_TASK_ID"
signalCache = "SIGNAL_CACHE:"
signalListCache = "SIGNAL_LIST_CACHE:"
- fcmToken = "FCM_TOKEN:"
+ FCM_TOKEN = "FCM_TOKEN:"
messageCache = "MESSAGE_CACHE:"
messageDelUserList = "MESSAGE_DEL_USER_LIST:"
@@ -650,15 +650,15 @@ func (c *msgCache) GetSendMsgStatus(ctx context.Context, id string) (int32, erro
}
func (c *msgCache) SetFcmToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) (err error) {
- return errs.Wrap(c.rdb.Set(ctx, fcmToken+account+":"+strconv.Itoa(platformID), fcmToken, time.Duration(expireTime)*time.Second).Err())
+ return errs.Wrap(c.rdb.Set(ctx, FCM_TOKEN+account+":"+strconv.Itoa(platformID), fcmToken, time.Duration(expireTime)*time.Second).Err())
}
func (c *msgCache) GetFcmToken(ctx context.Context, account string, platformID int) (string, error) {
- return utils.Wrap2(c.rdb.Get(ctx, fcmToken+account+":"+strconv.Itoa(platformID)).Result())
+ return utils.Wrap2(c.rdb.Get(ctx, FCM_TOKEN+account+":"+strconv.Itoa(platformID)).Result())
}
func (c *msgCache) DelFcmToken(ctx context.Context, account string, platformID int) error {
- return errs.Wrap(c.rdb.Del(ctx, fcmToken+account+":"+strconv.Itoa(platformID)).Err())
+ return errs.Wrap(c.rdb.Del(ctx, FCM_TOKEN+account+":"+strconv.Itoa(platformID)).Err())
}
func (c *msgCache) IncrUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) {
diff --git a/pkg/common/db/controller/msg.go b/pkg/common/db/controller/msg.go
index e3b6559dc..62ecf7232 100644
--- a/pkg/common/db/controller/msg.go
+++ b/pkg/common/db/controller/msg.go
@@ -436,6 +436,25 @@ func (db *commonMsgDatabase) getMsgBySeqsRange(ctx context.Context, userID strin
return seqMsgs, nil
}
+// GetMsgBySeqsRange In the context of group chat, we have the following parameters:
+//
+// "maxSeq" of a conversation: It represents the maximum value of messages in the group conversation.
+// "minSeq" of a conversation (default: 1): It represents the minimum value of messages in the group conversation.
+//
+// For a user's perspective regarding the group conversation, we have the following parameters:
+//
+// "userMaxSeq": It represents the user's upper limit for message retrieval in the group. If not set (default: 0),
+// it means the upper limit is the same as the conversation's "maxSeq".
+// "userMinSeq": It represents the user's starting point for message retrieval in the group. If not set (default: 0),
+// it means the starting point is the same as the conversation's "minSeq".
+//
+// The scenarios for these parameters are as follows:
+//
+// For users who have been kicked out of the group, "userMaxSeq" can be set as the maximum value they had before
+// being kicked out. This limits their ability to retrieve messages up to a certain point.
+// For new users joining the group, if they don't need to receive old messages,
+// "userMinSeq" can be set as the same value as the conversation's "maxSeq" at the moment they join the group.
+// This ensures that their message retrieval starts from the point they joined.
func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID string, conversationID string, begin, end, num, userMaxSeq int64) (int64, int64, []*sdkws.MsgData, error) {
userMinSeq, err := db.cache.GetConversationUserMinSeq(ctx, conversationID, userID)
if err != nil && errs.Unwrap(err) != redis.Nil {
@@ -448,6 +467,7 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
if userMinSeq > minSeq {
minSeq = userMinSeq
}
+ //"minSeq" represents the startSeq value that the user can retrieve.
if minSeq > end {
log.ZInfo(ctx, "minSeq > end", "minSeq", minSeq, "end", end)
return 0, 0, nil, nil
@@ -462,23 +482,41 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
maxSeq = userMaxSeq
}
}
+ //"maxSeq" represents the endSeq value that the user can retrieve.
+
if begin < minSeq {
begin = minSeq
}
if end > maxSeq {
end = maxSeq
}
+ //"begin" and "end" represent the actual startSeq and endSeq values that the user can retrieve.
if end < begin {
return 0, 0, nil, errs.ErrArgs.Wrap("seq end < begin")
}
var seqs []int64
- for i := end; i > end-num; i-- {
- if i >= begin {
- seqs = append([]int64{i}, seqs...)
- } else {
- break
+ if end-begin+1 <= num {
+ for i := begin; i <= end; i++ {
+ seqs = append(seqs, i)
}
- }
+ } else {
+ for i := end - num + 1; i <= end; i++ {
+ seqs = append(seqs, i)
+ }
+ }
+
+ //167 178 10
+ //if end-num < {
+ //
+ //}
+ //var seqs []int64
+ //for i := end; i > end-num; i-- {
+ // if i >= begin {
+ // seqs = append([]int64{i}, seqs...)
+ // } else {
+ // break
+ // }
+ //}
if len(seqs) == 0 {
return 0, 0, nil, nil
}
@@ -545,8 +583,7 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
return 0, 0, nil, err
}
-
- successMsgs = append(successMsgs, mongoMsgs...)
+ successMsgs = append(mongoMsgs, successMsgs...)
}
return minSeq, maxSeq, successMsgs, nil
From b562da5c3776ef6bc42658a1d090bd577c434f1f Mon Sep 17 00:00:00 2001
From: Gordon <46924906+FGadvancer@users.noreply.github.com>
Date: Wed, 8 Nov 2023 18:17:09 +0800
Subject: [PATCH 03/20] fix: sync close ws conn when kick old user avoid wrong
trigger order about online status. (#1368)
---
internal/msggateway/client.go | 4 +++-
internal/msggateway/n_ws_server.go | 7 +------
2 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/internal/msggateway/client.go b/internal/msggateway/client.go
index b32130c9a..9eeac3835 100644
--- a/internal/msggateway/client.go
+++ b/internal/msggateway/client.go
@@ -297,7 +297,9 @@ func (c *Client) KickOnlineMessage() error {
resp := Resp{
ReqIdentifier: WSKickOnlineMsg,
}
- return c.writeBinaryMsg(resp)
+ err := c.writeBinaryMsg(resp)
+ c.close()
+ return err
}
func (c *Client) writeBinaryMsg(resp Resp) error {
diff --git a/internal/msggateway/n_ws_server.go b/internal/msggateway/n_ws_server.go
index ad56c1373..c58710e1b 100644
--- a/internal/msggateway/n_ws_server.go
+++ b/internal/msggateway/n_ws_server.go
@@ -225,12 +225,7 @@ func (ws *WsServer) registerClient(client *Client) {
ws.onlineUserNum.Add(1)
ws.onlineUserConnNum.Add(1)
} else {
- i := &kickHandler{
- clientOK: clientOK,
- oldClients: oldClients,
- newClient: client,
- }
- ws.kickHandlerChan <- i
+ ws.multiTerminalLoginChecker(clientOK, oldClients, client)
log.ZDebug(client.ctx, "user exist", "userID", client.UserID, "platformID", client.PlatformID)
if clientOK {
ws.clients.Set(client.UserID, client)
From 9d542edc963a1d084969db5755a0f582d2508ec6 Mon Sep 17 00:00:00 2001
From: skiffer-git <72860476+skiffer-git@users.noreply.github.com>
Date: Fri, 10 Nov 2023 08:44:36 +0800
Subject: [PATCH 04/20] Update README-zh_CN.md
---
README-zh_CN.md | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/README-zh_CN.md b/README-zh_CN.md
index f24ee4739..699fdc476 100644
--- a/README-zh_CN.md
+++ b/README-zh_CN.md
@@ -29,6 +29,10 @@
+## 🟢 扫描微信进群交流
+
+
+
## Ⓜ️ 关于 OpenIM
OpenIM 不仅仅是一个开源的即时消息组件,它是你的应用程序生态系统的一个不可或缺的部分。查看下面的图表,了解 AppServer、AppClient、OpenIMServer 和 OpenIMSDK 是如何交互的。
From 4eb8e0068d6a93299497d31f06e8c9d7f27ad6fc Mon Sep 17 00:00:00 2001
From: skiffer-git <72860476+skiffer-git@users.noreply.github.com>
Date: Fri, 10 Nov 2023 08:46:00 +0800
Subject: [PATCH 05/20] Update README-zh_CN.md
---
README-zh_CN.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README-zh_CN.md b/README-zh_CN.md
index 699fdc476..db1e8beab 100644
--- a/README-zh_CN.md
+++ b/README-zh_CN.md
@@ -30,7 +30,7 @@
## 🟢 扫描微信进群交流
-
+
## Ⓜ️ 关于 OpenIM
From e40aca81dc753adc80f2d8e998e58db54bd75b08 Mon Sep 17 00:00:00 2001
From: chao <48119764+withchao@users.noreply.github.com>
Date: Thu, 9 Nov 2023 21:30:33 -0600
Subject: [PATCH 06/20] fix: GetUserReqApplicationList error when there is a
disbanded group chat (#1374)
---
internal/rpc/group/group.go | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/internal/rpc/group/group.go b/internal/rpc/group/group.go
index 17d748920..6a0ce5733 100644
--- a/internal/rpc/group/group.go
+++ b/internal/rpc/group/group.go
@@ -1126,9 +1126,6 @@ func (s *groupServer) GetUserReqApplicationList(ctx context.Context, req *pbgrou
groupMap := utils.SliceToMap(groups, func(e *relationtb.GroupModel) string {
return e.GroupID
})
- if ids := utils.Single(groupIDs, utils.Keys(groupMap)); len(ids) > 0 {
- return nil, errs.ErrGroupIDNotFound.Wrap(strings.Join(ids, ","))
- }
owners, err := s.FindGroupMember(ctx, groupIDs, nil, []int32{constant.GroupOwner})
if err != nil {
return nil, err
@@ -1144,7 +1141,11 @@ func (s *groupServer) GetUserReqApplicationList(ctx context.Context, req *pbgrou
return nil, err
}
resp.GroupRequests = utils.Slice(requests, func(e *relationtb.GroupRequestModel) *sdkws.GroupRequest {
- return convert.Db2PbGroupRequest(e, user, convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerMap[e.GroupID].UserID, uint32(groupMemberNum[e.GroupID])))
+ var ownerUserID string
+ if owner, ok := ownerMap[e.GroupID]; ok {
+ ownerUserID = owner.UserID
+ }
+ return convert.Db2PbGroupRequest(e, user, convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerUserID, groupMemberNum[e.GroupID]))
})
return resp, nil
}
From a285f02631feb12f430157d1b85bb908e9eb2dac Mon Sep 17 00:00:00 2001
From: chao <48119764+withchao@users.noreply.github.com>
Date: Fri, 10 Nov 2023 01:44:28 -0600
Subject: [PATCH 07/20] fix: error when querying some information about
disbanded group (#1376)
* fix: GetUserReqApplicationList error when there is a disbanded group chat
* fix: error when querying some information about disbanded group
---
internal/rpc/group/group.go | 32 +++++++++++++++++++-------------
1 file changed, 19 insertions(+), 13 deletions(-)
diff --git a/internal/rpc/group/group.go b/internal/rpc/group/group.go
index 6a0ce5733..2a79c5d93 100644
--- a/internal/rpc/group/group.go
+++ b/internal/rpc/group/group.go
@@ -690,7 +690,11 @@ func (s *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup.
return e.GroupID
})
resp.GroupRequests = utils.Slice(groupRequests, func(e *relationtb.GroupRequestModel) *sdkws.GroupRequest {
- return convert.Db2PbGroupRequest(e, userMap[e.UserID], convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerMap[e.GroupID].UserID, groupMemberNumMap[e.GroupID]))
+ var ownerUserID string
+ if owner, ok := ownerMap[e.GroupID]; ok {
+ ownerUserID = owner.UserID
+ }
+ return convert.Db2PbGroupRequest(e, userMap[e.UserID], convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerUserID, groupMemberNumMap[e.GroupID]))
})
return resp, nil
}
@@ -1056,16 +1060,20 @@ func (s *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq)
ownerMemberMap := utils.SliceToMap(ownerMembers, func(e *relationtb.GroupMemberModel) string {
return e.GroupID
})
- if ids := utils.Single(groupIDs, utils.Keys(ownerMemberMap)); len(ids) > 0 {
- return nil, errs.ErrDatabase.Wrap("group not owner " + strings.Join(ids, ","))
- }
groupMemberNumMap, err := s.GroupDatabase.MapGroupMemberNum(ctx, groupIDs)
if err != nil {
return nil, err
}
resp.Groups = utils.Slice(groups, func(group *relationtb.GroupModel) *pbgroup.CMSGroup {
- member := ownerMemberMap[group.GroupID]
- return convert.Db2PbCMSGroup(group, member.UserID, member.Nickname, uint32(groupMemberNumMap[group.GroupID]))
+ var (
+ userID string
+ username string
+ )
+ if member, ok := ownerMemberMap[group.GroupID]; ok {
+ userID = member.UserID
+ username = member.Nickname
+ }
+ return convert.Db2PbCMSGroup(group, userID, username, groupMemberNumMap[group.GroupID])
})
return resp, nil
}
@@ -1133,9 +1141,6 @@ func (s *groupServer) GetUserReqApplicationList(ctx context.Context, req *pbgrou
ownerMap := utils.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string {
return e.GroupID
})
- if ids := utils.Single(groupIDs, utils.Keys(ownerMap)); len(ids) > 0 {
- return nil, errs.ErrData.Wrap("group no owner", strings.Join(ids, ","))
- }
groupMemberNum, err := s.GroupDatabase.MapGroupMemberNum(ctx, groupIDs)
if err != nil {
return nil, err
@@ -1564,15 +1569,16 @@ func (s *groupServer) GetGroupUsersReqApplicationList(ctx context.Context, req *
ownerMap := utils.SliceToMap(owners, func(e *relationtb.GroupMemberModel) string {
return e.GroupID
})
- if ids := utils.Single(groupIDs, utils.Keys(ownerMap)); len(ids) > 0 {
- return nil, errs.ErrData.Wrap("group no owner", strings.Join(ids, ","))
- }
groupMemberNum, err := s.GroupDatabase.MapGroupMemberNum(ctx, groupIDs)
if err != nil {
return nil, err
}
resp.GroupRequests = utils.Slice(requests, func(e *relationtb.GroupRequestModel) *sdkws.GroupRequest {
- return convert.Db2PbGroupRequest(e, nil, convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerMap[e.GroupID].UserID, uint32(groupMemberNum[e.GroupID])))
+ var ownerUserID string
+ if owner, ok := ownerMap[e.GroupID]; ok {
+ ownerUserID = owner.UserID
+ }
+ return convert.Db2PbGroupRequest(e, nil, convert.Db2PbGroupInfo(groupMap[e.GroupID], ownerUserID, groupMemberNum[e.GroupID]))
})
resp.Total = total
return resp, nil
From a32e94b5ae927ba819d63aff8a3e89710be619d2 Mon Sep 17 00:00:00 2001
From: chao <48119764+withchao@users.noreply.github.com>
Date: Fri, 10 Nov 2023 02:39:16 -0600
Subject: [PATCH 08/20] fix: GetUserReqApplicationList dismissed group error
(#1378)
* fix: GetUserReqApplicationList error when there is a disbanded group chat
* fix: error when querying some information about disbanded group
* fix: GetUserReqApplicationList dismissed group error
---
internal/rpc/group/group.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/internal/rpc/group/group.go b/internal/rpc/group/group.go
index 2a79c5d93..85b78cfb2 100644
--- a/internal/rpc/group/group.go
+++ b/internal/rpc/group/group.go
@@ -1127,7 +1127,7 @@ func (s *groupServer) GetUserReqApplicationList(ctx context.Context, req *pbgrou
groupIDs := utils.Distinct(utils.Slice(requests, func(e *relationtb.GroupRequestModel) string {
return e.GroupID
}))
- groups, err := s.GroupDatabase.FindNotDismissedGroup(ctx, groupIDs)
+ groups, err := s.GroupDatabase.FindGroup(ctx, groupIDs)
if err != nil {
return nil, err
}
From 686fa80800d54ab7992359d60c33e392c7790ba7 Mon Sep 17 00:00:00 2001
From: "fengyun.rui"
Date: Fri, 10 Nov 2023 17:55:50 +0800
Subject: [PATCH 09/20] refactor: lower the level of code nesting (#1370)
* refactor: lower the level of code nesting
Signed-off-by: rfyiamcool
* refactor: lower the level of code nesting
Signed-off-by: rfyiamcool
---------
Signed-off-by: rfyiamcool
---
internal/push/push_to_client.go | 107 +++++++++++++++++++-------------
1 file changed, 64 insertions(+), 43 deletions(-)
diff --git a/internal/push/push_to_client.go b/internal/push/push_to_client.go
index 2f3156c28..2d2c647ac 100644
--- a/internal/push/push_to_client.go
+++ b/internal/push/push_to_client.go
@@ -18,14 +18,9 @@ import (
"context"
"encoding/json"
"errors"
- "github.com/openimsdk/open-im-server/v3/pkg/common/prom_metrics"
- "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/dummy"
-
- "github.com/OpenIMSDK/protocol/conversation"
-
- "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/OpenIMSDK/protocol/constant"
+ "github.com/OpenIMSDK/protocol/conversation"
"github.com/OpenIMSDK/protocol/msggateway"
"github.com/OpenIMSDK/protocol/sdkws"
"github.com/OpenIMSDK/tools/discoveryregistry"
@@ -34,6 +29,7 @@ import (
"github.com/OpenIMSDK/tools/utils"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
+ "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/dummy"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/fcm"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/getui"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/jpush"
@@ -41,6 +37,8 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/controller"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/localcache"
+ "github.com/openimsdk/open-im-server/v3/pkg/common/prom_metrics"
+ "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
)
@@ -53,7 +51,6 @@ type Pusher struct {
msgRpcClient *rpcclient.MessageRpcClient
conversationRpcClient *rpcclient.ConversationRpcClient
groupRpcClient *rpcclient.GroupRpcClient
- successCount int
}
var errNoOfflinePusher = errors.New("no offlinePusher is configured")
@@ -104,24 +101,29 @@ func (p *Pusher) Push2User(ctx context.Context, userIDs []string, msg *sdkws.Msg
if err := callbackOnlinePush(ctx, userIDs, msg); err != nil {
return err
}
+
// push
wsResults, err := p.GetConnsAndOnlinePush(ctx, msg, userIDs)
if err != nil {
return err
}
+
isOfflinePush := utils.GetSwitchFromOptions(msg.Options, constant.IsOfflinePush)
log.ZDebug(ctx, "push_result", "ws push result", wsResults, "sendData", msg, "isOfflinePush", isOfflinePush, "push_to_userID", userIDs)
- p.successCount++
- if isOfflinePush {
- for _, v := range wsResults {
- if msg.SendID != v.UserID && (!v.OnlinePush) {
- if err := callbackOfflinePush(ctx, userIDs, msg, &[]string{}); err != nil {
- return err
- }
- err = p.offlinePushMsg(ctx, msg.SendID, msg, []string{v.UserID})
- if err != nil {
- return err
- }
+
+ if !isOfflinePush {
+ return nil
+ }
+
+ for _, v := range wsResults {
+ if msg.SendID != v.UserID && (!v.OnlinePush) {
+ if err = callbackOfflinePush(ctx, userIDs, msg, &[]string{}); err != nil {
+ return err
+ }
+
+ err = p.offlinePushMsg(ctx, msg.SendID, msg, []string{v.UserID})
+ if err != nil {
+ return err
}
}
}
@@ -140,14 +142,16 @@ func (p *Pusher) UnmarshalNotificationElem(bytes []byte, t interface{}) error {
func (p *Pusher) Push2SuperGroup(ctx context.Context, groupID string, msg *sdkws.MsgData) (err error) {
log.ZDebug(ctx, "Get super group msg from msg_transfer and push msg", "msg", msg.String(), "groupID", groupID)
var pushToUserIDs []string
- if err := callbackBeforeSuperGroupOnlinePush(ctx, groupID, msg, &pushToUserIDs); err != nil {
+ if err = callbackBeforeSuperGroupOnlinePush(ctx, groupID, msg, &pushToUserIDs); err != nil {
return err
}
+
if len(pushToUserIDs) == 0 {
pushToUserIDs, err = p.groupLocalCache.GetGroupMemberIDs(ctx, groupID)
if err != nil {
return err
}
+
switch msg.ContentType {
case constant.MemberQuitNotification:
var tips sdkws.MemberQuitTips
@@ -155,7 +159,7 @@ func (p *Pusher) Push2SuperGroup(ctx context.Context, groupID string, msg *sdkws
return err
}
defer func(groupID string, userIDs []string) {
- if err := p.DeleteMemberAndSetConversationSeq(ctx, groupID, userIDs); err != nil {
+ if err = p.DeleteMemberAndSetConversationSeq(ctx, groupID, userIDs); err != nil {
log.ZError(ctx, "MemberQuitNotification DeleteMemberAndSetConversationSeq", err, "groupID", groupID, "userIDs", userIDs)
}
}(groupID, []string{tips.QuitUser.UserID})
@@ -167,7 +171,7 @@ func (p *Pusher) Push2SuperGroup(ctx context.Context, groupID string, msg *sdkws
}
kickedUsers := utils.Slice(tips.KickedUserList, func(e *sdkws.GroupMemberFullInfo) string { return e.UserID })
defer func(groupID string, userIDs []string) {
- if err := p.DeleteMemberAndSetConversationSeq(ctx, groupID, userIDs); err != nil {
+ if err = p.DeleteMemberAndSetConversationSeq(ctx, groupID, userIDs); err != nil {
log.ZError(ctx, "MemberKickedNotification DeleteMemberAndSetConversationSeq", err, "groupID", groupID, "userIDs", userIDs)
}
}(groupID, kickedUsers)
@@ -183,48 +187,61 @@ func (p *Pusher) Push2SuperGroup(ctx context.Context, groupID string, msg *sdkws
ctx = mcontext.WithOpUserIDContext(ctx, config.Config.Manager.UserID[0])
}
defer func(groupID string) {
- if err := p.groupRpcClient.DismissGroup(ctx, groupID); err != nil {
+ if err = p.groupRpcClient.DismissGroup(ctx, groupID); err != nil {
log.ZError(ctx, "DismissGroup Notification clear members", err, "groupID", groupID)
}
}(groupID)
}
}
}
+
wsResults, err := p.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs)
if err != nil {
return err
}
+
log.ZDebug(ctx, "get conn and online push success", "result", wsResults, "msg", msg)
- p.successCount++
isOfflinePush := utils.GetSwitchFromOptions(msg.Options, constant.IsOfflinePush)
if isOfflinePush {
- var onlineSuccessUserIDs []string
- var WebAndPcBackgroundUserIDs []string
- onlineSuccessUserIDs = append(onlineSuccessUserIDs, msg.SendID)
+ var (
+ onlineSuccessUserIDs = []string{msg.SendID}
+ webAndPcBackgroundUserIDs []string
+ )
+
for _, v := range wsResults {
if v.OnlinePush && v.UserID != msg.SendID {
onlineSuccessUserIDs = append(onlineSuccessUserIDs, v.UserID)
}
- if !v.OnlinePush {
- if len(v.Resp) != 0 {
- for _, singleResult := range v.Resp {
- if singleResult.ResultCode == -2 {
- if constant.PlatformIDToName(int(singleResult.RecvPlatFormID)) == constant.TerminalPC ||
- singleResult.RecvPlatFormID == constant.WebPlatformID {
- WebAndPcBackgroundUserIDs = append(WebAndPcBackgroundUserIDs, v.UserID)
- }
- }
- }
+
+ if v.OnlinePush {
+ continue
+ }
+
+ if len(v.Resp) == 0 {
+ continue
+ }
+
+ for _, singleResult := range v.Resp {
+ if singleResult.ResultCode != -2 {
+ continue
+ }
+
+ isPC := constant.PlatformIDToName(int(singleResult.RecvPlatFormID)) == constant.TerminalPC
+ isWebID := singleResult.RecvPlatFormID == constant.WebPlatformID
+
+ if isPC || isWebID {
+ webAndPcBackgroundUserIDs = append(webAndPcBackgroundUserIDs, v.UserID)
}
}
}
+
needOfflinePushUserIDs := utils.DifferenceString(onlineSuccessUserIDs, pushToUserIDs)
if msg.ContentType != constant.SignalingNotification {
notNotificationUserIDs, err := p.conversationLocalCache.GetRecvMsgNotNotifyUserIDs(ctx, groupID)
if err != nil {
- // log.ZError(ctx, "GetRecvMsgNotNotifyUserIDs failed", err, "groupID", groupID)
return err
}
+
needOfflinePushUserIDs = utils.SliceSub(needOfflinePushUserIDs, notNotificationUserIDs)
}
// Use offline push messaging
@@ -234,6 +251,7 @@ func (p *Pusher) Push2SuperGroup(ctx context.Context, groupID string, msg *sdkws
if err != nil {
return err
}
+
if len(offlinePushUserIDs) > 0 {
needOfflinePushUserIDs = offlinePushUserIDs
}
@@ -250,8 +268,8 @@ func (p *Pusher) Push2SuperGroup(ctx context.Context, groupID string, msg *sdkws
log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg)
return err
}
- if _, err := p.GetConnsAndOnlinePush(ctx, msg, utils.IntersectString(resp.UserIDs, WebAndPcBackgroundUserIDs)); err != nil {
- log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg, "userIDs", utils.IntersectString(needOfflinePushUserIDs, WebAndPcBackgroundUserIDs))
+ if _, err := p.GetConnsAndOnlinePush(ctx, msg, utils.IntersectString(resp.UserIDs, webAndPcBackgroundUserIDs)); err != nil {
+ log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg, "userIDs", utils.IntersectString(needOfflinePushUserIDs, webAndPcBackgroundUserIDs))
return err
}
}
@@ -319,15 +337,18 @@ func (p *Pusher) getOfflinePushInfos(conversationID string, msg *sdkws.MsgData)
err = errNoOfflinePusher
return
}
- type AtContent struct {
+
+ type atContent struct {
Text string `json:"text"`
AtUserList []string `json:"atUserList"`
IsAtSelf bool `json:"isAtSelf"`
}
+
opts, err = p.GetOfflinePushOpts(msg)
if err != nil {
return
}
+
if msg.OfflinePushInfo != nil {
title = msg.OfflinePushInfo.Title
content = msg.OfflinePushInfo.Desc
@@ -345,9 +366,9 @@ func (p *Pusher) getOfflinePushInfos(conversationID string, msg *sdkws.MsgData)
case constant.File:
title = constant.ContentType2PushContent[int64(msg.ContentType)]
case constant.AtText:
- a := AtContent{}
- _ = utils.JsonStringToStruct(string(msg.Content), &a)
- if utils.IsContain(conversationID, a.AtUserList) {
+ ac := atContent{}
+ _ = utils.JsonStringToStruct(string(msg.Content), &ac)
+ if utils.IsContain(conversationID, ac.AtUserList) {
title = constant.ContentType2PushContent[constant.AtText] + constant.ContentType2PushContent[constant.Common]
} else {
title = constant.ContentType2PushContent[constant.GroupMsg]
From e2004c1e9df5e6f9c3f12e217568cb133f49564c Mon Sep 17 00:00:00 2001
From: Xinwei Xiong <3293172751@qq.com>
Date: Fri, 10 Nov 2023 19:37:25 +0800
Subject: [PATCH 10/20] =?UTF-8?q?=E2=98=80=EF=B8=8F=20feat:=20Enhancing=20?=
=?UTF-8?q?OpenIM=20with=20Integrated=20E2E=20Testing=20and=20CI/CD=20Enha?=
=?UTF-8?q?ncements=20(#1359)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* cicd: robot automated Change
* feat: add api test
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* feat: add api test make file
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* feat: add openim e2e test
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* feat: add openim e2e test
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* fix: Fixed some unused scripts and some names
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* docs: optimize openim docs
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* feat: add prom address
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* feat: add openim info test
* feat: add openim images config path
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* fix: fix tim file rename
* fix: fix tim file rename
* fix: fix tim file rename
* fix: fix tim file rename
* fix: add openim test e2e
* feat: add openim test .keep
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* feat: add openim test .keep
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* feat: openim test
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* feat: openim test
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
* feat: openim test
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
---------
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
Co-authored-by: cubxxw
---
.env | 4 +-
.github/.codecov.yml | 18 +-
.github/workflows/api-test.yml | 90 ++
.github/workflows/create_branch_on_tag.yml | 14 +
.github/workflows/e2e-test.yml | 60 +-
.github/workflows/openimci.yml | 33 +-
CHANGELOG/CHANGELOG.md | 2 +-
CONTRIBUTING.md | 8 +-
Makefile | 11 +-
README-zh_CN.md | 14 +-
README.md | 81 +-
build/images/Dockerfile | 14 +
chat | Bin 0 -> 7068773 bytes
cmd/openim-api/main.go | 10 +-
config/config.yaml | 2 +-
deployments/README.md | 2 +-
.../openim-api/templates/deployment.yaml | 4 +-
.../templates/deployment.yaml | 18 +-
.../templates/deployment.yaml | 4 +-
.../openim-push/templates/deployment.yaml | 4 +-
.../openim-rpc-auth/templates/deployment.yaml | 4 +-
.../templates/deployment.yaml | 4 +-
.../templates/deployment.yaml | 4 +-
.../templates/deployment.yaml | 4 +-
.../openim-rpc-msg/templates/deployment.yaml | 4 +-
.../templates/deployment.yaml | 4 +-
.../openim-rpc-user/templates/deployment.yaml | 4 +-
docker-compose.yml | 68 +-
docs/README.md | 52 +-
docs/contrib/README.md | 42 +
docs/{conversions => contrib}/api.md | 0
.../bash_log.md => contrib/bash-log.md} | 0
.../cicd-actions.md} | 0
...ode_conventions.md => code-conventions.md} | 43 +-
docs/{conversions => contrib}/commit.md | 0
docs/{conversions => contrib}/directory.md | 0
docs/contrib/environment.md | 4 +-
.../error_code.md => contrib/error-code.md} | 0
.../{git_workflow.md => git-workflow.md} | 0
.../{git_cherry-pick.md => gitcherry-pick.md} | 0
.../github-workflow.md | 2 +-
.../go_code.md => contrib/go-code.md} | 12 +
docs/{conversions => contrib}/images.md | 0
.../{init_config.md => init-config.md} | 0
.../{install_docker.md => install-docker.md} | 0
...ux_development.md => linux-development.md} | 0
.../{local_actions.md => local-actions.md} | 0
docs/{conversions => contrib}/logging.md | 0
docs/contrib/offline-deployment.md | 4 +-
.../{protoc_tools.md => protoc-tools.md} | 0
docs/contrib/test.md | 183 +++
docs/contrib/{util_go.md => util-go.md} | 0
.../{util_makefile.md => util-makefile.md} | 0
.../{util_scripts.md => util-scripts.md} | 0
docs/{conversions => contrib}/version.md | 2 +-
docs/conversions/README.md | 10 -
go.mod | 3 +
go.sum | 4 +
install.sh | 15 +-
install_guide.sh | 2 +-
internal/msggateway/n_ws_server.go | 6 +-
internal/msgtransfer/init.go | 15 +-
.../online_msg_to_mongo_handler.go | 6 +-
internal/push/offlinepush/fcm/push_test.go | 32 -
internal/push/push_to_client.go | 7 +-
internal/rpc/auth/auth.go | 4 +-
internal/rpc/msg/send.go | 12 +-
internal/tools/conversation.go | 12 +-
internal/tools/msg.go | 11 +-
pkg/apistruct/manage.go | 79 +-
pkg/common/cmd/msg_gateway_test.go | 51 +
pkg/common/config/parse.go | 4 +-
pkg/common/config/parse_test.go | 117 ++
pkg/common/db/cache/meta_cache.go | 3 +-
pkg/common/db/cache/s3.go | 8 +-
pkg/common/db/controller/msg.go | 10 +-
pkg/common/db/controller/s3.go | 8 +-
pkg/common/db/relation/conversation_model.go | 1 +
pkg/common/db/s3/cont/controller.go | 3 +-
pkg/common/db/s3/minio/minio.go | 3 +-
pkg/common/db/s3/minio/thumbnail.go | 12 +-
.../discoveryregister.go} | 2 +-
.../discoveryregister_test.go | 407 +++++
.../ginprometheus.go} | 2 +-
pkg/common/http/http_client_test.go | 154 ++
pkg/common/locker/doc.go | 15 -
pkg/common/locker/message_locker.go | 72 -
.../gin-api.go => prommetrics/gin_api.go} | 4 +-
.../grpc-auth.go => prommetrics/grpc_auth.go} | 2 +-
.../grpc-msg.go => prommetrics/grpc_msg.go} | 2 +-
.../grpc_msggateway.go} | 2 +-
.../grpc_push.go | 2 +-
.../func.go => prommetrics/prommetrics.go} | 10 +-
pkg/common/prommetrics/prommetrics_test.go | 60 +
.../{prom_metrics => prommetrics}/transfer.go | 2 +-
pkg/common/startrpc/start.go | 15 +-
pkg/common/startrpc/start_test.go | 52 +
pkg/common/tls/tls.go | 1 +
pkg/msgprocessor/conversation_test.go | 334 ++++
scripts/README.md | 6 +-
scripts/batch_start_all.sh | 84 -
scripts/build.cmd | 13 -
scripts/cherry-pick.sh | 6 +-
scripts/{create_topic.sh => create-topic.sh} | 0
scripts/genconfig.sh | 5 +-
scripts/init-config.sh | 2 +-
scripts/{init_pwd.sh => init-pwd.sh} | 0
scripts/install/dependency.sh | 6 +-
scripts/install/environment.sh | 10 +-
scripts/install/install.sh | 9 +-
scripts/install/openim-api.sh | 3 +-
scripts/install/openim-crontask.sh | 12 +-
scripts/install/openim-msggateway.sh | 12 +-
scripts/install/openim-msgtransfer.sh | 15 +-
scripts/install/openim-push.sh | 12 +-
scripts/install/openim-rpc.sh | 9 +-
scripts/install/test.sh | 1394 ++++++++++++++---
scripts/install_im_compose.sh | 66 -
scripts/lib/color.sh | 3 +-
scripts/lib/logging.sh | 3 +-
scripts/lib/util.sh | 15 +
scripts/make-rules/golang.mk | 12 +
scripts/make-rules/image.mk | 2 +-
scripts/start.bat | 12 -
scripts/template/head.md.tmpl | 4 +-
scripts/wait-for-it.sh | 9 +-
test/e2e/README.md | 134 ++
test/e2e/api/.keep | 1 +
test/e2e/api/token/token.go | 138 ++
test/e2e/api/user/curd.go | 44 +
test/e2e/api/user/user.go | 101 ++
test/e2e/conformance/.keep | 1 +
test/e2e/e2e.go | 37 +
test/e2e/e2e_test.go | 23 +
test/e2e/framework/config/.keep | 1 +
test/e2e/framework/config/config.go | 21 +
test/e2e/framework/config/config_test.go | 75 +
test/e2e/framework/ginkgowrapper/.keep | 1 +
.../framework/ginkgowrapper/ginkgowrapper.go | 1 +
.../ginkgowrapper/ginkgowrapper_test.go | 1 +
test/e2e/framework/helpers/.keep | 1 +
test/e2e/framework/helpers/chat/chat.go | 152 ++
test/e2e/performance/.keep | 1 +
test/e2e/rpc/auth/.keep | 1 +
test/e2e/rpc/conversation/.keep | 1 +
test/e2e/rpc/friend/.keep | 1 +
test/e2e/rpc/group/.keep | 1 +
test/e2e/rpc/message/.keep | 1 +
test/e2e/scalability/.keep | 1 +
test/e2e/upgrade/.keep | 1 +
test/e2e/web/.keep | 1 +
test/readme | 2 +
test/typecheck/typecheck_test.go | 4 +-
test/wrktest.sh | 12 +-
154 files changed, 3992 insertions(+), 900 deletions(-)
create mode 100644 .github/workflows/api-test.yml
create mode 100755 chat
create mode 100644 docs/contrib/README.md
rename docs/{conversions => contrib}/api.md (100%)
rename docs/{conversions/bash_log.md => contrib/bash-log.md} (100%)
rename docs/{conversions/cicd_actions.md => contrib/cicd-actions.md} (100%)
rename docs/contrib/{code_conventions.md => code-conventions.md} (52%)
rename docs/{conversions => contrib}/commit.md (100%)
rename docs/{conversions => contrib}/directory.md (100%)
rename docs/{conversions/error_code.md => contrib/error-code.md} (100%)
rename docs/contrib/{git_workflow.md => git-workflow.md} (100%)
rename docs/contrib/{git_cherry-pick.md => gitcherry-pick.md} (100%)
rename docs/{conversions => contrib}/github-workflow.md (99%)
rename docs/{conversions/go_code.md => contrib/go-code.md} (96%)
rename docs/{conversions => contrib}/images.md (100%)
rename docs/contrib/{init_config.md => init-config.md} (100%)
rename docs/contrib/{install_docker.md => install-docker.md} (100%)
rename docs/contrib/{linux_development.md => linux-development.md} (100%)
rename docs/contrib/{local_actions.md => local-actions.md} (100%)
rename docs/{conversions => contrib}/logging.md (100%)
rename docs/contrib/{protoc_tools.md => protoc-tools.md} (100%)
create mode 100644 docs/contrib/test.md
rename docs/contrib/{util_go.md => util-go.md} (100%)
rename docs/contrib/{util_makefile.md => util-makefile.md} (100%)
rename docs/contrib/{util_scripts.md => util-scripts.md} (100%)
rename docs/{conversions => contrib}/version.md (99%)
delete mode 100644 docs/conversions/README.md
delete mode 100644 internal/push/offlinepush/fcm/push_test.go
create mode 100644 pkg/common/cmd/msg_gateway_test.go
create mode 100644 pkg/common/config/parse_test.go
rename pkg/common/{discovery_register/k8s_discovery_register.go => discoveryregister/discoveryregister.go} (98%)
create mode 100644 pkg/common/discoveryregister/discoveryregister_test.go
rename pkg/common/{ginPrometheus/ginPrometheus.go => ginprometheus/ginprometheus.go} (99%)
create mode 100644 pkg/common/http/http_client_test.go
delete mode 100644 pkg/common/locker/doc.go
delete mode 100644 pkg/common/locker/message_locker.go
rename pkg/common/{prom_metrics/gin-api.go => prommetrics/gin_api.go} (91%)
rename pkg/common/{prom_metrics/grpc-auth.go => prommetrics/grpc_auth.go} (90%)
rename pkg/common/{prom_metrics/grpc-msg.go => prommetrics/grpc_msg.go} (97%)
rename pkg/common/{prom_metrics/grpc-msggateway.go => prommetrics/grpc_msggateway.go} (90%)
rename pkg/common/{prom_metrics => prommetrics}/grpc_push.go (92%)
rename pkg/common/{prom_metrics/func.go => prommetrics/prommetrics.go} (87%)
create mode 100644 pkg/common/prommetrics/prommetrics_test.go
rename pkg/common/{prom_metrics => prommetrics}/transfer.go (97%)
create mode 100644 pkg/common/startrpc/start_test.go
create mode 100644 pkg/msgprocessor/conversation_test.go
delete mode 100755 scripts/batch_start_all.sh
delete mode 100644 scripts/build.cmd
rename scripts/{create_topic.sh => create-topic.sh} (100%)
rename scripts/{init_pwd.sh => init-pwd.sh} (100%)
delete mode 100755 scripts/install_im_compose.sh
delete mode 100644 scripts/start.bat
create mode 100644 test/e2e/README.md
create mode 100644 test/e2e/api/.keep
create mode 100644 test/e2e/api/token/token.go
create mode 100644 test/e2e/api/user/curd.go
create mode 100644 test/e2e/api/user/user.go
create mode 100644 test/e2e/conformance/.keep
create mode 100644 test/e2e/e2e.go
create mode 100644 test/e2e/e2e_test.go
create mode 100644 test/e2e/framework/config/.keep
create mode 100644 test/e2e/framework/config/config.go
create mode 100644 test/e2e/framework/config/config_test.go
create mode 100644 test/e2e/framework/ginkgowrapper/.keep
create mode 100644 test/e2e/framework/ginkgowrapper/ginkgowrapper.go
create mode 100644 test/e2e/framework/ginkgowrapper/ginkgowrapper_test.go
create mode 100644 test/e2e/framework/helpers/.keep
create mode 100644 test/e2e/framework/helpers/chat/chat.go
create mode 100644 test/e2e/performance/.keep
create mode 100644 test/e2e/rpc/auth/.keep
create mode 100644 test/e2e/rpc/conversation/.keep
create mode 100644 test/e2e/rpc/friend/.keep
create mode 100644 test/e2e/rpc/group/.keep
create mode 100644 test/e2e/rpc/message/.keep
create mode 100644 test/e2e/scalability/.keep
create mode 100644 test/e2e/upgrade/.keep
create mode 100644 test/e2e/web/.keep
diff --git a/.env b/.env
index bcfc0722a..cc42fbcb7 100644
--- a/.env
+++ b/.env
@@ -29,8 +29,8 @@ PASSWORD=openIM123
MINIO_ENDPOINT=http://172.28.0.1:10005
# Base URL for the application programming interface (API).
-# Default: API_URL=http://172.28.0.1:10002
-API_URL=http://172.28.0.1:10002
+# Default: API_URL=http://172.0.0.1:10002
+API_URL=http://172.0.0.1:10002
# Directory path for storing data files or related information.
# Default: DATA_DIR=./
diff --git a/.github/.codecov.yml b/.github/.codecov.yml
index 9e262e0e3..fab584a31 100644
--- a/.github/.codecov.yml
+++ b/.github/.codecov.yml
@@ -20,4 +20,20 @@ coverage:
paths:
- pkg/* # only include coverage in "pkg/" folder
informational: true # Always pass check
- patch: off # disable the commit only checks
\ No newline at end of file
+ tools: # declare a new status context "tools"
+ paths:
+ - tools/* # only include coverage in "tools/" folder
+ informational: true # Always pass check
+ test: # declare a new status context "test"
+ paths:
+ - test/* # only include coverage in "test/" folder
+ informational: true # Always pass check
+ # internal: # declare a new status context "internal"
+ # paths:
+ # - internal/* # only include coverage in "internal/" folder
+ # informational: true # Always pass check
+ # cmd: # declare a new status context "cmd"
+ # paths:
+ # - cmd/* # only include coverage in "cmd/" folder
+ # informational: true # Always pass check
+ patch: off # disable the commit only checks
diff --git a/.github/workflows/api-test.yml b/.github/workflows/api-test.yml
new file mode 100644
index 000000000..0bbc86619
--- /dev/null
+++ b/.github/workflows/api-test.yml
@@ -0,0 +1,90 @@
+# 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.
+
+name: OpenIM API TEST
+
+on:
+ push:
+ branches:
+ - main
+ paths-ignore:
+ - "docs/**"
+ - "README.md"
+ - "README_zh-CN.md"
+ - "CONTRIBUTING.md"
+ pull_request:
+ branches:
+ - main
+ paths-ignore:
+ - "README.md"
+ - "README_zh-CN.md"
+ - "CONTRIBUTING.md"
+ - "docs/**"
+
+env:
+ GO_VERSION: "1.19"
+ GOLANGCI_VERSION: "v1.50.1"
+
+jobs:
+ execute-linux-systemd-scripts:
+ name: Execute OpenIM script on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ environment:
+ name: openim
+ strategy:
+ matrix:
+ go_version: ["1.20"]
+ os: ["ubuntu-latest"]
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Go ${{ matrix.go_version }}
+ uses: actions/setup-go@v4
+ with:
+ go-version: ${{ matrix.go_version }}
+ id: go
+
+ - name: Install Task
+ uses: arduino/setup-task@v1
+ with:
+ version: '3.x' # If available, use the latest major version that's compatible
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Docker Operations
+ run: |
+ curl -o docker-compose.yml https://raw.githubusercontent.com/OpenIMSDK/openim-docker/main/example/basic-openim-server-dependency.yml
+ sudo docker compose up -d
+ sudo sleep 60
+
+ - name: Module Operations
+ run: |
+ sudo make tidy
+ sudo make tools.verify.go-gitlint
+
+ - name: Build, Start, Check Services and Print Logs
+ run: |
+ sudo ./scripts/install/install.sh -i && \
+ sudo ./scripts/install/install.sh -s && \
+ (echo "An error occurred, printing logs:" && sudo cat ./_output/logs/* 2>/dev/null)
+
+ - name: Run Test
+ run: |
+ sudo make test-api && \
+ (echo "An error occurred, printing logs:" && sudo cat ./_output/logs/* 2>/dev/null)
+
+ - name: Stop Services
+ run: |
+ sudo ./scripts/install/install.sh -u && \
+ (echo "An error occurred, printing logs:" && sudo cat ./_output/logs/* 2>/dev/null)
\ No newline at end of file
diff --git a/.github/workflows/create_branch_on_tag.yml b/.github/workflows/create_branch_on_tag.yml
index 0d8b0d91a..b6b9eb8e8 100644
--- a/.github/workflows/create_branch_on_tag.yml
+++ b/.github/workflows/create_branch_on_tag.yml
@@ -1,3 +1,17 @@
+# 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.
+
name: Create Branch on Tag
on:
diff --git a/.github/workflows/e2e-test.yml b/.github/workflows/e2e-test.yml
index 1e5949edf..535c30f75 100644
--- a/.github/workflows/e2e-test.yml
+++ b/.github/workflows/e2e-test.yml
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-name: OpenIM E2E Test
+name: OpenIM Linux System E2E Test
on:
workflow_dispatch:
@@ -41,4 +41,60 @@ jobs:
- name: Create e2e test
run: |
- echo "...test e2e"
\ No newline at end of file
+ echo "...test e2e"
+
+ execute-linux-systemd-scripts:
+ name: Execute OpenIM script on ${{ matrix.os }}
+ runs-on: ${{ matrix.os }}
+ environment:
+ name: openim
+ strategy:
+ matrix:
+ go_version: ["1.20"]
+ os: ["ubuntu-latest"]
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up Go ${{ matrix.go_version }}
+ uses: actions/setup-go@v4
+ with:
+ go-version: ${{ matrix.go_version }}
+ id: go
+
+ - name: Install Task
+ uses: arduino/setup-task@v1
+ with:
+ version: '3.x' # If available, use the latest major version that's compatible
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+
+ - name: Docker Operations
+ run: |
+ curl -o docker-compose.yml https://raw.githubusercontent.com/OpenIMSDK/openim-docker/main/example/basic-openim-server-dependency.yml
+ sudo docker compose up -d
+ sudo sleep 60
+
+ - name: Module Operations
+ run: |
+ sudo make tidy
+ sudo make tools.verify.go-gitlint
+
+ - name: Build, Start
+ run: |
+ sudo ./scripts/install/install.sh -i
+
+ - name: Exec OpenIM System Status Chack
+ run: |
+ sudo ./scripts/install/install.sh -s
+
+ - name: Exec OpenIM API test
+ run: |
+ sudo make test-api
+
+ - name: Exec OpenIM E2E test
+ run: |
+ sudo make test-e2e
+
+ - name: Exec OpenIM System uninstall
+ run: |
+ sudo ./scripts/install/install.sh -u
\ No newline at end of file
diff --git a/.github/workflows/openimci.yml b/.github/workflows/openimci.yml
index 05a8ce5c5..2e4cc9e24 100644
--- a/.github/workflows/openimci.yml
+++ b/.github/workflows/openimci.yml
@@ -64,7 +64,8 @@ jobs:
- name: Install Task
uses: arduino/setup-task@v1
with:
- version: 2.x
+ version: '3.x' # If available, use the latest major version that's compatible
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Module Operations
run: |
@@ -122,7 +123,8 @@ jobs:
- name: Install Task
uses: arduino/setup-task@v1
with:
- version: 2.x
+ version: '3.x' # If available, use the latest major version that's compatible
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Run OpenIM make install start
run: |
sudo make install
@@ -145,10 +147,13 @@ jobs:
with:
go-version: ${{ matrix.go_version }}
id: go
+
- name: Install Task
uses: arduino/setup-task@v1
with:
- version: 2.x
+ version: '3.x' # If available, use the latest major version that's compatible
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+
- name: Docker Operations
run: |
curl -o docker-compose.yml https://raw.githubusercontent.com/OpenIMSDK/openim-docker/main/example/basic-openim-server-dependency.yml
@@ -160,18 +165,15 @@ jobs:
sudo make tidy
sudo make tools.verify.go-gitlint
- - name: Build, Start and Check Services
+ - name: Build, Start, Check Services and Print Logs
run: |
- sudo make init
- sudo make build
- sudo make start
- sudo make check
-
- - name: Print OpenIM Logs
- run: sudo cat ./_output/logs/* 2>/dev/null
- continue-on-error: true
+ sudo make init && \
+ sudo make build && \
+ sudo make start && \
+ sudo make check || \
+ (echo "An error occurred, printing logs:" && sudo cat ./_output/logs/* 2>/dev/null)
- openim-build-image:
+ openim-test-build-image:
name: Build OpenIM Docker Image
runs-on: ubuntu-latest
environment:
@@ -184,10 +186,13 @@ jobs:
with:
go-version: ${{ matrix.go_version }}
id: go
+
- name: Install Task
uses: arduino/setup-task@v1
with:
- version: 2.x
+ version: '3.x' # If available, use the latest major version that's compatible
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+
- name: Test Docker Build
run: |
sudo make image
\ No newline at end of file
diff --git a/CHANGELOG/CHANGELOG.md b/CHANGELOG/CHANGELOG.md
index 2358b5fb8..f5d62ab24 100644
--- a/CHANGELOG/CHANGELOG.md
+++ b/CHANGELOG/CHANGELOG.md
@@ -20,7 +20,7 @@ All notable changes to this project will be documented in this file.
## OpenIM versioning policy
-+ [OpenIM Version](../docs/conversions/version.md)
++ [OpenIM Version](../docs/contrib/version.md)
## command
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index afa8b35d4..ee275e7ad 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -42,6 +42,12 @@ If you are familiar with [Makefile](./Makefile) , you can easily see the clever
The [Makefile](./Makefile) is for every developer, even if you don't know how to use the Makefile tool, don't worry, we provide two great commands to get you up to speed with the Makefile architecture, `make help` and `make help-all`, it can reduce problems of the developing environment.
+In accordance with the naming conventions adopted by OpenIM and drawing reference from the Google Naming Conventions as per the guidelines available at https://google.github.io/styleguide/go/, the following expectations for naming practices within the project are set forth:
+
++ https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/code-conventions.md
++ https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/go-code.md
+
+
## Code of ConductCode of Conduct
#### Code and doc contribution
@@ -270,7 +276,7 @@ You can find some very formal PR in [RFC](https://github.com/openimsdk/open-im-s
**🈴 Reviewing PRs:**
+ Be respectful and constructive
-+ Assign yourself to the PR
++ Assign yourself to the PR (comment `/assign`)
+ Check if all checks are passing
+ Suggest changes instead of simply commenting on found issues
+ If you are unsure about something, ask the author
diff --git a/Makefile b/Makefile
index 67f595fb4..1941bde6c 100644
--- a/Makefile
+++ b/Makefile
@@ -6,7 +6,7 @@
## all: Run tidy, gen, add-copyright, format, lint, cover, build ✨
.PHONY: all
-all: tidy gen add-copyright verify lint cover restart
+all: tidy gen add-copyright verify test-api lint cover restart
# ==============================================================================
# Build set
@@ -166,6 +166,15 @@ test:
cover:
@$(MAKE) go.test.cover
+## test-api: Run api test. ✨
+.PHONY: test-api
+test-api:
+ @$(MAKE) go.test.api
+
+## test-e2e: Run e2e test
+test-e2e:
+ @$(MAKE) go.test.e2e
+
## updates: Check for updates to go.mod dependencies. ✨
.PHONY: updates
@$(MAKE) go.updates
diff --git a/README-zh_CN.md b/README-zh_CN.md
index db1e8beab..6dd264342 100644
--- a/README-zh_CN.md
+++ b/README-zh_CN.md
@@ -90,22 +90,22 @@ OpenIM 我们的目标是建立一个顶级的开源社区。我们有一套标
在开始之前,请确保你的更改是有需求的。最好的方法是创建一个[新的讨论](https://github.com/openimsdk/open-im-server/discussions/new/choose) 或 [Slack 通信](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q),或者如果你发现一个问题,首先[报告它](https://github.com/openimsdk/open-im-server/issues/new/choose)。
-+ [代码标准](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/go_code.md)
++ [代码标准](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/go-code.md)
-+ [Docker 镜像标准](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/images.md)
++ [Docker 镜像标准](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/images.md)
-+ [目录标准](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/directory.md)
++ [目录标准](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/directory.md)
-+ [提交标准](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/commit.md)
++ [提交标准](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/commit.md)
-+ [版本控制标准](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/version.md)
++ [版本控制标准](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/version.md)
-+ [接口标准](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/interface.md)
++ [接口标准](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/interface.md)
+ [OpenIM配置和环境变量设置](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/environment.md)
> **Note**
-> 针对中国的用户,阅读我们的 [Docker 镜像标准](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/images.md) 以便使用国内 aliyun 的镜像地址。OpenIM 也有针对中国的 gitee 同步仓库,你可以在 [gitee.com](https://gitee.com/openimsdk) 上找到它。
+> 针对中国的用户,阅读我们的 [Docker 镜像标准](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/images.md) 以便使用国内 aliyun 的镜像地址。OpenIM 也有针对中国的 gitee 同步仓库,你可以在 [gitee.com](https://gitee.com/openimsdk) 上找到它。
## :link: 链接
diff --git a/README.md b/README.md
index 0a569af6e..722de0240 100644
--- a/README.md
+++ b/README.md
@@ -4,31 +4,28 @@
-
- ⭐️ Open source Instant Messaging Server ⭐️
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+[](https://github.com/openimsdk/open-im-server/stargazers)
+[](https://github.com/openimsdk/open-im-server/network/members)
+[](https://app.codecov.io/gh/openimsdk/open-im-server)
+[](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
+[](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
+[](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
+[](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)
+[](https://www.bestpractices.dev/projects/8045)
+[](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
+[](https://golang.org/)
+
+[**English**](./README.md) •
+[**简体中文**](./README-zh_CN.md) •
+[**Docs**](https://openim.io/en)
-
- English •
- 简体中文 •
- Docs
-
+
+
## Ⓜ️ About OpenIM
OpenIM isn't just an open-source instant messaging component, it's an integral part of your application ecosystem. Check out this diagram to understand how AppServer, AppClient, OpenIMServer, and OpenIMSDK interact.
@@ -118,7 +115,7 @@ It is recommended to use Docker Compose for deployment, which can easily and qui
> **Note**
>
-> If you don't know OpenIM's versioning policy, 📚Read our release policy: https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/version.md
+> If you don't know OpenIM's versioning policy, 📚Read our release policy: https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/version.md
@@ -133,7 +130,7 @@ Ur need `Go 1.20` or higher version, and `make`.
go version && make --version || echo "Error: One of the commands failed."
```
-Version Details: https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/version.md
+Version Details: https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/version.md
You can get the version number from the command below or from [github releases](https://github.com/openimsdk/open-im-server/tags).
@@ -141,7 +138,7 @@ You can get the version number from the command below or from [github releases](
$ curl --silent "https://api.github.com/repos/openimsdk/open-im-server/releases" | jq -r '.[].tag_name'
```
-We have our own version management policy, if you are interested in our version management, I recommend reading [📚 OpenIM Version](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/version.md), We recommend using stable versions such as `v3.3.0` and `v3.2.0` whenever possible. `v3.1.1-alpha.3` as well as `v3.3.0-beta.0` and `v3.2.0-rc.0` are pre-release or beta versions and are not recommended.
+We have our own version management policy, if you are interested in our version management, I recommend reading [📚 OpenIM Version](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/version.md), We recommend using stable versions such as `v3.3.0` and `v3.2.0` whenever possible. `v3.1.1-alpha.3` as well as `v3.3.0-beta.0` and `v3.2.0-rc.0` are pre-release or beta versions and are not recommended.
Set `OPENIM_VERSION` environment variables for the latest `OPENIM_VERSION` number, or replace the `OPENIM_VERSION` for you to install the OpenIM-Server `OPENIM_VERSION`:
@@ -199,21 +196,41 @@ Delve into the heart of Open-IM-Server's functionality with our architecture dia
## :hammer_and_wrench: To start developing OpenIM
+[](https://vscode.dev/github/openimsdk/open-im-server)
+
OpenIM Our goal is to build a top-level open source community. We have a set of standards, in the [Community repository](https://github.com/OpenIMSDK/community).
If you'd like to contribute to this Open-IM-Server repository, please read our [contributor documentation](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
Before you start, please make sure your changes are in demand. The best for that is to create a [new discussion](https://github.com/openimsdk/open-im-server/discussions/new/choose) OR [Slack Communication](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), or if you find an issue, [report it](https://github.com/openimsdk/open-im-server/issues/new/choose) first.
-- [Code Standards](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/go_code.md)
-- [Docker Images Standards](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/images.md)
-- [Directory Standards](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/directory.md)
-- [Commit Standards](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/commit.md)
-- [Versioning Standards](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/version.md)
-- [Interface Standards](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/api.md)
-- [Log Standards](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/logging.md)
-- [Error Code Standards](https://github.com/openimsdk/open-im-server/blob/main/docs/conversions/error_code.md)
-- [OpenIM configuration and environment variable Settings](https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/environment.md)
+- [OpenIM API Reference](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
+- [OpenIM Bash Logging](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
+- [OpenIM CI/CD Actions](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/cicd-actions.md)
+- [OpenIM Code Conventions](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/code-conventions.md)
+- [OpenIM Commit Guidelines](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/commit.md)
+- [OpenIM Development Guide](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/development.md)
+- [OpenIM Directory Structure](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/directory.md)
+- [OpenIM Environment Setup](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/environment.md)
+- [OpenIM Error Code Reference](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/error-code.md)
+- [OpenIM Git Workflow](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/git-workflow.md)
+- [OpenIM Git Cherry Pick Guide](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/gitcherry-pick.md)
+- [OpenIM GitHub Workflow](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/github-workflow.md)
+- [OpenIM Go Code Standards](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/go-code.md)
+- [OpenIM Image Guidelines](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/images.md)
+- [OpenIM Initial Configuration](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/init-config.md)
+- [OpenIM Docker Installation Guide](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/install-docker.md)
+- [OpenIM OpenIM Linux System Installation](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/install-openim-linux-system.md)
+- [OpenIM Linux Development Guide](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/linux-development.md)
+- [OpenIM Local Actions Guide](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/local-actions.md)
+- [OpenIM Logging Conventions](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/logging.md)
+- [OpenIM Offline Deployment](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/offline-deployment.md)
+- [OpenIM Protoc Tools](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/protoc-tools.md)
+- [OpenIM Testing Guide](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/test.md)
+- [OpenIM Utility Go](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-go.md)
+- [OpenIM Makefile Utilities](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-makefile.md)
+- [OpenIM Script Utilities](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-scripts.md)
+- [OpenIM Versioning](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/version.md)
## :busts_in_silhouette: Community
diff --git a/build/images/Dockerfile b/build/images/Dockerfile
index 518de78b1..51fe94e1c 100644
--- a/build/images/Dockerfile
+++ b/build/images/Dockerfile
@@ -1,3 +1,17 @@
+# 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.
+
FROM BASE_IMAGE
WORKDIR ${SERVER_WORKDIR}
diff --git a/chat b/chat
new file mode 100755
index 0000000000000000000000000000000000000000..b79e06dd7fc98898f96d053443784f8a4e760d9b
GIT binary patch
literal 7068773
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
z
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
@DI=u^R(r!Zg*Ql!YX2B992D|d
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