mirror of
https://github.com/openimsdk/open-im-server.git
synced 2025-12-03 02:42:19 +08:00
Merge remote-tracking branch 'upstream/main' into main-new
This commit is contained in:
commit
682e78a8a7
9
.github/workflows/openimci.yml
vendored
9
.github/workflows/openimci.yml
vendored
@ -201,12 +201,17 @@ jobs:
|
||||
- name: Build, Start, Check Services and Print Logs for Ubuntu
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo make init && \
|
||||
sudo make build && \
|
||||
sudo make start && \
|
||||
sudo make check || \
|
||||
(echo "An error occurred, printing logs:" && sudo cat ./_output/logs/* 2>/dev/null)
|
||||
|
||||
- name: Restart Services and Print Logs for Ubuntu
|
||||
if: runner.os == 'Linux'
|
||||
run: |
|
||||
sudo make restart
|
||||
sudo make check
|
||||
|
||||
# - name: Build, Start, Check Services and Print Logs for macOS
|
||||
# if: runner.os == 'macOS'
|
||||
# run: |
|
||||
@ -239,4 +244,4 @@ jobs:
|
||||
- name: Test Docker Build
|
||||
run: |
|
||||
sudo make init
|
||||
sudo make image
|
||||
sudo make image
|
||||
|
||||
2
Makefile
2
Makefile
@ -95,7 +95,7 @@ stop:
|
||||
|
||||
## restart: Restart openim (make init configuration file is initialized) ✨
|
||||
.PHONY: restart
|
||||
restart: clean stop build init start check
|
||||
restart: clean stop build start check
|
||||
|
||||
## multiarch: Build binaries for multiple platforms. See option PLATFORMS. ✨
|
||||
.PHONY: multiarch
|
||||
|
||||
@ -97,6 +97,10 @@ It's crafted in Golang and supports cross-platform deployment, ensuring a cohere
|
||||
|
||||
## :rocket: Quick Start
|
||||
|
||||
We support many platforms. Here are the addresses for quick experience on the web side:
|
||||
|
||||
👉 **[OpenIM online web demo](https://web-enterprise.rentsoft.cn/)**
|
||||
|
||||
You can quickly learn OpenIM engineering solutions, all it takes is one simple command:
|
||||
|
||||
```bash
|
||||
|
||||
@ -312,6 +312,14 @@ callback:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
beforeUpdateUserInfoEx:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
afterUpdateUserInfoEx:
|
||||
enable: false
|
||||
timeout: 5
|
||||
failedContinue: true
|
||||
afterSendSingleMsg:
|
||||
enable: false
|
||||
timeout: 5
|
||||
@ -498,7 +506,7 @@ callback:
|
||||
# The number of ports needs to be consistent with msg_transfer_service_num in script/path_info.sh
|
||||
prometheus:
|
||||
enable: false
|
||||
prometheusUrl: 172.28.0.1:13000
|
||||
grafanaUrl: 172.28.0.1:13000
|
||||
apiPrometheusPort: [20100]
|
||||
userPrometheusPort: [ 20110 ]
|
||||
friendPrometheusPort: [ 20120 ]
|
||||
|
||||
@ -1,42 +1,23 @@
|
||||
# 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.
|
||||
# -----------------------------------------------------------------------------
|
||||
# General Configuration
|
||||
# This section contains general configuration options for the entire environment.
|
||||
# These options can be set via environment variables. If both environment variables
|
||||
# and settings in this .env file exist, the environment variables take precedence.
|
||||
# -----------------------------------------------------------------------------
|
||||
# ==========================
|
||||
# General Configuration
|
||||
# ==========================
|
||||
# These settings apply to the overall environment.
|
||||
|
||||
# ======================================
|
||||
# ========= Basic Configuration ========
|
||||
# ======================================
|
||||
|
||||
# The user for authentication or system operations.
|
||||
# Default: OPENIM_USER=root
|
||||
USER=${OPENIM_USER}
|
||||
|
||||
# Password associated with the specified user for authentication.
|
||||
# Default: PASSWORD=openIM123
|
||||
PASSWORD=${PASSWORD}
|
||||
|
||||
# Base URL for the application programming interface (API).
|
||||
# Default: API_URL=http://172.28.0.1:10002
|
||||
API_URL=${API_URL}
|
||||
|
||||
# Directory path for storing data files or related information.
|
||||
# Default: DATA_DIR=./
|
||||
# Data storage directory for persistent data.
|
||||
# Example: DATA_DIR=/path/to/data
|
||||
DATA_DIR=${DATA_DIR}
|
||||
|
||||
# Choose the appropriate image address, the default is GITHUB image,
|
||||
# you can choose docker hub, for Chinese users can choose Ali Cloud
|
||||
# export IMAGE_REGISTRY="ghcr.io/openimsdk"
|
||||
# export IMAGE_REGISTRY="openim"
|
||||
# export IMAGE_REGISTRY="registry.cn-hangzhou.aliyuncs.com/openimsdk"
|
||||
# Docker image registry. Uncomment the preferred one.
|
||||
# Options: ghcr.io/openimsdk, openim, registry.cn-hangzhou.aliyuncs.com/openimsdk
|
||||
# IMAGE_REGISTRY="ghcr.io/openimsdk"
|
||||
# IMAGE_REGISTRY="openim"
|
||||
# IMAGE_REGISTRY="registry.cn-hangzhou.aliyuncs.com/openimsdk"
|
||||
IMAGE_REGISTRY=${IMAGE_REGISTRY}
|
||||
|
||||
# ======================================
|
||||
@ -47,10 +28,9 @@ IMAGE_REGISTRY=${IMAGE_REGISTRY}
|
||||
# Default: DOCKER_BRIDGE_SUBNET=172.28.0.0/16
|
||||
DOCKER_BRIDGE_SUBNET=${DOCKER_BRIDGE_SUBNET}
|
||||
|
||||
# Gateway for the Docker network.
|
||||
# Default: DOCKER_BRIDGE_GATEWAY=172.28.0.1
|
||||
# Set and specify the IP addresses of some containers. Generally speaking,
|
||||
# you do not need to modify these configurations to facilitate debugging
|
||||
DOCKER_BRIDGE_GATEWAY=${DOCKER_BRIDGE_GATEWAY}
|
||||
|
||||
MONGO_NETWORK_ADDRESS=${MONGO_NETWORK_ADDRESS}
|
||||
REDIS_NETWORK_ADDRESS=${REDIS_NETWORK_ADDRESS}
|
||||
KAFKA_NETWORK_ADDRESS=${KAFKA_NETWORK_ADDRESS}
|
||||
@ -65,25 +45,45 @@ NODE_EXPORTER_NETWORK_ADDRESS=${NODE_EXPORTER_NETWORK_ADDRESS}
|
||||
OPENIM_ADMIN_FRONT_NETWORK_ADDRESS=${OPENIM_ADMIN_FRONT_NETWORK_ADDRESS}
|
||||
ALERT_MANAGER_NETWORK_ADDRESS=${ALERT_MANAGER_NETWORK_ADDRESS}
|
||||
|
||||
# ===============================================
|
||||
# = Component Extension Configuration =
|
||||
# ===============================================
|
||||
# ==============================================================================
|
||||
# Configuration Update Instructions
|
||||
# ==============================================================================
|
||||
# This header outlines the methods to update common variables in config.yaml and .env files.
|
||||
# These instructions are vital for maintaining the OpenIM environment's configuration.
|
||||
#
|
||||
# METHOD 1: Regenerate All Configurations
|
||||
# ----------------------------------------
|
||||
# Use this method to regenerate all configurations.
|
||||
# Steps:
|
||||
# 1. Delete existing config files:
|
||||
# - openim-server/config/config.yaml
|
||||
# - openim-chat/config/config.yaml
|
||||
# 2. Modify the .env file as required.
|
||||
# 3. Run 'docker compose up -d'. This will regenerate:
|
||||
# - config/config.yaml
|
||||
#
|
||||
# METHOD 2: Modify Individual Configuration Files
|
||||
# -----------------------------------------------
|
||||
# Use this method to update specific configuration files.
|
||||
# Steps:
|
||||
# 1. Modify the .env file as necessary.
|
||||
# 2. Update the corresponding entries in:
|
||||
# - config/config.yaml
|
||||
# 3. Restart the services with 'docker compose up -d'.
|
||||
# 4. Special Note: If you modify OPENIM_IP, API_OPENIM_PORT, or MINIO_PORT in .env,
|
||||
# ensure to update the corresponding services and configurations accordingly.
|
||||
#
|
||||
# It is essential to follow these methods to ensure consistent and correct application behavior.
|
||||
# ==============================================================================
|
||||
# Local IP address of the service. Modify if necessary.
|
||||
# Example: OPENIM_IP=172.28.0.1,
|
||||
OPENIM_IP=${OPENIM_IP}
|
||||
|
||||
# ============ Component Extension Configuration ==========
|
||||
# ----- ZooKeeper Configuration -----
|
||||
# Address or hostname for the ZooKeeper service.
|
||||
# Default: ZOOKEEPER_ADDRESS=172.28.0.1
|
||||
ZOOKEEPER_ADDRESS=${ZOOKEEPER_NETWORK_ADDRESS}
|
||||
|
||||
# Port for ZooKeeper service.
|
||||
# Default: ZOOKEEPER_PORT=12181
|
||||
ZOOKEEPER_PORT=${ZOOKEEPER_PORT}
|
||||
|
||||
# ----- MongoDB Configuration -----
|
||||
# Address or hostname for the MongoDB service.
|
||||
# Default: MONGO_ADDRESS=172.28.0.1
|
||||
MONGO_ADDRESS=${MONGO_NETWORK_ADDRESS}
|
||||
|
||||
# Port on which MongoDB service is running.
|
||||
# Default: MONGO_PORT=37017
|
||||
# MONGO_PORT=${MONGO_PORT}
|
||||
@ -101,9 +101,6 @@ MONGO_PASSWORD=${MONGO_PASSWORD}
|
||||
MONGO_DATABASE=${MONGO_DATABASE}
|
||||
|
||||
# ----- Redis Configuration -----
|
||||
# Address or hostname for the Redis service.
|
||||
# Default: REDIS_ADDRESS=172.28.0.1
|
||||
REDIS_ADDRESS=${REDIS_NETWORK_ADDRESS}
|
||||
|
||||
# Port on which Redis in-memory data structure store is running.
|
||||
# Default: REDIS_PORT=16379
|
||||
@ -113,11 +110,6 @@ REDIS_PORT=${REDIS_PORT}
|
||||
# Default: REDIS_PASSWORD=openIM123
|
||||
REDIS_PASSWORD=${REDIS_PASSWORD}
|
||||
|
||||
# ----- Kafka Configuration -----
|
||||
# Address or hostname for the Kafka service.
|
||||
# Default: KAFKA_ADDRESS=172.28.0.1
|
||||
KAFKA_ADDRESS=${KAFKA_NETWORK_ADDRESS}
|
||||
|
||||
# Kakfa username to authenticate with the Kafka service.
|
||||
# KAFKA_USERNAME=${KAFKA_USERNAME}
|
||||
|
||||
@ -129,20 +121,13 @@ KAFKA_PORT=${KAFKA_PORT}
|
||||
# Default: KAFKA_LATESTMSG_REDIS_TOPIC=latestMsgToRedis
|
||||
KAFKA_LATESTMSG_REDIS_TOPIC=${KAFKA_LATESTMSG_REDIS_TOPIC}
|
||||
|
||||
# Topic in Kafka for pushing messages (e.g. notifications or updates).
|
||||
# Default: KAFKA_MSG_PUSH_TOPIC=msgToPush
|
||||
KAFKA_MSG_PUSH_TOPIC=${KAFKA_MSG_PUSH_TOPIC}
|
||||
|
||||
# Topic in Kafka for storing offline messages in MongoDB.
|
||||
# Default: KAFKA_OFFLINEMSG_MONGO_TOPIC=offlineMsgToMongoMysql
|
||||
KAFKA_OFFLINEMSG_MONGO_TOPIC=${KAFKA_OFFLINEMSG_MONGO_TOPIC}
|
||||
|
||||
# ----- MinIO Configuration ----
|
||||
# Address or hostname for the MinIO object storage service.
|
||||
# Default: MINIO_ADDRESS=172.28.0.1
|
||||
MINIO_ADDRESS=${MINIO_NETWORK_ADDRESS}
|
||||
|
||||
# Port on which MinIO object storage service is running.
|
||||
# MINIO_PORT
|
||||
# ----------
|
||||
# MINIO_PORT sets the port for the MinIO object storage service.
|
||||
# Upon changing this port, the MinIO endpoint URLs in the `config/config.yaml` file must be updated
|
||||
# to reflect this change. The endpoints include both the 'endpoint' and 'signEndpoint'
|
||||
# under the MinIO configuration.
|
||||
#
|
||||
# Default: MINIO_PORT=10005
|
||||
MINIO_PORT=${MINIO_PORT}
|
||||
|
||||
@ -155,19 +140,11 @@ MINIO_PORT=${MINIO_PORT}
|
||||
MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
|
||||
|
||||
# ----- Prometheus Configuration -----
|
||||
# Address or hostname for the Prometheus service.
|
||||
# Default: PROMETHEUS_ADDRESS=172.28.0.1
|
||||
PROMETHEUS_ADDRESS=${PROMETHEUS_NETWORK_ADDRESS}
|
||||
|
||||
# Port on which Prometheus service is running.
|
||||
# Default: PROMETHEUS_PORT=19090
|
||||
PROMETHEUS_PORT=${PROMETHEUS_PORT}
|
||||
|
||||
# ----- Grafana Configuration -----
|
||||
# Address or hostname for the Grafana service.
|
||||
# Default: GRAFANA_ADDRESS=172.28.0.1
|
||||
GRAFANA_ADDRESS=${GRAFANA_NETWORK_ADDRESS}
|
||||
|
||||
# Port on which Grafana service is running.
|
||||
# Default: GRAFANA_PORT=13000
|
||||
GRAFANA_PORT=${GRAFANA_PORT}
|
||||
@ -184,23 +161,19 @@ OPENIM_WEB_DIST_PATH=${OPENIM_WEB_DIST_PATH}
|
||||
# Default: OPENIM_WEB_PORT=11001
|
||||
OPENIM_WEB_PORT=${OPENIM_WEB_PORT}
|
||||
|
||||
# Address or hostname for the OpenIM web service.
|
||||
# Default: OPENIM_WEB_ADDRESS=172.28.0.1
|
||||
OPENIM_WEB_ADDRESS=${OPENIM_WEB_NETWORK_ADDRESS}
|
||||
|
||||
# ======================================
|
||||
# ========= OpenIM Server ==============
|
||||
# ======================================
|
||||
|
||||
# Address or hostname for the OpenIM server.
|
||||
# Default: OPENIM_SERVER_ADDRESS=172.28.0.1
|
||||
OPENIM_SERVER_ADDRESS=${OPENIM_SERVER_NETWORK_ADDRESS}
|
||||
|
||||
# Port for the OpenIM WebSockets.
|
||||
# Default: OPENIM_WS_PORT=10001
|
||||
OPENIM_WS_PORT=${OPENIM_WS_PORT}
|
||||
|
||||
# Port for the OpenIM API.
|
||||
# API_OPENIM_PORT
|
||||
# ---------------
|
||||
# This variable defines the port on which the OpenIM API service will listen.
|
||||
# When changing this port, it's essential to update the apiURL in the config.yaml file
|
||||
# to ensure the API service is accessible at the new port.
|
||||
#
|
||||
# Default: API_OPENIM_PORT=10002
|
||||
API_OPENIM_PORT=${API_OPENIM_PORT}
|
||||
|
||||
@ -213,10 +186,6 @@ API_OPENIM_PORT=${API_OPENIM_PORT}
|
||||
# Default: CHAT_IMAGE_VERSION=main
|
||||
CHAT_IMAGE_VERSION=${CHAT_IMAGE_VERSION}
|
||||
|
||||
# Address or hostname for the OpenIM chat service.
|
||||
# Default: OPENIM_CHAT_ADDRESS=172.28.0.1
|
||||
OPENIM_CHAT_ADDRESS=${OPENIM_CHAT_NETWORK_ADDRESS}
|
||||
|
||||
# Port for the OpenIM chat API.
|
||||
# Default: OPENIM_CHAT_API_PORT=10008
|
||||
OPENIM_CHAT_API_PORT=${OPENIM_CHAT_API_PORT}
|
||||
@ -225,7 +194,6 @@ OPENIM_CHAT_API_PORT=${OPENIM_CHAT_API_PORT}
|
||||
# Default: OPENIM_CHAT_DATA_DIR=./openim-chat/main
|
||||
OPENIM_CHAT_DATA_DIR=${OPENIM_CHAT_DATA_DIR}
|
||||
|
||||
|
||||
# ======================================
|
||||
# ========== OpenIM Admin ==============
|
||||
# ======================================
|
||||
|
||||
@ -247,6 +247,14 @@ manager:
|
||||
userID: [ "${MANAGER_USERID_1}", "${MANAGER_USERID_2}", "${MANAGER_USERID_3}" ]
|
||||
nickname: [ "${NICKNAME_1}", "${NICKNAME_2}", "${NICKNAME_3}" ]
|
||||
|
||||
# chatAdmin, use for send notification
|
||||
#
|
||||
# Built-in app system notification account ID
|
||||
# Built-in app system notification account nickname
|
||||
im-admin:
|
||||
userID: [ "${IM_ADMIN_USERID}" ]
|
||||
nickname: [ "${IM_ADMIN_NAME}" ]
|
||||
|
||||
# Multi-platform login policy
|
||||
# For each platform(Android, iOS, Windows, Mac, web), only one can be online at a time
|
||||
multiLoginPolicy: ${MULTILOGIN_POLICY}
|
||||
@ -498,7 +506,7 @@ callback:
|
||||
# The number of ports needs to be consistent with msg_transfer_service_num in script/path_info.sh
|
||||
prometheus:
|
||||
enable: ${PROMETHEUS_ENABLE}
|
||||
prometheusUrl: ${PROMETHEUS_URL}
|
||||
grafanaUrl: ${GRAFANA_URL}
|
||||
apiPrometheusPort: [${API_PROM_PORT}]
|
||||
userPrometheusPort: [ ${USER_PROM_PORT} ]
|
||||
friendPrometheusPort: [ ${FRIEND_PROM_PORT} ]
|
||||
|
||||
@ -44,12 +44,12 @@ scrape_configs:
|
||||
# prometheus fetches application services
|
||||
- job_name: 'openimserver-openim-api'
|
||||
static_configs:
|
||||
- targets: [ '${OPENIM_SERVER_ADDRESS}:${API_PROM_PORT}' ]
|
||||
- targets: [ '${DOCKER_BRIDGE_GATEWAY}:${API_PROM_PORT}' ]
|
||||
labels:
|
||||
namespace: 'default'
|
||||
- job_name: 'openimserver-openim-msggateway'
|
||||
static_configs:
|
||||
- targets: [ '${OPENIM_SERVER_ADDRESS}:${MSG_GATEWAY_PROM_PORT}' ]
|
||||
- targets: [ '${DOCKER_BRIDGE_GATEWAY}:${MSG_GATEWAY_PROM_PORT}' ]
|
||||
labels:
|
||||
namespace: 'default'
|
||||
- job_name: 'openimserver-openim-msgtransfer'
|
||||
@ -59,41 +59,41 @@ scrape_configs:
|
||||
namespace: 'default'
|
||||
- job_name: 'openimserver-openim-push'
|
||||
static_configs:
|
||||
- targets: [ '${OPENIM_SERVER_ADDRESS}:${PUSH_PROM_PORT}' ]
|
||||
- targets: [ '${DOCKER_BRIDGE_GATEWAY}:${PUSH_PROM_PORT}' ]
|
||||
labels:
|
||||
namespace: 'default'
|
||||
- job_name: 'openimserver-openim-rpc-auth'
|
||||
static_configs:
|
||||
- targets: [ '${OPENIM_SERVER_ADDRESS}:${AUTH_PROM_PORT}' ]
|
||||
- targets: [ '${DOCKER_BRIDGE_GATEWAY}:${AUTH_PROM_PORT}' ]
|
||||
labels:
|
||||
namespace: 'default'
|
||||
- job_name: 'openimserver-openim-rpc-conversation'
|
||||
static_configs:
|
||||
- targets: [ '${OPENIM_SERVER_ADDRESS}:${CONVERSATION_PROM_PORT}' ]
|
||||
- targets: [ '${DOCKER_BRIDGE_GATEWAY}:${CONVERSATION_PROM_PORT}' ]
|
||||
labels:
|
||||
namespace: 'default'
|
||||
- job_name: 'openimserver-openim-rpc-friend'
|
||||
static_configs:
|
||||
- targets: [ '${OPENIM_SERVER_ADDRESS}:${FRIEND_PROM_PORT}' ]
|
||||
- targets: [ '${DOCKER_BRIDGE_GATEWAY}:${FRIEND_PROM_PORT}' ]
|
||||
labels:
|
||||
namespace: 'default'
|
||||
- job_name: 'openimserver-openim-rpc-group'
|
||||
static_configs:
|
||||
- targets: [ '${OPENIM_SERVER_ADDRESS}:${GROUP_PROM_PORT}' ]
|
||||
- targets: [ '${DOCKER_BRIDGE_GATEWAY}:${GROUP_PROM_PORT}' ]
|
||||
labels:
|
||||
namespace: 'default'
|
||||
- job_name: 'openimserver-openim-rpc-msg'
|
||||
static_configs:
|
||||
- targets: [ '${OPENIM_SERVER_ADDRESS}:${MESSAGE_PROM_PORT}' ]
|
||||
- targets: [ '${DOCKER_BRIDGE_GATEWAY}:${MESSAGE_PROM_PORT}' ]
|
||||
labels:
|
||||
namespace: 'default'
|
||||
- job_name: 'openimserver-openim-rpc-third'
|
||||
static_configs:
|
||||
- targets: [ '${OPENIM_SERVER_ADDRESS}:${THIRD_PROM_PORT}' ]
|
||||
- targets: [ '${DOCKER_BRIDGE_GATEWAY}:${THIRD_PROM_PORT}' ]
|
||||
labels:
|
||||
namespace: 'default'
|
||||
- job_name: 'openimserver-openim-rpc-user'
|
||||
static_configs:
|
||||
- targets: [ '${OPENIM_SERVER_ADDRESS}:${USER_PROM_PORT}' ]
|
||||
- targets: [ '${DOCKER_BRIDGE_GATEWAY}:${USER_PROM_PORT}' ]
|
||||
labels:
|
||||
namespace: 'default'
|
||||
|
||||
@ -122,9 +122,9 @@ services:
|
||||
server:
|
||||
ipv4_address: ${OPENIM_WEB_NETWORK_ADDRESS:-172.28.0.7}
|
||||
|
||||
# Uncomment and configure the following services as needed
|
||||
## Uncomment and configure the following services as needed
|
||||
# openim-admin:
|
||||
# image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-admin-front:v3.4.0
|
||||
# image: ${IMAGE_REGISTRY:-ghcr.io/openimsdk}/openim-admin:toc-base-open-docker.35
|
||||
# container_name: openim-admin
|
||||
# restart: always
|
||||
# ports:
|
||||
@ -167,6 +167,12 @@ services:
|
||||
# hostname: 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:
|
||||
# - "${GRAFANA_PORT:-13000}:3000"
|
||||
# volumes:
|
||||
|
||||
@ -453,43 +453,45 @@ This section involves configuring the log settings, including storage location,
|
||||
|
||||
This section involves setting up additional configuration variables for Websocket, Push Notifications, and Chat.
|
||||
|
||||
| Parameter | Example Value | Description |
|
||||
|-------------------------|-------------------|------------------------------------|
|
||||
| WEBSOCKET_MAX_CONN_NUM | "100000" | Maximum Websocket connections |
|
||||
| WEBSOCKET_MAX_MSG_LEN | "4096" | Maximum Websocket message length |
|
||||
| WEBSOCKET_TIMEOUT | "10" | Websocket timeout |
|
||||
| PUSH_ENABLE | "getui" | Push notification enable status |
|
||||
| GETUI_PUSH_URL | [Generated URL] | GeTui Push Notification URL |
|
||||
| GETUI_MASTER_SECRET | [User Defined] | GeTui Master Secret |
|
||||
| GETUI_APP_KEY | [User Defined] | GeTui Application Key |
|
||||
| GETUI_INTENT | [User Defined] | GeTui Push Intent |
|
||||
| GETUI_CHANNEL_ID | [User Defined] | GeTui Channel ID |
|
||||
| GETUI_CHANNEL_NAME | [User Defined] | GeTui Channel Name |
|
||||
| FCM_SERVICE_ACCOUNT | "x.json" | FCM Service Account |
|
||||
| JPNS_APP_KEY | [User Defined] | JPNS Application Key |
|
||||
| JPNS_MASTER_SECRET | [User Defined] | JPNS Master Secret |
|
||||
| JPNS_PUSH_URL | [User Defined] | JPNS Push Notification URL |
|
||||
| JPNS_PUSH_INTENT | [User Defined] | JPNS Push Intent |
|
||||
| MANAGER_USERID_1 | "openIM123456" | Administrator ID 1 |
|
||||
| MANAGER_USERID_2 | "openIM654321" | Administrator ID 2 |
|
||||
| MANAGER_USERID_3 | "openIMAdmin" | Administrator ID 3 |
|
||||
| NICKNAME_1 | "system1" | Nickname 1 |
|
||||
| NICKNAME_2 | "system2" | Nickname 2 |
|
||||
| NICKNAME_3 | "system3" | Nickname 3 |
|
||||
| MULTILOGIN_POLICY | "1" | Multi-login Policy |
|
||||
| CHAT_PERSISTENCE_MYSQL | "true" | Chat Persistence in MySQL |
|
||||
| MSG_CACHE_TIMEOUT | "86400" | Message Cache Timeout |
|
||||
| GROUP_MSG_READ_RECEIPT | "true" | Group Message Read Receipt Enable |
|
||||
| Parameter | Example Value | Description |
|
||||
|-------------------------|-------------------|----------------------------------|
|
||||
| WEBSOCKET_MAX_CONN_NUM | "100000" | Maximum Websocket connections |
|
||||
| WEBSOCKET_MAX_MSG_LEN | "4096" | Maximum Websocket message length |
|
||||
| WEBSOCKET_TIMEOUT | "10" | Websocket timeout |
|
||||
| PUSH_ENABLE | "getui" | Push notification enable status |
|
||||
| GETUI_PUSH_URL | [Generated URL] | GeTui Push Notification URL |
|
||||
| GETUI_MASTER_SECRET | [User Defined] | GeTui Master Secret |
|
||||
| GETUI_APP_KEY | [User Defined] | GeTui Application Key |
|
||||
| GETUI_INTENT | [User Defined] | GeTui Push Intent |
|
||||
| GETUI_CHANNEL_ID | [User Defined] | GeTui Channel ID |
|
||||
| GETUI_CHANNEL_NAME | [User Defined] | GeTui Channel Name |
|
||||
| FCM_SERVICE_ACCOUNT | "x.json" | FCM Service Account |
|
||||
| JPNS_APP_KEY | [User Defined] | JPNS Application Key |
|
||||
| JPNS_MASTER_SECRET | [User Defined] | JPNS Master Secret |
|
||||
| JPNS_PUSH_URL | [User Defined] | JPNS Push Notification URL |
|
||||
| JPNS_PUSH_INTENT | [User Defined] | JPNS Push Intent |
|
||||
| MANAGER_USERID_1 | "openIM123456" | Administrator ID 1 |
|
||||
| MANAGER_USERID_2 | "openIM654321" | Administrator ID 2 |
|
||||
| MANAGER_USERID_3 | "openIMAdmin" | Administrator ID 3 |
|
||||
| NICKNAME_1 | "system1" | Nickname 1 |
|
||||
| NICKNAME_2 | "system2" | Nickname 2 |
|
||||
| NICKNAME_3 | "system3" | Nickname 3 |
|
||||
| IM_ADMIN_USERID | "imAdmin" | IM Administrator ID |
|
||||
| IM_ADMIN_NAME | "imAdmin" | IM Administrator Nickname |
|
||||
| MULTILOGIN_POLICY | "1" | Multi-login Policy |
|
||||
| CHAT_PERSISTENCE_MYSQL | "true" | Chat Persistence in MySQL |
|
||||
| MSG_CACHE_TIMEOUT | "86400" | Message Cache Timeout |
|
||||
| GROUP_MSG_READ_RECEIPT | "true" | Group Message Read Receipt Enable |
|
||||
| SINGLE_MSG_READ_RECEIPT | "true" | Single Message Read Receipt Enable |
|
||||
| RETAIN_CHAT_RECORDS | "365" | Retain Chat Records (in days) |
|
||||
| CHAT_RECORDS_CLEAR_TIME | [Cron Expression] | Chat Records Clear Time |
|
||||
| MSG_DESTRUCT_TIME | [Cron Expression] | Message Destruct Time |
|
||||
| SECRET | "${PASSWORD}" | Secret Key |
|
||||
| TOKEN_EXPIRE | "90" | Token Expiry Time |
|
||||
| FRIEND_VERIFY | "false" | Friend Verification Enable |
|
||||
| IOS_PUSH_SOUND | "xxx" | iOS |
|
||||
| CALLBACK_ENABLE | "false" | Enable callback |
|
||||
| CALLBACK_TIMEOUT | "5" | Maximum timeout for callback call |
|
||||
| RETAIN_CHAT_RECORDS | "365" | Retain Chat Records (in days) |
|
||||
| CHAT_RECORDS_CLEAR_TIME | [Cron Expression] | Chat Records Clear Time |
|
||||
| MSG_DESTRUCT_TIME | [Cron Expression] | Message Destruct Time |
|
||||
| SECRET | "${PASSWORD}" | Secret Key |
|
||||
| TOKEN_EXPIRE | "90" | Token Expiry Time |
|
||||
| FRIEND_VERIFY | "false" | Friend Verification Enable |
|
||||
| IOS_PUSH_SOUND | "xxx" | iOS |
|
||||
| CALLBACK_ENABLE | "false" | Enable callback |
|
||||
| CALLBACK_TIMEOUT | "5" | Maximum timeout for callback call |
|
||||
| CALLBACK_FAILED_CONTINUE| "true" | fails to continue to the next step |
|
||||
### 2.20. <a name='PrometheusConfiguration-1'></a>Prometheus Configuration
|
||||
|
||||
|
||||
14
go.mod
14
go.mod
@ -4,6 +4,8 @@ go 1.19
|
||||
|
||||
require (
|
||||
firebase.google.com/go v3.13.0+incompatible
|
||||
github.com/OpenIMSDK/protocol v0.0.42
|
||||
github.com/OpenIMSDK/tools v0.0.21
|
||||
github.com/bwmarrin/snowflake v0.3.0 // indirect
|
||||
github.com/dtm-labs/rockscache v0.1.1
|
||||
github.com/gin-gonic/gin v1.9.1
|
||||
@ -33,11 +35,10 @@ require github.com/google/uuid v1.3.1
|
||||
|
||||
require (
|
||||
github.com/IBM/sarama v1.41.3
|
||||
github.com/OpenIMSDK/protocol v0.0.36
|
||||
github.com/OpenIMSDK/tools v0.0.20
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.9+incompatible
|
||||
github.com/go-redis/redis v6.15.9+incompatible
|
||||
github.com/redis/go-redis/v9 v9.2.1
|
||||
github.com/stathat/consistent v1.0.0
|
||||
github.com/tencentyun/cos-go-sdk-v5 v0.7.45
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/sync v0.4.0
|
||||
@ -132,7 +133,7 @@ require (
|
||||
golang.org/x/oauth2 v0.13.0 // indirect
|
||||
golang.org/x/sys v0.14.0 // indirect
|
||||
golang.org/x/text v0.13.0 // indirect
|
||||
golang.org/x/time v0.3.0 // indirect
|
||||
golang.org/x/time v0.5.0 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/genproto v0.0.0-20231002182017-d307bd883b97 // indirect
|
||||
@ -141,6 +142,7 @@ require (
|
||||
gopkg.in/src-d/go-billy.v4 v4.3.2 // indirect
|
||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||
gorm.io/gorm v1.23.8 // indirect
|
||||
stathat.com/c/consistent v1.0.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
@ -153,8 +155,4 @@ require (
|
||||
go.uber.org/zap v1.24.0 // indirect
|
||||
golang.org/x/crypto v0.14.0 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
)
|
||||
//replace (
|
||||
// github.com/OpenIMSDK/protocol v0.0.34 => github.com/AndrewZuo01/protocol v0.0.0-20231218034338-b8d838e0b182
|
||||
//)
|
||||
|
||||
)
|
||||
16
go.sum
16
go.sum
@ -18,10 +18,10 @@ firebase.google.com/go v3.13.0+incompatible/go.mod h1:xlah6XbEyW6tbfSklcfe5FHJIw
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/IBM/sarama v1.41.3 h1:MWBEJ12vHC8coMjdEXFq/6ftO6DUZnQlFYcxtOJFa7c=
|
||||
github.com/IBM/sarama v1.41.3/go.mod h1:Xxho9HkHd4K/MDUo/T/sOqwtX/17D33++E9Wib6hUdQ=
|
||||
github.com/OpenIMSDK/protocol v0.0.31 h1:ax43x9aqA6EKNXNukS5MT5BSTqkUmwO4uTvbJLtzCgE=
|
||||
github.com/OpenIMSDK/protocol v0.0.31/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y=
|
||||
github.com/OpenIMSDK/tools v0.0.20 h1:zBTjQZRJ5lR1FIzP9mtWyAvh5dKsmJXQugi4p8X/97k=
|
||||
github.com/OpenIMSDK/tools v0.0.20/go.mod h1:eg+q4A34Qmu73xkY0mt37FHGMCMfC6CtmOnm0kFEGFI=
|
||||
github.com/OpenIMSDK/protocol v0.0.42 h1:vIWXqZJZZ1ddleJA25fxhjZ1GyEHATpYM3wVWh4/+PY=
|
||||
github.com/OpenIMSDK/protocol v0.0.42/go.mod h1:F25dFrwrIx3lkNoiuf6FkCfxuwf8L4Z8UIsdTHP/r0Y=
|
||||
github.com/OpenIMSDK/tools v0.0.21 h1:iTapc2mIEVH/xl5Nd6jfwPub11Pgp44tVcE1rjB3a48=
|
||||
github.com/OpenIMSDK/tools v0.0.21/go.mod h1:eg+q4A34Qmu73xkY0mt37FHGMCMfC6CtmOnm0kFEGFI=
|
||||
github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs=
|
||||
github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
|
||||
@ -308,6 +308,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/src-d/gcfg v1.4.0 h1:xXbNR5AlLSA315x2UO+fTSSAXCDf+Ar38/6oyGbDKQ4=
|
||||
github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
|
||||
github.com/stathat/consistent v1.0.0 h1:ZFJ1QTRn8npNBKW065raSZ8xfOqhpb8vLOkfp4CcL/U=
|
||||
github.com/stathat/consistent v1.0.0/go.mod h1:uajTPbgSygZBJ+V+0mY7meZ8i0XAcZs7AQ6V121XSxw=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
@ -451,8 +453,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
|
||||
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
||||
@ -536,3 +538,5 @@ gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
|
||||
stathat.com/c/consistent v1.0.0 h1:ezyc51EGcRPJUxfHGSgJjWzJdj3NiMU9pNfLNGiXV0c=
|
||||
stathat.com/c/consistent v1.0.0/go.mod h1:QkzMWzcbB+yQBL2AttO6sgsQS/JSTapcDISJalmCDS0=
|
||||
|
||||
@ -33,6 +33,10 @@ func (o *ConversationApi) GetAllConversations(c *gin.Context) {
|
||||
a2r.Call(conversation.ConversationClient.GetAllConversations, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *ConversationApi) GetConversationsList(c *gin.Context) {
|
||||
a2r.Call(conversation.ConversationClient.GetConversationList, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *ConversationApi) GetConversation(c *gin.Context) {
|
||||
a2r.Call(conversation.ConversationClient.GetConversation, o.Client, c)
|
||||
}
|
||||
|
||||
@ -92,6 +92,6 @@ func (o *FriendApi) GetFriendIDs(c *gin.Context) {
|
||||
func (o *FriendApi) GetSpecifiedFriendsInfo(c *gin.Context) {
|
||||
a2r.Call(friend.FriendClient.GetSpecifiedFriendsInfo, o.Client, c)
|
||||
}
|
||||
func (o *FriendApi) SetPinFriends(c *gin.Context) {
|
||||
a2r.Call(friend.FriendClient.PinFriends, o.Client, c)
|
||||
func (o *FriendApi) UpdateFriends(c *gin.Context) {
|
||||
a2r.Call(friend.FriendClient.UpdateFriends, o.Client, c)
|
||||
}
|
||||
|
||||
@ -169,10 +169,10 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM
|
||||
case constant.OANotification:
|
||||
data = apistruct.OANotificationElem{}
|
||||
req.SessionType = constant.NotificationChatType
|
||||
if !authverify.IsManagerUserID(req.SendID) {
|
||||
return nil, errs.ErrNoPermission.
|
||||
Wrap("only app manager can as sender send OANotificationElem")
|
||||
if err = m.userRpcClient.GetNotificationByID(c, req.SendID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
default:
|
||||
return nil, errs.ErrArgs.WithDetail("not support err contentType")
|
||||
}
|
||||
@ -186,38 +186,63 @@ func (m *MessageApi) getSendMsgReq(c *gin.Context, req apistruct.SendMsg) (sendM
|
||||
return m.newUserSendMsgReq(c, &req), nil
|
||||
}
|
||||
|
||||
// SendMessage handles the sending of a message. It's an HTTP handler function to be used with Gin framework.
|
||||
func (m *MessageApi) SendMessage(c *gin.Context) {
|
||||
// Initialize a request struct for sending a message.
|
||||
req := apistruct.SendMsgReq{}
|
||||
|
||||
// Bind the JSON request body to the request struct.
|
||||
if err := c.BindJSON(&req); err != nil {
|
||||
// Respond with an error if request body binding fails.
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the user has the app manager role.
|
||||
if !authverify.IsAppManagerUid(c) {
|
||||
// Respond with a permission error if the user is not an app manager.
|
||||
apiresp.GinError(c, errs.ErrNoPermission.Wrap("only app manager can send message"))
|
||||
return
|
||||
}
|
||||
|
||||
// Prepare the message request with additional required data.
|
||||
sendMsgReq, err := m.getSendMsgReq(c, req.SendMsg)
|
||||
if err != nil {
|
||||
// Log and respond with an error if preparation fails.
|
||||
log.ZError(c, "decodeData failed", err)
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Set the receiver ID in the message data.
|
||||
sendMsgReq.MsgData.RecvID = req.RecvID
|
||||
|
||||
// Declare a variable to store the message sending status.
|
||||
var status int
|
||||
|
||||
// Attempt to send the message using the client.
|
||||
respPb, err := m.Client.SendMsg(c, sendMsgReq)
|
||||
if err != nil {
|
||||
// Set the status to failed and respond with an error if sending fails.
|
||||
status = constant.MsgSendFailed
|
||||
log.ZError(c, "send message err", err)
|
||||
apiresp.GinError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Set the status to successful if the message is sent.
|
||||
status = constant.MsgSendSuccessed
|
||||
|
||||
// Attempt to update the message sending status in the system.
|
||||
_, err = m.Client.SetSendMsgStatus(c, &msg.SetSendMsgStatusReq{
|
||||
Status: int32(status),
|
||||
})
|
||||
if err != nil {
|
||||
// Log the error if updating the status fails.
|
||||
log.ZError(c, "SetSendMsgStatus failed", err)
|
||||
}
|
||||
|
||||
// Respond with a success message and the response payload.
|
||||
apiresp.GinSuccess(c, respPb)
|
||||
}
|
||||
|
||||
@ -225,13 +250,14 @@ func (m *MessageApi) SendBusinessNotification(c *gin.Context) {
|
||||
req := struct {
|
||||
Key string `json:"key"`
|
||||
Data string `json:"data"`
|
||||
SendUserID string `json:"sendUserID"`
|
||||
RecvUserID string `json:"recvUserID"`
|
||||
SendUserID string `json:"sendUserID" binding:"required"`
|
||||
RecvUserID string `json:"recvUserID" binding:"required"`
|
||||
}{}
|
||||
if err := c.BindJSON(&req); err != nil {
|
||||
apiresp.GinError(c, errs.ErrArgs.WithDetail(err.Error()).Wrap())
|
||||
return
|
||||
}
|
||||
|
||||
if !authverify.IsAppManagerUid(c) {
|
||||
apiresp.GinError(c, errs.ErrNoPermission.Wrap("only app manager can send message"))
|
||||
return
|
||||
|
||||
@ -67,6 +67,7 @@ func NewGinRouter(discov discoveryregistry.SvcDiscoveryRegistry, rdb redis.Unive
|
||||
{
|
||||
userRouterGroup.POST("/user_register", u.UserRegister)
|
||||
userRouterGroup.POST("/update_user_info", ParseToken, u.UpdateUserInfo)
|
||||
userRouterGroup.POST("/update_user_info_ex", ParseToken, u.UpdateUserInfoEx)
|
||||
userRouterGroup.POST("/set_global_msg_recv_opt", ParseToken, u.SetGlobalRecvMessageOpt)
|
||||
userRouterGroup.POST("/get_users_info", ParseToken, u.GetUsersPublicInfo)
|
||||
userRouterGroup.POST("/get_all_users_uid", ParseToken, u.GetAllUsersID)
|
||||
@ -82,6 +83,10 @@ func NewGinRouter(discov discoveryregistry.SvcDiscoveryRegistry, rdb redis.Unive
|
||||
userRouterGroup.POST("/process_user_command_delete", ParseToken, u.ProcessUserCommandDelete)
|
||||
userRouterGroup.POST("/process_user_command_update", ParseToken, u.ProcessUserCommandUpdate)
|
||||
userRouterGroup.POST("/process_user_command_get", ParseToken, u.ProcessUserCommandGet)
|
||||
|
||||
userRouterGroup.POST("/add_notification_account", ParseToken, u.AddNotificationAccount)
|
||||
userRouterGroup.POST("/update_notification_account", ParseToken, u.UpdateNotificationAccountInfo)
|
||||
userRouterGroup.POST("/search_notification_account", ParseToken, u.SearchNotificationAccount)
|
||||
}
|
||||
// friend routing group
|
||||
friendRouterGroup := r.Group("/friend", ParseToken)
|
||||
@ -103,7 +108,7 @@ func NewGinRouter(discov discoveryregistry.SvcDiscoveryRegistry, rdb redis.Unive
|
||||
friendRouterGroup.POST("/is_friend", f.IsFriend)
|
||||
friendRouterGroup.POST("/get_friend_id", f.GetFriendIDs)
|
||||
friendRouterGroup.POST("/get_specified_friends_info", f.GetSpecifiedFriendsInfo)
|
||||
friendRouterGroup.POST("/set_pin_friend", f.SetPinFriends)
|
||||
friendRouterGroup.POST("/update_friends", f.UpdateFriends)
|
||||
}
|
||||
g := NewGroupApi(*groupRpc)
|
||||
groupRouterGroup := r.Group("/group", ParseToken)
|
||||
@ -167,6 +172,8 @@ func NewGinRouter(discov discoveryregistry.SvcDiscoveryRegistry, rdb redis.Unive
|
||||
objectGroup.POST("/auth_sign", t.AuthSign)
|
||||
objectGroup.POST("/complete_multipart_upload", t.CompleteMultipartUpload)
|
||||
objectGroup.POST("/access_url", t.AccessURL)
|
||||
objectGroup.POST("/initiate_form_data", t.InitiateFormData)
|
||||
objectGroup.POST("/complete_form_data", t.CompleteFormData)
|
||||
objectGroup.GET("/*name", t.ObjectRedirect)
|
||||
}
|
||||
// Message
|
||||
@ -197,6 +204,7 @@ func NewGinRouter(discov discoveryregistry.SvcDiscoveryRegistry, rdb redis.Unive
|
||||
conversationGroup := r.Group("/conversation", ParseToken)
|
||||
{
|
||||
c := NewConversationApi(*conversationRpc)
|
||||
conversationGroup.POST("/get_conversations_list", c.GetConversationsList)
|
||||
conversationGroup.POST("/get_all_conversations", c.GetAllConversations)
|
||||
conversationGroup.POST("/get_conversation", c.GetConversation)
|
||||
conversationGroup.POST("/get_conversations", c.GetConversations)
|
||||
|
||||
@ -71,6 +71,14 @@ func (o *ThirdApi) AccessURL(c *gin.Context) {
|
||||
a2r.Call(third.ThirdClient.AccessURL, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *ThirdApi) InitiateFormData(c *gin.Context) {
|
||||
a2r.Call(third.ThirdClient.InitiateFormData, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *ThirdApi) CompleteFormData(c *gin.Context) {
|
||||
a2r.Call(third.ThirdClient.CompleteFormData, o.Client, c)
|
||||
}
|
||||
|
||||
func (o *ThirdApi) ObjectRedirect(c *gin.Context) {
|
||||
name := c.Param("name")
|
||||
if name == "" {
|
||||
@ -122,5 +130,5 @@ func (o *ThirdApi) SearchLogs(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetPrometheus(c *gin.Context) {
|
||||
c.Redirect(http.StatusFound, config2.Config.Prometheus.PrometheusUrl)
|
||||
c.Redirect(http.StatusFound, config2.Config.Prometheus.GrafanaUrl)
|
||||
}
|
||||
|
||||
@ -15,8 +15,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/OpenIMSDK/protocol/constant"
|
||||
"github.com/OpenIMSDK/protocol/msggateway"
|
||||
"github.com/OpenIMSDK/protocol/user"
|
||||
@ -24,6 +22,7 @@ import (
|
||||
"github.com/OpenIMSDK/tools/apiresp"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/rpcclient"
|
||||
@ -42,7 +41,9 @@ func (u *UserApi) UserRegister(c *gin.Context) {
|
||||
func (u *UserApi) UpdateUserInfo(c *gin.Context) {
|
||||
a2r.Call(user.UserClient.UpdateUserInfo, u.Client, c)
|
||||
}
|
||||
|
||||
func (u *UserApi) UpdateUserInfoEx(c *gin.Context) {
|
||||
a2r.Call(user.UserClient.UpdateUserInfoEx, u.Client, c)
|
||||
}
|
||||
func (u *UserApi) SetGlobalRecvMessageOpt(c *gin.Context) {
|
||||
a2r.Call(user.UserClient.SetGlobalRecvMessageOpt, u.Client, c)
|
||||
}
|
||||
@ -219,3 +220,15 @@ func (u *UserApi) ProcessUserCommandUpdate(c *gin.Context) {
|
||||
func (u *UserApi) ProcessUserCommandGet(c *gin.Context) {
|
||||
a2r.Call(user.UserClient.ProcessUserCommandGet, u.Client, c)
|
||||
}
|
||||
|
||||
func (u *UserApi) AddNotificationAccount(c *gin.Context) {
|
||||
a2r.Call(user.UserClient.AddNotificationAccount, u.Client, c)
|
||||
}
|
||||
|
||||
func (u *UserApi) UpdateNotificationAccountInfo(c *gin.Context) {
|
||||
a2r.Call(user.UserClient.UpdateNotificationAccountInfo, u.Client, c)
|
||||
}
|
||||
|
||||
func (u *UserApi) SearchNotificationAccount(c *gin.Context) {
|
||||
a2r.Call(user.UserClient.SearchNotificationAccount, u.Client, c)
|
||||
}
|
||||
|
||||
@ -87,6 +87,7 @@ func newClient(ctx *UserConnContext, conn LongConn, isCompress bool) *Client {
|
||||
}
|
||||
}
|
||||
|
||||
// ResetClient updates the client's state with new connection and context information.
|
||||
func (c *Client) ResetClient(
|
||||
ctx *UserConnContext,
|
||||
conn LongConn,
|
||||
@ -108,11 +109,13 @@ func (c *Client) ResetClient(
|
||||
c.token = token
|
||||
}
|
||||
|
||||
// pingHandler handles ping messages and sends pong responses.
|
||||
func (c *Client) pingHandler(_ string) error {
|
||||
_ = c.conn.SetReadDeadline(pongWait)
|
||||
return c.writePongMsg()
|
||||
}
|
||||
|
||||
// readMessage continuously reads messages from the connection.
|
||||
func (c *Client) readMessage() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@ -164,6 +167,7 @@ func (c *Client) readMessage() {
|
||||
}
|
||||
}
|
||||
|
||||
// handleMessage processes a single message received by the client.
|
||||
func (c *Client) handleMessage(message []byte) error {
|
||||
if c.IsCompress {
|
||||
var err error
|
||||
|
||||
@ -288,12 +288,13 @@ func (ws *WsServer) registerClient(client *Client) {
|
||||
}
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_ = ws.sendUserOnlineInfoToOtherNode(client.ctx, client)
|
||||
}()
|
||||
|
||||
if config.Config.Envs.Discovery == "zookeeper" {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
_ = ws.sendUserOnlineInfoToOtherNode(client.ctx, client)
|
||||
}()
|
||||
}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
||||
@ -67,13 +67,14 @@ func (c *ConsumerHandler) handleMs2PsChat(ctx context.Context, msg []byte) {
|
||||
case constant.SuperGroupChatType:
|
||||
err = c.pusher.Push2SuperGroup(ctx, pbData.MsgData.GroupID, pbData.MsgData)
|
||||
default:
|
||||
var pushUserIDs []string
|
||||
if pbData.MsgData.SendID != pbData.MsgData.RecvID {
|
||||
pushUserIDs = []string{pbData.MsgData.SendID, pbData.MsgData.RecvID}
|
||||
var pushUserIDList []string
|
||||
isSenderSync := utils.GetSwitchFromOptions(pbData.MsgData.Options, constant.IsSenderSync)
|
||||
if !isSenderSync || pbData.MsgData.SendID == pbData.MsgData.RecvID {
|
||||
pushUserIDList = append(pushUserIDList, pbData.MsgData.RecvID)
|
||||
} else {
|
||||
pushUserIDs = []string{pbData.MsgData.SendID}
|
||||
pushUserIDList = append(pushUserIDList, pbData.MsgData.RecvID, pbData.MsgData.SendID)
|
||||
}
|
||||
err = c.pusher.Push2User(ctx, pushUserIDs, pbData.MsgData)
|
||||
err = c.pusher.Push2User(ctx, pushUserIDList, pbData.MsgData)
|
||||
}
|
||||
if err != nil {
|
||||
if err == errNoOfflinePusher {
|
||||
|
||||
@ -16,9 +16,8 @@ package push
|
||||
|
||||
import (
|
||||
"context"
|
||||
"sync"
|
||||
|
||||
"github.com/OpenIMSDK/tools/utils"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
||||
|
||||
@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"google.golang.org/grpc"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/sync/errgroup"
|
||||
@ -142,6 +143,47 @@ func (p *Pusher) UnmarshalNotificationElem(bytes []byte, t any) error {
|
||||
return json.Unmarshal([]byte(notification.Detail), t)
|
||||
}
|
||||
|
||||
/*
|
||||
k8s deployment,offline push group messages function
|
||||
*/
|
||||
func (p *Pusher) k8sOfflinePush2SuperGroup(ctx context.Context, groupID string, msg *sdkws.MsgData, wsResults []*msggateway.SingleMsgToUserResults) error {
|
||||
|
||||
var needOfflinePushUserIDs []string
|
||||
for _, v := range wsResults {
|
||||
if !v.OnlinePush {
|
||||
needOfflinePushUserIDs = append(needOfflinePushUserIDs, v.UserID)
|
||||
}
|
||||
}
|
||||
if len(needOfflinePushUserIDs) > 0 {
|
||||
var offlinePushUserIDs []string
|
||||
err := callbackOfflinePush(ctx, needOfflinePushUserIDs, msg, &offlinePushUserIDs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(offlinePushUserIDs) > 0 {
|
||||
needOfflinePushUserIDs = offlinePushUserIDs
|
||||
}
|
||||
if msg.ContentType != constant.SignalingNotification {
|
||||
resp, err := p.conversationRpcClient.Client.GetConversationOfflinePushUserIDs(
|
||||
ctx,
|
||||
&conversation.GetConversationOfflinePushUserIDsReq{ConversationID: utils.GenGroupConversationID(groupID), UserIDs: needOfflinePushUserIDs},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resp.UserIDs) > 0 {
|
||||
err = p.offlinePushMsg(ctx, groupID, msg, resp.UserIDs)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg)
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (p *Pusher) Push2SuperGroup(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)
|
||||
var pushToUserIDs []string
|
||||
@ -205,7 +247,10 @@ func (p *Pusher) Push2SuperGroup(ctx context.Context, groupID string, msg *sdkws
|
||||
|
||||
log.ZDebug(ctx, "get conn and online push success", "result", wsResults, "msg", msg)
|
||||
isOfflinePush := utils.GetSwitchFromOptions(msg.Options, constant.IsOfflinePush)
|
||||
if isOfflinePush {
|
||||
if isOfflinePush && config.Config.Envs.Discovery == "k8s" {
|
||||
return p.k8sOfflinePush2SuperGroup(ctx, groupID, msg, wsResults)
|
||||
}
|
||||
if isOfflinePush && config.Config.Envs.Discovery == "zookeeper" {
|
||||
var (
|
||||
onlineSuccessUserIDs = []string{msg.SendID}
|
||||
webAndPcBackgroundUserIDs []string
|
||||
@ -239,14 +284,7 @@ func (p *Pusher) Push2SuperGroup(ctx context.Context, groupID string, msg *sdkws
|
||||
}
|
||||
|
||||
needOfflinePushUserIDs := utils.DifferenceString(onlineSuccessUserIDs, pushToUserIDs)
|
||||
if msg.ContentType != constant.SignalingNotification {
|
||||
notNotificationUserIDs, err := p.conversationLocalCache.GetRecvMsgNotNotifyUserIDs(ctx, groupID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
needOfflinePushUserIDs = utils.SliceSub(needOfflinePushUserIDs, notNotificationUserIDs)
|
||||
}
|
||||
// Use offline push messaging
|
||||
if len(needOfflinePushUserIDs) > 0 {
|
||||
var offlinePushUserIDs []string
|
||||
@ -258,30 +296,89 @@ func (p *Pusher) Push2SuperGroup(ctx context.Context, groupID string, msg *sdkws
|
||||
if len(offlinePushUserIDs) > 0 {
|
||||
needOfflinePushUserIDs = offlinePushUserIDs
|
||||
}
|
||||
resp, err := p.conversationRpcClient.Client.GetConversationOfflinePushUserIDs(
|
||||
ctx,
|
||||
&conversation.GetConversationOfflinePushUserIDsReq{ConversationID: utils.GenGroupConversationID(groupID), UserIDs: needOfflinePushUserIDs},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(resp.UserIDs) > 0 {
|
||||
err = p.offlinePushMsg(ctx, groupID, msg, resp.UserIDs)
|
||||
if msg.ContentType != constant.SignalingNotification {
|
||||
resp, err := p.conversationRpcClient.Client.GetConversationOfflinePushUserIDs(
|
||||
ctx,
|
||||
&conversation.GetConversationOfflinePushUserIDsReq{ConversationID: utils.GenGroupConversationID(groupID), UserIDs: needOfflinePushUserIDs},
|
||||
)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg)
|
||||
return err
|
||||
}
|
||||
if _, err := p.GetConnsAndOnlinePush(ctx, msg, utils.IntersectString(resp.UserIDs, webAndPcBackgroundUserIDs)); err != nil {
|
||||
log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg, "userIDs", utils.IntersectString(needOfflinePushUserIDs, webAndPcBackgroundUserIDs))
|
||||
return err
|
||||
if len(resp.UserIDs) > 0 {
|
||||
err = p.offlinePushMsg(ctx, groupID, msg, resp.UserIDs)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg)
|
||||
return err
|
||||
}
|
||||
if _, err := p.GetConnsAndOnlinePush(ctx, msg, utils.IntersectString(resp.UserIDs, webAndPcBackgroundUserIDs)); err != nil {
|
||||
log.ZError(ctx, "offlinePushMsg failed", err, "groupID", groupID, "msg", msg, "userIDs", utils.IntersectString(needOfflinePushUserIDs, webAndPcBackgroundUserIDs))
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Pusher) k8sOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) {
|
||||
var usersHost = make(map[string][]string)
|
||||
for _, v := range pushToUserIDs {
|
||||
tHost, err := p.discov.GetUserIdHashGatewayHost(ctx, v)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "get msggateway hash error", err)
|
||||
return nil, err
|
||||
}
|
||||
tUsers, tbl := usersHost[tHost]
|
||||
if tbl {
|
||||
tUsers = append(tUsers, v)
|
||||
usersHost[tHost] = tUsers
|
||||
} else {
|
||||
usersHost[tHost] = []string{v}
|
||||
}
|
||||
}
|
||||
log.ZDebug(ctx, "genUsers send hosts struct:", "usersHost", usersHost)
|
||||
var usersConns = make(map[*grpc.ClientConn][]string)
|
||||
for host, userIds := range usersHost {
|
||||
tconn, _ := p.discov.GetConn(ctx, host)
|
||||
usersConns[tconn] = userIds
|
||||
}
|
||||
var (
|
||||
mu sync.Mutex
|
||||
wg = errgroup.Group{}
|
||||
maxWorkers = config.Config.Push.MaxConcurrentWorkers
|
||||
)
|
||||
if maxWorkers < 3 {
|
||||
maxWorkers = 3
|
||||
}
|
||||
wg.SetLimit(maxWorkers)
|
||||
for conn, userIds := range usersConns {
|
||||
tcon := conn
|
||||
tuserIds := userIds
|
||||
wg.Go(func() error {
|
||||
input := &msggateway.OnlineBatchPushOneMsgReq{MsgData: msg, PushToUserIDs: tuserIds}
|
||||
msgClient := msggateway.NewMsgGatewayClient(tcon)
|
||||
reply, err := msgClient.SuperGroupOnlineBatchPushOneMsg(ctx, input)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
log.ZDebug(ctx, "push result", "reply", reply)
|
||||
if reply != nil && reply.SinglePushResult != nil {
|
||||
mu.Lock()
|
||||
wsResults = append(wsResults, reply.SinglePushResult...)
|
||||
mu.Unlock()
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
_ = wg.Wait()
|
||||
return wsResults, nil
|
||||
}
|
||||
func (p *Pusher) GetConnsAndOnlinePush(ctx context.Context, msg *sdkws.MsgData, pushToUserIDs []string) (wsResults []*msggateway.SingleMsgToUserResults, err error) {
|
||||
if config.Config.Envs.Discovery == "k8s" {
|
||||
return p.k8sOnlinePush(ctx, msg, pushToUserIDs)
|
||||
}
|
||||
conns, err := p.discov.GetConns(ctx, config.Config.RpcRegisterName.OpenImMessageGatewayName)
|
||||
log.ZDebug(ctx, "get gateway conn", "conn length", len(conns))
|
||||
if err != nil {
|
||||
|
||||
@ -17,6 +17,8 @@ package conversation
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/OpenIMSDK/protocol/sdkws"
|
||||
"sort"
|
||||
|
||||
"github.com/OpenIMSDK/tools/tx"
|
||||
|
||||
@ -41,6 +43,8 @@ import (
|
||||
)
|
||||
|
||||
type conversationServer struct {
|
||||
msgRpcClient *rpcclient.MessageRpcClient
|
||||
user *rpcclient.UserRpcClient
|
||||
groupRpcClient *rpcclient.GroupRpcClient
|
||||
conversationDatabase controller.ConversationDatabase
|
||||
conversationNotificationSender *notification.ConversationNotificationSender
|
||||
@ -61,7 +65,10 @@ func Start(client discoveryregistry.SvcDiscoveryRegistry, server *grpc.Server) e
|
||||
}
|
||||
groupRpcClient := rpcclient.NewGroupRpcClient(client)
|
||||
msgRpcClient := rpcclient.NewMessageRpcClient(client)
|
||||
userRpcClient := rpcclient.NewUserRpcClient(client)
|
||||
pbconversation.RegisterConversationServer(server, &conversationServer{
|
||||
msgRpcClient: &msgRpcClient,
|
||||
user: &userRpcClient,
|
||||
conversationNotificationSender: notification.NewConversationNotificationSender(&msgRpcClient),
|
||||
groupRpcClient: &groupRpcClient,
|
||||
conversationDatabase: controller.NewConversationDatabase(conversationDB, cache.NewConversationRedis(rdb, cache.GetDefaultOpt(), conversationDB), tx.NewMongo(mongo.GetClient())),
|
||||
@ -82,6 +89,73 @@ func (c *conversationServer) GetConversation(ctx context.Context, req *pbconvers
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (m *conversationServer) GetConversationList(ctx context.Context, req *pbconversation.GetConversationListReq) (resp *pbconversation.GetConversationListResp, err error) {
|
||||
log.ZDebug(ctx, "GetConversationList", "seqs", req, "userID", req.UserID)
|
||||
var conversationIDs []string
|
||||
if len(req.ConversationIDs) == 0 {
|
||||
conversationIDs, err = m.conversationDatabase.GetConversationIDs(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
conversationIDs = req.ConversationIDs
|
||||
}
|
||||
|
||||
conversations, err := m.conversationDatabase.FindConversations(ctx, req.UserID, conversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(conversations) == 0 {
|
||||
return nil, errs.ErrRecordNotFound.Wrap()
|
||||
}
|
||||
|
||||
maxSeqs, err := m.msgRpcClient.GetMaxSeqs(ctx, conversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
chatLogs, err := m.msgRpcClient.GetMsgByConversationIDs(ctx, conversationIDs, maxSeqs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conversationMsg, err := m.getConversationInfo(ctx, chatLogs, req.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hasReadSeqs, err := m.msgRpcClient.GetHasReadSeqs(ctx, req.UserID, conversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conversation_unreadCount := make(map[string]int64)
|
||||
for conversationID, maxSeq := range maxSeqs {
|
||||
conversation_unreadCount[conversationID] = maxSeq - hasReadSeqs[conversationID]
|
||||
}
|
||||
|
||||
conversation_isPinkTime := make(map[int64]string)
|
||||
conversation_notPinkTime := make(map[int64]string)
|
||||
for _, v := range conversations {
|
||||
conversationID := v.ConversationID
|
||||
time := conversationMsg[conversationID].MsgInfo.LatestMsgRecvTime
|
||||
conversationMsg[conversationID].RecvMsgOpt = v.RecvMsgOpt
|
||||
if v.IsPinned {
|
||||
conversationMsg[conversationID].IsPinned = v.IsPinned
|
||||
conversation_isPinkTime[time] = conversationID
|
||||
continue
|
||||
}
|
||||
conversation_notPinkTime[time] = conversationID
|
||||
}
|
||||
resp = &pbconversation.GetConversationListResp{
|
||||
ConversationElems: []*pbconversation.ConversationElem{},
|
||||
}
|
||||
|
||||
m.conversationSort(conversation_isPinkTime, resp, conversation_unreadCount, conversationMsg)
|
||||
m.conversationSort(conversation_notPinkTime, resp, conversation_unreadCount, conversationMsg)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) GetAllConversations(ctx context.Context, req *pbconversation.GetAllConversationsReq) (*pbconversation.GetAllConversationsResp, error) {
|
||||
conversations, err := c.conversationDatabase.GetUserAllConversation(ctx, req.OwnerUserID)
|
||||
if err != nil {
|
||||
@ -348,3 +422,102 @@ func (c *conversationServer) GetConversationOfflinePushUserIDs(
|
||||
}
|
||||
return &pbconversation.GetConversationOfflinePushUserIDsResp{UserIDs: utils.Keys(userIDSet)}, nil
|
||||
}
|
||||
|
||||
func (c *conversationServer) conversationSort(
|
||||
conversations map[int64]string,
|
||||
resp *pbconversation.GetConversationListResp,
|
||||
conversation_unreadCount map[string]int64,
|
||||
conversationMsg map[string]*pbconversation.ConversationElem,
|
||||
) {
|
||||
keys := []int64{}
|
||||
for key := range conversations {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
|
||||
sort.Slice(keys[:], func(i, j int) bool {
|
||||
return keys[i] > keys[j]
|
||||
})
|
||||
index := 0
|
||||
|
||||
cons := make([]*pbconversation.ConversationElem, len(conversations))
|
||||
for _, v := range keys {
|
||||
conversationID := conversations[v]
|
||||
conversationElem := conversationMsg[conversationID]
|
||||
conversationElem.UnreadCount = conversation_unreadCount[conversationID]
|
||||
cons[index] = conversationElem
|
||||
index++
|
||||
}
|
||||
resp.ConversationElems = append(resp.ConversationElems, cons...)
|
||||
}
|
||||
|
||||
func (c *conversationServer) getConversationInfo(
|
||||
ctx context.Context,
|
||||
chatLogs map[string]*sdkws.MsgData,
|
||||
userID string) (map[string]*pbconversation.ConversationElem, error) {
|
||||
var (
|
||||
sendIDs []string
|
||||
groupIDs []string
|
||||
sendMap = make(map[string]*sdkws.UserInfo)
|
||||
groupMap = make(map[string]*sdkws.GroupInfo)
|
||||
conversationMsg = make(map[string]*pbconversation.ConversationElem)
|
||||
)
|
||||
for _, chatLog := range chatLogs {
|
||||
switch chatLog.SessionType {
|
||||
case constant.SingleChatType:
|
||||
if chatLog.SendID == userID {
|
||||
sendIDs = append(sendIDs, chatLog.RecvID)
|
||||
}
|
||||
sendIDs = append(sendIDs, chatLog.SendID)
|
||||
case constant.GroupChatType, constant.SuperGroupChatType:
|
||||
groupIDs = append(groupIDs, chatLog.GroupID)
|
||||
sendIDs = append(sendIDs, chatLog.SendID)
|
||||
}
|
||||
}
|
||||
if len(sendIDs) != 0 {
|
||||
sendInfos, err := c.user.GetUsersInfo(ctx, sendIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, sendInfo := range sendInfos {
|
||||
sendMap[sendInfo.UserID] = sendInfo
|
||||
}
|
||||
}
|
||||
if len(groupIDs) != 0 {
|
||||
groupInfos, err := c.groupRpcClient.GetGroupInfos(ctx, groupIDs, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, groupInfo := range groupInfos {
|
||||
groupMap[groupInfo.GroupID] = groupInfo
|
||||
}
|
||||
}
|
||||
for conversationID, chatLog := range chatLogs {
|
||||
pbchatLog := &pbconversation.ConversationElem{}
|
||||
msgInfo := &pbconversation.MsgInfo{}
|
||||
if err := utils.CopyStructFields(msgInfo, chatLog); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
switch chatLog.SessionType {
|
||||
case constant.SingleChatType:
|
||||
if chatLog.SendID == userID {
|
||||
msgInfo.FaceURL = sendMap[chatLog.RecvID].FaceURL
|
||||
msgInfo.SenderName = sendMap[chatLog.RecvID].Nickname
|
||||
break
|
||||
}
|
||||
msgInfo.FaceURL = sendMap[chatLog.SendID].FaceURL
|
||||
msgInfo.SenderName = sendMap[chatLog.SendID].Nickname
|
||||
case constant.GroupChatType, constant.SuperGroupChatType:
|
||||
msgInfo.GroupName = groupMap[chatLog.GroupID].GroupName
|
||||
msgInfo.GroupFaceURL = groupMap[chatLog.GroupID].FaceURL
|
||||
msgInfo.GroupMemberCount = groupMap[chatLog.GroupID].MemberCount
|
||||
msgInfo.GroupID = chatLog.GroupID
|
||||
msgInfo.GroupType = groupMap[chatLog.GroupID].GroupType
|
||||
msgInfo.SenderName = sendMap[chatLog.SendID].Nickname
|
||||
}
|
||||
pbchatLog.ConversationID = conversationID
|
||||
msgInfo.LatestMsgRecvTime = chatLog.SendTime
|
||||
pbchatLog.MsgInfo = msgInfo
|
||||
conversationMsg[conversationID] = pbchatLog
|
||||
}
|
||||
return conversationMsg, nil
|
||||
}
|
||||
|
||||
@ -436,35 +436,45 @@ func (s *friendServer) GetSpecifiedFriendsInfo(ctx context.Context, req *pbfrien
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
func (s *friendServer) PinFriends(
|
||||
func (s *friendServer) UpdateFriends(
|
||||
ctx context.Context,
|
||||
req *pbfriend.PinFriendsReq,
|
||||
) (*pbfriend.PinFriendsResp, error) {
|
||||
req *pbfriend.UpdateFriendsReq,
|
||||
) (*pbfriend.UpdateFriendsResp, error) {
|
||||
if len(req.FriendUserIDs) == 0 {
|
||||
return nil, errs.ErrArgs.Wrap("friendIDList is empty")
|
||||
}
|
||||
if utils.Duplicate(req.FriendUserIDs) {
|
||||
return nil, errs.ErrArgs.Wrap("friendIDList repeated")
|
||||
}
|
||||
var isPinned bool
|
||||
if req.IsPinned != nil {
|
||||
isPinned = req.IsPinned.Value
|
||||
} else {
|
||||
return nil, errs.ErrArgs.Wrap("isPinned is nil")
|
||||
}
|
||||
//check whther in friend list
|
||||
|
||||
_, err := s.friendDatabase.FindFriendsWithError(ctx, req.OwnerUserID, req.FriendUserIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
//set friendslist friend pin status to isPinned
|
||||
for _, friendID := range req.FriendUserIDs {
|
||||
if err := s.friendDatabase.UpdateFriendPinStatus(ctx, req.OwnerUserID, friendID, isPinned); err != nil {
|
||||
return nil, err
|
||||
if req.IsPinned != nil {
|
||||
if err = s.friendDatabase.UpdateFriendPinStatus(ctx, req.OwnerUserID, friendID, req.IsPinned.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if req.Remark != nil {
|
||||
if err = s.friendDatabase.UpdateFriendRemark(ctx, req.OwnerUserID, friendID, req.Remark.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if req.Ex != nil {
|
||||
if err = s.friendDatabase.UpdateFriendEx(ctx, req.OwnerUserID, friendID, req.Ex.Value); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resp := &pbfriend.PinFriendsResp{}
|
||||
resp := &pbfriend.UpdateFriendsResp{}
|
||||
|
||||
err = s.notificationSender.FriendsInfoUpdateNotification(ctx, req.OwnerUserID, req.FriendUserIDs)
|
||||
if err != nil {
|
||||
return nil, errs.Wrap(err, "FriendsInfoUpdateNotification Error")
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
|
||||
utils2 "github.com/OpenIMSDK/tools/utils"
|
||||
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
|
||||
@ -26,8 +27,6 @@ import (
|
||||
"github.com/OpenIMSDK/protocol/sdkws"
|
||||
"github.com/OpenIMSDK/tools/errs"
|
||||
"github.com/OpenIMSDK/tools/log"
|
||||
|
||||
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
|
||||
)
|
||||
|
||||
func (m *msgServer) GetConversationsHasReadAndMaxSeq(ctx context.Context, req *msg.GetConversationsHasReadAndMaxSeqReq) (resp *msg.GetConversationsHasReadAndMaxSeqResp, err error) {
|
||||
|
||||
@ -100,7 +100,7 @@ func callbackAfterSendSingleMsg(ctx context.Context, msg *pbchat.SendMsgReq) err
|
||||
}
|
||||
|
||||
func callbackBeforeSendGroupMsg(ctx context.Context, msg *pbchat.SendMsgReq) error {
|
||||
if !config.Config.Callback.CallbackBeforeSendSingleMsg.Enable {
|
||||
if !config.Config.Callback.CallbackBeforeSendGroupMsg.Enable {
|
||||
return nil
|
||||
}
|
||||
req := &cbapi.CallbackBeforeSendGroupMsgReq{
|
||||
|
||||
@ -16,7 +16,6 @@ package msg
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pbmsg "github.com/OpenIMSDK/protocol/msg"
|
||||
)
|
||||
|
||||
@ -30,3 +29,27 @@ func (m *msgServer) GetConversationMaxSeq(
|
||||
}
|
||||
return &pbmsg.GetConversationMaxSeqResp{MaxSeq: maxSeq}, nil
|
||||
}
|
||||
|
||||
func (m *msgServer) GetMaxSeqs(ctx context.Context, req *pbmsg.GetMaxSeqsReq) (*pbmsg.SeqsInfoResp, error) {
|
||||
maxSeqs, err := m.MsgDatabase.GetMaxSeqs(ctx, req.ConversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbmsg.SeqsInfoResp{MaxSeqs: maxSeqs}, nil
|
||||
}
|
||||
|
||||
func (m *msgServer) GetHasReadSeqs(ctx context.Context, req *pbmsg.GetHasReadSeqsReq) (*pbmsg.SeqsInfoResp, error) {
|
||||
hasReadSeqs, err := m.MsgDatabase.GetHasReadSeqs(ctx, req.UserID, req.ConversationIDs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbmsg.SeqsInfoResp{MaxSeqs: hasReadSeqs}, nil
|
||||
}
|
||||
|
||||
func (m *msgServer) GetMsgByConversationIDs(ctx context.Context, req *pbmsg.GetMsgByConversationIDsReq) (*pbmsg.GetMsgByConversationIDsResp, error) {
|
||||
Msgs, err := m.MsgDatabase.FindOneByDocIDs(ctx, req.ConversationIDs, req.MaxSeqs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pbmsg.GetMsgByConversationIDsResp{MsgDatas: Msgs}, nil
|
||||
}
|
||||
|
||||
@ -16,6 +16,12 @@ package third
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"github.com/google/uuid"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/authverify"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
@ -179,6 +185,113 @@ func (t *thirdServer) AccessURL(ctx context.Context, req *third.AccessURLReq) (*
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *thirdServer) InitiateFormData(ctx context.Context, req *third.InitiateFormDataReq) (*third.InitiateFormDataResp, error) {
|
||||
if req.Name == "" {
|
||||
return nil, errs.ErrArgs.Wrap("name is empty")
|
||||
}
|
||||
if req.Size <= 0 {
|
||||
return nil, errs.ErrArgs.Wrap("size must be greater than 0")
|
||||
}
|
||||
if err := checkUploadName(ctx, req.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var duration time.Duration
|
||||
opUserID := mcontext.GetOpUserID(ctx)
|
||||
var key string
|
||||
if authverify.IsManagerUserID(opUserID) {
|
||||
if req.Millisecond <= 0 {
|
||||
duration = time.Minute * 10
|
||||
} else {
|
||||
duration = time.Millisecond * time.Duration(req.Millisecond)
|
||||
}
|
||||
if req.Absolute {
|
||||
key = req.Name
|
||||
}
|
||||
} else {
|
||||
duration = time.Minute * 10
|
||||
}
|
||||
uid, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if key == "" {
|
||||
date := time.Now().Format("20060102")
|
||||
key = path.Join(cont.DirectPath, date, opUserID, hex.EncodeToString(uid[:])+path.Ext(req.Name))
|
||||
}
|
||||
mate := FormDataMate{
|
||||
Name: req.Name,
|
||||
Size: req.Size,
|
||||
ContentType: req.ContentType,
|
||||
Group: req.Group,
|
||||
Key: key,
|
||||
}
|
||||
mateData, err := json.Marshal(&mate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := t.s3dataBase.FormData(ctx, key, req.Size, req.ContentType, duration)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &third.InitiateFormDataResp{
|
||||
Id: base64.RawStdEncoding.EncodeToString(mateData),
|
||||
Url: resp.URL,
|
||||
File: resp.File,
|
||||
Header: toPbMapArray(resp.Header),
|
||||
FormData: resp.FormData,
|
||||
Expires: resp.Expires.UnixMilli(),
|
||||
SuccessCodes: utils.Slice(resp.SuccessCodes, func(code int) int32 {
|
||||
return int32(code)
|
||||
}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *thirdServer) CompleteFormData(ctx context.Context, req *third.CompleteFormDataReq) (*third.CompleteFormDataResp, error) {
|
||||
if req.Id == "" {
|
||||
return nil, errs.ErrArgs.Wrap("id is empty")
|
||||
}
|
||||
data, err := base64.RawStdEncoding.DecodeString(req.Id)
|
||||
if err != nil {
|
||||
return nil, errs.ErrArgs.Wrap("invalid id " + err.Error())
|
||||
}
|
||||
var mate FormDataMate
|
||||
if err := json.Unmarshal(data, &mate); err != nil {
|
||||
return nil, errs.ErrArgs.Wrap("invalid id " + err.Error())
|
||||
}
|
||||
if err := checkUploadName(ctx, mate.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info, err := t.s3dataBase.StatObject(ctx, mate.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if info.Size > 0 && info.Size != mate.Size {
|
||||
return nil, errs.ErrData.Wrap("file size mismatch")
|
||||
}
|
||||
obj := &relation.ObjectModel{
|
||||
Name: mate.Name,
|
||||
UserID: mcontext.GetOpUserID(ctx),
|
||||
Hash: "etag_" + info.ETag,
|
||||
Key: info.Key,
|
||||
Size: info.Size,
|
||||
ContentType: mate.ContentType,
|
||||
Group: mate.Group,
|
||||
CreateTime: time.Now(),
|
||||
}
|
||||
if err := t.s3dataBase.SetObject(ctx, obj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &third.CompleteFormDataResp{Url: t.apiAddress(mate.Name)}, nil
|
||||
}
|
||||
|
||||
func (t *thirdServer) apiAddress(name string) string {
|
||||
return t.apiURL + name
|
||||
}
|
||||
|
||||
type FormDataMate struct {
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
ContentType string `json:"contentType"`
|
||||
Group string `json:"group"`
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
@ -101,16 +101,6 @@ type thirdServer struct {
|
||||
defaultExpire time.Duration
|
||||
}
|
||||
|
||||
func (t *thirdServer) InitiateFormData(ctx context.Context, req *third.InitiateFormDataReq) (*third.InitiateFormDataResp, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
func (t *thirdServer) CompleteFormData(ctx context.Context, req *third.CompleteFormDataReq) (*third.CompleteFormDataResp, error) {
|
||||
//TODO implement me
|
||||
panic("implement me")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
|
||||
@ -29,6 +29,9 @@ import (
|
||||
)
|
||||
|
||||
func toPbMapArray(m map[string][]string) []*third.KeyValues {
|
||||
if len(m) == 0 {
|
||||
return nil
|
||||
}
|
||||
res := make([]*third.KeyValues, 0, len(m))
|
||||
for key := range m {
|
||||
res = append(res, &third.KeyValues{
|
||||
|
||||
@ -16,7 +16,6 @@ package user
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
pbuser "github.com/OpenIMSDK/protocol/user"
|
||||
"github.com/OpenIMSDK/tools/utils"
|
||||
|
||||
@ -44,7 +43,6 @@ func CallbackBeforeUpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInf
|
||||
utils.NotNilReplace(&req.UserInfo.Nickname, resp.Nickname)
|
||||
return nil
|
||||
}
|
||||
|
||||
func CallbackAfterUpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInfoReq) error {
|
||||
if !config.Config.Callback.CallbackAfterUpdateUserInfo.Enable {
|
||||
return nil
|
||||
@ -61,6 +59,41 @@ func CallbackAfterUpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserInfo
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func CallbackBeforeUpdateUserInfoEx(ctx context.Context, req *pbuser.UpdateUserInfoExReq) error {
|
||||
if !config.Config.Callback.CallbackBeforeUpdateUserInfoEx.Enable {
|
||||
return nil
|
||||
}
|
||||
cbReq := &cbapi.CallbackBeforeUpdateUserInfoExReq{
|
||||
CallbackCommand: cbapi.CallbackBeforeUpdateUserInfoExCommand,
|
||||
UserID: req.UserInfo.UserID,
|
||||
FaceURL: req.UserInfo.FaceURL,
|
||||
Nickname: req.UserInfo.Nickname,
|
||||
}
|
||||
resp := &cbapi.CallbackBeforeUpdateUserInfoExResp{}
|
||||
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeUpdateUserInfoEx); err != nil {
|
||||
return err
|
||||
}
|
||||
utils.NotNilReplace(req.UserInfo.FaceURL, resp.FaceURL)
|
||||
utils.NotNilReplace(req.UserInfo.Ex, resp.Ex)
|
||||
utils.NotNilReplace(req.UserInfo.Nickname, resp.Nickname)
|
||||
return nil
|
||||
}
|
||||
func CallbackAfterUpdateUserInfoEx(ctx context.Context, req *pbuser.UpdateUserInfoExReq) error {
|
||||
if !config.Config.Callback.CallbackAfterUpdateUserInfoEx.Enable {
|
||||
return nil
|
||||
}
|
||||
cbReq := &cbapi.CallbackAfterUpdateUserInfoExReq{
|
||||
CallbackCommand: cbapi.CallbackAfterUpdateUserInfoExCommand,
|
||||
UserID: req.UserInfo.UserID,
|
||||
FaceURL: req.UserInfo.FaceURL,
|
||||
Nickname: req.UserInfo.Nickname,
|
||||
}
|
||||
resp := &cbapi.CallbackAfterUpdateUserInfoExResp{}
|
||||
if err := http.CallBackPostReturn(ctx, config.Config.Callback.CallbackUrl, cbReq, resp, config.Config.Callback.CallbackBeforeUpdateUserInfoEx); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func CallbackBeforeUserRegister(ctx context.Context, req *pbuser.UserRegisterReq) error {
|
||||
if !config.Config.Callback.CallbackBeforeUserRegister.Enable {
|
||||
|
||||
@ -17,6 +17,8 @@ package user
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/db/table/relation"
|
||||
"math/rand"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -72,6 +74,12 @@ func Start(client registry.SvcDiscoveryRegistry, server *grpc.Server) error {
|
||||
for k, v := range config.Config.Manager.UserID {
|
||||
users = append(users, &tablerelation.UserModel{UserID: v, Nickname: config.Config.Manager.Nickname[k], AppMangerLevel: constant.AppAdmin})
|
||||
}
|
||||
if len(config.Config.IMAdmin.UserID) != len(config.Config.IMAdmin.Nickname) {
|
||||
return errors.New("len(config.Config.AppNotificationAdmin.AppManagerUid) != len(config.Config.AppNotificationAdmin.Nickname)")
|
||||
}
|
||||
for k, v := range config.Config.IMAdmin.UserID {
|
||||
users = append(users, &tablerelation.UserModel{UserID: v, Nickname: config.Config.IMAdmin.Nickname[k], AppMangerLevel: constant.AppNotificationAdmin})
|
||||
}
|
||||
userDB, err := mgo.NewUserMongo(mongo.GetDatabase())
|
||||
if err != nil {
|
||||
return err
|
||||
@ -141,7 +149,41 @@ func (s *userServer) UpdateUserInfo(ctx context.Context, req *pbuser.UpdateUserI
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
func (s *userServer) UpdateUserInfoEx(ctx context.Context, req *pbuser.UpdateUserInfoExReq) (resp *pbuser.UpdateUserInfoExResp, err error) {
|
||||
resp = &pbuser.UpdateUserInfoExResp{}
|
||||
err = authverify.CheckAccessV3(ctx, req.UserInfo.UserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = CallbackBeforeUpdateUserInfoEx(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := convert.UserPb2DBMapEx(req.UserInfo)
|
||||
if err = s.UpdateByMap(ctx, req.UserInfo.UserID, data); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = 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 != nil || req.UserInfo.FaceURL != nil {
|
||||
if err := s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil {
|
||||
log.ZError(ctx, "NotificationUserInfoUpdate", err)
|
||||
}
|
||||
}
|
||||
for _, friendID := range friends {
|
||||
s.friendNotificationSender.FriendInfoUpdatedNotification(ctx, req.UserInfo.UserID, friendID)
|
||||
}
|
||||
if err := CallbackAfterUpdateUserInfoEx(ctx, req); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := s.groupRpcClient.NotificationUserInfoUpdate(ctx, req.UserInfo.UserID); err != nil {
|
||||
log.ZError(ctx, "NotificationUserInfoUpdate", err, "userID", req.UserInfo.UserID)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
func (s *userServer) SetGlobalRecvMessageOpt(ctx context.Context, req *pbuser.SetGlobalRecvMessageOptReq) (resp *pbuser.SetGlobalRecvMessageOptResp, err error) {
|
||||
resp = &pbuser.SetGlobalRecvMessageOptResp{}
|
||||
if _, err := s.FindWithError(ctx, []string{req.UserID}); err != nil {
|
||||
@ -390,3 +432,141 @@ func (s *userServer) ProcessUserCommandGet(ctx context.Context, req *pbuser.Proc
|
||||
// Return the response with the slice
|
||||
return &pbuser.ProcessUserCommandGetResp{KVArray: commandInfoSlice}, nil
|
||||
}
|
||||
|
||||
func (s *userServer) AddNotificationAccount(ctx context.Context, req *pbuser.AddNotificationAccountReq) (*pbuser.AddNotificationAccountResp, error) {
|
||||
if err := authverify.CheckIMAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var userID string
|
||||
for i := 0; i < 20; i++ {
|
||||
userId := s.genUserID()
|
||||
_, err := s.UserDatabase.FindWithError(ctx, []string{userId})
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
userID = userId
|
||||
break
|
||||
}
|
||||
if userID == "" {
|
||||
return nil, errs.ErrInternalServer.Wrap("gen user id failed")
|
||||
}
|
||||
|
||||
user := &tablerelation.UserModel{
|
||||
UserID: userID,
|
||||
Nickname: req.NickName,
|
||||
FaceURL: req.FaceURL,
|
||||
CreateTime: time.Now(),
|
||||
AppMangerLevel: constant.AppNotificationAdmin,
|
||||
}
|
||||
if err := s.UserDatabase.Create(ctx, []*tablerelation.UserModel{user}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbuser.AddNotificationAccountResp{}, nil
|
||||
}
|
||||
|
||||
func (s *userServer) UpdateNotificationAccountInfo(ctx context.Context, req *pbuser.UpdateNotificationAccountInfoReq) (*pbuser.UpdateNotificationAccountInfoResp, error) {
|
||||
if err := authverify.CheckIMAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if _, err := s.UserDatabase.FindWithError(ctx, []string{req.UserID}); err != nil {
|
||||
return nil, errs.ErrArgs.Wrap()
|
||||
}
|
||||
|
||||
user := map[string]interface{}{}
|
||||
|
||||
if req.NickName != "" {
|
||||
user["nickname"] = req.NickName
|
||||
}
|
||||
|
||||
if req.FaceURL != "" {
|
||||
user["face_url"] = req.FaceURL
|
||||
}
|
||||
|
||||
if err := s.UserDatabase.UpdateByMap(ctx, req.UserID, user); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &pbuser.UpdateNotificationAccountInfoResp{}, nil
|
||||
}
|
||||
|
||||
func (s *userServer) SearchNotificationAccount(ctx context.Context, req *pbuser.SearchNotificationAccountReq) (*pbuser.SearchNotificationAccountResp, error) {
|
||||
if err := authverify.CheckIMAdmin(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if req.NickName != "" {
|
||||
users, err := s.UserDatabase.FindByNickname(ctx, req.NickName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := s.userModelToResp(users)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if req.UserID != "" {
|
||||
users, err := s.UserDatabase.Find(ctx, []string{req.UserID})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := s.userModelToResp(users)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
_, users, err := s.UserDatabase.Page(ctx, req.Pagination)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp := s.userModelToResp(users)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *userServer) GetNotificationAccount(ctx context.Context, req *pbuser.GetNotificationAccountReq) (*pbuser.GetNotificationAccountResp, error) {
|
||||
if req.UserID == "" {
|
||||
return nil, errs.ErrArgs.Wrap("userID is empty")
|
||||
}
|
||||
user, err := s.UserDatabase.GetUserByID(ctx, req.UserID)
|
||||
if err != nil {
|
||||
return nil, errs.ErrUserIDNotFound.Wrap()
|
||||
}
|
||||
if user.AppMangerLevel == constant.AppAdmin || user.AppMangerLevel == constant.AppNotificationAdmin {
|
||||
return &pbuser.GetNotificationAccountResp{}, nil
|
||||
}
|
||||
|
||||
return nil, errs.ErrNoPermission.Wrap("notification messages cannot be sent for this ID")
|
||||
}
|
||||
|
||||
func (s *userServer) genUserID() string {
|
||||
const l = 10
|
||||
data := make([]byte, l)
|
||||
rand.Read(data)
|
||||
chars := []byte("0123456789")
|
||||
for i := 0; i < len(data); i++ {
|
||||
if i == 0 {
|
||||
data[i] = chars[1:][data[i]%9]
|
||||
} else {
|
||||
data[i] = chars[data[i]%10]
|
||||
}
|
||||
}
|
||||
return string(data)
|
||||
}
|
||||
|
||||
func (s *userServer) userModelToResp(users []*relation.UserModel) *pbuser.SearchNotificationAccountResp {
|
||||
accounts := make([]*pbuser.NotificationAccountInfo, 0)
|
||||
var total int64
|
||||
for _, v := range users {
|
||||
if v.AppMangerLevel == constant.AppNotificationAdmin || v.AppMangerLevel == constant.AppAdmin {
|
||||
temp := &pbuser.NotificationAccountInfo{
|
||||
UserID: v.UserID,
|
||||
FaceURL: v.FaceURL,
|
||||
NickName: v.Nickname,
|
||||
}
|
||||
accounts = append(accounts, temp)
|
||||
total += 1
|
||||
}
|
||||
}
|
||||
return &pbuser.SearchNotificationAccountResp{Total: total, NotificationAccounts: accounts}
|
||||
}
|
||||
|
||||
@ -64,6 +64,30 @@ type SendMsgReq struct {
|
||||
SendMsg
|
||||
}
|
||||
|
||||
type GetConversationListReq struct {
|
||||
// userID uniquely identifies the user.
|
||||
UserID string `protobuf:"bytes,1,opt,name=userID,proto3" json:"userID,omitempty" binding:"required"`
|
||||
|
||||
// ConversationIDs contains a list of unique identifiers for conversations.
|
||||
ConversationIDs []string `protobuf:"bytes,2,rep,name=conversationIDs,proto3" json:"conversationIDs,omitempty"`
|
||||
}
|
||||
|
||||
type GetConversationListResp struct {
|
||||
// ConversationElems is a map that associates conversation IDs with their respective details.
|
||||
ConversationElems map[string]*ConversationElem `protobuf:"bytes,1,rep,name=conversationElems,proto3" json:"conversationElems,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
|
||||
}
|
||||
|
||||
type ConversationElem struct {
|
||||
// MaxSeq represents the maximum sequence number within the conversation.
|
||||
MaxSeq int64 `protobuf:"varint,1,opt,name=maxSeq,proto3" json:"maxSeq,omitempty"`
|
||||
|
||||
// UnreadSeq represents the number of unread messages in the conversation.
|
||||
UnreadSeq int64 `protobuf:"varint,2,opt,name=unreadSeq,proto3" json:"unreadSeq,omitempty"`
|
||||
|
||||
// LastSeqTime represents the timestamp of the last sequence in the conversation.
|
||||
LastSeqTime int64 `protobuf:"varint,3,opt,name=LastSeqTime,proto3" json:"LastSeqTime,omitempty"`
|
||||
}
|
||||
|
||||
// BatchSendMsgReq defines the structure for sending a message to multiple recipients.
|
||||
type BatchSendMsgReq struct {
|
||||
SendMsg
|
||||
|
||||
@ -87,7 +87,7 @@ type OANotificationElem struct {
|
||||
NotificationType int32 `mapstructure:"notificationType" json:"notificationType" validate:"required"`
|
||||
Text string `mapstructure:"text" json:"text" validate:"required"`
|
||||
Url string `mapstructure:"url" json:"url"`
|
||||
MixType int32 `mapstructure:"mixType" json:"mixType" validate:"required"`
|
||||
MixType int32 `mapstructure:"mixType" json:"mixType"`
|
||||
PictureElem *PictureElem `mapstructure:"pictureElem" json:"pictureElem"`
|
||||
SoundElem *SoundElem `mapstructure:"soundElem" json:"soundElem"`
|
||||
VideoElem *VideoElem `mapstructure:"videoElem" json:"videoElem"`
|
||||
|
||||
@ -54,6 +54,15 @@ func CheckAdmin(ctx context.Context) error {
|
||||
}
|
||||
return errs.ErrNoPermission.Wrap(fmt.Sprintf("user %s is not admin userID", mcontext.GetOpUserID(ctx)))
|
||||
}
|
||||
func CheckIMAdmin(ctx context.Context) error {
|
||||
if utils.IsContain(mcontext.GetOpUserID(ctx), config.Config.IMAdmin.UserID) {
|
||||
return nil
|
||||
}
|
||||
if utils.IsContain(mcontext.GetOpUserID(ctx), config.Config.Manager.UserID) {
|
||||
return nil
|
||||
}
|
||||
return errs.ErrNoPermission.Wrap(fmt.Sprintf("user %s is not CheckIMAdmin userID", mcontext.GetOpUserID(ctx)))
|
||||
}
|
||||
|
||||
func ParseRedisInterfaceToken(redisToken any) (*tokenverify.Claims, error) {
|
||||
return tokenverify.GetClaimFromToken(string(redisToken.([]uint8)), Secret())
|
||||
|
||||
@ -37,6 +37,8 @@ const (
|
||||
CallbackGroupMsgReadCommand = "callbackGroupMsgReadCommand"
|
||||
CallbackMsgModifyCommand = "callbackMsgModifyCommand"
|
||||
CallbackAfterUpdateUserInfoCommand = "callbackAfterUpdateUserInfoCommand"
|
||||
CallbackAfterUpdateUserInfoExCommand = "callbackAfterUpdateUserInfoExCommand"
|
||||
CallbackBeforeUpdateUserInfoExCommand = "callbackBeforeUpdateUserInfoExCommand"
|
||||
CallbackBeforeUserRegisterCommand = "callbackBeforeUserRegisterCommand"
|
||||
CallbackAfterUserRegisterCommand = "callbackAfterUserRegisterCommand"
|
||||
CallbackTransferGroupOwnerAfter = "callbackTransferGroupOwnerAfter"
|
||||
|
||||
@ -14,7 +14,10 @@
|
||||
|
||||
package callbackstruct
|
||||
|
||||
import "github.com/OpenIMSDK/protocol/sdkws"
|
||||
import (
|
||||
"github.com/OpenIMSDK/protocol/sdkws"
|
||||
"github.com/OpenIMSDK/protocol/wrapperspb"
|
||||
)
|
||||
|
||||
type CallbackBeforeUpdateUserInfoReq struct {
|
||||
CallbackCommand `json:"callbackCommand"`
|
||||
@ -41,6 +44,31 @@ type CallbackAfterUpdateUserInfoResp struct {
|
||||
CommonCallbackResp
|
||||
}
|
||||
|
||||
type CallbackBeforeUpdateUserInfoExReq struct {
|
||||
CallbackCommand `json:"callbackCommand"`
|
||||
UserID string `json:"userID"`
|
||||
Nickname *wrapperspb.StringValue `json:"nickName"`
|
||||
FaceURL *wrapperspb.StringValue `json:"faceURL"`
|
||||
Ex *wrapperspb.StringValue `json:"ex"`
|
||||
}
|
||||
type CallbackBeforeUpdateUserInfoExResp struct {
|
||||
CommonCallbackResp
|
||||
Nickname *wrapperspb.StringValue `json:"nickName"`
|
||||
FaceURL *wrapperspb.StringValue `json:"faceURL"`
|
||||
Ex *wrapperspb.StringValue `json:"ex"`
|
||||
}
|
||||
|
||||
type CallbackAfterUpdateUserInfoExReq struct {
|
||||
CallbackCommand `json:"callbackCommand"`
|
||||
UserID string `json:"userID"`
|
||||
Nickname *wrapperspb.StringValue `json:"nickName"`
|
||||
FaceURL *wrapperspb.StringValue `json:"faceURL"`
|
||||
Ex *wrapperspb.StringValue `json:"ex"`
|
||||
}
|
||||
type CallbackAfterUpdateUserInfoExResp struct {
|
||||
CommonCallbackResp
|
||||
}
|
||||
|
||||
type CallbackBeforeUserRegisterReq struct {
|
||||
CallbackCommand `json:"callbackCommand"`
|
||||
Secret string `json:"secret"`
|
||||
|
||||
@ -45,7 +45,7 @@ type CmdOpts struct {
|
||||
|
||||
func WithCronTaskLogName() func(*CmdOpts) {
|
||||
return func(opts *CmdOpts) {
|
||||
opts.loggerPrefixName = "OpenIM.CronTask.log.all"
|
||||
opts.loggerPrefixName = "openim.crontask.log.all"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -236,6 +236,11 @@ type configStruct struct {
|
||||
Nickname []string `yaml:"nickname"`
|
||||
} `yaml:"manager"`
|
||||
|
||||
IMAdmin struct {
|
||||
UserID []string `yaml:"userID"`
|
||||
Nickname []string `yaml:"nickname"`
|
||||
} `yaml:"im-admin"`
|
||||
|
||||
MultiLoginPolicy int `yaml:"multiLoginPolicy"`
|
||||
ChatPersistenceMysql bool `yaml:"chatPersistenceMysql"`
|
||||
MsgCacheTimeout int `yaml:"msgCacheTimeout"`
|
||||
@ -277,6 +282,8 @@ type configStruct struct {
|
||||
CallbackBeforeSetFriendRemark CallBackConfig `yaml:"callbackBeforeSetFriendRemark"`
|
||||
CallbackAfterSetFriendRemark CallBackConfig `yaml:"callbackAfterSetFriendRemark"`
|
||||
CallbackBeforeUpdateUserInfo CallBackConfig `yaml:"beforeUpdateUserInfo"`
|
||||
CallbackBeforeUpdateUserInfoEx CallBackConfig `yaml:"beforeUpdateUserInfoEx"`
|
||||
CallbackAfterUpdateUserInfoEx CallBackConfig `yaml:"afterUpdateUserInfoEx"`
|
||||
CallbackBeforeUserRegister CallBackConfig `yaml:"beforeUserRegister"`
|
||||
CallbackAfterUpdateUserInfo CallBackConfig `yaml:"updateUserInfo"`
|
||||
CallbackAfterUserRegister CallBackConfig `yaml:"afterUserRegister"`
|
||||
@ -307,7 +314,7 @@ type configStruct struct {
|
||||
|
||||
Prometheus struct {
|
||||
Enable bool `yaml:"enable"`
|
||||
PrometheusUrl string `yaml:"prometheusUrl"`
|
||||
GrafanaUrl string `yaml:"grafanaUrl"`
|
||||
ApiPrometheusPort []int `yaml:"apiPrometheusPort"`
|
||||
UserPrometheusPort []int `yaml:"userPrometheusPort"`
|
||||
FriendPrometheusPort []int `yaml:"friendPrometheusPort"`
|
||||
|
||||
@ -64,7 +64,7 @@ func UserPb2DBMap(user *sdkws.UserInfo) map[string]any {
|
||||
"global_recv_msg_opt": user.GlobalRecvMsgOpt,
|
||||
}
|
||||
for key, value := range fields {
|
||||
if v, ok := value.(string); ok {
|
||||
if v, ok := value.(string); ok && v != "" {
|
||||
val[key] = v
|
||||
} else if v, ok := value.(int32); ok && v != 0 {
|
||||
val[key] = v
|
||||
@ -72,3 +72,25 @@ func UserPb2DBMap(user *sdkws.UserInfo) map[string]any {
|
||||
}
|
||||
return val
|
||||
}
|
||||
func UserPb2DBMapEx(user *sdkws.UserInfoWithEx) map[string]any {
|
||||
if user == nil {
|
||||
return nil
|
||||
}
|
||||
val := make(map[string]any)
|
||||
|
||||
// Map fields from UserInfoWithEx to val
|
||||
if user.Nickname != nil {
|
||||
val["nickname"] = user.Nickname.Value
|
||||
}
|
||||
if user.FaceURL != nil {
|
||||
val["face_url"] = user.FaceURL.Value
|
||||
}
|
||||
if user.Ex != nil {
|
||||
val["ex"] = user.Ex.Value
|
||||
}
|
||||
if user.GlobalRecvMsgOpt != nil {
|
||||
val["global_recv_msg_opt"] = user.GlobalRecvMsgOpt.Value
|
||||
}
|
||||
|
||||
return val
|
||||
}
|
||||
|
||||
10
pkg/common/db/cache/init_redis.go
vendored
10
pkg/common/db/cache/init_redis.go
vendored
@ -87,7 +87,15 @@ func NewRedis() (redis.UniversalClient, error) {
|
||||
// overrideConfigFromEnv overrides configuration fields with environment variables if present.
|
||||
func overrideConfigFromEnv() {
|
||||
if envAddr := os.Getenv("REDIS_ADDRESS"); envAddr != "" {
|
||||
config.Config.Redis.Address = strings.Split(envAddr, ",") // Assuming addresses are comma-separated
|
||||
if envPort := os.Getenv("REDIS_PORT"); envPort != "" {
|
||||
addresses := strings.Split(envAddr, ",")
|
||||
for i, addr := range addresses {
|
||||
addresses[i] = addr + ":" + envPort
|
||||
}
|
||||
config.Config.Redis.Address = addresses
|
||||
} else {
|
||||
config.Config.Redis.Address = strings.Split(envAddr, ",")
|
||||
}
|
||||
}
|
||||
if envUser := os.Getenv("REDIS_USERNAME"); envUser != "" {
|
||||
config.Config.Redis.Username = envUser
|
||||
|
||||
@ -32,33 +32,57 @@ import (
|
||||
)
|
||||
|
||||
type FriendDatabase interface {
|
||||
// 检查user2是否在user1的好友列表中(inUser1Friends==true) 检查user1是否在user2的好友列表中(inUser2Friends==true)
|
||||
// CheckIn checks if user2 is in user1's friend list (inUser1Friends==true) and if user1 is in user2's friend list (inUser2Friends==true)
|
||||
CheckIn(ctx context.Context, user1, user2 string) (inUser1Friends bool, inUser2Friends bool, err error)
|
||||
// 增加或者更新好友申请
|
||||
|
||||
// AddFriendRequest adds or updates a friend request
|
||||
AddFriendRequest(ctx context.Context, fromUserID, toUserID string, reqMsg string, ex string) (err error)
|
||||
// 先判断是否在好友表,如果在则不插入
|
||||
|
||||
// BecomeFriends first checks if the users are already in the friends table; if not, it inserts them as friends
|
||||
BecomeFriends(ctx context.Context, ownerUserID string, friendUserIDs []string, addSource int32) (err error)
|
||||
// 拒绝好友申请
|
||||
|
||||
// RefuseFriendRequest refuses a friend request
|
||||
RefuseFriendRequest(ctx context.Context, friendRequest *relation.FriendRequestModel) (err error)
|
||||
// 同意好友申请
|
||||
|
||||
// AgreeFriendRequest accepts a friend request
|
||||
AgreeFriendRequest(ctx context.Context, friendRequest *relation.FriendRequestModel) (err error)
|
||||
// 删除好友
|
||||
|
||||
// Delete removes a friend or friends from the owner's friend list
|
||||
Delete(ctx context.Context, ownerUserID string, friendUserIDs []string) (err error)
|
||||
// 更新好友备注
|
||||
|
||||
// UpdateRemark updates the remark for a friend
|
||||
UpdateRemark(ctx context.Context, ownerUserID, friendUserID, remark string) (err error)
|
||||
// 获取ownerUserID的好友列表
|
||||
|
||||
// PageOwnerFriends retrieves the friend list of ownerUserID with pagination
|
||||
PageOwnerFriends(ctx context.Context, ownerUserID string, pagination pagination.Pagination) (total int64, friends []*relation.FriendModel, err error)
|
||||
// friendUserID在哪些人的好友列表中
|
||||
|
||||
// PageInWhoseFriends finds the users who have friendUserID in their friend list with pagination
|
||||
PageInWhoseFriends(ctx context.Context, friendUserID string, pagination pagination.Pagination) (total int64, friends []*relation.FriendModel, err error)
|
||||
// 获取我发出去的好友申请
|
||||
|
||||
// PageFriendRequestFromMe retrieves the friend requests sent by the user with pagination
|
||||
PageFriendRequestFromMe(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, friends []*relation.FriendRequestModel, err error)
|
||||
// 获取我收到的的好友申请
|
||||
|
||||
// PageFriendRequestToMe retrieves the friend requests received by the user with pagination
|
||||
PageFriendRequestToMe(ctx context.Context, userID string, pagination pagination.Pagination) (total int64, friends []*relation.FriendRequestModel, err error)
|
||||
// 获取某人指定好友的信息
|
||||
|
||||
// FindFriendsWithError fetches specified friends of a user and returns an error if any do not exist
|
||||
FindFriendsWithError(ctx context.Context, ownerUserID string, friendUserIDs []string) (friends []*relation.FriendModel, err error)
|
||||
|
||||
// FindFriendUserIDs retrieves the friend IDs of a user
|
||||
FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error)
|
||||
|
||||
// FindBothFriendRequests finds friend requests sent and received
|
||||
FindBothFriendRequests(ctx context.Context, fromUserID, toUserID string) (friends []*relation.FriendRequestModel, err error)
|
||||
|
||||
// UpdateFriendPinStatus updates the pinned status of a friend
|
||||
UpdateFriendPinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error)
|
||||
|
||||
// UpdateFriendRemark updates the remark for a friend
|
||||
UpdateFriendRemark(ctx context.Context, ownerUserID string, friendUserID string, remark string) (err error)
|
||||
|
||||
// UpdateFriendEx updates the 'ex' field for a friend
|
||||
UpdateFriendEx(ctx context.Context, ownerUserID string, friendUserID string, ex string) (err error)
|
||||
|
||||
}
|
||||
|
||||
type friendDatabase struct {
|
||||
@ -305,3 +329,15 @@ func (f *friendDatabase) UpdateFriendPinStatus(ctx context.Context, ownerUserID
|
||||
}
|
||||
return f.cache.DelFriend(ownerUserID, friendUserID).ExecDel(ctx)
|
||||
}
|
||||
func (f *friendDatabase) UpdateFriendRemark(ctx context.Context, ownerUserID string, friendUserID string, remark string) (err error) {
|
||||
if err := f.friend.UpdateFriendRemark(ctx, ownerUserID, friendUserID, remark); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.cache.DelFriend(ownerUserID, friendUserID).ExecDel(ctx)
|
||||
}
|
||||
func (f *friendDatabase) UpdateFriendEx(ctx context.Context, ownerUserID string, friendUserID string, ex string) (err error) {
|
||||
if err := f.friend.UpdateFriendEx(ctx, ownerUserID, friendUserID, ex); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.cache.DelFriend(ownerUserID, friendUserID).ExecDel(ctx)
|
||||
}
|
||||
|
||||
@ -98,6 +98,7 @@ type CommonMsgDatabase interface {
|
||||
SetSendMsgStatus(ctx context.Context, id string, status int32) error
|
||||
GetSendMsgStatus(ctx context.Context, id string) (int32, error)
|
||||
SearchMessage(ctx context.Context, req *pbmsg.SearchMessageReq) (total int32, msgData []*sdkws.MsgData, err error)
|
||||
FindOneByDocIDs(ctx context.Context, docIDs []string, seqs map[string]int64) (map[string]*sdkws.MsgData, error)
|
||||
|
||||
// to mq
|
||||
MsgToMQ(ctx context.Context, key string, msg2mq *sdkws.MsgData) error
|
||||
@ -1051,6 +1052,21 @@ func (db *commonMsgDatabase) SearchMessage(ctx context.Context, req *pbmsg.Searc
|
||||
return total, totalMsgs, nil
|
||||
}
|
||||
|
||||
func (db *commonMsgDatabase) FindOneByDocIDs(ctx context.Context, conversationIDs []string, seqs map[string]int64) (map[string]*sdkws.MsgData, error) {
|
||||
totalMsgs := make(map[string]*sdkws.MsgData)
|
||||
for _, conversationID := range conversationIDs {
|
||||
seq := seqs[conversationID]
|
||||
docID := db.msg.GetDocID(conversationID, seq)
|
||||
msgs, err := db.msgDocDatabase.FindOneByDocID(ctx, docID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index := db.msg.GetMsgIndex(seq)
|
||||
totalMsgs[conversationID] = convert.MsgDB2Pb(msgs.Msg[index].Msg)
|
||||
}
|
||||
return totalMsgs, nil
|
||||
}
|
||||
|
||||
func (db *commonMsgDatabase) ConvertMsgsDocLen(ctx context.Context, conversationIDs []string) {
|
||||
db.msgDocDatabase.ConvertMsgsDocLen(ctx, conversationIDs)
|
||||
}
|
||||
|
||||
@ -35,6 +35,8 @@ type S3Database interface {
|
||||
CompleteMultipartUpload(ctx context.Context, uploadID string, parts []string) (*cont.UploadResult, error)
|
||||
AccessURL(ctx context.Context, name string, expire time.Duration, opt *s3.AccessURLOption) (time.Time, string, error)
|
||||
SetObject(ctx context.Context, info *relation.ObjectModel) error
|
||||
StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error)
|
||||
FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error)
|
||||
}
|
||||
|
||||
func NewS3Database(rdb redis.UniversalClient, s3 s3.Interface, obj relation.ObjectInfoModelInterface) S3Database {
|
||||
@ -100,3 +102,11 @@ func (s *s3Database) AccessURL(ctx context.Context, name string, expire time.Dur
|
||||
}
|
||||
return expireTime, rawURL, nil
|
||||
}
|
||||
|
||||
func (s *s3Database) StatObject(ctx context.Context, name string) (*s3.ObjectInfo, error) {
|
||||
return s.s3.StatObject(ctx, name)
|
||||
}
|
||||
|
||||
func (s *s3Database) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) {
|
||||
return s.s3.FormData(ctx, name, size, contentType, duration)
|
||||
}
|
||||
|
||||
@ -38,6 +38,8 @@ type UserDatabase interface {
|
||||
FindWithError(ctx context.Context, userIDs []string) (users []*relation.UserModel, err error)
|
||||
// Find Get the information of the specified user If the userID is not found, no error will be returned
|
||||
Find(ctx context.Context, userIDs []string) (users []*relation.UserModel, err error)
|
||||
// Find userInfo By Nickname
|
||||
FindByNickname(ctx context.Context, nickname string) (users []*relation.UserModel, err error)
|
||||
// Create Insert multiple external guarantees that the userID is not repeated and does not exist in the db
|
||||
Create(ctx context.Context, users []*relation.UserModel) (err error)
|
||||
// Update update (non-zero value) external guarantee userID exists
|
||||
@ -50,6 +52,8 @@ type UserDatabase interface {
|
||||
IsExist(ctx context.Context, userIDs []string) (exist bool, err error)
|
||||
// GetAllUserID Get all user IDs
|
||||
GetAllUserID(ctx context.Context, pagination pagination.Pagination) (int64, []string, error)
|
||||
// Get user by userID
|
||||
GetUserByID(ctx context.Context, userID string) (user *relation.UserModel, err error)
|
||||
// InitOnce Inside the function, first query whether it exists in the db, if it exists, do nothing; if it does not exist, insert it
|
||||
InitOnce(ctx context.Context, users []*relation.UserModel) (err error)
|
||||
// CountTotal Get the total number of users
|
||||
@ -131,6 +135,11 @@ func (u *userDatabase) Find(ctx context.Context, userIDs []string) (users []*rel
|
||||
return u.cache.GetUsersInfo(ctx, userIDs)
|
||||
}
|
||||
|
||||
// Find userInfo By Nickname
|
||||
func (u *userDatabase) FindByNickname(ctx context.Context, nickname string) (users []*relation.UserModel, err error) {
|
||||
return u.userDB.TakeByNickname(ctx, nickname)
|
||||
}
|
||||
|
||||
// Create Insert multiple external guarantees that the userID is not repeated and does not exist in the db.
|
||||
func (u *userDatabase) Create(ctx context.Context, users []*relation.UserModel) (err error) {
|
||||
return u.tx.Transaction(ctx, func(ctx context.Context) error {
|
||||
@ -183,6 +192,10 @@ func (u *userDatabase) GetAllUserID(ctx context.Context, pagination pagination.P
|
||||
return u.userDB.GetAllUserID(ctx, pagination)
|
||||
}
|
||||
|
||||
func (u *userDatabase) GetUserByID(ctx context.Context, userID string) (user *relation.UserModel, err error) {
|
||||
return u.userDB.Take(ctx, userID)
|
||||
}
|
||||
|
||||
// CountTotal Get the total number of users.
|
||||
func (u *userDatabase) CountTotal(ctx context.Context, before *time.Time) (count int64, err error) {
|
||||
return u.userDB.CountTotal(ctx, before)
|
||||
|
||||
@ -160,3 +160,33 @@ func (f *FriendMgo) UpdatePinStatus(ctx context.Context, ownerUserID string, fri
|
||||
|
||||
return nil
|
||||
}
|
||||
func (f *FriendMgo) UpdateFriendRemark(ctx context.Context, ownerUserID string, friendUserID string, remark string) (err error) {
|
||||
|
||||
filter := bson.M{"owner_user_id": ownerUserID, "friend_user_id": friendUserID}
|
||||
// Create an update operation to set the "is_pinned" field to isPinned for all documents.
|
||||
update := bson.M{"$set": bson.M{"remark": remark}}
|
||||
|
||||
// Perform the update operation for all documents in the collection.
|
||||
_, err = f.coll.UpdateMany(ctx, filter, update)
|
||||
|
||||
if err != nil {
|
||||
return errs.Wrap(err, "update remark error")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func (f *FriendMgo) UpdateFriendEx(ctx context.Context, ownerUserID string, friendUserID string, ex string) (err error) {
|
||||
|
||||
filter := bson.M{"owner_user_id": ownerUserID, "friend_user_id": friendUserID}
|
||||
// Create an update operation to set the "is_pinned" field to isPinned for all documents.
|
||||
update := bson.M{"$set": bson.M{"ex": ex}}
|
||||
|
||||
// Perform the update operation for all documents in the collection.
|
||||
_, err = f.coll.UpdateMany(ctx, filter, update)
|
||||
|
||||
if err != nil {
|
||||
return errs.Wrap(err, "update ex error")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -65,6 +65,10 @@ func (u *UserMgo) Take(ctx context.Context, userID string) (user *relation.UserM
|
||||
return mgoutil.FindOne[*relation.UserModel](ctx, u.coll, bson.M{"user_id": userID})
|
||||
}
|
||||
|
||||
func (u *UserMgo) TakeByNickname(ctx context.Context, nickname string) (user []*relation.UserModel, err error) {
|
||||
return mgoutil.Find[*relation.UserModel](ctx, u.coll, bson.M{"nickname": nickname})
|
||||
}
|
||||
|
||||
func (u *UserMgo) Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*relation.UserModel, err error) {
|
||||
return mgoutil.FindPage[*relation.UserModel](ctx, u.coll, bson.M{}, pagination)
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@ package cont
|
||||
const (
|
||||
hashPath = "openim/data/hash/"
|
||||
tempPath = "openim/temp/"
|
||||
DirectPath = "openim/direct"
|
||||
UploadTypeMultipart = 1 // 分片上传
|
||||
UploadTypePresigned = 2 // 预签名上传
|
||||
partSeparator = ","
|
||||
|
||||
@ -279,3 +279,7 @@ func (c *Controller) AccessURL(ctx context.Context, name string, expire time.Dur
|
||||
}
|
||||
return c.impl.AccessURL(ctx, name, expire, opt)
|
||||
}
|
||||
|
||||
func (c *Controller) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) {
|
||||
return c.impl.FormData(ctx, name, size, contentType, duration)
|
||||
}
|
||||
|
||||
@ -16,6 +16,11 @@ package cos
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@ -44,6 +49,8 @@ const (
|
||||
imageWebp = "webp"
|
||||
)
|
||||
|
||||
const successCode = http.StatusOK
|
||||
|
||||
const (
|
||||
videoSnapshotImagePng = "png"
|
||||
videoSnapshotImageJpg = "jpg"
|
||||
@ -326,3 +333,65 @@ func (c *Cos) getPresignedURL(ctx context.Context, name string, expire time.Dura
|
||||
}
|
||||
return c.client.Object.GetObjectURL(name), nil
|
||||
}
|
||||
|
||||
func (c *Cos) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) {
|
||||
// https://cloud.tencent.com/document/product/436/14690
|
||||
now := time.Now()
|
||||
expiration := now.Add(duration)
|
||||
keyTime := fmt.Sprintf("%d;%d", now.Unix(), expiration.Unix())
|
||||
conditions := []any{
|
||||
map[string]string{"q-sign-algorithm": "sha1"},
|
||||
map[string]string{"q-ak": c.credential.SecretID},
|
||||
map[string]string{"q-sign-time": keyTime},
|
||||
map[string]string{"key": name},
|
||||
}
|
||||
if contentType != "" {
|
||||
conditions = append(conditions, map[string]string{"Content-Type": contentType})
|
||||
}
|
||||
policy := map[string]any{
|
||||
"expiration": expiration.Format("2006-01-02T15:04:05.000Z"),
|
||||
"conditions": conditions,
|
||||
}
|
||||
policyJson, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
signKey := hmacSha1val(c.credential.SecretKey, keyTime)
|
||||
strToSign := sha1val(string(policyJson))
|
||||
signature := hmacSha1val(signKey, strToSign)
|
||||
|
||||
fd := &s3.FormData{
|
||||
URL: c.client.BaseURL.BucketURL.String(),
|
||||
File: "file",
|
||||
Expires: expiration,
|
||||
FormData: map[string]string{
|
||||
"policy": base64.StdEncoding.EncodeToString(policyJson),
|
||||
"q-sign-algorithm": "sha1",
|
||||
"q-ak": c.credential.SecretID,
|
||||
"q-key-time": keyTime,
|
||||
"q-signature": signature,
|
||||
"key": name,
|
||||
"success_action_status": strconv.Itoa(successCode),
|
||||
},
|
||||
SuccessCodes: []int{successCode},
|
||||
}
|
||||
if contentType != "" {
|
||||
fd.FormData["Content-Type"] = contentType
|
||||
}
|
||||
if c.credential.SessionToken != "" {
|
||||
fd.FormData["x-cos-security-token"] = c.credential.SessionToken
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
func hmacSha1val(key, msg string) string {
|
||||
v := hmac.New(sha1.New, []byte(key))
|
||||
v.Write([]byte(msg))
|
||||
return hex.EncodeToString(v.Sum(nil))
|
||||
}
|
||||
|
||||
func sha1val(msg string) string {
|
||||
sha1Hash := sha1.New()
|
||||
sha1Hash.Write([]byte(msg))
|
||||
return hex.EncodeToString(sha1Hash.Sum(nil))
|
||||
}
|
||||
|
||||
@ -57,6 +57,8 @@ const (
|
||||
imageThumbnailPath = "openim/thumbnail"
|
||||
)
|
||||
|
||||
const successCode = http.StatusOK
|
||||
|
||||
func NewMinio(cache cache.MinioCache) (s3.Interface, error) {
|
||||
u, err := url.Parse(config.Config.Object.Minio.Endpoint)
|
||||
if err != nil {
|
||||
@ -441,3 +443,51 @@ func (m *Minio) getObjectData(ctx context.Context, name string, limit int64) ([]
|
||||
}
|
||||
return io.ReadAll(io.LimitReader(object, limit))
|
||||
}
|
||||
|
||||
func (m *Minio) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) {
|
||||
if err := m.initMinio(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policy := minio.NewPostPolicy()
|
||||
if err := policy.SetKey(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
expires := time.Now().Add(duration)
|
||||
if err := policy.SetExpires(expires); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if size > 0 {
|
||||
if err := policy.SetContentLengthRange(0, size); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := policy.SetSuccessStatusAction(strconv.Itoa(successCode)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if contentType != "" {
|
||||
if err := policy.SetContentType(contentType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := policy.SetBucket(m.bucket); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u, fd, err := m.core.PresignedPostPolicy(ctx, policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
sign, err := url.Parse(m.signEndpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
u.Scheme = sign.Scheme
|
||||
u.Host = sign.Host
|
||||
return &s3.FormData{
|
||||
URL: u.String(),
|
||||
File: "file",
|
||||
Header: nil,
|
||||
FormData: fd,
|
||||
Expires: expires,
|
||||
SuccessCodes: []int{successCode},
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -16,8 +16,13 @@ package oss
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/hmac"
|
||||
"crypto/sha1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
@ -45,6 +50,8 @@ const (
|
||||
imageWebp = "webp"
|
||||
)
|
||||
|
||||
const successCode = http.StatusOK
|
||||
|
||||
const (
|
||||
videoSnapshotImagePng = "png"
|
||||
videoSnapshotImageJpg = "jpg"
|
||||
@ -327,3 +334,45 @@ func (o *OSS) AccessURL(ctx context.Context, name string, expire time.Duration,
|
||||
params := getURLParams(*o.bucket.Client.Conn, rawParams)
|
||||
return getURL(o.um, o.bucket.BucketName, name, params).String(), nil
|
||||
}
|
||||
|
||||
func (o *OSS) FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*s3.FormData, error) {
|
||||
// https://help.aliyun.com/zh/oss/developer-reference/postobject?spm=a2c4g.11186623.0.0.1cb83cebkP55nn
|
||||
expires := time.Now().Add(duration)
|
||||
conditions := []any{
|
||||
map[string]string{"bucket": o.bucket.BucketName},
|
||||
map[string]string{"key": name},
|
||||
}
|
||||
if size > 0 {
|
||||
conditions = append(conditions, []any{"content-length-range", 0, size})
|
||||
}
|
||||
policy := map[string]any{
|
||||
"expiration": expires.Format("2006-01-02T15:04:05.000Z"),
|
||||
"conditions": conditions,
|
||||
}
|
||||
policyJson, err := json.Marshal(policy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
policyStr := base64.StdEncoding.EncodeToString(policyJson)
|
||||
h := hmac.New(sha1.New, []byte(o.credentials.GetAccessKeySecret()))
|
||||
if _, err := io.WriteString(h, policyStr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fd := &s3.FormData{
|
||||
URL: o.bucketURL,
|
||||
File: "file",
|
||||
Expires: expires,
|
||||
FormData: map[string]string{
|
||||
"key": name,
|
||||
"policy": policyStr,
|
||||
"OSSAccessKeyId": o.credentials.GetAccessKeyID(),
|
||||
"success_action_status": strconv.Itoa(successCode),
|
||||
"signature": base64.StdEncoding.EncodeToString(h.Sum(nil)),
|
||||
},
|
||||
SuccessCodes: []int{successCode},
|
||||
}
|
||||
if contentType != "" {
|
||||
fd.FormData["x-oss-content-type"] = contentType
|
||||
}
|
||||
return fd, nil
|
||||
}
|
||||
|
||||
@ -74,6 +74,15 @@ type CopyObjectInfo struct {
|
||||
ETag string `json:"etag"`
|
||||
}
|
||||
|
||||
type FormData struct {
|
||||
URL string `json:"url"`
|
||||
File string `json:"file"`
|
||||
Header http.Header `json:"header"`
|
||||
FormData map[string]string `json:"form"`
|
||||
Expires time.Time `json:"expires"`
|
||||
SuccessCodes []int `json:"successActionStatus"`
|
||||
}
|
||||
|
||||
type SignPart struct {
|
||||
PartNumber int `json:"partNumber"`
|
||||
URL string `json:"url"`
|
||||
@ -152,4 +161,6 @@ type Interface interface {
|
||||
ListUploadedParts(ctx context.Context, uploadID string, name string, partNumberMarker int, maxParts int) (*ListUploadedPartsResult, error)
|
||||
|
||||
AccessURL(ctx context.Context, name string, expire time.Duration, opt *AccessURLOption) (string, error)
|
||||
|
||||
FormData(ctx context.Context, name string, size int64, contentType string, duration time.Duration) (*FormData, error)
|
||||
}
|
||||
|
||||
@ -59,4 +59,8 @@ type FriendModelInterface interface {
|
||||
FindFriendUserIDs(ctx context.Context, ownerUserID string) (friendUserIDs []string, err error)
|
||||
// UpdatePinStatus update friend's pin status
|
||||
UpdatePinStatus(ctx context.Context, ownerUserID string, friendUserID string, isPinned bool) (err error)
|
||||
// UpdateFriendRemark update friend's remark
|
||||
UpdateFriendRemark(ctx context.Context, ownerUserID string, friendUserID string, remark string) (err error)
|
||||
// UpdateFriendEx update friend's ex
|
||||
UpdateFriendEx(ctx context.Context, ownerUserID string, friendUserID string, ex string) (err error)
|
||||
}
|
||||
|
||||
@ -53,6 +53,7 @@ type UserModelInterface interface {
|
||||
UpdateByMap(ctx context.Context, userID string, args map[string]any) (err error)
|
||||
Find(ctx context.Context, userIDs []string) (users []*UserModel, err error)
|
||||
Take(ctx context.Context, userID string) (user *UserModel, err error)
|
||||
TakeByNickname(ctx context.Context, nickname string) (user []*UserModel, err error)
|
||||
Page(ctx context.Context, pagination pagination.Pagination) (count int64, users []*UserModel, err error)
|
||||
Exist(ctx context.Context, userID string) (exist bool, err error)
|
||||
GetAllUserID(ctx context.Context, pagination pagination.Pagination) (count int64, userIDs []string, err error)
|
||||
|
||||
@ -74,6 +74,10 @@ func buildMongoURI() string {
|
||||
return uri
|
||||
}
|
||||
|
||||
if config.Config.Mongo.Uri != "" {
|
||||
return config.Config.Mongo.Uri
|
||||
}
|
||||
|
||||
username := os.Getenv("MONGO_USERNAME")
|
||||
password := os.Getenv("MONGO_PASSWORD")
|
||||
address := os.Getenv("MONGO_ADDRESS")
|
||||
|
||||
@ -24,7 +24,8 @@ import (
|
||||
|
||||
func setupTestEnvironment() {
|
||||
os.Setenv("ZOOKEEPER_SCHEMA", "openim")
|
||||
os.Setenv("ZOOKEEPER_ADDRESS", "172.28.0.1:12181")
|
||||
os.Setenv("ZOOKEEPER_ADDRESS", "172.28.0.1")
|
||||
os.Setenv("ZOOKEEPER_PORT", "12181")
|
||||
os.Setenv("ZOOKEEPER_USERNAME", "")
|
||||
os.Setenv("ZOOKEEPER_PASSWORD", "")
|
||||
}
|
||||
|
||||
@ -18,6 +18,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/stathat/consistent"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -31,51 +32,57 @@ import (
|
||||
|
||||
// K8sDR represents the Kubernetes service discovery and registration client.
|
||||
type K8sDR struct {
|
||||
options []grpc.DialOption
|
||||
rpcRegisterAddr string
|
||||
options []grpc.DialOption
|
||||
rpcRegisterAddr string
|
||||
gatewayHostConsistent *consistent.Consistent
|
||||
}
|
||||
|
||||
// NewK8sDiscoveryRegister creates a new instance of K8sDR for Kubernetes service discovery and registration.
|
||||
func NewK8sDiscoveryRegister() (discoveryregistry.SvcDiscoveryRegistry, error) {
|
||||
|
||||
return &K8sDR{}, nil
|
||||
gatewayConsistent := consistent.New()
|
||||
gatewayHosts := getMsgGatewayHost(context.Background())
|
||||
for _, v := range gatewayHosts {
|
||||
gatewayConsistent.Add(v)
|
||||
}
|
||||
return &K8sDR{gatewayHostConsistent: gatewayConsistent}, nil
|
||||
}
|
||||
|
||||
// Register registers a service with Kubernetes.
|
||||
func (cli *K8sDR) Register(serviceName, host string, port int, opts ...grpc.DialOption) error {
|
||||
if serviceName != config.Config.RpcRegisterName.OpenImMessageGatewayName {
|
||||
cli.rpcRegisterAddr = serviceName
|
||||
} else {
|
||||
cli.rpcRegisterAddr = cli.getSelfHost(context.Background())
|
||||
cli.rpcRegisterAddr = getSelfHost(context.Background())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnRegister removes a service registration from Kubernetes.
|
||||
func (cli *K8sDR) UnRegister() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateRpcRootNodes creates root nodes for RPC in Kubernetes.
|
||||
func (cli *K8sDR) CreateRpcRootNodes(serviceNames []string) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RegisterConf2Registry registers a configuration to the registry.
|
||||
func (cli *K8sDR) RegisterConf2Registry(key string, conf []byte) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfFromRegistry retrieves a configuration from the registry.
|
||||
func (cli *K8sDR) GetConfFromRegistry(key string) ([]byte, error) {
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (cli *K8sDR) getSelfHost(ctx context.Context) string {
|
||||
func (cli *K8sDR) GetUserIdHashGatewayHost(ctx context.Context, userId string) (string, error) {
|
||||
host, err := cli.gatewayHostConsistent.Get(userId)
|
||||
if err != nil {
|
||||
log.ZError(ctx, "GetUserIdHashGatewayHost error", err)
|
||||
}
|
||||
return host, err
|
||||
}
|
||||
func getSelfHost(ctx context.Context) string {
|
||||
port := 88
|
||||
instance := "openimserver"
|
||||
selfPodName := os.Getenv("MY_POD_NAME")
|
||||
@ -95,26 +102,8 @@ func (cli *K8sDR) getSelfHost(ctx context.Context) string {
|
||||
return host
|
||||
}
|
||||
|
||||
// GetConns returns a list of gRPC client connections for a given service.
|
||||
func (cli *K8sDR) GetConns(ctx context.Context, serviceName string, opts ...grpc.DialOption) ([]*grpc.ClientConn, error) {
|
||||
if serviceName != config.Config.RpcRegisterName.OpenImMessageGatewayName {
|
||||
conn, err := grpc.DialContext(ctx, serviceName, append(cli.options, opts...)...)
|
||||
return []*grpc.ClientConn{conn}, err
|
||||
}
|
||||
var ret []*grpc.ClientConn
|
||||
gatewayHosts := cli.getMsgGatewayHost(ctx)
|
||||
for _, host := range gatewayHosts {
|
||||
conn, err := grpc.DialContext(ctx, host, append(cli.options, opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret = append(ret, conn)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// like openimserver-openim-msggateway-0.openimserver-openim-msggateway-headless.openim-lin.svc.cluster.local:88
|
||||
func (cli *K8sDR) getMsgGatewayHost(ctx context.Context) []string {
|
||||
func getMsgGatewayHost(ctx context.Context) []string {
|
||||
port := 88
|
||||
instance := "openimserver"
|
||||
selfPodName := os.Getenv("MY_POD_NAME")
|
||||
@ -135,40 +124,53 @@ func (cli *K8sDR) getMsgGatewayHost(ctx context.Context) []string {
|
||||
ret = append(ret, host)
|
||||
}
|
||||
log.ZInfo(ctx, "getMsgGatewayHost", "instance", instance, "selfPodName", selfPodName, "replicas", replicas, "ns", ns, "ret", ret)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
// GetConn returns a single gRPC client connection for a given service.
|
||||
// GetConns returns the gRPC client connections to the specified service.
|
||||
func (cli *K8sDR) GetConns(ctx context.Context, serviceName string, opts ...grpc.DialOption) ([]*grpc.ClientConn, error) {
|
||||
|
||||
if serviceName != config.Config.RpcRegisterName.OpenImMessageGatewayName {
|
||||
conn, err := grpc.DialContext(ctx, serviceName, append(cli.options, opts...)...)
|
||||
return []*grpc.ClientConn{conn}, err
|
||||
} else {
|
||||
var ret []*grpc.ClientConn
|
||||
gatewayHosts := getMsgGatewayHost(ctx)
|
||||
for _, host := range gatewayHosts {
|
||||
conn, err := grpc.DialContext(ctx, host, append(cli.options, opts...)...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
ret = append(ret, conn)
|
||||
}
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (cli *K8sDR) GetConn(ctx context.Context, serviceName string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
|
||||
|
||||
return grpc.DialContext(ctx, serviceName, append(cli.options, opts...)...)
|
||||
}
|
||||
|
||||
// GetSelfConnTarget returns the connection target of the client itself.
|
||||
func (cli *K8sDR) GetSelfConnTarget() string {
|
||||
|
||||
return cli.rpcRegisterAddr
|
||||
}
|
||||
|
||||
// AddOption adds gRPC dial options to the client.
|
||||
func (cli *K8sDR) AddOption(opts ...grpc.DialOption) {
|
||||
cli.options = append(cli.options, opts...)
|
||||
}
|
||||
|
||||
// CloseConn closes a given gRPC client connection.
|
||||
func (cli *K8sDR) CloseConn(conn *grpc.ClientConn) {
|
||||
conn.Close()
|
||||
}
|
||||
|
||||
// do not use this method for call rpc.
|
||||
// do not use this method for call rpc
|
||||
func (cli *K8sDR) GetClientLocalConns() map[string][]*grpc.ClientConn {
|
||||
fmt.Println("should not call this function!!!!!!!!!!!!!!!!!!!!!!!!!")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the K8sDR client.
|
||||
func (cli *K8sDR) Close() {
|
||||
|
||||
// Close any open resources here (if applicable)
|
||||
return
|
||||
}
|
||||
|
||||
@ -52,10 +52,18 @@ func getEnv(key, fallback string) string {
|
||||
return fallback
|
||||
}
|
||||
|
||||
// getZkAddrFromEnv returns the value of an environment variable if it exists, otherwise it returns the fallback value.
|
||||
// getZkAddrFromEnv returns the Zookeeper addresses combined from the ZOOKEEPER_ADDRESS and ZOOKEEPER_PORT environment variables.
|
||||
// If the environment variables are not set, it returns the fallback value.
|
||||
func getZkAddrFromEnv(fallback []string) []string {
|
||||
if value, exists := os.LookupEnv("ZOOKEEPER_ADDRESS"); exists {
|
||||
return strings.Split(value, ",")
|
||||
address, addrExists := os.LookupEnv("ZOOKEEPER_ADDRESS")
|
||||
port, portExists := os.LookupEnv("ZOOKEEPER_PORT")
|
||||
|
||||
if addrExists && portExists {
|
||||
addresses := strings.Split(address, ",")
|
||||
for i, addr := range addresses {
|
||||
addresses[i] = addr + ":" + port
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
return fallback
|
||||
}
|
||||
|
||||
@ -432,7 +432,7 @@ func computeApproximateRequestSize(r *http.Request) int {
|
||||
}
|
||||
s += len(r.Host)
|
||||
|
||||
// r.Form 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)
|
||||
|
||||
@ -112,7 +112,6 @@ func callBackPostReturn(ctx context.Context, url, command string, input interfac
|
||||
//v.Set(constant.CallbackCommand, command)
|
||||
//url = url + "/" + v.Encode()
|
||||
url = url + "/" + command
|
||||
|
||||
b, err := Post(ctx, url, nil, input, callbackConfig.CallbackTimeOut)
|
||||
if err != nil {
|
||||
if callbackConfig.CallbackFailedContinue != nil && *callbackConfig.CallbackFailedContinue {
|
||||
@ -121,6 +120,7 @@ func callBackPostReturn(ctx context.Context, url, command string, input interfac
|
||||
}
|
||||
return errs.ErrNetwork.Wrap(err.Error())
|
||||
}
|
||||
defer log.ZDebug(ctx, "callback", "data", string(b))
|
||||
|
||||
if err = json.Unmarshal(b, output); err != nil {
|
||||
if callbackConfig.CallbackFailedContinue != nil && *callbackConfig.CallbackFailedContinue {
|
||||
|
||||
@ -31,15 +31,14 @@ import (
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
)
|
||||
|
||||
const (
|
||||
maxRetry = 10 // Maximum number of retries for producer creation
|
||||
)
|
||||
const maxRetry = 10 // number of retries
|
||||
|
||||
var errEmptyMsg = errors.New("binary msg is empty")
|
||||
var errEmptyMsg = errors.New("kafka binary msg is empty")
|
||||
|
||||
// Producer represents a Kafka producer.
|
||||
type Producer struct {
|
||||
topic string
|
||||
addr []string
|
||||
topic string
|
||||
config *sarama.Config
|
||||
producer sarama.SyncProducer
|
||||
}
|
||||
@ -68,7 +67,7 @@ func NewKafkaProducer(addr []string, topic string) *Producer {
|
||||
// Get Kafka configuration from environment variables or fallback to config file
|
||||
kafkaUsername := getEnvOrConfig("KAFKA_USERNAME", config.Config.Kafka.Username)
|
||||
kafkaPassword := getEnvOrConfig("KAFKA_PASSWORD", config.Config.Kafka.Password)
|
||||
kafkaAddr := getEnvOrConfig("KAFKA_ADDRESS", addr[0]) // Assuming addr[0] contains address from config
|
||||
kafkaAddr := getKafkaAddrFromEnv(addr) // Updated to use the new function
|
||||
|
||||
// Configure SASL authentication if credentials are provided
|
||||
if kafkaUsername != "" && kafkaPassword != "" {
|
||||
@ -78,7 +77,7 @@ func NewKafkaProducer(addr []string, topic string) *Producer {
|
||||
}
|
||||
|
||||
// Set the Kafka address
|
||||
p.addr = []string{kafkaAddr}
|
||||
p.addr = kafkaAddr
|
||||
|
||||
// Set up TLS configuration (if required)
|
||||
SetupTLSConfig(p.config)
|
||||
|
||||
@ -15,7 +15,9 @@
|
||||
package kafka
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/IBM/sarama"
|
||||
|
||||
@ -44,3 +46,20 @@ func getEnvOrConfig(envName string, configValue string) string {
|
||||
}
|
||||
return configValue
|
||||
}
|
||||
|
||||
// getKafkaAddrFromEnv returns the Kafka addresses combined from the KAFKA_ADDRESS and KAFKA_PORT environment variables.
|
||||
// If the environment variables are not set, it returns the fallback value.
|
||||
func getKafkaAddrFromEnv(fallback []string) []string {
|
||||
envAddr := os.Getenv("KAFKA_ADDRESS")
|
||||
envPort := os.Getenv("KAFKA_PORT")
|
||||
|
||||
if envAddr != "" && envPort != "" {
|
||||
addresses := strings.Split(envAddr, ",")
|
||||
for i, addr := range addresses {
|
||||
addresses[i] = fmt.Sprintf("%s:%s", addr, envPort)
|
||||
}
|
||||
return addresses
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
@ -17,7 +17,6 @@ package rpcclient
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
@ -68,6 +67,7 @@ func newContentTypeConf() map[int32]config.NotificationConf {
|
||||
constant.BlackAddedNotification: config.Config.Notification.BlackAdded,
|
||||
constant.BlackDeletedNotification: config.Config.Notification.BlackDeleted,
|
||||
constant.FriendInfoUpdatedNotification: config.Config.Notification.FriendInfoUpdated,
|
||||
constant.FriendsInfoUpdateNotification: config.Config.Notification.FriendInfoUpdated, //use the same FriendInfoUpdated
|
||||
// conversation
|
||||
constant.ConversationChangeNotification: config.Config.Notification.ConversationChanged,
|
||||
constant.ConversationUnreadNotification: config.Config.Notification.ConversationChanged,
|
||||
@ -115,6 +115,7 @@ func newSessionTypeConf() map[int32]int32 {
|
||||
constant.BlackAddedNotification: constant.SingleChatType,
|
||||
constant.BlackDeletedNotification: constant.SingleChatType,
|
||||
constant.FriendInfoUpdatedNotification: constant.SingleChatType,
|
||||
constant.FriendsInfoUpdateNotification: constant.SingleChatType,
|
||||
// conversation
|
||||
constant.ConversationChangeNotification: constant.SingleChatType,
|
||||
constant.ConversationUnreadNotification: constant.SingleChatType,
|
||||
@ -155,6 +156,30 @@ func (m *MessageRpcClient) GetMaxSeq(ctx context.Context, req *sdkws.GetMaxSeqRe
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (m *MessageRpcClient) GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) {
|
||||
log.ZDebug(ctx, "GetMaxSeqs", "conversationIDs", conversationIDs)
|
||||
resp, err := m.Client.GetMaxSeqs(ctx, &msg.GetMaxSeqsReq{
|
||||
ConversationIDs: conversationIDs,
|
||||
})
|
||||
return resp.MaxSeqs, err
|
||||
}
|
||||
|
||||
func (m *MessageRpcClient) GetHasReadSeqs(ctx context.Context, userID string, conversationIDs []string) (map[string]int64, error) {
|
||||
resp, err := m.Client.GetHasReadSeqs(ctx, &msg.GetHasReadSeqsReq{
|
||||
UserID: userID,
|
||||
ConversationIDs: conversationIDs,
|
||||
})
|
||||
return resp.MaxSeqs, err
|
||||
}
|
||||
|
||||
func (m *MessageRpcClient) GetMsgByConversationIDs(ctx context.Context, docIDs []string, seqs map[string]int64) (map[string]*sdkws.MsgData, error) {
|
||||
resp, err := m.Client.GetMsgByConversationIDs(ctx, &msg.GetMsgByConversationIDsReq{
|
||||
ConversationIDs: docIDs,
|
||||
MaxSeqs: seqs,
|
||||
})
|
||||
return resp.MsgDatas, err
|
||||
}
|
||||
|
||||
func (m *MessageRpcClient) PullMessageBySeqList(ctx context.Context, req *sdkws.PullMessageBySeqsReq) (*sdkws.PullMessageBySeqsResp, error) {
|
||||
resp, err := m.Client.PullMessageBySeqs(ctx, req)
|
||||
return resp, err
|
||||
|
||||
@ -196,7 +196,12 @@ func (f *FriendNotificationSender) FriendRemarkSetNotification(ctx context.Conte
|
||||
tips.FromToUserID.ToUserID = toUserID
|
||||
return f.Notification(ctx, fromUserID, toUserID, constant.FriendRemarkSetNotification, &tips)
|
||||
}
|
||||
|
||||
func (f *FriendNotificationSender) FriendsInfoUpdateNotification(ctx context.Context, toUserID string, friendIDs []string) error {
|
||||
tips := sdkws.FriendsInfoUpdateTips{}
|
||||
tips.FromToUserID.ToUserID = toUserID
|
||||
tips.FriendIDs = friendIDs
|
||||
return f.Notification(ctx, toUserID, toUserID, constant.FriendsInfoUpdateNotification, &tips)
|
||||
}
|
||||
func (f *FriendNotificationSender) BlackAddedNotification(ctx context.Context, req *pbfriend.AddBlackReq) error {
|
||||
tips := sdkws.BlackAddedTips{FromToUserID: &sdkws.FromToUserID{}}
|
||||
tips.FromToUserID.FromUserID = req.OwnerUserID
|
||||
|
||||
@ -179,3 +179,10 @@ func (u *UserRpcClient) SetUserStatus(ctx context.Context, userID string, status
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (u *UserRpcClient) GetNotificationByID(ctx context.Context, userID string) error {
|
||||
_, err := u.Client.GetNotificationAccount(ctx, &user.GetNotificationAccountReq{
|
||||
UserID: userID,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
@ -30,29 +30,32 @@ OPENIM_VERBOSE=4
|
||||
|
||||
openim::log::info "\n# Begin to check all openim service"
|
||||
|
||||
# OpenIM status
|
||||
# Elegant printing function
|
||||
# Elegant printing function
|
||||
print_services_and_ports() {
|
||||
local -n service_names=$1
|
||||
local -n service_ports=$2
|
||||
local service_names=("$@")
|
||||
local half_length=$((${#service_names[@]} / 2))
|
||||
local service_ports=("${service_names[@]:half_length}")
|
||||
|
||||
echo "+-------------------------+----------+"
|
||||
echo "| Service Name | Port |"
|
||||
echo "+-------------------------+----------+"
|
||||
|
||||
for index in "${!service_names[@]}"; do
|
||||
for ((index=0; index < half_length; index++)); do
|
||||
printf "| %-23s | %-8s |\n" "${service_names[$index]}" "${service_ports[$index]}"
|
||||
done
|
||||
|
||||
echo "+-------------------------+----------+"
|
||||
}
|
||||
|
||||
# Assuming OPENIM_SERVER_NAME_TARGETS and OPENIM_SERVER_PORT_TARGETS are defined
|
||||
# Similarly for OPENIM_DEPENDENCY_TARGETS and OPENIM_DEPENDENCY_PORT_TARGETS
|
||||
|
||||
# Print out services and their ports
|
||||
print_services_and_ports OPENIM_SERVER_NAME_TARGETS OPENIM_SERVER_PORT_TARGETS
|
||||
print_services_and_ports "${OPENIM_SERVER_NAME_TARGETS[@]}" "${OPENIM_SERVER_PORT_TARGETS[@]}"
|
||||
|
||||
# Print out dependencies and their ports
|
||||
print_services_and_ports OPENIM_DEPENDENCY_TARGETS OPENIM_DEPENDENCY_PORT_TARGETS
|
||||
|
||||
print_services_and_ports "${OPENIM_DEPENDENCY_TARGETS[@]}" "${OPENIM_DEPENDENCY_PORT_TARGETS[@]}"
|
||||
|
||||
# OpenIM check
|
||||
echo "++ The port being checked: ${OPENIM_SERVER_PORT_LISTARIES[@]}"
|
||||
|
||||
@ -285,7 +285,6 @@ readonly ALERTMANAGER_SEND_RESOLVED=${ALERTMANAGER_SEND_RESOLVED:-"{SEND_RESOLVE
|
||||
###################### Grafana 配置信息 ######################
|
||||
def "GRAFANA_PORT" "13000" # Grafana的端口
|
||||
def "GRAFANA_ADDRESS" "${DOCKER_BRIDGE_GATEWAY}" # Grafana的地址
|
||||
|
||||
###################### RPC Port Configuration Variables ######################
|
||||
# For launching multiple programs, just fill in multiple ports separated by commas
|
||||
# For example:
|
||||
@ -353,6 +352,8 @@ def "MANAGER_USERID_3" "openIMAdmin" # 管理员ID 3
|
||||
def "NICKNAME_1" "system1" # 昵称1
|
||||
def "NICKNAME_2" "system2" # 昵称2
|
||||
def "NICKNAME_3" "system3" # 昵称3
|
||||
def "IM_ADMIN_USERID" "imAdmin" # IM管理员ID
|
||||
def "IM_ADMIN_NAME" "imAdmin" # IM管理员昵称
|
||||
def "MULTILOGIN_POLICY" "1" # 多登录策略
|
||||
def "CHAT_PERSISTENCE_MYSQL" "true" # 聊天持久化MySQL
|
||||
def "MSG_CACHE_TIMEOUT" "86400" # 消息缓存超时
|
||||
@ -376,8 +377,8 @@ def "CALLBACK_TIMEOUT" "5" # 最长超时时间
|
||||
def "CALLBACK_FAILED_CONTINUE" "true" # 失败后是否继续
|
||||
###################### Prometheus 配置信息 ######################
|
||||
# 是否启用 Prometheus
|
||||
readonly PROMETHEUS_ENABLE=${PROMETHEUS_ENABLE:-'false'}
|
||||
def "PROMETHEUS_URL" "${GRAFANA_ADDRESS}:${GRAFANA_PORT}"
|
||||
readonly PROMETHEUS_ENABLE=${PROMETHEUS_ENABLE:-'true'}
|
||||
readonly GRAFANA_URL=${GRAFANA_URL:-"http://${OPENIM_IP}:${GRAFANA_PORT}/"}
|
||||
# Api 服务的 Prometheus 端口
|
||||
readonly API_PROM_PORT=${API_PROM_PORT:-'20100'}
|
||||
# User 服务的 Prometheus 端口
|
||||
|
||||
@ -16,6 +16,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net"
|
||||
@ -222,8 +223,8 @@ func checkMinio() (string, error) {
|
||||
defer cancel()
|
||||
|
||||
if minioClient.IsOffline() {
|
||||
str := fmt.Sprintf("Minio server is offline;%s", str)
|
||||
return "", ErrComponentStart.Wrap(str)
|
||||
// str := fmt.Sprintf("Minio server is offline;%s", str)
|
||||
// return "", ErrComponentStart.Wrap(str)
|
||||
}
|
||||
|
||||
// Check for localhost in API URL and Minio SignEndpoint
|
||||
@ -285,10 +286,23 @@ func checkZookeeper() (string, error) {
|
||||
|
||||
// Connect to Zookeeper
|
||||
str := "the addr is:" + address
|
||||
c, _, err := zk.Connect(zookeeperAddresses, time.Second) // Adjust the timeout as necessary
|
||||
c, eventChan, err := zk.Connect(zookeeperAddresses, time.Second) // Adjust the timeout as necessary
|
||||
if err != nil {
|
||||
return "", errs.Wrap(errStr(err, str))
|
||||
}
|
||||
timeout := time.After(5 * time.Second)
|
||||
for {
|
||||
select {
|
||||
case event := <-eventChan:
|
||||
if event.State == zk.StateConnected {
|
||||
fmt.Println("Connected to Zookeeper")
|
||||
goto Connected
|
||||
}
|
||||
case <-timeout:
|
||||
return "", errs.Wrap(errors.New("timeout waiting for Zookeeper connection"), "Zookeeper Addr: "+strings.Join(config.Config.Zookeeper.ZkAddr, " "))
|
||||
}
|
||||
}
|
||||
Connected:
|
||||
defer c.Close()
|
||||
|
||||
// Set authentication if username and password are provided
|
||||
@ -298,12 +312,6 @@ func checkZookeeper() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Check if Zookeeper is reachable
|
||||
_, _, err = c.Get("/")
|
||||
if err != nil {
|
||||
return "", errs.Wrap(err, str)
|
||||
}
|
||||
|
||||
return str, nil
|
||||
}
|
||||
|
||||
|
||||
@ -21,22 +21,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/redis/go-redis/v9"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/openimsdk/open-im-server/v3/pkg/common/config"
|
||||
)
|
||||
|
||||
func TestCheckMysql(t *testing.T) {
|
||||
err := mockInitCfg()
|
||||
assert.NoError(t, err, "Initialization should not produce errors")
|
||||
|
||||
err = checkMysql()
|
||||
if err != nil {
|
||||
// You might expect an error if MySQL isn't running locally with the mock credentials.
|
||||
t.Logf("Expected error due to mock configuration: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Mock for initCfg for testing purpose
|
||||
func mockInitCfg() error {
|
||||
config.Config.Mysql.Username = "root"
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user