复制项目

This commit is contained in:
kim.dev.6789
2026-01-14 22:16:44 +08:00
parent e2577b8cee
commit e50142a3b9
691 changed files with 97009 additions and 1 deletions

View File

@@ -0,0 +1,28 @@
package apistruct
type GetConfigReq struct {
ConfigName string `json:"configName"`
}
type GetConfigListResp struct {
Environment string `json:"environment"`
Version string `json:"version"`
ConfigNames []string `json:"configNames"`
}
type SetConfigReq struct {
ConfigName string `json:"configName"`
Data string `json:"data"`
}
type SetConfigsReq struct {
Configs []SetConfigReq `json:"configs"`
}
type SetEnableConfigManagerReq struct {
Enable bool `json:"enable"`
}
type GetEnableConfigManagerResp struct {
Enable bool `json:"enable"`
}

15
pkg/apistruct/doc.go Normal file
View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apistruct // import "git.imall.cloud/openim/open-im-server-deploy/pkg/apistruct"

584
pkg/apistruct/manage.go Normal file
View File

@@ -0,0 +1,584 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apistruct
import (
"encoding/json"
"fmt"
"strconv"
pbmsg "git.imall.cloud/openim/protocol/msg"
"git.imall.cloud/openim/protocol/sdkws"
)
// SendMsg defines the structure for sending messages with various metadata.
type SendMsg struct {
// SendID uniquely identifies the sender.
SendID string `json:"sendID" binding:"required"`
// GroupID is the identifier for the group, required if SessionType is 2 or 3.
GroupID string `json:"groupID" binding:"required_if=SessionType 2|required_if=SessionType 3"`
// SenderNickname is the nickname of the sender.
SenderNickname string `json:"senderNickname"`
// SenderFaceURL is the URL to the sender's avatar.
SenderFaceURL string `json:"senderFaceURL"`
// SenderPlatformID is an integer identifier for the sender's platform.
SenderPlatformID int32 `json:"senderPlatformID"`
// Content is the actual content of the message, required and excluded from Swagger documentation.
Content map[string]any `json:"content" binding:"required" swaggerignore:"true"`
// ContentType is an integer that represents the type of the content.
ContentType int32 `json:"contentType" binding:"required"`
// SessionType is an integer that represents the type of session for the message.
SessionType int32 `json:"sessionType" binding:"required"`
// IsOnlineOnly specifies if the message is only sent when the receiver is online.
IsOnlineOnly bool `json:"isOnlineOnly"`
// NotOfflinePush specifies if the message should not trigger offline push notifications.
NotOfflinePush bool `json:"notOfflinePush"`
// SendTime is a timestamp indicating when the message was sent.
SendTime int64 `json:"sendTime"`
// OfflinePushInfo contains information for offline push notifications.
OfflinePushInfo *sdkws.OfflinePushInfo `json:"offlinePushInfo"`
// Ex stores extended fields
Ex string `json:"ex"`
}
// SendMsgReq extends SendMsg with the requirement of RecvID when SessionType indicates a one-on-one or notification chat.
type SendMsgReq struct {
// RecvID uniquely identifies the receiver and is required for one-on-one or notification chat types.
RecvID string `json:"recvID" binding:"required_if" message:"recvID is required if sessionType is SingleChatType or NotificationChatType"`
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
// IsSendAll indicates whether the message should be sent to all users.
IsSendAll bool `json:"isSendAll"`
// RecvIDs is a slice of receiver identifiers to whom the message will be sent, required field.
RecvIDs []string `json:"recvIDs" binding:"required"`
}
// BatchSendMsgResp contains the results of a batch message send operation.
type BatchSendMsgResp struct {
// Results is a slice of SingleReturnResult, representing the outcome of each message sent.
Results []*SingleReturnResult `json:"results"`
// FailedIDs is a slice of user IDs for whom the message send failed.
FailedIDs []string `json:"failedUserIDs"`
}
// SendRedPacketReq 发送红包请求
type SendRedPacketReq struct {
GroupID string `json:"groupID" binding:"required"` // 群ID
RedPacketType int32 `json:"redPacketType" binding:"required"` // 红包类型1-普通红包2-拼手气红包
TotalAmount int64 `json:"totalAmount" binding:"required"` // 总金额(分)
TotalCount int32 `json:"totalCount" binding:"required"` // 总个数
Blessing string `json:"blessing"` // 祝福语
}
// SendRedPacketResp 发送红包响应
type SendRedPacketResp struct {
RedPacketID string `json:"redPacketID"` // 红包ID
ServerMsgID string `json:"serverMsgID"` // 消息ID
ClientMsgID string `json:"clientMsgID"` // 客户端消息ID
SendTime int64 `json:"sendTime"` // 发送时间
}
// ReceiveRedPacketReq 领取红包请求
type ReceiveRedPacketReq struct {
RedPacketID string `json:"redPacketID" binding:"required"` // 红包ID
}
// ReceiveRedPacketResp 领取红包响应
type ReceiveRedPacketResp struct {
RedPacketID string `json:"redPacketID"` // 红包ID
Amount int64 `json:"amount"` // 领取金额(分)
IsLucky bool `json:"isLucky"` // 是否为手气最佳(仅拼手气红包有效)
}
// GetRedPacketsByGroupReq 根据群ID查询红包列表请求群ID为选填不填则查询所有红包
type GetRedPacketsByGroupReq struct {
GroupID string `json:"groupID"` // 群ID选填不填则查询所有红包
Pagination Pagination `json:"pagination"` // 分页参数
}
// Pagination 分页参数
type Pagination struct {
PageNumber int32 `json:"pageNumber"` // 页码从1开始
ShowNumber int32 `json:"showNumber"` // 每页数量
}
// GetRedPacketsByGroupResp 根据群ID查询红包列表响应
type GetRedPacketsByGroupResp struct {
Total int64 `json:"total"` // 总数
RedPackets []*RedPacketInfo `json:"redPackets"` // 红包列表
}
// RedPacketInfo 红包信息
type RedPacketInfo struct {
RedPacketID string `json:"redPacketID"` // 红包ID
SendUserID string `json:"sendUserID"` // 发送者ID
GroupID string `json:"groupID"` // 群ID
GroupName string `json:"groupName"` // 群名称
RedPacketType int32 `json:"redPacketType"` // 红包类型1-普通红包2-拼手气红包
TotalAmount int64 `json:"totalAmount"` // 总金额(分)
TotalCount int32 `json:"totalCount"` // 总个数
RemainAmount int64 `json:"remainAmount"` // 剩余金额(分)
RemainCount int32 `json:"remainCount"` // 剩余个数
Blessing string `json:"blessing"` // 祝福语
Status int32 `json:"status"` // 状态0-进行中1-已领完2-已过期
ExpireTime int64 `json:"expireTime"` // 过期时间戳(毫秒)
CreateTime int64 `json:"createTime"` // 创建时间戳(毫秒)
}
// GetRedPacketReceiveInfoReq 查询红包领取情况请求
type GetRedPacketReceiveInfoReq struct {
RedPacketID string `json:"redPacketID" binding:"required"` // 红包ID
}
// GetRedPacketReceiveInfoResp 查询红包领取情况响应
type GetRedPacketReceiveInfoResp struct {
RedPacketID string `json:"redPacketID"` // 红包ID
TotalAmount int64 `json:"totalAmount"` // 总金额(分)
TotalCount int32 `json:"totalCount"` // 总个数
RemainAmount int64 `json:"remainAmount"` // 剩余金额(分)
RemainCount int32 `json:"remainCount"` // 剩余个数
Status int32 `json:"status"` // 状态0-进行中1-已领完2-已过期
Receives []*RedPacketReceiveDetail `json:"receives"` // 领取记录列表
}
// RedPacketReceiveDetail 红包领取详情(后台管理接口使用)
type RedPacketReceiveDetail struct {
ReceiveID string `json:"receiveID"` // 领取记录ID
ReceiveUserID string `json:"receiveUserID"` // 领取者ID
Amount int64 `json:"amount"` // 领取金额(分)
ReceiveTime int64 `json:"receiveTime"` // 领取时间戳(毫秒)
IsLucky bool `json:"isLucky"` // 是否为手气最佳(仅拼手气红包有效)
}
// PauseRedPacketReq 暂停红包请求
type PauseRedPacketReq struct {
RedPacketID string `json:"redPacketID" binding:"required"` // 红包ID
}
// PauseRedPacketResp 暂停红包响应
type PauseRedPacketResp struct {
RedPacketID string `json:"redPacketID"` // 红包ID
}
// GetRedPacketDetailReq 查询红包详情请求(用户端)
type GetRedPacketDetailReq struct {
RedPacketID string `json:"redPacketID" binding:"required"` // 红包ID必填
}
// GetRedPacketDetailResp 查询红包详情响应(用户端)
type GetRedPacketDetailResp struct {
RedPacketID string `json:"redPacketID"` // 红包ID
GroupID string `json:"groupID"` // 群ID
RedPacketType int32 `json:"redPacketType"` // 红包类型1-普通红包2-拼手气红包
TotalAmount int64 `json:"totalAmount"` // 总金额(分)
TotalCount int32 `json:"totalCount"` // 总个数
RemainAmount int64 `json:"remainAmount"` // 剩余金额(分)
RemainCount int32 `json:"remainCount"` // 剩余个数
Blessing string `json:"blessing"` // 祝福语
Status int32 `json:"status"` // 状态0-进行中1-已领完2-已过期
IsExpired bool `json:"isExpired"` // 是否过期(超过一周)
MyReceive *RedPacketMyReceiveDetail `json:"myReceive"` // 当前用户的领取信息(如果已领取)
Receives []*RedPacketUserReceiveDetail `json:"receives"` // 领取记录列表(仅群主/管理员可见)
}
// RedPacketMyReceiveDetail 当前用户自己的领取详情不包含用户ID、昵称、头像
type RedPacketMyReceiveDetail struct {
ReceiveID string `json:"receiveID"` // 领取记录ID
Amount int64 `json:"amount"` // 领取金额(分)
ReceiveTime int64 `json:"receiveTime"` // 领取时间戳(毫秒)
IsLucky bool `json:"isLucky"` // 是否为手气最佳(仅拼手气红包有效)
}
// RedPacketUserReceiveDetail 红包用户领取详情(用户端,包含用户信息)
type RedPacketUserReceiveDetail struct {
ReceiveID string `json:"receiveID"` // 领取记录ID
ReceiveUserID string `json:"receiveUserID"` // 领取者ID
Nickname string `json:"nickname"` // 领取者昵称
FaceURL string `json:"faceURL"` // 领取者头像
Amount int64 `json:"amount"` // 领取金额(分)
ReceiveTime int64 `json:"receiveTime"` // 领取时间戳(毫秒)
IsLucky bool `json:"isLucky"` // 是否为手气最佳(仅拼手气红包有效)
}
// GetWalletsReq 查询用户钱包列表请求(后台管理接口)
type GetWalletsReq struct {
UserID string `json:"userID"` // 用户ID选填支持通过ID、手机号、账号查询
PhoneNumber string `json:"phoneNumber"` // 手机号选填支持通过ID、手机号、账号查询
Account string `json:"account"` // 账号选填支持通过ID、手机号、账号查询
Pagination Pagination `json:"pagination"` // 分页参数
}
// GetWalletsResp 查询用户钱包列表响应
type GetWalletsResp struct {
Total int64 `json:"total"` // 总数
Wallets []*WalletInfo `json:"wallets"` // 钱包列表
}
// WalletInfo 钱包信息
type WalletInfo struct {
UserID string `json:"userID"` // 用户ID
Nickname string `json:"nickname"` // 用户昵称
FaceURL string `json:"faceURL"` // 用户头像
Balance int64 `json:"balance"` // 余额(分)
CreateTime int64 `json:"createTime"` // 创建时间戳(毫秒)
UpdateTime int64 `json:"updateTime"` // 更新时间戳(毫秒)
}
// BatchUpdateWalletBalanceReq 批量修改用户余额请求(后台管理接口)
type BatchUpdateWalletBalanceReq struct {
Users []WalletUserIdentifier `json:"users" binding:"required"` // 用户标识列表支持用户ID、手机号、账号
Amount int64 `json:"amount"` // 默认金额(分),如果用户没有指定金额则使用此值
Operation string `json:"operation"` // 默认操作类型set设置为指定金额、add增加金额、subtract减少金额默认为add
}
// WalletUserIdentifier 钱包用户标识支持用户ID、手机号、账号
type WalletUserIdentifier struct {
UserID FlexibleString `json:"userID"` // 用户ID选填支持数字和字符串
PhoneNumber string `json:"phoneNumber"` // 手机号(选填)
Account string `json:"account"` // 账号(选填)
Amount int64 `json:"amount"` // 金额(分),如果未指定则使用请求中的默认金额
Operation string `json:"operation"` // 操作类型set设置为指定金额、add增加金额、subtract减少金额如果未指定则使用请求中的默认操作类型
Remark string `json:"remark"` // 备注(选填),每条修改记录对应的备注信息
}
// FlexibleString 灵活的字符串类型,可以接受数字和字符串
type FlexibleString string
// UnmarshalJSON 自定义JSON反序列化支持数字和字符串
func (f *FlexibleString) UnmarshalJSON(data []byte) error {
// 尝试解析为字符串
var s string
if err := json.Unmarshal(data, &s); err == nil {
*f = FlexibleString(s)
return nil
}
// 尝试解析为数字
var num json.Number
if err := json.Unmarshal(data, &num); err == nil {
*f = FlexibleString(num.String())
return nil
}
// 尝试解析为整数
var i int64
if err := json.Unmarshal(data, &i); err == nil {
*f = FlexibleString(strconv.FormatInt(i, 10))
return nil
}
// 尝试解析为浮点数
var f64 float64
if err := json.Unmarshal(data, &f64); err == nil {
*f = FlexibleString(strconv.FormatFloat(f64, 'f', -1, 64))
return nil
}
return fmt.Errorf("cannot unmarshal %s into FlexibleString", string(data))
}
// String 返回字符串值
func (f FlexibleString) String() string {
return string(f)
}
// BatchUpdateWalletBalanceResp 批量修改用户余额响应
type BatchUpdateWalletBalanceResp struct {
Total int32 `json:"total"` // 总处理数量
Success int32 `json:"success"` // 成功数量
Failed int32 `json:"failed"` // 失败数量
Results []WalletUpdateResult `json:"results"` // 处理结果列表
}
// WalletUpdateResult 钱包更新结果
type WalletUpdateResult struct {
UserID string `json:"userID"` // 用户ID
PhoneNumber string `json:"phoneNumber"` // 手机号
Account string `json:"account"` // 账号
Success bool `json:"success"` // 是否成功
Message string `json:"message"` // 结果消息
OldBalance int64 `json:"oldBalance"` // 修改前余额
NewBalance int64 `json:"newBalance"` // 修改后余额
Remark string `json:"remark"` // 备注信息
}
// SendSingleMsgReq defines the structure for sending a message to multiple recipients.
type SendSingleMsgReq struct {
// groupMsg should appoint sendID
SendID string `json:"sendID"`
Content string `json:"content" binding:"required"`
OfflinePushInfo *sdkws.OfflinePushInfo `json:"offlinePushInfo"`
Ex string `json:"ex"`
}
type KeyMsgData struct {
SendID string `json:"sendID"`
RecvID string `json:"recvID"`
GroupID string `json:"groupID"`
}
// SingleReturnResult encapsulates the result of a single message send attempt.
type SingleReturnResult struct {
// ServerMsgID is the message identifier on the server-side.
ServerMsgID string `json:"serverMsgID"`
// ClientMsgID is the message identifier on the client-side.
ClientMsgID string `json:"clientMsgID"`
// SendTime is the timestamp of when the message was sent.
SendTime int64 `json:"sendTime"`
// RecvID uniquely identifies the receiver of the message.
RecvID string `json:"recvID"`
// Modify fields modified via webhook.
Modify map[string]any `json:"modify,omitempty"`
}
type SendMsgResp struct {
// SendMsgResp original response.
*pbmsg.SendMsgResp
// Modify fields modified via webhook.
Modify map[string]any `json:"modify,omitempty"`
}
// CreateMeetingReq 创建会议请求
type CreateMeetingReq struct {
MeetingID string `json:"meetingID"` // 会议ID选填不填则自动生成
Subject string `json:"subject" binding:"required"` // 会议主题(必填)
CoverURL string `json:"coverURL"` // 封面URL
ScheduledTime int64 `json:"scheduledTime" binding:"required"` // 预约时间戳(毫秒,必填)
Description string `json:"description"` // 会议描述
Duration int32 `json:"duration"` // 会议时长(分钟)
EstimatedCount int32 `json:"estimatedCount"` // 会议预估人数
EnableMic bool `json:"enableMic"` // 是否开启连麦
EnableComment bool `json:"enableComment"` // 是否开启评论
AnchorUserIDs []string `json:"anchorUserIDs"` // 主播用户ID列表多选
Password string `json:"password"` // 会议密码6位数字选填不填则自动生成
Ex string `json:"ex"` // 扩展字段
}
// CreateMeetingResp 创建会议响应
type CreateMeetingResp struct {
MeetingInfo *MeetingInfo `json:"meetingInfo"` // 会议信息
GroupID string `json:"groupID"` // 创建的群聊ID
}
// UpdateMeetingReq 更新会议请求
type UpdateMeetingReq struct {
MeetingID string `json:"meetingID" binding:"required"` // 会议ID必填
Subject string `json:"subject"` // 会议主题
CoverURL string `json:"coverURL"` // 封面URL
ScheduledTime int64 `json:"scheduledTime"` // 预约时间戳(毫秒)
Status int32 `json:"status"` // 会议状态1-已预约2-进行中3-已结束4-已取消
Description string `json:"description"` // 会议描述
Duration int32 `json:"duration"` // 会议时长(分钟)
EstimatedCount int32 `json:"estimatedCount"` // 会议预估人数
EnableMic *bool `json:"enableMic"` // 是否开启连麦(使用指针以区分是否设置)
EnableComment *bool `json:"enableComment"` // 是否开启评论(使用指针以区分是否设置)
AnchorUserIDs []string `json:"anchorUserIDs"` // 主播用户ID列表多选
Password *string `json:"password"` // 会议密码6位数字使用指针以区分是否设置
Ex string `json:"ex"` // 扩展字段
}
// UpdateMeetingResp 更新会议响应
type UpdateMeetingResp struct {
MeetingInfo *MeetingInfo `json:"meetingInfo"` // 会议信息
}
// GetMeetingsReq 获取会议列表请求
type GetMeetingsReq struct {
CreatorUserID string `json:"creatorUserID"` // 创建者用户ID选填
Status int32 `json:"status"` // 会议状态选填1-已预约2-进行中3-已结束4-已取消
Keyword string `json:"keyword"` // 搜索关键词(选填,搜索主题和描述)
StartTime int64 `json:"startTime"` // 开始时间戳(毫秒,选填)
EndTime int64 `json:"endTime"` // 结束时间戳(毫秒,选填)
Pagination Pagination `json:"pagination"` // 分页参数
}
// GetMeetingsResp 获取会议列表响应
type GetMeetingsResp struct {
Total int64 `json:"total"` // 总数
Meetings []*MeetingInfo `json:"meetings"` // 会议列表
}
// DeleteMeetingReq 删除会议请求
type DeleteMeetingReq struct {
MeetingID string `json:"meetingID" binding:"required"` // 会议ID必填
}
// DeleteMeetingResp 删除会议响应
type DeleteMeetingResp struct {
}
// MeetingInfo 会议信息
type MeetingInfo struct {
MeetingID string `json:"meetingID"` // 会议ID
Subject string `json:"subject"` // 会议主题
CoverURL string `json:"coverURL"` // 封面URL
ScheduledTime int64 `json:"scheduledTime"` // 预约时间戳(毫秒)
Status int32 `json:"status"` // 会议状态1-已预约2-进行中3-已结束4-已取消
CreatorUserID string `json:"creatorUserID"` // 创建者用户ID
Description string `json:"description"` // 会议描述
Duration int32 `json:"duration"` // 会议时长(分钟)
EstimatedCount int32 `json:"estimatedCount"` // 会议预估人数
EnableMic bool `json:"enableMic"` // 是否开启连麦
EnableComment bool `json:"enableComment"` // 是否开启评论
AnchorUserIDs []string `json:"anchorUserIDs"` // 主播用户ID列表多选
AnchorUsers []*sdkws.UserInfo `json:"anchorUsers"` // 主播用户信息列表
CreateTime int64 `json:"createTime"` // 创建时间戳(毫秒)
UpdateTime int64 `json:"updateTime"` // 更新时间戳(毫秒)
Ex string `json:"ex"` // 扩展字段
GroupID string `json:"groupID"` // 关联的群聊ID
CheckInCount int32 `json:"checkInCount"` // 签到人数统计
Password string `json:"password"` // 会议密码6位数字
}
// GetMeetingReq 获取会议请求(用户端)
type GetMeetingReq struct {
MeetingID string `json:"meetingID" binding:"required"` // 会议ID必填
}
// GetMeetingResp 获取会议响应(用户端)
type GetMeetingResp struct {
MeetingInfo *MeetingPublicInfo `json:"meetingInfo"` // 会议信息
}
// GetMeetingsPublicReq 获取会议列表请求(用户端)
type GetMeetingsPublicReq struct {
Status int32 `json:"status"` // 会议状态选填1-已预约2-进行中3-已结束4-已取消
Keyword string `json:"keyword"` // 搜索关键词(选填,搜索主题和描述)
StartTime int64 `json:"startTime"` // 开始时间戳(毫秒,选填)
EndTime int64 `json:"endTime"` // 结束时间戳(毫秒,选填)
Pagination Pagination `json:"pagination"` // 分页参数
}
// GetMeetingsPublicResp 获取会议列表响应(用户端)
type GetMeetingsPublicResp struct {
Total int64 `json:"total"` // 总数
Meetings []*MeetingPublicInfo `json:"meetings"` // 会议列表
}
// MeetingPublicInfo 会议公开信息(用户端,过滤了管理字段)
type MeetingPublicInfo struct {
MeetingID string `json:"meetingID"` // 会议ID
Subject string `json:"subject"` // 会议主题
CoverURL string `json:"coverURL"` // 封面URL
ScheduledTime int64 `json:"scheduledTime"` // 预约时间戳(毫秒)
Status int32 `json:"status"` // 会议状态1-已预约2-进行中3-已结束4-已取消
Description string `json:"description"` // 会议描述
Duration int32 `json:"duration"` // 会议时长(分钟)
EstimatedCount int32 `json:"estimatedCount"` // 会议预估人数
EnableMic bool `json:"enableMic"` // 是否开启连麦
EnableComment bool `json:"enableComment"` // 是否开启评论
AnchorUsers []*sdkws.UserInfo `json:"anchorUsers"` // 主播用户信息列表
GroupID string `json:"groupID"` // 关联的群聊ID
CheckInCount int32 `json:"checkInCount"` // 签到人数统计
Password string `json:"password"` // 会议密码6位数字
}
// CheckInMeetingReq 会议签到请求
type CheckInMeetingReq struct {
MeetingID string `json:"meetingID" binding:"required"` // 会议ID必填
}
// CheckInMeetingResp 会议签到响应
type CheckInMeetingResp struct {
CheckInID string `json:"checkInID"` // 签到ID
CheckInTime int64 `json:"checkInTime"` // 签到时间戳(毫秒)
}
// GetMeetingCheckInsReq 获取会议签到列表请求
type GetMeetingCheckInsReq struct {
MeetingID string `json:"meetingID" binding:"required"` // 会议ID必填
Pagination Pagination `json:"pagination"` // 分页参数
}
// GetMeetingCheckInsResp 获取会议签到列表响应
type GetMeetingCheckInsResp struct {
Total int64 `json:"total"` // 总数
CheckIns []*MeetingCheckInInfo `json:"checkIns"` // 签到列表
}
// MeetingCheckInInfo 会议签到信息
type MeetingCheckInInfo struct {
CheckInID string `json:"checkInID"` // 签到ID
MeetingID string `json:"meetingID"` // 会议ID
UserID string `json:"userID"` // 用户ID
CheckInTime int64 `json:"checkInTime"` // 签到时间戳(毫秒)
UserInfo *sdkws.UserInfo `json:"userInfo"` // 用户信息
}
// GetMeetingCheckInStatsReq 获取会议签到统计请求
type GetMeetingCheckInStatsReq struct {
MeetingID string `json:"meetingID" binding:"required"` // 会议ID必填
}
// GetMeetingCheckInStatsResp 获取会议签到统计响应
type GetMeetingCheckInStatsResp struct {
MeetingID string `json:"meetingID"` // 会议ID
CheckInCount int64 `json:"checkInCount"` // 签到人数
}
// CheckUserCheckInReq 检查用户是否已签到请求
type CheckUserCheckInReq struct {
MeetingID string `json:"meetingID" binding:"required"` // 会议ID必填
}
// CheckUserCheckInResp 检查用户是否已签到响应
type CheckUserCheckInResp struct {
IsCheckedIn bool `json:"isCheckedIn"` // 是否已签到
CheckInInfo *MeetingCheckInInfo `json:"checkInInfo,omitempty"` // 签到信息(如果已签到)
}

186
pkg/apistruct/msg.go Normal file
View File

@@ -0,0 +1,186 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apistruct
import "git.imall.cloud/openim/protocol/sdkws"
type PictureBaseInfo struct {
UUID string `mapstructure:"uuid"`
Type string `mapstructure:"type" validate:"required"`
Size int64 `mapstructure:"size"`
Width int32 `mapstructure:"width" validate:"required"`
Height int32 `mapstructure:"height" validate:"required"`
Url string `mapstructure:"url" validate:"required"`
}
type PictureElem struct {
SourcePath string `mapstructure:"sourcePath"`
SourcePicture PictureBaseInfo `mapstructure:"sourcePicture" validate:"required"`
BigPicture PictureBaseInfo `mapstructure:"bigPicture" validate:"required"`
SnapshotPicture PictureBaseInfo `mapstructure:"snapshotPicture" validate:"required"`
}
type SoundElem struct {
UUID string `mapstructure:"uuid"`
SoundPath string `mapstructure:"soundPath"`
SourceURL string `mapstructure:"sourceUrl" validate:"required"`
DataSize int64 `mapstructure:"dataSize"`
Duration int64 `mapstructure:"duration" validate:"required,min=1"`
}
type VideoElem struct {
VideoPath string `mapstructure:"videoPath"`
VideoUUID string `mapstructure:"videoUUID"`
VideoURL string `mapstructure:"videoUrl" validate:"required"`
VideoType string `mapstructure:"videoType" validate:"required"`
VideoSize int64 `mapstructure:"videoSize" validate:"required"`
Duration int64 `mapstructure:"duration" validate:"required"`
SnapshotPath string `mapstructure:"snapshotPath"`
SnapshotUUID string `mapstructure:"snapshotUUID"`
SnapshotSize int64 `mapstructure:"snapshotSize"`
SnapshotURL string `mapstructure:"snapshotUrl" validate:"required"`
SnapshotWidth int32 `mapstructure:"snapshotWidth" validate:"required"`
SnapshotHeight int32 `mapstructure:"snapshotHeight" validate:"required"`
}
type FileElem struct {
FilePath string `mapstructure:"filePath"`
UUID string `mapstructure:"uuid"`
SourceURL string `mapstructure:"sourceUrl" validate:"required"`
FileName string `mapstructure:"fileName" validate:"required"`
FileSize int64 `mapstructure:"fileSize" validate:"required"`
}
type AtElem struct {
Text string `mapstructure:"text"`
AtUserList []string `mapstructure:"atUserList" validate:"required,max=1000"`
AtUsersInfo []*AtInfo `json:"atUsersInfo"`
QuoteMessage *MsgStruct `json:"quoteMessage"`
IsAtSelf bool `mapstructure:"isAtSelf"`
}
type LocationElem struct {
Description string `mapstructure:"description"`
Longitude float64 `mapstructure:"longitude" validate:"required"`
Latitude float64 `mapstructure:"latitude" validate:"required"`
}
type CustomElem struct {
Data string `mapstructure:"data" validate:"required"`
Description string `mapstructure:"description"`
Extension string `mapstructure:"extension"`
}
type TextElem struct {
Content string `json:"content" validate:"required"`
}
type MarkdownTextElem struct {
Content string `mapstructure:"content" validate:"required"`
}
type StreamMsgElem struct {
Type string `mapstructure:"type" validate:"required"`
Content string `mapstructure:"content" validate:"required"`
}
type RevokeElem struct {
RevokeMsgClientID string `mapstructure:"revokeMsgClientID" validate:"required"`
}
type QuoteElem struct {
Text string `json:"text,omitempty"`
QuoteMessage *MsgStruct `json:"quoteMessage,omitempty"`
}
type OANotificationElem struct {
NotificationName string `mapstructure:"notificationName" json:"notificationName" validate:"required"`
NotificationFaceURL string `mapstructure:"notificationFaceURL" json:"notificationFaceURL"`
NotificationType int32 `mapstructure:"notificationType" json:"notificationType" validate:"required"`
Text string `mapstructure:"text" json:"text" validate:"required"`
Url string `mapstructure:"url" json:"url"`
MixType int32 `mapstructure:"mixType" json:"mixType" validate:"gte=0,lte=5"`
PictureElem *PictureElem `mapstructure:"pictureElem" json:"pictureElem"`
SoundElem *SoundElem `mapstructure:"soundElem" json:"soundElem"`
VideoElem *VideoElem `mapstructure:"videoElem" json:"videoElem"`
FileElem *FileElem `mapstructure:"fileElem" json:"fileElem"`
Ex string `mapstructure:"ex" json:"ex"`
}
type MessageRevoked struct {
RevokerID string `mapstructure:"revokerID" json:"revokerID" validate:"required"`
RevokerRole int32 `mapstructure:"revokerRole" json:"revokerRole" validate:"required"`
ClientMsgID string `mapstructure:"clientMsgID" json:"clientMsgID" validate:"required"`
RevokerNickname string `mapstructure:"revokerNickname" json:"revokerNickname"`
SessionType int32 `mapstructure:"sessionType" json:"sessionType" validate:"required"`
Seq uint32 `mapstructure:"seq" json:"seq" validate:"required"`
}
type MsgStruct struct {
ClientMsgID string `json:"clientMsgID,omitempty"`
ServerMsgID string `json:"serverMsgID,omitempty"`
CreateTime int64 `json:"createTime"`
SendTime int64 `json:"sendTime"`
SessionType int32 `json:"sessionType"`
SendID string `json:"sendID,omitempty"`
RecvID string `json:"recvID,omitempty"`
MsgFrom int32 `json:"msgFrom"`
ContentType int32 `json:"contentType"`
SenderPlatformID int32 `json:"senderPlatformID"`
SenderNickname string `json:"senderNickname,omitempty"`
SenderFaceURL string `json:"senderFaceUrl,omitempty"`
GroupID string `json:"groupID,omitempty"`
Content string `json:"content,omitempty"`
Seq int64 `json:"seq"`
IsRead bool `json:"isRead"`
Status int32 `json:"status"`
IsReact bool `json:"isReact,omitempty"`
IsExternalExtensions bool `json:"isExternalExtensions,omitempty"`
OfflinePush *sdkws.OfflinePushInfo `json:"offlinePush,omitempty"`
AttachedInfo string `json:"attachedInfo,omitempty"`
Ex string `json:"ex,omitempty"`
LocalEx string `json:"localEx,omitempty"`
TextElem *TextElem `json:"textElem,omitempty"`
PictureElem *PictureElem `json:"pictureElem,omitempty"`
SoundElem *SoundElem `json:"soundElem,omitempty"`
VideoElem *VideoElem `json:"videoElem,omitempty"`
FileElem *FileElem `json:"fileElem,omitempty"`
AtTextElem *AtElem `json:"atTextElem,omitempty"`
LocationElem *LocationElem `json:"locationElem,omitempty"`
CustomElem *CustomElem `json:"customElem,omitempty"`
QuoteElem *QuoteElem `json:"quoteElem,omitempty"`
}
type AtInfo struct {
AtUserID string `json:"atUserID,omitempty"`
GroupNickname string `json:"groupNickname,omitempty"`
}
// RedPacketReceiveInfo 红包领取信息(已领取时返回)
type RedPacketReceiveInfo struct {
Amount int64 `json:"amount"` // 领取金额(分)
ReceiveTime int64 `json:"receiveTime"` // 领取时间戳(毫秒)
IsLucky bool `json:"isLucky"` // 是否为手气最佳(仅拼手气红包有效)
}
// RedPacketElem 红包消息元素
type RedPacketElem struct {
RedPacketID string `json:"redPacketID" validate:"required"` // 红包ID
RedPacketType int32 `json:"redPacketType" validate:"required"` // 红包类型1-普通红包2-拼手气红包
Blessing string `json:"blessing"` // 祝福语
IsReceived bool `json:"isReceived"` // 当前用户是否已领取
ReceiveInfo *RedPacketReceiveInfo `json:"receiveInfo,omitempty"` // 领取信息(已领取时返回,包含金额)
Status int32 `json:"status"` // 红包状态0-进行中1-已领完2-已过期
IsExpired bool `json:"isExpired"` // 是否已过期
IsFinished bool `json:"isFinished"` // 是否已领完
}

20
pkg/apistruct/public.go Normal file
View File

@@ -0,0 +1,20 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apistruct
type GroupAddMemberInfo struct {
UserID string `json:"userID" binding:"required"`
RoleLevel int32 `json:"roleLevel" binding:"required,oneof= 1 3"`
}

124
pkg/apistruct/statistics.go Normal file
View File

