From 6d499032fa1794edc6c62f44048ba250176b5c59 Mon Sep 17 00:00:00 2001
From: "Xinwei Xiong(cubxxw-openim)" <3293172751nss@gmail.com>
Date: Thu, 29 Jun 2023 22:35:31 +0800
Subject: [PATCH] v3 - main to cut out
---
.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md | 37 +
.github/ISSUE_TEMPLATE/deployment-issues.md | 36 +
.github/ISSUE_TEMPLATE/update-.md | 38 +
.github/workflows/codeql-analysis.yml | 71 +
.gitignore | 22 +
.gitmodules | 4 +
LICENSE | 201 +
README.md | 159 +
cmd/Open-IM-SDK-Core | 1 +
cmd/open_im_api/Makefile | 25 +
cmd/open_im_api/main.go | 177 +
cmd/open_im_cms_api/Makefile | 25 +
cmd/open_im_cms_api/main.go | 17 +
cmd/open_im_demo/Makefile | 25 +
cmd/open_im_demo/main.go | 42 +
cmd/open_im_msg_gateway/Makefile | 24 +
cmd/open_im_msg_gateway/main.go | 23 +
cmd/open_im_msg_transfer/Makefile | 25 +
cmd/open_im_msg_transfer/main.go | 19 +
cmd/open_im_push/Makefile | 25 +
cmd/open_im_push/main.go | 22 +
cmd/open_im_timer_task/Makefile | 25 +
cmd/open_im_timer_task/main.go | 63 +
cmd/rpc/open_im_admin_cms/Makefile | 23 +
cmd/rpc/open_im_admin_cms/main.go | 15 +
cmd/rpc/open_im_auth/Makefile | 24 +
cmd/rpc/open_im_auth/main.go | 16 +
cmd/rpc/open_im_friend/Makefile | 25 +
cmd/rpc/open_im_friend/main.go | 16 +
cmd/rpc/open_im_group/Makefile | 25 +
cmd/rpc/open_im_group/main.go | 15 +
cmd/rpc/open_im_message_cms/Makefile | 23 +
cmd/rpc/open_im_message_cms/main.go | 15 +
cmd/rpc/open_im_msg/Makefile | 23 +
cmd/rpc/open_im_msg/main.go | 15 +
cmd/rpc/open_im_office/Makefile | 23 +
cmd/rpc/open_im_office/main.go | 15 +
cmd/rpc/open_im_organization/Makefile | 25 +
cmd/rpc/open_im_organization/main.go | 15 +
cmd/rpc/open_im_statistics/Makefile | 23 +
cmd/rpc/open_im_statistics/main.go | 15 +
cmd/rpc/open_im_user/Makefile | 25 +
cmd/rpc/open_im_user/main.go | 15 +
cmd/test/main.go | 69 +
config/config.yaml | 609 ++
deploy.Dockerfile | 39 +
deploy/.dockerignore | 6 +
deploy/Makefile | 158 +
deploy/config.example.yaml | 183 +
deploy/dockerfiles/Dockerfile.api | 16 +
deploy/dockerfiles/Dockerfile.demo | 16 +
deploy/dockerfiles/Dockerfile.msg_gateway | 16 +
deploy/dockerfiles/Dockerfile.msg_transfer | 16 +
deploy/dockerfiles/Dockerfile.push | 16 +
deploy/dockerfiles/Dockerfile.rpc_auth | 16 +
deploy/dockerfiles/Dockerfile.rpc_friend | 16 +
deploy/dockerfiles/Dockerfile.rpc_group | 16 +
deploy/dockerfiles/Dockerfile.rpc_msg | 16 +
deploy/dockerfiles/Dockerfile.rpc_user | 16 +
deploy/dockerfiles/Dockerfile.timer_task | 16 +
deploy/env.yaml | 101 +
deploy/openim.yaml | 223 +
deploy/readme.md | 30 +
docker-compose.yaml | 116 +
docs/Architecture.jpg | Bin 0 -> 212410 bytes
docs/Open-IM-Servers-on-System.png | Bin 0 -> 21614 bytes
docs/Open-IM-Servers-on-docker.png | Bin 0 -> 10172 bytes
docs/Open-IM.png | Bin 0 -> 17614 bytes
docs/Wechat.jpg | Bin 0 -> 212904 bytes
docs/open-im-logo.png | Bin 0 -> 245627 bytes
docs/open-im-server.png | Bin 0 -> 109213 bytes
go.mod | 69 +
go.sum | 1138 ++++
internal/api/auth/auth.go | 91 +
internal/api/chat/del_msg.go | 43 +
internal/api/chat/get_max_min_seq.go | 61 +
internal/api/chat/pull_msg.go | 68 +
internal/api/chat/send_msg.go | 99 +
internal/api/conversation/conversation.go | 218 +
internal/api/friend/friend.go | 456 ++
internal/api/group/group.go | 698 +++
internal/api/manage/management_chat.go | 296 +
internal/api/manage/management_user.go | 180 +
internal/api/office/tag.go | 280 +
internal/api/office/work_moments.go | 371 ++
internal/api/organization/organization.go | 439 ++
internal/api/third/ali_oss_credential.go | 95 +
internal/api/third/minio_init.go | 67 +
.../api/third/minio_storage_credential.go | 138 +
.../third/tencent_cloud_storage_credential.go | 72 +
internal/api/user/user.go | 129 +
internal/cms_api/admin/admin.go | 42 +
internal/cms_api/group/group.go | 450 ++
internal/cms_api/message_cms/message.go | 109 +
internal/cms_api/middleware/cors.go | 23 +
internal/cms_api/middleware/jwt_auth.go | 24 +
internal/cms_api/organization/organization.go | 49 +
internal/cms_api/router.go | 95 +
internal/cms_api/statistics/statistics.go | 224 +
internal/cms_api/user/user.go | 307 +
internal/demo/register/login.go | 70 +
internal/demo/register/reset_password.go | 56 +
internal/demo/register/send_code.go | 130 +
internal/demo/register/set_password.go | 89 +
internal/demo/register/verify.go | 80 +
internal/msg_gateway/gate/init.go | 35 +
internal/msg_gateway/gate/logic.go | 297 +
.../msg_gateway/gate/open_im_media/room.go | 58 +
internal/msg_gateway/gate/rpc_server.go | 149 +
internal/msg_gateway/gate/validate.go | 253 +
internal/msg_gateway/gate/ws_server.go | 285 +
internal/msg_transfer/logic/db.go | 23 +
.../msg_transfer/logic/history_msg_handler.go | 142 +
internal/msg_transfer/logic/init.go | 25 +
.../logic/persistent_msg_handler.go | 78 +
internal/push/content_struct/content.go | 85 +
internal/push/getui/push.go | 222 +
internal/push/jpush/common/JGPlatform.go | 13 +
internal/push/jpush/push.go | 74 +
internal/push/jpush/requestBody/audience.go | 53 +
internal/push/jpush/requestBody/message.go | 27 +
.../push/jpush/requestBody/notification.go | 36 +
internal/push/jpush/requestBody/options.go | 9 +
internal/push/jpush/requestBody/platform.go | 83 +
internal/push/jpush/requestBody/pushObj.go | 28 +
internal/push/logic/init.go | 39 +
internal/push/logic/push_handler.go | 52 +
internal/push/logic/push_rpc_server.go | 57 +
internal/push/logic/push_to_client.go | 167 +
internal/push/logic/tpns.go | 34 +
internal/push/push_interface.go | 5 +
.../sdk/tpns-server-sdk-go/go/auth/auth.go | 62 +
.../tpns-server-sdk-go/go/client/client.go | 18 +
.../go/common/http_helper.go | 62 +
.../go/common/json_helper.go | 8 +
.../push/sdk/tpns-server-sdk-go/go/def.go | 256 +
.../push/sdk/tpns-server-sdk-go/go/req/req.go | 403 ++
internal/rpc/admin_cms/admin_cms.go | 87 +
internal/rpc/auth/auth.go | 104 +
internal/rpc/auth/callback.go | 1 +
internal/rpc/friend/callback.go | 1 +
internal/rpc/friend/firend.go | 519 ++
internal/rpc/group/callback.go | 13 +
internal/rpc/group/group.go | 1046 ++++
internal/rpc/message_cms/message_cms.go | 167 +
internal/rpc/msg/callback.go | 159 +
internal/rpc/msg/conversation_notification.go | 76 +
internal/rpc/msg/del_msg.go | 23 +
internal/rpc/msg/friend_notification.go | 165 +
internal/rpc/msg/group_notification.go | 508 ++
internal/rpc/msg/pull_message.go | 74 +
internal/rpc/msg/rpcChat.go | 67 +
internal/rpc/msg/send_msg.go | 593 ++
internal/rpc/msg/tag_send_msg.go | 45 +
internal/rpc/office/office.go | 445 ++
internal/rpc/organization/organization.go | 349 ++
internal/rpc/statistics/statistics.go | 378 ++
internal/rpc/user/callback.go | 1 +
internal/rpc/user/user.go | 590 ++
internal/timed_task/init.go | 26 +
internal/timed_task/timed_task.go | 26 +
internal/utils/callback.go | 1 +
internal/utils/cors_middleware_test.go | 68 +
internal/utils/get_server_ip_test.go | 13 +
internal/utils/id.go | 28 +
internal/utils/id_test.go | 15 +
internal/utils/image_test.go | 28 +
internal/utils/jwt_token_test.go | 89 +
internal/utils/md5_test.go | 16 +
.../utils/platform_number_id_to_name_test.go | 46 +
internal/utils/utils.go | 49 +
pkg/base_info/auth_api_struct.go | 39 +
pkg/base_info/conversation_api_struct.go | 117 +
pkg/base_info/cos_api_struct.go | 20 +
pkg/base_info/friend_api_struct.go | 136 +
pkg/base_info/group_api_struct.go | 222 +
pkg/base_info/manage_api_struct.go | 44 +
pkg/base_info/minio_api_struct.go | 25 +
pkg/base_info/msg.go | 12 +
pkg/base_info/office_struct.go | 88 +
pkg/base_info/organization_api_struct.go | 110 +
pkg/base_info/oss_api_struct.go | 22 +
pkg/base_info/public_struct.go | 139 +
pkg/base_info/user_api_struct.go | 34 +
pkg/base_info/work_moments_struct.go | 97 +
pkg/call_back_struct/common.go | 24 +
pkg/call_back_struct/group.go | 9 +
pkg/call_back_struct/message.go | 47 +
pkg/cms_api_struct/admin.go | 10 +
pkg/cms_api_struct/common.go | 11 +
pkg/cms_api_struct/group.go | 144 +
pkg/cms_api_struct/message_cms.go | 50 +
pkg/cms_api_struct/organization.go | 25 +
pkg/cms_api_struct/statistics.go | 89 +
pkg/cms_api_struct/user.go | 110 +
pkg/common/config/config.go | 434 ++
pkg/common/constant/constant.go | 235 +
pkg/common/constant/error.go | 100 +
.../constant/platform_number_id_to_name.go | 66 +
pkg/common/db/model.go | 119 +
pkg/common/db/model_struct.go | 272 +
pkg/common/db/mongoModel.go | 681 +++
pkg/common/db/mysql.go | 162 +
.../mysql_model/im_mysql_model/demo_model.go | 41 +
.../im_mysql_model/friend_model.go | 91 +
.../im_mysql_model/friend_request_model.go | 112 +
.../im_mysql_model/group_member_model.go | 316 ++
.../mysql_model/im_mysql_model/group_model.go | 225 +
.../im_mysql_model/group_request_model.go | 209 +
.../mysql_model/im_mysql_model/message_cms.go | 68 +
.../im_mysql_model/organization_model.go | 182 +
.../im_mysql_model/statistics_model.go | 153 +
.../im_mysql_model/user_black_list_model.go | 58 +
.../mysql_model/im_mysql_model/user_model.go | 398 ++
.../im_mysql_msg_model/chat_log_model.go | 51 +
.../im_mysql_msg_model/hash_code.go | 36 +
pkg/common/db/redisModel.go | 157 +
pkg/common/db/redisModel_test.go | 27 +
pkg/common/http/http_client.go | 66 +
pkg/common/http/http_resp.go | 43 +
pkg/common/kafka/consumer.go | 36 +
pkg/common/kafka/consumer_group.go | 53 +
pkg/common/kafka/producer.go | 49 +
pkg/common/log/es_hk.go | 107 +
pkg/common/log/file_line_hk.go | 71 +
pkg/common/log/logrus.go | 199 +
pkg/common/log/time_format.go | 57 +
.../multi_terminal_login.go | 73 +
pkg/common/token_verify/jwt_token.go | 231 +
pkg/common/utils/utils.go | 161 +
pkg/grpc-etcdv3/getcdv3/pool.go | 254 +
pkg/grpc-etcdv3/getcdv3/register.go | 118 +
pkg/grpc-etcdv3/getcdv3/resolver.go | 262 +
pkg/proto/admin_cms/admin_cms.pb.go | 317 ++
pkg/proto/admin_cms/admin_cms.proto | 18 +
pkg/proto/auth/auth.pb.go | 414 ++
pkg/proto/auth/auth.proto | 36 +
pkg/proto/auto_proto.sh | 13 +
pkg/proto/base/base.proto | 5 +
pkg/proto/chat/chat.pb.go | 650 +++
pkg/proto/chat/chat.proto | 82 +
pkg/proto/friend/friend.pb.go | 1990 +++++++
pkg/proto/friend/friend.proto | 169 +
pkg/proto/group/group.pb.go | 4481 +++++++++++++++
pkg/proto/group/group.proto | 423 ++
pkg/proto/message_cms/message_cms.pb.go | 1083 ++++
pkg/proto/message_cms/message_cms.proto | 71 +
pkg/proto/office/office.pb.go | 2851 ++++++++++
pkg/proto/office/office.proto | 263 +
pkg/proto/organization/organization.pb.go | 1780 ++++++
pkg/proto/organization/organization.proto | 172 +
pkg/proto/proto_dir.cfg | 15 +
pkg/proto/push/push.pb.go | 216 +
pkg/proto/push/push.proto | 38 +
pkg/proto/relay/relay.pb.go | 602 ++
pkg/proto/relay/relay.proto | 61 +
pkg/proto/rtc/rtc.pb.go | 2990 ++++++++++
pkg/proto/rtc/rtc.proto | 219 +
pkg/proto/sdk_ws/ws.pb.go | 5002 +++++++++++++++++
pkg/proto/sdk_ws/ws.proto | 571 ++
pkg/proto/statistics/statistics.pb.go | 1498 +++++
pkg/proto/statistics/statistics.proto | 93 +
pkg/proto/user/user.pb.go | 3505 ++++++++++++
pkg/proto/user/user.proto | 316 ++
pkg/statistics/statistics.go | 32 +
pkg/utils/cors_middleware.go | 24 +
pkg/utils/file.go | 39 +
pkg/utils/get_server_ip.go | 26 +
pkg/utils/image.go | 56 +
pkg/utils/map.go | 129 +
pkg/utils/md5.go | 13 +
pkg/utils/strings.go | 96 +
pkg/utils/time_format.go | 85 +
pkg/utils/utils.go | 95 +
script/build_all_service.sh | 36 +
script/build_images.sh | 11 +
script/check_all.sh | 66 +
script/demo_svr_start.sh | 47 +
script/docker_check_service.sh | 11 +
script/docker_start_all.sh | 34 +
script/env_check.sh | 43 +
script/function.sh | 15 +
script/msg_gateway_start.sh | 50 +
script/msg_transfer_start.sh | 36 +
script/mysql_database_init.sh | 38 +
script/path_info.cfg | 84 +
script/push_start.sh | 45 +
script/sdk_svr_start.sh | 48 +
script/start_all.sh | 29 +
script/start_rpc_service.sh | 69 +
script/stop_all.sh | 19 +
script/style_info.cfg | 9 +
script/timer_start.sh | 35 +
293 files changed, 57778 insertions(+)
create mode 100644 .github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md
create mode 100644 .github/ISSUE_TEMPLATE/deployment-issues.md
create mode 100644 .github/ISSUE_TEMPLATE/update-.md
create mode 100644 .github/workflows/codeql-analysis.yml
create mode 100644 .gitignore
create mode 100644 .gitmodules
create mode 100644 LICENSE
create mode 100644 README.md
create mode 160000 cmd/Open-IM-SDK-Core
create mode 100644 cmd/open_im_api/Makefile
create mode 100644 cmd/open_im_api/main.go
create mode 100644 cmd/open_im_cms_api/Makefile
create mode 100644 cmd/open_im_cms_api/main.go
create mode 100644 cmd/open_im_demo/Makefile
create mode 100644 cmd/open_im_demo/main.go
create mode 100644 cmd/open_im_msg_gateway/Makefile
create mode 100644 cmd/open_im_msg_gateway/main.go
create mode 100644 cmd/open_im_msg_transfer/Makefile
create mode 100644 cmd/open_im_msg_transfer/main.go
create mode 100644 cmd/open_im_push/Makefile
create mode 100644 cmd/open_im_push/main.go
create mode 100644 cmd/open_im_timer_task/Makefile
create mode 100644 cmd/open_im_timer_task/main.go
create mode 100644 cmd/rpc/open_im_admin_cms/Makefile
create mode 100644 cmd/rpc/open_im_admin_cms/main.go
create mode 100644 cmd/rpc/open_im_auth/Makefile
create mode 100644 cmd/rpc/open_im_auth/main.go
create mode 100644 cmd/rpc/open_im_friend/Makefile
create mode 100644 cmd/rpc/open_im_friend/main.go
create mode 100644 cmd/rpc/open_im_group/Makefile
create mode 100644 cmd/rpc/open_im_group/main.go
create mode 100644 cmd/rpc/open_im_message_cms/Makefile
create mode 100644 cmd/rpc/open_im_message_cms/main.go
create mode 100644 cmd/rpc/open_im_msg/Makefile
create mode 100644 cmd/rpc/open_im_msg/main.go
create mode 100644 cmd/rpc/open_im_office/Makefile
create mode 100644 cmd/rpc/open_im_office/main.go
create mode 100644 cmd/rpc/open_im_organization/Makefile
create mode 100644 cmd/rpc/open_im_organization/main.go
create mode 100644 cmd/rpc/open_im_statistics/Makefile
create mode 100644 cmd/rpc/open_im_statistics/main.go
create mode 100644 cmd/rpc/open_im_user/Makefile
create mode 100644 cmd/rpc/open_im_user/main.go
create mode 100644 cmd/test/main.go
create mode 100644 config/config.yaml
create mode 100644 deploy.Dockerfile
create mode 100644 deploy/.dockerignore
create mode 100644 deploy/Makefile
create mode 100644 deploy/config.example.yaml
create mode 100644 deploy/dockerfiles/Dockerfile.api
create mode 100644 deploy/dockerfiles/Dockerfile.demo
create mode 100644 deploy/dockerfiles/Dockerfile.msg_gateway
create mode 100644 deploy/dockerfiles/Dockerfile.msg_transfer
create mode 100644 deploy/dockerfiles/Dockerfile.push
create mode 100644 deploy/dockerfiles/Dockerfile.rpc_auth
create mode 100644 deploy/dockerfiles/Dockerfile.rpc_friend
create mode 100644 deploy/dockerfiles/Dockerfile.rpc_group
create mode 100644 deploy/dockerfiles/Dockerfile.rpc_msg
create mode 100644 deploy/dockerfiles/Dockerfile.rpc_user
create mode 100644 deploy/dockerfiles/Dockerfile.timer_task
create mode 100644 deploy/env.yaml
create mode 100644 deploy/openim.yaml
create mode 100644 deploy/readme.md
create mode 100644 docker-compose.yaml
create mode 100644 docs/Architecture.jpg
create mode 100644 docs/Open-IM-Servers-on-System.png
create mode 100644 docs/Open-IM-Servers-on-docker.png
create mode 100644 docs/Open-IM.png
create mode 100644 docs/Wechat.jpg
create mode 100644 docs/open-im-logo.png
create mode 100644 docs/open-im-server.png
create mode 100644 go.mod
create mode 100644 go.sum
create mode 100644 internal/api/auth/auth.go
create mode 100644 internal/api/chat/del_msg.go
create mode 100644 internal/api/chat/get_max_min_seq.go
create mode 100644 internal/api/chat/pull_msg.go
create mode 100644 internal/api/chat/send_msg.go
create mode 100644 internal/api/conversation/conversation.go
create mode 100644 internal/api/friend/friend.go
create mode 100644 internal/api/group/group.go
create mode 100644 internal/api/manage/management_chat.go
create mode 100644 internal/api/manage/management_user.go
create mode 100644 internal/api/office/tag.go
create mode 100644 internal/api/office/work_moments.go
create mode 100644 internal/api/organization/organization.go
create mode 100644 internal/api/third/ali_oss_credential.go
create mode 100644 internal/api/third/minio_init.go
create mode 100644 internal/api/third/minio_storage_credential.go
create mode 100644 internal/api/third/tencent_cloud_storage_credential.go
create mode 100644 internal/api/user/user.go
create mode 100644 internal/cms_api/admin/admin.go
create mode 100644 internal/cms_api/group/group.go
create mode 100644 internal/cms_api/message_cms/message.go
create mode 100644 internal/cms_api/middleware/cors.go
create mode 100644 internal/cms_api/middleware/jwt_auth.go
create mode 100644 internal/cms_api/organization/organization.go
create mode 100644 internal/cms_api/router.go
create mode 100644 internal/cms_api/statistics/statistics.go
create mode 100644 internal/cms_api/user/user.go
create mode 100644 internal/demo/register/login.go
create mode 100644 internal/demo/register/reset_password.go
create mode 100644 internal/demo/register/send_code.go
create mode 100644 internal/demo/register/set_password.go
create mode 100644 internal/demo/register/verify.go
create mode 100644 internal/msg_gateway/gate/init.go
create mode 100644 internal/msg_gateway/gate/logic.go
create mode 100644 internal/msg_gateway/gate/open_im_media/room.go
create mode 100644 internal/msg_gateway/gate/rpc_server.go
create mode 100644 internal/msg_gateway/gate/validate.go
create mode 100644 internal/msg_gateway/gate/ws_server.go
create mode 100644 internal/msg_transfer/logic/db.go
create mode 100644 internal/msg_transfer/logic/history_msg_handler.go
create mode 100644 internal/msg_transfer/logic/init.go
create mode 100644 internal/msg_transfer/logic/persistent_msg_handler.go
create mode 100644 internal/push/content_struct/content.go
create mode 100644 internal/push/getui/push.go
create mode 100644 internal/push/jpush/common/JGPlatform.go
create mode 100644 internal/push/jpush/push.go
create mode 100644 internal/push/jpush/requestBody/audience.go
create mode 100644 internal/push/jpush/requestBody/message.go
create mode 100644 internal/push/jpush/requestBody/notification.go
create mode 100644 internal/push/jpush/requestBody/options.go
create mode 100644 internal/push/jpush/requestBody/platform.go
create mode 100644 internal/push/jpush/requestBody/pushObj.go
create mode 100644 internal/push/logic/init.go
create mode 100644 internal/push/logic/push_handler.go
create mode 100644 internal/push/logic/push_rpc_server.go
create mode 100644 internal/push/logic/push_to_client.go
create mode 100644 internal/push/logic/tpns.go
create mode 100644 internal/push/push_interface.go
create mode 100644 internal/push/sdk/tpns-server-sdk-go/go/auth/auth.go
create mode 100644 internal/push/sdk/tpns-server-sdk-go/go/client/client.go
create mode 100644 internal/push/sdk/tpns-server-sdk-go/go/common/http_helper.go
create mode 100644 internal/push/sdk/tpns-server-sdk-go/go/common/json_helper.go
create mode 100644 internal/push/sdk/tpns-server-sdk-go/go/def.go
create mode 100644 internal/push/sdk/tpns-server-sdk-go/go/req/req.go
create mode 100644 internal/rpc/admin_cms/admin_cms.go
create mode 100644 internal/rpc/auth/auth.go
create mode 100644 internal/rpc/auth/callback.go
create mode 100644 internal/rpc/friend/callback.go
create mode 100644 internal/rpc/friend/firend.go
create mode 100644 internal/rpc/group/callback.go
create mode 100644 internal/rpc/group/group.go
create mode 100644 internal/rpc/message_cms/message_cms.go
create mode 100644 internal/rpc/msg/callback.go
create mode 100644 internal/rpc/msg/conversation_notification.go
create mode 100644 internal/rpc/msg/del_msg.go
create mode 100644 internal/rpc/msg/friend_notification.go
create mode 100644 internal/rpc/msg/group_notification.go
create mode 100644 internal/rpc/msg/pull_message.go
create mode 100644 internal/rpc/msg/rpcChat.go
create mode 100644 internal/rpc/msg/send_msg.go
create mode 100644 internal/rpc/msg/tag_send_msg.go
create mode 100644 internal/rpc/office/office.go
create mode 100644 internal/rpc/organization/organization.go
create mode 100644 internal/rpc/statistics/statistics.go
create mode 100644 internal/rpc/user/callback.go
create mode 100644 internal/rpc/user/user.go
create mode 100644 internal/timed_task/init.go
create mode 100644 internal/timed_task/timed_task.go
create mode 100644 internal/utils/callback.go
create mode 100644 internal/utils/cors_middleware_test.go
create mode 100644 internal/utils/get_server_ip_test.go
create mode 100644 internal/utils/id.go
create mode 100644 internal/utils/id_test.go
create mode 100644 internal/utils/image_test.go
create mode 100644 internal/utils/jwt_token_test.go
create mode 100644 internal/utils/md5_test.go
create mode 100644 internal/utils/platform_number_id_to_name_test.go
create mode 100644 internal/utils/utils.go
create mode 100644 pkg/base_info/auth_api_struct.go
create mode 100644 pkg/base_info/conversation_api_struct.go
create mode 100644 pkg/base_info/cos_api_struct.go
create mode 100644 pkg/base_info/friend_api_struct.go
create mode 100644 pkg/base_info/group_api_struct.go
create mode 100644 pkg/base_info/manage_api_struct.go
create mode 100644 pkg/base_info/minio_api_struct.go
create mode 100644 pkg/base_info/msg.go
create mode 100644 pkg/base_info/office_struct.go
create mode 100644 pkg/base_info/organization_api_struct.go
create mode 100644 pkg/base_info/oss_api_struct.go
create mode 100644 pkg/base_info/public_struct.go
create mode 100644 pkg/base_info/user_api_struct.go
create mode 100644 pkg/base_info/work_moments_struct.go
create mode 100644 pkg/call_back_struct/common.go
create mode 100644 pkg/call_back_struct/group.go
create mode 100644 pkg/call_back_struct/message.go
create mode 100644 pkg/cms_api_struct/admin.go
create mode 100644 pkg/cms_api_struct/common.go
create mode 100644 pkg/cms_api_struct/group.go
create mode 100644 pkg/cms_api_struct/message_cms.go
create mode 100644 pkg/cms_api_struct/organization.go
create mode 100644 pkg/cms_api_struct/statistics.go
create mode 100644 pkg/cms_api_struct/user.go
create mode 100644 pkg/common/config/config.go
create mode 100644 pkg/common/constant/constant.go
create mode 100644 pkg/common/constant/error.go
create mode 100644 pkg/common/constant/platform_number_id_to_name.go
create mode 100644 pkg/common/db/model.go
create mode 100644 pkg/common/db/model_struct.go
create mode 100644 pkg/common/db/mongoModel.go
create mode 100644 pkg/common/db/mysql.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/demo_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/friend_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/friend_request_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/group_member_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/group_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/group_request_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/message_cms.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/organization_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/statistics_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/user_black_list_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_model/user_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_msg_model/chat_log_model.go
create mode 100644 pkg/common/db/mysql_model/im_mysql_msg_model/hash_code.go
create mode 100644 pkg/common/db/redisModel.go
create mode 100644 pkg/common/db/redisModel_test.go
create mode 100644 pkg/common/http/http_client.go
create mode 100644 pkg/common/http/http_resp.go
create mode 100644 pkg/common/kafka/consumer.go
create mode 100644 pkg/common/kafka/consumer_group.go
create mode 100644 pkg/common/kafka/producer.go
create mode 100644 pkg/common/log/es_hk.go
create mode 100644 pkg/common/log/file_line_hk.go
create mode 100644 pkg/common/log/logrus.go
create mode 100644 pkg/common/log/time_format.go
create mode 100644 pkg/common/multi_terminal_login/multi_terminal_login.go
create mode 100644 pkg/common/token_verify/jwt_token.go
create mode 100644 pkg/common/utils/utils.go
create mode 100644 pkg/grpc-etcdv3/getcdv3/pool.go
create mode 100644 pkg/grpc-etcdv3/getcdv3/register.go
create mode 100644 pkg/grpc-etcdv3/getcdv3/resolver.go
create mode 100644 pkg/proto/admin_cms/admin_cms.pb.go
create mode 100644 pkg/proto/admin_cms/admin_cms.proto
create mode 100644 pkg/proto/auth/auth.pb.go
create mode 100644 pkg/proto/auth/auth.proto
create mode 100644 pkg/proto/auto_proto.sh
create mode 100644 pkg/proto/base/base.proto
create mode 100644 pkg/proto/chat/chat.pb.go
create mode 100644 pkg/proto/chat/chat.proto
create mode 100644 pkg/proto/friend/friend.pb.go
create mode 100644 pkg/proto/friend/friend.proto
create mode 100644 pkg/proto/group/group.pb.go
create mode 100644 pkg/proto/group/group.proto
create mode 100644 pkg/proto/message_cms/message_cms.pb.go
create mode 100644 pkg/proto/message_cms/message_cms.proto
create mode 100644 pkg/proto/office/office.pb.go
create mode 100644 pkg/proto/office/office.proto
create mode 100644 pkg/proto/organization/organization.pb.go
create mode 100644 pkg/proto/organization/organization.proto
create mode 100644 pkg/proto/proto_dir.cfg
create mode 100644 pkg/proto/push/push.pb.go
create mode 100644 pkg/proto/push/push.proto
create mode 100644 pkg/proto/relay/relay.pb.go
create mode 100644 pkg/proto/relay/relay.proto
create mode 100644 pkg/proto/rtc/rtc.pb.go
create mode 100644 pkg/proto/rtc/rtc.proto
create mode 100644 pkg/proto/sdk_ws/ws.pb.go
create mode 100644 pkg/proto/sdk_ws/ws.proto
create mode 100644 pkg/proto/statistics/statistics.pb.go
create mode 100644 pkg/proto/statistics/statistics.proto
create mode 100644 pkg/proto/user/user.pb.go
create mode 100644 pkg/proto/user/user.proto
create mode 100644 pkg/statistics/statistics.go
create mode 100644 pkg/utils/cors_middleware.go
create mode 100644 pkg/utils/file.go
create mode 100644 pkg/utils/get_server_ip.go
create mode 100644 pkg/utils/image.go
create mode 100644 pkg/utils/map.go
create mode 100644 pkg/utils/md5.go
create mode 100644 pkg/utils/strings.go
create mode 100644 pkg/utils/time_format.go
create mode 100644 pkg/utils/utils.go
create mode 100644 script/build_all_service.sh
create mode 100644 script/build_images.sh
create mode 100644 script/check_all.sh
create mode 100644 script/demo_svr_start.sh
create mode 100644 script/docker_check_service.sh
create mode 100644 script/docker_start_all.sh
create mode 100644 script/env_check.sh
create mode 100644 script/function.sh
create mode 100644 script/msg_gateway_start.sh
create mode 100644 script/msg_transfer_start.sh
create mode 100644 script/mysql_database_init.sh
create mode 100644 script/path_info.cfg
create mode 100644 script/push_start.sh
create mode 100644 script/sdk_svr_start.sh
create mode 100644 script/start_all.sh
create mode 100644 script/start_rpc_service.sh
create mode 100644 script/stop_all.sh
create mode 100644 script/style_info.cfg
create mode 100644 script/timer_start.sh
diff --git a/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md
new file mode 100644
index 000000000..e5d8ed6fa
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/ISSUE_TEMPLATE.md
@@ -0,0 +1,37 @@
+---
+name: "\U0001F41E Bug"
+about: File a bug/issue
+title: "[BUG]
"
+labels: ''
+assignees: ''
+
+---
+
+
+
+### Environment:
+
+
+### Physical Memory Capacity:
+
+
+### Docker Image:
+
+
+### Code Version:
+
+
+### Component installation:
+
+
+
+### Log File:
+
+
+
+### screenshot:
+
diff --git a/.github/ISSUE_TEMPLATE/deployment-issues.md b/.github/ISSUE_TEMPLATE/deployment-issues.md
new file mode 100644
index 000000000..57d7cbcb7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/deployment-issues.md
@@ -0,0 +1,36 @@
+---
+name: Deployment issues
+about: Deployment issues
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+If you are deploying OpenIM for the first time
+
+
+
+```
+git clone https://github.com/OpenIMSDK/Open-IM-Server.git --recursive
+```
+
+screenshot here
+
+```
+cd Open-IM-Server/script ; chmod +x *.sh ; ./env_check.sh
+```
+
+screenshot here
+
+```
+cd .. ; docker-compose up -d
+```
+
+screenshot here
+
+```
+cd script ; ./docker_check_service.sh
+```
+
+screenshot here
diff --git a/.github/ISSUE_TEMPLATE/update-.md b/.github/ISSUE_TEMPLATE/update-.md
new file mode 100644
index 000000000..ba8da7270
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/update-.md
@@ -0,0 +1,38 @@
+---
+name: 'update '
+about: update docker image
+title: update docker image
+labels: ''
+assignees: ''
+
+---
+
+```
+cd Open-IM-Server ; docker-compose down
+```
+
+screenshot here
+
+```
+git pull
+```
+
+screenshot here
+
+```
+docker-compose pull
+```
+
+screenshot here
+
+```
+chmod +x script/*.sh ; docker-compose up -d
+```
+
+screenshot here
+
+```
+cd script ; ./docker_check_service.sh
+```
+
+screenshot here
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
new file mode 100644
index 000000000..f106438e3
--- /dev/null
+++ b/.github/workflows/codeql-analysis.yml
@@ -0,0 +1,71 @@
+# For most projects, this workflow file will not need changing; you simply need
+# to commit it to your repository.
+#
+# You may wish to alter this file to override the set of languages analyzed,
+# or to provide custom queries or build logic.
+#
+# ******** NOTE ********
+# We have attempted to detect the languages in your repository. Please check
+# the `language` matrix defined below to confirm you have the correct set of
+# supported CodeQL languages.
+#
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ # The branches below must be a subset of the branches above
+ branches: [ main ]
+ schedule:
+ - cron: '23 2 * * 2'
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ 'go' ]
+ # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
+ # Learn more:
+ # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
+
+ steps:
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ # Initializes the CodeQL tools for scanning.
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v1
+ with:
+ languages: ${{ matrix.language }}
+ # If you wish to specify custom queries, you can do so here or in a config file.
+ # By default, queries listed here will override any specified in a config file.
+ # Prefix the list here with "+" to use these queries and those in the config file.
+ # queries: ./path/to/local/query, your-org/your-repo/queries@main
+
+ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
+ # If this step fails, then you should remove it and run the build manually (see below)
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v1
+
+ # ℹ️ Command-line programs to run using the OS shell.
+ # 📚 https://git.io/JvXDl
+
+ # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
+ # and modify them (or add more) to build your code if your project
+ # uses a compiled language
+
+ #- run: |
+ # make bootstrap
+ # make release
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v1
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..3390267f4
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+bin
+logs
+.devcontainer
+components
+logs
+out-test
+.github
+.idea
+
+
+deploy/open_im_demo
+deploy/open_im_api
+deploy/open_im_msg_gateway
+deploy/open_im_msg_transfer
+deploy/open_im_push
+deploy/open_im_timer_task
+deploy/open_im_rpc_user
+deploy/open_im_rpc_friend
+deploy/open_im_rpc_group
+deploy/open_im_rpc_msg
+deploy/open_im_rpc_auth
+deploy/Open-IM-SDK-Core
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 000000000..be3279250
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,4 @@
+
+[submodule "cmd/Open-IM-SDK-Core"]
+ path = cmd/Open-IM-SDK-Core
+ url = https://github.com/OpenIMSDK/Open-IM-SDK-Core.git
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 000000000..261eeb9e9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 000000000..4964bacb5
--- /dev/null
+++ b/README.md
@@ -0,0 +1,159 @@
+# Open-IM-Server
+
+
+
+[](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/LICENSE) [](https://golang.org/)
+
+## Open-IM-Server: Open source Instant Messaging Server
+
+Instant messaging server. Backend in pure Golang, wire transport protocol is JSON over websocket.
+
+Everything is a message in Open-IM-Server, so you can extend custom messages easily, there is no need to modify the server code.
+
+Using microservice architectures, Open-IM-Server can be deployed using clusters.
+
+By deployment of the Open-IM-Server on the customer's server, developers can integrate instant messaging and real-time network capabilities into their own applications free of charge and quickly, and ensure the security and privacy of business data.
+
+## Features
+
+- Everything in Free
+- Scalable architecture
+- Easy integration
+- Good scalability
+- High performance
+- Lightweight
+- Supports multiple protocols
+
+## Community
+
+- Join the Telegram-OpenIM group: https://t.me/joinchat/zSJLPaHBNLZmODI1
+- 中文官网访问这里:[Open-IM中文开发文档](https://doc.rentsoft.cn/)
+
+## Quick start
+
+### Installing Open-IM-Server
+
+> Open-IM relies on five open source high-performance components: ETCD, MySQL, MongoDB, Redis, and Kafka. Privatization deployment Before Open-IM-Server, please make sure that the above five components have been installed. If your server does not have the above components, you must first install Missing components. If you have the above components, it is recommended to use them directly. If not, it is recommended to use Docker-compose, no To install dependencies, one-click deployment, faster and more convenient.
+
+#### Source code deployment
+
+1. Install [Go environment](https://golang.org/doc/install). Make sure Go version is at least 1.15.
+
+2. Clone the Open-IM project to your server.
+
+ ```
+ git clone https://github.com/OpenIMSDK/Open-IM-Server.git --recursive
+ ```
+
+3. Build and start Service.
+
+ 1. Shell authorization
+
+ ```
+ #cd Open-IM-server/script
+
+ chmod +x *.sh
+ ```
+
+ 2. Execute the build shell
+
+ ```
+ ./build_all_service.sh
+ ```
+
+ 3. Start service
+
+ ```
+ ./start_all.sh
+ ```
+
+ 4. Check service
+
+ ```
+ ./check_all.sh
+ ```
+
+ 
+
+#### Docker deployment
+
+All images are available at https://hub.docker.com/r/lyt1123/open_im_server
+
+1. [Install Docker](https://docs.docker.com/install/) 1.13 or above.
+
+2. [Install Docker Compose](https://docs.docker.com/compose/install/) 1.22 or above.
+
+3. Clone the Open-IM project to your server.
+
+ ```
+ git clone https://github.com/OpenIMSDK/Open-IM-Server.git --recursive
+ ```
+
+4. Start docker-compose with one click(Docker automatically pulls all images)
+
+ ```
+ cd Open-IM-Server
+ docker-compose up -d
+ ```
+
+5. Check service
+
+ ```
+ ./docker_check_service.sh
+ ./check_all.sh
+ ```
+
+ 
+
+### CONFIGURATION INSTRUCTIONS
+
+> Open-IM configuration is divided into basic component configuration and business internal service configuration. Developers need to fill in the address of each component as the address of their server component when using the product, and ensure that the internal service port of the business is not occupied
+
+#### Basic Component Configuration Instructions
+
+- ETCD
+ - Etcd is used for the discovery and registration of rpc services, etcd Schema is the prefix of the registered name, it is recommended to modify it to your company name, etcd address (ip+port) supports clustered deployment, you can fill in multiple ETCD addresses separated by commas, and also only one etcd address.
+- MySQL
+ - mysql is used for full storage of messages and user relationships. Cluster deployment is not supported for the time being. Modify addresses and users, passwords, and database names.
+- Mongo
+ - Mongo is used for offline storage of messages. The default storage is 7 days. Cluster deployment is temporarily not supported. Just modify the address and database name.
+- Redis
+ - Redis is currently mainly used for message serial number storage and user token information storage. Cluster deployment is temporarily not supported. Just modify the corresponding redis address and password.
+- Kafka
+ - Kafka is used as a message transfer storage queue to support cluster deployment, just modify the corresponding address
+
+#### Internal Service Configuration Instructions
+
+- credential&&push
+ - The Open-IM needs to use the three-party offline push function. Currently, Tencent's three-party push is used. It supports IOS, Android and OSX push. This information is some registration information pushed by Tencent. Developers need to go to Tencent Cloud Mobile Push to register the corresponding information. If you do not fill in the corresponding information, you cannot use the offline message push function
+- api&&rpcport&&longconnsvr&&rpcregistername
+ - The api port is the http interface, longconnsvr is the websocket listening port, and rpcport is the internal service startup port. Both support cluster deployment. Make sure that these ports are not used. If you want to open multiple services for a single service, fill in multiple ports separated by commas. rpcregistername is the service name registered by each service to the registry etcd, no need to modify
+- log&&modulename
+ - The log configuration includes the storage path of the log file, and the log is sent to elasticsearch for log viewing. Currently, the log is not supported to be sent to elasticsearch. The configuration does not need to be modified for the time being. The modulename is used to split the log according to the name of the service module. The default configuration is fine.
+- multiloginpolicy&&tokenpolicy
+ - Open-IM supports multi-terminal login. Currently, there are three multi-terminal login policies. The PC terminal and the mobile terminal are online at the same time by default. When multiple policies are configured to be true, the first policy with true is used by default, and the token policy is the generated token policy. , The developer can customize the expiration time of the token
+
+### SCRIPT DESCRIPTION
+
+> Open-IM script provides service compilation, start, and stop scripts. There are four Open-IM script start modules, one is the http+rpc service start module, the second is the websocket service start module, then the msg_transfer module, and the last is the push module
+
+- path_info.cfg&&style_info.cfg&&functions.sh
+ - Contains the path information of each module, including the path where the source code is located, the name of the service startup, the shell print font style, and some functions for processing shell strings
+- build_all_service.sh
+ - Compile the module, compile all the source code of Open-IM into a binary file and put it into the bin directory
+- start_rpc_api_service.sh&&msg_gateway_start.sh&&msg_transfer_start.sh&&push_start.sh
+ - Independent script startup module, followed by api and rpc modules, message gateway module, message transfer module, and push module
+- start_all.sh&&stop_all.sh
+ - Total script, start all services and close all services
+
+## Authentication Clow Chart
+
+
+
+## Architecture
+
+
+
+## License
+
+Open-IM-Server is under the Apache 2.0 license. See the [LICENSE](https://github.com/OpenIMSDK/Open-IM-Server/blob/main/LICENSE) file for details
diff --git a/cmd/Open-IM-SDK-Core b/cmd/Open-IM-SDK-Core
new file mode 160000
index 000000000..3ecd23203
--- /dev/null
+++ b/cmd/Open-IM-SDK-Core
@@ -0,0 +1 @@
+Subproject commit 3ecd23203cd6bd746b1fcb0c70755bd2cbf5361c
diff --git a/cmd/open_im_api/Makefile b/cmd/open_im_api/Makefile
new file mode 100644
index 000000000..1f96bca5a
--- /dev/null
+++ b/cmd/open_im_api/Makefile
@@ -0,0 +1,25 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_api
+BIN_DIR=../../bin/
+
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
diff --git a/cmd/open_im_api/main.go b/cmd/open_im_api/main.go
new file mode 100644
index 000000000..a2f8522ef
--- /dev/null
+++ b/cmd/open_im_api/main.go
@@ -0,0 +1,177 @@
+package main
+
+import (
+ apiAuth "Open_IM/internal/api/auth"
+ apiChat "Open_IM/internal/api/chat"
+ "Open_IM/internal/api/conversation"
+ "Open_IM/internal/api/friend"
+ "Open_IM/internal/api/group"
+ "Open_IM/internal/api/manage"
+ "Open_IM/internal/api/office"
+ "Open_IM/internal/api/organization"
+ apiThird "Open_IM/internal/api/third"
+ "Open_IM/internal/api/user"
+ "Open_IM/pkg/common/config"
+ "Open_IM/pkg/common/log"
+ "Open_IM/pkg/utils"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "strconv"
+
+ "github.com/gin-gonic/gin"
+ //"syscall"
+ "Open_IM/pkg/common/constant"
+)
+
+func main() {
+ log.NewPrivateLog(constant.LogFileName)
+ gin.SetMode(gin.ReleaseMode)
+ f, _ := os.Create("../logs/api.log")
+ gin.DefaultWriter = io.MultiWriter(f)
+
+ r := gin.Default()
+ r.Use(utils.CorsHandler())
+
+ log.Info("load config: ", config.Config)
+ // user routing group, which handles user registration and login services
+ userRouterGroup := r.Group("/user")
+ {
+ userRouterGroup.POST("/update_user_info", user.UpdateUserInfo) //1
+ userRouterGroup.POST("/get_users_info", user.GetUsersInfo) //1
+ userRouterGroup.POST("/get_self_user_info", user.GetSelfUserInfo) //1
+ }
+ //friend routing group
+ friendRouterGroup := r.Group("/friend")
+ {
+ // friendRouterGroup.POST("/get_friends_info", friend.GetFriendsInfo)
+ friendRouterGroup.POST("/add_friend", friend.AddFriend) //1
+ friendRouterGroup.POST("/delete_friend", friend.DeleteFriend) //1
+ friendRouterGroup.POST("/get_friend_apply_list", friend.GetFriendApplyList) //1
+ friendRouterGroup.POST("/get_self_friend_apply_list", friend.GetSelfFriendApplyList) //1
+ friendRouterGroup.POST("/get_friend_list", friend.GetFriendList) //1
+ friendRouterGroup.POST("/add_friend_response", friend.AddFriendResponse) //1
+ friendRouterGroup.POST("/set_friend_remark", friend.SetFriendRemark) //1
+
+ friendRouterGroup.POST("/add_black", friend.AddBlack) //1
+ friendRouterGroup.POST("/get_black_list", friend.GetBlacklist) //1
+ friendRouterGroup.POST("/remove_black", friend.RemoveBlack) //1
+
+ friendRouterGroup.POST("/import_friend", friend.ImportFriend) //1
+ friendRouterGroup.POST("/is_friend", friend.IsFriend) //1
+ }
+ //group related routing group
+ groupRouterGroup := r.Group("/group")
+ {
+ groupRouterGroup.POST("/create_group", group.CreateGroup) //1
+ groupRouterGroup.POST("/set_group_info", group.SetGroupInfo) //1
+ groupRouterGroup.POST("join_group", group.JoinGroup) //1
+ groupRouterGroup.POST("/quit_group", group.QuitGroup) //1
+ groupRouterGroup.POST("/group_application_response", group.ApplicationGroupResponse) //1
+ groupRouterGroup.POST("/transfer_group", group.TransferGroupOwner) //1
+ groupRouterGroup.POST("/get_recv_group_applicationList", group.GetRecvGroupApplicationList) //1
+ groupRouterGroup.POST("/get_user_req_group_applicationList", group.GetUserReqGroupApplicationList)
+ groupRouterGroup.POST("/get_groups_info", group.GetGroupsInfo) //1
+ groupRouterGroup.POST("/kick_group", group.KickGroupMember) //1
+ groupRouterGroup.POST("/get_group_member_list", group.GetGroupMemberList) //no use
+ groupRouterGroup.POST("/get_group_all_member_list", group.GetGroupAllMemberList) //1
+ groupRouterGroup.POST("/get_group_members_info", group.GetGroupMembersInfo) //1
+ groupRouterGroup.POST("/invite_user_to_group", group.InviteUserToGroup) //1
+ groupRouterGroup.POST("/get_joined_group_list", group.GetJoinedGroupList) //1
+ groupRouterGroup.POST("/dismiss_group", group.DismissGroup) //
+ groupRouterGroup.POST("/mute_group_member", group.MuteGroupMember)
+ groupRouterGroup.POST("/cancel_mute_group_member", group.CancelMuteGroupMember) //MuteGroup
+ groupRouterGroup.POST("/mute_group", group.MuteGroup)
+ groupRouterGroup.POST("/cancel_mute_group", group.CancelMuteGroup)
+ }
+ //certificate
+ authRouterGroup := r.Group("/auth")
+ {
+ authRouterGroup.POST("/user_register", apiAuth.UserRegister) //1
+ authRouterGroup.POST("/user_token", apiAuth.UserToken) //1
+ }
+ //Third service
+ thirdGroup := r.Group("/third")
+ {
+ thirdGroup.POST("/tencent_cloud_storage_credential", apiThird.TencentCloudStorageCredential)
+ thirdGroup.POST("/ali_oss_credential", apiThird.AliOSSCredential)
+ thirdGroup.POST("/minio_storage_credential", apiThird.MinioStorageCredential)
+ thirdGroup.POST("/minio_upload", apiThird.MinioUploadFile)
+ }
+ //Message
+ chatGroup := r.Group("/msg")
+ {
+ chatGroup.POST("/newest_seq", apiChat.GetSeq)
+ chatGroup.POST("/send_msg", apiChat.SendMsg)
+ chatGroup.POST("/pull_msg_by_seq", apiChat.PullMsgBySeqList)
+ chatGroup.POST("/del_msg", apiChat.DelMsg)
+ }
+ //Manager
+ managementGroup := r.Group("/manager")
+ {
+ managementGroup.POST("/delete_user", manage.DeleteUser) //1
+ managementGroup.POST("/send_msg", manage.ManagementSendMsg)
+ managementGroup.POST("/get_all_users_uid", manage.GetAllUsersUid) //1
+ managementGroup.POST("/account_check", manage.AccountCheck) //1
+ managementGroup.POST("/get_users_online_status", manage.GetUsersOnlineStatus) //1
+ }
+ //Conversation
+ conversationGroup := r.Group("/conversation")
+ { //1
+ conversationGroup.POST("/get_all_conversations", conversation.GetAllConversations)
+ conversationGroup.POST("/get_conversation", conversation.GetConversation)
+ conversationGroup.POST("/get_conversations", conversation.GetConversations)
+ conversationGroup.POST("/set_conversation", conversation.SetConversation)
+ conversationGroup.POST("/batch_set_conversation", conversation.BatchSetConversations)
+ conversationGroup.POST("/set_recv_msg_opt", conversation.SetRecvMsgOpt)
+ }
+ // office
+ officeGroup := r.Group("/office")
+ {
+ officeGroup.POST("/get_user_tags", office.GetUserTags)
+ officeGroup.POST("/get_user_tag_by_id", office.GetUserTagByID)
+ officeGroup.POST("/create_tag", office.CreateTag)
+ officeGroup.POST("/delete_tag", office.DeleteTag)
+ officeGroup.POST("/set_tag", office.SetTag)
+ officeGroup.POST("/send_msg_to_tag", office.SendMsg2Tag)
+ officeGroup.POST("/get_send_tag_log", office.GetTagSendLogs)
+
+ officeGroup.POST("/create_one_work_moment", office.CreateOneWorkMoment)
+ officeGroup.POST("/delete_one_work_moment", office.DeleteOneWorkMoment)
+ officeGroup.POST("/like_one_work_moment", office.LikeOneWorkMoment)
+ officeGroup.POST("/comment_one_work_moment", office.CommentOneWorkMoment)
+ officeGroup.POST("/get_user_work_moments", office.GetUserWorkMoments)
+ officeGroup.POST("/get_user_friend_work_moments", office.GetUserFriendWorkMoments)
+ officeGroup.POST("/get_user_work_moments_comments_msg", office.GetUserWorkMomentsCommentsMsg)
+ officeGroup.POST("/clear_user_work_moments_comments_msg", office.ClearUserWorkMomentsCommentsMsg)
+ officeGroup.POST("/set_user_work_moments_level", office.SetUserWorkMomentsLevel)
+ }
+
+ organizationGroup := r.Group("/organization")
+ {
+ organizationGroup.POST("/create_department", organization.CreateDepartment)
+ organizationGroup.POST("/update_department", organization.UpdateDepartment)
+ organizationGroup.POST("/get_sub_department", organization.GetSubDepartment)
+ organizationGroup.POST("/delete_department", organization.DeleteDepartment)
+
+ organizationGroup.POST("/create_organization_user", organization.CreateOrganizationUser)
+ organizationGroup.POST("/update_organization_user", organization.UpdateOrganizationUser)
+ organizationGroup.POST("/create_department_member", organization.CreateDepartmentMember)
+
+ organizationGroup.POST("/get_user_in_department", organization.GetUserInDepartment)
+ organizationGroup.POST("/update_user_In_department", organization.UpdateUserInDepartment)
+ organizationGroup.POST("/delete_organization_user", organization.DeleteOrganizationUser)
+ organizationGroup.POST("/get_department_member", organization.GetDepartmentMember)
+ organizationGroup.POST("/delete_user_in_department", organization.DeleteUserInDepartment)
+ }
+
+ go apiThird.MinioInit()
+ ginPort := flag.Int("port", 10000, "get ginServerPort from cmd,default 10000 as port")
+ flag.Parse()
+ fmt.Println("start api server, port: ", *ginPort)
+ err := r.Run(":" + strconv.Itoa(*ginPort))
+ if err != nil {
+ log.Error("", "run failed ", *ginPort, err.Error())
+ }
+}
diff --git a/cmd/open_im_cms_api/Makefile b/cmd/open_im_cms_api/Makefile
new file mode 100644
index 000000000..31304542d
--- /dev/null
+++ b/cmd/open_im_cms_api/Makefile
@@ -0,0 +1,25 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_cms_api
+BIN_DIR=../../bin/
+
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
diff --git a/cmd/open_im_cms_api/main.go b/cmd/open_im_cms_api/main.go
new file mode 100644
index 000000000..262020fea
--- /dev/null
+++ b/cmd/open_im_cms_api/main.go
@@ -0,0 +1,17 @@
+package main
+
+import (
+ "Open_IM/internal/cms_api"
+ "Open_IM/pkg/utils"
+ "fmt"
+
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ gin.SetMode(gin.ReleaseMode)
+ router := cms_api.NewGinRouter()
+ router.Use(utils.CorsHandler())
+ fmt.Println("start cms api server, port: ", 8000)
+ router.Run(":" + "8000")
+}
diff --git a/cmd/open_im_demo/Makefile b/cmd/open_im_demo/Makefile
new file mode 100644
index 000000000..45829acd8
--- /dev/null
+++ b/cmd/open_im_demo/Makefile
@@ -0,0 +1,25 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_demo
+BIN_DIR=../../bin/
+
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
diff --git a/cmd/open_im_demo/main.go b/cmd/open_im_demo/main.go
new file mode 100644
index 000000000..af7809547
--- /dev/null
+++ b/cmd/open_im_demo/main.go
@@ -0,0 +1,42 @@
+package main
+
+import (
+ "Open_IM/internal/demo/register"
+ "Open_IM/pkg/utils"
+ "flag"
+ "fmt"
+ "io"
+ "os"
+ "strconv"
+
+ "Open_IM/pkg/common/constant"
+ "Open_IM/pkg/common/log"
+ "github.com/gin-gonic/gin"
+)
+
+func main() {
+ log.NewPrivateLog(constant.LogFileName)
+ gin.SetMode(gin.ReleaseMode)
+ f, _ := os.Create("../logs/api.log")
+ gin.DefaultWriter = io.MultiWriter(f)
+
+ r := gin.Default()
+ r.Use(utils.CorsHandler())
+
+ authRouterGroup := r.Group("/auth")
+ {
+ authRouterGroup.POST("/code", register.SendVerificationCode)
+ authRouterGroup.POST("/verify", register.Verify)
+ authRouterGroup.POST("/password", register.SetPassword)
+ authRouterGroup.POST("/login", register.Login)
+ authRouterGroup.POST("/reset_password", register.ResetPassword)
+ }
+
+ ginPort := flag.Int("port", 42233, "get ginServerPort from cmd,default 42233 as port")
+ flag.Parse()
+ fmt.Println("start demo api server, port: ", *ginPort)
+ err := r.Run(":" + strconv.Itoa(*ginPort))
+ if err != nil {
+ log.Error("", "run failed ", *ginPort, err.Error())
+ }
+}
diff --git a/cmd/open_im_msg_gateway/Makefile b/cmd/open_im_msg_gateway/Makefile
new file mode 100644
index 000000000..566d8d9c2
--- /dev/null
+++ b/cmd/open_im_msg_gateway/Makefile
@@ -0,0 +1,24 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_msg_gateway
+BIN_DIR=../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
diff --git a/cmd/open_im_msg_gateway/main.go b/cmd/open_im_msg_gateway/main.go
new file mode 100644
index 000000000..283f831b8
--- /dev/null
+++ b/cmd/open_im_msg_gateway/main.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ "Open_IM/internal/msg_gateway/gate"
+ "Open_IM/pkg/common/constant"
+ "Open_IM/pkg/common/log"
+ "flag"
+ "fmt"
+ "sync"
+)
+
+func main() {
+ log.NewPrivateLog(constant.LogFileName)
+ rpcPort := flag.Int("rpc_port", 10400, "rpc listening port")
+ wsPort := flag.Int("ws_port", 17778, "ws listening port")
+ flag.Parse()
+ var wg sync.WaitGroup
+ wg.Add(1)
+ fmt.Println("start rpc/msg_gateway server, port: ", *rpcPort, *wsPort)
+ gate.Init(*rpcPort, *wsPort)
+ gate.Run()
+ wg.Wait()
+}
diff --git a/cmd/open_im_msg_transfer/Makefile b/cmd/open_im_msg_transfer/Makefile
new file mode 100644
index 000000000..6a67b9bee
--- /dev/null
+++ b/cmd/open_im_msg_transfer/Makefile
@@ -0,0 +1,25 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_msg_transfer
+BIN_DIR=../../bin/
+
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
diff --git a/cmd/open_im_msg_transfer/main.go b/cmd/open_im_msg_transfer/main.go
new file mode 100644
index 000000000..9dfabecda
--- /dev/null
+++ b/cmd/open_im_msg_transfer/main.go
@@ -0,0 +1,19 @@
+package main
+
+import (
+ "Open_IM/internal/msg_transfer/logic"
+ "Open_IM/pkg/common/constant"
+ "Open_IM/pkg/common/log"
+ "fmt"
+ "sync"
+)
+
+func main() {
+ var wg sync.WaitGroup
+ wg.Add(1)
+ log.NewPrivateLog(constant.LogFileName)
+ logic.Init()
+ fmt.Println("start msg_transfer server")
+ logic.Run()
+ wg.Wait()
+}
diff --git a/cmd/open_im_push/Makefile b/cmd/open_im_push/Makefile
new file mode 100644
index 000000000..df7b9e88a
--- /dev/null
+++ b/cmd/open_im_push/Makefile
@@ -0,0 +1,25 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_push
+BIN_DIR=../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
+
diff --git a/cmd/open_im_push/main.go b/cmd/open_im_push/main.go
new file mode 100644
index 000000000..95e8521dd
--- /dev/null
+++ b/cmd/open_im_push/main.go
@@ -0,0 +1,22 @@
+package main
+
+import (
+ "Open_IM/internal/push/logic"
+ "Open_IM/pkg/common/constant"
+ "Open_IM/pkg/common/log"
+ "flag"
+ "fmt"
+ "sync"
+)
+
+func main() {
+ rpcPort := flag.Int("port", 10700, "rpc listening port")
+ flag.Parse()
+ var wg sync.WaitGroup
+ wg.Add(1)
+ log.NewPrivateLog(constant.LogFileName)
+ fmt.Println("start push rpc server, port: ", *rpcPort)
+ logic.Init(*rpcPort)
+ logic.Run()
+ wg.Wait()
+}
diff --git a/cmd/open_im_timer_task/Makefile b/cmd/open_im_timer_task/Makefile
new file mode 100644
index 000000000..6c6d713d8
--- /dev/null
+++ b/cmd/open_im_timer_task/Makefile
@@ -0,0 +1,25 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_timer_task
+BIN_DIR=../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
+
diff --git a/cmd/open_im_timer_task/main.go b/cmd/open_im_timer_task/main.go
new file mode 100644
index 000000000..20d9a4d1c
--- /dev/null
+++ b/cmd/open_im_timer_task/main.go
@@ -0,0 +1,63 @@
+package main
+
+import (
+ "Open_IM/pkg/common/constant"
+ "Open_IM/pkg/common/log"
+)
+
+func main() {
+ log.NewPrivateLog(constant.LogFileName)
+ //for {
+ // fmt.Println("start delete mongodb expired record")
+ // timeUnixBegin := time.Now().Unix()
+ // count, _ := db.DB.MgoUserCount()
+ // fmt.Println("mongodb record count: ", count)
+ // for i := 0; i < count; i++ {
+ // time.Sleep(1 * time.Millisecond)
+ // uid, _ := db.DB.MgoSkipUID(i)
+ // fmt.Println("operate uid: ", uid)
+ // err := db.DB.DelUserChat(uid)
+ // if err != nil {
+ // fmt.Println("operate uid failed: ", uid, err.Error())
+ // }
+ // }
+ //
+ // timeUnixEnd := time.Now().Unix()
+ // costTime := timeUnixEnd - timeUnixBegin
+ // if costTime > int64(config.Config.Mongo.DBRetainChatRecords*24*3600) {
+ // continue
+ // } else {
+ // sleepTime := 0
+ // if int64(config.Config.Mongo.DBRetainChatRecords*24*3600)-costTime > 24*3600 {
+ // sleepTime = 24 * 3600
+ // } else {
+ // sleepTime = config.Config.Mongo.DBRetainChatRecords*24*3600 - int(costTime)
+ // }
+ // fmt.Println("sleep: ", sleepTime)
+ // time.Sleep(time.Duration(sleepTime) * time.Second)
+ // }
+ //}
+ //for {
+ // uidList, err := im_mysql_model.SelectAllUserID()
+ // if err != nil {
+ // //log.NewError("999999", err.Error())
+ // } else {
+ // for _, v := range uidList {
+ // minSeq, err := commonDB.DB.GetMinSeqFromMongo(v)
+ // if err != nil {
+ // //log.NewError("999999", "get user minSeq err", err.Error(), v)
+ // continue
+ // } else {
+ // err := commonDB.DB.SetUserMinSeq(v, minSeq)
+ // if err != nil {
+ // //log.NewError("999999", "set user minSeq err", err.Error(), v)
+ // }
+ // }
+ // time.Sleep(time.Duration(100) * time.Millisecond)
+ // }
+ //
+ // }
+ //
+ //}
+
+}
diff --git a/cmd/rpc/open_im_admin_cms/Makefile b/cmd/rpc/open_im_admin_cms/Makefile
new file mode 100644
index 000000000..f6efc1c08
--- /dev/null
+++ b/cmd/rpc/open_im_admin_cms/Makefile
@@ -0,0 +1,23 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_admin_cms
+BIN_DIR=../../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
diff --git a/cmd/rpc/open_im_admin_cms/main.go b/cmd/rpc/open_im_admin_cms/main.go
new file mode 100644
index 000000000..c69135645
--- /dev/null
+++ b/cmd/rpc/open_im_admin_cms/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ rpcMessageCMS "Open_IM/internal/rpc/admin_cms"
+ "flag"
+ "fmt"
+)
+
+func main() {
+ rpcPort := flag.Int("port", 11000, "rpc listening port")
+ flag.Parse()
+ fmt.Println("start cms rpc server, port: ", *rpcPort)
+ rpcServer := rpcMessageCMS.NewAdminCMSServer(*rpcPort)
+ rpcServer.Run()
+}
diff --git a/cmd/rpc/open_im_auth/Makefile b/cmd/rpc/open_im_auth/Makefile
new file mode 100644
index 000000000..da280c3b5
--- /dev/null
+++ b/cmd/rpc/open_im_auth/Makefile
@@ -0,0 +1,24 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_auth
+BIN_DIR=../../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
diff --git a/cmd/rpc/open_im_auth/main.go b/cmd/rpc/open_im_auth/main.go
new file mode 100644
index 000000000..53b0f9de5
--- /dev/null
+++ b/cmd/rpc/open_im_auth/main.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+ rpcAuth "Open_IM/internal/rpc/auth"
+ "flag"
+ "fmt"
+)
+
+func main() {
+ rpcPort := flag.Int("port", 10600, "RpcToken default listen port 10800")
+ flag.Parse()
+ fmt.Println("start auth rpc server, port: ", *rpcPort)
+ rpcServer := rpcAuth.NewRpcAuthServer(*rpcPort)
+ rpcServer.Run()
+
+}
diff --git a/cmd/rpc/open_im_friend/Makefile b/cmd/rpc/open_im_friend/Makefile
new file mode 100644
index 000000000..c47cbcf1d
--- /dev/null
+++ b/cmd/rpc/open_im_friend/Makefile
@@ -0,0 +1,25 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_friend
+BIN_DIR=../../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
+
diff --git a/cmd/rpc/open_im_friend/main.go b/cmd/rpc/open_im_friend/main.go
new file mode 100644
index 000000000..85d05e39c
--- /dev/null
+++ b/cmd/rpc/open_im_friend/main.go
@@ -0,0 +1,16 @@
+package main
+
+import (
+ "Open_IM/internal/rpc/friend"
+ "flag"
+ "fmt"
+)
+
+func main() {
+
+ rpcPort := flag.Int("port", 10200, "get RpcFriendPort from cmd,default 12000 as port")
+ flag.Parse()
+ fmt.Println("start friend rpc server, port: ", *rpcPort)
+ rpcServer := friend.NewFriendServer(*rpcPort)
+ rpcServer.Run()
+}
diff --git a/cmd/rpc/open_im_group/Makefile b/cmd/rpc/open_im_group/Makefile
new file mode 100644
index 000000000..e7d1cb29d
--- /dev/null
+++ b/cmd/rpc/open_im_group/Makefile
@@ -0,0 +1,25 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_group
+BIN_DIR=../../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
+
diff --git a/cmd/rpc/open_im_group/main.go b/cmd/rpc/open_im_group/main.go
new file mode 100644
index 000000000..7afc7ec57
--- /dev/null
+++ b/cmd/rpc/open_im_group/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "Open_IM/internal/rpc/group"
+ "flag"
+ "fmt"
+)
+
+func main() {
+ rpcPort := flag.Int("port", 10500, "get RpcGroupPort from cmd,default 16000 as port")
+ flag.Parse()
+ fmt.Println("start group rpc server, port: ", *rpcPort)
+ rpcServer := group.NewGroupServer(*rpcPort)
+ rpcServer.Run()
+}
diff --git a/cmd/rpc/open_im_message_cms/Makefile b/cmd/rpc/open_im_message_cms/Makefile
new file mode 100644
index 000000000..4ac4cba3a
--- /dev/null
+++ b/cmd/rpc/open_im_message_cms/Makefile
@@ -0,0 +1,23 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_message_cms
+BIN_DIR=../../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
diff --git a/cmd/rpc/open_im_message_cms/main.go b/cmd/rpc/open_im_message_cms/main.go
new file mode 100644
index 000000000..16151a2ed
--- /dev/null
+++ b/cmd/rpc/open_im_message_cms/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ rpcMessageCMS "Open_IM/internal/rpc/message_cms"
+ "flag"
+ "fmt"
+)
+
+func main() {
+ rpcPort := flag.Int("port", 10900, "rpc listening port")
+ flag.Parse()
+ fmt.Println("start msg cms rpc server, port: ", *rpcPort)
+ rpcServer := rpcMessageCMS.NewMessageCMSServer(*rpcPort)
+ rpcServer.Run()
+}
diff --git a/cmd/rpc/open_im_msg/Makefile b/cmd/rpc/open_im_msg/Makefile
new file mode 100644
index 000000000..7214d3fe1
--- /dev/null
+++ b/cmd/rpc/open_im_msg/Makefile
@@ -0,0 +1,23 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_msg
+BIN_DIR=../../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
diff --git a/cmd/rpc/open_im_msg/main.go b/cmd/rpc/open_im_msg/main.go
new file mode 100644
index 000000000..95908f48a
--- /dev/null
+++ b/cmd/rpc/open_im_msg/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ rpcChat "Open_IM/internal/rpc/msg"
+ "flag"
+ "fmt"
+)
+
+func main() {
+ rpcPort := flag.Int("port", 10300, "rpc listening port")
+ flag.Parse()
+ fmt.Println("start msg rpc server, port: ", *rpcPort)
+ rpcServer := rpcChat.NewRpcChatServer(*rpcPort)
+ rpcServer.Run()
+}
diff --git a/cmd/rpc/open_im_office/Makefile b/cmd/rpc/open_im_office/Makefile
new file mode 100644
index 000000000..b86230c64
--- /dev/null
+++ b/cmd/rpc/open_im_office/Makefile
@@ -0,0 +1,23 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_office
+BIN_DIR=../../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
diff --git a/cmd/rpc/open_im_office/main.go b/cmd/rpc/open_im_office/main.go
new file mode 100644
index 000000000..c9a05c791
--- /dev/null
+++ b/cmd/rpc/open_im_office/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ rpc "Open_IM/internal/rpc/office"
+ "flag"
+ "fmt"
+)
+
+func main() {
+ rpcPort := flag.Int("port", 11100, "rpc listening port")
+ flag.Parse()
+ fmt.Println("start office rpc server, port: ", *rpcPort)
+ rpcServer := rpc.NewOfficeServer(*rpcPort)
+ rpcServer.Run()
+}
diff --git a/cmd/rpc/open_im_organization/Makefile b/cmd/rpc/open_im_organization/Makefile
new file mode 100644
index 000000000..77d658db9
--- /dev/null
+++ b/cmd/rpc/open_im_organization/Makefile
@@ -0,0 +1,25 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_organization
+BIN_DIR=../../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
+
diff --git a/cmd/rpc/open_im_organization/main.go b/cmd/rpc/open_im_organization/main.go
new file mode 100644
index 000000000..906ac510f
--- /dev/null
+++ b/cmd/rpc/open_im_organization/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "Open_IM/internal/rpc/organization"
+ "flag"
+ "fmt"
+)
+
+func main() {
+ rpcPort := flag.Int("port", 11200, "get RpcOrganizationPort from cmd,default 11200 as port")
+ flag.Parse()
+ fmt.Println("start organization rpc server, port: ", *rpcPort)
+ rpcServer := organization.NewServer(*rpcPort)
+ rpcServer.Run()
+}
diff --git a/cmd/rpc/open_im_statistics/Makefile b/cmd/rpc/open_im_statistics/Makefile
new file mode 100644
index 000000000..37dbb3efe
--- /dev/null
+++ b/cmd/rpc/open_im_statistics/Makefile
@@ -0,0 +1,23 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_statistics
+BIN_DIR=../../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
diff --git a/cmd/rpc/open_im_statistics/main.go b/cmd/rpc/open_im_statistics/main.go
new file mode 100644
index 000000000..b87440abf
--- /dev/null
+++ b/cmd/rpc/open_im_statistics/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "Open_IM/internal/rpc/statistics"
+ "flag"
+ "fmt"
+)
+
+func main() {
+ rpcPort := flag.Int("port", 10800, "rpc listening port")
+ flag.Parse()
+ fmt.Println("start statistics rpc server, port: ", *rpcPort)
+ rpcServer := statistics.NewStatisticsServer(*rpcPort)
+ rpcServer.Run()
+}
diff --git a/cmd/rpc/open_im_user/Makefile b/cmd/rpc/open_im_user/Makefile
new file mode 100644
index 000000000..c379db284
--- /dev/null
+++ b/cmd/rpc/open_im_user/Makefile
@@ -0,0 +1,25 @@
+.PHONY: all build run gotool install clean help
+
+BINARY_NAME=open_im_user
+BIN_DIR=../../../bin/
+
+all: gotool build
+
+build:
+ CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s"
+
+run:
+ @go run ./
+
+gotool:
+ go fmt ./
+ go vet ./
+
+install:
+ make build
+ mv ${BINARY_NAME} ${BIN_DIR}
+
+clean:
+ @if [ -f ${BINARY_NAME} ] ; then rm ${BINARY_NAME} ; fi
+
+
diff --git a/cmd/rpc/open_im_user/main.go b/cmd/rpc/open_im_user/main.go
new file mode 100644
index 000000000..d8644d028
--- /dev/null
+++ b/cmd/rpc/open_im_user/main.go
@@ -0,0 +1,15 @@
+package main
+
+import (
+ "Open_IM/internal/rpc/user"
+ "flag"
+ "fmt"
+)
+
+func main() {
+ rpcPort := flag.Int("port", 10100, "rpc listening port")
+ flag.Parse()
+ fmt.Println("start user rpc server, port: ", *rpcPort)
+ rpcServer := user.NewUserServer(*rpcPort)
+ rpcServer.Run()
+}
diff --git a/cmd/test/main.go b/cmd/test/main.go
new file mode 100644
index 000000000..87d5165f7
--- /dev/null
+++ b/cmd/test/main.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+ "Open_IM/pkg/utils"
+ "context"
+ "fmt"
+ "go.mongodb.org/mongo-driver/bson"
+ "go.mongodb.org/mongo-driver/mongo"
+ "go.mongodb.org/mongo-driver/mongo/options"
+ "time"
+)
+
+type MongoMsg struct {
+ UID string
+ Msg []string
+}
+
+
+func main() {
+ //"mongodb://%s:%s@%s/%s/?maxPoolSize=%d"
+ uri := "mongodb://user:pass@sample.host:27017/?maxPoolSize=20&w=majority"
+ DBAddress := "127.0.0.1:37017"
+ DBDatabase := "new-test-db"
+ Collection := "new-test-collection"
+ DBMaxPoolSize := 100
+ uri = fmt.Sprintf("mongodb://%s/%s/?maxPoolSize=%d",
+ DBAddress,DBDatabase,
+ DBMaxPoolSize)
+
+ mongoClient, err := mongo.Connect(context.TODO(), options.Client().ApplyURI(uri))
+ if err != nil {
+ panic(err)
+ }
+ filter := bson.M{"uid":"my_uid"}
+ ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
+ for i:=0; i < 2; i++{
+
+ if err = mongoClient.Database(DBDatabase).Collection(Collection).FindOneAndUpdate(ctx, filter,
+ bson.M{"$push": bson.M{"msg": utils.Int32ToString(int32(i))}}).Err(); err != nil{
+ fmt.Println("FindOneAndUpdate failed ", i, )
+ var mmsg MongoMsg
+ mmsg.UID = "my_uid"
+ mmsg.Msg = append(mmsg.Msg, utils.Int32ToString(int32(i)))
+ _, err := mongoClient.Database(DBDatabase).Collection(Collection).InsertOne(ctx, &mmsg)
+ if err != nil {
+ fmt.Println("insertone failed ", err.Error(), i)
+ } else{
+ fmt.Println("insertone ok ", i)
+ }
+
+ }else {
+ fmt.Println("FindOneAndUpdate ok ", i)
+ }
+
+ }
+
+ var mmsg MongoMsg
+
+ if err = mongoClient.Database(DBDatabase).Collection(Collection).FindOne(ctx, filter).Decode(&mmsg); err != nil {
+ fmt.Println("findone failed ", err.Error())
+ }else{
+ fmt.Println("findone ok ", mmsg.UID)
+ for i, v:=range mmsg.Msg{
+ fmt.Println("find value: ", i, v)
+ }
+ }
+
+
+}
diff --git a/config/config.yaml b/config/config.yaml
new file mode 100644
index 000000000..d61292bdc
--- /dev/null
+++ b/config/config.yaml
@@ -0,0 +1,609 @@
+# The class cannot be named by Pascal or camel case.
+# If it is not used, the corresponding structure will not be set,
+# and it will not be read naturally.
+serverversion: 2.0.0
+#---------------Infrastructure configuration---------------------#
+etcd:
+ etcdSchema: openIM #默认即可
+ etcdAddr: [ 127.0.0.1:2379 ] #单机部署时,默认即可
+
+mysql:
+ dbMysqlAddress: [ 127.0.0.1:13306 ] #mysql地址 目前仅支持单机,默认即可
+ dbMysqlUserName: root #mysql用户名,建议修改
+ dbMysqlPassword: openIM # mysql密码,建议修改
+ dbMysqlDatabaseName: openIM_v2 #默认即可
+ dbTableName: eMsg #默认即可
+ dbMsgTableNum: 1
+ dbMaxOpenConns: 20
+ dbMaxIdleConns: 10
+ dbMaxLifeTime: 120
+
+mongo:
+ dbUri: ""#当dbUri值不为空则直接使用该值
+ dbAddress: [ 127.0.0.1:37017 ] #mongo地址 目前仅支持单机,默认即可
+ dbDirect: false
+ dbTimeout: 10
+ dbDatabase: openIM #mongo db 默认即可
+ dbSource: admin
+ dbUserName: #mongo用户名,建议先不设置
+ dbPassword: #mongo密码,建议先不设置
+ dbMaxPoolSize: 20
+ dbRetainChatRecords: 3650 #mongo保存离线消息时间(天),根据需求修改
+
+redis:
+ dbAddress: 127.0.0.1:16379 #redis地址 目前仅支持单机,默认即可
+ dbMaxIdle: 128
+ dbMaxActive: 0
+ dbIdleTimeout: 120
+ dbPassWord: openIM #redis密码 建议修改
+
+kafka:
+ ws2mschat:
+ addr: [ 127.0.0.1:9092 ] #kafka配置,默认即可
+ topic: "ws2ms_chat"
+ ms2pschat:
+ addr: [ 127.0.0.1:9092 ] #kafka配置,默认即可
+ topic: "ms2ps_chat"
+ consumergroupid:
+ msgToMongo: mongo
+ msgToMySql: mysql
+ msgToPush: push
+
+
+
+#---------------Internal service configuration---------------------#
+
+# The service ip default is empty,
+# automatically obtain the machine's valid network card ip as the service ip,
+# otherwise the configuration ip is preferred
+#如果是单机模式,用0.0.0.0或者不填,默认即可
+serverip: 0.0.0.0
+
+# endpoints 内部组件间访问的端点host名称,访问时,可以内部直接访问 host:port 来访问
+endpoints:
+ api: openim_api
+ cmsapi: openim_cms_api
+ push: openim_push
+ msg_gateway: openim_msg_gateway
+ rpc_auth: openim_rpc_auth
+ rpc_friend: openim_rpc_friend
+ rpc_group: openim_rpc_group
+ rpc_msg: openim_rpc_msg
+ rpc_user: openim_rpc_user
+ rpc_statistic: openim_rpc_statistic
+ rpc_admin_cms: openim_rpc_admin_cms
+ rpc_message_cms: openim_rpc_admin_cms
+ rpc_office: openim_rpc_office
+
+api:
+ openImApiPort: [ 10000 ] #api服务端口,默认即可,需要开放此端口或做nginx转发
+cmsapi:
+ openImCmsApiPort: [ 8000 ] #管理后台api服务端口,默认即可,需要开放此端口或做nginx转发
+sdk:
+ openImSdkWsPort: [ 30000 ] #jssdk服务端口,默认即可,项目中使用jssdk才需开放此端口或做nginx转发
+#对象存储服务,以下配置二选一,目前支持两种,腾讯云和minio,二者配置好其中一种即可(如果使用minio参考https://doc.rentsoft.cn/#/qa/minio搭建minio服务器)
+credential: #腾讯cos,发送图片、视频、文件时需要,请自行申请后替换,必须修改
+ tencent:
+ appID: 1302656840
+ region: ap-chengdu
+ bucket: echat-1302656840
+ secretID: AKIDGNYVChzIQinu7QEgtNp0hnNgqcV8vZTC
+ secretKey: kz15vW83qM6dBUWIq681eBZA0c0vlIbe
+ minio: #MinIO 发送图片、视频、文件时需要,请自行申请后替换,必须修改。 客户端初始化InitSDK,中 object_storage参数为minio
+ bucket: openim
+ location: us-east-1
+ endpoint: http://127.0.0.1:9000
+ endpointInner: http://127.0.0.1:9000 #minio内网地址
+ endpointInnerEnable: true #是否启用minio内网地址 启用可以让桶初始化,IM server连接minio走内网地址访问
+ accessKeyID: user12345
+ secretAccessKey: key12345
+ ali: # ali oss
+ regionID: "oss-cn-beijing"
+ accessKeyID: ""
+ accessKeySecret: ""
+ stsEndpoint: "sts.cn-beijing.aliyun.com"
+ ossEndpoint: "oss-cn-beijing.aliyuncs.com"
+ bucket: "bucket1"
+ finalHost: "http://bucket1.oss-cn-beijing.aliyuncs.com"
+ stsDurationSeconds: 3600
+ OssRoleArn: "acs:ram::xxx:role/xxx"
+
+
+
+rpcport: #rpc服务端口 默认即可
+ openImUserPort: [ 10100 ]
+ openImFriendPort: [ 10200 ]
+ openImOfflineMessagePort: [ 10300 ]
+ openImOnlineRelayPort: [ 10400 ]
+ openImGroupPort: [ 10500 ]
+ openImAuthPort: [ 10600 ]
+ openImPushPort: [ 10700 ]
+ openImStatisticsPort: [ 10800 ]
+ openImMessageCmsPort: [ 10900 ]
+ openImAdminCmsPort: [ 11000 ]
+ openImOfficePort: [ 11100 ]
+ openImOrganizationPort: [ 11200 ]
+ c2c:
+ callbackBeforeSendMsg:
+ switch: false
+ timeoutStrategy: 1 #1:send
+ callbackAfterSendMsg:
+ switch: false
+ state:
+ stateChange:
+ switch: false
+
+rpcregistername: #rpc注册服务名,默认即可
+ openImUserName: User
+ openImFriendName: Friend
+ openImOfflineMessageName: OfflineMessage
+ openImPushName: Push
+ openImOnlineMessageRelayName: OnlineMessageRelay
+ openImGroupName: Group
+ openImAuthName: Auth
+ OpenImStatisticsName: Statistics
+ OpenImMessageCMSName: MessageCMS
+ openImAdminCMSName: AdminCMS
+ openImOfficeName: Office
+ openImOrganizationName: Organization
+
+log:
+ storageLocation: ../logs/
+ rotationTime: 24
+ remainRotationCount: 3 #日志数量
+ #日志级别 6表示全都打印,测试阶段建议设置为6
+ remainLogLevel: 6
+ elasticSearchSwitch: false
+ elasticSearchAddr: [ 127.0.0.1:9201 ]
+ elasticSearchUser: ""
+ elasticSearchPassword: ""
+
+modulename: #日志文件按模块命名,默认即可
+ longConnSvrName: msg_gateway
+ msgTransferName: msg_transfer
+ pushName: push
+
+longconnsvr:
+ openImWsPort: [ 17778 ] # ws服务端口,默认即可,要开放此端口或做nginx转发
+ websocketMaxConnNum: 10000
+ websocketMaxMsgLen: 4096
+ websocketTimeOut: 10
+
+## 推送只能开启一个 enable代表开启
+push:
+ tpns: #腾讯推送,暂未测试 暂不要使用
+ ios:
+ accessID: 1600018281
+ secretKey: 3cd68a77a95b89e5089a1aca523f318f
+ android:
+ accessID: 111
+ secretKey: 111
+ enable: false
+ jpns: #极光推送 在极光后台申请后,修改以下四项,必须修改
+ appKey: cf47465a368f24c659608e7e
+ masterSecret: 02204efe3f3832947a236ee5
+ pushUrl: "https://api.jpush.cn/v3/push"
+ pushIntent: "intent:#Intent;component=io.openim.app.enterprisechat/io.openim.app.enterprisechat.MainActivity;end"
+ enable: true
+ getui: #个推推送,暂未测试 暂不要使用
+ pushUrl: "https://restapi.getui.com/v2/$appId"
+ masterSecret: ""
+ appKey: ""
+ intent: ""
+ enable: false
+
+
+
+manager:
+ #app管理员userID和对应的secret 建议修改。 用于管理后台登录,也可以用户管理后台对应的api
+ appManagerUid: [ "openIM123456","openIM654321", "openIM333", "openIMAdmin"]
+ secrets: [ "openIM1","openIM2", "openIM333", "openIMAdmin"]
+
+secret: tuoyun
+# 多端互踢策略
+# 1:多平台登录:Android、iOS、Windows、Mac 每种平台只能一个在线,web端可以多个同时在线
+multiloginpolicy: 1
+
+#token config
+tokenpolicy:
+ accessSecret: "open_im_server" #token生成相关,默认即可
+ # Token effective time day as a unit
+ accessExpire: 3650 #token过期时间(天) 默认即可
+messageverify:
+ friendVerify: false
+
+# c2c:
+# callbackBeforeSendMsg:
+# switch: false
+# timeoutStrategy: 1 #1:send
+# callbackAfterSendMsg:
+# switch: false
+# state:
+# stateChange:
+# switch: false
+#ios系统推送声音以及标记计数
+iospush:
+ pushSound: "xxx"
+ badgeCount: true
+
+callback:
+ # callback url 需要自行更换callback url
+ callbackUrl : "http://127.0.0.1:8080/callback"
+ # 开启关闭操作前后回调的配置
+ callbackbeforeSendSingleMsg:
+ enable: false # 回调是否启用
+ callbackTimeOut: 2 # 回调超时时间
+ callbackFailedContinue: true # 回调超时是否继续执行代码
+ callbackAfterSendSingleMsg:
+ enable: false
+ callbackTimeOut: 2
+ callbackBeforeSendGroupMsg:
+ enable: false
+ callbackTimeOut: 2
+ callbackFailedContinue: true
+ callbackAfterSendGroupMsg:
+ enable: false
+ callbackTimeOut: 2
+ callbackWordFilter:
+ enable: false
+ callbackTimeOut: 2
+ callbackFailedContinue: true
+
+
+notification:
+ groupCreated:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: true
+ title: "create group title" # xx create the group
+ desc: "create group desc"
+ ext: "create group ext"
+ defaultTips:
+ tips: "create the group" # xx create the group
+
+ groupInfoSet:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "groupInfoSet title"
+ desc: "groupInfoSet desc"
+ ext: "groupInfoSet ext"
+ defaultTips:
+ tips: "modified the group profile" # group info changed by xx
+
+ joinGroupApplication:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: false
+ title: "joinGroupApplication title"
+ desc: "joinGroupApplication desc"
+ ext: "joinGroupApplication ext"
+ defaultTips:
+ tips: "apply to join the group" # group info changed by xx
+
+ memberQuit:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "memberQuit title"
+ desc: "memberQuit desc"
+ ext: "memberQuit ext"
+ defaultTips:
+ tips: "quit group chat" # group info changed by xx
+
+ groupApplicationAccepted:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "groupApplicationAccepted title"
+ desc: "groupApplicationAccepted desc"
+ ext: "groupApplicationAccepted ext"
+ defaultTips:
+ tips: "was allowed to join the group" # group info changed by xx
+
+ groupApplicationRejected:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: false
+ title: " title"
+ desc: " desc"
+ ext: " ext"
+ defaultTips:
+ tips: "was rejected into the group" # group info changed by xx
+
+ groupOwnerTransferred:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "groupOwnerTransferred title"
+ desc: "groupOwnerTransferred desc"
+ ext: "groupOwnerTransferred ext"
+ defaultTips:
+ tips: "become a new group owner" # group info changed by xx
+
+ memberKicked:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "memberKicked title"
+ desc: "memberKicked desc"
+ ext: "memberKicked ext"
+ defaultTips:
+ tips: "was kicked out of the group" # group info changed by xx
+
+ memberInvited:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "memberInvited title"
+ desc: "memberInvited desc"
+ ext: "memberInvited ext"
+ defaultTips:
+ tips: "was invited into the group" # group info changed by xx
+
+ memberEnter:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "memberEnter title"
+ desc: "memberEnter desc"
+ ext: "memberEnter ext"
+ defaultTips:
+ tips: "entered the group" # group info changed by xx
+
+ groupDismissed:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "groupDismissed title"
+ desc: "groupDismissed desc"
+ ext: "groupDismissed ext"
+ defaultTips:
+ tips: "group dismissed"
+
+
+ groupMuted:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "groupMuted title"
+ desc: "groupMuted desc"
+ ext: "groupMuted ext"
+ defaultTips:
+ tips: "group Muted"
+
+ groupCancelMuted:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "groupCancelMuted title"
+ desc: "groupCancelMuted desc"
+ ext: "groupCancelMuted ext"
+ defaultTips:
+ tips: "group Cancel Muted"
+
+
+ groupMemberMuted:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "groupMemberMuted title"
+ desc: "groupMemberMuted desc"
+ ext: "groupMemberMuted ext"
+ defaultTips:
+ tips: "group Member Muted"
+
+ groupMemberCancelMuted:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: false
+ title: "groupMemberCancelMuted title"
+ desc: "groupMemberCancelMuted desc"
+ ext: "groupMemberCancelMuted ext"
+ defaultTips:
+ tips: "group Member Cancel Muted"
+ #############################friend#################################
+
+ friendApplicationAdded:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: true
+ title: "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"
+ defaultTips:
+ tips: "I applies to add you as a friend" #
+
+ friendApplicationApproved:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: true
+ title: "Someone applies to add your friend application"
+ desc: "Someone applies to add your friend application"
+ ext: "Someone applies to add your friend application"
+ defaultTips:
+ tips: "I applies to add your friend application" #
+
+
+ friendApplicationRejected:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: true
+ title: "Someone rejected your friend application"
+ desc: "Someone rejected your friend application"
+ ext: "Someone rejected your friend application"
+ defaultTips:
+ tips: "I rejected your friend application" #
+
+
+
+
+
+ friendAdded:
+ conversation:
+ reliabilityLevel: 3
+ unreadCount: true
+ offlinePush:
+ switch: true
+ title: "We have become friends"
+ desc: "We have become friends"
+ ext: "We have become friends"
+ defaultTips:
+ tips: "We have become friends" #
+
+
+
+ friendDeleted:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: true
+ title: "deleted a friend"
+ desc: "deleted a friend"
+ ext: "deleted a friend"
+ defaultTips:
+ tips: "deleted a friend" #
+
+
+ friendRemarkSet:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: true
+ title: "Your friend's profile has been changed"
+ desc: "Your friend's profile has been changed"
+ ext: "Your friend's profile has been changed"
+ defaultTips:
+ tips: "Your friend's profile has been changed" #
+
+
+
+ blackAdded:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: true
+ title: "blocked a user"
+ desc: "blocked a user"
+ ext: "blocked a user"
+ defaultTips:
+ tips: "blocked a user" #
+
+
+ blackDeleted:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: true
+ title: "Remove a blocked user"
+ desc: "Remove a blocked user"
+ ext: "Remove a blocked user"
+ defaultTips:
+ tips: "Remove a blocked user"
+
+ #####################user#########################
+ userInfoUpdated:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: true
+ title: "Remove a blocked user"
+ desc: "Remove a blocked user"
+ ext: "Remove a blocked user"
+ defaultTips:
+ tips: "remove a blocked user"
+
+ #####################conversation#########################
+ conversationOptUpdate:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: false
+ offlinePush:
+ switch: true
+ title: "conversation opt update"
+ desc: "conversation opt update"
+ ext: "conversation opt update"
+ defaultTips:
+ tips: "conversation opt update"
+
+ conversationSetPrivate:
+ conversation:
+ reliabilityLevel: 2
+ unreadCount: true
+ offlinePush:
+ switch: true
+ title: "burn after reading"
+ desc: "burn after reading"
+ ext: "burn after reading"
+ defaultTips:
+ openTips: "burn after reading was opened"
+ closeTips: "burn after reading was closed"
+
+
+
+#---------------demo configuration---------------------#
+#The following configuration items are applied to openIM Demo configuration
+#是否启动demo,如果自身没有账号体系,设置为true
+demoswitch: true
+demo:
+ #demo对外服务端口,默认即可,需要开放此端口或做nginx转发
+ openImDemoPort: [ 42233 ]
+ alismsverify: #阿里云短信配置,在阿里云申请成功后修改以下四项,必须修改
+ accessKeyId: LTAI5tJPkn4HuuePdiLdGqe7
+ accessKeySecret: 4n9OJ7ZCVN1U6KeHDAtOyNeVZcjOuV
+ signName: 托云信息技术
+ verificationCodeTemplateCode: SMS_226810164
+ superCode: 666666 #超级验证码,建议修改掉,收不到短信验证码时可以用此替代
+ # second
+ codeTTL: 300
+ mail: #仅支持qq邮箱,具体操作参考 https://service.mail.qq.com/cgi-bin/help?subtype=1&id=28&no=1001256 必须修改
+ title: "openIM"
+ senderMail: "765567899@qq.com"
+ senderAuthorizationCode: "gxyausfoevlzbfag"
+ smtpAddr: "smtp.qq.com"
+ smtpPort: 25 #需开放此端口 出口方向
+
+rtc:
+ port: 11300
+ address: 127.0.0.1
\ No newline at end of file
diff --git a/deploy.Dockerfile b/deploy.Dockerfile
new file mode 100644
index 000000000..1738bda80
--- /dev/null
+++ b/deploy.Dockerfile
@@ -0,0 +1,39 @@
+FROM golang as build
+
+# go mod Installation source, container environment variable addition will override the default variable value
+ENV GO111MODULE=on
+ENV GOPROXY=https://goproxy.cn,direct
+
+# Set up the working directory
+WORKDIR /Open-IM-Server
+# add all files to the container
+COPY . .
+
+WORKDIR /Open-IM-Server/script
+RUN chmod +x *.sh
+
+RUN /bin/sh -c ./build_all_service.sh
+
+#Blank image Multi-Stage Build
+FROM ubuntu
+
+RUN rm -rf /var/lib/apt/lists/*
+RUN apt-get update && apt-get install apt-transport-https && apt-get install procps\
+&&apt-get install net-tools
+#Non-interactive operation
+ENV DEBIAN_FRONTEND=noninteractive
+RUN apt-get install -y vim curl tzdata gawk
+#Time zone adjusted to East eighth District
+RUN ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && dpkg-reconfigure -f noninteractive tzdata
+
+
+#set directory to map logs,config file,script file.
+VOLUME ["/Open-IM-Server/logs","/Open-IM-Server/config","/Open-IM-Server/script","/Open-IM-Server/db/sdk"]
+
+#Copy scripts files and binary files to the blank image
+COPY --from=build /Open-IM-Server/script /Open-IM-Server/script
+COPY --from=build /Open-IM-Server/bin /Open-IM-Server/bin
+
+WORKDIR /Open-IM-Server/script
+
+CMD ["./docker_start_all.sh"]
diff --git a/deploy/.dockerignore b/deploy/.dockerignore
new file mode 100644
index 000000000..019893ce7
--- /dev/null
+++ b/deploy/.dockerignore
@@ -0,0 +1,6 @@
+# 先设为忽略所有内容
+**/**
+
+
+# 然后逐个排除
+!open_im_*
\ No newline at end of file
diff --git a/deploy/Makefile b/deploy/Makefile
new file mode 100644
index 000000000..8250a06a4
--- /dev/null
+++ b/deploy/Makefile
@@ -0,0 +1,158 @@
+
+GREEN_PREFIX="\033[32m"
+COLOR_SUFFIX="\033[0m"
+SKY_BLUE_PREFIX="\033[36m"
+
+
+# 编译所有需要的组件源码
+win-build-all:
+ go env -w GOOS=linux
+
+ make build-api && make build-msg-gateway && make build-msg-transfer && make build-push && make build-timer-task
+ make build-rpc-user && make build-rpc-friend && make build-rpc-group && make build-rpc-msg && make build-rpc-auth
+ make build-demo
+
+ go env -w GOOS=windows
+
+# 编译 open_im_api
+build-api:
+ echo -e ${GREEN_PREFIX} "open_im_api building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_api ../cmd/open_im_api/main.go
+ echo -e ${GREEN_PREFIX} "open_im_api build ok" ${COLOR_SUFFIX}
+
+# 编译 open_im_msg_gateway
+build-msg-gateway:
+ echo -e ${GREEN_PREFIX} "open_im_msg_gateway building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_msg_gateway ../cmd/open_im_msg_gateway/main.go
+ echo -e ${GREEN_PREFIX} "open_im_msg_gateway build ok" ${COLOR_SUFFIX}
+
+# 编译 open_im_msg_transfer
+build-msg-transfer:
+ echo -e ${GREEN_PREFIX} "open_im_msg_transfer building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_msg_transfer ../cmd/open_im_msg_transfer/main.go
+ echo -e ${GREEN_PREFIX} "open_im_msg_transfer build ok" ${COLOR_SUFFIX}
+
+# 编译 open_im_push
+build-push:
+ echo -e ${GREEN_PREFIX} "open_im_push building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_push ../cmd/open_im_push/main.go
+ echo -e ${GREEN_PREFIX} "open_im_push build ok" ${COLOR_SUFFIX}
+
+# 编译 open_im_timer_task
+build-timer-task:
+ echo -e ${GREEN_PREFIX} "open_im_timer_task building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_timer_task ../cmd/open_im_timer_task/main.go
+ echo -e ${GREEN_PREFIX} "open_im_timer_task build ok" ${COLOR_SUFFIX}
+
+# 编译 build-rpc-user
+build-rpc-user:
+ echo -e ${SKY_BLUE_PREFIX} "open_im_rpc_user building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_rpc_user ../cmd/rpc/open_im_user/main.go
+ echo -e ${SKY_BLUE_PREFIX} "open_im_rpc_user build ok" ${COLOR_SUFFIX}
+
+# 编译 build-rpc-friend
+build-rpc-friend:
+ echo -e ${SKY_BLUE_PREFIX} "open_im_rpc_friend building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_rpc_friend ../cmd/rpc/open_im_friend/main.go
+ echo -e ${SKY_BLUE_PREFIX} "open_im_rpc_friend build ok" ${COLOR_SUFFIX}
+
+# 编译 build-rpc-group
+build-rpc-group:
+ echo -e ${SKY_BLUE_PREFIX} "open_im_rpc_group building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_rpc_group ../cmd/rpc/open_im_group/main.go
+ echo -e ${SKY_BLUE_PREFIX} "open_im_rpc_group build ok" ${COLOR_SUFFIX}
+
+# 编译 build-rpc-auth
+build-rpc-auth:
+ echo -e ${SKY_BLUE_PREFIX} "open_im_rpc_auth building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_rpc_auth ../cmd/rpc/open_im_auth/main.go
+ echo -e ${SKY_BLUE_PREFIX} "open_im_rpc_auth build ok" ${COLOR_SUFFIX}
+
+# 编译 build-rpc-msg
+build-rpc-msg:
+ echo -e ${SKY_BLUE_PREFIX} "open_im_rpc_msg building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_rpc_msg ../cmd/rpc/open_im_msg/main.go
+ echo -e ${SKY_BLUE_PREFIX} "open_im_rpc_msg build ok" ${COLOR_SUFFIX}
+
+# 编译 open_im_demo
+build-demo:
+ echo -e ${SKY_BLUE_PREFIX} "open_im_demo building..." ${COLOR_SUFFIX}
+ go build -ldflags="-w -s" -o open_im_demo ../cmd/open_im_demo/main.go
+ echo -e ${SKY_BLUE_PREFIX} "open_im_demo build ok" ${COLOR_SUFFIX}
+
+# 打包所有组件为镜像
+image-all:
+ make image-api && make image-msg-gateway && make image-msg-transfer & make image-push && make image-timer-task
+ make image-rpc-user && make image-rpc-friend && make image-rpc-group && make image-rpc-msg && make image-rpc-auth
+ make image-demo
+
+# 打包 open_im_api
+image-api:
+ echo -e ${GREEN_PREFIX} "IMAGE:openim/api building..." ${COLOR_SUFFIX}
+ docker build -t openim/api:latest -f ./dockerfiles/Dockerfile.api .
+ echo -e ${GREEN_PREFIX} "IMAGE:openim/api build ok" ${COLOR_SUFFIX}
+
+# 打包 open_im_msg_gateway
+image-msg-gateway:
+ echo -e ${GREEN_PREFIX} "IMAGE:openim/msg_gateway building..." ${COLOR_SUFFIX}
+ docker build -t openim/msg_gateway:latest -f ./dockerfiles/Dockerfile.msg_gateway .
+ echo -e ${GREEN_PREFIX} "IMAGE:openim/msg_gateway build ok" ${COLOR_SUFFIX}
+
+# 打包 open_im_msg_transfer
+image-msg-transfer:
+ echo -e ${GREEN_PREFIX} "IMAGE:openim/msg_transfer building..." ${COLOR_SUFFIX}
+ docker build -t openim/msg_transfer:latest -f ./dockerfiles/Dockerfile.msg_transfer .
+ echo -e ${GREEN_PREFIX} "IMAGE:openim/msg_transfer build ok" ${COLOR_SUFFIX}
+
+# 打包 open_im_push
+image-push:
+ echo -e ${GREEN_PREFIX} "IMAGE:openim/push building..." ${COLOR_SUFFIX}
+ docker build -t openim/push:latest -f ./dockerfiles/Dockerfile.push .
+ echo -e ${GREEN_PREFIX} "IMAGE:openim/push build ok" ${COLOR_SUFFIX}
+
+# 打包 open_im_timer_task
+image-timer-task:
+ echo -e ${GREEN_PREFIX} "IMAGE:openim/timer_task building..." ${COLOR_SUFFIX}
+ docker build -t openim/timer_task:latest -f ./dockerfiles/Dockerfile.timer_task .
+ echo -e ${GREEN_PREFIX} "IMAGE:openim/timer_task build ok" ${COLOR_SUFFIX}
+
+# 打包 build-rpc-user
+image-rpc-user:
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/rpc_user building..." ${COLOR_SUFFIX}
+ docker build -t openim/rpc_user:latest -f ./dockerfiles/Dockerfile.rpc_user .
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/rpc_user build ok" ${COLOR_SUFFIX}
+
+# 打包 build-rpc-friend
+image-rpc-friend:
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/rpc_friend building..." ${COLOR_SUFFIX}
+ docker build -t openim/rpc_friend:latest -f ./dockerfiles/Dockerfile.rpc_friend .
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/rpc_friend build ok" ${COLOR_SUFFIX}
+
+# 打包 build-rpc-group
+image-rpc-group:
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/rpc_group building..." ${COLOR_SUFFIX}
+ docker build -t openim/rpc_group:latest -f ./dockerfiles/Dockerfile.rpc_group .
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/rpc_group build ok" ${COLOR_SUFFIX}
+
+# 打包 build-rpc-auth
+image-rpc-auth:
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/rpc_auth building..." ${COLOR_SUFFIX}
+ docker build -t openim/rpc_auth:latest -f ./dockerfiles/Dockerfile.rpc_auth .
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/rpc_auth build ok" ${COLOR_SUFFIX}
+
+# 打包 build-rpc-msg
+image-rpc-msg:
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/rpc_msg building..." ${COLOR_SUFFIX}
+ docker build -t openim/rpc_msg:latest -f ./dockerfiles/Dockerfile.rpc_msg .
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/rpc_msg build ok" ${COLOR_SUFFIX}
+
+# 打包 open_im_demo
+image-demo:
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/demo building..." ${COLOR_SUFFIX}
+ docker build -t openim/demo:latest -f ./dockerfiles/Dockerfile.demo .
+ echo -e ${SKY_BLUE_PREFIX} "IMAGE:openim/demo build ok" ${COLOR_SUFFIX}
+
+.PHONY: win-build-all build-api build-msg-gateway build-msg-transfer build-push
+ build-timer-task build-rpc-user build-rpc-friend build-rpc-group build-rpc-msg build-demo
+ image-all image-api image-msg-gateway image-msg-transfer image-push
+ image-timer-task image-rpc-user image-rpc-friend image-rpc-group image-rpc-msg image-demo
diff --git a/deploy/config.example.yaml b/deploy/config.example.yaml
new file mode 100644
index 000000000..08d8858e4
--- /dev/null
+++ b/deploy/config.example.yaml
@@ -0,0 +1,183 @@
+# The class cannot be named by Pascal or camel case.
+# If it is not used, the corresponding structure will not be set,
+# and it will not be read naturally.
+serverversion: 1.0.3
+#---------------Infrastructure configuration---------------------#
+etcd:
+ etcdSchema: openIM
+ etcdAddr: [ openim_etcd:2379 ]
+
+mysql:
+ dbMysqlAddress: [ openim_mysql:3306 ] # openim_mysql 是对应的mysql服务的host
+ dbMysqlUserName: openIM
+ dbMysqlPassword: openIM
+ dbMysqlDatabaseName: openIM
+ dbTableName: eMsg
+ dbMsgTableNum: 1
+ dbMaxOpenConns: 20
+ dbMaxIdleConns: 10
+ dbMaxLifeTime: 120
+
+mongo:
+ dbUri: ""#当dbUri值不为空则直接使用该值
+ dbAddress: [ openim_mongo:27017 ]
+ dbDirect: false
+ dbTimeout: 10
+ dbDatabase: openIM
+ dbSource: admin
+ dbUserName:
+ dbPassword:
+ dbMaxPoolSize: 20
+ dbRetainChatRecords: 7
+
+redis:
+ dbAddress: openim_redis:6379
+ dbMaxIdle: 128
+ dbMaxActive: 0
+ dbIdleTimeout: 120
+ dbPassWord: openIM
+
+kafka:
+ ws2mschat:
+ addr: [ openim_kafka:9092 ]
+ topic: "ws2ms_chat"
+ ms2pschat:
+ addr: [ openim_kafka:9092 ]
+ topic: "ms2ps_chat"
+ consumergroupid:
+ msgToMongo: mongo
+ msgToMySql: mysql
+ msgToPush: push
+
+
+
+#---------------Internal service configuration---------------------#
+
+# The service ip default is empty,
+# automatically obtain the machine's valid network card ip as the service ip,
+# otherwise the configuration ip is preferred
+serverip: 0.0.0.0
+
+# endpoints 内部组件间访问的端点host名称,访问时,可以内部直接访问 host:port 来访问
+# 新增的这一段配置节,主要是位了注册到etcd时,可以使用同一network下的容器名(host)来访问不同的容器,拆分到不同容器后原来全部使用serverip的形式不能用了
+endpoints:
+ api: openim_api
+ push: openim_push
+ msg_gateway: openim_msg_gateway
+ rpc_auth: openim_rpc_auth
+ rpc_friend: openim_rpc_friend
+ rpc_group: openim_rpc_group
+ rpc_msg: openim_rpc_msg
+ rpc_user: openim_rpc_user
+
+api:
+ openImApiPort: [ 10000 ]
+sdk:
+ openImSdkWsPort: [ 30000 ]
+cmsapi:
+ openImCmsApiPort: [ 8000 ]
+
+credential:
+ tencent:
+ appID: 1302656840
+ region: ap-chengdu
+ bucket: echat-1302656840
+ secretID: AKIDGNYVChzIQinu7QEgtNp0hnNgqcV8vZTC
+ secretKey: kz15vW83qM6dBUWIq681eBZA0c0vlIbe
+
+
+rpcport:
+ openImUserPort: [ 10100 ]
+ openImFriendPort: [ 10200 ]
+ openImOfflineMessagePort: [ 10300]
+ openImOnlineRelayPort: [ 10400 ]
+ openImGroupPort: [ 10500 ]
+ openImAuthPort: [ 10600 ]
+ openImPushPort: [ 10700 ]
+ openImStatisticsPort: [ 10800 ]
+ openImMessageCmsPort: [ 10900 ]
+ openImAdminCmsPort: [ 11000 ]
+
+rpcregistername:
+ openImUserName: User
+ openImFriendName: Friend
+ openImOfflineMessageName: OfflineMessage
+ openImPushName: Push
+ openImOnlineMessageRelayName: OnlineMessageRelay
+ openImGroupName: Group
+ openImAuthName: Auth
+
+log:
+ storageLocation: ../logs/
+ rotationTime: 24
+ remainRotationCount: 5
+ remainLogLevel: 6
+ elasticSearchSwitch: false
+ elasticSearchAddr: [ 127.0.0.1:9201 ]
+ elasticSearchUser: ""
+ elasticSearchPassword: ""
+
+modulename:
+ longConnSvrName: msg_gateway
+ msgTransferName: msg_transfer
+ pushName: push
+
+longconnsvr:
+ openImWsPort: [ 17778 ]
+ websocketMaxConnNum: 10000
+ websocketMaxMsgLen: 4096
+ websocketTimeOut: 10
+
+push:
+ tpns:
+ ios:
+ accessID: 1600018281
+ secretKey: 3cd68a77a95b89e5089a1aca523f318f
+ android:
+ accessID: 111
+ secretKey: 111
+ jpns:
+ appKey: cf47465a368f24c659608e7e
+ masterSecret: 02204efe3f3832947a236ee5
+ pushUrl: "https://api.jpush.cn/v3/push"
+ pushIntent: "intent:#Intent;component=io.openim.app.enterprisechat/io.openim.app.enterprisechat.MainActivity;end"
+manager:
+ appManagerUid: ["openIM123456","openIM654321"]
+ secrets: ["openIM1","openIM2"]
+
+secret: tuoyun
+
+multiloginpolicy: 1
+
+#token config
+tokenpolicy:
+ accessSecret: "open_im_server"
+ # Token effective time day as a unit
+ accessExpire: 7
+
+messagecallback:
+ callbackSwitch: false
+ callbackUrl: "http://www.xxx.com/msg/judge"
+ #TimeOut use second as unit
+ callbackTimeOut: 10
+
+
+#---------------demo configuration---------------------#
+#The following configuration items are applied to openIM Demo configuration
+demoswitch: true
+demo:
+ openImDemoPort: [ 42233 ]
+ alismsverify:
+ accessKeyId: LTAI5tJPkn4HuuePdiLdGqe71
+ accessKeySecret: 4n9OJ7ZCVN1U6KeHDAtOyNeVZcjOuV1
+ signName: OpenIM Corporation
+ verificationCodeTemplateCode: SMS_2268101641
+ superCode: 666666
+ mail:
+ title: "openIM"
+ senderMail: "1765567899@qq.com"
+ senderAuthorizationCode: "1gxyausfoevlzbfag"
+ smtpAddr: "smtp.qq.com"
+ smtpPort: 25
+
+
diff --git a/deploy/dockerfiles/Dockerfile.api b/deploy/dockerfiles/Dockerfile.api
new file mode 100644
index 000000000..9c70d735d
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.api
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_api $WORKDIR/main
+
+# 创建用于挂载的几个目录,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/dockerfiles/Dockerfile.demo b/deploy/dockerfiles/Dockerfile.demo
new file mode 100644
index 000000000..ac77ad8d3
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.demo
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_demo $WORKDIR/main
+
+# 创建用于挂载的几个目录,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/dockerfiles/Dockerfile.msg_gateway b/deploy/dockerfiles/Dockerfile.msg_gateway
new file mode 100644
index 000000000..ffd012bc4
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.msg_gateway
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_msg_gateway $WORKDIR/main
+
+# 创建用于挂载的几个目录,重命名可执行文件为 main,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/dockerfiles/Dockerfile.msg_transfer b/deploy/dockerfiles/Dockerfile.msg_transfer
new file mode 100644
index 000000000..a82c4da8b
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.msg_transfer
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_msg_transfer $WORKDIR/main
+
+# 创建用于挂载的几个目录,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/dockerfiles/Dockerfile.push b/deploy/dockerfiles/Dockerfile.push
new file mode 100644
index 000000000..b929dd82c
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.push
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_push $WORKDIR/main
+
+# 创建用于挂载的几个目录,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/dockerfiles/Dockerfile.rpc_auth b/deploy/dockerfiles/Dockerfile.rpc_auth
new file mode 100644
index 000000000..a8e50fdb1
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.rpc_auth
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_rpc_auth $WORKDIR/main
+
+# 创建用于挂载的几个目录,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/dockerfiles/Dockerfile.rpc_friend b/deploy/dockerfiles/Dockerfile.rpc_friend
new file mode 100644
index 000000000..cfa2fe18c
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.rpc_friend
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_rpc_friend $WORKDIR/main
+
+# 创建用于挂载的几个目录,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/dockerfiles/Dockerfile.rpc_group b/deploy/dockerfiles/Dockerfile.rpc_group
new file mode 100644
index 000000000..d56a0fefe
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.rpc_group
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_rpc_group $WORKDIR/main
+
+# 创建用于挂载的几个目录,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/dockerfiles/Dockerfile.rpc_msg b/deploy/dockerfiles/Dockerfile.rpc_msg
new file mode 100644
index 000000000..01b5de02f
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.rpc_msg
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_rpc_msg $WORKDIR/main
+
+# 创建用于挂载的几个目录,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/dockerfiles/Dockerfile.rpc_user b/deploy/dockerfiles/Dockerfile.rpc_user
new file mode 100644
index 000000000..5be6298bd
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.rpc_user
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_rpc_user $WORKDIR/main
+
+# 创建用于挂载的几个目录,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/dockerfiles/Dockerfile.timer_task b/deploy/dockerfiles/Dockerfile.timer_task
new file mode 100644
index 000000000..0c2222cfb
--- /dev/null
+++ b/deploy/dockerfiles/Dockerfile.timer_task
@@ -0,0 +1,16 @@
+FROM alpine:3.13
+
+# 设置固定的项目路径
+ENV WORKDIR /app
+ENV CONFIG_NAME $WORKDIR/config/config.yaml
+
+# 将可执行文件复制到目标目录
+ADD ./open_im_timer_task $WORKDIR/main
+
+# 创建用于挂载的几个目录,添加可执行权限
+RUN mkdir $WORKDIR/logs $WORKDIR/config $WORKDIR/db && \
+ chmod +x $WORKDIR/main
+
+
+WORKDIR $WORKDIR
+CMD ./main
\ No newline at end of file
diff --git a/deploy/env.yaml b/deploy/env.yaml
new file mode 100644
index 000000000..423807e4f
--- /dev/null
+++ b/deploy/env.yaml
@@ -0,0 +1,101 @@
+version: "3.7"
+networks:
+ openim:
+ external: true
+
+services:
+ mysql:
+ networks:
+ - openim
+ image: mysql:5.7
+ # ports:
+ # #- 13306:3306
+ # - 23306:33060
+ container_name: openim_mysql
+ volumes:
+ - ./components/mysql/data:/var/lib/mysql
+ - /etc/localtime:/etc/localtime
+ environment:
+ MYSQL_ROOT_PASSWORD: openIM
+ restart: always
+
+ mongodb:
+ networks:
+ - openim
+ image: mongo:4.4.5-bionic
+ # ports:
+ # - 37017:27017
+ container_name: openim_mongo
+ volumes:
+ - ./components/mongodb/data/db:/data/db
+ - ./components/mongodb/data/logs:/data/logs
+ - ./components/mongodb/data/conf:/etc/mongo
+ environment:
+ TZ: Asia/Shanghai
+ # - MONGO_INITDB_ROOT_USERNAME=openIM
+ # - MONGO_INITDB_ROOT_PASSWORD=openIM
+ restart: always
+
+ redis:
+ networks:
+ - openim
+ image: redis:6.2.4-alpine
+ # ports:
+ # - 16379:6379
+ container_name: openim_redis
+ volumes:
+ - ./components/redis/data:/data
+ #redis config file
+ #- ./components/redis/config/redis.conf:/usr/local/redis/config/redis.conf
+ environment:
+ TZ: Asia/Shanghai
+ restart: always
+ sysctls:
+ net.core.somaxconn: 1024
+ command: redis-server --requirepass openIM --appendonly yes
+
+
+ zookeeper:
+ networks:
+ - openim
+ image: wurstmeister/zookeeper
+ # ports:
+ # - 2181:2181
+ container_name: openim_zookeeper
+ volumes:
+ - /etc/localtime:/etc/localtime
+ environment:
+ TZ: Asia/Shanghai
+ restart: always
+
+ kafka:
+ networks:
+ - openim
+ image: wurstmeister/kafka
+ container_name: openim_kafka
+ restart: always
+ environment:
+ TZ: Asia/Shanghai
+ KAFKA_BROKER_ID: 0
+ KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
+ depends_on:
+ - zookeeper
+
+ etcd:
+ networks:
+ - openim
+ image: quay.io/coreos/etcd
+ # ports:
+ # - 2379:2379
+ # - 2380:2380
+ container_name: openim_etcd
+ volumes:
+ - /etc/timezone:/etc/timezone
+ - /etc/localtime:/etc/localtime
+ environment:
+ ETCDCTL_API: 3
+ restart: always
+ command: /usr/local/bin/etcd --name etcd0 --data-dir /etcd-data --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379 --listen-peer-urls http://0.0.0.0:2380 --initial-advertise-peer-urls http://0.0.0.0:2380 --initial-cluster etcd0=http://0.0.0.0:2380 --initial-cluster-token tkn --initial-cluster-state new
+
diff --git a/deploy/openim.yaml b/deploy/openim.yaml
new file mode 100644
index 000000000..350729399
--- /dev/null
+++ b/deploy/openim.yaml
@@ -0,0 +1,223 @@
+version: "3.7"
+networks:
+ openim:
+ external: true
+
+services:
+ api:
+ networks:
+ - openim
+ image: openim/api
+ container_name: openim_api
+ ports:
+ - 10000:10000 # API,必须开
+ volumes:
+ - ./logs:/app/logs
+ # Dockerfile 里定义了配置文件的路径环境变量,CONFIG_NAME,默认指向了 /app/config/config.yaml
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
+ # depends_on:
+ # - kafka
+ # # - mysql
+ # # - mongodb
+ # - redis
+ # - etcd
+ logging:
+ driver: json-file
+ options:
+ max-size: "1g"
+ max-file: "2"
+
+ msg_gateway:
+ networks:
+ - openim
+ image: openim/msg_gateway
+ container_name: openim_msg_gateway
+ ports:
+ - 17778:17778 # 消息,必须开
+ volumes:
+ - ./logs:/app/logs
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
+ # depends_on:
+ # - kafka
+ # # - mysql
+ # # - mongodb
+ # - redis
+ # - etcd
+ logging:
+ driver: json-file
+ options:
+ max-size: "1g"
+ max-file: "2"
+
+ msg_transfer:
+ networks:
+ - openim
+ image: openim/msg_transfer
+ container_name: openim_msg_transfer
+ volumes:
+ - ./logs:/app/logs
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
+ # depends_on:
+ # - kafka
+ # # - mysql
+ # # - mongodb
+ # - redis
+ # - etcd
+ logging:
+ driver: json-file
+ options:
+ max-size: "1g"
+ max-file: "2"
+
+ push:
+ networks:
+ - openim
+ image: openim/push
+ container_name: openim_push
+ volumes:
+ - ./logs:/app/logs
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
+ # depends_on:
+ # - kafka
+ # # - mysql
+ # # - mongodb
+ # - redis
+ # - etcd
+ logging:
+ driver: json-file
+ options:
+ max-size: "1g"
+ max-file: "2"
+
+ timer_task:
+ networks:
+ - openim
+ image: openim/timer_task
+ container_name: openim_timer_task
+ volumes:
+ - ./logs:/app/logs
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
+ # depends_on:
+ # - kafka
+ # # - mysql
+ # # - mongodb
+ # - redis
+ # - etcd
+ logging:
+ driver: json-file
+ options:
+ max-size: "1g"
+ max-file: "2"
+
+ rpc_user:
+ networks:
+ - openim
+ image: openim/rpc_user
+ container_name: openim_rpc_user
+ volumes:
+ - ./logs:/app/logs
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
+ # depends_on:
+ # - kafka
+ # # - mysql
+ # # - mongodb
+ # - redis
+ # - etcd
+ logging:
+ driver: json-file
+ options:
+ max-size: "1g"
+ max-file: "2"
+
+ rpc_friend:
+ networks:
+ - openim
+ image: openim/rpc_friend
+ container_name: openim_rpc_friend
+ volumes:
+ - ./logs:/app/logs
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
+ # depends_on:
+ # - kafka
+ # # - mysql
+ # # - mongodb
+ # - redis
+ # - etcd
+
+ rpc_group:
+ networks:
+ - openim
+ image: openim/rpc_group
+ container_name: openim_rpc_group
+ volumes:
+ - ./logs:/app/logs
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
+ # depends_on:
+ # - kafka
+ # # - mysql
+ # # - mongodb
+ # - redis
+ # - etcd
+
+ rpc_auth:
+ networks:
+ - openim
+ image: openim/rpc_auth
+ container_name: openim_rpc_auth
+ volumes:
+ - ./logs:/app/logs
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
+ # depends_on:
+ # - kafka
+ # # - mysql
+ # # - mongodb
+ # - redis
+ # - etcd
+
+ rpc_msg:
+ networks:
+ - openim
+ image: openim/rpc_msg
+ container_name: openim_rpc_msg
+ volumes:
+ - ./logs:/app/logs
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
+ # depends_on:
+ # - kafka
+ # # - mysql
+ # # - mongodb
+ # - redis
+ # - etcd
+
+ demo:
+ networks:
+ - openim
+ image: openim/demo
+ container_name: openim_demo
+ ports:
+ - 42233:42233
+ volumes:
+ - ./logs:/app/logs
+ - ./config/config.yaml:/app/config/config.yaml
+ - ./db/sdk:/app/db/sdk
+ restart: always
\ No newline at end of file
diff --git a/deploy/readme.md b/deploy/readme.md
new file mode 100644
index 000000000..61fbd9700
--- /dev/null
+++ b/deploy/readme.md
@@ -0,0 +1,30 @@
+
+### 以docker-compose 形式单独部署
+```sh
+# 查看 ./Makefile ,先编译各个需要的源码到 ../bin
+# win-* 表示在win平台编译位linux二进制,其实就是处理了 go env -w GOOS=linux
+make win-build-all
+
+# 得到各个二进制程序之后,打包为镜像
+# 目前没有处理 Open-IM-SDK-Core ,需要的话可以自己单独处理这个模块
+make image-all
+
+# docker-compose.yaml 分成了两部分,一部分是openIM的镜像容器 openim.yaml,一部分是依赖的环境 env.yaml
+# 两部分使用一个外部的网络来联通,所以首先创建用到的 network
+docker network create openim --attachable=true -d bridge
+
+# 处理openim组件需要的挂载目录,主要是处理config目录
+mkdir ./config
+cp ./config.example.yaml ./config/config.yaml # 修改 ./config/config.yaml 内容,比如各个依赖组件的 host
+
+# 然后拉起env.yaml
+docker-compose -f ./env.yaml up -d
+
+# 等env 容器全部拉起成功之后,拉起openim.yaml
+docker-compose -f ./openim.yaml up -d
+
+# 查看容器运行,推荐使用下 portainer ,web查看容器情况,查看日志等等
+docker container ps -a | grep openim
+
+# 正常应该是查看api,demo等的容器日志,看到gin打印的路由日志才算是成功
+```
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 000000000..c67203773
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,116 @@
+version: "3"
+#fixme Clone openIM Server project before using docker-compose,project address:https://github.com/OpenIMSDK/Open-IM-Server.git
+
+services:
+ mysql:
+ image: mysql:5.7
+ ports:
+ - 13306:3306
+ - 23306:33060
+ container_name: mysql
+ volumes:
+ - ./components/mysql/data:/var/lib/mysql
+ - /etc/localtime:/etc/localtime
+ environment:
+ MYSQL_ROOT_PASSWORD: openIM
+ restart: always
+
+ mongodb:
+ image: mongo:4.0
+ ports:
+ - 37017:27017
+ container_name: mongo
+ volumes:
+ - ./components/mongodb/data/db:/data/db
+ - ./components/mongodb/data/logs:/data/logs
+ - ./components/mongodb/data/conf:/etc/mongo
+ environment:
+ - TZ=Asia/Shanghai
+ # cache
+ - wiredTigerCacheSizeGB=1
+# environment:
+# - MONGO_INITDB_ROOT_USERNAME=openIM
+# - MONGO_INITDB_ROOT_PASSWORD=openIM
+
+
+ #TZ: Asia/Shanghai
+ restart: always
+
+ redis:
+ image: redis
+ ports:
+ - 16379:6379
+ container_name: redis
+ volumes:
+ - ./components/redis/data:/data
+ #redis config file
+ #- ./components/redis/config/redis.conf:/usr/local/redis/config/redis.conf
+ environment:
+ TZ: Asia/Shanghai
+ restart: always
+ sysctls:
+ net.core.somaxconn: 1024
+ command: redis-server --requirepass openIM --appendonly yes
+
+
+ zookeeper:
+ image: wurstmeister/zookeeper
+ ports:
+ - 2181:2181
+ container_name: zookeeper
+ volumes:
+ - /etc/localtime:/etc/localtime
+ environment:
+ TZ: Asia/Shanghai
+ restart: always
+
+
+ kafka:
+ image: wurstmeister/kafka
+ container_name: kafka
+ restart: always
+ environment:
+ TZ: Asia/Shanghai
+ KAFKA_BROKER_ID: 0
+ KAFKA_ZOOKEEPER_CONNECT: 127.0.0.1:2181
+ KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://127.0.0.1:9092
+ KAFKA_LISTENERS: PLAINTEXT://0.0.0.0:9092
+ network_mode: "host"
+ depends_on:
+ - zookeeper
+
+ etcd:
+ image: quay.io/coreos/etcd
+ ports:
+ - 2379:2379
+ - 2380:2380
+ container_name: etcd
+ volumes:
+ - /etc/timezone:/etc/timezone
+ - /etc/localtime:/etc/localtime
+ environment:
+ ETCDCTL_API: 3
+ restart: always
+ command: /usr/local/bin/etcd --name etcd0 --data-dir /etcd-data --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379 --listen-peer-urls http://0.0.0.0:2380 --initial-advertise-peer-urls http://0.0.0.0:2380 --initial-cluster etcd0=http://0.0.0.0:2380 --initial-cluster-token tkn --initial-cluster-state new
+
+ open_im_server:
+ image: openim/open_im_server:v2.0.5
+ container_name: open_im_server
+ volumes:
+ - ./logs:/Open-IM-Server/logs
+ - ./config/config.yaml:/Open-IM-Server/config/config.yaml
+ - ./db/sdk:/Open-IM-Server/db/sdk
+ - ./script:/Open-IM-Server/script
+ restart: always
+ depends_on:
+ - kafka
+ - mysql
+ - mongodb
+ - redis
+ - etcd
+ network_mode: "host"
+ logging:
+ driver: json-file
+ options:
+ max-size: "1g"
+ max-file: "2"
diff --git a/docs/Architecture.jpg b/docs/Architecture.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..138a72472b2c599624085a7c5e19d96dea39bf84
GIT binary patch
literal 212410
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