coderwander-main
skiffer-git 4 months ago
commit 69296539e2

@ -5,6 +5,9 @@ ZOOKEEPER_IMAGE=bitnami/zookeeper:3.8
KAFKA_IMAGE=bitnami/kafka:3.5.1 KAFKA_IMAGE=bitnami/kafka:3.5.1
MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z MINIO_IMAGE=minio/minio:RELEASE.2024-01-11T07-46-16Z
ETCD_IMAGE=quay.io/coreos/etcd:v3.5.13 ETCD_IMAGE=quay.io/coreos/etcd:v3.5.13
PROMETHEUS_IMAGE=prom/prometheus:v2.45.6
ALERTMANAGER_IMAGE=prom/alertmanager:v0.27.0
GRAFANA_IMAGE=grafana/grafana:11.0.1
OPENIM_WEB_FRONT_IMAGE=openim/openim-web-front:release-v3.5.1 OPENIM_WEB_FRONT_IMAGE=openim/openim-web-front:release-v3.5.1
OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.7 OPENIM_ADMIN_FRONT_IMAGE=openim/openim-admin-front:release-v1.7

4
.gitignore vendored

@ -34,11 +34,7 @@ deployments/charts/generated-configs/
### OpenIM Config ### ### OpenIM Config ###
.env .env
config/config.yaml config/config.yaml
config/alertmanager.yml
config/prometheus.yml
config/email.tmpl
config/notification.yaml config/notification.yaml
config/instance-down-rules.yml
### OpenIM deploy ### ### OpenIM deploy ###
deployments/openim-server/charts deployments/openim-server/charts

@ -43,7 +43,7 @@ COPY --from=builder $SERVER_DIR/start-config.yml $SERVER_DIR/
COPY --from=builder $SERVER_DIR/go.mod $SERVER_DIR/ COPY --from=builder $SERVER_DIR/go.mod $SERVER_DIR/
COPY --from=builder $SERVER_DIR/go.sum $SERVER_DIR/ COPY --from=builder $SERVER_DIR/go.sum $SERVER_DIR/
RUN go get github.com/openimsdk/gomake@v0.0.13 RUN go get github.com/openimsdk/gomake@v0.0.14-alpha.5
# Set the command to run when the container starts # Set the command to run when the container starts
ENTRYPOINT ["sh", "-c", "mage start && tail -f /dev/null"] ENTRYPOINT ["sh", "-c", "mage start && tail -f /dev/null"]

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="./README.md">Englist</a> · <a href="./README.md">English</a> ·
<a href="./README_zh_CN.md">中文</a> · <a href="./README_zh_CN.md">中文</a> ·
<a href="./docs/readme/README_uk.md">Українська</a> · <a href="./docs/readme/README_uk.md">Українська</a> ·
<a href="./docs/readme/README_cs.md">Česky</a> · <a href="./docs/readme/README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="./README.md">Englist</a> · <a href="./README.md">English</a> ·
<a href="./README_zh_CN.md">中文</a> · <a href="./README_zh_CN.md">中文</a> ·
<a href="./docs/readme/README_uk.md">Українська</a> · <a href="./docs/readme/README_uk.md">Українська</a> ·
<a href="./docs/readme/README_cs.md">Česky</a> · <a href="./docs/readme/README_cs.md">Česky</a> ·

@ -25,5 +25,4 @@ func main() {
if err := cmd.NewApiCmd().Exec(); err != nil { if err := cmd.NewApiCmd().Exec(); err != nil {
program.ExitWithError(err) program.ExitWithError(err)
} }
} }

@ -0,0 +1,34 @@
global:
resolve_timeout: 5m
smtp_from: alert@openim.io
smtp_smarthost: smtp.163.com:465
smtp_auth_username: alert@openim.io
smtp_auth_password: YOURAUTHPASSWORD
smtp_require_tls: false
smtp_hello: xxx
templates:
- /etc/alertmanager/email.tmpl
route:
group_by: [ 'alertname' ]
group_wait: 5s
group_interval: 5s
repeat_interval: 5m
receiver: email
routes:
- matchers:
- alertname = "XXX"
group_by: [ 'instance' ]
group_wait: 5s
group_interval: 5s
repeat_interval: 5m
receiver: email
receivers:
- name: email
email_configs:
- to: 'alert@example.com'
html: '{{ template "email.to.html" . }}'
headers: { Subject: "[OPENIM-SERVER]Alarm" }
send_resolved: true

@ -0,0 +1,36 @@
{{ define "email.to.html" }}
{{ if eq .Status "firing" }}
{{ range .Alerts }}
<!-- Begin of OpenIM Alert -->
<div style="border:1px solid #ccc; padding:10px; margin-bottom:10px;">
<h3>OpenIM Alert</h3>
<p><strong>Alert Status:</strong> firing</p>
<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 }}
{{ else if eq .Status "resolved" }}
{{ range .Alerts }}
<!-- Begin of OpenIM Alert -->
<div style="border:1px solid #ccc; padding:10px; margin-bottom:10px;">
<h3>OpenIM Alert</h3>
<p><strong>Alert Status:</strong> resolved</p>
<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 }}
<!-- End of OpenIM Alert -->
{{ end }}
{{ end }}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,44 @@
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."
- name: registrations_few
rules:
- alert: RegistrationsFew
expr: increase(user_login_total[1h]) == 0
for: 1m
labels:
severity: info
annotations:
summary: "Too few registrations within the time frame"
description: "The number of registrations in the last hour is 0. There might be some issues."
- name: messages_few
rules:
- alert: MessagesFew
expr: (increase(single_chat_msg_process_success_total[1h])+increase(group_chat_msg_process_success_total[1h])) == 0
for: 1m
labels:
severity: info
annotations:
summary: "Too few messages within the time frame"
description: "The number of messages sent in the last hour is 0. There might be some issues."

@ -10,4 +10,5 @@ remainLogLevel: 6
isStdout: false isStdout: false
# Whether to log in JSON format, default is acceptable # Whether to log in JSON format, default is acceptable
isJson: false isJson: false
# output simplify log when KeyAndValues's value len is bigger than 50 in rpc method log
isSimplify: true

@ -1,2 +1,3 @@
chatRecordsClearTime: "0 2 * * *" cronExecuteTime: "0 2 * * *"
retainChatRecords: 365 retainChatRecords: 365
fileExpireTime: 90

@ -23,7 +23,9 @@ geTui:
channelID: '' channelID: ''
channelName: '' channelName: ''
fcm: fcm:
serviceAccount: "x.json" # Prioritize using file paths. If the file path is empty, use URL
filePath: "" # File path is concatenated with the parameters passed in through - c(`mage` default pass in `config/`) and filePath.
authURL: "" # Must start with https or http.
jpns: jpns:
appKey: '' appKey: ''
masterSecret: '' masterSecret: ''

@ -29,4 +29,12 @@ object:
accessKeyID: '' accessKeyID: ''
accessKeySecret: '' accessKeySecret: ''
sessionToken: '' sessionToken: ''
publicRead: false
kodo:
endpoint: "http://s3.cn-south-1.qiniucs.com"
bucket: "kodo-bucket-test"
bucketURL: "http://kodo-bucket-test-oetobfb.qiniudns.com"
accessKeyID: ''
accessKeySecret: ''
sessionToken: ''
publicRead: false publicRead: false