@@ -0,0 +1,124 @@
package apistruct
// OnlineUserCountResp 在线人数统计返回
type OnlineUserCountResp struct {
OnlineCount int64 `json:"onlineCount"`
}
// UserSendMsgCountReq 用户发送消息总数统计请求
type UserSendMsgCountReq struct{}
// UserSendMsgCountResp 用户发送消息总数统计返回
type UserSendMsgCountResp struct {
// Count24h 最近24小时发送消息总数
Count24h int64 `json:"count24h"`
// Count7d 最近7天发送消息总数
Count7d int64 `json:"count7d"`
// Count30d 最近30天发送消息总数
Count30d int64 `json:"count30d"`
}
// UserSendMsgQueryReq 用户发送消息查询请求
type UserSendMsgQueryReq struct {
UserID string `json:"userID"`
StartTime int64 `json:"startTime"`
EndTime int64 `json:"endTime"`
Content string `json:"content"`
PageNumber int32 `json:"pageNumber"`
// ShowNumber 每页条数 默认50 最大200
ShowNumber int32 `json:"showNumber"`
}
// UserSendMsgQueryRecord 用户发送消息查询记录
type UserSendMsgQueryRecord struct {
// MsgID 使用服务端消息ID
MsgID string `json:"msgID"`
// SendID 发送者ID
SendID string `json:"sendID"`
// SenderName 发送者昵称或名称
SenderName string `json:"senderName"`
// RecvID 接收者ID 群聊为群ID
RecvID string `json:"recvID"`
// RecvName 接收者昵称或名称 群聊为群名称
RecvName string `json:"recvName"`
// ContentType 消息类型编号
ContentType int32 `json:"contentType"`
// ContentTypeName 消息类型名称
ContentTypeName string `json:"contentTypeName"`
// SessionType 聊天类型编号
SessionType int32 `json:"sessionType"`
// ChatTypeName 聊天类型名称
ChatTypeName string `json:"chatTypeName"`
// Content 消息内容
Content string `json:"content"`
// SendTime 消息发送时间
SendTime int64 `json:"sendTime"`
}
// UserSendMsgQueryResp 用户发送消息查询返回
type UserSendMsgQueryResp struct {
Count int64 `json:"count"`
PageNumber int32 `json:"pageNumber"`
ShowNumber int32 `json:"showNumber"`
Records []*UserSendMsgQueryRecord `json:"records"`
}
// OnlineUserCountTrendReq 在线人数走势统计请求
type OnlineUserCountTrendReq struct {
// StartTime 统计开始时间毫秒时间戳为空时默认最近24小时
StartTime int64 `json:"startTime"`
// EndTime 统计结束时间(毫秒时间戳),为空时默认当前时间
EndTime int64 `json:"endTime"`
// IntervalMinutes 统计间隔分钟仅支持15/30/60
IntervalMinutes int32 `json:"intervalMinutes" binding:"required,oneof=15 30 60"`
}
// OnlineUserCountTrendItem 在线人数走势数据点
type OnlineUserCountTrendItem struct {
// Timestamp 区间起始时间(毫秒时间戳)
Timestamp int64 `json:"timestamp"`
// OnlineCount 区间内平均在线人数
OnlineCount int64 `json:"onlineCount"`
}
// OnlineUserCountTrendResp 在线人数走势统计返回
type OnlineUserCountTrendResp struct {
// IntervalMinutes 统计间隔(分钟)
IntervalMinutes int32 `json:"intervalMinutes"`
// Points 走势数据点
Points []*OnlineUserCountTrendItem `json:"points"`
}
// UserSendMsgCountTrendReq 用户发送消息走势统计请求
type UserSendMsgCountTrendReq struct {
// UserID 发送者用户ID
UserID string `json:"userID" binding:"required"`
// ChatType 聊天类型1-单聊2-群聊
ChatType int32 `json:"chatType" binding:"required,oneof=1 2"`
// StartTime 统计开始时间毫秒时间戳为空时默认最近24小时
StartTime int64 `json:"startTime"`
// EndTime 统计结束时间(毫秒时间戳),为空时默认当前时间
EndTime int64 `json:"endTime"`
// IntervalMinutes 统计间隔分钟仅支持15/30/60
IntervalMinutes int32 `json:"intervalMinutes" binding:"required,oneof=15 30 60"`
}
// UserSendMsgCountTrendItem 用户发送消息走势数据点
type UserSendMsgCountTrendItem struct {
// Timestamp 区间起始时间(毫秒时间戳)
Timestamp int64 `json:"timestamp"`
// Count 区间内发送消息数
Count int64 `json:"count"`
}
// UserSendMsgCountTrendResp 用户发送消息走势统计返回
type UserSendMsgCountTrendResp struct {
// UserID 发送者用户ID
UserID string `json:"userID"`
// ChatType 聊天类型1-单聊2-群聊
ChatType int32 `json:"chatType"`
// IntervalMinutes 统计间隔(分钟)
IntervalMinutes int32 `json:"intervalMinutes"`
// Points 走势数据点
Points []*UserSendMsgCountTrendItem `json:"points"`
}

15
pkg/authverify/doc.go Normal file
View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package authverify // import "git.imall.cloud/openim/open-im-server-deploy/pkg/authverify"

120
pkg/authverify/token.go Normal file
View File

@@ -0,0 +1,120 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package authverify
import (
"context"
"fmt"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/servererrs"
"git.imall.cloud/openim/protocol/constant"
"github.com/golang-jwt/jwt/v4"
"github.com/openimsdk/tools/mcontext"
"github.com/openimsdk/tools/utils/datautil"
)
func Secret(secret string) jwt.Keyfunc {
return func(token *jwt.Token) (any, error) {
return []byte(secret), nil
}
}
func CheckAdmin(ctx context.Context) error {
if IsAdmin(ctx) {
return nil
}
return servererrs.ErrNoPermission.WrapMsg(fmt.Sprintf("user %s is not admin userID", mcontext.GetOpUserID(ctx)))
}
//func IsManagerUserID(opUserID string, imAdminUserID []string) bool {
// return datautil.Contain(opUserID, imAdminUserID...)
//}
func CheckUserIsAdmin(ctx context.Context, userID string) bool {
return datautil.Contain(userID, GetIMAdminUserIDs(ctx)...)
}
func CheckSystemAccount(ctx context.Context, level int32) bool {
return level >= constant.AppAdmin
}
const (
CtxAdminUserIDsKey = "CtxAdminUserIDsKey"
)
func WithIMAdminUserIDs(ctx context.Context, imAdminUserID []string) context.Context {
return context.WithValue(ctx, CtxAdminUserIDsKey, imAdminUserID)
}
func GetIMAdminUserIDs(ctx context.Context) []string {
imAdminUserID, _ := ctx.Value(CtxAdminUserIDsKey).([]string)
return imAdminUserID
}
func IsAdmin(ctx context.Context) bool {
return IsTempAdmin(ctx) || IsSystemAdmin(ctx)
}
func CheckAccess(ctx context.Context, ownerUserID string) error {
if mcontext.GetOpUserID(ctx) == ownerUserID {
return nil
}
if IsAdmin(ctx) {
return nil
}
return servererrs.ErrNoPermission.WrapMsg("ownerUserID", ownerUserID)
}
func CheckAccessIn(ctx context.Context, ownerUserIDs ...string) error {
opUserID := mcontext.GetOpUserID(ctx)
for _, userID := range ownerUserIDs {
if opUserID == userID {
return nil
}
}
if IsAdmin(ctx) {
return nil
}
return servererrs.ErrNoPermission.WrapMsg("opUser in ownerUserIDs")
}
var tempAdminValue = []string{"1"}
const ctxTempAdminKey = "ctxImTempAdminKey"
func WithTempAdmin(ctx context.Context) context.Context {
keys, _ := ctx.Value(constant.RpcCustomHeader).([]string)
if datautil.Contain(ctxTempAdminKey, keys...) {
return ctx
}
if len(keys) > 0 {
temp := make([]string, 0, len(keys)+1)
temp = append(temp, keys...)
keys = append(temp, ctxTempAdminKey)
} else {
keys = []string{ctxTempAdminKey}
}
ctx = context.WithValue(ctx, constant.RpcCustomHeader, keys)
return context.WithValue(ctx, ctxTempAdminKey, tempAdminValue)
}
func IsTempAdmin(ctx context.Context) bool {
values, _ := ctx.Value(ctxTempAdminKey).([]string)
return datautil.Equal(tempAdminValue, values)
}
func IsSystemAdmin(ctx context.Context) bool {
return datautil.Contain(mcontext.GetOpUserID(ctx), GetIMAdminUserIDs(ctx)...)
}

View File

@@ -0,0 +1,93 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package callbackstruct
import (
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/servererrs"
"github.com/openimsdk/tools/errs"
)
const (
Next = 1
)
type CommonCallbackReq struct {
SendID string `json:"sendID"`
CallbackCommand string `json:"callbackCommand"`
ServerMsgID string `json:"serverMsgID"`
ClientMsgID string `json:"clientMsgID"`
OperationID string `json:"operationID"`
SenderPlatformID int32 `json:"senderPlatformID"`
SenderNickname string `json:"senderNickname"`
SessionType int32 `json:"sessionType"`
MsgFrom int32 `json:"msgFrom"`
ContentType int32 `json:"contentType"`
Status int32 `json:"status"`
SendTime int64 `json:"sendTime"`
CreateTime int64 `json:"createTime"`
Content string `json:"content"`
Seq uint32 `json:"seq"`
AtUserIDList []string `json:"atUserList"`
SenderFaceURL string `json:"faceURL"`
Ex string `json:"ex"`
}
func (c *CommonCallbackReq) GetCallbackCommand() string {
return c.CallbackCommand
}
type CallbackReq interface {
GetCallbackCommand() string
}
type CallbackResp interface {
Parse() (err error)
}
type CommonCallbackResp struct {
ActionCode int32 `json:"actionCode"`
ErrCode int32 `json:"errCode"`
ErrMsg string `json:"errMsg"`
ErrDlt string `json:"errDlt"`
NextCode int32 `json:"nextCode"`
}
func (c CommonCallbackResp) Parse() error {
if c.ActionCode == servererrs.NoError && c.NextCode == Next {
return errs.NewCodeError(int(c.ErrCode), c.ErrMsg).WithDetail(c.ErrDlt)
}
return nil
}
type UserStatusBaseCallback struct {
CallbackCommand string `json:"callbackCommand"`
OperationID string `json:"operationID"`
PlatformID int `json:"platformID"`
Platform string `json:"platform"`
}
func (c UserStatusBaseCallback) GetCallbackCommand() string {
return c.CallbackCommand
}
type UserStatusCallbackReq struct {
UserStatusBaseCallback
UserID string `json:"userID"`
}
type UserStatusBatchCallbackReq struct {
UserStatusBaseCallback
UserIDList []string `json:"userIDList"`
}

View File

@@ -0,0 +1,70 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package callbackstruct
const (
CallbackBeforeInviteJoinGroupCommand = "callbackBeforeInviteJoinGroupCommand"
CallbackAfterJoinGroupCommand = "callbackAfterJoinGroupCommand"
CallbackAfterSetGroupInfoCommand = "callbackAfterSetGroupInfoCommand"
CallbackAfterSetGroupInfoExCommand = "callbackAfterSetGroupInfoExCommand"
CallbackBeforeSetGroupInfoCommand = "callbackBeforeSetGroupInfoCommand"
CallbackBeforeSetGroupInfoExCommand = "callbackBeforeSetGroupInfoExCommand"
CallbackAfterRevokeMsgCommand = "callbackBeforeAfterMsgCommand"
CallbackBeforeAddBlackCommand = "callbackBeforeAddBlackCommand"
CallbackAfterAddFriendCommand = "callbackAfterAddFriendCommand"
CallbackBeforeAddFriendAgreeCommand = "callbackBeforeAddFriendAgreeCommand"
CallbackAfterAddFriendAgreeCommand = "callbackAfterAddFriendAgreeCommand"
CallbackAfterDeleteFriendCommand = "callbackAfterDeleteFriendCommand"
CallbackBeforeImportFriendsCommand = "callbackBeforeImportFriendsCommand"
CallbackAfterImportFriendsCommand = "callbackAfterImportFriendsCommand"
CallbackAfterRemoveBlackCommand = "callbackAfterRemoveBlackCommand"
CallbackAfterQuitGroupCommand = "callbackAfterQuitGroupCommand"
CallbackAfterKickGroupCommand = "callbackAfterKickGroupCommand"
CallbackAfterDisMissGroupCommand = "callbackAfterDisMissGroupCommand"
CallbackBeforeJoinGroupCommand = "callbackBeforeJoinGroupCommand"
CallbackAfterGroupMsgReadCommand = "callbackAfterGroupMsgReadCommand"
CallbackBeforeMsgModifyCommand = "callbackBeforeMsgModifyCommand"
CallbackAfterUpdateUserInfoCommand = "callbackAfterUpdateUserInfoCommand"
CallbackAfterUpdateUserInfoExCommand = "callbackAfterUpdateUserInfoExCommand"
CallbackBeforeUpdateUserInfoExCommand = "callbackBeforeUpdateUserInfoExCommand"
CallbackBeforeUserRegisterCommand = "callbackBeforeUserRegisterCommand"
CallbackAfterUserRegisterCommand = "callbackAfterUserRegisterCommand"
CallbackAfterTransferGroupOwnerCommand = "callbackAfterTransferGroupOwnerCommand"
CallbackBeforeSetFriendRemarkCommand = "callbackBeforeSetFriendRemarkCommand"
CallbackAfterSetFriendRemarkCommand = "callbackAfterSetFriendRemarkCommand"
CallbackAfterSingleMsgReadCommand = "callbackAfterSingleMsgReadCommand"
CallbackBeforeSendSingleMsgCommand = "callbackBeforeSendSingleMsgCommand"
CallbackAfterSendSingleMsgCommand = "callbackAfterSendSingleMsgCommand"
CallbackBeforeSendGroupMsgCommand = "callbackBeforeSendGroupMsgCommand"
CallbackAfterSendGroupMsgCommand = "callbackAfterSendGroupMsgCommand"
CallbackAfterUserOnlineCommand = "callbackAfterUserOnlineCommand"
CallbackAfterUserOfflineCommand = "callbackAfterUserOfflineCommand"
CallbackAfterUserKickOffCommand = "callbackAfterUserKickOffCommand"
CallbackBeforeOfflinePushCommand = "callbackBeforeOfflinePushCommand"
CallbackBeforeOnlinePushCommand = "callbackBeforeOnlinePushCommand"
CallbackBeforeGroupOnlinePushCommand = "callbackBeforeGroupOnlinePushCommand"
CallbackBeforeAddFriendCommand = "callbackBeforeAddFriendCommand"
CallbackBeforeUpdateUserInfoCommand = "callbackBeforeUpdateUserInfoCommand"
CallbackBeforeCreateGroupCommand = "callbackBeforeCreateGroupCommand"
CallbackAfterCreateGroupCommand = "callbackAfterCreateGroupCommand"
CallbackBeforeMembersJoinGroupCommand = "callbackBeforeMembersJoinGroupCommand"
CallbackBeforeSetGroupMemberInfoCommand = "callbackBeforeSetGroupMemberInfoCommand"
CallbackAfterSetGroupMemberInfoCommand = "callbackAfterSetGroupMemberInfoCommand"
CallbackBeforeCreateSingleChatConversationsCommand = "callbackBeforeCreateSingleChatConversationsCommand"
CallbackAfterCreateSingleChatConversationsCommand = "callbackAfterCreateSingleChatConversationsCommand"
CallbackBeforeCreateGroupChatConversationsCommand = "callbackBeforeCreateGroupChatConversationsCommand"
CallbackAfterCreateGroupChatConversationsCommand = "callbackAfterCreateGroupChatConversationsCommand"
CallbackAfterMsgSaveDBCommand = "callbackAfterMsgSaveDBCommand"
)

View File

@@ -0,0 +1,91 @@
package callbackstruct
type CallbackBeforeCreateSingleChatConversationsReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"owner_user_id"`
ConversationID string `json:"conversation_id"`
ConversationType int32 `json:"conversation_type"`
UserID string `json:"user_id"`
RecvMsgOpt int32 `json:"recv_msg_opt"`
IsPinned bool `json:"is_pinned"`
IsPrivateChat bool `json:"is_private_chat"`
BurnDuration int32 `json:"burn_duration"`
GroupAtType int32 `json:"group_at_type"`
AttachedInfo string `json:"attached_info"`
Ex string `json:"ex"`
}
type CallbackBeforeCreateSingleChatConversationsResp struct {
CommonCallbackResp
RecvMsgOpt *int32 `json:"recv_msg_opt"`
IsPinned *bool `json:"is_pinned"`
IsPrivateChat *bool `json:"is_private_chat"`
BurnDuration *int32 `json:"burn_duration"`
GroupAtType *int32 `json:"group_at_type"`
AttachedInfo *string `json:"attached_info"`
Ex *string `json:"ex"`
}
type CallbackAfterCreateSingleChatConversationsReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"owner_user_id"`
ConversationID string `json:"conversation_id"`
ConversationType int32 `json:"conversation_type"`
UserID string `json:"user_id"`
RecvMsgOpt int32 `json:"recv_msg_opt"`
IsPinned bool `json:"is_pinned"`
IsPrivateChat bool `json:"is_private_chat"`
BurnDuration int32 `json:"burn_duration"`
GroupAtType int32 `json:"group_at_type"`
AttachedInfo string `json:"attached_info"`
Ex string `json:"ex"`
}
type CallbackAfterCreateSingleChatConversationsResp struct {
CommonCallbackResp
}
type CallbackBeforeCreateGroupChatConversationsReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"owner_user_id"`
ConversationID string `json:"conversation_id"`
ConversationType int32 `json:"conversation_type"`
GroupID string `json:"group_id"`
RecvMsgOpt int32 `json:"recv_msg_opt"`
IsPinned bool `json:"is_pinned"`
IsPrivateChat bool `json:"is_private_chat"`
BurnDuration int32 `json:"burn_duration"`
GroupAtType int32 `json:"group_at_type"`
AttachedInfo string `json:"attached_info"`
Ex string `json:"ex"`
}
type CallbackBeforeCreateGroupChatConversationsResp struct {
CommonCallbackResp
RecvMsgOpt *int32 `json:"recv_msg_opt"`
IsPinned *bool `json:"is_pinned"`
IsPrivateChat *bool `json:"is_private_chat"`
BurnDuration *int32 `json:"burn_duration"`
GroupAtType *int32 `json:"group_at_type"`
AttachedInfo *string `json:"attached_info"`
Ex *string `json:"ex"`
}
type CallbackAfterCreateGroupChatConversationsReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"owner_user_id"`
ConversationID string `json:"conversation_id"`
ConversationType int32 `json:"conversation_type"`
GroupID string `json:"group_id"`
RecvMsgOpt int32 `json:"recv_msg_opt"`
IsPinned bool `json:"is_pinned"`
IsPrivateChat bool `json:"is_private_chat"`
BurnDuration int32 `json:"burn_duration"`
GroupAtType int32 `json:"group_at_type"`
AttachedInfo string `json:"attached_info"`
Ex string `json:"ex"`
}
type CallbackAfterCreateGroupChatConversationsResp struct {
CommonCallbackResp
}

15
pkg/callbackstruct/doc.go Normal file
View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package callbackstruct // import "git.imall.cloud/openim/open-im-server-deploy/pkg/callbackstruct"

View File

@@ -0,0 +1,139 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package callbackstruct
type CallbackBeforeAddFriendReq struct {
CallbackCommand `json:"callbackCommand"`
FromUserID string `json:"fromUserID" `
ToUserID string `json:"toUserID"`
ReqMsg string `json:"reqMsg"`
Ex string `json:"ex"`
}
type CallbackBeforeAddFriendResp struct {
CommonCallbackResp
}
type CallBackAddFriendReplyBeforeReq struct {
CallbackCommand `json:"callbackCommand"`
FromUserID string `json:"fromUserID" `
ToUserID string `json:"toUserID"`
}
type CallBackAddFriendReplyBeforeResp struct {
CommonCallbackResp
}
type CallbackBeforeSetFriendRemarkReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID"`
FriendUserID string `json:"friendUserID"`
Remark string `json:"remark"`
}
type CallbackBeforeSetFriendRemarkResp struct {
CommonCallbackResp
Remark string `json:"remark"`
}
type CallbackAfterSetFriendRemarkReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID"`
FriendUserID string `json:"friendUserID"`
Remark string `json:"remark"`
}
type CallbackAfterSetFriendRemarkResp struct {
CommonCallbackResp
}
type CallbackAfterAddFriendReq struct {
CallbackCommand `json:"callbackCommand"`
FromUserID string `json:"fromUserID" `
ToUserID string `json:"toUserID"`
ReqMsg string `json:"reqMsg"`
}
type CallbackAfterAddFriendResp struct {
CommonCallbackResp
}
type CallbackBeforeAddBlackReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID" `
BlackUserID string `json:"blackUserID"`
}
type CallbackBeforeAddBlackResp struct {
CommonCallbackResp
}
type CallbackBeforeAddFriendAgreeReq struct {
CallbackCommand `json:"callbackCommand"`
FromUserID string `json:"fromUserID" `
ToUserID string `json:"blackUserID"`
HandleResult int32 `json:"HandleResult"`
HandleMsg string `json:"HandleMsg"`
}
type CallbackBeforeAddFriendAgreeResp struct {
CommonCallbackResp
}
type CallbackAfterAddFriendAgreeReq struct {
CallbackCommand `json:"callbackCommand"`
FromUserID string `json:"fromUserID" `
ToUserID string `json:"blackUserID"`
HandleResult int32 `json:"HandleResult"`
HandleMsg string `json:"HandleMsg"`
}
type CallbackAfterAddFriendAgreeResp struct {
CommonCallbackResp
}
type CallbackAfterDeleteFriendReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID" `
FriendUserID string `json:"friendUserID"`
}
type CallbackAfterDeleteFriendResp struct {
CommonCallbackResp
}
type CallbackBeforeImportFriendsReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID" `
FriendUserIDs []string `json:"friendUserIDs"`
}
type CallbackBeforeImportFriendsResp struct {
CommonCallbackResp
FriendUserIDs []string `json:"friendUserIDs"`
}
type CallbackAfterImportFriendsReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID" `
FriendUserIDs []string `json:"friendUserIDs"`
}
type CallbackAfterImportFriendsResp struct {
CommonCallbackResp
}
type CallbackAfterRemoveBlackReq struct {
CallbackCommand `json:"callbackCommand"`
OwnerUserID string `json:"ownerUserID"`
BlackUserID string `json:"blackUserID"`
}
type CallbackAfterRemoveBlackResp struct {
CommonCallbackResp
}

290
pkg/callbackstruct/group.go Normal file
View File

@@ -0,0 +1,290 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package callbackstruct
import (
"git.imall.cloud/openim/open-im-server-deploy/pkg/apistruct"
common "git.imall.cloud/openim/protocol/sdkws"
"git.imall.cloud/openim/protocol/wrapperspb"
)
type CallbackCommand string
func (c CallbackCommand) GetCallbackCommand() string {
return string(c)
}
type CallbackBeforeCreateGroupReq struct {
OperationID string `json:"operationID"`
CallbackCommand `json:"callbackCommand"`
*common.GroupInfo
InitMemberList []*apistruct.GroupAddMemberInfo `json:"initMemberList"`
}
type CallbackBeforeCreateGroupResp struct {
CommonCallbackResp
GroupID *string `json:"groupID"`
GroupName *string `json:"groupName"`
Notification *string `json:"notification"`
Introduction *string `json:"introduction"`
FaceURL *string `json:"faceURL"`
OwnerUserID *string `json:"ownerUserID"`
Ex *string `json:"ex"`
Status *int32 `json:"status"`
CreatorUserID *string `json:"creatorUserID"`
GroupType *int32 `json:"groupType"`
NeedVerification *int32 `json:"needVerification"`
LookMemberInfo *int32 `json:"lookMemberInfo"`
ApplyMemberFriend *int32 `json:"applyMemberFriend"`
}
type CallbackAfterCreateGroupReq struct {
CallbackCommand `json:"callbackCommand"`
*common.GroupInfo
InitMemberList []*apistruct.GroupAddMemberInfo `json:"initMemberList"`
}
type CallbackAfterCreateGroupResp struct {
CommonCallbackResp
}
type CallbackGroupMember struct {
UserID string `json:"userID"`
Ex string `json:"ex"`
}
type CallbackBeforeMembersJoinGroupReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
MembersList []*CallbackGroupMember `json:"memberList"`
GroupEx string `json:"groupEx"`
}
type MemberJoinGroupCallBack struct {
UserID *string `json:"userID"`
Nickname *string `json:"nickname"`
FaceURL *string `json:"faceURL"`
RoleLevel *int32 `json:"roleLevel"`
MuteEndTime *int64 `json:"muteEndTime"`
Ex *string `json:"ex"`
}
type CallbackBeforeMembersJoinGroupResp struct {
CommonCallbackResp
MemberCallbackList []*MemberJoinGroupCallBack `json:"memberCallbackList"`
}
type CallbackBeforeSetGroupMemberInfoReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
UserID string `json:"userID"`
Nickname *string `json:"nickName"`
FaceURL *string `json:"faceURL"`
RoleLevel *int32 `json:"roleLevel"`
Ex *string `json:"ex"`
}
type CallbackBeforeSetGroupMemberInfoResp struct {
CommonCallbackResp
Ex *string `json:"ex"`
Nickname *string `json:"nickName"`
FaceURL *string `json:"faceURL"`
RoleLevel *int32 `json:"roleLevel"`
}
type CallbackAfterSetGroupMemberInfoReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
UserID string `json:"userID"`
Nickname *string `json:"nickName"`
FaceURL *string `json:"faceURL"`
RoleLevel *int32 `json:"roleLevel"`
Ex *string `json:"ex"`
}
type CallbackAfterSetGroupMemberInfoResp struct {
CommonCallbackResp
}
type CallbackQuitGroupReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
UserID string `json:"userID"`
}
type CallbackQuitGroupResp struct {
CommonCallbackResp
}
type CallbackKillGroupMemberReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
KickedUserIDs []string `json:"kickedUserIDs"`
Reason string `json:"reason"`
}
type CallbackKillGroupMemberResp struct {
CommonCallbackResp
}
type CallbackDisMissGroupReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
OwnerID string `json:"ownerID"`
GroupType string `json:"groupType"`
MembersID []string `json:"membersID"`
}
type CallbackDisMissGroupResp struct {
CommonCallbackResp
}
type CallbackJoinGroupReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
GroupType string `json:"groupType"`
ApplyID string `json:"applyID"`
ReqMessage string `json:"reqMessage"`
Ex string `json:"ex"`
}
type CallbackJoinGroupResp struct {
CommonCallbackResp
}
type CallbackTransferGroupOwnerReq struct {
CallbackCommand `json:"callbackCommand"`
GroupID string `json:"groupID"`
OldOwnerUserID string `json:"oldOwnerUserID"`
NewOwnerUserID string `json:"newOwnerUserID"`
}
type CallbackTransferGroupOwnerResp struct {
CommonCallbackResp
}
type CallbackBeforeInviteUserToGroupReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
Reason string `json:"reason"`
InvitedUserIDs []string `json:"invitedUserIDs"`
}
type CallbackBeforeInviteUserToGroupResp struct {
CommonCallbackResp
RefusedMembersAccount []string `json:"refusedMembersAccount,omitempty"` // Optional field to list members whose invitation is refused.
}
type CallbackAfterJoinGroupReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
ReqMessage string `json:"reqMessage"`
JoinSource int32 `json:"joinSource"`
InviterUserID string `json:"inviterUserID"`
}
type CallbackAfterJoinGroupResp struct {
CommonCallbackResp
}
type CallbackBeforeSetGroupInfoReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
GroupName string `json:"groupName"`
Notification string `json:"notification"`
Introduction string `json:"introduction"`
FaceURL string `json:"faceURL"`
Ex string `json:"ex"`
NeedVerification int32 `json:"needVerification"`
LookMemberInfo int32 `json:"lookMemberInfo"`
ApplyMemberFriend int32 `json:"applyMemberFriend"`
}
type CallbackBeforeSetGroupInfoResp struct {
CommonCallbackResp
GroupID string ` json:"groupID"`
GroupName string `json:"groupName"`
Notification string `json:"notification"`
Introduction string `json:"introduction"`
FaceURL string `json:"faceURL"`
Ex *string `json:"ex"`
NeedVerification *int32 `json:"needVerification"`
LookMemberInfo *int32 `json:"lookMemberInfo"`
ApplyMemberFriend *int32 `json:"applyMemberFriend"`
}
type CallbackAfterSetGroupInfoReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
GroupName string `json:"groupName"`
Notification string `json:"notification"`
Introduction string `json:"introduction"`
FaceURL string `json:"faceURL"`
Ex *string `json:"ex"`
NeedVerification *int32 `json:"needVerification"`
LookMemberInfo *int32 `json:"lookMemberInfo"`
ApplyMemberFriend *int32 `json:"applyMemberFriend"`
}
type CallbackAfterSetGroupInfoResp struct {
CommonCallbackResp
}
type CallbackBeforeSetGroupInfoExReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
GroupName *wrapperspb.StringValue `json:"groupName"`
Notification *wrapperspb.StringValue `json:"notification"`
Introduction *wrapperspb.StringValue `json:"introduction"`
FaceURL *wrapperspb.StringValue `json:"faceURL"`
Ex *wrapperspb.StringValue `json:"ex"`
NeedVerification *wrapperspb.Int32Value `json:"needVerification"`
LookMemberInfo *wrapperspb.Int32Value `json:"lookMemberInfo"`
ApplyMemberFriend *wrapperspb.Int32Value `json:"applyMemberFriend"`
}
type CallbackBeforeSetGroupInfoExResp struct {
CommonCallbackResp
GroupID string `json:"groupID"`
GroupName *wrapperspb.StringValue `json:"groupName"`
Notification *wrapperspb.StringValue `json:"notification"`
Introduction *wrapperspb.StringValue `json:"introduction"`
FaceURL *wrapperspb.StringValue `json:"faceURL"`
Ex *wrapperspb.StringValue `json:"ex"`
NeedVerification *wrapperspb.Int32Value `json:"needVerification"`
LookMemberInfo *wrapperspb.Int32Value `json:"lookMemberInfo"`
ApplyMemberFriend *wrapperspb.Int32Value `json:"applyMemberFriend"`
}
type CallbackAfterSetGroupInfoExReq struct {
CallbackCommand `json:"callbackCommand"`
OperationID string `json:"operationID"`
GroupID string `json:"groupID"`
GroupName *wrapperspb.StringValue `json:"groupName"`
Notification *wrapperspb.StringValue `json:"notification"`
Introduction *wrapperspb.StringValue `json:"introduction"`
FaceURL *wrapperspb.StringValue `json:"faceURL"`
Ex *wrapperspb.StringValue `json:"ex"`
NeedVerification *wrapperspb.Int32Value `json:"needVerification"`
LookMemberInfo *wrapperspb.Int32Value `json:"lookMemberInfo"`
ApplyMemberFriend *wrapperspb.Int32Value `json:"applyMemberFriend"`
}
type CallbackAfterSetGroupInfoExResp struct {
CommonCallbackResp
}

View File

@@ -0,0 +1,115 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package callbackstruct
import (
sdkws "git.imall.cloud/openim/protocol/sdkws"
)
type CallbackBeforeSendSingleMsgReq struct {
CommonCallbackReq
RecvID string `json:"recvID"`
}
type CallbackBeforeSendSingleMsgResp struct {
CommonCallbackResp
}
type CallbackAfterSendSingleMsgReq struct {
CommonCallbackReq
RecvID string `json:"recvID"`
}
type CallbackAfterSendSingleMsgResp struct {
CommonCallbackResp
}
type CallbackBeforeSendGroupMsgReq struct {
CommonCallbackReq
GroupID string `json:"groupID"`
}
type CallbackBeforeSendGroupMsgResp struct {
CommonCallbackResp
}
type CallbackAfterSendGroupMsgReq struct {
CommonCallbackReq
GroupID string `json:"groupID"`
}
type CallbackAfterSendGroupMsgResp struct {
CommonCallbackResp
}
type CallbackMsgModifyCommandReq struct {
CommonCallbackReq
}
type CallbackMsgModifyCommandResp struct {
CommonCallbackResp
Content *string `json:"content"`
RecvID *string `json:"recvID"`
GroupID *string `json:"groupID"`
ClientMsgID *string `json:"clientMsgID"`
ServerMsgID *string `json:"serverMsgID"`
SenderPlatformID *int32 `json:"senderPlatformID"`
SenderNickname *string `json:"senderNickname"`
SenderFaceURL *string `json:"senderFaceURL"`
SessionType *int32 `json:"sessionType"`
MsgFrom *int32 `json:"msgFrom"`
ContentType *int32 `json:"contentType"`
Status *int32 `json:"status"`
Options *map[string]bool `json:"options"`
OfflinePushInfo *sdkws.OfflinePushInfo `json:"offlinePushInfo"`
AtUserIDList *[]string `json:"atUserIDList"`
MsgDataList *[]byte `json:"msgDataList"`
AttachedInfo *string `json:"attachedInfo"`
Ex *string `json:"ex"`
}
type CallbackGroupMsgReadReq struct {
CallbackCommand `json:"callbackCommand"`
SendID string `json:"sendID"`
ReceiveID string `json:"receiveID"`
UnreadMsgNum int64 `json:"unreadMsgNum"`
ContentType int64 `json:"contentType"`
}
type CallbackGroupMsgReadResp struct {
CommonCallbackResp
}
type CallbackSingleMsgReadReq struct {
CallbackCommand `json:"callbackCommand"`
ConversationID string `json:"conversationID"`
UserID string `json:"userID"`
Seqs []int64 `json:"Seqs"`
ContentType int32 `json:"contentType"`
}
type CallbackSingleMsgReadResp struct {
CommonCallbackResp
}
type CallbackAfterMsgSaveDBReq struct {
CommonCallbackReq
RecvID string `json:"recvID"`
GroupID string `json:"groupID"`
}
type CallbackAfterMsgSaveDBResp struct {
CommonCallbackResp
}

View File

@@ -0,0 +1,46 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package callbackstruct
type CallbackUserOnlineReq struct {
UserStatusCallbackReq
// Token string `json:"token"`
Seq int64 `json:"seq"`
IsAppBackground bool `json:"isAppBackground"`
ConnID string `json:"connID"`
}
type CallbackUserOnlineResp struct {
CommonCallbackResp
}
type CallbackUserOfflineReq struct {
UserStatusCallbackReq
Seq int64 `json:"seq"`
ConnID string `json:"connID"`
}
type CallbackUserOfflineResp struct {
CommonCallbackResp
}
type CallbackUserKickOffReq struct {
UserStatusCallbackReq
Seq int64 `json:"seq"`
}
type CallbackUserKickOffResp struct {
CommonCallbackResp
}

View File

@@ -0,0 +1,53 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package callbackstruct
import common "git.imall.cloud/openim/protocol/sdkws"
type CallbackBeforePushReq struct {
UserStatusBatchCallbackReq
*common.OfflinePushInfo
ClientMsgID string `json:"clientMsgID"`
SendID string `json:"sendID"`
GroupID string `json:"groupID"`
ContentType int32 `json:"contentType"`
SessionType int32 `json:"sessionType"`
AtUserIDs []string `json:"atUserIDList"`
Content string `json:"content"`
}
type CallbackBeforePushResp struct {
CommonCallbackResp
UserIDs []string `json:"userIDList"`
OfflinePushInfo *common.OfflinePushInfo `json:"offlinePushInfo"`
}
type CallbackBeforeSuperGroupOnlinePushReq struct {
UserStatusBaseCallback
ClientMsgID string `json:"clientMsgID"`
SendID string `json:"sendID"`
GroupID string `json:"groupID"`
ContentType int32 `json:"contentType"`
SessionType int32 `json:"sessionType"`
AtUserIDs []string `json:"atUserIDList"`
Content string `json:"content"`
Seq int64 `json:"seq"`
}
type CallbackBeforeSuperGroupOnlinePushResp struct {
CommonCallbackResp
UserIDs []string `json:"userIDList"`
OfflinePushInfo *common.OfflinePushInfo `json:"offlinePushInfo"`
}

View File

@@ -0,0 +1,26 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package callbackstruct
type CallbackAfterRevokeMsgReq struct {
CallbackCommand `json:"callbackCommand"`
ConversationID string `json:"conversationID"`
Seq int64 `json:"seq"`
UserID string `json:"userID"`
}
type CallbackAfterRevokeMsgResp struct {
CommonCallbackResp
}

102
pkg/callbackstruct/user.go Normal file
View File

