Compare commits

...

98 Commits

Author SHA1 Message Date
mo3et 2cc6c167c1 Update CHANGELOG for release v3.8.3-patch.9 2025-08-19 08:39:11 +00:00
icey-yu 68a735ba99 feat: enable redis aof-use-rdb-preamble && disable auto rdb (#3529) 2025-08-19 08:23:27 +00:00
OpenIM-Gordon 123abe9803 fix: fill in the most recent sendTime for a gap message to prevent the client from repeatedly retrieving the same message due to sendTime being 0. (#3522) 2025-08-14 06:16:50 +00:00
Monet Lee 34971c8b96 fix: optimize to lru local cache. (#3514)
* fix: optimize to lru  local cache.

* revert lock timing.
2025-08-14 02:19:11 +00:00
Monet Lee d542df7000 fix: revert contentType in API msg (#3509) 2025-08-13 09:01:27 +00:00
chao d817cb0ffd fix: admin token in standalone mode (#3499)
* fix: performance issues with Kafka caused by encapsulating the MQ interface

* fix: admin token in standalone mode
2025-08-01 02:48:25 +00:00
xuzhijvn c208c3789f fix: searchMessage method has potential NPE bug (#3287) (#3289) 2025-07-29 08:16:30 +00:00
chao 2d4cf99744 fix: performance issues with Kafka caused by encapsulating the MQ interface (#3485) 2025-07-28 09:10:38 +00:00
OpenIM-Gordon b44e56b151 docs: update readme of config file. (#3356) 2025-07-28 07:51:26 +00:00
github-actions[bot] b19b81b10e Update CHANGELOG for release v3.8.3-patch.6 (#3473)
Co-authored-by: FGadvancer <46924906+FGadvancer@users.noreply.github.com>
Co-authored-by: Monet Lee <monet_lee@163.com>
2025-07-28 07:33:05 +00:00
github-actions[bot] 80de08e8ed Update CHANGELOG for release v3.8.3-patch.5 (#3405)
Co-authored-by: mo3et <34803812+mo3et@users.noreply.github.com>
Co-authored-by: Monet Lee <monet_lee@163.com>
2025-07-28 07:29:41 +00:00
Monet Lee 4c9fdf70db feat: support mongo replicaset mode. (#3433)
* feat: support mongo replicaset mode.

* fix mongo config.
2025-07-28 06:22:40 +00:00
OpenIM-Gordon d6cd0258a5 fix: correctly aggregate read seqs by conversation and user before DB update. (#3442)
* build: docker compose file add some comments.

* fix: correctly aggregate read seqs by conversation and user before DB update.
2025-07-28 04:11:38 +00:00
Monet Lee becc999d63 fix: solve batch incorrect error in Find DocIDs (#3476) 2025-07-28 03:29:51 +00:00
OpenIM-Gordon f9475bdd87 fix: added AtUserIDList to the @ message for API sending. (#3472) 2025-07-28 03:18:49 +00:00
github-actions[bot] 839d7d90ac Update CHANGELOG for release v3.8.3-patch.4 (#3226)
Co-authored-by: mo3et <34803812+mo3et@users.noreply.github.com>
Co-authored-by: Monet Lee <monet_lee@163.com>
2025-07-28 03:13:35 +00:00
github-actions[bot] 359201a005 Update CHANGELOG for release v3.8.3-patch.3 (#3206)
Co-authored-by: withchao <48119764+withchao@users.noreply.github.com>
Co-authored-by: Monet Lee <monet_lee@163.com>
2025-07-28 03:10:42 +00:00
github-actions[bot] 865eaa719f Update CHANGELOG for release v3.8.3-patch.2 (#3175)
Co-authored-by: icey-yu <119291641+icey-yu@users.noreply.github.com>
Co-authored-by: Monet Lee <monet_lee@163.com>
2025-07-28 03:07:59 +00:00
Monet Lee 097e033347 fix: fix incorrect kicked logic. (#3480)
* fix: fix incorrect ws check logic.

* optimize checkSameToken logic.
2025-07-28 02:57:44 +00:00
github-actions[bot] 3ad24d9521 Update CHANGELOG for release v3.8.3-patch.1 (#3164)
Co-authored-by: withchao <48119764+withchao@users.noreply.github.com>
2025-07-28 02:52:25 +00:00
Monet Lee 230c93cc16 docs: update slack link. (#3479) 2025-07-28 02:46:23 +00:00
Monet Lee 4ae05d2e38 fix: use safe submodule init in workflows. (#3468)
* fix: use safe submodule init in workflows.

* update to latest version.
2025-07-25 07:02:49 +00:00
Monet Lee b1659ef84d refactor: support modified config and args in mage. (#3466)
* refactor: support modified config and args in mage.

* fix: update go make version.
2025-07-22 02:24:01 +00:00
Monet Lee 5e813ef079 fix: solve createTime not set in setConversation and Create Conversation. (#3447) 2025-07-08 08:33:39 +00:00
Monet Lee bd56bd23b8 fix: update log level in crontask dist look. (#3440) 2025-07-08 08:33:25 +00:00
icey-yu a5ac7f2a81 feat: add nickname for adminUser (#3435)
* feat: add nickname for adminUser

* feat: add nickname for adminUser

* feat: add nickname for adminUser
2025-06-25 03:33:19 +00:00
chao 6912ad6f33 feat: add api logger (#3427)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* fix: transferring the group owner to a muted member, incremental version error

* feat: unified conversion code

* feat: update gomake

* fix: in standalone mode, the user online status is wrong

* fix: add permission check

* fix: add permission check

* fix: add rpc interface permission check

* fix: CreateGroupChatConversations

* feat: optimize friend and group applications

* feat: optimize friend and group applications

* feat: optimize friend and group applications

* feat: optimize friend and group applications

* fix: optimize friend and group applications

* feat: add api logger
2025-06-19 03:51:35 +00:00
Monet Lee 53bf8acc21 fix: solve webhook incorrect attentionID references. (#3411) 2025-06-18 06:32:29 +00:00
Monet Lee 8f7b02979d feat: support redis sentinel. (#3423)
* feat: support redis sentinel.

* update docker compose contents.

* update config contents.

* revert content.

* supoort redisMode.

* update config.

* remvove print.
2025-06-18 06:31:09 +00:00
Monet Lee 1baf9a8e0f feat: Implement etcd and kafka auth. (#3394)
* feat: Implement etcd and kafka auth.

* Update etcd command contents.

* update contents.

* feat: update auth logic to compatible old version.

* update comment.

* update contents.
2025-06-17 07:33:25 +00:00
Monet Lee 1e2375faca fix: improve mileston PR workflows contents. (#3382) 2025-06-17 07:12:25 +00:00
icey-yu 545125884e fix: import friends send notification (#3420) 2025-06-16 02:46:07 +00:00
icey-yu d156e1e519 fix: prometheus discovery (#3408) 2025-06-11 08:29:44 +00:00
Monet Lee 75367545ea feat: support distributed lock in crontask. (#3401)
* feat: support distributed lock in crontask.

* remove space.

* remove comment.

* remove log.

* Update logic.

* Update contents.
2025-06-06 06:25:58 +00:00
Monet Lee 04ee509b68 fix: solve incorrect when sendMsg webhook callback after. (#3409)
* fix: solve incorrect when sendMsg webhook callback after.

* remove print.
2025-06-06 03:29:45 +00:00
HonQi b7ca3bd95f fix redis config db field (#3395) 2025-06-04 02:46:20 +00:00
chao 812c1e4127 fix: optimize friend and group applications (#3389)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* fix: transferring the group owner to a muted member, incremental version error

* feat: unified conversion code

* feat: update gomake

* fix: in standalone mode, the user online status is wrong

* fix: add permission check

* fix: add permission check

* fix: add rpc interface permission check

* fix: CreateGroupChatConversations

* feat: optimize friend and group applications

* feat: optimize friend and group applications

* feat: optimize friend and group applications

* feat: optimize friend and group applications

* fix: optimize friend and group applications
2025-05-27 07:01:09 +00:00
chao 8e61f30e9c feat: optimize friend and group applications (#3384)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* fix: transferring the group owner to a muted member, incremental version error

* feat: unified conversion code

* feat: update gomake

* fix: in standalone mode, the user online status is wrong

* fix: add permission check

* fix: add permission check

* fix: add rpc interface permission check

* fix: CreateGroupChatConversations

* feat: optimize friend and group applications

* feat: optimize friend and group applications

* feat: optimize friend and group applications

* feat: optimize friend and group applications
2025-05-26 02:06:35 +00:00
chao 4d3ec5367f fix: add rpc interface permission check (#3377)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* fix: transferring the group owner to a muted member, incremental version error

* feat: unified conversion code

* feat: update gomake

* fix: in standalone mode, the user online status is wrong

* fix: add permission check

* fix: add permission check

* fix: add rpc interface permission check

* fix: CreateGroupChatConversations
2025-05-22 09:14:30 +00:00
Monet Lee 6ae00dfab9 fix: solve updateUserInfoEx null pointer. (#3326) 2025-05-22 06:27:30 +00:00
icey-yu 4d69194f62 fix: send simple msg (#3362)
* fix: content in sendSimpleMessage

* fix: send simple msg

* fix: send simple msg
2025-05-22 06:23:43 +00:00
Monet Lee 8b23d4f5bb fix: solve user not found when notification invitedUserID is zero in InviteUserToGroup. (#3375) 2025-05-22 06:21:44 +00:00
chao 748d783d36 feat: add rpc interface permission check (#3366)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* fix: transferring the group owner to a muted member, incremental version error

* feat: unified conversion code

* feat: update gomake

* fix: in standalone mode, the user online status is wrong

* fix: add permission check

* fix: add permission check
2025-05-20 03:30:00 +00:00
chao 632a65303c fix: standalone mode cannot be used (#3360)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* fix: transferring the group owner to a muted member, incremental version error

* feat: unified conversion code

* feat: update gomake

* fix: in standalone mode, the user online status is wrong
2025-05-16 09:30:38 +00:00
chao 1d7660bedb fix: optimize grpc option and fix some interface permission checks (#3327)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* fix: transferring the group owner to a muted member, incremental version error

* feat: unified conversion code

* feat: update gomake

* feat: grpc mw

* fix: permission verification

* fix: optimizing the code

* fix: optimize grpc option and fix some interface permission checks
2025-05-14 08:07:05 +00:00
chao 1178808ba7 feat: optimize server code (#3319)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* fix: transferring the group owner to a muted member, incremental version error

* feat: unified conversion code

* feat: update gomake
2025-05-09 01:31:30 +00:00
icey-yu 56c5c1f015 fix: delete token by correct platformID && feat: adminToken can be retained for five minutes after deleting (#3313) 2025-05-06 07:10:10 +00:00
Monet Lee dd981b976d refactor: move stress-test tools location. (#3295)
* refactor: move stress-test tools location.

* improve stress tools.

* improve stress_test-v2
2025-04-25 07:57:23 +00:00
icey-yu 91db18168d fix: data version SetVersion will add record (#3304) 2025-04-24 09:26:52 +00:00
skiffer-git bed81af75c Update README_zh_CN.md 2025-04-22 21:52:32 +08:00
skiffer-git 986237af2e Update README.md 2025-04-22 21:52:02 +08:00
skiffer-git c54b948f1a Update LICENSE 2025-04-22 21:47:13 +08:00
chao fa3d251dcb feat: GroupApplicationAgreeMemberEnterNotification splitting (#3297)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* fix: transferring the group owner to a muted member, incremental version error

* feat: GroupApplicationAgreeMemberEnterNotification splitting, rpc body size limit

* feat: GroupApplicationAgreeMemberEnterNotification splitting, rpc body size limit
2025-04-22 09:02:45 +00:00
skiffer-git 58994095a5 License (#3293)
* 3.6.1 code conventions (#2203)

* Adjust configuration settings

* Adjust configuration settings

* Adjust configuration settings

* refactor: webhooks update.

* refactor: kafka update.

* Simplify the Docker Compose configuration, remove unnecessary environment variables, and eliminate the gateway service.

* refactor: kafka update.

* refactor: kafka update.

* Simplify the Docker Compose configuration, remove unnecessary environment variables, and eliminate the gateway service.

* Simplify the Docker Compose configuration, remove unnecessary environment variables, and eliminate the gateway service.

* Windows can compile and run.

* Windows can compile and run.

* refactor: kafka update.

* feat: msg cache split

* refactor: webhooks update

* refactor: webhooks update

* refactor: friends update

* refactor: group update

* refactor: third update

* refactor: api update

* refactor: crontab update

* refactor: msggateway update

* mage

* mage

* refactor: all module update.

* check

* refactor: all module update.

* load config

* load config

* load config

* load config

* refactor: all module update.

* refactor: all module update.

* refactor: all module update.

* refactor: all module update.

* refactor: all module update.

* Optimize Docker configuration and script.

* refactor: all module update.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* refactor: all module update.

* Optimize Docker configuration and script.

* refactor: all module update.

* refactor: all module update.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* update tools

* update tools

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* update protocol

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* refactor: all module update.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* refactor: api remove token auth by redis directly.

* Code Refactoring

* refactor: websocket auth change to call rpc of auth.

* refactor: kick online user and remove token change to call auth rpc.

* refactor: kick online user and remove token change to call auth rpc.

* refactor: remove msggateway redis.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor webhook

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor webhook

* refactor: cmd update.

* refactor: cmd update.

* fix: runtime: goroutine stack exceeds

* refactor: cmd update.

* refactor notification

* refactor notification

* refactor

* refactor: cmd update.

* refactor: cmd update.

* refactor

* refactor

* refactor

* protojson

* protojson

* protojson

* go mod

* wrapperspb

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: context update.

* refactor: websocket update info.

* refactor: websocket update info.

* refactor: websocket update info.

* refactor: websocket update info.

* refactor: api name change.

* refactor: debug info.

* refactor: debug info.

* refactor: debug info.

* fix: update file

* refactor

* refactor

* refactor: debug info.

* refactor: debug info.

* refactor: debug info.

* refactor: debug info.

* refactor: debug info.

* refactor: debug info.

* fix: callback update.

* fix: callback update.

* refactor

* fix: update message.

* fix: msg cache timeout.

* refactor

* refactor

* fix: push update.

* fix: push update.

* fix: push update.

* fix: push update.

* fix: push update.

* fix: push update.

* refactor

* refactor

* fix: push update.

* fix: websocket handle error remove when upgrade error.

* fix: priority url

* fix: minio config

* refactor: add zk logger.

* refactor

* fix: minio config

* refactor

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* fix bug: get localIP

* refactor

* refactor

* refactor

* refactor: remove zk logger.

* refactor: update tools version.

* refactor

* refactor: update server version to 3.7.0.

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor: zk log debug.

* refactor: zk log debug.

* refactor: zk log debug.

* refactor: zk log debug.

* refactor: zk log debug.

* refactor

* refactor

* refactor

* refactor: log level change.

* refactor: 3.7.0 code conventions.

---------

Co-authored-by: skiffer-git <44203734@qq.com>
Co-authored-by: withchao <993506633@qq.com>
Co-authored-by: root <root@localhost.localdomain>

* update go.mod go.sum (#2209)

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* remove \r

* fix bug: get localIP

* update some ci file (#2200)

* Update openimci.yml

* Update golangci-lint.yml

* Update e2e-test.yml

* 3.6.1 code conventions (#2202)

* refactor: webhooks update.

* Adjust configuration settings

* Adjust configuration settings

* Adjust configuration settings

* feat: s3 api addr

* refactor: webhooks update.

* Adjust configuration settings

* Adjust configuration settings

* Adjust configuration settings

* Adjust configuration settings

* Adjust configuration settings

* Adjust configuration settings

* Adjust configuration settings

* refactor: webhooks update.

* refactor: kafka update.

* Simplify the Docker Compose configuration, remove unnecessary environment variables, and eliminate the gateway service.

* refactor: kafka update.

* refactor: kafka update.

* Simplify the Docker Compose configuration, remove unnecessary environment variables, and eliminate the gateway service.

* Simplify the Docker Compose configuration, remove unnecessary environment variables, and eliminate the gateway service.

* Windows can compile and run.

* Windows can compile and run.

* refactor: kafka update.

* feat: msg cache split

* refactor: webhooks update

* refactor: webhooks update

* refactor: friends update

* refactor: group update

* refactor: third update

* refactor: api update

* refactor: crontab update

* refactor: msggateway update

* mage

* mage

* refactor: all module update.

* check

* refactor: all module update.

* load config

* load config

* load config

* load config

* refactor: all module update.

* refactor: all module update.

* refactor: all module update.

* refactor: all module update.

* refactor: all module update.

* Optimize Docker configuration and script.

* refactor: all module update.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* refactor: all module update.

* Optimize Docker configuration and script.

* refactor: all module update.

* refactor: all module update.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* update tools

* update tools

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* update protocol

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* refactor: all module update.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* Optimize Docker configuration and script.

* refactor: api remove token auth by redis directly.

* Code Refactoring

* refactor: websocket auth change to call rpc of auth.

* refactor: kick online user and remove token change to call auth rpc.

* refactor: kick online user and remove token change to call auth rpc.

* refactor: remove msggateway redis.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor webhook

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor webhook

* refactor: cmd update.

* refactor: cmd update.

* fix: runtime: goroutine stack exceeds

* refactor: cmd update.

* refactor notification

* refactor notification

* refactor

* refactor: cmd update.

* refactor: cmd update.

* refactor

* refactor

* refactor

* protojson

* protojson

* protojson

* go mod

* wrapperspb

* refactor: cmd update.

* refactor: cmd update.

* refactor: cmd update.

* refactor: context update.

* refactor: websocket update info.

* refactor: websocket update info.

* refactor: websocket update info.

* refactor: websocket update info.

* refactor: api name change.

* refactor: debug info.

* refactor: debug info.

* refactor: debug info.

* fix: update file

* refactor

* refactor

* refactor: debug info.

* refactor: debug info.

* refactor: debug info.

* refactor: debug info.

* refactor: debug info.

* refactor: debug info.

* fix: callback update.

* fix: callback update.

* refactor

* fix: update message.

* fix: msg cache timeout.

* refactor

* refactor

* fix: push update.

* fix: push update.

* fix: push update.

* fix: push update.

* fix: push update.

* fix: push update.

* refactor

* refactor

* fix: push update.

* fix: websocket handle error remove when upgrade error.

* fix: priority url

* fix: minio config

* refactor: add zk logger.

* refactor

* fix: minio config

* refactor

* refactor

* refactor

* refactor

* refactor: remove zk logger.

* refactor: update tools version.

* refactor

* refactor: update server version to 3.7.0.

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor

* refactor: zk log debug.

* refactor: zk log debug.

* refactor: zk log debug.

* refactor: zk log debug.

* refactor: zk log debug.

* refactor

* refactor

* refactor

* refactor: log level change.

* refactor: 3.7.0 code conventions.

---------

Co-authored-by: skiffer-git <44203734@qq.com>
Co-authored-by: withchao <993506633@qq.com>

* update go.mod go.sum

* Remove Chinese comments

* user localhost for minio

* user localhost for minio

* Remove Chinese comments

* Remove Chinese comments

* Remove Chinese comments

* Set up 4 instances of transfer

* Set up 4 instances of transfer

* Add comments to the configuration file

* Add comments to the configuration file

---------

Co-authored-by: root <root@localhost.localdomain>
Co-authored-by: xuan <146319162+wxuanF@users.noreply.github.com>
Co-authored-by: OpenIM-Gordon <46924906+FGadvancer@users.noreply.github.com>
Co-authored-by: withchao <993506633@qq.com>

* Update the document (#2221)

* Update the document

* Update the document

* use openim/openim-admin openim/openim-web image

* Update .golangci.yml

* Add etcd as a service discovery mechanism

* Add etcd as a service discovery mechanism

* update

* update license

* update license

* update license

* update license

* update license

---------

Co-authored-by: OpenIM-Gordon <46924906+FGadvancer@users.noreply.github.com>
Co-authored-by: withchao <993506633@qq.com>
Co-authored-by: root <root@localhost.localdomain>
Co-authored-by: xuan <146319162+wxuanF@users.noreply.github.com>
2025-04-22 16:48:12 +08:00
Monet Lee 97b8c07d57 feat: Implement stress test v2. (#3292)
* feat: improve stress test code.

* feat: Implement stress test v2.
2025-04-22 07:52:55 +00:00
skiffer-git 045afd74b9 Update LICENSE 2025-04-22 14:35:50 +08:00
icey-yu 8e824c7e8e fix: group status in GroupDismissedNotification (#3286) 2025-04-17 09:28:23 +00:00
chao 52bd5e8be9 fix: transferring the group owner to a muted member, incremental version error (#3284)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* fix: transferring the group owner to a muted member, incremental version error
2025-04-15 10:27:39 +00:00
chao 1697651100 feat: GetConversationsHasReadAndMaxSeq support pinned (#3281)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned

* feat: GetConversationsHasReadAndMaxSeq support pinned
2025-04-14 03:18:07 +00:00
chao d385fdd0aa feat: support server-issued configuration, which can be set for individual users (#3271)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break

* feat: support client config

* feat: support client config
2025-04-02 10:18:06 +00:00
chao aca0eac955 fix: oss specifies content-type when uploading (#3267)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* fix: oss specifies content-type when uploading

* fix: the version number contains a line break

* fix: the version number contains a line break
2025-04-01 09:13:32 +00:00
Monet Lee 3b710fdfdb fix: improve stress test tools parms. (#3265)
* feat: implement stress-test tools.

* revert config file.

* fix: improve tools parms.

* fix modify args.
2025-03-31 08:45:52 +00:00
Monet Lee 4dc9b45861 feat: implement stress-test tools. (#3261)
* feat: implement stress-test tools.

* revert config file.
2025-03-28 07:46:42 +00:00
OpenIM-Gordon 73934fd955 feat: add filtering for invalid messages and invalid conversations to prevent data-fetching exceptions after conversations are deleted. (#3239) 2025-03-21 07:19:21 +00:00
OpenIM-Gordon 11044eac58 feat: add a function for business info change to update related conve… (#3225)
* feat: add a function for business info change to update related conversation's ex info.

* feat: add a function for business info change to update related conversation's ex info.

* feat: add a function for business info change to update related conversation's ex info.

* feat: add a function for business info change to update related conversation's ex info.
2025-03-21 07:10:31 +00:00
Monet Lee b969827b9a feat: Implement webhook in createConversation (#3228)
* update test method args.

* feat: implement createConversations webhook function.

* improve webhookCreateConversations Implement

* implement createconversation webhook.

* remove unused paramaters.
2025-03-14 08:46:29 +00:00
icey-yu 0b9dbd301c feat: check if the secret in config/share.yml has been changed during registration (#3223)
* feat: check if the secret in config/share.yml has been changed during registration.

* fix: cicd

* fix: code

* fix: cicd

* fix: cicd

* fix: cicd

* fix: cicd

* fix: cicd
2025-03-13 10:21:48 +00:00
icey-yu 3516f843db fix: AdminToken save to redis && limit 1 for each userID (#3224) 2025-03-13 09:17:50 +00:00
icey-yu e76e02fdba feat: set configs (#3183) 2025-03-12 06:54:37 +00:00
Monet Lee c0c4ba21a8 fix: solve unocrrect invite notification (#3213) 2025-03-12 03:38:18 +00:00
Monet Lee 7859f2ccb3 refactor: improve setConversations method. (#3194) 2025-03-07 09:03:40 +00:00
chao 964ee7a8dd feat: sending messages supports returning fields modified by webhook (#3192)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook

* feat: Sending messages supports returning fields modified by webhook
2025-03-05 09:04:57 +00:00
Monet Lee 0541d0bf06 fix: solve uncorrect GroupMember enter group notification type. (#3188) 2025-03-04 10:12:35 +00:00
icey-yu 737169a466 feat: system account send msg doesn't need friend verify (#3187) 2025-03-04 10:04:21 +00:00
chao 68ee86c1d9 fix: the sorting is wrong after canceling the administrator in group settings (#3185)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: the sorting is wrong after canceling the administrator in group settings
2025-03-04 08:09:29 +00:00
chao ab6c9dca71 feat: optimizing BatchGetIncrementalGroupMember (#3180)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline

* fix: optimizing BatchGetIncrementalGroupMember
2025-03-03 09:59:35 +00:00
Monet Lee 887e0b7314 fix: solve uncorrect notification when set group info (#3172)
* fix: setGroupInfoEx uncorrect call.

* update notification logic.

* update group notification logic.

* update update group announcement notication.

* fix errror.

* refresh

* solve conflict.

* update args.
2025-03-03 02:39:53 +00:00
OpenIM-Gordon 1df02692bf refactor: change sendNotification to sendMessage to avoid ambiguity regarding message sending behavior. (#3173)
* feat: add a field to specify whether to send a notification message when creating a group.

* feat: add a field to specify whether to send a notification message when creating a group.

* refactor: change sendNotification to sendMessage to avoid ambiguity regarding message sending behavior.

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
2025-02-28 08:59:04 +00:00
OpenIM-Gordon 4d2fce0e52 feat: add a field to specify whether to send a notification message w… (#3163)
* feat: add a field to specify whether to send a notification message when creating a group.

* feat: add a field to specify whether to send a notification message when creating a group.

---------

Co-authored-by: Monet Lee <monet_lee@163.com>
2025-02-28 08:38:57 +00:00
OpenIM-Gordon b7f88138bd feat: add a new message type: Markdown text (#3162) 2025-02-28 08:22:00 +00:00
chao bf6d77a267 feat: the default notification.yml is not configured properly (#3168)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* feat: optimize the default notification.yml

* fix: shouldPushOffline
2025-02-27 08:17:00 +00:00
icey-yu 46f1a9c7a3 fix: PCAndOther multi login policy can`t get old clients correctly (#3158) 2025-02-24 07:29:39 +00:00
icey-yu df1c8df693 feat: Change after webhook filter && feat SendSimpleMsg (#3151)
* feat: msg filter and search system account

* feat: search system account

* chore: msg

* chore: msg

* chore: msg

* chore: webhook filter && sendSimpleMessage
2025-02-20 08:13:47 +00:00
icey-yu 14393b0f53 fix: Offline push does not have a badge && Android offline push (#3146)
* fix: offline push can display badge

* feat: strategy

* feat: log

* feat: log

* chore: offlinepush

* fix: offlinepush

* fix: log
2025-02-17 10:20:31 +00:00
chao 9ed6200e45 feat: optimize code and support running in single process mode (#3142)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* monolithic

* fix: DeleteDoc crash

* fix: DeleteDoc crash

* fix: monolithic

* fix: monolithic

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: mq

* fix: mq

* fix: user msg timestamp

* fix: mq

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* seq read config

* seq read config

* 1

* 1

* fix: the source message of the reference is withdrawn, and the referenced message is deleted

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1

* 1
2025-02-14 08:18:27 +00:00
chao e37ea50b94 fix: the source message of the reference is withdrawn, and the referenced message is deleted (#3137)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config

* fix: the source message of the reference is withdrawn, and the referenced message is deleted
2025-02-12 10:46:38 +00:00
chao a3c43a49d8 fix: seq conversion not reading env in docker environment (#3130)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp

* seq read config

* seq read config
2025-02-10 03:44:57 +00:00
Monet Lee 489571f4b6 fix: solve stop when merge failed (#3106) 2025-02-08 07:26:35 +00:00
Monet Lee ad8829c5a6 build: keep conflict is true. (#3103) 2025-02-08 04:03:29 +00:00
chao 0bf076bb05 fix: the user sets the conversation timer cleanup timestamp unit incorrectly (#3102)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group

* fix: user msg timestamp
2025-02-08 02:36:00 +00:00
chao ed416f8376 fix: crash caused by withdrawing messages from users who have left the group (#3100)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time

* fix: crash caused by withdrawing messages from users who have left the group
2025-02-06 02:38:46 +00:00
skiffer-git 86aa26ef62 Update README.md 2025-02-05 08:28:22 +08:00
skiffer-git d1b4d84628 Update LICENSE 2025-02-04 23:25:46 +08:00
chao 274a9bee65 fix: the abnormal message has no sending time, causing the SDK to be abnormal (#3087)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash

* fix: fill send time

* fix: fill send time
2025-01-24 08:13:12 +00:00
Monet Lee 83c7b7134c refactor: improve workflows logic. (#3072)
* refactor: improve workflows logic.

* update args.

* remove unused contents.

* update milestone merge contents.

* update contents.
2025-01-23 07:18:39 +00:00
chao bbb5473d26 fix: DeleteDoc crash (#3078)
* pb

* fix: Modifying other fields while setting IsPrivateChat does not take effect

* fix: quote message error revoke

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* refactoring scheduled tasks

* upgrading pkg tools

* fix

* fix

* optimize log output

* feat: support GetLastMessage

* feat: support GetLastMessage

* feat: s3 switch

* feat: s3 switch

* fix: GetUsersOnline

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: SendBusinessNotification supported configuration parameters

* feat: seq conversion failed without exiting

* fix: DeleteDoc crash
2025-01-22 07:24:40 +00:00
icey-yu 96baa5a0ff fix: check error in BatchSetTokenMapByUidPid (#3076) 2025-01-22 06:38:27 +00:00
icey-yu 47e916aebe feat: add backup volume && optimize log print (#3066)
* feat: backup

* feat: backup

* feat: backup

* feat: optimize log print
2025-01-17 01:38:55 +00:00
245 changed files with 9561 additions and 4322 deletions
+3 -1
View File
@@ -2,7 +2,7 @@ MONGO_IMAGE=mongo:7.0
REDIS_IMAGE=redis:7.0.0 REDIS_IMAGE=redis:7.0.0
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=bitnami/etcd:3.5.13
PROMETHEUS_IMAGE=prom/prometheus:v2.45.6 PROMETHEUS_IMAGE=prom/prometheus:v2.45.6
ALERTMANAGER_IMAGE=prom/alertmanager:v0.27.0 ALERTMANAGER_IMAGE=prom/alertmanager:v0.27.0
GRAFANA_IMAGE=grafana/grafana:11.0.1 GRAFANA_IMAGE=grafana/grafana:11.0.1
@@ -17,6 +17,8 @@ OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.8.4
DATA_DIR=./ DATA_DIR=./
MONGO_BACKUP_DIR=${DATA_DIR}components/backup/mongo/
PROMETHEUS_PORT=19091 PROMETHEUS_PORT=19091
ALERTMANAGER_PORT=19093 ALERTMANAGER_PORT=19093
GRAFANA_PORT=13000 GRAFANA_PORT=13000
+1 -1
View File
@@ -4,7 +4,7 @@ contact_links:
# description: "Report a bug in the project" # description: "Report a bug in the project"
# file: "bug-report.yml" # file: "bug-report.yml"
- name: 📢 Connect on slack - name: 📢 Connect on slack
url: https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg url: https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A
about: Support OpenIM-related requests or issues, get in touch with developers and help on slack about: Support OpenIM-related requests or issues, get in touch with developers and help on slack
- name: 🌐 OpenIM Blog - name: 🌐 OpenIM Blog
url: https://www.openim.io/ url: https://www.openim.io/
+1 -2
View File
@@ -4,7 +4,6 @@ title: "[Other]: <give this problem a name>"
labels: ["other"] labels: ["other"]
# assignees: [] # assignees: []
body: body:
- type: markdown - type: markdown
attributes: attributes:
@@ -26,5 +25,5 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | value: |
You can also join our Discord community [here](https://join.slack.com/t/openimsdk/shared_invite/zt-1tmoj26uf-_FDy3dowVHBiGvLk9e5Xkg) You can also join our Discord community [here](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
Feel free to check out other cool repositories of the openim Community [here](https://github.com/openimsdk) Feel free to check out other cool repositories of the openim Community [here](https://github.com/openimsdk)
+2 -3
View File
@@ -11,7 +11,6 @@ jobs:
permissions: permissions:
issues: write issues: write
steps: steps:
- name: Invite user to join OpenIM Community - name: Invite user to join OpenIM Community
uses: peter-evans/create-or-update-comment@v4 uses: peter-evans/create-or-update-comment@v4
with: with:
@@ -20,11 +19,11 @@ jobs:
body: | body: |
We value close connections with our users, developers, and contributors here at Open-IM-Server. With a large community and maintainer team, we're always here to help and support you. Whether you're looking to join our community or have any questions or suggestions, we welcome you to get in touch with us. We value close connections with our users, developers, and contributors here at Open-IM-Server. With a large community and maintainer team, we're always here to help and support you. Whether you're looking to join our community or have any questions or suggestions, we welcome you to get in touch with us.
Our most recommended way to get in touch is through [Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q). Even if you're in China, Slack is usually not blocked by firewalls, making it an easy way to connect with us. Our Slack community is the ideal place to discuss and share ideas and suggestions with other users and developers of Open-IM-Server. You can ask technical questions, seek help, or share your experiences with other users of Open-IM-Server. Our most recommended way to get in touch is through [Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A). Even if you're in China, Slack is usually not blocked by firewalls, making it an easy way to connect with us. Our Slack community is the ideal place to discuss and share ideas and suggestions with other users and developers of Open-IM-Server. You can ask technical questions, seek help, or share your experiences with other users of Open-IM-Server.
In addition to Slack, we also offer the following ways to get in touch: In addition to Slack, we also offer the following ways to get in touch:
+ <a href="https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q" target="_blank"><img src="https://img.shields.io/badge/Slack-OpenIM%2B-blueviolet?logo=slack&amp;logoColor=white"></a> We also have Slack channels for you to communicate and discuss. To join, visit https://slack.com/ and join our [👀 Open-IM-Server slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) team channel. + <a href="https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A" target="_blank"><img src="https://img.shields.io/badge/Slack-OpenIM%2B-blueviolet?logo=slack&amp;logoColor=white"></a> We also have Slack channels for you to communicate and discuss. To join, visit https://slack.com/ and join our [👀 Open-IM-Server slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) team channel.
+ <a href="https://mail.google.com/mail/u/0/?fs=1&tf=cm&to=info@openim.io" target="_blank"><img src="https://img.shields.io/badge/gmail-%40OOpenIMSDKCore?style=social&logo=gmail"></a> Get in touch with us on [Gmail](https://mail.google.com/mail/u/0/?fs=1&tf=cm&to=winxu81@gmail.com). If you have any questions or issues that need resolving, or any suggestions and feedback for our open source projects, please feel free to contact us via email. + <a href="https://mail.google.com/mail/u/0/?fs=1&tf=cm&to=info@openim.io" target="_blank"><img src="https://img.shields.io/badge/gmail-%40OOpenIMSDKCore?style=social&logo=gmail"></a> Get in touch with us on [Gmail](https://mail.google.com/mail/u/0/?fs=1&tf=cm&to=winxu81@gmail.com). If you have any questions or issues that need resolving, or any suggestions and feedback for our open source projects, please feel free to contact us via email.
+ <a href="https://doc.rentsoft.cn/" target="_blank"><img src="https://img.shields.io/badge/%E5%8D%9A%E5%AE%A2-%40OpenIMSDKCore-blue?style=social&logo=Octopus%20Deploy"></a> Read our [blog](https://doc.rentsoft.cn/). Our blog is a great place to stay up-to-date with Open-IM-Server projects and trends. On the blog, we share our latest developments, tech trends, and other interesting information. + <a href="https://doc.rentsoft.cn/" target="_blank"><img src="https://img.shields.io/badge/%E5%8D%9A%E5%AE%A2-%40OpenIMSDKCore-blue?style=social&logo=Octopus%20Deploy"></a> Read our [blog](https://doc.rentsoft.cn/). Our blog is a great place to stay up-to-date with Open-IM-Server projects and trends. On the blog, we share our latest developments, tech trends, and other interesting information.
+ <a href="https://github.com/OpenIMSDK/OpenIM-Docs/blob/main/docs/images/WechatIMG20.jpeg" target="_blank"><img src="https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1-OpenIMSDKCore-brightgreen?logo=wechat&style=flat-square"></a> Add [Wechat](https://github.com/OpenIMSDK/OpenIM-Docs/blob/main/docs/images/WechatIMG20.jpeg) and indicate that you are a user or developer of Open-IM-Server. We will process your request as soon as possible. + <a href="https://github.com/OpenIMSDK/OpenIM-Docs/blob/main/docs/images/WechatIMG20.jpeg" target="_blank"><img src="https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1-OpenIMSDKCore-brightgreen?logo=wechat&style=flat-square"></a> Add [Wechat](https://github.com/OpenIMSDK/OpenIM-Docs/blob/main/docs/images/WechatIMG20.jpeg) and indicate that you are a user or developer of Open-IM-Server. We will process your request as soon as possible.
@@ -19,26 +19,26 @@ jobs:
steps: steps:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v3 uses: actions/checkout@v4
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2 uses: docker/setup-buildx-action@v3.8.0
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Aliyun Container Registry - name: Log in to Aliyun Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
registry: registry.cn-hangzhou.aliyuncs.com registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALIREGISTRY_USERNAME }} username: ${{ secrets.ALIREGISTRY_USERNAME }}
@@ -46,7 +46,7 @@ jobs:
- name: Extract metadata for Docker (tags, labels) - name: Extract metadata for Docker (tags, labels)
id: meta id: meta
uses: docker/metadata-action@v5 uses: docker/metadata-action@v5.6.0
with: with:
tags: | tags: |
type=ref,event=tag type=ref,event=tag
@@ -54,7 +54,6 @@ jobs:
type=ref,event=branch type=ref,event=branch
type=semver,pattern={{version}} type=semver,pattern={{version}}
type=semver,pattern=v{{version}} type=semver,pattern=v{{version}}
# type=semver,pattern={{major}}.{{minor}}
type=semver,pattern=release-{{raw}} type=semver,pattern=release-{{raw}}
type=sha type=sha
type=raw,value=${{ github.event.inputs.tag }} type=raw,value=${{ github.event.inputs.tag }}
+118 -38
View File
@@ -4,7 +4,7 @@ on:
push: push:
pull_request: pull_request:
paths-ignore: paths-ignore:
- '**/*.md' - "**/*.md"
workflow_dispatch: workflow_dispatch:
@@ -12,13 +12,17 @@ jobs:
go-build: go-build:
name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }} name: Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
env:
SHARE_CONFIG_PATH: config/share.yml
permissions: permissions:
contents: write contents: write
pull-requests: write pull-requests: write
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
go_version: ["1.21.x", "1.22.x"] go_version: ["1.22.x"]
steps: steps:
- name: Checkout Server repository - name: Checkout Server repository
@@ -37,27 +41,24 @@ jobs:
- name: Set up infra services - name: Set up infra services
uses: hoverkraft-tech/compose-action@v2.0.1 uses: hoverkraft-tech/compose-action@v2.0.1
# Uncomment and set the correct path to your docker-compose file
with: with:
compose-file: "./docker-compose.yml" compose-file: "./docker-compose.yml"
# run: | - name: Modify Server Configuration
# sudo docker compose up -d run: |
# sudo sleep 30 # Increased sleep time for better stability yq e '.secret = 123456' -i ${{ env.SHARE_CONFIG_PATH }}
# timeout-minutes: 60 # Increased timeout for Docker setup
# - name: Get Internal IP Address
# id: get-ip
# run: |
# IP=$(hostname -I | awk '{print $1}')
# echo "The IP Address is: $IP"
# echo "::set-output name=ip::$IP"
# - name: Get Internal IP Address # - name: Update .env
# id: get-ip # run: |
# run: | # sed -i 's|externalAddress:.*|externalAddress: "http://${{ steps.get-ip.outputs.ip }}:10005"|' config/minio.yml
# IP=$(hostname -I | awk '{print $1}') # cat config/minio.yml
# echo "The IP Address is: $IP"
# echo "::set-output name=ip::$IP"
# - name: Update .env
# run: |
# sed -i 's|externalAddress:.*|externalAddress: "http://${{ steps.get-ip.outputs.ip }}:10005"|' config/minio.yml
# cat config/minio.yml
- name: Build and test Server Services - name: Build and test Server Services
run: | run: |
@@ -78,6 +79,11 @@ jobs:
go mod download go mod download
go install github.com/magefile/mage@latest go install github.com/magefile/mage@latest
- name: Modify Chat Configuration
run: |
cd ${{ github.workspace }}/chat-repo
yq e '.openIM.secret = 123456' -i ${{ env.SHARE_CONFIG_PATH }}
- name: Build and test Chat Services - name: Build and test Chat Services
run: | run: |
cd ${{ github.workspace }}/chat-repo cd ${{ github.workspace }}/chat-repo
@@ -85,6 +91,90 @@ jobs:
mage start mage start
mage check mage check
- name: Test Server and Chat
run: |
check_error() {
echo "Response: $1"
errCode=$(echo $1 | jq -r '.errCode')
if [ "$errCode" != "0" ]; then
errMsg=$(echo $1 | jq -r '.errMsg')
echo "Error: $errMsg"
exit 1
fi
}
# Test register
response1=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -d '{
"verifyCode": "666666",
"platform": 3,
"autoLogin": true,
"user":{
"nickname": "test12312",
"areaCode":"+86",
"phoneNumber": "12345678190",
"password":"test123456"
}
}' http://127.0.0.1:10008/account/register)
check_error "$response1"
userID1=$(echo $response1 | jq -r '.data.userID')
echo "userID1: $userID1"
response2=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -d '{
"verifyCode": "666666",
"platform": 3,
"autoLogin": true,
"user":{
"nickname": "test22312",
"areaCode":"+86",
"phoneNumber": "12345678290",
"password":"test123456"
}
}' http://127.0.0.1:10008/account/register)
check_error "$response2"
userID2=$(echo $response2 | jq -r '.data.userID')
echo "userID2: $userID2"
# Test login
login_response=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -d '{
"platform": 3,
"areaCode":"+86",
"phoneNumber": "12345678190",
"password":"test123456"
}' http://localhost:10008/account/login)
check_error "$login_response"
# Test get admin token
get_admin_token_response=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -d '{
"secret": "123456",
"platformID": 2,
"userID": "imAdmin"
}' http://127.0.0.1:10002/auth/get_admin_token)
check_error "$get_admin_token_response"
adminToken=$(echo $get_admin_token_response | jq -r '.data.token')
echo "adminToken: $adminToken"
# Test send message
send_msg_response=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -H "token: $adminToken" -d '{
"sendID": "'$userID1'",
"recvID": "'$userID2'",
"senderPlatformID": 3,
"content": {
"content": "hello!!"
},
"contentType": 101,
"sessionType": 1
}' http://127.0.0.1:10002/msg/send_msg)
check_error "$send_msg_response"
# Test get users
get_users_response=$(curl -X POST -H "Content-Type: application/json" -H "operationID: imAdmin" -H "token: $adminToken" -d '{
"pagination": {
"pageNumber": 1,
"showNumber": 100
}
}' http://127.0.0.1:10002/user/get_users)
check_error "$get_users_response"
go-test: go-test:
name: Benchmark Test with go ${{ matrix.go_version }} on ${{ matrix.os }} name: Benchmark Test with go ${{ matrix.go_version }} on ${{ matrix.os }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@@ -92,12 +182,13 @@ jobs:
contents: write contents: write
env: env:
SDK_DIR: openim-sdk-core SDK_DIR: openim-sdk-core
CONFIG_PATH: config/notification.yml NOTIFICATION_CONFIG_PATH: config/notification.yml
# pull-requests: write SHARE_CONFIG_PATH: config/share.yml
strategy: strategy:
matrix: matrix:
os: [ ubuntu-latest ] os: [ubuntu-latest]
go_version: [ "1.22.x" ] go_version: ["1.22.x"]
steps: steps:
- name: Checkout Server repository - name: Checkout Server repository
@@ -106,7 +197,8 @@ jobs:
- name: Checkout SDK repository - name: Checkout SDK repository
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
repository: 'openimsdk/openim-sdk-core' repository: "openimsdk/openim-sdk-core"
ref: "main"
path: ${{ env.SDK_DIR }} path: ${{ env.SDK_DIR }}
- name: Set up Go ${{ matrix.go_version }} - name: Set up Go ${{ matrix.go_version }}
@@ -119,15 +211,11 @@ jobs:
go install github.com/magefile/mage@latest go install github.com/magefile/mage@latest
go mod download go mod download
- name: Install yq
run: |
sudo wget https://github.com/mikefarah/yq/releases/download/v4.34.1/yq_linux_amd64 -O /usr/bin/yq
sudo chmod +x /usr/bin/yq
- name: Modify Server Configuration - name: Modify Server Configuration
run: | run: |
yq e '.groupCreated.isSendMsg = true' -i ${{ env.CONFIG_PATH }} yq e '.groupCreated.isSendMsg = true' -i ${{ env.NOTIFICATION_CONFIG_PATH }}
yq e '.friendApplicationApproved.isSendMsg = true' -i ${{ env.CONFIG_PATH }} yq e '.friendApplicationApproved.isSendMsg = true' -i ${{ env.NOTIFICATION_CONFIG_PATH }}
yq e '.secret = 123456' -i ${{ env.SHARE_CONFIG_PATH }}
- name: Start Server Services - name: Start Server Services
run: | run: |
@@ -183,11 +271,3 @@ jobs:
run: | run: |
CONTAINER_NAME="${{ github.event.repository.name }}-container" CONTAINER_NAME="${{ github.event.repository.name }}-container"
docker logs $CONTAINER_NAME docker logs $CONTAINER_NAME
# - name: Cleanup Docker Container
# run: |
# CONTAINER_NAME="${{ github.event.repository.name }}-container"
# IMAGE_NAME="${{ github.event.repository.name }}-test"
# docker stop $CONTAINER_NAME
# docker rm $CONTAINER_NAME
# docker rmi $IMAGE_NAME
+1 -1
View File
@@ -32,5 +32,5 @@ jobs:
token: ${{ secrets.BOT_TOKEN }} token: ${{ secrets.BOT_TOKEN }}
body: | body: |
This issue is available for anyone to work on. **Make sure to reference this issue in your pull request.** :sparkles: Thank you for your contribution! :sparkles: This issue is available for anyone to work on. **Make sure to reference this issue in your pull request.** :sparkles: Thank you for your contribution! :sparkles:
[Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) to connect and communicate with our developers. [Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) to connect and communicate with our developers.
If you wish to accept this assignment, please leave a comment in the comments section: `/accept`.🎯 If you wish to accept this assignment, please leave a comment in the comments section: `/accept`.🎯
+89 -126
View File
@@ -1,4 +1,4 @@
name: Create Pre-Release PR from Milestone name: Create Individual PRs from Milestone
permissions: permissions:
contents: write contents: write
@@ -9,24 +9,24 @@ on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
milestone_name: milestone_name:
description: 'Milestone name to collect closed PRs from' description: "Milestone name to collect closed PRs from"
required: true required: true
default: 'v3.8.2' default: "v3.8.4"
target_branch: target_branch:
description: 'Target branch to merge the consolidated PR' description: "Target branch to merge the consolidated PR"
required: true required: true
default: 'pre-release-v3.8.2' default: "pre-release-v3.8.4"
env: env:
MILESTONE_NAME: ${{ github.event.inputs.milestone_name || 'v3.8.2' }} MILESTONE_NAME: ${{ github.event.inputs.milestone_name || 'v3.8.4' }}
TARGET_BRANCH: ${{ github.event.inputs.target_branch || 'pre-release-v3.8.2' }} TARGET_BRANCH: ${{ github.event.inputs.target_branch || 'pre-release-v3.8.4' }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BOT_TOKEN: ${{ secrets.BOT_TOKEN }} BOT_TOKEN: ${{ secrets.BOT_TOKEN }}
LABEL_NAME: cherry-picked LABEL_NAME: cherry-picked
TEMP_DIR: /tmp # Using /tmp as the temporary directory TEMP_DIR: /tmp
jobs: jobs:
cherry_pick_milestone_prs: merge_milestone_prs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Setup temp directory - name: Setup temp directory
@@ -47,7 +47,6 @@ jobs:
- name: Setup Git User for OpenIM-Robot - name: Setup Git User for OpenIM-Robot
run: | run: |
# Set up Git credentials for the bot
git config --global user.email "OpenIM-Robot@users.noreply.github.com" git config --global user.email "OpenIM-Robot@users.noreply.github.com"
git config --global user.name "OpenIM-Robot" git config --global user.name "OpenIM-Robot"
@@ -83,136 +82,100 @@ jobs:
if ! echo "$labels" | grep -q "${LABEL_NAME}"; then 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 #$pr_number does not have the 'cherry-picked' label. Adding to the list."
echo "$pr_number" >> ${{ env.TEMP_DIR }}/pr_numbers.txt echo "$pr_number" >> ${{ env.TEMP_DIR }}/pr_numbers.txt
else
echo "PR #$pr_number already has the 'cherry-picked' label. Skipping."
fi fi
done done
# Sort the filtered PR numbers
sort -n ${{ env.TEMP_DIR }}/pr_numbers.txt -o ${{ env.TEMP_DIR }}/pr_numbers.txt sort -n ${{ env.TEMP_DIR }}/pr_numbers.txt -o ${{ env.TEMP_DIR }}/pr_numbers.txt
echo "Filtered and sorted PR numbers:" - name: Create Individual PRs
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: | 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 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" \ pr_details=$(curl -s -H "Authorization: token $BOT_TOKEN" \
-H "Accept: application/vnd.github+json" \ -H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/${{ github.repository }}/pulls/$pr_number") "https://api.github.com/repos/${{ github.repository }}/pulls/$pr_number")
pr_title=$(echo "$pr_details" | jq -r '.title') pr_title=$(echo "$pr_details" | jq -r '.title')
pr_body=$(echo "$pr_details" | jq -r '.body')
pr_creator=$(echo "$pr_details" | jq -r '.user.login')
merge_commit=$(echo "$pr_details" | jq -r '.merge_commit_sha') merge_commit=$(echo "$pr_details" | jq -r '.merge_commit_sha')
short_commit_hash=$(echo "$merge_commit" | cut -c 1-7) short_commit_hash=$(echo "$merge_commit" | cut -c 1-7)
# Append PR details to the body if [ "$merge_commit" != "null" ]; then
echo "- $pr_title: (#$pr_number) ($short_commit_hash)" >> ${{ env.TEMP_DIR }}/pr_body.txt git fetch origin
if [ "$merge_commit" != "null" ];then echo "Checking out target branch: $TARGET_BRANCH"
echo "$merge_commit" >> ${{ env.TEMP_DIR }}/commit_hashes.txt git checkout $TARGET_BRANCH
echo "#$pr_number" >> ${{ env.TEMP_DIR }}/pr_title.txt
pr_numbers_in_title="$pr_numbers_in_title #$pr_number" echo "Pulling latest changes from target branch: $TARGET_BRANCH"
git pull origin $TARGET_BRANCH
cherry_pick_branch="cherry-pick-${short_commit_hash}"
git checkout -b $cherry_pick_branch
echo "Cherry-picking commit: $merge_commit"
if ! git cherry-pick "$merge_commit" --strategy=recursive -X theirs; then
echo "Conflict detected for $merge_commit. 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 || { echo "Cherry-pick failed, but continuing to create PR."; }
else
echo "Cherry-pick successful for commit $merge_commit."
fi
git remote set-url origin "https://${BOT_TOKEN}@github.com/${{ github.repository }}.git"
echo "Pushing branch: $cherry_pick_branch"
if ! git push origin $cherry_pick_branch --force; then
echo "Push failed, but continuing to create PR..."
fi
new_pr_title="$pr_title [Created by @$pr_creator from #$pr_number]"
new_pr_body="$pr_body
> This PR is created from original PR #$pr_number."
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 "$new_pr_title" \
--arg head "$cherry_pick_branch" \
--arg base "$TARGET_BRANCH" \
--arg body "$new_pr_body" \
'{title: $title, head: $head, base: $base, body: $body}')")
new_pr_number=$(echo "$response" | jq -r '.number')
if [[ "$new_pr_number" == "null" || -z "$new_pr_number" ]]; then
echo "Failed to create PR. Response: $response"
git checkout $TARGET_BRANCH
git branch -D $cherry_pick_branch
echo "Deleted branch: $cherry_pick_branch"
git push origin --delete $cherry_pick_branch
else
echo "Created PR #$new_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/$new_pr_number/labels"
fi
echo ""
echo "----------------------------------------"
echo ""
fi fi
done 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."
+36 -47
View File
@@ -25,11 +25,11 @@ jobs:
with: with:
path: main-repo path: main-repo
- name: Set up QEMU # - name: Set up QEMU
uses: docker/setup-qemu-action@v3 # uses: docker/setup-qemu-action@v3.3.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3.8.0
- name: Build Docker image - name: Build Docker image
id: build id: build
@@ -38,11 +38,8 @@ jobs:
context: ./main-repo context: ./main-repo
load: true load: true
tags: "openim/openim-server:local" tags: "openim/openim-server:local"
cache-from: type=gha cache-from: type=gha,scope=build
cache-to: type=gha,mode=max cache-to: type=gha,mode=max,scope=build
- name: Save Docker image to file
run: docker save -o image.tar openim/openim-server:local
- name: Checkout compose repository - name: Checkout compose repository
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -66,43 +63,12 @@ jobs:
run: | run: |
cd ${{ github.workspace }}/compose-repo cd ${{ github.workspace }}/compose-repo
docker compose up -d docker compose up -d
sleep 60
# - name: Check openim-server health docker compose ps
# run: |
# timeout=300
# interval=30
# elapsed=0
# while [[ $elapsed -le $timeout ]]; do
# if ! docker exec openim-server mage check; then
# echo "openim-server is not ready, waiting..."
# sleep $interval
# elapsed=$(($elapsed + $interval))
# else
# echo "Health check successful"
# exit 0
# fi
# done
# echo "Health check failed after 5 minutes"
# exit 1
# - name: Check openim-chat health
# if: success()
# run: |
# if ! docker exec openim-chat mage check; then
# echo "openim-chat check failed"
# exit 1
# else
# echo "Health check successful"
# exit 0
# fi
- name: Load Docker image from file
run: docker load -i image.tar
- name: Extract metadata for Docker (tags, labels) - name: Extract metadata for Docker (tags, labels)
id: meta id: meta
uses: docker/metadata-action@v5.5.1 uses: docker/metadata-action@v5.6.0
with: with:
images: | images: |
openim/openim-server openim/openim-server
@@ -112,29 +78,27 @@ jobs:
type=ref,event=tag type=ref,event=tag
type=schedule type=schedule
type=ref,event=branch type=ref,event=branch
type=semver,pattern={{version}} # type=semver,pattern={{version}}
type=semver,pattern=v{{version}} type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=semver,pattern=release-{{raw}} type=semver,pattern=release-{{raw}}
type=sha type=sha
type=raw,value=${{ github.event.inputs.tag }} type=raw,value=${{ github.event.inputs.tag }}
- name: Log in to Docker Hub - name: Log in to Docker Hub
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Log in to GitHub Container Registry - name: Log in to GitHub Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Log in to Aliyun Container Registry - name: Log in to Aliyun Container Registry
uses: docker/login-action@v2 uses: docker/login-action@v3.3.0
with: with:
registry: registry.cn-hangzhou.aliyuncs.com registry: registry.cn-hangzhou.aliyuncs.com
username: ${{ secrets.ALIREGISTRY_USERNAME }} username: ${{ secrets.ALIREGISTRY_USERNAME }}
@@ -148,3 +112,28 @@ jobs:
platforms: linux/amd64,linux/arm64 platforms: linux/amd64,linux/arm64
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha,scope=build
cache-to: type=gha,mode=max,scope=build
- name: Verify multi-platform support
run: |
images=("openim/openim-server" "ghcr.io/openimsdk/openim-server" "registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server")
for image in "${images[@]}"; do
for tag in $(echo "${{ steps.meta.outputs.tags }}" | tr ',' '\n'); do
manifest=$(docker manifest inspect "$image:$tag" || echo "error")
if [[ "$manifest" == "error" ]]; then
echo "Manifest not found for $image:$tag"
exit 1
fi
amd64_found=$(echo "$manifest" | jq '.manifests[] | select(.platform.architecture == "amd64")')
arm64_found=$(echo "$manifest" | jq '.manifests[] | select(.platform.architecture == "arm64")')
if [[ -z "$amd64_found" ]]; then
echo "Multi-platform support check failed for $image:$tag - missing amd64"
exit 1
fi
if [[ -z "$arm64_found" ]]; then
echo "Multi-platform support check failed for $image:$tag - missing arm64"
exit 1
fi
done
done
@@ -15,12 +15,33 @@ jobs:
uses: actions/checkout@v4 uses: actions/checkout@v4
with: with:
fetch-depth: 0 fetch-depth: 0
# submodules: "recursive"
- name: Safe submodule initialization
run: |
echo "Checking for submodules..."
if [ -f .gitmodules ]; then
if [ -s .gitmodules ]; then
echo "Initializing submodules..."
if git submodule sync --recursive 2>/dev/null; then
git submodule update --init --force --recursive || {
echo "Warning: Some submodules failed to initialize, continuing anyway..."
}
else
echo "Warning: Submodule sync failed, continuing without submodules..."
fi
else
echo ".gitmodules exists but is empty, skipping submodule initialization"
fi
else
echo "No .gitmodules file found, no submodules to initialize"
fi
# Step 2: Set up Git with official account # Step 2: Set up Git with official account
- name: Set up Git - name: Set up Git
run: | run: |
git config user.name "github-actions[bot]" git config --global user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com" git config --global user.email "github-actions[bot]@users.noreply.github.com"
# Step 3: Check and delete existing tag # Step 3: Check and delete existing tag
- name: Check and delete existing tag - name: Check and delete existing tag
@@ -33,7 +54,8 @@ jobs:
# Step 4: Update version file # Step 4: Update version file
- name: Update version file - name: Update version file
run: | run: |
echo "${{ env.TAG_VERSION }}" > version/version mkdir -p version
echo -n "${{ env.TAG_VERSION }}" > version/version
# Step 5: Commit and push changes # Step 5: Commit and push changes
- name: Commit and push changes - name: Commit and push changes
@@ -42,43 +64,56 @@ jobs:
run: | run: |
git add version/version git add version/version
git commit -m "Update version to ${{ env.TAG_VERSION }}" git commit -m "Update version to ${{ env.TAG_VERSION }}"
git push origin HEAD:${{ github.ref }}
# Step 6: Create and push tag # Step 6: Update tag
- name: Create and push tag - name: Update tag
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
git tag ${{ env.TAG_VERSION }} git tag -fa ${{ env.TAG_VERSION }} -m "Update version to ${{ env.TAG_VERSION }}"
git push origin ${{ env.TAG_VERSION }} git push origin ${{ env.TAG_VERSION }} --force
# Step 7: Find and Publish Draft Release # Step 7: Find and Publish Draft Release
- name: Find and Publish Draft Release - name: Find and Publish Draft Release
uses: actions/github-script@v6 uses: actions/github-script@v7
with: with:
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
script: | script: |
// Get the list of releases const { owner, repo } = context.repo;
const releases = await github.rest.repos.listReleases({ const tagName = process.env.TAG_VERSION;
owner: context.repo.owner,
repo: context.repo.repo
});
// Find the draft release where the title and tag_name are the same try {
const draftRelease = releases.data.find(release => let release;
release.draft && release.name === release.tag_name try {
); const response = await github.rest.repos.getReleaseByTag({
owner,
repo,
tag: tagName
});
release = response.data;
} catch (tagError) {
core.info(`Release not found by tag, searching all releases...`);
const releases = await github.rest.repos.listReleases({
owner,
repo,
per_page: 100
});
release = releases.data.find(r => r.draft && r.tag_name === tagName);
if (!release) {
throw new Error(`No release found with tag ${tagName}`);
}
}
if (draftRelease) {
// Publish the draft release using the release_id
await github.rest.repos.updateRelease({ await github.rest.repos.updateRelease({
owner: context.repo.owner, owner,
repo: context.repo.repo, repo,
release_id: draftRelease.id, // Use release_id release_id: release.id,
draft: false draft: false,
prerelease: release.prerelease
}); });
core.info(`Draft Release ${draftRelease.tag_name} published successfully.`); const status = release.draft ? "was draft" : "was already published";
} else { core.info(`Release ${tagName} ensured to be published (${status}).`);
core.info("No matching draft release found.");
} catch (error) {
core.warning(`Could not find or update release for tag ${tagName}: ${error.message}`);
} }
+2 -2
View File
@@ -22,7 +22,7 @@ jobs:
If you are implementing a feature request, please check with the maintainers that the feature will be accepted first. If you are implementing a feature request, please check with the maintainers that the feature will be accepted first.
[Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) to connect and communicate with our developers. [Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) to connect and communicate with our developers.
Please leave your information in the [✨ discussions](https://github.com/orgs/OpenIMSDK/discussions/426), we expect anyone to join OpenIM developer community. Please leave your information in the [✨ discussions](https://github.com/orgs/OpenIMSDK/discussions/426), we expect anyone to join OpenIM developer community.
@@ -31,5 +31,5 @@ jobs:
If this is a bug report, please include relevant logs to help us debug the problem. If this is a bug report, please include relevant logs to help us debug the problem.
[Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) to connect and communicate with our developers. [Join slack 🤖](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) to connect and communicate with our developers.
continue-on-error: true continue-on-error: true
+2
View File
@@ -28,6 +28,8 @@ run:
# - util # - util
# - .*~ # - .*~
# - api/swagger/docs # - api/swagger/docs
# - server/docs # - server/docs
# - components/mnt/config/certs # - components/mnt/config/certs
# - logs # - logs
+10
View File
@@ -0,0 +1,10 @@
## [v3.8.3-patch.9](https://github.com/openimsdk/open-im-server/releases/tag/v3.8.3-patch.9) (2025-08-19)
### New Features
* feat: enable redis aof-use-rdb-preamble && disable auto rdb [Created [#3535](https://github.com/openimsdk/open-im-server/pull/3535)
### Bug Fixes
* fix: fill in the most recent sendTime for a gap message to prevent th… [#3523](https://github.com/openimsdk/open-im-server/pull/3523)
**Full Changelog**: [v3.8.3-patch.8...v3.8.3-patch.9](https://github.com/openimsdk/open-im-server/compare/v3.8.3-patch.8...v3.8.3-patch.9)
+4
View File
@@ -0,0 +1,4 @@
# CHANGELOGs
- [CHANGELOG-3.8.md](./CHANGELOG-3.8.md)
+18 -21
View File
@@ -12,13 +12,12 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
[![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20OpenIM%20Guru-006BFF?style=for-the-badge)](https://gurubase.io/g/openim) [![Gurubase](https://img.shields.io/badge/Gurubase-Ask%20OpenIM%20Guru-006BFF?style=for-the-badge)](https://gurubase.io/g/openim)
<p align="center"> <p align="center">
<a href="./README.md">English</a> · <a href="./README.md">English</a> ·
<a href="./README_zh_CN.md">中文</a> · <a href="./README_zh_CN.md">中文</a> ·
@@ -47,16 +46,15 @@
<a href="./docs/readme/README_tr.md">Türkçe</a> <a href="./docs/readme/README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
## :busts_in_silhouette: Join Our Community ## :busts_in_silhouette: Join Our Community
+ 💬 [Follow us on Twitter](https://twitter.com/founder_im63606) - 💬 [Follow us on Twitter](https://twitter.com/founder_im63606)
+ 🚀 [Join our Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) - 🚀 [Join our Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [Join our WeChat Group](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [Join our WeChat Group](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## Ⓜ️ About OpenIM ## Ⓜ️ About OpenIM
@@ -68,13 +66,14 @@ Unlike standalone chat applications such as Telegram, Signal, and Rocket.Chat, O
**OpenIMSDK**, designed for **OpenIMServer**, is an IM SDK created specifically for integration into client applications. It supports various functionalities and modules: **OpenIMSDK**, designed for **OpenIMServer**, is an IM SDK created specifically for integration into client applications. It supports various functionalities and modules:
+ 🌟 Main Features: - 🌟 Main Features:
- 📦 Local Storage - 📦 Local Storage
- 🔔 Listener Callbacks - 🔔 Listener Callbacks
- 🛡️ API Wrapping - 🛡️ API Wrapping
- 🌐 Connection Management - 🌐 Connection Management
+ 📚 Main Modules: - 📚 Main Modules:
1. 🚀 Initialization and Login 1. 🚀 Initialization and Login
2. 👤 User Management 2. 👤 User Management
3. 👫 Friends Management 3. 👫 Friends Management
@@ -87,16 +86,16 @@ Built with Golang and supports cross-platform deployment to ensure a consistent
## 🌐 Introduction to OpenIMServer ## 🌐 Introduction to OpenIMServer
+ **OpenIMServer** features include: - **OpenIMServer** features include:
- 🌐 Microservices Architecture: Supports cluster mode, including a gateway and multiple rpc services. - 🌐 Microservices Architecture: Supports cluster mode, including a gateway and multiple rpc services.
- 🚀 Diverse Deployment Options: Supports source code, Kubernetes, or Docker deployment. - 🚀 Diverse Deployment Options: Supports source code, Kubernetes, or Docker deployment.
- Massive User Support: Supports large-scale groups with hundreds of thousands, millions of users, and billions of messages. - Massive User Support: Supports large-scale groups with hundreds of thousands, millions of users, and billions of messages.
### Enhanced Business Functions: ### Enhanced Business Functions:
+ **REST API**: Provides a REST API for business systems to enhance functionality, such as group creation and message pushing through backend interfaces. - **REST API**: Provides a REST API for business systems to enhance functionality, such as group creation and message pushing through backend interfaces.
+ **Webhooks**: Expands business forms through callbacks, sending requests to business servers before or after certain events. - **Webhooks**: Expands business forms through callbacks, sending requests to business servers before or after certain events.
![Overall Architecture](./docs/images/architecture-layers.png) ![Overall Architecture](./docs/images/architecture-layers.png)
@@ -108,8 +107,8 @@ Experience online for iOS/Android/H5/PC/Web:
To facilitate user experience, we offer various deployment solutions. You can choose your preferred deployment method from the list below: To facilitate user experience, we offer various deployment solutions. You can choose your preferred deployment method from the list below:
+ **[Source Code Deployment Guide](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[Source Code Deployment Guide](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
## System Support ## System Support
@@ -117,24 +116,22 @@ Supports Linux, Windows, Mac systems, and ARM and AMD CPU architectures.
## :link: Links ## :link: Links
+ **[Developer Manual](https://docs.openim.io/)** - **[Developer Manual](https://docs.openim.io/)**
+ **[Changelog](https://github.com/openimsdk/open-im-server/blob/main/CHANGELOG.md)** - **[Changelog](https://github.com/openimsdk/open-im-server/blob/main/CHANGELOG.md)**
## :writing_hand: How to Contribute ## :writing_hand: How to Contribute
We welcome contributions of any kind! Please make sure to read our [Contributor Documentation](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md) before submitting a Pull Request. We welcome contributions of any kind! Please make sure to read our [Contributor Documentation](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md) before submitting a Pull Request.
+ **[Report a Bug](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=bug&template=bug_report.md&title=)** - **[Report a Bug](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=bug&template=bug_report.md&title=)**
+ **[Suggest a Feature](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)** - **[Suggest a Feature](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)**
+ **[Submit a Pull Request](https://github.com/openimsdk/open-im-server/pulls)** - **[Submit a Pull Request](https://github.com/openimsdk/open-im-server/pulls)**
Thank you for contributing to building a powerful instant messaging solution! Thank you for contributing to building a powerful instant messaging solution!
## :closed_book: License ## :closed_book: License
OpenIMSDK is available under the Apache License 2.0. See the [LICENSE file](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) for more information. This software is licensed under the Apache License 2.0
## 🔮 Thanks to our contributors! ## 🔮 Thanks to our contributors!
+26 -29
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="./README.md">English</a> · <a href="./README.md">English</a> ·
<a href="./README_zh_CN.md">中文</a> · <a href="./README_zh_CN.md">中文</a> ·
@@ -46,34 +45,34 @@
<a href="./docs/readme/README_tr.md">Türkçe</a> <a href="./docs/readme/README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
## :busts_in_silhouette: 加入我们的社区 ## :busts_in_silhouette: 加入我们的社区
+ 💬 [关注我们的 Twitter](https://twitter.com/founder_im63606) - 💬 [关注我们的 Twitter](https://twitter.com/founder_im63606)
+ 🚀 [加入我们的 Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2hljfom5u-9ZuzP3NfEKW~BJKbpLm0Hw) - 🚀 [加入我们的 Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2hljfom5u-9ZuzP3NfEKW~BJKbpLm0Hw)
+ :eyes: [加入我们的微信群](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [加入我们的微信群](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## Ⓜ️ 关于 OpenIM ## Ⓜ️ 关于 OpenIM
与Telegram、Signal、Rocket.Chat等独立聊天应用不同,OpenIM提供了专为开发者设计的开源即时通讯解决方案,而不是直接安装使用的独立聊天应用。OpenIMOpenIM SDKOpenIM Server两大部分组成,为开发者提供了一整套集成即时通讯功能的工具和服务,包括消息发送接收、用户管理和群组管理等。总体来说,OpenIM旨在为开发者提供必要的工具和框架,帮助他们在自己的应用中实现高效的即时通讯解决方案。 Telegram、Signal、Rocket.Chat 等独立聊天应用不同,OpenIM 提供了专为开发者设计的开源即时通讯解决方案,而不是直接安装使用的独立聊天应用。OpenIMOpenIM SDKOpenIM Server 两大部分组成,为开发者提供了一整套集成即时通讯功能的工具和服务,包括消息发送接收、用户管理和群组管理等。总体来说,OpenIM 旨在为开发者提供必要的工具和框架,帮助他们在自己的应用中实现高效的即时通讯解决方案。
![App-OpenIM 关系](./docs/images/oepnim-design.png) ![App-OpenIM 关系](./docs/images/oepnim-design.png)
## 🚀 OpenIMSDK 介绍 ## 🚀 OpenIMSDK 介绍
**OpenIMSDK** 是为 **OpenIMServer** 设计的IM SDK,专为集成到客户端应用而生。它支持多种功能和模块: **OpenIMSDK** 是为 **OpenIMServer** 设计的 IM SDK,专为集成到客户端应用而生。它支持多种功能和模块:
- 🌟 主要功能:
+ 🌟 主要功能:
- 📦 本地存储 - 📦 本地存储
- 🔔 监听器回调 - 🔔 监听器回调
- 🛡️ API封装 - 🛡️ API 封装
- 🌐 连接管理 - 🌐 连接管理
+ 📚 主要模块: - 📚 主要模块:
1. 🚀 初始化及登录 1. 🚀 初始化及登录
2. 👤 用户管理 2. 👤 用户管理
3. 👫 好友管理 3. 👫 好友管理
@@ -86,31 +85,29 @@
## 🌐 OpenIMServer 介绍 ## 🌐 OpenIMServer 介绍
+ **OpenIMServer** 的特点包括: - **OpenIMServer** 的特点包括:
- 🌐 微服务架构:支持集群模式,包括网关(gateway)和多个rpc服务。 - 🌐 微服务架构:支持集群模式,包括网关(gateway)和多个 rpc 服务。
- 🚀 多样的部署方式:支持源代码、KubernetesDocker部署。 - 🚀 多样的部署方式:支持源代码、KubernetesDocker 部署。
- 海量用户支持:支持十万级超大群组,千万级用户和百亿级消息。 - 海量用户支持:支持十万级超大群组,千万级用户和百亿级消息。
### 增强的业务功能: ### 增强的业务功能:
+ **REST API**:为业务系统提供REST API,增加群组创建、消息推送等后台接口功能。 - **REST API**:为业务系统提供 REST API,增加群组创建、消息推送等后台接口功能。
+ **Webhooks**:通过事件前后的回调,向业务服务器发送请求,扩展更多的业务形态。 - **Webhooks**:通过事件前后的回调,向业务服务器发送请求,扩展更多的业务形态。
![整体架构](./docs/images/architecture-layers.png) ![整体架构](./docs/images/architecture-layers.png)
## :rocket: 快速入门 ## :rocket: 快速入门
在线体验iOS/Android/H5/PC/Web 在线体验 iOS/Android/H5/PC/Web
👉 **[OpenIM在线演示](https://www.openim.io/en/commercial)** 👉 **[OpenIM 在线演示](https://www.openim.io/en/commercial)**
为了便于用户体验,我们提供了多种部署解决方案,您可以根据以下列表选择适合您的部署方式: 为了便于用户体验,我们提供了多种部署解决方案,您可以根据以下列表选择适合您的部署方式:
+ **[源代码部署指南](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[源代码部署指南](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Docker 部署指南](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Docker 部署指南](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
## 系统支持 ## 系统支持
@@ -118,22 +115,22 @@
## :link: 相关链接 ## :link: 相关链接
+ **[开发手册](https://docs.openim.io/)** - **[开发手册](https://docs.openim.io/)**
+ **[更新日志](https://github.com/openimsdk/open-im-server/blob/main/CHANGELOG.md)** - **[更新日志](https://github.com/openimsdk/open-im-server/blob/main/CHANGELOG.md)**
## :writing_hand: 如何贡献 ## :writing_hand: 如何贡献
我们欢迎任何形式的贡献!在提交 Pull Request 之前,请确保阅读我们的[贡献者文档](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md) 我们欢迎任何形式的贡献!在提交 Pull Request 之前,请确保阅读我们的[贡献者文档](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)
+ **[报告 Bug](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=bug&template=bug_report.md&title=)** - **[报告 Bug](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=bug&template=bug_report.md&title=)**
+ **[提出新特性](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)** - **[提出新特性](https://github.com/openimsdk/open-im-server/issues/new?assignees=&labels=enhancement&template=feature_request.md&title=)**
+ **[提交 Pull Request](https://github.com/openimsdk/open-im-server/pulls)** - **[提交 Pull Request](https://github.com/openimsdk/open-im-server/pulls)**
感谢您的贡献,一起来打造强大的即时通讯解决方案! 感谢您的贡献,一起来打造强大的即时通讯解决方案!
## :closed_book: 许可证 ## :closed_book: 开源许可证 License
OpenIMSDK 在 Apache License 2.0 许可下可用。查看[LICENSE 文件](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)了解更多信息。 This software is licensed under the Apache License 2.0
## 🔮 Thanks to our contributors! ## 🔮 Thanks to our contributors!
+406
View File
@@ -0,0 +1,406 @@
package main
import (
"bytes"
"context"
"flag"
"fmt"
"net"
"os"
"os/signal"
"path"
"path/filepath"
"reflect"
"runtime"
"strings"
"sync"
"syscall"
"time"
"github.com/mitchellh/mapstructure"
"github.com/openimsdk/open-im-server/v3/internal/api"
"github.com/openimsdk/open-im-server/v3/internal/msggateway"
"github.com/openimsdk/open-im-server/v3/internal/msgtransfer"
"github.com/openimsdk/open-im-server/v3/internal/push"
"github.com/openimsdk/open-im-server/v3/internal/rpc/auth"
"github.com/openimsdk/open-im-server/v3/internal/rpc/conversation"
"github.com/openimsdk/open-im-server/v3/internal/rpc/group"
"github.com/openimsdk/open-im-server/v3/internal/rpc/msg"
"github.com/openimsdk/open-im-server/v3/internal/rpc/relation"
"github.com/openimsdk/open-im-server/v3/internal/rpc/third"
"github.com/openimsdk/open-im-server/v3/internal/rpc/user"
"github.com/openimsdk/open-im-server/v3/internal/tools/cron"
"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/version"
"github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/discovery/standalone"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/system/program"
"github.com/openimsdk/tools/utils/datautil"
"github.com/spf13/viper"
"google.golang.org/grpc"
)
func init() {
config.SetStandalone()
prommetrics.RegistryAll()
}
func main() {
var configPath string
flag.StringVar(&configPath, "c", "", "config path")
flag.Parse()
if configPath == "" {
_, _ = fmt.Fprintln(os.Stderr, "config path is empty")
os.Exit(1)
return
}
cmd := newCmds(configPath)
putCmd(cmd, false, auth.Start)
putCmd(cmd, false, conversation.Start)
putCmd(cmd, false, relation.Start)
putCmd(cmd, false, group.Start)
putCmd(cmd, false, msg.Start)
putCmd(cmd, false, third.Start)
putCmd(cmd, false, user.Start)
putCmd(cmd, false, push.Start)
putCmd(cmd, true, msggateway.Start)
putCmd(cmd, true, msgtransfer.Start)
putCmd(cmd, true, api.Start)
putCmd(cmd, true, cron.Start)
ctx := context.Background()
if err := cmd.run(ctx); err != nil {
_, _ = fmt.Fprintf(os.Stderr, "server exit %s", err)
os.Exit(1)
return
}
}
func newCmds(confPath string) *cmds {
return &cmds{confPath: confPath}
}
type cmdName struct {
Name string
Func func(ctx context.Context) error
Block bool
}
type cmds struct {
confPath string
cmds []cmdName
config config.AllConfig
conf map[string]reflect.Value
}
func (x *cmds) getTypePath(typ reflect.Type) string {
return path.Join(typ.PkgPath(), typ.Name())
}
func (x *cmds) initDiscovery() {
x.config.Discovery.Enable = "standalone"
vof := reflect.ValueOf(&x.config.Discovery.RpcService).Elem()
tof := reflect.TypeOf(&x.config.Discovery.RpcService).Elem()
num := tof.NumField()
for i := 0; i < num; i++ {
field := tof.Field(i)
if !field.IsExported() {
continue
}
if field.Type.Kind() != reflect.String {
continue
}
vof.Field(i).SetString(field.Name)
}
}
func (x *cmds) initAllConfig() error {
x.conf = make(map[string]reflect.Value)
vof := reflect.ValueOf(&x.config).Elem()
num := vof.NumField()
for i := 0; i < num; i++ {
field := vof.Field(i)
for ptr := true; ptr; {
if field.Kind() == reflect.Ptr {
field = field.Elem()
} else {
ptr = false
}
}
x.conf[x.getTypePath(field.Type())] = field
val := field.Addr().Interface()
name := val.(interface{ GetConfigFileName() string }).GetConfigFileName()
confData, err := os.ReadFile(filepath.Join(x.confPath, name))
if err != nil {
if os.IsNotExist(err) {
continue
}
return err
}
v := viper.New()
v.SetConfigType("yaml")
if err := v.ReadConfig(bytes.NewReader(confData)); err != nil {
return err
}
opt := func(conf *mapstructure.DecoderConfig) {
conf.TagName = config.StructTagName
}
if err := v.Unmarshal(val, opt); err != nil {
return err
}
}
x.initDiscovery()
x.config.Redis.Disable = false
x.config.LocalCache = config.LocalCache{}
config.InitNotification(&x.config.Notification)
return nil
}
func (x *cmds) parseConf(conf any) error {
vof := reflect.ValueOf(conf)
for {
if vof.Kind() == reflect.Ptr {
vof = vof.Elem()
} else {
break
}
}
tof := vof.Type()
numField := vof.NumField()
for i := 0; i < numField; i++ {
typeField := tof.Field(i)
if !typeField.IsExported() {
continue
}
field := vof.Field(i)
pkt := x.getTypePath(field.Type())
val, ok := x.conf[pkt]
if !ok {
switch field.Interface().(type) {
case config.Index:
case config.Path:
field.SetString(x.confPath)
case config.AllConfig:
field.Set(reflect.ValueOf(x.config))
case *config.AllConfig:
field.Set(reflect.ValueOf(&x.config))
default:
return fmt.Errorf("config field %s %s not found", vof.Type().Name(), typeField.Name)
}
continue
}
field.Set(val)
}
return nil
}
func (x *cmds) add(name string, block bool, fn func(ctx context.Context) error) {
x.cmds = append(x.cmds, cmdName{Name: name, Block: block, Func: fn})
}
func (x *cmds) initLog() error {
conf := x.config.Log
if err := log.InitLoggerFromConfig(
"openim-server",
program.GetProcessName(),
"", "",
conf.RemainLogLevel,
conf.IsStdout,
conf.IsJson,
conf.StorageLocation,
conf.RemainRotationCount,
conf.RotationTime,
strings.TrimSpace(version.Version),
conf.IsSimplify,
); err != nil {
return err
}
return nil
}
func (x *cmds) run(ctx context.Context) error {
if len(x.cmds) == 0 {
return fmt.Errorf("no command to run")
}
if err := x.initAllConfig(); err != nil {
return err
}
if err := x.initLog(); err != nil {
return err
}
ctx, cancel := context.WithCancelCause(ctx)
go func() {
<-ctx.Done()
log.ZError(ctx, "context server exit cause", context.Cause(ctx))
}()
if prometheus := x.config.API.Prometheus; prometheus.Enable {
var (
port int
err error
)
if !prometheus.AutoSetPorts {
port, err = datautil.GetElemByIndex(prometheus.Ports, 0)
if err != nil {
return err
}
}
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err != nil {
return fmt.Errorf("prometheus listen %d error %w", port, err)
}
defer listener.Close()
log.ZDebug(ctx, "prometheus start", "addr", listener.Addr())
go func() {
err := prommetrics.Start(listener)
if err == nil {
err = fmt.Errorf("http done")
}
cancel(fmt.Errorf("prometheus %w", err))
}()
}
go func() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
select {
case <-ctx.Done():
return
case val := <-sigs:
log.ZDebug(ctx, "recv signal", "signal", val.String())
cancel(fmt.Errorf("signal %s", val.String()))
}
}()
for i := range x.cmds {
cmd := x.cmds[i]
if cmd.Block {
continue
}
if err := cmd.Func(ctx); err != nil {
cancel(fmt.Errorf("server %s exit %w", cmd.Name, err))
return err
}
go func() {
if cmd.Block {
cancel(fmt.Errorf("server %s exit", cmd.Name))
}
}()
}
var wait cmdManger
for i := range x.cmds {
cmd := x.cmds[i]
if !cmd.Block {
continue
}
wait.Start(cmd.Name)
go func() {
defer wait.Shutdown(cmd.Name)
if err := cmd.Func(ctx); err != nil {
cancel(fmt.Errorf("server %s exit %w", cmd.Name, err))
return
}
cancel(fmt.Errorf("server %s exit", cmd.Name))
}()
}
<-ctx.Done()
exitCause := context.Cause(ctx)
log.ZWarn(ctx, "notification of service closure", exitCause)
done := wait.Wait()
timeout := time.NewTimer(time.Second * 10)
defer timeout.Stop()
for {
select {
case <-timeout.C:
log.ZWarn(ctx, "server exit timeout", nil, "running", wait.Running())
return exitCause
case _, ok := <-done:
if ok {
log.ZWarn(ctx, "waiting for the service to exit", nil, "running", wait.Running())
} else {
log.ZInfo(ctx, "all server exit done")
return exitCause
}
}
}
}
func putCmd[C any](cmd *cmds, block bool, fn func(ctx context.Context, config *C, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error) {
name := path.Base(runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name())
if index := strings.Index(name, "."); index >= 0 {
name = name[:index]
}
cmd.add(name, block, func(ctx context.Context) error {
var conf C
if err := cmd.parseConf(&conf); err != nil {
return err
}
return fn(ctx, &conf, standalone.GetSvcDiscoveryRegistry(), standalone.GetServiceRegistrar())
})
}
type cmdManger struct {
lock sync.Mutex
done chan struct{}
count int
names map[string]struct{}
}
func (x *cmdManger) Start(name string) {
x.lock.Lock()
defer x.lock.Unlock()
if x.names == nil {
x.names = make(map[string]struct{})
}
if x.done == nil {
x.done = make(chan struct{}, 1)
}
if _, ok := x.names[name]; ok {
panic(fmt.Errorf("cmd %s already exists", name))
}
x.count++
x.names[name] = struct{}{}
}
func (x *cmdManger) Shutdown(name string) {
x.lock.Lock()
defer x.lock.Unlock()
if _, ok := x.names[name]; !ok {
panic(fmt.Errorf("cmd %s not exists", name))
}
delete(x.names, name)
x.count--
if x.count == 0 {
close(x.done)
} else {
select {
case x.done <- struct{}{}:
default:
}
}
}
func (x *cmdManger) Wait() <-chan struct{} {
x.lock.Lock()
defer x.lock.Unlock()
if x.count == 0 || x.done == nil {
tmp := make(chan struct{})
close(tmp)
return tmp
}
return x.done
}
func (x *cmdManger) Running() []string {
x.lock.Lock()
defer x.lock.Unlock()
names := make([]string, 0, len(x.names))
for name := range x.names {
names = append(names, name)
}
return names
}
+64 -40
View File
@@ -1,48 +1,66 @@
--- # OpenIM Configuration File Descriptions and Common Configuration Modifications
title: 'OpenIM Configuration Files and Common Configuration Item Modifications Guide'
## Configuration Files Explanation ## External Component Configurations
| Configuration File | Description | | Configuration File | Description |
| ------------------------------- | ------------------------------------------------------------ | | ------------------ |-------------------------------------------------------------|
| **kafka.yml** | Configurations for Kafka username, password, address, etc. | | **kafka.yml** | Configuration for Kafka username, password, address, etc. |
| **redis.yml** | Configurations for Redis password, address, etc. | | **redis.yml** | Configuration for Redis password, address, etc. |
| **minio.yml** | Configurations for MinIO username, password, address, and external IP/domain; failing to modify external IP or domain may cause image file sending failures | | **minio.yml** | Configuration for MinIO username, password, address, etc. |
| **zookeeper.yml** | Configurations for ZooKeeper user, password, address, etc. | | **mongodb.yml** | Configuration for MongoDB username, password, address, etc. |
| **mongodb.yml** | Configurations for MongoDB username, password, address, etc. | | **discovery.yml** | Service discovery and etcd credentials and address. |
| **log.yml** | Configurations for log level and storage directory. |
| **notification.yml** | Configurations for events like adding friends, creating groups, etc. |
| **share.yml** | Common configurations needed by various OpenIM services, such as secret. |
| **webhooks.yml** | Configurations for URLs in Webhook. |
| **local-cache.yml** | Local cache configurations. |
| **openim-rpc-third.yml** | Configurations for listening IP, port, and storage settings for images and videos in openim-rpc-third service. |
| **openim-rpc-user.yml** | Configurations for listening IP and port in openim-rpc-user service. |
| **openim-api.yml** | Configurations for listening IP, port, etc., in openim-api service. |
| **openim-crontask.yml** | Configurations for openim-crontask service. |
| **openim-msggateway.yml** | Configurations for listening IP, port, etc., in openim-msggateway service. |
| **openim-msgtransfer.yml** | Configurations for openim-msgtransfer service. |
| **openim-push.yml** | Configurations for listening IP, port, and offline push settings in openim-push service. |
| **openim-rpc-auth.yml** | Configurations for listening IP, port, and token expiration settings in openim-rpc-auth service. |
| **openim-rpc-conversation.yml** | Configurations for listening IP, port, etc., in openim-rpc-conversation service. |
| **openim-rpc-friend.yml** | Configurations for listening IP, port, etc., in openim-rpc-friend service. |
| **openim-rpc-group.yml** | Configurations for listening IP, port, etc., in openim-rpc-group service. |
| **openim-rpc-msg.yml** | Configurations for listening IP, port, and whether to verify friendship before sending messages in openim-rpc-msg service. |
## Common Configuration Item Modifications ## OpenIMServer Related Configurations
| Configuration File | Description |
| ------------------------------- | ---------------------------------------------- |
| **log.yml** | Configuration for logging levels and storage directory |
| **notification.yml** | Event notification settings (e.g., add friend, create group) |
| **share.yml** | Common settings for all services (e.g., secrets) |
| **webhooks.yml** | Webhook URLs and related settings |
| **local-cache.yml** | Local cache settings (generally do not modify) |
| **openim-rpc-third.yml** | openim-rpc-third listen IP, port, and object storage settings |
| **openim-rpc-user.yml** | openim-rpc-user listen IP and port settings |
| **openim-api.yml** | openim-api listen IP, port, and other settings |
| **openim-crontask.yml** | openim-crontask scheduled task settings |
| **openim-msggateway.yml** | openim-msggateway listen IP, port, and other settings |
| **openim-msgtransfer.yml** | Settings for openim-msgtransfer service |
| **openim-push.yml** | openim-push listen IP, port, and offline push settings |
| **openim-rpc-auth.yml** | openim-rpc-auth listen IP, port, token validity settings |
| **openim-rpc-conversation.yml** | openim-rpc-conversation listen IP and port settings |
| **openim-rpc-friend.yml** | openim-rpc-friend listen IP and port settings |
| **openim-rpc-group.yml** | openim-rpc-group listen IP and port settings |
| **openim-rpc-msg.yml** | openim-rpc-msg listen IP and port settings |
| Configuration Item Modification | Configuration File |
| ----------------------------------------------------- | ----------------------- |
| Using MinIO for image and video file object storage | `minio.yml` |
| Adjusting production environment logs | `log.yml` |
| Verifying friendship before sending messages | `openim-rpc-msg.yml` |
| Modifying secret | `share.yml` |
| Using OSS, COS, AWS, Kodo for image and video storage | `openim-rpc-third.yml` |
| Setting multiple login policy | `openim-msggateway.yml` |
| Setting up offline push | `openim-push.yml` |
## Starting Multiple Instances of an OpenIM Service ## Monitoring and Alerting Related Configurations
| Configuration File | Description |
| ------------------------------ | --------------- |
| **prometheus.yml** | Prometheus configuration |
| **instance-down-rules.yml** | Alert rules |
| **alertmanager.yml** | Alertmanager configuration |
| **email.tmpl** | Email alert template |
| **grefana-template/Demo.json** | Default Grafana dashboard |
To start multiple instances of an OpenIM service, simply increase the corresponding port numbers and modify the `start-config.yml` file in the project root directory. Restart the service to take effect. For example, the configuration to start 2 instances of `openim-rpc-user` is as follows: ## Common Configuration Modifications
| Configuration Item | Configuration File |
| -------------------------------------------------------- | ----------------------- |
| Configure MinIO as object storage (focus on the externalAddress field) | `minio.yml` |
| Adjust log level and number of log files | `log.yml` |
| Enable or disable friend verification when sending messages | `openim-rpc-msg.yml` |
| OpenIMServer secret | `share.yml` |
| Configure OSS, COS, AWS, or Kodo as object storage | `openim-rpc-third.yml` |
| Multi-end mutual kick strategy and max concurrent connections per gateway | `openim-msggateway.yml` |
| Offline message push configuration | `openim-push.yml` |
| Configure webhooks for callback notifications (e.g., before/after message send) | `webhooks.yml` |
| Whether new group members can view historical messages | `openim-rpc-group.yml` |
| Token expiration time settings | `openim-rpc-auth.yml` |
| Scheduled task settings (e.g., how long to retain messages) | `openim-crontask.yml` |
## Starting Multiple Instances of a Service and Maximum File Descriptors
To start multiple instances of an OpenIM service, simply add the corresponding port numbers and modify the `start-config.yml` file in the projects root directory,
then restart the service. For example, to start 2 instances of `openim-rpc-user`:
```yaml ```yaml
rpc: rpc:
@@ -55,9 +73,15 @@ prometheus:
ports: [ 20100, 20101 ] ports: [ 20100, 20101 ]
``` ```
Modify `start-config.yml`: Modify`start-config.yml`:
```yaml ```yaml
serviceBinaries: serviceBinaries:
openim-rpc-user: 2 openim-rpc-user: 2
``` ```
To set the maximum number of open file descriptors (typically one per online user):
```
maxFileDescriptors: 10000
```
+57 -36
View File
@@ -1,45 +1,63 @@
# OpenIM配置文件说明以及常用配置修改说明 # OpenIM配置文件说明以及常用配置修改说明
## 配置文件说明 ## 外部组件相关配置
| Configuration File | Description | | Configuration File | Description |
| ------------------------------- | ------------------------------------------------------------ | | ------------------ | ---------------------------------- |
| **kafka.yml** | Kafka用户名、密码、地址等配置 | | **kafka.yml** | Kafka用户名、密码、地址等配置 |
| **redis.yml** | Redis密码、地址等配置 | | **redis.yml** | Redis密码、地址等配置 |
| **minio.yml** | MinIO用户名、密码、地址及外网IP域名等配置;未修改外网IP或域名可能导致图片文件发送失败 | | **minio.yml** | MinIO用户名、密码、地址等配置 |
| **zookeeper.yml** | ZooKeeper用户、密码、地址等配置 | | **mongodb.yml** | MongoDB用户、密码、地址等配置 |
| **mongodb.yml** | MongoDB用户名、密码、地址等配置 | | **discovery.yml** | 服务发现以及etcd用户名、密码、地址 |
| **log.yml** | 日志级别及存储目录等配置 |
| **notification.yml** | 添加好友、创建群组等事件通知配置 | ## OpenIMServer相关配置
| **share.yml** | OpenIM各服务所需的公共配置,如secret等 | | Configuration File | Description |
| **webhooks.yml** | Webhook中URL等配置 | | ------------------------------- | ---------------------------------------------- |
| **local-cache.yml** | 本地缓存配置 | | **log.yml** | 日志级别及存储目录等配置 |
| **openim-rpc-third.yml** | openim-rpc-third服务的监听IP、端口及图片视频对象存储配置 | | **notification.yml** | 添加好友、创建群组等事件通知配置 |
| **openim-rpc-user.yml** | openim-rpc-user服务的监听IP、端口配置 | | **share.yml** | 各服务所需的公共配置,如secret等 |
| **openim-api.yml** | openim-api服务的监听IP、端口等配置项 | | **webhooks.yml** | Webhook中URL等配置 |
| **openim-crontask.yml** | openim-crontask服务配置 | | **local-cache.yml** | 本地缓存配置,一般不用修改 |
| **openim-msggateway.yml** | openim-msggateway服务的监听IP、端口等配置 | | **openim-rpc-third.yml** | openim-rpc-third监听IP、端口及对象存储配置 |
| **openim-msgtransfer.yml** | openim-msgtransfer服务配置 | | **openim-rpc-user.yml** | openim-rpc-user监听IP、端口配置 |
| **openim-push.yml** | openim-push服务的监听IP、端口及离线推送配置 | | **openim-api.yml** | openim-api监听IP、端口配置 |
| **openim-rpc-auth.yml** | openim-rpc-auth服务的监听IP、端口及token有效期等配置 | | **openim-crontask.yml** | openim-crontask定时任务配置 |
| **openim-rpc-conversation.yml** | openim-rpc-conversation服务的监听IP、端口等配置 | | **openim-msggateway.yml** | openim-msggateway监听IP、端口等配置 |
| **openim-rpc-friend.yml** | openim-rpc-friend服务的监听IP、端口等配置 | | **openim-msgtransfer.yml** | openim-msgtransfer服务配置 |
| **openim-rpc-group.yml** | openim-rpc-group服务的监听IP、端口等配置 | | **openim-push.yml** | openim-push监听IP、端口及离线推送配置 |
| **openim-rpc-msg.yml** | openim-rpc-msg服务的监听IP、端口及消息发送是否验证好友关系等配置 | | **openim-rpc-auth.yml** | openim-rpc-auth监听IP、端口及token有效期等配置 |
| **openim-rpc-conversation.yml** | openim-rpc-conversation监听IP、端口等配置 |
| **openim-rpc-friend.yml** | openim-rpc-friend监听IP、端口等配置 |
| **openim-rpc-group.yml** | openim-rpc-group监听IP、端口等配置 |
| **openim-rpc-msg.yml** | openim-rpc-msg服务的监听IP、端口等配置 |
## 监控告警相关配置
| Configuration File | Description |
| ------------------------------ | --------------- |
| **prometheus.yml** | prometheus配置 |
| **instance-down-rules.yml** | 告警规则 |
| **alertmanager.yml** | 告警管理配置 |
| **email.tmpl** | 邮件告警模版 |
| **grefana-template/Demo.json** | 默认的dashboard |
## 常用配置修改 ## 常用配置修改
| 修改配置项 | 配置文件 |
| -------------------------------------------------------- | ----------------------- |
| 使用minio作为对象存储时配置,重点关注externalAddress字段 | `minio.yml` |
| 日志级别及日志文件数量调整 | `log.yml` |
| 发送消息是否需要验证好友关系 | `openim-rpc-msg.yml` |
| OpenIMServer秘钥 | `share.yml` |
| 使用oss, cos, aws, kodo作为对象存储时配置 | `openim-rpc-third.yml` |
| 多端互踢策略,单个gateway同时最大连接数 | `openim-msggateway.yml` |
| 消息离线推送 | `openim-push.yml` |
| 配置webhook来通知回调服务器,如消息发送前后回调 | `webhooks.yml` |
| 新入群用户是否可以查看历史消息 | `openim-rpc-group.yml` |
| token 过期时间设置 | `openim-rpc-auth.yml` |
| 定时任务设置,例如消息保存多长时间 | `openim-crontask.yml` |
| 修改配置项 | 配置文件 | ## 启动某个服务的多个实例和最大文件句柄数
| ----------------------------------------------- | ----------------------- |
| 使用minio作为图片视频文件对象存储 | `minio.yml` |
| 生产环境日志调整 | `log.yml` |
| 发送消息是否验证好友关系 | `openim-rpc-msg.yml` |
| 修改secret | `share.yml` |
| 使用oss, cos, aws, kodo作为图片视频文件对象存储 | `openim-rpc-third.yml` |
| 设置多端互踢策略 | `openim-msggateway.yml` |
| 设置离线推送 | `openim-push.yml` |
## 启动某个OpenIM服务的多个实例
若要启动某个OpenIM的多个实例,只需增加对应的端口数,并修改项目根目录下的`start-config.yml`文件,重启服务即可生效。例如,启动2个`openim-rpc-user`实例的配置如下: 若要启动某个OpenIM的多个实例,只需增加对应的端口数,并修改项目根目录下的`start-config.yml`文件,重启服务即可生效。例如,启动2个`openim-rpc-user`实例的配置如下:
@@ -61,5 +79,8 @@ serviceBinaries:
openim-rpc-user: 2 openim-rpc-user: 2
``` ```
修改最大同时打开的文件句柄数,一般是每个在线用户占用一个
```
maxFileDescriptors: 10000
```
+5 -3
View File
@@ -1,9 +1,11 @@
enable: etcd enable: etcd
etcd: etcd:
rootDirectory: openim rootDirectory: openim
address: [ localhost:12379 ] address: [localhost:12379]
username: '' ## Attention: If you set auth in etcd
password: '' ## you must also update the username and password in Chat project.
username:
password:
kubernetes: kubernetes:
namespace: default namespace: default
+5 -5
View File
@@ -1,13 +1,13 @@
# Username for authentication ## Kafka authentication
username: '' username:
# Password for authentication password:
password: ''
# Producer acknowledgment settings # Producer acknowledgment settings
producerAck: producerAck:
# Compression type to use (e.g., none, gzip, snappy) # Compression type to use (e.g., none, gzip, snappy)
compressType: none compressType: none
# List of Kafka broker addresses # List of Kafka broker addresses
address: [ localhost:19094 ] address: [localhost:19094]
# Kafka topic for Redis integration # Kafka topic for Redis integration
toRedisTopic: toRedis toRedisTopic: toRedis
# Kafka topic for MongoDB integration # Kafka topic for MongoDB integration
+36 -1
View File
@@ -1,7 +1,7 @@
# URI for database connection, leave empty if using address and credential settings directly # URI for database connection, leave empty if using address and credential settings directly
uri: uri:
# List of MongoDB server addresses # List of MongoDB server addresses
address: [ localhost:37017 ] address: [localhost:37017]
# Name of the database # Name of the database
database: openim_v3 database: openim_v3
# Username for database authentication # Username for database authentication
@@ -14,3 +14,38 @@ authSource: openim_v3
maxPoolSize: 100 maxPoolSize: 100
# Maximum number of retry attempts for a failed database connection # Maximum number of retry attempts for a failed database connection
maxRetry: 10 maxRetry: 10
# MongoDB Mode, including "standalone", "replicaSet"
mongoMode: "standalone"
# The following configurations only take effect when mongoMode is set to "replicaSet"
replicaSet:
name: rs0
hosts: [127.0.0.1:37017, 127.0.0.1:37018, 127.0.0.1:37019]
# Read concern level: "local", "available", "majority", "linearizable", "snapshot"
readConcern: majority
# maximum staleness of data in seconds
maxStaleness: 90s
# The following configurations only take effect when mongoMode is set to "replicaSet"
readPreference:
# Read preference mode, can be "primary", "primaryPreferred", "secondary", "secondaryPreferred", "nearest"
mode: primary
maxStaleness: 90s
# TagSets is an array of maps with priority based on order, empty map must be placed last for fallback tagSets
tagSets:
- datacenter: "cn-east"
rack: "1"
storage: "ssd"
- datacenter: "cn-east"
storage: "ssd"
- datacenter: "cn-east"
- {} # Empty map, indicates any node
# The following configurations only take effect when mongoMode is set to "replicaSet"
writeConcern:
# Write node count or tag (int, "majority", or custom tag)
w: majority
# Whether to wait for journal confirmation
j: true
# Write timeout duration
wtimeout: 30s
+13 -13
View File
@@ -31,7 +31,7 @@ joinGroupApplication:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: false enable: true
title: joinGroupApplication title title: joinGroupApplication title
desc: joinGroupApplication desc desc: joinGroupApplication desc
ext: joinGroupApplication ext ext: joinGroupApplication ext
@@ -51,7 +51,7 @@ groupApplicationAccepted:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: false enable: true
title: groupApplicationAccepted title title: groupApplicationAccepted title
desc: groupApplicationAccepted desc desc: groupApplicationAccepted desc
ext: groupApplicationAccepted ext ext: groupApplicationAccepted ext
@@ -61,7 +61,7 @@ groupApplicationRejected:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: false enable: true
title: groupApplicationRejected title title: groupApplicationRejected title
desc: groupApplicationRejected desc desc: groupApplicationRejected desc
ext: groupApplicationRejected ext ext: groupApplicationRejected ext
@@ -198,7 +198,7 @@ friendApplicationAdded:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: false enable: true
title: Somebody applies to add you as a friend title: Somebody applies to add you as a friend
desc: Somebody applies to add you as a friend desc: Somebody applies to add you as a friend
ext: Somebody applies to add you as a friend ext: Somebody applies to add you as a friend
@@ -228,7 +228,7 @@ friendAdded:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: We have become friends title: We have become friends
desc: We have become friends desc: We have become friends
ext: We have become friends ext: We have become friends
@@ -238,7 +238,7 @@ friendDeleted:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: deleted a friend title: deleted a friend
desc: deleted a friend desc: deleted a friend
ext: deleted a friend ext: deleted a friend
@@ -248,7 +248,7 @@ friendRemarkSet:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: Your friend's profile has been changed title: Your friend's profile has been changed
desc: Your friend's profile has been changed desc: Your friend's profile has been changed
ext: Your friend's profile has been changed ext: Your friend's profile has been changed
@@ -258,7 +258,7 @@ blackAdded:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: blocked a user title: blocked a user
desc: blocked a user desc: blocked a user
ext: blocked a user ext: blocked a user
@@ -268,7 +268,7 @@ blackDeleted:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: Remove a blocked user title: Remove a blocked user
desc: Remove a blocked user desc: Remove a blocked user
ext: Remove a blocked user ext: Remove a blocked user
@@ -278,7 +278,7 @@ friendInfoUpdated:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: friend info updated title: friend info updated
desc: friend info updated desc: friend info updated
ext: friend info updated ext: friend info updated
@@ -289,7 +289,7 @@ userInfoUpdated:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: userInfo updated title: userInfo updated
desc: userInfo updated desc: userInfo updated
ext: userInfo updated ext: userInfo updated
@@ -310,7 +310,7 @@ conversationChanged:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: conversation changed title: conversation changed
desc: conversation changed desc: conversation changed
ext: conversation changed ext: conversation changed
@@ -320,7 +320,7 @@ conversationSetPrivate:
reliabilityLevel: 1 reliabilityLevel: 1
unreadCount: false unreadCount: false
offlinePush: offlinePush:
enable: true enable: false
title: burn after reading title: burn after reading
desc: burn after reading desc: burn after reading
ext: burn after reading ext: burn after reading
+9 -2
View File
@@ -1,7 +1,14 @@
address: [ localhost:16379 ] address: [localhost:16379]
username: username:
password: openIM123 password: openIM123
clusterMode: false # redis Mode, including "standalone","cluster","sentinel"
redisMode: "standalone"
db: 0 db: 0
maxRetry: 10 maxRetry: 10
poolSize: 100 poolSize: 100
# Sentinel configuration (only used when redisMode is "sentinel")
sentinelMode:
masterName: "redis-master"
sentinelsAddrs: ["127.0.0.1:26379", "127.0.0.1:26380", "127.0.0.1:26381"]
routeByLatency: true
routeRandomly: true
+12 -1
View File
@@ -1,9 +1,20 @@
secret: openIM123 secret: openIM123
imAdminUserID: [ imAdmin ] # imAdminUser: Configuration for instant messaging system administrators
imAdminUser:
# userIDs: List of administrator user IDs.
# Each entry here corresponds by index to the matching entry in the nicknames list below.
userIDs: [imAdmin]
# nicknames: List of administrator display names.
# Each entry here corresponds by index to the matching entry in the userIDs list above.
nicknames: [superAdmin]
# 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
multiLogin: multiLogin:
policy: 1 policy: 1
# max num of tokens in one end # max num of tokens in one end
maxNumOneEnd: 30 maxNumOneEnd: 30
rpcMaxBodySize:
requestMaxBodySize: 8388608
responseMaxBodySize: 8388608
+22 -14
View File
@@ -3,51 +3,43 @@ beforeSendSingleMsg:
enable: false enable: false
timeout: 5 timeout: 5
failedContinue: true failedContinue: true
# Only the contentType in allowedTypes will send the callback.
# Supports two formats: a single type or a range. The range is defined by the lower and upper bounds connected with a hyphen ("-").
# e.g. allowedTypes: [1, 100, 200-500, 600-700] means that only contentType within the range
# {1, 100} [200, 500] [600, 700] will be allowed through the filter.
# If not set, all contentType messages will through this filter.
allowedTypes: []
# Only the contentType not in deniedTypes will send the callback. # Only the contentType not in deniedTypes will send the callback.
# Supports two formats, same as allowedTypes.
# If not set, all contentType messages will through this filter. # If not set, all contentType messages will through this filter.
deniedTypes: [] deniedTypes: []
beforeUpdateUserInfoEx: beforeUpdateUserInfoEx:
enable: false enable: false
timeout: 5 timeout: 5
failedContinue: true failedContinue: true
afterUpdateUserInfoEx: afterUpdateUserInfoEx:
enable: false enable: false
timeout: 5 timeout: 5
afterSendSingleMsg: afterSendSingleMsg:
enable: false enable: false
timeout: 5 timeout: 5
# Only the senID/recvID specified in attentionIds will send the callback # Only the recvIDs specified in attentionIds will send the callback
# if not set, all user messages will be callback # if not set, all user messages will be callback
attentionIds: [] attentionIds: []
# See beforeSendSingleMsg comment. # See beforeSendSingleMsg comment.
allowedTypes: []
deniedTypes: [] deniedTypes: []
beforeSendGroupMsg: beforeSendGroupMsg:
enable: false enable: false
timeout: 5 timeout: 5
failedContinue: true failedContinue: true
# See beforeSendSingleMsg comment. # See beforeSendSingleMsg comment.
allowedTypes: []
deniedTypes: [] deniedTypes: []
beforeMsgModify: beforeMsgModify:
enable: false enable: false
timeout: 5 timeout: 5
failedContinue: true failedContinue: true
# See beforeSendSingleMsg comment. # See beforeSendSingleMsg comment.
allowedTypes: []
deniedTypes: [] deniedTypes: []
afterSendGroupMsg: afterSendGroupMsg:
enable: false enable: false
timeout: 5 timeout: 5
# Only the GroupIDs specified in attentionIds will send the callback
# if not set, all user messages will be callback
attentionIds: []
# See beforeSendSingleMsg comment. # See beforeSendSingleMsg comment.
allowedTypes: []
deniedTypes: [] deniedTypes: []
afterUserOnline: afterUserOnline:
enable: false enable: false
@@ -189,3 +181,19 @@ afterImportFriends:
afterRemoveBlack: afterRemoveBlack:
enable: false enable: false
timeout: 5 timeout: 5
beforeCreateSingleChatConversations:
enable: false
timeout: 5
failedContinue: false
afterCreateSingleChatConversations:
enable: false
timeout: 5
failedContinue: false
beforeCreateGroupChatConversations:
enable: false
timeout: 5
failedContinue: false
afterCreateGroupChatConversations:
enable: false
timeout: 5
failedContinue: false
+141 -14
View File
@@ -37,6 +37,7 @@ services:
- "${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"
- "${MONGO_BACKUP_DIR}:/data/backup"
environment: environment:
- TZ=Asia/Shanghai - TZ=Asia/Shanghai
- wiredTigerCacheSizeGB=1 - wiredTigerCacheSizeGB=1
@@ -62,7 +63,12 @@ services:
restart: always restart: always
sysctls: sysctls:
net.core.somaxconn: 1024 net.core.somaxconn: 1024
command: redis-server /usr/local/redis/config/redis.conf --requirepass openIM123 --appendonly yes command: >
redis-server
--requirepass openIM123
--appendonly yes
--aof-use-rdb-preamble yes
--save ""
networks: networks:
- openim - openim
@@ -82,8 +88,83 @@ services:
- ETCD_INITIAL_CLUSTER=s1=http://0.0.0.0:2380 - ETCD_INITIAL_CLUSTER=s1=http://0.0.0.0:2380
- ETCD_INITIAL_CLUSTER_TOKEN=tkn - ETCD_INITIAL_CLUSTER_TOKEN=tkn
- ETCD_INITIAL_CLUSTER_STATE=new - ETCD_INITIAL_CLUSTER_STATE=new
- ALLOW_NONE_AUTHENTICATION=no
## Optional: Enable etcd authentication by setting the following credentials
# - ETCD_ROOT_USER=root
# - ETCD_ROOT_PASSWORD=openIM123
# - ETCD_USERNAME=openIM
# - ETCD_PASSWORD=openIM123
volumes: volumes:
- "${DATA_DIR}/components/etcd:/etcd-data" - "${DATA_DIR}/components/etcd:/etcd-data"
command: >
/bin/sh -c '
etcd &
export ETCDCTL_API=3
echo "Waiting for etcd to become healthy..."
until etcdctl --endpoints=http://127.0.0.1:2379 endpoint health &>/dev/null; do
echo "Waiting for ETCD to start..."
sleep 1
done
echo "etcd is healthy."
if [ -n "$${ETCD_ROOT_USER}" ] && [ -n "$${ETCD_ROOT_PASSWORD}" ] && [ -n "$${ETCD_USERNAME}" ] && [ -n "$${ETCD_PASSWORD}" ]; then
echo "Authentication credentials provided. Setting up authentication..."
echo "Checking authentication status..."
if ! etcdctl --endpoints=http://127.0.0.1:2379 auth status | grep -q "Authentication Status: true"; then
echo "Authentication is disabled. Creating users and enabling..."
# Create users and setup permissions
etcdctl --endpoints=http://127.0.0.1:2379 user add $${ETCD_ROOT_USER} --new-user-password=$${ETCD_ROOT_PASSWORD} || true
etcdctl --endpoints=http://127.0.0.1:2379 user add $${ETCD_USERNAME} --new-user-password=$${ETCD_PASSWORD} || true
etcdctl --endpoints=http://127.0.0.1:2379 role add openim-role || true
etcdctl --endpoints=http://127.0.0.1:2379 role grant-permission openim-role --prefix=true readwrite / || true
etcdctl --endpoints=http://127.0.0.1:2379 role grant-permission openim-role --prefix=true readwrite "" || true
etcdctl --endpoints=http://127.0.0.1:2379 user grant-role $${ETCD_USERNAME} openim-role || true
etcdctl --endpoints=http://127.0.0.1:2379 user grant-role $${ETCD_ROOT_USER} $${ETCD_USERNAME} root || true
echo "Enabling authentication..."
etcdctl --endpoints=http://127.0.0.1:2379 auth enable
echo "Authentication enabled successfully"
else
echo "Authentication is already enabled. Checking OpenIM user..."
# Check if openIM user exists and can perform operations
if ! etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} put /test/auth "auth-check" &>/dev/null; then
echo "OpenIM user test failed. Recreating user with root credentials..."
# Try to create/update the openIM user using root credentials
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} user add $${ETCD_USERNAME} --new-user-password=$${ETCD_PASSWORD} --no-password-file || true
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} role add openim-role || true
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} role grant-permission openim-role --prefix=true readwrite / || true
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} role grant-permission openim-role --prefix=true readwrite "" || true
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_ROOT_USER}:$${ETCD_ROOT_PASSWORD} user grant-role $${ETCD_USERNAME} openim-role || true
etcdctl --endpoints=http://127.0.0.1:2379 user grant-role $${ETCD_ROOT_USER} $${ETCD_USERNAME} root || true
echo "OpenIM user recreated with required permissions"
else
echo "OpenIM user exists and has correct permissions"
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} del /test/auth &>/dev/null
fi
fi
echo "Testing authentication with OpenIM user..."
if etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} put /test/auth "auth-works"; then
echo "Authentication working properly"
etcdctl --endpoints=http://127.0.0.1:2379 --user=$${ETCD_USERNAME}:$${ETCD_PASSWORD} del /test/auth
else
echo "WARNING: Authentication test failed"
fi
else
echo "No authentication credentials provided. Running in no-auth mode."
echo "To enable authentication, set ETCD_ROOT_USER, ETCD_ROOT_PASSWORD, ETCD_USERNAME, and ETCD_PASSWORD environment variables."
fi
tail -f /dev/null
'
restart: always restart: always
networks: networks:
- openim - openim
@@ -100,15 +181,62 @@ services:
environment: environment:
#KAFKA_HEAP_OPTS: "-Xms128m -Xmx256m" #KAFKA_HEAP_OPTS: "-Xms128m -Xmx256m"
TZ: Asia/Shanghai TZ: Asia/Shanghai
# Unique identifier for the Kafka node (required in controller mode)
KAFKA_CFG_NODE_ID: 0 KAFKA_CFG_NODE_ID: 0
# Defines the roles this Kafka node plays: broker, controller, or both
KAFKA_CFG_PROCESS_ROLES: controller,broker KAFKA_CFG_PROCESS_ROLES: controller,broker
# Specifies which nodes are controller nodes for quorum voting.
# The syntax follows the KRaft mode (no ZooKeeper): node.id@host:port
# The controller listener endpoint here is kafka:9093
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093 KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 0@kafka:9093
KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094 # Specifies which listener is used for controller-to-controller communication
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092,EXTERNAL://localhost:19094
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,PLAINTEXT:PLAINTEXT
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
# Default number of partitions for new topics
KAFKA_NUM_PARTITIONS: 8 KAFKA_NUM_PARTITIONS: 8
# Whether to enable automatic topic creation
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true" KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true"
# Kafka internal listeners; Kafka supports multiple ports with different protocols
# Each port is used for a specific purpose: INTERNAL for internal broker communication,
# CONTROLLER for controller communication, EXTERNAL for external client connections.
# These logical listener names are mapped to actual protocols via KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP
# In short, Kafka is listening on three logical ports: 9092 for internal communication,
# 9093 for controller traffic, and 9094 for external access.
KAFKA_CFG_LISTENERS: "INTERNAL://:9092,CONTROLLER://:9093,EXTERNAL://:9094"
# Addresses advertised to clients. INTERNAL://kafka:9092 uses the internal Docker service name 'kafka',
# so other containers can access Kafka via kafka:9092.
# EXTERNAL://localhost:19094 is the address external clients (e.g., in the LAN) should use to connect.
# If Kafka is deployed on a different machine than IM, 'localhost' should be replaced with the LAN IP.
KAFKA_CFG_ADVERTISED_LISTENERS: "INTERNAL://kafka:9092,EXTERNAL://localhost:19094"
# Maps logical listener names to actual protocols.
# Supported protocols include: PLAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "CONTROLLER:PLAINTEXT,EXTERNAL:PLAINTEXT,INTERNAL:PLAINTEXT"
# Defines which listener is used for inter-broker communication within the Kafka cluster
KAFKA_CFG_INTER_BROKER_LISTENER_NAME: "INTERNAL"
# Authentication configuration variables - comment out to disable auth
# KAFKA_USERNAME: "openIM"
# KAFKA_PASSWORD: "openIM123"
command: >
/bin/sh -c '
if [ -n "$${KAFKA_USERNAME}" ] && [ -n "$${KAFKA_PASSWORD}" ]; then
echo "=== Kafka SASL Authentication ENABLED ==="
echo "Username: $${KAFKA_USERNAME}"
# Set environment variables for SASL authentication
export KAFKA_CFG_LISTENERS="SASL_PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://:9094"
export KAFKA_CFG_ADVERTISED_LISTENERS="SASL_PLAINTEXT://kafka:9092,EXTERNAL://localhost:19094"
export KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP="CONTROLLER:PLAINTEXT,EXTERNAL:SASL_PLAINTEXT,SASL_PLAINTEXT:SASL_PLAINTEXT"
export KAFKA_CFG_SASL_ENABLED_MECHANISMS="PLAIN"
export KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL="PLAIN"
export KAFKA_CFG_INTER_BROKER_LISTENER_NAME="SASL_PLAINTEXT"
export KAFKA_CLIENT_USERS="$${KAFKA_USERNAME}"
export KAFKA_CLIENT_PASSWORDS="$${KAFKA_PASSWORD}"
fi
# Start Kafka with the configured environment
exec /opt/bitnami/scripts/kafka/entrypoint.sh /opt/bitnami/scripts/kafka/run.sh
'
networks: networks:
- openim - openim
@@ -160,9 +288,9 @@ services:
- ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml - ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
- ${DATA_DIR}/components/prometheus/data:/prometheus - ${DATA_DIR}/components/prometheus/data:/prometheus
command: command:
- '--config.file=/etc/prometheus/prometheus.yml' - "--config.file=/etc/prometheus/prometheus.yml"
- '--storage.tsdb.path=/prometheus' - "--storage.tsdb.path=/prometheus"
- '--web.listen-address=:${PROMETHEUS_PORT}' - "--web.listen-address=:${PROMETHEUS_PORT}"
network_mode: host network_mode: host
alertmanager: alertmanager:
@@ -175,8 +303,8 @@ services:
- ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml - ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml
- ./config/email.tmpl:/etc/alertmanager/email.tmpl - ./config/email.tmpl:/etc/alertmanager/email.tmpl
command: command:
- '--config.file=/etc/alertmanager/alertmanager.yml' - "--config.file=/etc/alertmanager/alertmanager.yml"
- '--web.listen-address=:${ALERTMANAGER_PORT}' - "--web.listen-address=:${ALERTMANAGER_PORT}"
network_mode: host network_mode: host
grafana: grafana:
@@ -208,9 +336,8 @@ services:
- /sys:/host/sys:ro - /sys:/host/sys:ro
- /:/rootfs:ro - /:/rootfs:ro
command: command:
- '--path.procfs=/host/proc' - "--path.procfs=/host/proc"
- '--path.sysfs=/host/sys' - "--path.sysfs=/host/sys"
- '--path.rootfs=/rootfs' - "--path.rootfs=/rootfs"
- '--web.listen-address=:19100' - "--web.listen-address=:19100"
network_mode: host network_mode: host
+16 -20
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,7 +45,6 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
@@ -61,14 +59,14 @@ OpenIM je platforma služeb speciálně navržená pro integraci chatu, audio-vi
**OpenIMSDK** je IM SDK navržený pro**OpenIMServer**, vytvořený speciálně pro vkládání do klientských aplikací. Jeho hlavní vlastnosti a moduly jsou následující: **OpenIMSDK** je IM SDK navržený pro**OpenIMServer**, vytvořený speciálně pro vkládání do klientských aplikací. Jeho hlavní vlastnosti a moduly jsou následující:
+ 🌟 Hlavní vlastnosti: - 🌟 Hlavní vlastnosti:
- 📦 Místní úložiště - 📦 Místní úložiště
- 🔔 Zpětná volání posluchačů - 🔔 Zpětná volání posluchačů
- 🛡️ API obalování - 🛡️ API obalování
- 🌐 Správa připojení - 🌐 Správa připojení
+ 📚 hlavní moduly: - 📚 hlavní moduly:
1. 🚀 Inicializace a přihlášení 1. 🚀 Inicializace a přihlášení
2. 👤 Správa uživatelů 2. 👤 Správa uživatelů
@@ -82,15 +80,15 @@ Je postaven pomocí Golang a podporuje nasazení napříč platformami, což zaj
## 🌐 O OpenIMServeru ## 🌐 O OpenIMServeru
+ **OpenIMServer** má následující vlastnosti: - **OpenIMServer** má následující vlastnosti:
- 🌐 Architektura mikroslužeb: Podporuje režim clusteru, včetně brány a více služeb RPC. - 🌐 Architektura mikroslužeb: Podporuje režim clusteru, včetně brány a více služeb RPC.
- 🚀 Různé metody nasazení: Podporuje nasazení prostřednictvím zdrojového kódu, Kubernetes nebo Docker. - 🚀 Různé metody nasazení: Podporuje nasazení prostřednictvím zdrojového kódu, Kubernetes nebo Docker.
- Podpora masivní uživatelské základny: Super velké skupiny se stovkami tisíc uživatelů, desítkami milionů uživatelů a miliardami zpráv. - Podpora masivní uživatelské základny: Super velké skupiny se stovkami tisíc uživatelů, desítkami milionů uživatelů a miliardami zpráv.
### Vylepšené obchodní funkce: ### Vylepšené obchodní funkce:
+ **REST API**: OpenIMServer nabízí REST API pro podnikové systémy, jejichž cílem je poskytnout podnikům více funkcí, jako je vytváření skupin a odesílání push zpráv přes backendová rozhraní. - **REST API**: OpenIMServer nabízí REST API pro podnikové systémy, jejichž cílem je poskytnout podnikům více funkcí, jako je vytváření skupin a odesílání push zpráv přes backendová rozhraní.
+ **Webhooks**: OpenIMServer poskytuje možnosti zpětného volání pro rozšíření více obchodních formulářů. Zpětné volání znamená, že OpenIMServer odešle požadavek na obchodní server před nebo po určité události, jako jsou zpětná volání před nebo po odeslání zprávy. - **Webhooks**: OpenIMServer poskytuje možnosti zpětného volání pro rozšíření více obchodních formulářů. Zpětné volání znamená, že OpenIMServer odešle požadavek na obchodní server před nebo po určité události, jako jsou zpětná volání před nebo po odeslání zprávy.
👉 **[Další informace](https://docs.openim.io/guides/introduction/product)** 👉 **[Další informace](https://docs.openim.io/guides/introduction/product)**
@@ -100,7 +98,6 @@ Ponořte se do srdce funkčnosti Open-IM-Server s naším diagramem architektury
![Overall Architecture](../images/architecture-layers.png) ![Overall Architecture](../images/architecture-layers.png)
## :rocket: Rychlý start ## :rocket: Rychlý start
Podporujeme mnoho platforem. Zde jsou adresy pro rychlou práci na webové stránce: Podporujeme mnoho platforem. Zde jsou adresy pro rychlou práci na webové stránce:
@@ -109,10 +106,10 @@ Podporujeme mnoho platforem. Zde jsou adresy pro rychlou práci na webové strá
🤲 Pro usnadnění uživatelské zkušenosti nabízíme různá řešení nasazení. Způsob nasazení si můžete vybrat ze seznamu níže: 🤲 Pro usnadnění uživatelské zkušenosti nabízíme různá řešení nasazení. Způsob nasazení si můžete vybrat ze seznamu níže:
+ **[Průvodce nasazením zdrojového kódu](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[Průvodce nasazením zdrojového kódu](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Průvodce nasazením Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Průvodce nasazením Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Průvodce nasazením pro vývojáře Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Průvodce nasazením pro vývojáře Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: Chcete-li začít vyvíjet OpenIM ## :hammer_and_wrench: Chcete-li začít vyvíjet OpenIM
@@ -122,7 +119,7 @@ OpenIM Naším cílem je vybudovat špičkovou open source komunitu. Máme soubo
Pokud byste chtěli přispět do tohoto úložiště Open-IM-Server, přečtěte si naši [dokumentaci pro přispěvatele](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md). Pokud byste chtěli přispět do tohoto úložiště Open-IM-Server, přečtěte si naši [dokumentaci pro přispěvatele](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
Než začnete, ujistěte se, že jsou vaše změny vyžadovány. Nejlepší pro to je vytvořit [nová diskuze](https://github.com/openimsdk/open-im-server/discussions/new/choose) NEBO [Slack Communication](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), nebo pokud narazíte na problém, [nahlásit jej](https://github.com/openimsdk/open-im-server/issues/new/choose) jako první. Než začnete, ujistěte se, že jsou vaše změny vyžadovány. Nejlepší pro to je vytvořit [nová diskuze](https://github.com/openimsdk/open-im-server/discussions/new/choose) NEBO [Slack Communication](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A), nebo pokud narazíte na problém, [nahlásit jej](https://github.com/openimsdk/open-im-server/issues/new/choose) jako první.
- [OpenIM API Reference](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [OpenIM API Reference](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [Protokolování OpenIM Bash](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [Protokolování OpenIM Bash](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
@@ -154,19 +151,18 @@ Než začnete, ujistěte se, že jsou vaše změny vyžadovány. Nejlepší pro
- [Spravovat backend a monitorovat nasazení](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [Spravovat backend a monitorovat nasazení](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [Průvodce nasazením pro vývojáře Mac pro OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [Průvodce nasazením pro vývojáře Mac pro OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :busts_in_silhouette: Společenství ## :busts_in_silhouette: Společenství
+ 📚 [Komunita OpenIM](https://github.com/OpenIMSDK/community) - 📚 [Komunita OpenIM](https://github.com/OpenIMSDK/community)
+ 💕 [Zájmová skupina OpenIM](https://github.com/Openim-sigs) - 💕 [Zájmová skupina OpenIM](https://github.com/Openim-sigs)
+ 🚀 [Připojte se k naší komunitě Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [Připojte se k naší komunitě Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [Připojte se k našemu wechatu](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [Připojte se k našemu wechatu](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## :calendar: Komunitní setkání ## :calendar: Komunitní setkání
Chceme, aby se do naší komunity a přispívání kódu zapojil kdokoli, nabízíme dárky a odměny a vítáme vás, abyste se k nám připojili každý čtvrtek večer. Chceme, aby se do naší komunity a přispívání kódu zapojil kdokoli, nabízíme dárky a odměny a vítáme vás, abyste se k nám připojili každý čtvrtek večer.
Naše konference je v [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, pak můžete vyhledat kanál Open-IM-Server a připojit se Naše konference je v [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, pak můžete vyhledat kanál Open-IM-Server a připojit se
Zaznamenáváme si každou [dvoutýdenní schůzku](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)do [diskuzí na GitHubu](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), naše historické poznámky ze schůzek a také záznamy schůzek jsou k dispozici na [Dokumenty Google :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing). Zaznamenáváme si každou [dvoutýdenní schůzku](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)do [diskuzí na GitHubu](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), naše historické poznámky ze schůzek a také záznamy schůzek jsou k dispozici na [Dokumenty Google :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
+18 -25
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,35 +45,30 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
## :busts_in_silhouette: Fællesskab ## :busts_in_silhouette: Fællesskab
+ 📚 [OpenIM-fællesskab](https://github.com/OpenIMSDK/community) - 📚 [OpenIM-fællesskab](https://github.com/OpenIMSDK/community)
+ 💕 [OpenIM-interessegruppe](https://github.com/Openim-sigs) - 💕 [OpenIM-interessegruppe](https://github.com/Openim-sigs)
+ 🚀 [Deltag i vores Slack-fællesskab](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [Deltag i vores Slack-fællesskab](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [Deltag i vores WeChat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [Deltag i vores WeChat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
+ 👫 [Deltag i vores Reddit](https://www.reddit.com/r/OpenIMessaging) - 👫 [Deltag i vores Reddit](https://www.reddit.com/r/OpenIMessaging)
+ 💬 [Følg vores Twitter-konto](https://twitter.com/openimsdk) - 💬 [Følg vores Twitter-konto](https://twitter.com/openimsdk)
## Ⓜ️ Om OpenIM ## Ⓜ️ Om OpenIM
OpenIM er en serviceplatform designet specifikt til integration af chat, lyd-videoopkald, notifikationer og AI-chatbots i applikationer. Den tilbyder en række kraftfulde API'er og Webhooks, som gør det let for udviklere at integrere disse interaktive funktioner i deres applikationer. OpenIM er ikke en selvstændig chatapplikation, men fungerer snarere som en platform, der understøtter andre applikationer i at opnå omfattende kommunikationsfunktionaliteter. Følgende diagram illustrerer interaktionen mellem AppServer, AppClient, OpenIMServer og OpenIMSDK for at forklare detaljeret. OpenIM er en serviceplatform designet specifikt til integration af chat, lyd-videoopkald, notifikationer og AI-chatbots i applikationer. Den tilbyder en række kraftfulde API'er og Webhooks, som gør det let for udviklere at integrere disse interaktive funktioner i deres applikationer. OpenIM er ikke en selvstændig chatapplikation, men fungerer snarere som en platform, der understøtter andre applikationer i at opnå omfattende kommunikationsfunktionaliteter. Følgende diagram illustrerer interaktionen mellem AppServer, AppClient, OpenIMServer og OpenIMSDK for at forklare detaljeret.
![App-OpenIM Relationship](../images/oepnim-design.png) ![App-OpenIM Relationship](../images/oepnim-design.png)
## 🚀 Om OpenIMSDK ## 🚀 Om OpenIMSDK
**OpenIMSDK** er en IM SDK designet til **OpenIMServer**, skabt specifikt til indlejring i klientapplikationer. Dens vigtigste funktioner og moduler er som følger: **OpenIMSDK** er en IM SDK designet til **OpenIMServer**, skabt specifikt til indlejring i klientapplikationer. Dens vigtigste funktioner og moduler er som følger:
+ 🌟 Hovedfunktioner: - 🌟 Hovedfunktioner:
- 📦 Lokal lagring - 📦 Lokal lagring
- 🔔 Lytter-callbacks - 🔔 Lytter-callbacks
@@ -95,15 +89,15 @@ Det er bygget ved hjælp af Golang og understøtter tværplatformsudrulning, hvi
## 🌐 Om OpenIMServer ## 🌐 Om OpenIMServer
+ **OpenIMServer** har følgende karakteristika: - **OpenIMServer** har følgende karakteristika:
- 🌐 Mikroservicarkitektur: Understøtter klyngetilstand, inklusive en gateway og flere rpc-tjenester. - 🌐 Mikroservicarkitektur: Understøtter klyngetilstand, inklusive en gateway og flere rpc-tjenester.
- 🚀 Forskellige udrulningsmetoder: Understøtter udrulning via kildekode, Kubernetes eller Docker. - 🚀 Forskellige udrulningsmetoder: Understøtter udrulning via kildekode, Kubernetes eller Docker.
- Støtte til massiv brugerbase: Super store grupper med hundredtusinder af brugere, titusinder af brugere og milliarder af beskeder. - Støtte til massiv brugerbase: Super store grupper med hundredtusinder af brugere, titusinder af brugere og milliarder af beskeder.
### Forbedret forretningsfunktionalitet: ### Forbedret forretningsfunktionalitet:
+ **REST API**OpenIMServer tilbyder REST API'er til forretningssystemer, med det formål at give virksomheder flere funktioner, såsom at oprette grupper og sende push-beskeder gennem backend-grænseflader. - **REST API**OpenIMServer tilbyder REST API'er til forretningssystemer, med det formål at give virksomheder flere funktioner, såsom at oprette grupper og sende push-beskeder gennem backend-grænseflader.
+ **Webhooks**OpenIMServer giver mulighed for callback-funktionalitet for at udvide flere forretningsformer. Et callback betyder, at OpenIMServer sender en anmodning til forretningsserveren før eller efter en bestemt begivenhed, som callbacks før eller efter at have sendt en besked. - **Webhooks**OpenIMServer giver mulighed for callback-funktionalitet for at udvide flere forretningsformer. Et callback betyder, at OpenIMServer sender en anmodning til forretningsserveren før eller efter en bestemt begivenhed, som callbacks før eller efter at have sendt en besked.
👉 **[Lær mere](https://docs.openim.io/guides/introduction/product)** 👉 **[Lær mere](https://docs.openim.io/guides/introduction/product)**
@@ -121,10 +115,10 @@ Vi understøtter mange platforme. Her er adresserne for hurtig oplevelse på web
🤲 For at lette brugeroplevelsen tilbyder vi forskellige udrulningsløsninger. Du kan vælge din udrulningsmetode fra listen nedenfor: 🤲 For at lette brugeroplevelsen tilbyder vi forskellige udrulningsløsninger. Du kan vælge din udrulningsmetode fra listen nedenfor:
+ **[Vejledning til udrulning af kildekode](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[Vejledning til udrulning af kildekode](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Vejledning til Docker-udrulning](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Vejledning til Docker-udrulning](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Vejledning til Kubernetes-udrulning](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Vejledning til Kubernetes-udrulning](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Vejledning til Mac-udviklerudrulning](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Vejledning til Mac-udviklerudrulning](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: For at starte udviklingen af OpenIM ## :hammer_and_wrench: For at starte udviklingen af OpenIM
@@ -134,7 +128,7 @@ OpenIM Vores mål er at bygge et topniveau åben kildekode-fællesskab. Vi har e
Hvis du gerne vil bidrage til dette Open-IM-Server-repositorium, bedes du læse vores [dokumentation for bidragydere](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md). Hvis du gerne vil bidrage til dette Open-IM-Server-repositorium, bedes du læse vores [dokumentation for bidragydere](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
Før du starter, skal du sikre dig, at dine ændringer er efterspurgte. Det bedste for det er at oprette en [ny diskussion](https://github.com/openimsdk/open-im-server/discussions/new/choose) ELLER [Slack-kommunikation](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), eller hvis du finder et problem, [rapportere det](https://github.com/openimsdk/open-im-server/issues/new/choose) først. Før du starter, skal du sikre dig, at dine ændringer er efterspurgte. Det bedste for det er at oprette en [ny diskussion](https://github.com/openimsdk/open-im-server/discussions/new/choose) ELLER [Slack-kommunikation](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A), eller hvis du finder et problem, [rapportere det](https://github.com/openimsdk/open-im-server/issues/new/choose) først.
- [OpenIM API-referencer](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [OpenIM API-referencer](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [OpenIM Bash-logging](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [OpenIM Bash-logging](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
@@ -166,12 +160,11 @@ Før du starter, skal du sikre dig, at dine ændringer er efterspurgte. Det beds
- [Administrer backend og overvåg udrulning](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [Administrer backend og overvåg udrulning](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [Mac-udviklerudrulningsguide for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [Mac-udviklerudrulningsguide for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :calendar: Fællesskabsmøder ## :calendar: Fællesskabsmøder
Vi ønsker, at alle involverer sig i vores fællesskab og bidrager med kode, vi tilbyder gaver og belønninger, og vi byder dig velkommen til at deltage hver torsdag aften. Vi ønsker, at alle involverer sig i vores fællesskab og bidrager med kode, vi tilbyder gaver og belønninger, og vi byder dig velkommen til at deltage hver torsdag aften.
Vores konference er på [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, derefter kan du søge Open-IM-Server pipeline for at deltage. Vores konference er på [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, derefter kan du søge Open-IM-Server pipeline for at deltage.
Vi tager [notater](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) af hvert fjortendages møde i [GitHub-diskussioner](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Vores historiske mødenotater samt genudsendelser af møderne er tilgængelige på [Google Docs](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) 📑. Vi tager [notater](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) af hvert fjortendages møde i [GitHub-diskussioner](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Vores historiske mødenotater samt genudsendelser af møderne er tilgængelige på [Google Docs](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) 📑.
+18 -21
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,7 +45,6 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
@@ -61,14 +59,14 @@
Το **OpenIMSDK** είναι ένα SDK για αμεση ανταλλαγή μηνυμάτων σχεδιασμένο για το **OpenIMServer**, δημιουργήθηκε ειδικά για ενσωμάτωση σε εφαρμογές πελατών. Οι κύριες δυνατότητες και μονάδες του είναι οι εξής: Το **OpenIMSDK** είναι ένα SDK για αμεση ανταλλαγή μηνυμάτων σχεδιασμένο για το **OpenIMServer**, δημιουργήθηκε ειδικά για ενσωμάτωση σε εφαρμογές πελατών. Οι κύριες δυνατότητες και μονάδες του είναι οι εξής:
+ 🌟 Κύριες Δυνατότητες: - 🌟 Κύριες Δυνατότητες:
- 📦 Τοπική αποθήκευση - 📦 Τοπική αποθήκευση
- 🔔 Callbacks ακροατών - 🔔 Callbacks ακροατών
- 🛡️ Περιτύλιγμα API - 🛡️ Περιτύλιγμα API
- 🌐 Διαχείριση σύνδεσης - 🌐 Διαχείριση σύνδεσης
+ 📚 Κύριες Μονάδες: - 📚 Κύριες Μονάδες:
1. 🚀 Αρχικοποίηση και Σύνδεση 1. 🚀 Αρχικοποίηση και Σύνδεση
2. 👤 Διαχείριση Χρηστών 2. 👤 Διαχείριση Χρηστών
@@ -82,15 +80,15 @@
## 🌐 Σχετικά με το OpenIMServer ## 🌐 Σχετικά με το OpenIMServer
+ Το **OpenIMServer** έχει τις ακόλουθες χαρακτηριστικές: - Το **OpenIMServer** έχει τις ακόλουθες χαρακτηριστικές:
- 🌐 Αρχιτεκτονική μικροϋπηρεσιών: Υποστηρίζει λειτουργία σε σύμπλεγμα, περιλαμβάνοντας έναν πύλη και πολλαπλές υπηρεσίες rpc. - 🌐 Αρχιτεκτονική μικροϋπηρεσιών: Υποστηρίζει λειτουργία σε σύμπλεγμα, περιλαμβάνοντας έναν πύλη και πολλαπλές υπηρεσίες rpc.
- 🚀 Διάφοροι τρόποι ανάπτυξης: Υποστηρίζει ανάπτυξη μέσω πηγαίου κώδικα, Kubernetes, ή Docker. - 🚀 Διάφοροι τρόποι ανάπτυξης: Υποστηρίζει ανάπτυξη μέσω πηγαίου κώδικα, Kubernetes, ή Docker.
- Υποστήριξη για τεράστια βάση χρηστών: Πολύ μεγάλες ομάδες με εκατοντάδες χιλιάδες χρήστες, δεκάδες εκατομμύρια χρήστες και δισεκατομμύρια μηνύματα. - Υποστήριξη για τεράστια βάση χρηστών: Πολύ μεγάλες ομάδες με εκατοντάδες χιλιάδες χρήστες, δεκάδες εκατομμύρια χρήστες και δισεκατομμύρια μηνύματα.
### Ενισχυμένη Επιχειρηματική Λειτουργικότητα: ### Ενισχυμένη Επιχειρηματική Λειτουργικότητα:
+ **REST API**: Το OpenIMServer προσφέρει REST APIs για επιχειρηματικά συστήματα, με στόχο την ενδυνάμωση των επιχειρήσεων με περισσότερες λειτουργικότητες, όπως η δημιουργία ομάδων και η αποστολή μηνυμάτων push μέσω backend διεπαφών. - **REST API**: Το OpenIMServer προσφέρει REST APIs για επιχειρηματικά συστήματα, με στόχο την ενδυνάμωση των επιχειρήσεων με περισσότερες λειτουργικότητες, όπως η δημιουργία ομάδων και η αποστολή μηνυμάτων push μέσω backend διεπαφών.
+ **Webhooks**: Το OpenIMServer παρέχει δυνατότητες επανάκλησης για την επέκταση περισσότερων επιχειρηματικών μορφών. Μια επανάκληση σημαίνει ότι το OpenIMServer στέλνει ένα αίτημα στον επιχειρηματικό διακομιστή πριν ή μετά από ένα συγκεκριμένο γεγονός, όπως επανακλήσεις πριν ή μετά την αποστολή ενός μηνύματος. - **Webhooks**: Το OpenIMServer παρέχει δυνατότητες επανάκλησης για την επέκταση περισσότερων επιχειρηματικών μορφών. Μια επανάκληση σημαίνει ότι το OpenIMServer στέλνει ένα αίτημα στον επιχειρηματικό διακομιστή πριν ή μετά από ένα συγκεκριμένο γεγονός, όπως επανακλήσεις πριν ή μετά την αποστολή ενός μηνύματος.
👉 **[Μάθετε περισσότερα](https://docs.openim.io/guides/introduction/product)** 👉 **[Μάθετε περισσότερα](https://docs.openim.io/guides/introduction/product)**
@@ -100,7 +98,6 @@
![Overall Architecture](../../docs/images/architecture-layers.png) ![Overall Architecture](../../docs/images/architecture-layers.png)
## :rocket: Γρήγορη Εκκίνηση ## :rocket: Γρήγορη Εκκίνηση
Υποστηρίζουμε πολλές πλατφόρμες. Εδώ είναι οι διευθύνσεις για γρήγορη εμπειρία στην πλευρά του διαδικτύου: Υποστηρίζουμε πολλές πλατφόρμες. Εδώ είναι οι διευθύνσεις για γρήγορη εμπειρία στην πλευρά του διαδικτύου:
@@ -109,10 +106,10 @@
🤲 Για να διευκολύνουμε την εμπειρία του χρήστη, προσφέρουμε διάφορες λύσεις ανάπτυξης. Μπορείτε να επιλέξετε τη μέθοδο ανάπτυξης σας από την παρακάτω λίστα: 🤲 Για να διευκολύνουμε την εμπειρία του χρήστη, προσφέρουμε διάφορες λύσεις ανάπτυξης. Μπορείτε να επιλέξετε τη μέθοδο ανάπτυξης σας από την παρακάτω λίστα:
+ **[Οδηγός Ανάπτυξης Κώδικα Πηγής](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[Οδηγός Ανάπτυξης Κώδικα Πηγής](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[δηγός Ανάπτυξης μέσω Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[δηγός Ανάπτυξης μέσω Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Οδηγός Ανάπτυξης Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Οδηγός Ανάπτυξης Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Οδηγός Ανάπτυξης για Αναπτυξιακούς στο Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Οδηγός Ανάπτυξης για Αναπτυξιακούς στο Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: Για να Αρχίσετε την Ανάπτυξη του OpenIM ## :hammer_and_wrench: Για να Αρχίσετε την Ανάπτυξη του OpenIM
@@ -122,7 +119,7 @@ OpenIM Στόχος μας είναι να δημιουργήσουμε μια
Εάν θέλετε να συνεισφέρετε σε αυτό το αποθετήριο Open-IM-Server, παρακαλούμε διαβάστε την [τεκμηρίωση συνεισφέροντος](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md). Εάν θέλετε να συνεισφέρετε σε αυτό το αποθετήριο Open-IM-Server, παρακαλούμε διαβάστε την [τεκμηρίωση συνεισφέροντος](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
Πριν ξεκινήσετε, παρακαλούμε βεβαιωθείτε ότι οι αλλαγές σας είναι ζητούμενες. Το καλύτερο για αυτό είναι να δημιουργήσετε ένα [νέα συζήτηση](https://github.com/openimsdk/open-im-server/discussions/new/choose) ή [Επικοινωνία Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), ή αν βρείτε ένα ζήτημα, [αναφέρετέ το](https://github.com/openimsdk/open-im-server/issues/new/choose) πρώτα. Πριν ξεκινήσετε, παρακαλούμε βεβαιωθείτε ότι οι αλλαγές σας είναι ζητούμενες. Το καλύτερο για αυτό είναι να δημιουργήσετε ένα [νέα συζήτηση](https://github.com/openimsdk/open-im-server/discussions/new/choose) ή [Επικοινωνία Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A), ή αν βρείτε ένα ζήτημα, [αναφέρετέ το](https://github.com/openimsdk/open-im-server/issues/new/choose) πρώτα.
- [Αναφορά API του OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [Αναφορά API του OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [Καταγραφή Bash του OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [Καταγραφή Bash του OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
@@ -154,28 +151,28 @@ OpenIM Στόχος μας είναι να δημιουργήσουμε μια
- [Διαχείριση backend και παρακολούθηση ανάπτυξης](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [Διαχείριση backend και παρακολούθηση ανάπτυξης](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [Οδηγός Ανάπτυξης για Προγραμματιστές Mac του OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [Οδηγός Ανάπτυξης για Προγραμματιστές Mac του OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :busts_in_silhouette: Κοινότητα ## :busts_in_silhouette: Κοινότητα
+ 📚 [Κοινότητα OpenIM](https://github.com/OpenIMSDK/community) - 📚 [Κοινότητα OpenIM](https://github.com/OpenIMSDK/community)
+ 💕 [Ομάδα Ενδιαφέροντος OpenIM](https://github.com/Openim-sigs) - 💕 [Ομάδα Ενδιαφέροντος OpenIM](https://github.com/Openim-sigs)
+ 🚀 [Εγγραφείτε στην κοινότητα Slack μας](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [Εγγραφείτε στην κοινότητα Slack μας](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [γγραφείτε στην ομάδα μας wechat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [γγραφείτε στην ομάδα μας wechat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## :calendar: Συναντήσεις της κοινότητας ## :calendar: Συναντήσεις της κοινότητας
Θέλουμε οποιονδήποτε να εμπλακεί στην κοινότητά μας και να συνεισφέρει κώδικα. Προσφέρουμε δώρα και ανταμοιβές και σας καλωσορίζουμε να μας ενταχθείτε κάθε Πέμπτη βράδυ. Θέλουμε οποιονδήποτε να εμπλακεί στην κοινότητά μας και να συνεισφέρει κώδικα. Προσφέρουμε δώρα και ανταμοιβές και σας καλωσορίζουμε να μας ενταχθείτε κάθε Πέμπτη βράδυ.
Η διάσκεψή μας είναι στο [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, στη συνέχεια μπορείτε να αναζητήσετε τη διαδικασία Open-IM-Server για να συμμετάσχετε Η διάσκεψή μας είναι στο [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, στη συνέχεια μπορείτε να αναζητήσετε τη διαδικασία Open-IM-Server για να συμμετάσχετε
Κάνουμε σημειώσεις για κάθε μια [Σημειώνουμε κάθε διμηνιαία συνάντηση](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) στις [συζητήσεις του GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Οι ιστορικές μας σημειώσεις συναντήσεων, καθώς και οι επαναλήψεις των συναντήσεων είναι διαθέσιμες στο[Έγγραφα της Google :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing). Κάνουμε σημειώσεις για κάθε μια [Σημειώνουμε κάθε διμηνιαία συνάντηση](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) στις [συζητήσεις του GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Οι ιστορικές μας σημειώσεις συναντήσεων, καθώς και οι επαναλήψεις των συναντήσεων είναι διαθέσιμες στο[Έγγραφα της Google :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
## :eyes: Ποιοί Χρησιμοποιούν το OpenIM ## :eyes: Ποιοί Χρησιμοποιούν το OpenIM
Ελέγξτε τη σελίδα με τις [μελέτες περίπτωσης χρήσης ](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md) μας για μια λίστα των χρηστών του έργου. Μην διστάσετε να αφήσετε ένα[📝σχόλιο](https://github.com/openimsdk/open-im-server/issues/379) και να μοιραστείτε την περίπτωση χρήσης σας. Ελέγξτε τη σελίδα με τις [μελέτες περίπτωσης χρήσης ](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md) μας για μια λίστα των χρηστών του έργου. Μην διστάσετε να αφήσετε ένα[📝σχόλιο](https://github.com/openimsdk/open-im-server/issues/379) και να μοιραστείτε την περίπτωση χρήσης σας.
## :page_facing_up: Άδεια Χρήσης ## :page_facing_up: Άδεια Χρήσης
Το OpenIM διατίθεται υπό την άδεια Apache 2.0. Δείτε τη [ΑΔΕΙΑ ΧΡΗΣΗΣ](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) για το πλήρες κείμενο της άδειας. Το OpenIM διατίθεται υπό την άδεια Apache 2.0. Δείτε τη [ΑΔΕΙΑ ΧΡΗΣΗΣ](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) για το πλήρες κείμενο της άδειας.
Το λογότυπο του OpenIM, συμπεριλαμβανομένων των παραλλαγών και των κινούμενων εικόνων, που εμφανίζονται σε αυτό το αποθετήριο[OpenIM](https://github.com/openimsdk/open-im-server) υπό τις διευθύνσεις [assets/logo](../../assets/logo) και [assets/logo-gif](../../assets/logo-gif) προστατεύονται από τους νόμους περί πνευματικής ιδιοκτησίας. Το λογότυπο του OpenIM, συμπεριλαμβανομένων των παραλλαγών και των κινούμενων εικόνων, που εμφανίζονται σε αυτό το αποθετήριο[OpenIM](https://github.com/openimsdk/open-im-server) υπό τις διευθύνσεις [assets/logo](../../assets/logo) και [assets/logo-gif](../../assets/logo-gif) προστατεύονται από τους νόμους περί πνευματικής ιδιοκτησίας.
+19 -27
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,7 +45,6 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
@@ -61,14 +59,14 @@ OpenIM es una plataforma de servicio diseñada específicamente para integrar ch
**OpenIMSDK** es un SDK de mensajería instantánea diseñado para **OpenIMServer**, creado específicamente para su incorporación en aplicaciones cliente. Sus principales características y módulos son los siguientes: **OpenIMSDK** es un SDK de mensajería instantánea diseñado para **OpenIMServer**, creado específicamente para su incorporación en aplicaciones cliente. Sus principales características y módulos son los siguientes:
+ 🌟 Características Principales: - 🌟 Características Principales:
- 📦 Almacenamiento local - 📦 Almacenamiento local
- 🔔 Callbacks de escuchas - 🔔 Callbacks de escuchas
- 🛡️ Envoltura de API - 🛡️ Envoltura de API
- 🌐 Gestión de conexiones - 🌐 Gestión de conexiones
+ 📚 Módulos Principales: - 📚 Módulos Principales:
1. 🚀 Inicialización y acceso 1. 🚀 Inicialización y acceso
2. 👤 Gestión de usuarios 2. 👤 Gestión de usuarios
@@ -82,17 +80,15 @@ Está construido con Golang y soporta despliegue multiplataforma, asegurando una
## 🌐 Acerca de OpenIMServer ## 🌐 Acerca de OpenIMServer
+ **OpenIMServer** tiene las siguientes características: - **OpenIMServer** tiene las siguientes características:
- 🌐 Arquitectura de microservicios: Soporta modo cluster, incluyendo un gateway y múltiples servicios rpc. - 🌐 Arquitectura de microservicios: Soporta modo cluster, incluyendo un gateway y múltiples servicios rpc.
- 🚀 Métodos de despliegue diversos: Soporta el despliegue a través de código fuente, Kubernetes o Docker. - 🚀 Métodos de despliegue diversos: Soporta el despliegue a través de código fuente, Kubernetes o Docker.
- Soporte para una base de usuarios masiva: Grupos super grandes con cientos de miles de usuarios, decenas de millones de usuarios y miles de millones de mensajes. - Soporte para una base de usuarios masiva: Grupos super grandes con cientos de miles de usuarios, decenas de millones de usuarios y miles de millones de mensajes.
### Funcionalidad Empresarial Mejorada: ### Funcionalidad Empresarial Mejorada:
+ **API REST**: OpenIMServer ofrece APIs REST para sistemas empresariales, destinadas a empoderar a las empresas con más funcionalidades, como la creación de grupos y el envío de mensajes push a través de interfaces de backend. - **API REST**: OpenIMServer ofrece APIs REST para sistemas empresariales, destinadas a empoderar a las empresas con más funcionalidades, como la creación de grupos y el envío de mensajes push a través de interfaces de backend.
+ **Webhooks**: OpenIMServer proporciona capacidades de callback para extender más formas de negocio. Un callback significa que OpenIMServer envía una solicitud al servidor empresarial antes o después de un cierto evento, como callbacks antes o después de enviar un mensaje. - **Webhooks**: OpenIMServer proporciona capacidades de callback para extender más formas de negocio. Un callback significa que OpenIMServer envía una solicitud al servidor empresarial antes o después de un cierto evento, como callbacks antes o después de enviar un mensaje.
👉 **[Aprende más](https://docs.openim.io/guides/introduction/product)** 👉 **[Aprende más](https://docs.openim.io/guides/introduction/product)**
@@ -102,10 +98,8 @@ Adéntrate en el corazón de la funcionalidad de Open-IM-Server con nuestro diag
![Arquitectura General](../../docs/images/architecture-layers.png) ![Arquitectura General](../../docs/images/architecture-layers.png)
## :rocket: Inicio Rápido ## :rocket: Inicio Rápido
:rocket: Inicio Rápido :rocket: Inicio Rápido
Apoyamos muchas plataformas. Aquí están las direcciones para una experiencia rápida en el lado web: Apoyamos muchas plataformas. Aquí están las direcciones para una experiencia rápida en el lado web:
@@ -113,10 +107,10 @@ Apoyamos muchas plataformas. Aquí están las direcciones para una experiencia r
🤲 Para facilitar la experiencia del usuario, ofrecemos varias soluciones de despliegue. Puedes elegir tu método de despliegue de la lista a continuación: 🤲 Para facilitar la experiencia del usuario, ofrecemos varias soluciones de despliegue. Puedes elegir tu método de despliegue de la lista a continuación:
+ **[Guía de Despliegue de Código Fuente](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[Guía de Despliegue de Código Fuente](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Guía de Despliegue con Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Guía de Despliegue con Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Guía de Despliegue con Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Guía de Despliegue con Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Guía de Despliegue para Desarrolladores en Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Guía de Despliegue para Desarrolladores en Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: Para Comenzar a Desarrollar en OpenIM ## :hammer_and_wrench: Para Comenzar a Desarrollar en OpenIM
@@ -127,8 +121,7 @@ en el [repositorio de la Comunidad.](https://github.com/OpenIMSDK/community).
Si te gustaría contribuir a este repositorio de Open-IM-Server, por favor lee nuestra [documentación para colaboradores](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md). Si te gustaría contribuir a este repositorio de Open-IM-Server, por favor lee nuestra [documentación para colaboradores](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
Antes de comenzar, asegúrate de que tus cambios sean demandados. Lo mejor para eso es crear una [nueva discusión](https://github.com/openimsdk/open-im-server/discussions/new/choose) O [Comunicación en Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A), o si encuentras un problema, [repórtalo](https://github.com/openimsdk/open-im-server/issues/new/choose) primero.
Antes de comenzar, asegúrate de que tus cambios sean demandados. Lo mejor para eso es crear una [nueva discusión](https://github.com/openimsdk/open-im-server/discussions/new/choose) O [Comunicación en Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), o si encuentras un problema, [repórtalo](https://github.com/openimsdk/open-im-server/issues/new/choose) primero.
- [Referencia de API de OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [Referencia de API de OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [Registro de Bash de OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [Registro de Bash de OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
@@ -160,32 +153,31 @@ Antes de comenzar, asegúrate de que tus cambios sean demandados. Lo mejor para
- [Gestión de backend y despliegue de monitoreo](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [Gestión de backend y despliegue de monitoreo](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [Guía de Despliegue para Desarrolladores Mac de OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [Guía de Despliegue para Desarrolladores Mac de OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :busts_in_silhouette: Comunidad ## :busts_in_silhouette: Comunidad
+ 📚 [Comunidad de OpenIM](https://github.com/OpenIMSDK/community) - 📚 [Comunidad de OpenIM](https://github.com/OpenIMSDK/community)
+ 💕 [Grupo de Interés de OpenIM](https://github.com/Openim-sigs) - 💕 [Grupo de Interés de OpenIM](https://github.com/Openim-sigs)
+ 🚀 [Únete a nuestra comunidad de Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [Únete a nuestra comunidad de Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [Únete a nuestro wechat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [Únete a nuestro wechat (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## :calendar: Reuniones de la Comunidad ## :calendar: Reuniones de la Comunidad
Queremos que cualquiera se involucre en nuestra comunidad y contribuya con código, ofrecemos regalos y recompensas, y te damos la bienvenida para que te unas a nosotros cada jueves por la noche. Queremos que cualquiera se involucre en nuestra comunidad y contribuya con código, ofrecemos regalos y recompensas, y te damos la bienvenida para que te unas a nosotros cada jueves por la noche.
Nuestra conferencia está en [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, luego puedes buscar el pipeline de Open-IM-Server para unirte Nuestra conferencia está en [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, luego puedes buscar el pipeline de Open-IM-Server para unirte
Tomamos notas de cada [reunión quincenal](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) en [discusiones de GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Nuestras notas de reuniones históricas, así como las repeticiones de las reuniones están disponibles en [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing). Tomamos notas de cada [reunión quincenal](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) en [discusiones de GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Nuestras notas de reuniones históricas, así como las repeticiones de las reuniones están disponibles en [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
## :eyes: Quiénes Están Usando OpenIM ## :eyes: Quiénes Están Usando OpenIM
Consulta nuestros [estudios de caso de usuarios](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md) página para obtener una lista de los usuarios del proyecto. No dudes en dejar un [📝comentario](https://github.com/openimsdk/open-im-server/issues/379) y compartir tu caso de uso. Consulta nuestros [estudios de caso de usuarios](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md) página para obtener una lista de los usuarios del proyecto. No dudes en dejar un [📝comentario](https://github.com/openimsdk/open-im-server/issues/379) y compartir tu caso de uso.
## :page_facing_up: Licencia ## :page_facing_up: Licencia
OpenIM está bajo la licencia Apache 2.0. Consulta [LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) para ver el texto completo de la licencia.
OpenIM está bajo la licencia Apache 2.0. Consulta [LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) para ver el texto completo de la licencia.
El logotipo de OpenIM, incluyendo sus variaciones y versiones animadas, que se muestran en este repositorio [OpenIM](https://github.com/openimsdk/open-im-server) en los directorios [assets/logo](../../assets/logo) y [assets/logo-gif](assets/logo-gif) están protegidos por las leyes de derechos de autor. El logotipo de OpenIM, incluyendo sus variaciones y versiones animadas, que se muestran en este repositorio [OpenIM](https://github.com/openimsdk/open-im-server) en los directorios [assets/logo](../../assets/logo) y [assets/logo-gif](assets/logo-gif) están protegidos por las leyes de derechos de autor.
## 🔮 iGracias a nuestros colaboradores! ## 🔮 iGracias a nuestros colaboradores!
<a href="https://github.com/openimsdk/open-im-server/graphs/contributors"> <a href="https://github.com/openimsdk/open-im-server/graphs/contributors">
+17 -21
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,7 +45,6 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
@@ -61,14 +59,14 @@ OpenIM یک پلتفرم خدماتی است که به طور خاص برای ا
**OpenIMSDK** یک IM SDK است که برای **OpenIMServer** طراحی شده است که به طور خاص برای جاسازی در برنامه های مشتری ایجاد شده است. ویژگی ها و ماژول های اصلی آن به شرح زیر است: **OpenIMSDK** یک IM SDK است که برای **OpenIMServer** طراحی شده است که به طور خاص برای جاسازی در برنامه های مشتری ایجاد شده است. ویژگی ها و ماژول های اصلی آن به شرح زیر است:
+ 🌟 ویژگی های اصلی: - 🌟 ویژگی های اصلی:
- 📦 ذخیره سازی محلی - 📦 ذخیره سازی محلی
- 🔔 پاسخ تماس شنونده - 🔔 پاسخ تماس شنونده
- 🛡️ بسته بندی API - 🛡️ بسته بندی API
- 🌐 مدیریت اتصال - 🌐 مدیریت اتصال
+ 📚 ماژول های اصلی: - 📚 ماژول های اصلی:
1. 🚀 مقداردهی اولیه و ورود 1. 🚀 مقداردهی اولیه و ورود
2. 👤 مدیریت کاربر 2. 👤 مدیریت کاربر
@@ -82,15 +80,15 @@ OpenIM یک پلتفرم خدماتی است که به طور خاص برای ا
## 🌐 درباره OpenIMServer ## 🌐 درباره OpenIMServer
+ **OpenIMServer** دارای ویژگی های زیر است: - **OpenIMServer** دارای ویژگی های زیر است:
- 🌐 معماری Microservice: از حالت کلاستر، از جمله یک دروازه و چندین سرویس rpc پشتیبانی می کند. - 🌐 معماری Microservice: از حالت کلاستر، از جمله یک دروازه و چندین سرویس rpc پشتیبانی می کند.
- 🚀 روش‌های استقرار متنوع: از استقرار از طریق کد منبع، Kubernetes یا Docker پشتیبانی می‌کند. - 🚀 روش‌های استقرار متنوع: از استقرار از طریق کد منبع، Kubernetes یا Docker پشتیبانی می‌کند.
- پشتیبانی از پایگاه عظیم کاربران: گروه های فوق العاده بزرگ با صدها هزار کاربر، ده ها میلیون کاربر و میلیاردها پیام. - پشتیبانی از پایگاه عظیم کاربران: گروه های فوق العاده بزرگ با صدها هزار کاربر، ده ها میلیون کاربر و میلیاردها پیام.
### عملکردهای تجاری پیشرفته: ### عملکردهای تجاری پیشرفته:
+ **REST API**: OpenIMServer APIهای REST را برای سیستم‌های تجاری ارائه می‌کند، با هدف توانمندسازی کسب‌وکارها با قابلیت‌های بیشتر، مانند ایجاد گروه‌ها و ارسال پیام‌های فشار از طریق رابط‌های باطنی. - **REST API**: OpenIMServer APIهای REST را برای سیستم‌های تجاری ارائه می‌کند، با هدف توانمندسازی کسب‌وکارها با قابلیت‌های بیشتر، مانند ایجاد گروه‌ها و ارسال پیام‌های فشار از طریق رابط‌های باطنی.
+ **Webhooks**: OpenIMServer قابلیت های پاسخ به تماس را برای گسترش بیشتر فرم های تجاری ارائه می دهد. پاسخ به تماس به این معنی است که OpenIMServer درخواستی را قبل یا بعد از یک رویداد خاص به سرور تجاری ارسال می کند، مانند تماس های قبل یا بعد از ارسال یک پیام. - **Webhooks**: OpenIMServer قابلیت های پاسخ به تماس را برای گسترش بیشتر فرم های تجاری ارائه می دهد. پاسخ به تماس به این معنی است که OpenIMServer درخواستی را قبل یا بعد از یک رویداد خاص به سرور تجاری ارسال می کند، مانند تماس های قبل یا بعد از ارسال یک پیام.
👉 **[بیشتر بدانید](https://docs.openim.io/guides/introduction/product)** 👉 **[بیشتر بدانید](https://docs.openim.io/guides/introduction/product)**
@@ -100,7 +98,6 @@ OpenIM یک پلتفرم خدماتی است که به طور خاص برای ا
![Overall Architecture](../images/architecture-layers.png) ![Overall Architecture](../images/architecture-layers.png)
## :rocket: شروع سریع ## :rocket: شروع سریع
ما از بسیاری از پلتفرم ها پشتیبانی می کنیم. در اینجا آدرس هایی برای تجربه سریع در سمت وب آمده است: ما از بسیاری از پلتفرم ها پشتیبانی می کنیم. در اینجا آدرس هایی برای تجربه سریع در سمت وب آمده است:
@@ -109,10 +106,10 @@ OpenIM یک پلتفرم خدماتی است که به طور خاص برای ا
🤲 برای تسهیل تجربه کاربر، ما راه حل های مختلف استقرار را ارائه می دهیم. می توانید روش استقرار خود را از لیست زیر انتخاب کنید: 🤲 برای تسهیل تجربه کاربر، ما راه حل های مختلف استقرار را ارائه می دهیم. می توانید روش استقرار خود را از لیست زیر انتخاب کنید:
+ **[راهنمای استقرار کد منبع](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[راهنمای استقرار کد منبع](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[راهنمای استقرار داکر](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[راهنمای استقرار داکر](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[راهنمای استقرار Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[راهنمای استقرار Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[راهنمای استقرار توسعه دهنده مک](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[راهنمای استقرار توسعه دهنده مک](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: برای شروع توسعه OpenIM ## :hammer_and_wrench: برای شروع توسعه OpenIM
@@ -122,7 +119,7 @@ OpenIM هدف ما ایجاد یک جامعه منبع باز سطح بالا ا
اگر می‌خواهید در این مخزن Open-IM-Server مشارکت کنید، لطفاً [مستندات مشارکت‌کننده](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md) ما را بخوانید. اگر می‌خواهید در این مخزن Open-IM-Server مشارکت کنید، لطفاً [مستندات مشارکت‌کننده](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md) ما را بخوانید.
قبل از شروع، لطفاً مطمئن شوید که تغییرات شما مورد تقاضا هستند. بهترین کار برای آن این است که یک [بحث جدید](https://github.com/openimsdk/open-im-server/discussions/new/choose) یا [ارتباط اسلک](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) ایجاد کنید، یا اگر مشکلی پیدا کردید، ابتدا [آن را گزارش کنید](https://github.com/openimsdk/open-im-server/issues/new/choose). قبل از شروع، لطفاً مطمئن شوید که تغییرات شما مورد تقاضا هستند. بهترین کار برای آن این است که یک [بحث جدید](https://github.com/openimsdk/open-im-server/discussions/new/choose) یا [ارتباط اسلک](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) ایجاد کنید، یا اگر مشکلی پیدا کردید، ابتدا [آن را گزارش کنید](https://github.com/openimsdk/open-im-server/issues/new/choose).
- [مرجع OpenIM API](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [مرجع OpenIM API](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [OpenIM Bash Logging](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [OpenIM Bash Logging](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
@@ -154,21 +151,20 @@ OpenIM هدف ما ایجاد یک جامعه منبع باز سطح بالا ا
- [مدیریت استقرار باطن و نظارت](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [مدیریت استقرار باطن و نظارت](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [راهنمای استقرار توسعه دهنده مک برای OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [راهنمای استقرار توسعه دهنده مک برای OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :busts_in_silhouette: انجمن ## :busts_in_silhouette: انجمن
+ 📚 [انجمن OpenIM](https://github.com/OpenIMSDK/community) - 📚 [انجمن OpenIM](https://github.com/OpenIMSDK/community)
+ 💕 [گروه علاقه OpenIM](https://github.com/Openim-sigs) - 💕 [گروه علاقه OpenIM](https://github.com/Openim-sigs)
+ 🚀 [به انجمن Slack ما بپیوندید](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [به انجمن Slack ما بپیوندید](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [به وی چت ما بپیوندید](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [به وی چت ما بپیوندید](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## :calendar: جلسات جامعه ## :calendar: جلسات جامعه
ما می‌خواهیم هر کسی در انجمن ما مشارکت کند و در کد مشارکت کند، ما هدایا و جوایزی ارائه می‌کنیم، و از شما استقبال می‌کنیم که هر پنجشنبه شب به ما بپیوندید. ما می‌خواهیم هر کسی در انجمن ما مشارکت کند و در کد مشارکت کند، ما هدایا و جوایزی ارائه می‌کنیم، و از شما استقبال می‌کنیم که هر پنجشنبه شب به ما بپیوندید.
کنفرانس ما در [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯 است، سپس می توانید خط لوله Open-IM-Server را برای پیوستن جستجو کنید. کنفرانس ما در [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯 است، سپس می توانید خط لوله Open-IM-Server را برای پیوستن جستجو کنید.
ما از هر [جلسه دو هفته‌ای](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) در [بحث‌های GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting) یادداشت‌برداری می‌کنیم، یادداشت‌های جلسه تاریخی ما، و همچنین بازپخش جلسات در [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) موجود است. ما از هر [جلسه دو هفته‌ای](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) در [بحث‌های GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting) یادداشت‌برداری می‌کنیم، یادداشت‌های جلسه تاریخی ما، و همچنین بازپخش جلسات در [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) موجود است.
## :eyes: چه کسانی از OpenIM استفاده می کنند ## :eyes: چه کسانی از OpenIM استفاده می کنند
+18 -24
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,25 +45,21 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
## Ⓜ️ À propos de OpenIM ## Ⓜ️ À propos de OpenIM
OpenIM est une plateforme de services conçue spécifiquement pour intégrer des fonctionnalités de communication telles que le chat, les appels audio et vidéo, les notifications, ainsi que les robots de chat IA dans les applications. Elle offre une série d'API puissantes et de Webhooks, permettant aux développeurs d'incorporer facilement ces caractéristiques interactives dans leurs applications. OpenIM n'est pas en soi une application de chat autonome, mais sert de plateforme supportant d'autres applications pour réaliser des fonctionnalités de communication enrichies. L'image ci-dessous montre les relations d'interaction entre AppServer, AppClient, OpenIMServer et OpenIMSDK pour illustrer spécifiquement. OpenIM est une plateforme de services conçue spécifiquement pour intégrer des fonctionnalités de communication telles que le chat, les appels audio et vidéo, les notifications, ainsi que les robots de chat IA dans les applications. Elle offre une série d'API puissantes et de Webhooks, permettant aux développeurs d'incorporer facilement ces caractéristiques interactives dans leurs applications. OpenIM n'est pas en soi une application de chat autonome, mais sert de plateforme supportant d'autres applications pour réaliser des fonctionnalités de communication enrichies. L'image ci-dessous montre les relations d'interaction entre AppServer, AppClient, OpenIMServer et OpenIMSDK pour illustrer spécifiquement.
![Relation App-OpenIM](../../images/oepnim-design.png) ![Relation App-OpenIM](../../images/oepnim-design.png)
## 🚀 À propos de OpenIMSDK ## 🚀 À propos de OpenIMSDK
**OpenIMSDK** est un SDK IM conçu pour **OpenIMServer** spécialement créé pour être intégré dans les applications clientes. Ses principales fonctionnalités et modules comprennent : **OpenIMSDK** est un SDK IM conçu pour **OpenIMServer** spécialement créé pour être intégré dans les applications clientes. Ses principales fonctionnalités et modules comprennent :
+ 🌟 Fonctionnalités clés : - 🌟 Fonctionnalités clés :
- 📦 Stockage local - 📦 Stockage local
- 🔔 Rappels de l'écouteur - 🔔 Rappels de l'écouteur
@@ -85,15 +80,15 @@ Il est construit avec Golang et supporte le déploiement multiplateforme, assura
## 🌐 À propos de OpenIMServer ## 🌐 À propos de OpenIMServer
+ **OpenIMServer** présente les caractéristiques suivantes - **OpenIMServer** présente les caractéristiques suivantes
- 🌐 Architecture microservices : prend en charge le mode cluster, incluant le gateway (passerelle) et plusieurs services rpc。 - 🌐 Architecture microservices : prend en charge le mode cluster, incluant le gateway (passerelle) et plusieurs services rpc。
- 🚀 Divers modes de déploiement : supporte le déploiement via le code source, Kubernetes ou Docker。 - 🚀 Divers modes de déploiement : supporte le déploiement via le code source, Kubernetes ou Docker。
- Support d'une masse d'utilisateurs : plus de cent mille pour les super grands groupes, des millions d'utilisateurs, et des milliards de messages。 - Support d'une masse d'utilisateurs : plus de cent mille pour les super grands groupes, des millions d'utilisateurs, et des milliards de messages。
### Fonctionnalités commerciales améliorées : ### Fonctionnalités commerciales améliorées :
+ **REST API**OpenIMServer fournit une REST API pour les systèmes commerciaux, visant à accorder plus de fonctionnalités, telles que la création de groupes via l'interface backend, l'envoi de messages push, etc。 - **REST API**OpenIMServer fournit une REST API pour les systèmes commerciaux, visant à accorder plus de fonctionnalités, telles que la création de groupes via l'interface backend, l'envoi de messages push, etc。
+ **Webhooks**OpenIMServer offre des capacités de rappel pour étendre davantage les formes d'entreprise. Un rappel signifie que OpenIMServer enverra une requête au serveur d'entreprise avant ou après qu'un événement se soit produit, comme un rappel avant ou après l'envoi d'un message。 - **Webhooks**OpenIMServer offre des capacités de rappel pour étendre davantage les formes d'entreprise. Un rappel signifie que OpenIMServer enverra une requête au serveur d'entreprise avant ou après qu'un événement se soit produit, comme un rappel avant ou après l'envoi d'un message。
👉 **[En savoir plus](https://docs.openim.io/guides/introduction/product)** 👉 **[En savoir plus](https://docs.openim.io/guides/introduction/product)**
@@ -103,7 +98,6 @@ Plongez dans le cœur de la fonctionnalité d'Open-IM-Server avec notre diagramm
![Architecture globale](../../images/architecture-layers.png) ![Architecture globale](../../images/architecture-layers.png)
## :rocket: Démarrage rapide ## :rocket: Démarrage rapide
Nous prenons en charge de nombreuses plateformes. Voici les adresses pour une expérience rapide du côté web : Nous prenons en charge de nombreuses plateformes. Voici les adresses pour une expérience rapide du côté web :
@@ -112,17 +106,18 @@ Nous prenons en charge de nombreuses plateformes. Voici les adresses pour une ex
🤲 Pour faciliter l'expérience utilisateur, nous proposons plusieurs solutions de déploiement. Vous pouvez choisir votre méthode de déploiement selon la liste ci-dessous 🤲 Pour faciliter l'expérience utilisateur, nous proposons plusieurs solutions de déploiement. Vous pouvez choisir votre méthode de déploiement selon la liste ci-dessous
+ **[Guide de déploiement du code source](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[Guide de déploiement du code source](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Guide de déploiement Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Guide de déploiement Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Guide de déploiement Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Guide de déploiement Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Guide de déploiement pour développeur Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Guide de déploiement pour développeur Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: Commencer à développer avec OpenIM ## :hammer_and_wrench: Commencer à développer avec OpenIM
Chez OpenIM, notre objectif est de construire une communauté open source de premier plan. Nous avons un ensemble de standards, disponibles dans le[ dépôt communautaire](https://github.com/OpenIMSDK/community)。 Chez OpenIM, notre objectif est de construire une communauté open source de premier plan. Nous avons un ensemble de standards, disponibles dans le[ dépôt communautaire](https://github.com/OpenIMSDK/community)。
Si vous souhaitez contribuer à ce dépôt Open-IM-Server, veuillez lire notre[ document pour les contributeurs](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)。 Si vous souhaitez contribuer à ce dépôt Open-IM-Server, veuillez lire notre[ document pour les contributeurs](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)。
Avant de commencer, assurez-vous que vos modifications sont nécessaires. La meilleure manière est de créer une[ nouvelle discussion ](https://github.com/openimsdk/open-im-server/discussions/new/choose) ou une [ communication Slack,](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)ou si vous identifiez un problème, de[ signaler d'abord ](https://github.com/openimsdk/open-im-server/issues/new/choose)。 Avant de commencer, assurez-vous que vos modifications sont nécessaires. La meilleure manière est de créer une[ nouvelle discussion ](https://github.com/openimsdk/open-im-server/discussions/new/choose) ou une [ communication Slack,](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)ou si vous identifiez un problème, de[ signaler d'abord ](https://github.com/openimsdk/open-im-server/issues/new/choose)。
- [Référence de l'API OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [Référence de l'API OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [Journalisation Bash OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [Journalisation Bash OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
- [Actions CI/CD OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/cicd-actions.md) - [Actions CI/CD OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/cicd-actions.md)
@@ -153,13 +148,12 @@ Avant de commencer, assurez-vous que vos modifications sont nécessaires. La mei
- [Gérer le déploiement du backend et la surveillance](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [Gérer le déploiement du backend et la surveillance](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [Guide de déploiement pour développeur Mac pour OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [Guide de déploiement pour développeur Mac pour OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
> ## :calendar: Réunions de la Communauté
>## :calendar: Réunions de la Communauté
Nous voulons que tout le monde s'implique dans notre communauté et contribue au code, nous offrons des cadeaux et des récompenses, et nous vous invitons à nous rejoindre chaque jeudi soir. Nous voulons que tout le monde s'implique dans notre communauté et contribue au code, nous offrons des cadeaux et des récompenses, et nous vous invitons à nous rejoindre chaque jeudi soir.
Notre conférence se trouve dans le [ Slack OpenIM ](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, ensuite vous pouvez rechercher le pipeline Open-IM-Server pour rejoindre Notre conférence se trouve dans le [ Slack OpenIM ](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, ensuite vous pouvez rechercher le pipeline Open-IM-Server pour rejoindre
Nous prenons des notes de chaque [réunion bihebdomadaire ](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) dans les [discussions GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Nos notes de réunion historiques, ainsi que les rediffusions des réunions sont disponibles sur [ Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing). Nous prenons des notes de chaque [réunion bihebdomadaire ](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) dans les [discussions GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Nos notes de réunion historiques, ainsi que les rediffusions des réunions sont disponibles sur [ Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
## :eyes: Qui Utilise OpenIM ## :eyes: Qui Utilise OpenIM
@@ -167,9 +161,9 @@ Consultez notre page [ études de cas d'utilisateurs ](https://github.com/OpenIM
## :page_facing_up: License ## :page_facing_up: License
OpenIM est sous licence Apache 2.0. Voir [LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) pour le texte complet de la licence. OpenIM est sous licence Apache 2.0. Voir [LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE) pour le texte complet de la licence.
Le logo OpenIM, y compris ses variations et versions animées, affiché dans ce dépôt[OpenIM](https://github.com/openimsdk/open-im-server) sous les répertoires [assets/logo](../../assets/logo) et [assets/logo-gif](assets/logo-gif) sont protégés par les lois sur le droit d'auteur. Le logo OpenIM, y compris ses variations et versions animées, affiché dans ce dépôt[OpenIM](https://github.com/openimsdk/open-im-server) sous les répertoires [assets/logo](../../assets/logo) et [assets/logo-gif](assets/logo-gif) sont protégés par les lois sur le droit d'auteur.
## 🔮 Merci à nos contributeurs ! ## 🔮 Merci à nos contributeurs !
+16 -20
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,7 +45,6 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
@@ -61,14 +59,14 @@ Az OpenIM egy szolgáltatási platform, amelyet kifejezetten a csevegés, az aud
Az **OpenIMSDK** egy **OpenIMServer** számára készült azonnali üzenetküldő SDK, amelyet kifejezetten ügyfélalkalmazásokba való beágyazáshoz hoztak létre. Fő jellemzői és moduljai a következők: Az **OpenIMSDK** egy **OpenIMServer** számára készült azonnali üzenetküldő SDK, amelyet kifejezetten ügyfélalkalmazásokba való beágyazáshoz hoztak létre. Fő jellemzői és moduljai a következők:
+ 🌟 Főbb jellemzők: - 🌟 Főbb jellemzők:
- 📦 Helyi raktár - 📦 Helyi raktár
- 🔔 Hallgatói visszahívások - 🔔 Hallgatói visszahívások
- 🛡️ API-csomagolás - 🛡️ API-csomagolás
- 🌐 Kapcsolatkezelés - 🌐 Kapcsolatkezelés
+ 📚 Fő modulok: - 📚 Fő modulok:
1. 🚀 Inicializálás és bejelentkezés 1. 🚀 Inicializálás és bejelentkezés
2. 👤 Felhasználókezelés 2. 👤 Felhasználókezelés
@@ -82,15 +80,15 @@ Golang használatával készült, és támogatja a többplatformos telepítést,
## 🌐 Az OpenIMServerről ## 🌐 Az OpenIMServerről
+ **OpenIMServer** a következő jellemzőkkel rendelkezik: - **OpenIMServer** a következő jellemzőkkel rendelkezik:
- 🌐 Mikroszolgáltatási architektúra: Támogatja a fürt módot, beleértve az átjárót és több rpc szolgáltatást. - 🌐 Mikroszolgáltatási architektúra: Támogatja a fürt módot, beleértve az átjárót és több rpc szolgáltatást.
- 🚀 Változatos telepítési módszerek: Támogatja a forráskódon, Kubernetesen vagy Dockeren keresztül történő telepítést. - 🚀 Változatos telepítési módszerek: Támogatja a forráskódon, Kubernetesen vagy Dockeren keresztül történő telepítést.
- Hatalmas felhasználói bázis támogatása: Szuper nagy csoportok több százezer felhasználóval, több tízmillió felhasználóval és több milliárd üzenettel. - Hatalmas felhasználói bázis támogatása: Szuper nagy csoportok több százezer felhasználóval, több tízmillió felhasználóval és több milliárd üzenettel.
### Továbbfejlesztett üzleti funkcionalitás: ### Továbbfejlesztett üzleti funkcionalitás:
+ **REST API**: Az OpenIMServer REST API-kat kínál az üzleti rendszerek számára, amelyek célja, hogy a vállalkozásokat több funkcióval ruházza fel, mint például csoportok létrehozása és push üzenetek küldése háttérfelületeken keresztül. - **REST API**: Az OpenIMServer REST API-kat kínál az üzleti rendszerek számára, amelyek célja, hogy a vállalkozásokat több funkcióval ruházza fel, mint például csoportok létrehozása és push üzenetek küldése háttérfelületeken keresztül.
+ **Webhooks**: Az OpenIMServer visszahívási lehetőségeket biztosít több üzleti forma kiterjesztéséhez. A visszahívás azt jelenti, hogy az OpenIMServer kérelmet küld az üzleti szervernek egy bizonyos esemény előtt vagy után, például visszahívásokat üzenet küldése előtt vagy után. - **Webhooks**: Az OpenIMServer visszahívási lehetőségeket biztosít több üzleti forma kiterjesztéséhez. A visszahívás azt jelenti, hogy az OpenIMServer kérelmet küld az üzleti szervernek egy bizonyos esemény előtt vagy után, például visszahívásokat üzenet küldése előtt vagy után.
👉 **[Tudj meg többet](https://docs.openim.io/guides/introduction/product)** 👉 **[Tudj meg többet](https://docs.openim.io/guides/introduction/product)**
@@ -100,7 +98,6 @@ Merüljön el az Open-IM-Server funkcióinak szívében az architektúra diagram
![Overall Architecture](../images/architecture-layers.png) ![Overall Architecture](../images/architecture-layers.png)
## :rocket: Gyors indítás ## :rocket: Gyors indítás
Számos platformot támogatunk. Íme a címek a gyors weboldali használathoz: Számos platformot támogatunk. Íme a címek a gyors weboldali használathoz:
@@ -109,10 +106,10 @@ Számos platformot támogatunk. Íme a címek a gyors weboldali használathoz:
🤲 A felhasználói élmény megkönnyítése érdekében különféle telepítési megoldásokat kínálunk. Az alábbi listából választhatja ki a telepítési módot: 🤲 A felhasználói élmény megkönnyítése érdekében különféle telepítési megoldásokat kínálunk. Az alábbi listából választhatja ki a telepítési módot:
+ **[Forráskód-telepítési útmutató](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[Forráskód-telepítési útmutató](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Docker telepítési útmutató](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Docker telepítési útmutató](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Kubernetes telepítési útmutató](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Kubernetes telepítési útmutató](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Mac fejlesztői telepítési útmutató](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Mac fejlesztői telepítési útmutató](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: Az OpenIM fejlesztésének megkezdéséhez ## :hammer_and_wrench: Az OpenIM fejlesztésének megkezdéséhez
@@ -122,7 +119,7 @@ OpenIM Célunk egy felső szintű nyílt forráskódú közösség felépítése
Ha hozzá szeretne járulni ehhez az Open-IM-Server adattárhoz, kérjük, olvassa el [közreműködői dokumentációnkat](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md). Ha hozzá szeretne járulni ehhez az Open-IM-Server adattárhoz, kérjük, olvassa el [közreműködői dokumentációnkat](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
Mielőtt elkezdené, győződjön meg arról, hogy a változtatásokra van-e igény. Erre a legjobb egy [új beszélgetés](https://github.com/openimsdk/open-im-server/discussions/new/choose) VAGY [Slack Communication](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)létrehozása, vagy ha problémát talál, először [jelentse](https://github.com/openimsdk/open-im-server/issues/new/choose) first. Mielőtt elkezdené, győződjön meg arról, hogy a változtatásokra van-e igény. Erre a legjobb egy [új beszélgetés](https://github.com/openimsdk/open-im-server/discussions/new/choose) VAGY [Slack Communication](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)létrehozása, vagy ha problémát talál, először [jelentse](https://github.com/openimsdk/open-im-server/issues/new/choose) first.
- [OpenIM API referencia](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [OpenIM API referencia](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [OpenIM Bash naplózás](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [OpenIM Bash naplózás](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
@@ -154,19 +151,18 @@ Mielőtt elkezdené, győződjön meg arról, hogy a változtatásokra van-e ig
- [A háttérrendszer kezelése és a telepítés figyelése](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [A háttérrendszer kezelése és a telepítés figyelése](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [Mac Developer Deployment Guide for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [Mac Developer Deployment Guide for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :busts_in_silhouette: Közösség ## :busts_in_silhouette: Közösség
+ 📚 [OpenIM közösség](https://github.com/OpenIMSDK/community) - 📚 [OpenIM közösség](https://github.com/OpenIMSDK/community)
+ 💕 [OpenIM érdeklődési csoport](https://github.com/Openim-sigs) - 💕 [OpenIM érdeklődési csoport](https://github.com/Openim-sigs)
+ 🚀 [Csatlakozz a Slack közösségünkhöz](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [Csatlakozz a Slack közösségünkhöz](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [Csatlakozz a wechathez](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [Csatlakozz a wechathez](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## :calendar: Közösségi Találkozók ## :calendar: Közösségi Találkozók
Szeretnénk, ha bárki bekapcsolódna közösségünkbe és hozzájárulna kódunkhoz, ajándékokat és jutalmakat kínálunk, és szeretettel várjuk, hogy csatlakozzon hozzánk minden csütörtök este. Szeretnénk, ha bárki bekapcsolódna közösségünkbe és hozzájárulna kódunkhoz, ajándékokat és jutalmakat kínálunk, és szeretettel várjuk, hogy csatlakozzon hozzánk minden csütörtök este.
Konferenciánk az [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯alatt van, akkor kereshet az Open-IM-Server folyamatban a csatlakozáshoz Konferenciánk az [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯alatt van, akkor kereshet az Open-IM-Server folyamatban a csatlakozáshoz
A [GitHub-beszélgetések](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)minden [kéthetente történő megbeszélésről](https://github.com/openimsdk/open-im-server/discussions/categories/meeting) jegyzeteket készítünk. A találkozók történeti feljegyzései, valamint az értekezletek visszajátszásai a [Google Dokumentumok :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) webhelyen érhetők el. A [GitHub-beszélgetések](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)minden [kéthetente történő megbeszélésről](https://github.com/openimsdk/open-im-server/discussions/categories/meeting) jegyzeteket készítünk. A találkozók történeti feljegyzései, valamint az értekezletek visszajátszásai a [Google Dokumentumok :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) webhelyen érhetők el.
+37 -44
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,29 +45,25 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
## Ⓜ️ OpenIM について
## Ⓜ️ OpenIMについて OpenIM は、アプリケーション内でチャット、音声通話、通知、AI チャットボットなどの通信機能を統合するために特別に設計されたサービスプラットフォームです。一連の強力な API と Webhooks を提供することで、開発者はアプリケーションに簡単にこれらの通信機能を統合できます。OpenIM 自体は独立したチャットアプリではなく、アプリケーションにサポートを提供し、豊富な通信機能を実現するプラットフォームです。以下の図は、AppServer、AppClient、OpenIMServer、OpenIMSDK 間の相互作用を示しています。
OpenIMは、アプリケーション内でチャット、音声通話、通知、AIチャットボットなどの通信機能を統合するために特別に設計されたサービスプラットフォームです。一連の強力なAPIとWebhooksを提供することで、開発者はアプリケーションに簡単にこれらの通信機能を統合できます。OpenIM自体は独立したチャットアプリではなく、アプリケーションにサポートを提供し、豊富な通信機能を実現するプラットフォームです。以下の図は、AppServer、AppClient、OpenIMServer、OpenIMSDK間の相互作用を示しています。
![App-OpenIM Relationship](../images/oepnim-design.png) ![App-OpenIM Relationship](../images/oepnim-design.png)
## 🚀 OpenIMSDKについて ## 🚀 OpenIMSDK について
**OpenIMSDK**は、**OpenIMServer**用に設計されたIM SDKで、クライアントアプリケーションに組み込むためのものです。主な機能とモジュールは以下の通りです: **OpenIMSDK**は、**OpenIMServer**用に設計された IM SDK で、クライアントアプリケーションに組み込むためのものです。主な機能とモジュールは以下の通りです:
+ 🌟 主な機能: - 🌟 主な機能:
- 📦 ローカルストレージ - 📦 ローカルストレージ
- 🔔 リスナーコールバック - 🔔 リスナーコールバック
- 🛡️ APIのラッピング - 🛡️ API のラッピング
- 🌐 接続管理 - 🌐 接続管理
## 📚 主なモジュール: ## 📚 主なモジュール:
@@ -79,54 +74,54 @@ OpenIMは、アプリケーション内でチャット、音声通話、通知
4. 🤖 グループ機能 4. 🤖 グループ機能
5. 💬 会話処理 5. 💬 会話処理
Golangを使用して構築され、クロスプラットフォームの導入をサポートし、すべてのプラットフォームで一貫したアクセス体験を提供します。 Golang を使用して構築され、クロスプラットフォームの導入をサポートし、すべてのプラットフォームで一貫したアクセス体験を提供します。
👉 **[GO SDKを探索する](https://github.com/openimsdk/openim-sdk-core)** 👉 **[GO SDK を探索する](https://github.com/openimsdk/openim-sdk-core)**
## 🌐 OpenIMServerについて ## 🌐 OpenIMServer について
+ **OpenIMServer** には以下の特徴があります: - **OpenIMServer** には以下の特徴があります:
- 🌐 マイクロサービスアーキテクチャ:クラスターモードをサポートし、ゲートウェイ(gateway)と複数のrpcサービスを含みます。 - 🌐 マイクロサービスアーキテクチャ:クラスターモードをサポートし、ゲートウェイ(gateway)と複数の rpc サービスを含みます。
- 🚀 多様なデプロイメント方法:ソースコード、kubernetes、またはdockerでのデプロイメントをサポートします。 - 🚀 多様なデプロイメント方法:ソースコード、kubernetes、または docker でのデプロイメントをサポートします。
- 海量ユーザーサポート:十万人規模の超大型グループ、千万人のユーザー、および百億のメッセージ - 海量ユーザーサポート:十万人規模の超大型グループ、千万人のユーザー、および百億のメッセージ
### 強化されたビジネス機能: ### 強化されたビジネス機能:
+ **REST API**OpenIMServerは、ビジネスシステム用のREST APIを提供しており、ビジネスにさらに多くの機能を提供することを目指しています。たとえば、バックエンドインターフェースを通じてグループを作成したり、プッシュメッセージを送信したりするなどです。 - **REST API**OpenIMServer は、ビジネスシステム用の REST API を提供しており、ビジネスにさらに多くの機能を提供することを目指しています。たとえば、バックエンドインターフェースを通じてグループを作成したり、プッシュメッセージを送信したりするなどです。
+ **Webhooks**OpenIMServerは、より多くのビジネス形態を拡張するためのコールバック機能を提供しています。コールバックとは、特定のイベントが発生する前後に、OpenIMServerがビジネスサーバーにリクエストを送信することを意味します。例えば、メッセージ送信の前後のコールバックなどです。 - **Webhooks**OpenIMServer は、より多くのビジネス形態を拡張するためのコールバック機能を提供しています。コールバックとは、特定のイベントが発生する前後に、OpenIMServer がビジネスサーバーにリクエストを送信することを意味します。例えば、メッセージ送信の前後のコールバックなどです。
👉 **[もっと詳しく知る](https://docs.openim.io/guides/introduction/product)** 👉 **[もっと詳しく知る](https://docs.openim.io/guides/introduction/product)**
## :building_construction: 全体のアーキテクチャ ## :building_construction: 全体のアーキテクチャ
Open-IM-Serverの機能の核心に迫るために、アーキテクチャダイアグラムをご覧ください。 Open-IM-Server の機能の核心に迫るために、アーキテクチャダイアグラムをご覧ください。
![Overall Architecture](../images/architecture-layers.png) ![Overall Architecture](../images/architecture-layers.png)
## :rocket: クイックスタート ## :rocket: クイックスタート
iOS/Android/H5/PC/Webでのオンライン体験: iOS/Android/H5/PC/Web でのオンライン体験:
👉 **[OpenIM online demo](https://www.openim.io/zh/commercial)** 👉 **[OpenIM online demo](https://www.openim.io/zh/commercial)**
🤲 ユーザー体験を容易にするために、私たちは様々なデプロイメントソリューションを提供しています。以下のリストから、ご自身のデプロイメント方法を選択できます: 🤲 ユーザー体験を容易にするために、私たちは様々なデプロイメントソリューションを提供しています。以下のリストから、ご自身のデプロイメント方法を選択できます:
+ **[ソースコードデプロイメントガイド](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[ソースコードデプロイメントガイド](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Docker デプロイメントガイド](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Docker デプロイメントガイド](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Kubernetes デプロイメントガイド](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Kubernetes デプロイメントガイド](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Mac 開発者向けデプロイメントガイド](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Mac 開発者向けデプロイメントガイド](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: OpenIMの開発を始める ## :hammer_and_wrench: OpenIM の開発を始める
[![Open in Dev Container](https://img.shields.io/static/v1?label=Dev%20Container&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/github/openimsdk/open-im-server) [![Open in Dev Container](https://img.shields.io/static/v1?label=Dev%20Container&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/github/openimsdk/open-im-server)
OpenIM 私たちの目標は、トップレベルのオープンソースコミュニティを構築することです。[コミュニティリポジトリ](https://github.com/OpenIMSDK/community)には一連の基準があります。 OpenIM 私たちの目標は、トップレベルのオープンソースコミュニティを構築することです。[コミュニティリポジトリ](https://github.com/OpenIMSDK/community)には一連の基準があります。
このOpen-IM-Serverリポジトリに貢献したい場合は、[貢献者ドキュメントをお読みください](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)。 この Open-IM-Server リポジトリに貢献したい場合は、[貢献者ドキュメントをお読みください](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)。
始める前に、変更に必要があることを確認してください。最良の方法は、[新しいディスカッション](https://github.com/openimsdk/open-im-server/discussions/new/choose)や[Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)での通信を作成すること、または問題を発見した場合は、まずそれを[報告](https://github.com/openimsdk/open-im-server/issues/new/choose)することです。 始める前に、変更に必要があることを確認してください。最良の方法は、[新しいディスカッション](https://github.com/openimsdk/open-im-server/discussions/new/choose)や[Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)での通信を作成すること、または問題を発見した場合は、まずそれを[報告](https://github.com/openimsdk/open-im-server/issues/new/choose)することです。
- [OpenIM APIリファレンス](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [OpenIM API リファレンス](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [OpenIM Bash ロギング](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [OpenIM Bash ロギング](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
- [OpenIM CI/CD アクション](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/cicd-actions.md) - [OpenIM CI/CD アクション](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/cicd-actions.md)
- [OpenIM コード規約](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/code-conventions.md) - [OpenIM コード規約](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/code-conventions.md)
@@ -149,37 +144,35 @@ OpenIM 私たちの目標は、トップレベルのオープンソースコミ
- [OpenIM オフラインデプロイメント](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/offline-deployment.md) - [OpenIM オフラインデプロイメント](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/offline-deployment.md)
- [OpenIM Protoc ツール](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/protoc-tools.md) - [OpenIM Protoc ツール](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/protoc-tools.md)
- [OpenIM テスティングガイド](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/test.md) - [OpenIM テスティングガイド](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/test.md)
- [OpenIM ユーティリティGo](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-go.md) - [OpenIM ユーティリティ Go](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-go.md)
- [OpenIM Makefile ユーティリティ](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-makefile.md) - [OpenIM Makefile ユーティリティ](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-makefile.md)
- [OpenIM スクリプトユーティリティ](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-scripts.md) - [OpenIM スクリプトユーティリティ](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-scripts.md)
- [OpenIM バージョニング](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/version.md) - [OpenIM バージョニング](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/version.md)
- [バックエンド管理とモニターデプロイメント](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [バックエンド管理とモニターデプロイメント](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [OpenIMMac開発者デプロイメントガイド](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [OpenIMMac 開発者デプロイメントガイド](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :busts_in_silhouette: コミュニティ ## :busts_in_silhouette: コミュニティ
+ 📚 [OpenIM コミュニティ](https://github.com/OpenIMSDK/community) - 📚 [OpenIM コミュニティ](https://github.com/OpenIMSDK/community)
+ 💕 [OpenIM 興味グループ](https://github.com/Openim-sigs) - 💕 [OpenIM 興味グループ](https://github.com/Openim-sigs)
+ 🚀 [私たちのSlackコミュニティに参加する](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [私たちの Slack コミュニティに参加する](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [私たちのWeChat(微信群)に参加する](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [私たちの WeChat(微信群)に参加する](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## :calendar: コミュニティミーティング ## :calendar: コミュニティミーティング
私たちは、誰もがコミュニティに参加し、コードに貢献してもらいたいと考えています。私たちは、ギフトや報酬を提供し、毎週木曜日の夜に参加していただくことを歓迎します。 私たちは、誰もがコミュニティに参加し、コードに貢献してもらいたいと考えています。私たちは、ギフトや報酬を提供し、毎週木曜日の夜に参加していただくことを歓迎します。
私たちの会議は[OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)🎯で行われます。そこでOpen-IM-Serverパイプラインを検索して参加できます。 私たちの会議は[OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)🎯 で行われます。そこで Open-IM-Server パイプラインを検索して参加できます。
私たちは[隔週の会議](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)のメモを[GitHub ディスカッション](https://github.com/openimsdk/open-im-server/discussions/categories/meeting)に記録しています。歴史的な会議のメモや会議のリプレイは[Google Docs📑](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing)で利用可能です。
私たちは[隔週の会議](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)のメモを[GitHubディスカッション](https://github.com/openimsdk/open-im-server/discussions/categories/meeting)に記録しています。歴史的な会議のメモや会議のリプレイは[Google Docs📑](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing)で利用可能です。 ## :eyes: OpenIM を使用している人たち
## :eyes: OpenIMを使用している人たち プロジェクトユーザーのリストについては、[ユーザーケーススタディ](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md)ページをご覧ください。[コメント 📝](https://github.com/openimsdk/open-im-server/issues/379)を残して、あなたの使用例を共有することを躊躇しないでください。
プロジェクトユーザーのリストについては、[ユーザーケーススタディ](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md)ページをご覧ください。[コメント📝](https://github.com/openimsdk/open-im-server/issues/379)を残して、あなたの使用例を共有することを躊躇しないでください。
## :page_facing_up: ライセンス ## :page_facing_up: ライセンス
OpenIMApache 2.0ライセンスの下でライセンスされています。完全なライセンステキストについては、[LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE)を参照してください。 OpenIMApache 2.0 ライセンスの下でライセンスされています。完全なライセンステキストについては、[LICENSE](https://github.com/openimsdk/open-im-server/tree/main/LICENSE)を参照してください。
このリポジトリに表示される[OpenIM](https://github.com/openimsdk/open-im-server)ロゴ、そのバリエーション、およびアニメーションバージョン([assets/logo](./assets/logo)および[assets/logo-gif](assets/logo-gif)ディレクトリ内)は、著作権法によって保護されています。 このリポジトリに表示される[OpenIM](https://github.com/openimsdk/open-im-server)ロゴ、そのバリエーション、およびアニメーションバージョン([assets/logo](./assets/logo)および[assets/logo-gif](assets/logo-gif)ディレクトリ内)は、著作権法によって保護されています。
+17 -26
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,26 +45,21 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
## Ⓜ️ OpenIM에 대하여 ## Ⓜ️ OpenIM에 대하여
OpenIM은 채팅, 오디오-비디오 통화, 알림 및 AI 챗봇을 애플리케이션에 통합하기 위해 특별히 설계된 서비스 플랫폼입니다. 이 플랫폼은 강력한 API와 웹훅을 제공하여 개발자가 이러한 상호작용 기능을 애플리케이션에 쉽게 통합할 수 있게 합니다. OpenIM은 독립 실행형 채팅 애플리케이션이 아니라, 다른 애플리케이션들이 풍부한 커뮤니케이션 기능을 달성할 수 있도록 지원하는 플랫폼으로서의 역할을 합니다. 다음 다이어그램은 AppServer, AppClient, OpenIMServer, 및 OpenIMSDK 간의 상호작용을 자세히 설명하기 위해 제시되었습니다. OpenIM은 채팅, 오디오-비디오 통화, 알림 및 AI 챗봇을 애플리케이션에 통합하기 위해 특별히 설계된 서비스 플랫폼입니다. 이 플랫폼은 강력한 API와 웹훅을 제공하여 개발자가 이러한 상호작용 기능을 애플리케이션에 쉽게 통합할 수 있게 합니다. OpenIM은 독립 실행형 채팅 애플리케이션이 아니라, 다른 애플리케이션들이 풍부한 커뮤니케이션 기능을 달성할 수 있도록 지원하는 플랫폼으로서의 역할을 합니다. 다음 다이어그램은 AppServer, AppClient, OpenIMServer, 및 OpenIMSDK 간의 상호작용을 자세히 설명하기 위해 제시되었습니다.
![App-OpenIM Relationship](../images/oepnim-design.png) ![App-OpenIM Relationship](../images/oepnim-design.png)
## 🚀 OpenIMSDK에 대하여 ## 🚀 OpenIMSDK에 대하여
**OpenIMSDK**는**OpenIMServer**를 위해 특별히 제작된 IM SDK로, 클라이언트 애플리케이션 내에 내장하기 위해 설계되었습니다. 그 주요 기능 및 모듈은 다음과 같습니다: **OpenIMSDK**는**OpenIMServer**를 위해 특별히 제작된 IM SDK로, 클라이언트 애플리케이션 내에 내장하기 위해 설계되었습니다. 그 주요 기능 및 모듈은 다음과 같습니다:
- 🌟 주요 기능:
+ 🌟 주요 기능:
- 📦 로컬 스토리지 - 📦 로컬 스토리지
- 🔔 리스너 콜백 - 🔔 리스너 콜백
@@ -86,15 +80,15 @@ OpenIM은 채팅, 오디오-비디오 통화, 알림 및 AI 챗봇을 애플리
## 🌐 OpenIMServer에 대하여 ## 🌐 OpenIMServer에 대하여
+ **OpenIMServer** 는 다음과 같은 특성을 가지고 있습니다: - **OpenIMServer** 는 다음과 같은 특성을 가지고 있습니다:
- 🌐 마이크로서비스 아키텍처: 게이트웨이 및 다수의 rpc 서비스를 포함하는 클러스터 모드를 지원합니다. - 🌐 마이크로서비스 아키텍처: 게이트웨이 및 다수의 rpc 서비스를 포함하는 클러스터 모드를 지원합니다.
- 🚀 다양한 배포 방법: 소스 코드, 쿠버네티스 또는 도커를 통한 배포를 지원합니다. - 🚀 다양한 배포 방법: 소스 코드, 쿠버네티스 또는 도커를 통한 배포를 지원합니다.
- 대규모 사용자 기반 지원: 수십만 명의 사용자를 포함하는 초대형 그룹, 수천만 명의 사용자 및 수십억 건의 메시지를 지원합니다. - 대규모 사용자 기반 지원: 수십만 명의 사용자를 포함하는 초대형 그룹, 수천만 명의 사용자 및 수십억 건의 메시지를 지원합니다.
### 강화된 비즈니스 기능: ### 강화된 비즈니스 기능:
+ **REST API**OpenIMServer는 비즈니스 시스템을 위한 REST API를 제공하여, 백엔드 인터페이스를 통해 그룹 생성 및 푸시 메시지 전송과 같은 더 많은 기능을 비즈니스에 제공하기 위해 설계되었습니다. - **REST API**OpenIMServer는 비즈니스 시스템을 위한 REST API를 제공하여, 백엔드 인터페이스를 통해 그룹 생성 및 푸시 메시지 전송과 같은 더 많은 기능을 비즈니스에 제공하기 위해 설계되었습니다.
+ **Webhooks**OpenIMServer는 더 많은 비즈니스 형태를 확장할 수 있는 콜백 기능을 제공합니다. 콜백이란 메시지 전송 전후와 같은 특정 이벤트 전후에 OpenIMServer가 비즈니스 서버로 요청을 보내는 것을 의미합니다. - **Webhooks**OpenIMServer는 더 많은 비즈니스 형태를 확장할 수 있는 콜백 기능을 제공합니다. 콜백이란 메시지 전송 전후와 같은 특정 이벤트 전후에 OpenIMServer가 비즈니스 서버로 요청을 보내는 것을 의미합니다.
👉 **[더 알아보기](https://docs.openim.io/guides/introduction/product)** 👉 **[더 알아보기](https://docs.openim.io/guides/introduction/product)**
@@ -112,10 +106,10 @@ Open-IM-Server의 기능의 핵심으로 들어가 우리의 아키텍처 다이
🤲 사용자 경험을 용이하게 하기 위해, 다양한 배포 솔루션을 제공합니다. 아래 목록에서 배포 방법을 선택할 수 있습니다: 🤲 사용자 경험을 용이하게 하기 위해, 다양한 배포 솔루션을 제공합니다. 아래 목록에서 배포 방법을 선택할 수 있습니다:
+ **[소스 코드 배포 가이드](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[소스 코드 배포 가이드](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[docker 배포 가이드](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[docker 배포 가이드](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Kubernetes 배포 가이드](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Kubernetes 배포 가이드](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Mac 개발자 배포 가이드](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Mac 개발자 배포 가이드](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: OpenIM 개발 시작하기 ## :hammer_and_wrench: OpenIM 개발 시작하기
@@ -125,7 +119,7 @@ OpenIM의 목표는 최상위 수준의 오픈 소스 커뮤니티를 구축하
이 Open-IM-Server 리포지토리에 기여하고 싶다면, 우리의 [기여자 문서](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)를 읽어주세요. 이 Open-IM-Server 리포지토리에 기여하고 싶다면, 우리의 [기여자 문서](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md)를 읽어주세요.
시작하기 전에, 변경 사항이 필요한지 확인해 주세요. 가장 좋은 방법은 [새로운 토론](https://github.com/openimsdk/open-im-server/discussions/new/choose)을 생성하거나 [Slack 통신을](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 하거나, 문제를 발견했다면 먼저 [보고](https://github.com/openimsdk/open-im-server/issues/new/choose)하는 것입니다. 시작하기 전에, 변경 사항이 필요한지 확인해 주세요. 가장 좋은 방법은 [새로운 토론](https://github.com/openimsdk/open-im-server/discussions/new/choose)을 생성하거나 [Slack 통신을](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 하거나, 문제를 발견했다면 먼저 [보고](https://github.com/openimsdk/open-im-server/issues/new/choose)하는 것입니다.
- [OpenIM API 참조](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [OpenIM API 참조](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [OpenIM Bash 로깅](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [OpenIM Bash 로깅](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
@@ -157,23 +151,21 @@ OpenIM의 목표는 최상위 수준의 오픈 소스 커뮤니티를 구축하
- [백엔드 관리 및 모니터 배포](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [백엔드 관리 및 모니터 배포](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [맥 개발자 배포 가이드 for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [맥 개발자 배포 가이드 for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :busts_in_silhouette: 커뮤니티 ## :busts_in_silhouette: 커뮤니티
+ 📚 [OpenIM 커뮤니티](https://github.com/OpenIMSDK/community) - 📚 [OpenIM 커뮤니티](https://github.com/OpenIMSDK/community)
+ 💕 [OpenIM 관심 그룹](https://github.com/Openim-sigs) - 💕 [OpenIM 관심 그룹](https://github.com/Openim-sigs)
+ 🚀 [우리의 Slack 커뮤니티에 가입하기](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [우리의 Slack 커뮤니티에 가입하기](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [우리의 위챗(微信群)에 가입하기](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [우리의 위챗(微信群)에 가입하기](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## :calendar: 커뮤니티 미팅 ## :calendar: 커뮤니티 미팅
우리는 누구나 커뮤니티에 참여하고 코드를 기여할 수 있도록 하며, 선물과 보상을 제공하며, 매주 목요일 밤에 여러분을 환영합니다. 우리는 누구나 커뮤니티에 참여하고 코드를 기여할 수 있도록 하며, 선물과 보상을 제공하며, 매주 목요일 밤에 여러분을 환영합니다.
우리의 회의는 [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯에서 이루어지며, Open-IM-Server 파이프라인을 검색하여 참여할 수 있습니다. 우리의 회의는 [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯에서 이루어지며, Open-IM-Server 파이프라인을 검색하여 참여할 수 있습니다.
우리는 격주 회의의 메모를 [GitHub 토론](https://github.com/openimsdk/open-im-server/discussions/categories/meeting)에서 기록하며, 우리의 역사적 [회의](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) 노트와 회의 재생은 [Google Docs 📑](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing)에서 이용할 수 있습니다. 우리는 격주 회의의 메모를 [GitHub 토론](https://github.com/openimsdk/open-im-server/discussions/categories/meeting)에서 기록하며, 우리의 역사적 [회의](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) 노트와 회의 재생은 [Google Docs 📑](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing)에서 이용할 수 있습니다.
## :eyes: OpenIM을 사용하는 사람들 ## :eyes: OpenIM을 사용하는 사람들
프로젝트 사용자 목록을 위한 우리의 [사용자 사례 연구](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md) 페이지를 확인하세요. 사용 사례를 공유하고 싶다면 주저하지 말고 [📝코멘트](https://github.com/openimsdk/open-im-server/issues/379)를 남겨주세요. 프로젝트 사용자 목록을 위한 우리의 [사용자 사례 연구](https://github.com/OpenIMSDK/community/blob/main/ADOPTERS.md) 페이지를 확인하세요. 사용 사례를 공유하고 싶다면 주저하지 말고 [📝코멘트](https://github.com/openimsdk/open-im-server/issues/379)를 남겨주세요.
@@ -184,7 +176,6 @@ OpenIM은 Apache 2.0 라이선스에 따라 라이선스가 부여됩니다. 전
이 리포지토리 [OpenIM](https://github.com/openimsdk/open-im-server)에 표시된 OpenIM 로고, 그 변형 및 애니메이션 버전은 [assets/logo](../../assets/logo) 및 [assets/logo-gif](../../assets/logo-gif) 디렉토리 아래에 있으며, 저작권 법에 의해 보호됩니다. 이 리포지토리 [OpenIM](https://github.com/openimsdk/open-im-server)에 표시된 OpenIM 로고, 그 변형 및 애니메이션 버전은 [assets/logo](../../assets/logo) 및 [assets/logo-gif](../../assets/logo-gif) 디렉토리 아래에 있으며, 저작권 법에 의해 보호됩니다.
## 🔮 우리의 기여자들에게 감사합니다! ## 🔮 우리의 기여자들에게 감사합니다!
<a href="https://github.com/openimsdk/open-im-server/graphs/contributors"> <a href="https://github.com/openimsdk/open-im-server/graphs/contributors">
+16 -22
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,25 +45,21 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
## Ⓜ️ OpenIM Hakkında ## Ⓜ️ OpenIM Hakkında
OpenIM, uygulamalara sohbet, sesli-görüntülü aramalar, bildirimler ve AI sohbet robotları entegre etmek için özel olarak tasarlanmış bir hizmet platformudur. Güçlü API'ler ve Webhook'lar sunarak, geliştiricilerin bu etkileşimli özellikleri uygulamalarına kolayca dahil etmelerini sağlar. OpenIM bağımsız bir sohbet uygulaması değildir, ancak zengin iletişim işlevselliği sağlama amacıyla diğer uygulamaları destekleyen bir platform olarak hizmet verir. Aşağıdaki diyagram, AppServer, AppClient, OpenIMServer ve OpenIMSDK arasındaki etkileşimi detaylandırmak için açıklar. OpenIM, uygulamalara sohbet, sesli-görüntülü aramalar, bildirimler ve AI sohbet robotları entegre etmek için özel olarak tasarlanmış bir hizmet platformudur. Güçlü API'ler ve Webhook'lar sunarak, geliştiricilerin bu etkileşimli özellikleri uygulamalarına kolayca dahil etmelerini sağlar. OpenIM bağımsız bir sohbet uygulaması değildir, ancak zengin iletişim işlevselliği sağlama amacıyla diğer uygulamaları destekleyen bir platform olarak hizmet verir. Aşağıdaki diyagram, AppServer, AppClient, OpenIMServer ve OpenIMSDK arasındaki etkileşimi detaylandırmak için açıklar.
![App-OpenIM Relationship](../images/oepnim-design.png) ![App-OpenIM Relationship](../images/oepnim-design.png)
## 🚀 OpenIMSDK Hakkında ## 🚀 OpenIMSDK Hakkında
**OpenIMSDK**, müşteri uygulamalarına gömülmek üzere özel olarak oluşturulan **OpenIMServer** için tasarlanmış bir IM SDK'sıdır. Ana özellikleri ve modülleri aşağıdaki gibidir: **OpenIMSDK**, müşteri uygulamalarına gömülmek üzere özel olarak oluşturulan **OpenIMServer** için tasarlanmış bir IM SDK'sıdır. Ana özellikleri ve modülleri aşağıdaki gibidir:
+ 🌟 Ana Özellikler: - 🌟 Ana Özellikler:
- 📦 Yerel depolama - 📦 Yerel depolama
- 🔔 Dinleyici geri çağırmaları - 🔔 Dinleyici geri çağırmaları
@@ -85,15 +80,15 @@ Golang kullanılarak inşa edilmiş ve tüm platformlarda tutarlı bir erişim d
## 🌐 OpenIMServer Hakkında ## 🌐 OpenIMServer Hakkında
+ **OpenIMServer** aşağıdaki özelliklere sahiptir: - **OpenIMServer** aşağıdaki özelliklere sahiptir:
- 🌐 Mikroservis mimarisi: Bir kapı ve çoklu rpc servisleri içeren küme modunu destekler. - 🌐 Mikroservis mimarisi: Bir kapı ve çoklu rpc servisleri içeren küme modunu destekler.
- 🚀 Çeşitli dağıtım yöntemleri: Kaynak kodu, Kubernetes veya Docker aracılığıyla dağıtımı destekler. - 🚀 Çeşitli dağıtım yöntemleri: Kaynak kodu, Kubernetes veya Docker aracılığıyla dağıtımı destekler.
- Büyük kullanıcı tabanı desteği: Yüz binlerce kullanıcısı olan süper büyük gruplar, on milyonlarca kullanıcı ve milyarlarca mesaj. - Büyük kullanıcı tabanı desteği: Yüz binlerce kullanıcısı olan süper büyük gruplar, on milyonlarca kullanıcı ve milyarlarca mesaj.
### Geliştirilmiş İşlevsellik: ### Geliştirilmiş İşlevsellik:
+ **REST API**OpenIMServer, işletmeleri gruplar oluşturma ve arka plan arayüzleri aracılığıyla itme mesajları gönderme gibi daha fazla işlevsellikle güçlendirmeyi amaçlayan iş sistemleri için REST API'leri sunar. - **REST API**OpenIMServer, işletmeleri gruplar oluşturma ve arka plan arayüzleri aracılığıyla itme mesajları gönderme gibi daha fazla işlevsellikle güçlendirmeyi amaçlayan iş sistemleri için REST API'leri sunar.
+ **Webhooks**OpenIMServer, daha fazla iş formunu genişletme yetenekleri sağlayan geri çağırma özellikleri sunar. Geri çağırma, OpenIMServer'ın belirli bir olaydan önce veya sonra, örneğin bir mesaj göndermeden önce veya sonra iş sunucusuna bir istek göndermesi anlamına gelir. - **Webhooks**OpenIMServer, daha fazla iş formunu genişletme yetenekleri sağlayan geri çağırma özellikleri sunar. Geri çağırma, OpenIMServer'ın belirli bir olaydan önce veya sonra, örneğin bir mesaj göndermeden önce veya sonra iş sunucusuna bir istek göndermesi anlamına gelir.
👉 **[Daha fazla bilgi edinin](https://docs.openim.io/guides/introduction/product)** 👉 **[Daha fazla bilgi edinin](https://docs.openim.io/guides/introduction/product)**
@@ -111,10 +106,10 @@ Birçok platformu destekliyoruz. Web tarafında hızlı deneyim için adresler
🤲 Kullanıcı deneyimini kolaylaştırmak için çeşitli dağıtım çözümleri sunuyoruz. Aşağıdaki listeden dağıtım yönteminizi seçebilirsiniz: 🤲 Kullanıcı deneyimini kolaylaştırmak için çeşitli dağıtım çözümleri sunuyoruz. Aşağıdaki listeden dağıtım yönteminizi seçebilirsiniz:
+ **[Kaynak Kodu Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[Kaynak Kodu Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Docker Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Docker Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Kubernetes Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Kubernetes Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Mac Geliştirici Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Mac Geliştirici Dağıtım Kılavuzu](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: OpenIM Geliştirmeye Başlamak ## :hammer_and_wrench: OpenIM Geliştirmeye Başlamak
@@ -124,7 +119,7 @@ OpenIM Amacımız, üst düzey bir açık kaynak topluluğu oluşturmaktır. [To
Bu Open-IM-Server deposuna katkıda bulunmak istiyorsanız, lütfen katkıda bulunanlar için [dokümantasyonumuzu](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md) okuyun. Bu Open-IM-Server deposuna katkıda bulunmak istiyorsanız, lütfen katkıda bulunanlar için [dokümantasyonumuzu](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md) okuyun.
Başlamadan önce, lütfen değişikliklerinizin talep edildiğinden emin olun. Bunun için en iyisi, [yeni bir tartışma OLUŞTURMAK](https://github.com/openimsdk/open-im-server/discussions/new/choose) veya [Slack İletişimi](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) kurmak, ya da bir sorun bulursanız, önce bunu [rapor](https://github.com/openimsdk/open-im-server/issues/new/choose) etmektir. Başlamadan önce, lütfen değişikliklerinizin talep edildiğinden emin olun. Bunun için en iyisi, [yeni bir tartışma OLUŞTURMAK](https://github.com/openimsdk/open-im-server/discussions/new/choose) veya [Slack İletişimi](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) kurmak, ya da bir sorun bulursanız, önce bunu [rapor](https://github.com/openimsdk/open-im-server/issues/new/choose) etmektir.
- [OpenIM API Referansı](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [OpenIM API Referansı](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [OpenIM Bash Günlüğü](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [OpenIM Bash Günlüğü](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
@@ -156,19 +151,18 @@ Başlamadan önce, lütfen değişikliklerinizin talep edildiğinden emin olun.
- [Arka uç yönetimi ve izleme dağıtımı](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [Arka uç yönetimi ve izleme dağıtımı](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [Mac Geliştirici Dağıtım Kılavuzu for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [Mac Geliştirici Dağıtım Kılavuzu for OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :busts_in_silhouette: Topluluk ## :busts_in_silhouette: Topluluk
+ 📚 [OpenIM Topluluğu](https://github.com/OpenIMSDK/community) - 📚 [OpenIM Topluluğu](https://github.com/OpenIMSDK/community)
+ 💕 [OpenIM İlgi Grubu](https://github.com/Openim-sigs) - 💕 [OpenIM İlgi Grubu](https://github.com/Openim-sigs)
+ 🚀 [Slack topluluğumuza katılın](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [Slack topluluğumuza katılın](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [Wechat grubumuza katılın (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [Wechat grubumuza katılın (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## :calendar: Topluluk Toplantıları ## :calendar: Topluluk Toplantıları
Topluluğumuza herkesin katılmasını ve kod katkısında bulunmasını istiyoruz, hediyeler ve ödüller sunuyoruz ve sizi her Perşembe gecesi bize katılmaya davet ediyoruz. Topluluğumuza herkesin katılmasını ve kod katkısında bulunmasını istiyoruz, hediyeler ve ödüller sunuyoruz ve sizi her Perşembe gecesi bize katılmaya davet ediyoruz.
Konferansımız [OpenIM Slack'te](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, ardından Open-IM-Server boru hattını arayıp katılabilirsiniz. Konferansımız [OpenIM Slack'te](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, ardından Open-IM-Server boru hattını arayıp katılabilirsiniz.
İki haftada bir yapılan toplantının [notlarını](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) [GitHub tartışmalarında alıyoruz](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Tarihi toplantı notlarımız ve toplantıların tekrarları [Google Docs'ta](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) 📑 mevcut. İki haftada bir yapılan toplantının [notlarını](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) [GitHub tartışmalarında alıyoruz](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), Tarihi toplantı notlarımız ve toplantıların tekrarları [Google Docs'ta](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing) 📑 mevcut.
+16 -20
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,7 +45,6 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
@@ -61,14 +59,14 @@ OpenIM — це сервісна платформа, спеціально роз
**OpenIMSDK** це пакет IM SDK, розроблений для **OpenIMServer**, створений спеціально для вбудовування в клієнтські програми. Його основні функції та модулі такі: **OpenIMSDK** це пакет IM SDK, розроблений для **OpenIMServer**, створений спеціально для вбудовування в клієнтські програми. Його основні функції та модулі такі:
+ 🌟 Основні характеристики: - 🌟 Основні характеристики:
- 📦 Локальне сховище - 📦 Локальне сховище
- 🔔 Зворотні виклики слухача - 🔔 Зворотні виклики слухача
- 🛡️ Обгортка API - 🛡️ Обгортка API
- 🌐 Керування підключенням - 🌐 Керування підключенням
+ 📚 Основні модулі: - 📚 Основні модулі:
1. 🚀 Ініціалізація та вхід 1. 🚀 Ініціалізація та вхід
2. 👤 Керування користувачами 2. 👤 Керування користувачами
@@ -82,15 +80,15 @@ OpenIM — це сервісна платформа, спеціально роз
## 🌐 Про OpenIMServer ## 🌐 Про OpenIMServer
+ **OpenIMServer** має такі характеристики: - **OpenIMServer** має такі характеристики:
- 🌐 Архітектура мікросервісу: підтримує режим кластера, включаючи шлюз і кілька служб rpc. - 🌐 Архітектура мікросервісу: підтримує режим кластера, включаючи шлюз і кілька служб rpc.
- 🚀 Різноманітні методи розгортання: підтримує розгортання через вихідний код, Kubernetes або Docker. - 🚀 Різноманітні методи розгортання: підтримує розгортання через вихідний код, Kubernetes або Docker.
- Підтримка величезної бази користувачів: надвеликі групи із сотнями тисяч користувачів, десятками мільйонів користувачів і мільярдами повідомлень. - Підтримка величезної бази користувачів: надвеликі групи із сотнями тисяч користувачів, десятками мільйонів користувачів і мільярдами повідомлень.
### Розширена бізнес-функціональність: ### Розширена бізнес-функціональність:
+ **REST API**: OpenIMServer пропонує REST API для бізнес-систем, спрямованих на надання компаніям додаткових можливостей, таких як створення груп і надсилання push-повідомлень через серверні інтерфейси. - **REST API**: OpenIMServer пропонує REST API для бізнес-систем, спрямованих на надання компаніям додаткових можливостей, таких як створення груп і надсилання push-повідомлень через серверні інтерфейси.
+ **Веб-перехоплення**: OpenIMServer надає можливості зворотного виклику, щоб розширити більше бізнес-форм. Зворотний виклик означає, що OpenIMServer надсилає запит на бізнес-сервер до або після певної події, як зворотні виклики до або після надсилання повідомлення. - **Веб-перехоплення**: OpenIMServer надає можливості зворотного виклику, щоб розширити більше бізнес-форм. Зворотний виклик означає, що OpenIMServer надсилає запит на бізнес-сервер до або після певної події, як зворотні виклики до або після надсилання повідомлення.
👉 **[Докладніше](https://docs.openim.io/guides/introduction/product)** 👉 **[Докладніше](https://docs.openim.io/guides/introduction/product)**
@@ -100,7 +98,6 @@ OpenIM — це сервісна платформа, спеціально роз
![Overall Architecture](../images/architecture-layers.png) ![Overall Architecture](../images/architecture-layers.png)
## :rocket: Швидкий початок ## :rocket: Швидкий початок
Ми підтримуємо багато платформ. Ось адреси для швидкого використання веб-сайту: Ми підтримуємо багато платформ. Ось адреси для швидкого використання веб-сайту:
@@ -109,10 +106,10 @@ OpenIM — це сервісна платформа, спеціально роз
🤲 Щоб полегшити роботу користувача, ми пропонуємо різні рішення для розгортання. Ви можете вибрати спосіб розгортання зі списку нижче: 🤲 Щоб полегшити роботу користувача, ми пропонуємо різні рішення для розгортання. Ви можете вибрати спосіб розгортання зі списку нижче:
+ **[Посібник із розгортання вихідного коду](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[Посібник із розгортання вихідного коду](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Посібник із розгортання Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Посібник із розгортання Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Посібник із розгортання Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Посібник із розгортання Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Посібник із розгортання розробника Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Посібник із розгортання розробника Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: Щоб розпочати розробку OpenIM ## :hammer_and_wrench: Щоб розпочати розробку OpenIM
@@ -122,7 +119,7 @@ OpenIM. Наша мета — побудувати спільноту з від
Якщо ви хочете внести свій внесок у це сховище Open-IM-Server, прочитайте нашу [документацію для учасників](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md). Якщо ви хочете внести свій внесок у це сховище Open-IM-Server, прочитайте нашу [документацію для учасників](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
Перш ніж почати, переконайтеся, що ваші зміни затребувані. Найкраще для цього створити [нове обговорення](https://github.com/openimsdk/open-im-server/discussions/new/choose) АБО [Нездійснене спілкування](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q)або, якщо ви виявите проблему, спершу [повідомити про неї](https://github.com/openimsdk/open-im-server/issues/new/choose). Перш ніж почати, переконайтеся, що ваші зміни затребувані. Найкраще для цього створити [нове обговорення](https://github.com/openimsdk/open-im-server/discussions/new/choose) АБО [Нездійснене спілкування](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)або, якщо ви виявите проблему, спершу [повідомити про неї](https://github.com/openimsdk/open-im-server/issues/new/choose).
- [Довідка щодо API OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [Довідка щодо API OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [Ведення журналу OpenIM Bash](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [Ведення журналу OpenIM Bash](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
@@ -154,19 +151,18 @@ OpenIM. Наша мета — побудувати спільноту з від
- [Керування серверною частиною та моніторинг розгортання](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [Керування серверною частиною та моніторинг розгортання](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [Посібник із розгортання розробника Mac для OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [Посібник із розгортання розробника Mac для OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :busts_in_silhouette: Спільнота ## :busts_in_silhouette: Спільнота
+ 📚 [Спільнота OpenIM](https://github.com/OpenIMSDK/community) - 📚 [Спільнота OpenIM](https://github.com/OpenIMSDK/community)
+ 💕 [Група інтересів OpenIM](https://github.com/Openim-sigs) - 💕 [Група інтересів OpenIM](https://github.com/Openim-sigs)
+ 🚀 [Приєднайтеся до нашої спільноти Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [Приєднайтеся до нашої спільноти Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [Приєднайтеся до нашого wechat](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [Приєднайтеся до нашого wechat](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## :calendar: Збори громади ## :calendar: Збори громади
Ми хочемо, щоб будь-хто долучився до нашої спільноти та додав код, ми пропонуємо подарунки та нагороди, і ми запрошуємо вас приєднатися до нас щочетверга ввечері. Ми хочемо, щоб будь-хто долучився до нашої спільноти та додав код, ми пропонуємо подарунки та нагороди, і ми запрошуємо вас приєднатися до нас щочетверга ввечері.
Наша конференція знаходиться в [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, тоді ви можете шукати конвеєр Open-IM-Server, щоб приєднатися. Наша конференція знаходиться в [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, тоді ви можете шукати конвеєр Open-IM-Server, щоб приєднатися.
Ми робимо нотатки про кожну [двотижневу зустріч](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)в [обговореннях GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting). Наші історичні нотатки зустрічей, а також повтори зустрічей доступні в[Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing). Ми робимо нотатки про кожну [двотижневу зустріч](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting)в [обговореннях GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting). Наші історичні нотатки зустрічей, а також повтори зустрічей доступні в[Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
+18 -23
View File
@@ -12,12 +12,11 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server) [![Go Report Card](https://goreportcard.com/badge/github.com/openimsdk/open-im-server?style=for-the-badge)](https://goreportcard.com/report/github.com/openimsdk/open-im-server)
[![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3) [![Go Reference](https://img.shields.io/badge/Go%20Reference-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://pkg.go.dev/github.com/openimsdk/open-im-server/v3)
[![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE) [![License](https://img.shields.io/badge/license-Apache--2.0-green?style=for-the-badge)](https://github.com/openimsdk/open-im-server/blob/main/LICENSE)
[![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) [![Slack](https://img.shields.io/badge/Slack-500%2B-blueviolet?style=for-the-badge&logo=slack&logoColor=white)](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
[![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045) [![Best Practices](https://img.shields.io/badge/Best%20Practices-purple?style=for-the-badge)](https://www.bestpractices.dev/projects/8045)
[![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22) [![Good First Issues](https://img.shields.io/github/issues/openimsdk/open-im-server/good%20first%20issue?style=for-the-badge&logo=github)](https://github.com/openimsdk/open-im-server/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22good+first+issue%22)
[![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/) [![Language](https://img.shields.io/badge/Language-Go-blue.svg?style=for-the-badge&logo=go&logoColor=white)](https://golang.org/)
<p align="center"> <p align="center">
<a href="../../README.md">English</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
@@ -46,7 +45,6 @@
<a href="./README_tr.md">Türkçe</a> <a href="./README_tr.md">Türkçe</a>
</p> </p>
</div> </div>
</p> </p>
@@ -61,14 +59,14 @@ OpenIM là một nền tảng dịch vụ được thiết kế đặc biệt ch
**OpenIMSDK** là một SDK IM được thiết kế cho **OpenIMServer**, được tạo ra đặc biệt để nhúng vào các ứng dụng khách. Các tính năng chính và các mô-đun của nó như sau: **OpenIMSDK** là một SDK IM được thiết kế cho **OpenIMServer**, được tạo ra đặc biệt để nhúng vào các ứng dụng khách. Các tính năng chính và các mô-đun của nó như sau:
+ 🌟 Các Tính Năng Chính: - 🌟 Các Tính Năng Chính:
- 📦 Lưu trữ cục bộ - 📦 Lưu trữ cục bộ
- 🔔 Gọi lại sự kiện (Listener callbacks) - 🔔 Gọi lại sự kiện (Listener callbacks)
- 🛡️ Bọc API - 🛡️ Bọc API
- 🌐 Quản lý kết nối - 🌐 Quản lý kết nối
+ 📚 Các Mô-đun Chính: - 📚 Các Mô-đun Chính:
1. 🚀 Khởi tạo và Đăng nhập 1. 🚀 Khởi tạo và Đăng nhập
2. 👤 Quản lý Người dùng 2. 👤 Quản lý Người dùng
@@ -82,25 +80,24 @@ Nó được xây dựng bằng Golang và hỗ trợ triển khai đa nền t
## 🌐 Về OpenIMServer ## 🌐 Về OpenIMServer
+ **OpenIMServer** có những đặc điểm sau: - **OpenIMServer** có những đặc điểm sau:
- 🌐 Kiến trúc vi dịch vụ: Hỗ trợ chế độ cluster, bao gồm một gateway và nhiều dịch vụ rpc. - 🌐 Kiến trúc vi dịch vụ: Hỗ trợ chế độ cluster, bao gồm một gateway và nhiều dịch vụ rpc.
- 🚀 Phương pháp triển khai đa dạng: Hỗ trợ triển khai qua mã nguồn, Kubernetes hoặc Docker. - 🚀 Phương pháp triển khai đa dạng: Hỗ trợ triển khai qua mã nguồn, Kubernetes hoặc Docker.
- Hỗ trợ cho cơ sở người dùng lớn: Nhóm siêu lớn với hàng trăm nghìn người dùng, hàng chục triệu người dùng và hàng tỷ tin nhắn. - Hỗ trợ cho cơ sở người dùng lớn: Nhóm siêu lớn với hàng trăm nghìn người dùng, hàng chục triệu người dùng và hàng tỷ tin nhắn.
### Tăng cường Chức năng Kinh doanh: ### Tăng cường Chức năng Kinh doanh:
+ **REST API**: OpenIMServer cung cấp REST APIs cho các hệ thống kinh doanh, nhằm tăng cường khả năng cho doanh nghiệp với nhiều chức năng hơn, như tạo nhóm và gửi tin nhắn đẩy qua giao diện backend. - **REST API**: OpenIMServer cung cấp REST APIs cho các hệ thống kinh doanh, nhằm tăng cường khả năng cho doanh nghiệp với nhiều chức năng hơn, như tạo nhóm và gửi tin nhắn đẩy qua giao diện backend.
+ **Webhooks**: OpenIMServer cung cấp khả năng gọi lại để mở rộng thêm hình thức kinh doanh. Một gọi lại có nghĩa là OpenIMServer gửi một yêu cầu đến máy chủ kinh doanh trước hoặc sau một sự kiện nhất định, giống như gọi lại trước hoặc sau khi gửi một tin nhắn. - **Webhooks**: OpenIMServer cung cấp khả năng gọi lại để mở rộng thêm hình thức kinh doanh. Một gọi lại có nghĩa là OpenIMServer gửi một yêu cầu đến máy chủ kinh doanh trước hoặc sau một sự kiện nhất định, giống như gọi lại trước hoặc sau khi gửi một tin nhắn.
👉 **[Learn more](https://docs.openim.io/guides/introduction/product)** 👉 **[Learn more](https://docs.openim.io/guides/introduction/product)**
## :building_construction: Kiến trúc tổng thể ## :building_construction: Kiến trúc tổng thể
Làm sâu sắc vào trái tim của chức năng Open-IM-Server với sơ đồ kiến trúc của chúng tôi. Làm sâu sắc vào trái tim của chức năng Open-IM-Server với sơ đồ kiến trúc của chúng tôi.
![Overall Architecture](../../docs/images/architecture-layers.png) ![Overall Architecture](../../docs/images/architecture-layers.png)
## :rocket: Bắt đầu nhanh ## :rocket: Bắt đầu nhanh
Chúng tôi hỗ trợ nhiều nền tảng. Dưới đây là các địa chỉ để trải nghiệm nhanh trên phía web Chúng tôi hỗ trợ nhiều nền tảng. Dưới đây là các địa chỉ để trải nghiệm nhanh trên phía web
@@ -109,12 +106,12 @@ Chúng tôi hỗ trợ nhiều nền tảng. Dưới đây là các địa chỉ
🤲 Để tạo thuận lợi cho trải nghiệm người dùng, chúng tôi cung cấp các giải pháp triển khai đa dạng. Bạn có thể chọn phương thức triển khai từ danh sách dưới đây: 🤲 Để tạo thuận lợi cho trải nghiệm người dùng, chúng tôi cung cấp các giải pháp triển khai đa dạng. Bạn có thể chọn phương thức triển khai từ danh sách dưới đây:
+ **[Hướng dẫn Triển khai Mã Nguồn](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)** - **[Hướng dẫn Triển khai Mã Nguồn](https://docs.openim.io/guides/gettingStarted/imSourceCodeDeployment)**
+ **[Hướng dẫn Triển khai Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)** - **[Hướng dẫn Triển khai Docker](https://docs.openim.io/guides/gettingStarted/dockerCompose)**
+ **[Hướng dẫn Triển khai Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)** - **[Hướng dẫn Triển khai Kubernetes](https://docs.openim.io/guides/gettingStarted/k8s-deployment)**
+ **[Hướng dẫn Triển khai cho Nhà Phát Triển Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)** - **[Hướng dẫn Triển khai cho Nhà Phát Triển Mac](https://docs.openim.io/guides/gettingstarted/mac-deployment-guide)**
## :hammer_and_wrench: Để Bắt Đầu Phát Triển OpenIM ## :hammer_and_wrench: Để Bắt Đầu Phát Triển OpenIM
[![Mở trong Dev Contain](https://img.shields.io/static/v1?label=Dev%20Container&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/github/openimsdk/open-im-server) [![Mở trong Dev Contain](https://img.shields.io/static/v1?label=Dev%20Container&message=Open&color=blue&logo=visualstudiocode)](https://vscode.dev/github/openimsdk/open-im-server)
@@ -122,8 +119,7 @@ Mục tiêu của OpenIM là xây dựng một cộng đồng mã nguồn mở c
Nếu bạn muốn đóng góp cho kho lưu trữ Open-IM-Server này, vui lòng đọc [tài liệu hướng dẫn cho người đóng góp](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md). Nếu bạn muốn đóng góp cho kho lưu trữ Open-IM-Server này, vui lòng đọc [tài liệu hướng dẫn cho người đóng góp](https://github.com/openimsdk/open-im-server/blob/main/CONTRIBUTING.md).
Trước khi bạn bắt đầu, hãy chắc chắn rằng các thay đổi của bạn được yêu cầu. Cách tốt nhất là tạo một [cuộc thảo luận mới](https://github.com/openimsdk/open-im-server/discussions/new/choose) hoặc [Giao tiếp Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A), hoặc nếu bạn tìm thấy một vấn đề, [báo cáo nó ](https://github.com/openimsdk/open-im-server/issues/new/choose) trước.
Trước khi bạn bắt đầu, hãy chắc chắn rằng các thay đổi của bạn được yêu cầu. Cách tốt nhất là tạo một [cuộc thảo luận mới](https://github.com/openimsdk/open-im-server/discussions/new/choose) hoặc [Giao tiếp Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q), hoặc nếu bạn tìm thấy một vấn đề, [báo cáo nó ](https://github.com/openimsdk/open-im-server/issues/new/choose) trước.
- [Tham khảo API OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md) - [Tham khảo API OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/api.md)
- [Nhật ký Bash OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md) - [Nhật ký Bash OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/bash-log.md)
@@ -155,19 +151,18 @@ Trước khi bạn bắt đầu, hãy chắc chắn rằng các thay đổi củ
- [Quản lý triển khai và giám sát backend](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md) - [Quản lý triển khai và giám sát backend](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
- [Hướng dẫn Triển khai cho Nhà Phát triển Mac OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md) - [Hướng dẫn Triển khai cho Nhà Phát triển Mac OpenIM](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/mac-developer-deployment-guide.md)
## :busts_in_silhouette: Cộng đồng ## :busts_in_silhouette: Cộng đồng
+ 📚 [Cộng đồng OpenIM](https://github.com/OpenIMSDK/community) - 📚 [Cộng đồng OpenIM](https://github.com/OpenIMSDK/community)
+ 💕 [Nhóm Quan tâm OpenIM](https://github.com/Openim-sigs) - 💕 [Nhóm Quan tâm OpenIM](https://github.com/Openim-sigs)
+ 🚀 [Tham gia cộng đồng Slack của chúng tôi](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) - 🚀 [Tham gia cộng đồng Slack của chúng tôi](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A)
+ :eyes: [Tham gia nhóm WeChat của chúng tôi (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg) - :eyes: [Tham gia nhóm WeChat của chúng tôi (微信群)](https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg)
## :calendar: Cuộc họp Cộng đồng ## :calendar: Cuộc họp Cộng đồng
Chúng tôi muốn bất kỳ ai cũng có thể tham gia cộng đồng và đóng góp mã nguồn, chúng tôi cung cấp quà tặng và phần thưởng, và chúng tôi chào đón bạn tham gia cùng chúng tôi mỗi tối thứ Năm. Chúng tôi muốn bất kỳ ai cũng có thể tham gia cộng đồng và đóng góp mã nguồn, chúng tôi cung cấp quà tặng và phần thưởng, và chúng tôi chào đón bạn tham gia cùng chúng tôi mỗi tối thứ Năm.
Hội nghị của chúng tôi được tổ chức trên Slack của [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q) 🎯, sau đó bạn có thể tìm kiếm pipeline Open-IM-Server để tham gia Hội nghị của chúng tôi được tổ chức trên Slack của [OpenIM Slack](https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A) 🎯, sau đó bạn có thể tìm kiếm pipeline Open-IM-Server để tham gia
Chúng tôi ghi chú mỗi [cuộc họp hai tuần một lần](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) trong [các cuộc thảo luận GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), ghi chú cuộc họp lịch sử của chúng tôi cũng như các bản ghi lại của cuộc họp có sẵn tại [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing). Chúng tôi ghi chú mỗi [cuộc họp hai tuần một lần](https://github.com/orgs/OpenIMSDK/discussions/categories/meeting) trong [các cuộc thảo luận GitHub](https://github.com/openimsdk/open-im-server/discussions/categories/meeting), ghi chú cuộc họp lịch sử của chúng tôi cũng như các bản ghi lại của cuộc họp có sẵn tại [Google Docs :bookmark_tabs:](https://docs.google.com/document/d/1nx8MDpuG74NASx081JcCpxPgDITNTpIIos0DS6Vr9GU/edit?usp=sharing).
+4 -4
View File
@@ -12,8 +12,8 @@ require (
github.com/gorilla/websocket v1.5.1 github.com/gorilla/websocket v1.5.1
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/openimsdk/protocol v0.0.72-alpha.71 github.com/openimsdk/protocol v0.0.73-alpha.12
github.com/openimsdk/tools v0.0.50-alpha.65 github.com/openimsdk/tools v0.0.50-alpha.97
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
@@ -27,7 +27,6 @@ require (
require github.com/google/uuid v1.6.0 require github.com/google/uuid v1.6.0
require ( require (
github.com/IBM/sarama v1.43.0
github.com/fatih/color v1.14.1 github.com/fatih/color v1.14.1
github.com/gin-contrib/gzip v1.0.1 github.com/gin-contrib/gzip v1.0.1
github.com/go-redis/redis v6.15.9+incompatible github.com/go-redis/redis v6.15.9+incompatible
@@ -35,7 +34,7 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/kelindar/bitmap v1.5.2 github.com/kelindar/bitmap v1.5.2
github.com/likexian/gokit v0.25.13 github.com/likexian/gokit v0.25.13
github.com/openimsdk/gomake v0.0.15-alpha.2 github.com/openimsdk/gomake v0.0.15-alpha.11
github.com/redis/go-redis/v9 v9.4.0 github.com/redis/go-redis/v9 v9.4.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
@@ -55,6 +54,7 @@ require (
cloud.google.com/go/iam v1.1.7 // indirect cloud.google.com/go/iam v1.1.7 // indirect
cloud.google.com/go/longrunning v0.5.5 // indirect cloud.google.com/go/longrunning v0.5.5 // indirect
cloud.google.com/go/storage v1.40.0 // indirect cloud.google.com/go/storage v1.40.0 // indirect
github.com/IBM/sarama v1.43.0 // indirect
github.com/MicahParks/keyfunc v1.9.0 // indirect github.com/MicahParks/keyfunc v1.9.0 // indirect
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
github.com/aws/aws-sdk-go-v2 v1.32.5 // indirect github.com/aws/aws-sdk-go-v2 v1.32.5 // indirect
+6 -6
View File
@@ -345,12 +345,12 @@ github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA
github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To=
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= 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.15-alpha.2 h1:5Q8yl8ezy2yx+q8/ucU/t4kJnDfCzNOrkXcDACCqtyM= github.com/openimsdk/gomake v0.0.15-alpha.11 h1:PQudYDRESYeYlUYrrLLJhYIlUPO5x7FAx+o5El9U/Bw=
github.com/openimsdk/gomake v0.0.15-alpha.2/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI= github.com/openimsdk/gomake v0.0.15-alpha.11/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
github.com/openimsdk/protocol v0.0.72-alpha.71 h1:R3utzOlqepaJWTAmnfJi4ccUM/XIoFasSyjQMOipM70= github.com/openimsdk/protocol v0.0.73-alpha.12 h1:2NYawXeHChYUeSme6QJ9pOLh+Empce2WmwEtbP4JvKk=
github.com/openimsdk/protocol v0.0.72-alpha.71/go.mod h1:WF7EuE55vQvpyUAzDXcqg+B+446xQyEba0X35lTINmw= github.com/openimsdk/protocol v0.0.73-alpha.12/go.mod h1:WF7EuE55vQvpyUAzDXcqg+B+446xQyEba0X35lTINmw=
github.com/openimsdk/tools v0.0.50-alpha.65 h1:BRtxkyWxDWPHuHphSwEyHZj7kJSR98am/fHOH84naK8= github.com/openimsdk/tools v0.0.50-alpha.97 h1:6ik5w3PpgDG6VjSo3nb3FT/fxN3JX7iIARVxVu9g7VY=
github.com/openimsdk/tools v0.0.50-alpha.65/go.mod h1:B+oqV0zdewN7OiEHYJm+hW+8/Te7B8tHHgD8rK5ZLZk= github.com/openimsdk/tools v0.0.50-alpha.97/go.mod h1:n2poR3asX1e1XZce4O+MOWAp+X02QJRFvhcLCXZdzRo=
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=
+1 -1
View File
@@ -531,7 +531,7 @@ O:::::::OOO:::::::O p:::::ppppp:::::::pe::::::::e n::::n n::::nII:
# Set text color to yellow for the Slack link # Set text color to yellow for the Slack link
echo -e "\033[1;33m" echo -e "\033[1;33m"
print_with_delay "Join our developer community on Slack: https://join.slack.com/t/openimsdk/shared_invite/zt-22720d66b-o_FvKxMTGXtcnnnHiMqe9Q" 0.01 print_with_delay "Join our developer community on Slack: https://join.slack.com/t/openimsdk/shared_invite/zt-2ijy1ys1f-O0aEDCr7ExRZ7mwsHAVg9A" 0.01
# Reset text color back to normal # Reset text color back to normal
echo -e "\033[0m" echo -e "\033[0m"
+109 -12
View File
@@ -15,6 +15,7 @@ import (
"github.com/openimsdk/tools/apiresp" "github.com/openimsdk/tools/apiresp"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/runtimeenv" "github.com/openimsdk/tools/utils/runtimeenv"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
@@ -30,22 +31,20 @@ type ConfigManager struct {
client *clientv3.Client client *clientv3.Client
configPath string configPath string
runtimeEnv string
} }
func NewConfigManager(IMAdminUserID []string, cfg *config.AllConfig, client *clientv3.Client, configPath string, runtimeEnv string) *ConfigManager { func NewConfigManager(IMAdminUserID []string, cfg *config.AllConfig, client *clientv3.Client, configPath string) *ConfigManager {
cm := &ConfigManager{ cm := &ConfigManager{
imAdminUserID: IMAdminUserID, imAdminUserID: IMAdminUserID,
config: cfg, config: cfg,
client: client, client: client,
configPath: configPath, configPath: configPath,
runtimeEnv: runtimeEnv,
} }
return cm return cm
} }
func (cm *ConfigManager) CheckAdmin(c *gin.Context) { func (cm *ConfigManager) CheckAdmin(c *gin.Context) {
if err := authverify.CheckAdmin(c, cm.imAdminUserID); err != nil { if err := authverify.CheckAdmin(c); err != nil {
apiresp.GinError(c, err) apiresp.GinError(c, err)
c.Abort() c.Abort()
} }
@@ -73,7 +72,7 @@ func (cm *ConfigManager) GetConfig(c *gin.Context) {
func (cm *ConfigManager) GetConfigList(c *gin.Context) { func (cm *ConfigManager) GetConfigList(c *gin.Context) {
var resp apistruct.GetConfigListResp var resp apistruct.GetConfigListResp
resp.ConfigNames = cm.config.GetConfigNames() resp.ConfigNames = cm.config.GetConfigNames()
resp.Environment = runtimeenv.PrintRuntimeEnvironment() resp.Environment = runtimeenv.RuntimeEnvironment()
resp.Version = version.Version resp.Version = version.Version
apiresp.GinSuccess(c, resp) apiresp.GinSuccess(c, resp)
@@ -146,6 +145,110 @@ func (cm *ConfigManager) SetConfig(c *gin.Context) {
apiresp.GinSuccess(c, nil) apiresp.GinSuccess(c, nil)
} }
func (cm *ConfigManager) SetConfigs(c *gin.Context) {
if cm.config.Discovery.Enable != config.ETCD {
apiresp.GinError(c, errs.New("only etcd support set config").Wrap())
return
}
var req apistruct.SetConfigsReq
if err := c.BindJSON(&req); err != nil {
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
return
}
var (
err error
ops []*clientv3.Op
)
for _, cf := range req.Configs {
var op *clientv3.Op
switch cf.ConfigName {
case cm.config.Discovery.GetConfigFileName():
op, err = compareAndOp[config.Discovery](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Kafka.GetConfigFileName():
op, err = compareAndOp[config.Kafka](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.LocalCache.GetConfigFileName():
op, err = compareAndOp[config.LocalCache](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Log.GetConfigFileName():
op, err = compareAndOp[config.Log](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Minio.GetConfigFileName():
op, err = compareAndOp[config.Minio](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Mongo.GetConfigFileName():
op, err = compareAndOp[config.Mongo](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Notification.GetConfigFileName():
op, err = compareAndOp[config.Notification](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.API.GetConfigFileName():
op, err = compareAndOp[config.API](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.CronTask.GetConfigFileName():
op, err = compareAndOp[config.CronTask](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.MsgGateway.GetConfigFileName():
op, err = compareAndOp[config.MsgGateway](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.MsgTransfer.GetConfigFileName():
op, err = compareAndOp[config.MsgTransfer](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Push.GetConfigFileName():
op, err = compareAndOp[config.Push](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Auth.GetConfigFileName():
op, err = compareAndOp[config.Auth](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Conversation.GetConfigFileName():
op, err = compareAndOp[config.Conversation](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Friend.GetConfigFileName():
op, err = compareAndOp[config.Friend](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Group.GetConfigFileName():
op, err = compareAndOp[config.Group](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Msg.GetConfigFileName():
op, err = compareAndOp[config.Msg](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Third.GetConfigFileName():
op, err = compareAndOp[config.Third](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.User.GetConfigFileName():
op, err = compareAndOp[config.User](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Redis.GetConfigFileName():
op, err = compareAndOp[config.Redis](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Share.GetConfigFileName():
op, err = compareAndOp[config.Share](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
case cm.config.Webhooks.GetConfigFileName():
op, err = compareAndOp[config.Webhooks](c, cm.config.Name2Config(cf.ConfigName), &cf, cm)
default:
apiresp.GinError(c, errs.ErrArgs.Wrap())
return
}
if err != nil {
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
return
}
if op != nil {
ops = append(ops, op)
}
}
if len(ops) > 0 {
tx := cm.client.Txn(c)
if _, err = tx.Then(datautil.Batch(func(op *clientv3.Op) clientv3.Op { return *op }, ops)...).Commit(); err != nil {
apiresp.GinError(c, errs.WrapMsg(err, "save to etcd failed"))
return
}
}
apiresp.GinSuccess(c, nil)
}
func compareAndOp[T any](c *gin.Context, old any, req *apistruct.SetConfigReq, cm *ConfigManager) (*clientv3.Op, error) {
conf := new(T)
err := json.Unmarshal([]byte(req.Data), &conf)
if err != nil {
return nil, errs.ErrArgs.WithDetail(err.Error()).Wrap()
}
eq := reflect.DeepEqual(old, conf)
if eq {
return nil, nil
}
data, err := json.Marshal(conf)
if err != nil {
return nil, errs.ErrArgs.WithDetail(err.Error()).Wrap()
}
op := clientv3.OpPut(etcd.BuildKey(req.ConfigName), string(data))
return &op, nil
}
func compareAndSave[T any](c *gin.Context, old any, req *apistruct.SetConfigReq, cm *ConfigManager) error { func compareAndSave[T any](c *gin.Context, old any, req *apistruct.SetConfigReq, cm *ConfigManager) error {
conf := new(T) conf := new(T)
err := json.Unmarshal([]byte(req.Data), &conf) err := json.Unmarshal([]byte(req.Data), &conf)
@@ -209,13 +312,7 @@ func (cm *ConfigManager) resetConfig(c *gin.Context, checkChange bool, ops ...cl
changedKeys := make([]string, 0, len(configMap)) changedKeys := make([]string, 0, len(configMap))
for k, v := range configMap { for k, v := range configMap {
err := config.Load( err := config.Load(cm.configPath, k, config.EnvPrefixMap[k], v.new)
cm.configPath,
k,
config.EnvPrefixMap[k],
cm.runtimeEnv,
v.new,
)
if err != nil { if err != nil {
log.ZError(c, "load config failed", err) log.ZError(c, "load config failed", err)
continue continue
+8 -3
View File
@@ -16,6 +16,7 @@ package api
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/openimsdk/protocol/conversation" "github.com/openimsdk/protocol/conversation"
"github.com/openimsdk/tools/a2r" "github.com/openimsdk/tools/a2r"
) )
@@ -48,9 +49,9 @@ func (o *ConversationApi) SetConversations(c *gin.Context) {
a2r.Call(c, conversation.ConversationClient.SetConversations, o.Client) a2r.Call(c, conversation.ConversationClient.SetConversations, o.Client)
} }
func (o *ConversationApi) GetConversationOfflinePushUserIDs(c *gin.Context) { //func (o *ConversationApi) GetConversationOfflinePushUserIDs(c *gin.Context) {
a2r.Call(c, conversation.ConversationClient.GetConversationOfflinePushUserIDs, o.Client) // a2r.Call(c, conversation.ConversationClient.GetConversationOfflinePushUserIDs, o.Client)
} //}
func (o *ConversationApi) GetFullOwnerConversationIDs(c *gin.Context) { func (o *ConversationApi) GetFullOwnerConversationIDs(c *gin.Context) {
a2r.Call(c, conversation.ConversationClient.GetFullOwnerConversationIDs, o.Client) a2r.Call(c, conversation.ConversationClient.GetFullOwnerConversationIDs, o.Client)
@@ -71,3 +72,7 @@ func (o *ConversationApi) GetNotNotifyConversationIDs(c *gin.Context) {
func (o *ConversationApi) GetPinnedConversationIDs(c *gin.Context) { func (o *ConversationApi) GetPinnedConversationIDs(c *gin.Context) {
a2r.Call(c, conversation.ConversationClient.GetPinnedConversationIDs, o.Client) a2r.Call(c, conversation.ConversationClient.GetPinnedConversationIDs, o.Client)
} }
func (o *ConversationApi) UpdateConversationsByUser(c *gin.Context) {
a2r.Call(c, conversation.ConversationClient.UpdateConversationsByUser, o.Client)
}
+4
View File
@@ -114,3 +114,7 @@ func (o *FriendApi) GetIncrementalBlacks(c *gin.Context) {
func (o *FriendApi) GetFullFriendUserIDs(c *gin.Context) { func (o *FriendApi) GetFullFriendUserIDs(c *gin.Context) {
a2r.Call(c, relation.FriendClient.GetFullFriendUserIDs, o.Client) a2r.Call(c, relation.FriendClient.GetFullFriendUserIDs, o.Client)
} }
func (o *FriendApi) GetSelfUnhandledApplyCount(c *gin.Context) {
a2r.Call(c, relation.FriendClient.GetSelfUnhandledApplyCount, o.Client)
}
+4
View File
@@ -165,3 +165,7 @@ func (o *GroupApi) GetFullGroupMemberUserIDs(c *gin.Context) {
func (o *GroupApi) GetFullJoinGroupIDs(c *gin.Context) { func (o *GroupApi) GetFullJoinGroupIDs(c *gin.Context) {
a2r.Call(c, group.GroupClient.GetFullJoinGroupIDs, o.Client) a2r.Call(c, group.GroupClient.GetFullJoinGroupIDs, o.Client)
} }
func (o *GroupApi) GetGroupApplicationUnhandledCount(c *gin.Context) {
a2r.Call(c, group.GroupClient.GetGroupApplicationUnhandledCount, o.Client)
}
+48 -120
View File
@@ -20,157 +20,85 @@ import (
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"os"
"os/signal"
"strconv" "strconv"
"syscall"
"time" "time"
conf "github.com/openimsdk/open-im-server/v3/pkg/common/config" conf "github.com/openimsdk/open-im-server/v3/pkg/common/config"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discovery" "github.com/openimsdk/tools/discovery"
disetcd "github.com/openimsdk/open-im-server/v3/pkg/common/discovery/etcd"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mw"
"github.com/openimsdk/tools/system/program"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/jsonutil"
"github.com/openimsdk/tools/utils/network" "github.com/openimsdk/tools/utils/network"
"github.com/openimsdk/tools/utils/runtimeenv" "github.com/openimsdk/tools/utils/runtimeenv"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
) )
type Config struct { type Config struct {
*conf.AllConfig conf.AllConfig
RuntimeEnv string ConfigPath conf.Path
ConfigPath string Index conf.Index
} }
func Start(ctx context.Context, index int, config *Config) error { func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, service grpc.ServiceRegistrar) error {
apiPort, err := datautil.GetElemByIndex(config.API.Api.Ports, index) apiPort, err := datautil.GetElemByIndex(config.API.Api.Ports, int(config.Index))
if err != nil { if err != nil {
return err return err
} }
config.RuntimeEnv = runtimeenv.PrintRuntimeEnvironment()
client, err := kdisc.NewDiscoveryRegister(&config.Discovery, config.RuntimeEnv, []string{
config.Discovery.RpcService.MessageGateway,
})
if err != nil {
return errs.WrapMsg(err, "failed to register discovery service")
}
client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin")))
var (
netDone = make(chan struct{}, 1)
netErr error
prometheusPort int
)
registerIP, err := network.GetRpcRegisterIP("")
if err != nil {
return err
}
getAutoPort := func() (net.Listener, int, error) {
registerAddr := net.JoinHostPort(registerIP, "0")
listener, err := net.Listen("tcp", registerAddr)
if err != nil {
return nil, 0, errs.WrapMsg(err, "listen err", "registerAddr", registerAddr)
}
_, portStr, _ := net.SplitHostPort(listener.Addr().String())
port, _ := strconv.Atoi(portStr)
return listener, port, nil
}
if config.API.Prometheus.AutoSetPorts && config.Discovery.Enable != conf.ETCD {
return errs.New("only etcd support autoSetPorts", "RegisterName", "api").Wrap()
}
router, err := newGinRouter(ctx, client, config) router, err := newGinRouter(ctx, client, config)
if err != nil { if err != nil {
return err return err
} }
if config.API.Prometheus.Enable {
var (
listener net.Listener
)
if config.API.Prometheus.AutoSetPorts { apiCtx, apiCancel := context.WithCancelCause(context.Background())
listener, prometheusPort, err = getAutoPort() done := make(chan struct{})
if err != nil { go func() {
return err httpServer := &http.Server{
} Handler: router,
Addr: net.JoinHostPort(network.GetListenIP(config.API.Api.ListenIP), strconv.Itoa(apiPort)),
etcdClient := client.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
_, err = etcdClient.Put(ctx, prommetrics.BuildDiscoveryKey(prommetrics.APIKeyName), jsonutil.StructToJsonString(prommetrics.BuildDefaultTarget(registerIP, prometheusPort)))
if err != nil {
return errs.WrapMsg(err, "etcd put err")
}
} else {
prometheusPort, err = datautil.GetElemByIndex(config.API.Prometheus.Ports, index)
if err != nil {
return err
}
listener, err = net.Listen("tcp", fmt.Sprintf(":%d", prometheusPort))
if err != nil {
return errs.WrapMsg(err, "listen err", "addr", fmt.Sprintf(":%d", prometheusPort))
}
} }
go func() { go func() {
if err := prommetrics.ApiInit(listener); err != nil && !errors.Is(err, http.ErrServerClosed) { defer close(done)
netErr = errs.WrapMsg(err, fmt.Sprintf("api prometheus start err: %d", prometheusPort)) select {
netDone <- struct{}{} case <-ctx.Done():
apiCancel(fmt.Errorf("recv ctx %w", context.Cause(ctx)))
case <-apiCtx.Done():
}
log.ZDebug(ctx, "api server is shutting down")
if err := httpServer.Shutdown(context.Background()); err != nil {
log.ZWarn(ctx, "api server shutdown err", err)
} }
}() }()
log.CInfo(ctx, "api server is init", "runtimeEnv", runtimeenv.RuntimeEnvironment(), "address", httpServer.Addr, "apiPort", apiPort)
} err := httpServer.ListenAndServe()
address := net.JoinHostPort(network.GetListenIP(config.API.Api.ListenIP), strconv.Itoa(apiPort)) if err == nil {
err = errors.New("api done")
server := http.Server{Addr: address, Handler: router}
log.CInfo(ctx, "API server is initializing", "runtimeEnv", config.RuntimeEnv, "address", address, "apiPort", apiPort, "prometheusPort", prometheusPort)
go func() {
err = server.ListenAndServe()
if err != nil && !errors.Is(err, http.ErrServerClosed) {
netErr = errs.WrapMsg(err, fmt.Sprintf("api start err: %s", server.Addr))
netDone <- struct{}{}
} }
apiCancel(err)
}() }()
if config.Discovery.Enable == conf.ETCD { //if config.Discovery.Enable == conf.ETCD {
cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), config.GetConfigNames()) // cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), config.GetConfigNames())
cm.Watch(ctx) // cm.Watch(ctx)
} //}
//sigs := make(chan os.Signal, 1)
sigs := make(chan os.Signal, 1) //signal.Notify(sigs, syscall.SIGTERM)
signal.Notify(sigs, syscall.SIGTERM) //select {
//case val := <-sigs:
shutdown := func() error { // log.ZDebug(ctx, "recv exit", "signal", val.String())
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) // cancel(fmt.Errorf("signal %s", val.String()))
defer cancel() //case <-ctx.Done():
err := server.Shutdown(ctx) //}
if err != nil { <-apiCtx.Done()
return errs.WrapMsg(err, "shutdown err") exitCause := context.Cause(apiCtx)
} log.ZWarn(ctx, "api server exit", exitCause)
return nil timer := time.NewTimer(time.Second * 15)
} defer timer.Stop()
disetcd.RegisterShutDown(shutdown)
select { select {
case <-sigs: case <-timer.C:
program.SIGTERMExit() log.ZWarn(ctx, "api server graceful stop timeout", nil)
if err := shutdown(); err != nil { case <-done:
return err log.ZDebug(ctx, "api server graceful stop done")
}
case <-netDone:
close(netDone)
return netErr
} }
return nil return exitCause
} }
+59 -21
View File
@@ -2,10 +2,14 @@ package jssdk
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"sort" "sort"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/protocol/constant"
"github.com/openimsdk/tools/log"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/openimsdk/protocol/conversation" "github.com/openimsdk/protocol/conversation"
"github.com/openimsdk/protocol/jssdk" "github.com/openimsdk/protocol/jssdk"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
@@ -109,10 +113,7 @@ func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActive
if len(conversationIDs) == 0 { if len(conversationIDs) == 0 {
return &jssdk.GetActiveConversationsResp{}, nil return &jssdk.GetActiveConversationsResp{}, nil
} }
readSeq, err := x.msgClient.GetHasReadSeqs(ctx, conversationIDs, req.OwnerUserID)
if err != nil {
return nil, err
}
activeConversation, err := x.msgClient.GetActiveConversation(ctx, conversationIDs) activeConversation, err := x.msgClient.GetActiveConversation(ctx, conversationIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -120,6 +121,10 @@ func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActive
if len(activeConversation) == 0 { if len(activeConversation) == 0 {
return &jssdk.GetActiveConversationsResp{}, nil return &jssdk.GetActiveConversationsResp{}, nil
} }
readSeq, err := x.msgClient.GetHasReadSeqs(ctx, conversationIDs, req.OwnerUserID)
if err != nil {
return nil, err
}
sortConversations := sortActiveConversations{ sortConversations := sortActiveConversations{
Conversation: activeConversation, Conversation: activeConversation,
} }
@@ -147,6 +152,7 @@ func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActive
if err != nil { if err != nil {
return nil, err return nil, err
} }
x.checkMessagesAndGetLastMessage(ctx, req.OwnerUserID, msgs)
conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string { conversationMap := datautil.SliceToMap(conversations, func(c *conversation.Conversation) string {
return c.ConversationID return c.ConversationID
}) })
@@ -156,16 +162,15 @@ func (x *JSSdk) getActiveConversations(ctx context.Context, req *jssdk.GetActive
if !ok { if !ok {
continue continue
} }
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] resp = append(resp, &jssdk.ConversationMsg{
Conversation: conv,
LastMsg: msgList.Msgs[0],
MaxSeq: c.MaxSeq,
ReadSeq: readSeq[c.ConversationID],
})
} }
resp = append(resp, &jssdk.ConversationMsg{
Conversation: conv,
LastMsg: lastMsg,
MaxSeq: c.MaxSeq,
ReadSeq: readSeq[c.ConversationID],
})
} }
if err := x.fillConversations(ctx, resp); err != nil { if err := x.fillConversations(ctx, resp); err != nil {
return nil, err return nil, err
@@ -219,18 +224,18 @@ func (x *JSSdk) getConversations(ctx context.Context, req *jssdk.GetConversation
return nil, err return nil, err
} }
} }
x.checkMessagesAndGetLastMessage(ctx, req.OwnerUserID, msgs)
resp := make([]*jssdk.ConversationMsg, 0, len(conversations)) resp := make([]*jssdk.ConversationMsg, 0, len(conversations))
for _, c := range conversations { for _, c := range conversations {
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] resp = append(resp, &jssdk.ConversationMsg{
Conversation: c,
LastMsg: msgList.Msgs[0],
MaxSeq: maxSeqs[c.ConversationID],
ReadSeq: readSeqs[c.ConversationID],
})
} }
resp = append(resp, &jssdk.ConversationMsg{
Conversation: c,
LastMsg: lastMsg,
MaxSeq: maxSeqs[c.ConversationID],
ReadSeq: readSeqs[c.ConversationID],
})
} }
if err := x.fillConversations(ctx, resp); err != nil { if err := x.fillConversations(ctx, resp); err != nil {
return nil, err return nil, err
@@ -247,3 +252,36 @@ func (x *JSSdk) getConversations(ctx context.Context, req *jssdk.GetConversation
UnreadCount: unreadCount, UnreadCount: unreadCount,
}, nil }, nil
} }
// This function checks whether the latest MaxSeq message is valid.
// If not, it needs to fetch a valid message again.
func (x *JSSdk) checkMessagesAndGetLastMessage(ctx context.Context, userID string, messages map[string]*sdkws.PullMsgs) {
var conversationIDs []string
for conversationID, message := range messages {
allInValid := true
for _, data := range message.Msgs {
if data.Status < constant.MsgStatusHasDeleted {
allInValid = false
break
}
}
if allInValid {
conversationIDs = append(conversationIDs, conversationID)
}
}
if len(conversationIDs) > 0 {
resp, err := x.msgClient.GetLastMessage(ctx, &msg.GetLastMessageReq{
UserID: userID,
ConversationIDs: conversationIDs,
})
if err != nil {
log.ZError(ctx, "fetchLatestValidMessages", err, "conversationIDs", conversationIDs)
return
}
for conversationID, message := range resp.Msgs {
messages[conversationID] = &sdkws.PullMsgs{Msgs: []*sdkws.MsgData{message}}
}
}
}
+216 -42
View File
@@ -15,12 +15,19 @@
package api package api
import ( import (
"encoding/base64"
"encoding/json"
"sync"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"google.golang.org/protobuf/reflect/protoreflect"
"github.com/openimsdk/open-im-server/v3/pkg/apistruct" "github.com/openimsdk/open-im-server/v3/pkg/apistruct"
"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/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
@@ -36,6 +43,39 @@ import (
"github.com/openimsdk/tools/utils/timeutil" "github.com/openimsdk/tools/utils/timeutil"
) )
var (
msgDataDescriptor []protoreflect.FieldDescriptor
msgDataDescriptorOnce sync.Once
)
func getMsgDataDescriptor() []protoreflect.FieldDescriptor {
msgDataDescriptorOnce.Do(func() {
skip := make(map[string]struct{})
respFields := new(msg.SendMsgResp).ProtoReflect().Descriptor().Fields()
for i := 0; i < respFields.Len(); i++ {
field := respFields.Get(i)
if !field.HasJSONName() {
continue
}
skip[field.JSONName()] = struct{}{}
}
fields := new(sdkws.MsgData).ProtoReflect().Descriptor().Fields()
num := fields.Len()
msgDataDescriptor = make([]protoreflect.FieldDescriptor, 0, num)
for i := 0; i < num; i++ {
field := fields.Get(i)
if !field.HasJSONName() {
continue
}
if _, ok := skip[field.JSONName()]; ok {
continue
}
msgDataDescriptor = append(msgDataDescriptor, fields.Get(i))
}
})
return msgDataDescriptor
}
type MessageApi struct { type MessageApi struct {
Client msg.MsgClient Client msg.MsgClient
userClient *rpcli.UserClient userClient *rpcli.UserClient
@@ -54,7 +94,22 @@ func (*MessageApi) SetOptions(options map[string]bool, value bool) {
datautil.SetSwitchFromOptions(options, constant.IsConversationUpdate, value) datautil.SetSwitchFromOptions(options, constant.IsConversationUpdate, value)
} }
func (m *MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg) *msg.SendMsgReq { func (m *MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg, data any) *msg.SendMsgReq {
msgData := &sdkws.MsgData{
SendID: params.SendID,
GroupID: params.GroupID,
ClientMsgID: idutil.GetMsgIDByMD5(params.SendID),
SenderPlatformID: params.SenderPlatformID,
SenderNickname: params.SenderNickname,
SenderFaceURL: params.SenderFaceURL,
SessionType: params.SessionType,
MsgFrom: constant.SysMsgType,
ContentType: params.ContentType,
CreateTime: timeutil.GetCurrentTimestampByMill(),
SendTime: params.SendTime,
OfflinePushInfo: params.OfflinePushInfo,
Ex: params.Ex,
}
var newContent string var newContent string
options := make(map[string]bool, 5) options := make(map[string]bool, 5)
switch params.ContentType { switch params.ContentType {
@@ -64,6 +119,11 @@ func (m *MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg
newContent = jsonutil.StructToJsonString(&notification) newContent = jsonutil.StructToJsonString(&notification)
case constant.Text: case constant.Text:
fallthrough fallthrough
case constant.AtText:
if atElem, ok := data.(*apistruct.AtElem); ok {
msgData.AtUserIDList = atElem.AtUserList
}
fallthrough
case constant.Picture: case constant.Picture:
fallthrough fallthrough
case constant.Custom: case constant.Custom:
@@ -83,24 +143,10 @@ func (m *MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg
if params.NotOfflinePush { if params.NotOfflinePush {
datautil.SetSwitchFromOptions(options, constant.IsOfflinePush, false) datautil.SetSwitchFromOptions(options, constant.IsOfflinePush, false)
} }
msgData.Content = []byte(newContent)
msgData.Options = options
pbData := msg.SendMsgReq{ pbData := msg.SendMsgReq{
MsgData: &sdkws.MsgData{ MsgData: msgData,
SendID: params.SendID,
GroupID: params.GroupID,
ClientMsgID: idutil.GetMsgIDByMD5(params.SendID),
SenderPlatformID: params.SenderPlatformID,
SenderNickname: params.SenderNickname,
SenderFaceURL: params.SenderFaceURL,
SessionType: params.SessionType,
MsgFrom: constant.SysMsgType,
ContentType: params.ContentType,
Content: []byte(newContent),
CreateTime: timeutil.GetCurrentTimestampByMill(),
SendTime: params.SendTime,
Options: options,
OfflinePushInfo: params.OfflinePushInfo,
Ex: params.Ex,
},
} }
return &pbData return &pbData
} }
@@ -158,21 +204,25 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM
log.ZDebug(c, "getSendMsgReq", "req", req.Content) log.ZDebug(c, "getSendMsgReq", "req", req.Content)
switch req.ContentType { switch req.ContentType {
case constant.Text: case constant.Text:
data = apistruct.TextElem{} data = &apistruct.TextElem{}
case constant.Picture: case constant.Picture:
data = apistruct.PictureElem{} data = &apistruct.PictureElem{}
case constant.Voice: case constant.Voice:
data = apistruct.SoundElem{} data = &apistruct.SoundElem{}
case constant.Video: case constant.Video:
data = apistruct.VideoElem{} data = &apistruct.VideoElem{}
case constant.File: case constant.File:
data = apistruct.FileElem{} data = &apistruct.FileElem{}
case constant.AtText: case constant.AtText:
data = apistruct.AtElem{} data = &apistruct.AtElem{}
case constant.Custom: case constant.Custom:
data = apistruct.CustomElem{} data = &apistruct.CustomElem{}
case constant.MarkdownText:
data = &apistruct.MarkdownTextElem{}
case constant.Quote:
data = &apistruct.QuoteElem{}
case constant.OANotification: case constant.OANotification:
data = apistruct.OANotificationElem{} data = &apistruct.OANotificationElem{}
req.SessionType = constant.NotificationChatType req.SessionType = constant.NotificationChatType
if err = m.userClient.GetNotificationByID(c, req.SendID); err != nil { if err = m.userClient.GetNotificationByID(c, req.SendID); err != nil {
return nil, err return nil, err
@@ -180,14 +230,50 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM
default: default:
return nil, errs.WrapMsg(errs.ErrArgs, "unsupported content type", "contentType", req.ContentType) return nil, errs.WrapMsg(errs.ErrArgs, "unsupported content type", "contentType", req.ContentType)
} }
if err := mapstructure.WeakDecode(req.Content, &data); err != nil { if err := mapstructure.WeakDecode(req.Content, data); err != nil {
return nil, errs.WrapMsg(err, "failed to decode message content") return nil, errs.WrapMsg(err, "failed to decode message content")
} }
log.ZDebug(c, "getSendMsgReq", "decodedContent", data) log.ZDebug(c, "getSendMsgReq", "decodedContent", data)
if err := m.validate.Struct(data); err != nil { if err := m.validate.Struct(data); err != nil {
return nil, errs.WrapMsg(err, "validation error") return nil, errs.WrapMsg(err, "validation error")
} }
return m.newUserSendMsgReq(c, &req), nil return m.newUserSendMsgReq(c, &req, data), nil
}
func (m *MessageApi) getModifyFields(req, respModify *sdkws.MsgData) map[string]any {
if req == nil || respModify == nil {
return nil
}
fields := make(map[string]any)
reqProtoReflect := req.ProtoReflect()
respProtoReflect := respModify.ProtoReflect()
for _, descriptor := range getMsgDataDescriptor() {
reqValue := reqProtoReflect.Get(descriptor)
respValue := respProtoReflect.Get(descriptor)
if !reqValue.Equal(respValue) {
val := respValue.Interface()
name := descriptor.JSONName()
if name == "content" {
if bs, ok := val.([]byte); ok {
val = string(bs)
}
}
fields[name] = val
}
}
if len(fields) == 0 {
fields = nil
}
return fields
}
func (m *MessageApi) ginRespSendMsg(c *gin.Context, req *msg.SendMsgReq, resp *msg.SendMsgResp) {
res := m.getModifyFields(req.MsgData, resp.Modify)
resp.Modify = nil
apiresp.GinSuccess(c, &apistruct.SendMsgResp{
SendMsgResp: resp,
Modify: res,
})
} }
// SendMessage handles the sending of a message. It's an HTTP handler function to be used with Gin framework. // SendMessage handles the sending of a message. It's an HTTP handler function to be used with Gin framework.
@@ -203,7 +289,7 @@ func (m *MessageApi) SendMessage(c *gin.Context) {
} }
// Check if the user has the app manager role. // Check if the user has the app manager role.
if !authverify.IsAppManagerUid(c, m.imAdminUserID) { if !authverify.IsAdmin(c) {
// Respond with a permission error if the user is not an app manager. // Respond with a permission error if the user is not an app manager.
apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message")) apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message"))
return return
@@ -243,7 +329,7 @@ func (m *MessageApi) SendMessage(c *gin.Context) {
} }
// Respond with a success message and the response payload. // Respond with a success message and the response payload.
apiresp.GinSuccess(c, respPb) m.ginRespSendMsg(c, sendMsgReq, respPb)
} }
func (m *MessageApi) SendBusinessNotification(c *gin.Context) { func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
@@ -277,7 +363,7 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
if req.ReliabilityLevel == nil { if req.ReliabilityLevel == nil {
req.ReliabilityLevel = datautil.ToPtr(1) req.ReliabilityLevel = datautil.ToPtr(1)
} }
if !authverify.IsAppManagerUid(c, m.imAdminUserID) { if !authverify.IsAdmin(c) {
apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message")) apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message"))
return return
} }
@@ -301,7 +387,7 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
IsSendMsg: req.SendMsg, IsSendMsg: req.SendMsg,
ReliabilityLevel: *req.ReliabilityLevel, ReliabilityLevel: *req.ReliabilityLevel,
UnreadCount: false, UnreadCount: false,
}), }, nil),
}, },
} }
respPb, err := m.Client.SendMsg(c, &sendMsgReq) respPb, err := m.Client.SendMsg(c, &sendMsgReq)
@@ -309,7 +395,7 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
apiresp.GinError(c, err) apiresp.GinError(c, err)
return return
} }
apiresp.GinSuccess(c, respPb) m.ginRespSendMsg(c, &sendMsgReq, respPb)
} }
func (m *MessageApi) BatchSendMsg(c *gin.Context) { func (m *MessageApi) BatchSendMsg(c *gin.Context) {
@@ -321,7 +407,7 @@ func (m *MessageApi) BatchSendMsg(c *gin.Context) {
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap()) apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
return return
} }
if err := authverify.CheckAdmin(c, m.imAdminUserID); err != nil { if err := authverify.CheckAdmin(c); err != nil {
apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message")) apiresp.GinError(c, errs.ErrNoPermission.WrapMsg("only app manager can send message"))
return return
} }
@@ -363,11 +449,107 @@ func (m *MessageApi) BatchSendMsg(c *gin.Context) {
ClientMsgID: rpcResp.ClientMsgID, ClientMsgID: rpcResp.ClientMsgID,
SendTime: rpcResp.SendTime, SendTime: rpcResp.SendTime,
RecvID: recvID, RecvID: recvID,
Modify: m.getModifyFields(sendMsgReq.MsgData, rpcResp.Modify),
}) })
} }
apiresp.GinSuccess(c, resp) apiresp.GinSuccess(c, resp)
} }
func (m *MessageApi) SendSimpleMessage(c *gin.Context) {
encodedKey, ok := c.GetQuery(webhook.Key)
if !ok {
apiresp.GinError(c, errs.ErrArgs.WithDetail("missing key in query").Wrap())
return
}
decodedData, err := base64.StdEncoding.DecodeString(encodedKey)
if err != nil {
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
return
}
var (
req apistruct.SendSingleMsgReq
keyMsgData apistruct.KeyMsgData
sendID string
sessionType int32
recvID string
)
if err = c.BindJSON(&req); err != nil {
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
return
}
err = json.Unmarshal(decodedData, &keyMsgData)
if err != nil {
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
return
}
if keyMsgData.GroupID != "" {
sessionType = constant.ReadGroupChatType
sendID = req.SendID
} else {
sessionType = constant.SingleChatType
sendID = keyMsgData.RecvID
recvID = keyMsgData.SendID
}
// check param
if keyMsgData.SendID == "" {
apiresp.GinError(c, errs.ErrArgs.WithDetail("missing recvID or GroupID").Wrap())
return
}
if sendID == "" {
apiresp.GinError(c, errs.ErrArgs.WithDetail("missing sendID").Wrap())
return
}
content, err := jsonutil.JsonMarshal(apistruct.MarkdownTextElem{Content: req.Content})
if err != nil {
apiresp.GinError(c, errs.Wrap(err))
return
}
msgData := &sdkws.MsgData{
SendID: sendID,
RecvID: recvID,
GroupID: keyMsgData.GroupID,
ClientMsgID: idutil.GetMsgIDByMD5(sendID),
SenderPlatformID: constant.AdminPlatformID,
SessionType: sessionType,
MsgFrom: constant.UserMsgType,
ContentType: constant.MarkdownText,
Content: content,
OfflinePushInfo: req.OfflinePushInfo,
Ex: req.Ex,
}
sendReq := &msg.SendSimpleMsgReq{
MsgData: msgData,
}
respPb, err := m.Client.SendSimpleMsg(c, sendReq)
if err != nil {
apiresp.GinError(c, err)
return
}
var status = constant.MsgSendSuccessed
_, err = m.Client.SetSendMsgStatus(c, &msg.SetSendMsgStatusReq{
Status: int32(status),
})
if err != nil {
apiresp.GinError(c, err)
return
}
m.ginRespSendMsg(c, &msg.SendMsgReq{MsgData: sendReq.MsgData}, &msg.SendMsgResp{
ServerMsgID: respPb.ServerMsgID,
ClientMsgID: respPb.ClientMsgID,
SendTime: respPb.SendTime,
Modify: respPb.Modify,
})
}
func (m *MessageApi) CheckMsgIsSendSuccess(c *gin.Context) { func (m *MessageApi) CheckMsgIsSendSuccess(c *gin.Context) {
a2r.Call(c, msg.MsgClient.GetSendMsgStatus, m.Client) a2r.Call(c, msg.MsgClient.GetSendMsgStatus, m.Client)
} }
@@ -391,11 +573,3 @@ func (m *MessageApi) SearchMsg(c *gin.Context) {
func (m *MessageApi) GetServerTime(c *gin.Context) { func (m *MessageApi) GetServerTime(c *gin.Context) {
a2r.Call(c, msg.MsgClient.GetServerTime, m.Client) a2r.Call(c, msg.MsgClient.GetServerTime, m.Client)
} }
func (m *MessageApi) GetStreamMsg(c *gin.Context) {
a2r.Call(c, msg.MsgClient.GetServerTime, m.Client)
}
func (m *MessageApi) AppendStreamMsg(c *gin.Context) {
a2r.Call(c, msg.MsgClient.GetServerTime, m.Client)
}
+24 -39
View File
@@ -2,71 +2,56 @@ package api
import ( import (
"encoding/json" "encoding/json"
"errors"
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
conf "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"
"github.com/openimsdk/tools/apiresp" "github.com/openimsdk/tools/apiresp"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
clientv3 "go.etcd.io/etcd/client/v3"
) )
type PrometheusDiscoveryApi struct { type PrometheusDiscoveryApi struct {
config *Config config *Config
client *clientv3.Client kv discovery.KeyValue
} }
func NewPrometheusDiscoveryApi(config *Config, client discovery.SvcDiscoveryRegistry) *PrometheusDiscoveryApi { func NewPrometheusDiscoveryApi(config *Config, client discovery.SvcDiscoveryRegistry) *PrometheusDiscoveryApi {
api := &PrometheusDiscoveryApi{ api := &PrometheusDiscoveryApi{
config: config, config: config,
} kv: client,
if config.Discovery.Enable == conf.ETCD {
api.client = client.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
} }
return api return api
} }
func (p *PrometheusDiscoveryApi) Enable(c *gin.Context) {
if p.config.Discovery.Enable != conf.ETCD {
c.JSON(http.StatusOK, []struct{}{})
c.Abort()
}
}
func (p *PrometheusDiscoveryApi) discovery(c *gin.Context, key string) { func (p *PrometheusDiscoveryApi) discovery(c *gin.Context, key string) {
eResp, err := p.client.Get(c, prommetrics.BuildDiscoveryKey(key)) value, err := p.kv.GetKeyWithPrefix(c, prommetrics.BuildDiscoveryKeyPrefix(key))
if err != nil { if err != nil {
// Log and respond with an error if preparation fails. if errors.Is(err, discovery.ErrNotSupported) {
apiresp.GinError(c, errs.WrapMsg(err, "etcd get err")) c.JSON(http.StatusOK, []struct{}{})
return
}
apiresp.GinError(c, errs.WrapMsg(err, "get key value"))
return return
} }
if len(eResp.Kvs) == 0 { if len(value) == 0 {
c.JSON(http.StatusOK, []*prommetrics.Target{}) c.JSON(http.StatusOK, []*prommetrics.RespTarget{})
return
}
var resp prommetrics.RespTarget
for i := range value {
var tmp prommetrics.Target
if err = json.Unmarshal(value[i], &tmp); err != nil {
apiresp.GinError(c, errs.WrapMsg(err, "json unmarshal err"))
return
}
resp.Targets = append(resp.Targets, tmp.Target)
resp.Labels = tmp.Labels // default label is fixed. See prommetrics.BuildDefaultTarget
} }
var ( c.JSON(http.StatusOK, []*prommetrics.RespTarget{&resp})
resp = &prommetrics.RespTarget{
Targets: make([]string, 0, len(eResp.Kvs)),
}
)
for i := range eResp.Kvs {
var target prommetrics.Target
err = json.Unmarshal(eResp.Kvs[i].Value, &target)
if err != nil {
log.ZError(c, "prometheus unmarshal err", errs.Wrap(err))
}
resp.Targets = append(resp.Targets, target.Target)
if resp.Labels == nil {
resp.Labels = target.Labels
}
}
c.JSON(200, []*prommetrics.RespTarget{resp})
} }
func (p *PrometheusDiscoveryApi) Api(c *gin.Context) { func (p *PrometheusDiscoveryApi) Api(c *gin.Context) {
+27 -9
View File
@@ -10,6 +10,7 @@ import (
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/openimsdk/open-im-server/v3/internal/api/jssdk" "github.com/openimsdk/open-im-server/v3/internal/api/jssdk"
"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/config"
"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/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
@@ -27,6 +28,7 @@ import (
"github.com/openimsdk/tools/discovery/etcd" "github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mw" "github.com/openimsdk/tools/mw"
"github.com/openimsdk/tools/mw/api"
clientv3 "go.etcd.io/etcd/client/v3" clientv3 "go.etcd.io/etcd/client/v3"
) )
@@ -95,8 +97,13 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
case BestSpeed: case BestSpeed:
r.Use(gzip.Gzip(gzip.BestSpeed)) r.Use(gzip.Gzip(gzip.BestSpeed))
} }
r.Use(prommetricsGin(), gin.RecoveryWithWriter(gin.DefaultErrorWriter, mw.GinPanicErr), mw.CorsHandler(), if config.Standalone() {
mw.GinParseOperationID(), GinParseToken(rpcli.NewAuthClient(authConn))) r.Use(func(c *gin.Context) {
c.Set(authverify.CtxAdminUserIDsKey, cfg.Share.IMAdminUser.UserIDs)
})
}
r.Use(api.GinLogger(), prommetricsGin(), gin.RecoveryWithWriter(gin.DefaultErrorWriter, mw.GinPanicErr), mw.CorsHandler(),
mw.GinParseOperationID(), GinParseToken(rpcli.NewAuthClient(authConn)), setGinIsAdmin(cfg.Share.IMAdminUser.UserIDs))
u := NewUserApi(user.NewUserClient(userConn), client, cfg.Discovery.RpcService) u := NewUserApi(user.NewUserClient(userConn), client, cfg.Discovery.RpcService)
{ {
@@ -124,6 +131,11 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
userRouterGroup.POST("/add_notification_account", u.AddNotificationAccount) userRouterGroup.POST("/add_notification_account", u.AddNotificationAccount)
userRouterGroup.POST("/update_notification_account", u.UpdateNotificationAccountInfo) userRouterGroup.POST("/update_notification_account", u.UpdateNotificationAccountInfo)
userRouterGroup.POST("/search_notification_account", u.SearchNotificationAccount) userRouterGroup.POST("/search_notification_account", u.SearchNotificationAccount)
userRouterGroup.POST("/get_user_client_config", u.GetUserClientConfig)
userRouterGroup.POST("/set_user_client_config", u.SetUserClientConfig)
userRouterGroup.POST("/del_user_client_config", u.DelUserClientConfig)
userRouterGroup.POST("/page_user_client_config", u.PageUserClientConfig)
} }
// friend routing group // friend routing group
{ {
@@ -150,6 +162,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
friendRouterGroup.POST("/update_friends", f.UpdateFriends) friendRouterGroup.POST("/update_friends", f.UpdateFriends)
friendRouterGroup.POST("/get_incremental_friends", f.GetIncrementalFriends) friendRouterGroup.POST("/get_incremental_friends", f.GetIncrementalFriends)
friendRouterGroup.POST("/get_full_friend_user_ids", f.GetFullFriendUserIDs) friendRouterGroup.POST("/get_full_friend_user_ids", f.GetFullFriendUserIDs)
friendRouterGroup.POST("/get_self_unhandled_apply_count", f.GetSelfUnhandledApplyCount)
} }
g := NewGroupApi(group.NewGroupClient(groupConn)) g := NewGroupApi(group.NewGroupClient(groupConn))
@@ -186,6 +199,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
groupRouterGroup.POST("/get_incremental_group_members_batch", g.GetIncrementalGroupMemberBatch) groupRouterGroup.POST("/get_incremental_group_members_batch", g.GetIncrementalGroupMemberBatch)
groupRouterGroup.POST("/get_full_group_member_user_ids", g.GetFullGroupMemberUserIDs) groupRouterGroup.POST("/get_full_group_member_user_ids", g.GetFullGroupMemberUserIDs)
groupRouterGroup.POST("/get_full_join_group_ids", g.GetFullJoinGroupIDs) groupRouterGroup.POST("/get_full_join_group_ids", g.GetFullJoinGroupIDs)
groupRouterGroup.POST("/get_group_application_unhandled_count", g.GetGroupApplicationUnhandledCount)
} }
// certificate // certificate
{ {
@@ -223,7 +237,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
objectGroup.GET("/*name", t.ObjectRedirect) objectGroup.GET("/*name", t.ObjectRedirect)
} }
// Message // Message
m := NewMessageApi(msg.NewMsgClient(msgConn), rpcli.NewUserClient(userConn), cfg.Share.IMAdminUserID) m := NewMessageApi(msg.NewMsgClient(msgConn), rpcli.NewUserClient(userConn), cfg.Share.IMAdminUser.UserIDs)
{ {
msgGroup := r.Group("/msg") msgGroup := r.Group("/msg")
msgGroup.POST("/newest_seq", m.GetSeq) msgGroup.POST("/newest_seq", m.GetSeq)
@@ -244,10 +258,9 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
msgGroup.POST("/delete_msg_physical", m.DeleteMsgPhysical) msgGroup.POST("/delete_msg_physical", m.DeleteMsgPhysical)
msgGroup.POST("/batch_send_msg", m.BatchSendMsg) msgGroup.POST("/batch_send_msg", m.BatchSendMsg)
msgGroup.POST("/send_simple_msg", m.SendSimpleMessage)
msgGroup.POST("/check_msg_is_send_success", m.CheckMsgIsSendSuccess) msgGroup.POST("/check_msg_is_send_success", m.CheckMsgIsSendSuccess)
msgGroup.POST("/get_server_time", m.GetServerTime) msgGroup.POST("/get_server_time", m.GetServerTime)
msgGroup.POST("/get_stream_msg", m.GetStreamMsg)
msgGroup.POST("/append_stream_msg", m.AppendStreamMsg)
} }
// Conversation // Conversation
{ {
@@ -258,7 +271,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
conversationGroup.POST("/get_conversation", c.GetConversation) conversationGroup.POST("/get_conversation", c.GetConversation)
conversationGroup.POST("/get_conversations", c.GetConversations) conversationGroup.POST("/get_conversations", c.GetConversations)
conversationGroup.POST("/set_conversations", c.SetConversations) conversationGroup.POST("/set_conversations", c.SetConversations)
conversationGroup.POST("/get_conversation_offline_push_user_ids", c.GetConversationOfflinePushUserIDs) //conversationGroup.POST("/get_conversation_offline_push_user_ids", c.GetConversationOfflinePushUserIDs)
conversationGroup.POST("/get_full_conversation_ids", c.GetFullOwnerConversationIDs) conversationGroup.POST("/get_full_conversation_ids", c.GetFullOwnerConversationIDs)
conversationGroup.POST("/get_incremental_conversations", c.GetIncrementalConversation) conversationGroup.POST("/get_incremental_conversations", c.GetIncrementalConversation)
conversationGroup.POST("/get_owner_conversation", c.GetOwnerConversation) conversationGroup.POST("/get_owner_conversation", c.GetOwnerConversation)
@@ -283,7 +296,7 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
} }
{ {
pd := NewPrometheusDiscoveryApi(cfg, client) pd := NewPrometheusDiscoveryApi(cfg, client)
proDiscoveryGroup := r.Group("/prometheus_discovery", pd.Enable) proDiscoveryGroup := r.Group("/prometheus_discovery")
proDiscoveryGroup.GET("/api", pd.Api) proDiscoveryGroup.GET("/api", pd.Api)
proDiscoveryGroup.GET("/user", pd.User) proDiscoveryGroup.GET("/user", pd.User)
proDiscoveryGroup.GET("/group", pd.Group) proDiscoveryGroup.GET("/group", pd.Group)
@@ -301,9 +314,8 @@ func newGinRouter(ctx context.Context, client discovery.SvcDiscoveryRegistry, cf
if cfg.Discovery.Enable == config.ETCD { if cfg.Discovery.Enable == config.ETCD {
etcdClient = client.(*etcd.SvcDiscoveryRegistryImpl).GetClient() etcdClient = client.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
} }
cm := NewConfigManager(cfg.Share.IMAdminUserID, cfg.AllConfig, etcdClient, cfg.ConfigPath, cfg.RuntimeEnv) cm := NewConfigManager(cfg.Share.IMAdminUser.UserIDs, &cfg.AllConfig, etcdClient, string(cfg.ConfigPath))
{ {
configGroup := r.Group("/config", cm.CheckAdmin) configGroup := r.Group("/config", cm.CheckAdmin)
configGroup.POST("/get_config_list", cm.GetConfigList) configGroup.POST("/get_config_list", cm.GetConfigList)
configGroup.POST("/get_config", cm.GetConfig) configGroup.POST("/get_config", cm.GetConfig)
@@ -349,6 +361,12 @@ func GinParseToken(authClient *rpcli.AuthClient) gin.HandlerFunc {
} }
} }
func setGinIsAdmin(imAdminUserID []string) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set(authverify.CtxAdminUserIDsKey, imAdminUserID)
}
}
// Whitelist api not parse token // Whitelist api not parse token
var Whitelist = []string{ var Whitelist = []string{
"/auth/get_admin_token", "/auth/get_admin_token",
+18 -2
View File
@@ -29,11 +29,11 @@ import (
type UserApi struct { type UserApi struct {
Client user.UserClient Client user.UserClient
discov discovery.SvcDiscoveryRegistry discov discovery.Conn
config config.RpcService config config.RpcService
} }
func NewUserApi(client user.UserClient, discov discovery.SvcDiscoveryRegistry, config config.RpcService) UserApi { func NewUserApi(client user.UserClient, discov discovery.Conn, config config.RpcService) UserApi {
return UserApi{Client: client, discov: discov, config: config} return UserApi{Client: client, discov: discov, config: config}
} }
@@ -242,3 +242,19 @@ func (u *UserApi) UpdateNotificationAccountInfo(c *gin.Context) {
func (u *UserApi) SearchNotificationAccount(c *gin.Context) { func (u *UserApi) SearchNotificationAccount(c *gin.Context) {
a2r.Call(c, user.UserClient.SearchNotificationAccount, u.Client) a2r.Call(c, user.UserClient.SearchNotificationAccount, u.Client)
} }
func (u *UserApi) GetUserClientConfig(c *gin.Context) {
a2r.Call(c, user.UserClient.GetUserClientConfig, u.Client)
}
func (u *UserApi) SetUserClientConfig(c *gin.Context) {
a2r.Call(c, user.UserClient.SetUserClientConfig, u.Client)
}
func (u *UserApi) DelUserClientConfig(c *gin.Context) {
a2r.Call(c, user.UserClient.DelUserClientConfig, u.Client)
}
func (u *UserApi) PageUserClientConfig(c *gin.Context) {
a2r.Call(c, user.UserClient.PageUserClientConfig, u.Client)
}
+23 -23
View File
@@ -22,7 +22,6 @@ import (
"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/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msggateway" "github.com/openimsdk/protocol/msggateway"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
@@ -35,7 +34,7 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
) )
func (s *Server) InitServer(ctx context.Context, config *Config, disCov discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func (s *Server) InitServer(ctx context.Context, config *Config, disCov discovery.Conn, server grpc.ServiceRegistrar) error {
userConn, err := disCov.GetConn(ctx, config.Discovery.RpcService.User) userConn, err := disCov.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return err return err
@@ -51,26 +50,26 @@ func (s *Server) InitServer(ctx context.Context, config *Config, disCov discover
return nil return nil
} }
func (s *Server) Start(ctx context.Context, index int, conf *Config) error { //func (s *Server) Start(ctx context.Context, index int, conf *Config) error {
return startrpc.Start(ctx, &conf.Discovery, &conf.MsgGateway.Prometheus, conf.MsgGateway.ListenIP, // return startrpc.Start(ctx, &conf.Discovery, &conf.MsgGateway.Prometheus, conf.MsgGateway.ListenIP,
conf.MsgGateway.RPC.RegisterIP, // conf.MsgGateway.RPC.RegisterIP,
conf.MsgGateway.RPC.AutoSetPorts, conf.MsgGateway.RPC.Ports, index, // conf.MsgGateway.RPC.AutoSetPorts, conf.MsgGateway.RPC.Ports, index,
conf.Discovery.RpcService.MessageGateway, // conf.Discovery.RpcService.MessageGateway,
nil, // nil,
conf, // conf,
[]string{ // []string{
conf.Share.GetConfigFileName(), // conf.Share.GetConfigFileName(),
conf.Discovery.GetConfigFileName(), // conf.Discovery.GetConfigFileName(),
conf.MsgGateway.GetConfigFileName(), // conf.MsgGateway.GetConfigFileName(),
conf.WebhooksConfig.GetConfigFileName(), // conf.WebhooksConfig.GetConfigFileName(),
conf.RedisConfig.GetConfigFileName(), // conf.RedisConfig.GetConfigFileName(),
}, // },
[]string{ // []string{
conf.Discovery.RpcService.MessageGateway, // conf.Discovery.RpcService.MessageGateway,
}, // },
s.InitServer, // s.InitServer,
) // )
} //}
type Server struct { type Server struct {
msggateway.UnimplementedMsgGatewayServer msggateway.UnimplementedMsgGatewayServer
@@ -101,7 +100,7 @@ func NewServer(longConnServer LongConnServer, conf *Config, ready func(srv *Serv
} }
func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUsersOnlineStatusReq) (*msggateway.GetUsersOnlineStatusResp, error) { func (s *Server) GetUsersOnlineStatus(ctx context.Context, req *msggateway.GetUsersOnlineStatusReq) (*msggateway.GetUsersOnlineStatusResp, error) {
if !authverify.IsAppManagerUid(ctx, s.config.Share.IMAdminUserID) { if !authverify.IsAdmin(ctx) {
return nil, errs.ErrNoPermission.WrapMsg("only app manager") return nil, errs.ErrNoPermission.WrapMsg("only app manager")
} }
var resp msggateway.GetUsersOnlineStatusResp var resp msggateway.GetUsersOnlineStatusResp
@@ -250,6 +249,7 @@ func (s *Server) MultiTerminalLoginCheck(ctx context.Context, req *msggateway.Mu
tempUserCtx.SetOperationID(mcontext.GetOperationID(ctx)) tempUserCtx.SetOperationID(mcontext.GetOperationID(ctx))
client := &Client{} client := &Client{}
client.ctx = tempUserCtx client.ctx = tempUserCtx
client.token = req.Token
client.UserID = req.UserID client.UserID = req.UserID
client.PlatformID = int(req.PlatformID) client.PlatformID = int(req.PlatformID)
i := &kickHandler{ i := &kickHandler{
+54 -15
View File
@@ -19,10 +19,12 @@ import (
"time" "time"
"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/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/rpccache" "github.com/openimsdk/open-im-server/v3/pkg/rpccache"
"github.com/openimsdk/tools/db/redisutil" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/runtimeenv" "github.com/openimsdk/tools/utils/runtimeenv"
"google.golang.org/grpc"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
) )
@@ -33,26 +35,25 @@ type Config struct {
RedisConfig config.Redis RedisConfig config.Redis
WebhooksConfig config.Webhooks WebhooksConfig config.Webhooks
Discovery config.Discovery Discovery config.Discovery
Index config.Index
RuntimeEnv string
} }
// Start run ws server. // Start run ws server.
func Start(ctx context.Context, index int, conf *Config) error { func Start(ctx context.Context, conf *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
conf.RuntimeEnv = runtimeenv.PrintRuntimeEnvironment() log.CInfo(ctx, "MSG-GATEWAY server is initializing", "runtimeEnv", runtimeenv.RuntimeEnvironment(),
log.CInfo(ctx, "MSG-GATEWAY server is initializing", "runtimeEnv", conf.RuntimeEnv,
"rpcPorts", conf.MsgGateway.RPC.Ports, "rpcPorts", conf.MsgGateway.RPC.Ports,
"wsPort", conf.MsgGateway.LongConnSvr.Ports, "prometheusPorts", conf.MsgGateway.Prometheus.Ports) "wsPort", conf.MsgGateway.LongConnSvr.Ports, "prometheusPorts", conf.MsgGateway.Prometheus.Ports)
wsPort, err := datautil.GetElemByIndex(conf.MsgGateway.LongConnSvr.Ports, index) wsPort, err := datautil.GetElemByIndex(conf.MsgGateway.LongConnSvr.Ports, int(conf.Index))
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, conf.RedisConfig.Build()) dbb := dbbuild.NewBuilder(nil, &conf.RedisConfig)
rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
longServer := NewWsServer( longServer := NewWsServer(
conf, conf,
WithPort(wsPort), WithPort(wsPort),
@@ -67,12 +68,50 @@ func Start(ctx context.Context, index int, conf *Config) error {
return err return err
}) })
if err := hubServer.InitServer(ctx, conf, client, server); err != nil {
return err
}
go longServer.ChangeOnlineStatus(4) go longServer.ChangeOnlineStatus(4)
netDone := make(chan error) return hubServer.LongConnServer.Run(ctx)
go func() {
err = hubServer.Start(ctx, index, conf)
netDone <- err
}()
return hubServer.LongConnServer.Run(netDone)
} }
//
//// Start run ws server.
//func Start(ctx context.Context, index int, conf *Config) error {
// log.CInfo(ctx, "MSG-GATEWAY server is initializing", "runtimeEnv", runtimeenv.RuntimeEnvironment(),
// "rpcPorts", conf.MsgGateway.RPC.Ports,
// "wsPort", conf.MsgGateway.LongConnSvr.Ports, "prometheusPorts", conf.MsgGateway.Prometheus.Ports)
// wsPort, err := datautil.GetElemByIndex(conf.MsgGateway.LongConnSvr.Ports, index)
// if err != nil {
// return err
// }
//
// rdb, err := redisutil.NewRedisClient(ctx, conf.RedisConfig.Build())
// if err != nil {
// return err
// }
// longServer := NewWsServer(
// conf,
// WithPort(wsPort),
// WithMaxConnNum(int64(conf.MsgGateway.LongConnSvr.WebsocketMaxConnNum)),
// WithHandshakeTimeout(time.Duration(conf.MsgGateway.LongConnSvr.WebsocketTimeout)*time.Second),
// WithMessageMaxMsgLength(conf.MsgGateway.LongConnSvr.WebsocketMaxMsgLen),
// )
//
// hubServer := NewServer(longServer, conf, func(srv *Server) error {
// var err error
// longServer.online, err = rpccache.NewOnlineCache(srv.userClient, nil, rdb, false, longServer.subscriberUserOnlineStatusChanges)
// return err
// })
//
// go longServer.ChangeOnlineStatus(4)
//
// netDone := make(chan error)
// go func() {
// err = hubServer.Start(ctx, index, conf)
// netDone <- err
// }()
// return hubServer.LongConnServer.Run(netDone)
//}
+88 -59
View File
@@ -2,7 +2,6 @@ package msggateway
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/http" "net/http"
"sync" "sync"
@@ -11,7 +10,6 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/open-im-server/v3/pkg/common/discovery/etcd"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/rpccache" "github.com/openimsdk/open-im-server/v3/pkg/rpccache"
pbAuth "github.com/openimsdk/protocol/auth" pbAuth "github.com/openimsdk/protocol/auth"
@@ -23,19 +21,18 @@ import (
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msggateway" "github.com/openimsdk/protocol/msggateway"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/stringutil" "github.com/openimsdk/tools/utils/stringutil"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
) )
type LongConnServer interface { type LongConnServer interface {
Run(done chan error) error Run(ctx context.Context) error
wsHandler(w http.ResponseWriter, r *http.Request) wsHandler(w http.ResponseWriter, r *http.Request)
GetUserAllCons(userID string) ([]*Client, bool) GetUserAllCons(userID string) ([]*Client, bool)
GetUserPlatformCons(userID string, platform int) ([]*Client, bool, bool) GetUserPlatformCons(userID string, platform int) ([]*Client, bool, bool)
Validate(s any) error Validate(s any) error
SetDiscoveryRegistry(ctx context.Context, client discovery.SvcDiscoveryRegistry, config *Config) error SetDiscoveryRegistry(ctx context.Context, client discovery.Conn, config *Config) error
KickUserConn(client *Client) error KickUserConn(client *Client) error
UnRegister(c *Client) UnRegister(c *Client)
SetKickHandlerInfo(i *kickHandler) SetKickHandlerInfo(i *kickHandler)
@@ -52,7 +49,7 @@ type WsServer struct {
unregisterChan chan *Client unregisterChan chan *Client
kickHandlerChan chan *kickHandler kickHandlerChan chan *kickHandler
clients UserMap clients UserMap
online *rpccache.OnlineCache online rpccache.OnlineCache
subscription *Subscription subscription *Subscription
clientPool sync.Pool clientPool sync.Pool
onlineUserNum atomic.Int64 onlineUserNum atomic.Int64
@@ -60,7 +57,7 @@ type WsServer struct {
handshakeTimeout time.Duration handshakeTimeout time.Duration
writeBufferSize int writeBufferSize int
validate *validator.Validate validate *validator.Validate
disCov discovery.SvcDiscoveryRegistry disCov discovery.Conn
Compressor Compressor
//Encoder //Encoder
MessageHandler MessageHandler
@@ -75,7 +72,7 @@ type kickHandler struct {
newClient *Client newClient *Client
} }
func (ws *WsServer) SetDiscoveryRegistry(ctx context.Context, disCov discovery.SvcDiscoveryRegistry, config *Config) error { func (ws *WsServer) SetDiscoveryRegistry(ctx context.Context, disCov discovery.Conn, config *Config) error {
userConn, err := disCov.GetConn(ctx, config.Discovery.RpcService.User) userConn, err := disCov.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return err return err
@@ -133,7 +130,7 @@ func NewWsServer(msgGatewayConfig *Config, opts ...Option) *WsServer {
for _, o := range opts { for _, o := range opts {
o(&config) o(&config)
} }
//userRpcClient := rpcclient.NewUserRpcClient(client, config.Discovery.RpcService.User, config.Share.IMAdminUserID) //userRpcClient := rpcclient.NewUserRpcClient(client, config.Discovery.RpcService.User, config.Share.IMAdminUser)
v := validator.New() v := validator.New()
return &WsServer{ return &WsServer{
@@ -158,19 +155,14 @@ func NewWsServer(msgGatewayConfig *Config, opts ...Option) *WsServer {
} }
} }
func (ws *WsServer) Run(done chan error) error { func (ws *WsServer) Run(ctx context.Context) error {
var ( var client *Client
client *Client
netErr error
shutdownDone = make(chan struct{}, 1)
)
server := http.Server{Addr: ":" + stringutil.IntToString(ws.port), Handler: nil}
ctx, cancel := context.WithCancelCause(ctx)
go func() { go func() {
for { for {
select { select {
case <-shutdownDone: case <-ctx.Done():
return return
case client = <-ws.registerChan: case client = <-ws.registerChan:
ws.registerClient(client) ws.registerClient(client)
@@ -181,57 +173,56 @@ func (ws *WsServer) Run(done chan error) error {
} }
} }
}() }()
netDone := make(chan struct{}, 1)
go func() {
http.HandleFunc("/", ws.wsHandler)
err := server.ListenAndServe()
if err != nil && !errors.Is(err, http.ErrServerClosed) {
netErr = errs.WrapMsg(err, "ws start err", server.Addr)
netDone <- struct{}{}
}
}()
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
shutDown := func() error {
sErr := server.Shutdown(ctx)
if sErr != nil {
return errs.WrapMsg(sErr, "shutdown err")
}
close(shutdownDone)
return nil
}
etcd.RegisterShutDown(shutDown)
defer cancel()
var err error
select {
case err = <-done:
if err := shutDown(); err != nil {
return err
}
if err != nil {
return err
}
case <-netDone:
}
return netErr
done := make(chan struct{})
go func() {
wsServer := http.Server{Addr: fmt.Sprintf(":%d", ws.port), Handler: nil}
http.HandleFunc("/", ws.wsHandler)
go func() {
defer close(done)
<-ctx.Done()
_ = wsServer.Shutdown(context.Background())
}()
err := wsServer.ListenAndServe()
if err == nil {
err = fmt.Errorf("http server closed")
}
cancel(fmt.Errorf("msg gateway %w", err))
}()
<-ctx.Done()
timeout := time.NewTimer(time.Second * 15)
defer timeout.Stop()
select {
case <-timeout.C:
log.ZWarn(ctx, "msg gateway graceful stop timeout", nil)
case <-done:
log.ZDebug(ctx, "msg gateway graceful stop done")
}
return context.Cause(ctx)
} }
var concurrentRequest = 3 const concurrentRequest = 3
func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *Client) error { func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *Client) error {
conns, err := ws.disCov.GetConns(ctx, ws.msgGatewayConfig.Discovery.RpcService.MessageGateway) conns, err := ws.disCov.GetConns(ctx, ws.msgGatewayConfig.Discovery.RpcService.MessageGateway)
if err != nil { if err != nil {
return err return err
} }
if len(conns) == 0 || (len(conns) == 1 && ws.disCov.IsSelfNode(conns[0])) {
return nil
}
wg := errgroup.Group{} wg := errgroup.Group{}
wg.SetLimit(concurrentRequest) wg.SetLimit(concurrentRequest)
// Online push user online message to other node // Online push user online message to other node
for _, v := range conns { for _, v := range conns {
v := v v := v
log.ZDebug(ctx, " sendUserOnlineInfoToOtherNode conn ", "target", v.Target()) log.ZDebug(ctx, "sendUserOnlineInfoToOtherNode conn")
if v.Target() == ws.disCov.GetSelfConnTarget() { if ws.disCov.IsSelfNode(v) {
log.ZDebug(ctx, "Filter out this node", "node", v.Target()) log.ZDebug(ctx, "Filter out this node")
continue continue
} }
@@ -242,7 +233,7 @@ func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *C
PlatformID: int32(client.PlatformID), Token: client.token, PlatformID: int32(client.PlatformID), Token: client.token,
}) })
if err != nil { if err != nil {
log.ZWarn(ctx, "MultiTerminalLoginCheck err", err, "node", v.Target()) log.ZWarn(ctx, "MultiTerminalLoginCheck err", err)
} }
return nil return nil
}) })
@@ -343,17 +334,51 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
} }
} }
// If reconnect: When multiple msgGateway instances are deployed, a client may disconnect from instance A and reconnect to instance B.
// During this process, instance A might still be executing, resulting in two clients with the same token existing simultaneously.
// This situation needs to be filtered to prevent duplicate clients.
checkSameTokenFunc := func(oldClients []*Client) []*Client {
var clientsNeedToKick []*Client
for _, c := range oldClients {
if c.token == newClient.token {
log.ZDebug(newClient.ctx, "token is same, not kick",
"userID", newClient.UserID,
"platformID", newClient.PlatformID,
"token", newClient.token)
continue
}
clientsNeedToKick = append(clientsNeedToKick, c)
}
return clientsNeedToKick
}
switch ws.msgGatewayConfig.Share.MultiLogin.Policy { 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 {
return return
} }
clients, ok := ws.clients.GetAll(newClient.UserID)
clientOK = ok
oldClients = make([]*Client, 0, len(clients))
for _, c := range clients {
if constant.PlatformIDToClass(c.PlatformID) == constant.TerminalPC {
continue
}
oldClients = append(oldClients, c)
}
fallthrough fallthrough
case constant.AllLoginButSameTermKick: case constant.AllLoginButSameTermKick:
if !clientOK { if !clientOK {
return return
} }
oldClients = checkSameTokenFunc(oldClients)
ws.clients.DeleteClients(newClient.UserID, oldClients) ws.clients.DeleteClients(newClient.UserID, oldClients)
for _, c := range oldClients { for _, c := range oldClients {
err := c.KickOnlineMessage() err := c.KickOnlineMessage()
@@ -361,6 +386,7 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
log.ZWarn(c.ctx, "KickOnlineMessage", err) log.ZWarn(c.ctx, "KickOnlineMessage", err)
} }
} }
ctx := mcontext.WithMustInfoCtx( ctx := mcontext.WithMustInfoCtx(
[]string{newClient.ctx.GetOperationID(), newClient.ctx.GetUserID(), []string{newClient.ctx.GetOperationID(), newClient.ctx.GetUserID(),
constant.PlatformIDToName(newClient.PlatformID), newClient.ctx.GetConnID()}, constant.PlatformIDToName(newClient.PlatformID), newClient.ctx.GetConnID()},
@@ -379,14 +405,17 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
if !ok { if !ok {
return return
} }
var (
kickClients []*Client var kickClients []*Client
)
for _, client := range clients { for _, client := range clients {
if constant.PlatformIDToClass(client.PlatformID) == constant.PlatformIDToClass(newClient.PlatformID) { if constant.PlatformIDToClass(client.PlatformID) == constant.PlatformIDToClass(newClient.PlatformID) {
kickClients = append(kickClients, client) {
kickClients = append(kickClients, client)
}
} }
} }
kickClients = checkSameTokenFunc(kickClients)
kickTokenFunc(kickClients) kickTokenFunc(kickClients)
} }
} }
+132
View File
@@ -0,0 +1,132 @@
package msgtransfer
import (
"context"
"encoding/base64"
"github.com/openimsdk/open-im-server/v3/pkg/apistruct"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/stringutil"
"google.golang.org/protobuf/proto"
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
)
func toCommonCallback(ctx context.Context, msg *sdkws.MsgData, command string) cbapi.CommonCallbackReq {
return cbapi.CommonCallbackReq{
SendID: msg.SendID,
ServerMsgID: msg.ServerMsgID,
CallbackCommand: command,
ClientMsgID: msg.ClientMsgID,
OperationID: mcontext.GetOperationID(ctx),
SenderPlatformID: msg.SenderPlatformID,
SenderNickname: msg.SenderNickname,
SessionType: msg.SessionType,
MsgFrom: msg.MsgFrom,
ContentType: msg.ContentType,
Status: msg.Status,
SendTime: msg.SendTime,
CreateTime: msg.CreateTime,
AtUserIDList: msg.AtUserIDList,
SenderFaceURL: msg.SenderFaceURL,
Content: GetContent(msg),
Seq: uint32(msg.Seq),
Ex: msg.Ex,
}
}
func GetContent(msg *sdkws.MsgData) string {
if msg.ContentType >= constant.NotificationBegin && msg.ContentType <= constant.NotificationEnd {
var tips sdkws.TipsComm
_ = proto.Unmarshal(msg.Content, &tips)
content := tips.JsonDetail
return content
} else {
return string(msg.Content)
}
}
func (mc *OnlineHistoryMongoConsumerHandler) webhookAfterSendSingleMsg(ctx context.Context, after *config.AfterConfig, msg *sdkws.MsgData) {
if msg.ContentType == constant.Typing {
return
}
if !filterAfterMsg(msg, after) {
return
}
cbReq := &cbapi.CallbackAfterSendSingleMsgReq{
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand),
RecvID: msg.RecvID,
}
mc.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendSingleMsgResp{}, after, buildKeyMsgDataQuery(msg))
}
func (mc *OnlineHistoryMongoConsumerHandler) webhookAfterSendGroupMsg(ctx context.Context, after *config.AfterConfig, msg *sdkws.MsgData) {
if msg.ContentType == constant.Typing {
return
}
if !filterAfterMsg(msg, after) {
return
}
cbReq := &cbapi.CallbackAfterSendGroupMsgReq{
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand),
GroupID: msg.GroupID,
}
mc.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendGroupMsgResp{}, after, buildKeyMsgDataQuery(msg))
}
func buildKeyMsgDataQuery(msg *sdkws.MsgData) map[string]string {
keyMsgData := apistruct.KeyMsgData{
SendID: msg.SendID,
RecvID: msg.RecvID,
GroupID: msg.GroupID,
}
return map[string]string{
webhook.Key: base64.StdEncoding.EncodeToString(stringutil.StructToJsonBytes(keyMsgData)),
}
}
func filterAfterMsg(msg *sdkws.MsgData, after *config.AfterConfig) bool {
return filterMsg(msg, after.AttentionIds, after.DeniedTypes)
}
func filterMsg(msg *sdkws.MsgData, attentionIds []string, deniedTypes []int32) bool {
// According to the attentionIds configuration, only some users are sent
if len(attentionIds) != 0 && msg.ContentType == constant.SingleChatType && !datautil.Contain(msg.RecvID, attentionIds...) {
return false
}
if len(attentionIds) != 0 && msg.ContentType == constant.ReadGroupChatType && !datautil.Contain(msg.GroupID, attentionIds...) {
return false
}
if defaultDeniedTypes(msg.ContentType) {
return false
}
if len(deniedTypes) != 0 && datautil.Contain(msg.ContentType, deniedTypes...) {
return false
}
return true
}
func defaultDeniedTypes(contentType int32) bool {
if contentType >= constant.NotificationBegin && contentType <= constant.NotificationEnd {
return true
}
if contentType == constant.Typing {
return true
}
return false
}
+93 -154
View File
@@ -16,51 +16,35 @@ package msgtransfer
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
disetcd "github.com/openimsdk/open-im-server/v3/pkg/common/discovery/etcd" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/mcache"
"github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/utils/jsonutil"
"github.com/openimsdk/tools/utils/network"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
"github.com/openimsdk/tools/db/mongoutil" "github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/tools/db/redisutil" "github.com/openimsdk/open-im-server/v3/pkg/mqbuild"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/mq"
"github.com/openimsdk/tools/utils/runtimeenv" "github.com/openimsdk/tools/utils/runtimeenv"
conf "github.com/openimsdk/open-im-server/v3/pkg/common/config" conf "github.com/openimsdk/open-im-server/v3/pkg/common/config"
discRegister "github.com/openimsdk/open-im-server/v3/pkg/common/discovery"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mw"
"github.com/openimsdk/tools/system/program"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
) )
type MsgTransfer struct { type MsgTransfer struct {
historyConsumer mq.Consumer
historyMongoConsumer mq.Consumer
// This consumer aggregated messages, subscribed to the topic:toRedis, // This consumer aggregated messages, subscribed to the topic:toRedis,
// the message is stored in redis, Incr Redis, and then the message is sent to toPush topic for push, // the message is stored in redis, Incr Redis, and then the message is sent to toPush topic for push,
// and the message is sent to toMongo topic for persistence // and the message is sent to toMongo topic for persistence
historyCH *OnlineHistoryRedisConsumerHandler historyHandler *OnlineHistoryRedisConsumerHandler
//This consumer handle message to mongo //This consumer handle message to mongo
historyMongoCH *OnlineHistoryMongoConsumerHandler historyMongoHandler *OnlineHistoryMongoConsumerHandler
ctx context.Context ctx context.Context
cancel context.CancelFunc //cancel context.CancelFunc
runTimeEnv string
} }
type Config struct { type Config struct {
@@ -71,48 +55,59 @@ type Config struct {
Share conf.Share Share conf.Share
WebhooksConfig conf.Webhooks WebhooksConfig conf.Webhooks
Discovery conf.Discovery Discovery conf.Discovery
Index conf.Index
} }
func Start(ctx context.Context, index int, config *Config) error { func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
runTimeEnv := runtimeenv.PrintRuntimeEnvironment() builder := mqbuild.NewBuilder(&config.KafkaConfig)
log.CInfo(ctx, "MSG-TRANSFER server is initializing", "runTimeEnv", runTimeEnv, "prometheusPorts", log.CInfo(ctx, "MSG-TRANSFER server is initializing", "runTimeEnv", runtimeenv.RuntimeEnvironment(), "prometheusPorts",
config.MsgTransfer.Prometheus.Ports, "index", index) config.MsgTransfer.Prometheus.Ports, "index", config.Index)
dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
client, err := discRegister.NewDiscoveryRegister(&config.Discovery, runTimeEnv, nil)
//if config.Discovery.Enable == conf.ETCD {
// cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), []string{
// config.MsgTransfer.GetConfigFileName(),
// config.RedisConfig.GetConfigFileName(),
// config.MongodbConfig.GetConfigFileName(),
// config.KafkaConfig.GetConfigFileName(),
// config.Share.GetConfigFileName(),
// config.WebhooksConfig.GetConfigFileName(),
// config.Discovery.GetConfigFileName(),
// conf.LogConfigFileName,
// })
// cm.Watch(ctx)
//}
mongoProducer, err := builder.GetTopicProducer(ctx, config.KafkaConfig.ToMongoTopic)
if err != nil { if err != nil {
return err return err
} }
client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), pushProducer, err := builder.GetTopicProducer(ctx, config.KafkaConfig.ToPushTopic)
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) if err != nil {
return err
if config.Discovery.Enable == conf.ETCD {
cm := disetcd.NewConfigManager(client.(*etcd.SvcDiscoveryRegistryImpl).GetClient(), []string{
config.MsgTransfer.GetConfigFileName(),
config.RedisConfig.GetConfigFileName(),
config.MongodbConfig.GetConfigFileName(),
config.KafkaConfig.GetConfigFileName(),
config.Share.GetConfigFileName(),
config.WebhooksConfig.GetConfigFileName(),
config.Discovery.GetConfigFileName(),
conf.LogConfigFileName,
})
cm.Watch(ctx)
} }
msgDocModel, err := mgo.NewMsgMongo(mgocli.GetDB()) msgDocModel, err := mgo.NewMsgMongo(mgocli.GetDB())
if err != nil { if err != nil {
return err return err
} }
msgModel := redis.NewMsgCache(rdb, msgDocModel) var msgModel cache.MsgCache
if rdb == nil {
cm, err := mgo.NewCacheMgo(mgocli.GetDB())
if err != nil {
return err
}
msgModel = mcache.NewMsgCache(cm, msgDocModel)
} else {
msgModel = redis.NewMsgCache(rdb, msgDocModel)
}
seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB()) seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB())
if err != nil { if err != nil {
return err return err
@@ -123,124 +118,68 @@ func Start(ctx context.Context, index int, config *Config) error {
return err return err
} }
seqUserCache := redis.NewSeqUserCacheRedis(rdb, seqUser) seqUserCache := redis.NewSeqUserCacheRedis(rdb, seqUser)
msgTransferDatabase, err := controller.NewMsgTransferDatabase(msgDocModel, msgModel, seqUserCache, seqConversationCache, &config.KafkaConfig) msgTransferDatabase, err := controller.NewMsgTransferDatabase(msgDocModel, msgModel, seqUserCache, seqConversationCache, mongoProducer, pushProducer)
if err != nil { if err != nil {
return err return err
} }
historyCH, err := NewOnlineHistoryRedisConsumerHandler(ctx, client, config, msgTransferDatabase) historyConsumer, err := builder.GetTopicConsumer(ctx, config.KafkaConfig.ToRedisTopic)
if err != nil { if err != nil {
return err return err
} }
historyMongoCH, err := NewOnlineHistoryMongoConsumerHandler(&config.KafkaConfig, msgTransferDatabase) historyMongoConsumer, err := builder.GetTopicConsumer(ctx, config.KafkaConfig.ToMongoTopic)
if err != nil { if err != nil {
return err return err
} }
historyHandler, err := NewOnlineHistoryRedisConsumerHandler(ctx, client, config, msgTransferDatabase)
if err != nil {
return err
}
historyMongoHandler := NewOnlineHistoryMongoConsumerHandler(msgTransferDatabase, config)
msgTransfer := &MsgTransfer{ msgTransfer := &MsgTransfer{
historyCH: historyCH, historyConsumer: historyConsumer,
historyMongoCH: historyMongoCH, historyMongoConsumer: historyMongoConsumer,
runTimeEnv: runTimeEnv, historyHandler: historyHandler,
historyMongoHandler: historyMongoHandler,
} }
return msgTransfer.Start(index, config, client) return msgTransfer.Start(ctx)
} }
func (m *MsgTransfer) Start(index int, config *Config, client discovery.SvcDiscoveryRegistry) error { func (m *MsgTransfer) Start(ctx context.Context) error {
m.ctx, m.cancel = context.WithCancel(context.Background()) var cancel context.CancelCauseFunc
var ( m.ctx, cancel = context.WithCancelCause(ctx)
netDone = make(chan struct{}, 1)
netErr error
)
go m.historyCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyCH) go func() {
go m.historyMongoCH.historyConsumerGroup.RegisterHandleAndConsumer(m.ctx, m.historyMongoCH) for {
go m.historyCH.HandleUserHasReadSeqMessages(m.ctx) if err := m.historyConsumer.Subscribe(m.ctx, m.historyHandler.HandlerRedisMessage); err != nil {
err := m.historyCH.redisMessageBatches.Start() cancel(fmt.Errorf("history consumer %w", err))
log.ZError(m.ctx, "historyConsumer err", err)
return
}
}
}()
go func() {
fn := func(msg mq.Message) error {
m.historyMongoHandler.HandleChatWs2Mongo(msg)
return nil
}
for {
if err := m.historyMongoConsumer.Subscribe(m.ctx, fn); err != nil {
cancel(fmt.Errorf("history mongo consumer %w", err))
log.ZError(m.ctx, "historyMongoConsumer err", err)
return
}
}
}()
go m.historyHandler.HandleUserHasReadSeqMessages(m.ctx)
err := m.historyHandler.redisMessageBatches.Start()
if err != nil { if err != nil {
return err return err
} }
<-m.ctx.Done()
registerIP, err := network.GetRpcRegisterIP("") return context.Cause(m.ctx)
if err != nil {
return err
}
getAutoPort := func() (net.Listener, int, error) {
registerAddr := net.JoinHostPort(registerIP, "0")
listener, err := net.Listen("tcp", registerAddr)
if err != nil {
return nil, 0, errs.WrapMsg(err, "listen err", "registerAddr", registerAddr)
}
_, portStr, _ := net.SplitHostPort(listener.Addr().String())
port, _ := strconv.Atoi(portStr)
return listener, port, nil
}
if config.MsgTransfer.Prometheus.AutoSetPorts && config.Discovery.Enable != conf.ETCD {
return errs.New("only etcd support autoSetPorts", "RegisterName", "api").Wrap()
}
if config.MsgTransfer.Prometheus.Enable {
var (
listener net.Listener
prometheusPort int
)
if config.MsgTransfer.Prometheus.AutoSetPorts {
listener, prometheusPort, err = getAutoPort()
if err != nil {
return err
}
etcdClient := client.(*etcd.SvcDiscoveryRegistryImpl).GetClient()
_, err = etcdClient.Put(context.TODO(), prommetrics.BuildDiscoveryKey(prommetrics.MessageTransferKeyName), jsonutil.StructToJsonString(prommetrics.BuildDefaultTarget(registerIP, prometheusPort)))
if err != nil {
return errs.WrapMsg(err, "etcd put err")
}
} else {
prometheusPort, err = datautil.GetElemByIndex(config.MsgTransfer.Prometheus.Ports, index)
if err != nil {
return err
}
listener, err = net.Listen("tcp", fmt.Sprintf(":%d", prometheusPort))
if err != nil {
return errs.WrapMsg(err, "listen err", "addr", fmt.Sprintf(":%d", prometheusPort))
}
}
go func() {
defer func() {
if r := recover(); r != nil {
log.ZPanic(m.ctx, "MsgTransfer Start Panic", errs.ErrPanic(r))
}
}()
if err := prommetrics.TransferInit(listener); err != nil && !errors.Is(err, http.ErrServerClosed) {
netErr = errs.WrapMsg(err, "prometheus start error", "prometheusPort", prometheusPort)
netDone <- struct{}{}
}
}()
}
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM)
select {
case <-sigs:
program.SIGTERMExit()
// graceful close kafka client.
m.cancel()
m.historyCH.redisMessageBatches.Close()
m.historyCH.Close()
m.historyCH.historyConsumerGroup.Close()
m.historyMongoCH.historyConsumerGroup.Close()
return nil
case <-netDone:
m.cancel()
m.historyCH.redisMessageBatches.Close()
m.historyCH.Close()
m.historyCH.historyConsumerGroup.Close()
m.historyMongoCH.historyConsumerGroup.Close()
close(netDone)
return netErr
}
} }
@@ -18,15 +18,17 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/tools/mq"
"github.com/openimsdk/tools/discovery"
"strconv"
"strings"
"sync" "sync"
"time" "time"
"github.com/IBM/sarama" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/tools/discovery"
"github.com/go-redis/redis" "github.com/go-redis/redis"
"google.golang.org/protobuf/proto"
"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/common/storage/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
@@ -37,9 +39,7 @@ import (
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/mq/kafka"
"github.com/openimsdk/tools/utils/stringutil" "github.com/openimsdk/tools/utils/stringutil"
"google.golang.org/protobuf/proto"
) )
const ( const (
@@ -64,9 +64,7 @@ type userHasReadSeq struct {
} }
type OnlineHistoryRedisConsumerHandler struct { type OnlineHistoryRedisConsumerHandler struct {
historyConsumerGroup *kafka.MConsumerGroup redisMessageBatches *batcher.Batcher[ConsumerMessage]
redisMessageBatches *batcher.Batcher[sarama.ConsumerMessage]
msgTransferDatabase controller.MsgTransferDatabase msgTransferDatabase controller.MsgTransferDatabase
conversationUserHasReadChan chan *userHasReadSeq conversationUserHasReadChan chan *userHasReadSeq
@@ -76,12 +74,14 @@ type OnlineHistoryRedisConsumerHandler struct {
conversationClient *rpcli.ConversationClient conversationClient *rpcli.ConversationClient
} }
func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.SvcDiscoveryRegistry, config *Config, database controller.MsgTransferDatabase) (*OnlineHistoryRedisConsumerHandler, error) { type ConsumerMessage struct {
kafkaConf := config.KafkaConfig Ctx context.Context
historyConsumerGroup, err := kafka.NewMConsumerGroup(kafkaConf.Build(), kafkaConf.ToRedisGroupID, []string{kafkaConf.ToRedisTopic}, false) Key string
if err != nil { Value []byte
return nil, err Raw mq.Message
} }
func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.Conn, config *Config, database controller.MsgTransferDatabase) (*OnlineHistoryRedisConsumerHandler, error) {
groupConn, err := client.GetConn(ctx, config.Discovery.RpcService.Group) groupConn, err := client.GetConn(ctx, config.Discovery.RpcService.Group)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -97,7 +97,7 @@ func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.
och.conversationClient = rpcli.NewConversationClient(conversationConn) och.conversationClient = rpcli.NewConversationClient(conversationConn)
och.wg.Add(1) och.wg.Add(1)
b := batcher.New[sarama.ConsumerMessage]( b := batcher.New[ConsumerMessage](
batcher.WithSize(size), batcher.WithSize(size),
batcher.WithWorker(worker), batcher.WithWorker(worker),
batcher.WithInterval(interval), batcher.WithInterval(interval),
@@ -109,16 +109,20 @@ func NewOnlineHistoryRedisConsumerHandler(ctx context.Context, client discovery.
hashCode := stringutil.GetHashCode(key) hashCode := stringutil.GetHashCode(key)
return int(hashCode) % och.redisMessageBatches.Worker() return int(hashCode) % och.redisMessageBatches.Worker()
} }
b.Key = func(consumerMessage *sarama.ConsumerMessage) string { b.Key = func(consumerMessage *ConsumerMessage) string {
return string(consumerMessage.Key) return consumerMessage.Key
} }
b.Do = och.do b.Do = och.do
och.redisMessageBatches = b och.redisMessageBatches = b
och.historyConsumerGroup = historyConsumerGroup
och.redisMessageBatches.OnComplete = func(lastMessage *ConsumerMessage, totalCount int) {
lastMessage.Raw.Mark()
lastMessage.Raw.Commit()
}
return &och, nil return &och, nil
} }
func (och *OnlineHistoryRedisConsumerHandler) do(ctx context.Context, channelID int, val *batcher.Msg[sarama.ConsumerMessage]) { func (och *OnlineHistoryRedisConsumerHandler) do(ctx context.Context, channelID int, val *batcher.Msg[ConsumerMessage]) {
ctx = mcontext.WithTriggerIDContext(ctx, val.TriggerID()) ctx = mcontext.WithTriggerIDContext(ctx, val.TriggerID())
ctxMessages := och.parseConsumerMessages(ctx, val.Val()) ctxMessages := och.parseConsumerMessages(ctx, val.Val())
ctx = withAggregationCtx(ctx, ctxMessages) ctx = withAggregationCtx(ctx, ctxMessages)
@@ -138,58 +142,53 @@ 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) {
var conversationID string // Outer map: conversationID -> (userID -> maxHasReadSeq)
var userSeqMap map[string]int64 conversationUserSeq := make(map[string]map[string]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.ZWarn(ctx, "handlerConversationRead Unmarshal NotificationElem msg err", err, "msg", msg) log.ZWarn(ctx, "Unmarshal NotificationElem error", 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.ZWarn(ctx, "handlerConversationRead Unmarshal MarkAsReadTips msg err", err, "msg", msg) log.ZWarn(ctx, "Unmarshal MarkAsReadTips error", err, "msg", msg)
continue continue
} }
//The conversation ID for each batch of messages processed by the batcher is the same. if len(tips.ConversationID) == 0 || tips.HasReadSeq < 0 {
conversationID = tips.ConversationID
if len(tips.Seqs) > 0 {
for _, seq := range tips.Seqs {
if tips.HasReadSeq < seq {
tips.HasReadSeq = seq
}
}
clear(tips.Seqs)
tips.Seqs = nil
}
if tips.HasReadSeq < 0 {
continue continue
} }
if userSeqMap == nil {
userSeqMap = make(map[string]int64)
}
if userSeqMap[tips.MarkAsReadUserID] > tips.HasReadSeq { // Calculate the max seq from tips.Seqs
continue for _, seq := range tips.Seqs {
if tips.HasReadSeq < seq {
tips.HasReadSeq = seq
}
}
if _, ok := conversationUserSeq[tips.ConversationID]; !ok {
conversationUserSeq[tips.ConversationID] = make(map[string]int64)
}
if conversationUserSeq[tips.ConversationID][tips.MarkAsReadUserID] < tips.HasReadSeq {
conversationUserSeq[tips.ConversationID][tips.MarkAsReadUserID] = tips.HasReadSeq
} }
userSeqMap[tips.MarkAsReadUserID] = tips.HasReadSeq
} }
if userSeqMap == nil { log.ZInfo(ctx, "doSetReadSeq", "conversationUserSeq", conversationUserSeq)
return
} // persist to db
if len(conversationID) == 0 { for convID, userSeqMap := range conversationUserSeq {
log.ZWarn(ctx, "conversation err", nil, "conversationID", conversationID) if err := och.msgTransferDatabase.SetHasReadSeqToDB(ctx, convID, userSeqMap); err != nil {
} log.ZWarn(ctx, "SetHasReadSeqToDB error", err, "conversationID", convID, "userSeqMap", userSeqMap)
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 []*ConsumerMessage) []*ContextMsg {
var ctxMessages []*ContextMsg var ctxMessages []*ContextMsg
for i := 0; i < len(consumerMessages); i++ { for i := 0; i < len(consumerMessages); i++ {
ctxMsg := &ContextMsg{} ctxMsg := &ContextMsg{}
@@ -199,16 +198,9 @@ func (och *OnlineHistoryRedisConsumerHandler) parseConsumerMessages(ctx context.
log.ZWarn(ctx, "msg_transfer Unmarshal msg err", err, string(consumerMessages[i].Value)) log.ZWarn(ctx, "msg_transfer Unmarshal msg err", err, string(consumerMessages[i].Value))
continue continue
} }
var arr []string ctxMsg.ctx = consumerMessages[i].Ctx
for i, header := range consumerMessages[i].Headers {
arr = append(arr, strconv.Itoa(i), string(header.Key), string(header.Value))
}
log.ZDebug(ctx, "consumer.kafka.GetContextWithMQHeader", "len", len(consumerMessages[i].Headers),
"header", strings.Join(arr, ", "))
ctxMsg.ctx = kafka.GetContextWithMQHeader(consumerMessages[i].Headers)
ctxMsg.message = msgFromMQ ctxMsg.message = msgFromMQ
log.ZDebug(ctx, "message parse finish", "message", msgFromMQ, "key", log.ZDebug(ctx, "message parse finish", "message", msgFromMQ, "key", consumerMessages[i].Key)
string(consumerMessages[i].Key))
ctxMessages = append(ctxMessages, ctxMsg) ctxMessages = append(ctxMessages, ctxMsg)
} }
return ctxMessages return ctxMessages
@@ -383,7 +375,9 @@ func (och *OnlineHistoryRedisConsumerHandler) Close() {
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 {
log.ZDebug(ctx, "push msg to topic", "msg", v.message.String()) log.ZDebug(ctx, "push msg to topic", "msg", v.message.String())
_, _, _ = och.msgTransferDatabase.MsgToPushMQ(v.ctx, key, conversationID, v.message) if err := och.msgTransferDatabase.MsgToPushMQ(v.ctx, key, conversationID, v.message); err != nil {
log.ZError(ctx, "msg to push topic error", err, "msg", v.message.String())
}
} }
} }
@@ -401,35 +395,10 @@ func withAggregationCtx(ctx context.Context, values []*ContextMsg) context.Conte
return mcontext.SetOperationID(ctx, allMessageOperationID) return mcontext.SetOperationID(ctx, allMessageOperationID)
} }
func (och *OnlineHistoryRedisConsumerHandler) Setup(_ sarama.ConsumerGroupSession) error { return nil } func (och *OnlineHistoryRedisConsumerHandler) HandlerRedisMessage(msg mq.Message) error { // a instance in the consumer group
func (och *OnlineHistoryRedisConsumerHandler) Cleanup(_ sarama.ConsumerGroupSession) error { err := och.redisMessageBatches.Put(msg.Context(), &ConsumerMessage{Ctx: msg.Context(), Key: msg.Key(), Value: msg.Value(), Raw: msg})
if err != nil {
log.ZWarn(msg.Context(), "put msg to error", err, "key", msg.Key(), "value", msg.Value())
}
return nil return nil
} }
func (och *OnlineHistoryRedisConsumerHandler) ConsumeClaim(session sarama.ConsumerGroupSession,
claim sarama.ConsumerGroupClaim) error { // a instance in the consumer group
log.ZDebug(context.Background(), "online new session msg come", "highWaterMarkOffset",
claim.HighWaterMarkOffset(), "topic", claim.Topic(), "partition", claim.Partition())
och.redisMessageBatches.OnComplete = func(lastMessage *sarama.ConsumerMessage, totalCount int) {
session.MarkMessage(lastMessage, "")
session.Commit()
}
for {
select {
case msg, ok := <-claim.Messages():
if !ok {
return nil
}
if len(msg.Value) == 0 {
continue
}
err := och.redisMessageBatches.Put(context.Background(), msg)
if err != nil {
log.ZWarn(context.Background(), "put msg to error", err, "msg", msg)
}
case <-session.Context().Done():
return nil
}
}
}
@@ -15,38 +15,35 @@
package msgtransfer package msgtransfer
import ( import (
"context" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/tools/mq"
"github.com/IBM/sarama"
"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"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
pbmsg "github.com/openimsdk/protocol/msg" pbmsg "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mq/kafka"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
type OnlineHistoryMongoConsumerHandler struct { type OnlineHistoryMongoConsumerHandler struct {
historyConsumerGroup *kafka.MConsumerGroup msgTransferDatabase controller.MsgTransferDatabase
msgTransferDatabase controller.MsgTransferDatabase config *Config
webhookClient *webhook.Client
} }
func NewOnlineHistoryMongoConsumerHandler(kafkaConf *config.Kafka, database controller.MsgTransferDatabase) (*OnlineHistoryMongoConsumerHandler, error) { func NewOnlineHistoryMongoConsumerHandler(database controller.MsgTransferDatabase, config *Config) *OnlineHistoryMongoConsumerHandler {
historyConsumerGroup, err := kafka.NewMConsumerGroup(kafkaConf.Build(), kafkaConf.ToMongoGroupID, []string{kafkaConf.ToMongoTopic}, true) return &OnlineHistoryMongoConsumerHandler{
if err != nil { msgTransferDatabase: database,
return nil, err config: config,
webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL),
} }
mc := &OnlineHistoryMongoConsumerHandler{
historyConsumerGroup: historyConsumerGroup,
msgTransferDatabase: database,
}
return mc, nil
} }
func (mc *OnlineHistoryMongoConsumerHandler) handleChatWs2Mongo(ctx context.Context, cMsg *sarama.ConsumerMessage, key string, session sarama.ConsumerGroupSession) { func (mc *OnlineHistoryMongoConsumerHandler) HandleChatWs2Mongo(val mq.Message) {
msg := cMsg.Value ctx := val.Context()
key := val.Key()
msg := val.Value()
msgFromMQ := pbmsg.MsgDataToMongoByMQ{} msgFromMQ := pbmsg.MsgDataToMongoByMQ{}
err := proto.Unmarshal(msg, &msgFromMQ) err := proto.Unmarshal(msg, &msgFromMQ)
if err != nil { if err != nil {
@@ -54,46 +51,34 @@ func (mc *OnlineHistoryMongoConsumerHandler) handleChatWs2Mongo(ctx context.Cont
return return
} }
if len(msgFromMQ.MsgData) == 0 { if len(msgFromMQ.MsgData) == 0 {
log.ZError(ctx, "msgFromMQ.MsgData is empty", nil, "cMsg", cMsg) log.ZError(ctx, "msgFromMQ.MsgData is empty", nil, "key", key, "msg", msg)
return return
} }
log.ZDebug(ctx, "mongo consumer recv msg", "msgs", msgFromMQ.String()) log.ZDebug(ctx, "mongo consumer recv msg", "msgs", msgFromMQ.String())
err = mc.msgTransferDatabase.BatchInsertChat2DB(ctx, msgFromMQ.ConversationID, msgFromMQ.MsgData, msgFromMQ.LastSeq) err = mc.msgTransferDatabase.BatchInsertChat2DB(ctx, msgFromMQ.ConversationID, msgFromMQ.MsgData, msgFromMQ.LastSeq)
if err != nil { if err != nil {
log.ZError( log.ZError(ctx, "single data insert to mongo err", err, "msg", msgFromMQ.MsgData, "conversationID", msgFromMQ.ConversationID)
ctx,
"single data insert to mongo err",
err,
"msg",
msgFromMQ.MsgData,
"conversationID",
msgFromMQ.ConversationID,
)
prommetrics.MsgInsertMongoFailedCounter.Inc() prommetrics.MsgInsertMongoFailedCounter.Inc()
} else { } else {
prommetrics.MsgInsertMongoSuccessCounter.Inc() prommetrics.MsgInsertMongoSuccessCounter.Inc()
val.Mark()
} }
var seqs []int64
for _, msg := range msgFromMQ.MsgData {
seqs = append(seqs, msg.Seq)
}
}
func (*OnlineHistoryMongoConsumerHandler) Setup(_ sarama.ConsumerGroupSession) error { return nil } for _, msgData := range msgFromMQ.MsgData {
switch msgData.SessionType {
func (*OnlineHistoryMongoConsumerHandler) Cleanup(_ sarama.ConsumerGroupSession) error { return nil } case constant.SingleChatType:
mc.webhookAfterSendSingleMsg(ctx, &mc.config.WebhooksConfig.AfterSendSingleMsg, msgData)
func (mc *OnlineHistoryMongoConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error { // an instance in the consumer group case constant.ReadGroupChatType:
log.ZDebug(context.Background(), "online new session msg come", "highWaterMarkOffset", mc.webhookAfterSendGroupMsg(ctx, &mc.config.WebhooksConfig.AfterSendGroupMsg, msgData)
claim.HighWaterMarkOffset(), "topic", claim.Topic(), "partition", claim.Partition())
for msg := range claim.Messages() {
ctx := mc.historyConsumerGroup.GetContextFromMsg(msg)
if len(msg.Value) != 0 {
mc.handleChatWs2Mongo(ctx, msg, string(msg.Key), sess)
} else {
log.ZError(ctx, "mongo msg get from kafka but is nil", nil, "conversationID", msg.Key)
} }
sess.MarkMessage(msg, "")
} }
return nil
//var seqs []int64
//for _, msg := range msgFromMQ.MsgData {
// seqs = append(seqs, msg.Seq)
//}
//if err := mc.msgTransferDatabase.DeleteMessagesFromCache(ctx, msgFromMQ.ConversationID, seqs); err != nil {
// log.ZError(ctx, "remove cache msg from redis err", err, "msg",
// msgFromMQ.MsgData, "conversationID", msgFromMQ.ConversationID)
//}
} }
+1
View File
@@ -17,6 +17,7 @@ package push
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
+5 -3
View File
@@ -16,12 +16,14 @@ package fcm
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
"github.com/openimsdk/tools/utils/httputil"
"path/filepath" "path/filepath"
"strings" "strings"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
"github.com/openimsdk/tools/utils/httputil"
firebase "firebase.google.com/go/v4" firebase "firebase.google.com/go/v4"
"firebase.google.com/go/v4/messaging" "firebase.google.com/go/v4/messaging"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
@@ -133,7 +135,7 @@ func (f *Fcm) Push(ctx context.Context, userIDs []string, title, content string,
unreadCountSum, err := f.cache.GetUserBadgeUnreadCountSum(ctx, userID) unreadCountSum, err := f.cache.GetUserBadgeUnreadCountSum(ctx, userID)
if err == nil && unreadCountSum != 0 { if err == nil && unreadCountSum != 0 {
apns.Payload.Aps.Badge = &unreadCountSum apns.Payload.Aps.Badge = &unreadCountSum
} else if err == redis.Nil || unreadCountSum == 0 { } else if errors.Is(err, redis.Nil) || unreadCountSum == 0 {
zero := 1 zero := 1
apns.Payload.Aps.Badge = &zero apns.Payload.Aps.Badge = &zero
} else { } else {
+36 -2
View File
@@ -18,6 +18,16 @@ import (
"fmt" "fmt"
"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/datautil"
)
var (
incOne = datautil.ToPtr("+1")
addNum = "1"
defaultStrategy = strategy{
Default: 1,
}
msgCategory = "CATEGORY_MESSAGE"
) )
type Resp struct { type Resp struct {
@@ -58,7 +68,24 @@ type TaskResp struct {
} }
type Settings struct { type Settings struct {
TTL *int64 `json:"ttl"` TTL *int64 `json:"ttl"`
Strategy strategy `json:"strategy"`
}
type strategy struct {
Default int64 `json:"default"`
//IOS int64 `json:"ios"`
//St int64 `json:"st"`
//Hw int64 `json:"hw"`
//Ho int64 `json:"ho"`
//XM int64 `json:"xm"`
//XMG int64 `json:"xmg"`
//VV int64 `json:"vv"`
//Op int64 `json:"op"`
//OpG int64 `json:"opg"`
//MZ int64 `json:"mz"`
//HosHw int64 `json:"hoshw"`
//WX int64 `json:"wx"`
} }
type Audience struct { type Audience struct {
@@ -112,6 +139,8 @@ type Notification struct {
ChannelID string `json:"channelID"` ChannelID string `json:"channelID"`
ChannelName string `json:"ChannelName"` ChannelName string `json:"ChannelName"`
ClickType string `json:"click_type"` ClickType string `json:"click_type"`
BadgeAddNum string `json:"badge_add_num"`
Category string `json:"category"`
} }
type Options struct { type Options struct {
@@ -120,6 +149,7 @@ type Options struct {
ChannelID string `json:"/message/android/notification/channel_id"` ChannelID string `json:"/message/android/notification/channel_id"`
Sound string `json:"/message/android/notification/sound"` Sound string `json:"/message/android/notification/sound"`
Importance string `json:"/message/android/notification/importance"` Importance string `json:"/message/android/notification/importance"`
Category string `json:"/message/android/category"`
} `json:"HW"` } `json:"HW"`
XM struct { XM struct {
ChannelID string `json:"/extra.channel_id"` ChannelID string `json:"/extra.channel_id"`
@@ -140,6 +170,8 @@ func newPushReq(pushConf *config.Push, title, content string) PushReq {
ClickType: "startapp", ClickType: "startapp",
ChannelID: pushConf.GeTui.ChannelID, ChannelID: pushConf.GeTui.ChannelID,
ChannelName: pushConf.GeTui.ChannelName, ChannelName: pushConf.GeTui.ChannelName,
BadgeAddNum: addNum,
Category: msgCategory,
}}} }}}
return pushReq return pushReq
} }
@@ -156,6 +188,7 @@ func (pushReq *PushReq) setPushChannel(title string, body string) {
notify := "notify" notify := "notify"
pushReq.PushChannel.Ios.NotificationType = &notify pushReq.PushChannel.Ios.NotificationType = &notify
pushReq.PushChannel.Ios.Aps.Sound = "default" pushReq.PushChannel.Ios.Aps.Sound = "default"
pushReq.PushChannel.Ios.AutoBadge = incOne
pushReq.PushChannel.Ios.Aps.Alert = Alert{ pushReq.PushChannel.Ios.Aps.Alert = Alert{
Title: title, Title: title,
Body: body, Body: body,
@@ -172,7 +205,8 @@ func (pushReq *PushReq) setPushChannel(title string, body string) {
ChannelID string `json:"/message/android/notification/channel_id"` ChannelID string `json:"/message/android/notification/channel_id"`
Sound string `json:"/message/android/notification/sound"` Sound string `json:"/message/android/notification/sound"`
Importance string `json:"/message/android/notification/importance"` Importance string `json:"/message/android/notification/importance"`
}{ChannelID: "RingRing4", Sound: "/raw/ring001", Importance: "NORMAL"}, Category string `json:"/message/android/category"`
}{ChannelID: "RingRing4", Sound: "/raw/ring001", Importance: "NORMAL", Category: "IM"},
XM: struct { XM: struct {
ChannelID string `json:"/extra.channel_id"` ChannelID string `json:"/extra.channel_id"`
}{ChannelID: "high_system"}, }{ChannelID: "high_system"},
+5 -3
View File
@@ -18,6 +18,7 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors"
"strconv" "strconv"
"sync" "sync"
"time" "time"
@@ -70,7 +71,7 @@ func NewClient(pushConf *config.Push, cache cache.ThirdCache) *Client {
func (g *Client) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error { func (g *Client) Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error {
token, err := g.cache.GetGetuiToken(ctx) token, err := g.cache.GetGetuiToken(ctx)
if err != nil { if err != nil {
if errs.Unwrap(err) == redis.Nil { if errors.Is(err, redis.Nil) {
log.ZDebug(ctx, "getui token not exist in redis") log.ZDebug(ctx, "getui token not exist in redis")
token, err = g.getTokenAndSave2Redis(ctx) token, err = g.getTokenAndSave2Redis(ctx)
if err != nil { if err != nil {
@@ -144,7 +145,7 @@ func (g *Client) Auth(ctx context.Context, timeStamp int64) (token string, expir
func (g *Client) GetTaskID(ctx context.Context, token string, pushReq PushReq) (string, error) { func (g *Client) GetTaskID(ctx context.Context, token string, pushReq PushReq) (string, error) {
respTask := TaskResp{} respTask := TaskResp{}
ttl := int64(1000 * 60 * 5) ttl := int64(1000 * 60 * 5)
pushReq.Settings = &Settings{TTL: &ttl} pushReq.Settings = &Settings{TTL: &ttl, Strategy: defaultStrategy}
err := g.request(ctx, taskURL, pushReq, token, &respTask) err := g.request(ctx, taskURL, pushReq, token, &respTask)
if err != nil { if err != nil {
return "", errs.Wrap(err) return "", errs.Wrap(err)
@@ -188,6 +189,7 @@ func (g *Client) postReturn(
if err != nil { if err != nil {
return err return err
} }
log.ZDebug(ctx, "postReturn", "url", url, "header", header, "input", input, "timeout", timeout, "output", output)
return output.parseError() return output.parseError()
} }
@@ -204,7 +206,7 @@ func (g *Client) getTokenAndSave2Redis(ctx context.Context) (token string, err e
} }
func (g *Client) GetTaskIDAndSave2Redis(ctx context.Context, token string, pushReq PushReq) (taskID string, err error) { func (g *Client) GetTaskIDAndSave2Redis(ctx context.Context, token string, pushReq PushReq) (taskID string, err error) {
pushReq.Settings = &Settings{TTL: &g.taskIDTTL} pushReq.Settings = &Settings{TTL: &g.taskIDTTL, Strategy: defaultStrategy}
taskID, err = g.GetTaskID(ctx, token, pushReq) taskID, err = g.GetTaskID(ctx, token, pushReq)
if err != nil { if err != nil {
return return
+5 -25
View File
@@ -3,7 +3,6 @@ package push
import ( import (
"context" "context"
"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"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
@@ -12,40 +11,21 @@ import (
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mq/kafka"
"github.com/openimsdk/tools/utils/jsonutil" "github.com/openimsdk/tools/utils/jsonutil"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
type OfflinePushConsumerHandler struct { type OfflinePushConsumerHandler struct {
OfflinePushConsumerGroup *kafka.MConsumerGroup offlinePusher offlinepush.OfflinePusher
offlinePusher offlinepush.OfflinePusher
} }
func NewOfflinePushConsumerHandler(config *Config, offlinePusher offlinepush.OfflinePusher) (*OfflinePushConsumerHandler, error) { func NewOfflinePushConsumerHandler(offlinePusher offlinepush.OfflinePusher) *OfflinePushConsumerHandler {
var offlinePushConsumerHandler OfflinePushConsumerHandler return &OfflinePushConsumerHandler{
var err error offlinePusher: offlinePusher,
offlinePushConsumerHandler.offlinePusher = offlinePusher
offlinePushConsumerHandler.OfflinePushConsumerGroup, err = kafka.NewMConsumerGroup(config.KafkaConfig.Build(), config.KafkaConfig.ToOfflineGroupID,
[]string{config.KafkaConfig.ToOfflinePushTopic}, true)
if err != nil {
return nil, err
} }
return &offlinePushConsumerHandler, nil
} }
func (*OfflinePushConsumerHandler) Setup(sarama.ConsumerGroupSession) error { return nil } func (o *OfflinePushConsumerHandler) HandleMsg2OfflinePush(ctx context.Context, msg []byte) {
func (*OfflinePushConsumerHandler) Cleanup(sarama.ConsumerGroupSession) error { return nil }
func (o *OfflinePushConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
for msg := range claim.Messages() {
ctx := o.OfflinePushConsumerGroup.GetContextFromMsg(msg)
o.handleMsg2OfflinePush(ctx, msg.Value)
sess.MarkMessage(msg, "")
}
return nil
}
func (o *OfflinePushConsumerHandler) handleMsg2OfflinePush(ctx context.Context, msg []byte) {
offlinePushMsg := pbpush.PushMsgReq{} offlinePushMsg := pbpush.PushMsgReq{}
if err := proto.Unmarshal(msg, &offlinePushMsg); err != nil { if err := proto.Unmarshal(msg, &offlinePushMsg); err != nil {
log.ZError(ctx, "offline push Unmarshal msg err", err, "msg", string(msg)) log.ZError(ctx, "offline push Unmarshal msg err", err, "msg", string(msg))
+15 -15
View File
@@ -2,7 +2,7 @@ package push
import ( import (
"context" "context"
"errors" "fmt"
"sync" "sync"
"github.com/openimsdk/protocol/msggateway" "github.com/openimsdk/protocol/msggateway"
@@ -11,6 +11,7 @@ import (
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/runtimeenv"
"golang.org/x/sync/errgroup" "golang.org/x/sync/errgroup"
"google.golang.org/grpc" "google.golang.org/grpc"
@@ -30,37 +31,36 @@ func newEmptyOnlinePusher() *emptyOnlinePusher {
return &emptyOnlinePusher{} return &emptyOnlinePusher{}
} }
func (emptyOnlinePusher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, func (emptyOnlinePusher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) {
pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) {
log.ZInfo(ctx, "emptyOnlinePusher GetConnsAndOnlinePush", nil) log.ZInfo(ctx, "emptyOnlinePusher GetConnsAndOnlinePush", nil)
return nil, nil return nil, nil
} }
func (u emptyOnlinePusher) GetOnlinePushFailedUserIDs(ctx context.Context, msg *sdkws.MsgData, func (u emptyOnlinePusher) GetOnlinePushFailedUserIDs(ctx context.Context, msg *sdkws.MsgData, wsResults []*msggateway.SingleMsgToUserResults, pushToUserIDs *[]string) []string {
wsResults []*msggateway.SingleMsgToUserResults, pushToUserIDs *[]string) []string {
log.ZInfo(ctx, "emptyOnlinePusher GetOnlinePushFailedUserIDs", nil) log.ZInfo(ctx, "emptyOnlinePusher GetOnlinePushFailedUserIDs", nil)
return nil return nil
} }
func NewOnlinePusher(disCov discovery.SvcDiscoveryRegistry, config *Config) OnlinePusher { func NewOnlinePusher(disCov discovery.Conn, config *Config) (OnlinePusher, error) {
if conf.Standalone() {
if config.runTimeEnv == conf.KUBERNETES { return NewDefaultAllNode(disCov, config), nil
return NewDefaultAllNode(disCov, config) }
if runtimeenv.RuntimeEnvironment() == conf.KUBERNETES {
return NewDefaultAllNode(disCov, config), nil
} }
switch config.Discovery.Enable { switch config.Discovery.Enable {
case conf.ETCD: case conf.ETCD:
return NewDefaultAllNode(disCov, config) return NewDefaultAllNode(disCov, config), nil
default: default:
log.ZError(context.Background(), "NewOnlinePusher is error", errs.Wrap(errors.New("unsupported discovery type")), "type", config.Discovery.Enable) return nil, errs.New(fmt.Sprintf("unsupported discovery type %s", config.Discovery.Enable))
return nil
} }
} }
type DefaultAllNode struct { type DefaultAllNode struct {
disCov discovery.SvcDiscoveryRegistry disCov discovery.Conn
config *Config config *Config
} }
func NewDefaultAllNode(disCov discovery.SvcDiscoveryRegistry, config *Config) *DefaultAllNode { func NewDefaultAllNode(disCov discovery.Conn, config *Config) *DefaultAllNode {
return &DefaultAllNode{disCov: disCov, config: config} return &DefaultAllNode{disCov: disCov, config: config}
} }
@@ -166,7 +166,7 @@ func (k *K8sStaticConsistentHash) GetConnsAndOnlinePush(ctx context.Context, msg
} }
} }
log.ZDebug(ctx, "genUsers send hosts struct:", "usersHost", usersHost) log.ZDebug(ctx, "genUsers send hosts struct:", "usersHost", usersHost)
var usersConns = make(map[*grpc.ClientConn][]string) var usersConns = make(map[grpc.ClientConnInterface][]string)
for host, userIds := range usersHost { for host, userIds := range usersHost {
tconn, _ := k.disCov.GetConn(ctx, host) tconn, _ := k.disCov.GetConn(ctx, host)
usersConns[tconn] = userIds usersConns[tconn] = userIds
+79 -25
View File
@@ -2,39 +2,45 @@ package push
import ( import (
"context" "context"
"github.com/openimsdk/tools/mq"
"math/rand"
"strconv"
"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/pkg/authverify"
"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/mcache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/mqbuild"
pbpush "github.com/openimsdk/protocol/push" pbpush "github.com/openimsdk/protocol/push"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/utils/runtimeenv" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
type pushServer struct { type pushServer struct {
pbpush.UnimplementedPushMsgServiceServer pbpush.UnimplementedPushMsgServiceServer
database controller.PushDatabase database controller.PushDatabase
disCov discovery.SvcDiscoveryRegistry disCov discovery.Conn
offlinePusher offlinepush.OfflinePusher offlinePusher offlinepush.OfflinePusher
pushCh *ConsumerHandler
offlinePushCh *OfflinePushConsumerHandler
} }
type Config struct { type Config struct {
RpcConfig config.Push RpcConfig config.Push
RedisConfig config.Redis RedisConfig config.Redis
MongoConfig config.Mongo
KafkaConfig config.Kafka KafkaConfig config.Kafka
NotificationConfig config.Notification NotificationConfig config.Notification
Share config.Share Share config.Share
WebhooksConfig config.Webhooks WebhooksConfig config.Webhooks
LocalCacheConfig config.LocalCache LocalCacheConfig config.LocalCache
Discovery config.Discovery Discovery config.Discovery
FcmConfigPath string FcmConfigPath config.Path
runTimeEnv string
} }
func (p pushServer) DelUserPushToken(ctx context.Context, func (p pushServer) DelUserPushToken(ctx context.Context,
@@ -45,42 +51,90 @@ func (p pushServer) DelUserPushToken(ctx context.Context,
return &pbpush.DelUserPushTokenResp{}, nil return &pbpush.DelUserPushTokenResp{}, nil
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
config.runTimeEnv = runtimeenv.PrintRuntimeEnvironment() dbb := dbbuild.NewBuilder(&config.MongoConfig, &config.RedisConfig)
rdb, err := dbb.Redis(ctx)
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build())
if err != nil { if err != nil {
return err return err
} }
cacheModel := redis.NewThirdCache(rdb) var cacheModel cache.ThirdCache
offlinePusher, err := offlinepush.NewOfflinePusher(&config.RpcConfig, cacheModel, config.FcmConfigPath) if rdb == nil {
mdb, err := dbb.Mongo(ctx)
if err != nil {
return err
}
mc, err := mgo.NewCacheMgo(mdb.GetDB())
if err != nil {
return err
}
cacheModel = mcache.NewThirdCache(mc)
} else {
cacheModel = redis.NewThirdCache(rdb)
}
offlinePusher, err := offlinepush.NewOfflinePusher(&config.RpcConfig, cacheModel, string(config.FcmConfigPath))
if err != nil {
return err
}
builder := mqbuild.NewBuilder(&config.KafkaConfig)
offlinePushProducer, err := builder.GetTopicProducer(ctx, config.KafkaConfig.ToOfflinePushTopic)
if err != nil {
return err
}
database := controller.NewPushDatabase(cacheModel, offlinePushProducer)
pushConsumer, err := builder.GetTopicConsumer(ctx, config.KafkaConfig.ToPushTopic)
if err != nil {
return err
}
offlinePushConsumer, err := builder.GetTopicConsumer(ctx, config.KafkaConfig.ToOfflinePushTopic)
if err != nil { if err != nil {
return err return err
} }
database := controller.NewPushDatabase(cacheModel, &config.KafkaConfig) pushHandler, err := NewConsumerHandler(ctx, config, database, offlinePusher, rdb, client)
consumer, err := NewConsumerHandler(ctx, config, database, offlinePusher, rdb, client)
if err != nil { if err != nil {
return err return err
} }
offlinePushConsumer, err := NewOfflinePushConsumerHandler(config, offlinePusher) offlineHandler := NewOfflinePushConsumerHandler(offlinePusher)
if err != nil {
return err
}
pbpush.RegisterPushMsgServiceServer(server, &pushServer{ pbpush.RegisterPushMsgServiceServer(server, &pushServer{
database: database, database: database,
disCov: client, disCov: client,
offlinePusher: offlinePusher, offlinePusher: offlinePusher,
pushCh: consumer,
offlinePushCh: offlinePushConsumer,
}) })
go consumer.pushConsumerGroup.RegisterHandleAndConsumer(ctx, consumer) go func() {
pushHandler.WaitCache()
fn := func(msg mq.Message) error {
pushHandler.HandleMs2PsChat(authverify.WithTempAdmin(msg.Context()), msg.Value())
return nil
}
consumerCtx := mcontext.SetOperationID(context.Background(), "push_"+strconv.Itoa(int(rand.Uint32())))
log.ZInfo(consumerCtx, "begin consume messages")
for {
if err := pushConsumer.Subscribe(consumerCtx, fn); err != nil {
log.ZError(consumerCtx, "subscribe err", err)
return
}
}
}()
go offlinePushConsumer.OfflinePushConsumerGroup.RegisterHandleAndConsumer(ctx, offlinePushConsumer) go func() {
fn := func(msg mq.Message) error {
offlineHandler.HandleMsg2OfflinePush(msg.Context(), msg.Value())
return nil
}
consumerCtx := mcontext.SetOperationID(context.Background(), "push_"+strconv.Itoa(int(rand.Uint32())))
log.ZInfo(consumerCtx, "begin consume messages")
for {
if err := offlinePushConsumer.Subscribe(consumerCtx, fn); err != nil {
log.ZError(consumerCtx, "subscribe err", err)
return
}
}
}()
return nil return nil
} }
+31 -52
View File
@@ -3,12 +3,8 @@ package push
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"math/rand"
"strconv"
"time" "time"
"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"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
@@ -16,6 +12,7 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/open-im-server/v3/pkg/rpccache" "github.com/openimsdk/open-im-server/v3/pkg/rpccache"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil" "github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msggateway" "github.com/openimsdk/protocol/msggateway"
@@ -24,7 +21,6 @@ import (
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/mq/kafka"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/jsonutil" "github.com/openimsdk/tools/utils/jsonutil"
"github.com/openimsdk/tools/utils/timeutil" "github.com/openimsdk/tools/utils/timeutil"
@@ -33,11 +29,11 @@ import (
) )
type ConsumerHandler struct { type ConsumerHandler struct {
pushConsumerGroup *kafka.MConsumerGroup //pushConsumerGroup mq.Consumer
offlinePusher offlinepush.OfflinePusher offlinePusher offlinepush.OfflinePusher
onlinePusher OnlinePusher onlinePusher OnlinePusher
pushDatabase controller.PushDatabase pushDatabase controller.PushDatabase
onlineCache *rpccache.OnlineCache onlineCache rpccache.OnlineCache
groupLocalCache *rpccache.GroupLocalCache groupLocalCache *rpccache.GroupLocalCache
conversationLocalCache *rpccache.ConversationLocalCache conversationLocalCache *rpccache.ConversationLocalCache
webhookClient *webhook.Client webhookClient *webhook.Client
@@ -48,15 +44,7 @@ type ConsumerHandler struct {
conversationClient *rpcli.ConversationClient conversationClient *rpcli.ConversationClient
} }
func NewConsumerHandler(ctx context.Context, config *Config, database controller.PushDatabase, offlinePusher offlinepush.OfflinePusher, rdb redis.UniversalClient, func NewConsumerHandler(ctx context.Context, config *Config, database controller.PushDatabase, offlinePusher offlinepush.OfflinePusher, rdb redis.UniversalClient, client discovery.Conn) (*ConsumerHandler, error) {
client discovery.SvcDiscoveryRegistry) (*ConsumerHandler, error) {
var consumerHandler ConsumerHandler
var err error
consumerHandler.pushConsumerGroup, err = kafka.NewMConsumerGroup(config.KafkaConfig.Build(), config.KafkaConfig.ToPushGroupID,
[]string{config.KafkaConfig.ToPushTopic}, true)
if err != nil {
return nil, err
}
userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User) userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -73,13 +61,18 @@ func NewConsumerHandler(ctx context.Context, config *Config, database controller
if err != nil { if err != nil {
return nil, err return nil, err
} }
onlinePusher, err := NewOnlinePusher(client, config)
if err != nil {
return nil, err
}
var consumerHandler ConsumerHandler
consumerHandler.userClient = rpcli.NewUserClient(userConn) consumerHandler.userClient = rpcli.NewUserClient(userConn)
consumerHandler.groupClient = rpcli.NewGroupClient(groupConn) consumerHandler.groupClient = rpcli.NewGroupClient(groupConn)
consumerHandler.msgClient = rpcli.NewMsgClient(msgConn) consumerHandler.msgClient = rpcli.NewMsgClient(msgConn)
consumerHandler.conversationClient = rpcli.NewConversationClient(conversationConn) consumerHandler.conversationClient = rpcli.NewConversationClient(conversationConn)
consumerHandler.offlinePusher = offlinePusher consumerHandler.offlinePusher = offlinePusher
consumerHandler.onlinePusher = NewOnlinePusher(client, config) consumerHandler.onlinePusher = onlinePusher
consumerHandler.groupLocalCache = rpccache.NewGroupLocalCache(consumerHandler.groupClient, &config.LocalCacheConfig, rdb) consumerHandler.groupLocalCache = rpccache.NewGroupLocalCache(consumerHandler.groupClient, &config.LocalCacheConfig, rdb)
consumerHandler.conversationLocalCache = rpccache.NewConversationLocalCache(consumerHandler.conversationClient, &config.LocalCacheConfig, rdb) consumerHandler.conversationLocalCache = rpccache.NewConversationLocalCache(consumerHandler.conversationClient, &config.LocalCacheConfig, rdb)
consumerHandler.webhookClient = webhook.NewWebhookClient(config.WebhooksConfig.URL) consumerHandler.webhookClient = webhook.NewWebhookClient(config.WebhooksConfig.URL)
@@ -92,7 +85,7 @@ func NewConsumerHandler(ctx context.Context, config *Config, database controller
return &consumerHandler, nil return &consumerHandler, nil
} }
func (c *ConsumerHandler) handleMs2PsChat(ctx context.Context, msg []byte) { func (c *ConsumerHandler) HandleMs2PsChat(ctx context.Context, msg []byte) {
msgFromMQ := pbpush.PushMsgReq{} msgFromMQ := pbpush.PushMsgReq{}
if err := proto.Unmarshal(msg, &msgFromMQ); err != nil { if err := proto.Unmarshal(msg, &msgFromMQ); err != nil {
log.ZError(ctx, "push Unmarshal msg err", err, "msg", string(msg)) log.ZError(ctx, "push Unmarshal msg err", err, "msg", string(msg))
@@ -126,25 +119,8 @@ func (c *ConsumerHandler) handleMs2PsChat(ctx context.Context, msg []byte) {
} }
} }
func (*ConsumerHandler) Setup(sarama.ConsumerGroupSession) error { return nil } func (c *ConsumerHandler) WaitCache() {
c.onlineCache.WaitCache()
func (*ConsumerHandler) Cleanup(sarama.ConsumerGroupSession) error { return nil }
func (c *ConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim sarama.ConsumerGroupClaim) error {
c.onlineCache.Lock.Lock()
for c.onlineCache.CurrentPhase.Load() < rpccache.DoSubscribeOver {
c.onlineCache.Cond.Wait()
}
c.onlineCache.Lock.Unlock()
ctx := mcontext.SetOperationID(context.TODO(), strconv.FormatInt(time.Now().UnixNano()+int64(rand.Uint32()), 10))
log.ZInfo(ctx, "begin consume messages")
for msg := range claim.Messages() {
ctx := c.pushConsumerGroup.GetContextFromMsg(msg)
c.handleMs2PsChat(ctx, msg.Value)
sess.MarkMessage(msg, "")
}
return nil
} }
// Push2User Suitable for two types of conversations, one is SingleChatType and the other is NotificationChatType. // Push2User Suitable for two types of conversations, one is SingleChatType and the other is NotificationChatType.
@@ -152,24 +128,24 @@ func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *
log.ZInfo(ctx, "Get msg from msg_transfer And push msg", "userIDs", userIDs, "msg", msg.String()) log.ZInfo(ctx, "Get msg from msg_transfer And push msg", "userIDs", userIDs, "msg", msg.String())
defer func(duration time.Time) { defer func(duration time.Time) {
t := time.Since(duration) t := time.Since(duration)
log.ZInfo(ctx, "Get msg from msg_transfer And push msg", "msg", msg.String(), "time cost", t) log.ZInfo(ctx, "Get msg from msg_transfer And push msg end", "msg", msg.String(), "time cost", t)
}(time.Now()) }(time.Now())
if err := c.webhookBeforeOnlinePush(ctx, &c.config.WebhooksConfig.BeforeOnlinePush, userIDs, msg); err != nil { if err := c.webhookBeforeOnlinePush(ctx, &c.config.WebhooksConfig.BeforeOnlinePush, userIDs, msg); err != nil {
return err return err
} }
log.ZInfo(ctx, "webhookBeforeOnlinePush end")
wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, userIDs) wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, userIDs)
if err != nil { if err != nil {
return err return err
} }
log.ZInfo(ctx, "single and notification push result", "result", wsResults, "msg", msg, "push_to_userID", userIDs) log.ZDebug(ctx, "single and notification push result", "result", wsResults, "msg", msg, "push_to_userID", userIDs)
log.ZInfo(ctx, "single and notification push end")
if !c.shouldPushOffline(ctx, msg) { if !c.shouldPushOffline(ctx, msg) {
return nil return nil
} }
log.ZInfo(ctx, "shouldPushOffline end") log.ZInfo(ctx, "pushOffline start")
for _, v := range wsResults { for _, v := range wsResults {
//message sender do not need offline push //message sender do not need offline push
@@ -188,14 +164,14 @@ func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *
if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, needOfflinePushUserID, msg, &offlinePushUserID); err != nil { if err = c.webhookBeforeOfflinePush(ctx, &c.config.WebhooksConfig.BeforeOfflinePush, needOfflinePushUserID, msg, &offlinePushUserID); err != nil {
return err return err
} }
log.ZInfo(ctx, "webhookBeforeOfflinePush end")
if len(offlinePushUserID) > 0 { if len(offlinePushUserID) > 0 {
needOfflinePushUserID = offlinePushUserID needOfflinePushUserID = offlinePushUserID
} }
err = c.offlinePushMsg(ctx, msg, needOfflinePushUserID) err = c.offlinePushMsg(ctx, msg, needOfflinePushUserID)
if err != nil { if err != nil {
log.ZWarn(ctx, "offlinePushMsg failed", err, "needOfflinePushUserID", needOfflinePushUserID, "msg", msg) log.ZDebug(ctx, "offlinePushMsg failed", err, "needOfflinePushUserID", needOfflinePushUserID, "msg", msg)
log.ZWarn(ctx, "offlinePushMsg failed", err, "needOfflinePushUserID length", len(needOfflinePushUserID), "msg", msg)
return nil return nil
} }
@@ -207,7 +183,10 @@ func (c *ConsumerHandler) shouldPushOffline(_ context.Context, msg *sdkws.MsgDat
if !isOfflinePush { if !isOfflinePush {
return false return false
} }
if msg.ContentType == constant.SignalingNotification { switch msg.ContentType {
case constant.RoomParticipantsConnectedNotification:
return false
case constant.RoomParticipantsDisconnectedNotification:
return false return false
} }
return true return true
@@ -250,26 +229,24 @@ func (c *ConsumerHandler) Push2Group(ctx context.Context, groupID string, msg *s
&pushToUserIDs); err != nil { &pushToUserIDs); err != nil {
return err return err
} }
log.ZInfo(ctx, "webhookBeforeGroupOnlinePush end")
err = c.groupMessagesHandler(ctx, groupID, &pushToUserIDs, msg) err = c.groupMessagesHandler(ctx, groupID, &pushToUserIDs, msg)
if err != nil { if err != nil {
return err return err
} }
log.ZInfo(ctx, "groupMessagesHandler end")
wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs) wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs)
if err != nil { if err != nil {
return err return err
} }
log.ZInfo(ctx, "group push result", "result", wsResults, "msg", msg) log.ZDebug(ctx, "group push result", "result", wsResults, "msg", msg)
log.ZInfo(ctx, "online group push end")
if !c.shouldPushOffline(ctx, msg) { if !c.shouldPushOffline(ctx, msg) {
return nil return nil
} }
needOfflinePushUserIDs := c.onlinePusher.GetOnlinePushFailedUserIDs(ctx, msg, wsResults, &pushToUserIDs) needOfflinePushUserIDs := c.onlinePusher.GetOnlinePushFailedUserIDs(ctx, msg, wsResults, &pushToUserIDs)
log.ZInfo(ctx, "GetOnlinePushFailedUserIDs end")
//filter some user, like don not disturb or don't need offline push etc. //filter some user, like don not disturb or don't need offline push etc.
needOfflinePushUserIDs, err = c.filterGroupMessageOfflinePush(ctx, groupID, msg, needOfflinePushUserIDs) needOfflinePushUserIDs, err = c.filterGroupMessageOfflinePush(ctx, groupID, msg, needOfflinePushUserIDs)
if err != nil { if err != nil {
@@ -297,9 +274,11 @@ func (c *ConsumerHandler) asyncOfflinePush(ctx context.Context, needOfflinePushU
needOfflinePushUserIDs = offlinePushUserIDs needOfflinePushUserIDs = offlinePushUserIDs
} }
if err := c.pushDatabase.MsgToOfflinePushMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(msg.SendID, msg.RecvID), needOfflinePushUserIDs, msg); err != nil { if err := c.pushDatabase.MsgToOfflinePushMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(msg.SendID, msg.RecvID), needOfflinePushUserIDs, msg); err != nil {
log.ZError(ctx, "Msg To OfflinePush MQ error", err, "needOfflinePushUserIDs", log.ZDebug(ctx, "Msg To OfflinePush MQ error", err, "needOfflinePushUserIDs",
needOfflinePushUserIDs, "msg", msg) needOfflinePushUserIDs, "msg", msg)
prommetrics.SingleChatMsgProcessFailedCounter.Inc() log.ZWarn(ctx, "Msg To OfflinePush MQ error", err, "needOfflinePushUserIDs length",
len(needOfflinePushUserIDs), "msg", msg)
prommetrics.GroupChatMsgProcessFailedCounter.Inc()
return return
} }
} }
@@ -338,8 +317,8 @@ func (c *ConsumerHandler) groupMessagesHandler(ctx context.Context, groupID stri
return err return err
} }
log.ZDebug(ctx, "GroupDismissedNotificationInfo****", "groupID", groupID, "num", len(*pushToUserIDs), "list", pushToUserIDs) log.ZDebug(ctx, "GroupDismissedNotificationInfo****", "groupID", groupID, "num", len(*pushToUserIDs), "list", pushToUserIDs)
if len(c.config.Share.IMAdminUserID) > 0 { if len(c.config.Share.IMAdminUser.UserIDs) > 0 {
ctx = mcontext.WithOpUserIDContext(ctx, c.config.Share.IMAdminUserID[0]) ctx = mcontext.WithOpUserIDContext(ctx, c.config.Share.IMAdminUser.UserIDs[0])
} }
defer func(groupID string) { defer func(groupID string) {
if err := c.groupClient.DismissGroup(ctx, groupID, true); err != nil { if err := c.groupClient.DismissGroup(ctx, groupID, true); err != nil {
+48 -18
View File
@@ -18,11 +18,14 @@ import (
"context" "context"
"errors" "errors"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/mcache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"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"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
@@ -43,23 +46,40 @@ import (
type authServer struct { type authServer struct {
pbauth.UnimplementedAuthServer pbauth.UnimplementedAuthServer
authDatabase controller.AuthDatabase authDatabase controller.AuthDatabase
RegisterCenter discovery.SvcDiscoveryRegistry RegisterCenter discovery.Conn
config *Config config *Config
userClient *rpcli.UserClient userClient *rpcli.UserClient
adminUserIDs []string
} }
type Config struct { type Config struct {
RpcConfig config.Auth RpcConfig config.Auth
RedisConfig config.Redis RedisConfig config.Redis
MongoConfig config.Mongo
Share config.Share Share config.Share
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongoConfig, &config.RedisConfig)
rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
var token cache.TokenModel
if rdb == nil {
mdb, err := dbb.Mongo(ctx)
if err != nil {
return err
}
mc, err := mgo.NewCacheMgo(mdb.GetDB())
if err != nil {
return err
}
token = mcache.NewTokenCacheModel(mc, config.RpcConfig.TokenPolicy.Expire)
} else {
token = redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire)
}
userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User) userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return err return err
@@ -67,14 +87,15 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
pbauth.RegisterAuthServer(server, &authServer{ pbauth.RegisterAuthServer(server, &authServer{
RegisterCenter: client, RegisterCenter: client,
authDatabase: controller.NewAuthDatabase( authDatabase: controller.NewAuthDatabase(
redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire), token,
config.Share.Secret, config.Share.Secret,
config.RpcConfig.TokenPolicy.Expire, config.RpcConfig.TokenPolicy.Expire,
config.Share.MultiLogin, config.Share.MultiLogin,
config.Share.IMAdminUserID, config.Share.IMAdminUser.UserIDs,
), ),
config: config, config: config,
userClient: rpcli.NewUserClient(userConn), userClient: rpcli.NewUserClient(userConn),
adminUserIDs: config.Share.IMAdminUser.UserIDs,
}) })
return nil return nil
} }
@@ -85,8 +106,8 @@ func (s *authServer) GetAdminToken(ctx context.Context, req *pbauth.GetAdminToke
return nil, errs.ErrNoPermission.WrapMsg("secret invalid") return nil, errs.ErrNoPermission.WrapMsg("secret invalid")
} }
if !datautil.Contain(req.UserID, s.config.Share.IMAdminUserID...) { if !datautil.Contain(req.UserID, s.adminUserIDs...) {
return nil, errs.ErrArgs.WrapMsg("userID is error.", "userID", req.UserID, "adminUserID", s.config.Share.IMAdminUserID) return nil, errs.ErrArgs.WrapMsg("userID is error.", "userID", req.UserID, "adminUserID", s.adminUserIDs)
} }
@@ -106,7 +127,7 @@ func (s *authServer) GetAdminToken(ctx context.Context, req *pbauth.GetAdminToke
} }
func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenReq) (*pbauth.GetUserTokenResp, error) { func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenReq) (*pbauth.GetUserTokenResp, error) {
if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err return nil, err
} }
@@ -116,7 +137,7 @@ func (s *authServer) GetUserToken(ctx context.Context, req *pbauth.GetUserTokenR
resp := pbauth.GetUserTokenResp{} resp := pbauth.GetUserTokenResp{}
if authverify.IsManagerUserID(req.UserID, s.config.Share.IMAdminUserID) { if authverify.CheckUserIsAdmin(ctx, req.UserID) {
return nil, errs.ErrNoPermission.WrapMsg("don't get Admin token") return nil, errs.ErrNoPermission.WrapMsg("don't get Admin token")
} }
user, err := s.userClient.GetUserInfo(ctx, req.UserID) user, err := s.userClient.GetUserInfo(ctx, req.UserID)
@@ -140,15 +161,17 @@ func (s *authServer) parseToken(ctx context.Context, tokensString string) (claim
if err != nil { if err != nil {
return nil, err return nil, 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
} }
if len(m) == 0 { if len(m) == 0 {
isAdmin := authverify.CheckUserIsAdmin(ctx, claims.UserID)
if isAdmin {
if err = s.authDatabase.GetTemporaryTokensWithoutError(ctx, claims.UserID, claims.PlatformID, tokensString); err == nil {
return claims, nil
}
}
return nil, servererrs.ErrTokenNotExist.Wrap() return nil, servererrs.ErrTokenNotExist.Wrap()
} }
if v, ok := m[tokensString]; ok { if v, ok := m[tokensString]; ok {
@@ -160,6 +183,13 @@ func (s *authServer) parseToken(ctx context.Context, tokensString string) (claim
default: default:
return nil, errs.Wrap(errs.ErrTokenUnknown) return nil, errs.Wrap(errs.ErrTokenUnknown)
} }
} else {
isAdmin := authverify.CheckUserIsAdmin(ctx, claims.UserID)
if isAdmin {
if err = s.authDatabase.GetTemporaryTokensWithoutError(ctx, claims.UserID, claims.PlatformID, tokensString); err == nil {
return claims, nil
}
}
} }
return nil, servererrs.ErrTokenNotExist.Wrap() return nil, servererrs.ErrTokenNotExist.Wrap()
} }
@@ -177,7 +207,7 @@ func (s *authServer) ParseToken(ctx context.Context, req *pbauth.ParseTokenReq)
} }
func (s *authServer) ForceLogout(ctx context.Context, req *pbauth.ForceLogoutReq) (*pbauth.ForceLogoutResp, error) { func (s *authServer) ForceLogout(ctx context.Context, req *pbauth.ForceLogoutReq) (*pbauth.ForceLogoutResp, error) {
if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err return nil, err
} }
if err := s.forceKickOff(ctx, req.UserID, req.PlatformID); err != nil { if err := s.forceKickOff(ctx, req.UserID, req.PlatformID); err != nil {
@@ -192,7 +222,7 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID
return err return err
} }
for _, v := range conns { for _, v := range conns {
log.ZDebug(ctx, "forceKickOff", "conn", v.Target()) log.ZDebug(ctx, "forceKickOff", "userID", userID, "platformID", platformID)
client := msggateway.NewMsgGatewayClient(v) client := msggateway.NewMsgGatewayClient(v)
kickReq := &msggateway.KickUserOfflineReq{KickUserIDList: []string{userID}, PlatformID: platformID} kickReq := &msggateway.KickUserOfflineReq{KickUserIDList: []string{userID}, PlatformID: platformID}
_, err := client.KickUserOffline(ctx, kickReq) _, err := client.KickUserOffline(ctx, kickReq)
+117
View File
@@ -0,0 +1,117 @@
package conversation
import (
"context"
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
dbModel "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/tools/utils/datautil"
)
func (c *conversationServer) webhookBeforeCreateSingleChatConversations(ctx context.Context, before *config.BeforeConfig, req *dbModel.Conversation) error {
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
cbReq := &callbackstruct.CallbackBeforeCreateSingleChatConversationsReq{
CallbackCommand: callbackstruct.CallbackBeforeCreateSingleChatConversationsCommand,
OwnerUserID: req.OwnerUserID,
ConversationID: req.ConversationID,
ConversationType: req.ConversationType,
UserID: req.UserID,
RecvMsgOpt: req.RecvMsgOpt,
IsPinned: req.IsPinned,
IsPrivateChat: req.IsPrivateChat,
BurnDuration: req.BurnDuration,
GroupAtType: req.GroupAtType,
AttachedInfo: req.AttachedInfo,
Ex: req.Ex,
}
resp := &callbackstruct.CallbackBeforeCreateSingleChatConversationsResp{}
if err := c.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
return err
}
datautil.NotNilReplace(&req.RecvMsgOpt, resp.RecvMsgOpt)
datautil.NotNilReplace(&req.IsPinned, resp.IsPinned)
datautil.NotNilReplace(&req.IsPrivateChat, resp.IsPrivateChat)
datautil.NotNilReplace(&req.BurnDuration, resp.BurnDuration)
datautil.NotNilReplace(&req.GroupAtType, resp.GroupAtType)
datautil.NotNilReplace(&req.AttachedInfo, resp.AttachedInfo)
datautil.NotNilReplace(&req.Ex, resp.Ex)
return nil
})
}
func (c *conversationServer) webhookAfterCreateSingleChatConversations(ctx context.Context, after *config.AfterConfig, req *dbModel.Conversation) error {
cbReq := &callbackstruct.CallbackAfterCreateSingleChatConversationsReq{
CallbackCommand: callbackstruct.CallbackAfterCreateSingleChatConversationsCommand,
OwnerUserID: req.OwnerUserID,
ConversationID: req.ConversationID,
ConversationType: req.ConversationType,
UserID: req.UserID,
RecvMsgOpt: req.RecvMsgOpt,
IsPinned: req.IsPinned,
IsPrivateChat: req.IsPrivateChat,
BurnDuration: req.BurnDuration,
GroupAtType: req.GroupAtType,
AttachedInfo: req.AttachedInfo,
Ex: req.Ex,
}
c.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterCreateSingleChatConversationsResp{}, after)
return nil
}
func (c *conversationServer) webhookBeforeCreateGroupChatConversations(ctx context.Context, before *config.BeforeConfig, req *dbModel.Conversation) error {
return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
cbReq := &callbackstruct.CallbackBeforeCreateGroupChatConversationsReq{
CallbackCommand: callbackstruct.CallbackBeforeCreateGroupChatConversationsCommand,
ConversationID: req.ConversationID,
ConversationType: req.ConversationType,
GroupID: req.GroupID,
RecvMsgOpt: req.RecvMsgOpt,
IsPinned: req.IsPinned,
IsPrivateChat: req.IsPrivateChat,
BurnDuration: req.BurnDuration,
GroupAtType: req.GroupAtType,
AttachedInfo: req.AttachedInfo,
Ex: req.Ex,
}
resp := &callbackstruct.CallbackBeforeCreateGroupChatConversationsResp{}
if err := c.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
return err
}
datautil.NotNilReplace(&req.RecvMsgOpt, resp.RecvMsgOpt)
datautil.NotNilReplace(&req.IsPinned, resp.IsPinned)
datautil.NotNilReplace(&req.IsPrivateChat, resp.IsPrivateChat)
datautil.NotNilReplace(&req.BurnDuration, resp.BurnDuration)
datautil.NotNilReplace(&req.GroupAtType, resp.GroupAtType)
datautil.NotNilReplace(&req.AttachedInfo, resp.AttachedInfo)
datautil.NotNilReplace(&req.Ex, resp.Ex)
return nil
})
}
func (c *conversationServer) webhookAfterCreateGroupChatConversations(ctx context.Context, after *config.AfterConfig, req *dbModel.Conversation) error {
cbReq := &callbackstruct.CallbackAfterCreateGroupChatConversationsReq{
CallbackCommand: callbackstruct.CallbackAfterCreateGroupChatConversationsCommand,
ConversationID: req.ConversationID,
ConversationType: req.ConversationType,
GroupID: req.GroupID,
RecvMsgOpt: req.RecvMsgOpt,
IsPinned: req.IsPinned,
IsPrivateChat: req.IsPrivateChat,
BurnDuration: req.BurnDuration,
GroupAtType: req.GroupAtType,
AttachedInfo: req.AttachedInfo,
Ex: req.Ex,
}
c.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &callbackstruct.CallbackAfterCreateGroupChatConversationsResp{}, after)
return nil
}
+150 -138
View File
@@ -16,31 +16,32 @@ package conversation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"sort" "sort"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" "github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
dbModel "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/localcache"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/tools/db/redisutil"
"google.golang.org/grpc"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/convert" "github.com/openimsdk/open-im-server/v3/pkg/common/convert"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
dbModel "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/localcache"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
pbconversation "github.com/openimsdk/protocol/conversation" pbconversation "github.com/openimsdk/protocol/conversation"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"google.golang.org/grpc"
) )
type conversationServer struct { type conversationServer struct {
@@ -50,9 +51,10 @@ type conversationServer struct {
conversationNotificationSender *ConversationNotificationSender conversationNotificationSender *ConversationNotificationSender
config *Config config *Config
userClient *rpcli.UserClient webhookClient *webhook.Client
msgClient *rpcli.MsgClient userClient *rpcli.UserClient
groupClient *rpcli.GroupClient msgClient *rpcli.MsgClient
groupClient *rpcli.GroupClient
} }
type Config struct { type Config struct {
@@ -61,16 +63,18 @@ type Config struct {
MongodbConfig config.Mongo MongodbConfig config.Mongo
NotificationConfig config.Notification NotificationConfig config.Notification
Share config.Share Share config.Share
WebhooksConfig config.Webhooks
LocalCacheConfig config.LocalCache LocalCacheConfig config.LocalCache
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
@@ -90,20 +94,32 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
if err != nil { if err != nil {
return err return err
} }
msgClient := rpcli.NewMsgClient(msgConn) msgClient := rpcli.NewMsgClient(msgConn)
cs := conversationServer{
config: config,
webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL),
userClient: rpcli.NewUserClient(userConn),
groupClient: rpcli.NewGroupClient(groupConn),
msgClient: msgClient,
}
cs.conversationNotificationSender = NewConversationNotificationSender(&config.NotificationConfig, msgClient)
cs.conversationDatabase = controller.NewConversationDatabase(
conversationDB,
redis.NewConversationRedis(rdb, &config.LocalCacheConfig, conversationDB),
mgocli.GetTx())
localcache.InitLocalCache(&config.LocalCacheConfig) localcache.InitLocalCache(&config.LocalCacheConfig)
pbconversation.RegisterConversationServer(server, &conversationServer{ pbconversation.RegisterConversationServer(server, &cs)
conversationNotificationSender: NewConversationNotificationSender(&config.NotificationConfig, msgClient),
conversationDatabase: controller.NewConversationDatabase(conversationDB,
redis.NewConversationRedis(rdb, &config.LocalCacheConfig, redis.GetRocksCacheOptions(), conversationDB), mgocli.GetTx()),
userClient: rpcli.NewUserClient(userConn),
groupClient: rpcli.NewGroupClient(groupConn),
msgClient: msgClient,
})
return nil return nil
} }
func (c *conversationServer) GetConversation(ctx context.Context, req *pbconversation.GetConversationReq) (*pbconversation.GetConversationResp, error) { func (c *conversationServer) GetConversation(ctx context.Context, req *pbconversation.GetConversationReq) (*pbconversation.GetConversationResp, error) {
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err
}
conversations, err := c.conversationDatabase.FindConversations(ctx, req.OwnerUserID, []string{req.ConversationID}) conversations, err := c.conversationDatabase.FindConversations(ctx, req.OwnerUserID, []string{req.ConversationID})
if err != nil { if err != nil {
return nil, err return nil, err
@@ -117,7 +133,9 @@ func (c *conversationServer) GetConversation(ctx context.Context, req *pbconvers
} }
func (c *conversationServer) GetSortedConversationList(ctx context.Context, req *pbconversation.GetSortedConversationListReq) (resp *pbconversation.GetSortedConversationListResp, err error) { func (c *conversationServer) GetSortedConversationList(ctx context.Context, req *pbconversation.GetSortedConversationListReq) (resp *pbconversation.GetSortedConversationListResp, err error) {
log.ZDebug(ctx, "GetSortedConversationList", "seqs", req, "userID", req.UserID) if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
var conversationIDs []string var conversationIDs []string
if len(req.ConversationIDs) == 0 { if len(req.ConversationIDs) == 0 {
conversationIDs, err = c.conversationDatabase.GetConversationIDs(ctx, req.UserID) conversationIDs, err = c.conversationDatabase.GetConversationIDs(ctx, req.UserID)
@@ -190,6 +208,9 @@ func (c *conversationServer) GetSortedConversationList(ctx context.Context, req
} }
func (c *conversationServer) GetAllConversations(ctx context.Context, req *pbconversation.GetAllConversationsReq) (*pbconversation.GetAllConversationsResp, error) { func (c *conversationServer) GetAllConversations(ctx context.Context, req *pbconversation.GetAllConversationsReq) (*pbconversation.GetAllConversationsResp, error) {
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err
}
conversations, err := c.conversationDatabase.GetUserAllConversation(ctx, req.OwnerUserID) conversations, err := c.conversationDatabase.GetUserAllConversation(ctx, req.OwnerUserID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -200,6 +221,9 @@ func (c *conversationServer) GetAllConversations(ctx context.Context, req *pbcon
} }
func (c *conversationServer) GetConversations(ctx context.Context, req *pbconversation.GetConversationsReq) (*pbconversation.GetConversationsResp, error) { func (c *conversationServer) GetConversations(ctx context.Context, req *pbconversation.GetConversationsReq) (*pbconversation.GetConversationsResp, error) {
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err
}
conversations, err := c.getConversations(ctx, req.OwnerUserID, req.ConversationIDs) conversations, err := c.getConversations(ctx, req.OwnerUserID, req.ConversationIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -219,8 +243,14 @@ func (c *conversationServer) getConversations(ctx context.Context, ownerUserID s
return convert.ConversationsDB2Pb(conversations), nil return convert.ConversationsDB2Pb(conversations), nil
} }
// Deprecated
func (c *conversationServer) SetConversation(ctx context.Context, req *pbconversation.SetConversationReq) (*pbconversation.SetConversationResp, error) { func (c *conversationServer) SetConversation(ctx context.Context, req *pbconversation.SetConversationReq) (*pbconversation.SetConversationResp, error) {
if err := authverify.CheckAccess(ctx, req.GetConversation().GetUserID()); err != nil {
return nil, err
}
var conversation dbModel.Conversation var conversation dbModel.Conversation
conversation.CreateTime = time.Now()
if err := datautil.CopyStructFields(&conversation, req.Conversation); err != nil { if err := datautil.CopyStructFields(&conversation, req.Conversation); err != nil {
return nil, err return nil, err
} }
@@ -234,8 +264,10 @@ func (c *conversationServer) SetConversation(ctx context.Context, req *pbconvers
} }
func (c *conversationServer) SetConversations(ctx context.Context, req *pbconversation.SetConversationsReq) (*pbconversation.SetConversationsResp, error) { func (c *conversationServer) SetConversations(ctx context.Context, req *pbconversation.SetConversationsReq) (*pbconversation.SetConversationsResp, error) {
if req.Conversation == nil { for _, userID := range req.UserIDs {
return nil, errs.ErrArgs.WrapMsg("conversation must not be nil") if err := authverify.CheckAccess(ctx, userID); err != nil {
return nil, err
}
} }
if req.Conversation.ConversationType == constant.WriteGroupChatType { if req.Conversation.ConversationType == constant.WriteGroupChatType {
groupInfo, err := c.groupClient.GetGroupInfo(ctx, req.Conversation.GroupID) groupInfo, err := c.groupClient.GetGroupInfo(ctx, req.Conversation.GroupID)
@@ -270,110 +302,31 @@ func (c *conversationServer) SetConversations(ctx context.Context, req *pbconver
conversation.ConversationType = req.Conversation.ConversationType conversation.ConversationType = req.Conversation.ConversationType
conversation.UserID = req.Conversation.UserID conversation.UserID = req.Conversation.UserID
conversation.GroupID = req.Conversation.GroupID conversation.GroupID = req.Conversation.GroupID
conversation.CreateTime = time.Now()
m := make(map[string]any) m, conversation, err := UpdateConversationsMap(ctx, req)
if err != nil {
setConversationFieldsFunc := func() { return nil, err
if req.Conversation.RecvMsgOpt != nil {
conversation.RecvMsgOpt = req.Conversation.RecvMsgOpt.Value
m["recv_msg_opt"] = req.Conversation.RecvMsgOpt.Value
}
if req.Conversation.AttachedInfo != nil {
conversation.AttachedInfo = req.Conversation.AttachedInfo.Value
m["attached_info"] = req.Conversation.AttachedInfo.Value
}
if req.Conversation.Ex != nil {
conversation.Ex = req.Conversation.Ex.Value
m["ex"] = req.Conversation.Ex.Value
}
if req.Conversation.IsPinned != nil {
conversation.IsPinned = req.Conversation.IsPinned.Value
m["is_pinned"] = req.Conversation.IsPinned.Value
}
if req.Conversation.GroupAtType != nil {
conversation.GroupAtType = req.Conversation.GroupAtType.Value
m["group_at_type"] = req.Conversation.GroupAtType.Value
}
if req.Conversation.MsgDestructTime != nil {
conversation.MsgDestructTime = req.Conversation.MsgDestructTime.Value
m["msg_destruct_time"] = req.Conversation.MsgDestructTime.Value
}
if req.Conversation.IsMsgDestruct != nil {
conversation.IsMsgDestruct = req.Conversation.IsMsgDestruct.Value
m["is_msg_destruct"] = req.Conversation.IsMsgDestruct.Value
}
if req.Conversation.BurnDuration != nil {
conversation.BurnDuration = req.Conversation.BurnDuration.Value
m["burn_duration"] = req.Conversation.BurnDuration.Value
}
} }
// set need set field in conversation
setConversationFieldsFunc()
for userID := range conversationMap { for userID := range conversationMap {
unequal := len(m) unequal := UserUpdateCheckMap(ctx, userID, req.Conversation, conversationMap[userID])
if req.Conversation.RecvMsgOpt != nil { if unequal {
if req.Conversation.RecvMsgOpt.Value == conversationMap[userID].RecvMsgOpt {
unequal--
}
}
if req.Conversation.AttachedInfo != nil {
if req.Conversation.AttachedInfo.Value == conversationMap[userID].AttachedInfo {
unequal--
}
}
if req.Conversation.Ex != nil {
if req.Conversation.Ex.Value == conversationMap[userID].Ex {
unequal--
}
}
if req.Conversation.IsPinned != nil {
if req.Conversation.IsPinned.Value == conversationMap[userID].IsPinned {
unequal--
}
}
if req.Conversation.GroupAtType != nil {
if req.Conversation.GroupAtType.Value == conversationMap[userID].GroupAtType {
unequal--
}
}
if req.Conversation.MsgDestructTime != nil {
if req.Conversation.MsgDestructTime.Value == conversationMap[userID].MsgDestructTime {
unequal--
}
}
if req.Conversation.IsMsgDestruct != nil {
if req.Conversation.IsMsgDestruct.Value == conversationMap[userID].IsMsgDestruct {
unequal--
}
}
if req.Conversation.BurnDuration != nil {
if req.Conversation.BurnDuration.Value == conversationMap[userID].BurnDuration {
unequal--
}
}
if unequal > 0 {
needUpdateUsersList = append(needUpdateUsersList, userID) needUpdateUsersList = append(needUpdateUsersList, userID)
} }
} }
if len(m) != 0 && len(needUpdateUsersList) != 0 { if len(m) != 0 && len(needUpdateUsersList) != 0 {
if err := c.conversationDatabase.SetUsersConversationFieldTx(ctx, needUpdateUsersList, &conversation, m); err != nil { if err := c.conversationDatabase.SetUsersConversationFieldTx(ctx, needUpdateUsersList, &conversation, m); err != nil {
return nil, err return nil, err
} }
for _, v := range needUpdateUsersList { for _, userID := range needUpdateUsersList {
c.conversationNotificationSender.ConversationChangeNotification(ctx, v, []string{req.Conversation.ConversationID}) c.conversationNotificationSender.ConversationChangeNotification(ctx, userID, []string{req.Conversation.ConversationID})
} }
} }
if req.Conversation.IsPrivateChat != nil && req.Conversation.ConversationType != constant.ReadGroupChatType { if req.Conversation.IsPrivateChat != nil && req.Conversation.ConversationType != constant.ReadGroupChatType {
var conversations []*dbModel.Conversation var conversations []*dbModel.Conversation
for _, ownerUserID := range req.UserIDs { for _, ownerUserID := range req.UserIDs {
@@ -396,58 +349,103 @@ func (c *conversationServer) SetConversations(ctx context.Context, req *pbconver
return &pbconversation.SetConversationsResp{}, nil return &pbconversation.SetConversationsResp{}, nil
} }
// Get user IDs with "Do Not Disturb" enabled in super large groups. func (c *conversationServer) UpdateConversationsByUser(ctx context.Context, req *pbconversation.UpdateConversationsByUserReq) (*pbconversation.UpdateConversationsByUserResp, error) {
func (c *conversationServer) GetRecvMsgNotNotifyUserIDs(ctx context.Context, req *pbconversation.GetRecvMsgNotNotifyUserIDsReq) (*pbconversation.GetRecvMsgNotNotifyUserIDsResp, error) { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, errs.New("deprecated") return nil, err
}
m := make(map[string]any)
if req.Ex != nil {
m["ex"] = req.Ex.Value
}
if len(m) > 0 {
if err := c.conversationDatabase.UpdateUserConversations(ctx, req.UserID, m); err != nil {
return nil, err
}
}
return &pbconversation.UpdateConversationsByUserResp{}, nil
} }
// create conversation without notification for msg redis transfer. // create conversation without notification for msg redis transfer.
func (c *conversationServer) CreateSingleChatConversations(ctx context.Context, func (c *conversationServer) CreateSingleChatConversations(ctx context.Context, req *pbconversation.CreateSingleChatConversationsReq) (*pbconversation.CreateSingleChatConversationsResp, error) {
req *pbconversation.CreateSingleChatConversationsReq, var conversation dbModel.Conversation
) (*pbconversation.CreateSingleChatConversationsResp, error) { conversation.CreateTime = time.Now()
switch req.ConversationType { switch req.ConversationType {
case constant.SingleChatType: case constant.SingleChatType:
var conversation dbModel.Conversation // sendUser create
conversation.ConversationID = req.ConversationID conversation.ConversationID = req.ConversationID
conversation.ConversationType = req.ConversationType conversation.ConversationType = req.ConversationType
conversation.OwnerUserID = req.SendID conversation.OwnerUserID = req.SendID
conversation.UserID = req.RecvID conversation.UserID = req.RecvID
if err := c.webhookBeforeCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.BeforeCreateSingleChatConversations, &conversation); err != nil && err != servererrs.ErrCallbackContinue {
return nil, err
}
err := c.conversationDatabase.CreateConversation(ctx, []*dbModel.Conversation{&conversation}) err := c.conversationDatabase.CreateConversation(ctx, []*dbModel.Conversation{&conversation})
if err != nil { if err != nil {
log.ZWarn(ctx, "create conversation failed", err, "conversation", conversation) log.ZWarn(ctx, "create conversation failed", err, "conversation", conversation)
} }
c.webhookAfterCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.AfterCreateSingleChatConversations, &conversation)
// recvUser create
conversation2 := conversation conversation2 := conversation
conversation2.OwnerUserID = req.RecvID conversation2.OwnerUserID = req.RecvID
conversation2.UserID = req.SendID conversation2.UserID = req.SendID
if err := c.webhookBeforeCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.BeforeCreateSingleChatConversations, &conversation); err != nil && err != servererrs.ErrCallbackContinue {
return nil, err
}
err = c.conversationDatabase.CreateConversation(ctx, []*dbModel.Conversation{&conversation2}) err = c.conversationDatabase.CreateConversation(ctx, []*dbModel.Conversation{&conversation2})
if err != nil { if err != nil {
log.ZWarn(ctx, "create conversation failed", err, "conversation2", conversation) log.ZWarn(ctx, "create conversation failed", err, "conversation2", conversation)
} }
c.webhookAfterCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.AfterCreateSingleChatConversations, &conversation2)
case constant.NotificationChatType: case constant.NotificationChatType:
var conversation dbModel.Conversation
conversation.ConversationID = req.ConversationID conversation.ConversationID = req.ConversationID
conversation.ConversationType = req.ConversationType conversation.ConversationType = req.ConversationType
conversation.OwnerUserID = req.RecvID conversation.OwnerUserID = req.RecvID
conversation.UserID = req.SendID conversation.UserID = req.SendID
if err := c.webhookBeforeCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.BeforeCreateSingleChatConversations, &conversation); err != nil && err != servererrs.ErrCallbackContinue {
return nil, err
}
err := c.conversationDatabase.CreateConversation(ctx, []*dbModel.Conversation{&conversation}) err := c.conversationDatabase.CreateConversation(ctx, []*dbModel.Conversation{&conversation})
if err != nil { if err != nil {
log.ZWarn(ctx, "create conversation failed", err, "conversation2", conversation) log.ZWarn(ctx, "create conversation failed", err, "conversation2", conversation)
} }
c.webhookAfterCreateSingleChatConversations(ctx, &c.config.WebhooksConfig.AfterCreateSingleChatConversations, &conversation)
} }
return &pbconversation.CreateSingleChatConversationsResp{}, nil return &pbconversation.CreateSingleChatConversationsResp{}, nil
} }
func (c *conversationServer) CreateGroupChatConversations(ctx context.Context, req *pbconversation.CreateGroupChatConversationsReq) (*pbconversation.CreateGroupChatConversationsResp, error) { func (c *conversationServer) CreateGroupChatConversations(ctx context.Context, req *pbconversation.CreateGroupChatConversationsReq) (*pbconversation.CreateGroupChatConversationsResp, error) {
err := c.conversationDatabase.CreateGroupChatConversation(ctx, req.GroupID, req.UserIDs) var conversation dbModel.Conversation
conversation.ConversationID = msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID)
conversation.GroupID = req.GroupID
conversation.ConversationType = constant.ReadGroupChatType
conversation.CreateTime = time.Now()
if err := c.webhookBeforeCreateGroupChatConversations(ctx, &c.config.WebhooksConfig.BeforeCreateGroupChatConversations, &conversation); err != nil {
return nil, err
}
err := c.conversationDatabase.CreateGroupChatConversation(ctx, req.GroupID, req.UserIDs, &conversation)
if err != nil { if err != nil {
return nil, err return nil, err
} }
conversationID := msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID) if err := c.msgClient.SetUserConversationMaxSeq(ctx, conversation.ConversationID, req.UserIDs, 0); err != nil {
if err := c.msgClient.SetUserConversationMaxSeq(ctx, conversationID, req.UserIDs, 0); err != nil {
return nil, err return nil, err
} }
c.webhookAfterCreateGroupChatConversations(ctx, &c.config.WebhooksConfig.AfterCreateGroupChatConversations, &conversation)
return &pbconversation.CreateGroupChatConversationsResp{}, nil return &pbconversation.CreateGroupChatConversationsResp{}, nil
} }
@@ -480,6 +478,9 @@ func (c *conversationServer) SetConversationMinSeq(ctx context.Context, req *pbc
} }
func (c *conversationServer) GetConversationIDs(ctx context.Context, req *pbconversation.GetConversationIDsReq) (*pbconversation.GetConversationIDsResp, error) { func (c *conversationServer) GetConversationIDs(ctx context.Context, req *pbconversation.GetConversationIDsReq) (*pbconversation.GetConversationIDsResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
conversationIDs, err := c.conversationDatabase.GetConversationIDs(ctx, req.UserID) conversationIDs, err := c.conversationDatabase.GetConversationIDs(ctx, req.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -488,6 +489,9 @@ func (c *conversationServer) GetConversationIDs(ctx context.Context, req *pbconv
} }
func (c *conversationServer) GetUserConversationIDsHash(ctx context.Context, req *pbconversation.GetUserConversationIDsHashReq) (*pbconversation.GetUserConversationIDsHashResp, error) { func (c *conversationServer) GetUserConversationIDsHash(ctx context.Context, req *pbconversation.GetUserConversationIDsHashReq) (*pbconversation.GetUserConversationIDsHashResp, error) {
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err
}
hash, err := c.conversationDatabase.GetUserConversationIDsHash(ctx, req.OwnerUserID) hash, err := c.conversationDatabase.GetUserConversationIDsHash(ctx, req.OwnerUserID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -495,10 +499,7 @@ func (c *conversationServer) GetUserConversationIDsHash(ctx context.Context, req
return &pbconversation.GetUserConversationIDsHashResp{Hash: hash}, nil return &pbconversation.GetUserConversationIDsHashResp{Hash: hash}, nil
} }
func (c *conversationServer) GetConversationsByConversationID( func (c *conversationServer) GetConversationsByConversationID(ctx context.Context, req *pbconversation.GetConversationsByConversationIDReq) (*pbconversation.GetConversationsByConversationIDResp, error) {
ctx context.Context,
req *pbconversation.GetConversationsByConversationIDReq,
) (*pbconversation.GetConversationsByConversationIDResp, error) {
conversations, err := c.conversationDatabase.GetConversationsByConversationID(ctx, req.ConversationIDs) conversations, err := c.conversationDatabase.GetConversationsByConversationID(ctx, req.ConversationIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -552,10 +553,7 @@ func (c *conversationServer) conversationSort(conversations map[int64]string, re
resp.ConversationElems = append(resp.ConversationElems, cons...) resp.ConversationElems = append(resp.ConversationElems, cons...)
} }
func (c *conversationServer) getConversationInfo( func (c *conversationServer) getConversationInfo(ctx context.Context, chatLogs map[string]*sdkws.MsgData, userID string) (map[string]*pbconversation.ConversationElem, error) {
ctx context.Context,
chatLogs map[string]*sdkws.MsgData,
userID string) (map[string]*pbconversation.ConversationElem, error) {
var ( var (
sendIDs []string sendIDs []string
groupIDs []string groupIDs []string
@@ -641,6 +639,11 @@ func (c *conversationServer) GetConversationNotReceiveMessageUserIDs(ctx context
} }
func (c *conversationServer) UpdateConversation(ctx context.Context, req *pbconversation.UpdateConversationReq) (*pbconversation.UpdateConversationResp, error) { func (c *conversationServer) UpdateConversation(ctx context.Context, req *pbconversation.UpdateConversationReq) (*pbconversation.UpdateConversationResp, error) {
for _, userID := range req.UserIDs {
if err := authverify.CheckAccess(ctx, userID); err != nil {
return nil, err
}
}
m := make(map[string]any) m := make(map[string]any)
if req.RecvMsgOpt != nil { if req.RecvMsgOpt != nil {
m["recv_msg_opt"] = req.RecvMsgOpt.Value m["recv_msg_opt"] = req.RecvMsgOpt.Value
@@ -687,6 +690,9 @@ func (c *conversationServer) UpdateConversation(ctx context.Context, req *pbconv
} }
func (c *conversationServer) GetOwnerConversation(ctx context.Context, req *pbconversation.GetOwnerConversationReq) (*pbconversation.GetOwnerConversationResp, error) { func (c *conversationServer) GetOwnerConversation(ctx context.Context, req *pbconversation.GetOwnerConversationReq) (*pbconversation.GetOwnerConversationResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
total, conversations, err := c.conversationDatabase.GetOwnerConversation(ctx, req.UserID, req.Pagination) total, conversations, err := c.conversationDatabase.GetOwnerConversation(ctx, req.UserID, req.Pagination)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -711,7 +717,7 @@ func (c *conversationServer) GetConversationsNeedClearMsg(ctx context.Context, _
maxPage := (num + batchNum - 1) / batchNum maxPage := (num + batchNum - 1) / batchNum
temp := make([]*model.Conversation, 0, maxPage*batchNum) temp := make([]*dbModel.Conversation, 0, maxPage*batchNum)
for pageNumber := 0; pageNumber < int(maxPage); pageNumber++ { for pageNumber := 0; pageNumber < int(maxPage); pageNumber++ {
pagination := &sdkws.RequestPagination{ pagination := &sdkws.RequestPagination{
@@ -748,6 +754,9 @@ func (c *conversationServer) GetConversationsNeedClearMsg(ctx context.Context, _
} }
func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, req *pbconversation.GetNotNotifyConversationIDsReq) (*pbconversation.GetNotNotifyConversationIDsResp, error) { func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, req *pbconversation.GetNotNotifyConversationIDsReq) (*pbconversation.GetNotNotifyConversationIDsResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
conversationIDs, err := c.conversationDatabase.GetNotNotifyConversationIDs(ctx, req.UserID) conversationIDs, err := c.conversationDatabase.GetNotNotifyConversationIDs(ctx, req.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -756,6 +765,9 @@ func (c *conversationServer) GetNotNotifyConversationIDs(ctx context.Context, re
} }
func (c *conversationServer) GetPinnedConversationIDs(ctx context.Context, req *pbconversation.GetPinnedConversationIDsReq) (*pbconversation.GetPinnedConversationIDsResp, error) { func (c *conversationServer) GetPinnedConversationIDs(ctx context.Context, req *pbconversation.GetPinnedConversationIDsReq) (*pbconversation.GetPinnedConversationIDsResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
conversationIDs, err := c.conversationDatabase.GetPinnedConversationIDs(ctx, req.UserID) conversationIDs, err := c.conversationDatabase.GetPinnedConversationIDs(ctx, req.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -773,7 +785,7 @@ func (c *conversationServer) ClearUserConversationMsg(ctx context.Context, req *
if conversation.IsMsgDestruct == false || conversation.MsgDestructTime == 0 { if conversation.IsMsgDestruct == false || conversation.MsgDestructTime == 0 {
continue continue
} }
seq, err := c.msgClient.GetLastMessageSeqByTime(ctx, conversation.ConversationID, req.Timestamp-conversation.MsgDestructTime) seq, err := c.msgClient.GetLastMessageSeqByTime(ctx, conversation.ConversationID, req.Timestamp-(conversation.MsgDestructTime*1000))
if err != nil { if err != nil {
return nil, err return nil, err
} }
+85
View File
@@ -0,0 +1,85 @@
package conversation
import (
"context"
dbModel "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/protocol/conversation"
)
func UpdateConversationsMap(ctx context.Context, req *conversation.SetConversationsReq) (m map[string]any, conversation dbModel.Conversation, err error) {
m = make(map[string]any)
conversation.ConversationID = req.Conversation.ConversationID
conversation.ConversationType = req.Conversation.ConversationType
conversation.UserID = req.Conversation.UserID
conversation.GroupID = req.Conversation.GroupID
if req.Conversation.RecvMsgOpt != nil {
conversation.RecvMsgOpt = req.Conversation.RecvMsgOpt.Value
m["recv_msg_opt"] = req.Conversation.RecvMsgOpt.Value
}
if req.Conversation.AttachedInfo != nil {
conversation.AttachedInfo = req.Conversation.AttachedInfo.Value
m["attached_info"] = req.Conversation.AttachedInfo.Value
}
if req.Conversation.Ex != nil {
conversation.Ex = req.Conversation.Ex.Value
m["ex"] = req.Conversation.Ex.Value
}
if req.Conversation.IsPinned != nil {
conversation.IsPinned = req.Conversation.IsPinned.Value
m["is_pinned"] = req.Conversation.IsPinned.Value
}
if req.Conversation.GroupAtType != nil {
conversation.GroupAtType = req.Conversation.GroupAtType.Value
m["group_at_type"] = req.Conversation.GroupAtType.Value
}
if req.Conversation.MsgDestructTime != nil {
conversation.MsgDestructTime = req.Conversation.MsgDestructTime.Value
m["msg_destruct_time"] = req.Conversation.MsgDestructTime.Value
}
if req.Conversation.IsMsgDestruct != nil {
conversation.IsMsgDestruct = req.Conversation.IsMsgDestruct.Value
m["is_msg_destruct"] = req.Conversation.IsMsgDestruct.Value
}
if req.Conversation.BurnDuration != nil {
conversation.BurnDuration = req.Conversation.BurnDuration.Value
m["burn_duration"] = req.Conversation.BurnDuration.Value
}
return m, conversation, nil
}
func UserUpdateCheckMap(ctx context.Context, userID string, req *conversation.ConversationReq, conversation *dbModel.Conversation) (unequal bool) {
unequal = false
if req.RecvMsgOpt != nil && conversation.RecvMsgOpt != req.RecvMsgOpt.Value {
unequal = true
}
if req.AttachedInfo != nil && conversation.AttachedInfo != req.AttachedInfo.Value {
unequal = true
}
if req.Ex != nil && conversation.Ex != req.Ex.Value {
unequal = true
}
if req.IsPinned != nil && conversation.IsPinned != req.IsPinned.Value {
unequal = true
}
if req.GroupAtType != nil && conversation.GroupAtType != req.GroupAtType.Value {
unequal = true
}
if req.MsgDestructTime != nil && conversation.MsgDestructTime != req.MsgDestructTime.Value {
unequal = true
}
if req.IsMsgDestruct != nil && conversation.IsMsgDestruct != req.IsMsgDestruct.Value {
unequal = true
}
if req.BurnDuration != nil && conversation.BurnDuration != req.BurnDuration.Value {
unequal = true
}
return unequal
}
+3 -2
View File
@@ -16,6 +16,7 @@ package conversation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
@@ -26,11 +27,11 @@ import (
) )
type ConversationNotificationSender struct { type ConversationNotificationSender struct {
*rpcclient.NotificationSender *notification.NotificationSender
} }
func NewConversationNotificationSender(conf *config.Notification, msgClient *rpcli.MsgClient) *ConversationNotificationSender { func NewConversationNotificationSender(conf *config.Notification, msgClient *rpcli.MsgClient) *ConversationNotificationSender {
return &ConversationNotificationSender{rpcclient.NewNotificationSender(conf, rpcclient.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) { return &ConversationNotificationSender{notification.NewNotificationSender(conf, notification.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) {
return msgClient.SendMsg(ctx, req) return msgClient.SendMsg(ctx, req)
}))} }))}
} }
+7
View File
@@ -4,12 +4,16 @@ import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion" "github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"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/open-im-server/v3/pkg/util/hashutil" "github.com/openimsdk/open-im-server/v3/pkg/util/hashutil"
"github.com/openimsdk/protocol/conversation" "github.com/openimsdk/protocol/conversation"
) )
func (c *conversationServer) GetFullOwnerConversationIDs(ctx context.Context, req *conversation.GetFullOwnerConversationIDsReq) (*conversation.GetFullOwnerConversationIDsResp, error) { func (c *conversationServer) GetFullOwnerConversationIDs(ctx context.Context, req *conversation.GetFullOwnerConversationIDsReq) (*conversation.GetFullOwnerConversationIDsResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
vl, err := c.conversationDatabase.FindMaxConversationUserVersionCache(ctx, req.UserID) vl, err := c.conversationDatabase.FindMaxConversationUserVersionCache(ctx, req.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -31,6 +35,9 @@ func (c *conversationServer) GetFullOwnerConversationIDs(ctx context.Context, re
} }
func (c *conversationServer) GetIncrementalConversation(ctx context.Context, req *conversation.GetIncrementalConversationReq) (*conversation.GetIncrementalConversationResp, error) { func (c *conversationServer) GetIncrementalConversation(ctx context.Context, req *conversation.GetIncrementalConversationReq) (*conversation.GetIncrementalConversationResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
opt := incrversion.Option[*conversation.Conversation, conversation.GetIncrementalConversationResp]{ opt := incrversion.Option[*conversation.Conversation, conversation.GetIncrementalConversationResp]{
Ctx: ctx, Ctx: ctx,
VersionKey: req.UserID, VersionKey: req.UserID,
+3
View File
@@ -33,6 +33,9 @@ func (g *groupServer) GetGroupInfoCache(ctx context.Context, req *pbgroup.GetGro
} }
func (g *groupServer) GetGroupMemberCache(ctx context.Context, req *pbgroup.GetGroupMemberCacheReq) (*pbgroup.GetGroupMemberCacheResp, error) { func (g *groupServer) GetGroupMemberCache(ctx context.Context, req *pbgroup.GetGroupMemberCacheReq) (*pbgroup.GetGroupMemberCacheResp, error) {
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
return nil, err
}
members, err := g.db.TakeGroupMember(ctx, req.GroupID, req.GroupMemberID) members, err := g.db.TakeGroupMember(ctx, req.GroupID, req.GroupMemberID)
if err != nil { if err != nil {
return nil, err return nil, err
+18 -6
View File
@@ -16,6 +16,7 @@ package group
import ( import (
"context" "context"
"strings"
"time" "time"
pbgroup "github.com/openimsdk/protocol/group" pbgroup "github.com/openimsdk/protocol/group"
@@ -55,41 +56,52 @@ func UpdateGroupInfoMap(ctx context.Context, group *sdkws.GroupInfoForSet) map[s
return m return m
} }
func UpdateGroupInfoExMap(ctx context.Context, group *pbgroup.SetGroupInfoExReq) (map[string]any, error) { func UpdateGroupInfoExMap(ctx context.Context, group *pbgroup.SetGroupInfoExReq) (m map[string]any, normalFlag, groupNameFlag, notificationFlag bool, err error) {
m := make(map[string]any) m = make(map[string]any)
if group.GroupName != nil { if group.GroupName != nil {
if group.GroupName.Value != "" { if strings.TrimSpace(group.GroupName.Value) != "" {
m["group_name"] = group.GroupName.Value m["group_name"] = group.GroupName.Value
groupNameFlag = true
} else { } else {
return nil, errs.ErrArgs.WrapMsg("group name is empty") return nil, normalFlag, notificationFlag, groupNameFlag, errs.ErrArgs.WrapMsg("group name is empty")
} }
} }
if group.Notification != nil { if group.Notification != nil {
notificationFlag = true
group.Notification.Value = strings.TrimSpace(group.Notification.Value) // if Notification only contains spaces, set it to empty string
m["notification"] = group.Notification.Value m["notification"] = group.Notification.Value
m["notification_update_time"] = time.Now()
m["notification_user_id"] = mcontext.GetOpUserID(ctx) m["notification_user_id"] = mcontext.GetOpUserID(ctx)
m["notification_update_time"] = time.Now()
} }
if group.Introduction != nil { if group.Introduction != nil {
m["introduction"] = group.Introduction.Value m["introduction"] = group.Introduction.Value
normalFlag = true
} }
if group.FaceURL != nil { if group.FaceURL != nil {
m["face_url"] = group.FaceURL.Value m["face_url"] = group.FaceURL.Value
normalFlag = true
} }
if group.NeedVerification != nil { if group.NeedVerification != nil {
m["need_verification"] = group.NeedVerification.Value m["need_verification"] = group.NeedVerification.Value
normalFlag = true
} }
if group.LookMemberInfo != nil { if group.LookMemberInfo != nil {
m["look_member_info"] = group.LookMemberInfo.Value m["look_member_info"] = group.LookMemberInfo.Value
normalFlag = true
} }
if group.ApplyMemberFriend != nil { if group.ApplyMemberFriend != nil {
m["apply_member_friend"] = group.ApplyMemberFriend.Value m["apply_member_friend"] = group.ApplyMemberFriend.Value
normalFlag = true
} }
if group.Ex != nil { if group.Ex != nil {
m["ex"] = group.Ex.Value m["ex"] = group.Ex.Value
normalFlag = true
} }
return m, nil return m, normalFlag, groupNameFlag, notificationFlag, nil
} }
func UpdateGroupStatusMap(status int) map[string]any { func UpdateGroupStatusMap(status int) map[string]any {
+198 -97
View File
@@ -17,13 +17,16 @@ package group
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"math/big" "math/big"
"math/rand" "math/rand"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"google.golang.org/grpc"
"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/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
@@ -42,8 +45,6 @@ import (
pbgroup "github.com/openimsdk/protocol/group" pbgroup "github.com/openimsdk/protocol/group"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/protocol/wrapperspb" "github.com/openimsdk/protocol/wrapperspb"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
@@ -51,7 +52,6 @@ import (
"github.com/openimsdk/tools/mw/specialerror" "github.com/openimsdk/tools/mw/specialerror"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/encrypt" "github.com/openimsdk/tools/utils/encrypt"
"google.golang.org/grpc"
) )
type groupServer struct { type groupServer struct {
@@ -63,6 +63,7 @@ type groupServer struct {
userClient *rpcli.UserClient userClient *rpcli.UserClient
msgClient *rpcli.MsgClient msgClient *rpcli.MsgClient
conversationClient *rpcli.ConversationClient conversationClient *rpcli.ConversationClient
adminUserIDs []string
} }
type Config struct { type Config struct {
@@ -76,12 +77,13 @@ type Config struct {
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
@@ -97,11 +99,6 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
if err != nil { if err != nil {
return err return err
} }
//userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID)
//msgRpcClient := rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg)
//conversationRpcClient := rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation)
userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User) userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return err return err
@@ -120,6 +117,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
userClient: rpcli.NewUserClient(userConn), userClient: rpcli.NewUserClient(userConn),
msgClient: rpcli.NewMsgClient(msgConn), msgClient: rpcli.NewMsgClient(msgConn),
conversationClient: rpcli.NewConversationClient(conversationConn), conversationClient: rpcli.NewConversationClient(conversationConn),
adminUserIDs: config.Share.IMAdminUser.UserIDs,
} }
gs.db = controller.NewGroupDatabase(rdb, &config.LocalCacheConfig, groupDB, groupMemberDB, groupRequestDB, mgocli.GetTx(), grouphash.NewGroupHashFromGroupServer(&gs)) gs.db = controller.NewGroupDatabase(rdb, &config.LocalCacheConfig, groupDB, groupMemberDB, groupRequestDB, mgocli.GetTx(), grouphash.NewGroupHashFromGroupServer(&gs))
gs.notification = NewNotificationSender(gs.db, config, gs.userClient, gs.msgClient, gs.conversationClient) gs.notification = NewNotificationSender(gs.db, config, gs.userClient, gs.msgClient, gs.conversationClient)
@@ -155,11 +153,15 @@ func (g *groupServer) NotificationUserInfoUpdate(ctx context.Context, req *pbgro
} }
func (g *groupServer) CheckGroupAdmin(ctx context.Context, groupID string) error { func (g *groupServer) CheckGroupAdmin(ctx context.Context, groupID string) error {
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) { if !authverify.IsAdmin(ctx) {
groupMember, err := g.db.TakeGroupMember(ctx, groupID, mcontext.GetOpUserID(ctx)) members, err := g.db.FindGroupMembers(ctx, groupID, []string{mcontext.GetOpUserID(ctx)})
if err != nil { if err != nil {
return err return err
} }
if len(members) == 0 {
return errs.ErrNoPermission.WrapMsg("op user not in group")
}
groupMember := members[0]
if !(groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin) { if !(groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin) {
return errs.ErrNoPermission.WrapMsg("no group owner or admin") return errs.ErrNoPermission.WrapMsg("no group owner or admin")
} }
@@ -207,7 +209,7 @@ func (g *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR
if req.OwnerUserID == "" { if req.OwnerUserID == "" {
return nil, errs.ErrArgs.WrapMsg("no group owner") return nil, errs.ErrArgs.WrapMsg("no group owner")
} }
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, g.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err return nil, err
} }
userIDs := append(append(req.MemberUserIDs, req.AdminUserIDs...), req.OwnerUserID) userIDs := append(append(req.MemberUserIDs, req.AdminUserIDs...), req.OwnerUserID)
@@ -288,13 +290,14 @@ func (g *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR
break break
} }
} }
g.notification.GroupCreatedNotification(ctx, tips) g.notification.GroupCreatedNotification(ctx, tips, req.SendMessage)
if req.GroupInfo.Notification != "" { if req.GroupInfo.Notification != "" {
notificationFlag := true
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{ g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{
Group: tips.Group, Group: tips.Group,
OpUser: tips.OpUser, OpUser: tips.OpUser,
}) }, &notificationFlag)
} }
reqCallBackAfter := &pbgroup.CreateGroupReq{ reqCallBackAfter := &pbgroup.CreateGroupReq{
@@ -310,7 +313,7 @@ func (g *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR
} }
func (g *groupServer) GetJoinedGroupList(ctx context.Context, req *pbgroup.GetJoinedGroupListReq) (*pbgroup.GetJoinedGroupListResp, error) { func (g *groupServer) GetJoinedGroupList(ctx context.Context, req *pbgroup.GetJoinedGroupListReq) (*pbgroup.GetJoinedGroupListResp, error) {
if err := authverify.CheckAccessV3(ctx, req.FromUserID, g.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.FromUserID); err != nil {
return nil, err return nil, err
} }
total, members, err := g.db.PageGetJoinGroup(ctx, req.FromUserID, req.Pagination) total, members, err := g.db.PageGetJoinGroup(ctx, req.FromUserID, req.Pagination)
@@ -371,6 +374,10 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
return nil, servererrs.ErrDismissedAlready.WrapMsg("group dismissed checking group status found it dismissed") return nil, servererrs.ErrDismissedAlready.WrapMsg("group dismissed checking group status found it dismissed")
} }
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
return nil, err
}
userMap, err := g.userClient.GetUsersInfoMap(ctx, req.InvitedUserIDs) userMap, err := g.userClient.GetUsersInfoMap(ctx, req.InvitedUserIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -381,9 +388,9 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
} }
var groupMember *model.GroupMember var groupMember *model.GroupMember
var opUserID string opUserID := mcontext.GetOpUserID(ctx)
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) {
opUserID = mcontext.GetOpUserID(ctx) if !authverify.IsAdmin(ctx) {
var err error var err error
groupMember, err = g.db.TakeGroupMember(ctx, req.GroupID, opUserID) groupMember, err = g.db.TakeGroupMember(ctx, req.GroupID, opUserID)
if err != nil { if err != nil {
@@ -399,7 +406,7 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
} }
if group.NeedVerification == constant.AllNeedVerification { if group.NeedVerification == constant.AllNeedVerification {
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) { if !authverify.IsAdmin(ctx) {
if !(groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin) { if !(groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin) {
var requests []*model.GroupRequest var requests []*model.GroupRequest
for _, userID := range req.InvitedUserIDs { for _, userID := range req.InvitedUserIDs {
@@ -421,12 +428,13 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
ReqMessage: request.ReqMsg, ReqMessage: request.ReqMsg,
JoinSource: request.JoinSource, JoinSource: request.JoinSource,
InviterUserID: request.InviterUserID, InviterUserID: request.InviterUserID,
}) }, request)
} }
return &pbgroup.InviteUserToGroupResp{}, nil return &pbgroup.InviteUserToGroupResp{}, nil
} }
} }
} }
var groupMembers []*model.GroupMember var groupMembers []*model.GroupMember
for _, userID := range req.InvitedUserIDs { for _, userID := range req.InvitedUserIDs {
member := &model.GroupMember{ member := &model.GroupMember{
@@ -447,12 +455,22 @@ func (g *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
return nil, err return nil, err
} }
if err := g.db.CreateGroup(ctx, nil, groupMembers); err != nil { const singleQuantity = 50
return nil, err for start := 0; start < len(groupMembers); start += singleQuantity {
} end := min(start+singleQuantity, len(groupMembers))
currentMembers := groupMembers[start:end]
if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, opUserID, req.InvitedUserIDs...); err != nil { if err := g.db.CreateGroup(ctx, nil, currentMembers); err != nil {
return nil, err return nil, err
}
userIDs := datautil.Slice(currentMembers, func(e *model.GroupMember) string {
return e.UserID
})
if len(userIDs) != 0 {
g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, req.SendMessage, opUserID, userIDs...)
}
} }
return &pbgroup.InviteUserToGroupResp{}, nil return &pbgroup.InviteUserToGroupResp{}, nil
} }
@@ -462,6 +480,19 @@ func (g *groupServer) GetGroupAllMember(ctx context.Context, req *pbgroup.GetGro
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !authverify.IsAdmin(ctx) {
var inGroup bool
opUserID := mcontext.GetOpUserID(ctx)
for _, member := range members {
if member.UserID == opUserID {
inGroup = true
break
}
}
if !inGroup {
return nil, errs.ErrNoPermission.WrapMsg("opuser not in group")
}
}
if err := g.PopulateGroupMember(ctx, members...); err != nil { if err := g.PopulateGroupMember(ctx, members...); err != nil {
return nil, err return nil, err
} }
@@ -472,7 +503,25 @@ func (g *groupServer) GetGroupAllMember(ctx context.Context, req *pbgroup.GetGro
return &resp, nil return &resp, nil
} }
func (g *groupServer) checkAdminOrInGroup(ctx context.Context, groupID string) error {
if authverify.IsAdmin(ctx) {
return nil
}
opUserID := mcontext.GetOpUserID(ctx)
members, err := g.db.FindGroupMembers(ctx, groupID, []string{opUserID})
if err != nil {
return err
}
if len(members) == 0 {
return errs.ErrNoPermission.WrapMsg("op user not in group")
}
return nil
}
func (g *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGroupMemberListReq) (*pbgroup.GetGroupMemberListResp, error) { func (g *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGroupMemberListReq) (*pbgroup.GetGroupMemberListResp, error) {
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
return nil, err
}
var ( var (
total int64 total int64
members []*model.GroupMember members []*model.GroupMember
@@ -481,7 +530,7 @@ func (g *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGr
if req.Keyword == "" { if req.Keyword == "" {
total, members, err = g.db.PageGetGroupMember(ctx, req.GroupID, req.Pagination) total, members, err = g.db.PageGetGroupMember(ctx, req.GroupID, req.Pagination)
} else { } else {
members, err = g.db.FindGroupMemberAll(ctx, req.GroupID) total, members, err = g.db.SearchGroupMember(ctx, req.GroupID, req.Keyword, req.Pagination)
} }
if err != nil { if err != nil {
return nil, err return nil, err
@@ -489,27 +538,6 @@ func (g *groupServer) GetGroupMemberList(ctx context.Context, req *pbgroup.GetGr
if err := g.PopulateGroupMember(ctx, members...); err != nil { if err := g.PopulateGroupMember(ctx, members...); err != nil {
return nil, err return nil, err
} }
if req.Keyword != "" {
groupMembers := make([]*model.GroupMember, 0)
for _, member := range members {
if member.UserID == req.Keyword {
groupMembers = append(groupMembers, member)
total++
continue
}
if member.Nickname == req.Keyword {
groupMembers = append(groupMembers, member)
total++
continue
}
}
members := datautil.Paginate(groupMembers, int(req.Pagination.GetPageNumber()), int(req.Pagination.GetShowNumber()))
return &pbgroup.GetGroupMemberListResp{
Total: uint32(total),
Members: datautil.Batch(convert.Db2PbGroupMember, members),
}, nil
}
return &pbgroup.GetGroupMemberListResp{ return &pbgroup.GetGroupMemberListResp{
Total: uint32(total), Total: uint32(total),
Members: datautil.Batch(convert.Db2PbGroupMember, members), Members: datautil.Batch(convert.Db2PbGroupMember, members),
@@ -550,7 +578,7 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
for i, member := range members { for i, member := range members {
memberMap[member.UserID] = members[i] memberMap[member.UserID] = members[i]
} }
isAppManagerUid := authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) isAppManagerUid := authverify.IsAdmin(ctx)
opMember := memberMap[opUserID] opMember := memberMap[opUserID]
for _, userID := range req.KickedUserIDs { for _, userID := range req.KickedUserIDs {
member, ok := memberMap[userID] member, ok := memberMap[userID]
@@ -617,7 +645,7 @@ func (g *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
for _, userID := range req.KickedUserIDs { for _, userID := range req.KickedUserIDs {
tips.KickedUserList = append(tips.KickedUserList, convert.Db2PbGroupMember(memberMap[userID])) tips.KickedUserList = append(tips.KickedUserList, convert.Db2PbGroupMember(memberMap[userID]))
} }
g.notification.MemberKickedNotification(ctx, tips) g.notification.MemberKickedNotification(ctx, tips, req.SendMessage)
if err := g.deleteMemberAndSetConversationSeq(ctx, req.GroupID, req.KickedUserIDs); err != nil { if err := g.deleteMemberAndSetConversationSeq(ctx, req.GroupID, req.KickedUserIDs); err != nil {
return nil, err return nil, err
} }
@@ -633,6 +661,9 @@ func (g *groupServer) GetGroupMembersInfo(ctx context.Context, req *pbgroup.GetG
if req.GroupID == "" { if req.GroupID == "" {
return nil, errs.ErrArgs.WrapMsg("groupID empty") return nil, errs.ErrArgs.WrapMsg("groupID empty")
} }
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
return nil, err
}
members, err := g.getGroupMembersInfo(ctx, req.GroupID, req.UserIDs) members, err := g.getGroupMembersInfo(ctx, req.GroupID, req.UserIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -660,15 +691,37 @@ func (g *groupServer) getGroupMembersInfo(ctx context.Context, groupID string, u
// GetGroupApplicationList handles functions that get a list of group requests. // GetGroupApplicationList handles functions that get a list of group requests.
func (g *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup.GetGroupApplicationListReq) (*pbgroup.GetGroupApplicationListResp, error) { func (g *groupServer) GetGroupApplicationList(ctx context.Context, req *pbgroup.GetGroupApplicationListReq) (*pbgroup.GetGroupApplicationListResp, error) {
groupIDs, err := g.db.FindUserManagedGroupID(ctx, req.FromUserID) if err := authverify.CheckAccess(ctx, req.FromUserID); err != nil {
if err != nil {
return nil, err return nil, err
} }
var (
groupIDs []string
err error
)
if len(req.GroupIDs) == 0 {
groupIDs, err = g.db.FindUserManagedGroupID(ctx, req.FromUserID)
if err != nil {
return nil, err
}
} else {
req.GroupIDs = datautil.Distinct(req.GroupIDs)
if !authverify.IsAdmin(ctx) {
for _, groupID := range req.GroupIDs {
if err := g.CheckGroupAdmin(ctx, groupID); err != nil {
return nil, err
}
}
}
groupIDs = req.GroupIDs
}
resp := &pbgroup.GetGroupApplicationListResp{} resp := &pbgroup.GetGroupApplicationListResp{}
if len(groupIDs) == 0 { if len(groupIDs) == 0 {
return resp, nil return resp, nil
} }
total, groupRequests, err := g.db.PageGroupRequest(ctx, groupIDs, req.Pagination) handleResults := datautil.Slice(req.HandleResults, func(e int32) int {
return int(e)
})
total, groupRequests, err := g.db.PageGroupRequest(ctx, groupIDs, handleResults, req.Pagination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -733,6 +786,23 @@ func (g *groupServer) GetGroupsInfo(ctx context.Context, req *pbgroup.GetGroupsI
}, nil }, nil
} }
func (g *groupServer) GetGroupApplicationUnhandledCount(ctx context.Context, req *pbgroup.GetGroupApplicationUnhandledCountReq) (*pbgroup.GetGroupApplicationUnhandledCountResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
groupIDs, err := g.db.FindUserManagedGroupID(ctx, req.UserID)
if err != nil {
return nil, err
}
count, err := g.db.GetGroupApplicationUnhandledCount(ctx, groupIDs, req.Time)
if err != nil {
return nil, err
}
return &pbgroup.GetGroupApplicationUnhandledCountResp{
Count: count,
}, nil
}
func (g *groupServer) getGroupsInfo(ctx context.Context, groupIDs []string) ([]*sdkws.GroupInfo, error) { func (g *groupServer) getGroupsInfo(ctx context.Context, groupIDs []string) ([]*sdkws.GroupInfo, error) {
if len(groupIDs) == 0 { if len(groupIDs) == 0 {
return nil, nil return nil, nil
@@ -768,7 +838,7 @@ func (g *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup
if !datautil.Contain(req.HandleResult, constant.GroupResponseAgree, constant.GroupResponseRefuse) { if !datautil.Contain(req.HandleResult, constant.GroupResponseAgree, constant.GroupResponseRefuse) {
return nil, errs.ErrArgs.WrapMsg("HandleResult unknown") return nil, errs.ErrArgs.WrapMsg("HandleResult unknown")
} }
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) { if !authverify.IsAdmin(ctx) {
groupMember, err := g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx)) groupMember, err := g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx))
if err != nil { if err != nil {
return nil, err return nil, err
@@ -826,8 +896,14 @@ func (g *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup
if member == nil { if member == nil {
log.ZDebug(ctx, "GroupApplicationResponse", "member is nil") log.ZDebug(ctx, "GroupApplicationResponse", "member is nil")
} else { } else {
if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, groupRequest.InviterUserID, req.FromUserID); err != nil { if groupRequest.InviterUserID == "" {
return nil, err if err = g.notification.MemberEnterNotification(ctx, req.GroupID, req.FromUserID); err != nil {
return nil, err
}
} else {
if err = g.notification.GroupApplicationAgreeMemberEnterNotification(ctx, req.GroupID, nil, groupRequest.InviterUserID, req.FromUserID); err != nil {
return nil, err
}
} }
} }
case constant.GroupResponseRefuse: case constant.GroupResponseRefuse:
@@ -908,7 +984,7 @@ func (g *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq)
if err = g.db.CreateGroupRequest(ctx, []*model.GroupRequest{&groupRequest}); err != nil { if err = g.db.CreateGroupRequest(ctx, []*model.GroupRequest{&groupRequest}); err != nil {
return nil, err return nil, err
} }
g.notification.JoinGroupApplicationNotification(ctx, req) g.notification.JoinGroupApplicationNotification(ctx, req, &groupRequest)
return &pbgroup.JoinGroupResp{}, nil return &pbgroup.JoinGroupResp{}, nil
} }
@@ -916,7 +992,7 @@ func (g *groupServer) QuitGroup(ctx context.Context, req *pbgroup.QuitGroupReq)
if req.UserID == "" { if req.UserID == "" {
req.UserID = mcontext.GetOpUserID(ctx) req.UserID = mcontext.GetOpUserID(ctx)
} else { } else {
if err := authverify.CheckAccessV3(ctx, req.UserID, g.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
} }
@@ -954,7 +1030,7 @@ func (g *groupServer) deleteMemberAndSetConversationSeq(ctx context.Context, gro
func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInfoReq) (*pbgroup.SetGroupInfoResp, error) { func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInfoReq) (*pbgroup.SetGroupInfoResp, error) {
var opMember *model.GroupMember var opMember *model.GroupMember
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) { if !authverify.IsAdmin(ctx) {
var err error var err error
opMember, err = g.db.TakeGroupMember(ctx, req.GroupInfoForSet.GroupID, mcontext.GetOpUserID(ctx)) opMember, err = g.db.TakeGroupMember(ctx, req.GroupInfoForSet.GroupID, mcontext.GetOpUserID(ctx))
if err != nil { if err != nil {
@@ -1029,7 +1105,8 @@ func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
log.ZWarn(ctx, "SetConversations", err, "UserIDs", resp.UserIDs, "conversation", conversation) log.ZWarn(ctx, "SetConversations", err, "UserIDs", resp.UserIDs, "conversation", conversation)
} }
}() }()
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}) notficationFlag := true
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}, &notficationFlag)
} }
if req.GroupInfoForSet.GroupName != "" { if req.GroupInfoForSet.GroupName != "" {
num-- num--
@@ -1047,7 +1124,7 @@ func (g *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupInfoExReq) (*pbgroup.SetGroupInfoExResp, error) { func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupInfoExReq) (*pbgroup.SetGroupInfoExResp, error) {
var opMember *model.GroupMember var opMember *model.GroupMember
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) { if !authverify.IsAdmin(ctx) {
var err error var err error
opMember, err = g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx)) opMember, err = g.db.TakeGroupMember(ctx, req.GroupID, mcontext.GetOpUserID(ctx))
@@ -1090,7 +1167,7 @@ func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupI
return nil, err return nil, err
} }
updatedData, err := UpdateGroupInfoExMap(ctx, req) updatedData, normalFlag, groupNameFlag, notificationFlag, err := UpdateGroupInfoExMap(ctx, req)
if len(updatedData) == 0 { if len(updatedData) == 0 {
return &pbgroup.SetGroupInfoExResp{}, nil return &pbgroup.SetGroupInfoExResp{}, nil
} }
@@ -1118,41 +1195,38 @@ func (g *groupServer) SetGroupInfoEx(ctx context.Context, req *pbgroup.SetGroupI
tips.OpUser = g.groupMemberDB2PB(opMember, 0) tips.OpUser = g.groupMemberDB2PB(opMember, 0)
} }
num := len(updatedData) if notificationFlag {
if req.Notification != nil {
num -= 3
if req.Notification.Value != "" { if req.Notification.Value != "" {
func() { conversation := &pbconv.ConversationReq{
conversation := &pbconv.ConversationReq{ ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID),
ConversationID: msgprocessor.GetConversationIDBySessionType(constant.ReadGroupChatType, req.GroupID), ConversationType: constant.ReadGroupChatType,
ConversationType: constant.ReadGroupChatType, GroupID: req.GroupID,
GroupID: req.GroupID, }
}
resp, err := g.GetGroupMemberUserIDs(ctx, &pbgroup.GetGroupMemberUserIDsReq{GroupID: req.GroupID}) resp, err := g.GetGroupMemberUserIDs(ctx, &pbgroup.GetGroupMemberUserIDsReq{GroupID: req.GroupID})
if err != nil { if err != nil {
log.ZWarn(ctx, "GetGroupMemberIDs is failed.", err) log.ZWarn(ctx, "GetGroupMemberIDs is failed.", err)
return return nil, err
} }
conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.GroupNotification} conversation.GroupAtType = &wrapperspb.Int32Value{Value: constant.GroupNotification}
if err := g.conversationClient.SetConversations(ctx, resp.UserIDs, conversation); err != nil { if err := g.conversationClient.SetConversations(ctx, resp.UserIDs, conversation); err != nil {
log.ZWarn(ctx, "SetConversations", err, "UserIDs", resp.UserIDs, "conversation", conversation) log.ZWarn(ctx, "SetConversations", err, "UserIDs", resp.UserIDs, "conversation", conversation)
} }
}()
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}) g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}, &notificationFlag)
} else {
notificationFlag = false
g.notification.GroupInfoSetAnnouncementNotification(ctx, &sdkws.GroupInfoSetAnnouncementTips{Group: tips.Group, OpUser: tips.OpUser}, &notificationFlag)
} }
} }
if req.GroupName != nil { if groupNameFlag {
num--
g.notification.GroupInfoSetNameNotification(ctx, &sdkws.GroupInfoSetNameTips{Group: tips.Group, OpUser: tips.OpUser}) g.notification.GroupInfoSetNameNotification(ctx, &sdkws.GroupInfoSetNameTips{Group: tips.Group, OpUser: tips.OpUser})
} }
if num > 0 { // if updatedData > 0, send the normal notification
if normalFlag {
g.notification.GroupInfoSetNotification(ctx, tips) g.notification.GroupInfoSetNotification(ctx, tips)
} }
@@ -1199,7 +1273,7 @@ func (g *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
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.IsAdmin(ctx) {
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")
} }
@@ -1274,6 +1348,9 @@ func (g *groupServer) GetGroups(ctx context.Context, req *pbgroup.GetGroupsReq)
} }
func (g *groupServer) GetGroupMembersCMS(ctx context.Context, req *pbgroup.GetGroupMembersCMSReq) (*pbgroup.GetGroupMembersCMSResp, error) { func (g *groupServer) GetGroupMembersCMS(ctx context.Context, req *pbgroup.GetGroupMembersCMSReq) (*pbgroup.GetGroupMembersCMSResp, error) {
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
return nil, err
}
total, members, err := g.db.SearchGroupMember(ctx, req.UserName, req.GroupID, req.Pagination) total, members, err := g.db.SearchGroupMember(ctx, req.UserName, req.GroupID, req.Pagination)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1290,11 +1367,17 @@ func (g *groupServer) GetGroupMembersCMS(ctx context.Context, req *pbgroup.GetGr
} }
func (g *groupServer) GetUserReqApplicationList(ctx context.Context, req *pbgroup.GetUserReqApplicationListReq) (*pbgroup.GetUserReqApplicationListResp, error) { func (g *groupServer) GetUserReqApplicationList(ctx context.Context, req *pbgroup.GetUserReqApplicationListReq) (*pbgroup.GetUserReqApplicationListResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
user, err := g.userClient.GetUserInfo(ctx, req.UserID) user, err := g.userClient.GetUserInfo(ctx, req.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
total, requests, err := g.db.PageGroupRequestUser(ctx, req.UserID, req.Pagination) handleResults := datautil.Slice(req.HandleResults, func(e int32) int {
return int(e)
})
total, requests, err := g.db.PageGroupRequestUser(ctx, req.UserID, req.GroupIDs, handleResults, req.Pagination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -1342,7 +1425,7 @@ func (g *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGrou
if err != nil { if err != nil {
return nil, err return nil, err
} }
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) { if !authverify.IsAdmin(ctx) {
if owner.UserID != mcontext.GetOpUserID(ctx) { if owner.UserID != mcontext.GetOpUserID(ctx) {
return nil, errs.ErrNoPermission.WrapMsg("not group owner") return nil, errs.ErrNoPermission.WrapMsg("not group owner")
} }
@@ -1365,6 +1448,7 @@ func (g *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGrou
if err != nil { if err != nil {
return nil, err return nil, err
} }
group.Status = constant.GroupStatusDismissed
tips := &sdkws.GroupDismissedTips{ tips := &sdkws.GroupDismissedTips{
Group: g.groupDB2PB(group, owner.UserID, num), Group: g.groupDB2PB(group, owner.UserID, num),
OpUser: &sdkws.GroupMemberFullInfo{}, OpUser: &sdkws.GroupMemberFullInfo{},
@@ -1372,7 +1456,7 @@ func (g *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGrou
if mcontext.GetOpUserID(ctx) == owner.UserID { if mcontext.GetOpUserID(ctx) == owner.UserID {
tips.OpUser = g.groupMemberDB2PB(owner, 0) tips.OpUser = g.groupMemberDB2PB(owner, 0)
} }
g.notification.GroupDismissedNotification(ctx, tips) g.notification.GroupDismissedNotification(ctx, tips, req.SendMessage)
} }
membersID, err := g.db.FindGroupMemberUserID(ctx, group.GroupID) membersID, err := g.db.FindGroupMemberUserID(ctx, group.GroupID)
if err != nil { if err != nil {
@@ -1398,7 +1482,7 @@ func (g *groupServer) MuteGroupMember(ctx context.Context, req *pbgroup.MuteGrou
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.IsAdmin(ctx) {
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
@@ -1434,7 +1518,7 @@ func (g *groupServer) CancelMuteGroupMember(ctx context.Context, req *pbgroup.Ca
return nil, err return nil, err
} }
if !authverify.IsAppManagerUid(ctx, g.config.Share.IMAdminUserID) { if !authverify.IsAdmin(ctx) {
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
@@ -1494,7 +1578,7 @@ func (g *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
if opUserID == "" { if opUserID == "" {
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.IsAdmin(ctx)
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 {
@@ -1649,6 +1733,11 @@ func (g *groupServer) GetGroupAbstractInfo(ctx context.Context, req *pbgroup.Get
if datautil.Duplicate(req.GroupIDs) { if datautil.Duplicate(req.GroupIDs) {
return nil, errs.ErrArgs.WrapMsg("groupIDs duplicate") return nil, errs.ErrArgs.WrapMsg("groupIDs duplicate")
} }
for _, groupID := range req.GroupIDs {
if err := g.checkAdminOrInGroup(ctx, groupID); err != nil {
return nil, err
}
}
groups, err := g.db.FindGroup(ctx, req.GroupIDs) groups, err := g.db.FindGroup(ctx, req.GroupIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1677,6 +1766,9 @@ func (g *groupServer) GetUserInGroupMembers(ctx context.Context, req *pbgroup.Ge
if len(req.GroupIDs) == 0 { if len(req.GroupIDs) == 0 {
return nil, errs.ErrArgs.WrapMsg("groupIDs empty") return nil, errs.ErrArgs.WrapMsg("groupIDs empty")
} }
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
members, err := g.db.FindGroupMemberUser(ctx, req.GroupIDs, req.UserID) members, err := g.db.FindGroupMemberUser(ctx, req.GroupIDs, req.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1696,6 +1788,9 @@ func (g *groupServer) GetGroupMemberUserIDs(ctx context.Context, req *pbgroup.Ge
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := authverify.CheckAccessIn(ctx, userIDs...); err != nil {
return nil, err
}
return &pbgroup.GetGroupMemberUserIDsResp{ return &pbgroup.GetGroupMemberUserIDsResp{
UserIDs: userIDs, UserIDs: userIDs,
}, nil }, nil
@@ -1705,6 +1800,9 @@ func (g *groupServer) GetGroupMemberRoleLevel(ctx context.Context, req *pbgroup.
if len(req.RoleLevels) == 0 { if len(req.RoleLevels) == 0 {
return nil, errs.ErrArgs.WrapMsg("RoleLevels empty") return nil, errs.ErrArgs.WrapMsg("RoleLevels empty")
} }
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
return nil, err
}
members, err := g.db.FindGroupMemberRoleLevels(ctx, req.GroupID, req.RoleLevels) members, err := g.db.FindGroupMemberRoleLevels(ctx, req.GroupID, req.RoleLevels)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1720,6 +1818,9 @@ func (g *groupServer) GetGroupMemberRoleLevel(ctx context.Context, req *pbgroup.
} }
func (g *groupServer) GetGroupUsersReqApplicationList(ctx context.Context, req *pbgroup.GetGroupUsersReqApplicationListReq) (*pbgroup.GetGroupUsersReqApplicationListResp, error) { func (g *groupServer) GetGroupUsersReqApplicationList(ctx context.Context, req *pbgroup.GetGroupUsersReqApplicationListReq) (*pbgroup.GetGroupUsersReqApplicationListResp, error) {
if err := g.CheckGroupAdmin(ctx, req.GroupID); err != nil {
return nil, err
}
requests, err := g.db.FindGroupRequests(ctx, req.GroupID, req.UserIDs) requests, err := g.db.FindGroupRequests(ctx, req.GroupID, req.UserIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -1802,7 +1903,7 @@ 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.adminUserIDs...)
if !datautil.Contain(opUserID, adminIDs...) { if !datautil.Contain(opUserID, adminIDs...) {
return nil, errs.ErrNoPermission.WrapMsg("opUser no permission") return nil, errs.ErrNoPermission.WrapMsg("opUser no permission")
+108 -42
View File
@@ -20,8 +20,11 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/google/uuid"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"go.mongodb.org/mongo-driver/mongo"
"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/convert" "github.com/openimsdk/open-im-server/v3/pkg/common/convert"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
@@ -41,7 +44,6 @@ import (
"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" "github.com/openimsdk/tools/utils/stringutil"
"go.mongodb.org/mongo-driver/mongo"
) )
// GroupApplicationReceiver // GroupApplicationReceiver
@@ -52,11 +54,11 @@ const (
func NewNotificationSender(db controller.GroupDatabase, config *Config, userClient *rpcli.UserClient, msgClient *rpcli.MsgClient, conversationClient *rpcli.ConversationClient) *NotificationSender { func NewNotificationSender(db controller.GroupDatabase, config *Config, userClient *rpcli.UserClient, msgClient *rpcli.MsgClient, conversationClient *rpcli.ConversationClient) *NotificationSender {
return &NotificationSender{ return &NotificationSender{
NotificationSender: rpcclient.NewNotificationSender(&config.NotificationConfig, NotificationSender: notification.NewNotificationSender(&config.NotificationConfig,
rpcclient.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) { notification.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) {
return msgClient.SendMsg(ctx, req) return msgClient.SendMsg(ctx, req)
}), }),
rpcclient.WithUserRpcClient(userClient.GetUserInfo), notification.WithUserRpcClient(userClient.GetUserInfo),
), ),
getUsersInfo: func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error) { getUsersInfo: func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error) {
users, err := userClient.GetUsersInfo(ctx, userIDs) users, err := userClient.GetUsersInfo(ctx, userIDs)
@@ -73,7 +75,7 @@ func NewNotificationSender(db controller.GroupDatabase, config *Config, userClie
} }
type NotificationSender struct { type NotificationSender struct {
*rpcclient.NotificationSender *notification.NotificationSender
getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error) getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error)
db controller.GroupDatabase db controller.GroupDatabase
config *Config config *Config
@@ -233,17 +235,17 @@ func (g *NotificationSender) groupMemberDB2PB(member *model.GroupMember, appMang
return result, nil return result, nil
} */ } */
func (g *NotificationSender) fillOpUser(ctx context.Context, opUser **sdkws.GroupMemberFullInfo, groupID string) (err error) { func (g *NotificationSender) fillOpUser(ctx context.Context, targetUser **sdkws.GroupMemberFullInfo, groupID string) (err error) {
return g.fillOpUserByUserID(ctx, mcontext.GetOpUserID(ctx), opUser, groupID) return g.fillUserByUserID(ctx, mcontext.GetOpUserID(ctx), targetUser, groupID)
} }
func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID string, opUser **sdkws.GroupMemberFullInfo, groupID string) error { func (g *NotificationSender) fillUserByUserID(ctx context.Context, userID string, targetUser **sdkws.GroupMemberFullInfo, groupID string) error {
if opUser == nil { if targetUser == nil {
return errs.ErrInternalServer.WrapMsg("**sdkws.GroupMemberFullInfo is nil") return errs.ErrInternalServer.WrapMsg("**sdkws.GroupMemberFullInfo is nil")
} }
if groupID != "" { if groupID != "" {
if authverify.IsManagerUserID(userID, g.config.Share.IMAdminUserID) { if authverify.CheckUserIsAdmin(ctx, userID) {
*opUser = &sdkws.GroupMemberFullInfo{ *targetUser = &sdkws.GroupMemberFullInfo{
GroupID: groupID, GroupID: groupID,
UserID: userID, UserID: userID,
RoleLevel: constant.GroupAdmin, RoleLevel: constant.GroupAdmin,
@@ -252,7 +254,7 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
} else { } else {
member, err := g.db.TakeGroupMember(ctx, groupID, userID) member, err := g.db.TakeGroupMember(ctx, groupID, userID)
if err == nil { if err == nil {
*opUser = g.groupMemberDB2PB(member, 0) *targetUser = g.groupMemberDB2PB(member, 0)
} else if !(errors.Is(err, mongo.ErrNoDocuments) || errs.ErrRecordNotFound.Is(err)) { } else if !(errors.Is(err, mongo.ErrNoDocuments) || errs.ErrRecordNotFound.Is(err)) {
return err return err
} }
@@ -262,8 +264,8 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
if err != nil { if err != nil {
return err return err
} }
if *opUser == nil { if *targetUser == nil {
*opUser = &sdkws.GroupMemberFullInfo{ *targetUser = &sdkws.GroupMemberFullInfo{
GroupID: groupID, GroupID: groupID,
UserID: userID, UserID: userID,
Nickname: user.Nickname, Nickname: user.Nickname,
@@ -271,11 +273,11 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
OperatorUserID: userID, OperatorUserID: userID,
} }
} else { } else {
if (*opUser).Nickname == "" { if (*targetUser).Nickname == "" {
(*opUser).Nickname = user.Nickname (*targetUser).Nickname = user.Nickname
} }
if (*opUser).FaceURL == "" { if (*targetUser).FaceURL == "" {
(*opUser).FaceURL = user.FaceURL (*targetUser).FaceURL = user.FaceURL
} }
} }
return nil return nil
@@ -283,7 +285,8 @@ func (g *NotificationSender) fillOpUserByUserID(ctx context.Context, userID stri
func (g *NotificationSender) setVersion(ctx context.Context, version *uint64, versionID *string, collName string, id string) { func (g *NotificationSender) setVersion(ctx context.Context, version *uint64, versionID *string, collName string, id string) {
versions := versionctx.GetVersionLog(ctx).Get() versions := versionctx.GetVersionLog(ctx).Get()
for _, coll := range versions { for i := len(versions) - 1; i >= 0; i-- {
coll := versions[i]
if coll.Name == collName && coll.Doc.DID == id { if coll.Name == collName && coll.Doc.DID == id {
*version = uint64(coll.Doc.Version) *version = uint64(coll.Doc.Version)
*versionID = coll.Doc.ID.Hex() *versionID = coll.Doc.ID.Hex()
@@ -307,7 +310,7 @@ func (g *NotificationSender) setSortVersion(ctx context.Context, version *uint64
} }
} }
func (g *NotificationSender) GroupCreatedNotification(ctx context.Context, tips *sdkws.GroupCreatedTips) { func (g *NotificationSender) GroupCreatedNotification(ctx context.Context, tips *sdkws.GroupCreatedTips, SendMessage *bool) {
var err error var err error
defer func() { defer func() {
if err != nil { if err != nil {
@@ -318,7 +321,7 @@ func (g *NotificationSender) GroupCreatedNotification(ctx context.Context, tips
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupCreatedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupCreatedNotification, tips, notification.WithSendMessage(SendMessage))
} }
func (g *NotificationSender) GroupInfoSetNotification(ctx context.Context, tips *sdkws.GroupInfoSetTips) { func (g *NotificationSender) GroupInfoSetNotification(ctx context.Context, tips *sdkws.GroupInfoSetTips) {
@@ -332,7 +335,7 @@ func (g *NotificationSender) GroupInfoSetNotification(ctx context.Context, tips
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNotification, tips, rpcclient.WithRpcGetUserName()) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNotification, tips, notification.WithRpcGetUserName())
} }
func (g *NotificationSender) GroupInfoSetNameNotification(ctx context.Context, tips *sdkws.GroupInfoSetNameTips) { func (g *NotificationSender) GroupInfoSetNameNotification(ctx context.Context, tips *sdkws.GroupInfoSetNameTips) {
@@ -349,7 +352,7 @@ func (g *NotificationSender) GroupInfoSetNameNotification(ctx context.Context, t
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNameNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNameNotification, tips)
} }
func (g *NotificationSender) GroupInfoSetAnnouncementNotification(ctx context.Context, tips *sdkws.GroupInfoSetAnnouncementTips) { func (g *NotificationSender) GroupInfoSetAnnouncementNotification(ctx context.Context, tips *sdkws.GroupInfoSetAnnouncementTips, sendMessage *bool) {
var err error var err error
defer func() { defer func() {
if err != nil { if err != nil {
@@ -360,16 +363,49 @@ func (g *NotificationSender) GroupInfoSetAnnouncementNotification(ctx context.Co
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetAnnouncementNotification, tips, rpcclient.WithRpcGetUserName()) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetAnnouncementNotification, tips, notification.WithRpcGetUserName(), notification.WithSendMessage(sendMessage))
} }
func (g *NotificationSender) JoinGroupApplicationNotification(ctx context.Context, req *pbgroup.JoinGroupReq) { func (g *NotificationSender) uuid() string {
return uuid.New().String()
}
func (g *NotificationSender) getGroupRequest(ctx context.Context, groupID string, userID string) (*sdkws.GroupRequest, error) {
request, err := g.db.TakeGroupRequest(ctx, groupID, userID)
if err != nil {
return nil, err
}
users, err := g.getUsersInfo(ctx, []string{userID})
if err != nil {
return nil, err
}
if len(users) == 0 {
return nil, servererrs.ErrUserIDNotFound.WrapMsg(fmt.Sprintf("user %s not found", userID))
}
info, ok := users[0].(*sdkws.UserInfo)
if !ok {
info = &sdkws.UserInfo{
UserID: users[0].GetUserID(),
Nickname: users[0].GetNickname(),
FaceURL: users[0].GetFaceURL(),
Ex: users[0].GetEx(),
}
}
return convert.Db2PbGroupRequest(request, info, nil), nil
}
func (g *NotificationSender) JoinGroupApplicationNotification(ctx context.Context, req *pbgroup.JoinGroupReq, dbReq *model.GroupRequest) {
var err error var err error
defer func() { defer func() {
if err != nil { if err != nil {
log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err)
} }
}() }()
request, err := g.getGroupRequest(ctx, dbReq.GroupID, dbReq.UserID)
if err != nil {
log.ZError(ctx, "JoinGroupApplicationNotification getGroupRequest", err, "dbReq", dbReq)
return
}
var group *sdkws.GroupInfo var group *sdkws.GroupInfo
group, err = g.getGroupInfo(ctx, req.GroupID) group, err = g.getGroupInfo(ctx, req.GroupID)
if err != nil { if err != nil {
@@ -385,7 +421,13 @@ func (g *NotificationSender) JoinGroupApplicationNotification(ctx context.Contex
return return
} }
userIDs = append(userIDs, req.InviterUserID, mcontext.GetOpUserID(ctx)) userIDs = append(userIDs, req.InviterUserID, mcontext.GetOpUserID(ctx))
tips := &sdkws.JoinGroupApplicationTips{Group: group, Applicant: user, ReqMsg: req.ReqMessage} tips := &sdkws.JoinGroupApplicationTips{
Group: group,
Applicant: user,
ReqMsg: req.ReqMessage,
Uuid: g.uuid(),
Request: request,
}
for _, userID := range datautil.Distinct(userIDs) { for _, userID := range datautil.Distinct(userIDs) {
g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.JoinGroupApplicationNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.JoinGroupApplicationNotification, tips)
} }
@@ -415,6 +457,11 @@ func (g *NotificationSender) GroupApplicationAcceptedNotification(ctx context.Co
log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err)
} }
}() }()
request, err := g.getGroupRequest(ctx, req.GroupID, req.FromUserID)
if err != nil {
log.ZError(ctx, "GroupApplicationAcceptedNotification getGroupRequest", err, "req", req)
return
}
var group *sdkws.GroupInfo var group *sdkws.GroupInfo
group, err = g.getGroupInfo(ctx, req.GroupID) group, err = g.getGroupInfo(ctx, req.GroupID)
if err != nil { if err != nil {
@@ -430,8 +477,14 @@ func (g *NotificationSender) GroupApplicationAcceptedNotification(ctx context.Co
if err = g.fillOpUser(ctx, &opUser, group.GroupID); err != nil { if err = g.fillOpUser(ctx, &opUser, group.GroupID); err != nil {
return return
} }
tips := &sdkws.GroupApplicationAcceptedTips{
Group: group,
OpUser: opUser,
HandleMsg: req.HandledMsg,
Uuid: g.uuid(),
Request: request,
}
for _, userID := range append(userIDs, req.FromUserID) { for _, userID := range append(userIDs, req.FromUserID) {
tips := &sdkws.GroupApplicationAcceptedTips{Group: group, OpUser: opUser, HandleMsg: req.HandledMsg}
if userID == req.FromUserID { if userID == req.FromUserID {
tips.ReceiverAs = applicantReceiver tips.ReceiverAs = applicantReceiver
} else { } else {
@@ -448,6 +501,11 @@ func (g *NotificationSender) GroupApplicationRejectedNotification(ctx context.Co
log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err) log.ZError(ctx, stringutil.GetFuncName(1)+" failed", err)
} }
}() }()
request, err := g.getGroupRequest(ctx, req.GroupID, req.FromUserID)
if err != nil {
log.ZError(ctx, "GroupApplicationAcceptedNotification getGroupRequest", err, "req", req)
return
}
var group *sdkws.GroupInfo var group *sdkws.GroupInfo
group, err = g.getGroupInfo(ctx, req.GroupID) group, err = g.getGroupInfo(ctx, req.GroupID)
if err != nil { if err != nil {
@@ -463,8 +521,14 @@ func (g *NotificationSender) GroupApplicationRejectedNotification(ctx context.Co
if err = g.fillOpUser(ctx, &opUser, group.GroupID); err != nil { if err = g.fillOpUser(ctx, &opUser, group.GroupID); err != nil {
return return
} }
tips := &sdkws.GroupApplicationRejectedTips{
Group: group,
OpUser: opUser,
HandleMsg: req.HandledMsg,
Uuid: g.uuid(),
Request: request,
}
for _, userID := range append(userIDs, req.FromUserID) { for _, userID := range append(userIDs, req.FromUserID) {
tips := &sdkws.GroupApplicationAcceptedTips{Group: group, OpUser: opUser, HandleMsg: req.HandledMsg}
if userID == req.FromUserID { if userID == req.FromUserID {
tips.ReceiverAs = applicantReceiver tips.ReceiverAs = applicantReceiver
} else { } else {
@@ -505,7 +569,7 @@ func (g *NotificationSender) GroupOwnerTransferredNotification(ctx context.Conte
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupOwnerTransferredNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupOwnerTransferredNotification, tips)
} }
func (g *NotificationSender) MemberKickedNotification(ctx context.Context, tips *sdkws.MemberKickedTips) { func (g *NotificationSender) MemberKickedNotification(ctx context.Context, tips *sdkws.MemberKickedTips, SendMessage *bool) {
var err error var err error
defer func() { defer func() {
if err != nil { if err != nil {
@@ -516,10 +580,14 @@ func (g *NotificationSender) MemberKickedNotification(ctx context.Context, tips
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.MemberKickedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.MemberKickedNotification, tips, notification.WithSendMessage(SendMessage))
} }
func (g *NotificationSender) GroupApplicationAgreeMemberEnterNotification(ctx context.Context, groupID string, invitedOpUserID string, entrantUserID ...string) error { func (g *NotificationSender) GroupApplicationAgreeMemberEnterNotification(ctx context.Context, groupID string, SendMessage *bool, invitedOpUserID string, entrantUserID ...string) error {
return g.groupApplicationAgreeMemberEnterNotification(ctx, groupID, SendMessage, invitedOpUserID, entrantUserID...)
}
func (g *NotificationSender) groupApplicationAgreeMemberEnterNotification(ctx context.Context, groupID string, SendMessage *bool, invitedOpUserID string, entrantUserID ...string) error {
var err error var err error
defer func() { defer func() {
if err != nil { if err != nil {
@@ -556,20 +624,18 @@ func (g *NotificationSender) GroupApplicationAgreeMemberEnterNotification(ctx co
InvitedUserList: users, InvitedUserList: users,
} }
opUserID := mcontext.GetOpUserID(ctx) opUserID := mcontext.GetOpUserID(ctx)
if err = g.fillOpUserByUserID(ctx, opUserID, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillUserByUserID(ctx, opUserID, &tips.OpUser, tips.Group.GroupID); err != nil {
return nil return nil
} }
switch { if invitedOpUserID == opUserID {
case invitedOpUserID == "":
case invitedOpUserID == opUserID:
tips.InviterUser = tips.OpUser tips.InviterUser = tips.OpUser
default: } else {
if err = g.fillOpUserByUserID(ctx, invitedOpUserID, &tips.InviterUser, tips.Group.GroupID); err != nil { if err = g.fillUserByUserID(ctx, invitedOpUserID, &tips.InviterUser, tips.Group.GroupID); err != nil {
return err return err
} }
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberInvitedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberInvitedNotification, tips, notification.WithSendMessage(SendMessage))
return nil return nil
} }
@@ -614,7 +680,7 @@ func (g *NotificationSender) MemberEnterNotification(ctx context.Context, groupI
return nil return nil
} }
func (g *NotificationSender) GroupDismissedNotification(ctx context.Context, tips *sdkws.GroupDismissedTips) { func (g *NotificationSender) GroupDismissedNotification(ctx context.Context, tips *sdkws.GroupDismissedTips, SendMessage *bool) {
var err error var err error
defer func() { defer func() {
if err != nil { if err != nil {
@@ -624,7 +690,7 @@ func (g *NotificationSender) GroupDismissedNotification(ctx context.Context, tip
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupDismissedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupDismissedNotification, tips, notification.WithSendMessage(SendMessage))
} }
func (g *NotificationSender) GroupMemberMutedNotification(ctx context.Context, groupID, groupMemberUserID string, mutedSeconds uint32) { func (g *NotificationSender) GroupMemberMutedNotification(ctx context.Context, groupID, groupMemberUserID string, mutedSeconds uint32) {
@@ -781,7 +847,7 @@ func (g *NotificationSender) GroupMemberSetToAdminNotification(ctx context.Conte
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) g.setSortVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID, &tips.GroupSortVersion)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToAdminNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToAdminNotification, tips)
} }
@@ -806,6 +872,6 @@ func (g *NotificationSender) GroupMemberSetToOrdinaryUserNotification(ctx contex
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID) g.setSortVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID, &tips.GroupSortVersion)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToOrdinaryUserNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToOrdinaryUserNotification, tips)
} }
+4
View File
@@ -18,6 +18,7 @@ import (
"context" "context"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/protocol/group" "github.com/openimsdk/protocol/group"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
) )
@@ -26,6 +27,9 @@ func (g *groupServer) GroupCreateCount(ctx context.Context, req *group.GroupCrea
if req.Start > req.End { if req.Start > req.End {
return nil, errs.ErrArgs.WrapMsg("start > end: %d > %d", req.Start, req.End) return nil, errs.ErrArgs.WrapMsg("start > end: %d > %d", req.Start, req.End)
} }
if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err
}
total, err := g.db.CountTotal(ctx, nil) total, err := g.db.CountTotal(ctx, nil)
if err != nil { if err != nil {
return nil, err return nil, err
+34 -149
View File
@@ -11,16 +11,19 @@ import (
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
pbgroup "github.com/openimsdk/protocol/group" pbgroup "github.com/openimsdk/protocol/group"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
) )
const versionSyncLimit = 500
func (g *groupServer) GetFullGroupMemberUserIDs(ctx context.Context, req *pbgroup.GetFullGroupMemberUserIDsReq) (*pbgroup.GetFullGroupMemberUserIDsResp, error) { func (g *groupServer) GetFullGroupMemberUserIDs(ctx context.Context, req *pbgroup.GetFullGroupMemberUserIDsReq) (*pbgroup.GetFullGroupMemberUserIDsResp, error) {
vl, err := g.db.FindMaxGroupMemberVersionCache(ctx, req.GroupID) userIDs, err := g.db.FindGroupMemberUserID(ctx, req.GroupID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
userIDs, err := g.db.FindGroupMemberUserID(ctx, req.GroupID) if err := authverify.CheckAccessIn(ctx, userIDs...); err != nil {
return nil, err
}
vl, err := g.db.FindMaxGroupMemberVersionCache(ctx, req.GroupID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -37,6 +40,9 @@ func (g *groupServer) GetFullGroupMemberUserIDs(ctx context.Context, req *pbgrou
} }
func (g *groupServer) GetFullJoinGroupIDs(ctx context.Context, req *pbgroup.GetFullJoinGroupIDsReq) (*pbgroup.GetFullJoinGroupIDsResp, error) { func (g *groupServer) GetFullJoinGroupIDs(ctx context.Context, req *pbgroup.GetFullJoinGroupIDsReq) (*pbgroup.GetFullJoinGroupIDsResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
vl, err := g.db.FindMaxJoinGroupVersionCache(ctx, req.UserID) vl, err := g.db.FindMaxJoinGroupVersionCache(ctx, req.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -58,6 +64,9 @@ func (g *groupServer) GetFullJoinGroupIDs(ctx context.Context, req *pbgroup.GetF
} }
func (g *groupServer) GetIncrementalGroupMember(ctx context.Context, req *pbgroup.GetIncrementalGroupMemberReq) (*pbgroup.GetIncrementalGroupMemberResp, error) { func (g *groupServer) GetIncrementalGroupMember(ctx context.Context, req *pbgroup.GetIncrementalGroupMemberReq) (*pbgroup.GetIncrementalGroupMemberResp, error) {
if err := g.checkAdminOrInGroup(ctx, req.GroupID); err != nil {
return nil, err
}
group, err := g.db.TakeGroup(ctx, req.GroupID) group, err := g.db.TakeGroup(ctx, req.GroupID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -132,152 +141,8 @@ func (g *groupServer) GetIncrementalGroupMember(ctx context.Context, req *pbgrou
return resp, nil return resp, nil
} }
func (g *groupServer) BatchGetIncrementalGroupMember(ctx context.Context, req *pbgroup.BatchGetIncrementalGroupMemberReq) (resp *pbgroup.BatchGetIncrementalGroupMemberResp, err error) {
type VersionInfo struct {
GroupID string
VersionID string
VersionNumber uint64
}
var groupIDs []string
groupsVersionMap := make(map[string]*VersionInfo)
groupsMap := make(map[string]*model.Group)
hasGroupUpdateMap := make(map[string]bool)
sortVersionMap := make(map[string]uint64)
var targetKeys, versionIDs []string
var versionNumbers []uint64
var requestBodyLen int
for _, group := range req.ReqList {
groupsVersionMap[group.GroupID] = &VersionInfo{
GroupID: group.GroupID,
VersionID: group.VersionID,
VersionNumber: group.Version,
}
groupIDs = append(groupIDs, group.GroupID)
}
groups, err := g.db.FindGroup(ctx, groupIDs)
if err != nil {
return nil, errs.Wrap(err)
}
for _, group := range groups {
if group.Status == constant.GroupStatusDismissed {
err = servererrs.ErrDismissedAlready.Wrap()
log.ZError(ctx, "This group is Dismissed Already", err, "group is", group.GroupID)
delete(groupsVersionMap, group.GroupID)
} else {
groupsMap[group.GroupID] = group
}
}
for groupID, vInfo := range groupsVersionMap {
targetKeys = append(targetKeys, groupID)
versionIDs = append(versionIDs, vInfo.VersionID)
versionNumbers = append(versionNumbers, vInfo.VersionNumber)
}
opt := incrversion.BatchOption[[]*sdkws.GroupMemberFullInfo, pbgroup.BatchGetIncrementalGroupMemberResp]{
Ctx: ctx,
TargetKeys: targetKeys,
VersionIDs: versionIDs,
VersionNumbers: versionNumbers,
Versions: func(ctx context.Context, groupIDs []string, versions []uint64, limits []int) (map[string]*model.VersionLog, error) {
vLogs, err := g.db.BatchFindMemberIncrVersion(ctx, groupIDs, versions, limits)
if err != nil {
return nil, errs.Wrap(err)
}
for groupID, vlog := range vLogs {
vlogElems := make([]model.VersionLogElem, 0, len(vlog.Logs))
for i, log := range vlog.Logs {
switch log.EID {
case model.VersionGroupChangeID:
vlog.LogLen--
hasGroupUpdateMap[groupID] = true
case model.VersionSortChangeID:
vlog.LogLen--
sortVersionMap[groupID] = uint64(log.Version)
default:
vlogElems = append(vlogElems, vlog.Logs[i])
}
}
vlog.Logs = vlogElems
if vlog.LogLen > 0 {
hasGroupUpdateMap[groupID] = true
}
}
return vLogs, nil
},
CacheMaxVersions: g.db.BatchFindMaxGroupMemberVersionCache,
Find: func(ctx context.Context, groupID string, ids []string) ([]*sdkws.GroupMemberFullInfo, error) {
memberInfo, err := g.getGroupMembersInfo(ctx, groupID, ids)
if err != nil {
return nil, err
}
return memberInfo, err
},
Resp: func(versions map[string]*model.VersionLog, deleteIdsMap map[string][]string, insertListMap, updateListMap map[string][]*sdkws.GroupMemberFullInfo, fullMap map[string]bool) *pbgroup.BatchGetIncrementalGroupMemberResp {
resList := make(map[string]*pbgroup.GetIncrementalGroupMemberResp)
for groupID, versionLog := range versions {
resList[groupID] = &pbgroup.GetIncrementalGroupMemberResp{
VersionID: versionLog.ID.Hex(),
Version: uint64(versionLog.Version),
Full: fullMap[groupID],
Delete: deleteIdsMap[groupID],
Insert: insertListMap[groupID],
Update: updateListMap[groupID],
SortVersion: sortVersionMap[groupID],
}
requestBodyLen += len(insertListMap[groupID]) + len(updateListMap[groupID]) + len(deleteIdsMap[groupID])
if requestBodyLen > 200 {
break
}
}
return &pbgroup.BatchGetIncrementalGroupMemberResp{
RespList: resList,
}
},
}
resp, err = opt.Build()
if err != nil {
return nil, errs.Wrap(err)
}
for groupID, val := range resp.RespList {
if val.Full || hasGroupUpdateMap[groupID] {
count, err := g.db.FindGroupMemberNum(ctx, groupID)
if err != nil {
return nil, err
}
owner, err := g.db.TakeGroupOwner(ctx, groupID)
if err != nil {
return nil, err
}
resp.RespList[groupID].Group = g.groupDB2PB(groupsMap[groupID], owner.UserID, count)
}
}
return resp, nil
}
func (g *groupServer) GetIncrementalJoinGroup(ctx context.Context, req *pbgroup.GetIncrementalJoinGroupReq) (*pbgroup.GetIncrementalJoinGroupResp, error) { func (g *groupServer) GetIncrementalJoinGroup(ctx context.Context, req *pbgroup.GetIncrementalJoinGroupReq) (*pbgroup.GetIncrementalJoinGroupResp, error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, g.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
opt := incrversion.Option[*sdkws.GroupInfo, pbgroup.GetIncrementalJoinGroupResp]{ opt := incrversion.Option[*sdkws.GroupInfo, pbgroup.GetIncrementalJoinGroupResp]{
@@ -301,3 +166,23 @@ func (g *groupServer) GetIncrementalJoinGroup(ctx context.Context, req *pbgroup.
} }
return opt.Build() return opt.Build()
} }
func (g *groupServer) BatchGetIncrementalGroupMember(ctx context.Context, req *pbgroup.BatchGetIncrementalGroupMemberReq) (*pbgroup.BatchGetIncrementalGroupMemberResp, error) {
var num int
resp := make(map[string]*pbgroup.GetIncrementalGroupMemberResp)
for _, memberReq := range req.ReqList {
if _, ok := resp[memberReq.GroupID]; ok {
continue
}
memberResp, err := g.GetIncrementalGroupMember(ctx, memberReq)
if err != nil {
return nil, err
}
resp[memberReq.GroupID] = memberResp
num += len(memberResp.Insert) + len(memberResp.Update) + len(memberResp.Delete)
if num >= versionSyncLimit {
break
}
}
return &pbgroup.BatchGetIncrementalGroupMemberResp{RespList: resp}, nil
}
+19 -3
View File
@@ -18,6 +18,7 @@ import (
"context" "context"
"errors" "errors"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
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"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
@@ -29,6 +30,9 @@ import (
) )
func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *msg.GetConversationsHasReadAndMaxSeqReq) (*msg.GetConversationsHasReadAndMaxSeqResp, error) { func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *msg.GetConversationsHasReadAndMaxSeqReq) (*msg.GetConversationsHasReadAndMaxSeqResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
var conversationIDs []string var conversationIDs []string
if len(req.ConversationIDs) == 0 { if len(req.ConversationIDs) == 0 {
var err error var err error
@@ -61,6 +65,13 @@ func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *m
return nil, err return nil, err
} }
resp := &msg.GetConversationsHasReadAndMaxSeqResp{Seqs: make(map[string]*msg.Seqs)} resp := &msg.GetConversationsHasReadAndMaxSeqResp{Seqs: make(map[string]*msg.Seqs)}
if req.ReturnPinned {
pinnedConversationIDs, err := m.ConversationLocalCache.GetPinnedConversationIDs(ctx, req.UserID)
if err != nil {
return nil, err
}
resp.PinnedConversationIDs = pinnedConversationIDs
}
for conversationID, maxSeq := range maxSeqs { for conversationID, maxSeq := range maxSeqs {
resp.Seqs[conversationID] = &msg.Seqs{ resp.Seqs[conversationID] = &msg.Seqs{
HasReadSeq: hasReadSeqs[conversationID], HasReadSeq: hasReadSeqs[conversationID],
@@ -75,6 +86,9 @@ func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *m
} }
func (m *msgServer) SetConversationHasReadSeq(ctx context.Context, req *msg.SetConversationHasReadSeqReq) (*msg.SetConversationHasReadSeqResp, error) { func (m *msgServer) SetConversationHasReadSeq(ctx context.Context, req *msg.SetConversationHasReadSeqReq) (*msg.SetConversationHasReadSeqResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID) maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -90,8 +104,8 @@ func (m *msgServer) SetConversationHasReadSeq(ctx context.Context, req *msg.SetC
} }
func (m *msgServer) MarkMsgsAsRead(ctx context.Context, req *msg.MarkMsgsAsReadReq) (*msg.MarkMsgsAsReadResp, error) { func (m *msgServer) MarkMsgsAsRead(ctx context.Context, req *msg.MarkMsgsAsReadReq) (*msg.MarkMsgsAsReadResp, error) {
if len(req.Seqs) < 1 { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, errs.ErrArgs.WrapMsg("seqs must not be empty") return nil, err
} }
maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID) maxSeq, err := m.MsgDatabase.GetMaxSeq(ctx, req.ConversationID)
if err != nil { if err != nil {
@@ -132,6 +146,9 @@ func (m *msgServer) MarkMsgsAsRead(ctx context.Context, req *msg.MarkMsgsAsReadR
} }
func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkConversationAsReadReq) (*msg.MarkConversationAsReadResp, error) { func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkConversationAsReadReq) (*msg.MarkConversationAsReadResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
conversation, err := m.ConversationLocalCache.GetConversation(ctx, req.UserID, req.ConversationID) conversation, err := m.ConversationLocalCache.GetConversation(ctx, req.UserID, req.ConversationID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -209,5 +226,4 @@ func (m *msgServer) sendMarkAsReadNotification(ctx context.Context, conversation
HasReadSeq: hasReadSeq, HasReadSeq: hasReadSeq,
} }
m.notificationSender.NotificationWithSessionType(ctx, sendID, recvID, constant.HasReadReceipt, sessionType, tips) m.notificationSender.NotificationWithSessionType(ctx, sendID, recvID, constant.HasReadReceipt, sessionType, tips)
} }
+54 -31
View File
@@ -16,7 +16,10 @@ package msg
import ( import (
"context" "context"
"encoding/json"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/tools/errs"
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
@@ -83,19 +86,20 @@ func (m *msgServer) webhookBeforeSendSingleMsg(ctx context.Context, before *conf
}) })
} }
func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) { // Move to msgtransfer
if msg.MsgData.ContentType == constant.Typing { // func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) {
return // if msg.MsgData.ContentType == constant.Typing {
} // return
if !filterAfterMsg(msg, after) { // }
return // if !filterAfterMsg(msg, after) {
} // return
cbReq := &cbapi.CallbackAfterSendSingleMsgReq{ // }
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand), // cbReq := &cbapi.CallbackAfterSendSingleMsgReq{
RecvID: msg.MsgData.RecvID, // CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand),
} // RecvID: msg.MsgData.RecvID,
m.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendSingleMsgResp{}, after) // }
} // m.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendSingleMsgResp{}, after, buildKeyMsgDataQuery(msg.MsgData))
// }
func (m *msgServer) webhookBeforeSendGroupMsg(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error { func (m *msgServer) webhookBeforeSendGroupMsg(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error {
return webhook.WithCondition(ctx, before, func(ctx context.Context) error { return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
@@ -117,25 +121,27 @@ func (m *msgServer) webhookBeforeSendGroupMsg(ctx context.Context, before *confi
}) })
} }
func (m *msgServer) webhookAfterSendGroupMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) { // Move to msgtransfer
if msg.MsgData.ContentType == constant.Typing { // func (m *msgServer) webhookAfterSendGroupMsg(ctx context.Context, after *config.AfterConfig, msg *pbchat.SendMsgReq) {
return // if msg.MsgData.ContentType == constant.Typing {
} // return
if !filterAfterMsg(msg, after) { // }
return // if !filterAfterMsg(msg, after) {
} // return
cbReq := &cbapi.CallbackAfterSendGroupMsgReq{ // }
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand), // cbReq := &cbapi.CallbackAfterSendGroupMsgReq{
GroupID: msg.MsgData.GroupID, // CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand),
} // GroupID: msg.MsgData.GroupID,
m.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendGroupMsgResp{}, after) // }
}
func (m *msgServer) webhookBeforeMsgModify(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq) error { // m.webhookClient.AsyncPostWithQuery(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterSendGroupMsgResp{}, after, buildKeyMsgDataQuery(msg.MsgData))
// }
func (m *msgServer) webhookBeforeMsgModify(ctx context.Context, before *config.BeforeConfig, msg *pbchat.SendMsgReq, beforeMsgData **sdkws.MsgData) error {
return webhook.WithCondition(ctx, before, func(ctx context.Context) error { return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
if msg.MsgData.ContentType != constant.Text { //if msg.MsgData.ContentType != constant.Text {
return nil // return nil
} //}
if !filterBeforeMsg(msg, before) { if !filterBeforeMsg(msg, before) {
return nil return nil
} }
@@ -146,9 +152,14 @@ func (m *msgServer) webhookBeforeMsgModify(ctx context.Context, before *config.B
if err := m.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil { if err := m.webhookClient.SyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, before); err != nil {
return err return err
} }
if beforeMsgData != nil {
*beforeMsgData = proto.Clone(msg.MsgData).(*sdkws.MsgData)
}
if resp.Content != nil { if resp.Content != nil {
msg.MsgData.Content = []byte(*resp.Content) msg.MsgData.Content = []byte(*resp.Content)
if err := json.Unmarshal(msg.MsgData.Content, &struct{}{}); err != nil {
return errs.ErrArgs.WrapMsg("webhook msg modify content is not json", "content", string(msg.MsgData.Content))
}
} }
datautil.NotNilReplace(msg.MsgData.OfflinePushInfo, resp.OfflinePushInfo) datautil.NotNilReplace(msg.MsgData.OfflinePushInfo, resp.OfflinePushInfo)
datautil.NotNilReplace(&msg.MsgData.RecvID, resp.RecvID) datautil.NotNilReplace(&msg.MsgData.RecvID, resp.RecvID)
@@ -192,3 +203,15 @@ func (m *msgServer) webhookAfterRevokeMsg(ctx context.Context, after *config.Aft
} }
m.webhookClient.AsyncPost(ctx, callbackReq.GetCallbackCommand(), callbackReq, &cbapi.CallbackAfterRevokeMsgResp{}, after) m.webhookClient.AsyncPost(ctx, callbackReq.GetCallbackCommand(), callbackReq, &cbapi.CallbackAfterRevokeMsgResp{}, after)
} }
// func buildKeyMsgDataQuery(msg *sdkws.MsgData) map[string]string {
// keyMsgData := apistruct.KeyMsgData{
// SendID: msg.SendID,
// RecvID: msg.RecvID,
// GroupID: msg.GroupID,
// }
// return map[string]string{
// webhook.Key: base64.StdEncoding.EncodeToString(stringutil.StructToJsonBytes(keyMsgData)),
// }
// }
+3 -2
View File
@@ -2,15 +2,16 @@ package msg
import ( import (
"context" "context"
"strings"
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"strings"
) )
// DestructMsgs hard delete in Database. // DestructMsgs hard delete in Database.
func (m *msgServer) DestructMsgs(ctx context.Context, req *msg.DestructMsgsReq) (*msg.DestructMsgsResp, error) { func (m *msgServer) DestructMsgs(ctx context.Context, req *msg.DestructMsgsReq) (*msg.DestructMsgsResp, error) {
if err := authverify.CheckAdmin(ctx, m.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err return nil, err
} }
docs, err := m.MsgDatabase.GetRandBeforeMsg(ctx, req.Timestamp, int(req.Limit)) docs, err := m.MsgDatabase.GetRandBeforeMsg(ctx, req.Timestamp, int(req.Limit))
+7 -4
View File
@@ -42,7 +42,7 @@ func (m *msgServer) validateDeleteSyncOpt(opt *msg.DeleteSyncOpt) (isSyncSelf, i
} }
func (m *msgServer) ClearConversationsMsg(ctx context.Context, req *msg.ClearConversationsMsgReq) (*msg.ClearConversationsMsgResp, error) { func (m *msgServer) ClearConversationsMsg(ctx context.Context, req *msg.ClearConversationsMsgReq) (*msg.ClearConversationsMsgResp, error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, m.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
if err := m.clearConversation(ctx, req.ConversationIDs, req.UserID, req.DeleteSyncOpt); err != nil { if err := m.clearConversation(ctx, req.ConversationIDs, req.UserID, req.DeleteSyncOpt); err != nil {
@@ -52,7 +52,7 @@ func (m *msgServer) ClearConversationsMsg(ctx context.Context, req *msg.ClearCon
} }
func (m *msgServer) UserClearAllMsg(ctx context.Context, req *msg.UserClearAllMsgReq) (*msg.UserClearAllMsgResp, error) { func (m *msgServer) UserClearAllMsg(ctx context.Context, req *msg.UserClearAllMsgReq) (*msg.UserClearAllMsgResp, error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, m.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
conversationIDs, err := m.ConversationLocalCache.GetConversationIDs(ctx, req.UserID) conversationIDs, err := m.ConversationLocalCache.GetConversationIDs(ctx, req.UserID)
@@ -66,7 +66,7 @@ func (m *msgServer) UserClearAllMsg(ctx context.Context, req *msg.UserClearAllMs
} }
func (m *msgServer) DeleteMsgs(ctx context.Context, req *msg.DeleteMsgsReq) (*msg.DeleteMsgsResp, error) { func (m *msgServer) DeleteMsgs(ctx context.Context, req *msg.DeleteMsgsReq) (*msg.DeleteMsgsResp, error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, m.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
isSyncSelf, isSyncOther := m.validateDeleteSyncOpt(req.DeleteSyncOpt) isSyncSelf, isSyncOther := m.validateDeleteSyncOpt(req.DeleteSyncOpt)
@@ -94,6 +94,9 @@ func (m *msgServer) DeleteMsgs(ctx context.Context, req *msg.DeleteMsgsReq) (*ms
} }
func (m *msgServer) DeleteMsgPhysicalBySeq(ctx context.Context, req *msg.DeleteMsgPhysicalBySeqReq) (*msg.DeleteMsgPhysicalBySeqResp, error) { func (m *msgServer) DeleteMsgPhysicalBySeq(ctx context.Context, req *msg.DeleteMsgPhysicalBySeqReq) (*msg.DeleteMsgPhysicalBySeqResp, error) {
if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err
}
err := m.MsgDatabase.DeleteMsgsPhysicalBySeqs(ctx, req.ConversationID, req.Seqs) err := m.MsgDatabase.DeleteMsgsPhysicalBySeqs(ctx, req.ConversationID, req.Seqs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -102,7 +105,7 @@ func (m *msgServer) DeleteMsgPhysicalBySeq(ctx context.Context, req *msg.DeleteM
} }
func (m *msgServer) DeleteMsgPhysical(ctx context.Context, req *msg.DeleteMsgPhysicalReq) (*msg.DeleteMsgPhysicalResp, error) { func (m *msgServer) DeleteMsgPhysical(ctx context.Context, req *msg.DeleteMsgPhysicalReq) (*msg.DeleteMsgPhysicalResp, error) {
if err := authverify.CheckAdmin(ctx, m.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err return nil, err
} }
remainTime := timeutil.GetCurrentTimestampBySecond() - req.Timestamp remainTime := timeutil.GetCurrentTimestampBySecond() - req.Timestamp
+36 -12
View File
@@ -1,11 +1,13 @@
package msg package msg
import ( import (
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
pbchat "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/tools/utils/datautil"
"strconv" "strconv"
"strings" "strings"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/protocol/constant"
pbchat "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/tools/utils/datautil"
) )
const ( const (
@@ -13,28 +15,50 @@ const (
) )
func filterAfterMsg(msg *pbchat.SendMsgReq, after *config.AfterConfig) bool { func filterAfterMsg(msg *pbchat.SendMsgReq, after *config.AfterConfig) bool {
return filterMsg(msg, after.AttentionIds, after.AllowedTypes, after.DeniedTypes) return filterMsg(msg, after.AttentionIds, after.DeniedTypes)
} }
func filterBeforeMsg(msg *pbchat.SendMsgReq, before *config.BeforeConfig) bool { func filterBeforeMsg(msg *pbchat.SendMsgReq, before *config.BeforeConfig) bool {
return filterMsg(msg, nil, before.AllowedTypes, before.DeniedTypes) return filterMsg(msg, nil, before.DeniedTypes)
} }
func filterMsg(msg *pbchat.SendMsgReq, attentionIds, allowedTypes, deniedTypes []string) bool { func filterMsg(msg *pbchat.SendMsgReq, attentionIds []string, deniedTypes []int32) bool {
// According to the attentionIds configuration, only some users are sent // According to the attentionIds configuration, only some users are sent
if len(attentionIds) != 0 && !datautil.Contains([]string{msg.MsgData.SendID, msg.MsgData.RecvID}, attentionIds...) { if len(attentionIds) != 0 && !datautil.Contain(msg.MsgData.RecvID, attentionIds...) {
return false return false
} }
if len(allowedTypes) != 0 && !isInInterval(msg.MsgData.ContentType, allowedTypes) {
if defaultDeniedTypes(msg.MsgData.ContentType) {
return false return false
} }
if len(deniedTypes) != 0 && isInInterval(msg.MsgData.ContentType, deniedTypes) {
if len(deniedTypes) != 0 && datautil.Contain(msg.MsgData.ContentType, deniedTypes...) {
return false return false
} }
//if len(allowedTypes) != 0 && !isInInterval(msg.MsgData.ContentType, allowedTypes) {
// return false
//}
//if len(deniedTypes) != 0 && isInInterval(msg.MsgData.ContentType, deniedTypes) {
// return false
//}
return true return true
} }
func isInInterval(contentType int32, interval []string) bool { func defaultDeniedTypes(contentType int32) bool {
if contentType >= constant.NotificationBegin && contentType <= constant.NotificationEnd {
return true
}
if contentType == constant.Typing {
return true
}
return false
}
// isInInterval if data is in interval
// Supports two formats: a single type or a range. The range is defined by the lower and upper bounds connected with a hyphen ("-")
// e.g. [1, 100, 200-500, 600-700] means that only data within the range
// {1, 100} [200, 500] [600, 700] will return true.
func isInInterval(data int32, interval []string) bool {
for _, v := range interval { for _, v := range interval {
if strings.Contains(v, separator) { if strings.Contains(v, separator) {
// is interval // is interval
@@ -50,7 +74,7 @@ func isInInterval(contentType int32, interval []string) bool {
if err != nil { if err != nil {
continue continue
} }
if datautil.BetweenEq(int(contentType), bottom, top) { if datautil.BetweenEq(int(data), bottom, top) {
return true return true
} }
} else { } else {
@@ -58,7 +82,7 @@ func isInInterval(contentType int32, interval []string) bool {
if err != nil { if err != nil {
continue continue
} }
if int(contentType) == iv { if int(data) == iv {
return true return true
} }
} }
+3 -7
View File
@@ -23,11 +23,11 @@ import (
) )
type MsgNotificationSender struct { type MsgNotificationSender struct {
*rpcclient.NotificationSender *notification.NotificationSender
} }
func NewMsgNotificationSender(config *Config, opts ...rpcclient.NotificationSenderOptions) *MsgNotificationSender { func NewMsgNotificationSender(config *Config, opts ...notification.NotificationSenderOptions) *MsgNotificationSender {
return &MsgNotificationSender{rpcclient.NewNotificationSender(&config.NotificationConfig, opts...)} return &MsgNotificationSender{notification.NewNotificationSender(&config.NotificationConfig, opts...)}
} }
func (m *MsgNotificationSender) UserDeleteMsgsNotification(ctx context.Context, userID, conversationID string, seqs []int64) { func (m *MsgNotificationSender) UserDeleteMsgsNotification(ctx context.Context, userID, conversationID string, seqs []int64) {
@@ -48,7 +48,3 @@ func (m *MsgNotificationSender) MarkAsReadNotification(ctx context.Context, conv
} }
m.NotificationWithSessionType(ctx, sendID, recvID, constant.HasReadReceipt, sessionType, tips) m.NotificationWithSessionType(ctx, sendID, recvID, constant.HasReadReceipt, sessionType, tips)
} }
func (m *MsgNotificationSender) StreamMsgNotification(ctx context.Context, sendID string, recvID string, sessionType int32, tips *sdkws.StreamMsgTips) {
m.NotificationWithSessionType(ctx, sendID, recvID, constant.StreamMsgNotification, sessionType, tips)
}
+11 -8
View File
@@ -17,9 +17,10 @@ package msg
import ( import (
"context" "context"
"encoding/json" "encoding/json"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"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/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
@@ -41,7 +42,7 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg.
if req.Seq < 0 { if req.Seq < 0 {
return nil, errs.ErrArgs.WrapMsg("seq is invalid") return nil, errs.ErrArgs.WrapMsg("seq is invalid")
} }
if err := authverify.CheckAccessV3(ctx, req.UserID, m.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
user, err := m.UserLocalCache.GetUserInfo(ctx, req.UserID) user, err := m.UserLocalCache.GetUserInfo(ctx, req.UserID)
@@ -62,11 +63,11 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg.
data, _ := json.Marshal(msgs[0]) data, _ := json.Marshal(msgs[0])
log.ZDebug(ctx, "GetMsgBySeqs", "conversationID", req.ConversationID, "seq", req.Seq, "msg", string(data)) log.ZDebug(ctx, "GetMsgBySeqs", "conversationID", req.ConversationID, "seq", req.Seq, "msg", string(data))
var role int32 var role int32
if !authverify.IsAppManagerUid(ctx, m.config.Share.IMAdminUserID) { if !authverify.IsAdmin(ctx) {
sessionType := msgs[0].SessionType sessionType := msgs[0].SessionType
switch sessionType { switch sessionType {
case constant.SingleChatType: case constant.SingleChatType:
if err := authverify.CheckAccessV3(ctx, msgs[0].SendID, m.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, msgs[0].SendID); err != nil {
return nil, err return nil, err
} }
role = user.AppMangerLevel role = user.AppMangerLevel
@@ -79,8 +80,10 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg.
switch members[req.UserID].RoleLevel { switch members[req.UserID].RoleLevel {
case constant.GroupOwner: case constant.GroupOwner:
case constant.GroupAdmin: case constant.GroupAdmin:
if members[msgs[0].SendID].RoleLevel != constant.GroupOrdinaryUsers { if sendMember, ok := members[msgs[0].SendID]; ok {
return nil, errs.ErrNoPermission.WrapMsg("no permission") if sendMember.RoleLevel != constant.GroupOrdinaryUsers {
return nil, errs.ErrNoPermission.WrapMsg("no permission")
}
} }
default: default:
return nil, errs.ErrNoPermission.WrapMsg("no permission") return nil, errs.ErrNoPermission.WrapMsg("no permission")
@@ -106,8 +109,8 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg.
revokerUserID := mcontext.GetOpUserID(ctx) revokerUserID := mcontext.GetOpUserID(ctx)
var flag bool var flag bool
if len(m.config.Share.IMAdminUserID) > 0 { if len(m.config.Share.IMAdminUser.UserIDs) > 0 {
flag = datautil.Contain(revokerUserID, m.config.Share.IMAdminUserID...) flag = datautil.Contain(revokerUserID, m.adminUserIDs...)
} }
tips := sdkws.RevokeMsgTips{ tips := sdkws.RevokeMsgTips{
RevokerUserID: revokerUserID, RevokerUserID: revokerUserID,
+64 -34
View File
@@ -17,6 +17,9 @@ package msg
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"google.golang.org/protobuf/proto"
"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"
"github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil" "github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil"
@@ -32,28 +35,38 @@ import (
) )
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) {
if req.MsgData != nil { if req.MsgData == nil {
m.encapsulateMsgData(req.MsgData) return nil, errs.ErrArgs.WrapMsg("msgData is nil")
if req.MsgData.ContentType == constant.Stream {
if err := m.handlerStreamMsg(ctx, req.MsgData); err != nil {
return nil, err
}
}
switch req.MsgData.SessionType {
case constant.SingleChatType:
return m.sendMsgSingleChat(ctx, req)
case constant.NotificationChatType:
return m.sendMsgNotification(ctx, req)
case constant.ReadGroupChatType:
return m.sendMsgGroupChat(ctx, req)
default:
return nil, errs.ErrArgs.WrapMsg("unknown sessionType")
}
} }
return nil, errs.ErrArgs.WrapMsg("msgData is nil") if err := authverify.CheckAccess(ctx, req.MsgData.SendID); err != nil {
return nil, err
}
before := new(*sdkws.MsgData)
resp, err := m.sendMsg(ctx, req, before)
if err != nil {
return nil, err
}
if *before != nil && proto.Equal(*before, req.MsgData) == false {
resp.Modify = req.MsgData
}
return resp, nil
} }
func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, err error) { func (m *msgServer) sendMsg(ctx context.Context, req *pbmsg.SendMsgReq, before **sdkws.MsgData) (*pbmsg.SendMsgResp, error) {
m.encapsulateMsgData(req.MsgData)
switch req.MsgData.SessionType {
case constant.SingleChatType:
return m.sendMsgSingleChat(ctx, req, before)
case constant.NotificationChatType:
return m.sendMsgNotification(ctx, req, before)
case constant.ReadGroupChatType:
return m.sendMsgGroupChat(ctx, req, before)
default:
return nil, errs.ErrArgs.WrapMsg("unknown sessionType")
}
}
func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq, before **sdkws.MsgData) (resp *pbmsg.SendMsgResp, err error) {
if err = m.messageVerification(ctx, req); err != nil { if err = m.messageVerification(ctx, req); err != nil {
prommetrics.GroupChatMsgProcessFailedCounter.Inc() prommetrics.GroupChatMsgProcessFailedCounter.Inc()
return nil, err return nil, err
@@ -62,7 +75,7 @@ func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq)
if err = m.webhookBeforeSendGroupMsg(ctx, &m.config.WebhooksConfig.BeforeSendGroupMsg, req); err != nil { if err = m.webhookBeforeSendGroupMsg(ctx, &m.config.WebhooksConfig.BeforeSendGroupMsg, req); err != nil {
return nil, err 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, before); err != nil {
return nil, err return nil, err
} }
err = m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForGroup(req.MsgData.GroupID), req.MsgData) err = m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForGroup(req.MsgData.GroupID), req.MsgData)
@@ -73,7 +86,8 @@ func (m *msgServer) sendMsgGroupChat(ctx context.Context, req *pbmsg.SendMsgReq)
go m.setConversationAtInfo(ctx, req.MsgData) go m.setConversationAtInfo(ctx, req.MsgData)
} }
m.webhookAfterSendGroupMsg(ctx, &m.config.WebhooksConfig.AfterSendGroupMsg, req) // m.webhookAfterSendGroupMsg(ctx, &m.config.WebhooksConfig.AfterSendGroupMsg, req)
prommetrics.GroupChatMsgProcessSuccessCounter.Inc() prommetrics.GroupChatMsgProcessSuccessCounter.Inc()
resp = &pbmsg.SendMsgResp{} resp = &pbmsg.SendMsgResp{}
resp.SendTime = req.MsgData.SendTime resp.SendTime = req.MsgData.SendTime
@@ -144,7 +158,7 @@ func (m *msgServer) setConversationAtInfo(nctx context.Context, msg *sdkws.MsgDa
} }
} }
func (m *msgServer) sendMsgNotification(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, err error) { func (m *msgServer) sendMsgNotification(ctx context.Context, req *pbmsg.SendMsgReq, before **sdkws.MsgData) (resp *pbmsg.SendMsgResp, err error) {
if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil { if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil {
return nil, err return nil, err
} }
@@ -156,37 +170,31 @@ func (m *msgServer) sendMsgNotification(ctx context.Context, req *pbmsg.SendMsgR
return resp, nil return resp, nil
} }
func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq) (resp *pbmsg.SendMsgResp, err error) { func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq, before **sdkws.MsgData) (resp *pbmsg.SendMsgResp, err error) {
if err := m.messageVerification(ctx, req); err != nil { if err := m.messageVerification(ctx, req); err != nil {
return nil, err return nil, err
} }
isSend := true isSend := true
isNotification := msgprocessor.IsNotificationByMsg(req.MsgData) isNotification := msgprocessor.IsNotificationByMsg(req.MsgData)
if !isNotification { if !isNotification {
isSend, err = m.modifyMessageByUserMessageReceiveOpt( isSend, err = m.modifyMessageByUserMessageReceiveOpt(authverify.WithTempAdmin(ctx), req.MsgData.RecvID, conversationutil.GenConversationIDForSingle(req.MsgData.SendID, req.MsgData.RecvID), constant.SingleChatType, req)
ctx,
req.MsgData.RecvID,
conversationutil.GenConversationIDForSingle(req.MsgData.SendID, req.MsgData.RecvID),
constant.SingleChatType,
req,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} }
if !isSend { if !isSend {
prommetrics.SingleChatMsgProcessFailedCounter.Inc() prommetrics.SingleChatMsgProcessFailedCounter.Inc()
return nil, nil return nil, errs.ErrArgs.WrapMsg("message is not sent")
} else { } else {
if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req); err != nil { if err := m.webhookBeforeMsgModify(ctx, &m.config.WebhooksConfig.BeforeMsgModify, req, before); err != nil {
return nil, err return nil, err
} }
if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil { if err := m.MsgDatabase.MsgToMQ(ctx, conversationutil.GenConversationUniqueKeyForSingle(req.MsgData.SendID, req.MsgData.RecvID), req.MsgData); err != nil {
prommetrics.SingleChatMsgProcessFailedCounter.Inc() prommetrics.SingleChatMsgProcessFailedCounter.Inc()
return nil, err return nil, err
} }
m.webhookAfterSendSingleMsg(ctx, &m.config.WebhooksConfig.AfterSendSingleMsg, req)
// m.webhookAfterSendSingleMsg(ctx, &m.config.WebhooksConfig.AfterSendSingleMsg, req)
prommetrics.SingleChatMsgProcessSuccessCounter.Inc() prommetrics.SingleChatMsgProcessSuccessCounter.Inc()
return &pbmsg.SendMsgResp{ return &pbmsg.SendMsgResp{
ServerMsgID: req.MsgData.ServerMsgID, ServerMsgID: req.MsgData.ServerMsgID,
@@ -195,3 +203,25 @@ func (m *msgServer) sendMsgSingleChat(ctx context.Context, req *pbmsg.SendMsgReq
}, nil }, nil
} }
} }
func (m *msgServer) SendSimpleMsg(ctx context.Context, req *pbmsg.SendSimpleMsgReq) (*pbmsg.SendSimpleMsgResp, error) {
if req.MsgData == nil {
return nil, errs.ErrArgs.WrapMsg("msg data is nil")
}
sender, err := m.UserLocalCache.GetUserInfo(ctx, req.MsgData.SendID)
if err != nil {
return nil, err
}
req.MsgData.SenderFaceURL = sender.FaceURL
req.MsgData.SenderNickname = sender.Nickname
resp, err := m.SendMsg(ctx, &pbmsg.SendMsgReq{MsgData: req.MsgData})
if err != nil {
return nil, err
}
return &pbmsg.SendSimpleMsgResp{
ServerMsgID: resp.ServerMsgID,
ClientMsgID: resp.ClientMsgID,
SendTime: resp.SendTime,
Modify: resp.Modify,
}, nil
}
+2 -1
View File
@@ -17,9 +17,10 @@ package msg
import ( import (
"context" "context"
"errors" "errors"
"sort"
pbmsg "github.com/openimsdk/protocol/msg" pbmsg "github.com/openimsdk/protocol/msg"
"github.com/redis/go-redis/v9" "github.com/redis/go-redis/v9"
"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) {
+36 -25
View File
@@ -16,24 +16,26 @@ package msg
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/mcache"
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/mqbuild"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"google.golang.org/grpc"
"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/redis" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/open-im-server/v3/pkg/notification" "github.com/openimsdk/open-im-server/v3/pkg/notification"
"github.com/openimsdk/open-im-server/v3/pkg/rpccache" "github.com/openimsdk/open-im-server/v3/pkg/rpccache"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/conversation" "github.com/openimsdk/protocol/conversation"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"google.golang.org/grpc"
) )
type MessageInterceptorFunc func(ctx context.Context, globalConfig *Config, req *msg.SendMsgReq) (*sdkws.MsgData, error) type MessageInterceptorFunc func(ctx context.Context, globalConfig *Config, req *msg.SendMsgReq) (*sdkws.MsgData, error)
@@ -56,19 +58,20 @@ type Config struct {
// MsgServer encapsulates dependencies required for message handling. // MsgServer encapsulates dependencies required for message handling.
type msgServer struct { type msgServer struct {
msg.UnimplementedMsgServer msg.UnimplementedMsgServer
RegisterCenter discovery.SvcDiscoveryRegistry // Service discovery registry for service registration. RegisterCenter discovery.Conn // Service discovery registry for service registration.
MsgDatabase controller.CommonMsgDatabase // Interface for message database operations. MsgDatabase controller.CommonMsgDatabase // Interface for message database operations.
StreamMsgDatabase controller.StreamMsgDatabase
UserLocalCache *rpccache.UserLocalCache // Local cache for user data. UserLocalCache *rpccache.UserLocalCache // Local cache for user data.
FriendLocalCache *rpccache.FriendLocalCache // Local cache for friend data. FriendLocalCache *rpccache.FriendLocalCache // Local cache for friend data.
GroupLocalCache *rpccache.GroupLocalCache // Local cache for group data. GroupLocalCache *rpccache.GroupLocalCache // Local cache for group data.
ConversationLocalCache *rpccache.ConversationLocalCache // Local cache for conversation data. ConversationLocalCache *rpccache.ConversationLocalCache // Local cache for conversation data.
Handlers MessageInterceptorChain // Chain of handlers for processing messages. Handlers MessageInterceptorChain // Chain of handlers for processing messages.
notificationSender *rpcclient.NotificationSender // RPC client for sending notifications. notificationSender *notification.NotificationSender // RPC client for sending notifications.
msgNotificationSender *MsgNotificationSender // RPC client for sending msg notifications. msgNotificationSender *MsgNotificationSender // RPC client for sending msg notifications.
config *Config // Global configuration settings. config *Config // Global configuration settings.
webhookClient *webhook.Client webhookClient *webhook.Client
conversationClient *rpcli.ConversationClient conversationClient *rpcli.ConversationClient
adminUserIDs []string
} }
func (m *msgServer) addInterceptorHandler(interceptorFunc ...MessageInterceptorFunc) { func (m *msgServer) addInterceptorHandler(interceptorFunc ...MessageInterceptorFunc) {
@@ -76,12 +79,18 @@ func (m *msgServer) addInterceptorHandler(interceptorFunc ...MessageInterceptorF
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) builder := mqbuild.NewBuilder(&config.KafkaConfig)
redisProducer, err := builder.GetTopicProducer(ctx, config.KafkaConfig.ToRedisTopic)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil {
return err
}
rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
@@ -89,7 +98,16 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
if err != nil { if err != nil {
return err return err
} }
msgModel := redis.NewMsgCache(rdb, msgDocModel) var msgModel cache.MsgCache
if rdb == nil {
cm, err := mgo.NewCacheMgo(mgocli.GetDB())
if err != nil {
return err
}
msgModel = mcache.NewMsgCache(cm, msgDocModel)
} else {
msgModel = redis.NewMsgCache(rdb, msgDocModel)
}
seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB()) seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB())
if err != nil { if err != nil {
return err return err
@@ -99,15 +117,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
if err != nil { if err != nil {
return err return err
} }
streamMsg, err := mgo.NewStreamMsgMongo(mgocli.GetDB())
if err != nil {
return err
}
seqUserCache := redis.NewSeqUserCacheRedis(rdb, seqUser) seqUserCache := redis.NewSeqUserCacheRedis(rdb, seqUser)
msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqUserCache, seqConversationCache, &config.KafkaConfig)
if err != nil {
return err
}
userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User) userConn, err := client.GetConn(ctx, config.Discovery.RpcService.User)
if err != nil { if err != nil {
return err return err
@@ -125,9 +135,9 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
return err return err
} }
conversationClient := rpcli.NewConversationClient(conversationConn) conversationClient := rpcli.NewConversationClient(conversationConn)
msgDatabase := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqUserCache, seqConversationCache, redisProducer)
s := &msgServer{ s := &msgServer{
MsgDatabase: msgDatabase, MsgDatabase: msgDatabase,
StreamMsgDatabase: controller.NewStreamMsgDatabase(streamMsg),
RegisterCenter: client, RegisterCenter: client,
UserLocalCache: rpccache.NewUserLocalCache(rpcli.NewUserClient(userConn), &config.LocalCacheConfig, rdb), UserLocalCache: rpccache.NewUserLocalCache(rpcli.NewUserClient(userConn), &config.LocalCacheConfig, rdb),
GroupLocalCache: rpccache.NewGroupLocalCache(rpcli.NewGroupClient(groupConn), &config.LocalCacheConfig, rdb), GroupLocalCache: rpccache.NewGroupLocalCache(rpcli.NewGroupClient(groupConn), &config.LocalCacheConfig, rdb),
@@ -136,10 +146,11 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
config: config, config: config,
webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL), webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL),
conversationClient: conversationClient, conversationClient: conversationClient,
adminUserIDs: config.Share.IMAdminUser.UserIDs,
} }
s.notificationSender = rpcclient.NewNotificationSender(&config.NotificationConfig, rpcclient.WithLocalSendMsg(s.SendMsg)) s.notificationSender = notification.NewNotificationSender(&config.NotificationConfig, notification.WithLocalSendMsg(s.SendMsg))
s.msgNotificationSender = NewMsgNotificationSender(config, rpcclient.WithLocalSendMsg(s.SendMsg)) s.msgNotificationSender = NewMsgNotificationSender(config, notification.WithLocalSendMsg(s.SendMsg))
msg.RegisterMsgServer(server, s) msg.RegisterMsgServer(server, s)
+9 -1
View File
@@ -16,15 +16,20 @@ package msg
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
) )
func (m *msgServer) GetActiveUser(ctx context.Context, req *msg.GetActiveUserReq) (*msg.GetActiveUserResp, error) { func (m *msgServer) GetActiveUser(ctx context.Context, req *msg.GetActiveUserReq) (*msg.GetActiveUserResp, error) {
if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err
}
msgCount, userCount, users, dateCount, err := m.MsgDatabase.RangeUserSendCount(ctx, time.UnixMilli(req.Start), time.UnixMilli(req.End), req.Group, req.Ase, req.Pagination.PageNumber, req.Pagination.ShowNumber) msgCount, userCount, users, dateCount, err := m.MsgDatabase.RangeUserSendCount(ctx, time.UnixMilli(req.Start), time.UnixMilli(req.End), req.Group, req.Ase, req.Pagination.PageNumber, req.Pagination.ShowNumber)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -60,6 +65,9 @@ func (m *msgServer) GetActiveUser(ctx context.Context, req *msg.GetActiveUserReq
} }
func (m *msgServer) GetActiveGroup(ctx context.Context, req *msg.GetActiveGroupReq) (*msg.GetActiveGroupResp, error) { func (m *msgServer) GetActiveGroup(ctx context.Context, req *msg.GetActiveGroupReq) (*msg.GetActiveGroupResp, error) {
if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err
}
msgCount, groupCount, groups, dateCount, err := m.MsgDatabase.RangeGroupSendCount(ctx, time.UnixMilli(req.Start), time.UnixMilli(req.End), req.Ase, req.Pagination.PageNumber, req.Pagination.ShowNumber) msgCount, groupCount, groups, dateCount, err := m.MsgDatabase.RangeGroupSendCount(ctx, time.UnixMilli(req.Start), time.UnixMilli(req.End), req.Ase, req.Pagination.PageNumber, req.Pagination.ShowNumber)
if err != nil { if err != nil {
return nil, err return nil, err
-115
View File
@@ -1,115 +0,0 @@
package msg
import (
"context"
"fmt"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msg"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/errs"
)
const StreamDeadlineTime = time.Second * 60 * 10
func (m *msgServer) handlerStreamMsg(ctx context.Context, msgData *sdkws.MsgData) error {
now := time.Now()
val := &model.StreamMsg{
ClientMsgID: msgData.ClientMsgID,
ConversationID: msgprocessor.GetConversationIDByMsg(msgData),
UserID: msgData.SendID,
CreateTime: now,
DeadlineTime: now.Add(StreamDeadlineTime),
}
return m.StreamMsgDatabase.CreateStreamMsg(ctx, val)
}
func (m *msgServer) getStreamMsg(ctx context.Context, clientMsgID string) (*model.StreamMsg, error) {
res, err := m.StreamMsgDatabase.GetStreamMsg(ctx, clientMsgID)
if err != nil {
return nil, err
}
now := time.Now()
if !res.End && res.DeadlineTime.Before(now) {
res.End = true
res.DeadlineTime = now
_ = m.StreamMsgDatabase.AppendStreamMsg(ctx, res.ClientMsgID, 0, nil, true, now)
}
return res, nil
}
func (m *msgServer) AppendStreamMsg(ctx context.Context, req *msg.AppendStreamMsgReq) (*msg.AppendStreamMsgResp, error) {
res, err := m.getStreamMsg(ctx, req.ClientMsgID)
if err != nil {
return nil, err
}
if res.End {
return nil, errs.ErrNoPermission.WrapMsg("stream msg is end")
}
if len(res.Packets) < int(req.StartIndex) {
return nil, errs.ErrNoPermission.WrapMsg("start index is invalid")
}
if val := len(res.Packets) - int(req.StartIndex); val > 0 {
exist := res.Packets[int(req.StartIndex):]
for i, s := range exist {
if len(req.Packets) == 0 {
break
}
if s != req.Packets[i] {
return nil, errs.ErrNoPermission.WrapMsg(fmt.Sprintf("packet %d has been written and is inconsistent", i))
}
req.StartIndex++
req.Packets = req.Packets[1:]
}
}
if len(req.Packets) == 0 && res.End == req.End {
return &msg.AppendStreamMsgResp{}, nil
}
deadlineTime := time.Now().Add(StreamDeadlineTime)
if err := m.StreamMsgDatabase.AppendStreamMsg(ctx, req.ClientMsgID, int(req.StartIndex), req.Packets, req.End, deadlineTime); err != nil {
return nil, err
}
conversation, err := m.conversationClient.GetConversation(ctx, res.ConversationID, res.UserID)
if err != nil {
return nil, err
}
tips := &sdkws.StreamMsgTips{
ConversationID: res.ConversationID,
ClientMsgID: res.ClientMsgID,
StartIndex: req.StartIndex,
Packets: req.Packets,
End: req.End,
}
var (
recvID string
sessionType int32
)
if conversation.GroupID == "" {
sessionType = constant.SingleChatType
recvID = conversation.UserID
} else {
sessionType = constant.ReadGroupChatType
recvID = conversation.GroupID
}
m.msgNotificationSender.StreamMsgNotification(ctx, res.UserID, recvID, sessionType, tips)
return &msg.AppendStreamMsgResp{}, nil
}
func (m *msgServer) GetStreamMsg(ctx context.Context, req *msg.GetStreamMsgReq) (*msg.GetStreamMsgResp, error) {
res, err := m.getStreamMsg(ctx, req.ClientMsgID)
if err != nil {
return nil, err
}
return &msg.GetStreamMsgResp{
ClientMsgID: res.ClientMsgID,
ConversationID: res.ConversationID,
UserID: res.UserID,
Packets: res.Packets,
End: res.End,
CreateTime: res.CreateTime.UnixMilli(),
DeadlineTime: res.DeadlineTime.UnixMilli(),
}, nil
}
+4 -1
View File
@@ -29,6 +29,9 @@ import (
) )
func (m *msgServer) PullMessageBySeqs(ctx context.Context, req *sdkws.PullMessageBySeqsReq) (*sdkws.PullMessageBySeqsResp, error) { func (m *msgServer) PullMessageBySeqs(ctx context.Context, req *sdkws.PullMessageBySeqsReq) (*sdkws.PullMessageBySeqsResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
resp := &sdkws.PullMessageBySeqsResp{} resp := &sdkws.PullMessageBySeqsResp{}
resp.Msgs = make(map[string]*sdkws.PullMsgs) resp.Msgs = make(map[string]*sdkws.PullMsgs)
resp.NotificationMsgs = make(map[string]*sdkws.PullMsgs) resp.NotificationMsgs = make(map[string]*sdkws.PullMsgs)
@@ -118,7 +121,7 @@ func (m *msgServer) GetSeqMessage(ctx context.Context, req *msg.GetSeqMessageReq
} }
func (m *msgServer) GetMaxSeq(ctx context.Context, req *sdkws.GetMaxSeqReq) (*sdkws.GetMaxSeqResp, error) { func (m *msgServer) GetMaxSeq(ctx context.Context, req *sdkws.GetMaxSeqReq) (*sdkws.GetMaxSeqResp, error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, m.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
conversationIDs, err := m.ConversationLocalCache.GetConversationIDs(ctx, req.UserID) conversationIDs, err := m.ConversationLocalCache.GetConversationIDs(ctx, req.UserID)
+17 -26
View File
@@ -16,13 +16,15 @@ package msg
import ( import (
"context" "context"
"math/rand"
"strconv"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/encrypt" "github.com/openimsdk/tools/utils/encrypt"
"github.com/openimsdk/tools/utils/timeutil" "github.com/openimsdk/tools/utils/timeutil"
"math/rand"
"strconv"
"time"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
@@ -52,7 +54,7 @@ type MessageRevoked struct {
func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgReq) error { func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgReq) error {
switch data.MsgData.SessionType { switch data.MsgData.SessionType {
case constant.SingleChatType: case constant.SingleChatType:
if datautil.Contain(data.MsgData.SendID, m.config.Share.IMAdminUserID...) { if datautil.Contain(data.MsgData.SendID, m.adminUserIDs...) {
return nil return nil
} }
if data.MsgData.ContentType <= constant.NotificationEnd && if data.MsgData.ContentType <= constant.NotificationEnd &&
@@ -62,6 +64,13 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe
if err := m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, data); err != nil { if err := m.webhookBeforeSendSingleMsg(ctx, &m.config.WebhooksConfig.BeforeSendSingleMsg, data); err != nil {
return err return err
} }
u, err := m.UserLocalCache.GetUserInfo(ctx, data.MsgData.SendID)
if err != nil {
return err
}
if authverify.CheckSystemAccount(ctx, u.AppMangerLevel) {
return nil
}
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
@@ -93,7 +102,7 @@ func (m *msgServer) messageVerification(ctx context.Context, data *msg.SendMsgRe
return nil return nil
} }
if datautil.Contain(data.MsgData.SendID, m.config.Share.IMAdminUserID...) { if datautil.Contain(data.MsgData.SendID, m.adminUserIDs...) {
return nil return nil
} }
if data.MsgData.ContentType <= constant.NotificationEnd && if data.MsgData.ContentType <= constant.NotificationEnd &&
@@ -137,27 +146,9 @@ func (m *msgServer) encapsulateMsgData(msg *sdkws.MsgData) {
msg.SendTime = timeutil.GetCurrentTimestampByMill() msg.SendTime = timeutil.GetCurrentTimestampByMill()
} }
switch msg.ContentType { switch msg.ContentType {
case constant.Text: case constant.Text, constant.Picture, constant.Voice, constant.Video,
fallthrough constant.File, constant.AtText, constant.Merger, constant.Card,
case constant.Picture: constant.Location, constant.Custom, constant.Quote, constant.AdvancedText, constant.MarkdownText:
fallthrough
case constant.Voice:
fallthrough
case constant.Video:
fallthrough
case constant.File:
fallthrough
case constant.AtText:
fallthrough
case constant.Merger:
fallthrough
case constant.Card:
fallthrough
case constant.Location:
fallthrough
case constant.Custom:
fallthrough
case constant.Quote:
case constant.Revoke: case constant.Revoke:
datautil.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false) datautil.SetSwitchFromOptions(msg.Options, constant.IsUnreadCount, false)
datautil.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false) datautil.SetSwitchFromOptions(msg.Options, constant.IsOfflinePush, false)
+7 -5
View File
@@ -29,10 +29,9 @@ import (
) )
func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *relation.GetPaginationBlacksReq) (resp *relation.GetPaginationBlacksResp, err error) { func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *relation.GetPaginationBlacksReq) (resp *relation.GetPaginationBlacksResp, err error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
total, blacks, err := s.blackDatabase.FindOwnerBlacks(ctx, req.UserID, req.Pagination) total, blacks, err := s.blackDatabase.FindOwnerBlacks(ctx, req.UserID, req.Pagination)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -47,6 +46,9 @@ func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *relation.Ge
} }
func (s *friendServer) IsBlack(ctx context.Context, req *relation.IsBlackReq) (*relation.IsBlackResp, error) { func (s *friendServer) IsBlack(ctx context.Context, req *relation.IsBlackReq) (*relation.IsBlackResp, error) {
if err := authverify.CheckAccessIn(ctx, req.UserID1, req.UserID2); err != nil {
return nil, err
}
in1, in2, err := s.blackDatabase.CheckIn(ctx, req.UserID1, req.UserID2) in1, in2, err := s.blackDatabase.CheckIn(ctx, req.UserID1, req.UserID2)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -58,7 +60,7 @@ func (s *friendServer) IsBlack(ctx context.Context, req *relation.IsBlackReq) (*
} }
func (s *friendServer) RemoveBlack(ctx context.Context, req *relation.RemoveBlackReq) (*relation.RemoveBlackResp, error) { func (s *friendServer) RemoveBlack(ctx context.Context, req *relation.RemoveBlackReq) (*relation.RemoveBlackResp, error) {
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err return nil, err
} }
@@ -73,7 +75,7 @@ func (s *friendServer) RemoveBlack(ctx context.Context, req *relation.RemoveBlac
} }
func (s *friendServer) AddBlack(ctx context.Context, req *relation.AddBlackReq) (*relation.AddBlackResp, error) { func (s *friendServer) AddBlack(ctx context.Context, req *relation.AddBlackReq) (*relation.AddBlackResp, error) {
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err return nil, err
} }
@@ -99,7 +101,7 @@ func (s *friendServer) AddBlack(ctx context.Context, req *relation.AddBlackReq)
} }
func (s *friendServer) GetSpecifiedBlacks(ctx context.Context, req *relation.GetSpecifiedBlacksReq) (*relation.GetSpecifiedBlacksResp, error) { func (s *friendServer) GetSpecifiedBlacks(ctx context.Context, req *relation.GetSpecifiedBlacksReq) (*relation.GetSpecifiedBlacksResp, error) {
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err return nil, err
} }
+90 -44
View File
@@ -16,26 +16,26 @@ package relation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/notification/common_user"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/tools/mq/memamq" "github.com/openimsdk/tools/mq/memamq"
"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/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
"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/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/localcache" "github.com/openimsdk/open-im-server/v3/pkg/localcache"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/relation" "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
@@ -47,7 +47,7 @@ type friendServer struct {
db controller.FriendDatabase db controller.FriendDatabase
blackDatabase controller.BlackDatabase blackDatabase controller.BlackDatabase
notificationSender *FriendNotificationSender notificationSender *FriendNotificationSender
RegisterCenter discovery.SvcDiscoveryRegistry RegisterCenter discovery.Conn
config *Config config *Config
webhookClient *webhook.Client webhookClient *webhook.Client
queue *memamq.MemoryQueue queue *memamq.MemoryQueue
@@ -66,12 +66,13 @@ type Config struct {
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
@@ -100,26 +101,27 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
return err return err
} }
userClient := rpcli.NewUserClient(userConn) userClient := rpcli.NewUserClient(userConn)
database := controller.NewFriendDatabase(
friendMongoDB,
friendRequestMongoDB,
redis.NewFriendCacheRedis(rdb, &config.LocalCacheConfig, friendMongoDB),
mgocli.GetTx(),
)
// Initialize notification sender // Initialize notification sender
notificationSender := NewFriendNotificationSender( notificationSender := NewFriendNotificationSender(
&config.NotificationConfig, &config.NotificationConfig,
rpcli.NewMsgClient(msgConn), rpcli.NewMsgClient(msgConn),
WithRpcFunc(userClient.GetUsersInfo), WithRpcFunc(userClient.GetUsersInfo),
WithFriendDB(database),
) )
localcache.InitLocalCache(&config.LocalCacheConfig) localcache.InitLocalCache(&config.LocalCacheConfig)
// Register Friend server with refactored MongoDB and Redis integrations // Register Friend server with refactored MongoDB and Redis integrations
relation.RegisterFriendServer(server, &friendServer{ relation.RegisterFriendServer(server, &friendServer{
db: controller.NewFriendDatabase( db: database,
friendMongoDB,
friendRequestMongoDB,
redis.NewFriendCacheRedis(rdb, &config.LocalCacheConfig, friendMongoDB, redis.GetRocksCacheOptions()),
mgocli.GetTx(),
),
blackDatabase: controller.NewBlackDatabase( blackDatabase: controller.NewBlackDatabase(
blackMongoDB, blackMongoDB,
redis.NewBlackCacheRedis(rdb, &config.LocalCacheConfig, blackMongoDB, redis.GetRocksCacheOptions()), redis.NewBlackCacheRedis(rdb, &config.LocalCacheConfig, blackMongoDB),
), ),
notificationSender: notificationSender, notificationSender: notificationSender,
RegisterCenter: client, RegisterCenter: client,
@@ -134,7 +136,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
// ok. // ok.
func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *relation.ApplyToAddFriendReq) (resp *relation.ApplyToAddFriendResp, err error) { func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *relation.ApplyToAddFriendReq) (resp *relation.ApplyToAddFriendResp, err error) {
resp = &relation.ApplyToAddFriendResp{} resp = &relation.ApplyToAddFriendResp{}
if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.FromUserID); err != nil {
return nil, err return nil, err
} }
if req.ToUserID == req.FromUserID { if req.ToUserID == req.FromUserID {
@@ -164,7 +166,7 @@ func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *relation.Apply
// ok. // ok.
func (s *friendServer) ImportFriends(ctx context.Context, req *relation.ImportFriendReq) (resp *relation.ImportFriendResp, err error) { func (s *friendServer) ImportFriends(ctx context.Context, req *relation.ImportFriendReq) (resp *relation.ImportFriendResp, err error) {
if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err return nil, err
} }
@@ -190,7 +192,7 @@ func (s *friendServer) ImportFriends(ctx context.Context, req *relation.ImportFr
FromUserID: req.OwnerUserID, FromUserID: req.OwnerUserID,
ToUserID: userID, ToUserID: userID,
HandleResult: constant.FriendResponseAgree, HandleResult: constant.FriendResponseAgree,
}) }, false)
} }
s.webhookAfterImportFriends(ctx, &s.config.WebhooksConfig.AfterImportFriends, req) s.webhookAfterImportFriends(ctx, &s.config.WebhooksConfig.AfterImportFriends, req)
@@ -200,7 +202,7 @@ func (s *friendServer) ImportFriends(ctx context.Context, req *relation.ImportFr
// ok. // ok.
func (s *friendServer) RespondFriendApply(ctx context.Context, req *relation.RespondFriendApplyReq) (resp *relation.RespondFriendApplyResp, err error) { func (s *friendServer) RespondFriendApply(ctx context.Context, req *relation.RespondFriendApplyReq) (resp *relation.RespondFriendApplyResp, err error) {
resp = &relation.RespondFriendApplyResp{} resp = &relation.RespondFriendApplyResp{}
if err := authverify.CheckAccessV3(ctx, req.ToUserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.ToUserID); err != nil {
return nil, err return nil, err
} }
@@ -219,7 +221,7 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *relation.Res
return nil, err return nil, err
} }
s.webhookAfterAddFriendAgree(ctx, &s.config.WebhooksConfig.AfterAddFriendAgree, req) s.webhookAfterAddFriendAgree(ctx, &s.config.WebhooksConfig.AfterAddFriendAgree, req)
s.notificationSender.FriendApplicationAgreedNotification(ctx, req) s.notificationSender.FriendApplicationAgreedNotification(ctx, req, true)
return resp, nil return resp, nil
} }
if req.HandleResult == constant.FriendResponseRefuse { if req.HandleResult == constant.FriendResponseRefuse {
@@ -235,7 +237,7 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *relation.Res
// ok. // ok.
func (s *friendServer) DeleteFriend(ctx context.Context, req *relation.DeleteFriendReq) (resp *relation.DeleteFriendResp, err error) { func (s *friendServer) DeleteFriend(ctx context.Context, req *relation.DeleteFriendReq) (resp *relation.DeleteFriendResp, err error) {
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err return nil, err
} }
@@ -260,7 +262,7 @@ func (s *friendServer) SetFriendRemark(ctx context.Context, req *relation.SetFri
return nil, err return nil, err
} }
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err return nil, err
} }
@@ -280,6 +282,9 @@ func (s *friendServer) SetFriendRemark(ctx context.Context, req *relation.SetFri
} }
func (s *friendServer) GetFriendInfo(ctx context.Context, req *relation.GetFriendInfoReq) (*relation.GetFriendInfoResp, error) { func (s *friendServer) GetFriendInfo(ctx context.Context, req *relation.GetFriendInfoReq) (*relation.GetFriendInfoResp, error) {
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err
}
friends, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs) friends, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -288,6 +293,9 @@ func (s *friendServer) GetFriendInfo(ctx context.Context, req *relation.GetFrien
} }
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) {
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err
}
resp = &relation.GetDesignatedFriendsResp{} resp = &relation.GetDesignatedFriendsResp{}
if datautil.Duplicate(req.FriendUserIDs) { if datautil.Duplicate(req.FriendUserIDs) {
return nil, errs.ErrArgs.WrapMsg("friend userID repeated") return nil, errs.ErrArgs.WrapMsg("friend userID repeated")
@@ -313,15 +321,16 @@ func (s *friendServer) getFriend(ctx context.Context, ownerUserID string, friend
} }
// Get the list of friend requests sent out proactively. // Get the list of friend requests sent out proactively.
func (s *friendServer) GetDesignatedFriendsApply(ctx context.Context, func (s *friendServer) GetDesignatedFriendsApply(ctx context.Context, req *relation.GetDesignatedFriendsApplyReq) (resp *relation.GetDesignatedFriendsApplyResp, err error) {
req *relation.GetDesignatedFriendsApplyReq, if err := authverify.CheckAccessIn(ctx, req.FromUserID, req.ToUserID); err != nil {
) (resp *relation.GetDesignatedFriendsApplyResp, err error) { return nil, err
}
friendRequests, err := s.db.FindBothFriendRequests(ctx, req.FromUserID, req.ToUserID) friendRequests, err := s.db.FindBothFriendRequests(ctx, req.FromUserID, req.ToUserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp = &relation.GetDesignatedFriendsApplyResp{} resp = &relation.GetDesignatedFriendsApplyResp{}
resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.userClient.GetUsersInfoMap) resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.getCommonUserMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -330,17 +339,20 @@ func (s *friendServer) GetDesignatedFriendsApply(ctx context.Context,
// Get received friend requests (i.e., those initiated by others). // Get received friend requests (i.e., those initiated by others).
func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *relation.GetPaginationFriendsApplyToReq) (resp *relation.GetPaginationFriendsApplyToResp, err error) { func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *relation.GetPaginationFriendsApplyToReq) (resp *relation.GetPaginationFriendsApplyToResp, err error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
total, friendRequests, err := s.db.PageFriendRequestToMe(ctx, req.UserID, req.Pagination) handleResults := datautil.Slice(req.HandleResults, func(e int32) int {
return int(e)
})
total, friendRequests, err := s.db.PageFriendRequestToMe(ctx, req.UserID, handleResults, req.Pagination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp = &relation.GetPaginationFriendsApplyToResp{} resp = &relation.GetPaginationFriendsApplyToResp{}
resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.userClient.GetUsersInfoMap) resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.getCommonUserMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -351,18 +363,20 @@ func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *rel
} }
func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *relation.GetPaginationFriendsApplyFromReq) (resp *relation.GetPaginationFriendsApplyFromResp, err error) { func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *relation.GetPaginationFriendsApplyFromReq) (resp *relation.GetPaginationFriendsApplyFromResp, err error) {
resp = &relation.GetPaginationFriendsApplyFromResp{} if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil {
return nil, err return nil, err
} }
total, friendRequests, err := s.db.PageFriendRequestFromMe(ctx, req.UserID, req.Pagination) handleResults := datautil.Slice(req.HandleResults, func(e int32) int {
return int(e)
})
total, friendRequests, err := s.db.PageFriendRequestFromMe(ctx, req.UserID, handleResults, req.Pagination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.userClient.GetUsersInfoMap) resp = &relation.GetPaginationFriendsApplyFromResp{}
resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.getCommonUserMap)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@@ -374,6 +388,9 @@ func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *r
// ok. // ok.
func (s *friendServer) IsFriend(ctx context.Context, req *relation.IsFriendReq) (resp *relation.IsFriendResp, err error) { func (s *friendServer) IsFriend(ctx context.Context, req *relation.IsFriendReq) (resp *relation.IsFriendResp, err error) {
if err := authverify.CheckAccessIn(ctx, req.UserID1, req.UserID2); err != nil {
return nil, err
}
resp = &relation.IsFriendResp{} resp = &relation.IsFriendResp{}
resp.InUser1Friends, resp.InUser2Friends, err = s.db.CheckIn(ctx, req.UserID1, req.UserID2) resp.InUser1Friends, resp.InUser2Friends, err = s.db.CheckIn(ctx, req.UserID1, req.UserID2)
if err != nil { if err != nil {
@@ -383,7 +400,7 @@ func (s *friendServer) IsFriend(ctx context.Context, req *relation.IsFriendReq)
} }
func (s *friendServer) GetPaginationFriends(ctx context.Context, req *relation.GetPaginationFriendsReq) (resp *relation.GetPaginationFriendsResp, err error) { func (s *friendServer) GetPaginationFriends(ctx context.Context, req *relation.GetPaginationFriendsReq) (resp *relation.GetPaginationFriendsResp, err error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
@@ -404,7 +421,7 @@ func (s *friendServer) GetPaginationFriends(ctx context.Context, req *relation.G
} }
func (s *friendServer) GetFriendIDs(ctx context.Context, req *relation.GetFriendIDsReq) (resp *relation.GetFriendIDsResp, err error) { func (s *friendServer) GetFriendIDs(ctx context.Context, req *relation.GetFriendIDsReq) (resp *relation.GetFriendIDsResp, err error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
@@ -426,6 +443,9 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *relatio
return nil, errs.ErrArgs.WrapMsg("userIDList repeated") return nil, errs.ErrArgs.WrapMsg("userIDList repeated")
} }
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err
}
userMap, err := s.userClient.GetUsersInfoMap(ctx, req.UserIDList) userMap, err := s.userClient.GetUsersInfoMap(ctx, req.UserIDList)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -494,10 +514,7 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *relatio
return resp, nil return resp, nil
} }
func (s *friendServer) UpdateFriends( func (s *friendServer) UpdateFriends(ctx context.Context, req *relation.UpdateFriendsReq) (*relation.UpdateFriendsResp, error) {
ctx context.Context,
req *relation.UpdateFriendsReq,
) (*relation.UpdateFriendsResp, error) {
if len(req.FriendUserIDs) == 0 { if len(req.FriendUserIDs) == 0 {
return nil, errs.ErrArgs.WrapMsg("friendIDList is empty") return nil, errs.ErrArgs.WrapMsg("friendIDList is empty")
} }
@@ -505,6 +522,10 @@ func (s *friendServer) UpdateFriends(
return nil, errs.ErrArgs.WrapMsg("friendIDList repeated") return nil, errs.ErrArgs.WrapMsg("friendIDList repeated")
} }
if err := authverify.CheckAccess(ctx, req.OwnerUserID); err != nil {
return nil, err
}
_, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs) _, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -530,3 +551,28 @@ func (s *friendServer) UpdateFriends(
s.notificationSender.FriendsInfoUpdateNotification(ctx, req.OwnerUserID, req.FriendUserIDs) s.notificationSender.FriendsInfoUpdateNotification(ctx, req.OwnerUserID, req.FriendUserIDs)
return resp, nil return resp, nil
} }
func (s *friendServer) GetSelfUnhandledApplyCount(ctx context.Context, req *relation.GetSelfUnhandledApplyCountReq) (*relation.GetSelfUnhandledApplyCountResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
count, err := s.db.GetUnhandledCount(ctx, req.UserID, req.Time)
if err != nil {
return nil, err
}
return &relation.GetSelfUnhandledApplyCountResp{
Count: count,
}, nil
}
func (s *friendServer) getCommonUserMap(ctx context.Context, userIDs []string) (map[string]common_user.CommonUser, error) {
users, err := s.userClient.GetUsersInfo(ctx, userIDs)
if err != nil {
return nil, err
}
return datautil.SliceToMapAny(users, func(e *sdkws.UserInfo) (string, common_user.CommonUser) {
return e.UserID, e
}), nil
}
+107 -60
View File
@@ -16,8 +16,12 @@ package relation
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli" "github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/versionctx" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/versionctx"
@@ -36,7 +40,7 @@ import (
) )
type FriendNotificationSender struct { type FriendNotificationSender struct {
*rpcclient.NotificationSender *notification.NotificationSender
// Target not found err // Target not found err
getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error) getUsersInfo func(ctx context.Context, userIDs []string) ([]common_user.CommonUser, error)
// db controller // db controller
@@ -51,9 +55,7 @@ func WithFriendDB(db controller.FriendDatabase) friendNotificationSenderOptions
} }
} }
func WithDBFunc( func WithDBFunc(fn func(ctx context.Context, userIDs []string) (users []*relationtb.User, err error)) friendNotificationSenderOptions {
fn func(ctx context.Context, userIDs []string) (users []*relationtb.User, err error),
) friendNotificationSenderOptions {
return func(s *FriendNotificationSender) { return func(s *FriendNotificationSender) {
f := func(ctx context.Context, userIDs []string) (result []common_user.CommonUser, err error) { f := func(ctx context.Context, userIDs []string) (result []common_user.CommonUser, err error) {
users, err := fn(ctx, userIDs) users, err := fn(ctx, userIDs)
@@ -69,9 +71,7 @@ func WithDBFunc(
} }
} }
func WithRpcFunc( func WithRpcFunc(fn func(ctx context.Context, userIDs []string) ([]*sdkws.UserInfo, error)) friendNotificationSenderOptions {
fn func(ctx context.Context, userIDs []string) ([]*sdkws.UserInfo, error),
) friendNotificationSenderOptions {
return func(s *FriendNotificationSender) { return func(s *FriendNotificationSender) {
f := func(ctx context.Context, userIDs []string) (result []common_user.CommonUser, err error) { f := func(ctx context.Context, userIDs []string) (result []common_user.CommonUser, err error) {
users, err := fn(ctx, userIDs) users, err := fn(ctx, userIDs)
@@ -89,7 +89,7 @@ func WithRpcFunc(
func NewFriendNotificationSender(conf *config.Notification, msgClient *rpcli.MsgClient, opts ...friendNotificationSenderOptions) *FriendNotificationSender { func NewFriendNotificationSender(conf *config.Notification, msgClient *rpcli.MsgClient, opts ...friendNotificationSenderOptions) *FriendNotificationSender {
f := &FriendNotificationSender{ f := &FriendNotificationSender{
NotificationSender: rpcclient.NewNotificationSender(conf, rpcclient.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) { NotificationSender: notification.NewNotificationSender(conf, notification.WithRpcClient(func(ctx context.Context, req *msg.SendMsgReq) (*msg.SendMsgResp, error) {
return msgClient.SendMsg(ctx, req) return msgClient.SendMsg(ctx, req)
})), })),
} }
@@ -99,10 +99,7 @@ func NewFriendNotificationSender(conf *config.Notification, msgClient *rpcli.Msg
return f return f
} }
func (f *FriendNotificationSender) getUsersInfoMap( func (f *FriendNotificationSender) getUsersInfoMap(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error) {
ctx context.Context,
userIDs []string,
) (map[string]*sdkws.UserInfo, error) {
users, err := f.getUsersInfo(ctx, userIDs) users, err := f.getUsersInfo(ctx, userIDs)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -115,10 +112,7 @@ func (f *FriendNotificationSender) getUsersInfoMap(
} }
//nolint:unused //nolint:unused
func (f *FriendNotificationSender) getFromToUserNickname( func (f *FriendNotificationSender) getFromToUserNickname(ctx context.Context, fromUserID, toUserID string) (string, string, error) {
ctx context.Context,
fromUserID, toUserID string,
) (string, string, error) {
users, err := f.getUsersInfoMap(ctx, []string{fromUserID, toUserID}) users, err := f.getUsersInfoMap(ctx, []string{fromUserID, toUserID})
if err != nil { if err != nil {
return "", "", nil return "", "", nil
@@ -131,60 +125,113 @@ func (f *FriendNotificationSender) UserInfoUpdatedNotification(ctx context.Conte
f.Notification(ctx, mcontext.GetOpUserID(ctx), changedUserID, constant.UserInfoUpdatedNotification, &tips) f.Notification(ctx, mcontext.GetOpUserID(ctx), changedUserID, constant.UserInfoUpdatedNotification, &tips)
} }
func (f *FriendNotificationSender) getCommonUserMap(ctx context.Context, userIDs []string) (map[string]common_user.CommonUser, error) {
users, err := f.getUsersInfo(ctx, userIDs)
if err != nil {
return nil, err
}
return datautil.SliceToMap(users, func(e common_user.CommonUser) string {
return e.GetUserID()
}), nil
}
func (f *FriendNotificationSender) getFriendRequests(ctx context.Context, fromUserID, toUserID string) (*sdkws.FriendRequest, error) {
if f.db == nil {
return nil, errs.ErrInternalServer.WithDetail("db is nil")
}
friendRequests, err := f.db.FindBothFriendRequests(ctx, fromUserID, toUserID)
if err != nil {
return nil, err
}
requests, err := convert.FriendRequestDB2Pb(ctx, friendRequests, f.getCommonUserMap)
if err != nil {
return nil, err
}
for _, request := range requests {
if request.FromUserID == fromUserID && request.ToUserID == toUserID {
return request, nil
}
}
return nil, errs.ErrRecordNotFound.WrapMsg("friend request not found", "fromUserID", fromUserID, "toUserID", toUserID)
}
func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.Context, req *relation.ApplyToAddFriendReq) { func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.Context, req *relation.ApplyToAddFriendReq) {
tips := sdkws.FriendApplicationTips{FromToUserID: &sdkws.FromToUserID{ request, err := f.getFriendRequests(ctx, req.FromUserID, req.ToUserID)
FromUserID: req.FromUserID, if err != nil {
ToUserID: req.ToUserID, log.ZError(ctx, "FriendApplicationAddNotification get friend request", err, "fromUserID", req.FromUserID, "toUserID", req.ToUserID)
}} return
}
tips := sdkws.FriendApplicationTips{
FromToUserID: &sdkws.FromToUserID{
FromUserID: req.FromUserID,
ToUserID: req.ToUserID,
},
Request: request,
}
f.Notification(ctx, req.FromUserID, req.ToUserID, constant.FriendApplicationNotification, &tips) f.Notification(ctx, req.FromUserID, req.ToUserID, constant.FriendApplicationNotification, &tips)
} }
func (f *FriendNotificationSender) FriendApplicationAgreedNotification( func (f *FriendNotificationSender) FriendApplicationAgreedNotification(ctx context.Context, req *relation.RespondFriendApplyReq, checkReq bool) {
ctx context.Context, var (
req *relation.RespondFriendApplyReq, request *sdkws.FriendRequest
) { err error
tips := sdkws.FriendApplicationApprovedTips{FromToUserID: &sdkws.FromToUserID{ )
FromUserID: req.FromUserID, if checkReq {
ToUserID: req.ToUserID, request, err = f.getFriendRequests(ctx, req.FromUserID, req.ToUserID)
}, HandleMsg: req.HandleMsg} if err != nil {
log.ZError(ctx, "FriendApplicationAgreedNotification get friend request", err, "fromUserID", req.FromUserID, "toUserID", req.ToUserID)
return
}
}
tips := sdkws.FriendApplicationApprovedTips{
FromToUserID: &sdkws.FromToUserID{
FromUserID: req.FromUserID,
ToUserID: req.ToUserID,
},
HandleMsg: req.HandleMsg,
Request: request,
}
f.Notification(ctx, req.ToUserID, req.FromUserID, constant.FriendApplicationApprovedNotification, &tips) f.Notification(ctx, req.ToUserID, req.FromUserID, constant.FriendApplicationApprovedNotification, &tips)
} }
func (f *FriendNotificationSender) FriendApplicationRefusedNotification( func (f *FriendNotificationSender) FriendApplicationRefusedNotification(ctx context.Context, req *relation.RespondFriendApplyReq) {
ctx context.Context, request, err := f.getFriendRequests(ctx, req.FromUserID, req.ToUserID)
req *relation.RespondFriendApplyReq, if err != nil {
) { log.ZError(ctx, "FriendApplicationRefusedNotification get friend request", err, "fromUserID", req.FromUserID, "toUserID", req.ToUserID)
tips := sdkws.FriendApplicationApprovedTips{FromToUserID: &sdkws.FromToUserID{ return
FromUserID: req.FromUserID, }
ToUserID: req.ToUserID, tips := sdkws.FriendApplicationRejectedTips{
}, HandleMsg: req.HandleMsg} FromToUserID: &sdkws.FromToUserID{
FromUserID: req.FromUserID,
ToUserID: req.ToUserID,
},
HandleMsg: req.HandleMsg,
Request: request,
}
f.Notification(ctx, req.ToUserID, req.FromUserID, constant.FriendApplicationRejectedNotification, &tips) f.Notification(ctx, req.ToUserID, req.FromUserID, constant.FriendApplicationRejectedNotification, &tips)
} }
func (f *FriendNotificationSender) FriendAddedNotification( //func (f *FriendNotificationSender) FriendAddedNotification(ctx context.Context, operationID, opUserID, fromUserID, toUserID string) error {
ctx context.Context, // tips := sdkws.FriendAddedTips{Friend: &sdkws.FriendInfo{}, OpUser: &sdkws.PublicUserInfo{}}
operationID, opUserID, fromUserID, toUserID string, // user, err := f.getUsersInfo(ctx, []string{opUserID})
) error { // if err != nil {
tips := sdkws.FriendAddedTips{Friend: &sdkws.FriendInfo{}, OpUser: &sdkws.PublicUserInfo{}} // return err
user, err := f.getUsersInfo(ctx, []string{opUserID}) // }
if err != nil { // tips.OpUser.UserID = user[0].GetUserID()
return err // tips.OpUser.Ex = user[0].GetEx()
} // tips.OpUser.Nickname = user[0].GetNickname()
tips.OpUser.UserID = user[0].GetUserID() // tips.OpUser.FaceURL = user[0].GetFaceURL()
tips.OpUser.Ex = user[0].GetEx() // friends, err := f.db.FindFriendsWithError(ctx, fromUserID, []string{toUserID})
tips.OpUser.Nickname = user[0].GetNickname() // if err != nil {
tips.OpUser.FaceURL = user[0].GetFaceURL() // return err
friends, err := f.db.FindFriendsWithError(ctx, fromUserID, []string{toUserID}) // }
if err != nil { // tips.Friend, err = convert.FriendDB2Pb(ctx, friends[0], f.getUsersInfoMap)
return err // if err != nil {
} // return err
tips.Friend, err = convert.FriendDB2Pb(ctx, friends[0], f.getUsersInfoMap) // }
if err != nil { // f.Notification(ctx, fromUserID, toUserID, constant.FriendAddedNotification, &tips)
return err // return nil
} //}
f.Notification(ctx, fromUserID, toUserID, constant.FriendAddedNotification, &tips)
return nil
}
func (f *FriendNotificationSender) FriendDeletedNotification(ctx context.Context, req *relation.DeleteFriendReq) { func (f *FriendNotificationSender) FriendDeletedNotification(ctx context.Context, req *relation.DeleteFriendReq) {
tips := sdkws.FriendDeletedTips{FromToUserID: &sdkws.FromToUserID{ tips := sdkws.FriendDeletedTips{FromToUserID: &sdkws.FromToUserID{
+6 -2
View File
@@ -2,10 +2,11 @@ package relation
import ( import (
"context" "context"
"slices"
"github.com/openimsdk/open-im-server/v3/pkg/util/hashutil" "github.com/openimsdk/open-im-server/v3/pkg/util/hashutil"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"slices"
"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion" "github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/authverify"
@@ -39,6 +40,9 @@ func (s *friendServer) NotificationUserInfoUpdate(ctx context.Context, req *rela
} }
func (s *friendServer) GetFullFriendUserIDs(ctx context.Context, req *relation.GetFullFriendUserIDsReq) (*relation.GetFullFriendUserIDsResp, error) { func (s *friendServer) GetFullFriendUserIDs(ctx context.Context, req *relation.GetFullFriendUserIDsReq) (*relation.GetFullFriendUserIDsResp, error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
vl, err := s.db.FindMaxFriendVersionCache(ctx, req.UserID) vl, err := s.db.FindMaxFriendVersionCache(ctx, req.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -60,7 +64,7 @@ func (s *friendServer) GetFullFriendUserIDs(ctx context.Context, req *relation.G
} }
func (s *friendServer) GetIncrementalFriends(ctx context.Context, req *relation.GetIncrementalFriendsReq) (*relation.GetIncrementalFriendsResp, error) { func (s *friendServer) GetIncrementalFriends(ctx context.Context, req *relation.GetIncrementalFriendsReq) (*relation.GetIncrementalFriendsResp, error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
var sortVersion uint64 var sortVersion uint64
+4 -3
View File
@@ -25,6 +25,7 @@ import (
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/third" "github.com/openimsdk/protocol/third"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
) )
@@ -45,7 +46,7 @@ func genLogID() string {
func (t *thirdServer) UploadLogs(ctx context.Context, req *third.UploadLogsReq) (*third.UploadLogsResp, error) { func (t *thirdServer) UploadLogs(ctx context.Context, req *third.UploadLogsReq) (*third.UploadLogsResp, error) {
var dbLogs []*relationtb.Log var dbLogs []*relationtb.Log
userID := ctx.Value(constant.OpUserID).(string) userID := mcontext.GetOpUserID(ctx)
platform := constant.PlatformID2Name[int(req.Platform)] platform := constant.PlatformID2Name[int(req.Platform)]
for _, fileURL := range req.FileURLs { for _, fileURL := range req.FileURLs {
log := relationtb.Log{ log := relationtb.Log{
@@ -82,7 +83,7 @@ func (t *thirdServer) UploadLogs(ctx context.Context, req *third.UploadLogsReq)
} }
func (t *thirdServer) DeleteLogs(ctx context.Context, req *third.DeleteLogsReq) (*third.DeleteLogsResp, error) { func (t *thirdServer) DeleteLogs(ctx context.Context, req *third.DeleteLogsReq) (*third.DeleteLogsResp, error) {
if err := authverify.CheckAdmin(ctx, t.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err return nil, err
} }
userID := "" userID := ""
@@ -123,7 +124,7 @@ func dbToPbLogInfos(logs []*relationtb.Log) []*third.LogInfo {
} }
func (t *thirdServer) SearchLogs(ctx context.Context, req *third.SearchLogsReq) (*third.SearchLogsResp, error) { func (t *thirdServer) SearchLogs(ctx context.Context, req *third.SearchLogsReq) (*third.SearchLogsResp, error) {
if err := authverify.CheckAdmin(ctx, t.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err return nil, err
} }
var ( var (
+9 -5
View File
@@ -19,11 +19,12 @@ import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"path" "path"
"strconv" "strconv"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
@@ -37,7 +38,10 @@ import (
) )
func (t *thirdServer) PartLimit(ctx context.Context, req *third.PartLimitReq) (*third.PartLimitResp, error) { func (t *thirdServer) PartLimit(ctx context.Context, req *third.PartLimitReq) (*third.PartLimitResp, error) {
limit := t.s3dataBase.PartLimit() limit, err := t.s3dataBase.PartLimit()
if err != nil {
return nil, err
}
return &third.PartLimitResp{ return &third.PartLimitResp{
MinPartSize: limit.MinPartSize, MinPartSize: limit.MinPartSize,
MaxPartSize: limit.MaxPartSize, MaxPartSize: limit.MaxPartSize,
@@ -58,7 +62,7 @@ func (t *thirdServer) InitiateMultipartUpload(ctx context.Context, req *third.In
return nil, err return nil, err
} }
expireTime := time.Now().Add(t.defaultExpire) expireTime := time.Now().Add(t.defaultExpire)
result, err := t.s3dataBase.InitiateMultipartUpload(ctx, req.Hash, req.Size, t.defaultExpire, int(req.MaxParts)) result, err := t.s3dataBase.InitiateMultipartUpload(ctx, req.Hash, req.Size, t.defaultExpire, int(req.MaxParts), req.ContentType)
if err != nil { if err != nil {
if haErr, ok := errs.Unwrap(err).(*cont.HashAlreadyExistsError); ok { if haErr, ok := errs.Unwrap(err).(*cont.HashAlreadyExistsError); ok {
obj := &model.Object{ obj := &model.Object{
@@ -194,7 +198,7 @@ func (t *thirdServer) InitiateFormData(ctx context.Context, req *third.InitiateF
var duration time.Duration var duration time.Duration
opUserID := mcontext.GetOpUserID(ctx) opUserID := mcontext.GetOpUserID(ctx)
var key string var key string
if t.IsManagerUserID(opUserID) { if authverify.CheckUserIsAdmin(ctx, opUserID) {
if req.Millisecond <= 0 { if req.Millisecond <= 0 {
duration = time.Minute * 10 duration = time.Minute * 10
} else { } else {
@@ -285,7 +289,7 @@ func (t *thirdServer) apiAddress(prefix, name string) string {
} }
func (t *thirdServer) DeleteOutdatedData(ctx context.Context, req *third.DeleteOutdatedDataReq) (*third.DeleteOutdatedDataResp, error) { func (t *thirdServer) DeleteOutdatedData(ctx context.Context, req *third.DeleteOutdatedDataReq) (*third.DeleteOutdatedDataResp, error) {
if err := authverify.CheckAdmin(ctx, t.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAdmin(ctx); err != nil {
return nil, err return nil, err
} }
engine := t.config.RpcConfig.Object.Enable engine := t.config.RpcConfig.Object.Enable
+41 -14
View File
@@ -17,9 +17,15 @@ package third
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/mcache"
"github.com/openimsdk/open-im-server/v3/pkg/dbbuild"
"github.com/openimsdk/open-im-server/v3/pkg/rpcli"
"github.com/openimsdk/tools/s3/disable"
"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/redis" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
@@ -29,8 +35,6 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/protocol/third" "github.com/openimsdk/protocol/third"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/s3" "github.com/openimsdk/tools/s3"
"github.com/openimsdk/tools/s3/cos" "github.com/openimsdk/tools/s3/cos"
@@ -60,15 +64,17 @@ type Config struct {
Discovery config.Discovery Discovery config.Discovery
} }
func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error {
mgocli, err := mongoutil.NewMongoDB(ctx, config.MongodbConfig.Build()) dbb := dbbuild.NewBuilder(&config.MongodbConfig, &config.RedisConfig)
mgocli, err := dbb.Mongo(ctx)
if err != nil { if err != nil {
return err return err
} }
rdb, err := redisutil.NewRedisClient(ctx, config.RedisConfig.Build()) rdb, err := dbb.Redis(ctx)
if err != nil { if err != nil {
return err return err
} }
logdb, err := mgo.NewLogMongo(mgocli.GetDB()) logdb, err := mgo.NewLogMongo(mgocli.GetDB())
if err != nil { if err != nil {
return err return err
@@ -77,15 +83,31 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
if err != nil { if err != nil {
return err return err
} }
var thirdCache cache.ThirdCache
if rdb == nil {
tc, err := mgo.NewCacheMgo(mgocli.GetDB())
if err != nil {
return err
}
thirdCache = mcache.NewThirdCache(tc)
} else {
thirdCache = redis.NewThirdCache(rdb)
}
// Select the oss method according to the profile policy // Select the oss method according to the profile policy
enable := config.RpcConfig.Object.Enable var o s3.Interface
var ( switch enable := config.RpcConfig.Object.Enable; enable {
o s3.Interface
)
switch enable {
case "minio": case "minio":
o, err = minio.NewMinio(ctx, redis.NewMinioCache(rdb), *config.MinioConfig.Build()) var minioCache minio.Cache
if rdb == nil {
mc, err := mgo.NewCacheMgo(mgocli.GetDB())
if err != nil {
return err
}
minioCache = mcache.NewMinioCache(mc)
} else {
minioCache = redis.NewMinioCache(rdb)
}
o, err = minio.NewMinio(ctx, minioCache, *config.MinioConfig.Build())
case "cos": case "cos":
o, err = cos.NewCos(*config.RpcConfig.Object.Cos.Build()) o, err = cos.NewCos(*config.RpcConfig.Object.Cos.Build())
case "oss": case "oss":
@@ -94,6 +116,8 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
o, err = kodo.NewKodo(*config.RpcConfig.Object.Kodo.Build()) o, err = kodo.NewKodo(*config.RpcConfig.Object.Kodo.Build())
case "aws": case "aws":
o, err = aws.NewAws(*config.RpcConfig.Object.Aws.Build()) o, err = aws.NewAws(*config.RpcConfig.Object.Aws.Build())
case "":
o = disable.NewDisable()
default: default:
err = fmt.Errorf("invalid object enable: %s", enable) err = fmt.Errorf("invalid object enable: %s", enable)
} }
@@ -106,7 +130,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
} }
localcache.InitLocalCache(&config.LocalCacheConfig) localcache.InitLocalCache(&config.LocalCacheConfig)
third.RegisterThirdServer(server, &thirdServer{ third.RegisterThirdServer(server, &thirdServer{
thirdDatabase: controller.NewThirdDatabase(redis.NewThirdCache(rdb), logdb), thirdDatabase: controller.NewThirdDatabase(thirdCache, logdb),
s3dataBase: controller.NewS3Database(rdb, o, s3db), s3dataBase: controller.NewS3Database(rdb, o, s3db),
defaultExpire: time.Hour * 24 * 7, defaultExpire: time.Hour * 24 * 7,
config: config, config: config,
@@ -125,6 +149,9 @@ func (t *thirdServer) FcmUpdateToken(ctx context.Context, req *third.FcmUpdateTo
} }
func (t *thirdServer) SetAppBadge(ctx context.Context, req *third.SetAppBadgeReq) (resp *third.SetAppBadgeResp, err error) { func (t *thirdServer) SetAppBadge(ctx context.Context, req *third.SetAppBadgeReq) (resp *third.SetAppBadgeResp, err error) {
if err := authverify.CheckAccess(ctx, req.UserID); err != nil {
return nil, err
}
err = t.thirdDatabase.SetAppBadge(ctx, req.UserID, int(req.AppUnreadCount)) err = t.thirdDatabase.SetAppBadge(ctx, req.UserID, int(req.AppUnreadCount))
if err != nil { if err != nil {
return nil, err return nil, err

Some files were not shown because too many files have changed in this diff Show More