Compare commits

...

38 Commits

Author SHA1 Message Date
Xinwei Xiong(cubxxw) 72e7fe931b fix: fix auto gen config
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
2023-11-29 23:03:06 +08:00
AndrewZuo01 0efc235f45 add webhooks (#1465)
* add callback before join group

* fix bug

* fix deployments/templates/alertmanager.yml

* fix bug

* update callback after join group

* merge callback after join group

* update callback setrgoup info

* test

* test

* test

* update three functions in friend category

* test friend and blacklist

* test

* test

* test

* Update openim.yaml

* merge callback after join group

* merge callback after join group

* merge callback after join group

* fix callbackbeforesetgroupinfo

* fix eventtime

* update api request required

* update api request required

* update api request required

* delete unused code

* delete unused code

* fix

* Update .env

* Update .env

* Update callback.go

* Update callback.go

* Update .env

* Update .env

* fix: merge

* update

* fix: merge

* fix: fix bugs

* update callback enable

* update callback enable

* update callback enable

* update callback enable

* update callback enable

* update callback enable

* Update openim.yaml

* Update environment.sh

* Update environment.md

* Update environment.md

* Update environment.sh

---------

Co-authored-by: Gordon <1432970085@qq.com>
2023-11-30 02:10:48 +00:00
fengyun.rui 35bac04f58 fix: grace shutdown for gw (#1478)
Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-29 02:44:37 +00:00
healingtjx 4c7e0295bf feat: OpenIMServer compatible qiniu kodo (#1460)
* build : add aws and kodo dependency

* feat: add qiniu kodo

* Doc : Add Qiniu Cloud Kodo Document and Config
2023-11-29 02:41:47 +00:00
xuexihuang ceb669dfb8 Feature middleware (#1476)
* fix:fix error values&logs

* modify: add logs

* feature:add redis io retry logic

* feature:add redis error alert rule

* test:for test alert

* fix:fix prometheus rules

* del:del test code

---------

Co-authored-by: lin.huang <lin.huang@apulis.com>
2023-11-29 02:41:11 +00:00
Brabem 02142c55b2 feat: add callback func (#1480)
* feat:add callback func

* fix: fix the error

* fix: fix the error of repalce

* fix: fix the error of repalce
2023-11-28 07:26:46 +00:00
Xinwei Xiong 100926da0e docs: add openim search test (#1485) 2023-11-28 06:44:02 +00:00
Gordon f935d36715 fix: wrong single message read state. (#1443)
* fix: wrong single message read state.

* Update as_read.go

* Update as_read.go

* Update as_read.go

---------

Co-authored-by: Xinwei Xiong <3293172751@qq.com>
2023-11-26 12:49:31 +00:00
fengyun.rui 3cecbbc69a fix: grace shutdown for api server (#1439)
* fix: add grace shutdown for api server

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

* fix: add grace shutdown for api server

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

---------

Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-26 12:25:37 +00:00
Gordon e4046994cf fix: update user's info will modify user create time when modify user's nickname or avatar. (#1446) 2023-11-26 12:22:41 +00:00
fengyun.rui 403cfb6055 perf: redis block with keys command (#1423)
Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-26 12:16:02 +00:00
Xinwei Xiong 1f7dfa33d7 Update README.md (#1477) 2023-11-26 03:19:12 +00:00
fengyun.rui a9153afc38 perf: control ws write buffer (#1451)
Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-22 09:31:37 +00:00
xuexihuang 7a13284b2e kafka work error ,alertmanager work error (#1455)
* Code adaptation k8s: service discovery and registration adaptation, configuration adaptation

* Initial submission of the help charts script for openim API

* change the help charts script

* change the help charts script

* change helm chart codes

* change dockerfiles script

* change chart script:add configmap mounts

* change chart script:change repository

* change chart script:msggateway add one service

* change config.yaml

* roll back some config values

* change chart script:change Ingress rule with a rewrite annotation

* add mysql charts scrible

* change chart script:add mysql.config.yaml

* add nfs provisioner charts

* change chart script:add nfs.config.yaml

* add ingress-nginx charts

* change chart script:add ingress-nginx.config.yaml

* add redis &mongodb charts

* add kafka&minio charts

* change chart script:change redis.values.yaml

* change chart script:add redis.config.yaml

* change chart script:change redis.config.yaml

* change chart script:change mongodb.value.yaml

* change chart script:change mongodb.value.yaml

* change chart script:add mongodb.config.yaml

* change chart script:change minio.values.yaml

* change chart script:add minio.config.yaml

* change chart script:change kafka.values.yaml

* change chart script:add kafka.config.yaml

* change chart script:change services.config.yaml

* bug fix:Delete websocket's Port restrictions

* bug fix:change port value

* change chart script:Submit a stable version script

* fix bug:Implement option interface

* fix bug:change K8sDR.Register

* change config.yaml

* change chats script:minio service add ingress

* change chats script:minio service add ingress

* change chats script:kafka.replicaCount=3& change minio.api ingress

* delete change chats script

* change config.yaml

* change openim.yaml

* merge go.sum

* Add monitoring function and struct for Prometheus on gin and GRPC

* Add GRPC and gin server monitoring logic

* Add GRPC and gin server monitoring logic2

* Add GRPC and gin server monitoring logic3

* Add GRPC and gin server monitoring logic4

* Add GRPC and gin server monitoring logic5

* Add GRPC and gin server monitoring logic6

* Add GRPC and gin server monitoring logic7

* delete:old monitoring code

* add for test

* fix bug:change packname

* fix bug:delete getPromPort funciton

* fix bug:delete getPromPort funciton

* fix bug:change logs

* fix bug:change registerName logic in GetGrpcCusMetrics function

* add getPrometheus url api

* fix:config path logic

* fix:prometheus enable function

* fix:prometheus enable function

* fix:transfer Multi process monitoring logic

* del:del not using manifest

* fix:openim-msgtransfer.sh

* fix:openim-msgtransfer.sh

* cicd: robot automated Change

* delete not using files

* add prometheus docker-compose for monitor

* fix prometheus.yaml

* fix environment.sh

* fix init-config.sh

* fix init-config.sh

* fix env_template.yaml

* fix docker-compose.yml

* fix docker-compose.yml

* add openim_admin_front service

* change openim-admin-front

* del not using files

* add node-exporter-dashaboard.yaml

* cicd: robot automated Change

* cicd: robot automated Change

* feature: add alertmanager function

* feature: add alertmanager function

* feature: add alertmanager function

* feature: add alertmanager function

* feature: add alertmanager function

* del:delete not using files

* del:delete not using files

* change:change to personal email info

* fix:alertmanager.yml

* fix:fix docker-compose.yml

* del:not using files

---------

Co-authored-by: lin.huang <lin.huang@apulis.com>
Co-authored-by: Xinwei Xiong <3293172751@qq.com>
Co-authored-by: xuexihuang <xuexihuang@users.noreply.github.com>
Co-authored-by: cubxxw <cubxxw@users.noreply.github.com>
2023-11-21 09:09:31 +00:00
Xinwei Xiong 75375adf62 feat: deployment and design of management backend and monitoring (#1432)
Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
2023-11-18 04:36:30 +00:00
Xinwei Xiong b17c6ec924 Update docker-compose.yml (#1425) 2023-11-16 10:47:04 +00:00
Xinwei Xiong fb74453c18 Add Prometheus alerting functionality (#1424)
* Code adaptation k8s: service discovery and registration adaptation, configuration adaptation

* Initial submission of the help charts script for openim API

* change the help charts script

* change the help charts script

* change helm chart codes

* change dockerfiles script

* change chart script:add configmap mounts

* change chart script:change repository

* change chart script:msggateway add one service

* change config.yaml

* roll back some config values

* change chart script:change Ingress rule with a rewrite annotation

* add mysql charts scrible

* change chart script:add mysql.config.yaml

* add nfs provisioner charts

* change chart script:add nfs.config.yaml

* add ingress-nginx charts

* change chart script:add ingress-nginx.config.yaml

* add redis &mongodb charts

* add kafka&minio charts

* change chart script:change redis.values.yaml

* change chart script:add redis.config.yaml

* change chart script:change redis.config.yaml

* change chart script:change mongodb.value.yaml

* change chart script:change mongodb.value.yaml

* change chart script:add mongodb.config.yaml

* change chart script:change minio.values.yaml

* change chart script:add minio.config.yaml

* change chart script:change kafka.values.yaml

* change chart script:add kafka.config.yaml

* change chart script:change services.config.yaml

* bug fix:Delete websocket's Port restrictions

* bug fix:change port value

* change chart script:Submit a stable version script

* fix bug:Implement option interface

* fix bug:change K8sDR.Register

* change config.yaml

* change chats script:minio service add ingress

* change chats script:minio service add ingress

* change chats script:kafka.replicaCount=3& change minio.api ingress

* delete change chats script

* change config.yaml

* change openim.yaml

* merge go.sum

* Add monitoring function and struct for Prometheus on gin and GRPC

* Add GRPC and gin server monitoring logic

* Add GRPC and gin server monitoring logic2

* Add GRPC and gin server monitoring logic3

* Add GRPC and gin server monitoring logic4

* Add GRPC and gin server monitoring logic5

* Add GRPC and gin server monitoring logic6

* Add GRPC and gin server monitoring logic7

* delete:old monitoring code

* add for test

* fix bug:change packname

* fix bug:delete getPromPort funciton

* fix bug:delete getPromPort funciton

* fix bug:change logs

* fix bug:change registerName logic in GetGrpcCusMetrics function

* add getPrometheus url api

* fix:config path logic

* fix:prometheus enable function

* fix:prometheus enable function

* fix:transfer Multi process monitoring logic

* del:del not using manifest

* fix:openim-msgtransfer.sh

* fix:openim-msgtransfer.sh

* cicd: robot automated Change

* delete not using files

* add prometheus docker-compose for monitor

* fix prometheus.yaml

* fix environment.sh

* fix init-config.sh

* fix init-config.sh

* fix env_template.yaml

* fix docker-compose.yml

* fix docker-compose.yml

* add openim_admin_front service

* change openim-admin-front

* del not using files

* add node-exporter-dashaboard.yaml

* cicd: robot automated Change

* cicd: robot automated Change

* feature: add alertmanager function

* feature: add alertmanager function

* feature: add alertmanager function

* feature: add alertmanager function

* feature: add alertmanager function

* del:delete not using files

* del:delete not using files

* change:change to personal email info

* feat: deployment and design of management backend and monitoring

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: deployment and design of management backend and monitoring

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: deployment and design of management backend and monitoring

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

---------

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
Co-authored-by: lin.huang <lin.huang@apulis.com>
Co-authored-by: xuexihuang <1339326187@qq.com>
Co-authored-by: xuexihuang <xuexihuang@users.noreply.github.com>
Co-authored-by: cubxxw <cubxxw@users.noreply.github.com>
2023-11-16 10:02:30 +00:00
skiffer-git 82d238afbe Add files via upload 2023-11-16 14:36:50 +08:00
skiffer-git 2c9a2239d8 Delete docs/images/Wechat.jpg 2023-11-16 14:36:26 +08:00
skiffer-git 56fd78653c Add files via upload 2023-11-16 14:35:56 +08:00
skiffer-git 872dcae27a Update README-zh_CN.md 2023-11-16 14:34:35 +08:00
skiffer-git 6ba0d618e4 Add files via upload 2023-11-16 14:33:27 +08:00
fengyun.rui a19f0e534f perf: redis batch delete msgs (#1395)
Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-15 17:14:26 +00:00
fengyun.rui eeb16d4116 perf: broadcast msg to all gateway with concurrency (#1411)
Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-15 14:24:17 +00:00
Xinwei Xiong ae048417ee "Add Prometheus Support with Documentation and Docker Compose Integration to OpenIM" (#1403)
* Code adaptation k8s: service discovery and registration adaptation, configuration adaptation

* Initial submission of the help charts script for openim API

* change the help charts script

* change the help charts script

* change helm chart codes

* change dockerfiles script

* change chart script:add configmap mounts

* change chart script:change repository

* change chart script:msggateway add one service

* change config.yaml

* roll back some config values

* change chart script:change Ingress rule with a rewrite annotation

* add mysql charts scrible

* change chart script:add mysql.config.yaml

* add nfs provisioner charts

* change chart script:add nfs.config.yaml

* add ingress-nginx charts

* change chart script:add ingress-nginx.config.yaml

* add redis &mongodb charts

* add kafka&minio charts

* change chart script:change redis.values.yaml

* change chart script:add redis.config.yaml

* change chart script:change redis.config.yaml

* change chart script:change mongodb.value.yaml

* change chart script:change mongodb.value.yaml

* change chart script:add mongodb.config.yaml

* change chart script:change minio.values.yaml

* change chart script:add minio.config.yaml

* change chart script:change kafka.values.yaml

* change chart script:add kafka.config.yaml

* change chart script:change services.config.yaml

* bug fix:Delete websocket's Port restrictions

* bug fix:change port value

* change chart script:Submit a stable version script

* fix bug:Implement option interface

* fix bug:change K8sDR.Register

* change config.yaml

* change chats script:minio service add ingress

* change chats script:minio service add ingress

* change chats script:kafka.replicaCount=3& change minio.api ingress

* delete change chats script

* change config.yaml

* change openim.yaml

* merge go.sum

* Add monitoring function and struct for Prometheus on gin and GRPC

* Add GRPC and gin server monitoring logic

* Add GRPC and gin server monitoring logic2

* Add GRPC and gin server monitoring logic3

* Add GRPC and gin server monitoring logic4

* Add GRPC and gin server monitoring logic5

* Add GRPC and gin server monitoring logic6

* Add GRPC and gin server monitoring logic7

* delete:old monitoring code

* add for test

* fix bug:change packname

* fix bug:delete getPromPort funciton

* fix bug:delete getPromPort funciton

* fix bug:change logs

* fix bug:change registerName logic in GetGrpcCusMetrics function

* add getPrometheus url api

* fix:config path logic

* fix:prometheus enable function

* fix:prometheus enable function

* fix:transfer Multi process monitoring logic

* del:del not using manifest

* fix:openim-msgtransfer.sh

* fix:openim-msgtransfer.sh

* cicd: robot automated Change

* delete not using files

* add prometheus docker-compose for monitor

* fix prometheus.yaml

* fix environment.sh

* fix init-config.sh

* fix init-config.sh

* fix env_template.yaml

* fix docker-compose.yml

* fix docker-compose.yml

* add openim_admin_front service

* change openim-admin-front

* del not using files

* add node-exporter-dashaboard.yaml

* fix: fix prom config file them

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* fix: fix prom config file them

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: Complete the source code module design of monitoring

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: Complete the source code module docs

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: Complete the source code module docs

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: add openim prometheus

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

---------

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
Co-authored-by: lin.huang <lin.huang@apulis.com>
Co-authored-by: xuexihuang <1339326187@qq.com>
Co-authored-by: xuexihuang <xuexihuang@users.noreply.github.com>
2023-11-15 02:38:21 +00:00
fengyun.rui 7502b4ac0f refactor: lower the level of code nesting (#1396)
Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-14 06:21:44 +00:00
chao 05ab3fcd06 fix: the original quoted message is withdrawn and the quoted original message is displayed. (#1391)
* fix: GetUserReqApplicationList error when there is a disbanded group chat

* fix: error when querying some information about disbanded group

* fix: GetUserReqApplicationList dismissed group error

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* merge

* cicd: robot automated Change

* sdkws.MsgData

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>
2023-11-14 02:47:56 +00:00
Gordon 7698368957 Bug/fix online status sync trigger (#1393)
* fix: sync close ws conn when kick old user avoid wrong trigger order about  online status.

* fix: reverse conversation notification id.
2023-11-14 02:01:08 +00:00
Xinwei Xiong 0d5fe4e6d6 Formatting adjustments, script removal, and helm template rendering (#1389)
* cicd: robot automated Change

* fix: add chat thmp

* fix: fix openim test file

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: add openim ctl system sctips remove

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: add openim cicd images

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: add openim cicd images

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: add openim config ete code

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>

* feat: fix openim pkg

---------

Signed-off-by: Xinwei Xiong(cubxxw) <3293172751nss@gmail.com>
Co-authored-by: cubxxw <cubxxw@users.noreply.github.com>
2023-11-13 13:27:17 +00:00
chao 2ac54e09a6 fix: the original quoted message is withdrawn and the quoted original message is displayed. (#1388)
* fix: GetUserReqApplicationList error when there is a disbanded group chat

* fix: error when querying some information about disbanded group

* fix: GetUserReqApplicationList dismissed group error

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* fix: the original message referenced by the pull message processing is withdrawn

* merge

* cicd: robot automated Change

---------

Co-authored-by: withchao <withchao@users.noreply.github.com>
2023-11-13 12:01:31 +00:00
fengyun.rui 7153eeb178 refactor: crontask cmd (#1331)
* refactor: cron task

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

* refactor: cron task

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

---------

Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-13 07:21:54 +00:00
fengyun.rui fd42c6dced fix: reduce lock msg transfer (#1308)
* fix: reduce lock msg transfer

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

* fix: reduce lock msg transfer

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

---------

Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-13 05:17:50 +00:00
fengyun.rui 69eb24f702 perf: concurrent notify node on register (#1327)
Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-13 05:08:48 +00:00
fengyun.rui 65c1c412da refactor: gin prometheus for api (#1371)
Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-13 05:04:33 +00:00
fengyun.rui 2496a16a88 perf: add concurrency and pipeline for redis cache (#1338)
* perf: add concurrency and pipeline mode for redis cache

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

* perf: add concurrency and pipeline mode for redis cache

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

* perf: unit test for redis cache

Signed-off-by: rfyiamcool <rfyiamcool@163.com>

---------

Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-13 03:20:31 +00:00
fengyun.rui d1af343b13 fix: add kafka compress type and producer ack params (#1310)
Signed-off-by: rfyiamcool <rfyiamcool@163.com>
2023-11-10 14:04:16 +00:00
fengyun.rui a580c15f6a perf: improve gzip performance with sync.pool (#1321)
Signed-off-by: rfyiamcool <rfyiamcool@163.com>
Co-authored-by: Gordon <46924906+FGadvancer@users.noreply.github.com>
2023-11-10 14:02:02 +00:00
Xinwei Xiong 8e0cb6dc47 build: build openim image (#1381) 2023-11-10 13:14:21 +00:00
114 changed files with 7183 additions and 10504 deletions
-79
View File
@@ -1,79 +0,0 @@
# Copyright © 2023 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#---------------Infrastructure configuration---------------------#
etcd:
etcdSchema: openim #默认即可
etcdAddr: [ 127.0.0.1:2379 ] #单机部署时,默认即可
userName:
password:
secret: openIM123
mysql:
dbMysqlDatabaseName: admin_chat # 数据库名字 默认即可
# 默认管理员账号
admin:
defaultAccount:
account: [ "admin1", "admin2" ]
defaultPassword: [ "password1", "password2" ]
openIMUserID: [ "openIM123456", "openIMAdmin" ]
faceURL: [ "", "" ]
nickname: [ "admin1", "admin2" ]
level: [ 1, 100 ]
adminapi:
openImAdminApiPort: [ 10009 ] #管理后台api服务端口,默认即可,需要开放此端口或做nginx转发
listenIP: 0.0.0.0
chatapi:
openImChatApiPort: [ 10008 ] #登录注册,默认即可,需要开放此端口或做nginx转发
listenIP: 0.0.0.0
rpcport: # rpc服务端口 默认即可
openImAdminPort: [ 30200 ]
openImChatPort: [ 30300 ]
rpcregistername: #rpc注册服务名,默认即可
openImChatName: Chat
openImAdminCMSName: Admin
chat:
codeTTL: 300 #短信验证码有效时间(秒)
superVerificationCode: 666666 # 超级验证码
alismsverify: #阿里云短信配置,在阿里云申请成功后修改以下四项
accessKeyId:
accessKeySecret:
signName:
verificationCodeTemplateCode:
oss:
tempDir: enterprise-temp # 临时密钥上传的目录
dataDir: enterprise-data # 最终存放目录
aliyun:
endpoint: https://oss-cn-chengdu.aliyuncs.com
accessKeyID: ""
accessKeySecret: ""
bucket: ""
tencent:
BucketURL: ""
serviceURL: https://cos.COS_REGION.myqcloud.com
secretID: ""
secretKey: ""
sessionToken: ""
bucket: ""
use: "minio"
@@ -1,27 +0,0 @@
# Copyright © 2023 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#more datasource-compose.yaml
apiVersion: 1
datasources:
- name: Prometheus
type: prometheus
access: proxy
orgId: 1
url: http://127.0.0.1:9091
basicAuth: false
isDefault: true
version: 1
editable: true
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,85 +0,0 @@
# Copyright © 2023 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#more prometheus-compose.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
external_labels:
monitor: 'openIM-monitor'
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9091']
- job_name: 'openIM-server'
metrics_path: /metrics
static_configs:
- targets: ['localhost:10002']
labels:
group: 'api'
- targets: ['localhost:20110']
labels:
group: 'user'
- targets: ['localhost:20120']
labels:
group: 'friend'
- targets: ['localhost:20130']
labels:
group: 'message'
- targets: ['localhost:20140']
labels:
group: 'msg-gateway'
- targets: ['localhost:20150']
labels:
group: 'group'
- targets: ['localhost:20160']
labels:
group: 'auth'
- targets: ['localhost:20170']
labels:
group: 'push'
- targets: ['localhost:20120']
labels:
group: 'friend'
- targets: ['localhost:20230']
labels:
group: 'conversation'
- targets: ['localhost:21400', 'localhost:21401', 'localhost:21402', 'localhost:21403']
labels:
group: 'msg-transfer'
- job_name: 'node'
scrape_interval: 8s
static_configs:
- targets: ['localhost:9100']
-285
View File
@@ -1,285 +0,0 @@
# Copyright © 2023 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ======================================
# ========= Basic Configuration ========
# ======================================
# The user for authentication or system operations.
# Default: USER=root
USER=root
# Password associated with the specified user for authentication.
# Default: PASSWORD=openIM123
PASSWORD=openIM123
# Endpoint for the MinIO object storage service.
# Default: MINIO_ENDPOINT=http://172.28.0.1:10005
MINIO_ENDPOINT=http://172.28.0.1:10005
# Base URL for the application programming interface (API).
# Default: API_URL=http://172.0.0.1:10002
API_URL=http://172.0.0.1:10002
# Directory path for storing data files or related information.
# Default: DATA_DIR=./
DATA_DIR=./
# Choose the appropriate image address, the default is GITHUB image,
# you can choose docker hub, for Chinese users can choose Ali Cloud
# export IMAGE_REGISTRY="ghcr.io/openimsdk"
# export IMAGE_REGISTRY="openim"
# export IMAGE_REGISTRY="registry.cn-hangzhou.aliyuncs.com/openimsdk"
IMAGE_REGISTRY=ghcr.io/openimsdk
# ======================================
# ========= Network Configuration ======
# ======================================
# Subnet for the Docker network.
# Default: DOCKER_BRIDGE_SUBNET=172.28.0.0/16
DOCKER_BRIDGE_SUBNET=172.28.0.0/16
# Gateway for the Docker network.
# Default: DOCKER_BRIDGE_GATEWAY=172.28.0.1
DOCKER_BRIDGE_GATEWAY=172.28.0.1
# Address or hostname for the MySQL network.
# Default: MYSQL_NETWORK_ADDRESS=172.28.0.2
MYSQL_NETWORK_ADDRESS=172.28.0.2
# Address or hostname for the MongoDB network.
# Default: MONGO_NETWORK_ADDRESS=172.28.0.3
MONGO_NETWORK_ADDRESS=172.28.0.3
# Address or hostname for the Redis network.
# Default: REDIS_NETWORK_ADDRESS=172.28.0.4
REDIS_NETWORK_ADDRESS=172.28.0.4
# Address or hostname for the Kafka network.
# Default: KAFKA_NETWORK_ADDRESS=172.28.0.5
KAFKA_NETWORK_ADDRESS=172.28.0.5
# Address or hostname for the ZooKeeper network.
# Default: ZOOKEEPER_NETWORK_ADDRESS=172.28.0.6
ZOOKEEPER_NETWORK_ADDRESS=172.28.0.6
# Address or hostname for the MinIO network.
# Default: MINIO_NETWORK_ADDRESS=172.28.0.7
MINIO_NETWORK_ADDRESS=172.28.0.7
# Address or hostname for the OpenIM web network.
# Default: OPENIM_WEB_NETWORK_ADDRESS=172.28.0.8
OPENIM_WEB_NETWORK_ADDRESS=172.28.0.8
# Address or hostname for the OpenIM server network.
# Default: OPENIM_SERVER_NETWORK_ADDRESS=172.28.0.9
OPENIM_SERVER_NETWORK_ADDRESS=172.28.0.9
# Address or hostname for the OpenIM chat network.
# Default: OPENIM_CHAT_NETWORK_ADDRESS=172.28.0.10
OPENIM_CHAT_NETWORK_ADDRESS=172.28.0.10
# Address or hostname for the Prometheus network.
# Default: PROMETHEUS_NETWORK_ADDRESS=172.28.0.11
PROMETHEUS_NETWORK_ADDRESS=172.28.0.11
# Address or hostname for the Grafana network.
# Default: GRAFANA_NETWORK_ADDRESS=172.28.0.12
GRAFANA_NETWORK_ADDRESS=172.28.0.12
# ===============================================
# = Component Extension Configuration =
# ===============================================
# ============ Component Extension Configuration ==========
# ----- ZooKeeper Configuration -----
# Address or hostname for the ZooKeeper service.
# Default: ZOOKEEPER_ADDRESS=172.28.0.1
ZOOKEEPER_ADDRESS=172.28.0.6
# Port for ZooKeeper service.
# Default: ZOOKEEPER_PORT=12181
ZOOKEEPER_PORT=12181
# ----- MySQL Configuration -----
# Address or hostname for the MySQL service.
# Default: MYSQL_ADDRESS=172.28.0.1
MYSQL_ADDRESS=172.28.0.2
# Port on which MySQL database service is running.
# Default: MYSQL_PORT=13306
MYSQL_PORT=13306
# Password to authenticate with the MySQL database service.
# Default: MYSQL_PASSWORD=openIM123
MYSQL_PASSWORD=openIM123
# ----- MongoDB Configuration -----
# Address or hostname for the MongoDB service.
# Default: MONGO_ADDRESS=172.28.0.1
MONGO_ADDRESS=172.28.0.3
# Port on which MongoDB service is running.
# Default: MONGO_PORT=37017
MONGO_PORT=37017
# Username to authenticate with the MongoDB service.
# Default: MONGO_USERNAME=root
MONGO_USERNAME=root
# Password to authenticate with the MongoDB service.
# Default: MONGO_PASSWORD=openIM123
MONGO_PASSWORD=openIM123
# Name of the database in MongoDB to be used.
# Default: MONGO_DATABASE=openIM_v3
MONGO_DATABASE=openIM_v3
# ----- Redis Configuration -----
# Address or hostname for the Redis service.
# Default: REDIS_ADDRESS=172.28.0.1
REDIS_ADDRESS=172.28.0.4
# Port on which Redis in-memory data structure store is running.
# Default: REDIS_PORT=16379
REDIS_PORT=16379
# Password to authenticate with the Redis service.
# Default: REDIS_PASSWORD=openIM123
REDIS_PASSWORD=openIM123
# ----- Kafka Configuration -----
# Address or hostname for the Kafka service.
# Default: KAFKA_ADDRESS=172.28.0.1
KAFKA_ADDRESS=172.28.0.5
# Port on which Kafka distributed streaming platform is running.
# Default: KAFKA_PORT=19092
KAFKA_PORT=19094
# Topic in Kafka for storing the latest messages in Redis.
# Default: KAFKA_LATESTMSG_REDIS_TOPIC=latestMsgToRedis
KAFKA_LATESTMSG_REDIS_TOPIC=latestMsgToRedis
# Topic in Kafka for pushing messages (e.g. notifications or updates).
# Default: KAFKA_MSG_PUSH_TOPIC=msgToPush
KAFKA_MSG_PUSH_TOPIC=msgToPush
# Topic in Kafka for storing offline messages in MongoDB.
# Default: KAFKA_OFFLINEMSG_MONGO_TOPIC=offlineMsgToMongoMysql
KAFKA_OFFLINEMSG_MONGO_TOPIC=offlineMsgToMongoMysql
# ----- MinIO Configuration ----
# Address or hostname for the MinIO object storage service.
# Default: MINIO_ADDRESS=172.28.0.1
MINIO_ADDRESS=172.28.0.7
# Port on which MinIO object storage service is running.
# Default: MINIO_PORT=10005
MINIO_PORT=10005
# Access key to authenticate with the MinIO service.
# Default: MINIO_ACCESS_KEY=root
MINIO_ACCESS_KEY=root
# Secret key corresponding to the access key for MinIO authentication.
# Default: MINIO_SECRET_KEY=openIM123
MINIO_SECRET_KEY=openIM123
# ----- Prometheus Configuration -----
# Address or hostname for the Prometheus service.
# Default: PROMETHEUS_ADDRESS=172.28.0.1
PROMETHEUS_ADDRESS=172.28.0.11
# Port on which Prometheus service is running.
# Default: PROMETHEUS_PORT=19090
PROMETHEUS_PORT=19090
# ----- Grafana Configuration -----
# Address or hostname for the Grafana service.
# Default: GRAFANA_ADDRESS=172.28.0.1
GRAFANA_ADDRESS=172.28.0.12
# Port on which Grafana service is running.
# Default: GRAFANA_PORT=3000
GRAFANA_PORT=3000
# ======================================
# ============ OpenIM Web ===============
# ======================================
# Path to the OpenIM web distribution.
# Default: OPENIM_WEB_DIST_PATH=/app/dist
OPENIM_WEB_DIST_PATH=/app/dist
# Port on which OpenIM web service is running.
# Default: OPENIM_WEB_PORT=11001
OPENIM_WEB_PORT=11001
# Address or hostname for the OpenIM web service.
# Default: OPENIM_WEB_ADDRESS=172.28.0.1
OPENIM_WEB_ADDRESS=172.28.0.8
# ======================================
# ========= OpenIM Server ==============
# ======================================
# Address or hostname for the OpenIM server.
# Default: OPENIM_SERVER_ADDRESS=172.28.0.1
OPENIM_SERVER_ADDRESS=172.28.0.9
# Port for the OpenIM WebSockets.
# Default: OPENIM_WS_PORT=10001
OPENIM_WS_PORT=10001
# Port for the OpenIM API.
# Default: API_OPENIM_PORT=10002
API_OPENIM_PORT=10002
# ======================================
# ========== OpenIM Chat ===============
# ======================================
# Branch name for OpenIM chat.
# Default: CHAT_BRANCH=main
CHAT_BRANCH=main
# Address or hostname for the OpenIM chat service.
# Default: OPENIM_CHAT_ADDRESS=172.28.0.1
OPENIM_CHAT_ADDRESS=172.28.0.10
# Port for the OpenIM chat API.
# Default: OPENIM_CHAT_API_PORT=10008
OPENIM_CHAT_API_PORT=10008
# Directory path for storing data files or related information for OpenIM chat.
# Default: OPENIM_CHAT_DATA_DIR=./openim-chat/main
OPENIM_CHAT_DATA_DIR=./openim-chat/main
# ======================================
# ========== OpenIM Admin ==============
# ======================================
# Branch name for OpenIM server.
# Default: SERVER_BRANCH=main
SERVER_BRANCH=main
# Port for the OpenIM admin API.
# Default: OPENIM_ADMIN_API_PORT=10009
OPENIM_ADMIN_API_PORT=10009
-90
View File
@@ -1,90 +0,0 @@
# Copyright © 2023 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
name: OpenIM API TEST
on:
push:
branches:
- main
paths-ignore:
- "docs/**"
- "README.md"
- "README_zh-CN.md"
- "CONTRIBUTING.md"
pull_request:
branches:
- main
paths-ignore:
- "README.md"
- "README_zh-CN.md"
- "CONTRIBUTING.md"
- "docs/**"
env:
GO_VERSION: "1.19"
GOLANGCI_VERSION: "v1.50.1"
jobs:
execute-linux-systemd-scripts:
name: Execute OpenIM script on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
environment:
name: openim
strategy:
matrix:
go_version: ["1.20"]
os: ["ubuntu-latest"]
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Go ${{ matrix.go_version }}
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go_version }}
id: go
- name: Install Task
uses: arduino/setup-task@v1
with:
version: '3.x' # If available, use the latest major version that's compatible
repo-token: ${{ secrets.GITHUB_TOKEN }}
- name: Docker Operations
run: |
curl -o docker-compose.yml https://raw.githubusercontent.com/OpenIMSDK/openim-docker/main/example/basic-openim-server-dependency.yml
sudo docker compose up -d
sudo sleep 60
- name: Module Operations
run: |
sudo make tidy
sudo make tools.verify.go-gitlint
- name: Build, Start, Check Services and Print Logs
run: |
sudo ./scripts/install/install.sh -i && \
sudo ./scripts/install/install.sh -s && \
(echo "An error occurred, printing logs:" && sudo cat ./_output/logs/* 2>/dev/null)
- name: Run Test
run: |
sudo make test-api && \
(echo "An error occurred, printing logs:" && sudo cat ./_output/logs/* 2>/dev/null)
- name: Stop Services
run: |
sudo ./scripts/install/install.sh -u && \
(echo "An error occurred, printing logs:" && sudo cat ./_output/logs/* 2>/dev/null)
+12
View File
@@ -52,6 +52,7 @@ jobs:
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
@@ -87,6 +88,16 @@ jobs:
uses: docker/metadata-action@v5.0.0
with:
images: registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-server
# generate Docker tags based on the following events/attributes
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
- name: Log in to AliYun Docker Hub
uses: docker/login-action@v3
@@ -126,6 +137,7 @@ jobs:
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern=v{{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha
+3 -3
View File
@@ -15,11 +15,11 @@
name: Build OpenIM Web Docker image
on:
schedule:
- cron: '30 3 * * *'
# schedule:
# - cron: '30 3 * * *'
push:
branches:
- main
# - main
- release-*
tags:
- v*
+12 -1
View File
@@ -97,4 +97,15 @@ jobs:
- name: Exec OpenIM System uninstall
run: |
sudo ./scripts/install/install.sh -u
sudo ./scripts/install/install.sh -u
- name: gobenchdata publish
uses: bobheadxi/gobenchdata@v1
with:
PRUNE_COUNT: 30
GO_TEST_FLAGS: -cpu 1,2
PUBLISH: true
PUBLISH_BRANCH: gh-pages
env:
GITHUB_TOKEN: ${{ secrets.BOT_GITHUB_TOKEN }}
continue-on-error: true
+1 -1
View File
@@ -41,7 +41,7 @@ jobs:
# ./*.md all markdown files in the root directory
args: --verbose -E -i --no-progress --exclude-path './CHANGELOG' './**/*.md'
env:
GITHUB_TOKEN: ${{secrets.GH_PAT}}
GITHUB_TOKEN: ${{secrets.BOT_GITHUB_TOKEN}}
- name: Create Issue From File
if: env.lychee_exit_code != 0
+3 -2
View File
@@ -25,7 +25,8 @@ WORKDIR ${SERVER_WORKDIR}
# Copy scripts and binary files to the production image
COPY --from=builder ${OPENIM_SERVER_BINDIR} /openim/openim-server/_output/bin
# COPY --from=builder ${OPENIM_SERVER_CMDDIR} /openim/openim-server/scripts
# COPY --from=builder ${SERVER_WORKDIR}/config /openim/openim-server/config
COPY --from=builder ${OPENIM_SERVER_CMDDIR} /openim/openim-server/scripts
COPY --from=builder ${SERVER_WORKDIR}/config /openim/openim-server/config
COPY --from=builder ${SERVER_WORKDIR}/deployments /openim/openim-server/deployments
CMD ["/openim/openim-server/scripts/docker-start-all.sh"]
+1 -1
View File
@@ -30,7 +30,7 @@
</p>
## 🟢 扫描微信进群交流
<img src="https://openim-1253691595.cos.ap-nanjing.myqcloud.com/WechatIMG20.jpeg" width="300">
<img src="./docs/images/Wechat.jpg" width="300">
## Ⓜ️ 关于 OpenIM
+1 -1
View File
@@ -231,7 +231,7 @@ Before you start, please make sure your changes are in demand. The best for that
- [OpenIM Makefile Utilities](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-makefile.md)
- [OpenIM Script Utilities](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/util-scripts.md)
- [OpenIM Versioning](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/version.md)
- [Manage backend and monitor deployment](https://github.com/openimsdk/open-im-server/tree/main/docs/contrib/prometheus-grafana.md)
## :busts_in_silhouette: Community
BIN
View File
Binary file not shown.
+30 -13
View File
@@ -18,11 +18,13 @@ import (
"context"
"fmt"
"net"
"net/http"
_ "net/http/pprof"
"os"
"os/signal"
"strconv"
ginProm "github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"syscall"
"time"
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/tools/discoveryregistry"
@@ -33,6 +35,8 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister"
ginProm "github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
)
func main() {
@@ -51,13 +55,12 @@ func run(port int, proPort int) error {
if port == 0 || proPort == 0 {
err := "port or proPort is empty:" + strconv.Itoa(port) + "," + strconv.Itoa(proPort)
log.ZError(context.Background(), err, nil)
return fmt.Errorf(err)
}
rdb, err := cache.NewRedis()
if err != nil {
log.ZError(context.Background(), "Failed to initialize Redis", err)
return err
}
log.ZInfo(context.Background(), "api start init discov client")
@@ -68,30 +71,29 @@ func run(port int, proPort int) error {
client, err = kdisc.NewDiscoveryRegister(config.Config.Envs.Discovery)
if err != nil {
log.ZError(context.Background(), "Failed to initialize discovery register", err)
return err
}
if err = client.CreateRpcRootNodes(config.Config.GetServiceNames()); err != nil {
log.ZError(context.Background(), "Failed to create RPC root nodes", err)
return err
}
log.ZInfo(context.Background(), "api register public config to discov")
if err = client.RegisterConf2Registry(constant.OpenIMCommonConfigKey, config.Config.EncodeConfig()); err != nil {
log.ZError(context.Background(), "Failed to register public config to discov", err)
return err
}
log.ZInfo(context.Background(), "api register public config to discov success")
router := api.NewGinRouter(client, rdb)
//////////////////////////////
if config.Config.Prometheus.Enable {
p := ginProm.NewPrometheus("app", prommetrics.GetGinCusMetrics("Api"))
p.SetListenAddress(fmt.Sprintf(":%d", proPort))
p.Use(router)
}
/////////////////////////////////
log.ZInfo(context.Background(), "api init router success")
var address string
if config.Config.Api.ListenIP != "" {
address = net.JoinHostPort(config.Config.Api.ListenIP, strconv.Itoa(port))
@@ -100,10 +102,25 @@ func run(port int, proPort int) error {
}
log.ZInfo(context.Background(), "start api server", "address", address, "OpenIM version", config.Version)
err = router.Run(address)
if err != nil {
log.ZError(context.Background(), "api run failed", err, "address", address)
server := http.Server{Addr: address, Handler: router}
go func() {
err = server.ListenAndServe()
if err != nil && err != http.ErrServerClosed {
log.ZError(context.Background(), "api run failed", err, "address", address)
os.Exit(1)
}
}()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
<-sigs
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// graceful shutdown operation.
if err := server.Shutdown(ctx); err != nil {
log.ZError(context.Background(), "failed to api-server shutdown", err)
return err
}
+1
View File
@@ -23,6 +23,7 @@ func main() {
msgGatewayCmd.AddWsPortFlag()
msgGatewayCmd.AddPortFlag()
msgGatewayCmd.AddPrometheusPortFlag()
if err := msgGatewayCmd.Exec(); err != nil {
panic(err.Error())
}
-398
View File
@@ -1,398 +0,0 @@
# Copyright © 2023 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the License);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# -----------------------------------------------------------------
# TODO: This config file is the template file
# --| source: deployments/templates/openim.yaml
# --| env: scripts/install/environment
# --| target: config/config.yaml
# -----------------------------------------------------------------
envs:
discovery: zookeeper
###################### Zookeeper ######################
# Zookeeper configuration
# It's not recommended to modify the schema
#
# Zookeeper address
# Zookeeper username
# Zookeeper password
zookeeper:
schema: openim
address: [ 172.28.0.1:12181 ]
username: ''
password: ''
###################### Mysql ######################
# MySQL configuration
# Currently, only single machine setup is supported
#
# Maximum number of open connections
# Maximum number of idle connections
# Maximum lifetime in seconds a connection can be reused
# Log level: 1=slient, 2=error, 3=warn, 4=info
# Slow query threshold in milliseconds
mysql:
address: [ 172.28.0.1:13306 ]
username: root
password: openIM123
database: openIM_v3
maxOpenConn: 1000
maxIdleConn: 100
maxLifeTime: 60
logLevel: 4
slowThreshold: 500
###################### Mongo ######################
# MongoDB configuration
# If uri is not empty, it will be used directly
#
# MongoDB address for standalone setup, Mongos address for sharded cluster setup
# Default MongoDB database name
# Maximum connection pool size
mongo:
uri: ''
address: [ 172.28.0.1:37017 ]
database: openIM_v3
username: root
password: openIM123
maxPoolSize: 100
###################### Redis configuration information ######################
# Redis configuration
#
# Username is required only for Redis version 6.0+
redis:
address: [ 172.28.0.1:16379 ]
username: ''
password: openIM123
###################### Kafka configuration information ######################
# Kafka configuration
#
# Kafka username
# Kafka password
# It's not recommended to modify this topic name
# Consumer group ID, it's not recommended to modify
kafka:
username: ''
password: ''
addr: [ 172.28.0.1:19094 ]
latestMsgToRedis:
topic: "latestMsgToRedis"
offlineMsgToMongo:
topic: "offlineMsgToMongoMysql"
msgToPush:
topic: "msgToPush"
consumerGroupID:
msgToRedis: redis
msgToMongo: mongo
msgToMySql: mysql
msgToPush: push
###################### RPC configuration information ######################
# RPC configuration
#
# IP address to register with zookeeper when starting RPC, the IP and corresponding rpcPort should be accessible by api/gateway
# Default listen IP is 0.0.0.0
rpc:
registerIP: ''
listenIP: 0.0.0.0
###################### API configuration information ######################
# API configuration
#
# API service port
# Default listen IP is 0.0.0.0
api:
openImApiPort: [ 10002 ]
listenIP: 0.0.0.0
###################### Object configuration information ######################
# Object storage configuration
#
# Use minio for object storage
# API URL should be accessible by the app
# It's not recommended to modify the bucket name
# Endpoint should be accessible by the app
# Session token
# Configuration for Tencent COS
# Configuration for Aliyun OSS
# apiURL is the address of the api, the access address of the app, use s3 must be configured
# minio.endpoint can be configured as an intranet address,
# minio.signEndpoint is minio public network address
object:
enable: "minio"
apiURL: "http://127.0.0.1:10002"
minio:
bucket: "openim"
endpoint: "http://172.28.0.1:10005"
accessKeyID: "root"
secretAccessKey: "openIM123"
sessionToken: ''
signEndpoint: "http://127.0.0.1:10005"
publicRead: false
cos:
bucketURL: https://temp-1252357374.cos.ap-chengdu.myqcloud.com
secretID: ''
secretKey: ''
sessionToken: ''
publicRead: false
oss:
endpoint: "https://oss-cn-chengdu.aliyuncs.com"
bucket: "demo-9999999"
bucketURL: "https://demo-9999999.oss-cn-chengdu.aliyuncs.com"
accessKeyID: ''
accessKeySecret: ''
sessionToken: ''
publicRead: false
###################### RPC Port Configuration ######################
# RPC service ports
# These ports are passed into the program by the script and are not recommended to modify
# For launching multiple programs, just fill in multiple ports separated by commas
# For example, [10110, 10111]
rpcPort:
openImUserPort: [ 10110 ]
openImFriendPort: [ 10120 ]
openImMessagePort: [ 10130 ]
openImGroupPort: [ 10150 ]
openImAuthPort: [ 10160 ]
openImPushPort: [ 10170 ]
openImConversationPort: [ 10180 ]
openImThirdPort: [ 10190 ]
###################### RPC Register Name Configuration ######################
# RPC service names for registration, it's not recommended to modify these
rpcRegisterName:
openImUserName: User
openImFriendName: Friend
openImMsgName: Msg
openImPushName: Push
openImMessageGatewayName: MessageGateway
openImGroupName: Group
openImAuthName: Auth
openImConversationName: Conversation
openImThirdName: Third
###################### Log Configuration ######################
# Log configuration
#
# Storage directory
# Log rotation time
# Maximum number of logs to retain
# Log level, 6 means all levels
# Whether to output to stdout
# Whether to output in json format
# Whether to include stack trace in logs
log:
storageLocation: ./logs/
rotationTime: 24
remainRotationCount: 2
remainLogLevel: 6
isStdout: false
isJson: false
withStack: false
###################### Variables definition ######################
# Long connection server configuration
#
# Websocket port for msg_gateway
# Maximum number of websocket connections
# Maximum length of websocket request package
# Websocket connection handshake timeout
longConnSvr:
openImWsPort: [ 10001 ]
websocketMaxConnNum: 100000
openImMessageGatewayPort: [ 10140 ]
websocketMaxMsgLen: 4096
websocketTimeout: 10
# Push notification service configuration
#
# Use GeTui for push notifications
# GeTui offline push configuration
# FCM offline push configuration
# Account file, place it in the config directory
# JPush configuration, modify these after applying in JPush backend
push:
enable: getui
geTui:
pushUrl: "https://restapi.getui.com/v2/$appId"
masterSecret: ''
appKey: ''
intent: ''
channelID: ''
channelName: ''
fcm:
serviceAccount: "x.json"
jpns:
appKey: ''
masterSecret: ''
pushUrl: ''
pushIntent: ''
# App manager configuration
#
# Built-in app manager user IDs
# Built-in app manager nicknames
manager:
userID: [ "openIM123456", "openIM654321", "openIMAdmin" ]
nickname: [ "system1", "system2", "system3" ]
# Multi-platform login policy
# For each platform(Android, iOS, Windows, Mac, web), only one can be online at a time
multiLoginPolicy: 1
# Whether to store messages in MySQL, messages in MySQL are only used for management background
chatPersistenceMysql: true
# Message cache timeout in seconds, it's not recommended to modify
msgCacheTimeout: 86400
# Whether to enable read receipts for group chat
groupMessageHasReadReceiptEnable: true
# Whether to enable read receipts for single chat
singleMessageHasReadReceiptEnable: true
# MongoDB offline message retention period in days
retainChatRecords: 365
# Schedule to clear expired messages(older than retainChatRecords days) in MongoDB every Wednesday at 2am
# This deletion is just for cleaning up disk usage according to previous configuration retainChatRecords, no notification will be sent
chatRecordsClearTime: "0 2 * * 3"
# Schedule to auto delete messages every day at 2am
# This deletion is for messages that have been retained for more than msg_destruct_time (seconds) in the conversation field
msgDestructTime: "0 2 * * *"
# Secret key
secret: openIM123
# Token policy
#
# Token expiration period in days
tokenPolicy:
expire: 90
# Message verification policy
#
# Whether to verify friendship when sending messages
messageVerify:
friendVerify: false
# iOS push notification configuration
#
# iOS push notification sound
# Whether to count badge
# Whether it's production environment
iosPush:
pushSound: "xxx"
badgeCount: true
production: false
###################### Third-party service configuration ######################
# Callback configuration
#
# Callback URL
# Whether to enable this callback event
# Timeout in seconds
# Whether to continue execution if callback fails
callback:
url:
beforeSendSingleMsg:
enable: false
timeout: 5
failedContinue: true
afterSendSingleMsg:
enable: false
timeout: 5
beforeSendGroupMsg:
enable: false
timeout: 5
failedContinue: true
afterSendGroupMsg:
enable: false
timeout: 5
msgModify:
enable: false
timeout: 5
failedContinue: true
userOnline:
enable: false
timeout: 5
userOffline:
enable: false
timeout: 5
userKickOff:
enable: false
timeout: 5
offlinePush:
enable: false
timeout: 5
failedContinue: true
onlinePush:
enable: false
timeout: 5
failedContinue: true
superGroupOnlinePush:
enable: false
timeout: 5
failedContinue: true
beforeAddFriend:
enable: false
timeout: 5
failedContinue: true
beforeUpdateUserInfo:
enable: false
timeout: 5
failedContinue: true
beforeCreateGroup:
enable: false
timeout: 5
failedContinue: true
beforeMemberJoinGroup:
enable: false
timeout: 5
failedContinue: true
beforeSetGroupMemberInfo:
enable: false
timeout: 5
failedContinue: true
setMessageReactionExtensions:
enable: false
timeout: 5
failedContinue: true
###################### Prometheus ######################
# Prometheus configuration for various services
# The number of Prometheus ports per service needs to correspond to rpcPort
# The number of ports needs to be consistent with msg_transfer_service_num in script/path_info.sh
prometheus:
enable: true
prometheusUrl: "https://openim.prometheus"
apiPrometheusPort: [20100]
userPrometheusPort: [ 20110 ]
friendPrometheusPort: [ 20120 ]
messagePrometheusPort: [ 20130 ]
messageGatewayPrometheusPort: [ 20140 ]
groupPrometheusPort: [ 20150 ]
authPrometheusPort: [ 20160 ]
pushPrometheusPort: [ 20170 ]
conversationPrometheusPort: [ 20230 ]
rtcPrometheusPort: [ 21300 ]
thirdPrometheusPort: [ 21301 ]
messageTransferPrometheusPort: [ 21400, 21401, 21402, 21403 ] # List of ports
+16
View File
@@ -0,0 +1,16 @@
{{ define "email.to.html" }}
{{ range .Alerts }}
<!-- Begin of OpenIM Alert -->
<div style="border:1px solid #ccc; padding:10px; margin-bottom:10px;">
<h3>OpenIM Alert</h3>
<p><strong>Alert Program:</strong> Prometheus Alert</p>
<p><strong>Severity Level:</strong> {{ .Labels.severity }}</p>
<p><strong>Alert Type:</strong> {{ .Labels.alertname }}</p>
<p><strong>Affected Host:</strong> {{ .Labels.instance }}</p>
<p><strong>Affected Service:</strong> {{ .Labels.job }}</p>
<p><strong>Alert Subject:</strong> {{ .Annotations.summary }}</p>
<p><strong>Trigger Time:</strong> {{ .StartsAt.Format "2006-01-02 15:04:05" }}</p>
</div>
<!-- End of OpenIM Alert -->
{{ end }}
{{ end }}
+22
View File
@@ -0,0 +1,22 @@
groups:
- name: instance_down
rules:
- alert: InstanceDown
expr: up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "Instance {{ $labels.instance }} down"
description: "{{ $labels.instance }} of job {{ $labels.job }} has been down for more than 1 minutes."
- name: database_insert_failure_alerts
rules:
- alert: DatabaseInsertFailed
expr: (increase(msg_insert_redis_failed_total[5m]) > 0) or (increase(msg_insert_mongo_failed_total[5m]) > 0)
for: 1m
labels:
severity: critical
annotations:
summary: "Increase in MsgInsertRedisFailedCounter or MsgInsertMongoFailedCounter detected"
description: "Either MsgInsertRedisFailedCounter or MsgInsertMongoFailedCounter has increased in the last 5 minutes, indicating failures in message insert operations to Redis or MongoDB,maybe the redis or mongodb is crash."
File diff suppressed because it is too large Load Diff
+85
View File
@@ -0,0 +1,85 @@
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets: ['172.28.0.1:19093']
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
- "instance-down-rules.yml"
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label "job='job_name'"" to any timeseries scraped from this config.
# Monitored information captured by prometheus
- job_name: 'node-exporter'
static_configs:
- targets: [ '172.28.0.1:19100' ]
labels:
namespace: 'default'
# prometheus fetches application services
- job_name: 'openimserver-openim-api'
static_configs:
- targets: [ '172.28.0.1:20100' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-msggateway'
static_configs:
- targets: [ '172.28.0.1:20140' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-msgtransfer'
static_configs:
- targets: [ 172.28.0.1:21400, 172.28.0.1:21401, 172.28.0.1:21402, 172.28.0.1:21403 ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-push'
static_configs:
- targets: [ '172.28.0.1:20170' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-auth'
static_configs:
- targets: [ '172.28.0.1:20160' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-conversation'
static_configs:
- targets: [ '172.28.0.1:20230' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-friend'
static_configs:
- targets: [ '172.28.0.1:20120' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-group'
static_configs:
- targets: [ '172.28.0.1:20150' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-msg'
static_configs:
- targets: [ '172.28.0.1:20130' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-third'
static_configs:
- targets: [ '172.28.0.1:21301' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-user'
static_configs:
- targets: [ '172.28.0.1:20110' ]
labels:
namespace: 'default'
+27 -3
View File
@@ -84,8 +84,8 @@ $ sudo sealos run labring/kubernetes:v1.25.0 labring/helm:v3.8.2 labring/calico:
If you are local, you can also use Kind and Minikube to test, for example, using Kind:
```bash
$ sGO111MODULE="on" go get sigs.k8s.io/kind@v0.11.1
$ skind create cluster
$ GO111MODULE="on" go get sigs.k8s.io/kind@v0.11.1
$ kind create cluster
```
### Installing helm
@@ -123,6 +123,30 @@ Explore our Helm-Charts repository and read through: [Helm-Charts Repository](ht
Using the helm charts repository, you can ignore the following configuration, but if you want to just use the server and scale on top of it, you can go ahead:
**Use the Helm template to generate the deployment yaml file: `openim-charts.yaml`**
**Gen Image:**
```bash
../scripts/genconfig.sh ../scripts/install/environment.sh ./templates/helm-image.yaml > ./charts/generated-configs/helm-image.yaml
```
**Gen Charts:**
```bash
for chart in ./charts/*/; do
if [[ "$chart" == *"generated-configs"* || "$chart" == *"helmfile.yaml"* ]]; then
continue
fi
if [ -f "${chart}values.yaml" ]; then
helm template "$chart" -f "./charts/generated-configs/helm-image.yaml" -f "./charts/generated-configs/config.yaml" -f "./charts/generated-configs/notification.yaml" >> openim-charts.yaml
else
helm template "$chart" >> openim-charts.yaml
fi
done
```
**Use Helmfile:**
```bash
@@ -150,4 +174,4 @@ cp ../config/notification.yaml ./charts/generated-configs/notification.yaml
```bash
helmfile apply
```
```
File diff suppressed because it is too large Load Diff
+33
View File
@@ -0,0 +1,33 @@
###################### AlertManager Configuration ######################
# AlertManager configuration using environment variables
#
# Resolve timeout
# SMTP configuration for sending alerts
# Templates for email notifications
# Routing configurations for alerts
# Receiver configurations
global:
resolve_timeout: ${ALERTMANAGER_RESOLVE_TIMEOUT}
smtp_from: ${ALERTMANAGER_SMTP_FROM}
smtp_smarthost: ${ALERTMANAGER_SMTP_SMARTHOST}
smtp_auth_username: ${ALERTMANAGER_SMTP_AUTH_USERNAME}
smtp_auth_password: ${ALERTMANAGER_SMTP_AUTH_PASSWORD}
smtp_require_tls: ${ALERTMANAGER_SMTP_REQUIRE_TLS}
smtp_hello: ${ALERTMANAGER_SMTP_HELLO}
templates:
- /etc/alertmanager/email.tmpl
route:
group_by: ['alertname']
group_wait: 5s
group_interval: 5s
repeat_interval: 5m
receiver: email
receivers:
- name: email
email_configs:
- to: '${ALERTMANAGER_EMAIL_TO}'
html: '{{ template "email.to.html" . }}'
headers: { Subject: "[OPENIM-SERVER]Alarm" }
send_resolved: true
+8 -1
View File
@@ -96,7 +96,14 @@ verifyCode:
accessKeySecret: ""
signName: ""
verificationCodeTemplateCode: ""
mail: # 根据对应的发件邮箱更改 sendMail、senderAuthorizationCode、smtpAddr、smtpPort 即可
title: ""
senderMail: "" # 发送者
senderAuthorizationCode: "" # 授权码
smtpAddr: "smtp.qq.com" # smtp 服务器地址
smtpPort: 25 # smtp 服务器邮件发送端口
testDepartMentID: 001
imAPIURL: http://127.0.0.1:10002
###################### Proxy Header ######################
# 获取ip的header,没有配置直接获取远程地址
+34 -4
View File
@@ -94,12 +94,22 @@ OPENIM_CHAT_NETWORK_ADDRESS=${OPENIM_CHAT_NETWORK_ADDRESS}
# Address or hostname for the Prometheus network.
# Default: PROMETHEUS_NETWORK_ADDRESS=172.28.0.11
PROMETHEUS_NETWORK_ADDRESS=${PROMETHEUS_NETWORK_ADDRESS}
# Address or hostname for the Grafana network.
# Default: GRAFANA_NETWORK_ADDRESS=172.28.0.12
GRAFANA_NETWORK_ADDRESS=${GRAFANA_NETWORK_ADDRESS}
# Address or hostname for the node_exporter network.
# Default: NODE_EXPORTER_NETWORK_ADDRESS=172.28.0.13
NODE_EXPORTER_NETWORK_ADDRESS=${NODE_EXPORTER_NETWORK_ADDRESS}
# Address or hostname for the OpenIM admin network.
# Default: OPENIM_ADMIN_NETWORK_ADDRESS=172.28.0.14
OPENIM_ADMIN_FRONT_NETWORK_ADDRESS=${OPENIM_ADMIN_FRONT_NETWORK_ADDRESS}
# Address or hostname for the alertmanager network.
# Default: ALERT_MANAGER_NETWORK_ADDRESS=172.28.0.15
ALERT_MANAGER_NETWORK_ADDRESS=${ALERT_MANAGER_NETWORK_ADDRESS}
# ===============================================
# = Component Extension Configuration =
# ===============================================
@@ -215,7 +225,7 @@ PROMETHEUS_PORT=${PROMETHEUS_PORT}
GRAFANA_ADDRESS=${GRAFANA_NETWORK_ADDRESS}
# Port on which Grafana service is running.
# Default: GRAFANA_PORT=3000
# Default: GRAFANA_PORT=13000
GRAFANA_PORT=${GRAFANA_PORT}
# ======================================
@@ -283,3 +293,23 @@ SERVER_BRANCH=${SERVER_BRANCH}
# Port for the OpenIM admin API.
# Default: OPENIM_ADMIN_API_PORT=10009
OPENIM_ADMIN_API_PORT=${OPENIM_ADMIN_API_PORT}
# Port for the node exporter.
# Default: NODE_EXPORTER_PORT=19100
NODE_EXPORTER_PORT=${NODE_EXPORTER_PORT}
# Port for the prometheus.
# Default: PROMETHEUS_PORT=19090
PROMETHEUS_PORT=${PROMETHEUS_PORT}
# Port for the grafana.
# Default: GRAFANA_PORT=13000
GRAFANA_PORT=${GRAFANA_PORT}
# Port for the admin front.
# Default: OPENIM_ADMIN_FRONT_PORT=11002
OPENIM_ADMIN_FRONT_PORT=${OPENIM_ADMIN_FRONT_PORT}
# Port for the alertmanager.
# Default: ALERT_MANAGER_PORT=19093
ALERT_MANAGER_PORT=${ALERT_MANAGER_PORT}
+178 -50
View File
@@ -158,13 +158,21 @@ object:
accessKeySecret: ${OSS_ACCESS_KEY_SECRET}
sessionToken: ${OSS_SESSION_TOKEN}
publicRead: ${OSS_PUBLIC_READ}
kodo:
endpoint: "${KODO_ENDPOINT}"
bucket: "${KODO_BUCKET}"
bucketURL: "${KODO_BUCKET_URL}"
accessKeyID: ${KODO_ACCESS_KEY_ID}
accessKeySecret: ${KODO_ACCESS_KEY_SECRET}
sessionToken: ${KODO_SESSION_TOKEN}
publicRead: ${KODO_PUBLIC_READ}
###################### RPC Port Configuration ######################
# RPC service ports
# These ports are passed into the program by the script and are not recommended to modify
# For launching multiple programs, just fill in multiple ports separated by commas
# For example, [10110, 10111]
rpcPort:
rpcPort:
openImUserPort: [ ${OPENIM_USER_PORT} ]
openImFriendPort: [ ${OPENIM_FRIEND_PORT} ]
openImMessagePort: [ ${OPENIM_MESSAGE_PORT} ]
@@ -312,71 +320,191 @@ iosPush:
# Timeout in seconds
# Whether to continue execution if callback fails
callback:
url:
url: ""
beforeSendSingleMsg:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
afterSendSingleMsg:
enable: false
timeout: 5
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
beforeSendGroupMsg:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
afterSendGroupMsg:
enable: false
timeout: 5
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
msgModify:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
userOnline:
enable: false
timeout: 5
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
userOffline:
enable: false
timeout: 5
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
userKickOff:
enable: false
timeout: 5
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
offlinePush:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
onlinePush:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
superGroupOnlinePush:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
beforeAddFriend:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
beforeUpdateUserInfo:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
beforeCreateGroup:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
afterCreateGroup:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
beforeMemberJoinGroup:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
beforeSetGroupMemberInfo:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
afterSetGroupMemberInfo:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
setMessageReactionExtensions:
enable: false
timeout: 5
failedContinue: true
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
quitGroup:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
killGroupMember:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
dismissGroup:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
joinGroup:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
groupMsgRead:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
singleMsgRead:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
updateUserInfo:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
beforeUserRegister:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
afterUserRegister:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
transferGroupOwner:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
beforeSetFriendRemark:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
afterSetFriendRemark:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
afterGroupMsgRead:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
afterGroupMsgRevoke:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
afterJoinGroup:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
beforeInviteUserToGroup:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
joinGroupAfter:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
setGroupInfoAfter:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
setGroupInfoBefore:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
revokeMsgAfter:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
addBlackBefore:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
addFriendAfter:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
addFriendAgreeBefore:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
deleteFriendAfter:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
importFriendsBefore:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
importFriendsAfter:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
removeBlackAfter:
enable: ${CALLBACK_ENABLE}
timeout: ${CALLBACK_TIMEOUT}
failedContinue: ${CALLBACK_FAILED_CONTINUE}
###################### Prometheus ######################
# Prometheus configuration for various services
# The number of Prometheus ports per service needs to correspond to rpcPort
@@ -395,4 +523,4 @@ prometheus:
conversationPrometheusPort: [ ${CONVERSATION_PROM_PORT} ]
rtcPrometheusPort: [ ${RTC_PROM_PORT} ]
thirdPrometheusPort: [ ${THIRD_PROM_PORT} ]
messageTransferPrometheusPort: [ ${MSG_TRANSFER_PROM_PORT} ] # List of ports
messageTransferPrometheusPort: [ ${MSG_TRANSFER_PROM_PORT} ] # List of ports
+85
View File
@@ -0,0 +1,85 @@
# my global config
global:
scrape_interval: 15s # Set the scrape interval to every 15 seconds. Default is every 1 minute.
evaluation_interval: 15s # Evaluate rules every 15 seconds. The default is every 1 minute.
# scrape_timeout is set to the global default (10s).
# Alertmanager configuration
alerting:
alertmanagers:
- static_configs:
- targets: ['${ALERT_MANAGER_ADDRESS}:${ALERT_MANAGER_PORT}']
# Load rules once and periodically evaluate them according to the global 'evaluation_interval'.
rule_files:
- "instance-down-rules.yml"
# - "first_rules.yml"
# - "second_rules.yml"
# A scrape configuration containing exactly one endpoint to scrape:
# Here it's Prometheus itself.
scrape_configs:
# The job name is added as a label "job='job_name'"" to any timeseries scraped from this config.
# Monitored information captured by prometheus
- job_name: 'node-exporter'
static_configs:
- targets: [ '${NODE_EXPORTER_ADDRESS}:${NODE_EXPORTER_PORT}' ]
labels:
namespace: 'default'
# prometheus fetches application services
- job_name: 'openimserver-openim-api'
static_configs:
- targets: [ '${OPENIM_SERVER_ADDRESS}:${API_PROM_PORT}' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-msggateway'
static_configs:
- targets: [ '${OPENIM_SERVER_ADDRESS}:${MSG_GATEWAY_PROM_PORT}' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-msgtransfer'
static_configs:
- targets: [ ${MSG_TRANSFER_PROM_ADDRESS_PORT} ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-push'
static_configs:
- targets: [ '${OPENIM_SERVER_ADDRESS}:${PUSH_PROM_PORT}' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-auth'
static_configs:
- targets: [ '${OPENIM_SERVER_ADDRESS}:${AUTH_PROM_PORT}' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-conversation'
static_configs:
- targets: [ '${OPENIM_SERVER_ADDRESS}:${CONVERSATION_PROM_PORT}' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-friend'
static_configs:
- targets: [ '${OPENIM_SERVER_ADDRESS}:${FRIEND_PROM_PORT}' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-group'
static_configs:
- targets: [ '${OPENIM_SERVER_ADDRESS}:${GROUP_PROM_PORT}' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-msg'
static_configs:
- targets: [ '${OPENIM_SERVER_ADDRESS}:${MESSAGE_PROM_PORT}' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-third'
static_configs:
- targets: [ '${OPENIM_SERVER_ADDRESS}:${THIRD_PROM_PORT}' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-user'
static_configs:
- targets: [ '${OPENIM_SERVER_ADDRESS}:${USER_PROM_PORT}' ]
labels:
namespace: 'default'
+76 -11
View File
@@ -67,17 +67,17 @@ services:
ipv4_address: ${REDIS_NETWORK_ADDRESS}
zookeeper:
image: bitnami/zookeeper:3.8
container_name: zookeeper
ports:
- "${ZOOKEEPER_PORT}:2181"
volumes:
- "/etc/localtime:/etc/localtime"
environment:
- ALLOW_ANONYMOUS_LOGIN=yes
- TZ="Asia/Shanghai"
restart: always
networks:
image: bitnami/zookeeper:3.8
container_name: zookeeper
ports:
- "${ZOOKEEPER_PORT}:2181"
volumes:
- "/etc/localtime:/etc/localtime"
environment:
- ALLOW_ANONYMOUS_LOGIN=yes
- TZ="Asia/Shanghai"
restart: always
networks:
server:
ipv4_address: ${ZOOKEEPER_NETWORK_ADDRESS}
@@ -142,3 +142,68 @@ services:
server:
ipv4_address: ${OPENIM_WEB_NETWORK_ADDRESS}
openim-admin:
image: ${IMAGE_REGISTRY}/openim-admin-front:v3.4.0
# image: ghcr.io/openimsdk/openim-admin-front:v3.4.0
# image: registry.cn-hangzhou.aliyuncs.com/openimsdk/openim-admin-front:v3.4.0
# image: openim/openim-admin-front:v3.4.0
container_name: openim-admin
restart: always
ports:
- "${OPENIM_ADMIN_FRONT_PORT}:80"
networks:
server:
ipv4_address: ${OPENIM_ADMIN_FRONT_NETWORK_ADDRESS}
prometheus:
image: prom/prometheus
container_name: prometheus
hostname: prometheus
restart: always
volumes:
- ./config/prometheus.yml:/etc/prometheus/prometheus.yml
- ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
ports:
- "${PROMETHEUS_PORT}:9090"
networks:
server:
ipv4_address: ${PROMETHEUS_NETWORK_ADDRESS}
alertmanager:
image: prom/alertmanager
container_name: alertmanager
hostname: alertmanager
restart: always
volumes:
- ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml
- ./config/email.tmpl:/etc/alertmanager/email.tmpl
ports:
- "${ALERT_MANAGER_PORT}:9093"
networks:
server:
ipv4_address: ${ALERT_MANAGER_NETWORK_ADDRESS}
grafana:
image: grafana/grafana
container_name: grafana
hostname: grafana
user: root
restart: always
ports:
- "${GRAFANA_PORT}:3000"
volumes:
- ${DATA_DIR}/components/grafana:/var/lib/grafana
networks:
server:
ipv4_address: ${GRAFANA_NETWORK_ADDRESS}
node-exporter:
image: quay.io/prometheus/node-exporter
container_name: node-exporter
hostname: node-exporter
restart: always
ports:
- "${NODE_EXPORTER_PORT}:9100"
networks:
server:
ipv4_address: ${NODE_EXPORTER_NETWORK_ADDRESS}
+22 -6
View File
@@ -37,6 +37,7 @@
* 2.20. [Prometheus Configuration](#PrometheusConfiguration-1)
* 2.20.1. [General Configuration](#GeneralConfiguration)
* 2.20.2. [Service-Specific Prometheus Ports](#Service-SpecificPrometheusPorts)
* 2.21. [Qiniu Cloud Kodo Configuration](#QiniuCloudKODOConfiguration)
## 0. <a name='TableofContents'></a>OpenIM Config File
@@ -150,7 +151,7 @@ For convenience, configuration through modifying environment variables is recomm
+ **Description**: API address.
+ **Note**: If the server has an external IP, it will be automatically obtained. For internal networks, set this variable to the IP serving internally.
```
```bash
export API_URL="http://ip:10002"
```
@@ -412,7 +413,7 @@ Configuration for Grafana, including its port and address.
| Parameter | Example Value | Description |
| --------------- | -------------------------- | --------------------- |
| GRAFANA_PORT | "3000" | Port used by Grafana. |
| GRAFANA_PORT | "13000" | Port used by Grafana. |
| GRAFANA_ADDRESS | "${DOCKER_BRIDGE_GATEWAY}" | Address for Grafana. |
### 2.16. <a name='RPCPortConfigurationVariables'></a>RPC Port Configuration Variables
@@ -466,7 +467,7 @@ This section involves configuring the log settings, including storage location,
This section involves setting up additional configuration variables for Websocket, Push Notifications, and Chat.
| Parameter | Example Value | Description |
| ----------------------- | ----------------- | ---------------------------------- |
|-------------------------|-------------------|------------------------------------|
| WEBSOCKET_MAX_CONN_NUM | "100000" | Maximum Websocket connections |
| WEBSOCKET_MAX_MSG_LEN | "4096" | Maximum Websocket message length |
| WEBSOCKET_TIMEOUT | "10" | Websocket timeout |
@@ -500,9 +501,9 @@ This section involves setting up additional configuration variables for Websocke
| TOKEN_EXPIRE | "90" | Token Expiry Time |
| FRIEND_VERIFY | "false" | Friend Verification Enable |
| IOS_PUSH_SOUND | "xxx" | iOS |
| CALLBACK_ENABLE | "false" | Enable callback |
| CALLBACK_TIMEOUT | "5" | Maximum timeout for callback call |
| CALLBACK_FAILED_CONTINUE| "true" | fails to continue to the next step |
### 2.20. <a name='PrometheusConfiguration-1'></a>Prometheus Configuration
This section involves configuring Prometheus, including enabling/disabling it and setting up ports for various services.
@@ -528,3 +529,18 @@ This section involves configuring Prometheus, including enabling/disabling it an
| RTC Service | `RTC_PROM_PORT` | '21300' | Prometheus port for the RTC service. |
| Third Service | `THIRD_PROM_PORT` | '21301' | Prometheus port for the Third service. |
| Message Transfer Service | `MSG_TRANSFER_PROM_PORT` | '21400, 21401, 21402, 21403' | Prometheus ports for the Message Transfer service. |
### 2.21. <a name='QiniuCloudKODOConfiguration'></a>Qiniu Cloud Kodo Configuration
This section involves setting up Qiniu Cloud Kodo, including its endpoint, bucket name, and credentials.
| Parameter | Example Value | Description |
| --------------------- | ------------------------------------------------------------ | ---------------------------------------- |
| KODO_ENDPOINT | "[http://s3.cn-east-1.qiniucs.com](http://s3.cn-east-1.qiniucs.com)" | Endpoint URL for Qiniu Cloud Kodo. |
| KODO_BUCKET | "demo-9999999" | Bucket name for Qiniu Cloud Kodo. |
| KODO_BUCKET_URL | "[http://your.domain.com](http://your.domain.com)" | Bucket URL for Qiniu Cloud Kodo. |
| KODO_ACCESS_KEY_ID | [User Defined] | Access key ID for Qiniu Cloud Kodo. |
| KODO_ACCESS_KEY_SECRET | [User Defined] | Access key secret for Qiniu Cloud Kodo. |
| KODO_SESSION_TOKEN | [User Defined] | Session token for Qiniu Cloud Kodo. |
| KODO_PUBLIC_READ | "false" | Public read access. |
+323
View File
@@ -0,0 +1,323 @@
# Deployment and Design of OpenIM's Management Backend and Monitoring
<!-- vscode-markdown-toc -->
* 1. [Source Code & Docker](#SourceCodeDocker)
* 1.1. [Deployment](#Deployment)
* 1.2. [Configuration](#Configuration)
* 1.3. [Monitoring Running in Docker Guide](#MonitoringRunninginDockerGuide)
* 1.3.1. [Introduction](#Introduction)
* 1.3.2. [Prerequisites](#Prerequisites)
* 1.3.3. [Step 1: Clone the Repository](#Step1:ClonetheRepository)
* 1.3.4. [Step 2: Start Docker Compose](#Step2:StartDockerCompose)
* 1.3.5. [Step 3: Use the OpenIM Web Interface](#Step3:UsetheOpenIMWebInterface)
* 1.3.6. [Running Effect](#RunningEffect)
* 1.3.7. [Step 4: Access the Admin Panel](#Step4:AccesstheAdminPanel)
* 1.3.8. [Step 5: Access the Monitoring Interface](#Step5:AccesstheMonitoringInterface)
* 1.3.9. [Next Steps](#NextSteps)
* 1.3.10. [Troubleshooting](#Troubleshooting)
* 2. [Kubernetes](#Kubernetes)
* 2.1. [Middleware Monitoring](#MiddlewareMonitoring)
* 2.2. [Custom OpenIM Metrics](#CustomOpenIMMetrics)
* 2.3. [Node Exporter](#NodeExporter)
* 3. [Setting Up and Configuring AlertManager Using Environment Variables and `make init`](#SettingUpandConfiguringAlertManagerUsingEnvironmentVariablesandmakeinit)
* 3.1. [Introduction](#Introduction-1)
* 3.2. [Prerequisites](#Prerequisites-1)
* 3.3. [Configuration Steps](#ConfigurationSteps)
* 3.3.1. [Exporting Environment Variables](#ExportingEnvironmentVariables)
* 3.3.2. [Initializing AlertManager](#InitializingAlertManager)
* 3.3.3. [Key Configuration Fields](#KeyConfigurationFields)
* 3.3.4. [Configuring SMTP Authentication Password](#ConfiguringSMTPAuthenticationPassword)
* 3.3.5. [Useful Links for Common Email Servers](#UsefulLinksforCommonEmailServers)
* 3.4. [Conclusion](#Conclusion)
<!-- vscode-markdown-toc-config
numbering=true
autoSave=true
/vscode-markdown-toc-config -->
<!-- /vscode-markdown-toc -->
OpenIM offers various flexible deployment options to suit different environments and requirements. Here is a simplified and optimized description of these deployment options:
1. Source Code Deployment:
+ **Regular Source Code Deployment**: Deployment using the `nohup` method. This is a basic deployment method suitable for development and testing environments. For details, refer to the [Regular Source Code Deployment Guide](https://docs.openim.io/).
+ **Production-Level Deployment**: Deployment using the `system` method, more suitable for production environments. This method provides higher stability and reliability. For details, refer to the [Production-Level Deployment Guide](https://docs.openim.io/guides/gettingStarted/install-openim-linux-system).
2. Cluster Deployment:
+ **Kubernetes Deployment**: Provides two deployment methods, including deployment through Helm and sealos. This is suitable for environments that require high availability and scalability. Specific methods can be found in the [Kubernetes Deployment Guide](https://docs.openim.io/guides/gettingStarted/k8s-deployment).
3. Docker Deployment:
+ **Regular Docker Deployment**: Suitable for quick deployments and small projects. For detailed information, refer to the [Docker Deployment Guide](https://docs.openim.io/guides/gettingStarted/dockerCompose).
+ **Docker Compose Deployment**: Provides more convenient service management and configuration, suitable for complex multi-container applications.
Next, we will introduce the specific steps, monitoring, and management backend configuration for each of these deployment methods, as well as usage tips to help you choose the most suitable deployment option according to your needs.
## 1. <a name='SourceCodeDocker'></a>Source Code & Docker
### 1.1. <a name='Deployment'></a>Deployment
OpenIM deploys openim-server and openim-chat from source code, while other components are deployed via Docker.
For Docker deployment, you can deploy all components with a single command using the [openimsdk/openim-docker](https://github.com/openimsdk/openim-docker) repository. The deployment configuration can be found in the [environment.sh](https://github.com/openimsdk/open-im-server/blob/main/scripts/install/environment.sh) document, which provides information on how to learn and familiarize yourself with various environment variables.
For Prometheus, it is not enabled by default. To enable it, set the environment variable before executing `make init`:
```bash
export PROMETHEUS_ENABLE=true # Default is false
```
Then, execute:
```bash
make init
docker compose up -d
```
### 1.2. <a name='Configuration'></a>Configuration
To configure Prometheus data sources in Grafana, follow these steps:
1. **Log in to Grafana**: First, open your web browser and access the Grafana URL. If you haven't changed the port, the address is typically [http://localhost:13000](http://localhost:13000/).
2. **Log in with default credentials**: Grafana's default username and password are both `admin`. You will be prompted to change the password on your first login.
3. **Access Data Sources Settings**:
+ In the left menu of Grafana, look for and click the "gear" icon representing "Configuration."
+ In the configuration menu, select "Data Sources."
4. **Add a New Data Source**:
+ On the Data Sources page, click the "Add data source" button.
+ In the list, find and select "Prometheus."
![image-20231114175117374](http://sm.nsddd.top/sm202311141751692.png)
Click `Add New connection` to add more data sources, such as Loki (responsible for log storage and query processing).
5. **Configure the Prometheus Data Source**:
+ On the configuration page, fill in the details of the Prometheus server. This typically includes the URL of the Prometheus service (e.g., if Prometheus is running on the same machine as OpenIM, the URL might be `http://172.28.0.1:19090`, with the address matching the `DOCKER_BRIDGE_GATEWAY` variable address). OpenIM and the components are linked via a gateway. The default port used by OpenIM is `19090`.
+ Adjust other settings as needed, such as authentication and TLS settings.
![image-20231114180351923](http://sm.nsddd.top/sm202311141803076.png)
6. **Save and Test**:
+ After completing the configuration, click the "Save & Test" button to ensure that Grafana can successfully connect to Prometheus.
**Importing Dashboards in Grafana**
Importing Grafana Dashboards is a straightforward process and is applicable to OpenIM Server application services and Node Exporter. Here are detailed steps and necessary considerations:
**Key Metrics Overview and Deployment Steps**
To monitor OpenIM in Grafana, you need to focus on three categories of key metrics, each with its specific deployment and configuration steps:
1. **OpenIM Metrics (`prometheus-dashboard.yaml`)**:
+ **Configuration File Path**: Located at `config/prometheus-dashboard.yaml`.
+ **Enabling Monitoring**: Set the environment variable `export PROMETHEUS_ENABLE=true` to enable Prometheus monitoring.
+ **More Information**: Refer to the [OpenIM Configuration Guide](https://docs.openim.io/configurations/prometheus-integration).
2. **Node Exporter**:
+ **Container Deployment**: Deploy the `quay.io/prometheus/node-exporter` container for node monitoring.
+ **Get Dashboard**: Access the [Node Exporter Full Feature Dashboard](https://grafana.com/grafana/dashboards/1860-node-exporter-full/) and import it using YAML file download or ID import.
+ **Deployment Guide**: Refer to the [Node Exporter Deployment Documentation](https://prometheus.io/docs/guides/node-exporter/).
3. **Middleware Metrics**: Each middleware requires specific steps and configurations to enable monitoring. Here is a list of common middleware and links to their respective setup guides:
+ MySQL:
+ **Configuration**: Ensure MySQL has performance monitoring enabled.
+ **Link**: Refer to the [MySQL Monitoring Configuration Guide](https://grafana.com/docs/grafana/latest/datasources/mysql/).
+ Redis:
+ **Configuration**: Configure Redis to allow monitoring data export.
+ **Link**: Refer to the [Redis Monitoring Guide](https://grafana.com/docs/grafana/latest/datasources/redis/).
+ MongoDB:
+ **Configuration**: Set up monitoring metrics for MongoDB.
+ **Link**: Refer to the [MongoDB Monitoring Guide](https://grafana.com/grafana/plugins/grafana-mongodb-datasource/).
+ Kafka:
+ **Configuration**: Integrate Kafka with Prometheus monitoring.
+ **Link**: Refer to the [Kafka Monitoring Guide](https://grafana.com/grafana/plugins/grafana-kafka-datasource/).
+ Zookeeper:
+ **Configuration**: Ensure Zookeeper can be monitored by Prometheus.
+ **Link**: Refer to the [Zookeeper Monitoring Configuration](https://grafana.com/docs/grafana/latest/datasources/zookeeper/).
**Importing Steps**:
1. Access the Dashboard Import Interface:
+ Click the `+` icon on the left menu or in the top right corner of Grafana, then select "Create."
+ Choose "Import" to access the dashboard import interface.
2. **Perform Dashboard Import**:
+ **Upload via File**: Directly upload your YAML file.
+ **Paste Content**: Open the YAML file, copy its content, and paste it into the import interface.
+ **Import via Grafana.com Dashboard**: Visit [Grafana Dashboards](https://grafana.com/grafana/dashboards/), search for the desired dashboard, and import it using its ID.
3. **Configure the Dashboard**:
+ Select the appropriate data source, such as the previously configured Prometheus.
+ Adjust other settings, such as the dashboard name or folder.
4. **Save and View the Dashboard**:
+ After configuring, click "Import" to complete the process.
+ Immediately view the new dashboard after successful import.
**Graph Examples:**
![image-20231114194451673](http://sm.nsddd.top/sm202311141944953.png)
### 1.3. <a name='MonitoringRunninginDockerGuide'></a>Monitoring Running in Docker Guide
#### 1.3.1. <a name='Introduction'></a>Introduction
This guide provides the steps to run OpenIM using Docker. OpenIM is an open-source instant messaging solution that can be quickly deployed using Docker. For more information, please refer to the [OpenIM Docker GitHub](https://github.com/openimsdk/openim-docker).
#### 1.3.2. <a name='Prerequisites'></a>Prerequisites
+ Ensure that Docker and Docker Compose are installed.
+ Basic understanding of Docker and containerization technology.
#### 1.3.3. <a name='Step1:ClonetheRepository'></a>Step 1: Clone the Repository
First, clone the OpenIM Docker repository:
```bash
git clone https://github.com/openimsdk/openim-docker.git
```
Navigate to the repository directory and check the `README` file for more information and configuration options.
#### 1.3.4. <a name='Step2:StartDockerCompose'></a>Step 2: Start Docker Compose
In the repository directory, run the following command to start the service:
```bash
docker-compose up -d
```
This will download the required Docker images and start the OpenIM service.
#### 1.3.5. <a name='Step3:UsetheOpenIMWebInterface'></a>Step 3: Use the OpenIM Web Interface
+ Open a browser in private mode and access [OpenIM Web](http://localhost:11001/).
+ Register two users and try adding friends.
+ Test sending messages and pictures.
#### 1.3.6. <a name='RunningEffect'></a>Running Effect
![image-20231115100811208](http://sm.nsddd.top/sm202311151008639.png)
#### 1.3.7. <a name='Step4:AccesstheAdminPanel'></a>Step 4: Access the Admin Panel
+ Access the [OpenIM Admin Panel](http://localhost:11002/).
+ Log in using the default username and password (`admin1:admin1`).
Running Effect Image:
![image-20231115101039837](http://sm.nsddd.top/sm202311151010116.png)
#### 1.3.8. <a name='Step5:AccesstheMonitoringInterface'></a>Step 5: Access the Monitoring Interface
+ Log in to the [Monitoring Interface](http://localhost:3000/login) using the credentials (`admin:admin`).
#### 1.3.9. <a name='NextSteps'></a>Next Steps
+ Configure and manage the services following the steps provided in the OpenIM source code.
+ Refer to the `README` file for advanced configuration and management.
#### 1.3.10. <a name='Troubleshooting'></a>Troubleshooting
+ If you encounter any issues, please check the documentation on [OpenIM Docker GitHub](https://github.com/openimsdk/openim-docker) or search for related issues in the Issues section.
+ If the problem persists, you can create an issue on the [openim-docker](https://github.com/openimsdk/openim-docker/issues/new/choose) repository or the [openim-server](https://github.com/openimsdk/open-im-server/issues/new/choose) repository.
## 2. <a name='Kubernetes'></a>Kubernetes
Refer to [openimsdk/helm-charts](https://github.com/openimsdk/helm-charts).
When deploying and monitoring OpenIM in a Kubernetes environment, you will focus on three main metrics: middleware, custom OpenIM metrics, and Node Exporter. Here are detailed steps and guidelines:
### 2.1. <a name='MiddlewareMonitoring'></a>Middleware Monitoring
Middleware monitoring is crucial to ensure the overall system's stability. Typically, this includes monitoring the following components:
+ **MySQL**: Monitor database performance, query latency, and more.
+ **Redis**: Track operation latency, memory usage, and more.
+ **MongoDB**: Observe database operations, resource usage, and more.
+ **Kafka**: Monitor message throughput, latency, and more.
+ **Zookeeper**: Keep an eye on cluster status, performance metrics, and more.
For Kubernetes environments, you can use the corresponding Prometheus Exporters to collect monitoring data for these middleware components.
### 2.2. <a name='CustomOpenIMMetrics'></a>Custom OpenIM Metrics
Custom OpenIM metrics provide essential information about the OpenIM application itself, such as user activity, message traffic, system performance, and more. To monitor these metrics in Kubernetes:
+ Ensure OpenIM application configurations expose Prometheus metrics.
+ When deploying using Helm charts (refer to [OpenIM Helm Charts](https://github.com/openimsdk/helm-charts)), pay attention to configuring relevant monitoring settings.
### 2.3. <a name='NodeExporter'></a>Node Exporter
Node Exporter is used to collect hardware and operating system-level metrics for Kubernetes nodes, such as CPU, memory, disk usage, and more. To integrate Node Exporter in Kubernetes:
+ Deploy Node Exporter using the appropriate Helm chart. You can find information and guides on [Prometheus Community](https://prometheus.io/docs/guides/node-exporter/).
+ Ensure Node Exporter's data is collected by Prometheus instances within your cluster.
## 3. <a name='SettingUpandConfiguringAlertManagerUsingEnvironmentVariablesandmakeinit'></a>Setting Up and Configuring AlertManager Using Environment Variables and `make init`
### 3.1. <a name='Introduction-1'></a>Introduction
AlertManager, a component of the Prometheus monitoring system, handles alerts sent by client applications such as the Prometheus server. It takes care of deduplicating, grouping, and routing them to the correct receiver. This document outlines how to set up and configure AlertManager using environment variables and the `make init` command. We will focus on configuring key fields like the sender's email, SMTP settings, and SMTP authentication password.
### 3.2. <a name='Prerequisites-1'></a>Prerequisites
+ Basic knowledge of terminal and command-line operations.
+ AlertManager installed on your system.
+ Access to an SMTP server for sending emails.
### 3.3. <a name='ConfigurationSteps'></a>Configuration Steps
#### 3.3.1. <a name='ExportingEnvironmentVariables'></a>Exporting Environment Variables
Before initializing AlertManager, you need to set environment variables. These variables are used to configure the AlertManager settings without altering the code. Use the `export` command in your terminal. Here are some key variables you might set:
+ `export ALERTMANAGER_RESOLVE_TIMEOUT='5m'`
+ `export ALERTMANAGER_SMTP_FROM='alert@example.com'`
+ `export ALERTMANAGER_SMTP_SMARTHOST='smtp.example.com:465'`
+ `export ALERTMANAGER_SMTP_AUTH_USERNAME='alert@example.com'`
+ `export ALERTMANAGER_SMTP_AUTH_PASSWORD='your_password'`
+ `export ALERTMANAGER_SMTP_REQUIRE_TLS='false'`
#### 3.3.2. <a name='InitializingAlertManager'></a>Initializing AlertManager
After setting the necessary environment variables, you can initialize AlertManager by running the `make init` command. This command typically runs a script that prepares AlertManager with the provided configuration.
#### 3.3.3. <a name='KeyConfigurationFields'></a>Key Configuration Fields
##### a. Sender's Email (`ALERTMANAGER_SMTP_FROM`)
This variable sets the email address that will appear as the sender in the notifications sent by AlertManager.
##### b. SMTP Configuration
+ **SMTP Server (`ALERTMANAGER_SMTP_SMARTHOST`):** Specifies the address and port of the SMTP server used for sending emails.
+ **SMTP Authentication Username (`ALERTMANAGER_SMTP_AUTH_USERNAME`):** The username for authenticating with the SMTP server.
+ **SMTP Authentication Password (`ALERTMANAGER_SMTP_AUTH_PASSWORD`):** The password for SMTP server authentication. It's crucial to keep this value secure.
#### 3.3.4. <a name='ConfiguringSMTPAuthenticationPassword'></a>Configuring SMTP Authentication Password
The SMTP authentication password can be set using the `ALERTMANAGER_SMTP_AUTH_PASSWORD` environment variable. It's recommended to use a secure method to set this variable to avoid exposing sensitive information. For instance, you might read the password from a secure file or a secret management tool.
#### 3.3.5. <a name='UsefulLinksforCommonEmailServers'></a>Useful Links for Common Email Servers
For specific configurations related to common email servers, you may refer to their respective documentation:
+ Gmail SMTP Settings:
+ [Gmail SMTP Configuration](https://support.google.com/mail/answer/7126229?hl=en)
+ Microsoft Outlook SMTP Settings:
+ [Outlook Email Settings](https://support.microsoft.com/en-us/office/pop-imap-and-smtp-settings-8361e398-8af4-4e97-b147-6c6c4ac95353)
+ Yahoo Mail SMTP Settings:
+ [Yahoo SMTP Configuration](https://help.yahoo.com/kb/SLN4724.html)
### 3.4. <a name='Conclusion'></a>Conclusion
Setting up and configuring AlertManager with environment variables provides a flexible and secure way to manage alert settings. By following the above steps, you can easily configure AlertManager for your monitoring needs. Always ensure to secure sensitive information, especially when dealing with SMTP authentication credentials.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 118 KiB

+22 -3
View File
@@ -38,12 +38,14 @@ require github.com/google/uuid v1.3.1
require (
github.com/IBM/sarama v1.41.3
github.com/OpenIMSDK/protocol v0.0.31
github.com/OpenIMSDK/tools v0.0.16
github.com/OpenIMSDK/tools v0.0.17
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible
github.com/go-redis/redis v6.15.9+incompatible
github.com/go-sql-driver/mysql v1.7.1
github.com/redis/go-redis/v9 v9.2.1
github.com/tencentyun/cos-go-sdk-v5 v0.7.45
go.uber.org/automaxprocs v1.5.3
golang.org/x/sync v0.4.0
gopkg.in/src-d/go-git.v4 v4.13.1
gotest.tools v2.2.0+incompatible
)
@@ -56,6 +58,24 @@ require (
cloud.google.com/go/iam v1.1.2 // indirect
cloud.google.com/go/longrunning v0.5.1 // indirect
cloud.google.com/go/storage v1.30.1 // indirect
github.com/aws/aws-sdk-go-v2 v1.23.1 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1 // indirect
github.com/aws/aws-sdk-go-v2/config v1.25.4 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.16.3 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.4 // indirect
github.com/aws/aws-sdk-go-v2/service/s3 v1.43.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 // indirect
github.com/aws/smithy-go v1.17.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
@@ -113,6 +133,7 @@ require (
github.com/prometheus/client_model v0.4.1-0.20230718164431-9a2bf3000d16 // indirect
github.com/prometheus/common v0.44.0 // indirect
github.com/prometheus/procfs v0.11.1 // indirect
github.com/qiniu/go-sdk/v7 v7.18.2 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rs/xid v1.5.0 // indirect
github.com/sergi/go-diff v1.0.0 // indirect
@@ -127,12 +148,10 @@ require (
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.7.0 // indirect
go.uber.org/automaxprocs v1.5.3 // indirect
go.uber.org/multierr v1.6.0 // indirect
golang.org/x/arch v0.3.0 // indirect
golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.13.0 // indirect
golang.org/x/sync v0.4.0 // indirect
golang.org/x/sys v0.13.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.3.0 // indirect
+68 -2
View File
@@ -20,8 +20,8 @@ github.com/IBM/sarama v1.41.3 h1:MWBEJ12vHC8coMjdEXFq/6ftO6DUZnQlFYcxtOJFa7c=
github.com/IBM/sarama v1.41.3/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ=
github.com/OpenIMSDK/protocol v0.0.31 h1:ax43x9aqA6EKNXNukS5MT5BSTqkUmwO4uTvbJLtzCgE=
github.com/OpenIMSDK/protocol v0.0.31/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y=
github.com/OpenIMSDK/tools v0.0.16 h1:te/GIq2imCMsrRPgU9OObYKbzZ3rT08Lih/o+3QFIz0=
github.com/OpenIMSDK/tools v0.0.16/go.mod h1:eg+q4A34Qmu73xkY0mt37FHGMCMfC6CtmOnm0kFEGFI=
github.com/OpenIMSDK/tools v0.0.17 h1:1E1HUOL2W09YUHBb4wBwrXoTSZm5ONVwLxlEX1GhlKw=
github.com/OpenIMSDK/tools v0.0.17/go.mod h1:eg+q4A34Qmu73xkY0mt37FHGMCMfC6CtmOnm0kFEGFI=
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
@@ -31,6 +31,42 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/aws/aws-sdk-go-v2 v1.23.1 h1:qXaFsOOMA+HsZtX8WoCa+gJnbyW7qyFFBlPqvTSzbaI=
github.com/aws/aws-sdk-go-v2 v1.23.1/go.mod h1:i1XDttT4rnf6vxc9AuskLc6s7XBee8rlLilKlc03uAA=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1 h1:ZY3108YtBNq96jNZTICHxN1gSBSbnvIdYwwqnvCV4Mc=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.5.1/go.mod h1:t8PYl/6LzdAqsU4/9tz28V/kU+asFePvpOMkdul0gEQ=
github.com/aws/aws-sdk-go-v2/config v1.25.4 h1:r+X1x8QI6FEPdJDWCNBDZHyAcyFwSjHN8q8uuus+Axs=
github.com/aws/aws-sdk-go-v2/config v1.25.4/go.mod h1:8GTjImECskr7D88P/Nn9uM4M4rLY9i77hLJZgkZEWV8=
github.com/aws/aws-sdk-go-v2/credentials v1.16.3 h1:8PeI2krzzjDJ5etmgaMiD1JswsrLrWvKKu/uBUtNy1g=
github.com/aws/aws-sdk-go-v2/credentials v1.16.3/go.mod h1:Kdh/okh+//vQ/AjEt81CjvkTo64+/zIE4OewP7RpfXk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5 h1:KehRNiVzIfAcj6gw98zotVbb/K67taJE0fkfgM6vzqU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.14.5/go.mod h1:VhnExhw6uXy9QzetvpXDolo1/hjhx4u9qukBGkuUwjs=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4 h1:LAm3Ycm9HJfbSCd5I+wqC2S9Ej7FPrgr5CQoOljJZcE=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.2.4/go.mod h1:xEhvbJcyUf/31yfGSQBe01fukXwXJ0gxDp7rLfymWE0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4 h1:4GV0kKZzUxiWxSVpn/9gwR0g21NF1Jsyduzo9rHgC/Q=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.5.4/go.mod h1:dYvTNAggxDZy6y1AF7YDwXsPuHFy/VNEpEI/2dWK9IU=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1 h1:uR9lXYjdPX0xY+NhvaJ4dD8rpSRz5VY81ccIIoNG+lw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.7.1/go.mod h1:6fQQgfuGmw8Al/3M2IgIllycxV7ZW7WCdVSqfBeUiCY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.4 h1:40Q4X5ebZruRtknEZH/bg91sT5pR853F7/1X9QRbI54=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.2.4/go.mod h1:u77N7eEECzUv7F0xl2gcfK/vzc8wcjWobpy+DcrLJ5E=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1 h1:rpkF4n0CyFcrJUG/rNNohoTmhtWlFTRI4BsZOh9PvLs=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.10.1/go.mod h1:l9ymW25HOqymeU2m1gbUQ3rUIsTwKs8gYHXkqDQUhiI=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.4 h1:6DRKQc+9cChgzL5gplRGusI5dBGeiEod4m/pmGbcX48=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.2.4/go.mod h1:s8ORvrW4g4v7IvYKIAoBg17w3GQ+XuwXDXYrQ5SkzU0=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4 h1:rdovz3rEu0vZKbzoMYPTehp0E8veoE9AyfzqCr5Eeao=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.10.4/go.mod h1:aYCGNjyUCUelhofxlZyj63srdxWUSsBSGg5l6MCuXuE=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.4 h1:o3DcfCxGDIT20pTbVKVhp3vWXOj/VvgazNJvumWeYW0=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.16.4/go.mod h1:Uy0KVOxuTK2ne+/PKQ+VvEeWmjMMksE17k/2RK/r5oM=
github.com/aws/aws-sdk-go-v2/service/s3 v1.43.1 h1:1w11lfXOa8HoHoSlNtt4mqv/N3HmDOa+OnUH3Y9DHm8=
github.com/aws/aws-sdk-go-v2/service/s3 v1.43.1/go.mod h1:dqJ5JBL0clzgHriH35Amx3LRFY6wNIPUX7QO/BerSBo=
github.com/aws/aws-sdk-go-v2/service/sso v1.17.3 h1:CdsSOGlFF3Pn+koXOIpTtvX7st0IuGsZ8kJqcWMlX54=
github.com/aws/aws-sdk-go-v2/service/sso v1.17.3/go.mod h1:oA6VjNsLll2eVuUoF2D+CMyORgNzPEW/3PyUdq6WQjI=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1 h1:cbRqFTVnJV+KRpwFl76GJdIZJKKCdTPnjUZ7uWh3pIU=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.20.1/go.mod h1:hHL974p5auvXlZPIjJTblXJpbkfK4klBczlsEaMCGVY=
github.com/aws/aws-sdk-go-v2/service/sts v1.25.4 h1:yEvZ4neOQ/KpUqyR+X0ycUTW/kVRNR4nDZ38wStHGAA=
github.com/aws/aws-sdk-go-v2/service/sts v1.25.4/go.mod h1:feTnm2Tk/pJxdX+eooEsxvlvTWBvDm6CasRZ+JOs2IY=
github.com/aws/smithy-go v1.17.0 h1:wWJD7LX6PBV6etBUwO0zElG0nWN9rUhp0WdYeHSHAaI=
github.com/aws/smithy-go v1.17.0/go.mod h1:NukqUGpCZIILqqiV0NIjeFh24kd/FAa4beRb6nbIUPE=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -56,6 +92,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -91,11 +128,17 @@ github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg=
github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU=
github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0=
github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=
github.com/go-playground/validator/v10 v10.15.5 h1:LEBecTWb/1j5TNY1YYG2RcOUN3R7NLylN+x8TTueE24=
github.com/go-playground/validator/v10 v10.15.5/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
@@ -214,11 +257,15 @@ github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.2.5 h1:0E5MSMDEoAulmXNFquVs//DdoomxaoTY1kUhbc/qbZg=
github.com/klauspost/cpuid/v2 v2.2.5/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
@@ -275,11 +322,13 @@ github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZ
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ=
github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prashantv/gostub v1.1.0 h1:BTyx3RfQjRHnUWaGF9oQos79AlQ5k8WNktv7VGvVH4g=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@@ -289,12 +338,18 @@ github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdO
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY=
github.com/prometheus/procfs v0.11.1 h1:xRC8Iq1yyca5ypa9n1EZnWZkt7dwcoRPQwX/5gwaUuI=
github.com/prometheus/procfs v0.11.1/go.mod h1:eesXgaPo1q7lBpVMoMy0ZOFTth9hBn4W/y0/p/ScXhY=
github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk=
github.com/qiniu/go-sdk/v7 v7.18.2 h1:vk9eo5OO7aqgAOPF0Ytik/gt7CMKuNgzC/IPkhda6rk=
github.com/qiniu/go-sdk/v7 v7.18.2/go.mod h1:nqoYCNo53ZlGA521RvRethvxUDvXKt4gtYXOwye868w=
github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.2.1 h1:WlYJg71ODF0dVspZZCpYmoF1+U1Jjk9Rwd7pq6QmlCg=
github.com/redis/go-redis/v9 v9.2.1/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
@@ -317,6 +372,7 @@ github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpE
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@@ -367,8 +423,10 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
@@ -399,6 +457,7 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
@@ -431,16 +490,19 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -449,6 +511,7 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
@@ -509,6 +572,8 @@ google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqw
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@@ -528,6 +593,7 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.2 h1:QC2HRskSE75wBuOxe0+iCkyJZ+RqpudsQtqkp+IMuXs=
+6 -8
View File
@@ -15,14 +15,6 @@
package api
import (
"github.com/OpenIMSDK/tools/mcontext"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/mitchellh/mapstructure"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/protocol/msg"
"github.com/OpenIMSDK/protocol/sdkws"
@@ -30,7 +22,13 @@ import (
"github.com/OpenIMSDK/tools/apiresp"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/log"
"github.com/OpenIMSDK/tools/mcontext"
"github.com/OpenIMSDK/tools/utils"
"github.com/gin-gonic/gin"
"github.com/go-playground/validator/v10"
"github.com/mitchellh/mapstructure"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/apistruct"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
+2 -1
View File
@@ -15,11 +15,12 @@
package api
import (
config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"math/rand"
"net/http"
"strconv"
config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/gin-gonic/gin"
"github.com/OpenIMSDK/protocol/third"
+15 -6
View File
@@ -37,7 +37,7 @@ func CallbackUserOnline(ctx context.Context, userID string, platformID int, isAp
req := cbapi.CallbackUserOnlineReq{
UserStatusCallbackReq: cbapi.UserStatusCallbackReq{
UserStatusBaseCallback: cbapi.UserStatusBaseCallback{
CallbackCommand: constant.CallbackUserOnlineCommand,
CallbackCommand: cbapi.CallbackUserOnlineCommand,
OperationID: mcontext.GetOperationID(ctx),
PlatformID: platformID,
Platform: constant.PlatformIDToName(platformID),
@@ -49,7 +49,10 @@ func CallbackUserOnline(ctx context.Context, userID string, platformID int, isAp
ConnID: connID,
}
resp := cbapi.CommonCallbackResp{}
return http.CallBackPostReturn(ctx, callBackURL(), &req, &resp, config.Config.Callback.CallbackUserOnline)
if err := http.CallBackPostReturn(ctx, callBackURL(), &req, &resp, config.Config.Callback.CallbackUserOnline); err != nil {
return err
}
return nil
}
func CallbackUserOffline(ctx context.Context, userID string, platformID int, connID string) error {
@@ -59,7 +62,7 @@ func CallbackUserOffline(ctx context.Context, userID string, platformID int, con
req := &cbapi.CallbackUserOfflineReq{
UserStatusCallbackReq: cbapi.UserStatusCallbackReq{
UserStatusBaseCallback: cbapi.UserStatusBaseCallback{
CallbackCommand: constant.CallbackUserOfflineCommand,
CallbackCommand: cbapi.CallbackUserOfflineCommand,
OperationID: mcontext.GetOperationID(ctx),
PlatformID: platformID,
Platform: constant.PlatformIDToName(platformID),
@@ -70,7 +73,10 @@ func CallbackUserOffline(ctx context.Context, userID string, platformID int, con
ConnID: connID,
}
resp := &cbapi.CallbackUserOfflineResp{}
return http.CallBackPostReturn(ctx, callBackURL(), req, resp, config.Config.Callback.CallbackUserOffline)
if err := http.CallBackPostReturn(ctx, callBackURL(), req, resp, config.Config.Callback.CallbackUserOffline); err != nil {
return err
}
return nil
}
func CallbackUserKickOff(ctx context.Context, userID string, platformID int) error {
@@ -80,7 +86,7 @@ func CallbackUserKickOff(ctx context.Context, userID string, platformID int) err
req := &cbapi.CallbackUserKickOffReq{
UserStatusCallbackReq: cbapi.UserStatusCallbackReq{
UserStatusBaseCallback: cbapi.UserStatusBaseCallback{
CallbackCommand: constant.CallbackUserKickOffCommand,
CallbackCommand: cbapi.CallbackUserKickOffCommand,
OperationID: mcontext.GetOperationID(ctx),
PlatformID: platformID,
Platform: constant.PlatformIDToName(platformID),
@@ -90,7 +96,10 @@ func CallbackUserKickOff(ctx context.Context, userID string, platformID int) err
Seq: time.Now().UnixMilli(),
}
resp := &cbapi.CommonCallbackResp{}
return http.CallBackPostReturn(ctx, callBackURL(), req, resp, config.Config.Callback.CallbackUserOffline)
if err := http.CallBackPostReturn(ctx, callBackURL(), req, resp, config.Config.Callback.CallbackUserOffline); err != nil {
return err
}
return nil
}
// func callbackUserOnline(operationID, userID string, platformID int, token string, isAppBackground bool, connID
+2 -2
View File
@@ -167,7 +167,7 @@ func (c *Client) readMessage() {
func (c *Client) handleMessage(message []byte) error {
if c.IsCompress {
var err error
message, err = c.longConnServer.DeCompress(message)
message, err = c.longConnServer.DecompressWithPool(message)
if err != nil {
return utils.Wrap(err, "")
}
@@ -317,7 +317,7 @@ func (c *Client) writeBinaryMsg(resp Resp) error {
_ = c.conn.SetWriteDeadline(writeWait)
if c.IsCompress {
resultBuf, compressErr := c.longConnServer.Compress(encodedBuf)
resultBuf, compressErr := c.longConnServer.CompressWithPool(encodedBuf)
if compressErr != nil {
return utils.Wrap(compressErr, "")
}
+45
View File
@@ -17,14 +17,23 @@ package msggateway
import (
"bytes"
"compress/gzip"
"errors"
"io"
"sync"
"github.com/OpenIMSDK/tools/utils"
)
var (
gzipWriterPool = sync.Pool{New: func() any { return gzip.NewWriter(nil) }}
gzipReaderPool = sync.Pool{New: func() any { return new(gzip.Reader) }}
)
type Compressor interface {
Compress(rawData []byte) ([]byte, error)
CompressWithPool(rawData []byte) ([]byte, error)
DeCompress(compressedData []byte) ([]byte, error)
DecompressWithPool(compressedData []byte) ([]byte, error)
}
type GzipCompressor struct {
compressProtocol string
@@ -46,6 +55,22 @@ func (g *GzipCompressor) Compress(rawData []byte) ([]byte, error) {
return gzipBuffer.Bytes(), nil
}
func (g *GzipCompressor) CompressWithPool(rawData []byte) ([]byte, error) {
gz := gzipWriterPool.Get().(*gzip.Writer)
defer gzipWriterPool.Put(gz)
gzipBuffer := bytes.Buffer{}
gz.Reset(&gzipBuffer)
if _, err := gz.Write(rawData); err != nil {
return nil, utils.Wrap(err, "")
}
if err := gz.Close(); err != nil {
return nil, utils.Wrap(err, "")
}
return gzipBuffer.Bytes(), nil
}
func (g *GzipCompressor) DeCompress(compressedData []byte) ([]byte, error) {
buff := bytes.NewBuffer(compressedData)
reader, err := gzip.NewReader(buff)
@@ -59,3 +84,23 @@ func (g *GzipCompressor) DeCompress(compressedData []byte) ([]byte, error) {
_ = reader.Close()
return compressedData, nil
}
func (g *GzipCompressor) DecompressWithPool(compressedData []byte) ([]byte, error) {
reader := gzipReaderPool.Get().(*gzip.Reader)
if reader == nil {
return nil, errors.New("NewReader failed")
}
defer gzipReaderPool.Put(reader)
err := reader.Reset(bytes.NewReader(compressedData))
if err != nil {
return nil, utils.Wrap(err, "NewReader failed")
}
compressedData, err = io.ReadAll(reader)
if err != nil {
return nil, utils.Wrap(err, "ReadAll failed")
}
_ = reader.Close()
return compressedData, nil
}
+107
View File
@@ -0,0 +1,107 @@
package msggateway
import (
"crypto/rand"
"sync"
"testing"
"github.com/stretchr/testify/assert"
)
func mockRandom() []byte {
bs := make([]byte, 50)
rand.Read(bs)
return bs
}
func TestCompressDecompress(t *testing.T) {
compressor := NewGzipCompressor()
for i := 0; i < 2000; i++ {
src := mockRandom()
// compress
dest, err := compressor.CompressWithPool(src)
assert.Equal(t, nil, err)
// decompress
res, err := compressor.DecompressWithPool(dest)
assert.Equal(t, nil, err)
// check
assert.EqualValues(t, src, res)
}
}
func TestCompressDecompressWithConcurrency(t *testing.T) {
wg := sync.WaitGroup{}
compressor := NewGzipCompressor()
for i := 0; i < 200; i++ {
wg.Add(1)
go func() {
defer wg.Done()
src := mockRandom()
// compress
dest, err := compressor.CompressWithPool(src)
assert.Equal(t, nil, err)
// decompress
res, err := compressor.DecompressWithPool(dest)
assert.Equal(t, nil, err)
// check
assert.EqualValues(t, src, res)
}()
}
wg.Wait()
}
func BenchmarkCompress(b *testing.B) {
src := mockRandom()
compressor := NewGzipCompressor()
for i := 0; i < b.N; i++ {
_, err := compressor.Compress(src)
assert.Equal(b, nil, err)
}
}
func BenchmarkCompressWithSyncPool(b *testing.B) {
src := mockRandom()
compressor := NewGzipCompressor()
for i := 0; i < b.N; i++ {
_, err := compressor.CompressWithPool(src)
assert.Equal(b, nil, err)
}
}
func BenchmarkDecompress(b *testing.B) {
src := mockRandom()
compressor := NewGzipCompressor()
comdata, err := compressor.Compress(src)
assert.Equal(b, nil, err)
for i := 0; i < b.N; i++ {
_, err := compressor.DeCompress(comdata)
assert.Equal(b, nil, err)
}
}
func BenchmarkDecompressWithSyncPool(b *testing.B) {
src := mockRandom()
compressor := NewGzipCompressor()
comdata, err := compressor.Compress(src)
assert.Equal(b, nil, err)
for i := 0; i < b.N; i++ {
_, err := compressor.DecompressWithPool(comdata)
assert.Equal(b, nil, err)
}
}
+64 -54
View File
@@ -17,22 +17,19 @@ package msggateway
import (
"context"
"github.com/OpenIMSDK/tools/mcontext"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/OpenIMSDK/tools/errs"
"google.golang.org/grpc"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/protocol/msggateway"
"github.com/OpenIMSDK/tools/discoveryregistry"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/log"
"github.com/OpenIMSDK/tools/mcontext"
"github.com/OpenIMSDK/tools/utils"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
)
@@ -41,6 +38,7 @@ func (s *Server) InitServer(disCov discoveryregistry.SvcDiscoveryRegistry, serve
if err != nil {
return err
}
msgModel := cache.NewMsgCacheModel(rdb)
s.LongConnServer.SetDiscoveryRegistry(disCov)
s.LongConnServer.SetCacheHandler(msgModel)
@@ -97,22 +95,25 @@ func (s *Server) GetUsersOnlineStatus(
if !ok {
continue
}
temp := new(msggateway.GetUsersOnlineStatusResp_SuccessResult)
temp.UserID = userID
uresp := new(msggateway.GetUsersOnlineStatusResp_SuccessResult)
uresp.UserID = userID
for _, client := range clients {
if client != nil {
ps := new(msggateway.GetUsersOnlineStatusResp_SuccessDetail)
ps.Platform = constant.PlatformIDToName(client.PlatformID)
ps.Status = constant.OnlineStatus
ps.ConnID = client.ctx.GetConnID()
ps.Token = client.token
ps.IsBackground = client.IsBackground
temp.Status = constant.OnlineStatus
temp.DetailPlatformStatus = append(temp.DetailPlatformStatus, ps)
if client == nil {
continue
}
ps := new(msggateway.GetUsersOnlineStatusResp_SuccessDetail)
ps.Platform = constant.PlatformIDToName(client.PlatformID)
ps.Status = constant.OnlineStatus
ps.ConnID = client.ctx.GetConnID()
ps.Token = client.token
ps.IsBackground = client.IsBackground
uresp.Status = constant.OnlineStatus
uresp.DetailPlatformStatus = append(uresp.DetailPlatformStatus, ps)
}
if temp.Status == constant.OnlineStatus {
resp.SuccessResult = append(resp.SuccessResult, temp)
if uresp.Status == constant.OnlineStatus {
resp.SuccessResult = append(resp.SuccessResult, uresp)
}
}
return &resp, nil
@@ -129,50 +130,55 @@ func (s *Server) SuperGroupOnlineBatchPushOneMsg(
ctx context.Context,
req *msggateway.OnlineBatchPushOneMsgReq,
) (*msggateway.OnlineBatchPushOneMsgResp, error) {
var singleUserResult []*msggateway.SingleMsgToUserResults
var singleUserResults []*msggateway.SingleMsgToUserResults
for _, v := range req.PushToUserIDs {
var resp []*msggateway.SingleMsgToUserPlatform
tempT := &msggateway.SingleMsgToUserResults{
results := &msggateway.SingleMsgToUserResults{
UserID: v,
}
clients, ok := s.LongConnServer.GetUserAllCons(v)
if !ok {
log.ZDebug(ctx, "push user not online", "userID", v)
tempT.Resp = resp
singleUserResult = append(singleUserResult, tempT)
results.Resp = resp
singleUserResults = append(singleUserResults, results)
continue
}
log.ZDebug(ctx, "push user online", "clients", clients, "userID", v)
for _, client := range clients {
if client != nil {
temp := &msggateway.SingleMsgToUserPlatform{
RecvID: v,
RecvPlatFormID: int32(client.PlatformID),
}
if !client.IsBackground ||
(client.IsBackground == true && client.PlatformID != constant.IOSPlatformID) {
err := client.PushMessage(ctx, req.MsgData)
if err != nil {
temp.ResultCode = -2
resp = append(resp, temp)
} else {
if utils.IsContainInt(client.PlatformID, s.pushTerminal) {
tempT.OnlinePush = true
resp = append(resp, temp)
}
}
if client == nil {
continue
}
userPlatform := &msggateway.SingleMsgToUserPlatform{
RecvID: v,
RecvPlatFormID: int32(client.PlatformID),
}
if !client.IsBackground ||
(client.IsBackground && client.PlatformID != constant.IOSPlatformID) {
err := client.PushMessage(ctx, req.MsgData)
if err != nil {
userPlatform.ResultCode = -2
resp = append(resp, userPlatform)
} else {
temp.ResultCode = -3
resp = append(resp, temp)
if utils.IsContainInt(client.PlatformID, s.pushTerminal) {
results.OnlinePush = true
resp = append(resp, userPlatform)
}
}
} else {
userPlatform.ResultCode = -3
resp = append(resp, userPlatform)
}
}
tempT.Resp = resp
singleUserResult = append(singleUserResult, tempT)
results.Resp = resp
singleUserResults = append(singleUserResults, results)
}
return &msggateway.OnlineBatchPushOneMsgResp{
SinglePushResult: singleUserResult,
SinglePushResult: singleUserResults,
}, nil
}
@@ -181,17 +187,21 @@ func (s *Server) KickUserOffline(
req *msggateway.KickUserOfflineReq,
) (*msggateway.KickUserOfflineResp, error) {
for _, v := range req.KickUserIDList {
if clients, _, ok := s.LongConnServer.GetUserPlatformCons(v, int(req.PlatformID)); ok {
for _, client := range clients {
log.ZDebug(ctx, "kick user offline", "userID", v, "platformID", req.PlatformID, "client", client)
if err := client.longConnServer.KickUserConn(client); err != nil {
log.ZWarn(ctx, "kick user offline failed", err, "userID", v, "platformID", req.PlatformID)
}
}
} else {
clients, _, ok := s.LongConnServer.GetUserPlatformCons(v, int(req.PlatformID))
if !ok {
log.ZInfo(ctx, "conn not exist", "userID", v, "platformID", req.PlatformID)
continue
}
for _, client := range clients {
log.ZDebug(ctx, "kick user offline", "userID", v, "platformID", req.PlatformID, "client", client)
if err := client.longConnServer.KickUserConn(client); err != nil {
log.ZWarn(ctx, "kick user offline failed", err, "userID", v, "platformID", req.PlatformID)
}
}
continue
}
return &msggateway.KickUserOfflineResp{}, nil
}
+20 -7
View File
@@ -19,11 +19,12 @@ import (
"time"
"github.com/OpenIMSDK/tools/utils"
"golang.org/x/sync/errgroup"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
)
// RunWsAndServer run ws server
// RunWsAndServer run ws server.
func RunWsAndServer(rpcPort, wsPort, prometheusPort int) error {
fmt.Println(
"start rpc/msg_gateway server, port: ",
@@ -37,16 +38,28 @@ func RunWsAndServer(rpcPort, wsPort, prometheusPort int) error {
WithPort(wsPort),
WithMaxConnNum(int64(config.Config.LongConnSvr.WebsocketMaxConnNum)),
WithHandshakeTimeout(time.Duration(config.Config.LongConnSvr.WebsocketTimeout)*time.Second),
WithMessageMaxMsgLength(config.Config.LongConnSvr.WebsocketMaxMsgLen))
WithMessageMaxMsgLength(config.Config.LongConnSvr.WebsocketMaxMsgLen),
WithWriteBufferSize(config.Config.LongConnSvr.WebsocketWriteBufferSize),
)
if err != nil {
return err
}
hubServer := NewServer(rpcPort, prometheusPort, longServer)
go func() {
err := hubServer.Start()
wg := errgroup.Group{}
wg.Go(func() error {
err = hubServer.Start()
if err != nil {
panic(utils.Wrap1(err))
return utils.Wrap1(err)
}
}()
return hubServer.LongConnServer.Run()
return err
})
wg.Go(func() error {
return hubServer.LongConnServer.Run()
})
err = wg.Wait()
return err
}
+7 -2
View File
@@ -50,10 +50,11 @@ type GWebSocket struct {
protocolType int
conn *websocket.Conn
handshakeTimeout time.Duration
writeBufferSize int
}
func newGWebSocket(protocolType int, handshakeTimeout time.Duration) *GWebSocket {
return &GWebSocket{protocolType: protocolType, handshakeTimeout: handshakeTimeout}
func newGWebSocket(protocolType int, handshakeTimeout time.Duration, wbs int) *GWebSocket {
return &GWebSocket{protocolType: protocolType, handshakeTimeout: handshakeTimeout, writeBufferSize: wbs}
}
func (d *GWebSocket) Close() error {
@@ -65,6 +66,10 @@ func (d *GWebSocket) GenerateLongConn(w http.ResponseWriter, r *http.Request) er
HandshakeTimeout: d.handshakeTimeout,
CheckOrigin: func(r *http.Request) bool { return true },
}
if d.writeBufferSize > 0 { // default is 4kb.
upgrader.WriteBufferSize = d.writeBufferSize
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
return err
+137 -91
View File
@@ -18,31 +18,30 @@ import (
"context"
"errors"
"net/http"
"os"
"os/signal"
"strconv"
"sync"
"sync/atomic"
"syscall"
"time"
"github.com/OpenIMSDK/protocol/msggateway"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
"github.com/go-playground/validator/v10"
"github.com/redis/go-redis/v9"
"golang.org/x/sync/errgroup"
"github.com/OpenIMSDK/protocol/constant"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/redis/go-redis/v9"
"github.com/OpenIMSDK/protocol/msggateway"
"github.com/OpenIMSDK/tools/discoveryregistry"
"github.com/go-playground/validator/v10"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/log"
"github.com/OpenIMSDK/tools/utils"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
)
type LongConnServer interface {
@@ -61,12 +60,6 @@ type LongConnServer interface {
MessageHandler
}
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 1024)
},
}
type WsServer struct {
port int
wsMaxConnNum int64
@@ -78,7 +71,7 @@ type WsServer struct {
onlineUserNum atomic.Int64
onlineUserConnNum atomic.Int64
handshakeTimeout time.Duration
hubServer *Server
writeBufferSize int
validate *validator.Validate
cache cache.MsgModel
userClient *rpcclient.UserRpcClient
@@ -128,6 +121,7 @@ func (ws *WsServer) UnRegister(c *Client) {
}
func (ws *WsServer) Validate(s interface{}) error {
//?question?
return nil
}
@@ -148,6 +142,7 @@ func NewWsServer(opts ...Option) (*WsServer, error) {
return &WsServer{
port: config.port,
wsMaxConnNum: config.maxConnNum,
writeBufferSize: config.writeBufferSize,
handshakeTimeout: config.handshakeTimeout,
clientPool: sync.Pool{
New: func() interface{} {
@@ -165,10 +160,22 @@ func NewWsServer(opts ...Option) (*WsServer, error) {
}
func (ws *WsServer) Run() error {
var client *Client
go func() {
var (
client *Client
wg errgroup.Group
sigs = make(chan os.Signal, 1)
done = make(chan struct{}, 1)
)
server := http.Server{Addr: ":" + utils.IntToString(ws.port), Handler: nil}
wg.Go(func() error {
for {
select {
case <-done:
return nil
case client = <-ws.registerChan:
ws.registerClient(client)
case client = <-ws.unregisterChan:
@@ -177,33 +184,69 @@ func (ws *WsServer) Run() error {
ws.multiTerminalLoginChecker(onlineInfo.clientOK, onlineInfo.oldClients, onlineInfo.newClient)
}
}
})
wg.Go(func() error {
http.HandleFunc("/", ws.wsHandler)
return server.ListenAndServe()
})
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
<-sigs
go func() {
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
defer cancel()
// graceful exit operation for server
_ = server.Shutdown(ctx)
_ = wg.Wait()
close(done)
}()
http.HandleFunc("/", ws.wsHandler)
// http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {})
return http.ListenAndServe(":"+utils.IntToString(ws.port), nil) // Start listening
select {
case <-done:
return nil
case <-time.After(15 * time.Second):
return utils.Wrap1(errors.New("timeout exit"))
}
}
var concurrentRequest = 3
func (ws *WsServer) sendUserOnlineInfoToOtherNode(ctx context.Context, client *Client) error {
conns, err := ws.disCov.GetConns(ctx, config.Config.RpcRegisterName.OpenImMessageGatewayName)
if err != nil {
return err
}
wg := errgroup.Group{}
wg.SetLimit(concurrentRequest)
// Online push user online message to other node
for _, v := range conns {
v := v // safe closure var
if v.Target() == ws.disCov.GetSelfConnTarget() {
log.ZDebug(ctx, "Filter out this node", "node", v.Target())
continue
}
msgClient := msggateway.NewMsgGatewayClient(v)
_, err := msgClient.MultiTerminalLoginCheck(ctx, &msggateway.MultiTerminalLoginCheckReq{
UserID: client.UserID,
PlatformID: int32(client.PlatformID), Token: client.token,
wg.Go(func() error {
msgClient := msggateway.NewMsgGatewayClient(v)
_, err := msgClient.MultiTerminalLoginCheck(ctx, &msggateway.MultiTerminalLoginCheckReq{
UserID: client.UserID,
PlatformID: int32(client.PlatformID), Token: client.token,
})
if err != nil {
log.ZWarn(ctx, "MultiTerminalLoginCheck err", err, "node", v.Target())
}
return nil
})
if err != nil {
log.ZWarn(ctx, "MultiTerminalLoginCheck err", err, "node", v.Target())
continue
}
}
_ = wg.Wait()
return nil
}
@@ -289,70 +332,72 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
}
fallthrough
case constant.AllLoginButSameTermKick:
if clientOK {
isDeleteUser := ws.clients.deleteClients(newClient.UserID, oldClients)
if isDeleteUser {
ws.onlineUserNum.Add(-1)
if !clientOK {
return
}
isDeleteUser := ws.clients.deleteClients(newClient.UserID, oldClients)
if isDeleteUser {
ws.onlineUserNum.Add(-1)
}
for _, c := range oldClients {
err := c.KickOnlineMessage()
if err != nil {
log.ZWarn(c.ctx, "KickOnlineMessage", err)
}
for _, c := range oldClients {
err := c.KickOnlineMessage()
if err != nil {
log.ZWarn(c.ctx, "KickOnlineMessage", err)
}
}
m, err := ws.cache.GetTokensWithoutError(
}
m, err := ws.cache.GetTokensWithoutError(
newClient.ctx,
newClient.UserID,
newClient.PlatformID,
)
if err != nil && err != redis.Nil {
log.ZWarn(
newClient.ctx,
newClient.UserID,
newClient.PlatformID,
)
if err != nil && err != redis.Nil {
log.ZWarn(
newClient.ctx,
"get token from redis err",
err,
"userID",
newClient.UserID,
"platformID",
newClient.PlatformID,
)
return
}
if m == nil {
log.ZWarn(
newClient.ctx,
"m is nil",
errors.New("m is nil"),
"userID",
newClient.UserID,
"platformID",
newClient.PlatformID,
)
return
}
log.ZDebug(
newClient.ctx,
"get token from redis",
"get token from redis err",
err,
"userID",
newClient.UserID,
"platformID",
newClient.PlatformID,
"tokenMap",
m,
)
return
}
if m == nil {
log.ZWarn(
newClient.ctx,
"m is nil",
errors.New("m is nil"),
"userID",
newClient.UserID,
"platformID",
newClient.PlatformID,
)
return
}
log.ZDebug(
newClient.ctx,
"get token from redis",
"userID",
newClient.UserID,
"platformID",
newClient.PlatformID,
"tokenMap",
m,
)
for k := range m {
if k != newClient.ctx.GetToken() {
m[k] = constant.KickedToken
}
}
log.ZDebug(newClient.ctx, "set token map is ", "token map", m, "userID",
newClient.UserID, "token", newClient.ctx.GetToken())
err = ws.cache.SetTokenMapByUidPid(newClient.ctx, newClient.UserID, newClient.PlatformID, m)
if err != nil {
log.ZWarn(newClient.ctx, "SetTokenMapByUidPid err", err, "userID", newClient.UserID, "platformID", newClient.PlatformID)
return
for k := range m {
if k != newClient.ctx.GetToken() {
m[k] = constant.KickedToken
}
}
log.ZDebug(newClient.ctx, "set token map is ", "token map", m, "userID",
newClient.UserID, "token", newClient.ctx.GetToken())
err = ws.cache.SetTokenMapByUidPid(newClient.ctx, newClient.UserID, newClient.PlatformID, m)
if err != nil {
log.ZWarn(newClient.ctx, "SetTokenMapByUidPid err", err, "userID", newClient.UserID, "platformID", newClient.PlatformID)
return
}
}
}
@@ -365,7 +410,7 @@ func (ws *WsServer) unregisterClient(client *Client) {
}
ws.onlineUserConnNum.Add(-1)
ws.SetUserOnlineStatus(client.ctx, client, constant.Offline)
log.ZInfo(client.ctx, "user offline", "close reason", client.closedErr, "online user Num", ws.onlineUserNum, "online user conn Num",
log.ZInfo(client.ctx, "user offline", "close reason", client.closedErr, "online user Num", ws.onlineUserNum.Load(), "online user conn Num",
ws.onlineUserConnNum.Load(),
)
}
@@ -404,7 +449,7 @@ func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
httpError(connContext, errs.ErrConnArgsErr)
return
}
if err := authverify.WsVerifyToken(token, userID, platformID); err != nil {
if err = authverify.WsVerifyToken(token, userID, platformID); err != nil {
httpError(connContext, err)
return
}
@@ -427,7 +472,8 @@ func (ws *WsServer) wsHandler(w http.ResponseWriter, r *http.Request) {
httpError(connContext, errs.ErrTokenNotExist.Wrap())
return
}
wsLongConn := newGWebSocket(WebSocket, ws.handshakeTimeout)
wsLongConn := newGWebSocket(WebSocket, ws.handshakeTimeout, ws.writeBufferSize)
err = wsLongConn.GenerateLongConn(w, r)
if err != nil {
httpError(connContext, err)
+8
View File
@@ -27,6 +27,8 @@ type (
handshakeTimeout time.Duration
// 允许消息最大长度
messageMaxMsgLength int
// websocket write buffer, default: 4096, 4kb.
writeBufferSize int
}
)
@@ -53,3 +55,9 @@ func WithMessageMaxMsgLength(length int) Option {
opt.messageMaxMsgLength = length
}
}
func WithWriteBufferSize(size int) Option {
return func(opt *configs) {
opt.writeBufferSize = size
}
}
+4 -6
View File
@@ -17,17 +17,15 @@ package msgtransfer
import (
"errors"
"fmt"
"log"
"net/http"
"sync"
"github.com/OpenIMSDK/tools/mw"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/OpenIMSDK/tools/mw"
"log"
"net/http"
"sync"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
@@ -252,7 +252,10 @@ func (och *OnlineHistoryRedisConsumerHandler) handleNotification(
return
}
log.ZDebug(ctx, "success to next topic", "conversationID", conversationID)
och.msgDatabase.MsgToMongoMQ(ctx, key, conversationID, storageList, lastSeq)
err = och.msgDatabase.MsgToMongoMQ(ctx, key, conversationID, storageList, lastSeq)
if err != nil {
log.ZError(ctx, "MsgToMongoMQ error", err)
}
och.toPushTopic(ctx, key, conversationID, storageList)
}
}
@@ -277,9 +280,6 @@ func (och *OnlineHistoryRedisConsumerHandler) handleMsg(
lastSeq, isNewConversation, err := och.msgDatabase.BatchInsertChat2Cache(ctx, conversationID, storageList)
if err != nil && errs.Unwrap(err) != redis.Nil {
log.ZError(ctx, "batch data insert to redis err", err, "storageMsgList", storageList)
och.singleMsgFailedCountMutex.Lock()
och.singleMsgFailedCount += uint64(len(storageList))
och.singleMsgFailedCountMutex.Unlock()
return
}
if isNewConversation {
@@ -311,10 +311,10 @@ func (och *OnlineHistoryRedisConsumerHandler) handleMsg(
}
log.ZDebug(ctx, "success incr to next topic")
och.singleMsgSuccessCountMutex.Lock()
och.singleMsgSuccessCount += uint64(len(storageList))
och.singleMsgSuccessCountMutex.Unlock()
och.msgDatabase.MsgToMongoMQ(ctx, key, conversationID, storageList, lastSeq)
err = och.msgDatabase.MsgToMongoMQ(ctx, key, conversationID, storageList, lastSeq)
if err != nil {
log.ZError(ctx, "MsgToMongoMQ error", err)
}
och.toPushTopic(ctx, key, conversationID, storageList)
}
}
@@ -427,49 +427,62 @@ func (och *OnlineHistoryRedisConsumerHandler) ConsumeClaim(
break
}
}
rwLock := new(sync.RWMutex)
log.ZDebug(context.Background(), "online new session msg come", "highWaterMarkOffset",
claim.HighWaterMarkOffset(), "topic", claim.Topic(), "partition", claim.Partition())
cMsg := make([]*sarama.ConsumerMessage, 0, 1000)
t := time.NewTicker(time.Millisecond * 100)
split := 1000
rwLock := new(sync.RWMutex)
messages := make([]*sarama.ConsumerMessage, 0, 1000)
ticker := time.NewTicker(time.Millisecond * 100)
go func() {
for {
select {
case <-t.C:
if len(cMsg) > 0 {
rwLock.Lock()
ccMsg := make([]*sarama.ConsumerMessage, 0, 1000)
for _, v := range cMsg {
ccMsg = append(ccMsg, v)
}
cMsg = make([]*sarama.ConsumerMessage, 0, 1000)
rwLock.Unlock()
split := 1000
ctx := mcontext.WithTriggerIDContext(context.Background(), utils.OperationIDGenerator())
log.ZDebug(ctx, "timer trigger msg consumer start", "length", len(ccMsg))
for i := 0; i < len(ccMsg)/split; i++ {
// log.Debug()
och.msgDistributionCh <- Cmd2Value{Cmd: ConsumerMsgs, Value: TriggerChannelValue{
ctx: ctx, cMsgList: ccMsg[i*split : (i+1)*split],
}}
}
if (len(ccMsg) % split) > 0 {
och.msgDistributionCh <- Cmd2Value{Cmd: ConsumerMsgs, Value: TriggerChannelValue{
ctx: ctx, cMsgList: ccMsg[split*(len(ccMsg)/split):],
}}
}
log.ZDebug(ctx, "timer trigger msg consumer end", "length", len(ccMsg))
case <-ticker.C:
if len(messages) == 0 {
continue
}
rwLock.Lock()
buffer := make([]*sarama.ConsumerMessage, 0, len(messages))
buffer = append(buffer, messages...)
// reuse slice, set cap to 0
messages = messages[:0]
rwLock.Unlock()
start := time.Now()
ctx := mcontext.WithTriggerIDContext(context.Background(), utils.OperationIDGenerator())
log.ZDebug(ctx, "timer trigger msg consumer start", "length", len(buffer))
for i := 0; i < len(buffer)/split; i++ {
och.msgDistributionCh <- Cmd2Value{Cmd: ConsumerMsgs, Value: TriggerChannelValue{
ctx: ctx, cMsgList: buffer[i*split : (i+1)*split],
}}
}
if (len(buffer) % split) > 0 {
och.msgDistributionCh <- Cmd2Value{Cmd: ConsumerMsgs, Value: TriggerChannelValue{
ctx: ctx, cMsgList: buffer[split*(len(buffer)/split):],
}}
}
log.ZDebug(ctx, "timer trigger msg consumer end",
"length", len(buffer), "time_cost", time.Since(start),
)
}
}
}()
for msg := range claim.Messages() {
rwLock.Lock()
if len(msg.Value) != 0 {
cMsg = append(cMsg, msg)
if len(msg.Value) == 0 {
continue
}
rwLock.Lock()
messages = append(messages, msg)
rwLock.Unlock()
sess.MarkMessage(msg, "")
}
return nil
}
+8 -11
View File
@@ -19,7 +19,6 @@ import (
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/protocol/sdkws"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/mcontext"
"github.com/OpenIMSDK/tools/utils"
@@ -44,7 +43,7 @@ func callbackOfflinePush(
req := &callbackstruct.CallbackBeforePushReq{
UserStatusBatchCallbackReq: callbackstruct.UserStatusBatchCallbackReq{
UserStatusBaseCallback: callbackstruct.UserStatusBaseCallback{
CallbackCommand: constant.CallbackOfflinePushCommand,
CallbackCommand: callbackstruct.CallbackOfflinePushCommand,
OperationID: mcontext.GetOperationID(ctx),
PlatformID: int(msg.SenderPlatformID),
Platform: constant.PlatformIDToName(int(msg.SenderPlatformID)),
@@ -62,9 +61,6 @@ func callbackOfflinePush(
}
resp := &callbackstruct.CallbackBeforePushResp{}
if err := http.CallBackPostReturn(ctx, url(), req, resp, config.Config.Callback.CallbackOfflinePush); err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
if len(resp.UserIDs) != 0 {
@@ -83,7 +79,7 @@ func callbackOnlinePush(ctx context.Context, userIDs []string, msg *sdkws.MsgDat
req := callbackstruct.CallbackBeforePushReq{
UserStatusBatchCallbackReq: callbackstruct.UserStatusBatchCallbackReq{
UserStatusBaseCallback: callbackstruct.UserStatusBaseCallback{
CallbackCommand: constant.CallbackOnlinePushCommand,
CallbackCommand: callbackstruct.CallbackOnlinePushCommand,
OperationID: mcontext.GetOperationID(ctx),
PlatformID: int(msg.SenderPlatformID),
Platform: constant.PlatformIDToName(int(msg.SenderPlatformID)),
@@ -99,7 +95,10 @@ func callbackOnlinePush(ctx context.Context, userIDs []string, msg *sdkws.MsgDat
Content: GetContent(msg),
}
resp := &callbackstruct.CallbackBeforePushResp{}
return http.CallBackPostReturn(ctx, url(), req, resp, config.Config.Callback.CallbackOnlinePush)
if err := http.CallBackPostReturn(ctx, url(), req, resp, config.Config.Callback.CallbackOnlinePush); err != nil {
return err
}
return nil
}
func callbackBeforeSuperGroupOnlinePush(
@@ -113,7 +112,7 @@ func callbackBeforeSuperGroupOnlinePush(
}
req := callbackstruct.CallbackBeforeSuperGroupOnlinePushReq{
UserStatusBaseCallback: callbackstruct.UserStatusBaseCallback{
CallbackCommand: constant.CallbackSuperGroupOnlinePushCommand,
CallbackCommand: callbackstruct.CallbackSuperGroupOnlinePushCommand,
OperationID: mcontext.GetOperationID(ctx),
PlatformID: int(msg.SenderPlatformID),
Platform: constant.PlatformIDToName(int(msg.SenderPlatformID)),
@@ -129,11 +128,9 @@ func callbackBeforeSuperGroupOnlinePush(
}
resp := &callbackstruct.CallbackBeforeSuperGroupOnlinePushResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, req, resp, config.Config.Callback.CallbackBeforeSuperGroupOnlinePush); err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
return nil
if len(resp.UserIDs) != 0 {
*pushToUserIDs = resp.UserIDs
}
+1
View File
@@ -2,6 +2,7 @@ package dummy
import (
"context"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush"
)
+40 -12
View File
@@ -18,8 +18,9 @@ import (
"context"
"encoding/json"
"errors"
"sync"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"golang.org/x/sync/errgroup"
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/protocol/conversation"
@@ -40,6 +41,7 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/db/controller"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/localcache"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
)
@@ -285,18 +287,44 @@ func (p *Pusher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData,
if err != nil {
return nil, err
}
// Online push message
for _, v := range conns {
msgClient := msggateway.NewMsgGatewayClient(v)
reply, err := msgClient.SuperGroupOnlineBatchPushOneMsg(ctx, &msggateway.OnlineBatchPushOneMsgReq{MsgData: msg, PushToUserIDs: pushToUserIDs})
if err != nil {
continue
}
log.ZDebug(ctx, "push result", "reply", reply)
if reply != nil && reply.SinglePushResult != nil {
wsResults = append(wsResults, reply.SinglePushResult...)
}
var (
mu sync.Mutex
wg = errgroup.Group{}
input = &msggateway.OnlineBatchPushOneMsgReq{MsgData: msg, PushToUserIDs: pushToUserIDs}
maxWorkers = config.Config.Push.MaxConcurrentWorkers
)
if maxWorkers < 3 {
maxWorkers = 3
}
wg.SetLimit(maxWorkers)
// Online push message
for _, conn := range conns {
conn := conn // loop var safe
wg.Go(func() error {
msgClient := msggateway.NewMsgGatewayClient(conn)
reply, err := msgClient.SuperGroupOnlineBatchPushOneMsg(ctx, input)
if err != nil {
return nil
}
log.ZDebug(ctx, "push result", "reply", reply)
if reply != nil && reply.SinglePushResult != nil {
mu.Lock()
wsResults = append(wsResults, reply.SinglePushResult...)
mu.Unlock()
}
return nil
})
}
_ = wg.Wait()
// always return nil
return wsResults, nil
}
+6
View File
@@ -74,6 +74,9 @@ func (s *friendServer) RemoveBlack(
return nil, err
}
s.notificationSender.BlackDeletedNotification(ctx, req)
if err := CallbackAfterRemoveBlack(ctx, req); err != nil {
return nil, err
}
return &pbfriend.RemoveBlackResp{}, nil
}
@@ -85,6 +88,9 @@ func (s *friendServer) AddBlack(ctx context.Context, req *pbfriend.AddBlackReq)
if err != nil {
return nil, err
}
if err := CallbackBeforeAddBlack(ctx, req); err != nil {
return nil, err
}
black := relation.BlackModel{
OwnerUserID: req.OwnerUserID,
BlockUserID: req.BlackUserID,
+151 -10
View File
@@ -16,12 +16,8 @@ package friend
import (
"context"
"github.com/OpenIMSDK/protocol/constant"
pbfriend "github.com/OpenIMSDK/protocol/friend"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/mcontext"
"github.com/OpenIMSDK/tools/utils"
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/http"
@@ -32,17 +28,162 @@ func CallbackBeforeAddFriend(ctx context.Context, req *pbfriend.ApplyToAddFriend
return nil
}
cbReq := &cbapi.CallbackBeforeAddFriendReq{
CallbackCommand: constant.CallbackBeforeAddFriendCommand,
CallbackCommand: cbapi.CallbackBeforeAddFriendCommand,
FromUserID: req.FromUserID,
ToUserID: req.ToUserID,
ReqMsg: req.ReqMsg,
OperationID: mcontext.GetOperationID(ctx),
Ex: req.Ex,
}
resp := &cbapi.CallbackBeforeAddFriendResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeAddFriend); err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
return nil
}
func CallbackBeforeSetFriendRemark(ctx context.Context, req *pbfriend.SetFriendRemarkReq) error {
if !config.Config.Callback.CallbackBeforeSetFriendRemark.Enable {
return nil
}
cbReq := &cbapi.CallbackBeforeSetFriendRemarkReq{
CallbackCommand: cbapi.CallbackBeforeSetFriendRemark,
OwnerUserID: req.OwnerUserID,
FriendUserID: req.FriendUserID,
Remark: req.Remark,
}
resp := &cbapi.CallbackBeforeSetFriendRemarkResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeAddFriend); err != nil {
return err
}
utils.NotNilReplace(&req.Remark, &resp.Remark)
return nil
}
func CallbackAfterSetFriendRemark(ctx context.Context, req *pbfriend.SetFriendRemarkReq) error {
if !config.Config.Callback.CallbackAfterSetFriendRemark.Enable {
return nil
}
cbReq := &cbapi.CallbackAfterSetFriendRemarkReq{
CallbackCommand: cbapi.CallbackAfterSetFriendRemark,
OwnerUserID: req.OwnerUserID,
FriendUserID: req.FriendUserID,
Remark: req.Remark,
}
resp := &cbapi.CallbackAfterSetFriendRemarkResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeAddFriend); err != nil {
return err
}
return nil
}
func CallbackBeforeAddBlack(ctx context.Context, req *pbfriend.AddBlackReq) error {
if !config.Config.Callback.CallbackBeforeAddBlack.Enable {
return nil
}
cbReq := &cbapi.CallbackBeforeAddBlackReq{
CallbackCommand: cbapi.CallbackBeforeAddBlackCommand,
OwnerUserID: req.OwnerUserID,
BlackUserID: req.BlackUserID,
}
resp := &cbapi.CallbackBeforeAddBlackResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeAddBlack); err != nil {
return err
}
return nil
}
func CallbackAfterAddFriend(ctx context.Context, req *pbfriend.ApplyToAddFriendReq) error {
if !config.Config.Callback.CallbackAfterAddFriend.Enable {
return nil
}
cbReq := &cbapi.CallbackAfterAddFriendReq{
CallbackCommand: cbapi.CallbackAfterAddFriendCommand,
FromUserID: req.FromUserID,
ToUserID: req.ToUserID,
ReqMsg: req.ReqMsg,
}
resp := &cbapi.CallbackAfterAddFriendResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackAfterAddFriend); err != nil {
return err
}
return nil
}
func CallbackBeforeAddFriendAgree(ctx context.Context, req *pbfriend.RespondFriendApplyReq) error {
if !config.Config.Callback.CallbackBeforeAddFriendAgree.Enable {
return nil
}
cbReq := &cbapi.CallbackBeforeAddFriendAgreeReq{
CallbackCommand: cbapi.CallbackBeforeAddFriendAgreeCommand,
FromUserID: req.FromUserID,
ToUserID: req.ToUserID,
HandleMsg: req.HandleMsg,
HandleResult: req.HandleResult,
}
resp := &cbapi.CallbackBeforeAddFriendAgreeResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeAddFriendAgree); err != nil {
return err
}
return nil
}
func CallbackAfterDeleteFriend(ctx context.Context, req *pbfriend.DeleteFriendReq) error {
if !config.Config.Callback.CallbackAfterDeleteFriend.Enable {
return nil
}
cbReq := &cbapi.CallbackAfterDeleteFriendReq{
CallbackCommand: cbapi.CallbackAfterDeleteFriendCommand,
OwnerUserID: req.OwnerUserID,
FriendUserID: req.FriendUserID,
}
resp := &cbapi.CallbackAfterDeleteFriendResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackAfterDeleteFriend); err != nil {
return err
}
return nil
}
func CallbackBeforeImportFriends(ctx context.Context, req *pbfriend.ImportFriendReq) error {
if !config.Config.Callback.CallbackBeforeImportFriends.Enable {
return nil
}
cbReq := &cbapi.CallbackBeforeImportFriendsReq{
CallbackCommand: cbapi.CallbackBeforeImportFriendsCommand,
OwnerUserID: req.OwnerUserID,
FriendUserIDs: req.FriendUserIDs,
}
resp := &cbapi.CallbackBeforeImportFriendsResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeImportFriends); err != nil {
return err
}
if len(resp.FriendUserIDs) != 0 {
req.FriendUserIDs = resp.FriendUserIDs
}
return nil
}
func CallbackAfterImportFriends(ctx context.Context, req *pbfriend.ImportFriendReq) error {
if !config.Config.Callback.CallbackAfterImportFriends.Enable {
return nil
}
cbReq := &cbapi.CallbackAfterImportFriendsReq{
CallbackCommand: cbapi.CallbackAfterImportFriendsCommand,
OwnerUserID: req.OwnerUserID,
FriendUserIDs: req.FriendUserIDs,
}
resp := &cbapi.CallbackAfterImportFriendsResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackAfterImportFriends); err != nil {
return err
}
return nil
}
func CallbackAfterRemoveBlack(ctx context.Context, req *pbfriend.RemoveBlackReq) error {
if !config.Config.Callback.CallbackAfterRemoveBlack.Enable {
return nil
}
cbReq := &cbapi.CallbackAfterRemoveBlackReq{
CallbackCommand: cbapi.CallbackAfterRemoveBlackCommand,
OwnerUserID: req.OwnerUserID,
BlackUserID: req.BlackUserID,
}
resp := &cbapi.CallbackAfterRemoveBlackResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackAfterRemoveBlack); err != nil {
return err
}
return nil
+24 -1
View File
@@ -103,7 +103,7 @@ func (s *friendServer) ApplyToAddFriend(
if req.ToUserID == req.FromUserID {
return nil, errs.ErrCanNotAddYourself.Wrap()
}
if err := CallbackBeforeAddFriend(ctx, req); err != nil && err != errs.ErrCallbackContinue {
if err = CallbackBeforeAddFriend(ctx, req); err != nil && err != errs.ErrCallbackContinue {
return nil, err
}
if _, err := s.userRpcClient.GetUsersInfoMap(ctx, []string{req.ToUserID, req.FromUserID}); err != nil {
@@ -120,6 +120,9 @@ func (s *friendServer) ApplyToAddFriend(
return nil, err
}
s.notificationSender.FriendApplicationAddNotification(ctx, req)
if err = CallbackAfterAddFriend(ctx, req); err != nil && err != errs.ErrCallbackContinue {
return nil, err
}
return resp, nil
}
@@ -141,6 +144,10 @@ func (s *friendServer) ImportFriends(
if utils.Duplicate(req.FriendUserIDs) {
return nil, errs.ErrArgs.Wrap("friend userID repeated")
}
if err := CallbackBeforeImportFriends(ctx, req); err != nil {
return nil, err
}
if err := s.friendDatabase.BecomeFriends(ctx, req.OwnerUserID, req.FriendUserIDs, constant.BecomeFriendByImport); err != nil {
return nil, err
}
@@ -151,6 +158,9 @@ func (s *friendServer) ImportFriends(
HandleResult: constant.FriendResponseAgree,
})
}
if err := CallbackAfterImportFriends(ctx, req); err != nil {
return nil, err
}
return &pbfriend.ImportFriendResp{}, nil
}
@@ -172,6 +182,9 @@ func (s *friendServer) RespondFriendApply(
HandleResult: req.HandleResult,
}
if req.HandleResult == constant.FriendResponseAgree {
if err := CallbackBeforeAddFriendAgree(ctx, req); err != nil && err != errs.ErrCallbackContinue {
return nil, err
}
err := s.friendDatabase.AgreeFriendRequest(ctx, &friendRequest)
if err != nil {
return nil, err
@@ -208,6 +221,9 @@ func (s *friendServer) DeleteFriend(
return nil, err
}
s.notificationSender.FriendDeletedNotification(ctx, req)
if err := CallbackAfterDeleteFriend(ctx, req); err != nil {
return nil, err
}
return resp, nil
}
@@ -217,6 +233,10 @@ func (s *friendServer) SetFriendRemark(
req *pbfriend.SetFriendRemarkReq,
) (resp *pbfriend.SetFriendRemarkResp, err error) {
defer log.ZInfo(ctx, utils.GetFuncName()+" Return")
if err = CallbackBeforeSetFriendRemark(ctx, req); err != nil && err != errs.ErrCallbackContinue {
return nil, err
}
resp = &pbfriend.SetFriendRemarkResp{}
if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil {
return nil, err
@@ -228,6 +248,9 @@ func (s *friendServer) SetFriendRemark(
if err := s.friendDatabase.UpdateRemark(ctx, req.OwnerUserID, req.FriendUserID, req.Remark); err != nil {
return nil, err
}
if err := CallbackAfterSetFriendRemark(ctx, req); err != nil && err != errs.ErrCallbackContinue {
return nil, err
}
s.notificationSender.FriendRemarkSetNotification(ctx, req.OwnerUserID, req.FriendUserID)
return resp, nil
}
+275 -23
View File
@@ -16,15 +16,17 @@ package group
import (
"context"
"github.com/OpenIMSDK/tools/log"
"time"
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/protocol/group"
"github.com/OpenIMSDK/protocol/wrapperspb"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/mcontext"
"github.com/OpenIMSDK/tools/utils"
pbgroup "github.com/OpenIMSDK/protocol/group"
"github.com/openimsdk/open-im-server/v3/pkg/apistruct"
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
@@ -37,7 +39,7 @@ func CallbackBeforeCreateGroup(ctx context.Context, req *group.CreateGroupReq) (
return nil
}
cbReq := &callbackstruct.CallbackBeforeCreateGroupReq{
CallbackCommand: constant.CallbackBeforeCreateGroupCommand,
CallbackCommand: callbackstruct.CallbackBeforeCreateGroupCommand,
OperationID: mcontext.GetOperationID(ctx),
GroupInfo: req.GroupInfo,
}
@@ -58,17 +60,7 @@ func CallbackBeforeCreateGroup(ctx context.Context, req *group.CreateGroupReq) (
})
}
resp := &callbackstruct.CallbackBeforeCreateGroupResp{}
err = http.CallBackPostReturn(
ctx,
config.Config.Callback.CallbackUrl,
cbReq,
resp,
config.Config.Callback.CallbackBeforeCreateGroup,
)
if err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
if err = http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeCreateGroup); err != nil {
return err
}
utils.NotNilReplace(&req.GroupInfo.GroupID, resp.GroupID)
@@ -86,6 +78,37 @@ func CallbackBeforeCreateGroup(ctx context.Context, req *group.CreateGroupReq) (
return nil
}
func CallbackAfterCreateGroup(ctx context.Context, req *group.CreateGroupReq) (err error) {
if !config.Config.Callback.CallbackAfterCreateGroup.Enable {
return nil
}
cbReq := &callbackstruct.CallbackAfterCreateGroupReq{
CallbackCommand: callbackstruct.CallbackAfterCreateGroupCommand,
GroupInfo: req.GroupInfo,
}
cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{
UserID: req.OwnerUserID,
RoleLevel: constant.GroupOwner,
})
for _, userID := range req.AdminUserIDs {
cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{
UserID: userID,
RoleLevel: constant.GroupAdmin,
})
}
for _, userID := range req.MemberUserIDs {
cbReq.InitMemberList = append(cbReq.InitMemberList, &apistruct.GroupAddMemberInfo{
UserID: userID,
RoleLevel: constant.GroupOrdinaryUsers,
})
}
resp := &callbackstruct.CallbackAfterCreateGroupResp{}
if err = http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackAfterCreateGroup); err != nil {
return err
}
return nil
}
func CallbackBeforeMemberJoinGroup(
ctx context.Context,
groupMember *relation.GroupMemberModel,
@@ -95,8 +118,7 @@ func CallbackBeforeMemberJoinGroup(
return nil
}
callbackReq := &callbackstruct.CallbackBeforeMemberJoinGroupReq{
CallbackCommand: constant.CallbackBeforeMemberJoinGroupCommand,
OperationID: mcontext.GetOperationID(ctx),
CallbackCommand: callbackstruct.CallbackBeforeMemberJoinGroupCommand,
GroupID: groupMember.GroupID,
UserID: groupMember.UserID,
Ex: groupMember.Ex,
@@ -111,9 +133,6 @@ func CallbackBeforeMemberJoinGroup(
config.Config.Callback.CallbackBeforeMemberJoinGroup,
)
if err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
if resp.MuteEndTime != nil {
@@ -131,8 +150,7 @@ func CallbackBeforeSetGroupMemberInfo(ctx context.Context, req *group.SetGroupMe
return nil
}
callbackReq := callbackstruct.CallbackBeforeSetGroupMemberInfoReq{
CallbackCommand: constant.CallbackBeforeSetGroupMemberInfoCommand,
OperationID: mcontext.GetOperationID(ctx),
CallbackCommand: callbackstruct.CallbackBeforeSetGroupMemberInfoCommand,
GroupID: req.GroupID,
UserID: req.UserID,
}
@@ -157,9 +175,6 @@ func CallbackBeforeSetGroupMemberInfo(ctx context.Context, req *group.SetGroupMe
config.Config.Callback.CallbackBeforeSetGroupMemberInfo,
)
if err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
if resp.FaceURL != nil {
@@ -176,3 +191,240 @@ func CallbackBeforeSetGroupMemberInfo(ctx context.Context, req *group.SetGroupMe
}
return nil
}
func CallbackAfterSetGroupMemberInfo(ctx context.Context, req *group.SetGroupMemberInfo) (err error) {
if !config.Config.Callback.CallbackBeforeSetGroupMemberInfo.Enable {
return nil
}
callbackReq := callbackstruct.CallbackAfterSetGroupMemberInfoReq{
CallbackCommand: callbackstruct.CallbackAfterSetGroupMemberInfoCommand,
GroupID: req.GroupID,
UserID: req.UserID,
}
if req.Nickname != nil {
callbackReq.Nickname = &req.Nickname.Value
}
if req.FaceURL != nil {
callbackReq.FaceURL = &req.FaceURL.Value
}
if req.RoleLevel != nil {
callbackReq.RoleLevel = &req.RoleLevel.Value
}
if req.Ex != nil {
callbackReq.Ex = &req.Ex.Value
}
resp := &callbackstruct.CallbackAfterSetGroupMemberInfoResp{}
if err = http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, callbackReq, resp, config.Config.Callback.CallbackAfterSetGroupMemberInfo); err != nil {
return err
}
return nil
}
func CallbackQuitGroup(ctx context.Context, req *group.QuitGroupReq) (err error) {
if !config.Config.Callback.CallbackQuitGroup.Enable {
return nil
}
cbReq := &callbackstruct.CallbackQuitGroupReq{
CallbackCommand: callbackstruct.CallbackQuitGroupCommand,
GroupID: req.GroupID,
UserID: req.UserID,
}
resp := &callbackstruct.CallbackQuitGroupResp{}
if err = http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackQuitGroup); err != nil {
return err
}
return nil
}
func CallbackKillGroupMember(ctx context.Context, req *pbgroup.KickGroupMemberReq) (err error) {
if !config.Config.Callback.CallbackKillGroupMember.Enable {
return nil
}
cbReq := &callbackstruct.CallbackKillGroupMemberReq{
CallbackCommand: callbackstruct.CallbackKillGroupCommand,
GroupID: req.GroupID,
KickedUserIDs: req.KickedUserIDs,
}
resp := &callbackstruct.CallbackKillGroupMemberResp{}
if err = http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackQuitGroup); err != nil {
return err
}
return nil
}
func CallbackDismissGroup(ctx context.Context, req *callbackstruct.CallbackDisMissGroupReq) (err error) {
if !config.Config.Callback.CallbackDismissGroup.Enable {
return nil
}
req.CallbackCommand = callbackstruct.CallbackDisMissGroupCommand
resp := &callbackstruct.CallbackDisMissGroupResp{}
if err = http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, req, resp, config.Config.Callback.CallbackQuitGroup); err != nil {
return err
}
return nil
}
func CallbackApplyJoinGroupBefore(ctx context.Context, req *callbackstruct.CallbackJoinGroupReq) (err error) {
if !config.Config.Callback.CallbackBeforeJoinGroup.Enable {
return nil
}
req.CallbackCommand = callbackstruct.CallbackBeforeJoinGroupCommand
resp := &callbackstruct.CallbackJoinGroupResp{}
if err = http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, req, resp, config.Config.Callback.CallbackBeforeJoinGroup); err != nil {
return err
}
return nil
}
func CallbackTransferGroupOwnerAfter(ctx context.Context, req *pbgroup.TransferGroupOwnerReq) (err error) {
if !config.Config.Callback.CallbackTransferGroupOwnerAfter.Enable {
return nil
}
cbReq := &callbackstruct.CallbackTransferGroupOwnerReq{
CallbackCommand: callbackstruct.CallbackTransferGroupOwnerAfter,
GroupID: req.GroupID,
OldOwnerUserID: req.OldOwnerUserID,
NewOwnerUserID: req.NewOwnerUserID,
}
resp := &callbackstruct.CallbackTransferGroupOwnerResp{}
if err = http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeJoinGroup); err != nil {
return err
}
return nil
}
func CallbackBeforeInviteUserToGroup(ctx context.Context, req *group.InviteUserToGroupReq) (err error) {
if !config.Config.Callback.CallbackBeforeInviteUserToGroup.Enable {
return nil
}
callbackReq := &callbackstruct.CallbackBeforeInviteUserToGroupReq{
CallbackCommand: callbackstruct.CallbackBeforeInviteJoinGroupCommand,
OperationID: mcontext.GetOperationID(ctx),
GroupID: req.GroupID,
Reason: req.Reason,
InvitedUserIDs: req.InvitedUserIDs,
}
resp := &callbackstruct.CallbackBeforeInviteUserToGroupResp{}
err = http.CallBackPostReturn(
ctx,
config.Config.Callback.CallbackUrl,
callbackReq,
resp,
config.Config.Callback.CallbackBeforeInviteUserToGroup,
)
if err != nil {
return err
}
if len(resp.RefusedMembersAccount) > 0 {
// Handle the scenario where certain members are refused
// You might want to update the req.Members list or handle it as per your business logic
}
utils.StructFieldNotNilReplace(req, resp)
return nil
}
func CallbackAfterJoinGroup(ctx context.Context, req *group.JoinGroupReq) error {
if !config.Config.Callback.CallbackAfterJoinGroup.Enable {
return nil
}
callbackReq := &callbackstruct.CallbackAfterJoinGroupReq{
CallbackCommand: callbackstruct.CallbackAfterJoinGroupCommand,
OperationID: mcontext.GetOperationID(ctx),
GroupID: req.GroupID,
ReqMessage: req.ReqMessage,
JoinSource: req.JoinSource,
InviterUserID: req.InviterUserID,
}
resp := &callbackstruct.CallbackAfterJoinGroupResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, callbackReq, resp, config.Config.Callback.CallbackAfterJoinGroup); err != nil {
return err
}
return nil
}
func CallbackBeforeSetGroupInfo(ctx context.Context, req *group.SetGroupInfoReq) error {
if !config.Config.Callback.CallbackBeforeSetGroupInfo.Enable {
return nil
}
callbackReq := &callbackstruct.CallbackBeforeSetGroupInfoReq{
CallbackCommand: callbackstruct.CallbackBeforeSetGroupInfoCommand,
GroupID: req.GroupInfoForSet.GroupID,
Notification: req.GroupInfoForSet.Notification,
Introduction: req.GroupInfoForSet.Introduction,
FaceURL: req.GroupInfoForSet.FaceURL,
GroupName: req.GroupInfoForSet.GroupName,
}
if req.GroupInfoForSet.Ex != nil {
callbackReq.Ex = req.GroupInfoForSet.Ex.Value
}
log.ZDebug(ctx, "debug CallbackBeforeSetGroupInfo", callbackReq.Ex)
if req.GroupInfoForSet.NeedVerification != nil {
callbackReq.NeedVerification = req.GroupInfoForSet.NeedVerification.Value
}
if req.GroupInfoForSet.LookMemberInfo != nil {
callbackReq.LookMemberInfo = req.GroupInfoForSet.LookMemberInfo.Value
}
if req.GroupInfoForSet.ApplyMemberFriend != nil {
callbackReq.ApplyMemberFriend = req.GroupInfoForSet.ApplyMemberFriend.Value
}
resp := &callbackstruct.CallbackBeforeSetGroupInfoResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, callbackReq, resp, config.Config.Callback.CallbackBeforeSetGroupInfo); err != nil {
return err
}
if resp.Ex != nil {
req.GroupInfoForSet.Ex = wrapperspb.String(*resp.Ex)
}
if resp.NeedVerification != nil {
req.GroupInfoForSet.NeedVerification = wrapperspb.Int32(*resp.NeedVerification)
}
if resp.LookMemberInfo != nil {
req.GroupInfoForSet.LookMemberInfo = wrapperspb.Int32(*resp.LookMemberInfo)
}
if resp.ApplyMemberFriend != nil {
req.GroupInfoForSet.ApplyMemberFriend = wrapperspb.Int32(*resp.ApplyMemberFriend)
}
utils.StructFieldNotNilReplace(req, resp)
return nil
}
func CallbackAfterSetGroupInfo(ctx context.Context, req *group.SetGroupInfoReq) error {
if !config.Config.Callback.CallbackAfterSetGroupInfo.Enable {
return nil
}
callbackReq := &callbackstruct.CallbackAfterSetGroupInfoReq{
CallbackCommand: callbackstruct.CallbackAfterSetGroupInfoCommand,
GroupID: req.GroupInfoForSet.GroupID,
Notification: req.GroupInfoForSet.Notification,
Introduction: req.GroupInfoForSet.Introduction,
FaceURL: req.GroupInfoForSet.FaceURL,
GroupName: req.GroupInfoForSet.GroupName,
}
if req.GroupInfoForSet.Ex != nil {
callbackReq.Ex = &req.GroupInfoForSet.Ex.Value
}
if req.GroupInfoForSet.NeedVerification != nil {
callbackReq.NeedVerification = &req.GroupInfoForSet.NeedVerification.Value
}
if req.GroupInfoForSet.LookMemberInfo != nil {
callbackReq.LookMemberInfo = &req.GroupInfoForSet.LookMemberInfo.Value
}
if req.GroupInfoForSet.ApplyMemberFriend != nil {
callbackReq.ApplyMemberFriend = &req.GroupInfoForSet.ApplyMemberFriend.Value
}
resp := &callbackstruct.CallbackAfterSetGroupInfoResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, callbackReq, resp, config.Config.Callback.CallbackAfterSetGroupInfo); err != nil {
return err
}
utils.StructFieldNotNilReplace(req, resp)
return nil
}
+75
View File
@@ -26,6 +26,8 @@ import (
"strings"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
@@ -225,6 +227,7 @@ func (s *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR
if len(userMap) != len(userIDs) {
return nil, errs.ErrUserIDNotFound.Wrap("user not found")
}
// Callback Before create Group
if err := CallbackBeforeCreateGroup(ctx, req); err != nil {
return nil, err
}
@@ -298,6 +301,17 @@ func (s *groupServer) CreateGroup(ctx context.Context, req *pbgroup.CreateGroupR
}
s.Notification.GroupCreatedNotification(ctx, tips)
}
reqCallBackAfter := &pbgroup.CreateGroupReq{
MemberUserIDs: userIDs,
GroupInfo: resp.GroupInfo,
OwnerUserID: req.OwnerUserID,
AdminUserIDs: req.AdminUserIDs,
}
if err := CallbackAfterCreateGroup(ctx, reqCallBackAfter); err != nil {
return nil, err
}
return resp, nil
}
@@ -352,6 +366,7 @@ func (s *groupServer) GetJoinedGroupList(ctx context.Context, req *pbgroup.GetJo
func (s *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.InviteUserToGroupReq) (*pbgroup.InviteUserToGroupResp, error) {
resp := &pbgroup.InviteUserToGroupResp{}
if len(req.InvitedUserIDs) == 0 {
return nil, errs.ErrArgs.Wrap("user empty")
}
@@ -362,6 +377,7 @@ func (s *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
if err != nil {
return nil, err
}
if group.Status == constant.GroupStatusDismissed {
return nil, errs.ErrDismissedAlready.Wrap()
}
@@ -385,6 +401,10 @@ func (s *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
}
groupMember = groupMembers[0]
}
if err := CallbackBeforeInviteUserToGroup(ctx, req); err != nil {
return nil, err
}
if group.NeedVerification == constant.AllNeedVerification {
if !authverify.IsAppManagerUid(ctx) {
if !(groupMember.RoleLevel == constant.GroupOwner || groupMember.RoleLevel == constant.GroupAdmin) {
@@ -399,6 +419,7 @@ func (s *groupServer) InviteUserToGroup(ctx context.Context, req *pbgroup.Invite
HandledTime: time.Unix(0, 0),
})
}
if err := s.GroupDatabase.CreateGroupRequest(ctx, requests); err != nil {
return nil, err
}
@@ -606,6 +627,10 @@ func (s *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
if err := s.deleteMemberAndSetConversationSeq(ctx, req.GroupID, req.KickedUserIDs); err != nil {
return nil, err
}
if err := CallbackKillGroupMember(ctx, req); err != nil {
return nil, err
}
return resp, nil
}
@@ -800,6 +825,7 @@ func (s *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup
case constant.GroupResponseRefuse:
s.Notification.GroupApplicationRejectedNotification(ctx, req)
}
return &pbgroup.GroupApplicationResponseResp{}, nil
}
@@ -816,6 +842,17 @@ func (s *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq)
if group.Status == constant.GroupStatusDismissed {
return nil, errs.ErrDismissedAlready.Wrap()
}
reqCall := &callbackstruct.CallbackJoinGroupReq{
GroupID: req.GroupID,
GroupType: string(group.GroupType),
ApplyID: req.InviterUserID,
ReqMessage: req.ReqMessage,
}
if err = CallbackApplyJoinGroupBefore(ctx, reqCall); err != nil {
return nil, err
}
_, err = s.GroupDatabase.TakeGroupMember(ctx, req.GroupID, req.InviterUserID)
if err == nil {
return nil, errs.ErrArgs.Wrap("already in group")
@@ -843,10 +880,14 @@ func (s *groupServer) JoinGroup(ctx context.Context, req *pbgroup.JoinGroupReq)
if err := s.GroupDatabase.CreateGroup(ctx, nil, []*relationtb.GroupMemberModel{groupMember}); err != nil {
return nil, err
}
if err := s.conversationRpcClient.GroupChatFirstCreateConversation(ctx, req.GroupID, []string{req.InviterUserID}); err != nil {
return nil, err
}
s.Notification.MemberEnterNotification(ctx, req.GroupID, req.InviterUserID)
if err = CallbackAfterJoinGroup(ctx, req); err != nil {
return nil, err
}
return resp, nil
}
groupRequest := relationtb.GroupRequestModel{
@@ -900,6 +941,10 @@ func (s *groupServer) QuitGroup(ctx context.Context, req *pbgroup.QuitGroupReq)
return nil, err
}
// callback
if err := CallbackQuitGroup(ctx, req); err != nil {
return nil, err
}
return resp, nil
}
@@ -924,6 +969,9 @@ func (s *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
return nil, errs.ErrNoPermission.Wrap("no group owner or admin")
}
}
if err := CallbackBeforeSetGroupInfo(ctx, req); err != nil {
return nil, err
}
group, err := s.GroupDatabase.TakeGroup(ctx, req.GroupInfoForSet.GroupID)
if err != nil {
return nil, err
@@ -992,6 +1040,9 @@ func (s *groupServer) SetGroupInfo(ctx context.Context, req *pbgroup.SetGroupInf
default:
s.Notification.GroupInfoSetNotification(ctx, tips)
}
if err := CallbackAfterSetGroupInfo(ctx, req); err != nil {
return nil, err
}
return resp, nil
}
@@ -1031,6 +1082,10 @@ func (s *groupServer) TransferGroupOwner(ctx context.Context, req *pbgroup.Trans
if err := s.GroupDatabase.TransferGroupOwner(ctx, req.GroupID, req.OldOwnerUserID, req.NewOwnerUserID, newOwner.RoleLevel); err != nil {
return nil, err
}
if err := CallbackTransferGroupOwnerAfter(ctx, req); err != nil {
return nil, err
}
s.Notification.GroupOwnerTransferredNotification(ctx, req)
return resp, nil
}
@@ -1201,6 +1256,20 @@ func (s *groupServer) DismissGroup(ctx context.Context, req *pbgroup.DismissGrou
s.Notification.GroupDismissedNotification(ctx, tips)
}
}
membersID, err := s.GroupDatabase.FindGroupMemberUserID(ctx, group.GroupID)
if err != nil {
return nil, err
}
reqCall := &callbackstruct.CallbackDisMissGroupReq{
GroupID: req.GroupID,
OwnerID: owner.UserID,
MembersID: membersID,
GroupType: string(group.GroupType),
}
if err := CallbackDismissGroup(ctx, reqCall); err != nil {
return nil, err
}
return resp, nil
}
@@ -1439,6 +1508,12 @@ func (s *groupServer) SetGroupMemberInfo(ctx context.Context, req *pbgroup.SetGr
}
}
}
for i := 0; i < len(req.Members); i++ {
if err := CallbackAfterSetGroupMemberInfo(ctx, req.Members[i]); err != nil {
return nil, err
}
}
return resp, nil
}
+51 -46
View File
@@ -16,10 +16,14 @@ package msg
import (
"context"
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
utils2 "github.com/OpenIMSDK/tools/utils"
"github.com/redis/go-redis/v9"
"github.com/OpenIMSDK/protocol/constant"
"github.com/OpenIMSDK/protocol/conversation"
"github.com/OpenIMSDK/protocol/msg"
"github.com/OpenIMSDK/protocol/sdkws"
"github.com/OpenIMSDK/tools/errs"
@@ -88,10 +92,7 @@ func (m *msgServer) SetConversationHasReadSeq(
return &msg.SetConversationHasReadSeqResp{}, nil
}
func (m *msgServer) MarkMsgsAsRead(
ctx context.Context,
req *msg.MarkMsgsAsReadReq,
) (resp *msg.MarkMsgsAsReadResp, err error) {
func (m *msgServer) MarkMsgsAsRead(ctx context.Context, req *msg.MarkMsgsAsReadReq) (resp *msg.MarkMsgsAsReadResp, err error) {
if len(req.Seqs) < 1 {
return nil, errs.ErrArgs.Wrap("seqs must not be empty")
}
@@ -127,10 +128,7 @@ func (m *msgServer) MarkMsgsAsRead(
return &msg.MarkMsgsAsReadResp{}, nil
}
func (m *msgServer) MarkConversationAsRead(
ctx context.Context,
req *msg.MarkConversationAsReadReq,
) (resp *msg.MarkConversationAsReadResp, err error) {
func (m *msgServer) MarkConversationAsRead(ctx context.Context, req *msg.MarkConversationAsReadReq) (resp *msg.MarkConversationAsReadResp, err error) {
conversation, err := m.Conversation.GetConversation(ctx, req.UserID, req.ConversationID)
if err != nil {
return nil, err
@@ -139,49 +137,56 @@ func (m *msgServer) MarkConversationAsRead(
if err != nil && errs.Unwrap(err) != redis.Nil {
return nil, err
}
seqs := generateSeqs(hasReadSeq, req)
if len(seqs) > 0 || req.HasReadSeq > hasReadSeq {
err = m.updateReadStatus(ctx, req, conversation, seqs, hasReadSeq)
if err != nil {
return nil, err
}
}
return &msg.MarkConversationAsReadResp{}, nil
}
func generateSeqs(hasReadSeq int64, req *msg.MarkConversationAsReadReq) []int64 {
var seqs []int64
for _, val := range req.Seqs {
if val > hasReadSeq && !utils2.Contain(val, seqs...) {
seqs = append(seqs, val)
}
}
return seqs
}
log.ZDebug(ctx, "MarkConversationAsRead", "hasReadSeq", hasReadSeq,
"req.HasReadSeq", req.HasReadSeq)
if conversation.ConversationType == constant.SingleChatType {
for i := hasReadSeq + 1; i <= req.HasReadSeq; i++ {
seqs = append(seqs, i)
func (m *msgServer) updateReadStatus(ctx context.Context, req *msg.MarkConversationAsReadReq, conversation *conversation.Conversation, seqs []int64, hasReadSeq int64) error {
if conversation.ConversationType == constant.SingleChatType && len(seqs) > 0 {
log.ZDebug(ctx, "MarkConversationAsRead", "seqs", seqs, "conversationID", req.ConversationID)
if err := m.MsgDatabase.MarkSingleChatMsgsAsRead(ctx, req.UserID, req.ConversationID, seqs); err != nil {
return err
}
if len(seqs) > 0 {
log.ZDebug(ctx, "MarkConversationAsRead", "seqs", seqs, "conversationID", req.ConversationID)
if err = m.MsgDatabase.MarkSingleChatMsgsAsRead(ctx, req.UserID, req.ConversationID, seqs); err != nil {
return nil, err
}
}
if req.HasReadSeq > hasReadSeq {
err = m.MsgDatabase.SetHasReadSeq(ctx, req.UserID, req.ConversationID, req.HasReadSeq)
if err != nil {
return nil, err
}
hasReadSeq = req.HasReadSeq
}
if err = m.sendMarkAsReadNotification(ctx, req.ConversationID, conversation.ConversationType, req.UserID,
m.conversationAndGetRecvID(conversation, req.UserID), seqs, hasReadSeq); err != nil {
return nil, err
}
} else if conversation.ConversationType == constant.SuperGroupChatType ||
conversation.ConversationType == constant.NotificationChatType {
if req.HasReadSeq > hasReadSeq {
err = m.MsgDatabase.SetHasReadSeq(ctx, req.UserID, req.ConversationID, req.HasReadSeq)
if err != nil {
return nil, err
}
hasReadSeq = req.HasReadSeq
}
if err = m.sendMarkAsReadNotification(ctx, req.ConversationID, constant.SingleChatType, req.UserID,
req.UserID, seqs, hasReadSeq); err != nil {
return nil, err
}
}
reqCall := &cbapi.CallbackGroupMsgReadReq{
SendID: conversation.OwnerUserID,
ReceiveID: req.UserID,
UnreadMsgNum: req.HasReadSeq,
ContentType: int64(conversation.ConversationType),
}
if err := CallbackGroupMsgRead(ctx, reqCall); err != nil {
return err
}
return &msg.MarkConversationAsReadResp{}, nil
if req.HasReadSeq > hasReadSeq {
if err := m.MsgDatabase.SetHasReadSeq(ctx, req.UserID, req.ConversationID, req.HasReadSeq); err != nil {
return err
}
}
recvID := m.conversationAndGetRecvID(conversation, req.UserID)
if conversation.ConversationType == constant.SuperGroupChatType || conversation.ConversationType == constant.NotificationChatType {
recvID = req.UserID
}
return m.sendMarkAsReadNotification(ctx, req.ConversationID, conversation.ConversationType, req.UserID, recvID, seqs, req.HasReadSeq)
}
func (m *msgServer) sendMarkAsReadNotification(
+51 -25
View File
@@ -16,18 +16,16 @@ package msg
import (
"context"
"github.com/OpenIMSDK/protocol/sdkws"
"google.golang.org/protobuf/proto"
"github.com/OpenIMSDK/protocol/constant"
pbchat "github.com/OpenIMSDK/protocol/msg"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/log"
"github.com/OpenIMSDK/tools/mcontext"
"github.com/OpenIMSDK/tools/utils"
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/http"
)
@@ -74,14 +72,11 @@ func callbackBeforeSendSingleMsg(ctx context.Context, msg *pbchat.SendMsgReq) er
return nil
}
req := &cbapi.CallbackBeforeSendSingleMsgReq{
CommonCallbackReq: toCommonCallback(ctx, msg, constant.CallbackBeforeSendSingleMsgCommand),
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeSendSingleMsgCommand),
RecvID: msg.MsgData.RecvID,
}
resp := &cbapi.CallbackBeforeSendSingleMsgResp{}
if err := http.CallBackPostReturn(ctx, cbURL(), req, resp, config.Config.Callback.CallbackBeforeSendSingleMsg); err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
return nil
@@ -92,32 +87,26 @@ func callbackAfterSendSingleMsg(ctx context.Context, msg *pbchat.SendMsgReq) err
return nil
}
req := &cbapi.CallbackAfterSendSingleMsgReq{
CommonCallbackReq: toCommonCallback(ctx, msg, constant.CallbackAfterSendSingleMsgCommand),
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand),
RecvID: msg.MsgData.RecvID,
}
resp := &cbapi.CallbackAfterSendSingleMsgResp{}
if err := http.CallBackPostReturn(ctx, cbURL(), req, resp, config.Config.Callback.CallbackAfterSendSingleMsg); err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
return nil
}
func callbackBeforeSendGroupMsg(ctx context.Context, msg *pbchat.SendMsgReq) error {
if !config.Config.Callback.CallbackAfterSendSingleMsg.Enable {
if !config.Config.Callback.CallbackBeforeSendSingleMsg.Enable {
return nil
}
req := &cbapi.CallbackAfterSendGroupMsgReq{
CommonCallbackReq: toCommonCallback(ctx, msg, constant.CallbackBeforeSendGroupMsgCommand),
req := &cbapi.CallbackBeforeSendGroupMsgReq{
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackBeforeSendGroupMsgCommand),
GroupID: msg.MsgData.GroupID,
}
resp := &cbapi.CallbackBeforeSendGroupMsgResp{}
if err := http.CallBackPostReturn(ctx, cbURL(), req, resp, config.Config.Callback.CallbackBeforeSendGroupMsg); err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
return nil
@@ -128,14 +117,11 @@ func callbackAfterSendGroupMsg(ctx context.Context, msg *pbchat.SendMsgReq) erro
return nil
}
req := &cbapi.CallbackAfterSendGroupMsgReq{
CommonCallbackReq: toCommonCallback(ctx, msg, constant.CallbackAfterSendGroupMsgCommand),
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendGroupMsgCommand),
GroupID: msg.MsgData.GroupID,
}
resp := &cbapi.CallbackAfterSendGroupMsgResp{}
if err := http.CallBackPostReturn(ctx, cbURL(), req, resp, config.Config.Callback.CallbackAfterSendGroupMsg); err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
return nil
@@ -146,13 +132,10 @@ func callbackMsgModify(ctx context.Context, msg *pbchat.SendMsgReq) error {
return nil
}
req := &cbapi.CallbackMsgModifyCommandReq{
CommonCallbackReq: toCommonCallback(ctx, msg, constant.CallbackMsgModifyCommand),
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackMsgModifyCommand),
}
resp := &cbapi.CallbackMsgModifyCommandResp{}
if err := http.CallBackPostReturn(ctx, cbURL(), req, resp, config.Config.Callback.CallbackMsgModify); err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
if resp.Content != nil {
@@ -177,3 +160,46 @@ func callbackMsgModify(ctx context.Context, msg *pbchat.SendMsgReq) error {
log.ZDebug(ctx, "callbackMsgModify", "msg", msg.MsgData)
return nil
}
func CallbackGroupMsgRead(ctx context.Context, req *cbapi.CallbackGroupMsgReadReq) error {
if !config.Config.Callback.CallbackGroupMsgRead.Enable || req.ContentType != constant.Text {
return nil
}
req.CallbackCommand = cbapi.CallbackGroupMsgReadCommand
resp := &cbapi.CallbackGroupMsgReadResp{}
if err := http.CallBackPostReturn(ctx, cbURL(), req, resp, config.Config.Callback.CallbackMsgModify); err != nil {
return err
}
return nil
}
func CallbackSingleMsgRead(ctx context.Context, req *cbapi.CallbackSingleMsgReadReq) error {
if !config.Config.Callback.CallbackSingleMsgRead.Enable || req.ContentType != constant.Text {
return nil
}
req.CallbackCommand = cbapi.CallbackSingleMsgRead
resp := &cbapi.CallbackSingleMsgReadResp{}
if err := http.CallBackPostReturn(ctx, cbURL(), req, resp, config.Config.Callback.CallbackMsgModify); err != nil {
return err
}
return nil
}
func CallbackAfterRevokeMsg(ctx context.Context, req *pbchat.RevokeMsgReq) error {
if !config.Config.Callback.CallbackAfterRevokeMsg.Enable {
return nil
}
callbackReq := &cbapi.CallbackAfterRevokeMsgReq{
CallbackCommand: cbapi.CallbackAfterRevokeMsgCommand,
ConversationID: req.ConversationID,
Seq: req.Seq,
UserID: req.UserID,
}
resp := &cbapi.CallbackAfterRevokeMsgResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, callbackReq, resp, config.Config.Callback.CallbackAfterRevokeMsg); err != nil {
return err
}
utils.StructFieldNotNilReplace(req, resp)
return nil
}
+4
View File
@@ -61,6 +61,7 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg.
if msgs[0].ContentType == constant.MsgRevokeNotification {
return nil, errs.ErrMsgAlreadyRevoke.Wrap("msg already revoke")
}
data, _ := json.Marshal(msgs[0])
log.ZInfo(ctx, "GetMsgBySeqs", "conversationID", req.ConversationID, "seq", req.Seq, "msg", string(data))
var role int32
@@ -128,5 +129,8 @@ func (m *msgServer) RevokeMsg(ctx context.Context, req *msg.RevokeMsgReq) (*msg.
if err := m.notificationSender.NotificationWithSesstionType(ctx, req.UserID, recvID, constant.MsgRevokeNotification, msgs[0].SessionType, &tips); err != nil {
return nil, err
}
if err = CallbackAfterRevokeMsg(ctx, req); err != nil {
return nil, err
}
return &msg.RevokeMsgResp{}, nil
}
+2 -10
View File
@@ -42,15 +42,8 @@ func (m *msgServer) PullMessageBySeqs(
log.ZError(ctx, "GetConversation error", err, "conversationID", seq.ConversationID)
continue
}
minSeq, maxSeq, msgs, err := m.MsgDatabase.GetMsgBySeqsRange(
ctx,
req.UserID,
seq.ConversationID,
seq.Begin,
seq.End,
seq.Num,
conversation.MaxSeq,
)
minSeq, maxSeq, msgs, err := m.MsgDatabase.GetMsgBySeqsRange(ctx, req.UserID, seq.ConversationID,
seq.Begin, seq.End, seq.Num, conversation.MaxSeq)
if err != nil {
log.ZWarn(ctx, "GetMsgBySeqsRange error", err, "conversationID", seq.ConversationID, "seq", seq)
continue
@@ -64,7 +57,6 @@ func (m *msgServer) PullMessageBySeqs(
}
if len(msgs) == 0 {
log.ZWarn(ctx, "not have msgs", nil, "conversationID", seq.ConversationID, "seq", seq)
continue
}
resp.Msgs[seq.ConversationID] = &sdkws.PullMsgs{Msgs: msgs, IsEnd: isEnd}
+4 -1
View File
@@ -17,14 +17,15 @@ package third
import (
"context"
"fmt"
"net/url"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/s3"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/s3/cos"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/s3/kodo"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/s3/minio"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/s3/oss"
"google.golang.org/grpc"
"github.com/OpenIMSDK/protocol/third"
@@ -72,6 +73,8 @@ func Start(client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) e
o, err = cos.NewCos()
case "oss":
o, err = oss.NewOSS()
case "kodo":
o, err = kodo.NewKodo()
default:
err = fmt.Errorf("invalid object enable: %s", enable)
}
+55 -9
View File
@@ -16,11 +16,7 @@ package user
import (
"context"
"github.com/OpenIMSDK/protocol/constant"
pbuser "github.com/OpenIMSDK/protocol/user"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/mcontext"
"github.com/OpenIMSDK/tools/utils"
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
@@ -33,17 +29,13 @@ func CallbackBeforeUpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInf
return nil
}
cbReq := &cbapi.CallbackBeforeUpdateUserInfoReq{
CallbackCommand: constant.CallbackBeforeUpdateUserInfoCommand,
OperationID: mcontext.GetOperationID(ctx),
CallbackCommand: cbapi.CallbackBeforeUpdateUserInfoCommand,
UserID: req.UserInfo.UserID,
FaceURL: &req.UserInfo.FaceURL,
Nickname: &req.UserInfo.Nickname,
}
resp := &cbapi.CallbackBeforeUpdateUserInfoResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeUpdateUserInfo); err != nil {
if err == errs.ErrCallbackContinue {
return nil
}
return err
}
utils.NotNilReplace(&req.UserInfo.FaceURL, resp.FaceURL)
@@ -51,3 +43,57 @@ func CallbackBeforeUpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInf
utils.NotNilReplace(&req.UserInfo.Nickname, resp.Nickname)
return nil
}
func CallbackAfterUpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInfoReq) error {
if !config.Config.Callback.CallbackAfterUpdateUserInfo.Enable {
return nil
}
cbReq := &cbapi.CallbackAfterUpdateUserInfoReq{
CallbackCommand: cbapi.CallbackAfterUpdateUserInfoCommand,
UserID: req.UserInfo.UserID,
FaceURL: req.UserInfo.FaceURL,
Nickname: req.UserInfo.Nickname,
}
resp := &cbapi.CallbackAfterUpdateUserInfoResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeUpdateUserInfo); err != nil {
return err
}
return nil
}
func CallbackBeforeUserRegister(ctx context.Context, req *pbuser.UserRegisterReq) error {
if !config.Config.Callback.CallbackBeforeUserRegister.Enable {
return nil
}
cbReq := &cbapi.CallbackBeforeUserRegisterReq{
CallbackCommand: cbapi.CallbackBeforeUserRegisterCommand,
Secret: req.Secret,
Users: req.Users,
}
resp := &cbapi.CallbackBeforeUserRegisterResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeUpdateUserInfo); err != nil {
return err
}
if len(resp.Users) != 0 {
req.Users = resp.Users
}
return nil
}
func CallbackAfterUserRegister(ctx context.Context, req *pbuser.UserRegisterReq) error {
if !config.Config.Callback.CallbackAfterUserRegister.Enable {
return nil
}
cbReq := &cbapi.CallbackAfterUserRegisterReq{
CallbackCommand: cbapi.CallbackAfterUserRegisterCommand,
Secret: req.Secret,
Users: req.Users,
}
resp := &cbapi.CallbackAfterUserRegisterResp{}
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackAfterUpdateUserInfo); err != nil {
return err
}
return nil
}
+10
View File
@@ -139,6 +139,9 @@ func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserI
for _, friendID := range friends {
s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID)
}
if err := CallbackAfterUpdateUserInfo(ctx, req); err != nil {
return nil, err
}
if err := s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil {
log.ZError(ctx, "NotificationUserInfoUpdate", err, "userID", req.UserInfo.UserID)
}
@@ -230,6 +233,9 @@ func (s *userServer) UserRegister(ctx context.Context, req *pbuser.UserRegisterR
if exist {
return nil, errs.ErrRegisteredAlready.Wrap("userID registered already")
}
if err := CallbackBeforeUserRegister(ctx, req); err != nil {
return nil, err
}
now := time.Now()
users := make([]*tablerelation.UserModel, 0, len(req.Users))
for _, user := range req.Users {
@@ -246,6 +252,10 @@ func (s *userServer) UserRegister(ctx context.Context, req *pbuser.UserRegisterR
if err := s.Create(ctx, users); err != nil {
return nil, err
}
if err := CallbackAfterUserRegister(ctx, req); err != nil {
return nil, err
}
return resp, nil
}
+66 -9
View File
@@ -17,13 +17,18 @@ package tools
import (
"context"
"fmt"
"sync"
"os"
"os/signal"
"syscall"
"time"
"github.com/redis/go-redis/v9"
"github.com/robfig/cron/v3"
"github.com/OpenIMSDK/tools/log"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
)
func StartTask() error {
@@ -32,23 +37,75 @@ func StartTask() error {
if err != nil {
return err
}
msgTool.ConvertTools()
c := cron.New()
var wg sync.WaitGroup
wg.Add(1)
msgTool.convertTools()
rdb, err := cache.NewRedis()
if err != nil {
return err
}
// register cron tasks
var crontab = cron.New()
log.ZInfo(context.Background(), "start chatRecordsClearTime cron task", "cron config", config.Config.ChatRecordsClearTime)
_, err = c.AddFunc(config.Config.ChatRecordsClearTime, msgTool.AllConversationClearMsgAndFixSeq)
_, err = crontab.AddFunc(config.Config.ChatRecordsClearTime, cronWrapFunc(rdb, "cron_clear_msg_and_fix_seq", msgTool.AllConversationClearMsgAndFixSeq))
if err != nil {
log.ZError(context.Background(), "start allConversationClearMsgAndFixSeq cron failed", err)
panic(err)
}
log.ZInfo(context.Background(), "start msgDestruct cron task", "cron config", config.Config.MsgDestructTime)
_, err = c.AddFunc(config.Config.MsgDestructTime, msgTool.ConversationsDestructMsgs)
_, err = crontab.AddFunc(config.Config.MsgDestructTime, cronWrapFunc(rdb, "cron_conversations_destruct_msgs", msgTool.ConversationsDestructMsgs))
if err != nil {
log.ZError(context.Background(), "start conversationsDestructMsgs cron failed", err)
panic(err)
}
c.Start()
wg.Wait()
// start crontab
crontab.Start()
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
<-sigs
// stop crontab, Wait for the running task to exit.
ctx := crontab.Stop()
select {
case <-ctx.Done():
// graceful exit
case <-time.After(15 * time.Second):
// forced exit on timeout
}
return nil
}
// netlock redis lock.
func netlock(rdb redis.UniversalClient, key string, ttl time.Duration) bool {
value := "used"
ok, err := rdb.SetNX(context.Background(), key, value, ttl).Result() // nolint
if err != nil {
// when err is about redis server, return true.
return false
}
return ok
}
func cronWrapFunc(rdb redis.UniversalClient, key string, fn func()) func() {
enableCronLocker := config.Config.EnableCronLocker
return func() {
// if don't enable cron-locker, call fn directly.
if !enableCronLocker {
fn()
return
}
// when acquire redis lock, call fn().
if netlock(rdb, key, 5*time.Second) {
fn()
}
}
}
+82
View File
@@ -0,0 +1,82 @@
package tools
import (
"fmt"
"math/rand"
"sync"
"testing"
"time"
"github.com/redis/go-redis/v9"
"github.com/robfig/cron/v3"
"github.com/stretchr/testify/assert"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
)
func TestDisLock(t *testing.T) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
assert.Equal(t, true, netlock(rdb, "cron-1", 1*time.Second))
// if exists, get false
assert.Equal(t, false, netlock(rdb, "cron-1", 1*time.Second))
time.Sleep(2 * time.Second)
// wait for key on timeout, get true
assert.Equal(t, true, netlock(rdb, "cron-1", 2*time.Second))
// set different key
assert.Equal(t, true, netlock(rdb, "cron-2", 2*time.Second))
}
func TestCronWrapFunc(t *testing.T) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
once := sync.Once{}
done := make(chan struct{}, 1)
cb := func() {
once.Do(func() {
close(done)
})
}
start := time.Now()
key := fmt.Sprintf("cron-%v", rand.Int31())
crontab := cron.New(cron.WithSeconds())
crontab.AddFunc("*/1 * * * * *", cronWrapFunc(rdb, key, cb))
crontab.Start()
<-done
dur := time.Since(start)
assert.LessOrEqual(t, dur.Seconds(), float64(2*time.Second))
crontab.Stop()
}
func TestCronWrapFuncWithNetlock(t *testing.T) {
config.Config.EnableCronLocker = true
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
done := make(chan string, 10)
crontab := cron.New(cron.WithSeconds())
key := fmt.Sprintf("cron-%v", rand.Int31())
crontab.AddFunc("*/1 * * * * *", cronWrapFunc(rdb, key, func() {
done <- "host1"
}))
crontab.AddFunc("*/1 * * * * *", cronWrapFunc(rdb, key, func() {
done <- "host2"
}))
crontab.Start()
time.Sleep(12 * time.Second)
// the ttl of netlock is 5s, so expected value is 2.
assert.Equal(t, len(done), 2)
crontab.Stop()
}
+1 -1
View File
@@ -22,7 +22,7 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
)
func (c *MsgTool) ConvertTools() {
func (c *MsgTool) convertTools() {
ctx := mcontext.NewCtx("convert")
conversationIDs, err := c.conversationDatabase.GetAllConversationIDs(ctx)
if err != nil {
+17 -17
View File
@@ -22,37 +22,37 @@ import (
type SendMsg struct {
// SendID uniquely identifies the sender.
SendID string `json:"sendID" binding:"required"`
// GroupID is the identifier for the group, required if SessionType is 2 or 3.
GroupID string `json:"groupID" binding:"required_if=SessionType 2|required_if=SessionType 3"`
// SenderNickname is the nickname of the sender.
SenderNickname string `json:"senderNickname"`
// SenderFaceURL is the URL to the sender's avatar.
SenderFaceURL string `json:"senderFaceURL"`
// SenderPlatformID is an integer identifier for the sender's platform.
SenderPlatformID int32 `json:"senderPlatformID"`
// Content is the actual content of the message, required and excluded from Swagger documentation.
Content map[string]interface{} `json:"content" binding:"required" swaggerignore:"true"`
// ContentType is an integer that represents the type of the content.
ContentType int32 `json:"contentType" binding:"required"`
// SessionType is an integer that represents the type of session for the message.
SessionType int32 `json:"sessionType" binding:"required"`
// IsOnlineOnly specifies if the message is only sent when the receiver is online.
IsOnlineOnly bool `json:"isOnlineOnly"`
// NotOfflinePush specifies if the message should not trigger offline push notifications.
NotOfflinePush bool `json:"notOfflinePush"`
// SendTime is a timestamp indicating when the message was sent.
SendTime int64 `json:"sendTime"`
// OfflinePushInfo contains information for offline push notifications.
OfflinePushInfo *sdkws.OfflinePushInfo `json:"offlinePushInfo"`
}
@@ -67,10 +67,10 @@ type SendMsgReq struct {
// BatchSendMsgReq defines the structure for sending a message to multiple recipients.
type BatchSendMsgReq struct {
SendMsg
// IsSendAll indicates whether the message should be sent to all users.
IsSendAll bool `json:"isSendAll"`
// RecvIDs is a slice of receiver identifiers to whom the message will be sent, required field.
RecvIDs []string `json:"recvIDs" binding:"required"`
}
@@ -79,7 +79,7 @@ type BatchSendMsgReq struct {
type BatchSendMsgResp struct {
// Results is a slice of SingleReturnResult, representing the outcome of each message sent.
Results []*SingleReturnResult `json:"results"`
// FailedIDs is a slice of user IDs for whom the message send failed.
FailedIDs []string `json:"failedUserIDs"`
}
@@ -88,13 +88,13 @@ type BatchSendMsgResp struct {
type SingleReturnResult struct {
// ServerMsgID is the message identifier on the server-side.
ServerMsgID string `json:"serverMsgID"`
// ClientMsgID is the message identifier on the client-side.
ClientMsgID string `json:"clientMsgID"`
// SendTime is the timestamp of when the message was sent.
SendTime int64 `json:"sendTime"`
// RecvID uniquely identifies the receiver of the message.
RecvID string `json:"recvID"`
}
+37 -36
View File
@@ -16,56 +16,56 @@ package apistruct
type PictureBaseInfo struct {
UUID string `mapstructure:"uuid"`
Type string `mapstructure:"type"`
Type string `mapstructure:"type" validate:"required"`
Size int64 `mapstructure:"size"`
Width int32 `mapstructure:"width"`
Height int32 `mapstructure:"height"`
Url string `mapstructure:"url"`
Width int32 `mapstructure:"width" validate:"required"`
Height int32 `mapstructure:"height" validate:"required"`
Url string `mapstructure:"url" validate:"required"`
}
type PictureElem struct {
SourcePath string `mapstructure:"sourcePath"`
SourcePicture PictureBaseInfo `mapstructure:"sourcePicture"`
BigPicture PictureBaseInfo `mapstructure:"bigPicture"`
SnapshotPicture PictureBaseInfo `mapstructure:"snapshotPicture"`
SourcePicture PictureBaseInfo `mapstructure:"sourcePicture" validate:"required"`
BigPicture PictureBaseInfo `mapstructure:"bigPicture" validate:"required"`
SnapshotPicture PictureBaseInfo `mapstructure:"snapshotPicture" validate:"required"`
}
type SoundElem struct {
UUID string `mapstructure:"uuid"`
SoundPath string `mapstructure:"soundPath"`
SourceURL string `mapstructure:"sourceUrl"`
SourceURL string `mapstructure:"sourceUrl" validate:"required"`
DataSize int64 `mapstructure:"dataSize"`
Duration int64 `mapstructure:"duration"`
Duration int64 `mapstructure:"duration" validate:"required,min=1"`
}
type VideoElem struct {
VideoPath string `mapstructure:"videoPath"`
VideoPath string `mapstructure:"videoPath" `
VideoUUID string `mapstructure:"videoUUID"`
VideoURL string `mapstructure:"videoUrl"`
VideoType string `mapstructure:"videoType"`
VideoSize int64 `mapstructure:"videoSize"`
Duration int64 `mapstructure:"duration"`
VideoURL string `mapstructure:"videoUrl" validate:"required"`
VideoType string `mapstructure:"videoType" validate:"required"`
VideoSize int64 `mapstructure:"videoSize" validate:"required"`
Duration int64 `mapstructure:"duration" validate:"required"`
SnapshotPath string `mapstructure:"snapshotPath"`
SnapshotUUID string `mapstructure:"snapshotUUID"`
SnapshotSize int64 `mapstructure:"snapshotSize"`
SnapshotURL string `mapstructure:"snapshotUrl"`
SnapshotWidth int32 `mapstructure:"snapshotWidth"`
SnapshotHeight int32 `mapstructure:"snapshotHeight"`
SnapshotURL string `mapstructure:"snapshotUrl" validate:"required"`
SnapshotWidth int32 `mapstructure:"snapshotWidth" validate:"required"`
SnapshotHeight int32 `mapstructure:"snapshotHeight" validate:"required"`
}
type FileElem struct {
FilePath string `mapstructure:"filePath"`
FilePath string `mapstructure:"filePath" `
UUID string `mapstructure:"uuid"`
SourceURL string `mapstructure:"sourceUrl"`
FileName string `mapstructure:"fileName"`
FileSize int64 `mapstructure:"fileSize"`
SourceURL string `mapstructure:"sourceUrl" validate:"required"`
FileName string `mapstructure:"fileName" validate:"required"`
FileSize int64 `mapstructure:"fileSize" validate:"required"`
}
type AtElem struct {
Text string `mapstructure:"text"`
AtUserList []string `mapstructure:"atUserList"`
AtUserList []string `mapstructure:"atUserList" validate:"required,max=1000"`
IsAtSelf bool `mapstructure:"isAtSelf"`
}
type LocationElem struct {
Description string `mapstructure:"description"`
Longitude float64 `mapstructure:"longitude"`
Latitude float64 `mapstructure:"latitude"`
Description string `mapstructure:"description" `
Longitude float64 `mapstructure:"longitude" validate:"required"`
Latitude float64 `mapstructure:"latitude" validate:"required"`
}
type CustomElem struct {
Data string `mapstructure:"data" validate:"required"`
@@ -80,18 +80,19 @@ type TextElem struct {
type RevokeElem struct {
RevokeMsgClientID string `mapstructure:"revokeMsgClientID" validate:"required"`
}
type OANotificationElem struct {
NotificationName string `mapstructure:"notificationName" json:"notificationName" validate:"required"`
NotificationFaceURL string `mapstructure:"notificationFaceURL" json:"notificationFaceURL"`
NotificationType int32 `mapstructure:"notificationType" json:"notificationType" validate:"required"`
Text string `mapstructure:"text" json:"text" validate:"required"`
Url string `mapstructure:"url" json:"url"`
MixType int32 `mapstructure:"mixType" json:"mixType"`
PictureElem PictureElem `mapstructure:"pictureElem" json:"pictureElem"`
SoundElem SoundElem `mapstructure:"soundElem" json:"soundElem"`
VideoElem VideoElem `mapstructure:"videoElem" json:"videoElem"`
FileElem FileElem `mapstructure:"fileElem" json:"fileElem"`
Ex string `mapstructure:"ex" json:"ex"`
NotificationName string `mapstructure:"notificationName" json:"notificationName" validate:"required"`
NotificationFaceURL string `mapstructure:"notificationFaceURL" json:"notificationFaceURL"`
NotificationType int32 `mapstructure:"notificationType" json:"notificationType" validate:"required"`
Text string `mapstructure:"text" json:"text" validate:"required"`
Url string `mapstructure:"url" json:"url"`
MixType int32 `mapstructure:"mixType" json:"mixType" validate:"required"`
PictureElem *PictureElem `mapstructure:"pictureElem" json:"pictureElem"`
SoundElem *SoundElem `mapstructure:"soundElem" json:"soundElem"`
VideoElem *VideoElem `mapstructure:"videoElem" json:"videoElem"`
FileElem *FileElem `mapstructure:"fileElem" json:"fileElem"`
Ex string `mapstructure:"ex" json:"ex"`
}
type MessageRevoked struct {
RevokerID string `mapstructure:"revokerID" json:"revokerID" validate:"required"`
+7 -4
View File
@@ -14,8 +14,10 @@
package callbackstruct
import (
"github.com/OpenIMSDK/tools/errs"
import "github.com/OpenIMSDK/tools/errs"
const (
Next = 1
)
type CommonCallbackReq struct {
@@ -51,14 +53,15 @@ type CallbackResp interface {
}
type CommonCallbackResp struct {
ActionCode int `json:"actionCode"`
ActionCode int32 `json:"actionCode"`
ErrCode int32 `json:"errCode"`
ErrMsg string `json:"errMsg"`
ErrDlt string `json:"errDlt"`
NextCode int32 `json:"nextCode"`
}
func (c CommonCallbackResp) Parse() error {
if c.ActionCode != errs.NoError || c.ErrCode != errs.NoError {
if c.ActionCode != errs.NoError || c.NextCode == Next {
return errs.NewCodeError(int(c.ErrCode), c.ErrMsg).WithDetail(c.ErrDlt)
}
return nil
+49
View File
@@ -0,0 +1,49 @@
package callbackstruct
const CallbackBeforeInviteJoinGroupCommand = "callbackBeforeInviteJoinGroupCommand"
const CallbackAfterJoinGroupCommand = "callbackAfterJoinGroupCommand"
const CallbackAfterSetGroupInfoCommand = "callbackAfterSetGroupInfoCommand"
const CallbackBeforeSetGroupInfoCommand = "callbackBeforeSetGroupInfoCommand"
const CallbackAfterRevokeMsgCommand = "callbackBeforeAfterMsgCommand"
const CallbackBeforeAddBlackCommand = "callbackBeforeAddBlackCommand"
const CallbackAfterAddFriendCommand = "callbackAfterAddFriendCommand"
const CallbackBeforeAddFriendAgreeCommand = "callbackBeforeAddFriendAgreeCommand"
const CallbackAfterDeleteFriendCommand = "callbackAfterDeleteFriendCommand"
const CallbackBeforeImportFriendsCommand = "callbackBeforeImportFriendsCommand"
const CallbackAfterImportFriendsCommand = "callbackAfterImportFriendsCommand"
const CallbackAfterRemoveBlackCommand = "callbackAfterRemoveBlackCommand"
const (
CallbackQuitGroupCommand = "callbackQuitGroupCommand"
CallbackKillGroupCommand = "callbackKillGroupCommand"
CallbackDisMissGroupCommand = "callbackDisMissGroupCommand"
CallbackBeforeJoinGroupCommand = "callbackBeforeJoinGroupCommand"
CallbackGroupMsgReadCommand = "callbackGroupMsgReadCommand"
CallbackMsgModifyCommand = "callbackMsgModifyCommand"
CallbackAfterUpdateUserInfoCommand = "callbackAfterUpdateUserInfoCommand"
CallbackBeforeUserRegisterCommand = "callbackBeforeUserRegisterCommand"
CallbackAfterUserRegisterCommand = "callbackAfterUserRegisterCommand"
CallbackTransferGroupOwnerAfter = "callbackTransferGroupOwnerAfter"
CallbackBeforeSetFriendRemark = "callbackBeforeSetFriendRemark"
CallbackAfterSetFriendRemark = "callbackAfterSetFriendRemark"
CallbackSingleMsgRead = "callbackSingleMsgRead"
CallbackBeforeSendSingleMsgCommand = "callbackBeforeSendSingleMsgCommand"
CallbackAfterSendSingleMsgCommand = "callbackAfterSendSingleMsgCommand"
CallbackBeforeSendGroupMsgCommand = "callbackBeforeSendGroupMsgCommand"
CallbackAfterSendGroupMsgCommand = "callbackAfterSendGroupMsgCommand"
CallbackUserOnlineCommand = "callbackUserOnlineCommand"
CallbackUserOfflineCommand = "callbackUserOfflineCommand"
CallbackUserKickOffCommand = "callbackUserKickOffCommand"
CallbackOfflinePushCommand = "callbackOfflinePushCommand"
CallbackOnlinePushCommand = "callbackOnlinePushCommand"
CallbackSuperGroupOnlinePushCommand = "callbackSuperGroupOnlinePushCommand"
CallbackBeforeAddFriendCommand = "callbackBeforeAddFriendCommand"
CallbackBeforeUpdateUserInfoCommand = "callbackBeforeUpdateUserInfoCommand"
CallbackBeforeCreateGroupCommand = "callbackBeforeCreateGroupCommand"
CallbackAfterCreateGroupCommand = "callbackAfterCreateGroupCommand"
CallbackBeforeMemberJoinGroupCommand = "callbackBeforeMemberJoinGroupCommand"
CallbackBeforeSetGroupMemberInfoCommand = "callbackBeforeSetGroupMemberInfoCommand"
CallbackAfterSetGroupMemberInfoCommand = "callbackAfterSetGroupMemberInfoCommand"
)
+101 -1
View File
@@ -19,9 +19,109 @@ type CallbackBeforeAddFriendReq struct {
FromUserID string `json:"fromUserID" `
ToUserID string `json:"toUserID"`
ReqMsg string `json:"reqMsg"`
OperationID string `json:"operationID"`
Ex string `json:"ex"`
}
type CallbackBeforeAddFriendResp struct {
CommonCallbackResp
}
type CallBackAddFriendReplyBeforeReq struct {
CallbackCommand `json:"callbackCommand"`
FromUserID string `json:"fromUserID" `
ToUserID string `json:"toUserID"`
}
type CallBackAddFriendReplyBeforeResp struct {
CommonCallbackResp
}
type CallbackBeforeSetFriendRemarkReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID"`
FriendUserID string `json:"friendUserID"`
Remark string `json:"remark"`
}
type CallbackBeforeSetFriendRemarkResp struct {
CommonCallbackResp
Remark string `json:"remark"`
}
type CallbackAfterSetFriendRemarkReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID"`
FriendUserID string `json:"friendUserID"`
Remark string `json:"remark"`
}
type CallbackAfterSetFriendRemarkResp struct {
CommonCallbackResp
}
type CallbackAfterAddFriendReq struct {
CallbackCommand `json:"callbackCommand"`
FromUserID string `json:"fromUserID" `
ToUserID string `json:"toUserID"`
ReqMsg string `json:"reqMsg"`
}
type CallbackAfterAddFriendResp struct {
CommonCallbackResp
}
type CallbackBeforeAddBlackReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID" `
BlackUserID string `json:"blackUserID"`
}
type CallbackBeforeAddBlackResp struct {
CommonCallbackResp
}
type CallbackBeforeAddFriendAgreeReq struct {
CallbackCommand `json:"callbackCommand"`
FromUserID string `json:"fromUserID" `
ToUserID string `json:"blackUserID"`
HandleResult int32 `json:"HandleResult"`
HandleMsg string `json:"HandleMsg"`
}
type CallbackBeforeAddFriendAgreeResp struct {
CommonCallbackResp
}
type CallbackAfterDeleteFriendReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID" `
FriendUserID string `json:"friendUserID"`
}
type CallbackAfterDeleteFriendResp struct {
CommonCallbackResp
}
type CallbackBeforeImportFriendsReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID" `
FriendUserIDs []string `json:"friendUserIDs"`
}
type CallbackBeforeImportFriendsResp struct {
CommonCallbackResp
FriendUserIDs []string `json:"friendUserIDs"`
}
type CallbackAfterImportFriendsReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID" `
FriendUserIDs []string `json:"friendUserIDs"`
}
type CallbackAfterImportFriendsResp struct {
CommonCallbackResp
}
type CallbackAfterRemoveBlackReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID"`
BlackUserID string `json:"blackUserID"`
}
type CallbackAfterRemoveBlackResp struct {
CommonCallbackResp
}
+149 -2
View File
@@ -50,9 +50,18 @@ type CallbackBeforeCreateGroupResp struct {
ApplyMemberFriend *int32 `json:"applyMemberFriend"`
}
type CallbackAfterCreateGroupReq struct {
CallbackCommand `json:"callbackCommand"`
*common.GroupInfo
InitMemberList []*apistruct.GroupAddMemberInfo `json:"initMemberList"`
}
type CallbackAfterCreateGroupResp struct {
CommonCallbackResp
}
type CallbackBeforeMemberJoinGroupReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
UserID string `json:"userID"`
Ex string `json:"ex"`
@@ -70,7 +79,6 @@ type CallbackBeforeMemberJoinGroupResp struct {
type CallbackBeforeSetGroupMemberInfoReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
UserID string `json:"userID"`
Nickname *string `json:"nickName"`
@@ -86,3 +94,142 @@ type CallbackBeforeSetGroupMemberInfoResp struct {
FaceURL *string `json:"faceURL"`
RoleLevel *int32 `json:"roleLevel"`
}
type CallbackAfterSetGroupMemberInfoReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
UserID string `json:"userID"`
Nickname *string `json:"nickName"`
FaceURL *string `json:"faceURL"`
RoleLevel *int32 `json:"roleLevel"`
Ex *string `json:"ex"`
}
type CallbackAfterSetGroupMemberInfoResp struct {
CommonCallbackResp
}
type CallbackQuitGroupReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
UserID string `json:"userID"`
}
type CallbackQuitGroupResp struct {
CommonCallbackResp
}
type CallbackKillGroupMemberReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
KickedUserIDs []string `json:"kickedUserIDs"`
Reason string `json:"reason"`
}
type CallbackKillGroupMemberResp struct {
CommonCallbackResp
}
type CallbackDisMissGroupReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
OwnerID string `json:"ownerID"`
GroupType string `json:"groupType"`
MembersID []string `json:"membersID"`
}
type CallbackDisMissGroupResp struct {
CommonCallbackResp
}
type CallbackJoinGroupReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
GroupType string `json:"groupType"`
ApplyID string `json:"applyID"`
ReqMessage string `json:"reqMessage"`
}
type CallbackJoinGroupResp struct {
CommonCallbackResp
}
type CallbackTransferGroupOwnerReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
OldOwnerUserID string `json:"oldOwnerUserID"`
NewOwnerUserID string `json:"newOwnerUserID"`
}
type CallbackTransferGroupOwnerResp struct {
CommonCallbackResp
}
type CallbackBeforeInviteUserToGroupReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
Reason string `json:"reason"`
InvitedUserIDs []string `json:"invitedUserIDs"`
}
type CallbackBeforeInviteUserToGroupResp struct {
CommonCallbackResp
RefusedMembersAccount []string `json:"refusedMembersAccount,omitempty"` // Optional field to list members whose invitation is refused.
}
type CallbackAfterJoinGroupReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
ReqMessage string `json:"reqMessage"`
JoinSource int32 `json:"joinSource"`
InviterUserID string `json:"inviterUserID"`
}
type CallbackAfterJoinGroupResp struct {
CommonCallbackResp
}
type CallbackBeforeSetGroupInfoReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
GroupName string `json:"groupName"`
Notification string `json:"notification"`
Introduction string `json:"introduction"`
FaceURL string `json:"faceURL"`
Ex string `json:"ex"`
NeedVerification int32 `json:"needVerification"`
LookMemberInfo int32 `json:"lookMemberInfo"`
ApplyMemberFriend int32 `json:"applyMemberFriend"`
}
type CallbackBeforeSetGroupInfoResp struct {
CommonCallbackResp
GroupID string ` json:"groupID"`
GroupName string `json:"groupName"`
Notification string `json:"notification"`
Introduction string `json:"introduction"`
FaceURL string `json:"faceURL"`
Ex *string `json:"ex"`
NeedVerification *int32 `json:"needVerification"`
LookMemberInfo *int32 `json:"lookMemberInfo"`
ApplyMemberFriend *int32 `json:"applyMemberFriend"`
}
type CallbackAfterSetGroupInfoReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
GroupName string `json:"groupName"`
Notification string `json:"notification"`
Introduction string `json:"introduction"`
FaceURL string `json:"faceURL"`
Ex *string `json:"ex"`
NeedVerification *int32 `json:"needVerification"`
LookMemberInfo *int32 `json:"lookMemberInfo"`
ApplyMemberFriend *int32 `json:"applyMemberFriend"`
}
type CallbackAfterSetGroupInfoResp struct {
CommonCallbackResp
}
+23
View File
@@ -79,3 +79,26 @@ type CallbackMsgModifyCommandResp struct {
AttachedInfo *string `json:"attachedInfo"`
Ex *string `json:"ex"`
}
type CallbackGroupMsgReadReq struct {
CallbackCommand `json:"callbackCommand"`
SendID string `json:"sendID"`
ReceiveID string `json:"receiveID"`
UnreadMsgNum int64 `json:"unreadMsgNum"`
ContentType int64 `json:"contentType"`
}
type CallbackGroupMsgReadResp struct {
CommonCallbackResp
}
type CallbackSingleMsgReadReq struct {
CallbackCommand `json:"callbackCommand"`
SendID string `json:"sendID"`
ReceiveID string `json:"receiveID"`
ContentType int64 `json:"contentType"`
}
type CallbackSingleMsgReadResp struct {
CommonCallbackResp
}
+11
View File
@@ -0,0 +1,11 @@
package callbackstruct
type CallbackAfterRevokeMsgReq struct {
CallbackCommand `json:"callbackCommand"`
ConversationID string `json:"conversationID"`
Seq int64 `json:"seq"`
UserID string `json:"userID"`
}
type CallbackAfterRevokeMsgResp struct {
CommonCallbackResp
}
+34 -1
View File
@@ -14,9 +14,10 @@
package callbackstruct
import "github.com/OpenIMSDK/protocol/sdkws"
type CallbackBeforeUpdateUserInfoReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
UserID string `json:"userID"`
Nickname *string `json:"nickName"`
FaceURL *string `json:"faceURL"`
@@ -28,3 +29,35 @@ type CallbackBeforeUpdateUserInfoResp struct {
FaceURL *string `json:"faceURL"`
Ex *string `json:"ex"`
}
type CallbackAfterUpdateUserInfoReq struct {
CallbackCommand `json:"callbackCommand"`
UserID string `json:"userID"`
Nickname string `json:"nickName"`
FaceURL string `json:"faceURL"`
Ex string `json:"ex"`
}
type CallbackAfterUpdateUserInfoResp struct {
CommonCallbackResp
}
type CallbackBeforeUserRegisterReq struct {
CallbackCommand `json:"callbackCommand"`
Secret string `json:"secret"`
Users []*sdkws.UserInfo `json:"users"`
}
type CallbackBeforeUserRegisterResp struct {
CommonCallbackResp
Users []*sdkws.UserInfo `json:"users"`
}
type CallbackAfterUserRegisterReq struct {
CallbackCommand `json:"callbackCommand"`
Secret string `json:"secret"`
Users []*sdkws.UserInfo `json:"users"`
}
type CallbackAfterUserRegisterResp struct {
CommonCallbackResp
}
+12 -7
View File
@@ -17,12 +17,12 @@ package cmd
import (
"log"
"github.com/openimsdk/open-im-server/v3/internal/msggateway"
v3config "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/spf13/cobra"
"github.com/OpenIMSDK/protocol/constant"
"github.com/openimsdk/open-im-server/v3/internal/msggateway"
v3config "github.com/openimsdk/open-im-server/v3/pkg/common/config"
)
type MsgGatewayCmd struct {
@@ -60,14 +60,19 @@ func (m *MsgGatewayCmd) Exec() error {
m.addRunE()
return m.Execute()
}
func (m *MsgGatewayCmd) GetPortFromConfig(portType string) int {
if portType == constant.FlagWsPort {
switch portType {
case constant.FlagWsPort:
return v3config.Config.LongConnSvr.OpenImWsPort[0]
} else if portType == constant.FlagPort {
case constant.FlagPort:
return v3config.Config.LongConnSvr.OpenImMessageGatewayPort[0]
} else if portType == constant.FlagPrometheusPort {
case constant.FlagPrometheusPort:
return v3config.Config.Prometheus.MessageGatewayPrometheusPort[0]
} else {
default:
return 0
}
}
+3 -1
View File
@@ -16,10 +16,12 @@ package cmd
import (
"fmt"
"github.com/OpenIMSDK/protocol/constant"
config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/spf13/cobra"
config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/internal/msgtransfer"
)
+52 -11
View File
@@ -16,7 +16,6 @@ package config
import (
"bytes"
"github.com/OpenIMSDK/tools/discoveryregistry"
"gopkg.in/yaml.v3"
)
@@ -78,17 +77,20 @@ type configStruct struct {
} `yaml:"mongo"`
Redis struct {
ClusterMode bool `yaml:"clusterMode"`
Address []string `yaml:"address"`
Username string `yaml:"username"`
Password string `yaml:"password"`
ClusterMode bool `yaml:"clusterMode"`
Address []string `yaml:"address"`
Username string `yaml:"username"`
Password string `yaml:"password"`
EnablePipeline bool `yaml:"enablePipeline"`
} `yaml:"redis"`
Kafka struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
Addr []string `yaml:"addr"`
TLS *struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
ProducerAck string `yaml:"producerAck"`
CompressType string `yaml:"compressType"`
Addr []string `yaml:"addr"`
TLS *struct {
CACrt string `yaml:"caCrt"`
ClientCrt string `yaml:"clientCrt"`
ClientKey string `yaml:"clientKey"`
@@ -150,6 +152,15 @@ type configStruct struct {
SessionToken string `yaml:"sessionToken"`
PublicRead bool `yaml:"publicRead"`
} `yaml:"oss"`
Kodo struct {
Endpoint string `yaml:"endpoint"`
Bucket string `yaml:"bucket"`
BucketURL string `yaml:"bucketURL"`
AccessKeyID string `yaml:"accessKeyID"`
AccessKeySecret string `yaml:"accessKeySecret"`
SessionToken string `yaml:"sessionToken"`
PublicRead bool `yaml:"publicRead"`
} `yaml:"kodo"`
} `yaml:"object"`
RpcPort struct {
@@ -193,11 +204,13 @@ type configStruct struct {
WebsocketMaxConnNum int `yaml:"websocketMaxConnNum"`
WebsocketMaxMsgLen int `yaml:"websocketMaxMsgLen"`
WebsocketTimeout int `yaml:"websocketTimeout"`
WebsocketWriteBufferSize int `yaml:"websocketWriteBufferSize"`
} `yaml:"longConnSvr"`
Push struct {
Enable string `yaml:"enable"`
GeTui struct {
MaxConcurrentWorkers int `yaml:"maxConcurrentWorkers"`
Enable string `yaml:"enable"`
GeTui struct {
PushUrl string `yaml:"pushUrl"`
AppKey string `yaml:"appKey"`
Intent string `yaml:"intent"`
@@ -229,6 +242,7 @@ type configStruct struct {
ChatRecordsClearTime string `yaml:"chatRecordsClearTime"`
MsgDestructTime string `yaml:"msgDestructTime"`
Secret string `yaml:"secret"`
EnableCronLocker bool `yaml:"enableCronLocker"`
TokenPolicy struct {
Expire int64 `yaml:"expire"`
} `yaml:"tokenPolicy"`
@@ -248,6 +262,8 @@ type configStruct struct {
CallbackBeforeSendGroupMsg CallBackConfig `yaml:"beforeSendGroupMsg"`
CallbackAfterSendGroupMsg CallBackConfig `yaml:"afterSendGroupMsg"`
CallbackMsgModify CallBackConfig `yaml:"msgModify"`
CallbackSingleMsgRead CallBackConfig `yaml:"singleMsgRead"`
CallbackGroupMsgRead CallBackConfig `yaml:"groupMsgRead"`
CallbackUserOnline CallBackConfig `yaml:"userOnline"`
CallbackUserOffline CallBackConfig `yaml:"userOffline"`
CallbackUserKickOff CallBackConfig `yaml:"userKickOff"`
@@ -255,10 +271,35 @@ type configStruct struct {
CallbackOnlinePush CallBackConfig `yaml:"onlinePush"`
CallbackBeforeSuperGroupOnlinePush CallBackConfig `yaml:"superGroupOnlinePush"`
CallbackBeforeAddFriend CallBackConfig `yaml:"beforeAddFriend"`
CallbackBeforeSetFriendRemark CallBackConfig `yaml:"callbackBeforeSetFriendRemark"`
CallbackAfterSetFriendRemark CallBackConfig `yaml:"callbackAfterSetFriendRemark"`
CallbackBeforeUpdateUserInfo CallBackConfig `yaml:"beforeUpdateUserInfo"`
CallbackBeforeUserRegister CallBackConfig `yaml:"beforeUserRegister"`
CallbackAfterUpdateUserInfo CallBackConfig `yaml:"updateUserInfo"`
CallbackAfterUserRegister CallBackConfig `yaml:"afterUserRegister"`
CallbackBeforeCreateGroup CallBackConfig `yaml:"beforeCreateGroup"`
CallbackAfterCreateGroup CallBackConfig `yaml:"afterCreateGroup"`
CallbackBeforeMemberJoinGroup CallBackConfig `yaml:"beforeMemberJoinGroup"`
CallbackBeforeSetGroupMemberInfo CallBackConfig `yaml:"beforeSetGroupMemberInfo"`
CallbackAfterSetGroupMemberInfo CallBackConfig `yaml:"afterSetGroupMemberInfo"`
CallbackQuitGroup CallBackConfig `yaml:"quitGroup"`
CallbackKillGroupMember CallBackConfig `yaml:"killGroupMember"`
CallbackDismissGroup CallBackConfig `yaml:"dismissGroup"`
CallbackBeforeJoinGroup CallBackConfig `yaml:"joinGroup"`
CallbackTransferGroupOwnerAfter CallBackConfig `yaml:"transferGroupOwner"`
CallbackBeforeInviteUserToGroup CallBackConfig `yaml:"beforeInviteUserToGroup"`
CallbackAfterJoinGroup CallBackConfig `yaml:"joinGroupAfter"`
CallbackAfterSetGroupInfo CallBackConfig `yaml:"setGroupInfoAfter"`
CallbackBeforeSetGroupInfo CallBackConfig `yaml:"setGroupInfoBefore"`
CallbackAfterRevokeMsg CallBackConfig `yaml:"revokeMsgAfter"`
CallbackBeforeAddBlack CallBackConfig `yaml:"addBlackBefore"`
CallbackAfterAddFriend CallBackConfig `yaml:"addFriendAfter"`
CallbackBeforeAddFriendAgree CallBackConfig `yaml:"addFriendAgreeBefore"`
CallbackAfterDeleteFriend CallBackConfig `yaml:"deleteFriendAfter"`
CallbackBeforeImportFriends CallBackConfig `yaml:"importFriendsBefore"`
CallbackAfterImportFriends CallBackConfig `yaml:"importFriendsAfter"`
CallbackAfterRemoveBlack CallBackConfig `yaml:"removeBlackAfter"`
} `yaml:"callback"`
Prometheus struct {
-3
View File
@@ -15,8 +15,6 @@
package convert
import (
"time"
"github.com/OpenIMSDK/protocol/sdkws"
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
@@ -43,7 +41,6 @@ func UserPb2DB(user *sdkws.UserInfo) *relationtb.UserModel {
userDB.Nickname = user.Nickname
userDB.FaceURL = user.FaceURL
userDB.Ex = user.Ex
userDB.CreateTime = time.UnixMilli(user.CreateTime)
userDB.AppMangerLevel = user.AppMangerLevel
userDB.GlobalRecvMsgOpt = user.GlobalRecvMsgOpt
return &userDB
+10
View File
@@ -28,12 +28,21 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
)
var (
// singleton pattern.
redisClient redis.UniversalClient
)
const (
maxRetry = 10 // number of retries
)
// NewRedis Initialize redis connection.
func NewRedis() (redis.UniversalClient, error) {
if redisClient != nil {
return redisClient, nil
}
if len(config.Config.Redis.Address) == 0 {
return nil, errors.New("redis address is empty")
}
@@ -66,5 +75,6 @@ func NewRedis() (redis.UniversalClient, error) {
return nil, fmt.Errorf("redis ping %w", err)
}
redisClient = rdb
return rdb, err
}
+227 -118
View File
@@ -20,9 +20,7 @@ import (
"strconv"
"time"
"github.com/dtm-labs/rockscache"
unrelationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation"
"golang.org/x/sync/errgroup"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
@@ -62,6 +60,8 @@ const (
uidPidToken = "UID_PID_TOKEN_STATUS:"
)
var concurrentLimit = 3
type SeqCache interface {
SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error
GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error)
@@ -133,10 +133,7 @@ func NewMsgCacheModel(client redis.UniversalClient) MsgModel {
type msgCache struct {
metaCache
rdb redis.UniversalClient
expireTime time.Duration
rcClient *rockscache.Client
msgDocDatabase unrelationtb.MsgDocModelInterface
rdb redis.UniversalClient
}
func (c *msgCache) getMaxSeqKey(conversationID string) string {
@@ -173,33 +170,23 @@ func (c *msgCache) getSeqs(ctx context.Context, items []string, getkey func(s st
}
return m, nil
//pipe := c.rdb.Pipeline()
//for _, v := range items {
// if err := pipe.Get(ctx, getkey(v)).Err(); err != nil && err != redis.Nil {
// return nil, errs.Wrap(err)
// }
//}
//result, err := pipe.Exec(ctx)
//if err != nil && err != redis.Nil {
// return nil, errs.Wrap(err)
//}
//m = make(map[string]int64, len(items))
//for i, v := range result {
// seq := v.(*redis.StringCmd)
// if seq.Err() != nil && seq.Err() != redis.Nil {
// return nil, errs.Wrap(v.Err())
// }
// val := utils.StringToInt64(seq.Val())
// if val != 0 {
// m[items[i]] = val
// }
//}
//return m, nil
}
func (c *msgCache) SetMaxSeq(ctx context.Context, conversationID string, maxSeq int64) error {
return c.setSeq(ctx, conversationID, maxSeq, c.getMaxSeqKey)
var retErr error
for {
select {
case <-ctx.Done():
return errs.Wrap(retErr, "SetMaxSeq redis retry too many amount")
default:
retErr = c.setSeq(ctx, conversationID, maxSeq, c.getMaxSeqKey)
if retErr != nil {
time.Sleep(time.Second * 2)
continue
}
return nil
}
}
}
func (c *msgCache) GetMaxSeqs(ctx context.Context, conversationIDs []string) (m map[string]int64, err error) {
@@ -207,7 +194,21 @@ func (c *msgCache) GetMaxSeqs(ctx context.Context, conversationIDs []string) (m
}
func (c *msgCache) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
return c.getSeq(ctx, conversationID, c.getMaxSeqKey)
var retErr error
var retData int64
for {
select {
case <-ctx.Done():
return -1, errs.Wrap(retErr, "GetMaxSeq redis retry too many amount")
default:
retData, retErr = c.getSeq(ctx, conversationID, c.getMaxSeqKey)
if retErr != nil && errs.Unwrap(retErr) != redis.Nil {
time.Sleep(time.Second * 2)
continue
}
return retData, retErr
}
}
}
func (c *msgCache) SetMinSeq(ctx context.Context, conversationID string, minSeq int64) error {
@@ -221,15 +222,6 @@ func (c *msgCache) setSeqs(ctx context.Context, seqs map[string]int64, getkey fu
}
}
return nil
//pipe := c.rdb.Pipeline()
//for k, seq := range seqs {
// err := pipe.Set(ctx, getkey(k), seq, 0).Err()
// if err != nil {
// return errs.Wrap(err)
// }
//}
//_, err := pipe.Exec(ctx)
//return err
}
func (c *msgCache) SetMinSeqs(ctx context.Context, seqs map[string]int64) error {
@@ -345,85 +337,165 @@ func (c *msgCache) allMessageCacheKey(conversationID string) string {
}
func (c *msgCache) GetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) {
if config.Config.Redis.EnablePipeline {
return c.PipeGetMessagesBySeq(ctx, conversationID, seqs)
}
return c.ParallelGetMessagesBySeq(ctx, conversationID, seqs)
}
func (c *msgCache) PipeGetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) {
pipe := c.rdb.Pipeline()
results := []*redis.StringCmd{}
for _, seq := range seqs {
res, err := c.rdb.Get(ctx, c.getMessageCacheKey(conversationID, seq)).Result()
if err != nil {
log.ZError(ctx, "GetMessagesBySeq failed", err, "conversationID", conversationID, "seq", seq)
results = append(results, pipe.Get(ctx, c.getMessageCacheKey(conversationID, seq)))
}
_, err = pipe.Exec(ctx)
if err != nil && err != redis.Nil {
return seqMsgs, failedSeqs, errs.Wrap(err, "pipe.get")
}
for idx, res := range results {
seq := seqs[idx]
if res.Err() != nil {
log.ZError(ctx, "GetMessagesBySeq failed", err, "conversationID", conversationID, "seq", seq, "err", res.Err())
failedSeqs = append(failedSeqs, seq)
continue
}
msg := sdkws.MsgData{}
if err = msgprocessor.String2Pb(res, &msg); err != nil {
if err = msgprocessor.String2Pb(res.Val(), &msg); err != nil {
log.ZError(ctx, "GetMessagesBySeq Unmarshal failed", err, "res", res, "conversationID", conversationID, "seq", seq)
failedSeqs = append(failedSeqs, seq)
continue
}
if msg.Status == constant.MsgDeleted {
failedSeqs = append(failedSeqs, seq)
continue
}
seqMsgs = append(seqMsgs, &msg)
}
return
//pipe := c.rdb.Pipeline()
//for _, v := range seqs {
// // MESSAGE_CACHE:169.254.225.224_reliability1653387820_0_1
// key := c.getMessageCacheKey(conversationID, v)
// if err := pipe.Get(ctx, key).Err(); err != nil && err != redis.Nil {
// return nil, nil, err
// }
//}
//result, err := pipe.Exec(ctx)
//for i, v := range result {
// cmd := v.(*redis.StringCmd)
// if cmd.Err() != nil {
// failedSeqs = append(failedSeqs, seqs[i])
// } else {
// msg := sdkws.MsgData{}
// err = msgprocessor.String2Pb(cmd.Val(), &msg)
// if err == nil {
// if msg.Status != constant.MsgDeleted {
// seqMsgs = append(seqMsgs, &msg)
// continue
// }
// } else {
// log.ZWarn(ctx, "UnmarshalString failed", err, "conversationID", conversationID, "seq", seqs[i], "msg", cmd.Val())
// }
// failedSeqs = append(failedSeqs, seqs[i])
// }
//}
//return seqMsgs, failedSeqs, err
}
func (c *msgCache) ParallelGetMessagesBySeq(ctx context.Context, conversationID string, seqs []int64) (seqMsgs []*sdkws.MsgData, failedSeqs []int64, err error) {
type entry struct {
err error
msg *sdkws.MsgData
}
wg := errgroup.Group{}
wg.SetLimit(concurrentLimit)
results := make([]entry, len(seqs)) // set slice len/cap to length of seqs.
for idx, seq := range seqs {
// closure safe var
idx := idx
seq := seq
wg.Go(func() error {
res, err := c.rdb.Get(ctx, c.getMessageCacheKey(conversationID, seq)).Result()
if err != nil {
log.ZError(ctx, "GetMessagesBySeq failed", err, "conversationID", conversationID, "seq", seq)
results[idx] = entry{err: err}
return nil
}
msg := sdkws.MsgData{}
if err = msgprocessor.String2Pb(res, &msg); err != nil {
log.ZError(ctx, "GetMessagesBySeq Unmarshal failed", err, "res", res, "conversationID", conversationID, "seq", seq)
results[idx] = entry{err: err}
return nil
}
if msg.Status == constant.MsgDeleted {
results[idx] = entry{err: err}
return nil
}
results[idx] = entry{msg: &msg}
return nil
})
}
_ = wg.Wait()
for idx, res := range results {
if res.err != nil {
failedSeqs = append(failedSeqs, seqs[idx])
continue
}
seqMsgs = append(seqMsgs, res.msg)
}
return
}
func (c *msgCache) SetMessageToCache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (int, error) {
if config.Config.Redis.EnablePipeline {
return c.PipeSetMessageToCache(ctx, conversationID, msgs)
}
return c.ParallelSetMessageToCache(ctx, conversationID, msgs)
}
func (c *msgCache) PipeSetMessageToCache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (int, error) {
pipe := c.rdb.Pipeline()
for _, msg := range msgs {
s, err := msgprocessor.Pb2String(msg)
if err != nil {
return 0, errs.Wrap(err)
return 0, errs.Wrap(err, "pb.marshal")
}
key := c.getMessageCacheKey(conversationID, msg.Seq)
if err := c.rdb.Set(ctx, key, s, time.Duration(config.Config.MsgCacheTimeout)*time.Second).Err(); err != nil {
_ = pipe.Set(ctx, key, s, time.Duration(config.Config.MsgCacheTimeout)*time.Second)
}
results, err := pipe.Exec(ctx)
if err != nil {
return 0, errs.Wrap(err, "pipe.set")
}
for _, res := range results {
if res.Err() != nil {
return 0, errs.Wrap(err)
}
}
return len(msgs), nil
}
func (c *msgCache) ParallelSetMessageToCache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (int, error) {
wg := errgroup.Group{}
wg.SetLimit(concurrentLimit)
for _, msg := range msgs {
msg := msg // closure safe var
wg.Go(func() error {
s, err := msgprocessor.Pb2String(msg)
if err != nil {
return errs.Wrap(err)
}
key := c.getMessageCacheKey(conversationID, msg.Seq)
if err := c.rdb.Set(ctx, key, s, time.Duration(config.Config.MsgCacheTimeout)*time.Second).Err(); err != nil {
return errs.Wrap(err)
}
return nil
})
}
err := wg.Wait()
if err != nil {
return 0, err
}
return len(msgs), nil
//pipe := c.rdb.Pipeline()
//var failedMsgs []*sdkws.MsgData
//for _, msg := range msgs {
// key := c.getMessageCacheKey(conversationID, msg.Seq)
// s, err := msgprocessor.Pb2String(msg)
// if err != nil {
// return 0, errs.Wrap(err)
// }
// err = pipe.Set(ctx, key, s, time.Duration(config.Config.MsgCacheTimeout)*time.Second).Err()
// if err != nil {
// failedMsgs = append(failedMsgs, msg)
// log.ZWarn(ctx, "set msg 2 cache failed", err, "msg", failedMsgs)
// }
//}
//_, err := pipe.Exec(ctx)
//return len(failedMsgs), err
}
func (c *msgCache) getMessageDelUserListKey(conversationID string, seq int64) string {
@@ -554,44 +626,81 @@ func (c *msgCache) DelUserDeleteMsgsList(ctx context.Context, conversationID str
}
func (c *msgCache) DeleteMessages(ctx context.Context, conversationID string, seqs []int64) error {
if config.Config.Redis.EnablePipeline {
return c.PipeDeleteMessages(ctx, conversationID, seqs)
}
return c.ParallelDeleteMessages(ctx, conversationID, seqs)
}
func (c *msgCache) ParallelDeleteMessages(ctx context.Context, conversationID string, seqs []int64) error {
wg := errgroup.Group{}
wg.SetLimit(concurrentLimit)
for _, seq := range seqs {
if err := c.rdb.Del(ctx, c.getMessageCacheKey(conversationID, seq)).Err(); err != nil {
seq := seq
wg.Go(func() error {
err := c.rdb.Del(ctx, c.getMessageCacheKey(conversationID, seq)).Err()
if err != nil {
return errs.Wrap(err)
}
return nil
})
}
return wg.Wait()
}
func (c *msgCache) PipeDeleteMessages(ctx context.Context, conversationID string, seqs []int64) error {
pipe := c.rdb.Pipeline()
for _, seq := range seqs {
_ = pipe.Del(ctx, c.getMessageCacheKey(conversationID, seq))
}
results, err := pipe.Exec(ctx)
if err != nil {
return errs.Wrap(err, "pipe.del")
}
for _, res := range results {
if res.Err() != nil {
return errs.Wrap(err)
}
}
return nil
//pipe := c.rdb.Pipeline()
//for _, seq := range seqs {
// if err := pipe.Del(ctx, c.getMessageCacheKey(conversationID, seq)).Err(); err != nil {
// return errs.Wrap(err)
// }
//}
//_, err := pipe.Exec(ctx)
//return errs.Wrap(err)
}
func (c *msgCache) CleanUpOneConversationAllMsg(ctx context.Context, conversationID string) error {
vals, err := c.rdb.Keys(ctx, c.allMessageCacheKey(conversationID)).Result()
if errors.Is(err, redis.Nil) {
return nil
}
if err != nil {
return errs.Wrap(err)
}
for _, v := range vals {
if err := c.rdb.Del(ctx, v).Err(); err != nil {
var (
cursor uint64
keys []string
err error
key = c.allMessageCacheKey(conversationID)
)
for {
// scan up to 10000 at a time, the count (10000) param refers to the number of scans on redis server.
// if the count is too small, needs to be run scan on redis frequently.
var limit int64 = 10000
keys, cursor, err = c.rdb.Scan(ctx, cursor, key, limit).Result()
if err != nil {
return errs.Wrap(err)
}
for _, key := range keys {
err := c.rdb.Del(ctx, key).Err()
if err != nil {
return errs.Wrap(err)
}
}
// scan end
if cursor == 0 {
return nil
}
}
return nil
//pipe := c.rdb.Pipeline()
//for _, v := range vals {
// if err := pipe.Del(ctx, v).Err(); err != nil {
// return errs.Wrap(err)
// }
//}
//_, err = pipe.Exec(ctx)
//return errs.Wrap(err)
}
func (c *msgCache) DelMsgFromCache(ctx context.Context, userID string, seqs []int64) error {
+434
View File
@@ -0,0 +1,434 @@
package cache
import (
"context"
"fmt"
"math/rand"
"testing"
"github.com/OpenIMSDK/protocol/sdkws"
"github.com/redis/go-redis/v9"
"github.com/stretchr/testify/assert"
)
func TestParallelSetMessageToCache(t *testing.T) {
var (
cid = fmt.Sprintf("cid-%v", rand.Int63())
seqFirst = rand.Int63()
msgs = []*sdkws.MsgData{}
)
for i := 0; i < 100; i++ {
msgs = append(msgs, &sdkws.MsgData{
Seq: seqFirst + int64(i),
})
}
testParallelSetMessageToCache(t, cid, msgs)
}
func testParallelSetMessageToCache(t *testing.T, cid string, msgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
ret, err := cacher.ParallelSetMessageToCache(context.Background(), cid, msgs)
assert.Nil(t, err)
assert.Equal(t, len(msgs), ret)
// validate
for _, msg := range msgs {
key := cacher.getMessageCacheKey(cid, msg.Seq)
val, err := rdb.Exists(context.Background(), key).Result()
assert.Nil(t, err)
assert.EqualValues(t, 1, val)
}
}
func TestPipeSetMessageToCache(t *testing.T) {
var (
cid = fmt.Sprintf("cid-%v", rand.Int63())
seqFirst = rand.Int63()
msgs = []*sdkws.MsgData{}
)
for i := 0; i < 100; i++ {
msgs = append(msgs, &sdkws.MsgData{
Seq: seqFirst + int64(i),
})
}
testPipeSetMessageToCache(t, cid, msgs)
}
func testPipeSetMessageToCache(t *testing.T, cid string, msgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
ret, err := cacher.PipeSetMessageToCache(context.Background(), cid, msgs)
assert.Nil(t, err)
assert.Equal(t, len(msgs), ret)
// validate
for _, msg := range msgs {
key := cacher.getMessageCacheKey(cid, msg.Seq)
val, err := rdb.Exists(context.Background(), key).Result()
assert.Nil(t, err)
assert.EqualValues(t, 1, val)
}
}
func TestGetMessagesBySeq(t *testing.T) {
var (
cid = fmt.Sprintf("cid-%v", rand.Int63())
seqFirst = rand.Int63()
msgs = []*sdkws.MsgData{}
)
seqs := []int64{}
for i := 0; i < 100; i++ {
msgs = append(msgs, &sdkws.MsgData{
Seq: seqFirst + int64(i),
SendID: fmt.Sprintf("fake-sendid-%v", i),
})
seqs = append(seqs, seqFirst+int64(i))
}
// set data to cache
testPipeSetMessageToCache(t, cid, msgs)
// get data from cache with parallet mode
testParallelGetMessagesBySeq(t, cid, seqs, msgs)
// get data from cache with pipeline mode
testPipeGetMessagesBySeq(t, cid, seqs, msgs)
}
func testParallelGetMessagesBySeq(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
respMsgs, failedSeqs, err := cacher.ParallelGetMessagesBySeq(context.Background(), cid, seqs)
assert.Nil(t, err)
assert.Equal(t, 0, len(failedSeqs))
assert.Equal(t, len(respMsgs), len(seqs))
// validate
for idx, msg := range respMsgs {
assert.Equal(t, msg.Seq, inputMsgs[idx].Seq)
assert.Equal(t, msg.SendID, inputMsgs[idx].SendID)
}
}
func testPipeGetMessagesBySeq(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
respMsgs, failedSeqs, err := cacher.PipeGetMessagesBySeq(context.Background(), cid, seqs)
assert.Nil(t, err)
assert.Equal(t, 0, len(failedSeqs))
assert.Equal(t, len(respMsgs), len(seqs))
// validate
for idx, msg := range respMsgs {
assert.Equal(t, msg.Seq, inputMsgs[idx].Seq)
assert.Equal(t, msg.SendID, inputMsgs[idx].SendID)
}
}
func TestGetMessagesBySeqWithEmptySeqs(t *testing.T) {
var (
cid = fmt.Sprintf("cid-%v", rand.Int63())
seqFirst int64 = 0
msgs = []*sdkws.MsgData{}
)
seqs := []int64{}
for i := 0; i < 100; i++ {
msgs = append(msgs, &sdkws.MsgData{
Seq: seqFirst + int64(i),
SendID: fmt.Sprintf("fake-sendid-%v", i),
})
seqs = append(seqs, seqFirst+int64(i))
}
// don't set cache, only get data from cache.
// get data from cache with parallet mode
testParallelGetMessagesBySeqWithEmptry(t, cid, seqs, msgs)
// get data from cache with pipeline mode
testPipeGetMessagesBySeqWithEmptry(t, cid, seqs, msgs)
}
func testParallelGetMessagesBySeqWithEmptry(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
respMsgs, failedSeqs, err := cacher.ParallelGetMessagesBySeq(context.Background(), cid, seqs)
assert.Nil(t, err)
assert.Equal(t, len(seqs), len(failedSeqs))
assert.Equal(t, 0, len(respMsgs))
}
func testPipeGetMessagesBySeqWithEmptry(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
respMsgs, failedSeqs, err := cacher.PipeGetMessagesBySeq(context.Background(), cid, seqs)
assert.Equal(t, err, redis.Nil)
assert.Equal(t, len(seqs), len(failedSeqs))
assert.Equal(t, 0, len(respMsgs))
}
func TestGetMessagesBySeqWithLostHalfSeqs(t *testing.T) {
var (
cid = fmt.Sprintf("cid-%v", rand.Int63())
seqFirst int64 = 0
msgs = []*sdkws.MsgData{}
)
seqs := []int64{}
for i := 0; i < 100; i++ {
msgs = append(msgs, &sdkws.MsgData{
Seq: seqFirst + int64(i),
SendID: fmt.Sprintf("fake-sendid-%v", i),
})
seqs = append(seqs, seqFirst+int64(i))
}
// Only set half the number of messages.
testParallelSetMessageToCache(t, cid, msgs[:50])
// get data from cache with parallet mode
testParallelGetMessagesBySeqWithLostHalfSeqs(t, cid, seqs, msgs)
// get data from cache with pipeline mode
testPipeGetMessagesBySeqWithLostHalfSeqs(t, cid, seqs, msgs)
}
func testParallelGetMessagesBySeqWithLostHalfSeqs(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
respMsgs, failedSeqs, err := cacher.ParallelGetMessagesBySeq(context.Background(), cid, seqs)
assert.Nil(t, err)
assert.Equal(t, len(seqs)/2, len(failedSeqs))
assert.Equal(t, len(seqs)/2, len(respMsgs))
for idx, msg := range respMsgs {
assert.Equal(t, msg.Seq, seqs[idx])
}
}
func testPipeGetMessagesBySeqWithLostHalfSeqs(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
respMsgs, failedSeqs, err := cacher.PipeGetMessagesBySeq(context.Background(), cid, seqs)
assert.Nil(t, err)
assert.Equal(t, len(seqs)/2, len(failedSeqs))
assert.Equal(t, len(seqs)/2, len(respMsgs))
for idx, msg := range respMsgs {
assert.Equal(t, msg.Seq, seqs[idx])
}
}
func TestPipeDeleteMessages(t *testing.T) {
var (
cid = fmt.Sprintf("cid-%v", rand.Int63())
seqFirst = rand.Int63()
msgs = []*sdkws.MsgData{}
)
var seqs []int64
for i := 0; i < 100; i++ {
msgs = append(msgs, &sdkws.MsgData{
Seq: seqFirst + int64(i),
})
seqs = append(seqs, msgs[i].Seq)
}
testPipeSetMessageToCache(t, cid, msgs)
testPipeDeleteMessagesOK(t, cid, seqs, msgs)
// set again
testPipeSetMessageToCache(t, cid, msgs)
testPipeDeleteMessagesMix(t, cid, seqs[:90], msgs)
}
func testPipeDeleteMessagesOK(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
assert.Nil(t, err)
// validate
for _, msg := range inputMsgs {
key := cacher.getMessageCacheKey(cid, msg.Seq)
val := rdb.Exists(context.Background(), key).Val()
assert.EqualValues(t, 0, val)
}
}
func testPipeDeleteMessagesMix(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
assert.Nil(t, err)
// validate
for idx, msg := range inputMsgs {
key := cacher.getMessageCacheKey(cid, msg.Seq)
val, err := rdb.Exists(context.Background(), key).Result()
assert.Nil(t, err)
if idx < 90 {
assert.EqualValues(t, 0, val) // not exists
continue
}
assert.EqualValues(t, 1, val) // exists
}
}
func TestParallelDeleteMessages(t *testing.T) {
var (
cid = fmt.Sprintf("cid-%v", rand.Int63())
seqFirst = rand.Int63()
msgs = []*sdkws.MsgData{}
)
var seqs []int64
for i := 0; i < 100; i++ {
msgs = append(msgs, &sdkws.MsgData{
Seq: seqFirst + int64(i),
})
seqs = append(seqs, msgs[i].Seq)
}
randSeqs := []int64{}
for i := seqFirst + 100; i < seqFirst+200; i++ {
randSeqs = append(randSeqs, i)
}
testParallelSetMessageToCache(t, cid, msgs)
testParallelDeleteMessagesOK(t, cid, seqs, msgs)
// set again
testParallelSetMessageToCache(t, cid, msgs)
testParallelDeleteMessagesMix(t, cid, seqs[:90], msgs, 90)
testParallelDeleteMessagesOK(t, cid, seqs[90:], msgs[:90])
// set again
testParallelSetMessageToCache(t, cid, msgs)
testParallelDeleteMessagesMix(t, cid, randSeqs, msgs, 0)
}
func testParallelDeleteMessagesOK(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
assert.Nil(t, err)
// validate
for _, msg := range inputMsgs {
key := cacher.getMessageCacheKey(cid, msg.Seq)
val := rdb.Exists(context.Background(), key).Val()
assert.EqualValues(t, 0, val)
}
}
func testParallelDeleteMessagesMix(t *testing.T, cid string, seqs []int64, inputMsgs []*sdkws.MsgData, lessValNonExists int) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
err := cacher.PipeDeleteMessages(context.Background(), cid, seqs)
assert.Nil(t, err)
// validate
for idx, msg := range inputMsgs {
key := cacher.getMessageCacheKey(cid, msg.Seq)
val, err := rdb.Exists(context.Background(), key).Result()
assert.Nil(t, err)
if idx < lessValNonExists {
assert.EqualValues(t, 0, val) // not exists
continue
}
assert.EqualValues(t, 1, val) // exists
}
}
func TestCleanUpOneConversationAllMsg(t *testing.T) {
rdb := redis.NewClient(&redis.Options{})
defer rdb.Close()
cacher := msgCache{rdb: rdb}
count := 1000
prefix := fmt.Sprintf("%v", rand.Int63())
ids := []string{}
for i := 0; i < count; i++ {
id := fmt.Sprintf("%v-cid-%v", prefix, rand.Int63())
ids = append(ids, id)
key := cacher.allMessageCacheKey(id)
rdb.Set(context.Background(), key, "openim", 0)
}
// delete 100 keys with scan.
for i := 0; i < 100; i++ {
pickedKey := ids[i]
err := cacher.CleanUpOneConversationAllMsg(context.Background(), pickedKey)
assert.Nil(t, err)
ls, err := rdb.Keys(context.Background(), pickedKey).Result()
assert.Nil(t, err)
assert.Equal(t, 0, len(ls))
rcode, err := rdb.Exists(context.Background(), pickedKey).Result()
assert.Nil(t, err)
assert.EqualValues(t, 0, rcode) // non-exists
}
sid := fmt.Sprintf("%v-cid-*", prefix)
ls, err := rdb.Keys(context.Background(), cacher.allMessageCacheKey(sid)).Result()
assert.Nil(t, err)
assert.Equal(t, count-100, len(ls))
// delete fuzzy matching keys.
err = cacher.CleanUpOneConversationAllMsg(context.Background(), sid)
assert.Nil(t, err)
// don't contains keys matched `{prefix}-cid-{random}` on redis
ls, err = rdb.Keys(context.Background(), cacher.allMessageCacheKey(sid)).Result()
assert.Nil(t, err)
assert.Equal(t, 0, len(ls))
}
+100 -41
View File
@@ -16,22 +16,27 @@ package controller
import (
"context"
"encoding/json"
"errors"
"time"
"github.com/OpenIMSDK/protocol/constant"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/redis/go-redis/v9"
"github.com/OpenIMSDK/tools/errs"
"github.com/OpenIMSDK/tools/log"
"go.mongodb.org/mongo-driver/mongo"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/cache"
unrelationtb "github.com/openimsdk/open-im-server/v3/pkg/common/db/table/unrelation"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/unrelation"
"github.com/openimsdk/open-im-server/v3/pkg/common/kafka"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"go.mongodb.org/mongo-driver/mongo"
pbmsg "github.com/OpenIMSDK/protocol/msg"
"github.com/OpenIMSDK/protocol/sdkws"
@@ -352,7 +357,9 @@ func (db *commonMsgDatabase) DelUserDeleteMsgsList(ctx context.Context, conversa
}
func (db *commonMsgDatabase) BatchInsertChat2Cache(ctx context.Context, conversationID string, msgs []*sdkws.MsgData) (seq int64, isNew bool, err error) {
currentMaxSeq, err := db.cache.GetMaxSeq(ctx, conversationID)
cancelCtx, cancel := context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
currentMaxSeq, err := db.cache.GetMaxSeq(cancelCtx, conversationID)
if err != nil && errs.Unwrap(err) != redis.Nil {
log.ZError(ctx, "db.cache.GetMaxSeq", err)
return 0, false, err
@@ -379,25 +386,27 @@ func (db *commonMsgDatabase) BatchInsertChat2Cache(ctx context.Context, conversa
prommetrics.MsgInsertRedisFailedCounter.Add(float64(failedNum))
log.ZError(ctx, "setMessageToCache error", err, "len", len(msgs), "conversationID", conversationID)
} else {
prommetrics.MsgInsertRedisSuccessCounter.Inc()
prommetrics.MsgInsertRedisSuccessCounter.Add(float64(len(msgs)))
}
err = db.cache.SetMaxSeq(ctx, conversationID, currentMaxSeq)
cancelCtx, cancel = context.WithTimeout(ctx, 1*time.Minute)
defer cancel()
err = db.cache.SetMaxSeq(cancelCtx, conversationID, currentMaxSeq)
if err != nil {
log.ZError(ctx, "db.cache.SetMaxSeq error", err, "conversationID", conversationID)
prommetrics.SeqSetFailedCounter.Inc()
}
err2 := db.cache.SetHasReadSeqs(ctx, conversationID, userSeqMap)
if err != nil {
if err2 != nil {
log.ZError(ctx, "SetHasReadSeqs error", err2, "userSeqMap", userSeqMap, "conversationID", conversationID)
prommetrics.SeqSetFailedCounter.Inc()
}
return lastMaxSeq, isNew, utils.Wrap(err, "")
return lastMaxSeq, isNew, errs.Wrap(err, "redis SetMaxSeq error")
}
func (db *commonMsgDatabase) getMsgBySeqs(ctx context.Context, userID, conversationID string, seqs []int64) (totalMsgs []*sdkws.MsgData, err error) {
for docID, seqs := range db.msg.GetDocIDSeqsMap(conversationID, seqs) {
// log.ZDebug(ctx, "getMsgBySeqs", "docID", docID, "seqs", seqs)
msgs, err := db.findMsgInfoBySeq(ctx, userID, docID, seqs)
msgs, err := db.findMsgInfoBySeq(ctx, userID, docID, conversationID, seqs)
if err != nil {
return nil, err
}
@@ -408,13 +417,71 @@ func (db *commonMsgDatabase) getMsgBySeqs(ctx context.Context, userID, conversat
return totalMsgs, nil
}
func (db *commonMsgDatabase) findMsgInfoBySeq(ctx context.Context, userID, docID string, seqs []int64) (totalMsgs []*unrelationtb.MsgInfoModel, err error) {
msgs, err := db.msgDocDatabase.GetMsgBySeqIndexIn1Doc(ctx, userID, docID, seqs)
for _, msg := range msgs {
if msg.IsRead {
msg.Msg.IsRead = true
func (db *commonMsgDatabase) handlerDBMsg(ctx context.Context, cache map[int64][]*unrelationtb.MsgInfoModel, userID, conversationID string, msg *unrelationtb.MsgInfoModel) {
if msg.IsRead {
msg.Msg.IsRead = true
}
if msg.Msg.ContentType != constant.Quote {
return
}
if msg.Msg.Content == "" {
return
}
var quoteMsg struct {
Text string `json:"text,omitempty"`
QuoteMessage *sdkws.MsgData `json:"quoteMessage,omitempty"`
MessageEntityList json.RawMessage `json:"messageEntityList,omitempty"`
}
if err := json.Unmarshal([]byte(msg.Msg.Content), &quoteMsg); err != nil {
log.ZError(ctx, "json.Unmarshal", err)
return
}
if quoteMsg.QuoteMessage == nil || quoteMsg.QuoteMessage.ContentType == constant.MsgRevokeNotification {
return
}
var msgs []*unrelationtb.MsgInfoModel
if v, ok := cache[quoteMsg.QuoteMessage.Seq]; ok {
msgs = v
} else {
if quoteMsg.QuoteMessage.Seq > 0 {
ms, err := db.msgDocDatabase.GetMsgBySeqIndexIn1Doc(ctx, userID, db.msg.GetDocID(conversationID, quoteMsg.QuoteMessage.Seq), []int64{quoteMsg.QuoteMessage.Seq})
if err != nil {
log.ZError(ctx, "GetMsgBySeqIndexIn1Doc", err, "conversationID", conversationID, "seq", quoteMsg.QuoteMessage.Seq)
return
}
msgs = ms
cache[quoteMsg.QuoteMessage.Seq] = ms
}
}
if len(msgs) != 0 && msgs[0].Msg.ContentType != constant.MsgRevokeNotification {
return
}
quoteMsg.QuoteMessage.ContentType = constant.MsgRevokeNotification
if len(msgs) > 0 {
quoteMsg.QuoteMessage.Content = []byte(msgs[0].Msg.Content)
} else {
quoteMsg.QuoteMessage.Content = []byte("{}")
}
data, err := json.Marshal(&quoteMsg)
if err != nil {
log.ZError(ctx, "json.Marshal", err)
return
}
msg.Msg.Content = string(data)
if _, err := db.msgDocDatabase.UpdateMsg(ctx, db.msg.GetDocID(conversationID, msg.Msg.Seq), db.msg.GetMsgIndex(msg.Msg.Seq), "msg", msg.Msg); err != nil {
log.ZError(ctx, "UpdateMsgContent", err)
}
}
func (db *commonMsgDatabase) findMsgInfoBySeq(ctx context.Context, userID, docID string, conversationID string, seqs []int64) (totalMsgs []*unrelationtb.MsgInfoModel, err error) {
msgs, err := db.msgDocDatabase.GetMsgBySeqIndexIn1Doc(ctx, userID, docID, seqs)
if err != nil {
return nil, err
}
tempCache := make(map[int64][]*unrelationtb.MsgInfoModel)
for _, msg := range msgs {
db.handlerDBMsg(ctx, tempCache, userID, conversationID, msg)
}
return msgs, err
}
@@ -422,7 +489,7 @@ func (db *commonMsgDatabase) getMsgBySeqsRange(ctx context.Context, userID strin
log.ZDebug(ctx, "getMsgBySeqsRange", "conversationID", conversationID, "allSeqs", allSeqs, "begin", begin, "end", end)
for docID, seqs := range db.msg.GetDocIDSeqsMap(conversationID, allSeqs) {
log.ZDebug(ctx, "getMsgBySeqsRange", "docID", docID, "seqs", seqs)
msgs, err := db.findMsgInfoBySeq(ctx, userID, docID, seqs)
msgs, err := db.findMsgInfoBySeq(ctx, userID, docID, conversationID, seqs)
if err != nil {
return nil, err
}
@@ -591,16 +658,26 @@ func (db *commonMsgDatabase) GetMsgBySeqsRange(ctx context.Context, userID strin
func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, conversationID string, seqs []int64) (int64, int64, []*sdkws.MsgData, error) {
userMinSeq, err := db.cache.GetConversationUserMinSeq(ctx, conversationID, userID)
if err != nil && errs.Unwrap(err) != redis.Nil {
return 0, 0, nil, err
if err != nil {
log.ZError(ctx, "cache.GetConversationUserMinSeq error", err)
if errs.Unwrap(err) != redis.Nil {
return 0, 0, nil, err
}
}
minSeq, err := db.cache.GetMinSeq(ctx, conversationID)
if err != nil && errs.Unwrap(err) != redis.Nil {
return 0, 0, nil, err
if err != nil {
log.ZError(ctx, "cache.GetMinSeq error", err)
if errs.Unwrap(err) != redis.Nil {
return 0, 0, nil, err
}
}
maxSeq, err := db.cache.GetMaxSeq(ctx, conversationID)
if err != nil && errs.Unwrap(err) != redis.Nil {
return 0, 0, nil, err
if err != nil {
log.ZError(ctx, "cache.GetMaxSeq error", err)
if errs.Unwrap(err) != redis.Nil {
return 0, 0, nil, err
}
}
if userMinSeq < minSeq {
minSeq = userMinSeq
@@ -613,34 +690,16 @@ func (db *commonMsgDatabase) GetMsgBySeqs(ctx context.Context, userID string, co
}
successMsgs, failedSeqs, err := db.cache.GetMessagesBySeq(ctx, conversationID, newSeqs)
if err != nil {
if err != redis.Nil {
log.ZError(ctx, "get message from redis exception", err, "failedSeqs", failedSeqs, "conversationID", conversationID)
}
log.ZError(ctx, "get message from redis exception", err, "failedSeqs", failedSeqs, "conversationID", conversationID)
}
log.ZInfo(
ctx,
"db.cache.GetMessagesBySeq",
"userID",
userID,
"conversationID",
conversationID,
"seqs",
seqs,
"successMsgs",
len(successMsgs),
"failedSeqs",
failedSeqs,
"conversationID",
conversationID,
)
log.ZInfo(ctx, "db.cache.GetMessagesBySeq", "userID", userID, "conversationID", conversationID, "seqs", seqs, "successMsgs",
len(successMsgs), "failedSeqs", failedSeqs, "conversationID", conversationID)
if len(failedSeqs) > 0 {
mongoMsgs, err := db.getMsgBySeqs(ctx, userID, conversationID, failedSeqs)
if err != nil {
return 0, 0, nil, err
}
successMsgs = append(successMsgs, mongoMsgs...)
}
return minSeq, maxSeq, successMsgs, nil
+16 -36
View File
@@ -144,9 +144,9 @@ func Test_BatchInsertChat2DB(t *testing.T) {
}
func GetDB() *commonMsgDatabase {
config.Config.Mongo.Address = []string{"192.168.44.128:37017"}
config.Config.Mongo.Address = []string{"203.56.175.233:37017"}
// config.Config.Mongo.Timeout = 60
config.Config.Mongo.Database = "openIM"
config.Config.Mongo.Database = "openIM_v3"
// config.Config.Mongo.Source = "admin"
config.Config.Mongo.Username = "root"
config.Config.Mongo.Password = "openIM123"
@@ -232,37 +232,17 @@ func Test_FindBySeq(t *testing.T) {
// }
//}
//func Test_Delete1(t *testing.T) {
// config.Config.Mongo.DBAddress = []string{"192.168.44.128:37017"}
// config.Config.Mongo.DBTimeout = 60
// config.Config.Mongo.DBDatabase = "openIM"
// config.Config.Mongo.DBSource = "admin"
// config.Config.Mongo.DBUserName = "root"
// config.Config.Mongo.DBPassword = "openIM123"
// config.Config.Mongo.DBMaxPoolSize = 100
// config.Config.Mongo.DBRetainChatRecords = 3650
// config.Config.Mongo.ChatRecordsClearTime = "0 2 * * 3"
//
// mongo, err := unrelation.NewMongo()
// if err != nil {
// panic(err)
// }
// err = mongo.GetDatabase().Client().Ping(context.Background(), nil)
// if err != nil {
// panic(err)
// }
//
// c := mongo.GetClient().Database("openIM").Collection("msg")
//
// var o unrelationtb.MsgDocModel
//
// err = c.FindOne(context.Background(), bson.M{"doc_id": "test:0"}).Decode(&o)
// if err != nil {
// panic(err)
// }
//
// for i, model := range o.Msg {
// fmt.Println(i, model == nil)
// }
//
//}
func TestName(t *testing.T) {
db := GetDB()
var seqs []int64
for i := int64(1); i <= 4; i++ {
seqs = append(seqs, i)
}
msgs, err := db.getMsgBySeqsRange(context.Background(), "4931176757", "si_3866692501_4931176757", seqs, seqs[0], seqs[len(seqs)-1])
if err != nil {
t.Fatal(err)
}
t.Log(msgs)
}
-1
View File
@@ -175,7 +175,6 @@ func (c *Controller) CompleteUpload(ctx context.Context, uploadID string, partHa
return nil, err
}
if md5Sum := md5.Sum([]byte(strings.Join(partHashs, partSeparator))); hex.EncodeToString(md5Sum[:]) != upload.Hash {
fmt.Println("CompleteUpload sum:", hex.EncodeToString(md5Sum[:]), "upload hash:", upload.Hash)
return nil, errors.New("md5 mismatching")
}
if info, err := c.StatObject(ctx, c.HashPath(upload.Hash)); err == nil {
+1
View File
@@ -0,0 +1 @@
package kodo
+323
View File
@@ -0,0 +1,323 @@
package kodo
import (
"context"
"errors"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
awss3config "github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
awss3 "github.com/aws/aws-sdk-go-v2/service/s3"
awss3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/db/s3"
"github.com/qiniu/go-sdk/v7/auth"
"github.com/qiniu/go-sdk/v7/storage"
)
const (
minPartSize = 1024 * 1024 * 1 // 1MB
maxPartSize = 1024 * 1024 * 1024 * 5 // 5GB
maxNumSize = 10000
)
type Kodo struct {
AccessKey string
SecretKey string
Region string
Token string
Endpoint string
BucketURL string
Auth *auth.Credentials
Client *awss3.Client
PresignClient *awss3.PresignClient
}
func NewKodo() (s3.Interface, error) {
conf := config.Config.Object.Kodo
//init client
cfg, err := awss3config.LoadDefaultConfig(context.TODO(),
awss3config.WithRegion(conf.Bucket),
awss3config.WithEndpointResolverWithOptions(
aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{URL: conf.Endpoint}, nil
})),
awss3config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(
conf.AccessKeyID,
conf.AccessKeySecret,
conf.SessionToken),
),
)
if err != nil {
panic(err)
}
client := awss3.NewFromConfig(cfg)
presignClient := awss3.NewPresignClient(client)
return &Kodo{
AccessKey: conf.AccessKeyID,
SecretKey: conf.AccessKeySecret,
Region: conf.Bucket,
BucketURL: conf.BucketURL,
Auth: auth.New(conf.AccessKeyID, conf.AccessKeySecret),
Client: client,
PresignClient: presignClient,
}, nil
}
func (k Kodo) Engine() string {
return "kodo"
}
func (k Kodo) PartLimit() *s3.PartLimit {
return &s3.PartLimit{
MinPartSize: minPartSize,
MaxPartSize: maxPartSize,
MaxNumSize: maxNumSize,
}
}
func (k Kodo) InitiateMultipartUpload(ctx context.Context, name string) (*s3.InitiateMultipartUploadResult, error) {
result, err := k.Client.CreateMultipartUpload(ctx, &awss3.CreateMultipartUploadInput{
Bucket: aws.String(k.Region),
Key: aws.String(name),
})
if err != nil {
return nil, err
}
return &s3.InitiateMultipartUploadResult{
UploadID: aws.ToString(result.UploadId),
Bucket: aws.ToString(result.Bucket),
Key: aws.ToString(result.Key),
}, nil
}
func (k Kodo) CompleteMultipartUpload(ctx context.Context, uploadID string, name string, parts []s3.Part) (*s3.CompleteMultipartUploadResult, error) {
kodoParts := make([]awss3types.CompletedPart, len(parts))
for i, part := range parts {
kodoParts[i] = awss3types.CompletedPart{
PartNumber: aws.Int32(int32(part.PartNumber)),
ETag: aws.String(part.ETag),
}
}
result, err := k.Client.CompleteMultipartUpload(ctx, &awss3.CompleteMultipartUploadInput{
Bucket: aws.String(k.Region),
Key: aws.String(name),
UploadId: aws.String(uploadID),
MultipartUpload: &awss3types.CompletedMultipartUpload{Parts: kodoParts},
})
if err != nil {
return nil, err
}
return &s3.CompleteMultipartUploadResult{
Location: aws.ToString(result.Location),
Bucket: aws.ToString(result.Bucket),
Key: aws.ToString(result.Key),
ETag: strings.ToLower(strings.ReplaceAll(aws.ToString(result.ETag), `"`, ``)),
}, nil
}
func (k Kodo) PartSize(ctx context.Context, size int64) (int64, error) {
if size <= 0 {
return 0, errors.New("size must be greater than 0")
}
if size > maxPartSize*maxNumSize {
return 0, fmt.Errorf("size must be less than %db", maxPartSize*maxNumSize)
}
if size <= minPartSize*maxNumSize {
return minPartSize, nil
}
partSize := size / maxNumSize
if size%maxNumSize != 0 {
partSize++
}
return partSize, nil
}
func (k Kodo) AuthSign(ctx context.Context, uploadID string, name string, expire time.Duration, partNumbers []int) (*s3.AuthSignResult, error) {
result := s3.AuthSignResult{
URL: k.BucketURL + "/" + name,
Query: url.Values{"uploadId": {uploadID}},
Header: make(http.Header),
Parts: make([]s3.SignPart, len(partNumbers)),
}
for i, partNumber := range partNumbers {
part, _ := k.PresignClient.PresignUploadPart(ctx, &awss3.UploadPartInput{
Bucket: aws.String(k.Region),
UploadId: aws.String(uploadID),
Key: aws.String(name),
PartNumber: aws.Int32(int32(partNumber)),
})
result.Parts[i] = s3.SignPart{
PartNumber: partNumber,
URL: part.URL,
Header: part.SignedHeader,
}
}
return &result, nil
}
func (k Kodo) PresignedPutObject(ctx context.Context, name string, expire time.Duration) (string, error) {
object, err := k.PresignClient.PresignPutObject(ctx, &awss3.PutObjectInput{
Bucket: aws.String(k.Region),
Key: aws.String(name),
}, func(po *awss3.PresignOptions) {
po.Expires = expire
})
return object.URL, err
}
func (k Kodo) DeleteObject(ctx context.Context, name string) error {
_, err := k.Client.DeleteObject(ctx, &awss3.DeleteObjectInput{
Bucket: aws.String(k.Region),
Key: aws.String(name),
})
return err
}
func (k Kodo) CopyObject(ctx context.Context, src string, dst string) (*s3.CopyObjectInfo, error) {
result, err := k.Client.CopyObject(ctx, &awss3.CopyObjectInput{
Bucket: aws.String(k.Region),
CopySource: aws.String(k.Region + "/" + src),
Key: aws.String(dst),
})
if err != nil {
return nil, err
}
return &s3.CopyObjectInfo{
Key: dst,
ETag: strings.ToLower(strings.ReplaceAll(aws.ToString(result.CopyObjectResult.ETag), `"`, ``)),
}, nil
}
func (k Kodo) StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error) {
info, err := k.Client.HeadObject(ctx, &awss3.HeadObjectInput{
Bucket: aws.String(k.Region),
Key: aws.String(name),
})
if err != nil {
return nil, err
}
res := &s3.ObjectInfo{Key: name}
res.Size = aws.ToInt64(info.ContentLength)
res.ETag = strings.ToLower(strings.ReplaceAll(aws.ToString(info.ETag), `"`, ``))
return res, nil
}
func (k Kodo) IsNotFound(err error) bool {
return true
}
func (k Kodo) AbortMultipartUpload(ctx context.Context, uploadID string, name string) error {
_, err := k.Client.AbortMultipartUpload(ctx, &awss3.AbortMultipartUploadInput{
UploadId: aws.String(uploadID),
Bucket: aws.String(k.Region),
Key: aws.String(name),
})
return err
}
func (k Kodo) ListUploadedParts(ctx context.Context, uploadID string, name string, partNumberMarker int, maxParts int) (*s3.ListUploadedPartsResult, error) {
result, err := k.Client.ListParts(ctx, &awss3.ListPartsInput{
Key: aws.String(name),
UploadId: aws.String(uploadID),
Bucket: aws.String(k.Region),
MaxParts: aws.Int32(int32(maxParts)),
PartNumberMarker: aws.String(strconv.Itoa(partNumberMarker)),
})
if err != nil {
return nil, err
}
res := &s3.ListUploadedPartsResult{
Key: aws.ToString(result.Key),
UploadID: aws.ToString(result.UploadId),
MaxParts: int(aws.ToInt32(result.MaxParts)),
UploadedParts: make([]s3.UploadedPart, len(result.Parts)),
}
// int to string
NextPartNumberMarker, err := strconv.Atoi(aws.ToString(result.NextPartNumberMarker))
if err != nil {
return nil, err
}
res.NextPartNumberMarker = NextPartNumberMarker
for i, part := range result.Parts {
res.UploadedParts[i] = s3.UploadedPart{
PartNumber: int(aws.ToInt32(part.PartNumber)),
LastModified: aws.ToTime(part.LastModified),
ETag: aws.ToString(part.ETag),
Size: aws.ToInt64(part.Size),
}
}
return res, nil
}
func (k Kodo) AccessURL(ctx context.Context, name string, expire time.Duration, opt *s3.AccessURLOption) (string, error) {
//get object head
info, err := k.Client.HeadObject(ctx, &awss3.HeadObjectInput{
Bucket: aws.String(k.Region),
Key: aws.String(name),
})
if err != nil {
return "", errors.New("AccessURL object not found")
}
if opt != nil {
if opt.ContentType != aws.ToString(info.ContentType) {
//修改文件类型
err := k.SetObjectContentType(ctx, name, opt.ContentType)
if err != nil {
return "", errors.New("AccessURL setContentType error")
}
}
}
imageMogr := ""
//image dispose
if opt != nil {
if opt.Image != nil {
//https://developer.qiniu.com/dora/8255/the-zoom
process := ""
if opt.Image.Width > 0 {
process += strconv.Itoa(opt.Image.Width) + "x"
}
if opt.Image.Height > 0 {
if opt.Image.Width > 0 {
process += strconv.Itoa(opt.Image.Height)
} else {
process += "x" + strconv.Itoa(opt.Image.Height)
}
}
imageMogr = "imageMogr2/thumbnail/" + process
}
}
//expire
deadline := time.Now().Add(time.Second * expire).Unix()
domain := k.BucketURL
query := url.Values{}
if opt != nil && opt.Filename != "" {
query.Add("attname", opt.Filename)
}
privateURL := storage.MakePrivateURLv2WithQuery(k.Auth, domain, name, query, deadline)
if imageMogr != "" {
privateURL += "&" + imageMogr
}
return privateURL, nil
}
func (k *Kodo) SetObjectContentType(ctx context.Context, name string, contentType string) error {
//set object content-type
_, err := k.Client.CopyObject(ctx, &awss3.CopyObjectInput{
Bucket: aws.String(k.Region),
CopySource: aws.String(k.Region + "/" + name),
Key: aws.String(name),
ContentType: aws.String(contentType),
MetadataDirective: awss3types.MetadataDirectiveReplace,
})
return err
}
+73 -63
View File
@@ -3,7 +3,7 @@ package ginprometheus
import (
"bytes"
"fmt"
"io/ioutil"
"io"
"net/http"
"os"
"strconv"
@@ -17,40 +17,42 @@ import (
var defaultMetricPath = "/metrics"
// counter, counter_vec, gauge, gauge_vec,
// histogram, histogram_vec, summary, summary_vec
var reqCnt = &Metric{
ID: "reqCnt",
Name: "requests_total",
Description: "How many HTTP requests processed, partitioned by status code and HTTP method.",
Type: "counter_vec",
Args: []string{"code", "method", "handler", "host", "url"}}
// histogram, histogram_vec, summary, summary_vec.
var (
reqCounter = &Metric{
ID: "reqCnt",
Name: "requests_total",
Description: "How many HTTP requests processed, partitioned by status code and HTTP method.",
Type: "counter_vec",
Args: []string{"code", "method", "handler", "host", "url"}}
var reqDur = &Metric{
ID: "reqDur",
Name: "request_duration_seconds",
Description: "The HTTP request latencies in seconds.",
Type: "histogram_vec",
Args: []string{"code", "method", "url"},
}
reqDuration = &Metric{
ID: "reqDur",
Name: "request_duration_seconds",
Description: "The HTTP request latencies in seconds.",
Type: "histogram_vec",
Args: []string{"code", "method", "url"},
}
var resSz = &Metric{
ID: "resSz",
Name: "response_size_bytes",
Description: "The HTTP response sizes in bytes.",
Type: "summary"}
resSize = &Metric{
ID: "resSz",
Name: "response_size_bytes",
Description: "The HTTP response sizes in bytes.",
Type: "summary"}
var reqSz = &Metric{
ID: "reqSz",
Name: "request_size_bytes",
Description: "The HTTP request sizes in bytes.",
Type: "summary"}
reqSize = &Metric{
ID: "reqSz",
Name: "request_size_bytes",
Description: "The HTTP request sizes in bytes.",
Type: "summary"}
var standardMetrics = []*Metric{
reqCnt,
reqDur,
resSz,
reqSz,
}
standardMetrics = []*Metric{
reqCounter,
reqDuration,
resSize,
reqSize,
}
)
/*
RequestCounterURLLabelMappingFn is a function which can be supplied to the middleware to control
@@ -74,7 +76,7 @@ which would map "/customer/alice" and "/customer/bob" to their template "/custom
type RequestCounterURLLabelMappingFn func(c *gin.Context) string
// Metric is a definition for the name, description, type, ID, and
// prometheus.Collector type (i.e. CounterVec, Summary, etc) of each metric
// prometheus.Collector type (i.e. CounterVec, Summary, etc) of each metric.
type Metric struct {
MetricCollector prometheus.Collector
ID string
@@ -84,7 +86,7 @@ type Metric struct {
Args []string
}
// Prometheus contains the metrics gathered by the instance and its path
// Prometheus contains the metrics gathered by the instance and its path.
type Prometheus struct {
reqCnt *prometheus.CounterVec
reqDur *prometheus.HistogramVec
@@ -102,7 +104,7 @@ type Prometheus struct {
URLLabelFromContext string
}
// PrometheusPushGateway contains the configuration for pushing to a Prometheus pushgateway (optional)
// PrometheusPushGateway contains the configuration for pushing to a Prometheus pushgateway (optional).
type PrometheusPushGateway struct {
// Push interval in seconds
@@ -112,7 +114,7 @@ type PrometheusPushGateway struct {
// where JOBNAME can be any string of your choice
PushGatewayURL string
// Local metrics URL where metrics are fetched from, this could be ommited in the future
// Local metrics URL where metrics are fetched from, this could be omitted in the future
// if implemented using prometheus common/expfmt instead
MetricsURL string
@@ -120,9 +122,11 @@ type PrometheusPushGateway struct {
Job string
}
// NewPrometheus generates a new set of metrics with a certain subsystem name
// NewPrometheus generates a new set of metrics with a certain subsystem name.
func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus {
subsystem = "app"
if subsystem == "" {
subsystem = "app"
}
var metricsList []*Metric
@@ -131,16 +135,13 @@ func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus
} else if len(customMetricsList) == 1 {
metricsList = customMetricsList[0]
}
for _, metric := range standardMetrics {
metricsList = append(metricsList, metric)
}
metricsList = append(metricsList, standardMetrics...)
p := &Prometheus{
MetricsList: metricsList,
MetricsPath: defaultMetricPath,
ReqCntURLLabelMappingFn: func(c *gin.Context) string {
return c.Request.URL.Path
return c.FullPath() // e.g. /user/:id , /user/:id/info
},
}
@@ -150,7 +151,7 @@ func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus
}
// SetPushGateway sends metrics to a remote pushgateway exposed on pushGatewayURL
// every pushIntervalSeconds. Metrics are fetched from metricsURL
// every pushIntervalSeconds. Metrics are fetched from metricsURL.
func (p *Prometheus) SetPushGateway(pushGatewayURL, metricsURL string, pushIntervalSeconds time.Duration) {
p.Ppg.PushGatewayURL = pushGatewayURL
p.Ppg.MetricsURL = metricsURL
@@ -158,13 +159,13 @@ func (p *Prometheus) SetPushGateway(pushGatewayURL, metricsURL string, pushInter
p.startPushTicker()
}
// SetPushGatewayJob job name, defaults to "gin"
// SetPushGatewayJob job name, defaults to "gin".
func (p *Prometheus) SetPushGatewayJob(j string) {
p.Ppg.Job = j
}
// SetListenAddress for exposing metrics on address. If not set, it will be exposed at the
// same address of the gin engine that is being used
// same address of the gin engine that is being used.
func (p *Prometheus) SetListenAddress(address string) {
p.listenAddress = address
if p.listenAddress != "" {
@@ -181,7 +182,7 @@ func (p *Prometheus) SetListenAddressWithRouter(listenAddress string, r *gin.Eng
}
}
// SetMetricsPath set metrics paths
// SetMetricsPath set metrics paths.
func (p *Prometheus) SetMetricsPath(e *gin.Engine) {
if p.listenAddress != "" {
@@ -192,7 +193,7 @@ func (p *Prometheus) SetMetricsPath(e *gin.Engine) {
}
}
// SetMetricsPathWithAuth set metrics paths with authentication
// SetMetricsPathWithAuth set metrics paths with authentication.
func (p *Prometheus) SetMetricsPathWithAuth(e *gin.Engine, accounts gin.Accounts) {
if p.listenAddress != "" {
@@ -205,34 +206,43 @@ func (p *Prometheus) SetMetricsPathWithAuth(e *gin.Engine, accounts gin.Accounts
}
func (p *Prometheus) runServer() {
if p.listenAddress != "" {
go p.router.Run(p.listenAddress)
}
go p.router.Run(p.listenAddress)
}
func (p *Prometheus) getMetrics() []byte {
response, _ := http.Get(p.Ppg.MetricsURL)
response, err := http.Get(p.Ppg.MetricsURL)
if err != nil {
return nil
}
defer response.Body.Close()
body, _ := ioutil.ReadAll(response.Body)
body, _ := io.ReadAll(response.Body)
return body
}
var hostname, _ = os.Hostname()
func (p *Prometheus) getPushGatewayURL() string {
h, _ := os.Hostname()
if p.Ppg.Job == "" {
p.Ppg.Job = "gin"
}
return p.Ppg.PushGatewayURL + "/metrics/job/" + p.Ppg.Job + "/instance/" + h
return p.Ppg.PushGatewayURL + "/metrics/job/" + p.Ppg.Job + "/instance/" + hostname
}
func (p *Prometheus) sendMetricsToPushGateway(metrics []byte) {
req, err := http.NewRequest("POST", p.getPushGatewayURL(), bytes.NewBuffer(metrics))
if err != nil {
return
}
client := &http.Client{}
if _, err = client.Do(req); err != nil {
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error sending to push gateway error:", err.Error())
}
resp.Body.Close()
}
func (p *Prometheus) startPushTicker() {
@@ -244,7 +254,7 @@ func (p *Prometheus) startPushTicker() {
}()
}
// NewMetric associates prometheus.Collector based on Metric.Type
// NewMetric associates prometheus.Collector based on Metric.Type.
func NewMetric(m *Metric, subsystem string) prometheus.Collector {
var metric prometheus.Collector
switch m.Type {
@@ -321,20 +331,20 @@ func NewMetric(m *Metric, subsystem string) prometheus.Collector {
}
func (p *Prometheus) registerMetrics(subsystem string) {
for _, metricDef := range p.MetricsList {
metric := NewMetric(metricDef, subsystem)
if err := prometheus.Register(metric); err != nil {
fmt.Println("could not be registered in Prometheus,metricDef.Name:", metricDef.Name, " error:", err.Error())
}
switch metricDef {
case reqCnt:
case reqCounter:
p.reqCnt = metric.(*prometheus.CounterVec)
case reqDur:
case reqDuration:
p.reqDur = metric.(*prometheus.HistogramVec)
case resSz:
case resSize:
p.resSz = metric.(prometheus.Summary)
case reqSz:
case reqSize:
p.reqSz = metric.(prometheus.Summary)
}
metricDef.MetricCollector = metric
@@ -353,7 +363,7 @@ func (p *Prometheus) UseWithAuth(e *gin.Engine, accounts gin.Accounts) {
p.SetMetricsPathWithAuth(e, accounts)
}
// HandlerFunc defines handler function for middleware
// HandlerFunc defines handler function for middleware.
func (p *Prometheus) HandlerFunc() gin.HandlerFunc {
return func(c *gin.Context) {
if c.Request.URL.Path == p.MetricsPath {
@@ -393,7 +403,7 @@ func prometheusHandler() gin.HandlerFunc {
}
func computeApproximateRequestSize(r *http.Request) int {
s := 0
var s int
if r.URL != nil {
s = len(r.URL.Path)
}
+8 -8
View File
@@ -20,7 +20,6 @@ import (
"encoding/json"
"io"
"net/http"
urllib "net/url"
"time"
"github.com/OpenIMSDK/protocol/constant"
@@ -107,17 +106,18 @@ func PostReturn(ctx context.Context, url string, header map[string]string, input
}
func callBackPostReturn(ctx context.Context, url, command string, input interface{}, output callbackstruct.CallbackResp, callbackConfig config.CallBackConfig) error {
defer log.ZDebug(ctx, "callback", "url", url, "command", command, "input", input, "callbackConfig", callbackConfig)
v := urllib.Values{}
v.Set(constant.CallbackCommand, command)
url = url + "?" + v.Encode()
defer log.ZDebug(ctx, "callback", "url", url, "command", command, "input", input, "output", output, "callbackConfig", callbackConfig)
//
//v := urllib.Values{}
//v.Set(constant.CallbackCommand, command)
//url = url + "/" + v.Encode()
url = url + "/" + command
b, err := Post(ctx, url, nil, input, callbackConfig.CallbackTimeOut)
if err != nil {
if callbackConfig.CallbackFailedContinue != nil && *callbackConfig.CallbackFailedContinue {
log.ZWarn(ctx, "callback failed but continue", err, "url", url)
return errs.ErrCallbackContinue
return nil
}
return errs.ErrNetwork.Wrap(err.Error())
}
@@ -125,7 +125,7 @@ func callBackPostReturn(ctx context.Context, url, command string, input interfac
if err = json.Unmarshal(b, output); err != nil {
if callbackConfig.CallbackFailedContinue != nil && *callbackConfig.CallbackFailedContinue {
log.ZWarn(ctx, "callback failed but continue", err, "url", url)
return errs.ErrCallbackContinue
return nil
}
return errs.ErrData.Wrap(err.Error())
}
+18 -1
View File
@@ -15,8 +15,10 @@
package kafka
import (
"bytes"
"context"
"errors"
"strings"
"time"
"github.com/OpenIMSDK/protocol/constant"
@@ -49,8 +51,23 @@ func NewKafkaProducer(addr []string, topic string) *Producer {
p.config = sarama.NewConfig() // Instantiate a sarama Config
p.config.Producer.Return.Successes = true // Whether to enable the successes channel to be notified after the message is sent successfully
p.config.Producer.Return.Errors = true
p.config.Producer.RequiredAcks = sarama.WaitForAll // Set producer Message Reply level 0 1 all
p.config.Producer.Partitioner = sarama.NewHashPartitioner // Set the hash-key automatic hash partition. When sending a message, you must specify the key value of the message. If there is no key, the partition will be selected randomly
var producerAck = sarama.WaitForAll // default: WaitForAll
switch strings.ToLower(config.Config.Kafka.ProducerAck) {
case "no_response":
producerAck = sarama.NoResponse
case "wait_for_local":
producerAck = sarama.WaitForLocal
case "wait_for_all":
producerAck = sarama.WaitForAll
}
p.config.Producer.RequiredAcks = producerAck
var compress = sarama.CompressionNone // default: no compress
_ = compress.UnmarshalText(bytes.ToLower([]byte(config.Config.Kafka.CompressType)))
p.config.Producer.Compression = compress
if config.Config.Kafka.Username != "" && config.Config.Kafka.Password != "" {
p.config.Net.SASL.Enable = true
p.config.Net.SASL.User = config.Config.Kafka.Username
+3 -2
View File
@@ -2,10 +2,11 @@ package prommetrics
import (
grpc_prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus"
)
func NewGrpcPromObj(cusMetrics []prometheus.Collector) (*prometheus.Registry, *grpc_prometheus.ServerMetrics, error) {
+2 -1
View File
@@ -3,9 +3,10 @@ package prommetrics
import (
"testing"
config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/assert"
config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config"
)
func TestNewGrpcPromObj(t *testing.T) {
+51 -7
View File
@@ -15,23 +15,31 @@
package startrpc
import (
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"os/signal"
"strconv"
"sync"
"syscall"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"golang.org/x/sync/errgroup"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
grpcprometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister"
"github.com/OpenIMSDK/tools/discoveryregistry"
"github.com/OpenIMSDK/tools/mw"
"github.com/OpenIMSDK/tools/network"
@@ -55,31 +63,37 @@ func Start(
if err != nil {
return err
}
defer listener.Close()
client, err := kdisc.NewDiscoveryRegister(config.Config.Envs.Discovery)
if err != nil {
return utils.Wrap1(err)
}
defer client.Close()
client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()))
registerIP, err := network.GetRpcRegisterIP(config.Config.Rpc.RegisterIP)
if err != nil {
return err
}
var reg *prometheus.Registry
var metric *grpcprometheus.ServerMetrics
// ctx 中间件
if config.Config.Prometheus.Enable {
//////////////////////////
cusMetrics := prommetrics.GetGrpcCusMetrics(rpcRegisterName)
reg, metric, err = prommetrics.NewGrpcPromObj(cusMetrics)
reg, metric, _ = prommetrics.NewGrpcPromObj(cusMetrics)
options = append(options, mw.GrpcServer(), grpc.StreamInterceptor(metric.StreamServerInterceptor()),
grpc.UnaryInterceptor(metric.UnaryServerInterceptor()))
} else {
options = append(options, mw.GrpcServer())
}
srv := grpc.NewServer(options...)
defer srv.GracefulStop()
once := sync.Once{}
defer func() {
once.Do(srv.GracefulStop)
}()
err = rpcFn(client, srv)
if err != nil {
return utils.Wrap1(err)
@@ -93,7 +107,10 @@ func Start(
if err != nil {
return utils.Wrap1(err)
}
go func() {
var wg errgroup.Group
wg.Go(func() error {
if config.Config.Prometheus.Enable && prometheusPort != 0 {
metric.InitializeMetrics(srv)
// Create a HTTP server for prometheus.
@@ -102,7 +119,34 @@ func Start(
log.Fatal("Unable to start a http server.")
}
}
return nil
})
wg.Go(func() error {
return utils.Wrap1(srv.Serve(listener))
})
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
<-sigs
var (
done = make(chan struct{}, 1)
gerr error
)
go func() {
once.Do(srv.GracefulStop)
gerr = wg.Wait()
close(done)
}()
return utils.Wrap1(srv.Serve(listener))
select {
case <-done:
return gerr
case <-time.After(15 * time.Second):
return utils.Wrap1(errors.New("timeout exit"))
}
}
+2 -1
View File
@@ -118,8 +118,9 @@ func GetNotificationConversationIDByConversationID(conversationID string) string
l := strings.Split(conversationID, "_")
if len(l) > 1 {
l[0] = "n"
return conversationID
return strings.Join(l, "_")
}
return ""
}
+1 -1
View File
@@ -39,7 +39,7 @@ scripts/
├── demo.sh # Demonstration or example script.
├── docker-check-service.sh # Docker script to check services' status.
├── docker-start-all.sh # Docker script to start all containers/services.
├── ensure_tag.sh # Ensure correct tags or labeling.
├── ensure-tag.sh # Ensure correct tags or labeling.
├── env_check.sh # Environment verification and checking.
├── gen-swagger-docs.sh # Script to generate Swagger documentation.
├── genconfig.sh # Generate configuration files.
-76
View File
@@ -1,76 +0,0 @@
#!/usr/bin/env bash
# Copyright © 2023 OpenIM. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#Include shell font styles and some basic information
SCRIPTS_ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
OPENIM_ROOT=$(dirname "${SCRIPTS_ROOT}")/..
#Include shell font styles and some basic information
source $SCRIPTS_ROOT/path_info.sh
source $SCRIPTS_ROOT/lib/init.sh
cd $SCRIPTS_ROOT
echo -e "check time synchronize.................................."
t=`curl http://time.akamai.com/?iso -s`
t1=`date -d $t +%s`
t2=`date +%s`
let between=t2-t1
if [[ $between -gt 10 ]] || [[ $between -lt -10 ]]; then
echo -e ${RED_PREFIX}"Warning: The difference between the iso time and the server's time is too large: "$between"s" ${COLOR_SUFFIX}
else
echo -e ${GREEN_PREFIX} "ok: Server time is synchronized " ${COLOR_SUFFIX}
fi
echo -e "check login user........................................"
user=`whoami`
if [ $user == "root" ] ; then
echo -e ${GREEN_PREFIX} "ok: login user is root" ${COLOR_SUFFIX}
else
echo -e ${RED_PREFIX}"Warning: The current user is not root "${COLOR_SUFFIX}
fi
echo -e "check docker............................................"
docker_running=`systemctl status docker | grep running | grep active | wc -l`
docker_version=`docker-compose -v; docker -v`
if [ $docker_running -gt 0 ]; then
echo -e ${GREEN_PREFIX} "ok: docker is running" ${COLOR_SUFFIX}
echo -e ${GREEN_PREFIX} $docker_version ${COLOR_SUFFIX}
else
echo -e ${RED_PREFIX}"docker not running"${COLOR_SUFFIX}
fi
echo -e "check environment......................................."
SYSTEM=`uname -s`
if [ $SYSTEM != "Linux" ] ; then
echo -e ${RED_PREFIX}"Warning: Currently only Linux is supported"${COLOR_SUFFIX}
else
echo -e ${GREEN_PREFIX} "ok: system is linux"${COLOR_SUFFIX}
fi
echo -e "check memory............................................"
available=`free -m | grep Mem | awk '{print $NF}'`
if [ $available -lt 2000 ] ; then
echo -e ${RED_PREFIX}"Warning: Your memory not enough, available is: " "$available"m${COLOR_SUFFIX}"\c"
echo -e ${RED_PREFIX}", must be greater than 2000m"${COLOR_SUFFIX}
else
echo -e ${GREEN_PREFIX} "ok: available memory is: "$available"m${COLOR_SUFFIX}"
fi
+12 -3
View File
@@ -13,8 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
# 本脚本功能:根据 scripts/environment.sh 配置,生成 OPENIM 组件 YAML 配置文件。
# 示例./scripts/genconfig.sh scripts/install/environment.sh scripts/template/config.yaml
# Function of this script: Generate the OPENIM component YAML configuration file according to the scripts/environment.sh configuration.
# eg./scripts/genconfig.sh scripts/install/environment.sh scripts/template/config.yaml
# Read: https://github.com/openimsdk/open-im-server/blob/main/docs/contrib/init-config.md
env_file="$1"
@@ -29,8 +29,17 @@ if [ $# -ne 2 ];then
exit 1
fi
# Check if the required commands exist
openim::util::require-dig
result=$?
if [ $result -ne 0 ]; then
openim::log::info "Please install 'dig' to use this feature."
openim::log::info "Installation instructions:"
openim::log::info " For Ubuntu/Debian: sudo apt-get install dnsutils"
openim::log::info " For CentOS/RedHat: sudo yum install bind-utils"
openim::log::info " For macOS: 'dig' should be preinstalled. If missing, try: brew install bind"
openim::log::info " For Windows: Install BIND9 tools from https://www.isc.org/download/"
openim::log::error_exit "Error: 'dig' command is required but not installed."
fi
source "${env_file}"

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