@@ -0,0 +1,102 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package callbackstruct
import (
"git.imall.cloud/openim/protocol/sdkws"
"git.imall.cloud/openim/protocol/wrapperspb"
)
type CallbackBeforeUpdateUserInfoReq struct {
CallbackCommand `json:"callbackCommand"`
UserID string `json:"userID"`
Nickname *string `json:"nickName"`
FaceURL *string `json:"faceURL"`
Ex *string `json:"ex"`
UserType *int32 `json:"userType"`
UserFlag *string `json:"userFlag"`
}
type CallbackBeforeUpdateUserInfoResp struct {
CommonCallbackResp
Nickname *string `json:"nickName"`
FaceURL *string `json:"faceURL"`
Ex *string `json:"ex"`
UserType *int32 `json:"userType"`
UserFlag *string `json:"userFlag"`
}
type CallbackAfterUpdateUserInfoReq struct {
CallbackCommand `json:"callbackCommand"`
UserID string `json:"userID"`
Nickname string `json:"nickName"`
FaceURL string `json:"faceURL"`
Ex string `json:"ex"`
UserType int32 `json:"userType"`
UserFlag string `json:"userFlag"`
}
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"`
UserType *wrapperspb.Int32Value `json:"userType"`
UserFlag *wrapperspb.StringValue `json:"userFlag"`
}
type CallbackBeforeUpdateUserInfoExResp struct {
CommonCallbackResp
Nickname *wrapperspb.StringValue `json:"nickName"`
FaceURL *wrapperspb.StringValue `json:"faceURL"`
Ex *wrapperspb.StringValue `json:"ex"`
UserType *wrapperspb.Int32Value `json:"userType"`
UserFlag *wrapperspb.StringValue `json:"userFlag"`
}
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"`
UserType *wrapperspb.Int32Value `json:"userType"`
UserFlag *wrapperspb.StringValue `json:"userFlag"`
}
type CallbackAfterUpdateUserInfoExResp struct {
CommonCallbackResp
}
type CallbackBeforeUserRegisterReq struct {
CallbackCommand `json:"callbackCommand"`
Users []*sdkws.UserInfo `json:"users"`
}
type CallbackBeforeUserRegisterResp struct {
CommonCallbackResp
Users []*sdkws.UserInfo `json:"users"`
}
type CallbackAfterUserRegisterReq struct {
CallbackCommand `json:"callbackCommand"`
Users []*sdkws.UserInfo `json:"users"`
}
type CallbackAfterUserRegisterResp struct {
CommonCallbackResp
}

97
pkg/common/cmd/api.go Normal file
View File

@@ -0,0 +1,97 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/api"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/prommetrics"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type ApiCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
apiConfig *api.Config
}
func NewApiCmd() *ApiCmd {
var apiConfig api.Config
ret := &ApiCmd{apiConfig: &apiConfig}
ret.configMap = map[string]any{
config.DiscoveryConfigFilename: &apiConfig.Discovery,
config.KafkaConfigFileName: &apiConfig.Kafka,
config.LocalCacheConfigFileName: &apiConfig.LocalCache,
config.LogConfigFileName: &apiConfig.Log,
config.MinioConfigFileName: &apiConfig.Minio,
config.MongodbConfigFileName: &apiConfig.Mongo,
config.NotificationFileName: &apiConfig.Notification,
config.OpenIMAPICfgFileName: &apiConfig.API,
config.OpenIMCronTaskCfgFileName: &apiConfig.CronTask,
config.OpenIMMsgGatewayCfgFileName: &apiConfig.MsgGateway,
config.OpenIMMsgTransferCfgFileName: &apiConfig.MsgTransfer,
config.OpenIMPushCfgFileName: &apiConfig.Push,
config.OpenIMRPCAuthCfgFileName: &apiConfig.Auth,
config.OpenIMRPCConversationCfgFileName: &apiConfig.Conversation,
config.OpenIMRPCFriendCfgFileName: &apiConfig.Friend,
config.OpenIMRPCGroupCfgFileName: &apiConfig.Group,
config.OpenIMRPCMsgCfgFileName: &apiConfig.Msg,
config.OpenIMRPCThirdCfgFileName: &apiConfig.Third,
config.OpenIMRPCUserCfgFileName: &apiConfig.User,
config.RedisConfigFileName: &apiConfig.Redis,
config.ShareFileName: &apiConfig.Share,
config.WebhooksConfigFileName: &apiConfig.Webhooks,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
apiConfig.ConfigPath = config.Path(ret.configPath)
return ret.runE()
}
return ret
}
func (a *ApiCmd) Exec() error {
return a.Execute()
}
func (a *ApiCmd) runE() error {
a.apiConfig.Index = config.Index(a.Index())
prometheus := config.Prometheus{
Enable: a.apiConfig.API.Prometheus.Enable,
Ports: a.apiConfig.API.Prometheus.Ports,
}
return startrpc.Start(
a.ctx, &a.apiConfig.Discovery,
nil, // circuitBreakerConfig - API doesn't use circuit breaker
nil, // rateLimiterConfig - API uses its own rate limiter middleware
&prometheus,
a.apiConfig.API.Api.ListenIP, "",
a.apiConfig.API.Prometheus.AutoSetPorts,
nil, int(a.apiConfig.Index),
prommetrics.APIKeyName,
&a.apiConfig.Notification,
a.apiConfig,
[]string{},
[]string{},
api.Start,
)
}

73
pkg/common/cmd/auth.go Normal file
View File

@@ -0,0 +1,73 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/rpc/auth"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type AuthRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
authConfig *auth.Config
}
func NewAuthRpcCmd() *AuthRpcCmd {
var authConfig auth.Config
ret := &AuthRpcCmd{authConfig: &authConfig}
ret.configMap = map[string]any{
config.OpenIMRPCAuthCfgFileName: &authConfig.RpcConfig,
config.RedisConfigFileName: &authConfig.RedisConfig,
config.MongodbConfigFileName: &authConfig.MongoConfig,
config.ShareFileName: &authConfig.Share,
config.LocalCacheConfigFileName: &authConfig.LocalCacheConfig,
config.DiscoveryConfigFilename: &authConfig.Discovery,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return ret
}
func (a *AuthRpcCmd) Exec() error {
return a.Execute()
}
func (a *AuthRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.authConfig.Discovery, &a.authConfig.RpcConfig.CircuitBreaker, &a.authConfig.RpcConfig.RateLimiter, &a.authConfig.RpcConfig.Prometheus, a.authConfig.RpcConfig.RPC.ListenIP,
a.authConfig.RpcConfig.RPC.RegisterIP, a.authConfig.RpcConfig.RPC.AutoSetPorts, a.authConfig.RpcConfig.RPC.Ports,
a.Index(), a.authConfig.Discovery.RpcService.Auth, nil, a.authConfig,
[]string{
a.authConfig.RpcConfig.GetConfigFileName(),
a.authConfig.Share.GetConfigFileName(),
a.authConfig.RedisConfig.GetConfigFileName(),
a.authConfig.Discovery.GetConfigFileName(),
},
[]string{
a.authConfig.Discovery.RpcService.MessageGateway,
},
auth.Start)
}

View File

@@ -0,0 +1,75 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/rpc/conversation"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type ConversationRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
conversationConfig *conversation.Config
}
func NewConversationRpcCmd() *ConversationRpcCmd {
var conversationConfig conversation.Config
ret := &ConversationRpcCmd{conversationConfig: &conversationConfig}
ret.configMap = map[string]any{
config.OpenIMRPCConversationCfgFileName: &conversationConfig.RpcConfig,
config.RedisConfigFileName: &conversationConfig.RedisConfig,
config.MongodbConfigFileName: &conversationConfig.MongodbConfig,
config.ShareFileName: &conversationConfig.Share,
config.NotificationFileName: &conversationConfig.NotificationConfig,
config.WebhooksConfigFileName: &conversationConfig.WebhooksConfig,
config.LocalCacheConfigFileName: &conversationConfig.LocalCacheConfig,
config.DiscoveryConfigFilename: &conversationConfig.Discovery,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return ret
}
func (a *ConversationRpcCmd) Exec() error {
return a.Execute()
}
func (a *ConversationRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.conversationConfig.Discovery, &a.conversationConfig.RpcConfig.CircuitBreaker, &a.conversationConfig.RpcConfig.RateLimiter, &a.conversationConfig.RpcConfig.Prometheus, a.conversationConfig.RpcConfig.RPC.ListenIP,
a.conversationConfig.RpcConfig.RPC.RegisterIP, a.conversationConfig.RpcConfig.RPC.AutoSetPorts, a.conversationConfig.RpcConfig.RPC.Ports,
a.Index(), a.conversationConfig.Discovery.RpcService.Conversation, &a.conversationConfig.NotificationConfig, a.conversationConfig,
[]string{
a.conversationConfig.RpcConfig.GetConfigFileName(),
a.conversationConfig.RedisConfig.GetConfigFileName(),
a.conversationConfig.MongodbConfig.GetConfigFileName(),
a.conversationConfig.NotificationConfig.GetConfigFileName(),
a.conversationConfig.Share.GetConfigFileName(),
a.conversationConfig.LocalCacheConfig.GetConfigFileName(),
a.conversationConfig.WebhooksConfig.GetConfigFileName(),
a.conversationConfig.Discovery.GetConfigFileName(),
}, nil,
conversation.Start)
}

View File

@@ -0,0 +1,75 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/tools/cron"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type CronTaskCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
cronTaskConfig *cron.Config
}
func NewCronTaskCmd() *CronTaskCmd {
var cronTaskConfig cron.Config
ret := &CronTaskCmd{cronTaskConfig: &cronTaskConfig}
ret.configMap = map[string]any{
config.OpenIMCronTaskCfgFileName: &cronTaskConfig.CronTask,
config.ShareFileName: &cronTaskConfig.Share,
config.DiscoveryConfigFilename: &cronTaskConfig.Discovery,
config.MongodbConfigFileName: &cronTaskConfig.Mongo,
config.RedisConfigFileName: &cronTaskConfig.Redis,
config.NotificationFileName: &cronTaskConfig.Notification,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return ret
}
func (a *CronTaskCmd) Exec() error {
return a.Execute()
}
func (a *CronTaskCmd) runE() error {
var prometheus config.Prometheus
return startrpc.Start(
a.ctx, &a.cronTaskConfig.Discovery,
nil,
nil,
&prometheus,
"", "",
true,
nil, 0,
"",
nil,
a.cronTaskConfig,
[]string{},
[]string{},
cron.Start,
)
}

15
pkg/common/cmd/doc.go Normal file
View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd // import "git.imall.cloud/openim/open-im-server-deploy/pkg/common/cmd"

75
pkg/common/cmd/friend.go Normal file
View File

@@ -0,0 +1,75 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/rpc/relation"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type FriendRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
relationConfig *relation.Config
}
func NewFriendRpcCmd() *FriendRpcCmd {
var relationConfig relation.Config
ret := &FriendRpcCmd{relationConfig: &relationConfig}
ret.configMap = map[string]any{
config.OpenIMRPCFriendCfgFileName: &relationConfig.RpcConfig,
config.RedisConfigFileName: &relationConfig.RedisConfig,
config.MongodbConfigFileName: &relationConfig.MongodbConfig,
config.ShareFileName: &relationConfig.Share,
config.NotificationFileName: &relationConfig.NotificationConfig,
config.WebhooksConfigFileName: &relationConfig.WebhooksConfig,
config.LocalCacheConfigFileName: &relationConfig.LocalCacheConfig,
config.DiscoveryConfigFilename: &relationConfig.Discovery,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return ret
}
func (a *FriendRpcCmd) Exec() error {
return a.Execute()
}
func (a *FriendRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.relationConfig.Discovery, &a.relationConfig.RpcConfig.CircuitBreaker, &a.relationConfig.RpcConfig.RateLimiter, &a.relationConfig.RpcConfig.Prometheus, a.relationConfig.RpcConfig.RPC.ListenIP,
a.relationConfig.RpcConfig.RPC.RegisterIP, a.relationConfig.RpcConfig.RPC.AutoSetPorts, a.relationConfig.RpcConfig.RPC.Ports,
a.Index(), a.relationConfig.Discovery.RpcService.Friend, &a.relationConfig.NotificationConfig, a.relationConfig,
[]string{
a.relationConfig.RpcConfig.GetConfigFileName(),
a.relationConfig.RedisConfig.GetConfigFileName(),
a.relationConfig.MongodbConfig.GetConfigFileName(),
a.relationConfig.NotificationConfig.GetConfigFileName(),
a.relationConfig.Share.GetConfigFileName(),
a.relationConfig.WebhooksConfig.GetConfigFileName(),
a.relationConfig.LocalCacheConfig.GetConfigFileName(),
a.relationConfig.Discovery.GetConfigFileName(),
}, nil,
relation.Start)
}

76
pkg/common/cmd/group.go Normal file
View File

@@ -0,0 +1,76 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/rpc/group"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/versionctx"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type GroupRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
groupConfig *group.Config
}
func NewGroupRpcCmd() *GroupRpcCmd {
var groupConfig group.Config
ret := &GroupRpcCmd{groupConfig: &groupConfig}
ret.configMap = map[string]any{
config.OpenIMRPCGroupCfgFileName: &groupConfig.RpcConfig,
config.RedisConfigFileName: &groupConfig.RedisConfig,
config.MongodbConfigFileName: &groupConfig.MongodbConfig,
config.ShareFileName: &groupConfig.Share,
config.NotificationFileName: &groupConfig.NotificationConfig,
config.WebhooksConfigFileName: &groupConfig.WebhooksConfig,
config.LocalCacheConfigFileName: &groupConfig.LocalCacheConfig,
config.DiscoveryConfigFilename: &groupConfig.Discovery,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return ret
}
func (a *GroupRpcCmd) Exec() error {
return a.Execute()
}
func (a *GroupRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.groupConfig.Discovery, &a.groupConfig.RpcConfig.CircuitBreaker, &a.groupConfig.RpcConfig.RateLimiter, &a.groupConfig.RpcConfig.Prometheus, a.groupConfig.RpcConfig.RPC.ListenIP,
a.groupConfig.RpcConfig.RPC.RegisterIP, a.groupConfig.RpcConfig.RPC.AutoSetPorts, a.groupConfig.RpcConfig.RPC.Ports,
a.Index(), a.groupConfig.Discovery.RpcService.Group, &a.groupConfig.NotificationConfig, a.groupConfig,
[]string{
a.groupConfig.RpcConfig.GetConfigFileName(),
a.groupConfig.RedisConfig.GetConfigFileName(),
a.groupConfig.MongodbConfig.GetConfigFileName(),
a.groupConfig.NotificationConfig.GetConfigFileName(),
a.groupConfig.Share.GetConfigFileName(),
a.groupConfig.WebhooksConfig.GetConfigFileName(),
a.groupConfig.LocalCacheConfig.GetConfigFileName(),
a.groupConfig.Discovery.GetConfigFileName(),
}, nil,
group.Start, versionctx.EnableVersionCtx())
}

77
pkg/common/cmd/msg.go Normal file
View File

@@ -0,0 +1,77 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/rpc/msg"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type MsgRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
msgConfig *msg.Config
}
func NewMsgRpcCmd() *MsgRpcCmd {
var msgConfig msg.Config
ret := &MsgRpcCmd{msgConfig: &msgConfig}
ret.configMap = map[string]any{
config.OpenIMRPCMsgCfgFileName: &msgConfig.RpcConfig,
config.RedisConfigFileName: &msgConfig.RedisConfig,
config.MongodbConfigFileName: &msgConfig.MongodbConfig,
config.KafkaConfigFileName: &msgConfig.KafkaConfig,
config.ShareFileName: &msgConfig.Share,
config.NotificationFileName: &msgConfig.NotificationConfig,
config.WebhooksConfigFileName: &msgConfig.WebhooksConfig,
config.LocalCacheConfigFileName: &msgConfig.LocalCacheConfig,
config.DiscoveryConfigFilename: &msgConfig.Discovery,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return ret
}
func (a *MsgRpcCmd) Exec() error {
return a.Execute()
}
func (a *MsgRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.msgConfig.Discovery, &a.msgConfig.RpcConfig.CircuitBreaker, &a.msgConfig.RpcConfig.RateLimiter, &a.msgConfig.RpcConfig.Prometheus, a.msgConfig.RpcConfig.RPC.ListenIP,
a.msgConfig.RpcConfig.RPC.RegisterIP, a.msgConfig.RpcConfig.RPC.AutoSetPorts, a.msgConfig.RpcConfig.RPC.Ports,
a.Index(), a.msgConfig.Discovery.RpcService.Msg, &a.msgConfig.NotificationConfig, a.msgConfig,
[]string{
a.msgConfig.RpcConfig.GetConfigFileName(),
a.msgConfig.RedisConfig.GetConfigFileName(),
a.msgConfig.MongodbConfig.GetConfigFileName(),
a.msgConfig.KafkaConfig.GetConfigFileName(),
a.msgConfig.NotificationConfig.GetConfigFileName(),
a.msgConfig.Share.GetConfigFileName(),
a.msgConfig.WebhooksConfig.GetConfigFileName(),
a.msgConfig.LocalCacheConfig.GetConfigFileName(),
a.msgConfig.Discovery.GetConfigFileName(),
}, nil,
msg.Start)
}

View File

@@ -0,0 +1,105 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"encoding/json"
"fmt"
"git.imall.cloud/openim/open-im-server-deploy/internal/msggateway"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type MsgGatewayCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
msgGatewayConfig *msggateway.Config
}
func NewMsgGatewayCmd() *MsgGatewayCmd {
var msgGatewayConfig msggateway.Config
ret := &MsgGatewayCmd{msgGatewayConfig: &msgGatewayConfig}
ret.configMap = map[string]any{
config.OpenIMMsgGatewayCfgFileName: &msgGatewayConfig.MsgGateway,
config.ShareFileName: &msgGatewayConfig.Share,
config.RedisConfigFileName: &msgGatewayConfig.RedisConfig,
config.WebhooksConfigFileName: &msgGatewayConfig.WebhooksConfig,
config.DiscoveryConfigFilename: &msgGatewayConfig.Discovery,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return ret
}
func (m *MsgGatewayCmd) Exec() error {
return m.Execute()
}
func (m *MsgGatewayCmd) runE() error {
m.msgGatewayConfig.Index = config.Index(m.Index())
rpc := m.msgGatewayConfig.MsgGateway.RPC
// 从配置读取 Prometheus避免空配置导致的下标越界
prometheus := m.msgGatewayConfig.MsgGateway.Prometheus
b, err := json.Marshal(prometheus)
if err != nil {
return err
}
fmt.Println(string(b))
log.CInfo(m.ctx, "prometheus", "prometheus", string(b))
// 调试日志:打印关键启动参数
log.CInfo(m.ctx, "msg-gateway starting",
"autoSetPorts", rpc.AutoSetPorts,
"rpcPorts", rpc.Ports,
"prometheusEnable", prometheus.Enable,
"prometheusPorts", prometheus.Ports,
"index", int(m.msgGatewayConfig.Index),
"listenIP", rpc.ListenIP,
"registerIP", rpc.RegisterIP,
)
if !rpc.AutoSetPorts && (len(rpc.Ports) == 0) {
log.ZWarn(m.ctx, "rpc ports is empty while autoSetPorts=false", nil)
}
if prometheus.Enable && len(prometheus.Ports) == 0 {
log.ZWarn(m.ctx, "prometheus enabled but ports is empty", nil)
}
return startrpc.Start(
m.ctx, &m.msgGatewayConfig.Discovery,
&m.msgGatewayConfig.MsgGateway.CircuitBreaker,
&m.msgGatewayConfig.MsgGateway.RateLimiter,
&prometheus,
rpc.ListenIP, rpc.RegisterIP,
rpc.AutoSetPorts,
rpc.Ports, int(m.msgGatewayConfig.Index),
m.msgGatewayConfig.Discovery.RpcService.MessageGateway,
nil,
m.msgGatewayConfig,
[]string{},
[]string{},
msggateway.Start,
)
}

View File

@@ -0,0 +1,69 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"math"
"testing"
"git.imall.cloud/openim/protocol/auth"
"github.com/openimsdk/tools/apiresp"
"github.com/openimsdk/tools/utils/jsonutil"
"github.com/stretchr/testify/mock"
"go.mongodb.org/mongo-driver/bson/primitive"
)
// MockRootCmd is a mock type for the RootCmd type
type MockRootCmd struct {
mock.Mock
}
func (m *MockRootCmd) Execute() error {
args := m.Called()
return args.Error(0)
}
func TestName(t *testing.T) {
resp := &apiresp.ApiResponse{
ErrCode: 1234,
ErrMsg: "test",
ErrDlt: "4567",
Data: &auth.GetUserTokenResp{
Token: "1234567",
ExpireTimeSeconds: math.MaxInt64,
},
}
data, err := resp.MarshalJSON()
if err != nil {
panic(err)
}
t.Log(string(data))
var rReso apiresp.ApiResponse
rReso.Data = &auth.GetUserTokenResp{}
if err := jsonutil.JsonUnmarshal(data, &rReso); err != nil {
panic(err)
}
t.Logf("%+v\n", rReso)
}
func TestName1(t *testing.T) {
t.Log(primitive.NewObjectID().String())
t.Log(primitive.NewObjectID().Hex())
}

View File

@@ -0,0 +1,78 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/msgtransfer"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/prommetrics"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type MsgTransferCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
msgTransferConfig *msgtransfer.Config
}
func NewMsgTransferCmd() *MsgTransferCmd {
var msgTransferConfig msgtransfer.Config
ret := &MsgTransferCmd{msgTransferConfig: &msgTransferConfig}
ret.configMap = map[string]any{
config.OpenIMMsgTransferCfgFileName: &msgTransferConfig.MsgTransfer,
config.RedisConfigFileName: &msgTransferConfig.RedisConfig,
config.MongodbConfigFileName: &msgTransferConfig.MongodbConfig,
config.KafkaConfigFileName: &msgTransferConfig.KafkaConfig,
config.ShareFileName: &msgTransferConfig.Share,
config.WebhooksConfigFileName: &msgTransferConfig.WebhooksConfig,
config.DiscoveryConfigFilename: &msgTransferConfig.Discovery,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return ret
}
func (m *MsgTransferCmd) Exec() error {
return m.Execute()
}
func (m *MsgTransferCmd) runE() error {
m.msgTransferConfig.Index = config.Index(m.Index())
var prometheus config.Prometheus
return startrpc.Start(
m.ctx, &m.msgTransferConfig.Discovery,
&m.msgTransferConfig.MsgTransfer.CircuitBreaker,
&m.msgTransferConfig.MsgTransfer.RateLimiter,
&prometheus,
"", "",
true,
nil, int(m.msgTransferConfig.Index),
prommetrics.MessageTransferKeyName,
nil,
m.msgTransferConfig,
[]string{},
[]string{},
msgtransfer.Start,
)
}

171
pkg/common/cmd/msg_utils.go Normal file
View File

@@ -0,0 +1,171 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"github.com/spf13/cobra"
)
type MsgUtilsCmd struct {
cobra.Command
}
func (m *MsgUtilsCmd) AddUserIDFlag() {
m.Command.PersistentFlags().StringP("userID", "u", "", "openIM userID")
}
func (m *MsgUtilsCmd) AddIndexFlag() {
m.Command.PersistentFlags().IntP(config.FlagTransferIndex, "i", 0, "process startup sequence number")
}
func (m *MsgUtilsCmd) AddConfigDirFlag() {
m.Command.PersistentFlags().StringP(config.FlagConf, "c", "", "path of config directory")
}
func (m *MsgUtilsCmd) getUserIDFlag(cmdLines *cobra.Command) string {
userID, _ := cmdLines.Flags().GetString("userID")
return userID
}
func (m *MsgUtilsCmd) AddFixAllFlag() {
m.Command.PersistentFlags().BoolP("fixAll", "f", false, "openIM fix all seqs")
}
/* func (m *MsgUtilsCmd) getFixAllFlag(cmdLines *cobra.Command) bool {
fixAll, _ := cmdLines.Flags().GetBool("fixAll")
return fixAll
} */
func (m *MsgUtilsCmd) AddClearAllFlag() {
m.Command.PersistentFlags().BoolP("clearAll", "", false, "openIM clear all seqs")
}
/* func (m *MsgUtilsCmd) getClearAllFlag(cmdLines *cobra.Command) bool {
clearAll, _ := cmdLines.Flags().GetBool("clearAll")
return clearAll
} */
func (m *MsgUtilsCmd) AddSuperGroupIDFlag() {
m.Command.PersistentFlags().StringP("superGroupID", "g", "", "openIM superGroupID")
}
func (m *MsgUtilsCmd) getSuperGroupIDFlag(cmdLines *cobra.Command) string {
superGroupID, _ := cmdLines.Flags().GetString("superGroupID")
return superGroupID
}
func (m *MsgUtilsCmd) AddBeginSeqFlag() {
m.Command.PersistentFlags().Int64P("beginSeq", "b", 0, "openIM beginSeq")
}
/* func (m *MsgUtilsCmd) getBeginSeqFlag(cmdLines *cobra.Command) int64 {
beginSeq, _ := cmdLines.Flags().GetInt64("beginSeq")
return beginSeq
} */
func (m *MsgUtilsCmd) AddLimitFlag() {
m.Command.PersistentFlags().Int64P("limit", "l", 0, "openIM limit")
}
/* func (m *MsgUtilsCmd) getLimitFlag(cmdLines *cobra.Command) int64 {
limit, _ := cmdLines.Flags().GetInt64("limit")
return limit
} */
func (m *MsgUtilsCmd) Execute() error {
return m.Command.Execute()
}
func NewMsgUtilsCmd(use, short string, args cobra.PositionalArgs) *MsgUtilsCmd {
return &MsgUtilsCmd{
Command: cobra.Command{
Use: use,
Short: short,
Args: args,
},
}
}
type GetCmd struct {
*MsgUtilsCmd
}
func NewGetCmd() *GetCmd {
return &GetCmd{
NewMsgUtilsCmd("get [resource]", "get action", cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs)),
}
}
type FixCmd struct {
*MsgUtilsCmd
}
func NewFixCmd() *FixCmd {
return &FixCmd{
NewMsgUtilsCmd("fix [resource]", "fix action", cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs)),
}
}
type ClearCmd struct {
*MsgUtilsCmd
}
func NewClearCmd() *ClearCmd {
return &ClearCmd{
NewMsgUtilsCmd("clear [resource]", "clear action", cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs)),
}
}
type SeqCmd struct {
*MsgUtilsCmd
}
func NewSeqCmd() *SeqCmd {
seqCmd := &SeqCmd{
NewMsgUtilsCmd("seq", "seq", nil),
}
return seqCmd
}
func (s *SeqCmd) GetSeqCmd() *cobra.Command {
s.Command.Run = func(cmdLines *cobra.Command, args []string) {
}
return &s.Command
}
func (s *SeqCmd) FixSeqCmd() *cobra.Command {
return &s.Command
}
type MsgCmd struct {
*MsgUtilsCmd
}
func NewMsgCmd() *MsgCmd {
msgCmd := &MsgCmd{
NewMsgUtilsCmd("msg", "msg", nil),
}
return msgCmd
}
func (m *MsgCmd) GetMsgCmd() *cobra.Command {
return &m.Command
}
func (m *MsgCmd) ClearMsgCmd() *cobra.Command {
return &m.Command
}

80
pkg/common/cmd/push.go Normal file
View File

@@ -0,0 +1,80 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/push"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type PushRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
pushConfig *push.Config
}
func NewPushRpcCmd() *PushRpcCmd {
var pushConfig push.Config
ret := &PushRpcCmd{pushConfig: &pushConfig}
ret.configMap = map[string]any{
config.OpenIMPushCfgFileName: &pushConfig.RpcConfig,
config.RedisConfigFileName: &pushConfig.RedisConfig,
config.MongodbConfigFileName: &pushConfig.MongoConfig,
config.KafkaConfigFileName: &pushConfig.KafkaConfig,
config.ShareFileName: &pushConfig.Share,
config.NotificationFileName: &pushConfig.NotificationConfig,
config.WebhooksConfigFileName: &pushConfig.WebhooksConfig,
config.LocalCacheConfigFileName: &pushConfig.LocalCacheConfig,
config.DiscoveryConfigFilename: &pushConfig.Discovery,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
ret.pushConfig.FcmConfigPath = config.Path(ret.ConfigPath())
return ret.runE()
}
return ret
}
func (a *PushRpcCmd) Exec() error {
return a.Execute()
}
func (a *PushRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.pushConfig.Discovery, &a.pushConfig.RpcConfig.CircuitBreaker, &a.pushConfig.RpcConfig.RateLimiter, &a.pushConfig.RpcConfig.Prometheus, a.pushConfig.RpcConfig.RPC.ListenIP,
a.pushConfig.RpcConfig.RPC.RegisterIP, a.pushConfig.RpcConfig.RPC.AutoSetPorts, a.pushConfig.RpcConfig.RPC.Ports,
a.Index(), a.pushConfig.Discovery.RpcService.Push, &a.pushConfig.NotificationConfig, a.pushConfig,
[]string{
a.pushConfig.RpcConfig.GetConfigFileName(),
a.pushConfig.RedisConfig.GetConfigFileName(),
a.pushConfig.KafkaConfig.GetConfigFileName(),
a.pushConfig.NotificationConfig.GetConfigFileName(),
a.pushConfig.Share.GetConfigFileName(),
a.pushConfig.WebhooksConfig.GetConfigFileName(),
a.pushConfig.LocalCacheConfig.GetConfigFileName(),
a.pushConfig.Discovery.GetConfigFileName(),
},
[]string{
a.pushConfig.Discovery.RpcService.MessageGateway,
},
push.Start)
}

251
pkg/common/cmd/root.go Normal file
View File

@@ -0,0 +1,251 @@
package cmd
import (
"context"
"encoding/json"
"fmt"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
kdisc "git.imall.cloud/openim/open-im-server-deploy/pkg/common/discovery"
disetcd "git.imall.cloud/openim/open-im-server-deploy/pkg/common/discovery/etcd"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"github.com/spf13/cobra"
clientv3 "go.etcd.io/etcd/client/v3"
)
type RootCmd struct {
Command cobra.Command
processName string
port int
prometheusPort int
log config.Log
index int
configPath string
etcdClient *clientv3.Client
}
func (r *RootCmd) ConfigPath() string {
return r.configPath
}
func (r *RootCmd) Index() int {
return r.index
}
func (r *RootCmd) Port() int {
return r.port
}
type CmdOpts struct {
loggerPrefixName string
configMap map[string]any
}
func WithCronTaskLogName() func(*CmdOpts) {
return func(opts *CmdOpts) {
opts.loggerPrefixName = "openim-crontask"
}
}
func WithLogName(logName string) func(*CmdOpts) {
return func(opts *CmdOpts) {
opts.loggerPrefixName = logName
}
}
func WithConfigMap(configMap map[string]any) func(*CmdOpts) {
return func(opts *CmdOpts) {
opts.configMap = configMap
}
}
func NewRootCmd(processName string, opts ...func(*CmdOpts)) *RootCmd {
rootCmd := &RootCmd{processName: processName}
cmd := cobra.Command{
Use: "Start openIM application",
Long: fmt.Sprintf(`Start %s `, processName),
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return rootCmd.persistentPreRun(cmd, opts...)
},
SilenceUsage: true,
SilenceErrors: false,
}
cmd.Flags().StringP(config.FlagConf, "c", "", "path of config directory")
cmd.Flags().IntP(config.FlagTransferIndex, "i", 0, "process startup sequence number")
rootCmd.Command = cmd
return rootCmd
}
func (r *RootCmd) initEtcd() error {
configDirectory, _, err := r.getFlag(&r.Command)
if err != nil {
return err
}
disConfig := config.Discovery{}
err = config.Load(configDirectory, config.DiscoveryConfigFilename, config.EnvPrefixMap[config.DiscoveryConfigFilename], &disConfig)
if err != nil {
return err
}
if disConfig.Enable == config.ETCD {
discov, _ := kdisc.NewDiscoveryRegister(&disConfig, nil)
if etcdDiscov, ok := discov.(*etcd.SvcDiscoveryRegistryImpl); ok {
r.etcdClient = etcdDiscov.GetClient()
}
}
return nil
}
func (r *RootCmd) persistentPreRun(cmd *cobra.Command, opts ...func(*CmdOpts)) error {
if err := r.initEtcd(); err != nil {
return err
}
cmdOpts := r.applyOptions(opts...)
if err := r.initializeConfiguration(cmd, cmdOpts); err != nil {
return err
}
if err := r.updateConfigFromEtcd(cmdOpts); err != nil {
return err
}
if err := r.initializeLogger(cmdOpts); err != nil {
return errs.WrapMsg(err, "failed to initialize logger")
}
if r.etcdClient != nil {
if err := r.etcdClient.Close(); err != nil {
return errs.WrapMsg(err, "failed to close etcd client")
}
}
return nil
}
func (r *RootCmd) initializeConfiguration(cmd *cobra.Command, opts *CmdOpts) error {
configDirectory, _, err := r.getFlag(cmd)
if err != nil {
return err
}
// Load common configuration file
//opts.configMap[ShareFileName] = StructEnvPrefix{EnvPrefix: shareEnvPrefix, ConfigStruct: &r.share}
for configFileName, configStruct := range opts.configMap {
err := config.Load(configDirectory, configFileName, config.EnvPrefixMap[configFileName], configStruct)
if err != nil {
return err
}
}
// Load common log configuration file
return config.Load(configDirectory, config.LogConfigFileName, config.EnvPrefixMap[config.LogConfigFileName], &r.log)
}
func (r *RootCmd) updateConfigFromEtcd(opts *CmdOpts) error {
if r.etcdClient == nil {
return nil
}
ctx := context.TODO()
res, err := r.etcdClient.Get(ctx, disetcd.BuildKey(disetcd.EnableConfigCenterKey))
if err != nil {
log.ZWarn(ctx, "root cmd updateConfigFromEtcd, etcd Get EnableConfigCenterKey err: %v", errs.Wrap(err))
return nil
}
if res.Count == 0 {
return nil
} else {
if string(res.Kvs[0].Value) == disetcd.Disable {
return nil
} else if string(res.Kvs[0].Value) != disetcd.Enable {
return errs.New("unknown EnableConfigCenter value").Wrap()
}
}
update := func(configFileName string, configStruct any) error {
key := disetcd.BuildKey(configFileName)
etcdRes, err := r.etcdClient.Get(ctx, key)
if err != nil {
log.ZWarn(ctx, "root cmd updateConfigFromEtcd, etcd Get err: %v", errs.Wrap(err))
return nil
}
if etcdRes.Count == 0 {
data, err := json.Marshal(configStruct)
if err != nil {
return errs.ErrArgs.WithDetail(err.Error()).Wrap()
}
_, err = r.etcdClient.Put(ctx, disetcd.BuildKey(configFileName), string(data))
if err != nil {
log.ZWarn(ctx, "root cmd updateConfigFromEtcd, etcd Put err: %v", errs.Wrap(err))
}
return nil
}
err = json.Unmarshal(etcdRes.Kvs[0].Value, configStruct)
if err != nil {
return errs.WrapMsg(err, "failed to unmarshal config from etcd")
}
return nil
}
for configFileName, configStruct := range opts.configMap {
if err := update(configFileName, configStruct); err != nil {
return err
}
}
if err := update(config.LogConfigFileName, &r.log); err != nil {
return err
}
// Load common log configuration file
return nil
}
func (r *RootCmd) applyOptions(opts ...func(*CmdOpts)) *CmdOpts {
cmdOpts := defaultCmdOpts()
for _, opt := range opts {
opt(cmdOpts)
}
return cmdOpts
}
func (r *RootCmd) initializeLogger(cmdOpts *CmdOpts) error {
err := log.InitLoggerFromConfig(
cmdOpts.loggerPrefixName,
r.processName,
"", "",
r.log.RemainLogLevel,
r.log.IsStdout,
r.log.IsJson,
r.log.StorageLocation,
r.log.RemainRotationCount,
r.log.RotationTime,
version.Version,
r.log.IsSimplify,
)
if err != nil {
return errs.Wrap(err)
}
return errs.Wrap(log.InitConsoleLogger(r.processName, r.log.RemainLogLevel, r.log.IsJson, version.Version))
}
func defaultCmdOpts() *CmdOpts {
return &CmdOpts{
loggerPrefixName: "openim-service-log",
}
}
func (r *RootCmd) getFlag(cmd *cobra.Command) (string, int, error) {
configDirectory, err := cmd.Flags().GetString(config.FlagConf)
if err != nil {
return "", 0, errs.Wrap(err)
}
r.configPath = configDirectory
index, err := cmd.Flags().GetInt(config.FlagTransferIndex)
if err != nil {
return "", 0, errs.Wrap(err)
}
r.index = index
return configDirectory, index, nil
}
func (r *RootCmd) Execute() error {
return r.Command.Execute()
}

75
pkg/common/cmd/third.go Normal file
View File