@ -0,0 +1,83 @@
# 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: ['internal_ip: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
# prometheus fetches application services
- job_name: 'node_exporter'
static_configs:
- targets: [ 'internal_ip:20114' ]
- job_name: 'openimserver-openim-api'
static_configs:
- targets: [ 'internal_ip:20113' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-msggateway'
static_configs:
- targets: [ 'internal_ip:20112' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-msgtransfer'
static_configs:
- targets: [ 'internal_ip:20111', 'internal_ip:20110', 'internal_ip:20109', 'internal_ip:20108' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-push'
static_configs:
- targets: [ 'internal_ip:20107' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-auth'
static_configs:
- targets: [ 'internal_ip:20106' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-conversation'
static_configs:
- targets: [ 'internal_ip:20105' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-friend'
static_configs:
- targets: [ 'internal_ip:20104' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-group'
static_configs:
- targets: [ 'internal_ip:20103' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-msg'
static_configs:
- targets: [ 'internal_ip:20102' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-third'
static_configs:
- targets: [ 'internal_ip:20101' ]
labels:
namespace: 'default'
- job_name: 'openimserver-openim-rpc-user'
static_configs:
- targets: [ 'internal_ip:20100' ]
labels:
namespace: 'default'

@ -3,4 +3,4 @@ username: ''
password: openIM123 password: openIM123
clusterMode: false clusterMode: false
db: 0 db: 0
maxRetry: 10 maxRetry: 10

@ -13,6 +13,9 @@ afterUpdateUserInfoEx:
afterSendSingleMsg: afterSendSingleMsg:
enable: false enable: false
timeout: 5 timeout: 5
# Only the senID/recvID specified in attentionIds will send the callback
# if not set, all user messages will be callback
attentionIds: []
beforeSendGroupMsg: beforeSendGroupMsg:
enable: false enable: false
timeout: 5 timeout: 5

@ -140,5 +140,50 @@ services:
networks: networks:
- openim - openim
prometheus:
image: ${PROMETHEUS_IMAGE}
container_name: prometheus
restart: always
volumes:
- ./config/prometheus.yml:/etc/prometheus/prometheus.yml
- ./config/instance-down-rules.yml:/etc/prometheus/instance-down-rules.yml
- ${DATA_DIR}/components/prometheus/data:/prometheus
command:
- '--config.file=/etc/prometheus/prometheus.yml'
- '--storage.tsdb.path=/prometheus'
ports:
- "19091:9090"
networks:
- openim
alertmanager:
image: ${ALERTMANAGER_IMAGE}
container_name: alertmanager
restart: always
volumes:
- ./config/alertmanager.yml:/etc/alertmanager/alertmanager.yml
- ./config/email.tmpl:/etc/alertmanager/email.tmpl
ports:
- "19093:9093"
networks:
- openim
grafana:
image: ${GRAFANA_IMAGE}
container_name: grafana
user: root
restart: always
environment:
- GF_SECURITY_ALLOW_EMBEDDING=true
- GF_SESSION_COOKIE_SAMESITE=none
- GF_SESSION_COOKIE_SECURE=true
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
ports:
- "13000:3000"
volumes:
- ${DATA_DIR:-./}/components/grafana:/var/lib/grafana
networks:
- openim

@ -32,7 +32,7 @@ This script offers a variety of utilities and helpers to enhance and simplify op
## brief descriptions of each function ## brief descriptions of each function
**Englist:** **English:**
1. `openim::util::ensure-gnu-sed` - Determines if GNU version of `sed` exists on the system and sets its name. 1. `openim::util::ensure-gnu-sed` - Determines if GNU version of `sed` exists on the system and sets its name.
2. `openim::util::ensure-gnu-date` - Determines if GNU version of `date` exists on the system and sets its name. 2. `openim::util::ensure-gnu-date` - Determines if GNU version of `date` exists on the system and sets its name.
3. `openim::util::check-file-in-alphabetical-order` - Checks if a file is sorted in alphabetical order. 3. `openim::util::check-file-in-alphabetical-order` - Checks if a file is sorted in alphabetical order.

@ -1,7 +1,7 @@
# How do I contribute code to OpenIM # How do I contribute code to OpenIM
<p align="center"> <p align="center">
<a href="./CONTRIBUTING.md">Englist</a> · <a href="./CONTRIBUTING.md">English</a> ·
<a href="./CONTRIBUTING-zh_CN.md">中文</a> · <a href="./CONTRIBUTING-zh_CN.md">中文</a> ·
<a href="docs/contributing/CONTRIBUTING-UA.md">Українська</a> · <a href="docs/contributing/CONTRIBUTING-UA.md">Українська</a> ·
<a href="docs/contributing/CONTRIBUTING-CS.md">Česky</a> · <a href="docs/contributing/CONTRIBUTING-CS.md">Česky</a> ·

@ -1,7 +1,7 @@
# How do I contribute code to OpenIM # How do I contribute code to OpenIM
<p align="center"> <p align="center">
<a href="./CONTRIBUTING.md">Englist</a> · <a href="./CONTRIBUTING.md">English</a> ·
<a href="./CONTRIBUTING-zh_CN.md">中文</a> · <a href="./CONTRIBUTING-zh_CN.md">中文</a> ·
<a href="docs/contributing/CONTRIBUTING-UA.md">Українська</a> · <a href="docs/contributing/CONTRIBUTING-UA.md">Українська</a> ·
<a href="docs/contributing/CONTRIBUTING-CS.md">Česky</a> · <a href="docs/contributing/CONTRIBUTING-CS.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md">Українська</a> · <a href="./README_uk.md">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -19,7 +19,7 @@
<p align="center"> <p align="center">
<a href="../../README.md">Englist</a> · <a href="../../README.md">English</a> ·
<a href="../../README_zh_CN.md">中文</a> · <a href="../../README_zh_CN.md">中文</a> ·
<a href="./README_uk.md ">Українська</a> · <a href="./README_uk.md ">Українська</a> ·
<a href="./README_cs.md">Česky</a> · <a href="./README_cs.md">Česky</a> ·

@ -11,10 +11,9 @@ require (
github.com/golang-jwt/jwt/v4 v4.5.0 github.com/golang-jwt/jwt/v4 v4.5.0
github.com/gorilla/websocket v1.5.1 github.com/gorilla/websocket v1.5.1
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/lestrrat-go/file-rotatelogs v2.4.0+incompatible // indirect
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/openimsdk/protocol v0.0.65 github.com/openimsdk/protocol v0.0.69-alpha.42
github.com/openimsdk/tools v0.0.49-alpha.19 github.com/openimsdk/tools v0.0.49-alpha.55
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_golang v1.18.0 github.com/prometheus/client_golang v1.18.0
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
@ -35,7 +34,7 @@ require (
github.com/hashicorp/golang-lru/v2 v2.0.7 github.com/hashicorp/golang-lru/v2 v2.0.7
github.com/kelindar/bitmap v1.5.2 github.com/kelindar/bitmap v1.5.2
github.com/likexian/gokit v0.25.13 github.com/likexian/gokit v0.25.13
github.com/openimsdk/gomake v0.0.13 github.com/openimsdk/gomake v0.0.14-alpha.5
github.com/redis/go-redis/v9 v9.4.0 github.com/redis/go-redis/v9 v9.4.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
github.com/shirou/gopsutil v3.21.11+incompatible github.com/shirou/gopsutil v3.21.11+incompatible
@ -54,6 +53,24 @@ require (
cloud.google.com/go/longrunning v0.5.4 // indirect cloud.google.com/go/longrunning v0.5.4 // indirect
cloud.google.com/go/storage v1.36.0 // indirect cloud.google.com/go/storage v1.36.0 // indirect
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible // indirect
github.com/aws/aws-sdk-go-v2 v1.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/beorn7/perks v1.0.1 // indirect
github.com/bytedance/sonic v1.9.1 // indirect github.com/bytedance/sonic v1.9.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect
@ -101,6 +118,7 @@ require (
github.com/klauspost/compress v1.17.7 // indirect github.com/klauspost/compress v1.17.7 // indirect
github.com/klauspost/cpuid/v2 v2.2.6 // indirect github.com/klauspost/cpuid/v2 v2.2.6 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/lestrrat-go/strftime v1.0.6 // indirect
github.com/lithammer/shortuuid v3.0.0+incompatible // indirect github.com/lithammer/shortuuid v3.0.0+incompatible // indirect
github.com/magefile/mage v1.15.0 // indirect github.com/magefile/mage v1.15.0 // indirect
github.com/magiconair/properties v1.8.7 // indirect github.com/magiconair/properties v1.8.7 // indirect
@ -119,6 +137,7 @@ require (
github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/client_model v0.5.0 // indirect
github.com/prometheus/common v0.45.0 // indirect github.com/prometheus/common v0.45.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect
github.com/qiniu/go-sdk/v7 v7.18.2 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rs/xid v1.5.0 // indirect github.com/rs/xid v1.5.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect
@ -168,7 +187,6 @@ require (
require ( require (
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-json v0.10.2 // indirect
github.com/lestrrat-go/strftime v1.0.6 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect github.com/mattn/go-isatty v0.0.19 // indirect
github.com/spf13/cobra v1.8.0 github.com/spf13/cobra v1.8.0
github.com/ugorji/go/codec v1.2.11 // indirect github.com/ugorji/go/codec v1.2.11 // indirect

@ -21,6 +21,42 @@ github.com/IBM/sarama v1.43.0/go.mod h1:zlE6HEbC/SMQ9mhEYaF7nNLYOUyrs0obySKCckWP
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
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/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@ -49,6 +85,7 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI= github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
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.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@ -95,12 +132,18 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
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/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
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 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 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 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 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.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U= github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
@ -216,10 +259,16 @@ github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa02
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc= github.com/klauspost/cpuid/v2 v2.2.6 h1:ndNyv040zDGIDh8thGkXYjnFtiN02M1PVVF+JE/48xc=
github.com/klauspost/cpuid/v2 v2.2.6/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/klauspost/cpuid/v2 v2.2.6/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/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 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.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8=
@ -268,16 +317,17 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y= github.com/onsi/gomega v1.25.0 h1:Vw7br2PCDYijJHSfBOWhov+8cAnUf8MfMaIOV323l6Y=
github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM= github.com/onsi/gomega v1.25.0/go.mod h1:r+zV744Re+DiYCIPRlYOTxn0YkOLcAnW8k1xXdMPGhM=
github.com/openimsdk/gomake v0.0.13 h1:xLDe/moqgWpRoptHzI4packAWzs4C16b+sVY+txNJp0= github.com/openimsdk/gomake v0.0.14-alpha.5 h1:VY9c5x515lTfmdhhPjMvR3BBRrRquAUCFsz7t7vbv7Y=
github.com/openimsdk/gomake v0.0.13/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI= github.com/openimsdk/gomake v0.0.14-alpha.5/go.mod h1:PndCozNc2IsQIciyn9mvEblYWZwJmAI+06z94EY+csI=
github.com/openimsdk/protocol v0.0.65 h1:SPT9qyUsFRTTKSKb/FjpS+xr6sxz/Kbnu+su1bxYagc= github.com/openimsdk/protocol v0.0.69-alpha.42 h1:Vwuru2NtyTHuqaM+1JGxcoGvP25QWjS92oI0zGJp+lM=
github.com/openimsdk/protocol v0.0.65/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8= github.com/openimsdk/protocol v0.0.69-alpha.42/go.mod h1:OZQA9FR55lseYoN2Ql1XAHYKHJGu7OMNkUbuekrKCM8=
github.com/openimsdk/tools v0.0.49-alpha.19 h1:CbASL0yefRSVAmWPVeRnhF7wZKd6umLfz31CIhEgrBs= github.com/openimsdk/tools v0.0.49-alpha.55 h1:KPgC53oqiwZYssLKljhtXbWXifMlTj2SSQEusj4Uf4k=
github.com/openimsdk/tools v0.0.49-alpha.19/go.mod h1:g7mkHXYUPi0/8aAX8VPMHpnb3hqdV69Jph+bXOGvvNM= github.com/openimsdk/tools v0.0.49-alpha.55/go.mod h1:h1cYmfyaVtgFbKmb1Cfsl8XwUOMTt8ubVUQrdGtsUh4=
github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -294,12 +344,18 @@ github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lne
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY= github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
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 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM=
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk= github.com/redis/go-redis/v9 v9.4.0 h1:Yzoz33UZw9I/mFhx4MNrB6Fk+XHO1VukNcCa1+lwyKk=
github.com/redis/go-redis/v9 v9.4.0/go.mod h1:hdY0cQFCN4fnSYT6TkisLufl/4W5UIXyv0b/CLO2V2M= github.com/redis/go-redis/v9 v9.4.0/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 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
@ -332,6 +388,7 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
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.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.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -404,7 +461,9 @@ golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/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-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-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
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.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
@ -431,6 +490,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 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.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.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
@ -453,20 +513,26 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/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-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-20220520151302-bc2c85ada10a/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-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 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.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.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
@ -523,8 +589,10 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
@ -533,6 +601,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= 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.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-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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/gorm v1.25.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo= gorm.io/gorm v1.25.8 h1:WAGEZ/aEcznN4D03laj8DKnehe1e9gYQAjW8xyPRdeo=

@ -50,3 +50,15 @@ func (o *ConversationApi) SetConversations(c *gin.Context) {
func (o *ConversationApi) GetConversationOfflinePushUserIDs(c *gin.Context) { func (o *ConversationApi) GetConversationOfflinePushUserIDs(c *gin.Context) {
a2r.Call(conversation.ConversationClient.GetConversationOfflinePushUserIDs, o.Client, c) a2r.Call(conversation.ConversationClient.GetConversationOfflinePushUserIDs, o.Client, c)
} }
func (o *ConversationApi) GetFullOwnerConversationIDs(c *gin.Context) {
a2r.Call(conversation.ConversationClient.GetFullOwnerConversationIDs, o.Client, c)
}
func (o *ConversationApi) GetIncrementalConversation(c *gin.Context) {
a2r.Call(conversation.ConversationClient.GetIncrementalConversation, o.Client, c)
}
func (o *ConversationApi) GetOwnerConversation(c *gin.Context) {
a2r.Call(conversation.ConversationClient.GetOwnerConversation, o.Client, c)
}

@ -16,8 +16,9 @@ package api
import ( import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
"github.com/openimsdk/protocol/friend" "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/tools/a2r" "github.com/openimsdk/tools/a2r"
) )
@ -28,68 +29,83 @@ func NewFriendApi(client rpcclient.Friend) FriendApi {
} }
func (o *FriendApi) ApplyToAddFriend(c *gin.Context) { func (o *FriendApi) ApplyToAddFriend(c *gin.Context) {
a2r.Call(friend.FriendClient.ApplyToAddFriend, o.Client, c) a2r.Call(relation.FriendClient.ApplyToAddFriend, o.Client, c)
} }
func (o *FriendApi) RespondFriendApply(c *gin.Context) { func (o *FriendApi) RespondFriendApply(c *gin.Context) {
a2r.Call(friend.FriendClient.RespondFriendApply, o.Client, c) a2r.Call(relation.FriendClient.RespondFriendApply, o.Client, c)
} }
func (o *FriendApi) DeleteFriend(c *gin.Context) { func (o *FriendApi) DeleteFriend(c *gin.Context) {
a2r.Call(friend.FriendClient.DeleteFriend, o.Client, c) a2r.Call(relation.FriendClient.DeleteFriend, o.Client, c)
} }
func (o *FriendApi) GetFriendApplyList(c *gin.Context) { func (o *FriendApi) GetFriendApplyList(c *gin.Context) {
a2r.Call(friend.FriendClient.GetPaginationFriendsApplyTo, o.Client, c) a2r.Call(relation.FriendClient.GetPaginationFriendsApplyTo, o.Client, c)
} }
func (o *FriendApi) GetDesignatedFriendsApply(c *gin.Context) { func (o *FriendApi) GetDesignatedFriendsApply(c *gin.Context) {
a2r.Call(friend.FriendClient.GetDesignatedFriendsApply, o.Client, c) a2r.Call(relation.FriendClient.GetDesignatedFriendsApply, o.Client, c)
} }
func (o *FriendApi) GetSelfApplyList(c *gin.Context) { func (o *FriendApi) GetSelfApplyList(c *gin.Context) {
a2r.Call(friend.FriendClient.GetPaginationFriendsApplyFrom, o.Client, c) a2r.Call(relation.FriendClient.GetPaginationFriendsApplyFrom, o.Client, c)
} }
func (o *FriendApi) GetFriendList(c *gin.Context) { func (o *FriendApi) GetFriendList(c *gin.Context) {
a2r.Call(friend.FriendClient.GetPaginationFriends, o.Client, c) a2r.Call(relation.FriendClient.GetPaginationFriends, o.Client, c)
} }
func (o *FriendApi) GetDesignatedFriends(c *gin.Context) { func (o *FriendApi) GetDesignatedFriends(c *gin.Context) {
a2r.Call(friend.FriendClient.GetDesignatedFriends, o.Client, c) a2r.Call(relation.FriendClient.GetDesignatedFriends, o.Client, c)
} }
func (o *FriendApi) SetFriendRemark(c *gin.Context) { func (o *FriendApi) SetFriendRemark(c *gin.Context) {
a2r.Call(friend.FriendClient.SetFriendRemark, o.Client, c) a2r.Call(relation.FriendClient.SetFriendRemark, o.Client, c)
} }
func (o *FriendApi) AddBlack(c *gin.Context) { func (o *FriendApi) AddBlack(c *gin.Context) {
a2r.Call(friend.FriendClient.AddBlack, o.Client, c) a2r.Call(relation.FriendClient.AddBlack, o.Client, c)
} }
func (o *FriendApi) GetPaginationBlacks(c *gin.Context) { func (o *FriendApi) GetPaginationBlacks(c *gin.Context) {
a2r.Call(friend.FriendClient.GetPaginationBlacks, o.Client, c) a2r.Call(relation.FriendClient.GetPaginationBlacks, o.Client, c)
} }
func (o *FriendApi) RemoveBlack(c *gin.Context) { func (o *FriendApi) RemoveBlack(c *gin.Context) {
a2r.Call(friend.FriendClient.RemoveBlack, o.Client, c) a2r.Call(relation.FriendClient.RemoveBlack, o.Client, c)
} }
func (o *FriendApi) ImportFriends(c *gin.Context) { func (o *FriendApi) ImportFriends(c *gin.Context) {
a2r.Call(friend.FriendClient.ImportFriends, o.Client, c) a2r.Call(relation.FriendClient.ImportFriends, o.Client, c)
} }
func (o *FriendApi) IsFriend(c *gin.Context) { func (o *FriendApi) IsFriend(c *gin.Context) {
a2r.Call(friend.FriendClient.IsFriend, o.Client, c) a2r.Call(relation.FriendClient.IsFriend, o.Client, c)
} }
func (o *FriendApi) GetFriendIDs(c *gin.Context) { func (o *FriendApi) GetFriendIDs(c *gin.Context) {
a2r.Call(friend.FriendClient.GetFriendIDs, o.Client, c) a2r.Call(relation.FriendClient.GetFriendIDs, o.Client, c)
} }
func (o *FriendApi) GetSpecifiedFriendsInfo(c *gin.Context) { func (o *FriendApi) GetSpecifiedFriendsInfo(c *gin.Context) {
a2r.Call(friend.FriendClient.GetSpecifiedFriendsInfo, o.Client, c) a2r.Call(relation.FriendClient.GetSpecifiedFriendsInfo, o.Client, c)
} }
func (o *FriendApi) UpdateFriends(c *gin.Context) { func (o *FriendApi) UpdateFriends(c *gin.Context) {
a2r.Call(friend.FriendClient.UpdateFriends, o.Client, c) a2r.Call(relation.FriendClient.UpdateFriends, o.Client, c)
}
func (o *FriendApi) GetIncrementalFriends(c *gin.Context) {
a2r.Call(relation.FriendClient.GetIncrementalFriends, o.Client, c)
}
// GetIncrementalBlacks is temporarily unused.
// Deprecated: This function is currently unused and may be removed in future versions.
func (o *FriendApi) GetIncrementalBlacks(c *gin.Context) {
a2r.Call(relation.FriendClient.GetIncrementalBlacks, o.Client, c)
}
func (o *FriendApi) GetFullFriendUserIDs(c *gin.Context) {
a2r.Call(relation.FriendClient.GetFullFriendUserIDs, o.Client, c)
} }

@ -65,6 +65,7 @@ func (o *GroupApi) GetGroupUsersReqApplicationList(c *gin.Context) {
func (o *GroupApi) GetGroupsInfo(c *gin.Context) { func (o *GroupApi) GetGroupsInfo(c *gin.Context) {
a2r.Call(group.GroupClient.GetGroupsInfo, o.Client, c) a2r.Call(group.GroupClient.GetGroupsInfo, o.Client, c)
//a2r.Call(group.GroupClient.GetGroupsInfo, o.Client, c, a2r.NewNilReplaceOption(group.GroupClient.GetGroupsInfo))
} }
func (o *GroupApi) KickGroupMember(c *gin.Context) { func (o *GroupApi) KickGroupMember(c *gin.Context) {
@ -73,6 +74,7 @@ func (o *GroupApi) KickGroupMember(c *gin.Context) {
func (o *GroupApi) GetGroupMembersInfo(c *gin.Context) { func (o *GroupApi) GetGroupMembersInfo(c *gin.Context) {
a2r.Call(group.GroupClient.GetGroupMembersInfo, o.Client, c) a2r.Call(group.GroupClient.GetGroupMembersInfo, o.Client, c)
//a2r.Call(group.GroupClient.GetGroupMembersInfo, o.Client, c, a2r.NewNilReplaceOption(group.GroupClient.GetGroupMembersInfo))
} }
func (o *GroupApi) GetGroupMemberList(c *gin.Context) { func (o *GroupApi) GetGroupMemberList(c *gin.Context) {
@ -134,3 +136,23 @@ func (o *GroupApi) GetGroups(c *gin.Context) {
func (o *GroupApi) GetGroupMemberUserIDs(c *gin.Context) { func (o *GroupApi) GetGroupMemberUserIDs(c *gin.Context) {
a2r.Call(group.GroupClient.GetGroupMemberUserIDs, o.Client, c) a2r.Call(group.GroupClient.GetGroupMemberUserIDs, o.Client, c)
} }
func (o *GroupApi) GetIncrementalJoinGroup(c *gin.Context) {
a2r.Call(group.GroupClient.GetIncrementalJoinGroup, o.Client, c)
}
func (o *GroupApi) GetIncrementalGroupMember(c *gin.Context) {
a2r.Call(group.GroupClient.GetIncrementalGroupMember, o.Client, c)
}
func (o *GroupApi) GetIncrementalGroupMemberBatch(c *gin.Context) {
a2r.Call(group.GroupClient.BatchGetIncrementalGroupMember, o.Client, c)
}
func (o *GroupApi) GetFullGroupMemberUserIDs(c *gin.Context) {
a2r.Call(group.GroupClient.GetFullGroupMemberUserIDs, o.Client, c)
}
func (o *GroupApi) GetFullJoinGroupIDs(c *gin.Context) {
a2r.Call(group.GroupClient.GetFullJoinGroupIDs, o.Client, c)
}

@ -29,7 +29,6 @@ import (
"time" "time"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" 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" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
@ -72,10 +71,8 @@ func Start(ctx context.Context, index int, config *Config) error {
netDone <- struct{}{} netDone <- struct{}{}
return return
} }
p := ginprom.NewPrometheus("app", prommetrics.GetGinCusMetrics("Api")) if err := prommetrics.ApiInit(prometheusPort); err != nil && err != http.ErrServerClosed {
p.SetListenAddress(fmt.Sprintf(":%d", prometheusPort)) netErr = errs.WrapMsg(err, fmt.Sprintf("api prometheus start err: %d", prometheusPort))
if err = p.Use(router); err != nil && err != http.ErrServerClosed {
netErr = errs.WrapMsg(err, fmt.Sprintf("prometheus start err: %d", prometheusPort))
netDone <- struct{}{} netDone <- struct{}{}
} }
}() }()

@ -101,6 +101,7 @@ func (m MessageApi) newUserSendMsgReq(_ *gin.Context, params *apistruct.SendMsg)
SendTime: params.SendTime, SendTime: params.SendTime,
Options: options, Options: options,
OfflinePushInfo: params.OfflinePushInfo, OfflinePushInfo: params.OfflinePushInfo,
Ex: params.Ex,
}, },
} }
return &pbData return &pbData

@ -5,6 +5,10 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
@ -12,12 +16,25 @@ import (
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mw" "github.com/openimsdk/tools/mw"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"net/http" "net/http"
"strings" "strings"
) )
func prommetricsGin() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
path := c.FullPath()
if c.Writer.Status() == http.StatusNotFound {
prommetrics.HttpCall("<404>", c.Request.Method, c.Writer.Status())
} else {
prommetrics.HttpCall(path, c.Request.Method, c.Writer.Status())
}
if resp := apiresp.GetGinApiResponse(c); resp != nil {
prommetrics.APICall(path, c.Request.Method, resp.ErrCode)
}
}
}
func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.Engine { func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.Engine {
disCov.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), disCov.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin")))
@ -36,7 +53,7 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
authRpc := rpcclient.NewAuth(disCov, config.Share.RpcRegisterName.Auth) authRpc := rpcclient.NewAuth(disCov, config.Share.RpcRegisterName.Auth)
thirdRpc := rpcclient.NewThird(disCov, config.Share.RpcRegisterName.Third, config.API.Prometheus.GrafanaURL) thirdRpc := rpcclient.NewThird(disCov, config.Share.RpcRegisterName.Third, config.API.Prometheus.GrafanaURL)
r.Use(gin.Recovery(), mw.CorsHandler(), mw.GinParseOperationID(), GinParseToken(authRpc)) r.Use(prommetricsGin(), gin.Recovery(), mw.CorsHandler(), mw.GinParseOperationID(), GinParseToken(authRpc))
u := NewUserApi(*userRpc) u := NewUserApi(*userRpc)
m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID) m := NewMessageApi(messageRpc, userRpc, config.Share.IMAdminUserID)
userRouterGroup := r.Group("/user") userRouterGroup := r.Group("/user")
@ -81,11 +98,14 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
friendRouterGroup.POST("/add_black", f.AddBlack) friendRouterGroup.POST("/add_black", f.AddBlack)
friendRouterGroup.POST("/get_black_list", f.GetPaginationBlacks) friendRouterGroup.POST("/get_black_list", f.GetPaginationBlacks)
friendRouterGroup.POST("/remove_black", f.RemoveBlack) friendRouterGroup.POST("/remove_black", f.RemoveBlack)
friendRouterGroup.POST("/get_incremental_blacks", f.GetIncrementalBlacks)
friendRouterGroup.POST("/import_friend", f.ImportFriends) friendRouterGroup.POST("/import_friend", f.ImportFriends)
friendRouterGroup.POST("/is_friend", f.IsFriend) friendRouterGroup.POST("/is_friend", f.IsFriend)
friendRouterGroup.POST("/get_friend_id", f.GetFriendIDs) friendRouterGroup.POST("/get_friend_id", f.GetFriendIDs)
friendRouterGroup.POST("/get_specified_friends_info", f.GetSpecifiedFriendsInfo) friendRouterGroup.POST("/get_specified_friends_info", f.GetSpecifiedFriendsInfo)
friendRouterGroup.POST("/update_friends", f.UpdateFriends) friendRouterGroup.POST("/update_friends", f.UpdateFriends)
friendRouterGroup.POST("/get_incremental_friends", f.GetIncrementalFriends)
friendRouterGroup.POST("/get_full_friend_user_ids", f.GetFullFriendUserIDs)
} }
g := NewGroupApi(*groupRpc) g := NewGroupApi(*groupRpc)
groupRouterGroup := r.Group("/group") groupRouterGroup := r.Group("/group")
@ -114,6 +134,11 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
groupRouterGroup.POST("/get_group_abstract_info", g.GetGroupAbstractInfo) groupRouterGroup.POST("/get_group_abstract_info", g.GetGroupAbstractInfo)
groupRouterGroup.POST("/get_groups", g.GetGroups) groupRouterGroup.POST("/get_groups", g.GetGroups)
groupRouterGroup.POST("/get_group_member_user_id", g.GetGroupMemberUserIDs) groupRouterGroup.POST("/get_group_member_user_id", g.GetGroupMemberUserIDs)
groupRouterGroup.POST("/get_incremental_join_groups", g.GetIncrementalJoinGroup)
groupRouterGroup.POST("/get_incremental_group_members", g.GetIncrementalGroupMember)
groupRouterGroup.POST("/get_incremental_group_members_batch", g.GetIncrementalGroupMemberBatch)
groupRouterGroup.POST("/get_full_group_member_user_ids", g.GetFullGroupMemberUserIDs)
groupRouterGroup.POST("/get_full_join_group_ids", g.GetFullJoinGroupIDs)
} }
// certificate // certificate
authRouterGroup := r.Group("/auth") authRouterGroup := r.Group("/auth")
@ -183,6 +208,9 @@ func newGinRouter(disCov discovery.SvcDiscoveryRegistry, config *Config) *gin.En
conversationGroup.POST("/get_conversations", c.GetConversations) conversationGroup.POST("/get_conversations", c.GetConversations)
conversationGroup.POST("/set_conversations", c.SetConversations) conversationGroup.POST("/set_conversations", c.SetConversations)
conversationGroup.POST("/get_conversation_offline_push_user_ids", c.GetConversationOfflinePushUserIDs) conversationGroup.POST("/get_conversation_offline_push_user_ids", c.GetConversationOfflinePushUserIDs)
conversationGroup.POST("/get_full_conversation_ids", c.GetFullOwnerConversationIDs)
conversationGroup.POST("/get_incremental_conversations", c.GetIncrementalConversation)
conversationGroup.POST("/get_owner_conversation", c.GetOwnerConversation)
} }
statisticsGroup := r.Group("/statistics") statisticsGroup := r.Group("/statistics")

@ -20,6 +20,7 @@ import (
"runtime/debug" "runtime/debug"
"sync" "sync"
"sync/atomic" "sync/atomic"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/msgprocessor" "github.com/openimsdk/open-im-server/v3/pkg/msgprocessor"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
@ -72,6 +73,10 @@ type Client struct {
closed atomic.Bool closed atomic.Bool
closedErr error closedErr error
token string token string
hbCtx context.Context
hbCancel context.CancelFunc
subLock *sync.Mutex
subUserIDs map[string]struct{} // client conn subscription list
} }
// ResetClient updates the client's state with new connection and context information. // ResetClient updates the client's state with new connection and context information.
@ -88,14 +93,28 @@ func (c *Client) ResetClient(ctx *UserConnContext, conn LongConn, longConnServer
c.closed.Store(false) c.closed.Store(false)
c.closedErr = nil c.closedErr = nil
c.token = ctx.GetToken() c.token = ctx.GetToken()
c.hbCtx, c.hbCancel = context.WithCancel(c.ctx)
c.subLock = new(sync.Mutex)
if c.subUserIDs != nil {
clear(c.subUserIDs)
}
c.subUserIDs = make(map[string]struct{})
} }
func (c *Client) pingHandler(_ string) error { func (c *Client) pingHandler(appData string) error {
if err := c.conn.SetReadDeadline(pongWait); err != nil { if err := c.conn.SetReadDeadline(pongWait); err != nil {
return err return err
} }
return c.writePongMsg() log.ZDebug(c.ctx, "ping Handler Success.", "appData", appData)
return c.writePongMsg(appData)
}
func (c *Client) pongHandler(_ string) error {
if err := c.conn.SetReadDeadline(pongWait); err != nil {
return err
}
return nil
} }
// readMessage continuously reads messages from the connection. // readMessage continuously reads messages from the connection.
@ -110,7 +129,9 @@ func (c *Client) readMessage() {
c.conn.SetReadLimit(maxMessageSize) c.conn.SetReadLimit(maxMessageSize)
_ = c.conn.SetReadDeadline(pongWait) _ = c.conn.SetReadDeadline(pongWait)
c.conn.SetPongHandler(c.pongHandler)
c.conn.SetPingHandler(c.pingHandler) c.conn.SetPingHandler(c.pingHandler)
c.activeHeartbeat(c.hbCtx)
for { for {
log.ZDebug(c.ctx, "readMessage") log.ZDebug(c.ctx, "readMessage")
@ -141,12 +162,13 @@ func (c *Client) readMessage() {
return return
case PingMessage: case PingMessage:
err := c.writePongMsg() err := c.writePongMsg("")
log.ZError(c.ctx, "writePongMsg", err) log.ZError(c.ctx, "writePongMsg", err)
case CloseMessage: case CloseMessage:
c.closedErr = ErrClientClosed c.closedErr = ErrClientClosed
return return
default: default:
} }
} }
@ -202,6 +224,8 @@ func (c *Client) handleMessage(message []byte) error {
resp, messageErr = c.longConnServer.UserLogout(ctx, binaryReq) resp, messageErr = c.longConnServer.UserLogout(ctx, binaryReq)
case WsSetBackgroundStatus: case WsSetBackgroundStatus:
resp, messageErr = c.setAppBackgroundStatus(ctx, binaryReq) resp, messageErr = c.setAppBackgroundStatus(ctx, binaryReq)
case WsSubUserOnlineStatus:
resp, messageErr = c.longConnServer.SubUserOnlineStatus(ctx, c, binaryReq)
default: default:
return fmt.Errorf( return fmt.Errorf(
"ReqIdentifier failed,sendID:%s,msgIncr:%s,reqIdentifier:%d", "ReqIdentifier failed,sendID:%s,msgIncr:%s,reqIdentifier:%d",
@ -226,15 +250,14 @@ func (c *Client) setAppBackgroundStatus(ctx context.Context, req *Req) ([]byte,
} }
func (c *Client) close() { func (c *Client) close() {
c.w.Lock()
defer c.w.Unlock()
if c.closed.Load() { if c.closed.Load() {
return return
} }
c.w.Lock()
defer c.w.Unlock()
c.closed.Store(true) c.closed.Store(true)
c.conn.Close() c.conn.Close()
c.hbCancel() // Close server-initiated heartbeat.
c.longConnServer.UnRegister(c) c.longConnServer.UnRegister(c)
} }
@ -292,6 +315,14 @@ func (c *Client) KickOnlineMessage() error {
return err return err
} }
func (c *Client) PushUserOnlineStatus(data []byte) error {
resp := Resp{
ReqIdentifier: WsSubUserOnlineStatus,
Data: data,
}
return c.writeBinaryMsg(resp)
}
func (c *Client) writeBinaryMsg(resp Resp) error { func (c *Client) writeBinaryMsg(resp Resp) error {
if c.closed.Load() { if c.closed.Load() {
return nil return nil
@ -321,7 +352,29 @@ func (c *Client) writeBinaryMsg(resp Resp) error {
return c.conn.WriteMessage(MessageBinary, encodedBuf) return c.conn.WriteMessage(MessageBinary, encodedBuf)
} }
func (c *Client) writePongMsg() error { // Actively initiate Heartbeat when platform in Web.
func (c *Client) activeHeartbeat(ctx context.Context) {
if c.PlatformID == constant.WebPlatformID {
go func() {
log.ZDebug(ctx, "server initiative send heartbeat start.")
ticker := time.NewTicker(pingPeriod)
defer ticker.Stop()
for {
select {
case <-ticker.C:
if err := c.writePingMsg(); err != nil {
log.ZWarn(c.ctx, "send Ping Message error.", err)
return
}
case <-c.hbCtx.Done():
return
}
}
}()
}
}
func (c *Client) writePingMsg() error {
if c.closed.Load() { if c.closed.Load() {
return nil return nil
} }
@ -334,5 +387,28 @@ func (c *Client) writePongMsg() error {
return err return err
} }
return c.conn.WriteMessage(PongMessage, nil) return c.conn.WriteMessage(PingMessage, nil)
}
func (c *Client) writePongMsg(appData string) error {
log.ZDebug(c.ctx, "write Pong Msg in Server", "appData", appData)
if c.closed.Load() {
return nil
}
log.ZDebug(c.ctx, "write Pong Msg in Server", "appData", appData)
c.w.Lock()
defer c.w.Unlock()
log.ZDebug(c.ctx, "write Pong Msg in Server", "appData", appData)
err := c.conn.SetWriteDeadline(writeWait)
if err != nil {
return errs.Wrap(err)
}
err = c.conn.WriteMessage(PongMessage, []byte(appData))
if err != nil {
log.ZWarn(c.ctx, "Write Message have error", errs.Wrap(err), "Pong msg", PongMessage)
}
return errs.Wrap(err)
} }

@ -16,10 +16,10 @@ package msggateway
import ( import (
"crypto/rand" "crypto/rand"
"github.com/stretchr/testify/assert"
"sync" "sync"
"testing" "testing"
"unsafe"
"github.com/stretchr/testify/assert"
) )
func mockRandom() []byte { func mockRandom() []byte {
@ -132,3 +132,8 @@ func BenchmarkDecompressWithSyncPool(b *testing.B) {
assert.Equal(b, nil, err) assert.Equal(b, nil, err)
} }
} }
func TestName(t *testing.T) {
t.Log(unsafe.Sizeof(Client{}))
}

@ -43,6 +43,7 @@ const (
WSKickOnlineMsg = 2002 WSKickOnlineMsg = 2002
WsLogoutMsg = 2003 WsLogoutMsg = 2003
WsSetBackgroundStatus = 2004 WsSetBackgroundStatus = 2004
WsSubUserOnlineStatus = 2005
WSDataError = 3001 WSDataError = 3001
) )
@ -53,6 +54,9 @@ const (
// Time allowed to read the next pong message from the peer. // Time allowed to read the next pong message from the peer.
pongWait = 30 * time.Second pongWait = 30 * time.Second
// Send pings to peer with this period. Must be less than pongWait.
pingPeriod = (pongWait * 9) / 10
// Maximum message size allowed from peer. // Maximum message size allowed from peer.
maxMessageSize = 51200 maxMessageSize = 51200
) )

@ -19,18 +19,27 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
"github.com/openimsdk/protocol/msggateway" "github.com/openimsdk/protocol/msggateway"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/mq/memamq"
"github.com/openimsdk/tools/utils/datautil"
"google.golang.org/grpc" "google.golang.org/grpc"
"sync/atomic"
) )
func (s *Server) InitServer(ctx context.Context, config *Config, disCov discovery.SvcDiscoveryRegistry, server *grpc.Server) error { func (s *Server) InitServer(ctx context.Context, config *Config, disCov discovery.SvcDiscoveryRegistry, server *grpc.Server) error {
s.LongConnServer.SetDiscoveryRegistry(disCov, config) s.LongConnServer.SetDiscoveryRegistry(disCov, config)
msggateway.RegisterMsgGatewayServer(server, s) msggateway.RegisterMsgGatewayServer(server, s)
s.userRcp = rpcclient.NewUserRpcClient(disCov, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID)
if s.ready != nil {
return s.ready(s)
}
return nil return nil
} }
@ -50,18 +59,23 @@ type Server struct {
LongConnServer LongConnServer LongConnServer LongConnServer
config *Config config *Config
pushTerminal map[int]struct{} pushTerminal map[int]struct{}
ready func(srv *Server) error
userRcp rpcclient.UserRpcClient
queue *memamq.MemoryQueue
} }
func (s *Server) SetLongConnServer(LongConnServer LongConnServer) { func (s *Server) SetLongConnServer(LongConnServer LongConnServer) {
s.LongConnServer = LongConnServer s.LongConnServer = LongConnServer
} }
func NewServer(rpcPort int, longConnServer LongConnServer, conf *Config) *Server { func NewServer(rpcPort int, longConnServer LongConnServer, conf *Config, ready func(srv *Server) error) *Server {
s := &Server{ s := &Server{
rpcPort: rpcPort, rpcPort: rpcPort,
LongConnServer: longConnServer, LongConnServer: longConnServer,
pushTerminal: make(map[int]struct{}), pushTerminal: make(map[int]struct{}),
config: conf, config: conf,
ready: ready,
queue: memamq.NewMemoryQueue(512, 1024*16),
} }
s.pushTerminal[constant.IOSPlatformID] = struct{}{} s.pushTerminal[constant.IOSPlatformID] = struct{}{}
s.pushTerminal[constant.AndroidPlatformID] = struct{}{} s.pushTerminal[constant.AndroidPlatformID] = struct{}{}
@ -117,55 +131,93 @@ func (s *Server) OnlineBatchPushOneMsg(ctx context.Context, req *msggateway.Onli
return nil, nil return nil, nil
} }
func (s *Server) SuperGroupOnlineBatchPushOneMsg(ctx context.Context, req *msggateway.OnlineBatchPushOneMsgReq, func (s *Server) pushToUser(ctx context.Context, userID string, msgData *sdkws.MsgData) *msggateway.SingleMsgToUserResults {
) (*msggateway.OnlineBatchPushOneMsgResp, error) { clients, ok := s.LongConnServer.GetUserAllCons(userID)
var singleUserResults []*msggateway.SingleMsgToUserResults if !ok {
for _, v := range req.PushToUserIDs { log.ZDebug(ctx, "push user not online", "userID", userID)
var resp []*msggateway.SingleMsgToUserPlatform return &msggateway.SingleMsgToUserResults{
results := &msggateway.SingleMsgToUserResults{ UserID: userID,
UserID: v,
} }
clients, ok := s.LongConnServer.GetUserAllCons(v) }
if !ok { log.ZDebug(ctx, "push user online", "clients", clients, "userID", userID)
log.ZDebug(ctx, "push user not online", "userID", v) result := &msggateway.SingleMsgToUserResults{
results.Resp = resp UserID: userID,
singleUserResults = append(singleUserResults, results) Resp: make([]*msggateway.SingleMsgToUserPlatform, 0, len(clients)),
}
for _, client := range clients {
if client == nil {
continue continue
} }
userPlatform := &msggateway.SingleMsgToUserPlatform{
log.ZDebug(ctx, "push user online", "clients", clients, "userID", v) RecvPlatFormID: int32(client.PlatformID),
for _, client := range clients { }
if client == nil { if !client.IsBackground ||
continue (client.IsBackground && client.PlatformID != constant.IOSPlatformID) {
err := client.PushMessage(ctx, msgData)
if err != nil {
userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code())
} else {
if _, ok := s.pushTerminal[client.PlatformID]; ok {
result.OnlinePush = true
}
} }
} else {
userPlatform.ResultCode = int64(servererrs.ErrIOSBackgroundPushErr.Code())
}
result.Resp = append(result.Resp, userPlatform)
}
return result
}
userPlatform := &msggateway.SingleMsgToUserPlatform{ func (s *Server) SuperGroupOnlineBatchPushOneMsg(ctx context.Context, req *msggateway.OnlineBatchPushOneMsgReq) (*msggateway.OnlineBatchPushOneMsgResp, error) {
RecvPlatFormID: int32(client.PlatformID), if len(req.PushToUserIDs) == 0 {
return &msggateway.OnlineBatchPushOneMsgResp{}, nil
}
ch := make(chan *msggateway.SingleMsgToUserResults, len(req.PushToUserIDs))
var count atomic.Int64
count.Add(int64(len(req.PushToUserIDs)))
for i := range req.PushToUserIDs {
userID := req.PushToUserIDs[i]
err := s.queue.PushCtx(ctx, func() {
ch <- s.pushToUser(ctx, userID, req.MsgData)
if count.Add(-1) == 0 {
close(ch)
} }
if !client.IsBackground || })
(client.IsBackground && client.PlatformID != constant.IOSPlatformID) { if err != nil {
err := client.PushMessage(ctx, req.MsgData) if count.Add(-1) == 0 {
if err != nil { close(ch)
userPlatform.ResultCode = int64(servererrs.ErrPushMsgErr.Code()) }
resp = append(resp, userPlatform) log.ZError(ctx, "pushToUser MemoryQueue failed", err, "userID", userID)
} else { ch <- &msggateway.SingleMsgToUserResults{
if _, ok := s.pushTerminal[client.PlatformID]; ok { UserID: userID,
results.OnlinePush = true
resp = append(resp, userPlatform)
}
}
} else {
userPlatform.ResultCode = int64(servererrs.ErrIOSBackgroundPushErr.Code())
resp = append(resp, userPlatform)
} }
} }
results.Resp = resp
singleUserResults = append(singleUserResults, results)
} }
resp := &msggateway.OnlineBatchPushOneMsgResp{
return &msggateway.OnlineBatchPushOneMsgResp{ SinglePushResult: make([]*msggateway.SingleMsgToUserResults, 0, len(req.PushToUserIDs)),
SinglePushResult: singleUserResults, }
}, nil for {
select {
case <-ctx.Done():
log.ZError(ctx, "SuperGroupOnlineBatchPushOneMsg ctx done", context.Cause(ctx))
userIDSet := datautil.SliceSet(req.PushToUserIDs)
for _, results := range resp.SinglePushResult {
delete(userIDSet, results.UserID)
}
for userID := range userIDSet {
resp.SinglePushResult = append(resp.SinglePushResult, &msggateway.SingleMsgToUserResults{
UserID: userID,
})
}
return resp, nil
case res, ok := <-ch:
if !ok {
return resp, nil
}
resp.SinglePushResult = append(resp.SinglePushResult, res)
}
}
} }
func (s *Server) KickUserOffline( func (s *Server) KickUserOffline(

@ -17,6 +17,8 @@ package msggateway
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"time" "time"
@ -26,6 +28,7 @@ import (
type Config struct { type Config struct {
MsgGateway config.MsgGateway MsgGateway config.MsgGateway
Share config.Share Share config.Share
RedisConfig config.Redis
WebhooksConfig config.Webhooks WebhooksConfig config.Webhooks
Discovery config.Discovery Discovery config.Discovery
} }
@ -42,18 +45,25 @@ func Start(ctx context.Context, index int, conf *Config) error {
if err != nil { if err != nil {
return err return err
} }
longServer, err := NewWsServer( rdb, err := redisutil.NewRedisClient(ctx, conf.RedisConfig.Build())
if err != nil {
return err
}
longServer := NewWsServer(
conf, conf,
WithPort(wsPort), WithPort(wsPort),
WithMaxConnNum(int64(conf.MsgGateway.LongConnSvr.WebsocketMaxConnNum)), WithMaxConnNum(int64(conf.MsgGateway.LongConnSvr.WebsocketMaxConnNum)),
WithHandshakeTimeout(time.Duration(conf.MsgGateway.LongConnSvr.WebsocketTimeout)*time.Second), WithHandshakeTimeout(time.Duration(conf.MsgGateway.LongConnSvr.WebsocketTimeout)*time.Second),
WithMessageMaxMsgLength(conf.MsgGateway.LongConnSvr.WebsocketMaxMsgLen), WithMessageMaxMsgLength(conf.MsgGateway.LongConnSvr.WebsocketMaxMsgLen),
) )
if err != nil {
return err
}
hubServer := NewServer(rpcPort, longServer, conf) hubServer := NewServer(rpcPort, longServer, conf, func(srv *Server) error {
longServer.online = rpccache.NewOnlineCache(srv.userRcp, nil, rdb, longServer.subscriberUserOnlineStatusChanges)
return nil
})
go longServer.ChangeOnlineStatus(4)
netDone := make(chan error) netDone := make(chan error)
go func() { go func() {
err = hubServer.Start(ctx, index, conf) err = hubServer.Start(ctx, index, conf)

@ -16,10 +16,11 @@ package msggateway
import ( import (
"encoding/json" "encoding/json"
"github.com/openimsdk/tools/apiresp"
"net/http" "net/http"
"time" "time"
"github.com/openimsdk/tools/apiresp"
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
) )

@ -0,0 +1,112 @@
package msggateway
import (
"context"
"crypto/md5"
"encoding/binary"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/cachekey"
pbuser "github.com/openimsdk/protocol/user"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil"
"math/rand"
"strconv"
"time"
)
func (ws *WsServer) ChangeOnlineStatus(concurrent int) {
if concurrent < 1 {
concurrent = 1
}
const renewalTime = cachekey.OnlineExpire / 3
//const renewalTime = time.Second * 10
renewalTicker := time.NewTicker(renewalTime)
requestChs := make([]chan *pbuser.SetUserOnlineStatusReq, concurrent)
changeStatus := make([][]UserState, concurrent)
for i := 0; i < concurrent; i++ {
requestChs[i] = make(chan *pbuser.SetUserOnlineStatusReq, 64)
changeStatus[i] = make([]UserState, 0, 100)
}
mergeTicker := time.NewTicker(time.Second)
local2pb := func(u UserState) *pbuser.UserOnlineStatus {
return &pbuser.UserOnlineStatus{
UserID: u.UserID,
Online: u.Online,
Offline: u.Offline,
}
}
rNum := rand.Uint64()
pushUserState := func(us ...UserState) {
for _, u := range us {
sum := md5.Sum([]byte(u.UserID))
i := (binary.BigEndian.Uint64(sum[:]) + rNum) % uint64(concurrent)
changeStatus[i] = append(changeStatus[i], u)
status := changeStatus[i]
if len(status) == cap(status) {
req := &pbuser.SetUserOnlineStatusReq{
Status: datautil.Slice(status, local2pb),
}
changeStatus[i] = status[:0]
select {
case requestChs[i] <- req:
default:
log.ZError(context.Background(), "user online processing is too slow", nil)
}
}
}
}
pushAllUserState := func() {
for i, status := range changeStatus {
if len(status) == 0 {
continue
}
req := &pbuser.SetUserOnlineStatusReq{
Status: datautil.Slice(status, local2pb),
}
changeStatus[i] = status[:0]
select {
case requestChs[i] <- req:
default:
log.ZError(context.Background(), "user online processing is too slow", nil)
}
}
}
opIdCtx := mcontext.SetOperationID(context.Background(), "r"+strconv.FormatUint(rNum, 10))
doRequest := func(req *pbuser.SetUserOnlineStatusReq) {
ctx, cancel := context.WithTimeout(opIdCtx, time.Second*5)
defer cancel()
if _, err := ws.userClient.Client.SetUserOnlineStatus(ctx, req); err != nil {
log.ZError(ctx, "update user online status", err)
}
}
for i := 0; i < concurrent; i++ {
go func(ch <-chan *pbuser.SetUserOnlineStatusReq) {
for req := range ch {
doRequest(req)
}
}(requestChs[i])
}
for {
select {
case <-mergeTicker.C:
pushAllUserState()
case now := <-renewalTicker.C:
deadline := now.Add(-cachekey.OnlineExpire / 3)
users := ws.clients.GetAllUserStatus(deadline, now)
log.ZDebug(context.Background(), "renewal ticker", "deadline", deadline, "nowtime", now, "num", len(users))
pushUserState(users...)
case state := <-ws.clients.UserState():
log.ZDebug(context.Background(), "OnlineCache user online change", "userID", state.UserID, "online", state.Online, "offline", state.Offline)
pushUserState(state)
}
}
}

@ -0,0 +1,166 @@
package msggateway
import (
"context"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil"
"google.golang.org/protobuf/proto"
"sync"
)
func (ws *WsServer) subscriberUserOnlineStatusChanges(ctx context.Context, userID string, platformIDs []int32) {
if ws.clients.RecvSubChange(userID, platformIDs) {
log.ZDebug(ctx, "gateway receive subscription message and go back online", "userID", userID, "platformIDs", platformIDs)
} else {
log.ZDebug(ctx, "gateway ignore user online status changes", "userID", userID, "platformIDs", platformIDs)
}
ws.pushUserIDOnlineStatus(ctx, userID, platformIDs)
}
func (ws *WsServer) SubUserOnlineStatus(ctx context.Context, client *Client, data *Req) ([]byte, error) {
var sub sdkws.SubUserOnlineStatus
if err := proto.Unmarshal(data.Data, &sub); err != nil {
return nil, err
}
ws.subscription.Sub(client, sub.SubscribeUserID, sub.UnsubscribeUserID)
var resp sdkws.SubUserOnlineStatusTips
if len(sub.SubscribeUserID) > 0 {
resp.Subscribers = make([]*sdkws.SubUserOnlineStatusElem, 0, len(sub.SubscribeUserID))
for _, userID := range sub.SubscribeUserID {
platformIDs, err := ws.online.GetUserOnlinePlatform(ctx, userID)
if err != nil {
return nil, err
}
resp.Subscribers = append(resp.Subscribers, &sdkws.SubUserOnlineStatusElem{
UserID: userID,
OnlinePlatformIDs: platformIDs,
})
}
}
return proto.Marshal(&resp)
}
func newSubscription() *Subscription {
return &Subscription{
userIDs: make(map[string]*subClient),
}
}
type subClient struct {
clients map[string]*Client
}
type Subscription struct {
lock sync.RWMutex
userIDs map[string]*subClient // subscribe to the user's client connection
}
func (s *Subscription) DelClient(client *Client) {
client.subLock.Lock()
userIDs := datautil.Keys(client.subUserIDs)
for _, userID := range userIDs {
delete(client.subUserIDs, userID)
}
client.subLock.Unlock()
if len(userIDs) == 0 {
return
}
addr := client.ctx.GetRemoteAddr()
s.lock.Lock()
defer s.lock.Unlock()
for _, userID := range userIDs {
sub, ok := s.userIDs[userID]
if !ok {
continue
}
delete(sub.clients, addr)
if len(sub.clients) == 0 {
delete(s.userIDs, userID)
}
}
}
func (s *Subscription) GetClient(userID string) []*Client {
s.lock.RLock()
defer s.lock.RUnlock()
cs, ok := s.userIDs[userID]
if !ok {
return nil
}
clients := make([]*Client, 0, len(cs.clients))
for _, client := range cs.clients {
clients = append(clients, client)
}
return clients
}
func (s *Subscription) Sub(client *Client, addUserIDs, delUserIDs []string) {
if len(addUserIDs)+len(delUserIDs) == 0 {
return
}
var (
del = make(map[string]struct{})
add = make(map[string]struct{})
)
client.subLock.Lock()
for _, userID := range delUserIDs {
if _, ok := client.subUserIDs[userID]; !ok {
continue
}
del[userID] = struct{}{}
delete(client.subUserIDs, userID)
}
for _, userID := range addUserIDs {
delete(del, userID)
if _, ok := client.subUserIDs[userID]; ok {
continue
}
client.subUserIDs[userID] = struct{}{}
add[userID] = struct{}{}
}
client.subLock.Unlock()
if len(del)+len(add) == 0 {
return
}
addr := client.ctx.GetRemoteAddr()
s.lock.Lock()
defer s.lock.Unlock()
for userID := range del {
sub, ok := s.userIDs[userID]
if !ok {
continue
}
delete(sub.clients, addr)
if len(sub.clients) == 0 {
delete(s.userIDs, userID)
}
}
for userID := range add {
sub, ok := s.userIDs[userID]
if !ok {
sub = &subClient{clients: make(map[string]*Client)}
s.userIDs[userID] = sub
}
sub.clients[addr] = client
}
}
func (ws *WsServer) pushUserIDOnlineStatus(ctx context.Context, userID string, platformIDs []int32) {
clients := ws.subscription.GetClient(userID)
if len(clients) == 0 {
return
}
onlineStatus, err := proto.Marshal(&sdkws.SubUserOnlineStatusTips{
Subscribers: []*sdkws.SubUserOnlineStatusElem{{UserID: userID, OnlinePlatformIDs: platformIDs}},
})
if err != nil {
log.ZError(ctx, "pushUserIDOnlineStatus json.Marshal", err)
return
}
for _, client := range clients {
if err := client.PushUserOnlineStatus(onlineStatus); err != nil {
log.ZError(ctx, "UserSubscribeOnlineStatusNotification push failed", err, "userID", client.UserID, "platformID", client.PlatformID, "changeUserID", userID, "changePlatformID", platformIDs)
}
}
}

@ -1,135 +1,185 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package msggateway package msggateway
import ( import (
"context"
"sync"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"sync"
"time"
) )
type UserMap struct { type UserMap interface {
m sync.Map GetAll(userID string) ([]*Client, bool)
Get(userID string, platformID int) ([]*Client, bool, bool)
Set(userID string, v *Client)
DeleteClients(userID string, clients []*Client) (isDeleteUser bool)
UserState() <-chan UserState
GetAllUserStatus(deadline time.Time, nowtime time.Time) []UserState
RecvSubChange(userID string, platformIDs []int32) bool
} }
func newUserMap() *UserMap { type UserState struct {
return &UserMap{} UserID string
Online []int32
Offline []int32
} }
func (u *UserMap) GetAll(key string) ([]*Client, bool) { type UserPlatform struct {
allClients, ok := u.m.Load(key) Time time.Time
if ok { Clients []*Client
return allClients.([]*Client), ok
}
return nil, ok
} }
func (u *UserMap) Get(key string, platformID int) ([]*Client, bool, bool) { func (u *UserPlatform) PlatformIDs() []int32 {
allClients, userExisted := u.m.Load(key) if len(u.Clients) == 0 {
if userExisted { return nil
var clients []*Client }
for _, client := range allClients.([]*Client) { platformIDs := make([]int32, 0, len(u.Clients))
if client.PlatformID == platformID { for _, client := range u.Clients {
clients = append(clients, client) platformIDs = append(platformIDs, int32(client.PlatformID))
}
}
if len(clients) > 0 {
return clients, userExisted, true
}
return clients, userExisted, false
} }
return nil, userExisted, false return platformIDs
} }
// Set adds a client to the map. func (u *UserPlatform) PlatformIDSet() map[int32]struct{} {
func (u *UserMap) Set(key string, v *Client) { if len(u.Clients) == 0 {
allClients, existed := u.m.Load(key) return nil
if existed { }
log.ZDebug(context.Background(), "Set existed", "user_id", key, "client_user_id", v.UserID) platformIDs := make(map[int32]struct{})
oldClients := allClients.([]*Client) for _, client := range u.Clients {
oldClients = append(oldClients, v) platformIDs[int32(client.PlatformID)] = struct{}{}
u.m.Store(key, oldClients) }
} else { return platformIDs
log.ZDebug(context.Background(), "Set not existed", "user_id", key, "client_user_id", v.UserID) }
var clients []*Client func newUserMap() UserMap {
clients = append(clients, v) return &userMap{
u.m.Store(key, clients) data: make(map[string]*UserPlatform),
ch: make(chan UserState, 10000),
} }
} }
func (u *UserMap) delete(key string, connRemoteAddr string) (isDeleteUser bool) { type userMap struct {
// Attempt to load the clients associated with the key. lock sync.RWMutex
allClients, existed := u.m.Load(key) data map[string]*UserPlatform
if !existed { ch chan UserState
// Return false immediately if the key does not exist. }
func (u *userMap) RecvSubChange(userID string, platformIDs []int32) bool {
u.lock.RLock()
defer u.lock.RUnlock()
result, ok := u.data[userID]
if !ok {
return false return false
} }
localPlatformIDs := result.PlatformIDSet()
// Convert allClients to a slice of *Client. for _, platformID := range platformIDs {
oldClients := allClients.([]*Client) delete(localPlatformIDs, platformID)
var remainingClients []*Client
for _, client := range oldClients {
// Keep clients that do not match the connRemoteAddr.
if client.ctx.GetRemoteAddr() != connRemoteAddr {
remainingClients = append(remainingClients, client)
}
} }
if len(localPlatformIDs) == 0 {
return false
}
u.push(userID, result, nil)
return true
}
// If no clients remain after filtering, delete the key from the map. func (u *userMap) push(userID string, userPlatform *UserPlatform, offline []int32) bool {
if len(remainingClients) == 0 { select {
u.m.Delete(key) case u.ch <- UserState{UserID: userID, Online: userPlatform.PlatformIDs(), Offline: offline}:
userPlatform.Time = time.Now()
return true return true
default:
return false
} }
}
// Otherwise, update the key with the remaining clients. func (u *userMap) GetAll(userID string) ([]*Client, bool) {
u.m.Store(key, remainingClients) u.lock.RLock()
return false defer u.lock.RUnlock()
result, ok := u.data[userID]
if !ok {
return nil, false
}
return result.Clients, true
} }
func (u *UserMap) deleteClients(key string, clients []*Client) (isDeleteUser bool) { func (u *userMap) Get(userID string, platformID int) ([]*Client, bool, bool) {
m := datautil.SliceToMapAny(clients, func(c *Client) (string, struct{}) { u.lock.RLock()
return c.ctx.GetRemoteAddr(), struct{}{} defer u.lock.RUnlock()
}) result, ok := u.data[userID]
allClients, existed := u.m.Load(key) if !ok {
if !existed { return nil, false, false
// If the key doesn't exist, return false.
return false
} }
var clients []*Client
for _, client := range result.Clients {
if client.PlatformID == platformID {
clients = append(clients, client)
}
}
return clients, true, len(clients) > 0
}
// Filter out clients that are in the deleteMap. func (u *userMap) Set(userID string, client *Client) {
oldClients := allClients.([]*Client) u.lock.Lock()
var remainingClients []*Client defer u.lock.Unlock()
for _, client := range oldClients { result, ok := u.data[userID]
if _, shouldBeDeleted := m[client.ctx.GetRemoteAddr()]; !shouldBeDeleted { if ok {
remainingClients = append(remainingClients, client) result.Clients = append(result.Clients, client)
} else {
result = &UserPlatform{
Clients: []*Client{client},
} }
u.data[userID] = result
} }
u.push(client.UserID, result, nil)
}
// Update or delete the key based on the remaining clients. func (u *userMap) DeleteClients(userID string, clients []*Client) (isDeleteUser bool) {
if len(remainingClients) == 0 { if len(clients) == 0 {
u.m.Delete(key) return false
return true
} }
u.lock.Lock()
defer u.lock.Unlock()
result, ok := u.data[userID]
if !ok {
return false
}
offline := make([]int32, 0, len(clients))
deleteAddr := datautil.SliceSetAny(clients, func(client *Client) string {
return client.ctx.GetRemoteAddr()
})
tmp := result.Clients
result.Clients = result.Clients[:0]
for _, client := range tmp {
if _, delCli := deleteAddr[client.ctx.GetRemoteAddr()]; delCli {
offline = append(offline, int32(client.PlatformID))
} else {
result.Clients = append(result.Clients, client)
}
}
defer u.push(userID, result, offline)
if len(result.Clients) > 0 {
return false
}
delete(u.data, userID)
return true
}
u.m.Store(key, remainingClients) func (u *userMap) GetAllUserStatus(deadline time.Time, nowtime time.Time) []UserState {
return false u.lock.RLock()
defer u.lock.RUnlock()
result := make([]UserState, 0, len(u.data))
for userID, userPlatform := range u.data {
if userPlatform.Time.Before(deadline) {
continue
}
userPlatform.Time = nowtime
online := make([]int32, 0, len(userPlatform.Clients))
for _, client := range userPlatform.Clients {
online = append(online, int32(client.PlatformID))
}
result = append(result, UserState{UserID: userID, Online: online})
}
return result
} }
func (u *UserMap) DeleteAll(key string) { func (u *userMap) UserState() <-chan UserState {
u.m.Delete(key) return u.ch
} }

@ -18,6 +18,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/rpccache"
pbAuth "github.com/openimsdk/protocol/auth" pbAuth "github.com/openimsdk/protocol/auth"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"net/http" "net/http"
@ -48,6 +49,7 @@ type LongConnServer interface {
KickUserConn(client *Client) error KickUserConn(client *Client) error
UnRegister(c *Client) UnRegister(c *Client)
SetKickHandlerInfo(i *kickHandler) SetKickHandlerInfo(i *kickHandler)
SubUserOnlineStatus(ctx context.Context, client *Client, data *Req) ([]byte, error)
Compressor Compressor
Encoder Encoder
MessageHandler MessageHandler
@ -60,7 +62,9 @@ type WsServer struct {
registerChan chan *Client registerChan chan *Client
unregisterChan chan *Client unregisterChan chan *Client
kickHandlerChan chan *kickHandler kickHandlerChan chan *kickHandler
clients *UserMap clients UserMap
online *rpccache.OnlineCache
subscription *Subscription
clientPool sync.Pool clientPool sync.Pool
onlineUserNum atomic.Int64 onlineUserNum atomic.Int64
onlineUserConnNum atomic.Int64 onlineUserConnNum atomic.Int64
@ -90,18 +94,18 @@ func (ws *WsServer) SetDiscoveryRegistry(disCov discovery.SvcDiscoveryRegistry,
ws.disCov = disCov ws.disCov = disCov
} }
func (ws *WsServer) SetUserOnlineStatus(ctx context.Context, client *Client, status int32) { //func (ws *WsServer) SetUserOnlineStatus(ctx context.Context, client *Client, status int32) {
err := ws.userClient.SetUserStatus(ctx, client.UserID, status, client.PlatformID) // err := ws.userClient.SetUserStatus(ctx, client.UserID, status, client.PlatformID)
if err != nil { // if err != nil {
log.ZWarn(ctx, "SetUserStatus err", err) // log.ZWarn(ctx, "SetUserStatus err", err)
} // }
switch status { // switch status {
case constant.Online: // case constant.Online:
ws.webhookAfterUserOnline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOnline, client.UserID, client.PlatformID, client.IsBackground, client.ctx.GetConnID()) // ws.webhookAfterUserOnline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOnline, client.UserID, client.PlatformID, client.IsBackground, client.ctx.GetConnID())
case constant.Offline: // case constant.Offline:
ws.webhookAfterUserOffline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOffline, client.UserID, client.PlatformID, client.ctx.GetConnID()) // ws.webhookAfterUserOffline(ctx, &ws.msgGatewayConfig.WebhooksConfig.AfterUserOffline, client.UserID, client.PlatformID, client.ctx.GetConnID())
} // }
} //}
func (ws *WsServer) UnRegister(c *Client) { func (ws *WsServer) UnRegister(c *Client) {
ws.unregisterChan <- c ws.unregisterChan <- c
@ -119,11 +123,13 @@ func (ws *WsServer) GetUserPlatformCons(userID string, platform int) ([]*Client,
return ws.clients.Get(userID, platform) return ws.clients.Get(userID, platform)
} }
func NewWsServer(msgGatewayConfig *Config, opts ...Option) (*WsServer, error) { func NewWsServer(msgGatewayConfig *Config, opts ...Option) *WsServer {
var config configs var config configs
for _, o := range opts { for _, o := range opts {
o(&config) o(&config)
} }
//userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID)
v := validator.New() v := validator.New()
return &WsServer{ return &WsServer{
msgGatewayConfig: msgGatewayConfig, msgGatewayConfig: msgGatewayConfig,
@ -141,10 +147,11 @@ func NewWsServer(msgGatewayConfig *Config, opts ...Option) (*WsServer, error) {
kickHandlerChan: make(chan *kickHandler, 1000), kickHandlerChan: make(chan *kickHandler, 1000),
validate: v, validate: v,
clients: newUserMap(), clients: newUserMap(),
subscription: newSubscription(),
Compressor: NewGzipCompressor(), Compressor: NewGzipCompressor(),
Encoder: NewGobEncoder(), Encoder: NewGobEncoder(),
webhookClient: webhook.NewWebhookClient(msgGatewayConfig.WebhooksConfig.URL), webhookClient: webhook.NewWebhookClient(msgGatewayConfig.WebhooksConfig.URL),
}, nil }
} }
func (ws *WsServer) Run(done chan error) error { func (ws *WsServer) Run(done chan error) error {
@ -278,11 +285,11 @@ func (ws *WsServer) registerClient(client *Client) {
}() }()
} }
wg.Add(1) //wg.Add(1)
go func() { //go func() {
defer wg.Done() // defer wg.Done()
ws.SetUserOnlineStatus(client.ctx, client, constant.Online) // ws.SetUserOnlineStatus(client.ctx, client, constant.Online)
}() //}()
wg.Wait() wg.Wait()
@ -309,7 +316,7 @@ func getRemoteAdders(client []*Client) string {
} }
func (ws *WsServer) KickUserConn(client *Client) error { func (ws *WsServer) KickUserConn(client *Client) error {
ws.clients.deleteClients(client.UserID, []*Client{client}) ws.clients.DeleteClients(client.UserID, []*Client{client})
return client.KickOnlineMessage() return client.KickOnlineMessage()
} }
@ -325,7 +332,7 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
if !clientOK { if !clientOK {
return return
} }
ws.clients.deleteClients(newClient.UserID, oldClients) ws.clients.DeleteClients(newClient.UserID, oldClients)
for _, c := range oldClients { for _, c := range oldClients {
err := c.KickOnlineMessage() err := c.KickOnlineMessage()
if err != nil { if err != nil {
@ -345,13 +352,14 @@ func (ws *WsServer) multiTerminalLoginChecker(clientOK bool, oldClients []*Clien
func (ws *WsServer) unregisterClient(client *Client) { func (ws *WsServer) unregisterClient(client *Client) {
defer ws.clientPool.Put(client) defer ws.clientPool.Put(client)
isDeleteUser := ws.clients.delete(client.UserID, client.ctx.GetRemoteAddr()) isDeleteUser := ws.clients.DeleteClients(client.UserID, []*Client{client})
if isDeleteUser { if isDeleteUser {
ws.onlineUserNum.Add(-1) ws.onlineUserNum.Add(-1)
prommetrics.OnlineUserGauge.Dec() prommetrics.OnlineUserGauge.Dec()
} }
ws.onlineUserConnNum.Add(-1) ws.onlineUserConnNum.Add(-1)
ws.SetUserOnlineStatus(client.ctx, client, constant.Offline) ws.subscription.DelClient(client)
//ws.SetUserOnlineStatus(client.ctx, client, constant.Offline)
log.ZInfo(client.ctx, "user offline", "close reason", client.closedErr, "online user Num", log.ZInfo(client.ctx, "user offline", "close reason", client.closedErr, "online user Num",
ws.onlineUserNum.Load(), "online user conn Num", ws.onlineUserNum.Load(), "online user conn Num",
ws.onlineUserConnNum.Load(), ws.onlineUserConnNum.Load(),

@ -17,6 +17,7 @@ package msgtransfer
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
"github.com/openimsdk/tools/db/mongoutil" "github.com/openimsdk/tools/db/mongoutil"
@ -29,16 +30,12 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mw" "github.com/openimsdk/tools/mw"
"github.com/openimsdk/tools/system/program" "github.com/openimsdk/tools/system/program"
"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"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
) )
@ -82,12 +79,21 @@ func Start(ctx context.Context, index int, config *Config) error {
client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()), client.AddOption(mw.GrpcClient(), grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin"))) grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin")))
msgModel := redis.NewMsgCache(rdb) msgModel := redis.NewMsgCache(rdb)
seqModel := redis.NewSeqCache(rdb)
msgDocModel, err := mgo.NewMsgMongo(mgocli.GetDB()) msgDocModel, err := mgo.NewMsgMongo(mgocli.GetDB())
if err != nil { if err != nil {
return err return err
} }
msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqModel, &config.KafkaConfig) seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB())
if err != nil {
return err
}
seqConversationCache := redis.NewSeqConversationCacheRedis(rdb, seqConversation)
seqUser, err := mgo.NewSeqUserMongo(mgocli.GetDB())
if err != nil {
return err
}
seqUserCache := redis.NewSeqUserCacheRedis(rdb, seqUser)
msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqUserCache, seqConversationCache, &config.KafkaConfig)
if err != nil { if err != nil {
return err return err
} }
@ -130,14 +136,8 @@ func (m *MsgTransfer) Start(index int, config *Config) error {
netDone <- struct{}{} netDone <- struct{}{}
return return
} }
proreg := prometheus.NewRegistry()
proreg.MustRegister( if err := prommetrics.TransferInit(prometheusPort); err != nil && err != http.ErrServerClosed {
collectors.NewGoCollector(),
)
proreg.MustRegister(prommetrics.GetGrpcCusMetrics("Transfer", &config.Share)...)
http.Handle("/metrics", promhttp.HandlerFor(proreg, promhttp.HandlerOpts{Registry: proreg}))
err = http.ListenAndServe(fmt.Sprintf(":%d", prometheusPort), nil)
if err != nil && err != http.ErrServerClosed {
netErr = errs.WrapMsg(err, "prometheus start error", "prometheusPort", prometheusPort) netErr = errs.WrapMsg(err, "prometheus start error", "prometheusPort", prometheusPort)
netDone <- struct{}{} netDone <- struct{}{}
} }

@ -16,8 +16,11 @@ package fcm
import ( import (
"context" "context"
"fmt"
"github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options" "github.com/openimsdk/open-im-server/v3/internal/push/offlinepush/options"
"github.com/openimsdk/tools/utils/httputil"
"path/filepath" "path/filepath"
"strings"
firebase "firebase.google.com/go" firebase "firebase.google.com/go"
"firebase.google.com/go/messaging" "firebase.google.com/go/messaging"
@ -40,13 +43,25 @@ type Fcm struct {
// NewClient initializes a new FCM client using the Firebase Admin SDK. // NewClient initializes a new FCM client using the Firebase Admin SDK.
// It requires the FCM service account credentials file located within the project's configuration directory. // It requires the FCM service account credentials file located within the project's configuration directory.
func NewClient(pushConf *config.Push, cache cache.ThirdCache) (*Fcm, error) { func NewClient(pushConf *config.Push, cache cache.ThirdCache, fcmConfigPath string) (*Fcm, error) {
projectRoot, err := config.GetProjectRoot() var opt option.ClientOption
if err != nil { switch {
return nil, err case len(pushConf.FCM.FilePath) != 0:
// with file path
credentialsFilePath := filepath.Join(fcmConfigPath, pushConf.FCM.FilePath)
opt = option.WithCredentialsFile(credentialsFilePath)
case len(pushConf.FCM.AuthURL) != 0:
// with authentication URL
client := httputil.NewHTTPClient(httputil.NewClientConfig())
resp, err := client.Get(pushConf.FCM.AuthURL)
if err != nil {
return nil, err
}
opt = option.WithCredentialsJSON(resp)
default:
return nil, errs.New("no FCM config").Wrap()
} }
credentialsFilePath := filepath.Join(projectRoot, "config", pushConf.FCM.ServiceAccount)
opt := option.WithCredentialsFile(credentialsFilePath)
fcmApp, err := firebase.NewApp(context.Background(), nil, opt) fcmApp, err := firebase.NewApp(context.Background(), nil, opt)
if err != nil { if err != nil {
return nil, errs.Wrap(err) return nil, errs.Wrap(err)
@ -56,7 +71,6 @@ func NewClient(pushConf *config.Push, cache cache.ThirdCache) (*Fcm, error) {
if err != nil { if err != nil {
return nil, errs.Wrap(err) return nil, errs.Wrap(err)
} }
return &Fcm{fcmMsgCli: fcmMsgClient, cache: cache}, nil return &Fcm{fcmMsgCli: fcmMsgClient, cache: cache}, nil
} }
@ -79,6 +93,8 @@ func (f *Fcm) Push(ctx context.Context, userIDs []string, title, content string,
notification.Body = content notification.Body = content
notification.Title = title notification.Title = title
var messages []*messaging.Message var messages []*messaging.Message
var sendErrBuilder strings.Builder
var msgErrBuilder strings.Builder
for userID, personTokens := range allTokens { for userID, personTokens := range allTokens {
apns := &messaging.APNSConfig{Payload: &messaging.APNSPayload{Aps: &messaging.Aps{Sound: opts.IOSPushSound}}} apns := &messaging.APNSConfig{Payload: &messaging.APNSPayload{Aps: &messaging.Aps{Sound: opts.IOSPushSound}}}
messageCount := len(messages) messageCount := len(messages)
@ -86,9 +102,21 @@ func (f *Fcm) Push(ctx context.Context, userIDs []string, title, content string,
response, err := f.fcmMsgCli.SendAll(ctx, messages) response, err := f.fcmMsgCli.SendAll(ctx, messages)
if err != nil { if err != nil {
Fail = Fail + messageCount Fail = Fail + messageCount
// Record push error
sendErrBuilder.WriteString(err.Error())
sendErrBuilder.WriteByte('.')
} else { } else {
Success = Success + response.SuccessCount Success = Success + response.SuccessCount
Fail = Fail + response.FailureCount Fail = Fail + response.FailureCount
if response.FailureCount != 0 {
// Record message error
for i := range response.Responses {
if !response.Responses[i].Success {
msgErrBuilder.WriteString(response.Responses[i].Error.Error())
msgErrBuilder.WriteByte('.')
}
}
}
} }
messages = messages[0:0] messages = messages[0:0]
} }
@ -134,5 +162,9 @@ func (f *Fcm) Push(ctx context.Context, userIDs []string, title, content string,
Fail = Fail + response.FailureCount Fail = Fail + response.FailureCount
} }
} }
if Fail != 0 {
return errs.New(fmt.Sprintf("%d message send failed;send err:%s;message err:%s",
Fail, sendErrBuilder.String(), msgErrBuilder.String())).Wrap()
}
return nil return nil
} }

@ -36,13 +36,13 @@ type OfflinePusher interface {
Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error Push(ctx context.Context, userIDs []string, title, content string, opts *options.Opts) error
} }
func NewOfflinePusher(pushConf *config.Push, cache cache.ThirdCache) (OfflinePusher, error) { func NewOfflinePusher(pushConf *config.Push, cache cache.ThirdCache, fcmConfigPath string) (OfflinePusher, error) {
var offlinePusher OfflinePusher var offlinePusher OfflinePusher
switch pushConf.Enable { switch pushConf.Enable {
case geTUI: case geTUI:
offlinePusher = getui.NewClient(pushConf, cache) offlinePusher = getui.NewClient(pushConf, cache)
case firebase: case firebase:
return fcm.NewClient(pushConf, cache) return fcm.NewClient(pushConf, cache, fcmConfigPath)
case jPush: case jPush:
offlinePusher = jpush.NewClient(pushConf) offlinePusher = jpush.NewClient(pushConf)
default: default:

@ -29,6 +29,7 @@ type Config struct {
WebhooksConfig config.Webhooks WebhooksConfig config.Webhooks
LocalCacheConfig config.LocalCache LocalCacheConfig config.LocalCache
Discovery config.Discovery Discovery config.Discovery
FcmConfigPath string
} }
func (p pushServer) PushMsg(ctx context.Context, req *pbpush.PushMsgReq) (*pbpush.PushMsgResp, error) { func (p pushServer) PushMsg(ctx context.Context, req *pbpush.PushMsgReq) (*pbpush.PushMsgResp, error) {
@ -50,7 +51,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
return err return err
} }
cacheModel := redis.NewThirdCache(rdb) cacheModel := redis.NewThirdCache(rdb)
offlinePusher, err := offlinepush.NewOfflinePusher(&config.RpcConfig, cacheModel) offlinePusher, err := offlinepush.NewOfflinePusher(&config.RpcConfig, cacheModel, config.FcmConfigPath)
if err != nil { if err != nil {
return err return err
} }

@ -28,6 +28,7 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil" "github.com/openimsdk/open-im-server/v3/pkg/util/conversationutil"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
pbchat "github.com/openimsdk/protocol/msg" pbchat "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/protocol/msggateway"
pbpush "github.com/openimsdk/protocol/push" pbpush "github.com/openimsdk/protocol/push"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
@ -45,6 +46,7 @@ type ConsumerHandler struct {
pushConsumerGroup *kafka.MConsumerGroup pushConsumerGroup *kafka.MConsumerGroup
offlinePusher offlinepush.OfflinePusher offlinePusher offlinepush.OfflinePusher
onlinePusher OnlinePusher onlinePusher OnlinePusher
onlineCache *rpccache.OnlineCache
groupLocalCache *rpccache.GroupLocalCache groupLocalCache *rpccache.GroupLocalCache
conversationLocalCache *rpccache.ConversationLocalCache conversationLocalCache *rpccache.ConversationLocalCache
msgRpcClient rpcclient.MessageRpcClient msgRpcClient rpcclient.MessageRpcClient
@ -63,16 +65,17 @@ func NewConsumerHandler(config *Config, offlinePusher offlinepush.OfflinePusher,
if err != nil { if err != nil {
return nil, err return nil, err
} }
userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID)
consumerHandler.offlinePusher = offlinePusher consumerHandler.offlinePusher = offlinePusher
consumerHandler.onlinePusher = NewOnlinePusher(client, config) consumerHandler.onlinePusher = NewOnlinePusher(client, config)
consumerHandler.groupRpcClient = rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group) consumerHandler.groupRpcClient = rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group)
consumerHandler.groupLocalCache = rpccache.NewGroupLocalCache(consumerHandler.groupRpcClient, &config.LocalCacheConfig, rdb) consumerHandler.groupLocalCache = rpccache.NewGroupLocalCache(consumerHandler.groupRpcClient, &config.LocalCacheConfig, rdb)
consumerHandler.msgRpcClient = rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg) consumerHandler.msgRpcClient = rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg)
consumerHandler.conversationRpcClient = rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation) consumerHandler.conversationRpcClient = rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation)
consumerHandler.conversationLocalCache = rpccache.NewConversationLocalCache(consumerHandler.conversationRpcClient, consumerHandler.conversationLocalCache = rpccache.NewConversationLocalCache(consumerHandler.conversationRpcClient, &config.LocalCacheConfig, rdb)
&config.LocalCacheConfig, rdb)
consumerHandler.webhookClient = webhook.NewWebhookClient(config.WebhooksConfig.URL) consumerHandler.webhookClient = webhook.NewWebhookClient(config.WebhooksConfig.URL)
consumerHandler.config = config consumerHandler.config = config
consumerHandler.onlineCache = rpccache.NewOnlineCache(userRpcClient, consumerHandler.groupLocalCache, rdb, nil)
return &consumerHandler, nil return &consumerHandler, nil
} }
@ -125,12 +128,12 @@ func (c *ConsumerHandler) ConsumeClaim(sess sarama.ConsumerGroupSession, claim s
} }
// Push2User Suitable for two types of conversations, one is SingleChatType and the other is NotificationChatType. // Push2User Suitable for two types of conversations, one is SingleChatType and the other is NotificationChatType.
func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *sdkws.MsgData) error { func (c *ConsumerHandler) Push2User(ctx context.Context, userIDs []string, msg *sdkws.MsgData) (err error) {
log.ZDebug(ctx, "Get msg from msg_transfer And push msg", "userIDs", userIDs, "msg", msg.String()) log.ZDebug(ctx, "Get msg from msg_transfer And push msg", "userIDs", userIDs, "msg", msg.String())
if err := c.webhookBeforeOnlinePush(ctx, &c.config.WebhooksConfig.BeforeOnlinePush, userIDs, msg); err != nil { if err := c.webhookBeforeOnlinePush(ctx, &c.config.WebhooksConfig.BeforeOnlinePush, userIDs, msg); err != nil {
return err return err
} }
wsResults, err := c.onlinePusher.GetConnsAndOnlinePush(ctx, msg, userIDs) wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, userIDs)
if err != nil { if err != nil {
return err return err
} }
@ -179,8 +182,40 @@ func (c *ConsumerHandler) shouldPushOffline(_ context.Context, msg *sdkws.MsgDat
return true return true
} }
func (c *ConsumerHandler) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) ([]*msggateway.SingleMsgToUserResults, error) {
var (
onlineUserIDs []string
offlineUserIDs []string
)
for _, userID := range pushToUserIDs {
online, err := c.onlineCache.GetUserOnline(ctx, userID)
if err != nil {
return nil, err
}
if online {
onlineUserIDs = append(onlineUserIDs, userID)
} else {
offlineUserIDs = append(offlineUserIDs, userID)
}
}
var result []*msggateway.SingleMsgToUserResults
if len(onlineUserIDs) > 0 {
var err error
result, err = c.onlinePusher.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs)
if err != nil {
return nil, err
}
}
for _, userID := range offlineUserIDs {
result = append(result, &msggateway.SingleMsgToUserResults{
UserID: userID,
})
}
return result, nil
}
func (c *ConsumerHandler) Push2Group(ctx context.Context, groupID string, msg *sdkws.MsgData) (err error) { func (c *ConsumerHandler) Push2Group(ctx context.Context, groupID string, msg *sdkws.MsgData) (err error) {
log.ZDebug(ctx, "Get super group msg from msg_transfer and push msg", "msg", msg.String(), "groupID", groupID) log.ZDebug(ctx, "Get group msg from msg_transfer and push msg", "msg", msg.String(), "groupID", groupID)
var pushToUserIDs []string var pushToUserIDs []string
if err = c.webhookBeforeGroupOnlinePush(ctx, &c.config.WebhooksConfig.BeforeGroupOnlinePush, groupID, msg, if err = c.webhookBeforeGroupOnlinePush(ctx, &c.config.WebhooksConfig.BeforeGroupOnlinePush, groupID, msg,
&pushToUserIDs); err != nil { &pushToUserIDs); err != nil {
@ -192,7 +227,7 @@ func (c *ConsumerHandler) Push2Group(ctx context.Context, groupID string, msg *s
return err return err
} }
wsResults, err := c.onlinePusher.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs) wsResults, err := c.GetConnsAndOnlinePush(ctx, msg, pushToUserIDs)
if err != nil { if err != nil {
return err return err
} }

@ -16,6 +16,7 @@ package auth
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" redis2 "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
"github.com/openimsdk/tools/db/redisutil" "github.com/openimsdk/tools/db/redisutil"
@ -32,7 +33,6 @@ import (
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/tokenverify" "github.com/openimsdk/tools/tokenverify"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -61,7 +61,7 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
userRpcClient: &userRpcClient, userRpcClient: &userRpcClient,
RegisterCenter: client, RegisterCenter: client,
authDatabase: controller.NewAuthDatabase( authDatabase: controller.NewAuthDatabase(
redis2.NewTokenCacheModel(rdb), redis2.NewTokenCacheModel(rdb, config.RpcConfig.TokenPolicy.Expire),
config.Share.Secret, config.Share.Secret,
config.RpcConfig.TokenPolicy.Expire, config.RpcConfig.TokenPolicy.Expire,
), ),
@ -153,21 +153,19 @@ func (s *authServer) ForceLogout(ctx context.Context, req *pbauth.ForceLogoutReq
if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil {
return nil, err return nil, err
} }
if err := s.forceKickOff(ctx, req.UserID, req.PlatformID, mcontext.GetOperationID(ctx)); err != nil { if err := s.forceKickOff(ctx, req.UserID, req.PlatformID); err != nil {
return nil, err return nil, err
} }
return &pbauth.ForceLogoutResp{}, nil return &pbauth.ForceLogoutResp{}, nil
} }
func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID int32, operationID string) error { func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID int32) error {
conns, err := s.RegisterCenter.GetConns(ctx, s.config.Share.RpcRegisterName.MessageGateway) conns, err := s.RegisterCenter.GetConns(ctx, s.config.Share.RpcRegisterName.MessageGateway)
if err != nil { if err != nil {
return err return err
} }
for _, v := range conns { for _, v := range conns {
log.ZDebug(ctx, "forceKickOff", "conn", v.Target()) log.ZDebug(ctx, "forceKickOff", "conn", v.Target())
}
for _, v := range conns {
client := msggateway.NewMsgGatewayClient(v) client := msggateway.NewMsgGatewayClient(v)
kickReq := &msggateway.KickUserOfflineReq{KickUserIDList: []string{userID}, PlatformID: platformID} kickReq := &msggateway.KickUserOfflineReq{KickUserIDList: []string{userID}, PlatformID: platformID}
_, err := client.KickUserOffline(ctx, kickReq) _, err := client.KickUserOffline(ctx, kickReq)
@ -175,8 +173,24 @@ func (s *authServer) forceKickOff(ctx context.Context, userID string, platformID
log.ZError(ctx, "forceKickOff", err, "kickReq", kickReq) log.ZError(ctx, "forceKickOff", err, "kickReq", kickReq)
} }
} }
m, err := s.authDatabase.GetTokensWithoutError(ctx, userID, int(platformID))
if err != nil && err != redis.Nil {
return err
}
for k := range m {
m[k] = constant.KickedToken
log.ZDebug(ctx, "set token map is ", "token map", m, "userID",
userID, "token", k)
err = s.authDatabase.SetTokenMapByUidPid(ctx, userID, int(platformID), m)
if err != nil {
return err
}
}
return nil return nil
} }
func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.InvalidateTokenReq) (*pbauth.InvalidateTokenResp, error) { func (s *authServer) InvalidateToken(ctx context.Context, req *pbauth.InvalidateTokenReq) (*pbauth.InvalidateTokenResp, error) {
m, err := s.authDatabase.GetTokensWithoutError(ctx, req.UserID, int(req.PlatformID)) m, err := s.authDatabase.GetTokensWithoutError(ctx, req.UserID, int(req.PlatformID))
if err != nil && err != redis.Nil { if err != nil && err != redis.Nil {

@ -184,13 +184,23 @@ func (c *conversationServer) GetAllConversations(ctx context.Context, req *pbcon
} }
func (c *conversationServer) GetConversations(ctx context.Context, req *pbconversation.GetConversationsReq) (*pbconversation.GetConversationsResp, error) { func (c *conversationServer) GetConversations(ctx context.Context, req *pbconversation.GetConversationsReq) (*pbconversation.GetConversationsResp, error) {
conversations, err := c.conversationDatabase.FindConversations(ctx, req.OwnerUserID, req.ConversationIDs) conversations, err := c.getConversations(ctx, req.OwnerUserID, req.ConversationIDs)
if err != nil {
return nil, err
}
return &pbconversation.GetConversationsResp{
Conversations: conversations,
}, nil
}
func (c *conversationServer) getConversations(ctx context.Context, ownerUserID string, conversationIDs []string) ([]*pbconversation.Conversation, error) {
conversations, err := c.conversationDatabase.FindConversations(ctx, ownerUserID, conversationIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp := &pbconversation.GetConversationsResp{Conversations: []*pbconversation.Conversation{}} resp := &pbconversation.GetConversationsResp{Conversations: []*pbconversation.Conversation{}}
resp.Conversations = convert.ConversationsDB2Pb(conversations) resp.Conversations = convert.ConversationsDB2Pb(conversations)
return resp, nil return convert.ConversationsDB2Pb(conversations), nil
} }
func (c *conversationServer) SetConversation(ctx context.Context, req *pbconversation.SetConversationReq) (*pbconversation.SetConversationResp, error) { func (c *conversationServer) SetConversation(ctx context.Context, req *pbconversation.SetConversationReq) (*pbconversation.SetConversationResp, error) {
@ -581,3 +591,14 @@ func (c *conversationServer) UpdateConversation(ctx context.Context, req *pbconv
} }
return &pbconversation.UpdateConversationResp{}, nil return &pbconversation.UpdateConversationResp{}, nil
} }
func (c *conversationServer) GetOwnerConversation(ctx context.Context, req *pbconversation.GetOwnerConversationReq) (*pbconversation.GetOwnerConversationResp, error) {
total, conversations, err := c.conversationDatabase.GetOwnerConversation(ctx, req.UserID, req.Pagination)
if err != nil {
return nil, err
}
return &pbconversation.GetOwnerConversationResp{
Total: total,
Conversations: convert.ConversationsDB2Pb(conversations),
}, nil
}

@ -0,0 +1,56 @@
package conversation
import (
"context"
"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/util/hashutil"
"github.com/openimsdk/protocol/conversation"
)
func (c *conversationServer) GetFullOwnerConversationIDs(ctx context.Context, req *conversation.GetFullOwnerConversationIDsReq) (*conversation.GetFullOwnerConversationIDsResp, error) {
vl, err := c.conversationDatabase.FindMaxConversationUserVersionCache(ctx, req.UserID)
if err != nil {
return nil, err
}
conversationIDs, err := c.conversationDatabase.GetConversationIDs(ctx, req.UserID)
if err != nil {
return nil, err
}
idHash := hashutil.IdHash(conversationIDs)
if req.IdHash == idHash {
conversationIDs = nil
}
return &conversation.GetFullOwnerConversationIDsResp{
Version: idHash,
VersionID: vl.ID.Hex(),
Equal: req.IdHash == idHash,
ConversationIDs: conversationIDs,
}, nil
}
func (c *conversationServer) GetIncrementalConversation(ctx context.Context, req *conversation.GetIncrementalConversationReq) (*conversation.GetIncrementalConversationResp, error) {
opt := incrversion.Option[*conversation.Conversation, conversation.GetIncrementalConversationResp]{
Ctx: ctx,
VersionKey: req.UserID,
VersionID: req.VersionID,
VersionNumber: req.Version,
Version: c.conversationDatabase.FindConversationUserVersion,
CacheMaxVersion: c.conversationDatabase.FindMaxConversationUserVersionCache,
Find: func(ctx context.Context, conversationIDs []string) ([]*conversation.Conversation, error) {
return c.getConversations(ctx, req.UserID, conversationIDs)
},
Resp: func(version *model.VersionLog, delIDs []string, insertList, updateList []*conversation.Conversation, full bool) *conversation.GetIncrementalConversationResp {
return &conversation.GetIncrementalConversationResp{
VersionID: version.ID.Hex(),
Version: uint64(version.Version),
Full: full,
Delete: delIDs,
Insert: insertList,
Update: updateList,
}
},
}
return opt.Build()
}

@ -16,16 +16,17 @@ package friend
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/convert" "github.com/openimsdk/open-im-server/v3/pkg/common/convert"
pbfriend "github.com/openimsdk/protocol/friend" "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
) )
func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *pbfriend.GetPaginationBlacksReq) (resp *pbfriend.GetPaginationBlacksResp, err error) { func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *relation.GetPaginationBlacksReq) (resp *relation.GetPaginationBlacksResp, err error) {
if err := s.userRpcClient.Access(ctx, req.UserID); err != nil { if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
@ -33,7 +34,7 @@ func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *pbfriend.Ge
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp = &pbfriend.GetPaginationBlacksResp{} resp = &relation.GetPaginationBlacksResp{}
resp.Blacks, err = convert.BlackDB2Pb(ctx, blacks, s.userRpcClient.GetUsersInfoMap) resp.Blacks, err = convert.BlackDB2Pb(ctx, blacks, s.userRpcClient.GetUsersInfoMap)
if err != nil { if err != nil {
return nil, err return nil, err
@ -42,18 +43,18 @@ func (s *friendServer) GetPaginationBlacks(ctx context.Context, req *pbfriend.Ge
return resp, nil return resp, nil
} }
func (s *friendServer) IsBlack(ctx context.Context, req *pbfriend.IsBlackReq) (*pbfriend.IsBlackResp, error) { func (s *friendServer) IsBlack(ctx context.Context, req *relation.IsBlackReq) (*relation.IsBlackResp, error) {
in1, in2, err := s.blackDatabase.CheckIn(ctx, req.UserID1, req.UserID2) in1, in2, err := s.blackDatabase.CheckIn(ctx, req.UserID1, req.UserID2)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp := &pbfriend.IsBlackResp{} resp := &relation.IsBlackResp{}
resp.InUser1Blacks = in1 resp.InUser1Blacks = in1
resp.InUser2Blacks = in2 resp.InUser2Blacks = in2
return resp, nil return resp, nil
} }
func (s *friendServer) RemoveBlack(ctx context.Context, req *pbfriend.RemoveBlackReq) (*pbfriend.RemoveBlackResp, error) { func (s *friendServer) RemoveBlack(ctx context.Context, req *relation.RemoveBlackReq) (*relation.RemoveBlackResp, error) {
if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil { if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil {
return nil, err return nil, err
} }
@ -64,10 +65,10 @@ func (s *friendServer) RemoveBlack(ctx context.Context, req *pbfriend.RemoveBlac
s.notificationSender.BlackDeletedNotification(ctx, req) s.notificationSender.BlackDeletedNotification(ctx, req)
return &pbfriend.RemoveBlackResp{}, nil return &relation.RemoveBlackResp{}, nil
} }
func (s *friendServer) AddBlack(ctx context.Context, req *pbfriend.AddBlackReq) (*pbfriend.AddBlackResp, error) { func (s *friendServer) AddBlack(ctx context.Context, req *relation.AddBlackReq) (*relation.AddBlackResp, error) {
if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccessV3(ctx, req.OwnerUserID, s.config.Share.IMAdminUserID); err != nil {
return nil, err return nil, err
} }
@ -87,5 +88,5 @@ func (s *friendServer) AddBlack(ctx context.Context, req *pbfriend.AddBlackReq)
return nil, err return nil, err
} }
s.notificationSender.BlackAddedNotification(ctx, req) s.notificationSender.BlackAddedNotification(ctx, req)
return &pbfriend.AddBlackResp{}, nil return &relation.AddBlackResp{}, nil
} }

@ -16,14 +16,15 @@ package friend
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
pbfriend "github.com/openimsdk/protocol/friend" "github.com/openimsdk/protocol/relation"
) )
func (s *friendServer) webhookAfterDeleteFriend(ctx context.Context, after *config.AfterConfig, req *pbfriend.DeleteFriendReq) { func (s *friendServer) webhookAfterDeleteFriend(ctx context.Context, after *config.AfterConfig, req *relation.DeleteFriendReq) {
cbReq := &cbapi.CallbackAfterDeleteFriendReq{ cbReq := &cbapi.CallbackAfterDeleteFriendReq{
CallbackCommand: cbapi.CallbackAfterDeleteFriendCommand, CallbackCommand: cbapi.CallbackAfterDeleteFriendCommand,
OwnerUserID: req.OwnerUserID, OwnerUserID: req.OwnerUserID,
@ -32,7 +33,7 @@ func (s *friendServer) webhookAfterDeleteFriend(ctx context.Context, after *conf
s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterDeleteFriendResp{}, after) s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, &cbapi.CallbackAfterDeleteFriendResp{}, after)
} }
func (s *friendServer) webhookBeforeAddFriend(ctx context.Context, before *config.BeforeConfig, req *pbfriend.ApplyToAddFriendReq) error { func (s *friendServer) webhookBeforeAddFriend(ctx context.Context, before *config.BeforeConfig, req *relation.ApplyToAddFriendReq) error {
return webhook.WithCondition(ctx, before, func(ctx context.Context) error { return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
cbReq := &cbapi.CallbackBeforeAddFriendReq{ cbReq := &cbapi.CallbackBeforeAddFriendReq{
CallbackCommand: cbapi.CallbackBeforeAddFriendCommand, CallbackCommand: cbapi.CallbackBeforeAddFriendCommand,
@ -50,7 +51,7 @@ func (s *friendServer) webhookBeforeAddFriend(ctx context.Context, before *confi
}) })
} }
func (s *friendServer) webhookAfterAddFriend(ctx context.Context, after *config.AfterConfig, req *pbfriend.ApplyToAddFriendReq) { func (s *friendServer) webhookAfterAddFriend(ctx context.Context, after *config.AfterConfig, req *relation.ApplyToAddFriendReq) {
cbReq := &cbapi.CallbackAfterAddFriendReq{ cbReq := &cbapi.CallbackAfterAddFriendReq{
CallbackCommand: cbapi.CallbackAfterAddFriendCommand, CallbackCommand: cbapi.CallbackAfterAddFriendCommand,
FromUserID: req.FromUserID, FromUserID: req.FromUserID,
@ -61,8 +62,7 @@ func (s *friendServer) webhookAfterAddFriend(ctx context.Context, after *config.
s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after) s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after)
} }
func (s *friendServer) webhookAfterSetFriendRemark(ctx context.Context, after *config.AfterConfig, req *pbfriend.SetFriendRemarkReq) { func (s *friendServer) webhookAfterSetFriendRemark(ctx context.Context, after *config.AfterConfig, req *relation.SetFriendRemarkReq) {
cbReq := &cbapi.CallbackAfterSetFriendRemarkReq{ cbReq := &cbapi.CallbackAfterSetFriendRemarkReq{
CallbackCommand: cbapi.CallbackAfterSetFriendRemarkCommand, CallbackCommand: cbapi.CallbackAfterSetFriendRemarkCommand,
OwnerUserID: req.OwnerUserID, OwnerUserID: req.OwnerUserID,
@ -73,7 +73,7 @@ func (s *friendServer) webhookAfterSetFriendRemark(ctx context.Context, after *c
s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after) s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after)
} }
func (s *friendServer) webhookAfterImportFriends(ctx context.Context, after *config.AfterConfig, req *pbfriend.ImportFriendReq) { func (s *friendServer) webhookAfterImportFriends(ctx context.Context, after *config.AfterConfig, req *relation.ImportFriendReq) {
cbReq := &cbapi.CallbackAfterImportFriendsReq{ cbReq := &cbapi.CallbackAfterImportFriendsReq{
CallbackCommand: cbapi.CallbackAfterImportFriendsCommand, CallbackCommand: cbapi.CallbackAfterImportFriendsCommand,
OwnerUserID: req.OwnerUserID, OwnerUserID: req.OwnerUserID,
@ -83,7 +83,7 @@ func (s *friendServer) webhookAfterImportFriends(ctx context.Context, after *con
s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after) s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after)
} }
func (s *friendServer) webhookAfterRemoveBlack(ctx context.Context, after *config.AfterConfig, req *pbfriend.RemoveBlackReq) { func (s *friendServer) webhookAfterRemoveBlack(ctx context.Context, after *config.AfterConfig, req *relation.RemoveBlackReq) {
cbReq := &cbapi.CallbackAfterRemoveBlackReq{ cbReq := &cbapi.CallbackAfterRemoveBlackReq{
CallbackCommand: cbapi.CallbackAfterRemoveBlackCommand, CallbackCommand: cbapi.CallbackAfterRemoveBlackCommand,
OwnerUserID: req.OwnerUserID, OwnerUserID: req.OwnerUserID,
@ -93,7 +93,7 @@ func (s *friendServer) webhookAfterRemoveBlack(ctx context.Context, after *confi
s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after) s.webhookClient.AsyncPost(ctx, cbReq.GetCallbackCommand(), cbReq, resp, after)
} }
func (s *friendServer) webhookBeforeSetFriendRemark(ctx context.Context, before *config.BeforeConfig, req *pbfriend.SetFriendRemarkReq) error { func (s *friendServer) webhookBeforeSetFriendRemark(ctx context.Context, before *config.BeforeConfig, req *relation.SetFriendRemarkReq) error {
return webhook.WithCondition(ctx, before, func(ctx context.Context) error { return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
cbReq := &cbapi.CallbackBeforeSetFriendRemarkReq{ cbReq := &cbapi.CallbackBeforeSetFriendRemarkReq{
CallbackCommand: cbapi.CallbackBeforeSetFriendRemarkCommand, CallbackCommand: cbapi.CallbackBeforeSetFriendRemarkCommand,
@ -112,7 +112,7 @@ func (s *friendServer) webhookBeforeSetFriendRemark(ctx context.Context, before
}) })
} }
func (s *friendServer) webhookBeforeAddBlack(ctx context.Context, before *config.BeforeConfig, req *pbfriend.AddBlackReq) error { func (s *friendServer) webhookBeforeAddBlack(ctx context.Context, before *config.BeforeConfig, req *relation.AddBlackReq) error {
return webhook.WithCondition(ctx, before, func(ctx context.Context) error { return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
cbReq := &cbapi.CallbackBeforeAddBlackReq{ cbReq := &cbapi.CallbackBeforeAddBlackReq{
CallbackCommand: cbapi.CallbackBeforeAddBlackCommand, CallbackCommand: cbapi.CallbackBeforeAddBlackCommand,
@ -124,7 +124,7 @@ func (s *friendServer) webhookBeforeAddBlack(ctx context.Context, before *config
}) })
} }
func (s *friendServer) webhookBeforeAddFriendAgree(ctx context.Context, before *config.BeforeConfig, req *pbfriend.RespondFriendApplyReq) error { func (s *friendServer) webhookBeforeAddFriendAgree(ctx context.Context, before *config.BeforeConfig, req *relation.RespondFriendApplyReq) error {
return webhook.WithCondition(ctx, before, func(ctx context.Context) error { return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
cbReq := &cbapi.CallbackBeforeAddFriendAgreeReq{ cbReq := &cbapi.CallbackBeforeAddFriendAgreeReq{
CallbackCommand: cbapi.CallbackBeforeAddFriendAgreeCommand, CallbackCommand: cbapi.CallbackBeforeAddFriendAgreeCommand,
@ -138,7 +138,7 @@ func (s *friendServer) webhookBeforeAddFriendAgree(ctx context.Context, before *
}) })
} }
func (s *friendServer) webhookBeforeImportFriends(ctx context.Context, before *config.BeforeConfig, req *pbfriend.ImportFriendReq) error { func (s *friendServer) webhookBeforeImportFriends(ctx context.Context, before *config.BeforeConfig, req *relation.ImportFriendReq) error {
return webhook.WithCondition(ctx, before, func(ctx context.Context) error { return webhook.WithCondition(ctx, before, func(ctx context.Context) error {
cbReq := &cbapi.CallbackBeforeImportFriendsReq{ cbReq := &cbapi.CallbackBeforeImportFriendsReq{
CallbackCommand: cbapi.CallbackBeforeImportFriendsCommand, CallbackCommand: cbapi.CallbackBeforeImportFriendsCommand,

@ -16,6 +16,8 @@ package friend
import ( import (
"context" "context"
"github.com/openimsdk/tools/mq/memamq"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
@ -30,7 +32,7 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/controller"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
pbfriend "github.com/openimsdk/protocol/friend" "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/db/mongoutil" "github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
@ -40,7 +42,7 @@ import (
) )
type friendServer struct { type friendServer struct {
friendDatabase controller.FriendDatabase db controller.FriendDatabase
blackDatabase controller.BlackDatabase blackDatabase controller.BlackDatabase
userRpcClient *rpcclient.UserRpcClient userRpcClient *rpcclient.UserRpcClient
notificationSender *FriendNotificationSender notificationSender *FriendNotificationSender
@ -48,13 +50,14 @@ type friendServer struct {
RegisterCenter discovery.SvcDiscoveryRegistry RegisterCenter discovery.SvcDiscoveryRegistry
config *Config config *Config
webhookClient *webhook.Client webhookClient *webhook.Client
queue *memamq.MemoryQueue
} }
type Config struct { type Config struct {
RpcConfig config.Friend RpcConfig config.Friend
RedisConfig config.Redis RedisConfig config.Redis
MongodbConfig config.Mongo MongodbConfig config.Mongo
//ZookeeperConfig config.ZooKeeper // ZookeeperConfig config.ZooKeeper
NotificationConfig config.Notification NotificationConfig config.Notification
Share config.Share Share config.Share
WebhooksConfig config.Webhooks WebhooksConfig config.Webhooks
@ -100,8 +103,8 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
localcache.InitLocalCache(&config.LocalCacheConfig) localcache.InitLocalCache(&config.LocalCacheConfig)
// Register Friend server with refactored MongoDB and Redis integrations // Register Friend server with refactored MongoDB and Redis integrations
pbfriend.RegisterFriendServer(server, &friendServer{ relation.RegisterFriendServer(server, &friendServer{
friendDatabase: controller.NewFriendDatabase( db: controller.NewFriendDatabase(
friendMongoDB, friendMongoDB,
friendRequestMongoDB, friendRequestMongoDB,
redis.NewFriendCacheRedis(rdb, &config.LocalCacheConfig, friendMongoDB, redis.GetRocksCacheOptions()), redis.NewFriendCacheRedis(rdb, &config.LocalCacheConfig, friendMongoDB, redis.GetRocksCacheOptions()),
@ -117,14 +120,14 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
conversationRpcClient: rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation), conversationRpcClient: rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation),
config: config, config: config,
webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL), webhookClient: webhook.NewWebhookClient(config.WebhooksConfig.URL),
queue: memamq.NewMemoryQueue(128, 1024*8),
}) })
return nil return nil
} }
// ok. // ok.
func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *pbfriend.ApplyToAddFriendReq) (resp *pbfriend.ApplyToAddFriendResp, err error) { func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *relation.ApplyToAddFriendReq) (resp *relation.ApplyToAddFriendResp, err error) {
resp = &pbfriend.ApplyToAddFriendResp{} resp = &relation.ApplyToAddFriendResp{}
if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccessV3(ctx, req.FromUserID, s.config.Share.IMAdminUserID); err != nil {
return nil, err return nil, err
} }
@ -138,14 +141,14 @@ func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *pbfriend.Apply
return nil, err return nil, err
} }
in1, in2, err := s.friendDatabase.CheckIn(ctx, req.FromUserID, req.ToUserID) in1, in2, err := s.db.CheckIn(ctx, req.FromUserID, req.ToUserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if in1 && in2 { if in1 && in2 {
return nil, servererrs.ErrRelationshipAlready.WrapMsg("already friends has f") return nil, servererrs.ErrRelationshipAlready.WrapMsg("already friends has f")
} }
if err = s.friendDatabase.AddFriendRequest(ctx, req.FromUserID, req.ToUserID, req.ReqMsg, req.Ex); err != nil { if err = s.db.AddFriendRequest(ctx, req.FromUserID, req.ToUserID, req.ReqMsg, req.Ex); err != nil {
return nil, err return nil, err
} }
s.notificationSender.FriendApplicationAddNotification(ctx, req) s.notificationSender.FriendApplicationAddNotification(ctx, req)
@ -154,7 +157,7 @@ func (s *friendServer) ApplyToAddFriend(ctx context.Context, req *pbfriend.Apply
} }
// ok. // ok.
func (s *friendServer) ImportFriends(ctx context.Context, req *pbfriend.ImportFriendReq) (resp *pbfriend.ImportFriendResp, err error) { func (s *friendServer) ImportFriends(ctx context.Context, req *relation.ImportFriendReq) (resp *relation.ImportFriendResp, err error) {
if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAdmin(ctx, s.config.Share.IMAdminUserID); err != nil {
return nil, err return nil, err
} }
@ -172,11 +175,11 @@ func (s *friendServer) ImportFriends(ctx context.Context, req *pbfriend.ImportFr
return nil, err return nil, err
} }
if err := s.friendDatabase.BecomeFriends(ctx, req.OwnerUserID, req.FriendUserIDs, constant.BecomeFriendByImport); err != nil { if err := s.db.BecomeFriends(ctx, req.OwnerUserID, req.FriendUserIDs, constant.BecomeFriendByImport); err != nil {
return nil, err return nil, err
} }
for _, userID := range req.FriendUserIDs { for _, userID := range req.FriendUserIDs {
s.notificationSender.FriendApplicationAgreedNotification(ctx, &pbfriend.RespondFriendApplyReq{ s.notificationSender.FriendApplicationAgreedNotification(ctx, &relation.RespondFriendApplyReq{
FromUserID: req.OwnerUserID, FromUserID: req.OwnerUserID,
ToUserID: userID, ToUserID: userID,
HandleResult: constant.FriendResponseAgree, HandleResult: constant.FriendResponseAgree,
@ -184,12 +187,12 @@ func (s *friendServer) ImportFriends(ctx context.Context, req *pbfriend.ImportFr
} }
s.webhookAfterImportFriends(ctx, &s.config.WebhooksConfig.AfterImportFriends, req) s.webhookAfterImportFriends(ctx, &s.config.WebhooksConfig.AfterImportFriends, req)
return &pbfriend.ImportFriendResp{}, nil return &relation.ImportFriendResp{}, nil
} }
// ok. // ok.
func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.RespondFriendApplyReq) (resp *pbfriend.RespondFriendApplyResp, err error) { func (s *friendServer) RespondFriendApply(ctx context.Context, req *relation.RespondFriendApplyReq) (resp *relation.RespondFriendApplyResp, err error) {
resp = &pbfriend.RespondFriendApplyResp{} resp = &relation.RespondFriendApplyResp{}
if err := authverify.CheckAccessV3(ctx, req.ToUserID, s.config.Share.IMAdminUserID); err != nil { if err := authverify.CheckAccessV3(ctx, req.ToUserID, s.config.Share.IMAdminUserID); err != nil {
return nil, err return nil, err
} }
@ -204,7 +207,7 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.Res
if err := s.webhookBeforeAddFriendAgree(ctx, &s.config.WebhooksConfig.BeforeAddFriendAgree, req); err != nil && err != servererrs.ErrCallbackContinue { if err := s.webhookBeforeAddFriendAgree(ctx, &s.config.WebhooksConfig.BeforeAddFriendAgree, req); err != nil && err != servererrs.ErrCallbackContinue {
return nil, err return nil, err
} }
err := s.friendDatabase.AgreeFriendRequest(ctx, &friendRequest) err := s.db.AgreeFriendRequest(ctx, &friendRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -212,7 +215,7 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.Res
return resp, nil return resp, nil
} }
if req.HandleResult == constant.FriendResponseRefuse { if req.HandleResult == constant.FriendResponseRefuse {
err := s.friendDatabase.RefuseFriendRequest(ctx, &friendRequest) err := s.db.RefuseFriendRequest(ctx, &friendRequest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -223,16 +226,16 @@ func (s *friendServer) RespondFriendApply(ctx context.Context, req *pbfriend.Res
} }
// ok. // ok.
func (s *friendServer) DeleteFriend(ctx context.Context, req *pbfriend.DeleteFriendReq) (resp *pbfriend.DeleteFriendResp, err error) { func (s *friendServer) DeleteFriend(ctx context.Context, req *relation.DeleteFriendReq) (resp *relation.DeleteFriendResp, err error) {
resp = &pbfriend.DeleteFriendResp{} resp = &relation.DeleteFriendResp{}
if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil { if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil {
return nil, err return nil, err
} }
_, err = s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID}) _, err = s.db.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := s.friendDatabase.Delete(ctx, req.OwnerUserID, []string{req.FriendUserID}); err != nil { if err := s.db.Delete(ctx, req.OwnerUserID, []string{req.FriendUserID}); err != nil {
return nil, err return nil, err
} }
s.notificationSender.FriendDeletedNotification(ctx, req) s.notificationSender.FriendDeletedNotification(ctx, req)
@ -241,19 +244,19 @@ func (s *friendServer) DeleteFriend(ctx context.Context, req *pbfriend.DeleteFri
} }
// ok. // ok.
func (s *friendServer) SetFriendRemark(ctx context.Context, req *pbfriend.SetFriendRemarkReq) (resp *pbfriend.SetFriendRemarkResp, err error) { func (s *friendServer) SetFriendRemark(ctx context.Context, req *relation.SetFriendRemarkReq) (resp *relation.SetFriendRemarkResp, err error) {
if err = s.webhookBeforeSetFriendRemark(ctx, &s.config.WebhooksConfig.BeforeSetFriendRemark, req); err != nil && err != servererrs.ErrCallbackContinue { if err = s.webhookBeforeSetFriendRemark(ctx, &s.config.WebhooksConfig.BeforeSetFriendRemark, req); err != nil && err != servererrs.ErrCallbackContinue {
return nil, err return nil, err
} }
resp = &pbfriend.SetFriendRemarkResp{} resp = &relation.SetFriendRemarkResp{}
if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil { if err := s.userRpcClient.Access(ctx, req.OwnerUserID); err != nil {
return nil, err return nil, err
} }
_, err = s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID}) _, err = s.db.FindFriendsWithError(ctx, req.OwnerUserID, []string{req.FriendUserID})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := s.friendDatabase.UpdateRemark(ctx, req.OwnerUserID, req.FriendUserID, req.Remark); err != nil { if err := s.db.UpdateRemark(ctx, req.OwnerUserID, req.FriendUserID, req.Remark); err != nil {
return nil, err return nil, err
} }
s.webhookAfterSetFriendRemark(ctx, &s.config.WebhooksConfig.AfterSetFriendRemark, req) s.webhookAfterSetFriendRemark(ctx, &s.config.WebhooksConfig.AfterSetFriendRemark, req)
@ -262,29 +265,40 @@ func (s *friendServer) SetFriendRemark(ctx context.Context, req *pbfriend.SetFri
} }
// ok. // ok.
func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *pbfriend.GetDesignatedFriendsReq) (resp *pbfriend.GetDesignatedFriendsResp, err error) { func (s *friendServer) GetDesignatedFriends(ctx context.Context, req *relation.GetDesignatedFriendsReq) (resp *relation.GetDesignatedFriendsResp, err error) {
resp = &pbfriend.GetDesignatedFriendsResp{} resp = &relation.GetDesignatedFriendsResp{}
if datautil.Duplicate(req.FriendUserIDs) { if datautil.Duplicate(req.FriendUserIDs) {
return nil, errs.ErrArgs.WrapMsg("friend userID repeated") return nil, errs.ErrArgs.WrapMsg("friend userID repeated")
} }
friends, err := s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs) friends, err := s.getFriend(ctx, req.OwnerUserID, req.FriendUserIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if resp.FriendsInfo, err = convert.FriendsDB2Pb(ctx, friends, s.userRpcClient.GetUsersInfoMap); err != nil { return &relation.GetDesignatedFriendsResp{
FriendsInfo: friends,
}, nil
}
func (s *friendServer) getFriend(ctx context.Context, ownerUserID string, friendUserIDs []string) ([]*sdkws.FriendInfo, error) {
if len(friendUserIDs) == 0 {
return nil, nil
}
friends, err := s.db.FindFriendsWithError(ctx, ownerUserID, friendUserIDs)
if err != nil {
return nil, err return nil, err
} }
return resp, nil return convert.FriendsDB2Pb(ctx, friends, s.userRpcClient.GetUsersInfoMap)
} }
// Get the list of friend requests sent out proactively. // Get the list of friend requests sent out proactively.
func (s *friendServer) GetDesignatedFriendsApply(ctx context.Context, func (s *friendServer) GetDesignatedFriendsApply(ctx context.Context,
req *pbfriend.GetDesignatedFriendsApplyReq) (resp *pbfriend.GetDesignatedFriendsApplyResp, err error) { req *relation.GetDesignatedFriendsApplyReq,
friendRequests, err := s.friendDatabase.FindBothFriendRequests(ctx, req.FromUserID, req.ToUserID) ) (resp *relation.GetDesignatedFriendsApplyResp, err error) {
friendRequests, err := s.db.FindBothFriendRequests(ctx, req.FromUserID, req.ToUserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp = &pbfriend.GetDesignatedFriendsApplyResp{} resp = &relation.GetDesignatedFriendsApplyResp{}
resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.userRpcClient.GetUsersInfoMap) resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.userRpcClient.GetUsersInfoMap)
if err != nil { if err != nil {
return nil, err return nil, err
@ -293,15 +307,15 @@ func (s *friendServer) GetDesignatedFriendsApply(ctx context.Context,
} }
// Get received friend requests (i.e., those initiated by others). // Get received friend requests (i.e., those initiated by others).
func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *pbfriend.GetPaginationFriendsApplyToReq) (resp *pbfriend.GetPaginationFriendsApplyToResp, err error) { func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *relation.GetPaginationFriendsApplyToReq) (resp *relation.GetPaginationFriendsApplyToResp, err error) {
if err := s.userRpcClient.Access(ctx, req.UserID); err != nil { if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
total, friendRequests, err := s.friendDatabase.PageFriendRequestToMe(ctx, req.UserID, req.Pagination) total, friendRequests, err := s.db.PageFriendRequestToMe(ctx, req.UserID, req.Pagination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp = &pbfriend.GetPaginationFriendsApplyToResp{} resp = &relation.GetPaginationFriendsApplyToResp{}
resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.userRpcClient.GetUsersInfoMap) resp.FriendRequests, err = convert.FriendRequestDB2Pb(ctx, friendRequests, s.userRpcClient.GetUsersInfoMap)
if err != nil { if err != nil {
return nil, err return nil, err
@ -310,12 +324,12 @@ func (s *friendServer) GetPaginationFriendsApplyTo(ctx context.Context, req *pbf
return resp, nil return resp, nil
} }
func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *pbfriend.GetPaginationFriendsApplyFromReq) (resp *pbfriend.GetPaginationFriendsApplyFromResp, err error) { func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *relation.GetPaginationFriendsApplyFromReq) (resp *relation.GetPaginationFriendsApplyFromResp, err error) {
resp = &pbfriend.GetPaginationFriendsApplyFromResp{} resp = &relation.GetPaginationFriendsApplyFromResp{}
if err := s.userRpcClient.Access(ctx, req.UserID); err != nil { if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
total, friendRequests, err := s.friendDatabase.PageFriendRequestFromMe(ctx, req.UserID, req.Pagination) total, friendRequests, err := s.db.PageFriendRequestFromMe(ctx, req.UserID, req.Pagination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -328,24 +342,24 @@ func (s *friendServer) GetPaginationFriendsApplyFrom(ctx context.Context, req *p
} }
// ok. // ok.
func (s *friendServer) IsFriend(ctx context.Context, req *pbfriend.IsFriendReq) (resp *pbfriend.IsFriendResp, err error) { func (s *friendServer) IsFriend(ctx context.Context, req *relation.IsFriendReq) (resp *relation.IsFriendResp, err error) {
resp = &pbfriend.IsFriendResp{} resp = &relation.IsFriendResp{}
resp.InUser1Friends, resp.InUser2Friends, err = s.friendDatabase.CheckIn(ctx, req.UserID1, req.UserID2) resp.InUser1Friends, resp.InUser2Friends, err = s.db.CheckIn(ctx, req.UserID1, req.UserID2)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
} }
func (s *friendServer) GetPaginationFriends(ctx context.Context, req *pbfriend.GetPaginationFriendsReq) (resp *pbfriend.GetPaginationFriendsResp, err error) { func (s *friendServer) GetPaginationFriends(ctx context.Context, req *relation.GetPaginationFriendsReq) (resp *relation.GetPaginationFriendsResp, err error) {
if err := s.userRpcClient.Access(ctx, req.UserID); err != nil { if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
total, friends, err := s.friendDatabase.PageOwnerFriends(ctx, req.UserID, req.Pagination) total, friends, err := s.db.PageOwnerFriends(ctx, req.UserID, req.Pagination)
if err != nil { if err != nil {
return nil, err return nil, err
} }
resp = &pbfriend.GetPaginationFriendsResp{} resp = &relation.GetPaginationFriendsResp{}
resp.FriendsInfo, err = convert.FriendsDB2Pb(ctx, friends, s.userRpcClient.GetUsersInfoMap) resp.FriendsInfo, err = convert.FriendsDB2Pb(ctx, friends, s.userRpcClient.GetUsersInfoMap)
if err != nil { if err != nil {
return nil, err return nil, err
@ -354,19 +368,19 @@ func (s *friendServer) GetPaginationFriends(ctx context.Context, req *pbfriend.G
return resp, nil return resp, nil
} }
func (s *friendServer) GetFriendIDs(ctx context.Context, req *pbfriend.GetFriendIDsReq) (resp *pbfriend.GetFriendIDsResp, err error) { func (s *friendServer) GetFriendIDs(ctx context.Context, req *relation.GetFriendIDsReq) (resp *relation.GetFriendIDsResp, err error) {
if err := s.userRpcClient.Access(ctx, req.UserID); err != nil { if err := s.userRpcClient.Access(ctx, req.UserID); err != nil {
return nil, err return nil, err
} }
resp = &pbfriend.GetFriendIDsResp{} resp = &relation.GetFriendIDsResp{}
resp.FriendIDs, err = s.friendDatabase.FindFriendUserIDs(ctx, req.UserID) resp.FriendIDs, err = s.db.FindFriendUserIDs(ctx, req.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
} }
func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfriend.GetSpecifiedFriendsInfoReq) (*pbfriend.GetSpecifiedFriendsInfoResp, error) { func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *relation.GetSpecifiedFriendsInfoReq) (*relation.GetSpecifiedFriendsInfoResp, error) {
if len(req.UserIDList) == 0 { if len(req.UserIDList) == 0 {
return nil, errs.ErrArgs.WrapMsg("userIDList is empty") return nil, errs.ErrArgs.WrapMsg("userIDList is empty")
} }
@ -377,7 +391,7 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
if err != nil { if err != nil {
return nil, err return nil, err
} }
friends, err := s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, req.UserIDList) friends, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.UserIDList)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -391,8 +405,8 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
blackMap := datautil.SliceToMap(blacks, func(e *model.Black) string { blackMap := datautil.SliceToMap(blacks, func(e *model.Black) string {
return e.BlockUserID return e.BlockUserID
}) })
resp := &pbfriend.GetSpecifiedFriendsInfoResp{ resp := &relation.GetSpecifiedFriendsInfoResp{
Infos: make([]*pbfriend.GetSpecifiedFriendsInfoInfo, 0, len(req.UserIDList)), Infos: make([]*relation.GetSpecifiedFriendsInfoInfo, 0, len(req.UserIDList)),
} }
for _, userID := range req.UserIDList { for _, userID := range req.UserIDList {
user := userMap[userID] user := userMap[userID]
@ -401,7 +415,6 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
} }
var friendInfo *sdkws.FriendInfo var friendInfo *sdkws.FriendInfo
if friend := friendMap[userID]; friend != nil { if friend := friendMap[userID]; friend != nil {
friendInfo = &sdkws.FriendInfo{ friendInfo = &sdkws.FriendInfo{
OwnerUserID: friend.OwnerUserID, OwnerUserID: friend.OwnerUserID,
Remark: friend.Remark, Remark: friend.Remark,
@ -422,7 +435,7 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
Ex: black.Ex, Ex: black.Ex,
} }
} }
resp.Infos = append(resp.Infos, &pbfriend.GetSpecifiedFriendsInfoInfo{ resp.Infos = append(resp.Infos, &relation.GetSpecifiedFriendsInfoInfo{
UserInfo: user, UserInfo: user,
FriendInfo: friendInfo, FriendInfo: friendInfo,
BlackInfo: blackInfo, BlackInfo: blackInfo,
@ -430,10 +443,11 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
} }
return resp, nil return resp, nil
} }
func (s *friendServer) UpdateFriends( func (s *friendServer) UpdateFriends(
ctx context.Context, ctx context.Context,
req *pbfriend.UpdateFriendsReq, req *relation.UpdateFriendsReq,
) (*pbfriend.UpdateFriendsResp, error) { ) (*relation.UpdateFriendsResp, error) {
if len(req.FriendUserIDs) == 0 { if len(req.FriendUserIDs) == 0 {
return nil, errs.ErrArgs.WrapMsg("friendIDList is empty") return nil, errs.ErrArgs.WrapMsg("friendIDList is empty")
} }
@ -441,7 +455,7 @@ func (s *friendServer) UpdateFriends(
return nil, errs.ErrArgs.WrapMsg("friendIDList repeated") return nil, errs.ErrArgs.WrapMsg("friendIDList repeated")
} }
_, err := s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs) _, err := s.db.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -457,12 +471,27 @@ func (s *friendServer) UpdateFriends(
if req.Ex != nil { if req.Ex != nil {
val["ex"] = req.Ex.Value val["ex"] = req.Ex.Value
} }
if err = s.friendDatabase.UpdateFriends(ctx, req.OwnerUserID, req.FriendUserIDs, val); err != nil { if err = s.db.UpdateFriends(ctx, req.OwnerUserID, req.FriendUserIDs, val); err != nil {
return nil, err return nil, err
} }
resp := &pbfriend.UpdateFriendsResp{} resp := &relation.UpdateFriendsResp{}
s.notificationSender.FriendsInfoUpdateNotification(ctx, req.OwnerUserID, req.FriendUserIDs) s.notificationSender.FriendsInfoUpdateNotification(ctx, req.OwnerUserID, req.FriendUserIDs)
return resp, nil return resp, nil
} }
func (s *friendServer) GetIncrementalFriendsApplyTo(ctx context.Context, req *relation.GetIncrementalFriendsApplyToReq) (*relation.GetIncrementalFriendsApplyToResp, error) {
// TODO implement me
return nil, nil
}
func (s *friendServer) GetIncrementalFriendsApplyFrom(ctx context.Context, req *relation.GetIncrementalFriendsApplyFromReq) (*relation.GetIncrementalFriendsApplyFromResp, error) {
// TODO implement me
return nil, nil
}
func (s *friendServer) GetIncrementalBlacks(ctx context.Context, req *relation.GetIncrementalBlacksReq) (*relation.GetIncrementalBlacksResp, error) {
// TODO implement me
return nil, nil
}

@ -16,6 +16,9 @@ package friend
import ( import (
"context" "context"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/versionctx"
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
@ -24,7 +27,7 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification"
"github.com/openimsdk/protocol/constant" "github.com/openimsdk/protocol/constant"
pbfriend "github.com/openimsdk/protocol/friend" "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
) )
@ -127,7 +130,7 @@ func (f *FriendNotificationSender) UserInfoUpdatedNotification(ctx context.Conte
f.Notification(ctx, mcontext.GetOpUserID(ctx), changedUserID, constant.UserInfoUpdatedNotification, &tips) f.Notification(ctx, mcontext.GetOpUserID(ctx), changedUserID, constant.UserInfoUpdatedNotification, &tips)
} }
func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.Context, req *pbfriend.ApplyToAddFriendReq) { func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.Context, req *relation.ApplyToAddFriendReq) {
tips := sdkws.FriendApplicationTips{FromToUserID: &sdkws.FromToUserID{ tips := sdkws.FriendApplicationTips{FromToUserID: &sdkws.FromToUserID{
FromUserID: req.FromUserID, FromUserID: req.FromUserID,
ToUserID: req.ToUserID, ToUserID: req.ToUserID,
@ -137,7 +140,7 @@ func (f *FriendNotificationSender) FriendApplicationAddNotification(ctx context.
func (f *FriendNotificationSender) FriendApplicationAgreedNotification( func (f *FriendNotificationSender) FriendApplicationAgreedNotification(
ctx context.Context, ctx context.Context,
req *pbfriend.RespondFriendApplyReq, req *relation.RespondFriendApplyReq,
) { ) {
tips := sdkws.FriendApplicationApprovedTips{FromToUserID: &sdkws.FromToUserID{ tips := sdkws.FriendApplicationApprovedTips{FromToUserID: &sdkws.FromToUserID{
FromUserID: req.FromUserID, FromUserID: req.FromUserID,
@ -148,7 +151,7 @@ func (f *FriendNotificationSender) FriendApplicationAgreedNotification(
func (f *FriendNotificationSender) FriendApplicationRefusedNotification( func (f *FriendNotificationSender) FriendApplicationRefusedNotification(
ctx context.Context, ctx context.Context,
req *pbfriend.RespondFriendApplyReq, req *relation.RespondFriendApplyReq,
) { ) {
tips := sdkws.FriendApplicationApprovedTips{FromToUserID: &sdkws.FromToUserID{ tips := sdkws.FriendApplicationApprovedTips{FromToUserID: &sdkws.FromToUserID{
FromUserID: req.FromUserID, FromUserID: req.FromUserID,
@ -182,7 +185,7 @@ func (f *FriendNotificationSender) FriendAddedNotification(
return nil return nil
} }
func (f *FriendNotificationSender) FriendDeletedNotification(ctx context.Context, req *pbfriend.DeleteFriendReq) { func (f *FriendNotificationSender) FriendDeletedNotification(ctx context.Context, req *relation.DeleteFriendReq) {
tips := sdkws.FriendDeletedTips{FromToUserID: &sdkws.FromToUserID{ tips := sdkws.FriendDeletedTips{FromToUserID: &sdkws.FromToUserID{
FromUserID: req.OwnerUserID, FromUserID: req.OwnerUserID,
ToUserID: req.FriendUserID, ToUserID: req.FriendUserID,
@ -190,10 +193,37 @@ func (f *FriendNotificationSender) FriendDeletedNotification(ctx context.Context
f.Notification(ctx, req.OwnerUserID, req.FriendUserID, constant.FriendDeletedNotification, &tips) f.Notification(ctx, req.OwnerUserID, req.FriendUserID, constant.FriendDeletedNotification, &tips)
} }
func (f *FriendNotificationSender) setVersion(ctx context.Context, version *uint64, versionID *string, collName string, id string) {
versions := versionctx.GetVersionLog(ctx).Get()
for _, coll := range versions {
if coll.Name == collName && coll.Doc.DID == id {
*version = uint64(coll.Doc.Version)
*versionID = coll.Doc.ID.Hex()
return
}
}
}
func (f *FriendNotificationSender) setSortVersion(ctx context.Context, version *uint64, versionID *string, collName string, id string, sortVersion *uint64) {
versions := versionctx.GetVersionLog(ctx).Get()
for _, coll := range versions {
if coll.Name == collName && coll.Doc.DID == id {
*version = uint64(coll.Doc.Version)
*versionID = coll.Doc.ID.Hex()
for _, elem := range coll.Doc.Logs {
if elem.EID == relationtb.VersionSortChangeID {
*sortVersion = uint64(elem.Version)
}
}
}
}
}
func (f *FriendNotificationSender) FriendRemarkSetNotification(ctx context.Context, fromUserID, toUserID string) { func (f *FriendNotificationSender) FriendRemarkSetNotification(ctx context.Context, fromUserID, toUserID string) {
tips := sdkws.FriendInfoChangedTips{FromToUserID: &sdkws.FromToUserID{}} tips := sdkws.FriendInfoChangedTips{FromToUserID: &sdkws.FromToUserID{}}
tips.FromToUserID.FromUserID = fromUserID tips.FromToUserID.FromUserID = fromUserID
tips.FromToUserID.ToUserID = toUserID tips.FromToUserID.ToUserID = toUserID
f.setSortVersion(ctx, &tips.FriendVersion, &tips.FriendVersionID, database.FriendVersionName, toUserID, &tips.FriendSortVersion)
f.Notification(ctx, fromUserID, toUserID, constant.FriendRemarkSetNotification, &tips) f.Notification(ctx, fromUserID, toUserID, constant.FriendRemarkSetNotification, &tips)
} }
@ -204,14 +234,14 @@ func (f *FriendNotificationSender) FriendsInfoUpdateNotification(ctx context.Con
f.Notification(ctx, toUserID, toUserID, constant.FriendsInfoUpdateNotification, &tips) f.Notification(ctx, toUserID, toUserID, constant.FriendsInfoUpdateNotification, &tips)
} }
func (f *FriendNotificationSender) BlackAddedNotification(ctx context.Context, req *pbfriend.AddBlackReq) { func (f *FriendNotificationSender) BlackAddedNotification(ctx context.Context, req *relation.AddBlackReq) {
tips := sdkws.BlackAddedTips{FromToUserID: &sdkws.FromToUserID{}} tips := sdkws.BlackAddedTips{FromToUserID: &sdkws.FromToUserID{}}
tips.FromToUserID.FromUserID = req.OwnerUserID tips.FromToUserID.FromUserID = req.OwnerUserID
tips.FromToUserID.ToUserID = req.BlackUserID tips.FromToUserID.ToUserID = req.BlackUserID
f.Notification(ctx, req.OwnerUserID, req.BlackUserID, constant.BlackAddedNotification, &tips) f.Notification(ctx, req.OwnerUserID, req.BlackUserID, constant.BlackAddedNotification, &tips)
} }
func (f *FriendNotificationSender) BlackDeletedNotification(ctx context.Context, req *pbfriend.RemoveBlackReq) { func (f *FriendNotificationSender) BlackDeletedNotification(ctx context.Context, req *relation.RemoveBlackReq) {
blackDeletedTips := sdkws.BlackDeletedTips{FromToUserID: &sdkws.FromToUserID{ blackDeletedTips := sdkws.BlackDeletedTips{FromToUserID: &sdkws.FromToUserID{
FromUserID: req.OwnerUserID, FromUserID: req.OwnerUserID,
ToUserID: req.BlackUserID, ToUserID: req.BlackUserID,

@ -0,0 +1,104 @@
package friend
import (
"context"
"github.com/openimsdk/open-im-server/v3/pkg/util/hashutil"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/log"
"slices"
"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/protocol/relation"
)
func (s *friendServer) NotificationUserInfoUpdate(ctx context.Context, req *relation.NotificationUserInfoUpdateReq) (*relation.NotificationUserInfoUpdateResp, error) {
userIDs, err := s.db.FindFriendUserIDs(ctx, req.UserID)
if err != nil {
return nil, err
}
if len(userIDs) > 0 {
friendUserIDs := []string{req.UserID}
noCancelCtx := context.WithoutCancel(ctx)
err := s.queue.PushCtx(ctx, func() {
for _, userID := range userIDs {
if err := s.db.OwnerIncrVersion(noCancelCtx, userID, friendUserIDs, model.VersionStateUpdate); err != nil {
log.ZError(ctx, "OwnerIncrVersion", err, "userID", userID, "friendUserIDs", friendUserIDs)
}
}
for _, userID := range userIDs {
s.notificationSender.FriendInfoUpdatedNotification(noCancelCtx, req.UserID, userID)
}
})
if err != nil {
log.ZError(ctx, "NotificationUserInfoUpdate timeout", err, "userID", req.UserID)
}
}
return &relation.NotificationUserInfoUpdateResp{}, nil
}
func (s *friendServer) GetFullFriendUserIDs(ctx context.Context, req *relation.GetFullFriendUserIDsReq) (*relation.GetFullFriendUserIDsResp, error) {
vl, err := s.db.FindMaxFriendVersionCache(ctx, req.UserID)
if err != nil {
return nil, err
}
userIDs, err := s.db.FindFriendUserIDs(ctx, req.UserID)
if err != nil {
return nil, err
}
idHash := hashutil.IdHash(userIDs)
if req.IdHash == idHash {
userIDs = nil
}
return &relation.GetFullFriendUserIDsResp{
Version: idHash,
VersionID: vl.ID.Hex(),
Equal: req.IdHash == idHash,
UserIDs: userIDs,
}, nil
}
func (s *friendServer) GetIncrementalFriends(ctx context.Context, req *relation.GetIncrementalFriendsReq) (*relation.GetIncrementalFriendsResp, error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil {
return nil, err
}
var sortVersion uint64
opt := incrversion.Option[*sdkws.FriendInfo, relation.GetIncrementalFriendsResp]{
Ctx: ctx,
VersionKey: req.UserID,
VersionID: req.VersionID,
VersionNumber: req.Version,
Version: func(ctx context.Context, ownerUserID string, version uint, limit int) (*model.VersionLog, error) {
vl, err := s.db.FindFriendIncrVersion(ctx, ownerUserID, version, limit)
if err != nil {
return nil, err
}
vl.Logs = slices.DeleteFunc(vl.Logs, func(elem model.VersionLogElem) bool {
if elem.EID == model.VersionSortChangeID {
vl.LogLen--
sortVersion = uint64(elem.Version)
return true
}
return false
})
return vl, nil
},
CacheMaxVersion: s.db.FindMaxFriendVersionCache,
Find: func(ctx context.Context, ids []string) ([]*sdkws.FriendInfo, error) {
return s.getFriend(ctx, req.UserID, ids)
},
Resp: func(version *model.VersionLog, deleteIds []string, insertList, updateList []*sdkws.FriendInfo, full bool) *relation.GetIncrementalFriendsResp {
return &relation.GetIncrementalFriendsResp{
VersionID: version.ID.Hex(),
Version: uint64(version.Version),
Full: full,
Delete: deleteIds,
Insert: insertList,
Update: updateList,
SortVersion: sortVersion,
}
},
}
return opt.Build()
}

@ -57,3 +57,7 @@ func (s *groupServer) groupMemberDB2PB(member *model.GroupMember, appMangerLevel
InviterUserID: member.InviterUserID, InviterUserID: member.InviterUserID,
} }
} }
func (s *groupServer) groupMemberDB2PB2(member *model.GroupMember) *sdkws.GroupMemberFullInfo {
return s.groupMemberDB2PB(member, 0)
}

@ -17,17 +17,18 @@ package group
import ( import (
"context" "context"
"fmt" "fmt"
"math/big"
"math/rand"
"strconv"
"strings"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/common" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/common"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/localcache" "github.com/openimsdk/open-im-server/v3/pkg/localcache"
"math/big"
"math/rand"
"strconv"
"strings"
"time"
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/callbackstruct" "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
@ -132,13 +133,17 @@ func (s *groupServer) NotificationUserInfoUpdate(ctx context.Context, req *pbgro
} }
groupIDs = append(groupIDs, member.GroupID) groupIDs = append(groupIDs, member.GroupID)
} }
for _, groupID := range groupIDs {
if err := s.db.MemberGroupIncrVersion(ctx, groupID, []string{req.UserID}, model.VersionStateUpdate); err != nil {
return nil, err
}
}
for _, groupID := range groupIDs { for _, groupID := range groupIDs {
s.notification.GroupMemberInfoSetNotification(ctx, groupID, req.UserID) s.notification.GroupMemberInfoSetNotification(ctx, groupID, req.UserID)
} }
if err = s.db.DeleteGroupMemberHash(ctx, groupIDs); err != nil { if err = s.db.DeleteGroupMemberHash(ctx, groupIDs); err != nil {
return nil, err return nil, err
} }
return &pbgroup.NotificationUserInfoUpdateResp{}, nil return &pbgroup.NotificationUserInfoUpdateResp{}, nil
} }
@ -527,6 +532,14 @@ func (s *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
if datautil.Contain(opUserID, req.KickedUserIDs...) { if datautil.Contain(opUserID, req.KickedUserIDs...) {
return nil, errs.ErrArgs.WrapMsg("opUserID in KickedUserIDs") return nil, errs.ErrArgs.WrapMsg("opUserID in KickedUserIDs")
} }
owner, err := s.db.TakeGroupOwner(ctx, req.GroupID)
if err != nil {
return nil, err
}
if datautil.Contain(owner.UserID, req.KickedUserIDs...) {
return nil, errs.ErrArgs.WrapMsg("ownerUID can not Kick")
}
members, err := s.db.FindGroupMembers(ctx, req.GroupID, append(req.KickedUserIDs, opUserID)) members, err := s.db.FindGroupMembers(ctx, req.GroupID, append(req.KickedUserIDs, opUserID))
if err != nil { if err != nil {
return nil, err return nil, err
@ -586,7 +599,7 @@ func (s *groupServer) KickGroupMember(ctx context.Context, req *pbgroup.KickGrou
FaceURL: group.FaceURL, FaceURL: group.FaceURL,
OwnerUserID: ownerUserID, OwnerUserID: ownerUserID,
CreateTime: group.CreateTime.UnixMilli(), CreateTime: group.CreateTime.UnixMilli(),
MemberCount: num, MemberCount: num - uint32(len(req.KickedUserIDs)),
Ex: group.Ex, Ex: group.Ex,
Status: group.Status, Status: group.Status,
CreatorUserID: group.CreatorUserID, CreatorUserID: group.CreatorUserID,
@ -621,18 +634,29 @@ func (s *groupServer) GetGroupMembersInfo(ctx context.Context, req *pbgroup.GetG
if req.GroupID == "" { if req.GroupID == "" {
return nil, errs.ErrArgs.WrapMsg("groupID empty") return nil, errs.ErrArgs.WrapMsg("groupID empty")
} }
members, err := s.db.FindGroupMembers(ctx, req.GroupID, req.UserIDs) members, err := s.getGroupMembersInfo(ctx, req.GroupID, req.UserIDs)
if err != nil {
return nil, err
}
return &pbgroup.GetGroupMembersInfoResp{
Members: members,
}, nil
}
func (s *groupServer) getGroupMembersInfo(ctx context.Context, groupID string, userIDs []string) ([]*sdkws.GroupMemberFullInfo, error) {
if len(userIDs) == 0 {
return nil, nil
}
members, err := s.db.FindGroupMembers(ctx, groupID, userIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := s.PopulateGroupMember(ctx, members...); err != nil { if err := s.PopulateGroupMember(ctx, members...); err != nil {
return nil, err return nil, err
} }
return &pbgroup.GetGroupMembersInfoResp{ return datautil.Slice(members, func(e *model.GroupMember) *sdkws.GroupMemberFullInfo {
Members: datautil.Slice(members, func(e *model.GroupMember) *sdkws.GroupMemberFullInfo { return convert.Db2PbGroupMember(e)
return convert.Db2PbGroupMember(e) }), nil
}),
}, nil
} }
// GetGroupApplicationList handles functions that get a list of group requests. // GetGroupApplicationList handles functions that get a list of group requests.
@ -701,15 +725,28 @@ func (s *groupServer) GetGroupsInfo(ctx context.Context, req *pbgroup.GetGroupsI
if len(req.GroupIDs) == 0 { if len(req.GroupIDs) == 0 {
return nil, errs.ErrArgs.WrapMsg("groupID is empty") return nil, errs.ErrArgs.WrapMsg("groupID is empty")
} }
groups, err := s.db.FindGroup(ctx, req.GroupIDs) groups, err := s.getGroupsInfo(ctx, req.GroupIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
groupMemberNumMap, err := s.db.MapGroupMemberNum(ctx, req.GroupIDs) return &pbgroup.GetGroupsInfoResp{
GroupInfos: groups,
}, nil
}
func (s *groupServer) getGroupsInfo(ctx context.Context, groupIDs []string) ([]*sdkws.GroupInfo, error) {
if len(groupIDs) == 0 {
return nil, nil
}
groups, err := s.db.FindGroup(ctx, groupIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
owners, err := s.db.FindGroupsOwner(ctx, req.GroupIDs) groupMemberNumMap, err := s.db.MapGroupMemberNum(ctx, groupIDs)
if err != nil {
return nil, err
}
owners, err := s.db.FindGroupsOwner(ctx, groupIDs)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -719,15 +756,13 @@ func (s *groupServer) GetGroupsInfo(ctx context.Context, req *pbgroup.GetGroupsI
ownerMap := datautil.SliceToMap(owners, func(e *model.GroupMember) string { ownerMap := datautil.SliceToMap(owners, func(e *model.GroupMember) string {
return e.GroupID return e.GroupID
}) })
return &pbgroup.GetGroupsInfoResp{ return datautil.Slice(groups, func(e *model.Group) *sdkws.GroupInfo {
GroupInfos: datautil.Slice(groups, func(e *model.Group) *sdkws.GroupInfo { var ownerUserID string
var ownerUserID string if owner, ok := ownerMap[e.GroupID]; ok {
if owner, ok := ownerMap[e.GroupID]; ok { ownerUserID = owner.UserID
ownerUserID = owner.UserID }
} return convert.Db2PbGroupInfo(e, ownerUserID, groupMemberNumMap[e.GroupID])
return convert.Db2PbGroupInfo(e, ownerUserID, groupMemberNumMap[e.GroupID]) }), nil
}),
}, nil
} }
func (s *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup.GroupApplicationResponseReq) (*pbgroup.GroupApplicationResponseResp, error) { func (s *groupServer) GroupApplicationResponse(ctx context.Context, req *pbgroup.GroupApplicationResponseReq) (*pbgroup.GroupApplicationResponseResp, error) {

@ -17,7 +17,10 @@ package group
import ( import (
"context" "context"
"fmt" "fmt"
"github.com/openimsdk/open-im-server/v3/pkg/common/convert"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/versionctx"
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification" "github.com/openimsdk/open-im-server/v3/pkg/rpcclient/notification"
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/authverify"
@ -34,6 +37,12 @@ import (
"github.com/openimsdk/tools/utils/stringutil" "github.com/openimsdk/tools/utils/stringutil"
) )
// GroupApplicationReceiver
const (
applicantReceiver = iota
adminReceiver
)
func NewGroupNotificationSender(db controller.GroupDatabase, msgRpcClient *rpcclient.MessageRpcClient, userRpcClient *rpcclient.UserRpcClient, config *Config, fn func(ctx context.Context, userIDs []string) ([]notification.CommonUser, error)) *GroupNotificationSender { func NewGroupNotificationSender(db controller.GroupDatabase, msgRpcClient *rpcclient.MessageRpcClient, userRpcClient *rpcclient.UserRpcClient, config *Config, fn func(ctx context.Context, userIDs []string) ([]notification.CommonUser, error)) *GroupNotificationSender {
return &GroupNotificationSender{ return &GroupNotificationSender{
NotificationSender: rpcclient.NewNotificationSender(&config.NotificationConfig, rpcclient.WithRpcClient(msgRpcClient), rpcclient.WithUserRpcClient(userRpcClient)), NotificationSender: rpcclient.NewNotificationSender(&config.NotificationConfig, rpcclient.WithRpcClient(msgRpcClient), rpcclient.WithUserRpcClient(userRpcClient)),
@ -118,25 +127,8 @@ func (g *GroupNotificationSender) getGroupInfo(ctx context.Context, groupID stri
if len(ownerUserIDs) > 0 { if len(ownerUserIDs) > 0 {
ownerUserID = ownerUserIDs[0] ownerUserID = ownerUserIDs[0]
} }
return &sdkws.GroupInfo{
GroupID: gm.GroupID, return convert.Db2PbGroupInfo(gm, ownerUserID, num), nil
GroupName: gm.GroupName,
Notification: gm.Notification,
Introduction: gm.Introduction,
FaceURL: gm.FaceURL,
OwnerUserID: ownerUserID,
CreateTime: gm.CreateTime.UnixMilli(),
MemberCount: num,
Ex: gm.Ex,
Status: gm.Status,
CreatorUserID: gm.CreatorUserID,
GroupType: gm.GroupType,
NeedVerification: gm.NeedVerification,
LookMemberInfo: gm.LookMemberInfo,
ApplyMemberFriend: gm.ApplyMemberFriend,
NotificationUpdateTime: gm.NotificationUpdateTime.UnixMilli(),
NotificationUserID: gm.NotificationUserID,
}, nil
} }
func (g *GroupNotificationSender) getGroupMembers(ctx context.Context, groupID string, userIDs []string) ([]*sdkws.GroupMemberFullInfo, error) { func (g *GroupNotificationSender) getGroupMembers(ctx context.Context, groupID string, userIDs []string) ([]*sdkws.GroupMemberFullInfo, error) {
@ -190,29 +182,6 @@ func (g *GroupNotificationSender) getGroupOwnerAndAdminUserID(ctx context.Contex
return datautil.Slice(members, fn), nil return datautil.Slice(members, fn), nil
} }
//nolint:unused
func (g *GroupNotificationSender) groupDB2PB(group *model.Group, ownerUserID string, memberCount uint32) *sdkws.GroupInfo {
return &sdkws.GroupInfo{
GroupID: group.GroupID,
GroupName: group.GroupName,
Notification: group.Notification,
Introduction: group.Introduction,
FaceURL: group.FaceURL,
OwnerUserID: ownerUserID,
CreateTime: group.CreateTime.UnixMilli(),
MemberCount: memberCount,
Ex: group.Ex,
Status: group.Status,
CreatorUserID: group.CreatorUserID,
GroupType: group.GroupType,
NeedVerification: group.NeedVerification,
LookMemberInfo: group.LookMemberInfo,
ApplyMemberFriend: group.ApplyMemberFriend,
NotificationUpdateTime: group.NotificationUpdateTime.UnixMilli(),
NotificationUserID: group.NotificationUserID,
}
}
func (g *GroupNotificationSender) groupMemberDB2PB(member *model.GroupMember, appMangerLevel int32) *sdkws.GroupMemberFullInfo { func (g *GroupNotificationSender) groupMemberDB2PB(member *model.GroupMember, appMangerLevel int32) *sdkws.GroupMemberFullInfo {
return &sdkws.GroupMemberFullInfo{ return &sdkws.GroupMemberFullInfo{
GroupID: member.GroupID, GroupID: member.GroupID,
@ -287,6 +256,32 @@ func (g *GroupNotificationSender) fillOpUser(ctx context.Context, opUser **sdkws
return nil return nil
} }
func (g *GroupNotificationSender) setVersion(ctx context.Context, version *uint64, versionID *string, collName string, id string) {
versions := versionctx.GetVersionLog(ctx).Get()
for _, coll := range versions {
if coll.Name == collName && coll.Doc.DID == id {
*version = uint64(coll.Doc.Version)
*versionID = coll.Doc.ID.Hex()
return
}
}
}
func (g *GroupNotificationSender) setSortVersion(ctx context.Context, version *uint64, versionID *string, collName string, id string, sortVersion *uint64) {
versions := versionctx.GetVersionLog(ctx).Get()
for _, coll := range versions {
if coll.Name == collName && coll.Doc.DID == id {
*version = uint64(coll.Doc.Version)
*versionID = coll.Doc.ID.Hex()
for _, elem := range coll.Doc.Logs {
if elem.EID == model.VersionSortChangeID {
*sortVersion = uint64(elem.Version)
}
}
}
}
}
func (g *GroupNotificationSender) GroupCreatedNotification(ctx context.Context, tips *sdkws.GroupCreatedTips) { func (g *GroupNotificationSender) GroupCreatedNotification(ctx context.Context, tips *sdkws.GroupCreatedTips) {
var err error var err error
defer func() { defer func() {
@ -297,6 +292,7 @@ func (g *GroupNotificationSender) GroupCreatedNotification(ctx context.Context,
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupCreatedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupCreatedNotification, tips)
} }
@ -310,6 +306,7 @@ func (g *GroupNotificationSender) GroupInfoSetNotification(ctx context.Context,
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNotification, tips, rpcclient.WithRpcGetUserName()) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNotification, tips, rpcclient.WithRpcGetUserName())
} }
@ -323,6 +320,7 @@ func (g *GroupNotificationSender) GroupInfoSetNameNotification(ctx context.Conte
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNameNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetNameNotification, tips)
} }
@ -336,6 +334,7 @@ func (g *GroupNotificationSender) GroupInfoSetAnnouncementNotification(ctx conte
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetAnnouncementNotification, tips, rpcclient.WithRpcGetUserName()) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.GroupInfoSetAnnouncementNotification, tips, rpcclient.WithRpcGetUserName())
} }
@ -380,6 +379,7 @@ func (g *GroupNotificationSender) MemberQuitNotification(ctx context.Context, me
return return
} }
tips := &sdkws.MemberQuitTips{Group: group, QuitUser: member} tips := &sdkws.MemberQuitTips{Group: group, QuitUser: member}
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, member.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), member.GroupID, constant.MemberQuitNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), member.GroupID, constant.MemberQuitNotification, tips)
} }
@ -400,15 +400,17 @@ func (g *GroupNotificationSender) GroupApplicationAcceptedNotification(ctx conte
if err != nil { if err != nil {
return return
} }
tips := &sdkws.GroupApplicationAcceptedTips{Group: group, HandleMsg: req.HandledMsg}
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { var opUser *sdkws.GroupMemberFullInfo
if err = g.fillOpUser(ctx, &opUser, group.GroupID); err != nil {
return return
} }
for _, userID := range append(userIDs, req.FromUserID) { for _, userID := range append(userIDs, req.FromUserID) {
tips := &sdkws.GroupApplicationAcceptedTips{Group: group, OpUser: opUser, HandleMsg: req.HandledMsg}
if userID == req.FromUserID { if userID == req.FromUserID {
tips.ReceiverAs = 0 tips.ReceiverAs = applicantReceiver
} else { } else {
tips.ReceiverAs = 1 tips.ReceiverAs = adminReceiver
} }
g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationAcceptedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationAcceptedNotification, tips)
} }
@ -431,15 +433,17 @@ func (g *GroupNotificationSender) GroupApplicationRejectedNotification(ctx conte
if err != nil { if err != nil {
return return
} }
tips := &sdkws.GroupApplicationRejectedTips{Group: group, HandleMsg: req.HandledMsg}
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { var opUser *sdkws.GroupMemberFullInfo
if err = g.fillOpUser(ctx, &opUser, group.GroupID); err != nil {
return return
} }
for _, userID := range append(userIDs, req.FromUserID) { for _, userID := range append(userIDs, req.FromUserID) {
tips := &sdkws.GroupApplicationAcceptedTips{Group: group, OpUser: opUser, HandleMsg: req.HandledMsg}
if userID == req.FromUserID { if userID == req.FromUserID {
tips.ReceiverAs = 0 tips.ReceiverAs = applicantReceiver
} else { } else {
tips.ReceiverAs = 1 tips.ReceiverAs = adminReceiver
} }
g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationRejectedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), userID, constant.GroupApplicationRejectedNotification, tips)
} }
@ -459,14 +463,20 @@ func (g *GroupNotificationSender) GroupOwnerTransferredNotification(ctx context.
} }
opUserID := mcontext.GetOpUserID(ctx) opUserID := mcontext.GetOpUserID(ctx)
var member map[string]*sdkws.GroupMemberFullInfo var member map[string]*sdkws.GroupMemberFullInfo
member, err = g.getGroupMemberMap(ctx, req.GroupID, []string{opUserID, req.NewOwnerUserID}) member, err = g.getGroupMemberMap(ctx, req.GroupID, []string{opUserID, req.NewOwnerUserID, req.OldOwnerUserID})
if err != nil { if err != nil {
return return
} }
tips := &sdkws.GroupOwnerTransferredTips{Group: group, OpUser: member[opUserID], NewGroupOwner: member[req.NewOwnerUserID]} tips := &sdkws.GroupOwnerTransferredTips{
Group: group,
OpUser: member[opUserID],
NewGroupOwner: member[req.NewOwnerUserID],
OldGroupOwnerInfo: member[req.OldOwnerUserID],
}
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, req.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupOwnerTransferredNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupOwnerTransferredNotification, tips)
} }
@ -480,6 +490,7 @@ func (g *GroupNotificationSender) MemberKickedNotification(ctx context.Context,
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.MemberKickedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), tips.Group.GroupID, constant.MemberKickedNotification, tips)
} }
@ -503,6 +514,7 @@ func (g *GroupNotificationSender) MemberInvitedNotification(ctx context.Context,
} }
tips := &sdkws.MemberInvitedTips{Group: group, InvitedUserList: users} tips := &sdkws.MemberInvitedTips{Group: group, InvitedUserList: users}
err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID) err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID)
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberInvitedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberInvitedNotification, tips)
} }
@ -524,6 +536,7 @@ func (g *GroupNotificationSender) MemberEnterNotification(ctx context.Context, g
return return
} }
tips := &sdkws.MemberEnterTips{Group: group, EntrantUser: user} tips := &sdkws.MemberEnterTips{Group: group, EntrantUser: user}
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberEnterNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.MemberEnterNotification, tips)
} }
@ -564,6 +577,7 @@ func (g *GroupNotificationSender) GroupMemberMutedNotification(ctx context.Conte
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberMutedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberMutedNotification, tips)
} }
@ -588,6 +602,7 @@ func (g *GroupNotificationSender) GroupMemberCancelMutedNotification(ctx context
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberCancelMutedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberCancelMutedNotification, tips)
} }
@ -615,6 +630,7 @@ func (g *GroupNotificationSender) GroupMutedNotification(ctx context.Context, gr
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, groupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMutedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMutedNotification, tips)
} }
@ -642,6 +658,7 @@ func (g *GroupNotificationSender) GroupCancelMutedNotification(ctx context.Conte
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, groupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupCancelMutedNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupCancelMutedNotification, tips)
} }
@ -666,6 +683,7 @@ func (g *GroupNotificationSender) GroupMemberInfoSetNotification(ctx context.Con
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setSortVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID, &tips.GroupSortVersion)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberInfoSetNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberInfoSetNotification, tips)
} }
@ -689,6 +707,7 @@ func (g *GroupNotificationSender) GroupMemberSetToAdminNotification(ctx context.
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToAdminNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToAdminNotification, tips)
} }
@ -713,5 +732,6 @@ func (g *GroupNotificationSender) GroupMemberSetToOrdinaryUserNotification(ctx c
if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil { if err = g.fillOpUser(ctx, &tips.OpUser, tips.Group.GroupID); err != nil {
return return
} }
g.setVersion(ctx, &tips.GroupMemberVersion, &tips.GroupMemberVersionID, database.GroupMemberVersionName, tips.Group.GroupID)
g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToOrdinaryUserNotification, tips) g.Notification(ctx, mcontext.GetOpUserID(ctx), group.GroupID, constant.GroupMemberSetToOrdinaryUserNotification, tips)
} }

@ -0,0 +1,303 @@
package group
import (
"context"
"github.com/openimsdk/open-im-server/v3/internal/rpc/incrversion"
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/util/hashutil"
"github.com/openimsdk/protocol/constant"
pbgroup "github.com/openimsdk/protocol/group"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
)
func (s *groupServer) GetFullGroupMemberUserIDs(ctx context.Context, req *pbgroup.GetFullGroupMemberUserIDsReq) (*pbgroup.GetFullGroupMemberUserIDsResp, error) {
vl, err := s.db.FindMaxGroupMemberVersionCache(ctx, req.GroupID)
if err != nil {
return nil, err
}
userIDs, err := s.db.FindGroupMemberUserID(ctx, req.GroupID)
if err != nil {
return nil, err
}
idHash := hashutil.IdHash(userIDs)
if req.IdHash == idHash {
userIDs = nil
}
return &pbgroup.GetFullGroupMemberUserIDsResp{
Version: idHash,
VersionID: vl.ID.Hex(),
Equal: req.IdHash == idHash,
UserIDs: userIDs,
}, nil
}
func (s *groupServer) GetFullJoinGroupIDs(ctx context.Context, req *pbgroup.GetFullJoinGroupIDsReq) (*pbgroup.GetFullJoinGroupIDsResp, error) {
vl, err := s.db.FindMaxJoinGroupVersionCache(ctx, req.UserID)
if err != nil {
return nil, err
}
groupIDs, err := s.db.FindJoinGroupID(ctx, req.UserID)
if err != nil {
return nil, err
}
idHash := hashutil.IdHash(groupIDs)
if req.IdHash == idHash {
groupIDs = nil
}
return &pbgroup.GetFullJoinGroupIDsResp{
Version: idHash,
VersionID: vl.ID.Hex(),
Equal: req.IdHash == idHash,
GroupIDs: groupIDs,
}, nil
}
func (s *groupServer) GetIncrementalGroupMember(ctx context.Context, req *pbgroup.GetIncrementalGroupMemberReq) (*pbgroup.GetIncrementalGroupMemberResp, error) {
group, err := s.db.TakeGroup(ctx, req.GroupID)
if err != nil {
return nil, err
}
if group.Status == constant.GroupStatusDismissed {
return nil, servererrs.ErrDismissedAlready.Wrap()
}
var (
hasGroupUpdate bool
sortVersion uint64
)
opt := incrversion.Option[*sdkws.GroupMemberFullInfo, pbgroup.GetIncrementalGroupMemberResp]{
Ctx: ctx,
VersionKey: req.GroupID,
VersionID: req.VersionID,
VersionNumber: req.Version,
Version: func(ctx context.Context, groupID string, version uint, limit int) (*model.VersionLog, error) {
vl, err := s.db.FindMemberIncrVersion(ctx, groupID, version, limit)
if err != nil {
return nil, err
}
logs := make([]model.VersionLogElem, 0, len(vl.Logs))
for i, log := range vl.Logs {
switch log.EID {
case model.VersionGroupChangeID:
vl.LogLen--
hasGroupUpdate = true
case model.VersionSortChangeID:
vl.LogLen--
sortVersion = uint64(log.Version)
default:
logs = append(logs, vl.Logs[i])
}
}
vl.Logs = logs
if vl.LogLen > 0 {
hasGroupUpdate = true
}
return vl, nil
},
CacheMaxVersion: s.db.FindMaxGroupMemberVersionCache,
Find: func(ctx context.Context, ids []string) ([]*sdkws.GroupMemberFullInfo, error) {
return s.getGroupMembersInfo(ctx, req.GroupID, ids)
},
Resp: func(version *model.VersionLog, delIDs []string, insertList, updateList []*sdkws.GroupMemberFullInfo, full bool) *pbgroup.GetIncrementalGroupMemberResp {
return &pbgroup.GetIncrementalGroupMemberResp{
VersionID: version.ID.Hex(),
Version: uint64(version.Version),
Full: full,
Delete: delIDs,
Insert: insertList,
Update: updateList,
SortVersion: sortVersion,
}
},
}
resp, err := opt.Build()
if err != nil {
return nil, err
}
if resp.Full || hasGroupUpdate {
count, err := s.db.FindGroupMemberNum(ctx, group.GroupID)
if err != nil {
return nil, err
}
owner, err := s.db.TakeGroupOwner(ctx, group.GroupID)
if err != nil {
return nil, err
}
resp.Group = s.groupDB2PB(group, owner.UserID, count)
}
return resp, nil
}
func (s *groupServer) BatchGetIncrementalGroupMember(ctx context.Context, req *pbgroup.BatchGetIncrementalGroupMemberReq) (resp *pbgroup.BatchGetIncrementalGroupMemberResp, err error) {
type VersionInfo struct {
GroupID string
VersionID string
VersionNumber uint64
}
var groupIDs []string
groupsVersionMap := make(map[string]*VersionInfo)
groupsMap := make(map[string]*model.Group)
hasGroupUpdateMap := make(map[string]bool)
sortVersionMap := make(map[string]uint64)
var targetKeys, versionIDs []string
var versionNumbers []uint64
var requestBodyLen int
for _, group := range req.ReqList {
groupsVersionMap[group.GroupID] = &VersionInfo{
GroupID: group.GroupID,
VersionID: group.VersionID,
VersionNumber: group.Version,
}
groupIDs = append(groupIDs, group.GroupID)
}
groups, err := s.db.FindGroup(ctx, groupIDs)
if err != nil {
return nil, errs.Wrap(err)
}
for _, group := range groups {
if group.Status == constant.GroupStatusDismissed {
err = servererrs.ErrDismissedAlready.Wrap()
log.ZError(ctx, "This group is Dismissed Already", err, "group is", group.GroupID)
delete(groupsVersionMap, group.GroupID)
} else {
groupsMap[group.GroupID] = group
}
}
for groupID, vInfo := range groupsVersionMap {
targetKeys = append(targetKeys, groupID)
versionIDs = append(versionIDs, vInfo.VersionID)
versionNumbers = append(versionNumbers, vInfo.VersionNumber)
}
opt := incrversion.BatchOption[[]*sdkws.GroupMemberFullInfo, pbgroup.BatchGetIncrementalGroupMemberResp]{
Ctx: ctx,
TargetKeys: targetKeys,
VersionIDs: versionIDs,
VersionNumbers: versionNumbers,
Versions: func(ctx context.Context, groupIDs []string, versions []uint64, limits []int) (map[string]*model.VersionLog, error) {
vLogs, err := s.db.BatchFindMemberIncrVersion(ctx, groupIDs, versions, limits)
if err != nil {
return nil, errs.Wrap(err)
}
for groupID, vlog := range vLogs {
vlogElems := make([]model.VersionLogElem, 0, len(vlog.Logs))
for i, log := range vlog.Logs {
switch log.EID {
case model.VersionGroupChangeID:
vlog.LogLen--
hasGroupUpdateMap[groupID] = true
case model.VersionSortChangeID:
vlog.LogLen--
sortVersionMap[groupID] = uint64(log.Version)
default:
vlogElems = append(vlogElems, vlog.Logs[i])
}
}
vlog.Logs = vlogElems
if vlog.LogLen > 0 {
hasGroupUpdateMap[groupID] = true
}
}
return vLogs, nil
},
CacheMaxVersions: s.db.BatchFindMaxGroupMemberVersionCache,
Find: func(ctx context.Context, groupID string, ids []string) ([]*sdkws.GroupMemberFullInfo, error) {
memberInfo, err := s.getGroupMembersInfo(ctx, groupID, ids)
if err != nil {
return nil, err
}
return memberInfo, err
},
Resp: func(versions map[string]*model.VersionLog, deleteIdsMap map[string][]string, insertListMap, updateListMap map[string][]*sdkws.GroupMemberFullInfo, fullMap map[string]bool) *pbgroup.BatchGetIncrementalGroupMemberResp {
resList := make(map[string]*pbgroup.GetIncrementalGroupMemberResp)
for groupID, versionLog := range versions {
resList[groupID] = &pbgroup.GetIncrementalGroupMemberResp{
VersionID: versionLog.ID.Hex(),
Version: uint64(versionLog.Version),
Full: fullMap[groupID],
Delete: deleteIdsMap[groupID],
Insert: insertListMap[groupID],
Update: updateListMap[groupID],
SortVersion: sortVersionMap[groupID],
}
requestBodyLen += len(insertListMap[groupID]) + len(updateListMap[groupID]) + len(deleteIdsMap[groupID])
if requestBodyLen > 200 {
break
}
}
return &pbgroup.BatchGetIncrementalGroupMemberResp{
RespList: resList,
}
},
}
resp, err = opt.Build()
if err != nil {
return nil, errs.Wrap(err)
}
for groupID, val := range resp.RespList {
if val.Full || hasGroupUpdateMap[groupID] {
count, err := s.db.FindGroupMemberNum(ctx, groupID)
if err != nil {
return nil, err
}
owner, err := s.db.TakeGroupOwner(ctx, groupID)
if err != nil {
return nil, err
}
resp.RespList[groupID].Group = s.groupDB2PB(groupsMap[groupID], owner.UserID, count)
}
}
return resp, nil
}
func (s *groupServer) GetIncrementalJoinGroup(ctx context.Context, req *pbgroup.GetIncrementalJoinGroupReq) (*pbgroup.GetIncrementalJoinGroupResp, error) {
if err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID); err != nil {
return nil, err
}
opt := incrversion.Option[*sdkws.GroupInfo, pbgroup.GetIncrementalJoinGroupResp]{
Ctx: ctx,
VersionKey: req.UserID,
VersionID: req.VersionID,
VersionNumber: req.Version,
Version: s.db.FindJoinIncrVersion,
CacheMaxVersion: s.db.FindMaxJoinGroupVersionCache,
Find: s.getGroupsInfo,
Resp: func(version *model.VersionLog, delIDs []string, insertList, updateList []*sdkws.GroupInfo, full bool) *pbgroup.GetIncrementalJoinGroupResp {
return &pbgroup.GetIncrementalJoinGroupResp{
VersionID: version.ID.Hex(),
Version: uint64(version.Version),
Full: full,
Delete: delIDs,
Insert: insertList,
Update: updateList,
}
},
}
return opt.Build()
}

@ -0,0 +1,207 @@
package incrversion
import (
"context"
"fmt"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson/primitive"
)
type BatchOption[A, B any] struct {
Ctx context.Context
TargetKeys []string
VersionIDs []string
VersionNumbers []uint64
//SyncLimit int
Versions func(ctx context.Context, dIds []string, versions []uint64, limits []int) (map[string]*model.VersionLog, error)
CacheMaxVersions func(ctx context.Context, dIds []string) (map[string]*model.VersionLog, error)
Find func(ctx context.Context, dId string, ids []string) (A, error)
Resp func(versionsMap map[string]*model.VersionLog, deleteIdsMap map[string][]string, insertListMap, updateListMap map[string]A, fullMap map[string]bool) *B
}
func (o *BatchOption[A, B]) newError(msg string) error {
return errs.ErrInternalServer.WrapMsg(msg)
}
func (o *BatchOption[A, B]) check() error {
if o.Ctx == nil {
return o.newError("opt ctx is nil")
}
if len(o.TargetKeys) == 0 {
return o.newError("targetKeys is empty")
}
if o.Versions == nil {
return o.newError("func versions is nil")
}
if o.Find == nil {
return o.newError("func find is nil")
}
if o.Resp == nil {
return o.newError("func resp is nil")
}
return nil
}
func (o *BatchOption[A, B]) validVersions() []bool {
valids := make([]bool, len(o.VersionIDs))
for i, versionID := range o.VersionIDs {
objID, err := primitive.ObjectIDFromHex(versionID)
valids[i] = (err == nil && (!objID.IsZero()) && o.VersionNumbers[i] > 0)
}
return valids
}
func (o *BatchOption[A, B]) equalIDs(objIDs []primitive.ObjectID) []bool {
equals := make([]bool, len(o.VersionIDs))
for i, versionID := range o.VersionIDs {
equals[i] = versionID == objIDs[i].Hex()
}
return equals
}
func (o *BatchOption[A, B]) getVersions(tags *[]int) (versions map[string]*model.VersionLog, err error) {
var dIDs []string
var versionNums []uint64
var limits []int
valids := o.validVersions()
if o.CacheMaxVersions == nil {
for i, valid := range valids {
if valid {
(*tags)[i] = tagQuery
dIDs = append(dIDs, o.TargetKeys[i])
versionNums = append(versionNums, o.VersionNumbers[i])
limits = append(limits, syncLimit)
} else {
(*tags)[i] = tagFull
dIDs = append(dIDs, o.TargetKeys[i])
versionNums = append(versionNums, 0)
limits = append(limits, 0)
}
}
versions, err = o.Versions(o.Ctx, dIDs, versionNums, limits)
if err != nil {
return nil, errs.Wrap(err)
}
return versions, nil
} else {
caches, err := o.CacheMaxVersions(o.Ctx, o.TargetKeys)
if err != nil {
return nil, errs.Wrap(err)
}
objIDs := make([]primitive.ObjectID, len(o.VersionIDs))
for i, versionID := range o.VersionIDs {
objID, _ := primitive.ObjectIDFromHex(versionID)
objIDs[i] = objID
}
equals := o.equalIDs(objIDs)
for i, valid := range valids {
if !valid {
(*tags)[i] = tagFull
} else if !equals[i] {
(*tags)[i] = tagFull
} else if o.VersionNumbers[i] == uint64(caches[o.TargetKeys[i]].Version) {
(*tags)[i] = tagEqual
} else {
(*tags)[i] = tagQuery
dIDs = append(dIDs, o.TargetKeys[i])
versionNums = append(versionNums, o.VersionNumbers[i])
limits = append(limits, syncLimit)
delete(caches, o.TargetKeys[i])
}
}
if dIDs != nil {
versionMap, err := o.Versions(o.Ctx, dIDs, versionNums, limits)
if err != nil {
return nil, errs.Wrap(err)
}
for k, v := range versionMap {
caches[k] = v
}
}
versions = caches
}
return versions, nil
}
func (o *BatchOption[A, B]) Build() (*B, error) {
if err := o.check(); err != nil {
return nil, errs.Wrap(err)
}
tags := make([]int, len(o.TargetKeys))
versions, err := o.getVersions(&tags)
if err != nil {
return nil, errs.Wrap(err)
}
fullMap := make(map[string]bool)
for i, tag := range tags {
switch tag {
case tagQuery:
vLog := versions[o.TargetKeys[i]]
fullMap[o.TargetKeys[i]] = vLog.ID.Hex() != o.VersionIDs[i] || uint64(vLog.Version) < o.VersionNumbers[i] || len(vLog.Logs) != vLog.LogLen
case tagFull:
fullMap[o.TargetKeys[i]] = true
case tagEqual:
fullMap[o.TargetKeys[i]] = false
default:
panic(fmt.Errorf("undefined tag %d", tag))
}
}
var (
insertIdsMap = make(map[string][]string)
deleteIdsMap = make(map[string][]string)
updateIdsMap = make(map[string][]string)
)
for _, targetKey := range o.TargetKeys {
if !fullMap[targetKey] {
version := versions[targetKey]
insertIds, deleteIds, updateIds := version.DeleteAndChangeIDs()
insertIdsMap[targetKey] = insertIds
deleteIdsMap[targetKey] = deleteIds
updateIdsMap[targetKey] = updateIds
}
}
var (
insertListMap = make(map[string]A)
updateListMap = make(map[string]A)
)
for targetKey, insertIds := range insertIdsMap {
if len(insertIds) > 0 {
insertList, err := o.Find(o.Ctx, targetKey, insertIds)
if err != nil {
return nil, errs.Wrap(err)
}
insertListMap[targetKey] = insertList
}
}
for targetKey, updateIds := range updateIdsMap {
if len(updateIds) > 0 {
updateList, err := o.Find(o.Ctx, targetKey, updateIds)
if err != nil {
return nil, errs.Wrap(err)
}
updateListMap[targetKey] = updateList
}
}
return o.Resp(versions, deleteIdsMap, insertListMap, updateListMap, fullMap), nil
}

@ -0,0 +1,153 @@
package incrversion
import (
"context"
"fmt"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/tools/errs"
"go.mongodb.org/mongo-driver/bson/primitive"
)
//func Limit(maxSync int, version uint64) int {
// if version == 0 {
// return 0
// }
// return maxSync
//}
const syncLimit = 200
const (
tagQuery = iota + 1
tagFull
tagEqual
)
type Option[A, B any] struct {
Ctx context.Context
VersionKey string
VersionID string
VersionNumber uint64
//SyncLimit int
CacheMaxVersion func(ctx context.Context, dId string) (*model.VersionLog, error)
Version func(ctx context.Context, dId string, version uint, limit int) (*model.VersionLog, error)
//SortID func(ctx context.Context, dId string) ([]string, error)
Find func(ctx context.Context, ids []string) ([]A, error)
Resp func(version *model.VersionLog, deleteIds []string, insertList, updateList []A, full bool) *B
}
func (o *Option[A, B]) newError(msg string) error {
return errs.ErrInternalServer.WrapMsg(msg)
}
func (o *Option[A, B]) check() error {
if o.Ctx == nil {
return o.newError("opt ctx is nil")
}
if o.VersionKey == "" {
return o.newError("versionKey is empty")
}
//if o.SyncLimit <= 0 {
// return o.newError("invalid synchronization quantity")
//}
if o.Version == nil {
return o.newError("func version is nil")
}
//if o.SortID == nil {
// return o.newError("func allID is nil")
//}
if o.Find == nil {
return o.newError("func find is nil")
}
if o.Resp == nil {
return o.newError("func resp is nil")
}
return nil
}
func (o *Option[A, B]) validVersion() bool {
objID, err := primitive.ObjectIDFromHex(o.VersionID)
return err == nil && (!objID.IsZero()) && o.VersionNumber > 0
}
func (o *Option[A, B]) equalID(objID primitive.ObjectID) bool {
return o.VersionID == objID.Hex()
}
func (o *Option[A, B]) getVersion(tag *int) (*model.VersionLog, error) {
if o.CacheMaxVersion == nil {
if o.validVersion() {
*tag = tagQuery
return o.Version(o.Ctx, o.VersionKey, uint(o.VersionNumber), syncLimit)
}
*tag = tagFull
return o.Version(o.Ctx, o.VersionKey, 0, 0)
} else {
cache, err := o.CacheMaxVersion(o.Ctx, o.VersionKey)
if err != nil {
return nil, err
}
if !o.validVersion() {
*tag = tagFull
return cache, nil
}
if !o.equalID(cache.ID) {
*tag = tagFull
return cache, nil
}
if o.VersionNumber == uint64(cache.Version) {
*tag = tagEqual
return cache, nil
}
*tag = tagQuery
return o.Version(o.Ctx, o.VersionKey, uint(o.VersionNumber), syncLimit)
}
}
func (o *Option[A, B]) Build() (*B, error) {
if err := o.check(); err != nil {
return nil, err
}
var tag int
version, err := o.getVersion(&tag)
if err != nil {
return nil, err
}
var full bool
switch tag {
case tagQuery:
full = version.ID.Hex() != o.VersionID || uint64(version.Version) < o.VersionNumber || len(version.Logs) != version.LogLen
case tagFull:
full = true
case tagEqual:
full = false
default:
panic(fmt.Errorf("undefined tag %d", tag))
}
var (
insertIds []string
deleteIds []string
updateIds []string
)
if !full {
insertIds, deleteIds, updateIds = version.DeleteAndChangeIDs()
}
var (
insertList []A
updateList []A
)
if len(insertIds) > 0 {
insertList, err = o.Find(o.Ctx, insertIds)
if err != nil {
return nil, err
}
}
if len(updateIds) > 0 {
updateList, err = o.Find(o.Ctx, updateIds)
if err != nil {
return nil, err
}
}
return o.Resp(version, deleteIds, insertList, updateList, full), nil
}

@ -83,6 +83,11 @@ func (m *msgServer) webhookAfterSendSingleMsg(ctx context.Context, after *config
if msg.MsgData.ContentType == constant.Typing { if msg.MsgData.ContentType == constant.Typing {
return return
} }
// According to the attentionIds configuration, only some users are sent
attentionIds := after.AttentionIds
if attentionIds != nil && !datautil.Contain(msg.MsgData.RecvID, attentionIds...) && !datautil.Contain(msg.MsgData.SendID, attentionIds...) {
return
}
cbReq := &cbapi.CallbackAfterSendSingleMsgReq{ cbReq := &cbapi.CallbackAfterSendSingleMsgReq{
CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand), CommonCallbackReq: toCommonCallback(ctx, msg, cbapi.CallbackAfterSendSingleMsgCommand),
RecvID: msg.MsgData.RecvID, RecvID: msg.MsgData.RecvID,

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

@ -86,12 +86,21 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
return err return err
} }
msgModel := redis.NewMsgCache(rdb) msgModel := redis.NewMsgCache(rdb)
seqModel := redis.NewSeqCache(rdb)
conversationClient := rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation) conversationClient := rpcclient.NewConversationRpcClient(client, config.Share.RpcRegisterName.Conversation)
userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID) userRpcClient := rpcclient.NewUserRpcClient(client, config.Share.RpcRegisterName.User, config.Share.IMAdminUserID)
groupRpcClient := rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group) groupRpcClient := rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group)
friendRpcClient := rpcclient.NewFriendRpcClient(client, config.Share.RpcRegisterName.Friend) friendRpcClient := rpcclient.NewFriendRpcClient(client, config.Share.RpcRegisterName.Friend)
msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqModel, &config.KafkaConfig) seqConversation, err := mgo.NewSeqConversationMongo(mgocli.GetDB())
if err != nil {
return err
}
seqConversationCache := redis.NewSeqConversationCacheRedis(rdb, seqConversation)
seqUser, err := mgo.NewSeqUserMongo(mgocli.GetDB())
if err != nil {
return err
}
seqUserCache := redis.NewSeqUserCacheRedis(rdb, seqUser)
msgDatabase, err := controller.NewCommonMsgDatabase(msgDocModel, msgModel, seqUserCache, seqConversationCache, &config.KafkaConfig)
if err != nil { if err != nil {
return err return err
} }

@ -111,7 +111,7 @@ func (m *msgServer) GetMaxSeq(ctx context.Context, req *sdkws.GetMaxSeqReq) (*sd
func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq) (resp *msg.SearchMessageResp, err error) { func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq) (resp *msg.SearchMessageResp, err error) {
var chatLogs []*sdkws.MsgData var chatLogs []*sdkws.MsgData
var total int32 var total int64
resp = &msg.SearchMessageResp{} resp = &msg.SearchMessageResp{}
if total, chatLogs, err = m.MsgDatabase.SearchMessage(ctx, req); err != nil { if total, chatLogs, err = m.MsgDatabase.SearchMessage(ctx, req); err != nil {
return nil, err return nil, err
@ -194,7 +194,7 @@ func (m *msgServer) SearchMessage(ctx context.Context, req *msg.SearchMessageReq
} }
resp.ChatLogs = append(resp.ChatLogs, pbchatLog) resp.ChatLogs = append(resp.ChatLogs, pbchatLog)
} }
resp.ChatLogsNum = total resp.ChatLogsNum = int32(total)
return resp, nil return resp, nil
} }

@ -50,13 +50,14 @@ func (t *thirdServer) UploadLogs(ctx context.Context, req *third.UploadLogsReq)
platform := constant.PlatformID2Name[int(req.Platform)] platform := constant.PlatformID2Name[int(req.Platform)]
for _, fileURL := range req.FileURLs { for _, fileURL := range req.FileURLs {
log := relationtb.Log{ log := relationtb.Log{
Version: req.Version,
SystemType: req.SystemType,
Platform: platform, Platform: platform,
UserID: userID, UserID: userID,
CreateTime: time.Now(), CreateTime: time.Now(),
Url: fileURL.URL, Url: fileURL.URL,
FileName: fileURL.Filename, FileName: fileURL.Filename,
SystemType: req.SystemType,
Version: req.Version,
Ex: req.Ex,
} }
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
id := genLogID() id := genLogID()

@ -19,13 +19,17 @@ import (
"encoding/base64" "encoding/base64"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"path" "path"
"strconv" "strconv"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"go.mongodb.org/mongo-driver/mongo"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/openimsdk/open-im-server/v3/pkg/common/servererrs" "github.com/openimsdk/open-im-server/v3/pkg/common/servererrs"
"github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/protocol/third" "github.com/openimsdk/protocol/third"
"github.com/openimsdk/tools/errs" "github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log" "github.com/openimsdk/tools/log"
@ -283,6 +287,52 @@ func (t *thirdServer) apiAddress(prefix, name string) string {
return prefix + name return prefix + name
} }
func (t *thirdServer) DeleteOutdatedData(ctx context.Context, req *third.DeleteOutdatedDataReq) (*third.DeleteOutdatedDataResp, error) {
var conf config.Third
expireTime := time.UnixMilli(req.ExpireTime)
findPagination := &sdkws.RequestPagination{
PageNumber: 1,
ShowNumber: 1000,
}
for {
total, models, err := t.s3dataBase.FindByExpires(ctx, expireTime, findPagination)
if err != nil && errs.Unwrap(err) != mongo.ErrNoDocuments {
return nil, errs.Wrap(err)
}
needDelObjectKeys := make([]string, 0)
for _, model := range models {
needDelObjectKeys = append(needDelObjectKeys, model.Key)
}
needDelObjectKeys = datautil.Distinct(needDelObjectKeys)
for _, key := range needDelObjectKeys {
count, err := t.s3dataBase.FindNotDelByS3(ctx, key, expireTime)
if err != nil && errs.Unwrap(err) != mongo.ErrNoDocuments {
return nil, errs.Wrap(err)
}
if int(count) < 1 && t.minio != nil {
thumbnailKey, err := t.getMinioImageThumbnailKey(ctx, key)
if err != nil {
return nil, errs.Wrap(err)
}
t.s3dataBase.DeleteObject(ctx, thumbnailKey)
t.s3dataBase.DelS3Key(ctx, conf.Object.Enable, needDelObjectKeys...)
t.s3dataBase.DeleteObject(ctx, key)
}
}
for _, model := range models {
err := t.s3dataBase.DeleteSpecifiedData(ctx, model.Engine, model.Name)
if err != nil {
return nil, errs.Wrap(err)
}
}
if total < int64(findPagination.ShowNumber) {
break
}
}
return &third.DeleteOutdatedDataResp{}, nil
}
type FormDataMate struct { type FormDataMate struct {
Name string `json:"name"` Name string `json:"name"`
Size int64 `json:"size"` Size int64 `json:"size"`

@ -31,6 +31,7 @@ import (
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/s3" "github.com/openimsdk/tools/s3"
"github.com/openimsdk/tools/s3/cos" "github.com/openimsdk/tools/s3/cos"
"github.com/openimsdk/tools/s3/kodo"
"github.com/openimsdk/tools/s3/minio" "github.com/openimsdk/tools/s3/minio"
"github.com/openimsdk/tools/s3/oss" "github.com/openimsdk/tools/s3/oss"
"google.golang.org/grpc" "google.golang.org/grpc"
@ -42,7 +43,9 @@ type thirdServer struct {
userRpcClient rpcclient.UserRpcClient userRpcClient rpcclient.UserRpcClient
defaultExpire time.Duration defaultExpire time.Duration
config *Config config *Config
minio *minio.Minio
} }
type Config struct { type Config struct {
RpcConfig config.Third RpcConfig config.Third
RedisConfig config.Redis RedisConfig config.Redis
@ -73,14 +76,20 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
} }
// Select the oss method according to the profile policy // Select the oss method according to the profile policy
enable := config.RpcConfig.Object.Enable enable := config.RpcConfig.Object.Enable
var o s3.Interface var (
o s3.Interface
minioCli *minio.Minio
)
switch enable { switch enable {
case "minio": case "minio":
o, err = minio.NewMinio(ctx, redis.NewMinioCache(rdb), *config.MinioConfig.Build()) minioCli, err = minio.NewMinio(ctx, redis.NewMinioCache(rdb), *config.MinioConfig.Build())
o = minioCli
case "cos": case "cos":
o, err = cos.NewCos(*config.RpcConfig.Object.Cos.Build()) o, err = cos.NewCos(*config.RpcConfig.Object.Cos.Build())
case "oss": case "oss":
o, err = oss.NewOSS(*config.RpcConfig.Object.Oss.Build()) o, err = oss.NewOSS(*config.RpcConfig.Object.Oss.Build())
case "kodo":
o, err = kodo.NewKodo(*config.RpcConfig.Object.Kodo.Build())
default: default:
err = fmt.Errorf("invalid object enable: %s", enable) err = fmt.Errorf("invalid object enable: %s", enable)
} }
@ -94,10 +103,15 @@ func Start(ctx context.Context, config *Config, client discovery.SvcDiscoveryReg
s3dataBase: controller.NewS3Database(rdb, o, s3db), s3dataBase: controller.NewS3Database(rdb, o, s3db),
defaultExpire: time.Hour * 24 * 7, defaultExpire: time.Hour * 24 * 7,
config: config, config: config,
minio: minioCli,
}) })
return nil return nil
} }
func (t *thirdServer) getMinioImageThumbnailKey(ctx context.Context, name string) (string, error) {
return t.minio.GetImageThumbnailKey(ctx, name)
}
func (t *thirdServer) FcmUpdateToken(ctx context.Context, req *third.FcmUpdateTokenReq) (resp *third.FcmUpdateTokenResp, err error) { func (t *thirdServer) FcmUpdateToken(ctx context.Context, req *third.FcmUpdateTokenReq) (resp *third.FcmUpdateTokenResp, err error) {
err = t.thirdDatabase.FcmUpdateToken(ctx, req.Account, int(req.PlatformID), req.FcmToken, req.ExpireTime) err = t.thirdDatabase.FcmUpdateToken(ctx, req.Account, int(req.PlatformID), req.FcmToken, req.ExpireTime)
if err != nil { if err != nil {

@ -0,0 +1,82 @@
package user
import (
"context"
"github.com/openimsdk/protocol/constant"
pbuser "github.com/openimsdk/protocol/user"
)
func (s *userServer) getUserOnlineStatus(ctx context.Context, userID string) (*pbuser.OnlineStatus, error) {
platformIDs, err := s.online.GetOnline(ctx, userID)
if err != nil {
return nil, err
}
status := pbuser.OnlineStatus{
UserID: userID,
PlatformIDs: platformIDs,
}
if len(platformIDs) > 0 {
status.Status = constant.Online
} else {
status.Status = constant.Offline
}
return &status, nil
}
func (s *userServer) getUsersOnlineStatus(ctx context.Context, userIDs []string) ([]*pbuser.OnlineStatus, error) {
res := make([]*pbuser.OnlineStatus, 0, len(userIDs))
for _, userID := range userIDs {
status, err := s.getUserOnlineStatus(ctx, userID)
if err != nil {
return nil, err
}
res = append(res, status)
}
return res, nil
}
// SubscribeOrCancelUsersStatus Subscribe online or cancel online users.
func (s *userServer) SubscribeOrCancelUsersStatus(ctx context.Context, req *pbuser.SubscribeOrCancelUsersStatusReq) (*pbuser.SubscribeOrCancelUsersStatusResp, error) {
return &pbuser.SubscribeOrCancelUsersStatusResp{}, nil
}
// GetUserStatus Get the online status of the user.
func (s *userServer) GetUserStatus(ctx context.Context, req *pbuser.GetUserStatusReq) (*pbuser.GetUserStatusResp, error) {
res, err := s.getUsersOnlineStatus(ctx, req.UserIDs)
if err != nil {
return nil, err
}
return &pbuser.GetUserStatusResp{StatusList: res}, nil
}
// SetUserStatus Synchronize user's online status.
func (s *userServer) SetUserStatus(ctx context.Context, req *pbuser.SetUserStatusReq) (*pbuser.SetUserStatusResp, error) {
var (
online []int32
offline []int32
)
switch req.Status {
case constant.Online:
online = []int32{req.PlatformID}
case constant.Offline:
online = []int32{req.PlatformID}
}
if err := s.online.SetUserOnline(ctx, req.UserID, online, offline); err != nil {
return nil, err
}
return &pbuser.SetUserStatusResp{}, nil
}
// GetSubscribeUsersStatus Get the online status of subscribers.
func (s *userServer) GetSubscribeUsersStatus(ctx context.Context, req *pbuser.GetSubscribeUsersStatusReq) (*pbuser.GetSubscribeUsersStatusResp, error) {
return &pbuser.GetSubscribeUsersStatusResp{}, nil
}
func (s *userServer) SetUserOnlineStatus(ctx context.Context, req *pbuser.SetUserOnlineStatusReq) (*pbuser.SetUserOnlineStatusResp, error) {
for _, status := range req.Status {
if err := s.online.SetUserOnline(ctx, status.UserID, status.Online, status.Offline); err != nil {
return nil, err
}
}
return &pbuser.SetUserOnlineStatusResp{}, nil
}

@ -16,16 +16,22 @@ package user
import ( import (
"context" "context"
"errors"
"github.com/openimsdk/open-im-server/v3/internal/rpc/friend" "github.com/openimsdk/open-im-server/v3/internal/rpc/friend"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/cache/redis"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo" "github.com/openimsdk/open-im-server/v3/pkg/common/storage/database/mgo"
tablerelation "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" tablerelation "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/open-im-server/v3/pkg/common/webhook" "github.com/openimsdk/open-im-server/v3/pkg/common/webhook"
"github.com/openimsdk/open-im-server/v3/pkg/localcache" "github.com/openimsdk/open-im-server/v3/pkg/localcache"
"github.com/openimsdk/protocol/group"
friendpb "github.com/openimsdk/protocol/relation"
"github.com/openimsdk/tools/db/redisutil" "github.com/openimsdk/tools/db/redisutil"
"math/rand" "math/rand"
"strings" "strings"
"sync"
"time" "time"
"github.com/openimsdk/open-im-server/v3/pkg/authverify" "github.com/openimsdk/open-im-server/v3/pkg/authverify"
@ -46,6 +52,7 @@ import (
) )
type userServer struct { type userServer struct {
online cache.OnlineCache
db controller.UserDatabase db controller.UserDatabase
friendNotificationSender *friend.FriendNotificationSender friendNotificationSender *friend.FriendNotificationSender
userNotificationSender *UserNotificationSender userNotificationSender *UserNotificationSender
@ -87,13 +94,13 @@ func Start(ctx context.Context, config *Config, client registry.SvcDiscoveryRegi
return err return err
} }
userCache := redis.NewUserCacheRedis(rdb, &config.LocalCacheConfig, userDB, redis.GetRocksCacheOptions()) userCache := redis.NewUserCacheRedis(rdb, &config.LocalCacheConfig, userDB, redis.GetRocksCacheOptions())
userMongoDB := mgo.NewUserMongoDriver(mgocli.GetDB()) database := controller.NewUserDatabase(userDB, userCache, mgocli.GetTx())
database := controller.NewUserDatabase(userDB, userCache, mgocli.GetTx(), userMongoDB)
friendRpcClient := rpcclient.NewFriendRpcClient(client, config.Share.RpcRegisterName.Friend) friendRpcClient := rpcclient.NewFriendRpcClient(client, config.Share.RpcRegisterName.Friend)
groupRpcClient := rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group) groupRpcClient := rpcclient.NewGroupRpcClient(client, config.Share.RpcRegisterName.Group)
msgRpcClient := rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg) msgRpcClient := rpcclient.NewMessageRpcClient(client, config.Share.RpcRegisterName.Msg)
localcache.InitLocalCache(&config.LocalCacheConfig) localcache.InitLocalCache(&config.LocalCacheConfig)
u := &userServer{ u := &userServer{
online: redis.NewUserOnline(rdb),
db: database, db: database,
RegisterCenter: client, RegisterCenter: client,
friendRpcClient: &friendRpcClient, friendRpcClient: &friendRpcClient,
@ -131,26 +138,29 @@ func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserI
if err := s.webhookBeforeUpdateUserInfo(ctx, &s.config.WebhooksConfig.BeforeUpdateUserInfo, req); err != nil { if err := s.webhookBeforeUpdateUserInfo(ctx, &s.config.WebhooksConfig.BeforeUpdateUserInfo, req); err != nil {
return nil, err return nil, err
} }
data := convert.UserPb2DBMap(req.UserInfo) data := convert.UserPb2DBMap(req.UserInfo)
if err := s.db.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil { oldUser, err := s.db.GetUserByID(ctx, req.UserInfo.UserID)
return nil, err
}
s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID)
friends, err := s.friendRpcClient.GetFriendIDs(ctx, req.UserInfo.UserID)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if req.UserInfo.Nickname != "" || req.UserInfo.FaceURL != "" { if err := s.db.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil {
if err = s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil { return nil, err
return nil, err
}
}
for _, friendID := range friends {
s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID)
} }
s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID)
//friends, err := s.friendRpcClient.GetFriendIDs(ctx, req.UserInfo.UserID)
//if err != nil {
// return nil, err
//}
//if req.UserInfo.Nickname != "" || req.UserInfo.FaceURL != "" {
// if err = s.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID,oldUser); err != nil {
// return nil, err
// }
//}
//for _, friendID := range friends {
// s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID)
//}
s.webhookAfterUpdateUserInfo(ctx, &s.config.WebhooksConfig.AfterUpdateUserInfo, req) s.webhookAfterUpdateUserInfo(ctx, &s.config.WebhooksConfig.AfterUpdateUserInfo, req)
if err = s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil { if err = s.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID, oldUser); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -164,25 +174,29 @@ func (s *userServer) UpdateUserInfoEx(ctx context.Context, req *pbuser.UpdateUse
if err = s.webhookBeforeUpdateUserInfoEx(ctx, &s.config.WebhooksConfig.BeforeUpdateUserInfoEx, req); err != nil { if err = s.webhookBeforeUpdateUserInfoEx(ctx, &s.config.WebhooksConfig.BeforeUpdateUserInfoEx, req); err != nil {
return nil, err return nil, err
} }
oldUser, err := s.db.GetUserByID(ctx, req.UserInfo.UserID)
if err != nil {
return nil, err
}
data := convert.UserPb2DBMapEx(req.UserInfo) data := convert.UserPb2DBMapEx(req.UserInfo)
if err = s.db.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil { if err = s.db.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil {
return nil, err return nil, err
} }
s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID) s.friendNotificationSender.UserInfoUpdatedNotification(ctx, req.UserInfo.UserID)
friends, err := s.friendRpcClient.GetFriendIDs(ctx, req.UserInfo.UserID) //friends, err := s.friendRpcClient.GetFriendIDs(ctx, req.UserInfo.UserID)
if err != nil { //if err != nil {
return nil, err // return nil, err
} //}
if req.UserInfo.Nickname != nil || req.UserInfo.FaceURL != nil { //if req.UserInfo.Nickname != nil || req.UserInfo.FaceURL != nil {
if err := s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil { // if err := s.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil {
return nil, err // return nil, err
} // }
} //}
for _, friendID := range friends { //for _, friendID := range friends {
s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID) // s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID)
} //}
s.webhookAfterUpdateUserInfoEx(ctx, &s.config.WebhooksConfig.AfterUpdateUserInfoEx, req) s.webhookAfterUpdateUserInfoEx(ctx, &s.config.WebhooksConfig.AfterUpdateUserInfoEx, req)
if err := s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil { if err := s.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID, oldUser); err != nil {
return nil, err return nil, err
} }
return resp, nil return resp, nil
@ -297,6 +311,8 @@ func (s *userServer) UserRegister(ctx context.Context, req *pbuser.UserRegisterR
return nil, err return nil, err
} }
prommetrics.UserRegisterCounter.Add(float64(len(users)))
s.webhookAfterUserRegister(ctx, &s.config.WebhooksConfig.AfterUserRegister, req) s.webhookAfterUserRegister(ctx, &s.config.WebhooksConfig.AfterUserRegister, req)
return resp, nil return resp, nil
} }
@ -318,76 +334,6 @@ func (s *userServer) GetAllUserID(ctx context.Context, req *pbuser.GetAllUserIDR
return &pbuser.GetAllUserIDResp{Total: int32(total), UserIDs: userIDs}, nil return &pbuser.GetAllUserIDResp{Total: int32(total), UserIDs: userIDs}, nil
} }
// SubscribeOrCancelUsersStatus Subscribe online or cancel online users.
func (s *userServer) SubscribeOrCancelUsersStatus(ctx context.Context, req *pbuser.SubscribeOrCancelUsersStatusReq) (resp *pbuser.SubscribeOrCancelUsersStatusResp, err error) {
if req.Genre == constant.SubscriberUser {
err = s.db.SubscribeUsersStatus(ctx, req.UserID, req.UserIDs)
if err != nil {
return nil, err
}
var status []*pbuser.OnlineStatus
status, err = s.db.GetUserStatus(ctx, req.UserIDs)
if err != nil {
return nil, err
}
return &pbuser.SubscribeOrCancelUsersStatusResp{StatusList: status}, nil
} else if req.Genre == constant.Unsubscribe {
err = s.db.UnsubscribeUsersStatus(ctx, req.UserID, req.UserIDs)
if err != nil {
return nil, err
}
}
return &pbuser.SubscribeOrCancelUsersStatusResp{}, nil
}
// GetUserStatus Get the online status of the user.
func (s *userServer) GetUserStatus(ctx context.Context, req *pbuser.GetUserStatusReq) (resp *pbuser.GetUserStatusResp,
err error) {
onlineStatusList, err := s.db.GetUserStatus(ctx, req.UserIDs)
if err != nil {
return nil, err
}
return &pbuser.GetUserStatusResp{StatusList: onlineStatusList}, nil
}
// SetUserStatus Synchronize user's online status.
func (s *userServer) SetUserStatus(ctx context.Context, req *pbuser.SetUserStatusReq) (resp *pbuser.SetUserStatusResp,
err error) {
err = s.db.SetUserStatus(ctx, req.UserID, req.Status, req.PlatformID)
if err != nil {
return nil, err
}
list, err := s.db.GetSubscribedList(ctx, req.UserID)
if err != nil {
return nil, err
}
for _, userID := range list {
tips := &sdkws.UserStatusChangeTips{
FromUserID: req.UserID,
ToUserID: userID,
Status: req.Status,
PlatformID: req.PlatformID,
}
s.userNotificationSender.UserStatusChangeNotification(ctx, tips)
}
return &pbuser.SetUserStatusResp{}, nil
}
// GetSubscribeUsersStatus Get the online status of subscribers.
func (s *userServer) GetSubscribeUsersStatus(ctx context.Context,
req *pbuser.GetSubscribeUsersStatusReq) (*pbuser.GetSubscribeUsersStatusResp, error) {
userList, err := s.db.GetAllSubscribeList(ctx, req.UserID)
if err != nil {
return nil, err
}
onlineStatusList, err := s.db.GetUserStatus(ctx, userList)
if err != nil {
return nil, err
}
return &pbuser.GetSubscribeUsersStatusResp{StatusList: onlineStatusList}, nil
}
// ProcessUserCommandAdd user general function add. // ProcessUserCommandAdd user general function add.
func (s *userServer) ProcessUserCommandAdd(ctx context.Context, req *pbuser.ProcessUserCommandAddReq) (*pbuser.ProcessUserCommandAddResp, error) { func (s *userServer) ProcessUserCommandAdd(ctx context.Context, req *pbuser.ProcessUserCommandAddReq) (*pbuser.ProcessUserCommandAddResp, error) {
err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID) err := authverify.CheckAccessV3(ctx, req.UserID, s.config.Share.IMAdminUserID)
@ -683,3 +629,45 @@ func (s *userServer) userModelToResp(users []*tablerelation.User, pagination pag
return &pbuser.SearchNotificationAccountResp{Total: total, NotificationAccounts: notificationAccounts} return &pbuser.SearchNotificationAccountResp{Total: total, NotificationAccounts: notificationAccounts}
} }
func (s *userServer) NotificationUserInfoUpdate(ctx context.Context, userID string, oldUser *tablerelation.User) error {
user, err := s.db.GetUserByID(ctx, userID)
if err != nil {
return err
}
if user.Nickname == oldUser.Nickname && user.FaceURL == oldUser.FaceURL {
return nil
}
oldUserInfo := convert.UserDB2Pb(oldUser)
newUserInfo := convert.UserDB2Pb(user)
var wg sync.WaitGroup
var es [2]error
wg.Add(len(es))
go func() {
defer wg.Done()
_, es[0] = s.groupRpcClient.Client.NotificationUserInfoUpdate(ctx, &group.NotificationUserInfoUpdateReq{
UserID: userID,
OldUserInfo: oldUserInfo,
NewUserInfo: newUserInfo,
})
}()
go func() {
defer wg.Done()
_, es[1] = s.friendRpcClient.Client.NotificationUserInfoUpdate(ctx, &friendpb.NotificationUserInfoUpdateReq{
UserID: userID,
OldUserInfo: oldUserInfo,
NewUserInfo: newUserInfo,
})
}()
wg.Wait()
return errors.Join(es[:]...)
}
func (s *userServer) SortQuery(ctx context.Context, req *pbuser.SortQueryReq) (*pbuser.SortQueryResp, error) {
users, err := s.db.SortQuery(ctx, req.UserIDName, req.Asc)
if err != nil {
return nil, err
}
return &pbuser.SortQueryResp{Users: convert.UsersDB2Pb(users)}, nil
}

@ -20,6 +20,7 @@ import (
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister"
"github.com/openimsdk/protocol/msg" "github.com/openimsdk/protocol/msg"
"github.com/openimsdk/protocol/third"
"github.com/openimsdk/tools/mcontext" "github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/mw" "github.com/openimsdk/tools/mw"
"google.golang.org/grpc" "google.golang.org/grpc"
@ -39,7 +40,7 @@ type CronTaskConfig struct {
} }
func Start(ctx context.Context, config *CronTaskConfig) error { func Start(ctx context.Context, config *CronTaskConfig) error {
log.CInfo(ctx, "CRON-TASK server is initializing", "chatRecordsClearTime", config.CronTask.ChatRecordsClearTime, "msgDestructTime", config.CronTask.RetainChatRecords) log.CInfo(ctx, "CRON-TASK server is initializing", "chatRecordsClearTime", config.CronTask.CronExecuteTime, "msgDestructTime", config.CronTask.RetainChatRecords)
if config.CronTask.RetainChatRecords < 1 { if config.CronTask.RetainChatRecords < 1 {
return errs.New("msg destruct time must be greater than 1").Wrap() return errs.New("msg destruct time must be greater than 1").Wrap()
} }
@ -66,10 +67,31 @@ func Start(ctx context.Context, config *CronTaskConfig) error {
} }
log.ZInfo(ctx, "cron clear chat records success", "deltime", deltime, "cont", time.Since(now)) log.ZInfo(ctx, "cron clear chat records success", "deltime", deltime, "cont", time.Since(now))
} }
if _, err := crontab.AddFunc(config.CronTask.ChatRecordsClearTime, clearFunc); err != nil { if _, err := crontab.AddFunc(config.CronTask.CronExecuteTime, clearFunc); err != nil {
return errs.Wrap(err) return errs.Wrap(err)
} }
log.ZInfo(ctx, "start cron task", "chatRecordsClearTime", config.CronTask.ChatRecordsClearTime)
tConn, err := client.GetConn(ctx, config.Share.RpcRegisterName.Third)
if err != nil {
return err
}
thirdClient := third.NewThirdClient(tConn)
deleteFunc := func() {
now := time.Now()
deleteTime := now.Add(-time.Hour * 24 * time.Duration(config.CronTask.FileExpireTime))
ctx := mcontext.SetOperationID(ctx, fmt.Sprintf("cron_%d_%d", os.Getpid(), deleteTime.UnixMilli()))
log.ZInfo(ctx, "deleteoutDatedData ", "deletetime", deleteTime, "timestamp", deleteTime.UnixMilli())
if _, err := thirdClient.DeleteOutdatedData(ctx, &third.DeleteOutdatedDataReq{ExpireTime: deleteTime.UnixMilli()}); err != nil {
log.ZError(ctx, "cron deleteoutDatedData failed", err, "deleteTime", deleteTime, "cont", time.Since(now))
return
}
log.ZInfo(ctx, "cron deleteoutDatedData success", "deltime", deleteTime, "cont", time.Since(now))
}
if _, err := crontab.AddFunc(config.CronTask.CronExecuteTime, deleteFunc); err != nil {
return errs.Wrap(err)
}
log.ZInfo(ctx, "start cron task", "CronExecuteTime", config.CronTask.CronExecuteTime)
crontab.Start() crontab.Start()
<-ctx.Done() <-ctx.Done()
return nil return nil

@ -15,7 +15,7 @@
package apistruct package apistruct
import ( import (
sdkws "github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
) )
// SendMsg defines the structure for sending messages with various metadata. // SendMsg defines the structure for sending messages with various metadata.
@ -55,6 +55,9 @@ type SendMsg struct {
// OfflinePushInfo contains information for offline push notifications. // OfflinePushInfo contains information for offline push notifications.
OfflinePushInfo *sdkws.OfflinePushInfo `json:"offlinePushInfo"` OfflinePushInfo *sdkws.OfflinePushInfo `json:"offlinePushInfo"`
// Ex stores extended fields
Ex string `json:"ex"`
} }
// SendMsgReq extends SendMsg with the requirement of RecvID when SessionType indicates a one-on-one or notification chat. // SendMsgReq extends SendMsg with the requirement of RecvID when SessionType indicates a one-on-one or notification chat.

@ -19,6 +19,7 @@ import (
"github.com/openimsdk/open-im-server/v3/internal/rpc/group" "github.com/openimsdk/open-im-server/v3/internal/rpc/group"
"github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/open-im-server/v3/pkg/common/startrpc" "github.com/openimsdk/open-im-server/v3/pkg/common/startrpc"
"github.com/openimsdk/open-im-server/v3/pkg/common/storage/versionctx"
"github.com/openimsdk/tools/system/program" "github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@ -58,5 +59,5 @@ func (a *GroupRpcCmd) Exec() error {
func (a *GroupRpcCmd) runE() error { func (a *GroupRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.groupConfig.Discovery, &a.groupConfig.RpcConfig.Prometheus, a.groupConfig.RpcConfig.RPC.ListenIP, return startrpc.Start(a.ctx, &a.groupConfig.Discovery, &a.groupConfig.RpcConfig.Prometheus, a.groupConfig.RpcConfig.RPC.ListenIP,
a.groupConfig.RpcConfig.RPC.RegisterIP, a.groupConfig.RpcConfig.RPC.Ports, a.groupConfig.RpcConfig.RPC.RegisterIP, a.groupConfig.RpcConfig.RPC.Ports,
a.Index(), a.groupConfig.Share.RpcRegisterName.Group, &a.groupConfig.Share, a.groupConfig, group.Start) a.Index(), a.groupConfig.Share.RpcRegisterName.Group, &a.groupConfig.Share, a.groupConfig, group.Start, versionctx.EnableVersionCtx())
} }

@ -37,6 +37,7 @@ func NewMsgGatewayCmd() *MsgGatewayCmd {
ret.configMap = map[string]any{ ret.configMap = map[string]any{
OpenIMMsgGatewayCfgFileName: &msgGatewayConfig.MsgGateway, OpenIMMsgGatewayCfgFileName: &msgGatewayConfig.MsgGateway,
ShareFileName: &msgGatewayConfig.Share, ShareFileName: &msgGatewayConfig.Share,
RedisConfigFileName: &msgGatewayConfig.RedisConfig,
WebhooksConfigFileName: &msgGatewayConfig.WebhooksConfig, WebhooksConfigFileName: &msgGatewayConfig.WebhooksConfig,
DiscoveryConfigFilename: &msgGatewayConfig.Discovery, DiscoveryConfigFilename: &msgGatewayConfig.Discovery,
} }

@ -19,6 +19,7 @@ import (
"github.com/openimsdk/tools/apiresp" "github.com/openimsdk/tools/apiresp"
"github.com/openimsdk/tools/utils/jsonutil" "github.com/openimsdk/tools/utils/jsonutil"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
"go.mongodb.org/mongo-driver/bson/primitive"
"math" "math"
"testing" "testing"
) )
@ -59,3 +60,9 @@ func TestName(t *testing.T) {
t.Logf("%+v\n", rReso) t.Logf("%+v\n", rReso)
} }
func TestName1(t *testing.T) {
t.Log(primitive.NewObjectID().String())
t.Log(primitive.NewObjectID().Hex())
}

@ -47,6 +47,7 @@ func NewPushRpcCmd() *PushRpcCmd {
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap)) ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", config.Version) ret.ctx = context.WithValue(context.Background(), "version", config.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error { ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
ret.pushConfig.FcmConfigPath = ret.ConfigPath()
return ret.runE() return ret.runE()
} }
return ret return ret

