Compare commits

...

40 Commits
main ... v3.8.2

Author SHA1 Message Date
Monet Lee
c328d39cae
Merge pull request #2889 from openimsdk/cherry-pick-cbade46
deps: Merge  #2881 #2883 #2884 #2885 PRs into release-v3.8.2
2024-11-25 18:35:19 +08:00
icey-yu
7d517970ec fix: webhookBeforeSendSingleMsg will call before black and friend check (#2885) 2024-11-25 10:34:50 +00:00
icey-yu
d8afbb82fc fix: webhookAfterSingleMsgRead (#2884) 2024-11-25 10:34:50 +00:00
icey-yu
3e220a3519 revert: write msg to redis (#2883) 2024-11-25 10:34:50 +00:00
Morya
3a30479b73 fix: minor log typo (#2881) 2024-11-25 10:34:50 +00:00
OpenIM-Robot
687b2ebc07
fix: err (#2876) (#2878)
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
2024-11-22 17:52:57 +08:00
Monet Lee
23966f3155
build: update version to v3.8.2 (#2877) 2024-11-22 17:50:49 +08:00
Monet Lee
674b288654
Merge pull request #2873 from openimsdk/cherry-pick-7f44319
deps: Merge  #2712 #2794 #2811 #2813 #2815 #2822 #2825 #2826 #2836 #2837 #2838 #2842 #2844 #2850 #2851 #2856 #2858 #2861 #2862 #2866 #2869 #2871 PRs into pre-release-v3.8.2
2024-11-22 16:33:57 +08:00
Monet Lee
a4287309ae fix error 2024-11-22 16:32:44 +08:00
Monet Lee
ce140beddc fix: fix error. 2024-11-22 16:25:38 +08:00
icey-yu
0e07ad70c3 fix: admin token limit (#2871) 2024-11-22 08:08:05 +00:00
Monet Lee
c9e2f7d375 build: create changelog tool and workflows. (#2869) 2024-11-22 08:08:05 +00:00
chao
1e749b6217 fix: concurrent write to websocket connection (#2866)
* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: implement no gob encoder.

* update unitTest content.

* Update hub_server.go

* feat: GroupApplicationAgreeMemberEnterNotification

* fix: encoder replace to json encoder.

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* merge:  merge main code into js branch. (#2648)

* feat: update group notification when set to null. (#2590)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* feat: update group notification when set to null.

* update log standard.

* feat: add long time push msg in prometheus (#2584)

* feat: add long time push msg in prometheus

* fix: log print

* fix: go mod

* fix: log msg

* fix: log init

* feat: push msg

* feat: go mod ,remove cgo package

* feat: remove error log

* feat: test dummy push

* feat:redis pool config

* feat: push to kafka log

* feat: supports getting messages based on session ID and seq (#2582)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: implement request batch count limit. (#2591)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* fix: getting messages based on session ID and seq (#2595)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: avoid pulling messages from sessions with a large number of max seq values of 0 (#2602)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* refactor: improve db structure in `storage/controller` (#2604)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* refactor: improve db structure in `storage/controller`

* feat: implement offline push using kafka (#2600)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* feat: implement offline push.

* feat: implement batch Push spilt

* update go mod

* feat: implement kafka producer and consumer.

* update format,

* add PushMQ log.

* feat: update Handler logic.

* update MQ logic.

* update

* update

* fix: update OfflinePushConsumerHandler.

* feat: API supports gzip (#2609)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* Fix err (#2608)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* feat: add rocksTimeout

* feat: wrap logs

* feat: add logs

* feat: listen config

* feat: enable listen TIME_WAIT port

* feat: add logs

* feat: cache batch

* chore: enable fullUserCache

* feat: push rpc num

* feat: push err

* feat: with operationID

* feat: sleep

* feat: change 1s

* feat: change log

* feat: implement Getbatch in rpcCache.

* feat: print getOnline cost

* feat: change log

* feat: change kafka and push config

* feat: del interface

* feat: fix err

* feat: change config

* feat: go mod

* feat: change config

* feat: change config

* feat: add sleep in push

* feat: warn logs

* feat: logs

* feat: logs

* feat: change port

* feat: start config

* feat: remove port reuse

* feat: prometheus config

* feat: prometheus config

* feat: prometheus config

* feat: add long time send msg to grafana

* feat: init

* feat: init

* feat: implement offline push.

* feat: batch get user online

* feat: implement batch Push spilt

* update go mod

* Revert "feat: change port"

This reverts commit 06d5e944

* feat: change port

* feat: change config

* feat: implement kafka producer and consumer.

* update format,

* add PushMQ log.

* feat: get all online users and init push

* feat: lock in online cache

* feat: config

* fix: init online status

* fix: add logs

* fix: userIDs

* fix: add logs

* feat: update Handler logic.

* update MQ logic.

* update

* update

* fix: method name

* fix: update OfflinePushConsumerHandler.

* fix: prommetrics

* fix: add logs

* fix: ctx

* fix: log

* fix: config

* feat: change port

* fix: atomic online cache status

---------

Co-authored-by: Monet Lee <monet_lee@163.com>

* feature: add GetConversationsHasReadAndMaxSeq interface to the WebSocket API. (#2611)

* fix: lru lock (#2613)

* fix: lru lock

* fix: lru lock

* fix: lru lock

* fix: nil pointer error on close (#2618)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: create group can push notification (#2617)

* fix: blockage caused by listen error (#2620)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: go.mod (#2621)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: improve searchMsg implement. (#2614)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* remove unused script.

* feat: improve searchMsg implement.

* update mongo config.

* Fix lock (#2622)

* fix:log

* fix: lock

* fix: update setGroupInfoEX field name. (#2625)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* fix: update setGroupInfoEX field name.

* fix: update setGroupInfoEX field name (#2626)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* fix: update setGroupInfoEX field name.

* fix: update setGroupInfoEX field name

* feat: msg gateway add log (#2631)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: update setGroupInfoEx func name and field. (#2634)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* fix: update setGroupInfoEx func name and field.

* refactor: update groupinfoEx field.

* refactor: update database name in mongodb.yml

* add groupName Condition

* fix: fix setConversations req fill. (#2645)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* fix: fix setConversations req fill.

* fix: GetMsgBySeqs boundary issues (#2647)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: the attribute version is obsolete, remove it (#2644)

* refactor: update Userregister request field. (#2650)

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: chao <48119764+withchao@users.noreply.github.com>
Co-authored-by: withchao <withchao@users.noreply.github.com>
Co-authored-by: 蔡相跃 <caixiangyue007@gmail.com>

* update go mod

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* merge: update code from main to v3.8-js-sdk-only. (#2720)

* fix: fix update groupName invalid. (#2673)

* refactor: change platform to platformID (#2670)

* feat: don`t return nil data (#2675)

Co-authored-by: Monet Lee <monet_lee@163.com>

* refactor: update fields type in userStatus and check registered. (#2676)

* fix: usertoken auth. (#2677)

* refactor: update fields type in userStatus and check registered.

* fix: usertoken auth.

* update contents.

* update content.

* update

* fix

* update pb file.

* feat: add friend agree after callback (#2680)

* fix: sn not sort (#2682)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* refactor: add GetAdminToken interface. (#2684)

* refactor: add GetAdminToken interface.

* update config.

* fix: admin token (#2686)

* fix: update workflows logic. (#2688)

* refactor: add GetAdminToken interface.

* update config.

* update workflows logic.

* fix: admin token (#2687)

* update the front image (#2692)

* update the front image

* update version

* feat: improve publish docker image workflows (#2697)

* refactor: add GetAdminToken interface.

* update config.

* update workflows logic.

* feat: improve publish docker image workflows

* update condition logic.

* fix: update load file logic. (#2700)

* refactor: add GetAdminToken interface.

* update config.

* update workflows logic.

* feat: improve publish docker image workflows

* update condition logic.

* fix: update load file logic.

* feat: Msg filter (#2703)

* feat: msg filter

* feat: msg filter

* feat: msg filter

* feat: provide the interface required by js sdk (#2712)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* Line webhook (#2716)

* feat: online and offline webhook

* feat: online and offline webhook

* feat: remove zk

* fix: the message I sent is not set to read seq in mongodb (#2718)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: cannot modify group member avatars (#2719)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: chao <48119764+withchao@users.noreply.github.com>
Co-authored-by: withchao <withchao@users.noreply.github.com>
Co-authored-by: skiffer-git <72860476+skiffer-git@users.noreply.github.com>

* fix: MemberEnterNotification

* fix: MemberEnterNotification

* fix: MsgData status

* merge

* merge: update code from main to v3.8-js-sdk-only.  (#2818)

* feat: implement merge milestone PR to target-branch. (#2796)

* build: improve workflows logic. (#2801)

* fix: improve time condition check mehtod. (#2804)

* fix: improve time condition check mehtod.

* fix

* fix: webhook before online push (#2805)

* fix: set own read seq in MongoDB when sender send a message. (#2808)

* fix: solve err Notification when setGroupInfo. (#2806)

* fix: solve err Notification when setGroupInfo.

* build: update checkout version.

* fix: update notification contents.

* Introducing OpenIM Guru on Gurubase.io (#2788)

* feat: support app update service (#2811)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* fix: MemberEnterNotification

* fix: MemberEnterNotification

* fix: MsgData status

* feat: add ApplicationVersion

* feat: add ApplicationVersion

* feat: add ApplicationVersion

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: ApplicationVersion move chat (#2813)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* fix: MemberEnterNotification

* fix: MemberEnterNotification

* fix: MsgData status

* feat: add ApplicationVersion

* feat: ApplicationVersion move chat

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: improve condition check. (#2815)

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: Kürşat Aktaş <kursat.ce@gmail.com>
Co-authored-by: chao <48119764+withchao@users.noreply.github.com>
Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: support text ping pong

* feat: support text ping pong

* feat: gob json encoder

* feat: gob json encoder

* feat: gob json encoder

* feat: gob json encoder

* feat: gob json encoder

* feat: gob json encoder

* fix: concurrent write to websocket connection

* fix: concurrent write to websocket connection

* fix: concurrent write to websocket connection

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>
Co-authored-by: Monet Lee <monet_lee@163.com>
Co-authored-by: OpenIM-Gordon <46924906+FGadvancer@users.noreply.github.com>
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: 蔡相跃 <caixiangyue007@gmail.com>
Co-authored-by: skiffer-git <72860476+skiffer-git@users.noreply.github.com>
Co-authored-by: Kürşat Aktaş <kursat.ce@gmail.com>
2024-11-22 08:08:05 +00:00
Sasaya
4698446050 fix: single chat offline push webhook (#2862) 2024-11-22 08:08:05 +00:00
Wiky Lyu
624ae99a12 fix #2860 migrate jpns to jpush (#2861) 2024-11-22 08:08:05 +00:00
Monet Lee
453c426ab5 build: update mongo and kafka start logic. (#2858)
* build: update mongo and kafka start logic.

* build: update go version image in dockerfile.

* build: remove zookeeper image.

* add authSource comment.

* update tools version.

* add created sucess print.

* remove unused script.

* format.
2024-11-22 08:08:05 +00:00
chao
0266dc830d feat: merge js sdk (#2856)
* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: implement no gob encoder.

* update unitTest content.

* Update hub_server.go

* feat: GroupApplicationAgreeMemberEnterNotification

* fix: encoder replace to json encoder.

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* merge:  merge main code into js branch. (#2648)

* feat: update group notification when set to null. (#2590)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* feat: update group notification when set to null.

* update log standard.

* feat: add long time push msg in prometheus (#2584)

* feat: add long time push msg in prometheus

* fix: log print

* fix: go mod

* fix: log msg

* fix: log init

* feat: push msg

* feat: go mod ,remove cgo package

* feat: remove error log

* feat: test dummy push

* feat:redis pool config

* feat: push to kafka log

* feat: supports getting messages based on session ID and seq (#2582)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: implement request batch count limit. (#2591)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* fix: getting messages based on session ID and seq (#2595)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: avoid pulling messages from sessions with a large number of max seq values of 0 (#2602)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* refactor: improve db structure in `storage/controller` (#2604)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* refactor: improve db structure in `storage/controller`

* feat: implement offline push using kafka (#2600)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* feat: implement offline push.

* feat: implement batch Push spilt

* update go mod

* feat: implement kafka producer and consumer.

* update format,

* add PushMQ log.

* feat: update Handler logic.

* update MQ logic.

* update

* update

* fix: update OfflinePushConsumerHandler.

* feat: API supports gzip (#2609)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* Fix err (#2608)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* feat: add rocksTimeout

* feat: wrap logs

* feat: add logs

* feat: listen config

* feat: enable listen TIME_WAIT port

* feat: add logs

* feat: cache batch

* chore: enable fullUserCache

* feat: push rpc num

* feat: push err

* feat: with operationID

* feat: sleep

* feat: change 1s

* feat: change log

* feat: implement Getbatch in rpcCache.

* feat: print getOnline cost

* feat: change log

* feat: change kafka and push config

* feat: del interface

* feat: fix err

* feat: change config

* feat: go mod

* feat: change config

* feat: change config

* feat: add sleep in push

* feat: warn logs

* feat: logs

* feat: logs

* feat: change port

* feat: start config

* feat: remove port reuse

* feat: prometheus config

* feat: prometheus config

* feat: prometheus config

* feat: add long time send msg to grafana

* feat: init

* feat: init

* feat: implement offline push.

* feat: batch get user online

* feat: implement batch Push spilt

* update go mod

* Revert "feat: change port"

This reverts commit 06d5e944

* feat: change port

* feat: change config

* feat: implement kafka producer and consumer.

* update format,

* add PushMQ log.

* feat: get all online users and init push

* feat: lock in online cache

* feat: config

* fix: init online status

* fix: add logs

* fix: userIDs

* fix: add logs

* feat: update Handler logic.

* update MQ logic.

* update

* update

* fix: method name

* fix: update OfflinePushConsumerHandler.

* fix: prommetrics

* fix: add logs

* fix: ctx

* fix: log

* fix: config

* feat: change port

* fix: atomic online cache status

---------

Co-authored-by: Monet Lee <monet_lee@163.com>

* feature: add GetConversationsHasReadAndMaxSeq interface to the WebSocket API. (#2611)

* fix: lru lock (#2613)

* fix: lru lock

* fix: lru lock

* fix: lru lock

* fix: nil pointer error on close (#2618)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: create group can push notification (#2617)

* fix: blockage caused by listen error (#2620)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: go.mod (#2621)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: improve searchMsg implement. (#2614)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* remove unused script.

* feat: improve searchMsg implement.

* update mongo config.

* Fix lock (#2622)

* fix:log

* fix: lock

* fix: update setGroupInfoEX field name. (#2625)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* fix: update setGroupInfoEX field name.

* fix: update setGroupInfoEX field name (#2626)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* fix: update setGroupInfoEX field name.

* fix: update setGroupInfoEX field name

* feat: msg gateway add log (#2631)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: update setGroupInfoEx func name and field. (#2634)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* fix: update setGroupInfoEx func name and field.

* refactor: update groupinfoEx field.

* refactor: update database name in mongodb.yml

* add groupName Condition

* fix: fix setConversations req fill. (#2645)

* refactor: refactor workflows contents.

* add tool workflows.

* update field.

* fix: remove chat error.

* Fix err.

* fix error.

* remove cn comment.

* update workflows files.

* update infra config.

* move workflows.

* feat: update bot.

* fix: solve uncorrect outdated msg get.

* update get docIDs logic.

* update

* update skip logic.

* fix

* update.

* fix: delay deleteObject func.

* remove unused content.

* update log type.

* feat: implement request batch count limit.

* update

* update

* fix: fix setConversations req fill.

* fix: GetMsgBySeqs boundary issues (#2647)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: the attribute version is obsolete, remove it (#2644)

* refactor: update Userregister request field. (#2650)

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: chao <48119764+withchao@users.noreply.github.com>
Co-authored-by: withchao <withchao@users.noreply.github.com>
Co-authored-by: 蔡相跃 <caixiangyue007@gmail.com>

* update go mod

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* merge: update code from main to v3.8-js-sdk-only. (#2720)

* fix: fix update groupName invalid. (#2673)

* refactor: change platform to platformID (#2670)

* feat: don`t return nil data (#2675)

Co-authored-by: Monet Lee <monet_lee@163.com>

* refactor: update fields type in userStatus and check registered. (#2676)

* fix: usertoken auth. (#2677)

* refactor: update fields type in userStatus and check registered.

* fix: usertoken auth.

* update contents.

* update content.

* update

* fix

* update pb file.

* feat: add friend agree after callback (#2680)

* fix: sn not sort (#2682)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* refactor: add GetAdminToken interface. (#2684)

* refactor: add GetAdminToken interface.

* update config.

* fix: admin token (#2686)

* fix: update workflows logic. (#2688)

* refactor: add GetAdminToken interface.

* update config.

* update workflows logic.

* fix: admin token (#2687)

* update the front image (#2692)

* update the front image

* update version

* feat: improve publish docker image workflows (#2697)

* refactor: add GetAdminToken interface.

* update config.

* update workflows logic.

* feat: improve publish docker image workflows

* update condition logic.

* fix: update load file logic. (#2700)

* refactor: add GetAdminToken interface.

* update config.

* update workflows logic.

* feat: improve publish docker image workflows

* update condition logic.

* fix: update load file logic.

* feat: Msg filter (#2703)

* feat: msg filter

* feat: msg filter

* feat: msg filter

* feat: provide the interface required by js sdk (#2712)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* Line webhook (#2716)

* feat: online and offline webhook

* feat: online and offline webhook

* feat: remove zk

* fix: the message I sent is not set to read seq in mongodb (#2718)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: cannot modify group member avatars (#2719)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: chao <48119764+withchao@users.noreply.github.com>
Co-authored-by: withchao <withchao@users.noreply.github.com>
Co-authored-by: skiffer-git <72860476+skiffer-git@users.noreply.github.com>

* fix: MemberEnterNotification

* fix: MemberEnterNotification

* fix: MsgData status

* merge

* merge: update code from main to v3.8-js-sdk-only.  (#2818)

* feat: implement merge milestone PR to target-branch. (#2796)

* build: improve workflows logic. (#2801)

* fix: improve time condition check mehtod. (#2804)

* fix: improve time condition check mehtod.

* fix

* fix: webhook before online push (#2805)

* fix: set own read seq in MongoDB when sender send a message. (#2808)

* fix: solve err Notification when setGroupInfo. (#2806)

* fix: solve err Notification when setGroupInfo.

* build: update checkout version.

* fix: update notification contents.

* Introducing OpenIM Guru on Gurubase.io (#2788)

* feat: support app update service (#2811)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* fix: MemberEnterNotification

* fix: MemberEnterNotification

* fix: MsgData status

* feat: add ApplicationVersion

* feat: add ApplicationVersion

* feat: add ApplicationVersion

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: ApplicationVersion move chat (#2813)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* fix: MemberEnterNotification

* fix: MemberEnterNotification

* fix: MsgData status

* feat: add ApplicationVersion

* feat: ApplicationVersion move chat

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: improve condition check. (#2815)

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: Kürşat Aktaş <kursat.ce@gmail.com>
Co-authored-by: chao <48119764+withchao@users.noreply.github.com>
Co-authored-by: withchao <withchao@users.noreply.github.com>

* feat: support text ping pong

* feat: support text ping pong

* feat: gob json encoder

* feat: gob json encoder

* feat: gob json encoder

* feat: gob json encoder

* feat: gob json encoder

* feat: gob json encoder

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>
Co-authored-by: Monet Lee <monet_lee@163.com>
Co-authored-by: OpenIM-Gordon <46924906+FGadvancer@users.noreply.github.com>
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: 蔡相跃 <caixiangyue007@gmail.com>
Co-authored-by: skiffer-git <72860476+skiffer-git@users.noreply.github.com>
Co-authored-by: Kürşat Aktaş <kursat.ce@gmail.com>
2024-11-22 08:08:05 +00:00
dependabot[bot]
9490d8f8ee build(deps): bump github.com/golang-jwt/jwt/v4 from 4.5.0 to 4.5.1 (#2851)
Bumps [github.com/golang-jwt/jwt/v4](https://github.com/golang-jwt/jwt) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/golang-jwt/jwt/releases)
- [Changelog](https://github.com/golang-jwt/jwt/blob/main/VERSION_HISTORY.md)
- [Commits](https://github.com/golang-jwt/jwt/compare/v4.5.0...v4.5.1)

---
updated-dependencies:
- dependency-name: github.com/golang-jwt/jwt/v4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-22 08:08:05 +00:00
icey-yu
0d84190ed6 feat: Print Panic Log (#2850)
* feat: catch panic

* feat: docker file

* feat: cicd

* feat: dockerfile

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
2024-11-22 08:08:05 +00:00
yoyoIU
5089568004 fix(push): push content with jpush (#2844)
* fix(push): push content with jpush

* docs: fix push enable example value

* fix(push): jpush error response
2024-11-22 08:08:05 +00:00
icey-yu
9e4cad1815 fix: get group return repeated result (#2842) 2024-11-22 08:08:05 +00:00
icey-yu
d4d626606b fix: SetConversations can update new conversation (#2838) 2024-11-22 08:08:05 +00:00
Monet Lee
058eeaefd0 fix: move workflow to correct path (#2837)
* build: implement version file update when release.

* build: move file to correct path.

* remove.
2024-11-22 08:08:05 +00:00
icey-yu
0ac6668a50 fix: write msg to redis (#2836) 2024-11-22 08:08:05 +00:00
Monet Lee
404a9048e2 build: implement version file update when release. (#2826) 2024-11-22 08:08:05 +00:00
icey-yu
7881c8c89a fix: del login Policy (#2825)
* fix: del login Policy

* feat: offline push

* feat: offline push

* fix: err

* fix: err
2024-11-22 08:08:05 +00:00
icey-yu
eb598ec0e6 Update login policy (#2822)
* fix: login Policy

* fix: del login Policy
2024-11-22 08:08:05 +00:00
Monet Lee
625fa77e89 fix: improve condition check. (#2815) 2024-11-22 08:08:05 +00:00
chao
f707069089 feat: ApplicationVersion move chat (#2813)
* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* fix: MemberEnterNotification

* fix: MemberEnterNotification

* fix: MsgData status

* feat: add ApplicationVersion

* feat: ApplicationVersion move chat

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>
2024-11-22 08:08:05 +00:00
chao
2bbd1bcfe9 feat: support app update service (#2811)
* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* fix: MemberEnterNotification

* fix: MemberEnterNotification

* fix: MsgData status

* feat: add ApplicationVersion

* feat: add ApplicationVersion

* feat: add ApplicationVersion

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>
2024-11-22 08:08:05 +00:00
chao
e53ae33e39 feat: support app update service (#2794)
* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* fix: MemberEnterNotification

* fix: MemberEnterNotification

* fix: MsgData status

* feat: add ApplicationVersion

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>
2024-11-22 08:08:05 +00:00
chao
3914dc1435 feat: provide the interface required by js sdk (#2712)
* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>
2024-11-22 08:08:05 +00:00
OpenIM-Robot
047fa33704
deps: Merge #2804 #2805 #2806 #2808 PRs into pre-release-v3.8.2 (#2812)
* fix: improve time condition check mehtod. (#2804)

* fix: improve time condition check mehtod.

* fix

* fix: webhook before online push (#2805)

* fix: solve err Notification when setGroupInfo. (#2806)

* fix: solve err Notification when setGroupInfo.

* build: update checkout version.

* fix: update notification contents.

* fix: set own read seq in MongoDB when sender send a message. (#2808)

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: OpenIM-Gordon <46924906+FGadvancer@users.noreply.github.com>
2024-10-30 11:47:21 +08:00
Monet Lee
caf5d5c2f3 Merge branch 'pre-release-v3.8.2' of github.com:openimsdk/open-im-server into pre-release-v3.8.2 2024-10-28 15:22:06 +08:00
Monet Lee
7fa2d08636 build: update protocol version to pre-releas-v3.8.2 2024-10-28 15:21:59 +08:00
Monet Lee
7b5c18b549
Merge pull request #2802 from openimsdk/cherry-pick-4de3bef
deps: Merge  #2801 PRs into pre-release-v3.8.2
2024-10-28 14:33:07 +08:00
Monet Lee
0a565070b8 build: improve workflows logic. (#2801) 2024-10-28 06:30:40 +00:00
Monet Lee
43bc87ce99
Merge pull request #2800 from openimsdk/cherry-pick-4a8abfa
deps: Merge  #2796 PRs into pre-release-v3.8.2
2024-10-28 11:24:28 +08:00
Monet Lee
c4fe659c69 feat: implement merge milestone PR to target-branch. (#2796) 2024-10-28 03:23:31 +00:00
OpenIM-Robot
59c4c7575d
deps: Merge #2716 #2718 #2719 #2724 #2730 #2770 #2772 #2773 #2775 #2777 #2779 #2782 #2785 #2786 #2789 #2790 #2792 PRs into pre-release-v3.8.2 (#2797)
* Line webhook (#2716)

* feat: online and offline webhook

* feat: online and offline webhook

* feat: remove zk

* fix: the message I sent is not set to read seq in mongodb (#2718)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: cannot modify group member avatars (#2719)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: auth package import twice (#2724)

* fix: group level change logic (#2730)

* feat: Add More Multi Login Policy (#2770)

* feat: multiLogin

* feat: change config

* fix: join the group chat directly, notification type error (#2772)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* fix: MemberEnterNotification

* fix: MemberEnterNotification

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: joinSource check args error. (#2773)

Co-authored-by: Monet Lee <monet_lee@163.com>

* feat: change push config (#2775)

* fix: change group member info send notification (#2777)

* fix: client sends message status error to server (#2779)

* fix: GroupApplicationAcceptedNotification

* fix: GroupApplicationAcceptedNotification

* fix: NotificationUserInfoUpdate

* cicd: robot automated Change

* fix: component

* fix: getConversationInfo

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* feat: cron task

* fix: minio config url recognition error

* update gomake version

* update gomake version

* fix: seq conversion bug

* fix: redis pipe exec

* fix: ImportFriends

* fix: A large number of logs keysAndValues ​​length is not even

* feat: mark read aggregate write

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* feat: online status supports redis cluster

* merge

* merge

* read seq is written to mongo

* read seq is written to mongo

* fix: invitation to join group notification

* fix: friend op_user_id

* feat: optimizing asynchronous context

* feat: optimizing memamq size

* feat: add GetSeqMessage

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: GroupApplicationAgreeMemberEnterNotification

* feat: go.mod

* feat: go.mod

* feat: join group notification and get seq

* feat: join group notification and get seq

* feat: avoid pulling messages from sessions with a large number of max seq values of 0

* feat: API supports gzip

* go.mod

* fix: nil pointer error on close

* fix: listen error

* fix: listen error

* update go.mod

* feat: add log

* fix: token parse token value

* fix: GetMsgBySeqs boundary issues

* fix: sn_ not sort

* fix: sn_ not sort

* fix: sn_ not sort

* fix: jssdk add

* fix: jssdk support

* fix: jssdk support

* fix: jssdk support

* fix: the message I sent is not set to read seq in mongodb

* fix: cannot modify group member avatars

* fix: MemberEnterNotification

* fix: MemberEnterNotification

* fix: MsgData status

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>

* fix: improve setConversationAtInfo logic. (#2782)

* fix: improve ConversationATInfo logic.

* fix logic err.

* fix: del UserB's conversation version cache when userA set conversation's isPrivateChat to true. (#2785)

* chore: remove unused .chglog and unnecessary content in goreleaser (#2786)

* Revert: Change group member roleLevel can`t send notification (#2789)

* fix: change group member info send notification

* fix: change group member info send notification

* fix: group

* fix: group

* fix: group

* fix: improve transfer Owner logic when newOwner is mute. (#2790)

* fix: improve ConversationATInfo logic.

* fix logic err.

* fix: improve transfer Owner logic when newOwner is mute.

* fix: improve getUserInfo logic. (#2792)

* fix: improve ConversationATInfo logic.

* fix logic err.

* fix: improve transfer Owner logic when newOwner is mute.

* fix: improve getUserInfo logic.

---------

Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: chao <48119764+withchao@users.noreply.github.com>
Co-authored-by: withchao <withchao@users.noreply.github.com>
Co-authored-by: liangkai <kevin.scnu@gmail.com>
Co-authored-by: Alilestera <75608652+alilestera@users.noreply.github.com>
Co-authored-by: Monet Lee <monet_lee@163.com>
Co-authored-by: OpenIM-Gordon <46924906+FGadvancer@users.noreply.github.com>
2024-10-25 18:19:01 +08:00
80 changed files with 1777 additions and 1192 deletions

4
.env
View File

@ -1,6 +1,5 @@
MONGO_IMAGE=mongo:6.0.2 MONGO_IMAGE=mongo:7.0
REDIS_IMAGE=redis:7.0.0 REDIS_IMAGE=redis:7.0.0
ZOOKEEPER_IMAGE=bitnami/zookeeper:3.8
KAFKA_IMAGE=bitnami/kafka:3.5.1 KAFKA_IMAGE=bitnami/kafka:3.5.1
MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.13 ETCD_IMAGE=quay.io/coreos/etcd:v3.5.13
@ -16,4 +15,3 @@ OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.8.2
#OPENIM_ADMIN_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-admin-front:release-v1.8.2 #OPENIM_ADMIN_FRONT_IMAGE=registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-admin-front:release-v1.8.2
DATA_DIR=./ DATA_DIR=./

78
.github/workflows/changelog.yml vendored Normal file
View File

@ -0,0 +1,78 @@
name: Release Changelog
on:
release:
types: [released]
permissions:
contents: write
pull-requests: write
jobs:
update-changelog:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run Go Changelog Generator
run: |
# Run the Go changelog generator, passing the release tag if available
if [ "${{ github.event.release.tag_name }}" = "latest" ]; then
go run tools/changelog/changelog.go > "${{ github.event.release.tag_name }}-changelog.md"
else
go run tools/changelog/changelog.go "${{ github.event.release.tag_name }}" > "${{ github.event.release.tag_name }}-changelog.md"
fi
- name: Handle changelog files
run: |
# Ensure that the CHANGELOG directory exists
mkdir -p CHANGELOG
# Extract Major.Minor version by removing the 'v' prefix from the tag name
TAG_NAME=${{ github.event.release.tag_name }}
CHANGELOG_VERSION_NUMBER=$(echo "$TAG_NAME" | sed 's/^v//' | grep -oP '^\d+\.\d+')
# Define the new changelog file path
CHANGELOG_FILENAME="CHANGELOG-$CHANGELOG_VERSION_NUMBER.md"
CHANGELOG_PATH="CHANGELOG/$CHANGELOG_FILENAME"
# Check if the changelog file for the current release already exists
if [ -f "$CHANGELOG_PATH" ]; then
# If the file exists, append the new changelog to the existing one
cat "$CHANGELOG_PATH" >> "${TAG_NAME}-changelog.md"
# Overwrite the existing changelog with the updated content
mv "${TAG_NAME}-changelog.md" "$CHANGELOG_PATH"
else
# If the changelog file doesn't exist, rename the temp changelog file to the new changelog file
mv "${TAG_NAME}-changelog.md" "$CHANGELOG_PATH"
# Ensure that README.md exists
if [ ! -f "CHANGELOG/README.md" ]; then
echo -e "# CHANGELOGs\n\n" > CHANGELOG/README.md
fi
# Add the new changelog entry at the top of the README.md
if ! grep -q "\[$CHANGELOG_FILENAME\]" CHANGELOG/README.md; then
sed -i "3i- [$CHANGELOG_FILENAME](./$CHANGELOG_FILENAME)" CHANGELOG/README.md
# Remove the extra newline character added by sed
# sed -i '4d' CHANGELOG/README.md
fi
fi
- name: Clean up
run: |
# Remove any temporary files that were created during the process
rm -f "${{ github.event.release.tag_name }}-changelog.md"
- name: Create Pull Request
uses: peter-evans/create-pull-request@v7.0.5
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "Update CHANGELOG for release ${{ github.event.release.tag_name }}"
title: "Update CHANGELOG for release ${{ github.event.release.tag_name }}"
body: "This PR updates the CHANGELOG files for release ${{ github.event.release.tag_name }}"
branch: changelog-${{ github.event.release.tag_name }}
base: main
delete-branch: true
labels: changelog

View File

@ -0,0 +1,65 @@
name: Cleanup After Milestone PRs Merged
on:
pull_request:
types:
- closed
jobs:
handle_pr:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4.2.0
- name: Get the PR title and extract PR numbers
id: extract_pr_numbers
run: |
# Get the PR title
PR_TITLE="${{ github.event.pull_request.title }}"
echo "PR Title: $PR_TITLE"
# Extract PR numbers from the title
PR_NUMBERS=$(echo "$PR_TITLE" | grep -oE "#[0-9]+" | tr -d '#' | tr '\n' ' ')
echo "Extracted PR Numbers: $PR_NUMBERS"
# Save PR numbers to a file
echo "$PR_NUMBERS" > pr_numbers.txt
echo "Saved PR Numbers to pr_numbers.txt"
# Check if the title matches a specific pattern
if echo "$PR_TITLE" | grep -qE "^deps: Merge( #[0-9]+)+ PRs into .+"; then
echo "proceed=true" >> $GITHUB_OUTPUT
else
echo "proceed=false" >> $GITHUB_OUTPUT
fi
- name: Use extracted PR numbers and label PRs
if: (steps.extract_pr_numbers.outputs.proceed == 'true' || contains(github.event.pull_request.labels.*.name, 'milestone-merge')) && github.event.pull_request.merged == true
run: |
# Read the previously saved PR numbers
PR_NUMBERS=$(cat pr_numbers.txt)
echo "Using extracted PR Numbers: $PR_NUMBERS"
# Loop through each PR number and add label
for PR_NUMBER in $PR_NUMBERS; do
echo "Adding 'cherry-picked' label to PR #$PR_NUMBER"
curl -X POST \
-H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/${{ github.repository }}/issues/$PR_NUMBER/labels \
-d '{"labels":["cherry-picked"]}'
done
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Delete branch after PR close
if: steps.extract_pr_numbers.outputs.proceed == 'true' || contains(github.event.pull_request.labels.*.name, 'milestone-merge')
run: |
BRANCH_NAME="${{ github.event.pull_request.head.ref }}"
echo "Branch to delete: $BRANCH_NAME"
git push origin --delete "$BRANCH_NAME"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -2,11 +2,7 @@ name: Go Build Test
on: on:
push: push:
branches:
- main
pull_request: pull_request:
branches:
- main
paths-ignore: paths-ignore:
- '**/*.md' - '**/*.md'
@ -153,7 +149,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
go_version: ["1.21"] go_version: ["1.22"]
steps: steps:
- name: Checkout Repository - name: Checkout Repository

View File

@ -0,0 +1,218 @@
name: Create Pre-Release PR from Milestone
permissions:
contents: write
pull-requests: write
issues: write
on:
workflow_dispatch:
inputs:
milestone_name:
description: 'Milestone name to collect closed PRs from'
required: true
default: 'v3.8.2'
target_branch:
description: 'Target branch to merge the consolidated PR'
required: true
default: 'pre-release-v3.8.2'
env:
MILESTONE_NAME: ${{ github.event.inputs.milestone_name || 'v3.8.2' }}
TARGET_BRANCH: ${{ github.event.inputs.target_branch || 'pre-release-v3.8.2' }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
LABEL_NAME: cherry-picked
TEMP_DIR: /tmp # Using /tmp as the temporary directory
jobs:
cherry_pick_milestone_prs:
runs-on: ubuntu-latest
steps:
- name: Setup temp directory
run: |
# Create the temporary directory and initialize necessary files
mkdir -p ${{ env.TEMP_DIR }}
touch ${{ env.TEMP_DIR }}/pr_numbers.txt
touch ${{ env.TEMP_DIR }}/commit_hashes.txt
touch ${{ env.TEMP_DIR }}/pr_title.txt
touch ${{ env.TEMP_DIR }}/pr_body.txt
touch ${{ env.TEMP_DIR }}/created_pr_number.txt
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.BOT_TOKEN }}
- name: Setup Git User for OpenIM-Robot
run: |
# Set up Git credentials for the bot
git config --global user.email "OpenIM-Robot@users.noreply.github.com"
git config --global user.name "OpenIM-Robot"
- name: Fetch Milestone ID and Filter PR Numbers
env:
MILESTONE_NAME: ${{ env.MILESTONE_NAME }}
run: |
# Fetch milestone details and extract milestone ID
milestones=$(curl -s -H "Authorization: token $BOT_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/milestones")
milestone_id=$(echo "$milestones" | grep -B3 "\"title\": \"$MILESTONE_NAME\"" | grep '"number":' | head -n1 | grep -o '[0-9]\+')
if [ -z "$milestone_id" ]; then
echo "Milestone '$MILESTONE_NAME' not found. Exiting."
exit 1
fi
echo "Milestone ID: $milestone_id"
echo "MILESTONE_ID=$milestone_id" >> $GITHUB_ENV
# Fetch issues for the milestone
issues=$(curl -s -H "Authorization: token $BOT_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/issues?milestone=$milestone_id&state=closed&per_page=100")
> ${{ env.TEMP_DIR }}/pr_numbers.txt
# Filter PRs that do not have the 'cherry-picked' label
for pr_number in $(echo "$issues" | jq -r '.[] | select(.pull_request != null) | .number'); do
labels=$(curl -s -H "Authorization: token $BOT_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/issues/$pr_number/labels" | jq -r '.[].name')
if ! echo "$labels" | grep -q "${LABEL_NAME}"; then
echo "PR #$pr_number does not have the 'cherry-picked' label. Adding to the list."
echo "$pr_number" >> ${{ env.TEMP_DIR }}/pr_numbers.txt
else
echo "PR #$pr_number already has the 'cherry-picked' label. Skipping."
fi
done
# Sort the filtered PR numbers
sort -n ${{ env.TEMP_DIR }}/pr_numbers.txt -o ${{ env.TEMP_DIR }}/pr_numbers.txt
echo "Filtered and sorted PR numbers:"
cat ${{ env.TEMP_DIR }}/pr_numbers.txt || echo "No closed PR numbers found for milestone."
- name: Fetch Merge Commits for PRs and Generate Title and Body
run: |
# Ensure the files are initialized
> ${{ env.TEMP_DIR }}/commit_hashes.txt
> ${{ env.TEMP_DIR }}/pr_title.txt
> ${{ env.TEMP_DIR }}/pr_body.txt
# Write description to the PR body
echo "### Description:" >> ${{ env.TEMP_DIR }}/pr_body.txt
echo "Merging PRs from milestone \`$MILESTONE_NAME\` into target branch \`$TARGET_BRANCH\`." >> ${{ env.TEMP_DIR }}/pr_body.txt
echo "" >> ${{ env.TEMP_DIR }}/pr_body.txt
echo "### Need Merge PRs:" >> ${{ env.TEMP_DIR }}/pr_body.txt
pr_numbers_in_title=""
# Process sorted PR numbers and generate commit hashes
for pr_number in $(cat ${{ env.TEMP_DIR }}/pr_numbers.txt); do
echo "Processing PR #$pr_number"
pr_details=$(curl -s -H "Authorization: token $BOT_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/pulls/$pr_number")
pr_title=$(echo "$pr_details" | jq -r '.title')
merge_commit=$(echo "$pr_details" | jq -r '.merge_commit_sha')
short_commit_hash=$(echo "$merge_commit" | cut -c 1-7)
# Append PR details to the body
echo "- $pr_title: (#$pr_number) ($short_commit_hash)" >> ${{ env.TEMP_DIR }}/pr_body.txt
if [ "$merge_commit" != "null" ];then
echo "$merge_commit" >> ${{ env.TEMP_DIR }}/commit_hashes.txt
echo "#$pr_number" >> ${{ env.TEMP_DIR }}/pr_title.txt
pr_numbers_in_title="$pr_numbers_in_title #$pr_number"
fi
done
commit_hashes=$(cat ${{ env.TEMP_DIR }}/commit_hashes.txt | tr '\n' ' ')
first_commit_hash=$(head -n 1 ${{ env.TEMP_DIR }}/commit_hashes.txt)
cherry_pick_branch="cherry-pick-${first_commit_hash:0:7}"
echo "COMMIT_HASHES=$commit_hashes" >> $GITHUB_ENV
echo "CHERRY_PICK_BRANCH=$cherry_pick_branch" >> $GITHUB_ENV
echo "pr_numbers_in_title=$pr_numbers_in_title" >> $GITHUB_ENV
- name: Pull and Cherry-pick Commits, Then Push
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
run: |
# Fetch and pull the latest changes from the target branch
git fetch origin
git checkout $TARGET_BRANCH
git pull origin $TARGET_BRANCH
# Create a new branch for cherry-picking
git checkout -b $CHERRY_PICK_BRANCH
# Cherry-pick the commits and handle conflicts
for commit_hash in $COMMIT_HASHES; do
echo "Attempting to cherry-pick commit $commit_hash"
if ! git cherry-pick "$commit_hash" --strategy=recursive -X theirs; then
echo "Conflict detected for $commit_hash. Resolving with incoming changes."
conflict_files=$(git diff --name-only --diff-filter=U)
echo "Conflicting files:"
echo "$conflict_files"
for file in $conflict_files; do
if [ -f "$file" ]; then
echo "Resolving conflict for $file"
git add "$file"
else
echo "File $file has been deleted. Skipping."
git rm "$file"
fi
done
echo "Conflicts resolved. Continuing cherry-pick."
git cherry-pick --continue
else
echo "Cherry-pick successful for commit $commit_hash."
fi
done
# Push the cherry-pick branch to the repository
git remote set-url origin "https://${BOT_TOKEN}@github.com/${{ github.repository }}.git"
git push origin $CHERRY_PICK_BRANCH --force
- name: Create Pull Request
run: |
# Prepare and create the PR
pr_title="deps: Merge ${{ env.pr_numbers_in_title }} PRs into $TARGET_BRANCH"
pr_body=$(cat ${{ env.TEMP_DIR }}/pr_body.txt)
echo "Prepared PR title:"
echo "$pr_title"
echo "Prepared PR body:"
echo "$pr_body"
# Create the PR using the GitHub API
response=$(curl -s -X POST -H "Authorization: token $BOT_TOKEN" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/${{ github.repository }}/pulls \
-d "$(jq -n --arg title "$pr_title" \
--arg head "$CHERRY_PICK_BRANCH" \
--arg base "$TARGET_BRANCH" \
--arg body "$pr_body" \
'{title: $title, head: $head, base: $base, body: $body}')")
pr_number=$(echo "$response" | jq -r '.number')
echo "$pr_number" > ${{ env.TEMP_DIR }}/created_pr_number.txt
echo "Created PR #$pr_number"
- name: Add Label to Created Pull Request
run: |
# Add 'milestone-merge' label to the created PR
pr_number=$(cat ${{ env.TEMP_DIR }}/created_pr_number.txt)
echo "Adding label to PR #$pr_number"
curl -s -X POST -H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
-d '{"labels": ["milestone-merge"]}' \
"https://api.github.com/repos/${{ github.repository }}/issues/$pr_number/labels"
echo "Added 'milestone-merge' label to PR #$pr_number."

View File

@ -0,0 +1,84 @@
name: Update Version File on Release
on:
release:
types: [created]
jobs:
update-version:
runs-on: ubuntu-latest
env:
TAG_VERSION: ${{ github.event.release.tag_name }}
steps:
# Step 1: Checkout the original repository's code
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
# Step 2: Set up Git with official account
- name: Set up Git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
# Step 3: Check and delete existing tag
- name: Check and delete existing tag
run: |
if git rev-parse ${{ env.TAG_VERSION }} >/dev/null 2>&1; then
git tag -d ${{ env.TAG_VERSION }}
git push --delete origin ${{ env.TAG_VERSION }}
fi
# Step 4: Update version file
- name: Update version file
run: |
echo "${{ env.TAG_VERSION }}" > version/version
# Step 5: Commit and push changes
- name: Commit and push changes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git add version/version
git commit -m "Update version to ${{ env.TAG_VERSION }}"
git push origin HEAD:${{ github.ref }}
# Step 6: Create and push tag
- name: Create and push tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
git tag ${{ env.TAG_VERSION }}
git push origin ${{ env.TAG_VERSION }}
# Step 7: Find and Publish Draft Release
- name: Find and Publish Draft Release
uses: actions/github-script@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
// Get the list of releases
const releases = await github.rest.repos.listReleases({
owner: context.repo.owner,
repo: context.repo.repo
});
// Find the draft release where the title and tag_name are the same
const draftRelease = releases.data.find(release =>
release.draft && release.name === release.tag_name
);
if (draftRelease) {
// Publish the draft release using the release_id
await github.rest.repos.updateRelease({
owner: context.repo.owner,
repo: context.repo.repo,
release_id: draftRelease.id, // Use release_id
draft: false
});
core.info(`Draft Release ${draftRelease.tag_name} published successfully.`);
} else {
core.info("No matching draft release found.");
}

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

@ -1,5 +1,5 @@
# Use Go 1.21 Alpine as the base image for building the application # Use Go 1.22 Alpine as the base image for building the application
FROM golang:1.21-alpine AS builder FROM golang:1.22-alpine AS builder
# Define the base directory for the application as an environment variable # Define the base directory for the application as an environment variable
ENV SERVER_DIR=/openim-server ENV SERVER_DIR=/openim-server
@ -22,7 +22,7 @@ RUN go install github.com/magefile/mage@v1.15.0
RUN mage build RUN mage build
# Using Alpine Linux with Go environment for the final image # Using Alpine Linux with Go environment for the final image
FROM golang:1.21-alpine FROM golang:1.22-alpine
# Install necessary packages, such as bash # Install necessary packages, such as bash
RUN apk add --no-cache bash RUN apk add --no-cache bash

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

@ -15,10 +15,9 @@
package main package main
import ( import (
_ "net/http/pprof"
"github.com/openimsdk/open-im-server/v3/pkg/common/cmd" "github.com/openimsdk/open-im-server/v3/pkg/common/cmd"
"github.com/openimsdk/tools/system/program" "github.com/openimsdk/tools/system/program"
_ "net/http/pprof"
) )
func main() { func main() {

View File

@ -5,9 +5,4 @@ etcd:
username: '' username: ''
password: '' password: ''
zookeeper:
schema: openim
address: [ localhost:12181 ]
username: ''
password: ''

View File

@ -8,6 +8,8 @@ database: openim_v3
username: openIM username: openIM
# Password for database authentication # Password for database authentication
password: openIM123 password: openIM123
# Authentication source for database authentication, if use root user, set it to admin
authSource: openim_v3
# Maximum number of connections in the connection pool # Maximum number of connections in the connection pool
maxPoolSize: 100 maxPoolSize: 100
# Maximum number of retry attempts for a failed database connection # Maximum number of retry attempts for a failed database connection

View File

@ -1,20 +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.
# Determines if a message should be sent. If set to false, it triggers a silent sync without a message. If true, it requires triggering a conversation.
# For rpc notification, send twice: once as a message and once as a notification.
# The options field 'isNotification' indicates if it's a notification.
groupCreated: groupCreated:
isSendMsg: true isSendMsg: true
# Reliability level of the message sending. # Reliability level of the message sending.
@ -309,9 +292,9 @@ userInfoUpdated:
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: true
title: Remove a blocked user title: userInfo updated
desc: Remove a blocked user desc: userInfo updated
ext: Remove a blocked user ext: userInfo updated
userStatusChanged: userStatusChanged:
isSendMsg: false isSendMsg: false

View File

@ -22,5 +22,3 @@ longConnSvr:
websocketMaxMsgLen: 4096 websocketMaxMsgLen: 4096
# WebSocket connection handshake timeout in seconds # WebSocket connection handshake timeout in seconds
websocketTimeout: 10 websocketTimeout: 10

View File

@ -13,7 +13,7 @@ prometheus:
ports: [ 12170, 12171, 12172, 12173, 12174, 12175, 12176, 12177, 12178, 12179, 12180, 12182, 12183, 12184, 12185, 12186 ] ports: [ 12170, 12171, 12172, 12173, 12174, 12175, 12176, 12177, 12178, 12179, 12180, 12182, 12183, 12184, 12185, 12186 ]
maxConcurrentWorkers: 3 maxConcurrentWorkers: 3
#Use geTui for offline push notifications, or choose fcm or jpns; corresponding configuration settings must be specified. #Use geTui for offline push notifications, or choose fcm or jpush; corresponding configuration settings must be specified.
enable: geTui enable: geTui
geTui: geTui:
pushUrl: https://restapi.getui.com/v2/$appId pushUrl: https://restapi.getui.com/v2/$appId
@ -26,7 +26,7 @@ fcm:
# Prioritize using file paths. If the file path is empty, use URL # Prioritize using file paths. If the file path is empty, use URL
filePath: # File path is concatenated with the parameters passed in through - c(`mage` default pass in `config/`) and filePath. filePath: # File path is concatenated with the parameters passed in through - c(`mage` default pass in `config/`) and filePath.
authURL: # Must start with https or http. authURL: # Must start with https or http.
jpns: jpush:
appKey: appKey:
masterSecret: masterSecret:
pushURL: pushURL:

View File

@ -13,4 +13,6 @@ 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

View File

@ -240,11 +240,11 @@ push:
channelName: ${GETUI_CHANNEL_NAME} channelName: ${GETUI_CHANNEL_NAME}
fcm: fcm:
serviceAccount: "${FCM_SERVICE_ACCOUNT}" serviceAccount: "${FCM_SERVICE_ACCOUNT}"
jpns: jpush:
appKey: ${JPNS_APP_KEY} appKey: ${JPUSH_APP_KEY}
masterSecret: ${JPNS_MASTER_SECRET} masterSecret: ${JPUSH_MASTER_SECRET}
pushUrl: ${JPNS_PUSH_URL} pushUrl: ${JPUSH_PUSH_URL}
pushIntent: ${JPNS_PUSH_INTENT} pushIntent: ${JPUSH_PUSH_INTENT}
# App manager configuration # App manager configuration
# #

View File

@ -8,12 +8,35 @@ services:
ports: ports:
- "37017:27017" - "37017:27017"
container_name: mongo container_name: mongo
command: ["/bin/bash", "-c", "/docker-entrypoint-initdb.d/mongo-init.sh; docker-entrypoint.sh mongod --wiredTigerCacheSizeGB 1 --auth"] command: >
bash -c '
docker-entrypoint.sh mongod --wiredTigerCacheSizeGB $$wiredTigerCacheSizeGB --auth &
until mongosh -u $$MONGO_INITDB_ROOT_USERNAME -p $$MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "db.runCommand({ ping: 1 })" &>/dev/null; do
echo "Waiting for MongoDB to start..."
sleep 1
done &&
mongosh -u $$MONGO_INITDB_ROOT_USERNAME -p $$MONGO_INITDB_ROOT_PASSWORD --authenticationDatabase admin --eval "
db = db.getSiblingDB(\"$$MONGO_INITDB_DATABASE\");
if (!db.getUser(\"$$MONGO_OPENIM_USERNAME\")) {
db.createUser({
user: \"$$MONGO_OPENIM_USERNAME\",
pwd: \"$$MONGO_OPENIM_PASSWORD\",
roles: [{role: \"readWrite\", db: \"$$MONGO_INITDB_DATABASE\"}]
});
print(\"User created successfully: \");
print(\"Username: $$MONGO_OPENIM_USERNAME\");
print(\"Password: $$MONGO_OPENIM_PASSWORD\");
print(\"Database: $$MONGO_INITDB_DATABASE\");
} else {
print(\"User already exists in database: $$MONGO_INITDB_DATABASE, Username: $$MONGO_OPENIM_USERNAME\");
}
" &&
tail -f /dev/null
'
volumes: volumes:
- "${DATA_DIR}/components/mongodb/data/db:/data/db" - "${DATA_DIR}/components/mongodb/data/db:/data/db"
- "${DATA_DIR}/components/mongodb/data/logs:/data/logs" - "${DATA_DIR}/components/mongodb/data/logs:/data/logs"
- "${DATA_DIR}/components/mongodb/data/conf:/etc/mongo" - "${DATA_DIR}/components/mongodb/data/conf:/etc/mongo"
- "./scripts/mongo-init.sh:/docker-entrypoint-initdb.d/mongo-init.sh:ro"
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- wiredTigerCacheSizeGB=1 - wiredTigerCacheSizeGB=1
@ -43,19 +66,6 @@ services:
networks: networks:
- openim - openim
zookeeper:
image: "${ZOOKEEPER_IMAGE}"
container_name: zookeeper
ports:
- "12181:2181"
environment:
#JVMFLAGS: "-Xms32m -Xmx128m"
TZ: "Asia/Shanghai"
ALLOW_ANONYMOUS_LOGIN: "yes"
restart: always
networks:
- openim
etcd: etcd:
image: "${ETCD_IMAGE}" image: "${ETCD_IMAGE}"
container_name: etcd container_name: etcd
@ -84,10 +94,7 @@ services:
ports: ports:
- "19094:9094" - "19094:9094"
volumes: volumes:
- ./scripts/create-topic.sh:/opt/bitnami/kafka/create-topic.sh
- "${DATA_DIR}/components/kafka:/bitnami/kafka" - "${DATA_DIR}/components/kafka:/bitnami/kafka"
command: >
bash -c "/opt/bitnami/scripts/kafka/run.sh & /opt/bitnami/kafka/create-topic.sh; wait"
environment: environment:
#KAFKA_HEAP_OPTS: "-Xms128m -Xmx256m" #KAFKA_HEAP_OPTS: "-Xms128m -Xmx256m"
TZ: Asia/Shanghai TZ: Asia/Shanghai
@ -98,10 +105,11 @@ services:
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:19094 KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:19094
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_NUM_PARTITIONS: 8
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true"
networks: networks:
- openim - openim
minio: minio:
image: "${MINIO_IMAGE}" image: "${MINIO_IMAGE}"
ports: ports:
@ -142,6 +150,7 @@ services:
# image: ${PROMETHEUS_IMAGE} # image: ${PROMETHEUS_IMAGE}
# container_name: prometheus # container_name: prometheus
# restart: always # restart: always
# user: root
# volumes: # volumes:
# - ./config/prometheus.yml:/etc/prometheus/prometheus.yml # - ./config/prometheus.yml:/etc/prometheus/prometheus.yml
# - ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml # - ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
@ -183,4 +192,3 @@ services:
# - ${DATA_DIR:-./}/components/grafana:/var/lib/grafana # - ${DATA_DIR:-./}/components/grafana:/var/lib/grafana
# networks: # networks:
# - openim # - openim

View File

@ -474,10 +474,10 @@ This section involves setting up additional configuration variables for Websocke
| GETUI_CHANNEL_ID | [User Defined] | GeTui Channel ID | | GETUI_CHANNEL_ID | [User Defined] | GeTui Channel ID |
| GETUI_CHANNEL_NAME | [User Defined] | GeTui Channel Name | | GETUI_CHANNEL_NAME | [User Defined] | GeTui Channel Name |
| FCM_SERVICE_ACCOUNT | "x.json" | FCM Service Account | | FCM_SERVICE_ACCOUNT | "x.json" | FCM Service Account |
| JPNS_APP_KEY | [User Defined] | JPNS Application Key | | JPUSH_APP_KEY | [User Defined] | JPUSH Application Key |
| JPNS_MASTER_SECRET | [User Defined] | JPNS Master Secret | | JPUSH_MASTER_SECRET | [User Defined] | JPUSH Master Secret |
| JPNS_PUSH_URL | [User Defined] | JPNS Push Notification URL | | JPUSH_PUSH_URL | [User Defined] | JPUSH Push Notification URL |
| JPNS_PUSH_INTENT | [User Defined] | JPNS Push Intent | | JPUSH_PUSH_INTENT | [User Defined] | JPUSH Push Intent |
| IM_ADMIN_USERID | "imAdmin" | IM Administrator ID | | IM_ADMIN_USERID | "imAdmin" | IM Administrator ID |
| IM_ADMIN_NAME | "imAdmin" | IM Administrator Nickname | | IM_ADMIN_NAME | "imAdmin" | IM Administrator Nickname |
| MULTILOGIN_POLICY | "1" | Multi-login Policy | | MULTILOGIN_POLICY | "1" | Multi-login Policy |

12
go.mod
View File

@ -1,6 +1,8 @@
module github.com/openimsdk/open-im-server/v3 module github.com/openimsdk/open-im-server/v3
go 1.21.2 go 1.22.0
toolchain go1.23.2
require ( require (
firebase.google.com/go/v4 v4.14.1 firebase.google.com/go/v4 v4.14.1
@ -8,12 +10,12 @@ require (
github.com/gin-gonic/gin v1.9.1 github.com/gin-gonic/gin v1.9.1
github.com/go-playground/validator/v10 v10.20.0 github.com/go-playground/validator/v10 v10.20.0
github.com/gogo/protobuf v1.3.2 // indirect github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.1
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 github.com/openimsdk/protocol v0.0.72-alpha.54
github.com/openimsdk/tools v0.0.50-alpha.16 github.com/openimsdk/tools v0.0.50-alpha.32
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
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
@ -92,7 +94,7 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect

20
go.sum
View File

@ -126,8 +126,8 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
@ -158,8 +158,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo=
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
@ -319,10 +319,10 @@ github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/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 h1:K+vslwaR7lDXyBzb07UuEQITaqsgighz7NyXVIWsu6A= github.com/openimsdk/protocol v0.0.72-alpha.54 h1:opato7N4QjjRq/SHD54bDSVBpOEEDp1VLWVk5Os2A9s=
github.com/openimsdk/protocol v0.0.72/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8= github.com/openimsdk/protocol v0.0.72-alpha.54/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8=
github.com/openimsdk/tools v0.0.50-alpha.16 h1:bC1AQvJMuOHtZm8LZRvN8L5mH1Ws2VYdL+TLTs1iGSc= github.com/openimsdk/tools v0.0.50-alpha.32 h1:JEsUFHFnaYg230TG+Ke3SUnaA2h44t4kABAzEdv5VZw=
github.com/openimsdk/tools v0.0.50-alpha.16/go.mod h1:h1cYmfyaVtgFbKmb1Cfsl8XwUOMTt8ubVUQrdGtsUh4= github.com/openimsdk/tools v0.0.50-alpha.32/go.mod h1:r5U6RbxcR4xhKb2fhTmKGC9Yt5LcErHBVt3lhXQIHSo=
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=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
@ -356,8 +356,8 @@ github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=

View File

@ -1,11 +1,15 @@
package jssdk package jssdk
import ( import (
"context"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/openimsdk/protocol/conversation" "github.com/openimsdk/protocol/conversation"
"github.com/openimsdk/protocol/group"
"github.com/openimsdk/protocol/jssdk"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/protocol/relation"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/a2r" "github.com/openimsdk/protocol/user"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"sort" "sort"
@ -16,14 +20,20 @@ const (
defaultGetActiveConversation = 100 defaultGetActiveConversation = 100
) )
func NewJSSdkApi(msg msg.MsgClient, conv conversation.ConversationClient) *JSSdk { func NewJSSdkApi(user user.UserClient, friend relation.FriendClient, group group.GroupClient, msg msg.MsgClient, conv conversation.ConversationClient) *JSSdk {
return &JSSdk{ return &JSSdk{
user: user,
friend: friend,
group: group,
msg: msg, msg: msg,
conv: conv, conv: conv,
} }
} }
type JSSdk struct { type JSSdk struct {
user user.UserClient
friend relation.FriendClient
group group.GroupClient
msg msg.MsgClient msg msg.MsgClient
conv conversation.ConversationClient conv conversation.ConversationClient
} }
@ -36,25 +46,71 @@ func (x *JSSdk) GetConversations(c *gin.Context) {
call(c, x.getConversations) call(c, x.getConversations)
} }
func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, error) { func (x *JSSdk) fillConversations(ctx context.Context, conversations []*jssdk.ConversationMsg) error {
req, err := a2r.ParseRequest[ActiveConversationsReq](ctx) if len(conversations) == 0 {
if err != nil { return nil
return nil, err
} }
var (
userIDs []string
groupIDs []string
)
for _, c := range conversations {
if c.Conversation.GroupID == "" {
userIDs = append(userIDs, c.Conversation.UserID)
} else {
groupIDs = append(groupIDs, c.Conversation.GroupID)
}
}
var (
userMap map[string]*sdkws.UserInfo
friendMap map[string]*relation.FriendInfoOnly
groupMap map[string]*sdkws.GroupInfo
)
if len(userIDs) > 0 {
users, err := field(ctx, x.user.GetDesignateUsers, &user.GetDesignateUsersReq{UserIDs: userIDs}, (*user.GetDesignateUsersResp).GetUsersInfo)
if err != nil {
return err
}
friends, err := field(ctx, x.friend.GetFriendInfo, &relation.GetFriendInfoReq{OwnerUserID: conversations[0].Conversation.OwnerUserID, FriendUserIDs: userIDs}, (*relation.GetFriendInfoResp).GetFriendInfos)
if err != nil {
return err
}
userMap = datautil.SliceToMap(users, (*sdkws.UserInfo).GetUserID)
friendMap = datautil.SliceToMap(friends, (*relation.FriendInfoOnly).GetFriendUserID)
}
if len(groupIDs) > 0 {
resp, err := x.group.GetGroupsInfo(ctx, &group.GetGroupsInfoReq{GroupIDs: groupIDs})
if err != nil {
return err
}
groupMap = datautil.SliceToMap(resp.GroupInfos, (*sdkws.GroupInfo).GetGroupID)
}
for _, c := range conversations {
if c.Conversation.GroupID == "" {
c.User = userMap[c.Conversation.UserID]
c.Friend = friendMap[c.Conversation.UserID]
} else {
c.Group = groupMap[c.Conversation.GroupID]
}
}
return nil
}
func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActiveConversationsReq) (*jssdk.GetActiveConversationsResp, error) {
if req.Count <= 0 || req.Count > maxGetActiveConversation { if req.Count <= 0 || req.Count > maxGetActiveConversation {
req.Count = defaultGetActiveConversation req.Count = defaultGetActiveConversation
} }
opUserID := mcontext.GetOpUserID(ctx) req.OwnerUserID = mcontext.GetOpUserID(ctx)
conversationIDs, err := field(ctx, x.conv.GetConversationIDs, conversationIDs, err := field(ctx, x.conv.GetConversationIDs,
&conversation.GetConversationIDsReq{UserID: opUserID}, (*conversation.GetConversationIDsResp).GetConversationIDs) &conversation.GetConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetConversationIDsResp).GetConversationIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(conversationIDs) == 0 { if len(conversationIDs) == 0 {
return &ConversationsResp{}, nil return &jssdk.GetActiveConversationsResp{}, nil
} }
readSeq, err := field(ctx, x.msg.GetHasReadSeqs, readSeq, err := field(ctx, x.msg.GetHasReadSeqs,
&msg.GetHasReadSeqsReq{UserID: opUserID, ConversationIDs: conversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs) &msg.GetHasReadSeqsReq{UserID: req.OwnerUserID, ConversationIDs: conversationIDs}, (*msg.SeqsInfoResp).GetMaxSeqs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -64,24 +120,24 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
return nil, err return nil, err
} }
if len(activeConversation) == 0 { if len(activeConversation) == 0 {
return &ConversationsResp{}, nil return &jssdk.GetActiveConversationsResp{}, nil
} }
sortConversations := sortActiveConversations{ sortConversations := sortActiveConversations{
Conversation: activeConversation, Conversation: activeConversation,
} }
if len(activeConversation) > 1 { if len(activeConversation) > 1 {
pinnedConversationIDs, err := field(ctx, x.conv.GetPinnedConversationIDs, pinnedConversationIDs, err := field(ctx, x.conv.GetPinnedConversationIDs,
&conversation.GetPinnedConversationIDsReq{UserID: opUserID}, (*conversation.GetPinnedConversationIDsResp).GetConversationIDs) &conversation.GetPinnedConversationIDsReq{UserID: req.OwnerUserID}, (*conversation.GetPinnedConversationIDsResp).GetConversationIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
sortConversations.PinnedConversationIDs = datautil.SliceSet(pinnedConversationIDs) sortConversations.PinnedConversationIDs = datautil.SliceSet(pinnedConversationIDs)
} }
sort.Sort(&sortConversations) sort.Sort(&sortConversations)
sortList := sortConversations.Top(req.Count) sortList := sortConversations.Top(int(req.Count))
conversations, err := field(ctx, x.conv.GetConversations, conversations, err := field(ctx, x.conv.GetConversations,
&conversation.GetConversationsReq{ &conversation.GetConversationsReq{
OwnerUserID: opUserID, OwnerUserID: req.OwnerUserID,
ConversationIDs: datautil.Slice(sortList, func(c *msg.ActiveConversation) string { ConversationIDs: datautil.Slice(sortList, func(c *msg.ActiveConversation) string {
return c.ConversationID return c.ConversationID
})}, (*conversation.GetConversationsResp).GetConversations) })}, (*conversation.GetConversationsResp).GetConversations)
@ -90,7 +146,7 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
} }
msgs, err := field(ctx, x.msg.GetSeqMessage, msgs, err := field(ctx, x.msg.GetSeqMessage,
&msg.GetSeqMessageReq{ &msg.GetSeqMessageReq{
UserID: opUserID, UserID: req.OwnerUserID,
Conversations: datautil.Slice(sortList, func(c *msg.ActiveConversation) *msg.ConversationSeqs { Conversations: datautil.Slice(sortList, func(c *msg.ActiveConversation) *msg.ConversationSeqs {
return &msg.ConversationSeqs{ return &msg.ConversationSeqs{
ConversationID: c.ConversationID, ConversationID: c.ConversationID,
@ -104,7 +160,7 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string { conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string {
return c.ConversationID return c.ConversationID
}) })
resp := make([]ConversationMsg, 0, len(sortList)) resp := make([]*jssdk.ConversationMsg, 0, len(sortList))
for _, c := range sortList { for _, c := range sortList {
conv, ok := conversationMap[c.ConversationID] conv, ok := conversationMap[c.ConversationID]
if !ok { if !ok {
@ -114,13 +170,16 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 { if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
lastMsg = msgList.Msgs[0] lastMsg = msgList.Msgs[0]
} }
resp = append(resp, ConversationMsg{ resp = append(resp, &jssdk.ConversationMsg{
Conversation: conv, Conversation: conv,
LastMsg: lastMsg, LastMsg: lastMsg,
MaxSeq: c.MaxSeq, MaxSeq: c.MaxSeq,
ReadSeq: readSeq[c.ConversationID], ReadSeq: readSeq[c.ConversationID],
}) })
} }
if err := x.fillConversations(ctx, resp); err != nil {
return nil, err
}
var unreadCount int64 var unreadCount int64
for _, c := range activeConversation { for _, c := range activeConversation {
count := c.MaxSeq - readSeq[c.ConversationID] count := c.MaxSeq - readSeq[c.ConversationID]
@ -128,24 +187,20 @@ func (x *JSSdk) getActiveConversations(ctx *gin.Context) (*ConversationsResp, er
unreadCount += count unreadCount += count
} }
} }
return &ConversationsResp{ return &jssdk.GetActiveConversationsResp{
Conversations: resp, Conversations: resp,
UnreadCount: unreadCount, UnreadCount: unreadCount,
}, nil }, nil
} }
func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) { func (x *JSSdk) getConversations(ctx context.Context, req *jssdk.GetConversationsReq) (*jssdk.GetConversationsResp, error) {
req, err := a2r.ParseRequest[conversation.GetConversationsReq](ctx)
if err != nil {
return nil, err
}
req.OwnerUserID = mcontext.GetOpUserID(ctx) req.OwnerUserID = mcontext.GetOpUserID(ctx)
conversations, err := field(ctx, x.conv.GetConversations, req, (*conversation.GetConversationsResp).GetConversations) conversations, err := field(ctx, x.conv.GetConversations, &conversation.GetConversationsReq{OwnerUserID: req.OwnerUserID, ConversationIDs: req.ConversationIDs}, (*conversation.GetConversationsResp).GetConversations)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if len(conversations) == 0 { if len(conversations) == 0 {
return &ConversationsResp{}, nil return &jssdk.GetConversationsResp{}, nil
} }
req.ConversationIDs = datautil.Slice(conversations, func(c *conversation.Conversation) string { req.ConversationIDs = datautil.Slice(conversations, func(c *conversation.Conversation) string {
return c.ConversationID return c.ConversationID
@ -177,19 +232,22 @@ func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) {
return nil, err return nil, err
} }
} }
resp := make([]ConversationMsg, 0, len(conversations)) resp := make([]*jssdk.ConversationMsg, 0, len(conversations))
for _, c := range conversations { for _, c := range conversations {
var lastMsg *sdkws.MsgData var lastMsg *sdkws.MsgData
if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 { if msgList, ok := msgs[c.ConversationID]; ok && len(msgList.Msgs) > 0 {
lastMsg = msgList.Msgs[0] lastMsg = msgList.Msgs[0]
} }
resp = append(resp, ConversationMsg{ resp = append(resp, &jssdk.ConversationMsg{
Conversation: c, Conversation: c,
LastMsg: lastMsg, LastMsg: lastMsg,
MaxSeq: maxSeqs[c.ConversationID], MaxSeq: maxSeqs[c.ConversationID],
ReadSeq: readSeqs[c.ConversationID], ReadSeq: readSeqs[c.ConversationID],
}) })
} }
if err := x.fillConversations(ctx, resp); err != nil {
return nil, err
}
var unreadCount int64 var unreadCount int64
for conversationID, maxSeq := range maxSeqs { for conversationID, maxSeq := range maxSeqs {
count := maxSeq - readSeqs[conversationID] count := maxSeq - readSeqs[conversationID]
@ -197,7 +255,7 @@ func (x *JSSdk) getConversations(ctx *gin.Context) (*ConversationsResp, error) {
unreadCount += count unreadCount += count
} }
} }
return &ConversationsResp{ return &jssdk.GetConversationsResp{
Conversations: resp, Conversations: resp,
UnreadCount: unreadCount, UnreadCount: unreadCount,
}, nil }, nil

View File

@ -1,22 +0,0 @@
package jssdk
import (
"github.com/openimsdk/protocol/conversation"
"github.com/openimsdk/protocol/sdkws"
)
type ActiveConversationsReq struct {
Count int `json:"count"`
}
type ConversationMsg struct {
Conversation *conversation.Conversation `json:"conversation"`
LastMsg *sdkws.MsgData `json:"lastMsg"`
MaxSeq int64 `json:"maxSeq"`
ReadSeq int64 `json:"readSeq"`
}
type ConversationsResp struct {
UnreadCount int64 `json:"unreadCount"`
Conversations []ConversationMsg `json:"conversations"`
}

View File

@ -3,8 +3,14 @@ package jssdk
import ( import (
"context" "context"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/openimsdk/tools/a2r"
"github.com/openimsdk/tools/apiresp" "github.com/openimsdk/tools/apiresp"
"github.com/openimsdk/tools/checker"
"github.com/openimsdk/tools/errs"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/protobuf/proto"
"io"
"strings"
) )
func field[A, B, C any](ctx context.Context, fn func(ctx context.Context, req *A, opts ...grpc.CallOption) (*B, error), req *A, get func(*B) C) (C, error) { func field[A, B, C any](ctx context.Context, fn func(ctx context.Context, req *A, opts ...grpc.CallOption) (*B, error), req *A, get func(*B) C) (C, error) {
@ -16,11 +22,56 @@ func field[A, B, C any](ctx context.Context, fn func(ctx context.Context, req *A
return get(resp), nil return get(resp), nil
} }
func call[R any](c *gin.Context, fn func(ctx *gin.Context) (R, error)) { func call[A, B any](c *gin.Context, fn func(ctx context.Context, req *A) (*B, error)) {
resp, err := fn(c) var isJSON bool
switch contentType := c.GetHeader("Content-Type"); {
case contentType == "":
isJSON = true
case strings.Contains(contentType, "application/json"):
isJSON = true
case strings.Contains(contentType, "application/protobuf"):
case strings.Contains(contentType, "application/x-protobuf"):
default:
apiresp.GinError(c, errs.ErrArgs.WrapMsg("unsupported content type"))
return
}
var req *A
if isJSON {
var err error
req, err = a2r.ParseRequest[A](c)
if err != nil { if err != nil {
apiresp.GinError(c, err) apiresp.GinError(c, err)
return return
} }
apiresp.GinSuccess(c, resp) } else {
body, err := io.ReadAll(c.Request.Body)
if err != nil {
apiresp.GinError(c, err)
return
}
req = new(A)
if err := proto.Unmarshal(body, any(req).(proto.Message)); err != nil {
apiresp.GinError(c, err)
return
}
if err := checker.Validate(&req); err != nil {
apiresp.GinError(c, err)
return
}
}
resp, err := fn(c, req)
if err != nil {
apiresp.GinError(c, err)
return
}
if isJSON {
apiresp.GinSuccess(c, resp)
return
}
body, err := proto.Marshal(any(resp).(proto.Message))
if err != nil {
apiresp.GinError(c, err)
return
}
apiresp.GinSuccess(c, body)
} }

View File

@ -1,37 +0,0 @@
package api
import (
"github.com/openimsdk/protocol/msg"
"sort"
"testing"
)
func TestName(t *testing.T) {
val := sortActiveConversations{
Conversation: []*msg.ActiveConversation{
{
ConversationID: "100",
LastTime: 100,
},
{
ConversationID: "200",
LastTime: 200,
},
{
ConversationID: "300",
LastTime: 300,
},
{
ConversationID: "400",
LastTime: 400,
},
},
//PinnedConversationIDs: map[string]struct{}{
// "100": {},
// "300": {},
//},
}
sort.Sort(&val)
t.Log(val)
}

View File

@ -74,10 +74,10 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
case BestSpeed: case BestSpeed:
r.Use(gzip.Gzip(gzip.BestSpeed)) r.Use(gzip.Gzip(gzip.BestSpeed))
} }
r.Use(prommetricsGin(), gin.Recovery(), mw.CorsHandler(), mw.GinParseOperationID(), GinParseToken(authRpc)) r.Use(prommetricsGin(), gin.RecoveryWithWriter(gin.DefaultErrorWriter, mw.GinPanicErr), mw.CorsHandler(), mw.GinParseOperationID(), GinParseToken(authRpc))
u := NewUserApi(*userRpc) u := NewUserApi(*userRpc)
m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID) m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID)
j := jssdk.NewJSSdkApi(messageRpc.Client, conversationRpc.Client) j := jssdk.NewJSSdkApi(userRpc.Client, friendRpc.Client, groupRpc.Client, messageRpc.Client, conversationRpc.Client)
userRouterGroup := r.Group("/user") userRouterGroup := r.Group("/user")
{ {
userRouterGroup.POST("/user_register", u.UserRegister) userRouterGroup.POST("/user_register", u.UserRegister)

View File

@ -16,7 +16,9 @@ package msggateway
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"github.com/openimsdk/tools/mw"
"runtime/debug" "runtime/debug"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -69,6 +71,8 @@ type Client struct {
IsCompress bool `json:"isCompress"` IsCompress bool `json:"isCompress"`
UserID string `json:"userID"` UserID string `json:"userID"`
IsBackground bool `json:"isBackground"` IsBackground bool `json:"isBackground"`
SDKType string `json:"sdkType"`
Encoder Encoder
ctx *UserConnContext ctx *UserConnContext
longConnServer LongConnServer longConnServer LongConnServer
closed atomic.Bool closed atomic.Bool
@ -94,11 +98,17 @@ func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer
c.closed.Store(false) c.closed.Store(false)
c.closedErr = nil c.closedErr = nil
c.token = ctx.GetToken() c.token = ctx.GetToken()
c.SDKType = ctx.GetSDKType()
c.hbCtx, c.hbCancel = context.WithCancel(c.ctx) c.hbCtx, c.hbCancel = context.WithCancel(c.ctx)
c.subLock = new(sync.Mutex) c.subLock = new(sync.Mutex)
if c.subUserIDs != nil { if c.subUserIDs != nil {
clear(c.subUserIDs) clear(c.subUserIDs)
} }
if c.SDKType == GoSDK {
c.Encoder = NewGobEncoder()
} else {
c.Encoder = NewJsonEncoder()
}
c.subUserIDs = make(map[string]struct{}) c.subUserIDs = make(map[string]struct{})
} }
@ -159,9 +169,12 @@ func (c *Client) readMessage() {
return return
} }
case MessageText: case MessageText:
c.closedErr = ErrNotSupportMessageProtocol _ = c.conn.SetReadDeadline(pongWait)
parseDataErr := c.handlerTextMessage(message)
if parseDataErr != nil {
c.closedErr = parseDataErr
return return
}
case PingMessage: case PingMessage:
err := c.writePongMsg("") err := c.writePongMsg("")
log.ZError(c.ctx, "writePongMsg", err) log.ZError(c.ctx, "writePongMsg", err)
@ -188,7 +201,7 @@ func (c *Client) handleMessage(message []byte) error {
var binaryReq = getReq() var binaryReq = getReq()
defer freeReq(binaryReq) defer freeReq(binaryReq)
err := c.longConnServer.Decode(message, binaryReq) err := c.Encoder.Decode(message, binaryReq)
if err != nil { if err != nil {
return err return err
} }
@ -335,7 +348,7 @@ func (c *Client) writeBinaryMsg(resp Resp) error {
return nil return nil
} }
encodedBuf, err := c.longConnServer.Encode(resp) encodedBuf, err := c.Encoder.Encode(resp)
if err != nil { if err != nil {
return err return err
} }
@ -363,6 +376,11 @@ func (c *Client) writeBinaryMsg(resp Resp) error {
func (c *Client) activeHeartbeat(ctx context.Context) { func (c *Client) activeHeartbeat(ctx context.Context) {
if c.PlatformID == constant.WebPlatformID { if c.PlatformID == constant.WebPlatformID {
go func() { go func() {
defer func() {
if r := recover(); r != nil {
mw.PanicStackToLog(ctx, r)
}
}()
log.ZDebug(ctx, "server initiative send heartbeat start.") log.ZDebug(ctx, "server initiative send heartbeat start.")
ticker := time.NewTicker(pingPeriod) ticker := time.NewTicker(pingPeriod)
defer ticker.Stop() defer ticker.Stop()
@ -419,3 +437,28 @@ func (c *Client) writePongMsg(appData string) error {
return errs.Wrap(err) return errs.Wrap(err)
} }
func (c *Client) handlerTextMessage(b []byte) error {
var msg TextMessage
if err := json.Unmarshal(b, &msg); err != nil {
return err
}
switch msg.Type {
case TextPong:
return nil
case TextPing:
msg.Type = TextPong
msgData, err := json.Marshal(msg)
if err != nil {
return err
}
c.w.Lock()
defer c.w.Unlock()
if err := c.conn.SetWriteDeadline(writeWait); err != nil {
return err
}
return c.conn.WriteMessage(MessageText, msgData)
default:
return fmt.Errorf("not support message type %s", msg.Type)
}
}

View File

@ -27,6 +27,12 @@ const (
GzipCompressionProtocol = "gzip" GzipCompressionProtocol = "gzip"
BackgroundStatus = "isBackground" BackgroundStatus = "isBackground"
SendResponse = "isMsgResp" SendResponse = "isMsgResp"
SDKType = "sdkType"
)
const (
GoSDK = "go"
JsSDK = "js"
) )
const ( const (

View File

@ -153,6 +153,14 @@ func (c *UserConnContext) GetCompression() bool {
return false return false
} }
func (c *UserConnContext) GetSDKType() string {
sdkType := c.Req.URL.Query().Get(SDKType)
if sdkType == "" {
sdkType = GoSDK
}
return sdkType
}
func (c *UserConnContext) ShouldSendResp() bool { func (c *UserConnContext) ShouldSendResp() bool {
errResp, exists := c.Query(SendResponse) errResp, exists := c.Query(SendResponse)
if exists { if exists {
@ -193,7 +201,11 @@ func (c *UserConnContext) ParseEssentialArgs() error {
_, err := strconv.Atoi(platformIDStr) _, err := strconv.Atoi(platformIDStr)
if err != nil { if err != nil {
return servererrs.ErrConnArgsErr.WrapMsg("platformID is not int") return servererrs.ErrConnArgsErr.WrapMsg("platformID is not int")
}
switch sdkType, _ := c.Query(SDKType); sdkType {
case "", GoSDK, JsSDK:
default:
return servererrs.ErrConnArgsErr.WrapMsg("sdkType is not go or js")
} }
return nil return nil
} }

View File

@ -17,6 +17,7 @@ package msggateway
import ( import (
"bytes" "bytes"
"encoding/gob" "encoding/gob"
"encoding/json"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
) )
@ -28,12 +29,12 @@ type Encoder interface {
type GobEncoder struct{} type GobEncoder struct{}
func NewGobEncoder() *GobEncoder { func NewGobEncoder() Encoder {
return &GobEncoder{} return GobEncoder{}
} }
func (g *GobEncoder) Encode(data any) ([]byte, error) { func (g GobEncoder) Encode(data any) ([]byte, error) {
buff := bytes.Buffer{} var buff bytes.Buffer
enc := gob.NewEncoder(&buff) enc := gob.NewEncoder(&buff)
if err := enc.Encode(data); err != nil { if err := enc.Encode(data); err != nil {
return nil, errs.WrapMsg(err, "GobEncoder.Encode failed", "action", "encode") return nil, errs.WrapMsg(err, "GobEncoder.Encode failed", "action", "encode")
@ -41,7 +42,7 @@ func (g *GobEncoder) Encode(data any) ([]byte, error) {
return buff.Bytes(), nil return buff.Bytes(), nil
} }
func (g *GobEncoder) Decode(encodeData []byte, decodeData any) error { func (g GobEncoder) Decode(encodeData []byte, decodeData any) error {
buff := bytes.NewBuffer(encodeData) buff := bytes.NewBuffer(encodeData)
dec := gob.NewDecoder(buff) dec := gob.NewDecoder(buff)
if err := dec.Decode(decodeData); err != nil { if err := dec.Decode(decodeData); err != nil {
@ -49,3 +50,25 @@ func (g *GobEncoder) Decode(encodeData []byte, decodeData any) error {
} }
return nil return nil
} }
type JsonEncoder struct{}
func NewJsonEncoder() Encoder {
return JsonEncoder{}
}
func (g JsonEncoder) Encode(data any) ([]byte, error) {
b, err := json.Marshal(data)
if err != nil {
return nil, errs.New("JsonEncoder.Encode failed", "action", "encode")
}
return b, nil
}
func (g JsonEncoder) Decode(encodeData []byte, decodeData any) error {
err := json.Unmarshal(encodeData, decodeData)
if err != nil {
return errs.New("JsonEncoder.Decode failed", "action", "decode")
}
return nil
}

View File

@ -83,17 +83,11 @@ func NewServer(rpcPort int, longConnServer LongConnServer, conf *Config, ready f
return s return s
} }
func (s *Server) OnlinePushMsg( func (s *Server) OnlinePushMsg(context context.Context, req *msggateway.OnlinePushMsgReq) (*msggateway.OnlinePushMsgResp, error) {
context context.Context,
req *msggateway.OnlinePushMsgReq,
) (*msggateway.OnlinePushMsgResp, error) {
panic("implement me") panic("implement me")
} }
func (s *Server) GetUsersOnlineStatus( func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUsersOnlineStatusReq) (*msggateway.GetUsersOnlineStatusResp, error) {
ctx context.Context,
req *msggateway.GetUsersOnlineStatusReq,
) (*msggateway.GetUsersOnlineStatusResp, error) {
if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) {
return nil, errs.ErrNoPermission.WrapMsg("only app manager") return nil, errs.ErrNoPermission.WrapMsg("only app manager")
} }
@ -155,6 +149,7 @@ func (s *Server) pushToUser(ctx context.Context, userID string, msgData *sdkws.M
(client.IsBackground && client.PlatformID != constant.IOSPlatformID) { (client.IsBackground && client.PlatformID != constant.IOSPlatformID) {
err := client.PushMessage(ctx, msgData) err := client.PushMessage(ctx, msgData)
if err != nil { if err != nil {
log.ZWarn(ctx, "online push msg failed", err, "userID", userID, "platformID", client.PlatformID)
userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code()) userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code())
} else { } else {
if _, ok := s.pushTerminal[client.PlatformID]; ok { if _, ok := s.pushTerminal[client.PlatformID]; ok {
@ -220,10 +215,7 @@ func (s *Server) SuperGroupOnlineBatchPushOneMsg(ctx context.Context, req *msgga
} }
} }
func (s *Server) KickUserOffline( func (s *Server) KickUserOffline(ctx context.Context, req *msggateway.KickUserOfflineReq) (*msggateway.KickUserOfflineResp, error) {
ctx context.Context,
req *msggateway.KickUserOfflineReq,
) (*msggateway.KickUserOfflineResp, error) {
for _, v := range req.KickUserIDList { for _, v := range req.KickUserIDList {
clients, _, ok := s.LongConnServer.GetUserPlatformCons(v, int(req.PlatformID)) clients, _, ok := s.LongConnServer.GetUserPlatformCons(v, int(req.PlatformID))
if !ok { if !ok {

View File

@ -16,6 +16,7 @@ package msggateway
import ( import (
"context" "context"
"encoding/json"
"sync" "sync"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
@ -31,6 +32,16 @@ import (
"github.com/openimsdk/tools/utils/jsonutil" "github.com/openimsdk/tools/utils/jsonutil"
) )
const (
TextPing = "ping"
TextPong = "pong"
)
type TextMessage struct {
Type string `json:"type"`
Body json.RawMessage `json:"body"`
}
type Req struct { type Req struct {
ReqIdentifier int32 `json:"reqIdentifier" validate:"required"` ReqIdentifier int32 `json:"reqIdentifier" validate:"required"`
Token string `json:"token"` Token string `json:"token"`

View File

@ -90,6 +90,19 @@ func (ws *WsServer) ChangeOnlineStatus(concurrent int) {
if _, err := ws.userClient.Client.SetUserOnlineStatus(ctx, req); err != nil { if _, err := ws.userClient.Client.SetUserOnlineStatus(ctx, req); err != nil {
log.ZError(ctx, "update user online status", err) log.ZError(ctx, "update user online status", err)
} }
for _, ss := range req.Status {
for _, online := range ss.Online {
client, _, _ := ws.clients.Get(ss.UserID, int(online))
back := false
if len(client) > 0 {
back = client[0].IsBackground
}
ws.webhookAfterUserOnline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOnline, ss.UserID, int(online), back, ss.ConnID)
}
for _, offline := range ss.Offline {
ws.webhookAfterUserOffline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOffline, ss.UserID, int(offline), ss.ConnID)
}
}
} }
for i := 0; i < concurrent; i++ { for i := 0; i < concurrent; i++ {

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 (
@ -51,7 +37,6 @@ type LongConnServer interface {
SetKickHandlerInfo(i *kickHandler) SetKickHandlerInfo(i *kickHandler)
SubUserOnlineStatus(ctx context.Context, client *Client, data *Req) ([]byte, error) SubUserOnlineStatus(ctx context.Context, client *Client, data *Req) ([]byte, error)
Compressor Compressor
Encoder
MessageHandler MessageHandler
} }
@ -75,7 +60,7 @@ type WsServer struct {
authClient *rpcclient.Auth authClient *rpcclient.Auth
disCov discovery.SvcDiscoveryRegistry disCov discovery.SvcDiscoveryRegistry
Compressor Compressor
Encoder //Encoder
MessageHandler MessageHandler
webhookClient *webhook.Client webhookClient *webhook.Client
} }
@ -149,7 +134,6 @@ func NewWsServer(msgGatewayConfig *Config, opts ...Option) *WsServer {
clients: newUserMap(), clients: newUserMap(),
subscription: newSubscription(), subscription: newSubscription(),
Compressor: NewGzipCompressor(), Compressor: NewGzipCompressor(),
Encoder: NewGobEncoder(),
webhookClient: webhook.NewWebhookClient(msgGatewayConfig.WebhooksConfig.URL), webhookClient: webhook.NewWebhookClient(msgGatewayConfig.WebhooksConfig.URL),
} }
} }
@ -212,7 +196,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)
@ -293,14 +276,7 @@ func (ws *WsServer) registerClient(client *Client) {
wg.Wait() wg.Wait()
log.ZDebug( log.ZDebug(client.ctx, "user online", "online user Num", ws.onlineUserNum.Load(), "online user conn Num", ws.onlineUserConnNum.Load())
client.ctx,
"user online",
"online user Num",
ws.onlineUserNum.Load(),
"online user conn Num",
ws.onlineUserConnNum.Load(),
)
} }
func getRemoteAdders(client []*Client) string { func getRemoteAdders(client []*Client) string {
@ -321,7 +297,26 @@ 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.PCAndOther: case constant.PCAndOther:
if constant.PlatformIDToClass(newClient.PlatformID) == constant.TerminalPC { if constant.PlatformIDToClass(newClient.PlatformID) == constant.TerminalPC {
@ -347,6 +342,20 @@ 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.AllLoginButSameClassKick:
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)
} }
} }

View File

@ -128,6 +128,7 @@ func (m *MsgTransfer) Start(index int, config *Config) error {
go m.historyCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyCH) go m.historyCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyCH)
go m.historyMongoCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyMongoCH) go m.historyMongoCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyMongoCH)
go m.historyCH.HandleUserHasReadSeqMessages(m.ctx)
err := m.historyCH.redisMessageBatches.Start() err := m.historyCH.redisMessageBatches.Start()
if err != nil { if err != nil {
return err return err
@ -135,6 +136,11 @@ func (m *MsgTransfer) Start(index int, config *Config) error {
if config.MsgTransfer.Prometheus.Enable { if config.MsgTransfer.Prometheus.Enable {
go func() { go func() {
defer func() {
if r := recover(); r != nil {
mw.PanicStackToLog(m.ctx, r)
}
}()
prometheusPort, err := datautil.GetElemByIndex(config.MsgTransfer.Prometheus.Ports, index) prometheusPort, err := datautil.GetElemByIndex(config.MsgTransfer.Prometheus.Ports, index)
if err != nil { if err != nil {
netErr = err netErr = err
@ -157,12 +163,14 @@ func (m *MsgTransfer) Start(index int, config *Config) error {
// graceful close kafka client. // graceful close kafka client.
m.cancel() m.cancel()
m.historyCH.redisMessageBatches.Close() m.historyCH.redisMessageBatches.Close()
m.historyCH.Close()
m.historyCH.historyConsumerGroup.Close() m.historyCH.historyConsumerGroup.Close()
m.historyMongoCH.historyConsumerGroup.Close() m.historyMongoCH.historyConsumerGroup.Close()
return nil return nil
case <-netDone: case <-netDone:
m.cancel() m.cancel()
m.historyCH.redisMessageBatches.Close() m.historyCH.redisMessageBatches.Close()
m.historyCH.Close()
m.historyCH.historyConsumerGroup.Close() m.historyCH.historyConsumerGroup.Close()
m.historyMongoCH.historyConsumerGroup.Close() m.historyMongoCH.historyConsumerGroup.Close()
close(netDone) close(netDone)

View File

@ -18,8 +18,11 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/tools/mw"
"strconv" "strconv"
"strings" "strings"
"sync"
"time" "time"
"github.com/IBM/sarama" "github.com/IBM/sarama"
@ -45,6 +48,7 @@ const (
subChanBuffer = 50 subChanBuffer = 50
worker = 50 worker = 50
interval = 100 * time.Millisecond interval = 100 * time.Millisecond
hasReadChanBuffer = 1000
) )
type ContextMsg struct { type ContextMsg struct {
@ -52,6 +56,13 @@ type ContextMsg struct {
ctx context.Context ctx context.Context
} }
// This structure is used for asynchronously writing the senders read sequence (seq) regarding a message into MongoDB.
// For example, if the sender sends a message with a seq of 10, then their own read seq for this conversation should be set to 10.
type userHasReadSeq struct {
conversationID string
userHasReadMap map[string]int64
}
type OnlineHistoryRedisConsumerHandler struct { type OnlineHistoryRedisConsumerHandler struct {
historyConsumerGroup *kafka.MConsumerGroup historyConsumerGroup *kafka.MConsumerGroup
@ -60,6 +71,8 @@ type OnlineHistoryRedisConsumerHandler struct {
msgTransferDatabase controller.MsgTransferDatabase msgTransferDatabase controller.MsgTransferDatabase
conversationRpcClient *rpcclient.ConversationRpcClient conversationRpcClient *rpcclient.ConversationRpcClient
groupRpcClient *rpcclient.GroupRpcClient groupRpcClient *rpcclient.GroupRpcClient
conversationUserHasReadChan chan *userHasReadSeq
wg sync.WaitGroup
} }
func NewOnlineHistoryRedisConsumerHandler(kafkaConf *config.Kafka, database controller.MsgTransferDatabase, func NewOnlineHistoryRedisConsumerHandler(kafkaConf *config.Kafka, database controller.MsgTransferDatabase,
@ -70,6 +83,8 @@ func NewOnlineHistoryRedisConsumerHandler(kafkaConf *config.Kafka, database cont
} }
var och OnlineHistoryRedisConsumerHandler var och OnlineHistoryRedisConsumerHandler
och.msgTransferDatabase = database och.msgTransferDatabase = database
och.conversationUserHasReadChan = make(chan *userHasReadSeq, hasReadChanBuffer)
och.wg.Add(1)
b := batcher.New[sarama.ConsumerMessage]( b := batcher.New[sarama.ConsumerMessage](
batcher.WithSize(size), batcher.WithSize(size),
@ -115,25 +130,25 @@ func (och *OnlineHistoryRedisConsumerHandler) do(ctx context.Context, channelID
} }
func (och *OnlineHistoryRedisConsumerHandler) doSetReadSeq(ctx context.Context, msgs []*ContextMsg) { func (och *OnlineHistoryRedisConsumerHandler) doSetReadSeq(ctx context.Context, msgs []*ContextMsg) {
type seqKey struct {
conversationID string var conversationID string
userID string var userSeqMap map[string]int64
}
var readSeq map[seqKey]int64
for _, msg := range msgs { for _, msg := range msgs {
if msg.message.ContentType != constant.HasReadReceipt { if msg.message.ContentType != constant.HasReadReceipt {
continue continue
} }
var elem sdkws.NotificationElem var elem sdkws.NotificationElem
if err := json.Unmarshal(msg.message.Content, &elem); err != nil { if err := json.Unmarshal(msg.message.Content, &elem); err != nil {
log.ZError(ctx, "handlerConversationRead Unmarshal NotificationElem msg err", err, "msg", msg) log.ZWarn(ctx, "handlerConversationRead Unmarshal NotificationElem msg err", err, "msg", msg)
continue continue
} }
var tips sdkws.MarkAsReadTips var tips sdkws.MarkAsReadTips
if err := json.Unmarshal([]byte(elem.Detail), &tips); err != nil { if err := json.Unmarshal([]byte(elem.Detail), &tips); err != nil {
log.ZError(ctx, "handlerConversationRead Unmarshal MarkAsReadTips msg err", err, "msg", msg) log.ZWarn(ctx, "handlerConversationRead Unmarshal MarkAsReadTips msg err", err, "msg", msg)
continue continue
} }
//The conversation ID for each batch of messages processed by the batcher is the same.
conversationID = tips.ConversationID
if len(tips.Seqs) > 0 { if len(tips.Seqs) > 0 {
for _, seq := range tips.Seqs { for _, seq := range tips.Seqs {
if tips.HasReadSeq < seq { if tips.HasReadSeq < seq {
@ -146,26 +161,25 @@ func (och *OnlineHistoryRedisConsumerHandler) doSetReadSeq(ctx context.Context,
if tips.HasReadSeq < 0 { if tips.HasReadSeq < 0 {
continue continue
} }
if readSeq == nil { if userSeqMap == nil {
readSeq = make(map[seqKey]int64) userSeqMap = make(map[string]int64)
} }
key := seqKey{
conversationID: tips.ConversationID, if userSeqMap[tips.MarkAsReadUserID] > tips.HasReadSeq {
userID: tips.MarkAsReadUserID,
}
if readSeq[key] > tips.HasReadSeq {
continue continue
} }
readSeq[key] = tips.HasReadSeq userSeqMap[tips.MarkAsReadUserID] = tips.HasReadSeq
} }
if readSeq == nil { if userSeqMap == nil {
return return
} }
for key, seq := range readSeq { if len(conversationID) == 0 {
if err := och.msgTransferDatabase.SetHasReadSeqToDB(ctx, key.userID, key.conversationID, seq); err != nil { log.ZWarn(ctx, "conversation err", nil, "conversationID", conversationID)
log.ZError(ctx, "set read seq to db error", err, "userID", key.userID, "conversationID", key.conversationID, "seq", seq)
} }
if err := och.msgTransferDatabase.SetHasReadSeqToDB(ctx, conversationID, userSeqMap); err != nil {
log.ZWarn(ctx, "set read seq to db error", err, "conversationID", conversationID, "userSeqMap", userSeqMap)
} }
} }
func (och *OnlineHistoryRedisConsumerHandler) parseConsumerMessages(ctx context.Context, consumerMessages []*sarama.ConsumerMessage) []*ContextMsg { func (och *OnlineHistoryRedisConsumerHandler) parseConsumerMessages(ctx context.Context, consumerMessages []*sarama.ConsumerMessage) []*ContextMsg {
@ -250,12 +264,21 @@ func (och *OnlineHistoryRedisConsumerHandler) handleMsg(ctx context.Context, key
} }
if len(storageMessageList) > 0 { if len(storageMessageList) > 0 {
msg := storageMessageList[0] msg := storageMessageList[0]
lastSeq, isNewConversation, err := och.msgTransferDatabase.BatchInsertChat2Cache(ctx, conversationID, storageMessageList) lastSeq, isNewConversation, userSeqMap, err := och.msgTransferDatabase.BatchInsertChat2Cache(ctx, conversationID, storageMessageList)
if err != nil && !errors.Is(errs.Unwrap(err), redis.Nil) { if err != nil && !errors.Is(errs.Unwrap(err), redis.Nil) {
log.ZError(ctx, "batch data insert to redis err", err, "storageMsgList", storageMessageList) log.ZWarn(ctx, "batch data insert to redis err", err, "storageMsgList", storageMessageList)
return return
} }
log.ZInfo(ctx, "BatchInsertChat2Cache end") log.ZInfo(ctx, "BatchInsertChat2Cache end")
err = och.msgTransferDatabase.SetHasReadSeqs(ctx, conversationID, userSeqMap)
if err != nil {
log.ZWarn(ctx, "SetHasReadSeqs error", err, "userSeqMap", userSeqMap, "conversationID", conversationID)
prommetrics.SeqSetFailedCounter.Inc()
}
och.conversationUserHasReadChan <- &userHasReadSeq{
conversationID: conversationID,
userHasReadMap: userSeqMap,
}
if isNewConversation { if isNewConversation {
switch msg.SessionType { switch msg.SessionType {
@ -308,7 +331,7 @@ func (och *OnlineHistoryRedisConsumerHandler) handleNotification(ctx context.Con
storageMessageList = append(storageMessageList, msg.message) storageMessageList = append(storageMessageList, msg.message)
} }
if len(storageMessageList) > 0 { if len(storageMessageList) > 0 {
lastSeq, _, err := och.msgTransferDatabase.BatchInsertChat2Cache(ctx, conversationID, storageMessageList) lastSeq, _, _, err := och.msgTransferDatabase.BatchInsertChat2Cache(ctx, conversationID, storageMessageList)
if err != nil { if err != nil {
log.ZError(ctx, "notification batch insert to redis error", err, "conversationID", conversationID, log.ZError(ctx, "notification batch insert to redis error", err, "conversationID", conversationID,
"storageList", storageMessageList) "storageList", storageMessageList)
@ -323,6 +346,27 @@ func (och *OnlineHistoryRedisConsumerHandler) handleNotification(ctx context.Con
och.toPushTopic(ctx, key, conversationID, storageList) och.toPushTopic(ctx, key, conversationID, storageList)
} }
} }
func (och *OnlineHistoryRedisConsumerHandler) HandleUserHasReadSeqMessages(ctx context.Context) {
defer func() {
if r := recover(); r != nil {
mw.PanicStackToLog(ctx, r)
}
}()
defer och.wg.Done()
for msg := range och.conversationUserHasReadChan {
if err := och.msgTransferDatabase.SetHasReadSeqToDB(ctx, msg.conversationID, msg.userHasReadMap); err != nil {
log.ZWarn(ctx, "set read seq to db error", err, "conversationID", msg.conversationID, "userSeqMap", msg.userHasReadMap)
}
}
log.ZInfo(ctx, "Channel closed, exiting handleUserHasReadSeqMessages")
}
func (och *OnlineHistoryRedisConsumerHandler) Close() {
close(och.conversationUserHasReadChan)
och.wg.Wait()
}
func (och *OnlineHistoryRedisConsumerHandler) toPushTopic(ctx context.Context, key, conversationID string, msgs []*ContextMsg) { func (och *OnlineHistoryRedisConsumerHandler) toPushTopic(ctx context.Context, key, conversationID string, msgs []*ContextMsg) {
for _, v := range msgs { for _, v := range msgs {

View File

@ -1,29 +0,0 @@
package push
import (
"github.com/openimsdk/protocol/sdkws"
"testing"
)
func TestName(t *testing.T) {
var c ConsumerHandler
c.readCh = make(chan *sdkws.MarkAsReadTips)
go c.loopRead()
go func() {
for i := 0; ; i++ {
seq := int64(i + 1)
if seq%3 == 0 {
seq = 1
}
c.readCh <- &sdkws.MarkAsReadTips{
ConversationID: "c100",
MarkAsReadUserID: "u100",
HasReadSeq: seq,
}
}
}()
select {}
}

View File

@ -24,7 +24,6 @@ import (
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil"
) )
func (c *ConsumerHandler) webhookBeforeOfflinePush(ctx context.Context, before *config.BeforeConfig, userIDs []string, msg *sdkws.MsgData, offlinePushUserIDs *[]string) error { func (c *ConsumerHandler) webhookBeforeOfflinePush(ctx context.Context, before *config.BeforeConfig, userIDs []string, msg *sdkws.MsgData, offlinePushUserIDs *[]string) error {
@ -70,7 +69,7 @@ func (c *ConsumerHandler) webhookBeforeOfflinePush(ctx context.Context, before *
func (c *ConsumerHandler) webhookBeforeOnlinePush(ctx context.Context, before *config.BeforeConfig, userIDs []string, msg *sdkws.MsgData) error { func (c *ConsumerHandler) webhookBeforeOnlinePush(ctx context.Context, before *config.BeforeConfig, userIDs []string, msg *sdkws.MsgData) error {
return webhook.WithCondition(ctx, before, func(ctx context.Context) error { return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
if datautil.Contain(msg.SendID, userIDs...) || msg.ContentType == constant.Typing { if msg.ContentType == constant.Typing {
return nil return nil
} }
req := callbackstruct.CallbackBeforePushReq{ req := callbackstruct.CallbackBeforePushReq{

View File

@ -29,5 +29,6 @@ type Dummy struct {
func (d *Dummy) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error { func (d *Dummy) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error {
log.ZDebug(ctx, "dummy push") log.ZDebug(ctx, "dummy push")
log.ZWarn(ctx, "Dummy push", nil, "ps", "The offline push is not configured. To configure it, please go to config/openim-push.yml.")
return nil return nil
} }

View File

@ -15,6 +15,7 @@
package body package body
import ( import (
"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"
) )
@ -26,38 +27,44 @@ type Notification struct {
type Android struct { type Android struct {
Alert string `json:"alert,omitempty"` Alert string `json:"alert,omitempty"`
Title string `json:"title,omitempty"`
Intent struct { Intent struct {
URL string `json:"url,omitempty"` URL string `json:"url,omitempty"`
} `json:"intent,omitempty"` } `json:"intent,omitempty"`
Extras Extras `json:"extras"` Extras map[string]string `json:"extras,omitempty"`
} }
type Ios struct { type Ios struct {
Alert string `json:"alert,omitempty"` Alert IosAlert `json:"alert,omitempty"`
Sound string `json:"sound,omitempty"` Sound string `json:"sound,omitempty"`
Badge string `json:"badge,omitempty"` Badge string `json:"badge,omitempty"`
Extras Extras `json:"extras"` Extras map[string]string `json:"extras,omitempty"`
MutableContent bool `json:"mutable-content"` MutableContent bool `json:"mutable-content"`
} }
type Extras struct { type IosAlert struct {
ClientMsgID string `json:"clientMsgID"` Title string `json:"title,omitempty"`
Body string `json:"body,omitempty"`
} }
func (n *Notification) SetAlert(alert string) { func (n *Notification) SetAlert(alert string, title string, opts *options.Opts) {
n.Alert = alert n.Alert = alert
n.Android.Alert = alert n.Android.Alert = alert
n.IOS.Alert = alert n.Android.Title = title
n.IOS.Sound = "default" n.IOS.Alert.Body = alert
n.IOS.Alert.Title = title
n.IOS.Sound = opts.IOSPushSound
if opts.IOSBadgeCount {
n.IOS.Badge = "+1" n.IOS.Badge = "+1"
} }
}
func (n *Notification) SetExtras(extras Extras) { func (n *Notification) SetExtras(extras map[string]string) {
n.IOS.Extras = extras n.IOS.Extras = extras
n.Android.Extras = extras n.Android.Extras = extras
} }
func (n *Notification) SetAndroidIntent(pushConf *config.Push) { func (n *Notification) SetAndroidIntent(pushConf *config.Push) {
n.Android.Intent.URL = pushConf.JPNS.PushIntent n.Android.Intent.URL = pushConf.JPush.PushIntent
} }
func (n *Notification) IOSEnableMutableContent() { func (n *Notification) IOSEnableMutableContent() {

View File

@ -18,9 +18,9 @@ import (
"context" "context"
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/jpush/body" "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/jpush/body"
"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/tools/utils/httputil" "github.com/openimsdk/tools/utils/httputil"
) )
@ -57,17 +57,23 @@ func (j *JPush) Push(ctx context.Context, userIDs []string, title, content strin
var au body.Audience var au body.Audience
au.SetAlias(userIDs) au.SetAlias(userIDs)
var no body.Notification var no body.Notification
var extras body.Extras extras := make(map[string]string)
extras["ex"] = opts.Ex
if opts.Signal.ClientMsgID != "" { if opts.Signal.ClientMsgID != "" {
extras.ClientMsgID = opts.Signal.ClientMsgID extras["ClientMsgID"] = opts.Signal.ClientMsgID
} }
no.IOSEnableMutableContent() no.IOSEnableMutableContent()
no.SetExtras(extras) no.SetExtras(extras)
no.SetAlert(title) no.SetAlert(content, title, opts)
no.SetAndroidIntent(j.pushConf) no.SetAndroidIntent(j.pushConf)
var msg body.Message var msg body.Message
msg.SetMsgContent(content) msg.SetMsgContent(content)
msg.SetTitle(title)
if opts.Signal.ClientMsgID != "" {
msg.SetExtras("ClientMsgID", opts.Signal.ClientMsgID)
}
msg.SetExtras("ex", opts.Ex)
var opt body.Options var opt body.Options
opt.SetApnsProduction(j.pushConf.IOSPush.Production) opt.SetApnsProduction(j.pushConf.IOSPush.Production)
var pushObj body.PushObj var pushObj body.PushObj
@ -76,19 +82,26 @@ func (j *JPush) Push(ctx context.Context, userIDs []string, title, content strin
pushObj.SetNotification(&no) pushObj.SetNotification(&no)
pushObj.SetMessage(&msg) pushObj.SetMessage(&msg)
pushObj.SetOptions(&opt) pushObj.SetOptions(&opt)
var resp any var resp map[string]any
return j.request(ctx, pushObj, resp, 5) return j.request(ctx, pushObj, &resp, 5)
} }
func (j *JPush) request(ctx context.Context, po body.PushObj, resp any, timeout int) error { func (j *JPush) request(ctx context.Context, po body.PushObj, resp *map[string]any, timeout int) error {
return j.httpClient.PostReturn( err := j.httpClient.PostReturn(
ctx, ctx,
j.pushConf.JPNS.PushURL, j.pushConf.JPush.PushURL,
map[string]string{ map[string]string{
"Authorization": j.getAuthorization(j.pushConf.JPNS.AppKey, j.pushConf.JPNS.MasterSecret), "Authorization": j.getAuthorization(j.pushConf.JPush.AppKey, j.pushConf.JPush.MasterSecret),
}, },
po, po,
resp, resp,
timeout, timeout,
) )
if err != nil {
return err
}
if (*resp)["sendno"] != "0" {
return fmt.Errorf("jpush push failed %v", resp)
}
return nil
} }

View File

@ -23,10 +23,11 @@ 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"
"strings"
) )
const ( const (
geTUI = "geTui" geTUI = "getui"
firebase = "fcm" firebase = "fcm"
jPush = "jpush" jPush = "jpush"
) )
@ -38,6 +39,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)

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)
@ -70,7 +73,7 @@ func (o *OfflinePushConsumerHandler) getOfflinePushInfos(msg *sdkws.MsgData) (ti
IsAtSelf bool `json:"isAtSelf"` IsAtSelf bool `json:"isAtSelf"`
} }
opts = &options.Opts{Signal: &options.Signal{}} opts = &options.Opts{Signal: &options.Signal{ClientMsgID: msg.ClientMsgID}}
if msg.OfflinePushInfo != nil { if msg.OfflinePushInfo != nil {
opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount
opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound

View File

@ -4,6 +4,10 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"math/rand"
"strconv"
"time"
"github.com/IBM/sarama" "github.com/IBM/sarama"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush" "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
@ -27,9 +31,6 @@ import (
"github.com/openimsdk/tools/utils/timeutil" "github.com/openimsdk/tools/utils/timeutil"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
"math/rand"
"strconv"
"time"
) )
type ConsumerHandler struct { type ConsumerHandler struct {
@ -165,17 +166,21 @@ func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *
return nil return nil
} }
} }
offlinePushUserID := []string{msg.RecvID} needOfflinePushUserID := []string{msg.RecvID}
var offlinePushUserID []string
//receiver offline push //receiver offline push
if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, needOfflinePushUserID, msg, &offlinePushUserID); err != nil {
offlinePushUserID, msg, nil); err != nil {
return err return err
} }
log.ZInfo(ctx, "webhookBeforeOfflinePush end") log.ZInfo(ctx, "webhookBeforeOfflinePush end")
err = c.offlinePushMsg(ctx, msg, offlinePushUserID)
if len(offlinePushUserID) > 0 {
needOfflinePushUserID = offlinePushUserID
}
err = c.offlinePushMsg(ctx, msg, needOfflinePushUserID)
if err != nil { if err != nil {
log.ZWarn(ctx, "offlinePushMsg failed", err, "offlinePushUserID", offlinePushUserID, "msg", msg) log.ZWarn(ctx, "offlinePushMsg failed", err, "needOfflinePushUserID", needOfflinePushUserID, "msg", msg)
return nil return nil
} }
@ -194,6 +199,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
@ -332,6 +340,7 @@ func (c *ConsumerHandler) groupMessagesHandler(ctx context.Context, groupID stri
func (c *ConsumerHandler) offlinePushMsg(ctx context.Context, msg *sdkws.MsgData, offlinePushUserIDs []string) error { func (c *ConsumerHandler) offlinePushMsg(ctx context.Context, msg *sdkws.MsgData, offlinePushUserIDs []string) error {
title, content, opts, err := c.getOfflinePushInfos(msg) title, content, opts, err := c.getOfflinePushInfos(msg)
if err != nil { if err != nil {
log.ZError(ctx, "getOfflinePushInfos failed", err, "msg", msg)
return err return err
} }
err = c.offlinePusher.Push(ctx, offlinePushUserIDs, title, content, opts) err = c.offlinePusher.Push(ctx, offlinePushUserIDs, title, content, opts)
@ -361,7 +370,7 @@ func (c *ConsumerHandler) getOfflinePushInfos(msg *sdkws.MsgData) (title, conten
IsAtSelf bool `json:"isAtSelf"` IsAtSelf bool `json:"isAtSelf"`
} }
opts = &options.Opts{Signal: &options.Signal{}} opts = &options.Opts{Signal: &options.Signal{ClientMsgID: msg.ClientMsgID}}
if msg.OfflinePushInfo != nil { if msg.OfflinePushInfo != nil {
opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount opts.IOSBadgeCount = msg.OfflinePushInfo.IOSBadgeCount
opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound opts.IOSPushSound = msg.OfflinePushInfo.IOSPushSound

View File

@ -16,6 +16,7 @@ package auth
import ( import (
"context" "context"
"errors"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
@ -65,7 +66,8 @@ 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.Share.IMAdminUserID,
), ),
config: config, config: config,
}) })
@ -129,6 +131,10 @@ func (s *authServer) parseToken(ctx context.Context, tokensString string) (claim
if err != nil { if err != nil {
return nil, errs.Wrap(err) return nil, errs.Wrap(err)
} }
isAdmin := authverify.IsManagerUserID(claims.UserID, s.config.Share.IMAdminUserID)
if isAdmin {
return claims, nil
}
m, err := s.authDatabase.GetTokensWithoutError(ctx, claims.UserID, claims.PlatformID) m, err := s.authDatabase.GetTokensWithoutError(ctx, claims.UserID, claims.PlatformID)
if err != nil { if err != nil {
return nil, err return nil, err
@ -190,7 +196,7 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID
} }
m, err := s.authDatabase.GetTokensWithoutError(ctx, userID, int(platformID)) m, err := s.authDatabase.GetTokensWithoutError(ctx, userID, int(platformID))
if err != nil && err != redis.Nil { if err != nil && errors.Is(err, redis.Nil) {
return err return err
} }
for k := range m { for k := range m {
@ -208,7 +214,7 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID
func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.InvalidateTokenReq) (*pbauth.InvalidateTokenResp, error) { func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.InvalidateTokenReq) (*pbauth.InvalidateTokenResp, error) {
m, err := s.authDatabase.GetTokensWithoutError(ctx, req.UserID, int(req.PlatformID)) m, err := s.authDatabase.GetTokensWithoutError(ctx, req.UserID, int(req.PlatformID))
if err != nil && err != redis.Nil { if err != nil && errors.Is(err, redis.Nil) {
return nil, err return nil, err
} }
if m == nil { if m == nil {
@ -230,3 +236,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

@ -261,27 +261,35 @@ func (c *conversationServer) SetConversations(ctx context.Context, req *pbconver
setConversationFieldsFunc := func() { setConversationFieldsFunc := func() {
if req.Conversation.RecvMsgOpt != nil { if req.Conversation.RecvMsgOpt != nil {
conversation.RecvMsgOpt = req.Conversation.RecvMsgOpt.Value
m["recv_msg_opt"] = req.Conversation.RecvMsgOpt.Value m["recv_msg_opt"] = req.Conversation.RecvMsgOpt.Value
} }
if req.Conversation.AttachedInfo != nil { if req.Conversation.AttachedInfo != nil {
conversation.AttachedInfo = req.Conversation.AttachedInfo.Value
m["attached_info"] = req.Conversation.AttachedInfo.Value m["attached_info"] = req.Conversation.AttachedInfo.Value
} }
if req.Conversation.Ex != nil { if req.Conversation.Ex != nil {
conversation.Ex = req.Conversation.Ex.Value
m["ex"] = req.Conversation.Ex.Value m["ex"] = req.Conversation.Ex.Value
} }
if req.Conversation.IsPinned != nil { if req.Conversation.IsPinned != nil {
conversation.IsPinned = req.Conversation.IsPinned.Value
m["is_pinned"] = req.Conversation.IsPinned.Value m["is_pinned"] = req.Conversation.IsPinned.Value
} }
if req.Conversation.GroupAtType != nil { if req.Conversation.GroupAtType != nil {
conversation.GroupAtType = req.Conversation.GroupAtType.Value
m["group_at_type"] = req.Conversation.GroupAtType.Value m["group_at_type"] = req.Conversation.GroupAtType.Value
} }
if req.Conversation.MsgDestructTime != nil { if req.Conversation.MsgDestructTime != nil {
conversation.MsgDestructTime = req.Conversation.MsgDestructTime.Value
m["msg_destruct_time"] = req.Conversation.MsgDestructTime.Value m["msg_destruct_time"] = req.Conversation.MsgDestructTime.Value
} }
if req.Conversation.IsMsgDestruct != nil { if req.Conversation.IsMsgDestruct != nil {
conversation.IsMsgDestruct = req.Conversation.IsMsgDestruct.Value
m["is_msg_destruct"] = req.Conversation.IsMsgDestruct.Value m["is_msg_destruct"] = req.Conversation.IsMsgDestruct.Value
} }
if req.Conversation.BurnDuration != nil { if req.Conversation.BurnDuration != nil {
conversation.BurnDuration = req.Conversation.BurnDuration.Value
m["burn_duration"] = req.Conversation.BurnDuration.Value m["burn_duration"] = req.Conversation.BurnDuration.Value
} }
} }

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
@ -1026,7 +1026,7 @@ func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
} }
num := len(update) num := len(update)
if req.GroupInfoForSet.Notification != "" { if req.GroupInfoForSet.Notification != "" {
num-- num -= 3
func() { func() {
conversation := &pbconversation.ConversationReq{ conversation := &pbconversation.ConversationReq{
ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupInfoForSet.GroupID), ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupInfoForSet.GroupID),
@ -1133,8 +1133,9 @@ func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupI
} }
num := len(updatedData) num := len(updatedData)
if req.Notification != nil { if req.Notification != nil {
num-- num -= 3
if req.Notification.Value != "" { if req.Notification.Value != "" {
func() { func() {
@ -1180,36 +1181,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.After(time.Now()) {
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 +1235,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 +1444,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
} }
@ -1485,9 +1510,6 @@ func (g *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
return nil, errs.ErrNoPermission.WrapMsg("no op user id") return nil, errs.ErrNoPermission.WrapMsg("no op user id")
} }
isAppManagerUid := authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) isAppManagerUid := authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID)
for i := range req.Members {
req.Members[i].FaceURL = nil
}
groupMembers := make(map[string][]*pbgroup.SetGroupMemberInfo) groupMembers := make(map[string][]*pbgroup.SetGroupMemberInfo)
for i, member := range req.Members { for i, member := range req.Members {
if member.RoleLevel != nil { if member.RoleLevel != nil {
@ -1529,10 +1551,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")
} }
@ -1542,17 +1577,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:
@ -1757,7 +1811,6 @@ func (g *groupServer) GetSpecifiedUserGroupRequestInfo(ctx context.Context, req
} }
if req.UserID != opUserID { if req.UserID != opUserID {
req.UserID = mcontext.GetOpUserID(ctx)
adminIDs, err := g.db.GetGroupRoleLevelMemberIDs(ctx, req.GroupID, constant.GroupAdmin) adminIDs, err := g.db.GetGroupRoleLevelMemberIDs(ctx, req.GroupID, constant.GroupAdmin)
if err != nil { if err != nil {
return nil, err return nil, err
@ -1766,10 +1819,11 @@ func (g *groupServer) GetSpecifiedUserGroupRequestInfo(ctx context.Context, req
adminIDs = append(adminIDs, owners[0].UserID) adminIDs = append(adminIDs, owners[0].UserID)
adminIDs = append(adminIDs, g.config.Share.IMAdminUserID...) adminIDs = append(adminIDs, g.config.Share.IMAdminUserID...)
if !datautil.Contain(req.UserID, adminIDs...) { if !datautil.Contain(opUserID, adminIDs...) {
return nil, errs.ErrNoPermission.WrapMsg("opUser no permission") return nil, errs.ErrNoPermission.WrapMsg("opUser no permission")
} }
} }
requests, err := g.db.FindGroupRequests(ctx, req.GroupID, []string{req.UserID}) requests, err := g.db.FindGroupRequests(ctx, req.GroupID, []string{req.UserID})
if err != nil { if err != nil {
return nil, err return nil, err

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

@ -16,6 +16,7 @@ package msg
import ( import (
"context" "context"
"errors"
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
@ -108,7 +109,7 @@ func (m *msgServer) MarkMsgsAsRead(ctx context.Context, req *msg.MarkMsgsAsReadR
return nil, err return nil, err
} }
currentHasReadSeq, err := m.MsgDatabase.GetHasReadSeq(ctx, req.UserID, req.ConversationID) currentHasReadSeq, err := m.MsgDatabase.GetHasReadSeq(ctx, req.UserID, req.ConversationID)
if err != nil && errs.Unwrap(err) != redis.Nil { if err != nil && !errors.Is(err, redis.Nil) {
return nil, err return nil, err
} }
if hasReadSeq > currentHasReadSeq { if hasReadSeq > currentHasReadSeq {
@ -136,7 +137,7 @@ func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkCon
return nil, err return nil, err
} }
hasReadSeq, err := m.MsgDatabase.GetHasReadSeq(ctx, req.UserID, req.ConversationID) hasReadSeq, err := m.MsgDatabase.GetHasReadSeq(ctx, req.UserID, req.ConversationID)
if err != nil && errs.Unwrap(err) != redis.Nil { if err != nil && errors.Is(err, redis.Nil) {
return nil, err return nil, err
} }
var seqs []int64 var seqs []int64
@ -180,14 +181,23 @@ func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkCon
req.UserID, seqs, hasReadSeq) req.UserID, seqs, hasReadSeq)
} }
if conversation.ConversationType == constant.SingleChatType {
reqCall := &cbapi.CallbackSingleMsgReadReq{
ConversationID: conversation.ConversationID,
UserID: conversation.OwnerUserID,
Seqs: req.Seqs,
ContentType: conversation.ConversationType,
}
m.webhookAfterSingleMsgRead(ctx, &m.config.WebhooksConfig.AfterSingleMsgRead, reqCall)
} else if conversation.ConversationType == constant.ReadGroupChatType {
reqCall := &cbapi.CallbackGroupMsgReadReq{ reqCall := &cbapi.CallbackGroupMsgReadReq{
SendID: conversation.OwnerUserID, SendID: conversation.OwnerUserID,
ReceiveID: req.UserID, ReceiveID: req.UserID,
UnreadMsgNum: req.HasReadSeq, UnreadMsgNum: req.HasReadSeq,
ContentType: int64(conversation.ConversationType), ContentType: int64(conversation.ConversationType),
} }
m.webhookAfterGroupMsgRead(ctx, &m.config.WebhooksConfig.AfterGroupMsgRead, reqCall) m.webhookAfterGroupMsgRead(ctx, &m.config.WebhooksConfig.AfterGroupMsgRead, reqCall)
}
return &msg.MarkConversationAsReadResp{}, nil return &msg.MarkConversationAsReadResp{}, nil
} }

View File

@ -16,6 +16,7 @@ package msg
import ( import (
"context" "context"
"github.com/openimsdk/tools/mw"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
@ -29,7 +30,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) {
@ -79,14 +79,25 @@ 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)
defer func() {
if r := recover(); r != nil {
mw.PanicStackToLog(nctx, r)
}
}()
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 +105,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)
@ -153,9 +174,6 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq
prommetrics.SingleChatMsgProcessFailedCounter.Inc() prommetrics.SingleChatMsgProcessFailedCounter.Inc()
return nil, nil return nil, nil
} else { } else {
if err = m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, req); err != nil {
return nil, err
}
if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil { if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil {
return nil, err return nil, err
} }

View File

@ -16,15 +16,15 @@ package msg
import ( import (
"context" "context"
"errors"
pbmsg "github.com/openimsdk/protocol/msg" pbmsg "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/tools/errs"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"sort" "sort"
) )
func (m *msgServer) GetConversationMaxSeq(ctx context.Context, req *pbmsg.GetConversationMaxSeqReq) (*pbmsg.GetConversationMaxSeqResp, error) { func (m *msgServer) GetConversationMaxSeq(ctx context.Context, req *pbmsg.GetConversationMaxSeqReq) (*pbmsg.GetConversationMaxSeqResp, error) {
maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID) maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID)
if err != nil && errs.Unwrap(err) != redis.Nil { if err != nil && !errors.Is(err, redis.Nil) {
return nil, err return nil, err
} }
return &pbmsg.GetConversationMaxSeqResp{MaxSeq: maxSeq}, nil return &pbmsg.GetConversationMaxSeqResp{MaxSeq: maxSeq}, nil

View File

@ -139,3 +139,11 @@ func (m *msgServer) conversationAndGetRecvID(conversation *conversation.Conversa
} }
return "" return ""
} }
func (m *msgServer) AppendStreamMsg(ctx context.Context, req *msg.AppendStreamMsgReq) (*msg.AppendStreamMsgResp, error) {
return nil, nil
}
func (m *msgServer) GetStreamMsg(ctx context.Context, req *msg.GetStreamMsgReq) (*msg.GetStreamMsgResp, error) {
return nil, nil
}

View File

@ -59,6 +59,9 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe
data.MsgData.ContentType >= constant.NotificationBegin { data.MsgData.ContentType >= constant.NotificationBegin {
return nil return nil
} }
if err := m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, data); err != nil {
return err
}
black, err := m.FriendLocalCache.IsBlack(ctx, data.MsgData.SendID, data.MsgData.RecvID) black, err := m.FriendLocalCache.IsBlack(ctx, data.MsgData.SendID, data.MsgData.RecvID)
if err != nil { if err != nil {
return err return err

View File

@ -273,7 +273,14 @@ func (s *friendServer) SetFriendRemark(ctx context.Context, req *relation.SetFri
return &relation.SetFriendRemarkResp{}, nil return &relation.SetFriendRemarkResp{}, nil
} }
// ok. func (s *friendServer) GetFriendInfo(ctx context.Context, req *relation.GetFriendInfoReq) (*relation.GetFriendInfoResp, error) {
friends, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs)
if err != nil {
return nil, err
}
return &relation.GetFriendInfoResp{FriendInfos: convert.FriendOnlyDB2PbOnly(friends)}, nil
}
func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *relation.GetDesignatedFriendsReq) (resp *relation.GetDesignatedFriendsResp, err error) { func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *relation.GetDesignatedFriendsReq) (resp *relation.GetDesignatedFriendsResp, err error) {
resp = &relation.GetDesignatedFriendsResp{} resp = &relation.GetDesignatedFriendsResp{}
if datautil.Duplicate(req.FriendUserIDs) { if datautil.Duplicate(req.FriendUserIDs) {

View File

@ -74,6 +74,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
if err != nil { if err != nil {
return err 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 (

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

@ -69,6 +69,7 @@ type Mongo struct {
Database string `mapstructure:"database"` Database string `mapstructure:"database"`
Username string `mapstructure:"username"` Username string `mapstructure:"username"`
Password string `mapstructure:"password"` Password string `mapstructure:"password"`
AuthSource string `mapstructure:"authSource"`
MaxPoolSize int `mapstructure:"maxPoolSize"` MaxPoolSize int `mapstructure:"maxPoolSize"`
MaxRetry int `mapstructure:"maxRetry"` MaxRetry int `mapstructure:"maxRetry"`
} }
@ -212,12 +213,12 @@ type Push struct {
FilePath string `mapstructure:"filePath"` FilePath string `mapstructure:"filePath"`
AuthURL string `mapstructure:"authURL"` AuthURL string `mapstructure:"authURL"`
} `mapstructure:"fcm"` } `mapstructure:"fcm"`
JPNS struct { JPush struct {
AppKey string `mapstructure:"appKey"` AppKey string `mapstructure:"appKey"`
MasterSecret string `mapstructure:"masterSecret"` MasterSecret string `mapstructure:"masterSecret"`
PushURL string `mapstructure:"pushURL"` PushURL string `mapstructure:"pushURL"`
PushIntent string `mapstructure:"pushIntent"` PushIntent string `mapstructure:"pushIntent"`
} `mapstructure:"jpns"` } `mapstructure:"jpush"`
IOSPush struct { IOSPush struct {
PushSound string `mapstructure:"pushSound"` PushSound string `mapstructure:"pushSound"`
BadgeCount bool `mapstructure:"badgeCount"` BadgeCount bool `mapstructure:"badgeCount"`
@ -364,8 +365,14 @@ 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"`
}
type RpcRegisterName struct { type RpcRegisterName struct {
User string `mapstructure:"user"` User string `mapstructure:"user"`
Friend string `mapstructure:"friend"` Friend string `mapstructure:"friend"`
@ -472,6 +479,7 @@ func (m *Mongo) Build() *mongoutil.Config {
Database: m.Database, Database: m.Database,
Username: m.Username, Username: m.Username,
Password: m.Password, Password: m.Password,
AuthSource: m.AuthSource,
MaxPoolSize: m.MaxPoolSize, MaxPoolSize: m.MaxPoolSize,
MaxRetry: m.MaxRetry, MaxRetry: m.MaxRetry,
} }

View File

@ -18,6 +18,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/protocol/relation"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
@ -35,9 +36,7 @@ func FriendPb2DB(friend *sdkws.FriendInfo) *model.Friend {
return dbFriend return dbFriend
} }
func FriendDB2Pb(ctx context.Context, friendDB *model.Friend, func FriendDB2Pb(ctx context.Context, friendDB *model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (*sdkws.FriendInfo, error) {
getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error),
) (*sdkws.FriendInfo, error) {
users, err := getUsers(ctx, []string{friendDB.FriendUserID}) users, err := getUsers(ctx, []string{friendDB.FriendUserID})
if err != nil { if err != nil {
return nil, err return nil, err
@ -53,11 +52,7 @@ func FriendDB2Pb(ctx context.Context, friendDB *model.Friend,
}, nil }, nil
} }
func FriendsDB2Pb( func FriendsDB2Pb(ctx context.Context, friendsDB []*model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (friendsPb []*sdkws.FriendInfo, err error) {
ctx context.Context,
friendsDB []*model.Friend,
getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error),
) (friendsPb []*sdkws.FriendInfo, err error) {
if len(friendsDB) == 0 { if len(friendsDB) == 0 {
return nil, nil return nil, nil
} }
@ -86,7 +81,21 @@ func FriendsDB2Pb(
friendsPb = append(friendsPb, friendPb) friendsPb = append(friendsPb, friendPb)
} }
return friendsPb, nil return friendsPb, nil
}
func FriendOnlyDB2PbOnly(friendsDB []*model.Friend) []*relation.FriendInfoOnly {
return datautil.Slice(friendsDB, func(f *model.Friend) *relation.FriendInfoOnly {
return &relation.FriendInfoOnly{
OwnerUserID: f.OwnerUserID,
FriendUserID: f.FriendUserID,
Remark: f.Remark,
CreateTime: f.CreateTime.UnixMilli(),
AddSource: f.AddSource,
OperatorUserID: f.OperatorUserID,
Ex: f.Ex,
IsPinned: f.IsPinned,
}
})
} }
func FriendRequestDB2Pb(ctx context.Context, friendRequests []*model.FriendRequest, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) ([]*sdkws.FriendRequest, error) { func FriendRequestDB2Pb(ctx context.Context, friendRequests []*model.FriendRequest, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) ([]*sdkws.FriendRequest, error) {

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

@ -38,7 +38,7 @@ const (
func NewConversationRedis(rdb redis.UniversalClient, localCache *config.LocalCache, opts *rockscache.Options, db database.Conversation) cache.ConversationCache { func NewConversationRedis(rdb redis.UniversalClient, localCache *config.LocalCache, opts *rockscache.Options, db database.Conversation) cache.ConversationCache {
batchHandler := NewBatchDeleterRedis(rdb, opts, []string{localCache.Conversation.Topic}) batchHandler := NewBatchDeleterRedis(rdb, opts, []string{localCache.Conversation.Topic})
c := localCache.Conversation c := localCache.Conversation
log.ZDebug(context.Background(), "black local cache init", "Topic", c.Topic, "SlotNum", c.SlotNum, "SlotSize", c.SlotSize, "enable", c.Enable()) log.ZDebug(context.Background(), "conversation local cache init", "Topic", c.Topic, "SlotNum", c.SlotNum, "SlotSize", c.SlotSize, "enable", c.Enable())
return &ConversationRedisCache{ return &ConversationRedisCache{
BatchDeleter: batchHandler, BatchDeleter: batchHandler,
rcClient: rockscache.NewClient(rdb, *opts), rcClient: rockscache.NewClient(rdb, *opts),

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 (

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

@ -1,28 +1,16 @@
// 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/log"
"github.com/openimsdk/tools/tokenverify" "github.com/openimsdk/tools/tokenverify"
) )
@ -32,18 +20,30 @@ 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
}
type authDatabase struct { type authDatabase struct {
cache cache.TokenModel cache cache.TokenModel
accessSecret string accessSecret string
accessExpire int64 accessExpire int64
multiLoginPolicy int multiLogin multiLoginConfig
adminUserIDs []string
} }
func NewAuthDatabase(cache cache.TokenModel, accessSecret string, accessExpire int64, policy int) AuthDatabase { func NewAuthDatabase(cache cache.TokenModel, accessSecret string, accessExpire int64, multiLogin config.MultiLogin, adminUserIDs []string) 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,
}, adminUserIDs: adminUserIDs,
}
} }
// If the result is empty. // If the result is empty.
@ -55,22 +55,41 @@ 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 isAdmin := authverify.IsManagerUserID(userID, a.adminUserIDs)
tokens, err := a.cache.GetTokensWithoutError(ctx, userID, platformID) if !isAdmin {
tokens, err := a.cache.GetAllTokensWithoutError(ctx, userID)
if err != nil { if err != nil {
return "", err return "", err
} }
var deleteTokenKey []string
var kickedTokenKey []string deleteTokenKey, kickedTokenKey, err := a.checkToken(ctx, tokens, platformID)
for k, v := range tokens { if err != nil {
t, err := tokenverify.GetClaimFromToken(k, authverify.Secret(a.accessSecret)) return "", err
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 +97,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)
@ -97,6 +106,7 @@ func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformI
log.ZDebug(ctx, "kicked token in create token", "token", k) log.ZDebug(ctx, "kicked token in create token", "token", k)
} }
} }
}
claims := tokenverify.BuildClaims(userID, platformID, a.accessExpire) claims := tokenverify.BuildClaims(userID, platformID, a.accessExpire)
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
@ -105,28 +115,124 @@ func (a *authDatabase) CreateToken(ctx context.Context, userID string, platformI
return "", errs.WrapMsg(err, "token.SignedString") return "", errs.WrapMsg(err, "token.SignedString")
} }
if !isAdmin {
if err = a.cache.SetTokenFlagEx(ctx, userID, platformID, tokenString, constant.NormalToken); err != nil { if err = a.cache.SetTokenFlagEx(ctx, userID, platformID, tokenString, constant.NormalToken); err != nil {
return "", err return "", err
} }
}
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.PCAndOther:
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.AllLoginButSameClassKick:
var (
reserved = make(map[string]struct{})
)
for plt, ts := range loginTokenMap {
if constant.PlatformIDToClass(plt) == constant.PlatformIDToClass(platformID) {
kickToken = append(kickToken, ts...)
} else {
if _, ok := reserved[constant.PlatformIDToClass(plt)]; !ok {
reserved[constant.PlatformIDToClass(plt)] = struct{}{}
kickToken = append(kickToken, ts[:len(ts)-1]...)
continue
} else {
kickToken = append(kickToken, ts...)
}
}
} }
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

@ -372,7 +372,7 @@ func (db *commonMsgDatabase) getMsgBySeqsRange(ctx context.Context, userID strin
// This ensures that their message retrieval starts from the point they joined. // 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) { func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID string, conversationID string, begin, end, num, userMaxSeq int64) (int64, int64, []*sdkws.MsgData, error) {
userMinSeq, err := db.seqUser.GetUserMinSeq(ctx, conversationID, userID) userMinSeq, err := db.seqUser.GetUserMinSeq(ctx, conversationID, userID)
if err != nil && errs.Unwrap(err) != redis.Nil { if err != nil && !errors.Is(err, redis.Nil) {
return 0, 0, nil, err return 0, 0, nil, err
} }
minSeq, err := db.seqConversation.GetMinSeq(ctx, conversationID) minSeq, err := db.seqConversation.GetMinSeq(ctx, conversationID)
@ -443,6 +443,11 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
return 0, 0, nil, err return 0, 0, nil, err
} }
successMsgs = append(mongoMsgs, successMsgs...) successMsgs = append(mongoMsgs, successMsgs...)
//_, err = db.msg.SetMessagesToCache(ctx, conversationID, mongoMsgs)
//if err != nil {
// return 0, 0, nil, err
//}
} }
return minSeq, maxSeq, successMsgs, nil return minSeq, maxSeq, successMsgs, nil
@ -485,7 +490,7 @@ func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, co
} }
successMsgs, failedSeqs, err := db.msg.GetMessagesBySeq(ctx, conversationID, newSeqs) successMsgs, failedSeqs, err := db.msg.GetMessagesBySeq(ctx, conversationID, newSeqs)
if err != nil { if err != nil {
if err != redis.Nil { if errors.Is(err, redis.Nil) {
log.ZError(ctx, "get message from redis exception", err, "failedSeqs", failedSeqs, "conversationID", conversationID) log.ZError(ctx, "get message from redis exception", err, "failedSeqs", failedSeqs, "conversationID", conversationID)
} }
} }
@ -500,6 +505,11 @@ func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, co
} }
successMsgs = append(successMsgs, mongoMsgs...) successMsgs = append(successMsgs, mongoMsgs...)
//_, err = db.msg.SetMessagesToCache(ctx, conversationID, mongoMsgs)
//if err != nil {
// return 0, 0, nil, err
//}
} }
return minSeq, maxSeq, successMsgs, nil return minSeq, maxSeq, successMsgs, nil
} }

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"
@ -23,8 +24,11 @@ type MsgTransferDatabase interface {
DeleteMessagesFromCache(ctx context.Context, conversationID string, seqs []int64) error DeleteMessagesFromCache(ctx context.Context, conversationID string, seqs []int64) error
// BatchInsertChat2Cache increments the sequence number and then batch inserts messages into the cache. // BatchInsertChat2Cache increments the sequence number and then batch inserts messages into the cache.
BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNewConversation bool, err error) BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNewConversation bool, userHasReadMap map[string]int64, err error)
SetHasReadSeqToDB(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error
SetHasReadSeqs(ctx context.Context, conversationID string, userSeqMap map[string]int64) error
SetHasReadSeqToDB(ctx context.Context, conversationID string, userSeqMap map[string]int64) error
// to mq // to mq
MsgToPushMQ(ctx context.Context, key, conversationID string, msg2mq *sdkws.MsgData) (int32, int64, error) MsgToPushMQ(ctx context.Context, key, conversationID string, msg2mq *sdkws.MsgData) (int32, int64, error)
@ -83,6 +87,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,
@ -215,18 +222,18 @@ func (db *msgTransferDatabase) DeleteMessagesFromCache(ctx context.Context, conv
return db.msg.DeleteMessagesFromCache(ctx, conversationID, seqs) return db.msg.DeleteMessagesFromCache(ctx, conversationID, seqs)
} }
func (db *msgTransferDatabase) BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNew bool, err error) { func (db *msgTransferDatabase) BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNew bool, userHasReadMap map[string]int64, err error) {
lenList := len(msgs) lenList := len(msgs)
if int64(lenList) > db.msgTable.GetSingleGocMsgNum() { if int64(lenList) > db.msgTable.GetSingleGocMsgNum() {
return 0, false, errs.New("message count exceeds limit", "limit", db.msgTable.GetSingleGocMsgNum()).Wrap() return 0, false, nil, errs.New("message count exceeds limit", "limit", db.msgTable.GetSingleGocMsgNum()).Wrap()
} }
if lenList < 1 { if lenList < 1 {
return 0, false, errs.New("no messages to insert", "minCount", 1).Wrap() return 0, false, nil, errs.New("no messages to insert", "minCount", 1).Wrap()
} }
currentMaxSeq, err := db.seqConversation.Malloc(ctx, conversationID, int64(len(msgs))) currentMaxSeq, err := db.seqConversation.Malloc(ctx, conversationID, int64(len(msgs)))
if err != nil { if err != nil {
log.ZError(ctx, "storage.seq.Malloc", err) log.ZError(ctx, "storage.seq.Malloc", err)
return 0, false, err return 0, false, nil, err
} }
isNew = currentMaxSeq == 0 isNew = currentMaxSeq == 0
lastMaxSeq := currentMaxSeq lastMaxSeq := currentMaxSeq
@ -244,15 +251,10 @@ func (db *msgTransferDatabase) BatchInsertChat2Cache(ctx context.Context, conver
} else { } else {
prommetrics.MsgInsertRedisSuccessCounter.Inc() prommetrics.MsgInsertRedisSuccessCounter.Inc()
} }
err = db.setHasReadSeqs(ctx, conversationID, userSeqMap) return lastMaxSeq, isNew, userSeqMap, errs.Wrap(err)
if err != nil {
log.ZError(ctx, "SetHasReadSeqs error", err, "userSeqMap", userSeqMap, "conversationID", conversationID)
prommetrics.SeqSetFailedCounter.Inc()
}
return lastMaxSeq, isNew, errs.Wrap(err)
} }
func (db *msgTransferDatabase) setHasReadSeqs(ctx context.Context, conversationID string, userSeqMap map[string]int64) error { func (db *msgTransferDatabase) SetHasReadSeqs(ctx context.Context, conversationID string, userSeqMap map[string]int64) error {
for userID, seq := range userSeqMap { for userID, seq := range userSeqMap {
if err := db.seqUser.SetUserReadSeq(ctx, conversationID, userID, seq); err != nil { if err := db.seqUser.SetUserReadSeq(ctx, conversationID, userID, seq); err != nil {
return err return err
@ -261,8 +263,13 @@ func (db *msgTransferDatabase) setHasReadSeqs(ctx context.Context, conversationI
return nil return nil
} }
func (db *msgTransferDatabase) SetHasReadSeqToDB(ctx context.Context, userID string, conversationID string, hasReadSeq int64) error { func (db *msgTransferDatabase) SetHasReadSeqToDB(ctx context.Context, conversationID string, userSeqMap map[string]int64) error {
return db.seqUser.SetUserReadSeqToDB(ctx, conversationID, userID, hasReadSeq) for userID, seq := range userSeqMap {
if err := db.seqUser.SetUserReadSeqToDB(ctx, conversationID, userID, seq); err != nil {
return err
}
}
return nil
} }
func (db *msgTransferDatabase) MsgToPushMQ(ctx context.Context, key, conversationID string, msg2mq *sdkws.MsgData) (int32, int64, error) { func (db *msgTransferDatabase) MsgToPushMQ(ctx context.Context, key, conversationID string, msg2mq *sdkws.MsgData) (int32, int64, error) {

View File

@ -76,7 +76,7 @@ func (g *GroupMgo) Take(ctx context.Context, groupID string) (group *model.Group
func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*model.Group, err error) { func (g *GroupMgo) Search(ctx context.Context, keyword string, pagination pagination.Pagination) (total int64, groups []*model.Group, err error) {
// Define the sorting options // Define the sorting options
opts := options.Find().SetSort(bson.D{{Key: "created_at", Value: -1}}) opts := options.Find().SetSort(bson.D{{Key: "create_time", Value: -1}})
// Perform the search with pagination and sorting // Perform the search with pagination and sorting
return mongoutil.FindPage[*model.Group](ctx, g.coll, bson.M{ return mongoutil.FindPage[*model.Group](ctx, g.coll, bson.M{

View File

@ -0,0 +1,18 @@
package model
import (
"go.mongodb.org/mongo-driver/bson/primitive"
"time"
)
type Application struct {
ID primitive.ObjectID `bson:"_id"`
Platform string `bson:"platform"`
Hot bool `bson:"hot"`
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

@ -17,12 +17,18 @@ package rpccache
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/openimsdk/tools/mw"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
) )
func subscriberRedisDeleteCache(ctx context.Context, client redis.UniversalClient, channel string, del func(ctx context.Context, key ...string)) { func subscriberRedisDeleteCache(ctx context.Context, client redis.UniversalClient, channel string, del func(ctx context.Context, key ...string)) {
defer func() {
if r := recover(); r != nil {
mw.PanicStackToLog(ctx, r)
}
}()
for message := range client.Subscribe(ctx, channel).Channel() { for message := range client.Subscribe(ctx, channel).Channel() {
log.ZDebug(ctx, "subscriberRedisDeleteCache", "channel", channel, "payload", message.Payload) log.ZDebug(ctx, "subscriberRedisDeleteCache", "channel", channel, "payload", message.Payload)
var keys []string var keys []string

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
}

View File

@ -1,55 +0,0 @@
#!/usr/bin/env bash
# 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.
# Wait for Kafka to be ready
KAFKA_SERVER=localhost:9092
MAX_ATTEMPTS=300
attempt_num=1
echo "Waiting for Kafka to be ready..."
until /opt/bitnami/kafka/bin/kafka-topics.sh --list --bootstrap-server $KAFKA_SERVER; do
echo "Attempt $attempt_num of $MAX_ATTEMPTS: Kafka not ready yet..."
if [ $attempt_num -eq $MAX_ATTEMPTS ]; then
echo "Kafka not ready after $MAX_ATTEMPTS attempts, exiting"
exit 1
fi
attempt_num=$((attempt_num+1))
sleep 1
done
echo "Kafka is ready. Creating topics..."
topics=("toRedis" "toMongo" "toPush" "toOfflinePush")
partitions=8
replicationFactor=1
for topic in "${topics[@]}"; do
if /opt/bitnami/kafka/bin/kafka-topics.sh --create \
--bootstrap-server $KAFKA_SERVER \
--replication-factor $replicationFactor \
--partitions $partitions \
--topic $topic
then
echo "Topic $topic created."
else
echo "Failed to create topic $topic."
fi
done
echo "All topics created."

View File

@ -1,66 +0,0 @@
# Copyright © 2023 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
mongosh <<EOF
var maxRetries = 300;
var connected = false;
var rootUsername = '$MONGO_INITDB_ROOT_USERNAME';
var rootPassword = '$MONGO_INITDB_ROOT_PASSWORD';
var dbName = '$MONGO_INITDB_DATABASE';
var openimUsername = '$MONGO_OPENIM_USERNAME';
var openimPassword = '$MONGO_OPENIM_PASSWORD';
while (!connected && maxRetries > 0) {
try {
db = connect('mongodb://127.0.0.1:27017/admin');
var authResult = db.auth(rootUsername, rootPassword);
if (authResult) {
print('Authentication successful for root user: ' + rootUsername);
connected = true;
} else {
print('Authentication failed for root user: ' + rootUsername + ' with password: ' + rootPassword);
quit(1);
}
} catch (e) {
maxRetries--;
print('Connection failed, retrying... Remaining attempts: ' + maxRetries);
sleep(1000); // Sleep for 1 second
}
}
if (connected) {
db = db.getSiblingDB(dbName);
var createUserResult = db.createUser({
user: openimUsername,
pwd: openimPassword,
roles: [{
role: 'readWrite',
db: dbName
}]
});
if (createUserResult.ok == 1) {
print('User creation successful. User: ' + openimUsername + ', Database: ' + dbName);
} else {
print('User creation failed for user: ' + openimUsername + ' in database: ' + dbName);
quit(1);
}
} else {
print('Failed to connect to MongoDB after 300 retries.');
quit(1);
}
EOF

View File

@ -0,0 +1,198 @@
package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"regexp"
"strings"
)
// You can specify a tag as a command line argument to generate the changelog for a specific version.
// Example: go run tools/changelog/changelog.go v0.0.33
// If no tag is provided, the latest release will be used.
// Setting repo owner and repo name by generate changelog
const (
repoOwner = "openimsdk"
repoName = "open-im-server"
)
// GitHubRepo struct represents the repo details.
type GitHubRepo struct {
Owner string
Repo string
FullChangelog string
}
// ReleaseData represents the JSON structure for release data.
type ReleaseData struct {
TagName string `json:"tag_name"`
Body string `json:"body"`
HtmlUrl string `json:"html_url"`
Published string `json:"published_at"`
}
// Method to classify and format release notes.
func (g *GitHubRepo) classifyReleaseNotes(body string) map[string][]string {
result := map[string][]string{
"feat": {},
"fix": {},
"chore": {},
"refactor": {},
"build": {},
"other": {},
}
// Regular expression to extract PR number and URL (case insensitive)
rePR := regexp.MustCompile(`(?i)in (https://github\.com/[^\s]+/pull/(\d+))`)
// Split the body into individual lines.
lines := strings.Split(body, "\n")
for _, line := range lines {
// Skip lines that contain "deps: Merge"
if strings.Contains(strings.ToLower(line), "deps: merge #") {
continue
}
// Use a regular expression to extract Full Changelog link and its title (case insensitive).
if strings.Contains(strings.ToLower(line), "**full changelog**") {
matches := regexp.MustCompile(`(?i)\*\*full changelog\*\*: (https://github\.com/[^\s]+/compare/([^\s]+))`).FindStringSubmatch(line)
if len(matches) > 2 {
// Format the Full Changelog link with title
g.FullChangelog = fmt.Sprintf("[%s](%s)", matches[2], matches[1])
}
continue // Skip further processing for this line.
}
if strings.HasPrefix(line, "*") {
var category string
// Use strings.ToLower to make the matching case insensitive
lowerLine := strings.ToLower(line)
// Determine the category based on the prefix (case insensitive).
if strings.HasPrefix(lowerLine, "* feat") {
category = "feat"
} else if strings.HasPrefix(lowerLine, "* fix") {
category = "fix"
} else if strings.HasPrefix(lowerLine, "* chore") {
category = "chore"
} else if strings.HasPrefix(lowerLine, "* refactor") {
category = "refactor"
} else if strings.HasPrefix(lowerLine, "* build") {
category = "build"
} else {
category = "other"
}
// Extract PR number and URL (case insensitive)
matches := rePR.FindStringSubmatch(line)
if len(matches) == 3 {
prURL := matches[1]
prNumber := matches[2]
// Format the line with the PR link and use original content for the final result
formattedLine := fmt.Sprintf("* %s [#%s](%s)", strings.Split(line, " by ")[0][2:], prNumber, prURL)
result[category] = append(result[category], formattedLine)
} else {
// If no PR link is found, just add the line as is
result[category] = append(result[category], line)
}
}
}
return result
}
// Method to generate the final changelog.
func (g *GitHubRepo) generateChangelog(tag, date, htmlURL, body string) string {
sections := g.classifyReleaseNotes(body)
// Convert ISO 8601 date to simpler format (YYYY-MM-DD)
formattedDate := date[:10]
// Changelog header with tag, date, and links.
changelog := fmt.Sprintf("## [%s](%s) \t(%s)\n\n", tag, htmlURL, formattedDate)
if len(sections["feat"]) > 0 {
changelog += "### New Features\n" + strings.Join(sections["feat"], "\n") + "\n\n"
}
if len(sections["fix"]) > 0 {
changelog += "### Bug Fixes\n" + strings.Join(sections["fix"], "\n") + "\n\n"
}
if len(sections["chore"]) > 0 {
changelog += "### Chores\n" + strings.Join(sections["chore"], "\n") + "\n\n"
}
if len(sections["refactor"]) > 0 {
changelog += "### Refactors\n" + strings.Join(sections["refactor"], "\n") + "\n\n"
}
if len(sections["build"]) > 0 {
changelog += "### Builds\n" + strings.Join(sections["build"], "\n") + "\n\n"
}
if len(sections["other"]) > 0 {
changelog += "### Others\n" + strings.Join(sections["other"], "\n") + "\n\n"
}
if g.FullChangelog != "" {
changelog += fmt.Sprintf("**Full Changelog**: %s\n", g.FullChangelog)
}
return changelog
}
// Method to fetch release data from GitHub API.
func (g *GitHubRepo) fetchReleaseData(version string) (*ReleaseData, error) {
var apiURL string
if version == "" {
// Fetch the latest release.
apiURL = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/latest", g.Owner, g.Repo)
} else {
// Fetch a specific version.
apiURL = fmt.Sprintf("https://api.github.com/repos/%s/%s/releases/tags/%s", g.Owner, g.Repo, version)
}
resp, err := http.Get(apiURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var releaseData ReleaseData
err = json.Unmarshal(body, &releaseData)
if err != nil {
return nil, err
}
return &releaseData, nil
}
func main() {
repo := &GitHubRepo{Owner: repoOwner, Repo: repoName}
// Get the version from command line arguments, if provided
var version string // Default is use latest
if len(os.Args) > 1 {
version = os.Args[1] // Use the provided version
}
// Fetch release data (either for latest or specific version)
releaseData, err := repo.fetchReleaseData(version)
if err != nil {
fmt.Println("Error fetching release data:", err)
return
}
// Generate and print the formatted changelog
changelog := repo.generateChangelog(releaseData.TagName, releaseData.Published, releaseData.HtmlUrl, releaseData.Body)
fmt.Println(changelog)
}

View File

@ -1,308 +0,0 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"log"
"os"
"os/exec"
"regexp"
"sort"
"strings"
)
var (
mergeRequest = regexp.MustCompile(`Merge pull request #([\d]+)`)
webconsoleBump = regexp.MustCompile(regexp.QuoteMeta("bump(github.com/openshift/origin-web-console): ") + `([\w]+)`)
upstreamKube = regexp.MustCompile(`^UPSTREAM: (\d+)+:(.+)`)
upstreamRepo = regexp.MustCompile(`^UPSTREAM: ([\w/-]+): (\d+)+:(.+)`)
prefix = regexp.MustCompile(`^[\w-]: `)
assignments = []prefixAssignment{
{"cluster up", "cluster"},
{" pv ", "storage"},
{"haproxy", "router"},
{"router", "router"},
{"route", "route"},
{"authoriz", "auth"},
{"rbac", "auth"},
{"authent", "auth"},
{"reconcil", "auth"},
{"auth", "auth"},
{"role", "auth"},
{" dc ", "deploy"},
{"deployment", "deploy"},
{"rolling", "deploy"},
{"security context constr", "security"},
{"scc", "security"},
{"pipeline", "build"},
{"build", "build"},
{"registry", "registry"},
{"registries", "image"},
{"image", "image"},
{" arp ", "network"},
{" cni ", "network"},
{"egress", "network"},
{"network", "network"},
{"oc ", "cli"},
{"template", "template"},
{"etcd", "server"},
{"pod", "node"},
{"scripts/", "hack"},
{"e2e", "test"},
{"integration", "test"},
{"cluster", "cluster"},
{"master", "server"},
{"packages", "hack"},
{"api", "server"},
}
)
type prefixAssignment struct {
term string
prefix string
}
type commit struct {
short string
parents []string
message string
}
func contains(arr []string, value string) bool {
for _, s := range arr {
if s == value {
return true
}
}
return false
}
func main() {
log.SetFlags(0)
if len(os.Args) != 3 {
log.Fatalf("Must specify two arguments, FROM and TO")
}
from := os.Args[1]
to := os.Args[2]
out, err := exec.Command("git", "log", "--topo-order", "--pretty=tformat:%h %p|%s", "--reverse", fmt.Sprintf("%s..%s", from, to)).CombinedOutput()
if err != nil {
log.Fatal(err)
}
hide := make(map[string]struct{})
var apiChanges []string
var webconsole []string
var commits []commit
var upstreams []commit
var bumps []commit
for _, line := range strings.Split(string(out), "\n") {
if len(strings.TrimSpace(line)) == 0 {
continue
}
parts := strings.SplitN(line, "|", 2)
hashes := strings.Split(parts[0], " ")
c := commit{short: hashes[0], parents: hashes[1:], message: parts[1]}
if strings.HasPrefix(c.message, "UPSTREAM: ") {
hide[c.short] = struct{}{}
upstreams = append(upstreams, c)
}
if strings.HasPrefix(c.message, "bump(") {
hide[c.short] = struct{}{}
bumps = append(bumps, c)
}
if len(c.parents) == 1 {
commits = append(commits, c)
continue
}
matches := mergeRequest.FindStringSubmatch(line)
if len(matches) == 0 {
// this may have been a human pressing the merge button, we'll just record this as a direct push
continue
}
// split the accumulated commits into any that are force merges (assumed to be the initial set due
// to --topo-order) from the PR commits as soon as we see any of our merge parents. Then print
// any of the force merges
var first int
for i := range commits {
first = i
if contains(c.parents, commits[i].short) {
first++
break
}
}
individual := commits[:first]
merged := commits[first:]
for _, commit := range individual {
if len(commit.parents) > 1 {
continue
}
if _, ok := hide[commit.short]; ok {
continue
}
fmt.Printf("force-merge: %s %s\n", commit.message, commit.short)
}
// try to find either the PR title or the first commit title from the merge commit
out, err := exec.Command("git", "show", "--pretty=tformat:%b", c.short).CombinedOutput()
if err != nil {
log.Fatal(err)
}
var message string
para := strings.Split(string(out), "\n\n")
if len(para) > 0 && strings.HasPrefix(para[0], "Automatic merge from submit-queue") {
para = para[1:]
}
// this is no longer necessary with the submit queue in place
if len(para) > 0 && strings.HasPrefix(para[0], "Merged by ") {
para = para[1:]
}
// post submit-queue, the merge bot will add the PR title, which is usually pretty good
if len(para) > 0 {
message = strings.Split(para[0], "\n")[0]
}
if len(message) == 0 && len(merged) > 0 {
message = merged[0].message
}
if len(message) > 0 && len(merged) == 1 && message == merged[0].message {
merged = nil
}
// try to calculate a prefix based on the diff
if len(message) > 0 && !prefix.MatchString(message) {
prefix, ok := findPrefixFor(message, merged)
if ok {
message = prefix + ": " + message
}
}
// github merge
// has api changes
display := fmt.Sprintf("%s [\\#%s](https://github.com/openimsdk/Open-IM-Server/pull/%s)", message, matches[1], matches[1])
if hasFileChanges(c.short, "pkg/apistruct/") {
apiChanges = append(apiChanges, display)
}
var filtered []commit
for _, commit := range merged {
if _, ok := hide[commit.short]; ok {
continue
}
filtered = append(filtered, commit)
}
if len(filtered) > 0 {
fmt.Printf("- %s\n", display)
for _, commit := range filtered {
fmt.Printf(" - %s (%s)\n", commit.message, commit.short)
}
}
// stick the merge commit in at the beginning of the next list so we can anchor the previous parent
commits = []commit{c}
}
// chunk the bumps
var lines []string
for _, commit := range bumps {
if m := webconsoleBump.FindStringSubmatch(commit.message); len(m) > 0 {
webconsole = append(webconsole, m[1])
continue
}
lines = append(lines, commit.message)
}
lines = sortAndUniq(lines)
for _, line := range lines {
fmt.Printf("- %s\n", line)
}
// chunk the upstreams
lines = nil
for _, commit := range upstreams {
lines = append(lines, commit.message)
}
lines = sortAndUniq(lines)
for _, line := range lines {
fmt.Printf("- %s\n", upstreamLinkify(line))
}
if len(webconsole) > 0 {
fmt.Printf("- web: from %s^..%s\n", webconsole[0], webconsole[len(webconsole)-1])
}
for _, apiChange := range apiChanges {
fmt.Printf(" - %s\n", apiChange)
}
}
func findPrefixFor(message string, commits []commit) (string, bool) {
message = strings.ToLower(message)
for _, m := range assignments {
if strings.Contains(message, m.term) {
return m.prefix, true
}
}
for _, c := range commits {
if prefix, ok := findPrefixFor(c.message, nil); ok {
return prefix, ok
}
}
return "", false
}
func hasFileChanges(commit string, prefixes ...string) bool {
out, err := exec.Command("git", "diff", "--name-only", fmt.Sprintf("%s^..%s", commit, commit)).CombinedOutput()
if err != nil {
log.Fatal(err)
}
for _, file := range strings.Split(string(out), "\n") {
for _, prefix := range prefixes {
if strings.HasPrefix(file, prefix) {
return true
}
}
}
return false
}
func sortAndUniq(lines []string) []string {
sort.Strings(lines)
out := make([]string, 0, len(lines))
last := ""
for _, s := range lines {
if last == s {
continue
}
last = s
out = append(out, s)
}
return out
}
func upstreamLinkify(line string) string {
if m := upstreamKube.FindStringSubmatch(line); len(m) > 0 {
return fmt.Sprintf("UPSTREAM: [#%s](https://github.com/openimsdk/open-im-server/pull/%s):%s", m[1], m[1], m[2])
}
if m := upstreamRepo.FindStringSubmatch(line); len(m) > 0 {
return fmt.Sprintf("UPSTREAM: [%s#%s](https://github.com/%s/pull/%s):%s", m[1], m[2], m[1], m[2], m[3])
}
return line
}

View File

@ -66,7 +66,7 @@ func CheckMinIO(ctx context.Context, config *config.Minio) error {
} }
func CheckKafka(ctx context.Context, conf *config.Kafka) error { func CheckKafka(ctx context.Context, conf *config.Kafka) error {
return kafka.Check(ctx, conf.Build(), []string{conf.ToMongoTopic, conf.ToRedisTopic, conf.ToPushTopic, conf.ToOfflinePushTopic}) return kafka.CheckHealth(ctx, conf.Build())
} }
func initConfig(configDir string) (*config.Mongo, *config.Redis, *config.Kafka, *config.Minio, *config.Discovery, error) { func initConfig(configDir string) (*config.Mongo, *config.Redis, *config.Kafka, *config.Minio, *config.Discovery, error) {

View File

@ -1 +1 @@
3.8.1 3.8.2