@@ -0,0 +1,75 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/rpc/third"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type ThirdRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
thirdConfig *third.Config
}
func NewThirdRpcCmd() *ThirdRpcCmd {
var thirdConfig third.Config
ret := &ThirdRpcCmd{thirdConfig: &thirdConfig}
ret.configMap = map[string]any{
config.OpenIMRPCThirdCfgFileName: &thirdConfig.RpcConfig,
config.RedisConfigFileName: &thirdConfig.RedisConfig,
config.MongodbConfigFileName: &thirdConfig.MongodbConfig,
config.ShareFileName: &thirdConfig.Share,
config.NotificationFileName: &thirdConfig.NotificationConfig,
config.MinioConfigFileName: &thirdConfig.MinioConfig,
config.LocalCacheConfigFileName: &thirdConfig.LocalCacheConfig,
config.DiscoveryConfigFilename: &thirdConfig.Discovery,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return ret
}
func (a *ThirdRpcCmd) Exec() error {
return a.Execute()
}
func (a *ThirdRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.thirdConfig.Discovery, &a.thirdConfig.RpcConfig.CircuitBreaker, &a.thirdConfig.RpcConfig.RateLimiter, &a.thirdConfig.RpcConfig.Prometheus, a.thirdConfig.RpcConfig.RPC.ListenIP,
a.thirdConfig.RpcConfig.RPC.RegisterIP, a.thirdConfig.RpcConfig.RPC.AutoSetPorts, a.thirdConfig.RpcConfig.RPC.Ports,
a.Index(), a.thirdConfig.Discovery.RpcService.Third, &a.thirdConfig.NotificationConfig, a.thirdConfig,
[]string{
a.thirdConfig.RpcConfig.GetConfigFileName(),
a.thirdConfig.RedisConfig.GetConfigFileName(),
a.thirdConfig.MongodbConfig.GetConfigFileName(),
a.thirdConfig.NotificationConfig.GetConfigFileName(),
a.thirdConfig.Share.GetConfigFileName(),
a.thirdConfig.MinioConfig.GetConfigFileName(),
a.thirdConfig.LocalCacheConfig.GetConfigFileName(),
a.thirdConfig.Discovery.GetConfigFileName(),
}, nil,
third.Start)
}

77
pkg/common/cmd/user.go Normal file
View File

@@ -0,0 +1,77 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/internal/rpc/user"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/startrpc"
"git.imall.cloud/openim/open-im-server-deploy/version"
"github.com/openimsdk/tools/system/program"
"github.com/spf13/cobra"
)
type UserRpcCmd struct {
*RootCmd
ctx context.Context
configMap map[string]any
userConfig *user.Config
}
func NewUserRpcCmd() *UserRpcCmd {
var userConfig user.Config
ret := &UserRpcCmd{userConfig: &userConfig}
ret.configMap = map[string]any{
config.OpenIMRPCUserCfgFileName: &userConfig.RpcConfig,
config.RedisConfigFileName: &userConfig.RedisConfig,
config.MongodbConfigFileName: &userConfig.MongodbConfig,
config.KafkaConfigFileName: &userConfig.KafkaConfig,
config.ShareFileName: &userConfig.Share,
config.NotificationFileName: &userConfig.NotificationConfig,
config.WebhooksConfigFileName: &userConfig.WebhooksConfig,
config.LocalCacheConfigFileName: &userConfig.LocalCacheConfig,
config.DiscoveryConfigFilename: &userConfig.Discovery,
}
ret.RootCmd = NewRootCmd(program.GetProcessName(), WithConfigMap(ret.configMap))
ret.ctx = context.WithValue(context.Background(), "version", version.Version)
ret.Command.RunE = func(cmd *cobra.Command, args []string) error {
return ret.runE()
}
return ret
}
func (a *UserRpcCmd) Exec() error {
return a.Execute()
}
func (a *UserRpcCmd) runE() error {
return startrpc.Start(a.ctx, &a.userConfig.Discovery, &a.userConfig.RpcConfig.CircuitBreaker, &a.userConfig.RpcConfig.RateLimiter, &a.userConfig.RpcConfig.Prometheus, a.userConfig.RpcConfig.RPC.ListenIP,
a.userConfig.RpcConfig.RPC.RegisterIP, a.userConfig.RpcConfig.RPC.AutoSetPorts, a.userConfig.RpcConfig.RPC.Ports,
a.Index(), a.userConfig.Discovery.RpcService.User, &a.userConfig.NotificationConfig, a.userConfig,
[]string{
a.userConfig.RpcConfig.GetConfigFileName(),
a.userConfig.RedisConfig.GetConfigFileName(),
a.userConfig.MongodbConfig.GetConfigFileName(),
a.userConfig.KafkaConfig.GetConfigFileName(),
a.userConfig.NotificationConfig.GetConfigFileName(),
a.userConfig.Share.GetConfigFileName(),
a.userConfig.WebhooksConfig.GetConfigFileName(),
a.userConfig.LocalCacheConfig.GetConfigFileName(),
a.userConfig.Discovery.GetConfigFileName(),
}, nil,
user.Start)
}

979
pkg/common/config/config.go Normal file
View File