@ -31,6 +31,11 @@ type RootCmd struct {
prometheusPort int prometheusPort int
log config.Log log config.Log
index int index int
configPath string
}
func (r *RootCmd) ConfigPath() string {
return r.configPath
} }
func (r *RootCmd) Index() int { func (r *RootCmd) Index() int {
@ -134,6 +139,7 @@ func (r *RootCmd) initializeLogger(cmdOpts *CmdOpts) error {
r.log.RemainRotationCount, r.log.RemainRotationCount,
r.log.RotationTime, r.log.RotationTime,
config.Version, config.Version,
r.log.IsSimplify,
) )
if err != nil { if err != nil {
return errs.Wrap(err) return errs.Wrap(err)
@ -153,6 +159,7 @@ func (r *RootCmd) getFlag(cmd *cobra.Command) (string, int, error) {
if err != nil { if err != nil {
return "", 0, errs.Wrap(err) return "", 0, errs.Wrap(err)
} }
r.configPath = configDirectory
index, err := cmd.Flags().GetInt(FlagTransferIndex) index, err := cmd.Flags().GetInt(FlagTransferIndex)
if err != nil { if err != nil {
return "", 0, errs.Wrap(err) return "", 0, errs.Wrap(err)

@ -15,14 +15,16 @@
package config package config
import ( import (
"strings"
"time"
"github.com/openimsdk/tools/db/mongoutil" "github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/redisutil" "github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/tools/mq/kafka" "github.com/openimsdk/tools/mq/kafka"
"github.com/openimsdk/tools/s3/cos" "github.com/openimsdk/tools/s3/cos"
"github.com/openimsdk/tools/s3/kodo"
"github.com/openimsdk/tools/s3/minio" "github.com/openimsdk/tools/s3/minio"
"github.com/openimsdk/tools/s3/oss" "github.com/openimsdk/tools/s3/oss"
"strings"
"time"
) )
type CacheConfig struct { type CacheConfig struct {
@ -47,6 +49,7 @@ type Log struct {
RemainLogLevel int `mapstructure:"remainLogLevel"` RemainLogLevel int `mapstructure:"remainLogLevel"`
IsStdout bool `mapstructure:"isStdout"` IsStdout bool `mapstructure:"isStdout"`
IsJson bool `mapstructure:"isJson"` IsJson bool `mapstructure:"isJson"`
IsSimplify bool `mapstructure:"isSimplify"`
WithStack bool `mapstructure:"withStack"` WithStack bool `mapstructure:"withStack"`
} }
@ -105,8 +108,9 @@ type API struct {
} }
type CronTask struct { type CronTask struct {
ChatRecordsClearTime string `mapstructure:"chatRecordsClearTime"` CronExecuteTime string `mapstructure:"cronExecuteTime"`
RetainChatRecords int `mapstructure:"retainChatRecords"` RetainChatRecords int `mapstructure:"retainChatRecords"`
FileExpireTime int `mapstructure:"fileExpireTime"`
} }
type OfflinePushConfig struct { type OfflinePushConfig struct {
@ -202,7 +206,8 @@ type Push struct {
ChannelName string `mapstructure:"channelName"` ChannelName string `mapstructure:"channelName"`
} `mapstructure:"geTui"` } `mapstructure:"geTui"`
FCM struct { FCM struct {
ServiceAccount string `mapstructure:"serviceAccount"` FilePath string `mapstructure:"filePath"`
AuthURL string `mapstructure:"authURL"`
} `mapstructure:"fcm"` } `mapstructure:"fcm"`
JPNS struct { JPNS struct {
AppKey string `mapstructure:"appKey"` AppKey string `mapstructure:"appKey"`
@ -277,16 +282,8 @@ type Third struct {
Enable string `mapstructure:"enable"` Enable string `mapstructure:"enable"`
Cos Cos `mapstructure:"cos"` Cos Cos `mapstructure:"cos"`
Oss Oss `mapstructure:"oss"` Oss Oss `mapstructure:"oss"`
Kodo struct { Kodo Kodo `mapstructure:"kodo"`
Endpoint string `mapstructure:"endpoint"` Aws struct {
Bucket string `mapstructure:"bucket"`
BucketURL string `mapstructure:"bucketURL"`
AccessKeyID string `mapstructure:"accessKeyID"`
AccessKeySecret string `mapstructure:"accessKeySecret"`
SessionToken string `mapstructure:"sessionToken"`
PublicRead bool `mapstructure:"publicRead"`
} `mapstructure:"kodo"`
Aws struct {
Endpoint string `mapstructure:"endpoint"` Endpoint string `mapstructure:"endpoint"`
Region string `mapstructure:"region"` Region string `mapstructure:"region"`
Bucket string `mapstructure:"bucket"` Bucket string `mapstructure:"bucket"`
@ -313,6 +310,16 @@ type Oss struct {
PublicRead bool `mapstructure:"publicRead"` PublicRead bool `mapstructure:"publicRead"`
} }
type Kodo struct {
Endpoint string `mapstructure:"endpoint"`
Bucket string `mapstructure:"bucket"`
BucketURL string `mapstructure:"bucketURL"`
AccessKeyID string `mapstructure:"accessKeyID"`
AccessKeySecret string `mapstructure:"accessKeySecret"`
SessionToken string `mapstructure:"sessionToken"`
PublicRead bool `mapstructure:"publicRead"`
}
type User struct { type User struct {
RPC struct { RPC struct {
RegisterIP string `mapstructure:"registerIP"` RegisterIP string `mapstructure:"registerIP"`
@ -338,8 +345,9 @@ type BeforeConfig struct {
} }
type AfterConfig struct { type AfterConfig struct {
Enable bool `mapstructure:"enable"` Enable bool `mapstructure:"enable"`
Timeout int `mapstructure:"timeout"` Timeout int `mapstructure:"timeout"`
AttentionIds []string `mapstructure:"attentionIds"`
} }
type Share struct { type Share struct {
@ -523,6 +531,18 @@ func (o *Oss) Build() *oss.Config {
} }
} }
func (o *Kodo) Build() *kodo.Config {
return &kodo.Config{
Endpoint: o.Endpoint,
Bucket: o.Bucket,
BucketURL: o.BucketURL,
AccessKeyID: o.AccessKeyID,
AccessKeySecret: o.AccessKeySecret,
SessionToken: o.SessionToken,
PublicRead: o.PublicRead,
}
}
func (l *CacheConfig) Failed() time.Duration { func (l *CacheConfig) Failed() time.Duration {
return time.Second * time.Duration(l.FailedExpire) return time.Second * time.Duration(l.FailedExpire)
} }

@ -16,26 +16,26 @@ package convert
import ( import (
relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model" relationtb "github.com/openimsdk/open-im-server/v3/pkg/common/storage/model"
"github.com/openimsdk/tools/utils/datautil"
"time" "time"
"github.com/openimsdk/protocol/sdkws" "github.com/openimsdk/protocol/sdkws"
) )
func UsersDB2Pb(users []*relationtb.User) []*sdkws.UserInfo { func UserDB2Pb(user *relationtb.User) *sdkws.UserInfo {
result := make([]*sdkws.UserInfo, 0, len(users)) return &sdkws.UserInfo{
for _, user := range users { UserID: user.UserID,
userPb := &sdkws.UserInfo{ Nickname: user.Nickname,
UserID: user.UserID, FaceURL: user.FaceURL,
Nickname: user.Nickname, Ex: user.Ex,
FaceURL: user.FaceURL, CreateTime: user.CreateTime.UnixMilli(),
Ex: user.Ex, AppMangerLevel: user.AppMangerLevel,
CreateTime: user.CreateTime.UnixMilli(), GlobalRecvMsgOpt: user.GlobalRecvMsgOpt,
AppMangerLevel: user.AppMangerLevel,
GlobalRecvMsgOpt: user.GlobalRecvMsgOpt,
}
result = append(result, userPb)
} }
return result }
func UsersDB2Pb(users []*relationtb.User) []*sdkws.UserInfo {
return datautil.Slice(users, UserDB2Pb)
} }
func UserPb2DB(user *sdkws.UserInfo) *relationtb.User { func UserPb2DB(user *sdkws.UserInfo) *relationtb.User {

@ -14,430 +14,431 @@
package ginprometheus package ginprometheus
import ( //
"bytes" //import (
"fmt" // "bytes"
"io" // "fmt"
"net/http" // "io"
"os" // "net/http"
"strconv" // "os"
"time" // "strconv"
// "time"
"github.com/gin-gonic/gin" //
"github.com/prometheus/client_golang/prometheus" // "github.com/gin-gonic/gin"
"github.com/prometheus/client_golang/prometheus/promhttp" // "github.com/prometheus/client_golang/prometheus"
) // "github.com/prometheus/client_golang/prometheus/promhttp"
//)
var defaultMetricPath = "/metrics" //
//var defaultMetricPath = "/metrics"
// counter, counter_vec, gauge, gauge_vec, //
// histogram, histogram_vec, summary, summary_vec. //// counter, counter_vec, gauge, gauge_vec,
var ( //// histogram, histogram_vec, summary, summary_vec.
reqCounter = &Metric{ //var (
ID: "reqCnt", // reqCounter = &Metric{
Name: "requests_total", // ID: "reqCnt",
Description: "How many HTTP requests processed, partitioned by status code and HTTP method.", // Name: "requests_total",
Type: "counter_vec", // Description: "How many HTTP requests processed, partitioned by status code and HTTP method.",
Args: []string{"code", "method", "handler", "host", "url"}} // Type: "counter_vec",
// Args: []string{"code", "method", "handler", "host", "url"}}
reqDuration = &Metric{ //
ID: "reqDur", // reqDuration = &Metric{
Name: "request_duration_seconds", // ID: "reqDur",
Description: "The HTTP request latencies in seconds.", // Name: "request_duration_seconds",
Type: "histogram_vec", // Description: "The HTTP request latencies in seconds.",
Args: []string{"code", "method", "url"}, // Type: "histogram_vec",
} // Args: []string{"code", "method", "url"},
// }
resSize = &Metric{ //
ID: "resSz", // resSize = &Metric{
Name: "response_size_bytes", // ID: "resSz",
Description: "The HTTP response sizes in bytes.", // Name: "response_size_bytes",
Type: "summary"} // Description: "The HTTP response sizes in bytes.",
// Type: "summary"}
reqSize = &Metric{ //
ID: "reqSz", // reqSize = &Metric{
Name: "request_size_bytes", // ID: "reqSz",
Description: "The HTTP request sizes in bytes.", // Name: "request_size_bytes",
Type: "summary"} // Description: "The HTTP request sizes in bytes.",
// Type: "summary"}
standardMetrics = []*Metric{ //
reqCounter, // standardMetrics = []*Metric{
reqDuration, // reqCounter,
resSize, // reqDuration,
reqSize, // resSize,
} // reqSize,
) // }
//)
/* //
RequestCounterURLLabelMappingFn is a function which can be supplied to the middleware to control ///*
the cardinality of the request counter's "url" label, which might be required in some contexts. //RequestCounterURLLabelMappingFn is a function which can be supplied to the middleware to control
For instance, if for a "/customer/:name" route you don't want to generate a time series for every //the cardinality of the request counter's "url" label, which might be required in some contexts.
possible customer name, you could use this function: //For instance, if for a "/customer/:name" route you don't want to generate a time series for every
//possible customer name, you could use this function:
func(c *gin.Context) string { //
url := c.Request.URL.Path // func(c *gin.Context) string {
for _, p := range c.Params { // url := c.Request.URL.Path
if p.Key == "name" { // for _, p := range c.Params {
url = strings.Replace(url, p.Value, ":name", 1) // if p.Key == "name" {
break // url = strings.Replace(url, p.Value, ":name", 1)
} // break
} // }
return url // }
} // return url
// }
which would map "/customer/alice" and "/customer/bob" to their template "/customer/:name". //
*/ //which would map "/customer/alice" and "/customer/bob" to their template "/customer/:name".
type RequestCounterURLLabelMappingFn func(c *gin.Context) string //*/
//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. //// Metric is a definition for the name, description, type, ID, and
type Metric struct { //// prometheus.Collector type (i.e. CounterVec, Summary, etc) of each metric.
MetricCollector prometheus.Collector //type Metric struct {
ID string // MetricCollector prometheus.Collector
Name string // ID string
Description string // Name string
Type string // Description string
Args []string // Type string
} // Args []string
//}
// Prometheus contains the metrics gathered by the instance and its path. //
type Prometheus struct { //// Prometheus contains the metrics gathered by the instance and its path.
reqCnt *prometheus.CounterVec //type Prometheus struct {
reqDur *prometheus.HistogramVec // reqCnt *prometheus.CounterVec
reqSz, resSz prometheus.Summary // reqDur *prometheus.HistogramVec
router *gin.Engine // reqSz, resSz prometheus.Summary
listenAddress string // router *gin.Engine
Ppg PrometheusPushGateway // listenAddress string
// Ppg PrometheusPushGateway
MetricsList []*Metric //
MetricsPath string // MetricsList []*Metric
// MetricsPath string
ReqCntURLLabelMappingFn RequestCounterURLLabelMappingFn //
// ReqCntURLLabelMappingFn RequestCounterURLLabelMappingFn
// gin.Context string to use as a prometheus URL label //
URLLabelFromContext string // // gin.Context string to use as a prometheus URL label
} // URLLabelFromContext string
//}
// PrometheusPushGateway contains the configuration for pushing to a Prometheus pushgateway (optional). //
type PrometheusPushGateway struct { //// PrometheusPushGateway contains the configuration for pushing to a Prometheus pushgateway (optional).
//type PrometheusPushGateway struct {
// Push interval in seconds //
PushIntervalSeconds time.Duration // // Push interval in seconds
// PushIntervalSeconds time.Duration
// Push Gateway URL in format http://domain:port //
// where JOBNAME can be any string of your choice // // Push Gateway URL in format http://domain:port
PushGatewayURL string // // where JOBNAME can be any string of your choice
// PushGatewayURL string
// Local metrics URL where metrics are fetched from, this could be omitted in the future //
// if implemented using prometheus common/expfmt instead // // Local metrics URL where metrics are fetched from, this could be omitted in the future
MetricsURL string // // if implemented using prometheus common/expfmt instead
// MetricsURL string
// pushgateway job name, defaults to "gin" //
Job string // // pushgateway job name, defaults to "gin"
} // Job string
//}
// NewPrometheus generates a new set of metrics with a certain subsystem name. //
func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus { //// NewPrometheus generates a new set of metrics with a certain subsystem name.
if subsystem == "" { //func NewPrometheus(subsystem string, customMetricsList ...[]*Metric) *Prometheus {
subsystem = "app" // if subsystem == "" {
} // subsystem = "app"
// }
var metricsList []*Metric //
// var metricsList []*Metric
if len(customMetricsList) > 1 { //
panic("Too many args. NewPrometheus( string, <optional []*Metric> ).") // if len(customMetricsList) > 1 {
} else if len(customMetricsList) == 1 { // panic("Too many args. NewPrometheus( string, <optional []*Metric> ).")
metricsList = customMetricsList[0] // } else if len(customMetricsList) == 1 {
} // metricsList = customMetricsList[0]
metricsList = append(metricsList, standardMetrics...) // }
// metricsList = append(metricsList, standardMetrics...)
p := &Prometheus{ //
MetricsList: metricsList, // p := &Prometheus{
MetricsPath: defaultMetricPath, // MetricsList: metricsList,
ReqCntURLLabelMappingFn: func(c *gin.Context) string { // MetricsPath: defaultMetricPath,
return c.FullPath() // e.g. /user/:id , /user/:id/info // ReqCntURLLabelMappingFn: func(c *gin.Context) string {
}, // return c.FullPath() // e.g. /user/:id , /user/:id/info
} // },
// }
p.registerMetrics(subsystem) //
// p.registerMetrics(subsystem)
return p //
} // return p
//}
// SetPushGateway sends metrics to a remote pushgateway exposed on pushGatewayURL //
// every pushIntervalSeconds. Metrics are fetched from metricsURL. //// SetPushGateway sends metrics to a remote pushgateway exposed on pushGatewayURL
func (p *Prometheus) SetPushGateway(pushGatewayURL, metricsURL string, pushIntervalSeconds time.Duration) { //// every pushIntervalSeconds. Metrics are fetched from metricsURL.
p.Ppg.PushGatewayURL = pushGatewayURL //func (p *Prometheus) SetPushGateway(pushGatewayURL, metricsURL string, pushIntervalSeconds time.Duration) {
p.Ppg.MetricsURL = metricsURL // p.Ppg.PushGatewayURL = pushGatewayURL
p.Ppg.PushIntervalSeconds = pushIntervalSeconds // p.Ppg.MetricsURL = metricsURL
p.startPushTicker() // p.Ppg.PushIntervalSeconds = pushIntervalSeconds
} // p.startPushTicker()
//}
// SetPushGatewayJob job name, defaults to "gin". //
func (p *Prometheus) SetPushGatewayJob(j string) { //// SetPushGatewayJob job name, defaults to "gin".
p.Ppg.Job = j //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. //// SetListenAddress for exposing metrics on address. If not set, it will be exposed at the
func (p *Prometheus) SetListenAddress(address string) { //// same address of the gin engine that is being used.
p.listenAddress = address //func (p *Prometheus) SetListenAddress(address string) {
if p.listenAddress != "" { // p.listenAddress = address
p.router = gin.Default() // if p.listenAddress != "" {
} // p.router = gin.Default()
} // }
//}
// SetListenAddressWithRouter for using a separate router to expose metrics. (this keeps things like GET /metrics out of //
// your content's access log). //// SetListenAddressWithRouter for using a separate router to expose metrics. (this keeps things like GET /metrics out of
func (p *Prometheus) SetListenAddressWithRouter(listenAddress string, r *gin.Engine) { //// your content's access log).
p.listenAddress = listenAddress //func (p *Prometheus) SetListenAddressWithRouter(listenAddress string, r *gin.Engine) {
if len(p.listenAddress) > 0 { // p.listenAddress = listenAddress
p.router = r // if len(p.listenAddress) > 0 {
} // p.router = r
} // }
//}
// SetMetricsPath set metrics paths. //
func (p *Prometheus) SetMetricsPath(e *gin.Engine) error { //// SetMetricsPath set metrics paths.
//func (p *Prometheus) SetMetricsPath(e *gin.Engine) error {
if p.listenAddress != "" { //
p.router.GET(p.MetricsPath, prometheusHandler()) // if p.listenAddress != "" {
return p.runServer() // p.router.GET(p.MetricsPath, prometheusHandler())
} else { // return p.runServer()
e.GET(p.MetricsPath, prometheusHandler()) // } else {
return nil // e.GET(p.MetricsPath, prometheusHandler())
} // return nil
} // }
//}
// SetMetricsPathWithAuth set metrics paths with authentication. //
func (p *Prometheus) SetMetricsPathWithAuth(e *gin.Engine, accounts gin.Accounts) error { //// SetMetricsPathWithAuth set metrics paths with authentication.
//func (p *Prometheus) SetMetricsPathWithAuth(e *gin.Engine, accounts gin.Accounts) error {
if p.listenAddress != "" { //
p.router.GET(p.MetricsPath, gin.BasicAuth(accounts), prometheusHandler()) // if p.listenAddress != "" {
return p.runServer() // p.router.GET(p.MetricsPath, gin.BasicAuth(accounts), prometheusHandler())
} else { // return p.runServer()
e.GET(p.MetricsPath, gin.BasicAuth(accounts), prometheusHandler()) // } else {
return nil // e.GET(p.MetricsPath, gin.BasicAuth(accounts), prometheusHandler())
} // return nil
// }
} //
//}
func (p *Prometheus) runServer() error { //
return p.router.Run(p.listenAddress) //func (p *Prometheus) runServer() error {
} // return p.router.Run(p.listenAddress)
//}
func (p *Prometheus) getMetrics() []byte { //
response, err := http.Get(p.Ppg.MetricsURL) //func (p *Prometheus) getMetrics() []byte {
if err != nil { // response, err := http.Get(p.Ppg.MetricsURL)
return nil // if err != nil {
} // return nil
// }
defer response.Body.Close() //
// defer response.Body.Close()
body, _ := io.ReadAll(response.Body) //
return body // body, _ := io.ReadAll(response.Body)
} // return body
//}
var hostname, _ = os.Hostname() //
//var hostname, _ = os.Hostname()
func (p *Prometheus) getPushGatewayURL() string { //
if p.Ppg.Job == "" { //func (p *Prometheus) getPushGatewayURL() string {
p.Ppg.Job = "gin" // if p.Ppg.Job == "" {
} // p.Ppg.Job = "gin"
return p.Ppg.PushGatewayURL + "/metrics/job/" + p.Ppg.Job + "/instance/" + hostname // }
} // 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)) //func (p *Prometheus) sendMetricsToPushGateway(metrics []byte) {
if err != nil { // req, err := http.NewRequest("POST", p.getPushGatewayURL(), bytes.NewBuffer(metrics))
return // if err != nil {
} // return
// }
client := &http.Client{} //
resp, err := client.Do(req) // client := &http.Client{}
if err != nil { // resp, err := client.Do(req)
fmt.Println("Error sending to push gateway error:", err.Error()) // if err != nil {
} // fmt.Println("Error sending to push gateway error:", err.Error())
// }
resp.Body.Close() //
} // resp.Body.Close()
//}
func (p *Prometheus) startPushTicker() { //
ticker := time.NewTicker(time.Second * p.Ppg.PushIntervalSeconds) //func (p *Prometheus) startPushTicker() {
go func() { // ticker := time.NewTicker(time.Second * p.Ppg.PushIntervalSeconds)
for range ticker.C { // go func() {
p.sendMetricsToPushGateway(p.getMetrics()) // for range ticker.C {
} // p.sendMetricsToPushGateway(p.getMetrics())
}() // }
} // }()
//}
// NewMetric associates prometheus.Collector based on Metric.Type. //
func NewMetric(m *Metric, subsystem string) prometheus.Collector { //// NewMetric associates prometheus.Collector based on Metric.Type.
var metric prometheus.Collector //func NewMetric(m *Metric, subsystem string) prometheus.Collector {
switch m.Type { // var metric prometheus.Collector
case "counter_vec": // switch m.Type {
metric = prometheus.NewCounterVec( // case "counter_vec":
prometheus.CounterOpts{ // metric = prometheus.NewCounterVec(
Subsystem: subsystem, // prometheus.CounterOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
m.Args, // },
) // m.Args,
case "counter": // )
metric = prometheus.NewCounter( // case "counter":
prometheus.CounterOpts{ // metric = prometheus.NewCounter(
Subsystem: subsystem, // prometheus.CounterOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
) // },
case "gauge_vec": // )
metric = prometheus.NewGaugeVec( // case "gauge_vec":
prometheus.GaugeOpts{ // metric = prometheus.NewGaugeVec(
Subsystem: subsystem, // prometheus.GaugeOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
m.Args, // },
) // m.Args,
case "gauge": // )
metric = prometheus.NewGauge( // case "gauge":
prometheus.GaugeOpts{ // metric = prometheus.NewGauge(
Subsystem: subsystem, // prometheus.GaugeOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
) // },
case "histogram_vec": // )
metric = prometheus.NewHistogramVec( // case "histogram_vec":
prometheus.HistogramOpts{ // metric = prometheus.NewHistogramVec(
Subsystem: subsystem, // prometheus.HistogramOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
m.Args, // },
) // m.Args,
case "histogram": // )
metric = prometheus.NewHistogram( // case "histogram":
prometheus.HistogramOpts{ // metric = prometheus.NewHistogram(
Subsystem: subsystem, // prometheus.HistogramOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
) // },
case "summary_vec": // )
metric = prometheus.NewSummaryVec( // case "summary_vec":
prometheus.SummaryOpts{ // metric = prometheus.NewSummaryVec(
Subsystem: subsystem, // prometheus.SummaryOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
m.Args, // },
) // m.Args,
case "summary": // )
metric = prometheus.NewSummary( // case "summary":
prometheus.SummaryOpts{ // metric = prometheus.NewSummary(
Subsystem: subsystem, // prometheus.SummaryOpts{
Name: m.Name, // Subsystem: subsystem,
Help: m.Description, // Name: m.Name,
}, // Help: m.Description,
) // },
} // )
return metric // }
} // return metric
//}
func (p *Prometheus) registerMetrics(subsystem string) { //
for _, metricDef := range p.MetricsList { //func (p *Prometheus) registerMetrics(subsystem string) {
metric := NewMetric(metricDef, subsystem) // for _, metricDef := range p.MetricsList {
if err := prometheus.Register(metric); err != nil { // metric := NewMetric(metricDef, subsystem)
fmt.Println("could not be registered in Prometheus,metricDef.Name:", metricDef.Name, " error:", err.Error()) // 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 reqCounter: // switch metricDef {
p.reqCnt = metric.(*prometheus.CounterVec) // case reqCounter:
case reqDuration: // p.reqCnt = metric.(*prometheus.CounterVec)
p.reqDur = metric.(*prometheus.HistogramVec) // case reqDuration:
case resSize: // p.reqDur = metric.(*prometheus.HistogramVec)
p.resSz = metric.(prometheus.Summary) // case resSize:
case reqSize: // p.resSz = metric.(prometheus.Summary)
p.reqSz = metric.(prometheus.Summary) // case reqSize:
} // p.reqSz = metric.(prometheus.Summary)
metricDef.MetricCollector = metric // }
} // metricDef.MetricCollector = metric
} // }
//}
// Use adds the middleware to a gin engine. //
func (p *Prometheus) Use(e *gin.Engine) error { //// Use adds the middleware to a gin engine.
e.Use(p.HandlerFunc()) //func (p *Prometheus) Use(e *gin.Engine) error {
return p.SetMetricsPath(e) // e.Use(p.HandlerFunc())
} // return p.SetMetricsPath(e)
//}
// UseWithAuth adds the middleware to a gin engine with BasicAuth. //
func (p *Prometheus) UseWithAuth(e *gin.Engine, accounts gin.Accounts) error { //// UseWithAuth adds the middleware to a gin engine with BasicAuth.
e.Use(p.HandlerFunc()) //func (p *Prometheus) UseWithAuth(e *gin.Engine, accounts gin.Accounts) error {
return p.SetMetricsPathWithAuth(e, accounts) // e.Use(p.HandlerFunc())
} // return p.SetMetricsPathWithAuth(e, accounts)
//}
// HandlerFunc defines handler function for middleware. //
func (p *Prometheus) HandlerFunc() gin.HandlerFunc { //// HandlerFunc defines handler function for middleware.
return func(c *gin.Context) { //func (p *Prometheus) HandlerFunc() gin.HandlerFunc {
if c.Request.URL.Path == p.MetricsPath { // return func(c *gin.Context) {
c.Next() // if c.Request.URL.Path == p.MetricsPath {
return // c.Next()
} // return
// }
start := time.Now() //
reqSz := computeApproximateRequestSize(c.Request) // start := time.Now()
// reqSz := computeApproximateRequestSize(c.Request)
c.Next() //
// c.Next()
status := strconv.Itoa(c.Writer.Status()) //
elapsed := float64(time.Since(start)) / float64(time.Second) // status := strconv.Itoa(c.Writer.Status())
resSz := float64(c.Writer.Size()) // elapsed := float64(time.Since(start)) / float64(time.Second)
// resSz := float64(c.Writer.Size())
url := p.ReqCntURLLabelMappingFn(c) //
if len(p.URLLabelFromContext) > 0 { // url := p.ReqCntURLLabelMappingFn(c)
u, found := c.Get(p.URLLabelFromContext) // if len(p.URLLabelFromContext) > 0 {
if !found { // u, found := c.Get(p.URLLabelFromContext)
u = "unknown" // if !found {
} // u = "unknown"
url = u.(string) // }
} // url = u.(string)
p.reqDur.WithLabelValues(status, c.Request.Method, url).Observe(elapsed) // }
p.reqCnt.WithLabelValues(status, c.Request.Method, c.HandlerName(), c.Request.Host, url).Inc() // p.reqDur.WithLabelValues(status, c.Request.Method, url).Observe(elapsed)
p.reqSz.Observe(float64(reqSz)) // p.reqCnt.WithLabelValues(status, c.Request.Method, c.HandlerName(), c.Request.Host, url).Inc()
p.resSz.Observe(resSz) // p.reqSz.Observe(float64(reqSz))
} // p.resSz.Observe(resSz)
} // }
//}
func prometheusHandler() gin.HandlerFunc { //
h := promhttp.Handler() //func prometheusHandler() gin.HandlerFunc {
return func(c *gin.Context) { // h := promhttp.Handler()
h.ServeHTTP(c.Writer, c.Request) // return func(c *gin.Context) {
} // h.ServeHTTP(c.Writer, c.Request)
} // }
//}
func computeApproximateRequestSize(r *http.Request) int { //
var s int //func computeApproximateRequestSize(r *http.Request) int {
if r.URL != nil { // var s int
s = len(r.URL.Path) // if r.URL != nil {
} // s = len(r.URL.Path)
// }
s += len(r.Method) //
s += len(r.Proto) // s += len(r.Method)
for name, values := range r.Header { // s += len(r.Proto)
s += len(name) // for name, values := range r.Header {
for _, value := range values { // s += len(name)
s += len(value) // for _, value := range values {
} // s += len(value)
} // }
s += len(r.Host) // }
// s += len(r.Host)
// r.FormData and r.MultipartForm are assumed to be included in r.URL. //
// // r.FormData and r.MultipartForm are assumed to be included in r.URL.
if r.ContentLength != -1 { //
s += int(r.ContentLength) // if r.ContentLength != -1 {
} // s += int(r.ContentLength)
return s // }
} // return s
//}

@ -0,0 +1,48 @@
package prommetrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"strconv"
)
var (
apiCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "api_count",
Help: "Total number of API calls",
},
[]string{"path", "method", "code"},
)
httpCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "http_count",
Help: "Total number of HTTP calls",
},
[]string{"path", "method", "status"},
)
)
func ApiInit(prometheusPort int) error {
apiRegistry := prometheus.NewRegistry()
cs := append(
baseCollector,
apiCounter,
httpCounter,
)
return Init(apiRegistry, prometheusPort, commonPath, promhttp.HandlerFor(apiRegistry, promhttp.HandlerOpts{}), cs...)
}
func APICall(path string, method string, apiCode int) {
apiCounter.With(prometheus.Labels{"path": path, "method": method, "code": strconv.Itoa(apiCode)}).Inc()
}
func HttpCall(path string, method string, status int) {
httpCounter.With(prometheus.Labels{"path": path, "method": method, "status": strconv.Itoa(status)}).Inc()
}
//func ApiHandler() http.Handler {
// return promhttp.InstrumentMetricHandler(
// apiRegistry, promhttp.HandlerFor(apiRegistry, promhttp.HandlerOpts{}),
// )
//}

@ -1,30 +0,0 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prommetrics
import ginprom "github.com/openimsdk/open-im-server/v3/pkg/common/ginprometheus"
/*
labels := prometheus.Labels{"label_one": "any", "label_two": "value"}
ApiCustomCnt.MetricCollector.(*prometheus.CounterVec).With(labels).Inc().
*/
var (
ApiCustomCnt = &ginprom.Metric{
Name: "custom_total",
Description: "Custom counter events.",
Type: "counter_vec",
Args: []string{"label_one", "label_two"},
}
)

@ -0,0 +1,10 @@
package prommetrics
import "github.com/prometheus/client_golang/prometheus"
var (
UserRegisterCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "user_register_total",
Help: "The number of user login",
})
)

@ -15,44 +15,24 @@
package prommetrics package prommetrics
import ( import (
gp "github.com/grpc-ecosystem/go-grpc-prometheus" "fmt"
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"
"github.com/prometheus/client_golang/prometheus/collectors" "github.com/prometheus/client_golang/prometheus/collectors"
"net/http"
) )
func NewGrpcPromObj(cusMetrics []prometheus.Collector) (*prometheus.Registry, *gp.ServerMetrics, error) { const commonPath = "/metrics"
reg := prometheus.NewRegistry()
grpcMetrics := gp.NewServerMetrics()
grpcMetrics.EnableHandlingTimeHistogram()
cusMetrics = append(cusMetrics, grpcMetrics, collectors.NewGoCollector())
reg.MustRegister(cusMetrics...)
return reg, grpcMetrics, nil
}
func GetGrpcCusMetrics(registerName string, share *config2.Share) []prometheus.Collector { var (
switch registerName { baseCollector = []prometheus.Collector{
case share.RpcRegisterName.MessageGateway: collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
return []prometheus.Collector{OnlineUserGauge} collectors.NewGoCollector(),
case share.RpcRegisterName.Msg:
return []prometheus.Collector{SingleChatMsgProcessSuccessCounter, SingleChatMsgProcessFailedCounter, GroupChatMsgProcessSuccessCounter, GroupChatMsgProcessFailedCounter}
case "Transfer":
return []prometheus.Collector{MsgInsertRedisSuccessCounter, MsgInsertRedisFailedCounter, MsgInsertMongoSuccessCounter, MsgInsertMongoFailedCounter, SeqSetFailedCounter}
case share.RpcRegisterName.Push:
return []prometheus.Collector{MsgOfflinePushFailedCounter}
case share.RpcRegisterName.Auth:
return []prometheus.Collector{UserLoginCounter}
default:
return nil
} }
} )
func GetGinCusMetrics(name string) []*ginprometheus.Metric { func Init(registry *prometheus.Registry, prometheusPort int, path string, handler http.Handler, cs ...prometheus.Collector) error {
switch name { registry.MustRegister(cs...)
case "Api": srv := http.NewServeMux()
return []*ginprometheus.Metric{ApiCustomCnt} srv.Handle(path, handler)
default: return http.ListenAndServe(fmt.Sprintf(":%d", prometheusPort), srv)
return []*ginprometheus.Metric{ApiCustomCnt}
}
} }

@ -14,46 +14,39 @@
package prommetrics package prommetrics
import ( //func TestNewGrpcPromObj(t *testing.T) {
"testing" // // Create a custom metric to pass into the NewGrpcPromObj function.
// customMetric := prometheus.NewCounter(prometheus.CounterOpts{
"github.com/prometheus/client_golang/prometheus" // Name: "test_metric",
"github.com/stretchr/testify/assert" // Help: "This is a test metric.",
) // })
// cusMetrics := []prometheus.Collector{customMetric}
func TestNewGrpcPromObj(t *testing.T) { //
// Create a custom metric to pass into the NewGrpcPromObj function. // // Call NewGrpcPromObj with the custom metrics.
customMetric := prometheus.NewCounter(prometheus.CounterOpts{ // reg, grpcMetrics, err := NewGrpcPromObj(cusMetrics)
Name: "test_metric", //
Help: "This is a test metric.", // // Assert no error was returned.
}) // assert.NoError(t, err)
cusMetrics := []prometheus.Collector{customMetric} //
// // Assert the registry was correctly initialized.
// Call NewGrpcPromObj with the custom metrics. // assert.NotNil(t, reg)
reg, grpcMetrics, err := NewGrpcPromObj(cusMetrics) //
// // Assert the grpcMetrics was correctly initialized.
// Assert no error was returned. // assert.NotNil(t, grpcMetrics)
assert.NoError(t, err) //
// // Assert that the custom metric is registered.
// Assert the registry was correctly initialized. // mfs, err := reg.Gather()
assert.NotNil(t, reg) // assert.NoError(t, err)
// assert.NotEmpty(t, mfs) // Ensure some metrics are present.
// Assert the grpcMetrics was correctly initialized. // found := false
assert.NotNil(t, grpcMetrics) // for _, mf := range mfs {
// if *mf.Name == "test_metric" {
// Assert that the custom metric is registered. // found = true
mfs, err := reg.Gather() // break
assert.NoError(t, err) // }
assert.NotEmpty(t, mfs) // Ensure some metrics are present. // }
found := false // assert.True(t, found, "Custom metric not found in registry")
for _, mf := range mfs { //}
if *mf.Name == "test_metric" {
found = true
break
}
}
assert.True(t, found, "Custom metric not found in registry")
}
//func TestGetGrpcCusMetrics(t *testing.T) { //func TestGetGrpcCusMetrics(t *testing.T) {
// conf := config2.NewGlobalConfig() // conf := config2.NewGlobalConfig()

@ -0,0 +1,60 @@
package prommetrics
import (
gp "github.com/grpc-ecosystem/go-grpc-prometheus"
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"strconv"
)
const rpcPath = commonPath
var (
grpcMetrics *gp.ServerMetrics
rpcCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "rpc_count",
Help: "Total number of RPC calls",
},
[]string{"name", "path", "code"},
)
)
func RpcInit(cs []prometheus.Collector, prometheusPort int) error {
reg := prometheus.NewRegistry()
cs = append(append(
baseCollector,
rpcCounter,
), cs...)
return Init(reg, prometheusPort, rpcPath, promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}), cs...)
}
func RPCCall(name string, path string, code int) {
rpcCounter.With(prometheus.Labels{"name": name, "path": path, "code": strconv.Itoa(code)}).Inc()
}
func GetGrpcServerMetrics() *gp.ServerMetrics {
if grpcMetrics == nil {
grpcMetrics = gp.NewServerMetrics()
grpcMetrics.EnableHandlingTimeHistogram()
}
return grpcMetrics
}
func GetGrpcCusMetrics(registerName string, share *config.Share) []prometheus.Collector {
switch registerName {
case share.RpcRegisterName.MessageGateway:
return []prometheus.Collector{OnlineUserGauge}
case share.RpcRegisterName.Msg:
return []prometheus.Collector{SingleChatMsgProcessSuccessCounter, SingleChatMsgProcessFailedCounter, GroupChatMsgProcessSuccessCounter, GroupChatMsgProcessFailedCounter}
case share.RpcRegisterName.Push:
return []prometheus.Collector{MsgOfflinePushFailedCounter}
case share.RpcRegisterName.Auth:
return []prometheus.Collector{UserLoginCounter}
case share.RpcRegisterName.User:
return []prometheus.Collector{UserRegisterCounter}
default:
return nil
}
}

@ -16,6 +16,7 @@ package prommetrics
import ( import (
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
) )
var ( var (
@ -40,3 +41,16 @@ var (
Help: "The number of failed set seq", Help: "The number of failed set seq",
}) })
) )
func TransferInit(prometheusPort int) error {
reg := prometheus.NewRegistry()
cs := append(
baseCollector,
MsgInsertRedisSuccessCounter,
MsgInsertRedisFailedCounter,
MsgInsertMongoSuccessCounter,
MsgInsertMongoFailedCounter,
SeqSetFailedCounter,
)
return Init(reg, prometheusPort, commonPath, promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}), cs...)
}

@ -17,9 +17,9 @@ package startrpc
import ( import (
"context" "context"
"fmt" "fmt"
config2 "github.com/openimsdk/open-im-server/v3/pkg/common/config" "github.com/openimsdk/open-im-server/v3/pkg/common/config"
"github.com/openimsdk/tools/utils/datautil" "github.com/openimsdk/tools/utils/datautil"
"github.com/prometheus/client_golang/prometheus" "google.golang.org/grpc/status"
"net" "net"
"net/http" "net/http"
"os" "os"
@ -29,7 +29,6 @@ import (
"syscall" "syscall"
"time" "time"
grpcprometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister" kdisc "github.com/openimsdk/open-im-server/v3/pkg/common/discoveryregister"
"github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics" "github.com/openimsdk/open-im-server/v3/pkg/common/prommetrics"
"github.com/openimsdk/tools/discovery" "github.com/openimsdk/tools/discovery"
@ -38,14 +37,13 @@ import (
"github.com/openimsdk/tools/mw" "github.com/openimsdk/tools/mw"
"github.com/openimsdk/tools/system/program" "github.com/openimsdk/tools/system/program"
"github.com/openimsdk/tools/utils/network" "github.com/openimsdk/tools/utils/network"
"github.com/prometheus/client_golang/prometheus/promhttp"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/credentials/insecure"
) )
// Start rpc server. // Start rpc server.
func Start[T any](ctx context.Context, discovery *config2.Discovery, prometheusConfig *config2.Prometheus, listenIP, func Start[T any](ctx context.Context, discovery *config.Discovery, prometheusConfig *config.Prometheus, listenIP,
registerIP string, rpcPorts []int, index int, rpcRegisterName string, share *config2.Share, config T, rpcFn func(ctx context.Context, registerIP string, rpcPorts []int, index int, rpcRegisterName string, share *config.Share, config T, rpcFn func(ctx context.Context,
config T, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error, options ...grpc.ServerOption) error { config T, client discovery.SvcDiscoveryRegistry, server *grpc.Server) error, options ...grpc.ServerOption) error {
rpcPort, err := datautil.GetElemByIndex(rpcPorts, index) rpcPort, err := datautil.GetElemByIndex(rpcPorts, index)
@ -77,13 +75,18 @@ func Start[T any](ctx context.Context, discovery *config2.Discovery, prometheusC
return err return err
} }
var reg *prometheus.Registry //var reg *prometheus.Registry
var metric *grpcprometheus.ServerMetrics //var metric *grpcprometheus.ServerMetrics
if prometheusConfig.Enable { if prometheusConfig.Enable {
cusMetrics := prommetrics.GetGrpcCusMetrics(rpcRegisterName, share) //cusMetrics := prommetrics.GetGrpcCusMetrics(rpcRegisterName, share)
reg, metric, _ = prommetrics.NewGrpcPromObj(cusMetrics) //reg, metric, _ = prommetrics.NewGrpcPromObj(cusMetrics)
options = append(options, mw.GrpcServer(), grpc.StreamInterceptor(metric.StreamServerInterceptor()), //options = append(options, mw.GrpcServer(), grpc.StreamInterceptor(metric.StreamServerInterceptor()),
grpc.UnaryInterceptor(metric.UnaryServerInterceptor())) // grpc.UnaryInterceptor(metric.UnaryServerInterceptor()))
options = append(
options, mw.GrpcServer(),
prommetricsUnaryInterceptor(rpcRegisterName),
prommetricsStreamInterceptor(rpcRegisterName),
)
} else { } else {
options = append(options, mw.GrpcServer()) options = append(options, mw.GrpcServer())
} }
@ -122,13 +125,18 @@ func Start[T any](ctx context.Context, discovery *config2.Discovery, prometheusC
netDone <- struct{}{} netDone <- struct{}{}
return return
} }
metric.InitializeMetrics(srv) cs := prommetrics.GetGrpcCusMetrics(rpcRegisterName, share)
// Create a HTTP server for prometheus. if err := prommetrics.RpcInit(cs, prometheusPort); err != nil && err != http.ErrServerClosed {
httpServer = &http.Server{Handler: promhttp.HandlerFor(reg, promhttp.HandlerOpts{}), Addr: fmt.Sprintf("0.0.0.0:%d", prometheusPort)} netErr = errs.WrapMsg(err, fmt.Sprintf("rpc %s prometheus start err: %d", rpcRegisterName, prometheusPort))
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
netErr = errs.WrapMsg(err, "prometheus start err", httpServer.Addr)
netDone <- struct{}{} netDone <- struct{}{}
} }
//metric.InitializeMetrics(srv)
// Create a HTTP server for prometheus.
//httpServer = &http.Server{Handler: promhttp.HandlerFor(reg, promhttp.HandlerOpts{}), Addr: fmt.Sprintf("0.0.0.0:%d", prometheusPort)}
//if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
// netErr = errs.WrapMsg(err, "prometheus start err", httpServer.Addr)
// netDone <- struct{}{}
//}
}() }()
} }
@ -158,7 +166,6 @@ func Start[T any](ctx context.Context, discovery *config2.Discovery, prometheusC
} }
return nil return nil
case <-netDone: case <-netDone:
close(netDone)
return netErr return netErr
} }
} }
@ -176,3 +183,25 @@ func gracefulStopWithCtx(ctx context.Context, f func()) error {
return nil return nil
} }
} }
func prommetricsUnaryInterceptor(rpcRegisterName string) grpc.ServerOption {
getCode := func(err error) int {
if err == nil {
return 0
}
rpcErr, ok := err.(interface{ GRPCStatus() *status.Status })
if !ok {
return -1
}
return int(rpcErr.GRPCStatus().Code())
}
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
resp, err := handler(ctx, req)
prommetrics.RPCCall(rpcRegisterName, info.FullMethod, getCode(err))
return resp, err
})
}
func prommetricsStreamInterceptor(rpcRegisterName string) grpc.ServerOption {
return grpc.ChainStreamInterceptor()
}