@@ -0,0 +1,979 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"strings"
"time"
"github.com/openimsdk/tools/db/mongoutil"
"github.com/openimsdk/tools/db/redisutil"
"github.com/openimsdk/tools/mq/kafka"
"github.com/openimsdk/tools/s3/aws"
"github.com/openimsdk/tools/s3/cos"
"github.com/openimsdk/tools/s3/kodo"
"github.com/openimsdk/tools/s3/minio"
"github.com/openimsdk/tools/s3/oss"
)
const StructTagName = "yaml"
type Path string
type Index int
type CacheConfig struct {
Topic string `yaml:"topic"`
SlotNum int `yaml:"slotNum"`
SlotSize int `yaml:"slotSize"`
SuccessExpire int `yaml:"successExpire"`
FailedExpire int `yaml:"failedExpire"`
}
type LocalCache struct {
Auth CacheConfig `yaml:"auth"`
User CacheConfig `yaml:"user"`
Group CacheConfig `yaml:"group"`
Friend CacheConfig `yaml:"friend"`
Conversation CacheConfig `yaml:"conversation"`
}
type Log struct {
StorageLocation string `yaml:"storageLocation"`
RotationTime uint `yaml:"rotationTime"`
RemainRotationCount uint `yaml:"remainRotationCount"`
RemainLogLevel int `yaml:"remainLogLevel"`
IsStdout bool `yaml:"isStdout"`
IsJson bool `yaml:"isJson"`
IsSimplify bool `yaml:"isSimplify"`
WithStack bool `yaml:"withStack"`
}
type Minio struct {
Bucket string `yaml:"bucket"`
AccessKeyID string `yaml:"accessKeyID"`
SecretAccessKey string `yaml:"secretAccessKey"`
SessionToken string `yaml:"sessionToken"`
InternalAddress string `yaml:"internalAddress"`
ExternalAddress string `yaml:"externalAddress"`
PublicRead bool `yaml:"publicRead"`
}
type Mongo struct {
URI string `yaml:"uri"`
Address []string `yaml:"address"`
Database string `yaml:"database"`
Username string `yaml:"username"`
Password string `yaml:"password"`
AuthSource string `yaml:"authSource"`
MaxPoolSize int `yaml:"maxPoolSize"`
MaxRetry int `yaml:"maxRetry"`
MongoMode string `yaml:"mongoMode"`
ReplicaSet ReplicaSetConfig
ReadPreference ReadPrefConfig
WriteConcern WriteConcernConfig
}
type ReplicaSetConfig struct {
Name string `yaml:"name"`
Hosts []string `yaml:"hosts"`
ReadConcern string `yaml:"readConcern"`
MaxStaleness time.Duration `yaml:"maxStaleness"`
}
type ReadPrefConfig struct {
Mode string `yaml:"mode"`
TagSets []map[string]string `yaml:"tagSets"`
MaxStaleness time.Duration `yaml:"maxStaleness"`
}
type WriteConcernConfig struct {
W any `yaml:"w"`
J bool `yaml:"j"`
WTimeout time.Duration `yaml:"wtimeout"`
}
type Kafka struct {
Username string `yaml:"username"`
Password string `yaml:"password"`
ProducerAck string `yaml:"producerAck"`
CompressType string `yaml:"compressType"`
Address []string `yaml:"address"`
ToRedisTopic string `yaml:"toRedisTopic"`
ToMongoTopic string `yaml:"toMongoTopic"`
ToPushTopic string `yaml:"toPushTopic"`
ToOfflinePushTopic string `yaml:"toOfflinePushTopic"`
ToRedisGroupID string `yaml:"toRedisGroupID"`
ToMongoGroupID string `yaml:"toMongoGroupID"`
ToPushGroupID string `yaml:"toPushGroupID"`
ToOfflineGroupID string `yaml:"toOfflinePushGroupID"`
Tls TLSConfig `yaml:"tls"`
}
type TLSConfig struct {
EnableTLS bool `yaml:"enableTLS"`
CACrt string `yaml:"caCrt"`
ClientCrt string `yaml:"clientCrt"`
ClientKey string `yaml:"clientKey"`
ClientKeyPwd string `yaml:"clientKeyPwd"`
InsecureSkipVerify bool `yaml:"insecureSkipVerify"`
}
type API struct {
Api struct {
ListenIP string `yaml:"listenIP"`
Ports []int `yaml:"ports"`
CompressionLevel int `yaml:"compressionLevel"`
} `yaml:"api"`
Prometheus struct {
Enable bool `yaml:"enable"`
AutoSetPorts bool `yaml:"autoSetPorts"`
Ports []int `yaml:"ports"`
GrafanaURL string `yaml:"grafanaURL"`
} `yaml:"prometheus"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
OnlineCountRefresh struct {
Enable bool `yaml:"enable"`
Interval time.Duration `yaml:"interval"`
} `yaml:"onlineCountRefresh"`
}
type CronTask struct {
CronExecuteTime string `yaml:"cronExecuteTime"`
RetainChatRecords int `yaml:"retainChatRecords"`
FileExpireTime int `yaml:"fileExpireTime"`
DeleteObjectType []string `yaml:"deleteObjectType"`
}
type OfflinePushConfig struct {
Enable bool `yaml:"enable"`
Title string `yaml:"title"`
Desc string `yaml:"desc"`
Ext string `yaml:"ext"`
}
type NotificationConfig struct {
IsSendMsg bool `yaml:"isSendMsg"`
ReliabilityLevel int `yaml:"reliabilityLevel"`
UnreadCount bool `yaml:"unreadCount"`
OfflinePush OfflinePushConfig `yaml:"offlinePush"`
}
type Notification struct {
GroupCreated NotificationConfig `yaml:"groupCreated"`
GroupInfoSet NotificationConfig `yaml:"groupInfoSet"`
JoinGroupApplication NotificationConfig `yaml:"joinGroupApplication"`
MemberQuit NotificationConfig `yaml:"memberQuit"`
GroupApplicationAccepted NotificationConfig `yaml:"groupApplicationAccepted"`
GroupApplicationRejected NotificationConfig `yaml:"groupApplicationRejected"`
GroupOwnerTransferred NotificationConfig `yaml:"groupOwnerTransferred"`
MemberKicked NotificationConfig `yaml:"memberKicked"`
MemberInvited NotificationConfig `yaml:"memberInvited"`
MemberEnter NotificationConfig `yaml:"memberEnter"`
GroupDismissed NotificationConfig `yaml:"groupDismissed"`
GroupMuted NotificationConfig `yaml:"groupMuted"`
GroupCancelMuted NotificationConfig `yaml:"groupCancelMuted"`
GroupMemberMuted NotificationConfig `yaml:"groupMemberMuted"`
GroupMemberCancelMuted NotificationConfig `yaml:"groupMemberCancelMuted"`
GroupMemberInfoSet NotificationConfig `yaml:"groupMemberInfoSet"`
GroupMemberSetToAdmin NotificationConfig `yaml:"groupMemberSetToAdmin"`
GroupMemberSetToOrdinary NotificationConfig `yaml:"groupMemberSetToOrdinaryUser"`
GroupInfoSetAnnouncement NotificationConfig `yaml:"groupInfoSetAnnouncement"`
GroupInfoSetName NotificationConfig `yaml:"groupInfoSetName"`
FriendApplicationAdded NotificationConfig `yaml:"friendApplicationAdded"`
FriendApplicationApproved NotificationConfig `yaml:"friendApplicationApproved"`
FriendApplicationRejected NotificationConfig `yaml:"friendApplicationRejected"`
FriendAdded NotificationConfig `yaml:"friendAdded"`
FriendDeleted NotificationConfig `yaml:"friendDeleted"`
FriendRemarkSet NotificationConfig `yaml:"friendRemarkSet"`
BlackAdded NotificationConfig `yaml:"blackAdded"`
BlackDeleted NotificationConfig `yaml:"blackDeleted"`
FriendInfoUpdated NotificationConfig `yaml:"friendInfoUpdated"`
UserInfoUpdated NotificationConfig `yaml:"userInfoUpdated"`
UserStatusChanged NotificationConfig `yaml:"userStatusChanged"`
ConversationChanged NotificationConfig `yaml:"conversationChanged"`
ConversationSetPrivate NotificationConfig `yaml:"conversationSetPrivate"`
}
type Prometheus struct {
Enable bool `yaml:"enable"`
Ports []int `yaml:"ports"`
}
type MsgGateway struct {
RPC RPC `yaml:"rpc"`
Prometheus Prometheus `yaml:"prometheus"`
ListenIP string `yaml:"listenIP"`
LongConnSvr struct {
Ports []int `yaml:"ports"`
WebsocketMaxConnNum int `yaml:"websocketMaxConnNum"`
WebsocketMaxMsgLen int `yaml:"websocketMaxMsgLen"`
WebsocketTimeout int `yaml:"websocketTimeout"`
} `yaml:"longConnSvr"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
}
type MsgTransfer struct {
Prometheus struct {
Enable bool `yaml:"enable"`
AutoSetPorts bool `yaml:"autoSetPorts"`
Ports []int `yaml:"ports"`
} `yaml:"prometheus"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
}
type Push struct {
RPC RPC `yaml:"rpc"`
Prometheus Prometheus `yaml:"prometheus"`
MaxConcurrentWorkers int `yaml:"maxConcurrentWorkers"`
Enable string `yaml:"enable"`
GeTui struct {
PushUrl string `yaml:"pushUrl"`
MasterSecret string `yaml:"masterSecret"`
AppKey string `yaml:"appKey"`
Intent string `yaml:"intent"`
ChannelID string `yaml:"channelID"`
ChannelName string `yaml:"channelName"`
} `yaml:"geTui"`
FCM struct {
FilePath string `yaml:"filePath"`
AuthURL string `yaml:"authURL"`
} `yaml:"fcm"`
JPush struct {
AppKey string `yaml:"appKey"`
MasterSecret string `yaml:"masterSecret"`
PushURL string `yaml:"pushURL"`
PushIntent string `yaml:"pushIntent"`
} `yaml:"jpush"`
IOSPush struct {
PushSound string `yaml:"pushSound"`
BadgeCount bool `yaml:"badgeCount"`
Production bool `yaml:"production"`
} `yaml:"iosPush"`
FullUserCache bool `yaml:"fullUserCache"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
}
type Auth struct {
RPC RPC `yaml:"rpc"`
Prometheus Prometheus `yaml:"prometheus"`
TokenPolicy struct {
Expire int64 `yaml:"expire"`
} `yaml:"tokenPolicy"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
}
type Conversation struct {
RPC RPC `yaml:"rpc"`
Prometheus Prometheus `yaml:"prometheus"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
}
type Friend struct {
RPC RPC `yaml:"rpc"`
Prometheus Prometheus `yaml:"prometheus"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
}
type Group struct {
RPC RPC `yaml:"rpc"`
Prometheus Prometheus `yaml:"prometheus"`
EnableHistoryForNewMembers bool `yaml:"enableHistoryForNewMembers"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
}
type Msg struct {
RPC RPC `yaml:"rpc"`
Prometheus Prometheus `yaml:"prometheus"`
FriendVerify bool `yaml:"friendVerify"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
}
type Third struct {
RPC RPC `yaml:"rpc"`
Prometheus Prometheus `yaml:"prometheus"`
Object struct {
Enable string `yaml:"enable"`
Cos Cos `yaml:"cos"`
Oss Oss `yaml:"oss"`
Kodo Kodo `yaml:"kodo"`
Aws Aws `yaml:"aws"`
} `yaml:"object"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
}
type Cos struct {
BucketURL string `yaml:"bucketURL"`
SecretID string `yaml:"secretID"`
SecretKey string `yaml:"secretKey"`
SessionToken string `yaml:"sessionToken"`
PublicRead bool `yaml:"publicRead"`
}
type Oss struct {
Endpoint string `yaml:"endpoint"`
Bucket string `yaml:"bucket"`
BucketURL string `yaml:"bucketURL"`
AccessKeyID string `yaml:"accessKeyID"`
AccessKeySecret string `yaml:"accessKeySecret"`
SessionToken string `yaml:"sessionToken"`
PublicRead bool `yaml:"publicRead"`
}
type Kodo struct {
Endpoint string `yaml:"endpoint"`
Bucket string `yaml:"bucket"`
BucketURL string `yaml:"bucketURL"`
AccessKeyID string `yaml:"accessKeyID"`
AccessKeySecret string `yaml:"accessKeySecret"`
SessionToken string `yaml:"sessionToken"`
PublicRead bool `yaml:"publicRead"`
}
type Aws struct {
Region string `yaml:"region"`
Bucket string `yaml:"bucket"`
Endpoint string `yaml:"endpoint"`
AccessKeyID string `yaml:"accessKeyID"`
SecretAccessKey string `yaml:"secretAccessKey"`
SessionToken string `yaml:"sessionToken"`
PublicRead bool `yaml:"publicRead"`
}
type User struct {
RPC RPC `yaml:"rpc"`
Prometheus Prometheus `yaml:"prometheus"`
RateLimiter RateLimiter `yaml:"ratelimiter"`
CircuitBreaker CircuitBreaker `yaml:"circuitBreaker"`
}
type RPC struct {
RegisterIP string `yaml:"registerIP"`
ListenIP string `yaml:"listenIP"`
AutoSetPorts bool `yaml:"autoSetPorts"`
Ports []int `yaml:"ports"`
}
type Redis struct {
Disable bool `yaml:"-"`
Address []string `yaml:"address"`
Username string `yaml:"username"`
Password string `yaml:"password"`
RedisMode string `yaml:"redisMode"`
DB int `yaml:"db"`
MaxRetry int `yaml:"maxRetry"`
PoolSize int `yaml:"poolSize"`
OnlineKeyPrefix string `yaml:"onlineKeyPrefix"`
OnlineKeyPrefixHashTag bool `yaml:"onlineKeyPrefixHashTag"`
SentinelMode Sentinel `yaml:"sentinelMode"`
}
type Sentinel struct {
MasterName string `yaml:"masterName"`
SentinelAddrs []string `yaml:"sentinelsAddrs"`
RouteByLatency bool `yaml:"routeByLatency"`
RouteRandomly bool `yaml:"routeRandomly"`
}
type BeforeConfig struct {
Enable bool `yaml:"enable"`
Timeout int `yaml:"timeout"`
FailedContinue bool `yaml:"failedContinue"`
DeniedTypes []int32 `yaml:"deniedTypes"`
}
type AfterConfig struct {
Enable bool `yaml:"enable"`
Timeout int `yaml:"timeout"`
AttentionIds []string `yaml:"attentionIds"`
DeniedTypes []int32 `yaml:"deniedTypes"`
}
type RateLimiter struct {
Enable bool `yaml:"enable"`
Window time.Duration `yaml:"window"`
Bucket int `yaml:"bucket"`
CPUThreshold int64 `yaml:"cpuThreshold"`
}
type CircuitBreaker struct {
Enable bool `yaml:"enable"`
Window time.Duration `yaml:"window"`
Bucket int `yaml:"bucket"`
Success float64 `yaml:"success"`
Request int64 `yaml:"request"`
}
type Share struct {
Secret string `yaml:"secret"`
IMAdminUser struct {
UserIDs []string `yaml:"userIDs"`
Nicknames []string `yaml:"nicknames"`
} `yaml:"imAdminUser"`
MultiLogin MultiLogin `yaml:"multiLogin"`
RPCMaxBodySize MaxRequestBody `yaml:"rpcMaxBodySize"`
}
type MaxRequestBody struct {
RequestMaxBodySize int `yaml:"requestMaxBodySize"`
ResponseMaxBodySize int `yaml:"responseMaxBodySize"`
}
type MultiLogin struct {
Policy int `yaml:"policy"`
MaxNumOneEnd int `yaml:"maxNumOneEnd"`
}
type RpcService struct {
User string `yaml:"user"`
Friend string `yaml:"friend"`
Msg string `yaml:"msg"`
Push string `yaml:"push"`
MessageGateway string `yaml:"messageGateway"`
Group string `yaml:"group"`
Auth string `yaml:"auth"`
Conversation string `yaml:"conversation"`
Third string `yaml:"third"`
}
func (r *RpcService) GetServiceNames() []string {
return []string{
r.User,
r.Friend,
r.Msg,
r.Push,
r.MessageGateway,
r.Group,
r.Auth,
r.Conversation,
r.Third,
}
}
// FullConfig stores all configurations for before and after events
type Webhooks struct {
URL string `yaml:"url"`
BeforeSendSingleMsg BeforeConfig `yaml:"beforeSendSingleMsg"`
BeforeUpdateUserInfoEx BeforeConfig `yaml:"beforeUpdateUserInfoEx"`
AfterUpdateUserInfoEx AfterConfig `yaml:"afterUpdateUserInfoEx"`
AfterSendSingleMsg AfterConfig `yaml:"afterSendSingleMsg"`
BeforeSendGroupMsg BeforeConfig `yaml:"beforeSendGroupMsg"`
BeforeMsgModify BeforeConfig `yaml:"beforeMsgModify"`
AfterSendGroupMsg AfterConfig `yaml:"afterSendGroupMsg"`
AfterMsgSaveDB AfterConfig `yaml:"afterMsgSaveDB"`
AfterUserOnline AfterConfig `yaml:"afterUserOnline"`
AfterUserOffline AfterConfig `yaml:"afterUserOffline"`
AfterUserKickOff AfterConfig `yaml:"afterUserKickOff"`
BeforeOfflinePush BeforeConfig `yaml:"beforeOfflinePush"`
BeforeOnlinePush BeforeConfig `yaml:"beforeOnlinePush"`
BeforeGroupOnlinePush BeforeConfig `yaml:"beforeGroupOnlinePush"`
BeforeAddFriend BeforeConfig `yaml:"beforeAddFriend"`
BeforeUpdateUserInfo BeforeConfig `yaml:"beforeUpdateUserInfo"`
AfterUpdateUserInfo AfterConfig `yaml:"afterUpdateUserInfo"`
BeforeCreateGroup BeforeConfig `yaml:"beforeCreateGroup"`
AfterCreateGroup AfterConfig `yaml:"afterCreateGroup"`
BeforeMemberJoinGroup BeforeConfig `yaml:"beforeMemberJoinGroup"`
BeforeSetGroupMemberInfo BeforeConfig `yaml:"beforeSetGroupMemberInfo"`
AfterSetGroupMemberInfo AfterConfig `yaml:"afterSetGroupMemberInfo"`
AfterQuitGroup AfterConfig `yaml:"afterQuitGroup"`
AfterKickGroupMember AfterConfig `yaml:"afterKickGroupMember"`
AfterDismissGroup AfterConfig `yaml:"afterDismissGroup"`
BeforeApplyJoinGroup BeforeConfig `yaml:"beforeApplyJoinGroup"`
AfterGroupMsgRead AfterConfig `yaml:"afterGroupMsgRead"`
AfterSingleMsgRead AfterConfig `yaml:"afterSingleMsgRead"`
BeforeUserRegister BeforeConfig `yaml:"beforeUserRegister"`
AfterUserRegister AfterConfig `yaml:"afterUserRegister"`
AfterTransferGroupOwner AfterConfig `yaml:"afterTransferGroupOwner"`
BeforeSetFriendRemark BeforeConfig `yaml:"beforeSetFriendRemark"`
AfterSetFriendRemark AfterConfig `yaml:"afterSetFriendRemark"`
AfterGroupMsgRevoke AfterConfig `yaml:"afterGroupMsgRevoke"`
AfterJoinGroup AfterConfig `yaml:"afterJoinGroup"`
BeforeInviteUserToGroup BeforeConfig `yaml:"beforeInviteUserToGroup"`
AfterSetGroupInfo AfterConfig `yaml:"afterSetGroupInfo"`
BeforeSetGroupInfo BeforeConfig `yaml:"beforeSetGroupInfo"`
AfterSetGroupInfoEx AfterConfig `yaml:"afterSetGroupInfoEx"`
BeforeSetGroupInfoEx BeforeConfig `yaml:"beforeSetGroupInfoEx"`
AfterRevokeMsg AfterConfig `yaml:"afterRevokeMsg"`
BeforeAddBlack BeforeConfig `yaml:"beforeAddBlack"`
AfterAddFriend AfterConfig `yaml:"afterAddFriend"`
BeforeAddFriendAgree BeforeConfig `yaml:"beforeAddFriendAgree"`
AfterAddFriendAgree AfterConfig `yaml:"afterAddFriendAgree"`
AfterDeleteFriend AfterConfig `yaml:"afterDeleteFriend"`
BeforeImportFriends BeforeConfig `yaml:"beforeImportFriends"`
AfterImportFriends AfterConfig `yaml:"afterImportFriends"`
AfterRemoveBlack AfterConfig `yaml:"afterRemoveBlack"`
BeforeCreateSingleChatConversations BeforeConfig `yaml:"beforeCreateSingleChatConversations"`
AfterCreateSingleChatConversations AfterConfig `yaml:"afterCreateSingleChatConversations"`
BeforeCreateGroupChatConversations BeforeConfig `yaml:"beforeCreateGroupChatConversations"`
AfterCreateGroupChatConversations AfterConfig `yaml:"afterCreateGroupChatConversations"`
}
type ZooKeeper struct {
Schema string `yaml:"schema"`
Address []string `yaml:"address"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}
type Discovery struct {
Enable string `yaml:"enable"`
Etcd Etcd `yaml:"etcd"`
Kubernetes Kubernetes `yaml:"kubernetes"`
RpcService RpcService `yaml:"rpcService"`
}
type Kubernetes struct {
Namespace string `yaml:"namespace"`
}
type Etcd struct {
RootDirectory string `yaml:"rootDirectory"`
Address []string `yaml:"address"`
Username string `yaml:"username"`
Password string `yaml:"password"`
}
func (m *Mongo) Build() *mongoutil.Config {
return &mongoutil.Config{
Uri: m.URI,
Address: m.Address,
Database: m.Database,
Username: m.Username,
Password: m.Password,
AuthSource: m.AuthSource,
MaxPoolSize: m.MaxPoolSize,
MaxRetry: m.MaxRetry,
MongoMode: m.MongoMode,
ReplicaSet: &mongoutil.ReplicaSetConfig{
Name: m.ReplicaSet.Name,
Hosts: m.ReplicaSet.Hosts,
ReadConcern: m.ReplicaSet.ReadConcern,
MaxStaleness: m.ReplicaSet.MaxStaleness,
},
ReadPreference: &mongoutil.ReadPrefConfig{
Mode: m.ReadPreference.Mode,
TagSets: m.ReadPreference.TagSets,
MaxStaleness: m.ReadPreference.MaxStaleness,
},
WriteConcern: &mongoutil.WriteConcernConfig{
W: m.WriteConcern.W,
J: m.WriteConcern.J,
WTimeout: m.WriteConcern.WTimeout,
},
}
}
func (r *Redis) Build() *redisutil.Config {
return &redisutil.Config{
RedisMode: r.RedisMode,
Address: r.Address,
Username: r.Username,
Password: r.Password,
DB: r.DB,
MaxRetry: r.MaxRetry,
PoolSize: r.PoolSize,
Sentinel: &redisutil.Sentinel{
MasterName: r.SentinelMode.MasterName,
SentinelAddrs: r.SentinelMode.SentinelAddrs,
RouteByLatency: r.SentinelMode.RouteByLatency,
RouteRandomly: r.SentinelMode.RouteRandomly,
},
}
}
func (k *Kafka) Build() *kafka.Config {
return &kafka.Config{
Username: k.Username,
Password: k.Password,
ProducerAck: k.ProducerAck,
CompressType: k.CompressType,
Addr: k.Address,
TLS: kafka.TLSConfig{
EnableTLS: k.Tls.EnableTLS,
CACrt: k.Tls.CACrt,
ClientCrt: k.Tls.ClientCrt,
ClientKey: k.Tls.ClientKey,
ClientKeyPwd: k.Tls.ClientKeyPwd,
InsecureSkipVerify: k.Tls.InsecureSkipVerify,
},
}
}
func (m *Minio) Build() *minio.Config {
formatEndpoint := func(address string) string {
if strings.HasPrefix(address, "http://") || strings.HasPrefix(address, "https://") {
return address
}
return "http://" + address
}
return &minio.Config{
Bucket: m.Bucket,
AccessKeyID: m.AccessKeyID,
SecretAccessKey: m.SecretAccessKey,
SessionToken: m.SessionToken,
PublicRead: m.PublicRead,
Endpoint: formatEndpoint(m.InternalAddress),
SignEndpoint: formatEndpoint(m.ExternalAddress),
}
}
func (c *Cos) Build() *cos.Config {
return &cos.Config{
BucketURL: c.BucketURL,
SecretID: c.SecretID,
SecretKey: c.SecretKey,
SessionToken: c.SessionToken,
PublicRead: c.PublicRead,
}
}
func (o *Oss) Build() *oss.Config {
return &oss.Config{
Endpoint: o.Endpoint,
Bucket: o.Bucket,
BucketURL: o.BucketURL,
AccessKeyID: o.AccessKeyID,
AccessKeySecret: o.AccessKeySecret,
SessionToken: o.SessionToken,
PublicRead: o.PublicRead,
}
}
func (o *Kodo) Build() *kodo.Config {
return &kodo.Config{
Endpoint: o.Endpoint,
Bucket: o.Bucket,
BucketURL: o.BucketURL,
AccessKeyID: o.AccessKeyID,
AccessKeySecret: o.AccessKeySecret,
SessionToken: o.SessionToken,
PublicRead: o.PublicRead,
}
}
func (o *Aws) Build() *aws.Config {
return &aws.Config{
Region: o.Region,
Bucket: o.Bucket,
AccessKeyID: o.AccessKeyID,
SecretAccessKey: o.SecretAccessKey,
SessionToken: o.SessionToken,
}
}
func (l *CacheConfig) Failed() time.Duration {
return time.Second * time.Duration(l.FailedExpire)
}
func (l *CacheConfig) Success() time.Duration {
return time.Second * time.Duration(l.SuccessExpire)
}
func (l *CacheConfig) Enable() bool {
return l.Topic != "" && l.SlotNum > 0 && l.SlotSize > 0
}
func InitNotification(notification *Notification) {
notification.GroupCreated.UnreadCount = false
notification.GroupCreated.ReliabilityLevel = 1
notification.GroupInfoSet.UnreadCount = false
notification.GroupInfoSet.ReliabilityLevel = 1
notification.JoinGroupApplication.UnreadCount = false
notification.JoinGroupApplication.ReliabilityLevel = 1
notification.MemberQuit.UnreadCount = false
notification.MemberQuit.ReliabilityLevel = 1
notification.GroupApplicationAccepted.UnreadCount = false
notification.GroupApplicationAccepted.ReliabilityLevel = 1
notification.GroupApplicationRejected.UnreadCount = false
notification.GroupApplicationRejected.ReliabilityLevel = 1
notification.GroupOwnerTransferred.UnreadCount = false
notification.GroupOwnerTransferred.ReliabilityLevel = 1
notification.MemberKicked.UnreadCount = false
notification.MemberKicked.ReliabilityLevel = 1
notification.MemberInvited.UnreadCount = false
notification.MemberInvited.ReliabilityLevel = 1
notification.MemberEnter.UnreadCount = false
notification.MemberEnter.ReliabilityLevel = 1
notification.GroupDismissed.UnreadCount = false
notification.GroupDismissed.ReliabilityLevel = 1
notification.GroupMuted.UnreadCount = false
notification.GroupMuted.ReliabilityLevel = 1
notification.GroupCancelMuted.UnreadCount = false
notification.GroupCancelMuted.ReliabilityLevel = 1
notification.GroupMemberMuted.UnreadCount = false
notification.GroupMemberMuted.ReliabilityLevel = 1
notification.GroupMemberCancelMuted.UnreadCount = false
notification.GroupMemberCancelMuted.ReliabilityLevel = 1
notification.GroupMemberInfoSet.UnreadCount = false
notification.GroupMemberInfoSet.ReliabilityLevel = 1
notification.GroupMemberSetToAdmin.UnreadCount = false
notification.GroupMemberSetToAdmin.ReliabilityLevel = 1
notification.GroupMemberSetToOrdinary.UnreadCount = false
notification.GroupMemberSetToOrdinary.ReliabilityLevel = 1
notification.GroupInfoSetAnnouncement.UnreadCount = false
notification.GroupInfoSetAnnouncement.ReliabilityLevel = 1
notification.GroupInfoSetName.UnreadCount = false
notification.GroupInfoSetName.ReliabilityLevel = 1
notification.FriendApplicationAdded.UnreadCount = false
notification.FriendApplicationAdded.ReliabilityLevel = 1
notification.FriendApplicationApproved.UnreadCount = false
notification.FriendApplicationApproved.ReliabilityLevel = 1
notification.FriendApplicationRejected.UnreadCount = false
notification.FriendApplicationRejected.ReliabilityLevel = 1
notification.FriendAdded.UnreadCount = false
notification.FriendAdded.ReliabilityLevel = 1
notification.FriendDeleted.UnreadCount = false
notification.FriendDeleted.ReliabilityLevel = 1
notification.FriendRemarkSet.UnreadCount = false
notification.FriendRemarkSet.ReliabilityLevel = 1
notification.BlackAdded.UnreadCount = false
notification.BlackAdded.ReliabilityLevel = 1
notification.BlackDeleted.UnreadCount = false
notification.BlackDeleted.ReliabilityLevel = 1
notification.FriendInfoUpdated.UnreadCount = false
notification.FriendInfoUpdated.ReliabilityLevel = 1
notification.UserInfoUpdated.UnreadCount = false
notification.UserInfoUpdated.ReliabilityLevel = 1
notification.UserStatusChanged.UnreadCount = false
notification.UserStatusChanged.ReliabilityLevel = 1
notification.ConversationChanged.UnreadCount = false
notification.ConversationChanged.ReliabilityLevel = 1
notification.ConversationSetPrivate.UnreadCount = false
notification.ConversationSetPrivate.ReliabilityLevel = 1
}
type AllConfig struct {
Discovery Discovery
Kafka Kafka
LocalCache LocalCache
Log Log
Minio Minio
Mongo Mongo
Notification Notification
API API
CronTask CronTask
MsgGateway MsgGateway
MsgTransfer MsgTransfer
Push Push
Auth Auth
Conversation Conversation
Friend Friend
Group Group
Msg Msg
Third Third
User User
Redis Redis
Share Share
Webhooks Webhooks
}
func (a *AllConfig) Name2Config(name string) any {
switch name {
case a.Discovery.GetConfigFileName():
return a.Discovery
case a.Kafka.GetConfigFileName():
return a.Kafka
case a.LocalCache.GetConfigFileName():
return a.LocalCache
case a.Log.GetConfigFileName():
return a.Log
case a.Minio.GetConfigFileName():
return a.Minio
case a.Mongo.GetConfigFileName():
return a.Mongo
case a.Notification.GetConfigFileName():
return a.Notification
case a.API.GetConfigFileName():
return a.API
case a.CronTask.GetConfigFileName():
return a.CronTask
case a.MsgGateway.GetConfigFileName():
return a.MsgGateway
case a.MsgTransfer.GetConfigFileName():
return a.MsgTransfer
case a.Push.GetConfigFileName():
return a.Push
case a.Auth.GetConfigFileName():
return a.Auth
case a.Conversation.GetConfigFileName():
return a.Conversation
case a.Friend.GetConfigFileName():
return a.Friend
case a.Group.GetConfigFileName():
return a.Group
case a.Msg.GetConfigFileName():
return a.Msg
case a.Third.GetConfigFileName():
return a.Third
case a.User.GetConfigFileName():
return a.User
case a.Redis.GetConfigFileName():
return a.Redis
case a.Share.GetConfigFileName():
return a.Share
case a.Webhooks.GetConfigFileName():
return a.Webhooks
default:
return nil
}
}
func (a *AllConfig) GetConfigNames() []string {
return []string{
a.Discovery.GetConfigFileName(),
a.Kafka.GetConfigFileName(),
a.LocalCache.GetConfigFileName(),
a.Log.GetConfigFileName(),
a.Minio.GetConfigFileName(),
a.Mongo.GetConfigFileName(),
a.Notification.GetConfigFileName(),
a.API.GetConfigFileName(),
a.CronTask.GetConfigFileName(),
a.MsgGateway.GetConfigFileName(),
a.MsgTransfer.GetConfigFileName(),
a.Push.GetConfigFileName(),
a.Auth.GetConfigFileName(),
a.Conversation.GetConfigFileName(),
a.Friend.GetConfigFileName(),
a.Group.GetConfigFileName(),
a.Msg.GetConfigFileName(),
a.Third.GetConfigFileName(),
a.User.GetConfigFileName(),
a.Redis.GetConfigFileName(),
a.Share.GetConfigFileName(),
a.Webhooks.GetConfigFileName(),
}
}
const (
FileName = "config.yaml"
DiscoveryConfigFilename = "discovery.yml"
KafkaConfigFileName = "kafka.yml"
LocalCacheConfigFileName = "local-cache.yml"
LogConfigFileName = "log.yml"
MinioConfigFileName = "minio.yml"
MongodbConfigFileName = "mongodb.yml"
NotificationFileName = "notification.yml"
OpenIMAPICfgFileName = "openim-api.yml"
OpenIMCronTaskCfgFileName = "openim-crontask.yml"
OpenIMMsgGatewayCfgFileName = "openim-msggateway.yml"
OpenIMMsgTransferCfgFileName = "openim-msgtransfer.yml"
OpenIMPushCfgFileName = "openim-push.yml"
OpenIMRPCAuthCfgFileName = "openim-rpc-auth.yml"
OpenIMRPCConversationCfgFileName = "openim-rpc-conversation.yml"
OpenIMRPCFriendCfgFileName = "openim-rpc-friend.yml"
OpenIMRPCGroupCfgFileName = "openim-rpc-group.yml"
OpenIMRPCMsgCfgFileName = "openim-rpc-msg.yml"
OpenIMRPCThirdCfgFileName = "openim-rpc-third.yml"
OpenIMRPCUserCfgFileName = "openim-rpc-user.yml"
RedisConfigFileName = "redis.yml"
ShareFileName = "share.yml"
WebhooksConfigFileName = "webhooks.yml"
)
func (d *Discovery) GetConfigFileName() string {
return DiscoveryConfigFilename
}
func (k *Kafka) GetConfigFileName() string {
return KafkaConfigFileName
}
func (lc *LocalCache) GetConfigFileName() string {
return LocalCacheConfigFileName
}
func (l *Log) GetConfigFileName() string {
return LogConfigFileName
}
func (m *Minio) GetConfigFileName() string {
return MinioConfigFileName
}
func (m *Mongo) GetConfigFileName() string {
return MongodbConfigFileName
}
func (n *Notification) GetConfigFileName() string {
return NotificationFileName
}
func (a *API) GetConfigFileName() string {
return OpenIMAPICfgFileName
}
func (ct *CronTask) GetConfigFileName() string {
return OpenIMCronTaskCfgFileName
}
func (mg *MsgGateway) GetConfigFileName() string {
return OpenIMMsgGatewayCfgFileName
}
func (mt *MsgTransfer) GetConfigFileName() string {
return OpenIMMsgTransferCfgFileName
}
func (p *Push) GetConfigFileName() string {
return OpenIMPushCfgFileName
}
func (a *Auth) GetConfigFileName() string {
return OpenIMRPCAuthCfgFileName
}
func (c *Conversation) GetConfigFileName() string {
return OpenIMRPCConversationCfgFileName
}
func (f *Friend) GetConfigFileName() string {
return OpenIMRPCFriendCfgFileName
}
func (g *Group) GetConfigFileName() string {
return OpenIMRPCGroupCfgFileName
}
func (m *Msg) GetConfigFileName() string {
return OpenIMRPCMsgCfgFileName
}
func (t *Third) GetConfigFileName() string {
return OpenIMRPCThirdCfgFileName
}
func (u *User) GetConfigFileName() string {
return OpenIMRPCUserCfgFileName
}
func (r *Redis) GetConfigFileName() string {
return RedisConfigFileName
}
func (s *Share) GetConfigFileName() string {
return ShareFileName
}
func (w *Webhooks) GetConfigFileName() string {
return WebhooksConfigFileName
}

View File

@@ -0,0 +1,47 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import "github.com/openimsdk/tools/utils/runtimeenv"
const ConfKey = "conf"
const (
MountConfigFilePath = "CONFIG_PATH"
DeploymentType = "DEPLOYMENT_TYPE"
KUBERNETES = runtimeenv.Kubernetes
ETCD = "etcd"
//Standalone = "standalone"
)
const (
// DefaultDirPerm is used for creating general directories, allowing the owner to read, write, and execute,
// while the group and others can only read and execute.
DefaultDirPerm = 0755
// PrivateFilePerm is used for sensitive files, allowing only the owner to read and write.
PrivateFilePerm = 0600
// ExecFilePerm is used for executable files, allowing the owner to read, write, and execute,
// while the group and others can only read.
ExecFilePerm = 0754
// SharedDirPerm is used for shared directories, allowing the owner and group to read, write, and execute,
// with no permissions for others.
SharedDirPerm = 0770
// ReadOnlyDirPerm is used for read-only directories, allowing the owner, group, and others to only read.
ReadOnlyDirPerm = 0555
)

15
pkg/common/config/doc.go Normal file
View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config // import "git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"

30
pkg/common/config/env.go Normal file
View File

@@ -0,0 +1,30 @@
package config
import "strings"
var EnvPrefixMap map[string]string
func init() {
EnvPrefixMap = make(map[string]string)
fileNames := []string{
FileName, NotificationFileName, ShareFileName, WebhooksConfigFileName,
KafkaConfigFileName, RedisConfigFileName,
MongodbConfigFileName, MinioConfigFileName, LogConfigFileName,
OpenIMAPICfgFileName, OpenIMCronTaskCfgFileName, OpenIMMsgGatewayCfgFileName,
OpenIMMsgTransferCfgFileName, OpenIMPushCfgFileName, OpenIMRPCAuthCfgFileName,
OpenIMRPCConversationCfgFileName, OpenIMRPCFriendCfgFileName, OpenIMRPCGroupCfgFileName,
OpenIMRPCMsgCfgFileName, OpenIMRPCThirdCfgFileName, OpenIMRPCUserCfgFileName, DiscoveryConfigFilename,
}
for _, fileName := range fileNames {
envKey := strings.TrimSuffix(strings.TrimSuffix(fileName, ".yml"), ".yaml")
envKey = "IMENV_" + envKey
envKey = strings.ToUpper(strings.ReplaceAll(envKey, "-", "_"))
EnvPrefixMap[fileName] = envKey
}
}
const (
FlagConf = "config_folder_path"
FlagTransferIndex = "index"
)

View File

@@ -0,0 +1,11 @@
package config
var standalone bool
func SetStandalone() {
standalone = true
}
func Standalone() bool {
return standalone
}

View File

@@ -0,0 +1,44 @@
package config
import (
"os"
"path/filepath"
"strings"
"github.com/mitchellh/mapstructure"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/runtimeenv"
"github.com/spf13/viper"
)
func Load(configDirectory string, configFileName string, envPrefix string, config any) error {
if runtimeenv.RuntimeEnvironment() == KUBERNETES {
mountPath := os.Getenv(MountConfigFilePath)
if mountPath == "" {
return errs.ErrArgs.WrapMsg(MountConfigFilePath + " env is empty")
}
return loadConfig(filepath.Join(mountPath, configFileName), envPrefix, config)
}
return loadConfig(filepath.Join(configDirectory, configFileName), envPrefix, config)
}
func loadConfig(path string, envPrefix string, config any) error {
v := viper.New()
v.SetConfigFile(path)
v.SetEnvPrefix(envPrefix)
v.AutomaticEnv()
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
if err := v.ReadInConfig(); err != nil {
return errs.WrapMsg(err, "failed to read config file", "path", path, "envPrefix", envPrefix)
}
if err := v.Unmarshal(config, func(config *mapstructure.DecoderConfig) {
config.TagName = StructTagName
}); err != nil {
return errs.WrapMsg(err, "failed to unmarshal config", "path", path, "envPrefix", envPrefix)
}
return nil
}

View File

@@ -0,0 +1,93 @@
package config
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestLoadLogConfig(t *testing.T) {
var log Log
os.Setenv("IMENV_LOG_REMAINLOGLEVEL", "5")
err := Load("../../../config/", "log.yml", "IMENV_LOG", &log)
assert.Nil(t, err)
t.Log(log.RemainLogLevel)
// assert.Equal(t, "../../../../logs/", log.StorageLocation)
}
func TestLoadMongoConfig(t *testing.T) {
var mongo Mongo
// os.Setenv("DEPLOYMENT_TYPE", "kubernetes")
os.Setenv("IMENV_MONGODB_PASSWORD", "openIM1231231")
// os.Setenv("IMENV_MONGODB_URI", "openIM123")
// os.Setenv("IMENV_MONGODB_USERNAME", "openIM123")
err := Load("../../../config/", "mongodb.yml", "IMENV_MONGODB", &mongo)
// err := LoadApiConfig("../../../config/mongodb.yml", "IMENV_MONGODB", &mongo)
assert.Nil(t, err)
t.Log(mongo.Password)
// assert.Equal(t, "openIM123", mongo.Password)
t.Log(os.Getenv("IMENV_MONGODB_PASSWORD"))
t.Log(mongo)
// //export IMENV_OPENIM_RPC_USER_RPC_LISTENIP="0.0.0.0"
// assert.Equal(t, "0.0.0.0", user.RPC.ListenIP)
// //export IMENV_OPENIM_RPC_USER_RPC_PORTS="10110,10111,10112"
// assert.Equal(t, []int{10110, 10111, 10112}, user.RPC.Ports)
}
func TestLoadMinioConfig(t *testing.T) {
var storageConfig Minio
err := Load("../../../config/minio.yml", "IMENV_MINIO", "", &storageConfig)
assert.Nil(t, err)
assert.Equal(t, "openim", storageConfig.Bucket)
}
func TestLoadWebhooksConfig(t *testing.T) {
var webhooks Webhooks
err := Load("../../../config/webhooks.yml", "IMENV_WEBHOOKS", "", &webhooks)
assert.Nil(t, err)
assert.Equal(t, 5, webhooks.BeforeAddBlack.Timeout)
}
func TestLoadOpenIMRpcUserConfig(t *testing.T) {
var user User
err := Load("../../../config/openim-rpc-user.yml", "IMENV_OPENIM_RPC_USER", "", &user)
assert.Nil(t, err)
//export IMENV_OPENIM_RPC_USER_RPC_LISTENIP="0.0.0.0"
assert.Equal(t, "0.0.0.0", user.RPC.ListenIP)
//export IMENV_OPENIM_RPC_USER_RPC_PORTS="10110,10111,10112"
assert.Equal(t, []int{10110, 10111, 10112}, user.RPC.Ports)
}
func TestLoadNotificationConfig(t *testing.T) {
var noti Notification
err := Load("../../../config/notification.yml", "IMENV_NOTIFICATION", "", &noti)
assert.Nil(t, err)
assert.Equal(t, "Your friend's profile has been changed", noti.FriendRemarkSet.OfflinePush.Title)
}
func TestLoadOpenIMThirdConfig(t *testing.T) {
var third Third
err := Load("../../../config/openim-rpc-third.yml", "IMENV_OPENIM_RPC_THIRD", "", &third)
assert.Nil(t, err)
assert.Equal(t, "enabled", third.Object.Enable)
assert.Equal(t, "https://oss-cn-chengdu.aliyuncs.com", third.Object.Oss.Endpoint)
assert.Equal(t, "my_bucket_name", third.Object.Oss.Bucket)
assert.Equal(t, "https://my_bucket_name.oss-cn-chengdu.aliyuncs.com", third.Object.Oss.BucketURL)
assert.Equal(t, "AKID1234567890", third.Object.Oss.AccessKeyID)
assert.Equal(t, "abc123xyz789", third.Object.Oss.AccessKeySecret)
assert.Equal(t, "session_token_value", third.Object.Oss.SessionToken) // Uncomment if session token is needed
assert.Equal(t, true, third.Object.Oss.PublicRead)
// Environment: IMENV_OPENIM_RPC_THIRD_OBJECT_ENABLE=enabled;IMENV_OPENIM_RPC_THIRD_OBJECT_OSS_ENDPOINT=https://oss-cn-chengdu.aliyuncs.com;IMENV_OPENIM_RPC_THIRD_OBJECT_OSS_BUCKET=my_bucket_name;IMENV_OPENIM_RPC_THIRD_OBJECT_OSS_BUCKETURL=https://my_bucket_name.oss-cn-chengdu.aliyuncs.com;IMENV_OPENIM_RPC_THIRD_OBJECT_OSS_ACCESSKEYID=AKID1234567890;IMENV_OPENIM_RPC_THIRD_OBJECT_OSS_ACCESSKEYSECRET=abc123xyz789;IMENV_OPENIM_RPC_THIRD_OBJECT_OSS_SESSIONTOKEN=session_token_value;IMENV_OPENIM_RPC_THIRD_OBJECT_OSS_PUBLICREAD=true
}
func TestTransferConfig(t *testing.T) {
var tran MsgTransfer
err := Load("../../../config/openim-msgtransfer.yml", "IMENV_OPENIM-MSGTRANSFER", "", &tran)
assert.Nil(t, err)
assert.Equal(t, true, tran.Prometheus.Enable)
assert.Equal(t, true, tran.Prometheus.AutoSetPorts)
}

107
pkg/common/config/parse.go Normal file
View File

@@ -0,0 +1,107 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package config
import (
"os"
"path/filepath"
"gopkg.in/yaml.v3"
"git.imall.cloud/openim/open-im-server-deploy/pkg/msgprocessor"
"git.imall.cloud/openim/protocol/constant"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/field"
)
const (
DefaultFolderPath = "../config/"
)
// return absolude path join ../config/, this is k8s container config path.
func GetDefaultConfigPath() (string, error) {
executablePath, err := os.Executable()
if err != nil {
return "", errs.WrapMsg(err, "failed to get executable path")
}
configPath, err := field.OutDir(filepath.Join(filepath.Dir(executablePath), "../config/"))
if err != nil {
return "", errs.WrapMsg(err, "failed to get output directory", "outDir", filepath.Join(filepath.Dir(executablePath), "../config/"))
}
return configPath, nil
}
// getProjectRoot returns the absolute path of the project root directory.
func GetProjectRoot() (string, error) {
executablePath, err := os.Executable()
if err != nil {
return "", errs.Wrap(err)
}
projectRoot, err := field.OutDir(filepath.Join(filepath.Dir(executablePath), "../../../../.."))
if err != nil {
return "", errs.Wrap(err)
}
return projectRoot, nil
}
func GetOptionsByNotification(cfg NotificationConfig, sendMessage *bool) msgprocessor.Options {
opts := msgprocessor.NewOptions()
if sendMessage != nil {
cfg.IsSendMsg = *sendMessage
}
if cfg.IsSendMsg {
opts = msgprocessor.WithOptions(opts, msgprocessor.WithUnreadCount(true))
}
if cfg.OfflinePush.Enable {
opts = msgprocessor.WithOptions(opts, msgprocessor.WithOfflinePush(true))
}
switch cfg.ReliabilityLevel {
case constant.UnreliableNotification:
case constant.ReliableNotificationNoMsg:
opts = msgprocessor.WithOptions(opts, msgprocessor.WithHistory(true), msgprocessor.WithPersistent())
}
opts = msgprocessor.WithOptions(opts, msgprocessor.WithSendMsg(cfg.IsSendMsg))
return opts
}
// initConfig loads configuration from a specified path into the provided config structure.
// If the specified config file does not exist, it attempts to load from the project's default "config" directory.
// It logs informative messages regarding the configuration path being used.
func initConfig(config any, configName, configFolderPath string) error {
configFolderPath = filepath.Join(configFolderPath, configName)
_, err := os.Stat(configFolderPath)
if err != nil {
if !os.IsNotExist(err) {
return errs.WrapMsg(err, "stat config path error", "config Folder Path", configFolderPath)
}
path, err := GetProjectRoot()
if err != nil {
return err
}
configFolderPath = filepath.Join(path, "config", configName)
}
data, err := os.ReadFile(configFolderPath)
if err != nil {
return errs.WrapMsg(err, "read file error", "config Folder Path", configFolderPath)
}
if err = yaml.Unmarshal(data, config); err != nil {
return errs.WrapMsg(err, "unmarshal yaml error", "config Folder Path", configFolderPath)
}
return nil
}

View File

@@ -0,0 +1,25 @@
package convert
func TokenMapDB2Pb(tokenMapDB map[string]int) map[string]int32 {
if tokenMapDB == nil {
return nil
}
tokenMapPB := make(map[string]int32, len(tokenMapDB))
for k, v := range tokenMapDB {
tokenMapPB[k] = int32(v)
}
return tokenMapPB
}
func TokenMapPb2DB(tokenMapPB map[string]int32) map[string]int {
if tokenMapPB == nil {
return nil
}
tokenMapDB := make(map[string]int, len(tokenMapPB))
for k, v := range tokenMapPB {
tokenMapDB[k] = int(v)
}
return tokenMapDB
}

View File

@@ -0,0 +1,56 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/protocol/sdkws"
sdk "git.imall.cloud/openim/protocol/sdkws"
)
func BlackDB2Pb(ctx context.Context, blackDBs []*model.Black, f func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (blackPbs []*sdk.BlackInfo, err error) {
if len(blackDBs) == 0 {
return nil, nil
}
var userIDs []string
for _, blackDB := range blackDBs {
userIDs = append(userIDs, blackDB.BlockUserID)
}
userInfos, err := f(ctx, userIDs)
if err != nil {
return nil, err
}
for _, blackDB := range blackDBs {
blackPb := &sdk.BlackInfo{
OwnerUserID: blackDB.OwnerUserID,
CreateTime: blackDB.CreateTime.Unix(),
AddSource: blackDB.AddSource,
Ex: blackDB.Ex,
OperatorUserID: blackDB.OperatorUserID,
BlackUserInfo: &sdkws.PublicUserInfo{
UserID: userInfos[blackDB.BlockUserID].UserID,
Nickname: userInfos[blackDB.BlockUserID].Nickname,
FaceURL: userInfos[blackDB.BlockUserID].FaceURL,
Ex: userInfos[blackDB.BlockUserID].Ex,
UserType: userInfos[blackDB.BlockUserID].UserType,
},
}
blackPbs = append(blackPbs, blackPb)
}
return blackPbs, nil
}

View File

@@ -0,0 +1,61 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/protocol/conversation"
"github.com/openimsdk/tools/utils/datautil"
)
func ConversationDB2Pb(conversationDB *model.Conversation) *conversation.Conversation {
conversationPB := &conversation.Conversation{}
conversationPB.LatestMsgDestructTime = conversationDB.LatestMsgDestructTime.UnixMilli()
if err := datautil.CopyStructFields(conversationPB, conversationDB); err != nil {
return nil
}
return conversationPB
}
func ConversationsDB2Pb(conversationsDB []*model.Conversation) (conversationsPB []*conversation.Conversation) {
for _, conversationDB := range conversationsDB {
conversationPB := &conversation.Conversation{}
if err := datautil.CopyStructFields(conversationPB, conversationDB); err != nil {
continue
}
conversationPB.LatestMsgDestructTime = conversationDB.LatestMsgDestructTime.UnixMilli()
conversationsPB = append(conversationsPB, conversationPB)
}
return conversationsPB
}
func ConversationPb2DB(conversationPB *conversation.Conversation) *model.Conversation {
conversationDB := &model.Conversation{}
if err := datautil.CopyStructFields(conversationDB, conversationPB); err != nil {
return nil
}
return conversationDB
}
func ConversationsPb2DB(conversationsPB []*conversation.Conversation) (conversationsDB []*model.Conversation) {
for _, conversationPB := range conversationsPB {
conversationDB := &model.Conversation{}
if err := datautil.CopyStructFields(conversationDB, conversationPB); err != nil {
continue
}
conversationsDB = append(conversationsDB, conversationDB)
}
return conversationsDB
}

15
pkg/common/convert/doc.go Normal file
View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert // import "git.imall.cloud/openim/open-im-server-deploy/pkg/common/convert"

View File

@@ -0,0 +1,171 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"context"
"fmt"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/open-im-server-deploy/pkg/notification/common_user"
"git.imall.cloud/openim/protocol/relation"
"git.imall.cloud/openim/protocol/sdkws"
"github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/timeutil"
)
func FriendPb2DB(friend *sdkws.FriendInfo) *model.Friend {
dbFriend := &model.Friend{}
err := datautil.CopyStructFields(dbFriend, friend)
if err != nil {
return nil
}
dbFriend.FriendUserID = friend.FriendUser.UserID
dbFriend.CreateTime = timeutil.UnixSecondToTime(friend.CreateTime)
return dbFriend
}
func FriendDB2Pb(ctx context.Context, friendDB *model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (*sdkws.FriendInfo, error) {
users, err := getUsers(ctx, []string{friendDB.FriendUserID})
if err != nil {
return nil, err
}
user, ok := users[friendDB.FriendUserID]
if !ok {
return nil, fmt.Errorf("user not found: %s", friendDB.FriendUserID)
}
return &sdkws.FriendInfo{
FriendUser: user,
CreateTime: friendDB.CreateTime.Unix(),
}, nil
}
func FriendsDB2Pb(ctx context.Context, friendsDB []*model.Friend, getUsers func(ctx context.Context, userIDs []string) (map[string]*sdkws.UserInfo, error)) (friendsPb []*sdkws.FriendInfo, err error) {
if len(friendsDB) == 0 {
return nil, nil
}
var userID []string
for _, friendDB := range friendsDB {
userID = append(userID, friendDB.FriendUserID)
}
users, err := getUsers(ctx, userID)
if err != nil {
return nil, err
}
for _, friend := range friendsDB {
friendPb := &sdkws.FriendInfo{FriendUser: &sdkws.UserInfo{}}
err := datautil.CopyStructFields(friendPb, friend)
if err != nil {
return nil, err
}
friendPb.FriendUser.UserID = users[friend.FriendUserID].UserID
friendPb.FriendUser.Nickname = users[friend.FriendUserID].Nickname
friendPb.FriendUser.FaceURL = users[friend.FriendUserID].FaceURL
friendPb.FriendUser.Ex = users[friend.FriendUserID].Ex
friendPb.CreateTime = friend.CreateTime.Unix()
friendPb.IsPinned = friend.IsPinned
friendsPb = append(friendsPb, friendPb)
}
return friendsPb, nil
}
func FriendOnlyDB2PbOnly(friendsDB []*model.Friend) []*relation.FriendInfoOnly {
return datautil.Slice(friendsDB, func(f *model.Friend) *relation.FriendInfoOnly {
return &relation.FriendInfoOnly{
OwnerUserID: f.OwnerUserID,
FriendUserID: f.FriendUserID,
Remark: f.Remark,
CreateTime: f.CreateTime.UnixMilli(),
AddSource: f.AddSource,
OperatorUserID: f.OperatorUserID,
Ex: f.Ex,
IsPinned: f.IsPinned,
}
})
}
func FriendRequestDB2Pb(ctx context.Context, friendRequests []*model.FriendRequest, getUsers func(ctx context.Context, userIDs []string) (map[string]common_user.CommonUser, error)) ([]*sdkws.FriendRequest, error) {
if len(friendRequests) == 0 {
return nil, nil
}
userIDMap := make(map[string]struct{})
for _, friendRequest := range friendRequests {
userIDMap[friendRequest.ToUserID] = struct{}{}
userIDMap[friendRequest.FromUserID] = struct{}{}
}
users, err := getUsers(ctx, datautil.Keys(userIDMap))
if err != nil {
return nil, err
}
res := make([]*sdkws.FriendRequest, 0, len(friendRequests))
for _, friendRequest := range friendRequests {
toUser := users[friendRequest.ToUserID]
fromUser := users[friendRequest.FromUserID]
res = append(res, &sdkws.FriendRequest{
FromUserID: friendRequest.FromUserID,
FromNickname: fromUser.GetNickname(),
FromFaceURL: fromUser.GetFaceURL(),
ToUserID: friendRequest.ToUserID,
ToNickname: toUser.GetNickname(),
ToFaceURL: toUser.GetFaceURL(),
HandleResult: friendRequest.HandleResult,
ReqMsg: friendRequest.ReqMsg,
CreateTime: friendRequest.CreateTime.UnixMilli(),
HandlerUserID: friendRequest.HandlerUserID,
HandleMsg: friendRequest.HandleMsg,
HandleTime: friendRequest.HandleTime.UnixMilli(),
Ex: friendRequest.Ex,
})
}
return res, nil
}
// FriendPb2DBMap converts a FriendInfo protobuf object to a map suitable for database operations.
// It only includes non-zero or non-empty fields in the map.
func FriendPb2DBMap(friend *sdkws.FriendInfo) map[string]any {
if friend == nil {
return nil
}
val := make(map[string]any)
// Assuming FriendInfo has similar fields to those in Friend.
// Add or remove fields based on your actual FriendInfo and Friend structures.
if friend.FriendUser != nil {
if friend.FriendUser.UserID != "" {
val["friend_user_id"] = friend.FriendUser.UserID
}
if friend.FriendUser.Nickname != "" {
val["nickname"] = friend.FriendUser.Nickname
}
if friend.FriendUser.FaceURL != "" {
val["face_url"] = friend.FriendUser.FaceURL
}
if friend.FriendUser.Ex != "" {
val["ex"] = friend.FriendUser.Ex
}
}
if friend.CreateTime != 0 {
val["create_time"] = friend.CreateTime // You might need to convert this to a proper time format.
}
// Include other fields from FriendInfo as needed, similar to the above pattern.
return val
}

174
pkg/common/convert/group.go Normal file
View File

@@ -0,0 +1,174 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"context"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
pbgroup "git.imall.cloud/openim/protocol/group"
sdkws "git.imall.cloud/openim/protocol/sdkws"
"github.com/openimsdk/tools/log"
)
func Db2PbGroupInfo(m *model.Group, ownerUserID string, memberCount uint32) *sdkws.GroupInfo {
return &sdkws.GroupInfo{
GroupID: m.GroupID,
GroupName: m.GroupName,
Notification: m.Notification,
Introduction: m.Introduction,
FaceURL: m.FaceURL,
OwnerUserID: ownerUserID,
CreateTime: m.CreateTime.UnixMilli(),
MemberCount: memberCount,
Ex: m.Ex,
Status: m.Status,
CreatorUserID: m.CreatorUserID,
GroupType: m.GroupType,
NeedVerification: m.NeedVerification,
LookMemberInfo: m.LookMemberInfo,
ApplyMemberFriend: m.ApplyMemberFriend,
NotificationUpdateTime: m.NotificationUpdateTime.UnixMilli(),
NotificationUserID: m.NotificationUserID,
}
}
func Pb2DbGroupRequest(req *pbgroup.GroupApplicationResponseReq, handleUserID string) *model.GroupRequest {
return &model.GroupRequest{
UserID: req.FromUserID,
GroupID: req.GroupID,
HandleResult: req.HandleResult,
HandledMsg: req.HandledMsg,
HandleUserID: handleUserID,
HandledTime: time.Now(),
}
}
func Db2PbCMSGroup(m *model.Group, ownerUserID string, ownerUserName string, memberCount uint32) *pbgroup.CMSGroup {
return &pbgroup.CMSGroup{
GroupInfo: Db2PbGroupInfo(m, ownerUserID, memberCount),
GroupOwnerUserID: ownerUserID,
GroupOwnerUserName: ownerUserName,
}
}
// Db2PbGroupMember 将数据库群成员模型转换为 protobuf 群成员信息
// 返回的 GroupMemberFullInfo 包含以下禁言相关字段:
// - MuteEndTime: 禁言结束时间(毫秒时间戳)
// 判断是否被禁言MuteEndTime >= 当前时间戳(毫秒)表示正在被禁言
// 判断剩余禁言时间max(0, MuteEndTime - 当前时间戳) / 1000 得到剩余秒数
func Db2PbGroupMember(m *model.GroupMember) *sdkws.GroupMemberFullInfo {
muteEndTime := int64(0)
if !m.MuteEndTime.IsZero() && m.MuteEndTime.After(time.Unix(0, 0)) {
muteEndTime = m.MuteEndTime.UnixMilli()
// 记录从数据库读取的禁言时间,用于排查自动禁言问题
now := time.Now()
if muteEndTime >= now.UnixMilli() {
// 只有在用户被禁言时才记录日志,避免日志过多
log.ZInfo(context.Background(), "Db2PbGroupMember: found muted user in database",
"groupID", m.GroupID,
"userID", m.UserID,
"muteEndTimeFromDB", m.MuteEndTime.Format(time.RFC3339),
"muteEndTimeTimestamp", muteEndTime,
"now", now.Format(time.RFC3339),
"mutedDurationSeconds", (muteEndTime-now.UnixMilli())/1000,
"isZero", m.MuteEndTime.IsZero(),
"afterUnixZero", m.MuteEndTime.After(time.Unix(0, 0)))
}
}
return &sdkws.GroupMemberFullInfo{
GroupID: m.GroupID,
UserID: m.UserID,
RoleLevel: m.RoleLevel,
JoinTime: m.JoinTime.UnixMilli(),
Nickname: m.Nickname,
FaceURL: m.FaceURL,
// AppMangerLevel: m.AppMangerLevel,
JoinSource: m.JoinSource,
OperatorUserID: m.OperatorUserID,
Ex: m.Ex,
MuteEndTime: muteEndTime,
InviterUserID: m.InviterUserID,
}
}
func Db2PbGroupRequest(m *model.GroupRequest, user *sdkws.UserInfo, group *sdkws.GroupInfo) *sdkws.GroupRequest {
var pu *sdkws.PublicUserInfo
if user != nil {
pu = &sdkws.PublicUserInfo{
UserID: user.UserID,
Nickname: user.Nickname,
FaceURL: user.FaceURL,
Ex: user.Ex,
UserType: user.UserType,
}
}
return &sdkws.GroupRequest{
UserInfo: pu,
GroupInfo: group,
HandleResult: m.HandleResult,
ReqMsg: m.ReqMsg,
HandleMsg: m.HandledMsg,
ReqTime: m.ReqTime.UnixMilli(),
HandleUserID: m.HandleUserID,
HandleTime: m.HandledTime.UnixMilli(),
Ex: m.Ex,
JoinSource: m.JoinSource,
InviterUserID: m.InviterUserID,
}
}
func Db2PbGroupAbstractInfo(
groupID string,
groupMemberNumber uint32,
groupMemberListHash uint64,
) *pbgroup.GroupAbstractInfo {
return &pbgroup.GroupAbstractInfo{
GroupID: groupID,
GroupMemberNumber: groupMemberNumber,
GroupMemberListHash: groupMemberListHash,
}
}
func Pb2DBGroupInfo(m *sdkws.GroupInfo) *model.Group {
return &model.Group{
GroupID: m.GroupID,
GroupName: m.GroupName,
Notification: m.Notification,
Introduction: m.Introduction,
FaceURL: m.FaceURL,
CreateTime: time.Now(),
Ex: m.Ex,
Status: m.Status,
CreatorUserID: m.CreatorUserID,
GroupType: m.GroupType,
NeedVerification: m.NeedVerification,
LookMemberInfo: m.LookMemberInfo,
ApplyMemberFriend: m.ApplyMemberFriend,
NotificationUpdateTime: time.UnixMilli(m.NotificationUpdateTime),
NotificationUserID: m.NotificationUserID,
}
}
// func Pb2DbGroupMember(m *sdkws.UserInfo) *relation.GroupMember {
// return &relation.GroupMember{
// UserID: m.UserID,
// Nickname: m.Nickname,
// FaceURL: m.FaceURL,
// Ex: m.Ex,
// }
//}

98
pkg/common/convert/msg.go Normal file
View File

@@ -0,0 +1,98 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/protocol/constant"
"git.imall.cloud/openim/protocol/sdkws"
)
func MsgPb2DB(msg *sdkws.MsgData) *model.MsgDataModel {
if msg == nil {
return nil
}
var msgDataModel model.MsgDataModel
msgDataModel.SendID = msg.SendID
msgDataModel.RecvID = msg.RecvID
msgDataModel.GroupID = msg.GroupID
msgDataModel.ClientMsgID = msg.ClientMsgID
msgDataModel.ServerMsgID = msg.ServerMsgID
msgDataModel.SenderPlatformID = msg.SenderPlatformID
msgDataModel.SenderNickname = msg.SenderNickname
msgDataModel.SenderFaceURL = msg.SenderFaceURL
msgDataModel.SessionType = msg.SessionType
msgDataModel.MsgFrom = msg.MsgFrom
msgDataModel.ContentType = msg.ContentType
msgDataModel.Content = string(msg.Content)
msgDataModel.Seq = msg.Seq
msgDataModel.SendTime = msg.SendTime
msgDataModel.CreateTime = msg.CreateTime
msgDataModel.Status = msg.Status
msgDataModel.Options = msg.Options
if msg.OfflinePushInfo != nil {
msgDataModel.OfflinePush = &model.OfflinePushModel{
Title: msg.OfflinePushInfo.Title,
Desc: msg.OfflinePushInfo.Desc,
Ex: msg.OfflinePushInfo.Ex,
IOSPushSound: msg.OfflinePushInfo.IOSPushSound,
IOSBadgeCount: msg.OfflinePushInfo.IOSBadgeCount,
}
}
msgDataModel.AtUserIDList = msg.AtUserIDList
msgDataModel.AttachedInfo = msg.AttachedInfo
msgDataModel.Ex = msg.Ex
return &msgDataModel
}
func MsgDB2Pb(msgModel *model.MsgDataModel) *sdkws.MsgData {
if msgModel == nil {
return nil
}
var msg sdkws.MsgData
msg.SendID = msgModel.SendID
msg.RecvID = msgModel.RecvID
msg.GroupID = msgModel.GroupID
msg.ClientMsgID = msgModel.ClientMsgID
msg.ServerMsgID = msgModel.ServerMsgID
msg.SenderPlatformID = msgModel.SenderPlatformID
msg.SenderNickname = msgModel.SenderNickname
msg.SenderFaceURL = msgModel.SenderFaceURL
msg.SessionType = msgModel.SessionType
msg.MsgFrom = msgModel.MsgFrom
msg.ContentType = msgModel.ContentType
msg.Content = []byte(msgModel.Content)
msg.Seq = msgModel.Seq
msg.SendTime = msgModel.SendTime
msg.CreateTime = msgModel.CreateTime
msg.Status = msgModel.Status
if msgModel.SessionType == constant.SingleChatType {
msg.IsRead = msgModel.IsRead
}
msg.Options = msgModel.Options
if msgModel.OfflinePush != nil {
msg.OfflinePushInfo = &sdkws.OfflinePushInfo{
Title: msgModel.OfflinePush.Title,
Desc: msgModel.OfflinePush.Desc,
Ex: msgModel.OfflinePush.Ex,
IOSPushSound: msgModel.OfflinePush.IOSPushSound,
IOSBadgeCount: msgModel.OfflinePush.IOSBadgeCount,
}
}
msg.AtUserIDList = msgModel.AtUserIDList
msg.AttachedInfo = msgModel.AttachedInfo
msg.Ex = msgModel.Ex
return &msg
}

109
pkg/common/convert/user.go Normal file
View File

@@ -0,0 +1,109 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"time"
relationtb "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"github.com/openimsdk/tools/utils/datautil"
"git.imall.cloud/openim/protocol/sdkws"
)
func UserDB2Pb(user *relationtb.User) *sdkws.UserInfo {
return &sdkws.UserInfo{
UserID: user.UserID,
Nickname: user.Nickname,
FaceURL: user.FaceURL,
Ex: user.Ex,
CreateTime: user.CreateTime.UnixMilli(),
AppMangerLevel: user.AppMangerLevel,
GlobalRecvMsgOpt: user.GlobalRecvMsgOpt,
UserType: user.UserType,
UserFlag: user.UserFlag,
}
}
func UsersDB2Pb(users []*relationtb.User) []*sdkws.UserInfo {
return datautil.Slice(users, UserDB2Pb)
}
func UserPb2DB(user *sdkws.UserInfo) *relationtb.User {
return &relationtb.User{
UserID: user.UserID,
Nickname: user.Nickname,
FaceURL: user.FaceURL,
Ex: user.Ex,
CreateTime: time.UnixMilli(user.CreateTime),
AppMangerLevel: user.AppMangerLevel,
GlobalRecvMsgOpt: user.GlobalRecvMsgOpt,
UserType: user.UserType,
UserFlag: user.UserFlag,
}
}
func UserPb2DBMap(user *sdkws.UserInfo) map[string]any {
if user == nil {
return nil
}
val := make(map[string]any)
fields := map[string]any{
"nickname": user.Nickname,
"face_url": user.FaceURL,
"ex": user.Ex,
"app_manager_level": user.AppMangerLevel,
"global_recv_msg_opt": user.GlobalRecvMsgOpt,
"user_type": user.UserType,
"user_flag": user.UserFlag,
}
for key, value := range fields {
if v, ok := value.(string); ok && v != "" {
val[key] = v
} else if v, ok := value.(int32); ok {
// 对于 int32 类型,包括 0 值也要更新
val[key] = v
}
}
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
}
if user.UserType != nil {
val["user_type"] = user.UserType.Value
}
if user.UserFlag != nil {
val["user_flag"] = user.UserFlag.Value
}
return val
}

View File

@@ -0,0 +1,87 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package convert
import (
"reflect"
"testing"
relationtb "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/protocol/sdkws"
)
func TestUsersDB2Pb(t *testing.T) {
type args struct {
users []*relationtb.User
}
tests := []struct {
name string
args args
wantResult []*sdkws.UserInfo
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if gotResult := UsersDB2Pb(tt.args.users); !reflect.DeepEqual(gotResult, tt.wantResult) {
t.Errorf("UsersDB2Pb() = %v, want %v", gotResult, tt.wantResult)
}
})
}
}
func TestUserPb2DB(t *testing.T) {
type args struct {
user *sdkws.UserInfo
}
tests := []struct {
name string
args args
want *relationtb.User
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := UserPb2DB(tt.args.user); !reflect.DeepEqual(got, tt.want) {
t.Errorf("UserPb2DB() = %v, want %v", got, tt.want)
}
})
}
}
func TestUserPb2DBMap(t *testing.T) {
user := &sdkws.UserInfo{
Nickname: "TestUser",
FaceURL: "http://openim.io/logo.jpg",
Ex: "Extra Data",
AppMangerLevel: 1,
GlobalRecvMsgOpt: 2,
}
expected := map[string]any{
"nickname": "TestUser",
"face_url": "http://openim.io/logo.jpg",
"ex": "Extra Data",
"app_manager_level": int32(1),
"global_recv_msg_opt": int32(2),
}
result := UserPb2DBMap(user)
if !reflect.DeepEqual(result, expected) {
t.Errorf("UserPb2DBMap returned unexpected map. Got %v, want %v", result, expected)
}
}

View File

@@ -0,0 +1,96 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package direct
import (
"context"
"math/rand"
"strings"
"github.com/openimsdk/tools/log"
"google.golang.org/grpc/resolver"
)
const (
slashSeparator = "/"
// EndpointSepChar is the separator char in endpoints.
EndpointSepChar = ','
subsetSize = 32
scheme = "direct"
)
type ResolverDirect struct {
}
func NewResolverDirect() *ResolverDirect {
return &ResolverDirect{}
}
func (rd *ResolverDirect) Build(target resolver.Target, cc resolver.ClientConn, _ resolver.BuildOptions) (
resolver.Resolver, error) {
log.ZDebug(context.Background(), "Build", "target", target)
endpoints := strings.FieldsFunc(GetEndpoints(target), func(r rune) bool {
return r == EndpointSepChar
})
endpoints = subset(endpoints, subsetSize)
addrs := make([]resolver.Address, 0, len(endpoints))
for _, val := range endpoints {
addrs = append(addrs, resolver.Address{
Addr: val,
})
}
if err := cc.UpdateState(resolver.State{
Addresses: addrs,
}); err != nil {
return nil, err
}
return &nopResolver{cc: cc}, nil
}
func init() {
resolver.Register(&ResolverDirect{})
}
func (rd *ResolverDirect) Scheme() string {
return scheme // return your custom scheme name
}
// GetEndpoints returns the endpoints from the given target.
func GetEndpoints(target resolver.Target) string {
return strings.Trim(target.URL.Path, slashSeparator)
}
func subset(set []string, sub int) []string {
rand.Shuffle(len(set), func(i, j int) {
set[i], set[j] = set[j], set[i]
})
if len(set) <= sub {
return set
}
return set[:sub]
}
type nopResolver struct {
cc resolver.ClientConn
}
func (n nopResolver) ResolveNow(options resolver.ResolveNowOptions) {
}
func (n nopResolver) Close() {
}

View File

@@ -0,0 +1,174 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package direct
//import (
// "context"
// "fmt"
//
// config2 "git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
// "github.com/openimsdk/tools/errs"
// "google.golang.org/grpc"
// "google.golang.org/grpc/credentials/insecure"
//)
//
//type ServiceAddresses map[string][]int
//
//func getServiceAddresses(rpcRegisterName *config2.RpcRegisterName,
// rpcPort *config2.RpcPort, longConnSvrPort []int) ServiceAddresses {
// return ServiceAddresses{
// rpcRegisterName.OpenImUserName: rpcPort.OpenImUserPort,
// rpcRegisterName.OpenImFriendName: rpcPort.OpenImFriendPort,
// rpcRegisterName.OpenImMsgName: rpcPort.OpenImMessagePort,
// rpcRegisterName.OpenImMessageGatewayName: longConnSvrPort,
// rpcRegisterName.OpenImGroupName: rpcPort.OpenImGroupPort,
// rpcRegisterName.OpenImAuthName: rpcPort.OpenImAuthPort,
// rpcRegisterName.OpenImPushName: rpcPort.OpenImPushPort,
// rpcRegisterName.OpenImConversationName: rpcPort.OpenImConversationPort,
// rpcRegisterName.OpenImThirdName: rpcPort.OpenImThirdPort,
// }
//}
//
//type ConnDirect struct {
// additionalOpts []grpc.DialOption
// currentServiceAddress string
// conns map[string][]*grpc.ClientConn
// resolverDirect *ResolverDirect
// config *config2.GlobalConfig
//}
//
//func (cd *ConnDirect) GetClientLocalConns() map[string][]*grpc.ClientConn {
// return nil
//}
//
//func (cd *ConnDirect) GetUserIdHashGatewayHost(ctx context.Context, userId string) (string, error) {
// return "", nil
//}
//
//func (cd *ConnDirect) Register(serviceName, host string, port int, opts ...grpc.DialOption) error {
// return nil
//}
//
//func (cd *ConnDirect) UnRegister() error {
// return nil
//}
//
//func (cd *ConnDirect) CreateRpcRootNodes(serviceNames []string) error {
// return nil
//}
//
//func (cd *ConnDirect) RegisterConf2Registry(key string, conf []byte) error {
// return nil
//}
//
//func (cd *ConnDirect) GetConfFromRegistry(key string) ([]byte, error) {
// return nil, nil
//}
//
//func (cd *ConnDirect) Close() {
//
//}
//
//func NewConnDirect(config *config2.GlobalConfig) (*ConnDirect, error) {
// return &ConnDirect{
// conns: make(map[string][]*grpc.ClientConn),
// resolverDirect: NewResolverDirect(),
// config: config,
// }, nil
//}
//
//func (cd *ConnDirect) GetConns(ctx context.Context,
// serviceName string, opts ...grpc.DialOption) ([]*grpc.ClientConn, error) {
//
// if conns, exists := cd.conns[serviceName]; exists {
// return conns, nil
// }
// ports := getServiceAddresses(&cd.config.RpcRegisterName,
// &cd.config.RpcPort, cd.config.LongConnSvr.OpenImMessageGatewayPort)[serviceName]
// var connections []*grpc.ClientConn
// for _, port := range ports {
// conn, err := cd.dialServiceWithoutResolver(ctx, fmt.Sprintf(cd.config.Rpc.ListenIP+":%d", port), append(cd.additionalOpts, opts...)...)
// if err != nil {
// return nil, errs.Wrap(fmt.Errorf("connect to port %d failed,serviceName %s, IP %s", port, serviceName, cd.config.Rpc.ListenIP))
// }
// connections = append(connections, conn)
// }
//
// if len(connections) == 0 {
// return nil, errs.New("no connections found for service", "serviceName", serviceName).Wrap()
// }
// return connections, nil
//}
//
//func (cd *ConnDirect) GetConn(ctx context.Context, serviceName string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
// // Get service addresses
// addresses := getServiceAddresses(&cd.config.RpcRegisterName,
// &cd.config.RpcPort, cd.config.LongConnSvr.OpenImMessageGatewayPort)
// address, ok := addresses[serviceName]
// if !ok {
// return nil, errs.New("unknown service name", "serviceName", serviceName).Wrap()
// }
// var result string
// for _, addr := range address {
// if result != "" {
// result = result + "," + fmt.Sprintf(cd.config.Rpc.ListenIP+":%d", addr)
// } else {
// result = fmt.Sprintf(cd.config.Rpc.ListenIP+":%d", addr)
// }
// }
// // Try to dial a new connection
// conn, err := cd.dialService(ctx, result, append(cd.additionalOpts, opts...)...)
// if err != nil {
// return nil, errs.WrapMsg(err, "address", result)
// }
//
// // Store the new connection
// cd.conns[serviceName] = append(cd.conns[serviceName], conn)
// return conn, nil
//}
//
//func (cd *ConnDirect) GetSelfConnTarget() string {
// return cd.currentServiceAddress
//}
//
//func (cd *ConnDirect) AddOption(opts ...grpc.DialOption) {
// cd.additionalOpts = append(cd.additionalOpts, opts...)
//}
//
//func (cd *ConnDirect) CloseConn(conn *grpc.ClientConn) {
// if conn != nil {
// conn.Close()
// }
//}
//
//func (cd *ConnDirect) dialService(ctx context.Context, address string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
// options := append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
// conn, err := grpc.DialContext(ctx, cd.resolverDirect.Scheme()+":///"+address, options...)
//
// if err != nil {
// return nil, errs.WrapMsg(err, "address", address)
// }
// return conn, nil
//}
//
//func (cd *ConnDirect) dialServiceWithoutResolver(ctx context.Context, address string, opts ...grpc.DialOption) (*grpc.ClientConn, error) {
// options := append(opts, grpc.WithTransportCredentials(insecure.NewCredentials()))
// conn, err := grpc.DialContext(ctx, address, options...)
//
// if err != nil {
// return nil, errs.Wrap(err)
// }
// return conn, nil
//}

View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package direct // import "git.imall.cloud/openim/open-im-server-deploy/pkg/common/discovery/direct"

View File

@@ -0,0 +1,55 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package discovery
import (
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/discovery/kubernetes"
"github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/discovery/etcd"
"github.com/openimsdk/tools/discovery/standalone"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/runtimeenv"
"google.golang.org/grpc"
)
// NewDiscoveryRegister creates a new service discovery and registry client based on the provided environment type.
func NewDiscoveryRegister(confDiscovery *config.Discovery, watchNames []string) (discovery.SvcDiscoveryRegistry, error) {
if config.Standalone() {
return standalone.GetSvcDiscoveryRegistry(), nil
}
if runtimeenv.RuntimeEnvironment() == config.KUBERNETES {
return kubernetes.NewKubernetesConnManager(confDiscovery.Kubernetes.Namespace, watchNames,
grpc.WithDefaultCallOptions(
grpc.MaxCallSendMsgSize(1024*1024*20),
),
)
}
switch confDiscovery.Enable {
case config.ETCD:
return etcd.NewSvcDiscoveryRegistry(
confDiscovery.Etcd.RootDirectory,
confDiscovery.Etcd.Address,
watchNames,
etcd.WithDialTimeout(10*time.Second),
etcd.WithMaxCallSendMsgSize(20*1024*1024),
etcd.WithUsernameAndPassword(confDiscovery.Etcd.Username, confDiscovery.Etcd.Password))
default:
return nil, errs.New("unsupported discovery type", "type", confDiscovery.Enable).Wrap()
}
}

View File

@@ -0,0 +1,60 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package discovery
import (
"os"
)
func setupTestEnvironment() {
os.Setenv("ZOOKEEPER_SCHEMA", "openim")
os.Setenv("ZOOKEEPER_ADDRESS", "172.28.0.1")
os.Setenv("ZOOKEEPER_PORT", "12181")
os.Setenv("ZOOKEEPER_USERNAME", "")
os.Setenv("ZOOKEEPER_PASSWORD", "")
}
//func TestNewDiscoveryRegister(t *testing.T) {
// setupTestEnvironment()
// conf := config.NewGlobalConfig()
// tests := []struct {
// envType string
// gatewayName string
// expectedError bool
// expectedResult bool
// }{
// {"zookeeper", "MessageGateway", false, true},
// {"k8s", "MessageGateway", false, true},
// {"direct", "MessageGateway", false, true},
// {"invalid", "MessageGateway", true, false},
// }
//
// for _, test := range tests {
// conf.Envs.Discovery = test.envType
// conf.RpcRegisterName.OpenImMessageGatewayName = test.gatewayName
// client, err := NewDiscoveryRegister(conf)
//
// if test.expectedError {
// assert.Error(t, err)
// } else {
// assert.NoError(t, err)
// if test.expectedResult {
// assert.Implements(t, (*discovery.SvcDiscoveryRegistry)(nil), client)
// } else {
// assert.Nil(t, client)
// }
// }
// }
//}

View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package discovery // import "git.imall.cloud/openim/open-im-server-deploy/pkg/common/discovery"

View File

@@ -0,0 +1,106 @@
package etcd
import (
"context"
"os"
"os/exec"
"runtime"
"sync"
"syscall"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil"
clientv3 "go.etcd.io/etcd/client/v3"
)
var (
ShutDowns []func() error
)
func RegisterShutDown(shutDown ...func() error) {
ShutDowns = append(ShutDowns, shutDown...)
}
type ConfigManager struct {
client *clientv3.Client
watchConfigNames []string
lock sync.Mutex
}
func BuildKey(s string) string {
return ConfigKeyPrefix + s
}
func NewConfigManager(client *clientv3.Client, configNames []string) *ConfigManager {
return &ConfigManager{
client: client,
watchConfigNames: datautil.Batch(func(s string) string { return BuildKey(s) }, append(configNames, RestartKey))}
}
func (c *ConfigManager) Watch(ctx context.Context) {
chans := make([]clientv3.WatchChan, 0, len(c.watchConfigNames))
for _, name := range c.watchConfigNames {
chans = append(chans, c.client.Watch(ctx, name, clientv3.WithPrefix()))
}
doWatch := func(watchChan clientv3.WatchChan) {
for watchResp := range watchChan {
if watchResp.Err() != nil {
log.ZError(ctx, "watch err", errs.Wrap(watchResp.Err()))
continue
}
for _, event := range watchResp.Events {
if event.IsModify() {
if datautil.Contain(string(event.Kv.Key), c.watchConfigNames...) {
c.lock.Lock()
err := restartServer(ctx)
if err != nil {
log.ZError(ctx, "restart server err", err)
}
c.lock.Unlock()
}
}
}
}
}
for _, ch := range chans {
go doWatch(ch)
}
}
func restartServer(ctx context.Context) error {
exePath, err := os.Executable()
if err != nil {
return errs.New("get executable path fail").Wrap()
}
args := os.Args
env := os.Environ()
cmd := exec.Command(exePath, args[1:]...)
cmd.Env = env
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
cmd.Stdin = os.Stdin
if runtime.GOOS != "windows" {
cmd.SysProcAttr = &syscall.SysProcAttr{}
}
log.ZInfo(ctx, "shutdown server")
for _, f := range ShutDowns {
if err = f(); err != nil {
log.ZError(ctx, "shutdown fail", err)
}
}
log.ZInfo(ctx, "restart server")
err = cmd.Start()
if err != nil {
return errs.New("restart server fail").Wrap()
}
log.ZInfo(ctx, "cmd start over")
os.Exit(0)
return nil
}

View File

@@ -0,0 +1,9 @@
package etcd
const (
ConfigKeyPrefix = "/open-im/config/"
RestartKey = "restart"
EnableConfigCenterKey = "enable-config-center"
Enable = "enable"
Disable = "disable"
)

View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package kubernetes // import "git.imall.cloud/openim/open-im-server-deploy/pkg/common/discovery/kubernetes"

View File

@@ -0,0 +1,640 @@
package kubernetes
import (
"context"
"fmt"
"os"
"strings"
"sync"
"time"
"github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/utils/datautil"
"google.golang.org/grpc"
"google.golang.org/grpc/connectivity"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/keepalive"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
)
// addrConn 存储连接和地址信息,用于连接复用
type addrConn struct {
conn *grpc.ClientConn
addr string
reused bool // 标记是否被复用
}
type KubernetesConnManager struct {
clientset *kubernetes.Clientset
namespace string
dialOptions []grpc.DialOption
rpcTargets map[string]string
selfTarget string
// watchNames 只监听这些服务的 Endpoints 变化
watchNames []string
mu sync.RWMutex
connMap map[string][]*addrConn
}
// NewKubernetesConnManager creates a new connection manager that uses Kubernetes services for service discovery.
func NewKubernetesConnManager(namespace string, watchNames []string, options ...grpc.DialOption) (*KubernetesConnManager, error) {
ctx := context.Background()
log.ZInfo(ctx, "K8s Discovery: Initializing connection manager", "namespace", namespace, "watchNames", watchNames)
// 获取集群内配置
config, err := rest.InClusterConfig()
if err != nil {
log.ZError(ctx, "K8s Discovery: Failed to create in-cluster config", err)
return nil, fmt.Errorf("failed to create in-cluster config: %v", err)
}
log.ZDebug(ctx, "K8s Discovery: Successfully created in-cluster config")
// 创建 K8s API 客户端
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
log.ZError(ctx, "K8s Discovery: Failed to create clientset", err)
return nil, fmt.Errorf("failed to create clientset: %v", err)
}
log.ZDebug(ctx, "K8s Discovery: Successfully created clientset")
// 初始化连接管理器
k := &KubernetesConnManager{
clientset: clientset,
namespace: namespace,
dialOptions: options,
connMap: make(map[string][]*addrConn),
rpcTargets: make(map[string]string),
watchNames: watchNames,
}
// 启动后台 goroutine 监听 Endpoints 变化
log.ZInfo(ctx, "K8s Discovery: Starting Endpoints watcher")
go k.watchEndpoints()
log.ZInfo(ctx, "K8s Discovery: Connection manager initialized successfully")
return k, nil
}
// parseServiceName 解析服务名,去掉端口信息
// 例如user-rpc-service:http-10320 -> user-rpc-service
func parseServiceName(serviceName string) string {
if idx := strings.Index(serviceName, ":"); idx != -1 {
return serviceName[:idx]
}
return serviceName
}
// initializeConns 初始化指定服务的所有 gRPC 连接(支持连接复用)
func (k *KubernetesConnManager) initializeConns(serviceName string) error {
ctx := context.Background()
log.ZInfo(ctx, "K8s Discovery: Starting to initialize connections", "serviceName", serviceName)
// 步骤 1: 获取 Service 的端口
port, err := k.getServicePort(serviceName)
if err != nil {
log.ZError(ctx, "K8s Discovery: Failed to get service port", err, "serviceName", serviceName)
return fmt.Errorf("failed to get service port: %w", err)
}
log.ZDebug(ctx, "K8s Discovery: Got service port", "serviceName", serviceName, "port", port)
// 步骤 2: 获取旧连接,建立地址到连接的映射(用于复用)
k.mu.Lock()
oldList := k.connMap[serviceName]
addrMap := make(map[string]*addrConn, len(oldList))
for _, ac := range oldList {
addrMap[ac.addr] = ac
ac.reused = false // 重置复用标记
}
k.mu.Unlock()
log.ZDebug(ctx, "K8s Discovery: Old connections snapshot", "serviceName", serviceName, "count", len(oldList))
// 步骤 3: 获取 Service 对应的 Endpoints
endpoints, err := k.clientset.CoreV1().Endpoints(k.namespace).Get(
ctx,
serviceName,
metav1.GetOptions{},
)
if err != nil {
log.ZError(ctx, "K8s Discovery: Failed to get endpoints", err, "serviceName", serviceName)
return fmt.Errorf("failed to get endpoints for service %s: %w", serviceName, err)
}
// 统计 Endpoints 数量
var totalAddresses int
for _, subset := range endpoints.Subsets {
totalAddresses += len(subset.Addresses)
}
log.ZDebug(ctx, "K8s Discovery: Found endpoint addresses", "serviceName", serviceName, "count", totalAddresses)
// 步骤 4: 为每个 Pod IP 创建或复用 gRPC 连接
var newList []*addrConn
var reusedCount, createdCount int
for _, subset := range endpoints.Subsets {
for _, address := range subset.Addresses {
target := fmt.Sprintf("%s:%d", address.IP, port)
// 检查是否可以复用旧连接
if ac, ok := addrMap[target]; ok {
// 复用旧连接
ac.reused = true
newList = append(newList, ac)
reusedCount++
log.ZDebug(ctx, "K8s Discovery: Reusing existing connection", "serviceName", serviceName, "target", target)
continue
}
// 创建新连接
log.ZDebug(ctx, "K8s Discovery: Creating new connection", "serviceName", serviceName, "target", target)
conn, err := grpc.Dial(
target,
append(k.dialOptions,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 10 * time.Second,
Timeout: 3 * time.Second,
PermitWithoutStream: true,
}),
)...,
)
if err != nil {
log.ZWarn(ctx, "K8s Discovery: Failed to dial endpoint, skipping", err, "serviceName", serviceName, "target", target)
// 跳过无法连接的端点,不终止整个初始化
continue
}
state := conn.GetState()
log.ZDebug(ctx, "K8s Discovery: New connection created", "serviceName", serviceName, "target", target, "state", state.String())
newList = append(newList, &addrConn{conn: conn, addr: target, reused: false})
createdCount++
}
}
log.ZInfo(ctx, "K8s Discovery: Connection initialization summary", "serviceName", serviceName,
"total", len(newList), "reused", reusedCount, "created", createdCount)
// 步骤 5: 收集需要关闭的旧连接(未被复用的)
var connsToClose []*addrConn
for _, ac := range oldList {
if !ac.reused {
connsToClose = append(connsToClose, ac)
}
}
// 步骤 6: 更新连接映射
k.mu.Lock()
k.connMap[serviceName] = newList
k.mu.Unlock()
log.ZDebug(ctx, "K8s Discovery: Connection map updated", "serviceName", serviceName,
"oldCount", len(oldList), "newCount", len(newList), "toClose", len(connsToClose))
// 步骤 7: 延迟关闭未复用的旧连接
if len(connsToClose) > 0 {
log.ZInfo(ctx, "K8s Discovery: Scheduling delayed close for unused connections", "serviceName", serviceName, "count", len(connsToClose), "delaySeconds", 5)
go func() {
time.Sleep(5 * time.Second)
log.ZDebug(ctx, "K8s Discovery: Closing unused old connections", "serviceName", serviceName, "count", len(connsToClose))
closedCount := 0
for _, ac := range connsToClose {
if err := ac.conn.Close(); err != nil {
log.ZError(ctx, "K8s Discovery: Failed to close old connection", err, "serviceName", serviceName, "addr", ac.addr)
} else {
closedCount++
}
}
log.ZInfo(ctx, "K8s Discovery: Closed unused connections", "serviceName", serviceName, "closed", closedCount, "total", len(connsToClose))
}()
}
log.ZInfo(ctx, "K8s Discovery: Connection initialization completed", "serviceName", serviceName)
return nil
}
// GetConns returns gRPC client connections for a given Kubernetes service name.
func (k *KubernetesConnManager) GetConns(ctx context.Context, serviceName string, opts ...grpc.DialOption) ([]grpc.ClientConnInterface, error) {
// 解析服务名,去掉端口信息
svcName := parseServiceName(serviceName)
log.ZDebug(ctx, "K8s Discovery: GetConns called", "serviceName", serviceName, "parsedName", svcName)
// 步骤 1: 第一次检查缓存(读锁)
k.mu.RLock()
conns, exists := k.connMap[svcName]
k.mu.RUnlock()
// 步骤 2: 如果缓存中有连接,检查健康状态
if exists && len(conns) > 0 {
log.ZDebug(ctx, "K8s Discovery: Found connections in cache, checking health", "serviceName", svcName, "count", len(conns))
// 检查连接健康状态
validConns := k.filterValidConns(ctx, svcName, conns)
// 如果还有有效连接,更新缓存并返回
if len(validConns) > 0 {
if len(validConns) < len(conns) {
log.ZWarn(ctx, "K8s Discovery: Removed invalid connections", nil, "serviceName", svcName, "removed", len(conns)-len(validConns), "remaining", len(validConns))
k.mu.Lock()
k.connMap[svcName] = validConns
k.mu.Unlock()
} else {
log.ZDebug(ctx, "K8s Discovery: All connections are healthy", "serviceName", svcName, "count", len(validConns))
}
// 转换为接口类型
result := make([]grpc.ClientConnInterface, len(validConns))
for i, ac := range validConns {
result[i] = ac.conn
}
return result, nil
}
// 如果所有连接都失效,清除缓存并重新初始化
log.ZWarn(ctx, "K8s Discovery: All connections are invalid, reinitializing", nil, "serviceName", svcName)
k.mu.Lock()
delete(k.connMap, svcName)
k.mu.Unlock()
} else {
log.ZDebug(ctx, "K8s Discovery: No connections in cache, initializing", "serviceName", svcName)
}
// 步骤 3: 缓存中没有连接或所有连接都失效,重新初始化
k.mu.Lock()
conns, exists = k.connMap[svcName]
if exists && len(conns) > 0 {
log.ZDebug(ctx, "K8s Discovery: Connections were initialized by another goroutine", "serviceName", svcName)
k.mu.Unlock()
result := make([]grpc.ClientConnInterface, len(conns))
for i, ac := range conns {
result[i] = ac.conn
}
return result, nil
}
k.mu.Unlock()
// 初始化新连接
log.ZDebug(ctx, "K8s Discovery: Initializing new connections", "serviceName", svcName)
if err := k.initializeConns(svcName); err != nil {
log.ZError(ctx, "K8s Discovery: Failed to initialize connections", err, "serviceName", svcName)
return nil, fmt.Errorf("failed to initialize connections for service %s: %w", svcName, err)
}
// 返回新初始化的连接
k.mu.RLock()
conns = k.connMap[svcName]
k.mu.RUnlock()
log.ZDebug(ctx, "K8s Discovery: Returning connections", "serviceName", svcName, "count", len(conns))
result := make([]grpc.ClientConnInterface, len(conns))
for i, ac := range conns {
result[i] = ac.conn
}
return result, nil
}
// filterValidConns 过滤出有效的连接
func (k *KubernetesConnManager) filterValidConns(ctx context.Context, serviceName string, conns []*addrConn) []*addrConn {
validConns := make([]*addrConn, 0, len(conns))
invalidStates := make(map[string]int)
for _, ac := range conns {
state := ac.conn.GetState()
// 只保留 Ready 和 Idle 状态的连接
if state == connectivity.Ready || state == connectivity.Idle {
validConns = append(validConns, ac)
} else {
invalidStates[state.String()]++
log.ZDebug(ctx, "K8s Discovery: Connection is invalid, closing", "serviceName", serviceName, "addr", ac.addr, "state", state.String())
if err := ac.conn.Close(); err != nil {
log.ZError(ctx, "K8s Discovery: Failed to close invalid connection", err, "serviceName", serviceName, "addr", ac.addr)
}
}
}
if len(invalidStates) > 0 {
log.ZWarn(ctx, "K8s Discovery: Found invalid connections", nil, "serviceName", serviceName, "invalidStates", invalidStates)
}
return validConns
}
// GetConn returns a single gRPC client connection for a given Kubernetes service name.
// 重要GetConn 使用 DNS避免连接被强制关闭
func (k *KubernetesConnManager) GetConn(ctx context.Context, serviceName string, opts ...grpc.DialOption) (grpc.ClientConnInterface, error) {
// 解析服务名,去掉端口信息
svcName := parseServiceName(serviceName)
log.ZDebug(ctx, "K8s Discovery: GetConn called (using DNS)", "serviceName", serviceName, "parsedName", svcName)
var target string
// 检查是否有自定义目标
if k.rpcTargets[svcName] == "" {
// 获取 Service 端口
svcPort, err := k.getServicePort(svcName)
if err != nil {
log.ZError(ctx, "K8s Discovery: Failed to get service port", err, "serviceName", svcName)
return nil, err
}
// 构建 K8s DNS 名称
target = fmt.Sprintf("%s.%s.svc.cluster.local:%d", svcName, k.namespace, svcPort)
log.ZDebug(ctx, "K8s Discovery: Using DNS target", "serviceName", svcName, "target", target)
} else {
target = k.rpcTargets[svcName]
log.ZDebug(ctx, "K8s Discovery: Using custom target", "serviceName", svcName, "target", target)
}
// 创建 gRPC 连接
log.ZDebug(ctx, "K8s Discovery: Dialing DNS target", "serviceName", svcName, "target", target)
conn, err := grpc.DialContext(
ctx,
target,
append([]grpc.DialOption{
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultCallOptions(
grpc.MaxCallRecvMsgSize(1024*1024*10),
grpc.MaxCallSendMsgSize(1024*1024*20),
),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 10 * time.Second,
Timeout: 3 * time.Second,
PermitWithoutStream: true,
}),
}, k.dialOptions...)...,
)
if err != nil {
log.ZError(ctx, "K8s Discovery: Failed to dial DNS target", err, "serviceName", svcName, "target", target)
return nil, err
}
log.ZDebug(ctx, "K8s Discovery: GetConn completed successfully", "serviceName", svcName)
return conn, nil
}
// IsSelfNode checks if the given connection is to the current node
func (k *KubernetesConnManager) IsSelfNode(cc grpc.ClientConnInterface) bool {
return false
}
// watchEndpoints 监听 Endpoints 资源变化
func (k *KubernetesConnManager) watchEndpoints() {
ctx := context.Background()
log.ZInfo(ctx, "K8s Discovery: Starting Endpoints watcher")
informerFactory := informers.NewSharedInformerFactoryWithOptions(k.clientset, time.Minute*10,
informers.WithNamespace(k.namespace))
informer := informerFactory.Core().V1().Endpoints().Informer()
log.ZDebug(ctx, "K8s Discovery: Endpoints Informer created")
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
k.handleEndpointChange(obj)
},
UpdateFunc: func(oldObj, newObj interface{}) {
oldEndpoint := oldObj.(*v1.Endpoints)
newEndpoint := newObj.(*v1.Endpoints)
if k.endpointsChanged(oldEndpoint, newEndpoint) {
k.handleEndpointChange(newObj)
}
},
DeleteFunc: func(obj interface{}) {
k.handleEndpointChange(obj)
},
})
log.ZDebug(ctx, "K8s Discovery: Starting Informer factory")
informerFactory.Start(ctx.Done())
log.ZDebug(ctx, "K8s Discovery: Waiting for Informer cache to sync")
if !cache.WaitForCacheSync(ctx.Done(), informer.HasSynced) {
log.ZError(ctx, "K8s Discovery: Failed to sync Informer cache", nil)
return
}
log.ZInfo(ctx, "K8s Discovery: Informer cache synced successfully")
log.ZInfo(ctx, "K8s Discovery: Endpoints watcher is running")
<-ctx.Done()
log.ZInfo(ctx, "K8s Discovery: Endpoints watcher stopped")
}
// endpointsChanged 检查 Endpoints 是否有实际变化
func (k *KubernetesConnManager) endpointsChanged(old, new *v1.Endpoints) bool {
oldAddresses := make(map[string]bool)
for _, subset := range old.Subsets {
for _, address := range subset.Addresses {
oldAddresses[address.IP] = true
}
}
newAddresses := make(map[string]bool)
for _, subset := range new.Subsets {
for _, address := range subset.Addresses {
newAddresses[address.IP] = true
}
}
if len(oldAddresses) != len(newAddresses) {
return true
}
for ip := range oldAddresses {
if !newAddresses[ip] {
return true
}
}
return false
}
// handleEndpointChange 处理 Endpoints 资源变化
func (k *KubernetesConnManager) handleEndpointChange(obj interface{}) {
ctx := context.Background()
endpoint, ok := obj.(*v1.Endpoints)
if !ok {
log.ZError(ctx, "K8s Discovery: Expected *v1.Endpoints", nil, "actualType", fmt.Sprintf("%T", obj))
return
}
serviceName := endpoint.Name
// 只处理 watchNames 中的服务
if len(k.watchNames) > 0 && !datautil.Contain(serviceName, k.watchNames...) {
log.ZDebug(ctx, "K8s Discovery: Ignoring Endpoints change (not in watchNames)", "serviceName", serviceName)
return
}
log.ZInfo(ctx, "K8s Discovery: Handling Endpoints change", "serviceName", serviceName)
var totalAddresses int
for _, subset := range endpoint.Subsets {
totalAddresses += len(subset.Addresses)
}
log.ZDebug(ctx, "K8s Discovery: Endpoint addresses count", "serviceName", serviceName, "count", totalAddresses)
if err := k.initializeConns(serviceName); err != nil {
log.ZError(ctx, "K8s Discovery: Failed to initialize connections", err, "serviceName", serviceName)
} else {
log.ZInfo(ctx, "K8s Discovery: Successfully updated connections", "serviceName", serviceName)
}
}
// getServicePort 获取 Service 的 RPC 端口
func (k *KubernetesConnManager) getServicePort(serviceName string) (int32, error) {
ctx := context.Background()
log.ZDebug(ctx, "K8s Discovery: Getting service port", "serviceName", serviceName)
svc, err := k.clientset.CoreV1().Services(k.namespace).Get(
ctx,
serviceName,
metav1.GetOptions{},
)
if err != nil {
log.ZError(ctx, "K8s Discovery: Failed to get service", err, "serviceName", serviceName, "namespace", k.namespace)
return 0, fmt.Errorf("failed to get service %s: %w", serviceName, err)
}
if len(svc.Spec.Ports) == 0 {
log.ZError(ctx, "K8s Discovery: Service has no ports defined", nil, "serviceName", serviceName)
return 0, fmt.Errorf("service %s has no ports defined", serviceName)
}
var svcPort int32
for _, port := range svc.Spec.Ports {
if port.Port != 10001 {
svcPort = port.Port
break
}
}
if svcPort == 0 {
log.ZError(ctx, "K8s Discovery: Service has no RPC port", nil, "serviceName", serviceName)
return 0, fmt.Errorf("service %s has no RPC port (all ports are 10001)", serviceName)
}
log.ZDebug(ctx, "K8s Discovery: Got service port", "serviceName", serviceName, "port", svcPort)
return svcPort, nil
}
// Close 关闭所有连接
func (k *KubernetesConnManager) Close() {
ctx := context.Background()
log.ZInfo(ctx, "K8s Discovery: Closing all connections")
k.mu.Lock()
defer k.mu.Unlock()
totalConns := 0
for serviceName, conns := range k.connMap {
log.ZDebug(ctx, "K8s Discovery: Closing connections for service", "serviceName", serviceName, "count", len(conns))
for _, ac := range conns {
if err := ac.conn.Close(); err != nil {
log.ZError(ctx, "K8s Discovery: Failed to close connection", err, "serviceName", serviceName, "addr", ac.addr)
}
}
totalConns += len(conns)
}
log.ZInfo(ctx, "K8s Discovery: Closed all connections", "totalCount", totalConns)
k.connMap = make(map[string][]*addrConn)
}
// GetSelfConnTarget returns the connection target for the current service.
func (k *KubernetesConnManager) GetSelfConnTarget() string {
ctx := context.Background()
if k.selfTarget == "" {
hostName := os.Getenv("HOSTNAME")
log.ZDebug(ctx, "K8s Discovery: Getting self connection target", "hostname", hostName)
pod, err := k.clientset.CoreV1().Pods(k.namespace).Get(ctx, hostName, metav1.GetOptions{})
if err != nil {
log.ZError(ctx, "K8s Discovery: Failed to get pod", err, "hostname", hostName)
}
for pod.Status.PodIP == "" {
log.ZDebug(ctx, "K8s Discovery: Waiting for pod IP to be assigned", "hostname", hostName)
pod, err = k.clientset.CoreV1().Pods(k.namespace).Get(context.TODO(), hostName, metav1.GetOptions{})
if err != nil {
log.ZError(ctx, "K8s Discovery: Failed to get pod", err)
}
time.Sleep(3 * time.Second)
}
var selfPort int32
for _, port := range pod.Spec.Containers[0].Ports {
if port.ContainerPort != 10001 {
selfPort = port.ContainerPort
break
}
}
k.selfTarget = fmt.Sprintf("%s:%d", pod.Status.PodIP, selfPort)
log.ZInfo(ctx, "K8s Discovery: Self connection target", "target", k.selfTarget)
}
return k.selfTarget
}
// AddOption appends gRPC dial options to the existing options.
func (k *KubernetesConnManager) AddOption(opts ...grpc.DialOption) {
k.mu.Lock()
defer k.mu.Unlock()
k.dialOptions = append(k.dialOptions, opts...)
log.ZDebug(context.Background(), "K8s Discovery: Added dial options", "count", len(opts))
}
// CloseConn closes a given gRPC client connection.
func (k *KubernetesConnManager) CloseConn(conn *grpc.ClientConn) {
log.ZDebug(context.Background(), "K8s Discovery: Closing single connection")
conn.Close()
}
func (k *KubernetesConnManager) Register(ctx context.Context, serviceName, host string, port int, opts ...grpc.DialOption) error {
// K8s 环境下不需要注册,返回 nil
return nil
}
func (k *KubernetesConnManager) UnRegister() error {
// K8s 环境下不需要注销,返回 nil
return nil
}
func (k *KubernetesConnManager) GetUserIdHashGatewayHost(ctx context.Context, userId string) (string, error) {
// K8s 环境下不支持,返回空
return "", nil
}
// KeyValue interface methods - K8s环境下不支持返回 discovery.ErrNotSupported
// 这样调用方可以通过 errors.Is(err, discovery.ErrNotSupported) 来判断并忽略
func (k *KubernetesConnManager) SetKey(ctx context.Context, key string, value []byte) error {
return discovery.ErrNotSupported
}
func (k *KubernetesConnManager) SetWithLease(ctx context.Context, key string, val []byte, ttl int64) error {
return discovery.ErrNotSupported
}
func (k *KubernetesConnManager) GetKey(ctx context.Context, key string) ([]byte, error) {
return nil, discovery.ErrNotSupported
}
func (k *KubernetesConnManager) GetKeyWithPrefix(ctx context.Context, key string) ([][]byte, error) {
return nil, discovery.ErrNotSupported
}
func (k *KubernetesConnManager) WatchKey(ctx context.Context, key string, fn discovery.WatchKeyHandler) error {
return discovery.ErrNotSupported
}

View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ginprometheus // import "git.imall.cloud/openim/open-im-server-deploy/pkg/common/ginprometheus"

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prommetrics
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
UserLoginCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "user_login_total",
Help: "The number of user login",
})
)
func RegistryAuth() {
registry.MustRegister(UserLoginCounter)
}

View File

@@ -0,0 +1,47 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prommetrics
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
SingleChatMsgProcessSuccessCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "single_chat_msg_process_success_total",
Help: "The number of single chat msg successful processed",
})
SingleChatMsgProcessFailedCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "single_chat_msg_process_failed_total",
Help: "The number of single chat msg failed processed",
})
GroupChatMsgProcessSuccessCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "group_chat_msg_process_success_total",
Help: "The number of group chat msg successful processed",
})
GroupChatMsgProcessFailedCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "group_chat_msg_process_failed_total",
Help: "The number of group chat msg failed processed",
})
)
func RegistryMsg() {
registry.MustRegister(
SingleChatMsgProcessSuccessCounter,
SingleChatMsgProcessFailedCounter,
GroupChatMsgProcessSuccessCounter,
GroupChatMsgProcessFailedCounter,
)
}

View File

@@ -0,0 +1,30 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prommetrics
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
OnlineUserGauge = prometheus.NewGauge(prometheus.GaugeOpts{
Name: "online_user_num",
Help: "The number of online user num",
})
)
func RegistryMsgGateway() {
registry.MustRegister(OnlineUserGauge)
}

View File

@@ -0,0 +1,37 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prommetrics
import (
"github.com/prometheus/client_golang/prometheus"
)
var (
MsgOfflinePushFailedCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "msg_offline_push_failed_total",
Help: "The number of msg failed offline pushed",
})
MsgLoneTimePushCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "msg_long_time_push_total",
Help: "The number of messages with a push time exceeding 10 seconds",
})
)
func RegistryPush() {
registry.MustRegister(
MsgOfflinePushFailedCounter,
MsgLoneTimePushCounter,
)
}

View File

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

View File

@@ -0,0 +1,117 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prommetrics
import (
"errors"
"fmt"
"net"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/collectors"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const commonPath = "/metrics"
var registry = &prometheusRegistry{prometheus.NewRegistry()}
type prometheusRegistry struct {
*prometheus.Registry
}
func (x *prometheusRegistry) MustRegister(cs ...prometheus.Collector) {
for _, c := range cs {
if err := x.Registry.Register(c); err != nil {
if errors.As(err, &prometheus.AlreadyRegisteredError{}) {
continue
}
panic(err)
}
}
}
func init() {
registry.MustRegister(
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
collectors.NewGoCollector(),
)
}
var (
baseCollector = []prometheus.Collector{
collectors.NewProcessCollector(collectors.ProcessCollectorOpts{}),
collectors.NewGoCollector(),
}
)
func Init(registry *prometheus.Registry, listener net.Listener, path string, handler http.Handler, cs ...prometheus.Collector) error {
registry.MustRegister(cs...)
srv := http.NewServeMux()
srv.Handle(path, handler)
return http.Serve(listener, srv)
}
func RegistryAll() {
RegistryApi()
RegistryAuth()
RegistryMsg()
RegistryMsgGateway()
RegistryPush()
RegistryUser()
RegistryRpc()
RegistryTransfer()
}
func Start(listener net.Listener) error {
srv := http.NewServeMux()
srv.Handle(commonPath, promhttp.HandlerFor(registry, promhttp.HandlerOpts{}))
return http.Serve(listener, srv)
}
const (
APIKeyName = "api"
MessageTransferKeyName = "message-transfer"
TTL = 300
)
type Target struct {
Target string `json:"target"`
Labels map[string]string `json:"labels"`
}
type RespTarget struct {
Targets []string `json:"targets"`
Labels map[string]string `json:"labels"`
}
func BuildDiscoveryKeyPrefix(name string) string {
return fmt.Sprintf("%s/%s/%s", "openim", "prometheus_discovery", name)
}
func BuildDiscoveryKey(name string, index int) string {
return fmt.Sprintf("%s/%s/%s/%d", "openim", "prometheus_discovery", name, index)
}
func BuildDefaultTarget(host string, ip int) Target {
return Target{
Target: fmt.Sprintf("%s:%d", host, ip),
Labels: map[string]string{
"namespace": "default",
},
}
}

View File

@@ -0,0 +1,77 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prommetrics
import "testing"
//func TestNewGrpcPromObj(t *testing.T) {
// // Create a custom metric to pass into the NewGrpcPromObj function.
// customMetric := prometheus.NewCounter(prometheus.CounterOpts{
// Name: "test_metric",
// Help: "This is a test metric.",
// })
// cusMetrics := []prometheus.Collector{customMetric}
//
// // Call NewGrpcPromObj with the custom metrics.
// reg, grpcMetrics, err := NewGrpcPromObj(cusMetrics)
//
// // Assert no error was returned.
// assert.NoError(t, err)
//
// // Assert the registry was correctly initialized.
// assert.NotNil(t, reg)
//
// // Assert the grpcMetrics was correctly initialized.
// assert.NotNil(t, grpcMetrics)
//
// // Assert that the custom metric is registered.
// mfs, err := reg.Gather()
// assert.NoError(t, err)
// assert.NotEmpty(t, mfs) // Ensure some metrics are present.
// found := false
// for _, mf := range mfs {
// if *mf.Name == "test_metric" {
// found = true
// break
// }
// }
// assert.True(t, found, "Custom metric not found in registry")
//}
//func TestGetGrpcCusMetrics(t *testing.T) {
// conf := config2.NewGlobalConfig()
//
// config2.InitConfig(conf, "../../config")
// // Test various cases based on the switch statement in the GetGrpcCusMetrics function.
// testCases := []struct {
// name string
// expected int // The expected number of metrics for each case.
// }{
// {conf.RpcRegisterName.OpenImMessageGatewayName, 1},
// }
//
// for _, tc := range testCases {
// t.Run(tc.name, func(t *testing.T) {
// metrics := GetGrpcCusMetrics(tc.name, &conf.RpcRegisterName)
// assert.Len(t, metrics, tc.expected)
// })
// }
//}
func TestName(t *testing.T) {
RegistryApi()
RegistryApi()
}

View File

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

View File

@@ -0,0 +1,68 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package prommetrics
import (
"net"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
MsgInsertRedisSuccessCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "msg_insert_redis_success_total",
Help: "The number of successful insert msg to redis",
})
MsgInsertRedisFailedCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "msg_insert_redis_failed_total",
Help: "The number of failed insert msg to redis",
})
MsgInsertMongoSuccessCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "msg_insert_mongo_success_total",
Help: "The number of successful insert msg to mongo",
})
MsgInsertMongoFailedCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "msg_insert_mongo_failed_total",
Help: "The number of failed insert msg to mongo",
})
SeqSetFailedCounter = prometheus.NewCounter(prometheus.CounterOpts{
Name: "seq_set_failed_total",
Help: "The number of failed set seq",
})
)
func RegistryTransfer() {
registry.MustRegister(
MsgInsertRedisSuccessCounter,
MsgInsertRedisFailedCounter,
MsgInsertMongoSuccessCounter,
MsgInsertMongoFailedCounter,
SeqSetFailedCounter,
)
}
func TransferInit(listener net.Listener) error {
reg := prometheus.NewRegistry()
cs := append(
baseCollector,
MsgInsertRedisSuccessCounter,
MsgInsertRedisFailedCounter,
MsgInsertMongoSuccessCounter,
MsgInsertMongoFailedCounter,
SeqSetFailedCounter,
)
return Init(reg, listener, commonPath, promhttp.HandlerFor(reg, promhttp.HandlerOpts{Registry: reg}), cs...)
}

View File

@@ -0,0 +1,104 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package servererrs
// UnknownCode represents the error code when code is not parsed or parsed code equals 0.
const UnknownCode = 1000
// Error codes for various error scenarios.
const (
FormattingError = 10001 // Error in formatting
HasRegistered = 10002 // user has already registered
NotRegistered = 10003 // user is not registered
PasswordErr = 10004 // Password error
GetIMTokenErr = 10005 // Error in getting IM token
RepeatSendCode = 10006 // Repeat sending code
MailSendCodeErr = 10007 // Error in sending code via email
SmsSendCodeErr = 10008 // Error in sending code via SMS
CodeInvalidOrExpired = 10009 // Code is invalid or expired
RegisterFailed = 10010 // Registration failed
ResetPasswordFailed = 10011 // Resetting password failed
RegisterLimit = 10012 // Registration limit exceeded
LoginLimit = 10013 // Login limit exceeded
InvitationError = 10014 // Error in invitation
)
// General error codes.
const (
NoError = 0 // No error
DatabaseError = 90002 // Database error (redis/mysql, etc.)
NetworkError = 90004 // Network error
DataError = 90007 // Data error
CallbackError = 80000
// General error codes.
ServerInternalError = 500 // Server internal error
ArgsError = 1001 // Input parameter error
NoPermissionError = 1002 // Insufficient permission
DuplicateKeyError = 1003
RecordNotFoundError = 1004 // Record does not exist
SecretNotChangedError = 1050 // secret not changed
// Account error codes.
UserIDNotFoundError = 1101 // UserID does not exist or is not registered
RegisteredAlreadyError = 1102 // user is already registered
// Group error codes.
GroupIDNotFoundError = 1201 // GroupID does not exist
GroupIDExisted = 1202 // GroupID already exists
NotInGroupYetError = 1203 // Not in the group yet
DismissedAlreadyError = 1204 // Group has already been dismissed
GroupTypeNotSupport = 1205
GroupRequestHandled = 1206
// Relationship error codes.
CanNotAddYourselfError = 1301 // Cannot add yourself as a friend
BlockedByPeer = 1302 // Blocked by the peer
NotPeersFriend = 1303 // Not the peer's friend
RelationshipAlreadyError = 1304 // Already in a friend relationship
// Message error codes.
MessageHasReadDisable = 1401
MutedInGroup = 1402 // Member muted in the group
MutedGroup = 1403 // Group is muted
MsgAlreadyRevoke = 1404 // Message already revoked
MessageContainsLink = 1405 // Message contains link (not allowed for userType=0)
ImageContainsQRCode = 1406 // Image contains QR code (not allowed for userType=0)
// Token error codes.
TokenExpiredError = 1501
TokenInvalidError = 1502
TokenMalformedError = 1503
TokenNotValidYetError = 1504
TokenUnknownError = 1505
TokenKickedError = 1506
TokenNotExistError = 1507
// Long connection gateway error codes.
ConnOverMaxNumLimit = 1601
ConnArgsErr = 1602
PushMsgErr = 1603
IOSBackgroundPushErr = 1604
// S3 error codes.
FileUploadedExpiredError = 1701 // Upload expired
// Red packet error codes.
RedPacketFinishedError = 1801 // Red packet has been finished
RedPacketExpiredError = 1802 // Red packet has expired
RedPacketAlreadyReceivedError = 1803 // User has already received this red packet
)

View File

@@ -0,0 +1 @@
package servererrs // import "git.imall.cloud/openim/open-im-server-deploy/pkg/common/servererrs"

View File

@@ -0,0 +1,77 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package servererrs
import "github.com/openimsdk/tools/errs"
var (
ErrSecretNotChanged = errs.NewCodeError(SecretNotChangedError, "secret not changed, please change secret in config/share.yml for security reasons")
ErrDatabase = errs.NewCodeError(DatabaseError, "DatabaseError")
ErrNetwork = errs.NewCodeError(NetworkError, "NetworkError")
ErrCallback = errs.NewCodeError(CallbackError, "CallbackError")
ErrCallbackContinue = errs.NewCodeError(CallbackError, "ErrCallbackContinue")
ErrInternalServer = errs.NewCodeError(ServerInternalError, "ServerInternalError")
ErrArgs = errs.NewCodeError(ArgsError, "ArgsError")
ErrNoPermission = errs.NewCodeError(NoPermissionError, "NoPermissionError")
ErrDuplicateKey = errs.NewCodeError(DuplicateKeyError, "DuplicateKeyError")
ErrRecordNotFound = errs.NewCodeError(RecordNotFoundError, "RecordNotFoundError")
ErrUserIDNotFound = errs.NewCodeError(UserIDNotFoundError, "UserIDNotFoundError")
ErrGroupIDNotFound = errs.NewCodeError(GroupIDNotFoundError, "GroupIDNotFoundError")
ErrGroupIDExisted = errs.NewCodeError(GroupIDExisted, "GroupIDExisted")
ErrNotInGroupYet = errs.NewCodeError(NotInGroupYetError, "NotInGroupYetError")
ErrDismissedAlready = errs.NewCodeError(DismissedAlreadyError, "DismissedAlreadyError")
ErrRegisteredAlready = errs.NewCodeError(RegisteredAlreadyError, "RegisteredAlreadyError")
ErrGroupTypeNotSupport = errs.NewCodeError(GroupTypeNotSupport, "")
ErrGroupRequestHandled = errs.NewCodeError(GroupRequestHandled, "GroupRequestHandled")
ErrData = errs.NewCodeError(DataError, "DataError")
ErrTokenExpired = errs.NewCodeError(TokenExpiredError, "TokenExpiredError")
ErrTokenInvalid = errs.NewCodeError(TokenInvalidError, "TokenInvalidError") //
ErrTokenMalformed = errs.NewCodeError(TokenMalformedError, "TokenMalformedError") //
ErrTokenNotValidYet = errs.NewCodeError(TokenNotValidYetError, "TokenNotValidYetError") //
ErrTokenUnknown = errs.NewCodeError(TokenUnknownError, "TokenUnknownError") //
ErrTokenKicked = errs.NewCodeError(TokenKickedError, "TokenKickedError")
ErrTokenNotExist = errs.NewCodeError(TokenNotExistError, "TokenNotExistError") //
ErrMessageHasReadDisable = errs.NewCodeError(MessageHasReadDisable, "MessageHasReadDisable")
ErrCanNotAddYourself = errs.NewCodeError(CanNotAddYourselfError, "CanNotAddYourselfError")
ErrBlockedByPeer = errs.NewCodeError(BlockedByPeer, "BlockedByPeer")
ErrNotPeersFriend = errs.NewCodeError(NotPeersFriend, "NotPeersFriend")
ErrRelationshipAlready = errs.NewCodeError(RelationshipAlreadyError, "RelationshipAlreadyError")
ErrMutedInGroup = errs.NewCodeError(MutedInGroup, "MutedInGroup")
ErrMutedGroup = errs.NewCodeError(MutedGroup, "MutedGroup")
ErrMsgAlreadyRevoke = errs.NewCodeError(MsgAlreadyRevoke, "MsgAlreadyRevoke")
ErrMessageContainsLink = errs.NewCodeError(MessageContainsLink, "MessageContainsLink")
ErrImageContainsQRCode = errs.NewCodeError(ImageContainsQRCode, "ImageContainsQRCode")
ErrConnOverMaxNumLimit = errs.NewCodeError(ConnOverMaxNumLimit, "ConnOverMaxNumLimit")
ErrConnArgsErr = errs.NewCodeError(ConnArgsErr, "args err, need token, sendID, platformID")
ErrPushMsgErr = errs.NewCodeError(PushMsgErr, "push msg err")
ErrIOSBackgroundPushErr = errs.NewCodeError(IOSBackgroundPushErr, "ios background push err")
ErrFileUploadedExpired = errs.NewCodeError(FileUploadedExpiredError, "FileUploadedExpiredError")
// Red packet errors.
ErrRedPacketFinished = errs.NewCodeError(RedPacketFinishedError, "red packet has been finished")
ErrRedPacketExpired = errs.NewCodeError(RedPacketExpiredError, "red packet has expired")
ErrRedPacketAlreadyReceived = errs.NewCodeError(RedPacketAlreadyReceivedError, "you have already received this red packet")
)

View File

@@ -0,0 +1,58 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package servererrs
import "github.com/openimsdk/tools/errs"
var Relation = &relation{m: make(map[int]map[int]struct{})}
func init() {
Relation.Add(errs.RecordNotFoundError, UserIDNotFoundError)
Relation.Add(errs.RecordNotFoundError, GroupIDNotFoundError)
Relation.Add(errs.DuplicateKeyError, GroupIDExisted)
}
type relation struct {
m map[int]map[int]struct{}
}
func (r *relation) Add(codes ...int) {
if len(codes) < 2 {
panic("codes length must be greater than 2")
}
for i := 1; i < len(codes); i++ {
parent := codes[i-1]
s, ok := r.m[parent]
if !ok {
s = make(map[int]struct{})
r.m[parent] = s
}
for _, code := range codes[i:] {
s[code] = struct{}{}
}
}
}
func (r *relation) Is(parent, child int) bool {
if parent == child {
return true
}
s, ok := r.m[parent]
if !ok {
return false
}
_, ok = s[child]
return ok
}

View File

@@ -0,0 +1,107 @@
package startrpc
import (
"context"
"time"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/stability/circuitbreaker"
"github.com/openimsdk/tools/stability/circuitbreaker/sre"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type CircuitBreaker struct {
Enable bool `yaml:"enable"`
Success float64 `yaml:"success"` // success rate threshold (0.0-1.0)
Request int64 `yaml:"request"` // request threshold
Bucket int `yaml:"bucket"` // number of buckets
Window time.Duration `yaml:"window"` // time window for statistics
}
func NewCircuitBreaker(config *CircuitBreaker) circuitbreaker.CircuitBreaker {
if !config.Enable {
return nil
}
return sre.NewSREBraker(
sre.WithWindow(config.Window),
sre.WithBucket(config.Bucket),
sre.WithSuccess(config.Success),
sre.WithRequest(config.Request),
)
}
func UnaryCircuitBreakerInterceptor(breaker circuitbreaker.CircuitBreaker) grpc.ServerOption {
if breaker == nil {
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
return handler(ctx, req)
})
}
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
if err := breaker.Allow(); err != nil {
log.ZWarn(ctx, "rpc circuit breaker open", err, "method", info.FullMethod)
return nil, status.Error(codes.Unavailable, "service unavailable due to circuit breaker")
}
resp, err = handler(ctx, req)
if err != nil {
if st, ok := status.FromError(err); ok {
switch st.Code() {
case codes.OK:
breaker.MarkSuccess()
case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied:
breaker.MarkSuccess()
default:
breaker.MarkFailed()
}
} else {
breaker.MarkFailed()
}
} else {
breaker.MarkSuccess()
}
return resp, err
})
}
func StreamCircuitBreakerInterceptor(breaker circuitbreaker.CircuitBreaker) grpc.ServerOption {
if breaker == nil {
return grpc.ChainStreamInterceptor(func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
return handler(srv, ss)
})
}
return grpc.ChainStreamInterceptor(func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if err := breaker.Allow(); err != nil {
log.ZWarn(ss.Context(), "rpc circuit breaker open", err, "method", info.FullMethod)
return status.Error(codes.Unavailable, "service unavailable due to circuit breaker")
}
err := handler(srv, ss)
if err != nil {
if st, ok := status.FromError(err); ok {
switch st.Code() {
case codes.OK:
breaker.MarkSuccess()
case codes.InvalidArgument, codes.NotFound, codes.AlreadyExists, codes.PermissionDenied:
breaker.MarkSuccess()
default:
breaker.MarkFailed()
}
} else {
breaker.MarkFailed()
}
} else {
breaker.MarkSuccess()
}
return err
})
}

15
pkg/common/startrpc/mw.go Normal file
View File

@@ -0,0 +1,15 @@
package startrpc
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/pkg/authverify"
"google.golang.org/grpc"
)
func grpcServerIMAdminUserID(imAdminUserID []string) grpc.ServerOption {
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
ctx = authverify.WithIMAdminUserIDs(ctx, imAdminUserID)
return handler(ctx, req)
})
}

View File

@@ -0,0 +1,70 @@
package startrpc
import (
"context"
"time"
"github.com/openimsdk/tools/log"
"github.com/openimsdk/tools/stability/ratelimit"
"github.com/openimsdk/tools/stability/ratelimit/bbr"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type RateLimiter struct {
Enable bool
Window time.Duration
Bucket int
CPUThreshold int64
}
func NewRateLimiter(config *RateLimiter) ratelimit.Limiter {
if !config.Enable {
return nil
}
return bbr.NewBBRLimiter(
bbr.WithWindow(config.Window),
bbr.WithBucket(config.Bucket),
bbr.WithCPUThreshold(config.CPUThreshold),
)
}
func UnaryRateLimitInterceptor(limiter ratelimit.Limiter) grpc.ServerOption {
if limiter == nil {
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
return handler(ctx, req)
})
}
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
done, err := limiter.Allow()
if err != nil {
log.ZWarn(ctx, "rpc rate limited", err, "method", info.FullMethod)
return nil, status.Errorf(codes.ResourceExhausted, "rpc request rate limit exceeded: %v, please try again later", err)
}
defer done(ratelimit.DoneInfo{})
return handler(ctx, req)
})
}
func StreamRateLimitInterceptor(limiter ratelimit.Limiter) grpc.ServerOption {
if limiter == nil {
return grpc.ChainStreamInterceptor(func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
return handler(srv, ss)
})
}
return grpc.ChainStreamInterceptor(func(srv any, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
done, err := limiter.Allow()
if err != nil {
log.ZWarn(ss.Context(), "rpc rate limited", err, "method", info.FullMethod)
return status.Errorf(codes.ResourceExhausted, "rpc request rate limit exceeded: %v, please try again later", err)
}
defer done(ratelimit.DoneInfo{})
return handler(srv, ss)
})
}

View File

@@ -0,0 +1,321 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package startrpc
import (
"context"
"errors"
"fmt"
"net"
"os"
"os/signal"
"reflect"
"strconv"
"syscall"
"time"
conf "git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
"github.com/openimsdk/tools/utils/datautil"
"github.com/openimsdk/tools/utils/jsonutil"
"github.com/openimsdk/tools/utils/network"
"google.golang.org/grpc/status"
kdisc "git.imall.cloud/openim/open-im-server-deploy/pkg/common/discovery"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/prommetrics"
"github.com/openimsdk/tools/discovery"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
grpccli "github.com/openimsdk/tools/mw/grpc/client"
grpcsrv "github.com/openimsdk/tools/mw/grpc/server"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
)
func init() {
prommetrics.RegistryAll()
}
func Start[T any](ctx context.Context, disc *conf.Discovery, circuitBreakerConfig *conf.CircuitBreaker, rateLimiterConfig *conf.RateLimiter, prometheusConfig *conf.Prometheus, listenIP,
registerIP string, autoSetPorts bool, rpcPorts []int, index int, rpcRegisterName string, notification *conf.Notification, config T,
watchConfigNames []string, watchServiceNames []string,
rpcFn func(ctx context.Context, config T, client discovery.SvcDiscoveryRegistry, server grpc.ServiceRegistrar) error,
options ...grpc.ServerOption) error {
if notification != nil {
conf.InitNotification(notification)
}
maxRequestBody := getConfigRpcMaxRequestBody(reflect.ValueOf(config))
shareConfig := getConfigShare(reflect.ValueOf(config))
log.ZDebug(ctx, "rpc start", "rpcMaxRequestBody", maxRequestBody, "rpcRegisterName", rpcRegisterName, "registerIP", registerIP, "listenIP", listenIP)
options = append(options,
grpcsrv.GrpcServerMetadataContext(),
grpcsrv.GrpcServerErrorConvert(),
grpcsrv.GrpcServerLogger(),
grpcsrv.GrpcServerRequestValidate(),
grpcsrv.GrpcServerPanicCapture(),
)
if shareConfig != nil && len(shareConfig.IMAdminUser.UserIDs) > 0 {
options = append(options, grpcServerIMAdminUserID(shareConfig.IMAdminUser.UserIDs))
}
var clientOptions []grpc.DialOption
if maxRequestBody != nil {
if maxRequestBody.RequestMaxBodySize > 0 {
options = append(options, grpc.MaxRecvMsgSize(maxRequestBody.RequestMaxBodySize))
clientOptions = append(clientOptions, grpc.WithDefaultCallOptions(grpc.MaxCallSendMsgSize(maxRequestBody.RequestMaxBodySize)))
}
if maxRequestBody.ResponseMaxBodySize > 0 {
options = append(options, grpc.MaxSendMsgSize(maxRequestBody.ResponseMaxBodySize))
clientOptions = append(clientOptions, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxRequestBody.ResponseMaxBodySize)))
}
}
if circuitBreakerConfig != nil && circuitBreakerConfig.Enable {
cb := &CircuitBreaker{
Enable: circuitBreakerConfig.Enable,
Success: circuitBreakerConfig.Success,
Request: circuitBreakerConfig.Request,
Bucket: circuitBreakerConfig.Bucket,
Window: circuitBreakerConfig.Window,
}
breaker := NewCircuitBreaker(cb)
options = append(options,
UnaryCircuitBreakerInterceptor(breaker),
StreamCircuitBreakerInterceptor(breaker),
)
log.ZInfo(ctx, "RPC circuit breaker enabled",
"service", rpcRegisterName,
"window", circuitBreakerConfig.Window,
"bucket", circuitBreakerConfig.Bucket,
"success", circuitBreakerConfig.Success,
"requestThreshold", circuitBreakerConfig.Request)
}
if rateLimiterConfig != nil && rateLimiterConfig.Enable {
rl := &RateLimiter{
Enable: rateLimiterConfig.Enable,
Window: rateLimiterConfig.Window,
Bucket: rateLimiterConfig.Bucket,
CPUThreshold: rateLimiterConfig.CPUThreshold,
}
limiter := NewRateLimiter(rl)
options = append(options,
UnaryRateLimitInterceptor(limiter),
StreamRateLimitInterceptor(limiter),
)
log.ZInfo(ctx, "RPC rate limiter enabled",
"service", rpcRegisterName,
"window", rateLimiterConfig.Window,
"bucket", rateLimiterConfig.Bucket,
"cpuThreshold", rateLimiterConfig.CPUThreshold)
}
registerIP, err := network.GetRpcRegisterIP(registerIP)
if err != nil {
return err
}
var prometheusListenAddr string
if autoSetPorts {
prometheusListenAddr = net.JoinHostPort(listenIP, "0")
} else {
prometheusPort, err := datautil.GetElemByIndex(prometheusConfig.Ports, index)
if err != nil {
return err
}
prometheusListenAddr = net.JoinHostPort(listenIP, strconv.Itoa(prometheusPort))
}
watchConfigNames = append(watchConfigNames, conf.LogConfigFileName)
client, err := kdisc.NewDiscoveryRegister(disc, watchServiceNames)
if err != nil {
return err
}
defer client.Close()
client.AddOption(
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"LoadBalancingPolicy": "%s"}`, "round_robin")),
grpccli.GrpcClientLogger(),
grpccli.GrpcClientContext(),
grpccli.GrpcClientErrorConvert(),
)
if len(clientOptions) > 0 {
client.AddOption(clientOptions...)
}
ctx, cancel := context.WithCancelCause(ctx)
go func() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT, syscall.SIGKILL)
select {
case <-ctx.Done():
return
case val := <-sigs:
log.ZDebug(ctx, "recv signal", "signal", val.String())
cancel(fmt.Errorf("signal %s", val.String()))
}
}()
if prometheusListenAddr != "" {
options = append(
options,
prommetricsUnaryInterceptor(rpcRegisterName),
prommetricsStreamInterceptor(rpcRegisterName),
)
prometheusListener, prometheusPort, err := listenTCP(prometheusListenAddr)
if err != nil {
return err
}
log.ZDebug(ctx, "prometheus start", "addr", prometheusListener.Addr(), "rpcRegisterName", rpcRegisterName)
target, err := jsonutil.JsonMarshal(prommetrics.BuildDefaultTarget(registerIP, prometheusPort))
if err != nil {
return err
}
if autoSetPorts {
if err = client.SetWithLease(ctx, prommetrics.BuildDiscoveryKey(rpcRegisterName, index), target, prommetrics.TTL); err != nil {
if !errors.Is(err, discovery.ErrNotSupported) {
return err
}
}
}
go func() {
err := prommetrics.Start(prometheusListener)
if err == nil {
err = fmt.Errorf("listener done")
}
cancel(fmt.Errorf("prommetrics %s %w", rpcRegisterName, err))
}()
}
var (
rpcServer *grpc.Server
rpcGracefulStop chan struct{}
)
onGrpcServiceRegistrar := func(desc *grpc.ServiceDesc, impl any) {
if rpcServer != nil {
rpcServer.RegisterService(desc, impl)
return
}
var rpcListenAddr string
if autoSetPorts {
rpcListenAddr = net.JoinHostPort(listenIP, "0")
} else {
rpcPort, err := datautil.GetElemByIndex(rpcPorts, index)
if err != nil {
cancel(fmt.Errorf("rpcPorts index out of range %s %w", rpcRegisterName, err))
return
}
rpcListenAddr = net.JoinHostPort(listenIP, strconv.Itoa(rpcPort))
}
rpcListener, err := net.Listen("tcp", rpcListenAddr)
if err != nil {
cancel(fmt.Errorf("listen rpc %s %s %w", rpcRegisterName, rpcListenAddr, err))
return
}
rpcServer = grpc.NewServer(options...)
rpcServer.RegisterService(desc, impl)
rpcGracefulStop = make(chan struct{})
rpcPort := rpcListener.Addr().(*net.TCPAddr).Port
log.ZDebug(ctx, "rpc start register", "rpcRegisterName", rpcRegisterName, "registerIP", registerIP, "rpcPort", rpcPort)
grpcOpt := grpc.WithTransportCredentials(insecure.NewCredentials())
rpcGracefulStop = make(chan struct{})
go func() {
<-ctx.Done()
rpcServer.GracefulStop()
close(rpcGracefulStop)
}()
if err := client.Register(ctx, rpcRegisterName, registerIP, rpcListener.Addr().(*net.TCPAddr).Port, grpcOpt); err != nil {
cancel(fmt.Errorf("rpc register %s %w", rpcRegisterName, err))
return
}
go func() {
err := rpcServer.Serve(rpcListener)
if err == nil {
err = fmt.Errorf("serve end")
}
cancel(fmt.Errorf("rpc %s %w", rpcRegisterName, err))
}()
}
err = rpcFn(ctx, config, client, &grpcServiceRegistrar{onRegisterService: onGrpcServiceRegistrar})
if err != nil {
return err
}
<-ctx.Done()
log.ZDebug(ctx, "cmd wait done", "err", context.Cause(ctx))
if rpcGracefulStop != nil {
timeout := time.NewTimer(time.Second * 15)
defer timeout.Stop()
select {
case <-timeout.C:
log.ZWarn(ctx, "rcp graceful stop timeout", nil)
case <-rpcGracefulStop:
log.ZDebug(ctx, "rcp graceful stop done")
}
}
return context.Cause(ctx)
}
func listenTCP(addr string) (net.Listener, int, error) {
listener, err := net.Listen("tcp", addr)
if err != nil {
return nil, 0, errs.WrapMsg(err, "listen err", "addr", addr)
}
return listener, listener.Addr().(*net.TCPAddr).Port, nil
}
func prommetricsUnaryInterceptor(rpcRegisterName string) grpc.ServerOption {
getCode := func(err error) int {
if err == nil {
return 0
}
rpcErr, ok := err.(interface{ GRPCStatus() *status.Status })
if !ok {
return -1
}
return int(rpcErr.GRPCStatus().Code())
}
return grpc.ChainUnaryInterceptor(func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
resp, err := handler(ctx, req)
prommetrics.RPCCall(rpcRegisterName, info.FullMethod, getCode(err))
return resp, err
})
}
func prommetricsStreamInterceptor(rpcRegisterName string) grpc.ServerOption {
return grpc.ChainStreamInterceptor()
}
type grpcServiceRegistrar struct {
onRegisterService func(desc *grpc.ServiceDesc, impl any)
}
func (x *grpcServiceRegistrar) RegisterService(desc *grpc.ServiceDesc, impl any) {
x.onRegisterService(desc, impl)
}

View File

@@ -0,0 +1,47 @@
package startrpc
import (
"reflect"
conf "git.imall.cloud/openim/open-im-server-deploy/pkg/common/config"
)
func getConfig[T any](value reflect.Value) *T {
for value.Kind() == reflect.Pointer {
value = value.Elem()
}
if value.Kind() == reflect.Struct {
num := value.NumField()
for i := 0; i < num; i++ {
field := value.Field(i)
for field.Kind() == reflect.Pointer {
field = field.Elem()
}
if field.Kind() == reflect.Struct {
if elem, ok := field.Interface().(T); ok {
return &elem
}
if elem := getConfig[T](field); elem != nil {
return elem
}
}
}
}
return nil
}
func getConfigRpcMaxRequestBody(value reflect.Value) *conf.MaxRequestBody {
return getConfig[conf.MaxRequestBody](value)
}
func getConfigShare(value reflect.Value) *conf.Share {
return getConfig[conf.Share](value)
}
func getConfigRateLimiter(value reflect.Value) *conf.RateLimiter {
return getConfig[conf.RateLimiter](value)
}
func getConfigCircuitBreaker(value reflect.Value) *conf.CircuitBreaker {
return getConfig[conf.CircuitBreaker](value)
}

View File

@@ -0,0 +1,17 @@
package cache
import (
"context"
)
// BatchDeleter interface defines a set of methods for batch deleting cache and publishing deletion information.
type BatchDeleter interface {
//ChainExecDel method is used for chain calls and must call Clone to prevent memory pollution.
ChainExecDel(ctx context.Context) error
//ExecDelWithKeys method directly takes keys for deletion.
ExecDelWithKeys(ctx context.Context, keys []string) error
//Clone method creates a copy of the BatchDeleter to avoid modifying the original object.
Clone() BatchDeleter
//AddKeys method adds keys to be deleted.
AddKeys(keys ...string)
}