@ -23,6 +23,7 @@ const (
SuperGroupRecvMsgNotNotifyUserIDsKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS:" SuperGroupRecvMsgNotNotifyUserIDsKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS:"
SuperGroupRecvMsgNotNotifyUserIDsHashKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS_HASH:" SuperGroupRecvMsgNotNotifyUserIDsHashKey = "SUPER_GROUP_RECV_MSG_NOT_NOTIFY_USER_IDS_HASH:"
ConversationNotReceiveMessageUserIDsKey = "CONVERSATION_NOT_RECEIVE_MESSAGE_USER_IDS:" ConversationNotReceiveMessageUserIDsKey = "CONVERSATION_NOT_RECEIVE_MESSAGE_USER_IDS:"
ConversationUserMaxKey = "CONVERSATION_USER_MAX:"
) )
func GetConversationKey(ownerUserID, conversationID string) string { func GetConversationKey(ownerUserID, conversationID string) string {
@ -56,3 +57,7 @@ func GetConversationNotReceiveMessageUserIDsKey(conversationID string) string {
func GetUserConversationIDsHashKey(ownerUserID string) string { func GetUserConversationIDsHashKey(ownerUserID string) string {
return ConversationIDsHashKey + ownerUserID return ConversationIDsHashKey + ownerUserID
} }
func GetConversationUserMaxVersionKey(userID string) string {
return ConversationUserMaxKey + userID
}

@ -19,6 +19,8 @@ const (
TwoWayFriendsIDsKey = "COMMON_FRIENDS_IDS:" TwoWayFriendsIDsKey = "COMMON_FRIENDS_IDS:"
FriendKey = "FRIEND_INFO:" FriendKey = "FRIEND_INFO:"
IsFriendKey = "IS_FRIEND:" // local cache key IsFriendKey = "IS_FRIEND:" // local cache key
//FriendSyncSortUserIDsKey = "FRIEND_SYNC_SORT_USER_IDS:"
FriendMaxVersionKey = "FRIEND_MAX_VERSION:"
) )
func GetFriendIDsKey(ownerUserID string) string { func GetFriendIDsKey(ownerUserID string) string {
@ -33,6 +35,14 @@ func GetFriendKey(ownerUserID, friendUserID string) string {
return FriendKey + ownerUserID + "-" + friendUserID return FriendKey + ownerUserID + "-" + friendUserID
} }
func GetFriendMaxVersionKey(ownerUserID string) string {
return FriendMaxVersionKey + ownerUserID
}
func GetIsFriendKey(possibleFriendUserID, userID string) string { func GetIsFriendKey(possibleFriendUserID, userID string) string {
return IsFriendKey + possibleFriendUserID + "-" + userID return IsFriendKey + possibleFriendUserID + "-" + userID
} }
//func GetFriendSyncSortUserIDsKey(ownerUserID string, count int) string {
// return FriendSyncSortUserIDsKey + strconv.Itoa(count) + ":" + ownerUserID
//}

@ -28,6 +28,8 @@ const (
JoinedGroupsKey = "JOIN_GROUPS_KEY:" JoinedGroupsKey = "JOIN_GROUPS_KEY:"
GroupMemberNumKey = "GROUP_MEMBER_NUM_CACHE:" GroupMemberNumKey = "GROUP_MEMBER_NUM_CACHE:"
GroupRoleLevelMemberIDsKey = "GROUP_ROLE_LEVEL_MEMBER_IDS:" GroupRoleLevelMemberIDsKey = "GROUP_ROLE_LEVEL_MEMBER_IDS:"
GroupMemberMaxVersionKey = "GROUP_MEMBER_MAX_VERSION:"
GroupJoinMaxVersionKey = "GROUP_JOIN_MAX_VERSION:"
) )
func GetGroupInfoKey(groupID string) string { func GetGroupInfoKey(groupID string) string {
@ -57,3 +59,11 @@ func GetGroupMemberNumKey(groupID string) string {
func GetGroupRoleLevelMemberIDsKey(groupID string, roleLevel int32) string { func GetGroupRoleLevelMemberIDsKey(groupID string, roleLevel int32) string {
return GroupRoleLevelMemberIDsKey + groupID + "-" + strconv.Itoa(int(roleLevel)) return GroupRoleLevelMemberIDsKey + groupID + "-" + strconv.Itoa(int(roleLevel))
} }
func GetGroupMemberMaxVersionKey(groupID string) string {
return GroupMemberMaxVersionKey + groupID
}
func GetJoinGroupMaxVersionKey(userID string) string {
return GroupJoinMaxVersionKey + userID
}

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

Loading…
Cancel
Save