27
pkg/common/storage/cache/black.go vendored Normal file
View File

@@ -0,0 +1,27 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"context"
)
type BlackCache interface {
BatchDeleter
CloneBlackCache() BlackCache
GetBlackIDs(ctx context.Context, userID string) (blackIDs []string, err error)
// del user's blackIDs msgCache, exec when a user's black list changed
DelBlackIDs(ctx context.Context, userID string) BlackCache
}

View File

@@ -0,0 +1,8 @@
package cache
import "context"
type ClientConfigCache interface {
DeleteUserCache(ctx context.Context, userIDs []string) error
GetUserConfig(ctx context.Context, userID string) (map[string]string, error)
}

View File

@@ -0,0 +1,65 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"context"
relationtb "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
)
// arg fn will exec when no data in msgCache.
type ConversationCache interface {
BatchDeleter
CloneConversationCache() ConversationCache
// get user's conversationIDs from msgCache
GetUserConversationIDs(ctx context.Context, ownerUserID string) ([]string, error)
GetUserNotNotifyConversationIDs(ctx context.Context, userID string) ([]string, error)
GetPinnedConversationIDs(ctx context.Context, userID string) ([]string, error)
DelConversationIDs(userIDs ...string) ConversationCache
GetUserConversationIDsHash(ctx context.Context, ownerUserID string) (hash uint64, err error)
DelUserConversationIDsHash(ownerUserIDs ...string) ConversationCache
// get one conversation from msgCache
GetConversation(ctx context.Context, ownerUserID, conversationID string) (*relationtb.Conversation, error)
DelConversations(ownerUserID string, conversationIDs ...string) ConversationCache
DelUsersConversation(conversationID string, ownerUserIDs ...string) ConversationCache
// get one conversation from msgCache
GetConversations(ctx context.Context, ownerUserID string,
conversationIDs []string) ([]*relationtb.Conversation, error)
// get one user's all conversations from msgCache
GetUserAllConversations(ctx context.Context, ownerUserID string) ([]*relationtb.Conversation, error)
// get user conversation recv msg from msgCache
GetUserRecvMsgOpt(ctx context.Context, ownerUserID, conversationID string) (opt int, err error)
DelUserRecvMsgOpt(ownerUserID, conversationID string) ConversationCache
// get one super group recv msg but do not notification userID list
// GetSuperGroupRecvMsgNotNotifyUserIDs(ctx context.Context, groupID string) (userIDs []string, err error)
DelSuperGroupRecvMsgNotNotifyUserIDs(groupID string) ConversationCache
// get one super group recv msg but do not notification userID list hash
// GetSuperGroupRecvMsgNotNotifyUserIDsHash(ctx context.Context, groupID string) (hash uint64, err error)
DelSuperGroupRecvMsgNotNotifyUserIDsHash(groupID string) ConversationCache
// GetUserAllHasReadSeqs(ctx context.Context, ownerUserID string) (map[string]int64, error)
DelUserAllHasReadSeqs(ownerUserID string, conversationIDs ...string) ConversationCache
GetConversationNotReceiveMessageUserIDs(ctx context.Context, conversationID string) ([]string, error)
DelConversationNotReceiveMessageUserIDs(conversationIDs ...string) ConversationCache
DelConversationNotNotifyMessageUserIDs(userIDs ...string) ConversationCache
DelUserPinnedConversations(userIDs ...string) ConversationCache
DelConversationVersionUserIDs(userIDs ...string) ConversationCache
FindMaxConversationUserVersion(ctx context.Context, userID string) (*relationtb.VersionLog, error)
}

15
pkg/common/storage/cache/doc.go vendored Normal file
View File

@@ -0,0 +1,15 @@
// Copyright © 2024 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache // import "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache"

48
pkg/common/storage/cache/friend.go vendored Normal file
View File

@@ -0,0 +1,48 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"context"
relationtb "git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
)
// FriendCache is an interface for caching friend-related data.
type FriendCache interface {
BatchDeleter
CloneFriendCache() FriendCache
GetFriendIDs(ctx context.Context, ownerUserID string) (friendIDs []string, err error)
// Called when friendID list changed
DelFriendIDs(ownerUserID ...string) FriendCache
// Get single friendInfo from the cache
GetFriend(ctx context.Context, ownerUserID, friendUserID string) (friend *relationtb.Friend, err error)
// Delete friend when friend info changed
DelFriend(ownerUserID, friendUserID string) FriendCache
// Delete friends when friends' info changed
DelFriends(ownerUserID string, friendUserIDs []string) FriendCache
DelOwner(friendUserID string, ownerUserIDs []string) FriendCache
DelMaxFriendVersion(ownerUserIDs ...string) FriendCache
//DelSortFriendUserIDs(ownerUserIDs ...string) FriendCache
//FindSortFriendUserIDs(ctx context.Context, ownerUserID string) ([]string, error)
//FindFriendIncrVersion(ctx context.Context, ownerUserID string, version uint, limit int) (*relationtb.VersionLog, error)
FindMaxFriendVersion(ctx context.Context, ownerUserID string) (*relationtb.VersionLog, error)
}

70
pkg/common/storage/cache/group.go vendored Normal file
View File

@@ -0,0 +1,70 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/common"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
)
type GroupHash interface {
GetGroupHash(ctx context.Context, groupID string) (uint64, error)
}
type GroupCache interface {
BatchDeleter
CloneGroupCache() GroupCache
GetGroupsInfo(ctx context.Context, groupIDs []string) (groups []*model.Group, err error)
GetGroupInfo(ctx context.Context, groupID string) (group *model.Group, err error)
DelGroupsInfo(groupIDs ...string) GroupCache
GetGroupMembersHash(ctx context.Context, groupID string) (hashCode uint64, err error)
GetGroupMemberHashMap(ctx context.Context, groupIDs []string) (map[string]*common.GroupSimpleUserID, error)
DelGroupMembersHash(groupID string) GroupCache
GetGroupMemberIDs(ctx context.Context, groupID string) (groupMemberIDs []string, err error)
DelGroupMemberIDs(groupID string) GroupCache
GetJoinedGroupIDs(ctx context.Context, userID string) (joinedGroupIDs []string, err error)
DelJoinedGroupID(userID ...string) GroupCache
GetGroupMemberInfo(ctx context.Context, groupID, userID string) (groupMember *model.GroupMember, err error)
GetGroupMembersInfo(ctx context.Context, groupID string, userID []string) (groupMembers []*model.GroupMember, err error)
GetAllGroupMembersInfo(ctx context.Context, groupID string) (groupMembers []*model.GroupMember, err error)
FindGroupMemberUser(ctx context.Context, groupIDs []string, userID string) ([]*model.GroupMember, error)
GetGroupRoleLevelMemberIDs(ctx context.Context, groupID string, roleLevel int32) ([]string, error)
GetGroupOwner(ctx context.Context, groupID string) (*model.GroupMember, error)
GetGroupsOwner(ctx context.Context, groupIDs []string) ([]*model.GroupMember, error)
DelGroupRoleLevel(groupID string, roleLevel []int32) GroupCache
DelGroupAllRoleLevel(groupID string) GroupCache
DelGroupMembersInfo(groupID string, userID ...string) GroupCache
GetGroupRoleLevelMemberInfo(ctx context.Context, groupID string, roleLevel int32) ([]*model.GroupMember, error)
GetGroupRolesLevelMemberInfo(ctx context.Context, groupID string, roleLevels []int32) ([]*model.GroupMember, error)
GetGroupMemberNum(ctx context.Context, groupID string) (memberNum int64, err error)
DelGroupsMemberNum(groupID ...string) GroupCache
//FindSortGroupMemberUserIDs(ctx context.Context, groupID string) ([]string, error)
//FindSortJoinGroupIDs(ctx context.Context, userID string) ([]string, error)
DelMaxGroupMemberVersion(groupIDs ...string) GroupCache
DelMaxJoinGroupVersion(userIDs ...string) GroupCache
FindMaxGroupMemberVersion(ctx context.Context, groupID string) (*model.VersionLog, error)
BatchFindMaxGroupMemberVersion(ctx context.Context, groupIDs []string) ([]*model.VersionLog, error)
FindMaxJoinGroupVersion(ctx context.Context, userID string) (*model.VersionLog, error)
}

View File

@@ -0,0 +1,50 @@
package mcache
import (
"context"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache/cachekey"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"github.com/openimsdk/tools/s3/minio"
)
func NewMinioCache(cache database.Cache) minio.Cache {
return &minioCache{
cache: cache,
expireTime: time.Hour * 24 * 7,
}
}
type minioCache struct {
cache database.Cache
expireTime time.Duration
}
func (g *minioCache) getObjectImageInfoKey(key string) string {
return cachekey.GetObjectImageInfoKey(key)
}
func (g *minioCache) getMinioImageThumbnailKey(key string, format string, width int, height int) string {
return cachekey.GetMinioImageThumbnailKey(key, format, width, height)
}
func (g *minioCache) DelObjectImageInfoKey(ctx context.Context, keys ...string) error {
ks := make([]string, 0, len(keys))
for _, key := range keys {
ks = append(ks, g.getObjectImageInfoKey(key))
}
return g.cache.Del(ctx, ks)
}
func (g *minioCache) DelImageThumbnailKey(ctx context.Context, key string, format string, width int, height int) error {
return g.cache.Del(ctx, []string{g.getMinioImageThumbnailKey(key, format, width, height)})
}
func (g *minioCache) GetImageObjectKeyInfo(ctx context.Context, key string, fn func(ctx context.Context) (*minio.ImageInfo, error)) (*minio.ImageInfo, error) {
return getCache[*minio.ImageInfo](ctx, g.cache, g.getObjectImageInfoKey(key), g.expireTime, fn)
}
func (g *minioCache) GetThumbnailKey(ctx context.Context, key string, format string, width int, height int, minioCache func(ctx context.Context) (string, error)) (string, error) {
return getCache[string](ctx, g.cache, g.getMinioImageThumbnailKey(key, format, width, height), g.expireTime, minioCache)
}

View File

@@ -0,0 +1,132 @@
package mcache
import (
"context"
"strconv"
"sync"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache/cachekey"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
"git.imall.cloud/openim/open-im-server-deploy/pkg/localcache"
"git.imall.cloud/openim/open-im-server-deploy/pkg/localcache/lru"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/utils/datautil"
"github.com/redis/go-redis/v9"
)
var (
memMsgCache lru.LRU[string, *model.MsgInfoModel]
initMemMsgCache sync.Once
)
func NewMsgCache(cache database.Cache, msgDocDatabase database.Msg) cache.MsgCache {
initMemMsgCache.Do(func() {
memMsgCache = lru.NewLazyLRU[string, *model.MsgInfoModel](1024*8, time.Hour, time.Second*10, localcache.EmptyTarget{}, nil)
})
return &msgCache{
cache: cache,
msgDocDatabase: msgDocDatabase,
memMsgCache: memMsgCache,
}
}
type msgCache struct {
cache database.Cache
msgDocDatabase database.Msg
memMsgCache lru.LRU[string, *model.MsgInfoModel]
}
func (x *msgCache) getSendMsgKey(id string) string {
return cachekey.GetSendMsgKey(id)
}
func (x *msgCache) SetSendMsgStatus(ctx context.Context, id string, status int32) error {
return x.cache.Set(ctx, x.getSendMsgKey(id), strconv.Itoa(int(status)), time.Hour*24)
}
func (x *msgCache) GetSendMsgStatus(ctx context.Context, id string) (int32, error) {
key := x.getSendMsgKey(id)
res, err := x.cache.Get(ctx, []string{key})
if err != nil {
return 0, err
}
val, ok := res[key]
if !ok {
return 0, errs.Wrap(redis.Nil)
}
status, err := strconv.Atoi(val)
if err != nil {
return 0, errs.WrapMsg(err, "GetSendMsgStatus strconv.Atoi error", "val", val)
}
return int32(status), nil
}
func (x *msgCache) getMsgCacheKey(conversationID string, seq int64) string {
return cachekey.GetMsgCacheKey(conversationID, seq)
}
func (x *msgCache) GetMessageBySeqs(ctx context.Context, conversationID string, seqs []int64) ([]*model.MsgInfoModel, error) {
if len(seqs) == 0 {
return nil, nil
}
keys := make([]string, 0, len(seqs))
keySeq := make(map[string]int64, len(seqs))
for _, seq := range seqs {
key := x.getMsgCacheKey(conversationID, seq)
keys = append(keys, key)
keySeq[key] = seq
}
res, err := x.memMsgCache.GetBatch(keys, func(keys []string) (map[string]*model.MsgInfoModel, error) {
findSeqs := make([]int64, 0, len(keys))
for _, key := range keys {
seq, ok := keySeq[key]
if !ok {
continue
}
findSeqs = append(findSeqs, seq)
}
res, err := x.msgDocDatabase.FindSeqs(ctx, conversationID, seqs)
if err != nil {
return nil, err
}
kv := make(map[string]*model.MsgInfoModel)
for i := range res {
msg := res[i]
if msg == nil || msg.Msg == nil || msg.Msg.Seq <= 0 {
continue
}
key := x.getMsgCacheKey(conversationID, msg.Msg.Seq)
kv[key] = msg
}
return kv, nil
})
if err != nil {
return nil, err
}
return datautil.Values(res), nil
}
func (x msgCache) DelMessageBySeqs(ctx context.Context, conversationID string, seqs []int64) error {
if len(seqs) == 0 {
return nil
}
for _, seq := range seqs {
x.memMsgCache.Del(x.getMsgCacheKey(conversationID, seq))
}
return nil
}
func (x *msgCache) SetMessageBySeqs(ctx context.Context, conversationID string, msgs []*model.MsgInfoModel) error {
for i := range msgs {
msg := msgs[i]
if msg == nil || msg.Msg == nil || msg.Msg.Seq <= 0 {
continue
}
x.memMsgCache.Set(x.getMsgCacheKey(conversationID, msg.Msg.Seq), msg)
}
return nil
}

View File

@@ -0,0 +1,82 @@
package mcache
import (
"context"
"sync"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache"
)
var (
globalOnlineCache cache.OnlineCache
globalOnlineOnce sync.Once
)
func NewOnlineCache() cache.OnlineCache {
globalOnlineOnce.Do(func() {
globalOnlineCache = &onlineCache{
user: make(map[string]map[int32]struct{}),
}
})
return globalOnlineCache
}
type onlineCache struct {
lock sync.RWMutex
user map[string]map[int32]struct{}
}
func (x *onlineCache) GetOnline(ctx context.Context, userID string) ([]int32, error) {
x.lock.RLock()
defer x.lock.RUnlock()
pSet, ok := x.user[userID]
if !ok {
return nil, nil
}
res := make([]int32, 0, len(pSet))
for k := range pSet {
res = append(res, k)
}
return res, nil
}
func (x *onlineCache) SetUserOnline(ctx context.Context, userID string, online, offline []int32) error {
x.lock.Lock()
defer x.lock.Unlock()
pSet, ok := x.user[userID]
if ok {
for _, p := range offline {
delete(pSet, p)
}
}
if len(online) > 0 {
if !ok {
pSet = make(map[int32]struct{})
x.user[userID] = pSet
}
for _, p := range online {
pSet[p] = struct{}{}
}
}
if len(pSet) == 0 {
delete(x.user, userID)
}
return nil
}
func (x *onlineCache) GetAllOnlineUsers(ctx context.Context, cursor uint64) (map[string][]int32, uint64, error) {
if cursor != 0 {
return nil, 0, nil
}
x.lock.RLock()
defer x.lock.RUnlock()
res := make(map[string][]int32)
for k, v := range x.user {
pSet := make([]int32, 0, len(v))
for p := range v {
pSet = append(pSet, p)
}
res[k] = pSet
}
return res, 0, nil
}

View File

@@ -0,0 +1,79 @@
package mcache
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
)
func NewSeqConversationCache(sc database.SeqConversation) cache.SeqConversationCache {
return &seqConversationCache{
sc: sc,
}
}
type seqConversationCache struct {
sc database.SeqConversation
}
func (x *seqConversationCache) Malloc(ctx context.Context, conversationID string, size int64) (int64, error) {
return x.sc.Malloc(ctx, conversationID, size)
}
func (x *seqConversationCache) SetMinSeq(ctx context.Context, conversationID string, seq int64) error {
return x.sc.SetMinSeq(ctx, conversationID, seq)
}
func (x *seqConversationCache) GetMinSeq(ctx context.Context, conversationID string) (int64, error) {
return x.sc.GetMinSeq(ctx, conversationID)
}
func (x *seqConversationCache) GetMaxSeqs(ctx context.Context, conversationIDs []string) (map[string]int64, error) {
res := make(map[string]int64)
for _, conversationID := range conversationIDs {
seq, err := x.GetMinSeq(ctx, conversationID)
if err != nil {
return nil, err
}
res[conversationID] = seq
}
return res, nil
}
func (x *seqConversationCache) GetMaxSeqsWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error) {
res := make(map[string]database.SeqTime)
for _, conversationID := range conversationIDs {
seq, err := x.GetMinSeq(ctx, conversationID)
if err != nil {
return nil, err
}
res[conversationID] = database.SeqTime{Seq: seq}
}
return res, nil
}
func (x *seqConversationCache) GetMaxSeq(ctx context.Context, conversationID string) (int64, error) {
return x.sc.GetMaxSeq(ctx, conversationID)
}
func (x *seqConversationCache) GetMaxSeqWithTime(ctx context.Context, conversationID string) (database.SeqTime, error) {
seq, err := x.GetMinSeq(ctx, conversationID)
if err != nil {
return database.SeqTime{}, err
}
return database.SeqTime{Seq: seq}, nil
}
func (x *seqConversationCache) SetMinSeqs(ctx context.Context, seqs map[string]int64) error {
for conversationID, seq := range seqs {
if err := x.sc.SetMinSeq(ctx, conversationID, seq); err != nil {
return err
}
}
return nil
}
func (x *seqConversationCache) GetCacheMaxSeqWithTime(ctx context.Context, conversationIDs []string) (map[string]database.SeqTime, error) {
return x.GetMaxSeqsWithTime(ctx, conversationIDs)
}

View File

@@ -0,0 +1,98 @@
package mcache
import (
"context"
"strconv"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache/cachekey"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"github.com/openimsdk/tools/errs"
"github.com/redis/go-redis/v9"
)
func NewThirdCache(cache database.Cache) cache.ThirdCache {
return &thirdCache{
cache: cache,
}
}
type thirdCache struct {
cache database.Cache
}
func (c *thirdCache) getGetuiTokenKey() string {
return cachekey.GetGetuiTokenKey()
}
func (c *thirdCache) getGetuiTaskIDKey() string {
return cachekey.GetGetuiTaskIDKey()
}
func (c *thirdCache) getUserBadgeUnreadCountSumKey(userID string) string {
return cachekey.GetUserBadgeUnreadCountSumKey(userID)
}
func (c *thirdCache) getFcmAccountTokenKey(account string, platformID int) string {
return cachekey.GetFcmAccountTokenKey(account, platformID)
}
func (c *thirdCache) get(ctx context.Context, key string) (string, error) {
res, err := c.cache.Get(ctx, []string{key})
if err != nil {
return "", err
}
if val, ok := res[key]; ok {
return val, nil
}
return "", errs.Wrap(redis.Nil)
}
func (c *thirdCache) SetFcmToken(ctx context.Context, account string, platformID int, fcmToken string, expireTime int64) (err error) {
return errs.Wrap(c.cache.Set(ctx, c.getFcmAccountTokenKey(account, platformID), fcmToken, time.Duration(expireTime)*time.Second))
}
func (c *thirdCache) GetFcmToken(ctx context.Context, account string, platformID int) (string, error) {
return c.get(ctx, c.getFcmAccountTokenKey(account, platformID))
}
func (c *thirdCache) DelFcmToken(ctx context.Context, account string, platformID int) error {
return c.cache.Del(ctx, []string{c.getFcmAccountTokenKey(account, platformID)})
}
func (c *thirdCache) IncrUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) {
return c.cache.Incr(ctx, c.getUserBadgeUnreadCountSumKey(userID), 1)
}
func (c *thirdCache) SetUserBadgeUnreadCountSum(ctx context.Context, userID string, value int) error {
return c.cache.Set(ctx, c.getUserBadgeUnreadCountSumKey(userID), strconv.Itoa(value), 0)
}
func (c *thirdCache) GetUserBadgeUnreadCountSum(ctx context.Context, userID string) (int, error) {
str, err := c.get(ctx, c.getUserBadgeUnreadCountSumKey(userID))
if err != nil {
return 0, err
}
val, err := strconv.Atoi(str)
if err != nil {
return 0, errs.WrapMsg(err, "strconv.Atoi", "str", str)
}
return val, nil
}
func (c *thirdCache) SetGetuiToken(ctx context.Context, token string, expireTime int64) error {
return c.cache.Set(ctx, c.getGetuiTokenKey(), token, time.Duration(expireTime)*time.Second)
}
func (c *thirdCache) GetGetuiToken(ctx context.Context) (string, error) {
return c.get(ctx, c.getGetuiTokenKey())
}
func (c *thirdCache) SetGetuiTaskID(ctx context.Context, taskID string, expireTime int64) error {
return c.cache.Set(ctx, c.getGetuiTaskIDKey(), taskID, time.Duration(expireTime)*time.Second)
}
func (c *thirdCache) GetGetuiTaskID(ctx context.Context) (string, error) {
return c.get(ctx, c.getGetuiTaskIDKey())
}

166
pkg/common/storage/cache/mcache/token.go vendored Normal file
View File

@@ -0,0 +1,166 @@
package mcache
import (
"context"
"fmt"
"strconv"
"strings"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache/cachekey"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
)
func NewTokenCacheModel(cache database.Cache, accessExpire int64) cache.TokenModel {
c := &tokenCache{cache: cache}
c.accessExpire = c.getExpireTime(accessExpire)
return c
}
type tokenCache struct {
cache database.Cache
accessExpire time.Duration
}
func (x *tokenCache) getTokenKey(userID string, platformID int, token string) string {
return cachekey.GetTokenKey(userID, platformID) + ":" + token
}
func (x *tokenCache) SetTokenFlag(ctx context.Context, userID string, platformID int, token string, flag int) error {
return x.cache.Set(ctx, x.getTokenKey(userID, platformID, token), strconv.Itoa(flag), x.accessExpire)
}
// SetTokenFlagEx set token and flag with expire time
func (x *tokenCache) SetTokenFlagEx(ctx context.Context, userID string, platformID int, token string, flag int) error {
return x.SetTokenFlag(ctx, userID, platformID, token, flag)
}
func (x *tokenCache) GetTokensWithoutError(ctx context.Context, userID string, platformID int) (map[string]int, error) {
prefix := x.getTokenKey(userID, platformID, "")
m, err := x.cache.Prefix(ctx, prefix)
if err != nil {
return nil, errs.Wrap(err)
}
mm := make(map[string]int)
for k, v := range m {
state, err := strconv.Atoi(v)
if err != nil {
log.ZError(ctx, "token value is not int", err, "value", v, "userID", userID, "platformID", platformID)
continue
}
mm[strings.TrimPrefix(k, prefix)] = state
}
return mm, nil
}
func (x *tokenCache) HasTemporaryToken(ctx context.Context, userID string, platformID int, token string) error {
key := cachekey.GetTemporaryTokenKey(userID, platformID, token)
if _, err := x.cache.Get(ctx, []string{key}); err != nil {
return err
}
return nil
}
func (x *tokenCache) GetAllTokensWithoutError(ctx context.Context, userID string) (map[int]map[string]int, error) {
prefix := cachekey.UidPidToken + userID + ":"
tokens, err := x.cache.Prefix(ctx, prefix)
if err != nil {
return nil, err
}
res := make(map[int]map[string]int)
for key, flagStr := range tokens {
flag, err := strconv.Atoi(flagStr)
if err != nil {
log.ZError(ctx, "token value is not int", err, "key", key, "value", flagStr, "userID", userID)
continue
}
arr := strings.SplitN(strings.TrimPrefix(key, prefix), ":", 2)
if len(arr) != 2 {
log.ZError(ctx, "token value is not int", err, "key", key, "value", flagStr, "userID", userID)
continue
}
platformID, err := strconv.Atoi(arr[0])
if err != nil {
log.ZError(ctx, "token value is not int", err, "key", key, "value", flagStr, "userID", userID)
continue
}
token := arr[1]
if token == "" {
log.ZError(ctx, "token value is not int", err, "key", key, "value", flagStr, "userID", userID)
continue
}
tk, ok := res[platformID]
if !ok {
tk = make(map[string]int)
res[platformID] = tk
}
tk[token] = flag
}
return res, nil
}
func (x *tokenCache) SetTokenMapByUidPid(ctx context.Context, userID string, platformID int, m map[string]int) error {
for token, flag := range m {
err := x.SetTokenFlag(ctx, userID, platformID, token, flag)
if err != nil {
return err
}
}
return nil
}
func (x *tokenCache) BatchSetTokenMapByUidPid(ctx context.Context, tokens map[string]map[string]any) error {
for prefix, tokenFlag := range tokens {
for token, flag := range tokenFlag {
flagStr := fmt.Sprintf("%v", flag)
if err := x.cache.Set(ctx, prefix+":"+token, flagStr, x.accessExpire); err != nil {
return err
}
}
}
return nil
}
func (x *tokenCache) DeleteTokenByUidPid(ctx context.Context, userID string, platformID int, fields []string) error {
keys := make([]string, 0, len(fields))
for _, token := range fields {
keys = append(keys, x.getTokenKey(userID, platformID, token))
}
return x.cache.Del(ctx, keys)
}
func (x *tokenCache) getExpireTime(t int64) time.Duration {
return time.Hour * 24 * time.Duration(t)
}
func (x *tokenCache) DeleteTokenByTokenMap(ctx context.Context, userID string, tokens map[int][]string) error {
keys := make([]string, 0, len(tokens))
for platformID, ts := range tokens {
for _, t := range ts {
keys = append(keys, x.getTokenKey(userID, platformID, t))
}
}
return x.cache.Del(ctx, keys)
}
func (x *tokenCache) DeleteAndSetTemporary(ctx context.Context, userID string, platformID int, fields []string) error {
keys := make([]string, 0, len(fields))
for _, f := range fields {
keys = append(keys, x.getTokenKey(userID, platformID, f))
}
if err := x.cache.Del(ctx, keys); err != nil {
return err
}
for _, f := range fields {
k := cachekey.GetTemporaryTokenKey(userID, platformID, f)
if err := x.cache.Set(ctx, k, "", x.accessExpire); err != nil {
return errs.Wrap(err)
}
}
return nil
}

View File

@@ -0,0 +1,63 @@
package mcache
import (
"context"
"encoding/json"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/database"
"github.com/openimsdk/tools/log"
)
func getCache[V any](ctx context.Context, cache database.Cache, key string, expireTime time.Duration, fn func(ctx context.Context) (V, error)) (V, error) {
getDB := func() (V, bool, error) {
res, err := cache.Get(ctx, []string{key})
if err != nil {
var val V
return val, false, err
}
var val V
if str, ok := res[key]; ok {
if json.Unmarshal([]byte(str), &val) != nil {
return val, false, err
}
return val, true, nil
}
return val, false, nil
}
dbVal, ok, err := getDB()
if err != nil {
return dbVal, err
}
if ok {
return dbVal, nil
}
lockValue, err := cache.Lock(ctx, key, time.Minute)
if err != nil {
return dbVal, err
}
defer func() {
if err := cache.Unlock(ctx, key, lockValue); err != nil {
log.ZError(ctx, "unlock cache key", err, "key", key, "value", lockValue)
}
}()
dbVal, ok, err = getDB()
if err != nil {
return dbVal, err
}
if ok {
return dbVal, nil
}
val, err := fn(ctx)
if err != nil {
return val, err
}
data, err := json.Marshal(val)
if err != nil {
return val, err
}
if err := cache.Set(ctx, key, string(data), expireTime); err != nil {
return val, err
}
return val, nil
}

30
pkg/common/storage/cache/msg.go vendored Normal file
View File

@@ -0,0 +1,30 @@
// Copyright © 2023 OpenIM. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"context"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/model"
)
type MsgCache interface {
SetSendMsgStatus(ctx context.Context, id string, status int32) error
GetSendMsgStatus(ctx context.Context, id string) (int32, error)
GetMessageBySeqs(ctx context.Context, conversationID string, seqs []int64) ([]*model.MsgInfoModel, error)
DelMessageBySeqs(ctx context.Context, conversationID string, seqs []int64) error
SetMessageBySeqs(ctx context.Context, conversationID string, msgs []*model.MsgInfoModel) error
}

9
pkg/common/storage/cache/online.go vendored Normal file
View File

@@ -0,0 +1,9 @@
package cache
import "context"
type OnlineCache interface {
GetOnline(ctx context.Context, userID string) ([]int32, error)
SetUserOnline(ctx context.Context, userID string, online, offline []int32) error
GetAllOnlineUsers(ctx context.Context, cursor uint64) (map[string][]int32, uint64, error)
}

135
pkg/common/storage/cache/redis/batch.go vendored Normal file
View File

@@ -0,0 +1,135 @@
package redis
import (
"context"
"encoding/json"
"time"
"git.imall.cloud/openim/open-im-server-deploy/pkg/common/storage/cache"
"github.com/dtm-labs/rockscache"
"github.com/openimsdk/tools/errs"
"github.com/openimsdk/tools/log"
"github.com/redis/go-redis/v9"
)
// GetRocksCacheOptions returns the default configuration options for RocksCache.
func GetRocksCacheOptions() *rockscache.Options {
opts := rockscache.NewDefaultOptions()
opts.LockExpire = rocksCacheTimeout
opts.WaitReplicasTimeout = rocksCacheTimeout
opts.StrongConsistency = true
opts.RandomExpireAdjustment = 0.2
return &opts
}
func newRocksCacheClient(rdb redis.UniversalClient) *rocksCacheClient {
if rdb == nil {
return &rocksCacheClient{}
}
rc := &rocksCacheClient{
rdb: rdb,
client: rockscache.NewClient(rdb, *GetRocksCacheOptions()),
}
return rc
}
type rocksCacheClient struct {
rdb redis.UniversalClient
client *rockscache.Client
}
func (x *rocksCacheClient) GetClient() *rockscache.Client {
return x.client
}
func (x *rocksCacheClient) Disable() bool {
return x.client == nil
}
func (x *rocksCacheClient) GetRedis() redis.UniversalClient {
return x.rdb
}
func (x *rocksCacheClient) GetBatchDeleter(topics ...string) cache.BatchDeleter {
return NewBatchDeleterRedis(x, topics)
}
func batchGetCache2[K comparable, V any](ctx context.Context, rcClient *rocksCacheClient, expire time.Duration, ids []K, idKey func(id K) string, vId func(v *V) K, fn func(ctx context.Context, ids []K) ([]*V, error)) ([]*V, error) {
if len(ids) == 0 {
return nil, nil
}
if rcClient.Disable() {
return fn(ctx, ids)
}
findKeys := make([]string, 0, len(ids))
keyId := make(map[string]K)
for _, id := range ids {
key := idKey(id)
if _, ok := keyId[key]; ok {
continue
}
keyId[key] = id
findKeys = append(findKeys, key)
}
slotKeys, err := groupKeysBySlot(ctx, rcClient.GetRedis(), findKeys)
if err != nil {
return nil, err
}
result := make([]*V, 0, len(findKeys))
for _, keys := range slotKeys {
indexCache, err := rcClient.GetClient().FetchBatch2(ctx, keys, expire, func(idx []int) (map[int]string, error) {
queryIds := make([]K, 0, len(idx))
idIndex := make(map[K]int)
for _, index := range idx {
id := keyId[keys[index]]
idIndex[id] = index
queryIds = append(queryIds, id)
}
values, err := fn(ctx, queryIds)
if err != nil {
log.ZError(ctx, "batchGetCache query database failed", err, "keys", keys, "queryIds", queryIds)
return nil, err
}
if len(values) == 0 {
return map[int]string{}, nil
}
cacheIndex := make(map[int]string)
for _, value := range values {
id := vId(value)
index, ok := idIndex[id]
if !ok {
continue
}
bs, err := json.Marshal(value)
if err != nil {
log.ZError(ctx, "marshal failed", err)
return nil, err
}
cacheIndex[index] = string(bs)
}
return cacheIndex, nil
})
if err != nil {
return nil, errs.WrapMsg(err, "FetchBatch2 failed")
}
for index, data := range indexCache {
if data == "" {
continue
}
var value V
if err := json.Unmarshal([]byte(data), &value); err != nil {
return nil, errs.WrapMsg(err, "Unmarshal failed")
}
if cb, ok := any(&value).(BatchCacheCallback[K]); ok {
cb.BatchCache(keyId[keys[index]])
}
result = append(result, &value)
}
}
return result, nil
}
type BatchCacheCallback[K comparable] interface {
BatchCache(id K)
}

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